SLOPSHOPPER

snippets

Expand #hashtag snippets in the prompt, with # autocomplete

prompt
A shopper browsing a rack in a slop shop
README

snippets

A Claude Code mod that expands #hashtag snippets in the prompt.

  • Type # and a prefix: matching snippets show in the typeahead. Press TAB to replace the #word with the snippet text.
  • Any #tag still in the message is expanded when you press Enter. Unknown tags stay as typed.
  • Snippets can include other #tags, and <prepend>…</prepend> / <append>…</append> move text to the start or end of the message.

Requires Claude Code v2.1.302 or later (tested on 2.1.302).

Install: see the marketplace README.

Edit snippets in the SNIPPETS array in hooks/register.ts.

Source 1 files
hooks/register.ts 240 lines
1/**
2 * Snippets - expand #hashtag snippets in user input, with #-autocomplete.
3 * Port of ~/.pi/agent/extensions/snippets.ts. Edit SNIPPETS; the mod hot-reloads.
4 */
5
6import type { Register } from 'claude-code'
7
8interface Snippet {
9  name: string;
10  aliases?: string[];
11  description?: string;
12  content: string;
13}
14
15const SNIPPETS: Snippet[] = [
16  {
17    name: "uv",
18    aliases: ["uv"],
19    description: "It provides uv information",
20    content: "you could use uv with VIRTUAL_ENV=/home/$USER/.venvs/uness uv run --sync [SCRIPTNAME]",
21  },
22  {
23    name: "premortem",
24    aliases: ["premortem"],
25    description: "premortem analysis",
26    content: "perform an epistemic premortem analysis using formal counterfactual reasoning to consider the consequences of following your suggestion.",
27  },
28  {
29    name: "blogpost",
30    aliases: ["blogpost"],
31    description: "write a medium-style blogspot",
32    content: "write an article in clear prose for the Medium.com platform. Avoid telegraphic sentences. The text must be self-contained. AI-sounding sentences and wording removed",
33  },
34  {
35    name: "datastorytelling",
36    aliases: ["datastorytelling"],
37    description: "write a datastorytelling",
38    content: "provide a data storytelling walkthrough as if it were a manual processing task, include intermediate inputs/outputs",
39  },
40  {
41    name: "defensible",
42    aliases: ["defensible"],
43    description: "Recover as much of the text's quality as possible ",
44    content: "select the most defensible cases and develop the strongest possible argument for each one",
45    },
46  {
47    name: "evidence",
48    aliases: ["evidence"],
49    description: "Show me raw evidence",
50    content: "show me the raw evidence for each of your claims. Identify any assumptions you have made and explain what you cannot verify",
51    },
52  {
53    name: "falsification",
54    aliases: ["epistemic"],
55    description: "epistemic falsification",
56    content: "epistemically audit your answer: (1) Fix definitions strictly to avoid semantic ambiguity; (2) State the core claim alongside its necessary auxiliary assumptions; (3) Define the operational domain where it holds; (4) Identify the specific empirical or systemic conditions under which the core claim is definitively falsified.",
57    },
58  {
59    name: "tldr",
60    aliases: ["tldr"],
61    description: "tl;dr",
62    content: "provide a TL;DR summary of your answer in plain text for a project manager.",
63    },
64  {
65    name: "cognitive_bridge",
66    aliases: ["cognitive_bridge"],
67    description: "cognitive bridge",
68    content: "what cognitive or epistemic bridge would a senior scientist use to spot the problem between X and Y?",
69    },
70  {
71    name: "cognitive_bias",
72    aliases: ["cognitive_bias"],
73    description: "cognitive bias",
74    content: "identify the cognitive biases that influenced your response and refine its epistemological quality.",
75    },
76  {
77    name: "dsl",
78    aliases: ["dsl"],
79    description: "dsl",
80    content: "show me the latent DSL (Domain-Specific language) for this text",
81    },
82  {
83    name: "traceability",
84    aliases: ["traceability"],
85    description: "traceability",
86    content: "follow every claim back to the measurement that produced it, and treat whatever resists tracing as the signal itself",
87    },
88  {
89    name: "claude",
90    aliases: ["claude"],
91    description: "claude code",
92    content: "use command ```claude -p \"[TASK]\" ---allowed-tools Read,Write``` to ask for a second opinion for solving the problem",
93    },
94  {
95    name: "codex",
96    aliases: ["codex"],
97    description: "codex",
98    content: "use command ```codex exec  --skip-git-repo-check \"[TASK]\"``` to ask for a second opinion for solving the problem",
99    },
100  {
101    name: "tmux_pane",
102    aliases: ["tmux_pane"],
103    description: "launch an operation in a tmux pane",
104    content: "use another tmux pane to evaluate the output: a) tmux send-keys -t [SESSION:WINDOW.PANE] \"pi --provider litellm --model openai/Inferact/Qwen3.8-27B-NVFP4 --thinking off -p \"[TASK]\" Enter , b) tmux capture-pane -p -t [SESSION:WINDOW.PANE] -S -[SCROLLBALL_LINES]",
105    },
106  {
107    name: "pi-link",
108    aliases: ["pi-link"],
109    description: "launch an operation in a tmux pane",
110    content: "use tmux send-keys -t [SESSION:WINDOW.PANE] \"pi --link-name slave\" Enter, to start a new pi agent and next, use link_send to ask to \"slave\" to perform this [TASK]",
111    },
112  {
113    name: "dead_code",
114    aliases: ["dead-code"],
115    description: "dead-code",
116    content: "remove associated dead code, leftovers, orphan code and landmines",
117     },
118  {
119    name: "confirm",
120    aliases: ["confirm"],
121    description: "Confirm understanding",
122    content: "confirm you understand, no coding yet. Wait for further instructions.",
123     },
124  {
125    name: "playwright",
126    aliases: ["playwright"],
127    description: "Use playwright headed vnc skill",
128    content: "use the skill playwright-headed-vnc",
129     },
130  {
131    name: "spurious_rigor",
132    aliases: ["spurious_rigor"],
133    description: "spurious_rigor",
134    content: "detect outcome biases like unfaithful Post-Hoc Rationalization, Spurious Rigor / Mathiness, Cargo Cult Reasoning, Framework-Forcing / Sycophancy and Formal Confabulation, Automation bias.",
135    }
136];
137
138const MAX_EXPANSIONS = 25;
139const HASHTAG = /#([a-z0-9\-_]+)/gi;
140
141const registry = new Map<string, Snippet>();
142for (const s of SNIPPETS) {
143  registry.set(s.name.toLowerCase(), s);
144  for (const a of s.aliases ?? []) registry.set(a.toLowerCase(), s);
145}
146
147interface Expanded {
148  text: string;
149  prepend: string[];
150  append: string[];
151}
152
153function extractBlocks(content: string): { inline: string; prepend: string[]; append: string[] } {
154  const prepend: string[] = [];
155  const append: string[] = [];
156  // Unclosed block runs to the end of the content; a stray closing tag stays inline.
157  const inline = content.replace(/<(prepend|append)>([\s\S]*?)(?:<\/\1>|$)/gi, (_m, tag: string, body: string) => {
158    const trimmed = body.trim();
159    if (trimmed) (tag.toLowerCase() === "prepend" ? prepend : append).push(trimmed);
160    return "";
161  });
162  return { inline: inline.trim(), prepend, append };
163}
164
165export function expand(text: string, counts: Map<string, number>): Expanded {
166  const prepend: string[] = [];
167  const append: string[] = [];
168  let out = text;
169  let previous: string;
170  let loop = false;
171
172  do {
173    previous = out;
174    out = out.replace(HASHTAG, (match, name: string) => {
175      const key = name.toLowerCase();
176      const snippet = registry.get(key);
177      if (!snippet) return match;
178
179      const count = (counts.get(key) ?? 0) + 1;
180      if (count > MAX_EXPANSIONS) {
181        loop = true;
182        return match;
183      }
184      counts.set(key, count);
185
186      const blocks = extractBlocks(snippet.content);
187      for (const [list, dest] of [
188        [blocks.prepend, prepend],
189        [blocks.append, append],
190      ] as const) {
191        for (const block of list) {
192          const r = expand(block, counts);
193          dest.push(r.text);
194          prepend.push(...r.prepend);
195          append.push(...r.append);
196        }
197      }
198      const nested = expand(blocks.inline, counts);
199      prepend.push(...nested.prepend);
200      append.push(...nested.append);
201      return nested.text;
202    });
203  } while (out !== previous && !loop);
204
205  return { text: out, prepend, append };
206}
207
208export function expandAll(text: string): string {
209  const r = expand(text, new Map())
210  return [r.prepend.join('\n\n'), r.text.trim() ? r.text : '', r.append.join('\n\n')].filter(Boolean).join('\n\n')
211}
212
213export const register: Register = (on) => {
214  on('prompt.autocomplete', { token: /^#/ }, async ($, e, next) => {
215    const query = e.token.slice(1).toLowerCase()
216    if (!/^[a-z0-9_-]*$/.test(query)) return next(e)
217    const mine = SNIPPETS.filter(
218      (s) => !query || [s.name, ...(s.aliases ?? [])].some((n) => n.toLowerCase().includes(query)),
219    )
220      .sort((a, b) => a.name.localeCompare(b.name))
221      .map((s) => {
222        const aliases = (s.aliases ?? []).filter((a) => a !== s.name).map((a) => `#${a}`).join(', ')
223        const desc = s.description ?? (s.content.split("\n")[0] ?? "").slice(0, 60)
224        return {
225          text: expandAll(`#${s.name}`) || `#${s.name}`,
226          label: `#${s.name}`,
227          description: aliases ? `${desc}  [${aliases}]` : desc,
228        }
229      })
230    const { suggestions } = await next(e)
231    return { suggestions: [...suggestions, ...mine] }
232  })
233
234  on('prompt.submit', async ($, e, next) => {
235    if (e.command || !e.text) return next(e)
236    const text = expandAll(e.text)
237    return text !== e.text ? next({ ...e, text }) : next(e)
238  })
239}
240