SLOPSHOPPER

nova

13 engineering skills, the nova output style and mod, and settings for Claude Code, with cross-platform installers, backups, and clean replacement.

newguardpromptagents
A shopper browsing a rack in a slop shop
README

Claude Code Agent Skills

English · Українська · Русский

A portable snapshot of the author's local Claude Code setup, synchronized on 2026-10-26. It contains 13 engineering skills, the nova output style, and the nova mod, plus portable settings.

Skills cover code changes, tests and defect diagnosis, security, server-side work, databases, frontend implementation, visual and UX design, architecture, implementation plans, builds and operations, commit messages, instructions for AI agents, and triage of problems on live sites and services. Every rule in a skill states when it applies, what to do, and why, followed by an example.

The nova output style keeps Claude silent between tool calls and sends one final answer in the user's language that opens with the result and usually fits in one short paragraph. Plan steps and questions to the user use the user's language too, while tool descriptions and briefs for other agents stay in English. The answer stays in plain paragraphs even when agent reports, tool results, or fetched pages arrive with tables or lists, and several rules use the wording of the built-in Concise style.

The nova mod is a Claude Code plugin with a hooks module:

  • It replaces the system prompt sections for task work, tools, workflow, skills, and research with its own rule files, where every rule carries a reason and an example. The rules about task scope keep the wording of Anthropic's prompting guides for current models.
  • It reminds Claude to load a matching skill when a file is edited before any skill was loaded.
  • It sends a correction after a message that put text before a tool call, so progress notes stop instead of repeating.
  • While agents or background commands it started are still running, it turns the desktop app's empty-reply prompt into an instruction to keep waiting, so no status message is written. It also adds to the ReadNotifications tool description that such work reports its own completion, so Claude ends the turn instead of checking notifications while it waits. On Windows it keeps the Bash tool deferred so local commands run in PowerShell.
  • It adds a block of working rules to the brief of every new subagent, covering skills, tools, verification, language, and cleanup, without the output style's length limits, so agent reports stay detailed.
  • It removes the parts of the Agent tool description that encourage delegating side tasks, and the bash-first guidance that suggests reading and editing files through shell commands and scripts.

The portable settings set CLAUDE_CODE_THRIFTY_SONIC to 0, which turns off a Claude Code experiment that shortens the Bash tool's guidance and steers Claude toward shell commands and scripts for file work.

This is a personal configuration, not an official Anthropic distribution. Credentials, account state, chat history, projects, sessions, plugins, synced skills, caches, and machine paths are not included.

[!WARNING] When your settings don't set them, the installer adds "permissions": { "defaultMode": "bypassPermissions" } and "skipDangerousModePermissionPrompt": true. Claude Code then runs every tool call without asking and skips the confirmation dialog for that mode. The permission modes documentation recommends this mode only for isolated environments, such as containers and virtual machines without internet access. Review the settings before installing, or set your own permissions.defaultMode first, because the installer keeps existing values.

Install or replace an older setup

Download and extract the ZIP archive, or clone the repository:

git clone https://github.com/dayfinggg/anthropic-claude-code-agent-skills.git
cd anthropic-claude-code-agent-skills

Run one installer from the extracted or cloned folder. It uses the included files without downloading another copy.

Windows

Requires Windows PowerShell 5.1 or PowerShell 7.

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install.ps1

macOS and Linux

Requires a POSIX shell, python3 for the settings merge, and a SHA-256 utility, such as sha256sum, shasum, or openssl.

sh scripts/install.sh

Restart Claude Code after installation. The mod loads through CLAUDE_CODE_PLUGIN_DIRS, which requires Claude Code v2.1.280 or later.

Replace is the default. The installer backs up and replaces all skills except skills/synced, all agents, the output-styles folder, and the mods folder, removing the old set. No agents are bundled, so agents from an earlier version of this setup are removed after the backup. It keeps synced skills, credentials, sessions, projects, plugins, and unrelated files. Existing settings values are preserved and missing keys are added, except outputStyle, which becomes nova, and env.CLAUDE_CODE_PLUGIN_DIRS, which lists the installed mod folder.

Update

To download and install the latest GitHub revision without updating your checkout:

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\update.ps1
sh scripts/update.sh

Updates require internet access, with curl and tar additionally required for the shell updater. The last install mode is reused. If you maintain a Git clone instead, run git pull --ff-only and rerun the installer.

Contents and installation details

The payload files are copied byte for byte from the local setup. The settings copy leaves out env.CLAUDE_CODE_PLUGIN_DIRS, which the installer writes for each platform, and the mod's .claude-plugin/types folder is left out because Claude Code generates those type definitions for the version it runs. The empty claude/agents folder holds only a .gitkeep file, so the installer still manages that folder.

Claude Code keeps its configuration in ~/.claude, or in the folder named by CLAUDE_CONFIG_DIR, as described in Environment variables and Explore the .claude directory. Skills load from skills/<skill-name>/SKILL.md (Extend Claude with skills), and output styles from output-styles (Output styles). CLAUDE_CODE_PLUGIN_DIRS loads each listed folder the way --plugin-dir does, with absolute paths separated by ; on Windows and : elsewhere (Mods reference).

Verify the installers

Requires Python 3.11 or newer:

python -m unittest discover -s tests -v

Tests use temporary directories, not your real Claude Code setup. Available Windows PowerShell, PowerShell 7, and POSIX shell implementations are tested; unavailable runtimes are reported as skipped.

Report problems through GitHub issues. Repository instructions and scripts use the MIT License.

Source 1 files
hooks/register.ts 249 lines
1import type { CoreEngineInterface, Register } from 'claude-code'
2
3type Configuration = {
4  discoveryTool: string
5  promptSectionFiles: Readonly<Record<string, string>>
6  upfrontTools: readonly string[]
7  deferredTools: readonly string[]
8  deferredPrefixes: readonly string[]
9  windowsDeferredTools?: readonly string[]
10  removedPromptSections: readonly string[]
11  removedInstructionPatterns: readonly string[]
12  removedAttachmentPatterns: Readonly<Record<string, readonly string[]>>
13  removedContextPatterns?: Readonly<Record<string, readonly string[]>>
14  attachmentReminders?: Readonly<Record<string, { pattern: string, file: string }>>
15  toolDescriptionReminders?: Readonly<Record<string, string>>
16  windowsToolDescriptionReminders?: Readonly<Record<string, string>>
17  agentRules?: string
18  skillReminder?: {
19    file: string
20    editTools: readonly string[]
21    filePattern: string
22  }
23  emptyTurnNudge?: {
24    pattern: string
25    file: string
26  }
27  narrationReminder?: {
28    file: string
29    closingTools: readonly string[]
30    limit: number
31  }
32}
33
34type LoopState = {
35  skillReminded: boolean
36  narrated: boolean
37  narrationReminders: number
38}
39
40const WINDOWS_PATH = /^[A-Za-z]:[\\/]/
41const ACTIVE_AGENT_STATUSES: readonly string[] = ['pending', 'running', 'waiting']
42const FINISHED_TASK = /<task-id>([^<]+)<\/task-id>[\s\S]*?<status>(?:completed|failed|killed|stopped)<\/status>/g
43
44function getLoopState(states: Map<string | undefined, LoopState>, agentId: string | undefined): LoopState {
45  let state = states.get(agentId)
46  if (!state) {
47    state = { skillReminded: false, narrated: false, narrationReminders: 0 }
48    states.set(agentId, state)
49  }
50  return state
51}
52
53function textOf(content: readonly unknown[]): string {
54  return content.map(block => stringField(block, 'text') ?? '').join('\n')
55}
56
57function isRecord(value: unknown): value is Record<string, unknown> {
58  return typeof value === 'object' && value !== null && !Array.isArray(value)
59}
60
61function stringField(value: unknown, key: string): string | undefined {
62  if (!isRecord(value)) return undefined
63  const field = value[key]
64  return typeof field === 'string' ? field : undefined
65}
66
67async function skillLoaded($: CoreEngineInterface, skillAgents: ReadonlySet<string | undefined>, agentId: string | undefined): Promise<boolean> {
68  if (skillAgents.has(agentId)) return true
69  if (agentId !== undefined) return false
70  const messages = await $.session.messages()
71  return messages.some(message => message.toolUses.some(use => use.tool === 'Skill'))
72}
73
74async function backgroundWorkRunning($: CoreEngineInterface, backgroundCommands: ReadonlySet<string>): Promise<boolean> {
75  if (backgroundCommands.size > 0) return true
76  const agents = await $.agent.list()
77  return agents.some(agent => agent.parentId === undefined && ACTIVE_AGENT_STATUSES.includes(agent.status))
78}
79
80function readConfiguration($: CoreEngineInterface): Promise<Configuration> {
81  return $.fs.read(`${$.plugin.root}/config.json`).then(text => JSON.parse(text) as Configuration)
82}
83
84async function readPromptSections($: CoreEngineInterface, files: Readonly<Record<string, string>>): Promise<Map<string, string>> {
85  const sections = new Map<string, string>()
86  for (const [id, path] of Object.entries(files)) {
87    sections.set(id, await $.fs.read(`${$.plugin.root}/${path}`))
88  }
89  return sections
90}
91
92function readReminder($: CoreEngineInterface, path: string, files: Map<string, Promise<string>>): Promise<string> {
93  let text = files.get(path)
94  if (!text) {
95    text = $.fs.read(`${$.plugin.root}/${path}`)
96    files.set(path, text)
97  }
98  return text
99}
100
101export const register: Register = on => {
102  let configuration: Promise<Configuration> | undefined
103  let promptSections: Promise<Map<string, string>> | undefined
104  const reminderFiles = new Map<string, Promise<string>>()
105  const loopStates = new Map<string | undefined, LoopState>()
106  const removeInstructions = (text: string, patterns: readonly string[]) => patterns.reduce((result, pattern) => result.replace(new RegExp(pattern, 'g'), ''), text)
107  const skillAgents = new Set<string | undefined>()
108  const backgroundCommands = new Set<string>()
109  let discoveryAvailable = false
110  let windowsHost = false
111
112  on('turn.start', async ($, event, next) => {
113    loopStates.delete(event.agentId)
114    return next(event)
115  })
116
117  on('agent.spawn', async ($, event, next) => {
118    const config = await (configuration ??= readConfiguration($))
119    if (!config.agentRules || event.fork || event.resume) return next(event)
120    const rules = await readReminder($, config.agentRules, reminderFiles)
121    return next({ ...event, prompt: `${event.prompt.trimEnd()}\n\n${rules.trimEnd()}` })
122  })
123
124  on('turn.step', async function* ($, event, next) {
125    const result = yield* next(event)
126    if (event.agentId !== undefined) return result
127    const config = await (configuration ??= readConfiguration($))
128    const narration = config.narrationReminder
129    if (narration && result.answer.trim() && result.toolUses.some(use => !narration.closingTools.includes(use.name))) {
130      getLoopState(loopStates, undefined).narrated = true
131    }
132    return result
133  })
134
135  on('session.append', async ($, event, next) => {
136    const content = event.message.content
137    for (const match of textOf(content).matchAll(FINISHED_TASK)) backgroundCommands.delete(match[1]!)
138    const config = await (configuration ??= readConfiguration($))
139    const nudge = config.emptyTurnNudge
140    if (!nudge || event.agentId !== undefined || event.message.type !== 'user' || !event.message.isMeta ||
141      !new RegExp(nudge.pattern).test(textOf(content)) || !await backgroundWorkRunning($, backgroundCommands)) return next(event)
142    const text = await readReminder($, nudge.file, reminderFiles)
143    return next({ ...event, message: { ...event.message, content: [{ type: 'text', text: text.trimEnd() }] } })
144  })
145
146  on('tool.call', async ($, event, next) => {
147    const config = await (configuration ??= readConfiguration($))
148    const state = getLoopState(loopStates, event.agentId)
149    const result = await next(event)
150    if (result.deny !== undefined || loopStates.get(event.agentId) !== state) return result
151    const paths: string[] = []
152    if (event.tool === 'Skill' && !result.isError) skillAgents.add(event.agentId)
153    const backgroundTask = !result.isError ? stringField(result.result, 'backgroundTaskId') : undefined
154    if (backgroundTask) backgroundCommands.add(backgroundTask)
155    const stoppedTask = event.tool === 'TaskStop' ? stringField(event, 'task_id') : undefined
156    if (stoppedTask) backgroundCommands.delete(stoppedTask)
157    const filePath = stringField(event, 'file_path')
158    const skill = config.skillReminder
159    if (skill && filePath && !result.isError && !result.isReadOnly && !state.skillReminded && skill.editTools.includes(event.tool) &&
160      new RegExp(skill.filePattern, 'i').test(filePath) && !await skillLoaded($, skillAgents, event.agentId)) {
161      state.skillReminded = true
162      paths.push(skill.file)
163    }
164    const narration = config.narrationReminder
165    if (narration && state.narrated && event.agentId === undefined) {
166      state.narrated = false
167      if (state.narrationReminders < narration.limit) {
168        state.narrationReminders += 1
169        paths.push(narration.file)
170      }
171    }
172    if (!paths.length) return result
173    const reminders: string[] = []
174    for (const file of new Set(paths)) reminders.push(await readReminder($, file, reminderFiles))
175    if (loopStates.get(event.agentId) !== state) return result
176    return { ...result, context: [...(result.context ?? []), ...reminders] }
177  })
178
179  on('session.start', async ($, event, next) => {
180    const config = await (configuration ??= readConfiguration($))
181    windowsHost = WINDOWS_PATH.test(event.cwd)
182    discoveryAvailable = (await $.tool.list()).some(tool => tool.name === config.discoveryTool)
183    $.ui.invalidate('tool.describe')
184    return next(event)
185  })
186
187  on('tool.describe', async ($, event, next) => {
188    const config = await (configuration ??= readConfiguration($))
189    const result = await next(event)
190    let description = removeInstructions(result.description, config.removedInstructionPatterns)
191    const reminder = config.toolDescriptionReminders?.[event.tool] ??
192      (windowsHost ? config.windowsToolDescriptionReminders?.[event.tool] : undefined)
193    if (reminder) description += `\n\n${await readReminder($, reminder, reminderFiles)}`
194    if (config.upfrontTools.includes(event.tool)) return { ...result, description, isDeferred: false }
195    const defer = discoveryAvailable && event.tool !== config.discoveryTool && (
196      config.deferredTools.includes(event.tool) ||
197      config.deferredPrefixes.some(prefix => event.tool.startsWith(prefix)) ||
198      (windowsHost && (config.windowsDeferredTools?.includes(event.tool) ?? false))
199    )
200    return defer ? { ...result, description, isDeferred: true } : { ...result, description }
201  })
202
203  on('prompt.context', async ($, event, next) => {
204    const config = await (configuration ??= readConfiguration($))
205    const result = await next(event)
206    return {
207      ...result,
208      blocks: result.blocks.map(block => ({
209        ...block,
210        text: removeInstructions(block.text, config.removedContextPatterns?.[block.name] ?? []),
211      })),
212    }
213  })
214
215  on('prompt.compose', async ($, event, next) => {
216    const config = await (configuration ??= readConfiguration($))
217    const result = await next(event)
218    const replacements = await (promptSections ??= readPromptSections($, config.promptSectionFiles))
219    const sections = result.sections
220      .filter(section => !config.removedPromptSections.includes(section.id))
221      .map(section => ({
222        ...section,
223        text: replacements.get(section.id) ?? removeInstructions(section.text, config.removedInstructionPatterns),
224      }))
225    for (const [id, text] of replacements) {
226      if (!sections.some(section => section.id === id)) {
227        sections.push({ id: `${$.plugin.name}:${id}`, text, scope: 'session' })
228      }
229    }
230    return { ...result, sections }
231  })
232
233  on('prompt.attachment', async ($, event, next) => {
234    const config = await (configuration ??= readConfiguration($))
235    const result = await next(event)
236    if (result.text === null) return result
237    const patterns = config.removedAttachmentPatterns[event.type]
238    const text = patterns ? removeInstructions(result.text, patterns) : result.text
239    if (text !== result.text && !text.trim()) return { ...result, text: null }
240    const reminder = config.attachmentReminders?.[event.type]
241    return {
242      ...result,
243      text: reminder && event.origin.kind === 'engine' && new RegExp(reminder.pattern).test(text)
244        ? await readReminder($, reminder.file, reminderFiles)
245        : text,
246    }
247  })
248}
249