← All Skills

snappy-chain

v1.0.0
7 files, 26.8 KB ~780 words · 4 min read Updated 2026-09-09

snappy-chain skill

31 of 34 checks pass
What does not pass yet
$ npx snappy-skills install snappy-chain
zip ↓
File Tree
├── AGENTS.md ├── SKILL.md ├── api.ts ├── chains/ │ ├── bug-report.ts │ └── question.ts ├── classify.ts └── contract.test.ts
Documents
AGENTS.md

snappy-chain — The Composer Layer#

Where snappy-channel-contract gives uniform hands and eyes across every channel, snappy-chain turns sensed events into action. Cron-safe, dedup-enforced, channel-agnostic handlers.

The loop#

adapters[] ─read─▶ Event[] ─classify─▶ Intent ─route─▶ ChainHandler ─post─▶
                                            │
                                            ▼
                                 chain-processed.ndjson (dedup)

API#

typescriptimport { dispatchOnce, registerChain } from "../snappy-chain/api.ts";

registerChain("bug-report", async (event, adapter) => {
  // ... clone repo, fix, screenshot, open PR ...
  const post = await adapter.post(
    { source: event.source, channel_id: event.channel_id, thread_id: event.thread_id, to_user: null },
    { text: "Fixed in PR #123" },
  );
  return { ok: true, intent: "bug-report", chain: "bug-report", event_id: event.event_id, source: event.source, post };
});

const summary = await dispatchOnce({ limit: 20 });

Commands#

bash# dry run — classify + log, do not fire handlers
npx tsx ~/.claude/skills/snappy-chain/api.ts --dry

# classifier self-test
npx tsx ~/.claude/skills/snappy-chain/classify.ts

Logs#

  • ~/.claude/logs/chain-processed.ndjson — one line per (source, event_id) ever seen. Dedup source of truth.
  • ~/.claude/logs/chain-runs.ndjson — one line per dispatchOnce() run with counts + results.

Rules#

  1. Channel-agnostic handlers only. Receive (event, adapter), use contract verbs. No hard-coded Slack calls inside a handler.
  2. Publish through adapter.post(). Per-channel rules (e.g. Gmail draft-only) stay honored.
  3. Every run logs a summary. Run log is the chain-level cert.
  4. Failed chains don't auto-retry. Root-cause, fix, manually re-inject.
  5. Dedup is mandatory and automatic. Dispatcher owns it, not handlers.

Intents (V1 classifier)#

  • bug-report — error/broken/crash language
  • question — ends with ? or starts with how|what|why|where
  • feature-request — "can you add...", "would be nice if..."
  • ack — "thanks", "got it", "perfect"
  • noise — everything else (still marked processed to stop re-classification)

V1 is pure keyword rules. Upgrade path: LLM fallback via snappy-dispatch for unmatched events.

When to add a new chain#

  1. Create chains/<intent>.ts exporting a ChainHandler.
  2. Call registerChain("<intent>", handler) once at startup.
  3. Dry-run the dispatcher against recent sweep data to verify classification fires.
  4. If the intent doesn't exist yet, add it to Intent in classify.ts and add rules.

<!-- SKILL-INDEX-START -->

[snappy-chain Index]|root: ~/.claude/skills/snappy-chain|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}

<!-- SKILL-INDEX-END -->

Used by#

Nothing in the collection names this skill.

<!-- SNAPPY-CONTRACT-VERBS-START -->

Contract verbs#

Generated from api.ts HAND_CONTRACT. Do not hand-edit this block.

This skill declares no executable verbs. Its instruction-only label is intentional.

Show the result#

When an answer carries face_hint, show it with one snappy_present(<answer>) call.

See /snappy-faces for face selection. Human-facing images must crop to the

element, render at 2x on Retina, and fill the destination channel instead of

placing a small card in a full-page screenshot.

<!-- SNAPPY-CONTRACT-VERBS-END -->

---
name: snappy-chain
role: Event-to-chain dispatcher sitting on top of snappy-channel-contract
loaded-by: preload-skill-context hook
---

# snappy-chain — The Composer Layer

Where `snappy-channel-contract` gives uniform hands and eyes across every channel, `snappy-chain` turns sensed events into action. Cron-safe, dedup-enforced, channel-agnostic handlers.

## The loop

```
adapters[] ─read─▶ Event[] ─classify─▶ Intent ─route─▶ ChainHandler ─post─▶
                                            │
                                            ▼
                                 chain-processed.ndjson (dedup)
```

## API

```typescript
import { dispatchOnce, registerChain } from "../snappy-chain/api.ts";

registerChain("bug-report", async (event, adapter) => {
  // ... clone repo, fix, screenshot, open PR ...
  const post = await adapter.post(
    { source: event.source, channel_id: event.channel_id, thread_id: event.thread_id, to_user: null },
    { text: "Fixed in PR #123" },
  );
  return { ok: true, intent: "bug-report", chain: "bug-report", event_id: event.event_id, source: event.source, post };
});

const summary = await dispatchOnce({ limit: 20 });
```

## Commands

```bash
# dry run — classify + log, do not fire handlers
npx tsx ~/.claude/skills/snappy-chain/api.ts --dry

# classifier self-test
npx tsx ~/.claude/skills/snappy-chain/classify.ts
```

## Logs

- `~/.claude/logs/chain-processed.ndjson` — one line per `(source, event_id)` ever seen. Dedup source of truth.
- `~/.claude/logs/chain-runs.ndjson` — one line per `dispatchOnce()` run with counts + results.

## Rules

1. **Channel-agnostic handlers only.** Receive `(event, adapter)`, use contract verbs. No hard-coded Slack calls inside a handler.
2. **Publish through `adapter.post()`.** Per-channel rules (e.g. Gmail draft-only) stay honored.
3. **Every run logs a summary.** Run log is the chain-level cert.
4. **Failed chains don't auto-retry.** Root-cause, fix, manually re-inject.
5. **Dedup is mandatory and automatic.** Dispatcher owns it, not handlers.

## Intents (V1 classifier)

- `bug-report` — error/broken/crash language
- `question` — ends with `?` or starts with `how|what|why|where`
- `feature-request` — "can you add...", "would be nice if..."
- `ack` — "thanks", "got it", "perfect"
- `noise` — everything else (still marked processed to stop re-classification)

V1 is pure keyword rules. Upgrade path: LLM fallback via `snappy-dispatch` for unmatched events.

## When to add a new chain

1. Create `chains/<intent>.ts` exporting a `ChainHandler`.
2. Call `registerChain("<intent>", handler)` once at startup.
3. Dry-run the dispatcher against recent sweep data to verify classification fires.
4. If the intent doesn't exist yet, add it to `Intent` in `classify.ts` and add rules.

<!-- SKILL-INDEX-START -->
[snappy-chain Index]|root: ~/.claude/skills/snappy-chain|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}
<!-- SKILL-INDEX-END -->

## Used by

Nothing in the collection names this skill.

<!-- SNAPPY-CONTRACT-VERBS-START -->
## Contract verbs

Generated from `api.ts` `HAND_CONTRACT`. Do not hand-edit this block.

This skill declares no executable verbs. Its `instruction-only` label is intentional.

## Show the result

When an answer carries `face_hint`, show it with one `snappy_present(<answer>)` call.
See `/snappy-faces` for face selection. Human-facing images must crop to the
element, render at 2x on Retina, and fill the destination channel instead of
placing a small card in a full-page screenshot.
<!-- SNAPPY-CONTRACT-VERBS-END -->

Keyboard Shortcuts

Search in document⌘K
Focus search/
Previous file tab
Next file tab
Close overlayEsc
Show shortcuts?