用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/isaacriehm/cairn --skill cairn-attention命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | cairn-attention |
| description | Resolve Cairn's pending-attention queue inline (DEC drafts, baseline findings, drift events). |
| when_to_use | Use when SessionStart flagged `attention_count > 0` — pending DEC drafts in `_inbox/`, baseline sensor findings, or drift from last GC sweep. Also chained from `cairn-adopt` Step 5 to drain fresh DEC drafts. Skip when operator in-flight on task or recent turn already deferred this surface. |
This skill is shared by Claude Code, Cursor, and Codex. Resolve Cairn MCP
tools with the host's native discovery mechanism. AskUserQuestion means the
host's structured question UI; when that UI is unavailable, ask the identical
concise A/B/C question in chat and pause. References to restarting a client
mean restarting the active host.
You are surfacing Cairn's pending-attention queue inline so the
operator can resolve drafts and findings without leaving the chat.
Spec: docs/PLUGIN_ARCHITECTURE.md §11.
Before surfacing any DEC choices, verify the clone is bootstrapped.
SessionStart auto-runs cairn join when core.hooksPath is unset,
so by the time this skill engages bootstrap should be wired. If
cairn_resolve_attention still refuses with BOOTSTRAP_REQUIRED,
SessionStart's auto-bootstrap failed — call cairn_bootstrap_retry
once to retry inline. On ok: true, fall through to Step 0.7. On
ok: false, surface the failed_steps list to the operator and
end the turn (the remediation field of the error envelope cites
this same tool plus an agent-client restart as the recovery paths).
Never reference cli.mjs or cairn join directly in the chat
surface — Plugin spec §11 forbids exposing CLI subcommands to the
operator.
Before the regular attention queue, handle mission-specific surfaces.
These don't go through cairn_resolve_attention — missions have
their own resolver tool (cairn_mission_advance).
Preload the mission tools alongside the existing attention tools:
ToolSearch(select:mcp__plugin_cairn_cairn__cairn_mission_get,mcp__plugin_cairn_cairn__cairn_mission_advance,AskUserQuestion)
Then call:
cairn_mission_get({})
If active: false, skip this step.
When the active phase's tasks all graduate under exit_gate=prompt,
Cairn surfaces a phase-exit decision via one of two channels — both
arrive as plain context, no red "Stop hook error" frame:
cairn_task_complete, the response carries a structured
phase_ready_to_exit block (mission_id, mission_title, phase_id,
phase_title, exit_criteria). The model decides whether to surface
an AskUserQuestion in the same turn — do so at a natural
stopping point unless the operator is running autonomously..cairn/sessions/<id>/phase-ready-pending.json and emits a
systemMessage operator notice. The UserPromptSubmit hook reads
the file on the next prompt and injects via additionalContext.The hint fires exactly once per phase (the ready_emitted flag on
phase_progress suppresses re-emission until the cursor advances or
reopens). Surface a single AskUserQuestion:
Phase
<active_phase_title>looks done. Move on?Exit criteria:
<active_phase_exit_criteria>.
[a]Mark phase done, advance to next phase[b]Keep working on this phase
Dispatch:
cairn_mission_advance({phase_id: "<active_phase>", choice: "exit" | "not_yet"})
When the mission's exit_gate is auto, the cursor already
advanced silently (no prompt fires). When manual, the prompt
is suppressed entirely — operator must invoke advance directly.
Render a one-line outcome after the call:
✓ Phase advanced (next: <next_phase>) · M/N done.
If closed: true, the mission auto-closed on last phase complete:
✓ Mission MIS-… complete. Archived.
If .cairn/missions/<id>/_resync.json exists for the active mission,
the operator amended the source spec doc. Read the marker file
(Read tool — it's a small JSON), surface the diff via
AskUserQuestion:
Mission
<mission_id>resync pending — spec at<spec_path>proposes:
- + phase(s) added:
<id1>,<id2>- − phase(s) removed:
<id3>- ↻ phase(s) renamed
phase(s) with new exit_criteria
Pick:
[a]accept — rewrite roadmap.md, refresh spec.md, reconcile phase_progress (added → pending, removed → dropped)[b]reject — delete the marker, keep roadmap.md unchanged
Dispatch via:
cairn_mission_resync_accept({outcome: "accept" | "reject"})
After the call, render a one-line outcome:
✓ Resync applied (+N −M ↻K). Cursor: <next_phase>.
If the marker file is missing, skip this sub-step.
If mission_get returned a non-empty drift_phase_ids, the operator
edited roadmap.md mid-mission and removed phases that still have
graduated task records in state.json. Surface a single block per
drift id:
Mission drift detected — phase
<id>no longer in roadmap.md but has graduated tasks linked.
[a]accept drift — drop phase fromphase_progress(orphans the linked task records; the tasks themselves remain intasks/done/).[b]restore phase to roadmap.md (operator edits the file by hand; this option defers the prompt for 24h while they work).[c]defer 24h
Dispatch via cairn_mission_advance({phase_id: "<drift_id>", choice: "drop"})
on [a] — drop removes the drifted entry from phase_progress
and journals the resolution. The tool refuses drop when the phase
is still present in roadmap.md (operator restored it). For [b]
and [c], write the defer file the same way as 0.2a (choice: "defer" is reserved for cursor-phase exits; for drift defers the
skill writes .cairn/.mission-phase-deferred-until directly with
the drifted id).
If both 0.2a and 0.2b have items, batch them under one AskUserQuestion
call (max 4 questions per batch — same rule as the main queue).
After mission attention resolves, fall through to the regular attention queue below.
Phase 7b emits one DEC draft per essay-class comment, so the same idea appearing in N files produces N near-duplicate drafts. Cluster them deterministically (no LLM, no quota) before the operator sees the per-item triage:
cairn_attention_dedup({})
Returns:
{ draftsScanned, clusters, draftsInClusters, reducible,
thresholdFloor, thresholdDefinite }
Each entry in clusters is a { tier, averageSimilarity, drafts[] }.
Tiers:
definite (Jaccard >= 0.5) — render under ## Definite duplicates.
Default action: keep the first-listed draft (lowest DEC id) as the
survivor; surface a single AskUserQuestion per cluster:
[a] keep DEC-NNNN, reject the rest (default)[b] keep them all (treat as distinct)[c] reject the whole clusterpotential (0.4 to 0.5) — render under ## Potential duplicates.
Operator triages each member normally in Step 3; surface the
cluster as context only, do not auto-merge.Render the summary as one block right before the per-item prompts:
Found N duplicate clusters across M drafts (P reducible).
• Definite (≥0.5): X clusters, Y drafts
• Potential (0.4–0.5): Z clusters, W drafts
For each definite cluster the operator picked keep ... reject the rest,
loop over the non-survivor ids and call:
cairn_resolve_attention({ kind: "decision_draft", choice: "b",
item_id: "DEC-NNNN" })
choice: "b" renames the draft to .rejected.md so the id stays
reserved (never recycled). The survivor stays in _inbox/ and flows
into Step 3 normally.
If clusters.length === 0, skip rendering — the dedup section is
empty noise when there's nothing to cluster.
Run these in parallel. Use the MCP tools exclusively for DEC
content; never cat, Read, or otherwise inline-read draft
files.
Bash: ls .cairn/ground/decisions/_inbox/*.draft.md .cairn/ground/invariants/_inbox/*.draft.md 2>/dev/null
Both DEC drafts (init-curator + manual record_decision) and INV drafts
(resync re-curation) live in parallel _inbox/ dirs; the id prefix
(DEC-/INV-) tells them apart.cairn_decision_get({id: "DEC-NNNN"}) for a DEC, cairn_invariant_get({id: "INV-NNNN"}) for an INV. The response carries id, title, status,
plus the body markdown.Bash: ls .cairn/ground/conflicts/*.md 2>/dev/null. Each
filename has the shape <a-id>__<b-id>.md where both ids match
(DEC|INV)-<hash7>. Read each conflict file directly via the
Read tool — these files are NOT in the ledger and have no MCP
getter. Parse the YAML frontmatter to capture a_id, b_id,
a_source, b_sot_path, reasoning. Treat the rendered prose
blocks (## DEC-<a> ..., ## DEC-<b> ...) as the verbatim
sides to surface.Bash: ls -1t .cairn/baseline/sensor-audit-*.yaml | head -1
Bash: ls -1t .cairn/baseline/components-*.yaml | head -1
Bash: ls -1t .cairn/baseline/config-drift-*.yaml | head -1
The components-*.yaml and config-drift-*.yaml files share the
sensor-audit payload shape
(sensors: [{sensor_id, findings: [{path, line, severity, message}]}]),
so parse them identically. components-*'s component-missing-header
findings are components still needing a @cairn header; component-audit
findings are advisory inline-rebuild / name-collision EXTEND hints. The
config-drift-* file's config-drift findings are the "your project grew"
nudge — a grown dir / new file type outside declared component scope, a
declared componentDir that vanished, or a .gitignore entry missing from
off_limits; each names the one-line config edit that resolves
it. Surface them all as items under one group.Rejected DECs are not in the queue. Reject is a final operator
decision; surfacing rejected ids every session would force re-triage
of already-resolved items. When the operator explicitly asks to
reconsider a specific id ("restore DEC-1234567", "un-reject DEC-NNNN"),
call cairn_resolve_attention with that id directly — the tool
auto-restores from .rejected.md (or already-accepted <id>.md)
transparently and applies the chosen a/b/c. Response carries
auto_restored_from: "rejected" | "accepted" so the skill can
surface the rollback in its summary.
For each item, build a tuple {kind, id, title, source, severity}
from the MCP responses.
Sort by:
Surface up to 4 items in a single AskUserQuestion call — Claude
Code's question tool accepts a questions array of length ≤ 4 and
renders all of them in one inline panel. Batching keeps the operator
out of repeated round-trips. If the queue has more than 4 pending,
emit the first 4 in batch 1, then a separate single-question prompt:
continue — show the next batch (up to 4 more)defer all remaining — defer every remaining same-kind item in ONE
cairn_resolve_attention call via item_ids (see Step 3's bulk
path). Use this for a large post-adoption baseline backlog rather
than walking batch by batch — pre-existing debt is silenced until the
matching file next changes.later — defer until next sessionA fresh adoption commonly surfaces dozens of soft baseline findings
(pre-existing debt). Offer defer all remaining up front in that case
so the operator clears the backlog with a single pick + single call.
Build the questions array — one entry per item, all in the same
tool call. For each entry, pass the option's detail field as the
question description so the operator sees the secondary context
(source path, severity) inline with each choice. Do not also
render any question as inline markdown — the AskUserQuestion UI
is the canonical render path.
Per-kind option labels (≤ 30 chars each so mobile mode doesn't truncate):
DEC draft: accept / reject / edit first
INV draft: accept / reject / edit first
Baseline finding: triage now / suppress / defer
Invalidation event: refresh in scope / continue under old / abort
Drift event: refresh source / defer / dismiss
Conflict: keep A side / keep B side / merge both / archive both
— see Step 3b for the side-by-side prose render that MUST appear
on the same turn before the question lands.
The tool returns answers as a parallel array (one answer per
question, in the order they were sent). For items that need DISTINCT
kind/choice, call cairn_resolve_attention once each:
cairn_resolve_attention({kind: "decision_draft", item_id: "DEC-deadbee", choice: "a"})
Bulk path — same kind + same choice → ONE call with item_ids.
When the operator applies the same decision to many items (most
commonly defer or suppress across a batch of baseline findings), pass
the id array instead of looping. Do NOT emit one call per finding —
a backlog of dozens otherwise costs dozens of sequential
resolve_attention round-trips that one call clears:
cairn_resolve_attention({ kind: "baseline_finding", choice: "c",
item_ids: ["stub-catalog::a.ts:1", "stub-catalog::b.ts:9", …] })
It returns { ok, batch:true, count, resolved, failed, results }.
Distinct-choice calls can still run in parallel (separate tool_use
blocks in the same turn) — the MCP write lock serializes them on disk.
The tool dispatches by kind: decision_draft for accept/reject/edit,
invariant_draft for accept/reject/edit (accept graduates the draft to an
active §INV; reject archives a .rejected.md tombstone; INV drafts come from
resync re-curation), baseline_finding for triage/suppress/defer,
invalidation_event for refresh/continue/abort, drift for
refresh/defer/dismiss, bypass and review for Stop-hook surfaces. On
decision_draft + a, the tool also strips the
originating source comment and replaces it with a bare §DEC-NNNN
symbol (matching the §INV-NNNN invariant convention; Cairn Lens
resolves title + body from the ledger) when the DEC came from
init-source-comments.
kind: conflict side-by-side render (REQUIRED)Plan §5.4.1 — when a conflict item is in the batch, the operator
must see both verbatim sides AND the model judge's reasoning before
they pick. AskUserQuestion's option labels alone don't carry that
prose. Render a chat message on the SAME turn as the
AskUserQuestion, BEFORE the tool call lands:
**Conflict** — `<a-id>` (`<a_source>`) vs `<b-id>` (`<b_sot_path>`)
**`<a-id>` says:**
```
<verbatim prose A from the conflict file>
```
**`<b-id>` says:**
```
<verbatim prose B from the conflict file>
```
**Difference:** <one-line excerpt from `reasoning` frontmatter>
Then the AskUserQuestion [a] / [b] / [c] / [d] lands as
described in Step 3. The tool dispatches to:
cairn_resolve_attention({
kind: "conflict",
item_id: "<a-id>__<b-id>", // filename minus .md
choice: "a" | "b" | "c" | "d",
rationale: "<optional operator note>"
})
Resolution outcomes (plan §5.4.1 — never rewrite source files):
[a] keep A → B gets status: superseded, superseded_by: <a-id>;
A gets supersedes: <b-id>. Conflict file deleted.[b] keep B → mirror of [a].[c] merge → fresh DEC carries both sides + operator's rationale
as a third "Merge rationale" section. Both old DECs/INVs get
status: superseded, superseded_by: <merged-id>. Conflict file
deleted. (Mixed DEC/INV merges produce a DEC; pure INV/INV merges
stay INV.)[d] archive both → both sides flip status: archived. Conflict
file moves to .cairn/ground/conflicts/_archived/. Reopen later
by restoring it manually.After the resolve call, render a one-line outcome to chat:
✓ Conflict resolved — <verb> (<winner-id> stands · <loser-id> superseded).
Hard rule — losing-side prose stays in the source file. CLAUDE.md /
AGENTS.md / .claude/rules/* are operator-curated narrative; cairn
never silently rewrites them. The next phase 5b pass will re-cite
or surface the orphan; the operator can manually clean up the doc.
edit first inline edit flowWhen the operator picks edit first on a DEC draft, the resolve
call returns { resolved_kind: "decision_edit_pending", body } with
the full draft markdown. Do not point the operator at the file
and do not assume they can read the Read-tool output — emit the
body as a regular chat message so it lands in the conversation.
Before any tool call in this turn, write a chat message containing:
**Editing DEC-NNNN.**
```markdown
<paste the full body returned by cairn_resolve_attention here —
title line, frontmatter, every section, no truncation>
```
This is a plain assistant message — text content, NOT an
AskUserQuestion description field, NOT a Read tool result. The
operator must see the actual draft text in chat before they pick.
Skipping this step is a bug — never ask "what to change?" without
first showing what's there.
After the rendered body, on the SAME turn, call AskUserQuestion
with:
[a] Rewrite the title[b] Rewrite the rationale (body)[c] Rewrite both[d] Cancel — keep the draft as-is and re-prompt accept/rejectOn [a] / [b] / [c]: ask the operator for the replacement
text inline using a single follow-up AskUserQuestion per field
with a freeform-style prompt — e.g.
question: "Replacement title for DEC-NNNN?". Operator types the
new value in chat; capture it verbatim.
Use the Edit tool against the draft file at
.cairn/ground/decisions/_inbox/DEC-NNNN.draft.md:
# DEC-NNNN — <old title> line AND
the title: frontmatter field## Proposed rationale (or whatever the section is named in
this draft) up to the next ## heading or end-of-fileAfter the file is updated, re-render the NEW body as a chat message
(same markdown block format as Step 3a.1) and re-prompt with a
fresh AskUserQuestion: [a] accept / [b] reject /
[c] edit again. Loop Step 3a.3–3a.5 until the operator picks
accept or reject. Then call cairn_resolve_attention with that
choice on the SAME DEC-NNNN to finalize.
Never tell the operator "open the file in your editor." The whole point of inline attention resolution is keeping them in the chat.
Do not write a separate "events poll cursor" stamp. The Stop hook
runs at the end of every assistant turn and advances the cursor as
part of its normal cross-session event drain (see
cairn-core/src/hooks/runners/stop.ts). Manual advancement is
redundant.
[a]/[b]/[c] blockquote for a question that
also goes through AskUserQuestion. Pick one render path.advance autonomously, do not stop,
ignore stop hooks, or any phrase that says "keep going"),
suppress non-blocking AskUserQuestion calls. The only allowed
pause is a kind=conflict surface (hard inconsistency)..cairn/ground/brand/voice.md when it appears in your context.
Default to plain English when it's absent. Any DEC body the skill
writes is always full English regardless of voice.messagebaseline_findingcairn_search({query: "drift"}) against the
staleness log if any..cairn/events/*.json newer than last_polled_ts.