Skip to content

Skill guides

Back to Prompts & guidance

3 verified entries in plugin / skill.

distill

plugin:skill:distill

Use when the user wants to inherit a finished, dead, or abandoned session — "distill session X / 继承那个 session 的经验 / 接手它的工作 / harvest or salvage a session". Given a SpexCode session id, harness thread id, or transcript path, read it without resuming, extract decisions, traps, and next actions, salvage unmerged work, retire only resources proven safe to remove, then rename the inheriting session to mark the handoff.

Field Value
Source path .spec/spexcode/.plugins/skills/distill/spec.md
Source revision 981942eb4ba0dc1a263842e2cd17fba0622308c6
Content SHA-256 799f8637c8cb782036767c1e9272313ff7111e97ba321e8dd0318eaf6deb14f5
Content role prompt
# distill

Inherit a finished session's **mind and desk** without waking it: mind is its transcript; desk is its
worktree and branch. **Never resume, reopen, send to, or otherwise re-prompt the old session.** Read files
and git state only until its work has landed.

## Resolve and digest

Accept a SpexCode session id, a Claude/Codex thread id, or a transcript `.jsonl` path.

- For a SpexCode id, find `~/.spexcode/projects/*/sessions/<id>/session.json` (a unique prefix is enough).
  Keep its prompt, `worktree_path`, `branch`, `harness`, and `harness_session_id`; Claude uses the session
  id as its transcript id, while Codex uses `harness_session_id`.
- Otherwise pass the harness id or transcript path directly. The digest header supplies its `cwd` and branch
  when recorded; those locate the desk even when it belongs to another repository.

From the repository root run the plugin's co-located digest:

`node .spec/*/.plugins/skills/distill/digest.mjs <id-or-path>`

It searches the harness's normal local transcript stores and prints human prompts, agent text, concise tool
calls, errors, metadata, edited files, and the raw path. A miss fails loudly; never recover by waking the old
session. For output above about 100 KB, have a subagent return only the distillation below.

## Distill forward

Do not paste the transcript or retell changes git already records. Preserve what git does not:

- goal, actual stopping point, and whether anything landed;
- decisions and rejected alternatives, with reasons;
- failures, dead ends, and user corrections;
- unfinished work and next actions;
- edited files, relevant spec nodes, and the raw transcript path.

## Salvage, then retire

Use the record or digest to inspect the old repository's worktree, branch, merge base, commits, dirty files,
and untracked files. Cross-check the digest's edited-file list because some work may live outside that
worktree. Distinguish an already-merged branch from a branch equal to its merge base, which never committed.

Carry unmerged commits onto the current branch with `git cherry-pick <base>..<branch>`; if replay is
unsuitable, apply the branch diff. Apply dirty changes and copy needed untracked files, then commit them
with the source session named. Keep authorship and `Session:` trailers where possible.

Only after verifying the salvage is present (or the branch truly merged), retire a SpexCode session with
`spex session close <id>`, or remove a bare worktree and then its branch. If proof is incomplete, keep the
resources and report why.

`<id>` is the OLD session's id — the one you are inheriting, spelled out. It is never `.` and never your own
id: `.` means THIS session (the rename step below relies on that), and closing yourself deletes the worktree
you are running in, mid-turn, along with your branch and record. Retiring the source is the only close this
skill asks for; your own ending stays a declaration.

## Rename yourself

Last step: mark the inheritance on the board. If you run as a SpexCode session (inside a session
worktree), `spex session rename . "<name>"` — `.` selects this worktree's own session. Name what this
session now carries — the inherited goal going forward, not the old session's id and no `distill:`
prefix. Outside a SpexCode session there is nothing to rename; skip this step.

e2e-review

plugin:skill:e2e-review

Use after an e2e run that recorded whole-session video with a timeline sidecar, or when the human asks to review/annotate recordings — "审录屏 / 标注 / annotate / review the e2e videos". Splits the recordings into per-scenario clips, files each as video eval evidence, and points the human at the dashboard annotator.

Field Value
Source path .spec/spexcode/.plugins/skills/e2e-review/spec.md
Source revision 1055e5774acc0d15194e57e6660f99fee1d52b48
Content SHA-256 a35e88371b647bb775dc2e856a2e744fe94a2fc8f486772c214fdc7433d07b28
Content role prompt
# e2e-review

Turn a whole-session e2e recording into reviewable, per-scenario **video evidence** — filed through
eval readings, reviewed in the dashboard. This skill imports a WORKFLOW (ported from gugu's `.agent/skills/e2e`,
its ancestor); the tooling it once shipped (a standalone HTML annotator, a Range-serving HTTP script, a
frame-PNG saver) is already product — the dashboard annotator, the blob route's Range support, the
issue/evidence seams — so the skill never starts a second UI.

## input contract

A directory of recordings, scanned recursively for pairs: one `.webm` beside one `*.timeline.json`
(Playwright POOL workers each produce such a pair; any emitter that writes the shape qualifies). The
emitter timeline is `{ events: [{ atMs, kind, label }] }`:

- `kind: "narrate"`, label `▶ <scenario> · <title>` — a scenario's start boundary (the next `▶` ends it).
- `kind: "frame"`, label `📷 <step>` — a named step inside the running scenario.

The same scenario appearing in several recordings (retry, another worker) resolves to the newest.

## the loop

1. **Split** — `node .spec/<root>/.plugins/e2e-review/split-recordings.mjs <recordings-dir> <out-dir>`
   (`--ffmpeg <path>` if ffmpeg isn't on PATH). Each scenario becomes `<scenario>.mp4` (h264 faststart,
   browser-seekable) plus `<scenario>.timeline.json` in SpexCode's step-timeline format (`{v:1, events:
   [{tMs, step}]}`, clip-relative — validated LOUD at filing). No title cards, no burned-in captions:
   the annotator renders scenario context live from the spec tree, so pixels stay evidence.
2. **File** — map each clip to the spec node whose behavior it exercises (`spex spec search <topic>`; the
   node's `eval.md` names its scenarios) and file your verdict WITH the clip:
   `spex eval add <node> --scenario <s> --pass|--fail --video <clip>.mp4 --timeline <clip>.timeline.json`.
3. **Hand to the human** — the dashboard Evals page (`#/evals`), video-first: each clip plays in
   the annotator with its clickable step ruler; the human circles a region to file an issue on the
   responsible node (clip + timeline ride as typed evidence), disputes a verdict with their own manual
   reading, or discusses on the eval's comment thread. Say where to look; do not build or serve
   anything else.

## judgment stays yours

The split is mechanical; the FILING is not. A clip's verdict is your reading of the recording against
the scenario's `expected` — watch before you file, and file `--fail` honestly when the loop broke.
Scenario names come from the `▶` markers, so the emitter's vocabulary should match the governed
node's `eval.md`; when they diverge, fix the emitter or the eval.md, never hand-rename clips.

taste

plugin:skill:taste

SpexCode's engineering taste — load when making a design/architecture decision, weighing whether a change is worth its complexity, or sanity-checking work against the project's principles (harness-agnostic zero-friction adoption, spend-complexity-only-to-buy-it-back, one unified mechanism over special-cases, self-launch-is-the-main-body).

Field Value
Source path .spec/spexcode/.plugins/skills/taste/spec.md
Source revision a2e5b0c52c04a694420a4408befc821cd2aff54d
Content SHA-256 baafa73d8bd29e0d62ed33a83fc1a84ece162c2851411239227286bef4ff536a
Content role prompt
# taste

## raw source

SpexCode is **self-referential** — we build a spec-driven tool *with* the spec-driven tool, so the principles
that guide its development must themselves live in the spec tree, not only in a chat that can be compacted away.
These are the durable "tastes" (品味) the maintainer has stated while building it. Preserve the raw intent; let
the campaign distill + refine, and let the rules that should govern every agent graduate into `.plugins`
surface:system nodes (the way [[memory-hygiene]] already did).

The principles, in the maintainer's own framing:

1. **Harness-agnostic, zero-friction adoption.** The ideal path is `npm install spexcode` → `spex init` →
   the user launches their own `claude`/`codex`, with NO further human operation, NO pollution of global
   claude/codex, and NO overwrite of the user's existing CLAUDE.md / AGENTS.md content.
2. **Deterministic, simple, UNIFIED system — not tons of special cases.** Favor one mechanism over many
   `if/else` branches.
3. **Spend complexity only to BUY it back.** A user need earns a code change ONLY when satisfying it
   *reduces* system complexity, or you have a clever way to make it a complexity-reducer — never "one more
   if-else." Don't add complexity trading for a need.
4. **The project folder holds only human-readable things.** Abstract internal runtime (manifests, hashes,
   locks) is hidden into the global store; what stays in-tree must be prose a human would accept.
5. **Self-launch is the MAIN BODY.** A user on the most naive Claude Code / a directly-launched Codex — NO
   dashboard, NO server — must still get the FULL experience (prompt, hooks, every mechanism) through
   `spex init`/`spex spec lint` + materialize→auto-discovery. The dashboard is one consumer that REDUCES to that
   path plus a minimal governed delta. Adapting to this "unmanaged" usage is the forcing function that makes
   the architecture robust and unified — the point is NOT to add if-else per usage mode (that loses the forcing).
6. **Memory hygiene** ([[memory-hygiene]]): never write session/role-specific content or identity markers to
   the project-keyed memory — by instruction, not programmatic control (no 画蛇添足).
7. **Use fresh-context agents** to brainstorm, confront, debate, and divide labor — they are less overfit,
   carry a naive taste, and dare to challenge the architecture. They are also a **complexity probe**: if a
   fresh agent can't understand the spec↔code relationship, grasp what the code is for, or round-trip
   spec→code→spec, that misalignment IS the measurement. Don't drag a huge context and brute-force solo.
8. **Read the docs, read the source, run experiments.** Many mechanisms are only understood after
   experimenting; the codex source reveals the least-convoluted, most-claude-unified implementation. Don't
   reason from assumption.
9. **Milestone merges, not big-bang** — land work in versioned milestones so a rollback is cheap.
10. **One frontend design language; unify the icons; NO emoji** (e.g. retire the attachment 📎).
11. **Keep finding behavior-equivalent but simpler / more-unified approaches**, and sanity-check every change
    against these existing principles.
12. **Self-reference**: sediment this guidance into the spec / `.plugins` / source so it is never lost.
13. **YATU** (You As The User): measure through the real product surface a user touches, not an internal helper.
14. **Capabilities enter the ecosystem through the pillars we already stand on** (git / agent harness / test
    framework) — before adopting a new protocol or dependency, check whether an existing pillar already
    delivers it indirectly. The canonical case: LSP-grade code intelligence reaches SpexCode *through the
    harness* (agents navigate code; the LLM judge reads semantics) and symbol-level history *through git*
    (`log -L`, hunk-header funcname drivers) — so SpexCode never speaks the LSP protocol itself. A capability
    worth having usually has a pillar-native form; integrating it directly is how tools bloat.
15. **Each pillar gets exactly ONE adapter seam.** The maintainer's framing: "harness adapter, language
    adapter, test framework adapter — 我们最终会拥有这三方面的适配". Harness adapter exists (`harness.ts` +
    launchers + materialize); language adapter is the anchor/coverage extraction seam; test-framework adapter
    is where eval evidence producers will plug in. Every seam shares one shape: an interface + an ordered
    registry + per-instance DATA rows (a new harness/language/runner is a row, not a branch), loud degradation
    when a tier is unavailable — and product semantics never learn which adapter sits on the other side.
16. **Don't invent what the pillar isn't prepared for.** The maintainer's framing: "不要擅自发明 git 没有准备
    好的东西". Stated when rejecting an explicit node-identity field to make rename tracking provable: git
    *detects* renames by content similarity rather than *recording* them, so an id field would erect a second
    identity model beside git's object model — two truths to keep in sync, and no way to know which to believe
    the first time they disagree. The consequence is accepted deliberately: we live inside git's rename
    semantics, and therefore the acceptance bar is "agrees with git's rename events across the whole tree",
    not "provably correct in some model git does not have". This is [[taste]] 14 applied to the seam itself —
    a pillar's *limits* enter the ecosystem along with its capabilities.
17. **Where the application-level call is genuinely unclear, let implementation simplicity decide.** The
    maintainer's framing: "对于这种应用层拿不准的地方,我们就从自己的实现角度考虑,哪种实现最简单就怎么来".
    Not a tie-breaker to reach for early — it applies precisely when the product argument has been made in
    good faith and stays balanced. Then the honest question is which option costs less mechanism, and the
    answer is often that one of them is already the behaviour and costs zero lines.
18. **Stabilise first, then measure the shape of the cost — not just its size.** The maintainer's framing:
    "先保证稳定,然后计算一下复杂度,是线性还是平方级,和 git 历史长度是否有关,还是说只和文件数目有关".
    A wall-clock number tells you a run was slow; the scaling dimension tells you which lever exists. The
    same 4-second lint means opposite things if it grows with history (nothing local can fix it) versus with
    source bytes (memoisation applies) versus with node count (the walk is wrong). Measure the dimension
    before designing the fix, and never buy a cache before knowing whether the work is repeated at all.

19. **Separate what is PERMANENT from what is CURRENT, and let the invalidation scope be derived, not
    assigned.** Git history is append-only: what a commit changed, what trailers it carries, what a merge
    itself authored are *permanent properties of that commit* — they can be accumulated once and never
    recomputed. What those facts mean *today* — which node owns a path after renames, which window a
    version opens — is a question about the current tree, and is cheap. The costly half is permanent; the
    tip-relative half is cheap. A read that recomputes the permanent half from scratch pays O(history)
    forever, so the project gets slower every day it is worked on — measured here as ~0.33 ms per commit
    across two full-history walks. The same confusion at the cache layer produces a worse failure: when a
    rebuild costs more than the interval of the watchdog that guards it, every finished rebuild is
    invalidated before it can be served, and a safety net becomes a latency amplifier — the fix is to make
    the rebuild cheap, never to lengthen the interval, which only moves the threshold. The corollary is
    about *scope*: derived data spread across several projections (board, review snapshot, session eval
    revision) has no single place declaring what depends on what, so an invalidation scope ends up
    hand-assigned by whoever traced the call chain last — and the honest default becomes "invalidate
    everything". Cheap correctness there comes from letting a projection *declare its inputs*, so the scope
    follows from the declaration; until it does, narrowing a scope by inspection is a correctness change
    disguised as a performance one, and must be proven field-by-field against a full recompute. Keep the
    slow, obviously-correct full recompute in the repo as the specification, and hold every faster path to
    byte-equality against it.

20. **What falls out at a seam is picked up, never designed for.** The maintainer's framing, invoking
    Liang Wenfeng's spirit: on the road to AGI, the things you can get along the way you simply take. The
    three layers ([[spexcode]]) are the main body and are judged by their own criteria; how natural or
    accessible the L0/L1 seam happens to be — the "borderland" — is **secondary**. Small capabilities fall
    out of it for free (a session with no board membership still has a readable mailbox; a read still works
    with no backend running; a subscription may or may not be persisted because it carries no truth). Take
    them: they cost nothing, and the fact that they fall out at all is evidence the seam sits where it
    should. But a borderland convenience is never a criterion and never a reason to move the seam — its
    real value is the quiet pressure it puts on the architecture to stay honest. The failure this guards
    against is promoting the byproduct to the definition: restating the layering as a horizontal
    read-versus-own cut classifies *operations* and silently destroys the *adoption ladder*, which is the
    thing that makes each layer worth having on its own.

21. **Our real failure mode is misidentifying the data source, and it never announces itself.** The two
    operating rules, in the maintainer's framing: "先读答案自己带的元数据,再 diff 它的正文" and "一个坏的提取器
    会给出最令人安心的那种假答案". This is knowledge about how *we* go wrong, so it is governed like any other
    contract. A reasoning error runs into a contradiction and gets caught; reading the wrong source returns a
    clean, internally consistent, confidently wrong answer, and nothing in the loop objects. Measured in one
    evening, four times, none caught by a human: `/api/graph`'s body read while ignoring the
    `x-spexcode-graph: stale, refreshing` header it attaches to itself, turning one mid-rebuild sample into a
    claimed permanent product hole; a comparison keyed on a top-level `overlays` field the board does not
    have (its keys are `identity`/`issuesStamp`/`nodes`/`sessions`, overlays live under `nodes[].overlays`),
    so both sides returned zero and the script reported "identical"; an isolated Vite server that ignored
    `?api=` and served its default backend, so two eval readings measured the wrong process; and a
    43-node/0.487-zoom reading taken as a whole-tree sample when it was already the drill-down's *folded*
    output, which defined a feature for a phenomenon that does not exist. So: before diffing a body, read what
    the answer says about itself — its freshness label, its own key set, which endpoint actually served it.
    And prefer the product's own instrument to a measurement built for the occasion: a `PATROL-REPAIR` count
    of zero answers the causal question ("was any watcher blind?") that a 1-second stopwatch poller can only
    circle around, and four separate experiments that evening were made unnecessary by first asking whether
    the product already had the answer. A cheap extractor is not the cheap half of the job; it is where the
    expensive mistake lives.

22. **Prose is not a free edit: you can hold a tree's proof and then move the contract that proof was
    against.** The maintainer's framing: "散文不是免费的编辑:你可以握着一棵树的证明,然后把那个证明所针对的
    契约挪走。" Freshness has more than one axis, and the rule we already wrote down only guards one of them:
    "commit the verified tree, then file" catches the *code* moving after the proof, and says nothing about the
    *contract* moving after the proof. Measured, self-caught by the session that did it: it filed a passing
    reading, then softened the wording of the same scenario in `eval.md`, and both readings went stale on the
    scenario axis — in its own words, "I was holding the proof of that tree, and then I moved the contract that
    proof was against." The reason this one gets its own entry rather than an amendment to the code-axis rule is
    that the two feel nothing alike: editing code, you know you are moving the thing under test; editing wording
    feels like polishing a document, and `scenarioHash` disagrees. Its sibling is the same shape wearing a
    different costume — a *sampling* choice that silently decides the claim. One-build-per-fresh-process does not
    merely hide an in-process memo, it **inverts the conclusion**: 507ms cold against 12–15ms warm is a factor of
    forty, so "a cache buys ~0 here" and "this is the headline finding" were separated by nothing but how the
    sample was taken; the corpus picked to prove it could not exhibit the defect at all (0 of 1144 distinct
    reading `codeSha`s reachable from that HEAD, therefore zero selector queries reaching the engine). Hence the
    method rule that came out of it: on a loaded box a **fixed wall-clock threshold is not a claim** — compare
    load-matched pairs, and put the falsifiable half of the claim on something load-independent. The stopwatch is
    the symptom; the count of git child processes is the assertion.

23. **An expected value that can be derived must be derived — hardcoding it once opens a second source of
    truth.** Say the fact once; derive it everywhere else. Two independent instances landed in one evening,
    arrived at from opposite directions, and both are *guards* rather than fixes: `docs-quickstart.test.ts`
    reconstructs the expected harness list from the live `NATIVE_HARNESS_IDS` registry, so registering a
    harness without updating the three surfaces that teach adoption (both READMEs' Quick start, `spex guide`'s
    setup page) fails there — measured, on the author of this entry, within the hour of adding a new harness;
    and `session-create-cli.test.ts` parses `cli.ts` with the TypeScript AST to derive three sets
    (the `VALUE_FLAGS` literal, every `flag('x')` value read, every `rejectUnknownFlags` allowlist) and
    assert their relationship with **no flag name written in the test at all**, so the next person who adds a
    valued flag and forgets the distant table fails on the right assertion instead of receiving a confident
    wrong sentence about a *different* flag. That second failure mode is the whole point: a restated list does
    not fail loudly when it drifts, it fails *elsewhere*, wearing the costume of an unrelated bug. This is the
    same bone as [[taste]] 16 — two truths will disagree eventually, and on that day nothing tells you which
    one to believe — and it is what [[taste]] 3 looks like when complexity is genuinely bought back: an AST
    test is more code than a string array, and it retires a class of defect rather than one instance of it.
    The discipline it pairs with is 22's, but the two are not the same kind: 22 is *temporal* (do not move the
    contract after the proof) and a human has to remember it, while this one is *structural* and enforces
    itself — a derived expectation catches you without anyone remembering anything. Two cautions keep it
    honest: derive from the authoritative home, because deriving from a copy silently promotes the copy; and
    the rule is about *expectations*, not about outlawing constants — a fact with exactly one home is fine
    where it lives, the defect is the second place that restates it.

## expanded spec

This node is the seed. The de-drift campaign distills these into a sharper checklist (the "20 tastes" + the
issue-selection criteria), audits the tree against them, and graduates the agent-governing ones into
`.plugins` surface:system so every launched agent inherits them. Until then, this node is the durable record —
read it when a design decision needs the project's own taste, and add to it (raw source first) when the
maintainer states a new one.