@linchpinagency/skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +337 -0
  2. package/bin/install.mjs +231 -0
  3. package/package.json +44 -0
  4. package/skills/browser-automation/SKILL.md +93 -0
  5. package/skills/commit-and-release/SKILL.md +135 -0
  6. package/skills/dependency-updates/SKILL.md +102 -0
  7. package/skills/design-previews/SKILL.md +118 -0
  8. package/skills/engagement-types/SKILL.md +108 -0
  9. package/skills/investigate/SKILL.md +95 -0
  10. package/skills/project-context/SKILL.md +89 -0
  11. package/skills/quality-gates/SKILL.md +94 -0
  12. package/skills/quality-gates/references/toolchain.md +104 -0
  13. package/skills/safety-hooks/SKILL.md +121 -0
  14. package/skills/safety-hooks/scripts/check-destructive.sh +80 -0
  15. package/skills/safety-hooks/scripts/check-edit-boundary.sh +65 -0
  16. package/skills/support-triage/SKILL.md +103 -0
  17. package/skills/task-tracking/SKILL.md +243 -0
  18. package/skills/web-qa/SKILL.md +108 -0
  19. package/skills/web-qa/references/qa-checklist.md +98 -0
  20. package/skills/wordpress-blocks/SKILL.md +110 -0
  21. package/skills/wordpress-blocks/references/block-grammar.md +94 -0
  22. package/skills/wordpress-blocks/references/core-blocks.md +123 -0
  23. package/skills/wordpress-blocks/references/patterns-and-parts.md +70 -0
  24. package/skills/wordpress-blocks/references/recipes/faq.md +56 -0
  25. package/skills/wordpress-blocks/references/recipes/hero.md +74 -0
  26. package/skills/wordpress-blocks/references/recipes/pricing-table.md +77 -0
  27. package/skills/wordpress-blocks/references/tool-contract.md +167 -0
  28. package/skills/wordpress-blocks/references/validation.md +38 -0
  29. package/skills/wp-audit/SKILL.md +115 -0
  30. package/skills/wp-block-conventions/SKILL.md +134 -0
  31. package/skills/wp-block-conventions/references/block-anatomy.md +175 -0
  32. package/skills/wp-implementation-choice/SKILL.md +88 -0
  33. package/skills/wp-local-setup/SKILL.md +262 -0
  34. package/skills/wp-pressable/SKILL.md +172 -0
  35. package/skills/wp-studio-cli/SKILL.md +165 -0
  36. package/skills/write-a-linchpin-skill/SKILL.md +195 -0
  37. package/skills/write-a-linchpin-skill/references/template.md +83 -0
  38. package/upstream.json +20 -0
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: project-context
3
+ description: Orient before acting on a Linchpin project — identify the repo and branch, the local environment (Studio, wp-env, LocalWP), the host (Pressable or Cloudflare), the ClickUp space, and the release model, from .linchpin.json, composer.json, package.json, and the git remote. Use when starting work on an unfamiliar repo, before running commands that assume an environment, when a skill's Preflight needs the project's shape, or when something behaves differently than expected. Not for running the checks themselves.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Project context
8
+
9
+ Our projects are not uniform: WordPress plugin repos, `wp-content`-shaped site repos, and
10
+ Cloudflare Workers services all live under the same conventions but expose different tools,
11
+ environments, and hosts. **Guessing wrong is the most common way an agent wastes a session** —
12
+ running `wp-env` on a Studio project, or looking for `phpcs.xml.dist` in a Workers repo.
13
+
14
+ This is the one place that answers "what am I working in?". Other skills reference it from
15
+ their Preflight instead of each re-deriving it.
16
+
17
+ ## When to use
18
+
19
+ - Starting work on a repo you haven't touched this session.
20
+ - Before any command that assumes an environment, host, or toolchain.
21
+ - A skill's Preflight needs the project's shape.
22
+ - Something behaves unexpectedly and the environment is a suspect.
23
+
24
+ **Not this skill:** running lint or tests — [`quality-gates`](../quality-gates/SKILL.md).
25
+ Operating the local site — [`wp-studio-cli`](../wp-studio-cli/SKILL.md). Operating the
26
+ server — [`wp-pressable`](../wp-pressable/SKILL.md).
27
+
28
+ ## Owns
29
+
30
+ Canonical for: what to read to identify a project, and what each signal means. Every other
31
+ skill's Preflight should link here rather than restate the detection table.
32
+
33
+ ## The orientation pass
34
+
35
+ Cheap, read-only, and worth doing once per session rather than per command:
36
+
37
+ ```bash
38
+ git rev-parse --show-toplevel && git branch --show-current && git remote get-url origin
39
+ ls .linchpin.json composer.json package.json phpcs.xml.dist .wp-env.json wrangler.toml 2>/dev/null
40
+ ```
41
+
42
+ Then read what exists:
43
+
44
+ | Signal | What it tells you |
45
+ | --- | --- |
46
+ | `.linchpin.json` | House metadata — plugin slug, declared local environments and their paths, preferred agent |
47
+ | Repo root has `themes/` + `plugins/` and no core | A **site repo** — the repo *is* `wp-content` ([`wp-local-setup`](../wp-local-setup/SKILL.md)) |
48
+ | A single plugin bootstrap file + `includes/` | A **plugin/product repo** (Mantle, linchpin-blocks) |
49
+ | `wrangler.toml` | A **Cloudflare Workers** service — no PHP toolchain at all |
50
+ | Site registered in Studio | Local env is **Studio** — the default ([`wp-studio-cli`](../wp-studio-cli/SKILL.md)) |
51
+ | `.wp-env.json` / `.linchpin.json` environments | Legacy wp-env or LocalWP; predates the Studio switch — confirm before using |
52
+ | `composer.json` scripts, `phpcs.xml.dist`, `phpstan.neon` | Which PHP gates exist ([`quality-gates`](../quality-gates/SKILL.md)) |
53
+ | Nested `blocks/package.json`, `themes/*/package.json` | Builds run **in that workspace**, not the root |
54
+ | `release-please-config.json` | Versions and `CHANGELOG.md` are machine-owned ([`commit-and-release`](../commit-and-release/SKILL.md)) |
55
+ | `commitlint.config.js` | This repo's allowed commit types — they differ between repos |
56
+ | Deploy workflows referencing Pressable | Hosted on Pressable ([`wp-pressable`](../wp-pressable/SKILL.md)) |
57
+ | Git remote name | Infers the ClickUp space ([`task-tracking`](../task-tracking/SKILL.md)) |
58
+
59
+ ## What to report
60
+
61
+ State the shape in one or two lines before doing the work, so the user can correct a wrong
62
+ assumption before it costs anything:
63
+
64
+ > `linchpin.com` on `issue/LINCHPIN-5210` — site repo (repo is `wp-content`), Studio local,
65
+ > Pressable hosted, release-please. PHP gates: phpcs + phpstan. Nested builds under
66
+ > `themes/linchpin` and `plugins/linchpin-functionality`.
67
+
68
+ Then carry it for the session. Re-check only when the branch changes or something surprises
69
+ you — this is orientation, not a per-command ritual.
70
+
71
+ ## Guardrails
72
+
73
+ - **Never assume the environment from a config file's presence alone.** A leftover
74
+ `.wp-env.json` in a Studio project is history, not intent — when two exist, ask.
75
+ - **Never infer the host from the repo name.** Read the deploy workflows.
76
+ - **Never run an environment-changing command** (starting containers, creating sites) as part
77
+ of orientation. This pass is read-only.
78
+ - **Never carry stale context across a branch switch** — base branch, gates, and task key can
79
+ all change.
80
+ - If a signal is missing and the answer matters, say what's missing rather than picking the
81
+ most likely option silently.
82
+
83
+ ## Done
84
+
85
+ - [ ] Repo root, branch, and remote identified.
86
+ - [ ] Project shape known: site repo, plugin/product repo, or Workers service.
87
+ - [ ] Local environment identified, and ambiguity resolved with the user rather than guessed.
88
+ - [ ] Host and release model known before anything is shipped or deployed.
89
+ - [ ] The shape was stated back to the user in a line or two.
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: quality-gates
3
+ description: Run a Linchpin project's own lint, coding-standards, static-analysis, and test gates before committing or opening a PR — detecting the toolchain from composer.json, package.json, phpcs.xml.dist and lint-staged rather than assuming it. Use when preparing to commit, when asked "is this ready to commit/ship", when CI lint or PHPCS is failing, when a pre-commit hook blocks you, or when a repo is missing the standard lint scripts. Not for writing the commit message — use `commit-and-release`.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Quality gates
8
+
9
+ Every Linchpin repo already declares how it wants to be checked — in `composer.json`
10
+ scripts, `package.json` scripts, `phpcs.xml.dist`, `lint-staged.config.js`. Your job is to
11
+ **find those declarations and run them**, not to invent commands. Getting this right is
12
+ what makes the difference between a clean PR and a red CI run.
13
+
14
+ The gates are the same ones CI runs, so passing here means passing there.
15
+
16
+ ## When to use
17
+
18
+ - About to commit or open a PR.
19
+ - The user asks whether a change is ready to ship.
20
+ - CI lint / PHPCS / PHPStan failed and you need to reproduce and fix it locally.
21
+ - A husky pre-commit hook is blocking a commit.
22
+ - A repo is missing the house lint scripts and should get them.
23
+
24
+ **Not this skill:** the commit message, branch, or release — [`commit-and-release`](../commit-and-release/SKILL.md).
25
+ Upgrading packages — [`dependency-updates`](../dependency-updates/SKILL.md).
26
+
27
+ ## Owns
28
+
29
+ Canonical for: which gates exist, how to detect them, the order they run in, and how to fix
30
+ failures without suppressing them. Skills that end in a commit defer here for verification.
31
+
32
+ ## Preflight — detect, never assume
33
+
34
+ Read these before running anything. Our repos genuinely differ: PHP-heavy plugin repos,
35
+ `wp-content`-shaped site repos with nested builds, and JS/TS Workers projects all live under
36
+ the same conventions but expose different gates.
37
+
38
+ | Look for | Tells you | If missing |
39
+ | --- | --- | --- |
40
+ | `composer.json` → `scripts.lint` | The canonical PHP gate: `composer run lint` | Run the individual tools below that do exist |
41
+ | `phpcs.xml.dist` or `phpcs.xml` | Coding standards are configured → PHPCS applies | **Skip PHPCS and say so** — never invent a standard |
42
+ | `phpstan.neon(.dist)` | Static analysis applies | Skip; don't add it unprompted |
43
+ | `.php-cs-fixer.dist.php` | PHP-CS-Fixer applies (`composer run fixer`) | Skip |
44
+ | `package.json` → `scripts` | JS/CSS gates (`lint:js`, `lint:css`, `format`, `lint:check`) | Fall back to `eslint`/`prettier` only if configured |
45
+ | `lint-staged.config.js` + `.husky/` | Pre-commit is already wired — **mirror those exact commands** | Run the scripts directly |
46
+ | Nested `package.json` (e.g. `themes/*`, `plugins/*`, `src/`) | Gates run **in that workspace**, not the root | Root only |
47
+ | `.linchpin.json` | Project metadata and local environments | Not every repo has one |
48
+
49
+ Full command matrix: [`references/toolchain.md`](references/toolchain.md).
50
+
51
+ ## Procedure
52
+
53
+ 1. **Scope to what changed.** Staged: `git diff --name-only --cached --diff-filter=ACMR`.
54
+ Whole branch: diff against the merge base with `main`. Split into PHP, JS/TS, CSS/SCSS,
55
+ and config buckets. → You can name every changed file and which gate covers it.
56
+ 2. **PHP gate** (any `.php` changed). Prefer `composer run lint`. If absent, run only the
57
+ tools whose config exists: `composer run phpcs`, `composer run phpstan`,
58
+ `composer run fixer:test`. → Each command exits 0, or you have the exact violations.
59
+ 3. **JS / CSS gate** (any `.js`/`.ts`/`.css`/`.scss` changed). Prefer `npm run lint:check`
60
+ when defined; otherwise `npm run lint:js` and `npm run lint:css`. Run them in the
61
+ workspace that owns the file. → Exit 0 or a concrete rule violation list.
62
+ 4. **Tests** when the change touches covered code: `composer run phpunit`, `npm run test:unit`.
63
+ E2E (`npm run test:e2e`) only when asked or when the change is UI-facing — it needs a
64
+ running environment. → Green, or a named failing test.
65
+ 5. **Fix, don't silence.** Auto-fixers first (`composer run phpcbf`, `composer run fixer`,
66
+ `npm run format`), then re-run the gate; hand-fix what remains. → Gate passes with the
67
+ fix in the code, not in the config.
68
+ 6. **Report gaps, then hand off.** State which gates ran, which were skipped and why. If the
69
+ repo lacks a house script, propose it (see `references/toolchain.md`) and add it **only
70
+ with approval**. → Then go to [`commit-and-release`](../commit-and-release/SKILL.md).
71
+
72
+ ## Guardrails
73
+
74
+ - **Never** commit with `--no-verify`. The hook is the gate; if it blocks you, fix the code.
75
+ - **Never** silence a violation to make a gate pass — no widening `phpcs.xml.dist` excludes,
76
+ no new `phpcs:ignore` / `phpcs:disable` / `eslint-disable` / `@phpstan-ignore` without a
77
+ stated reason the user accepted, no editing a PHPStan baseline to hide a new error.
78
+ - **Never** reformat files the change didn't touch. A formatting-only diff across the repo
79
+ buries the actual change and blows up review.
80
+ - **Never** run `composer update` or `npm update` to fix a lint failure — that's a
81
+ dependency change ([`dependency-updates`](../dependency-updates/SKILL.md)), not a fix.
82
+ - **Never** commit `vendor/`, `node_modules/`, or build output unless the repo already
83
+ tracks it — check `.gitignore` and `.distignore` first.
84
+ - If a tool can't run (not installed, no config, requires Docker that isn't up), **say so
85
+ explicitly**. A silently skipped gate reads as a passing gate.
86
+
87
+ ## Done
88
+
89
+ - [ ] Every changed file is covered by a gate that ran, or is explicitly reported as uncovered.
90
+ - [ ] PHP gate passed (or is correctly not applicable — no `phpcs.xml.dist`, no PHP changed).
91
+ - [ ] JS/CSS gate passed in the owning workspace (or correctly not applicable).
92
+ - [ ] Tests run for touched, covered code.
93
+ - [ ] No suppressions, config widenings, or `--no-verify` were used to get green.
94
+ - [ ] Skipped gates and missing house scripts are named in the report.
@@ -0,0 +1,104 @@
1
+ # Toolchain reference
2
+
3
+ The commands Linchpin projects actually expose, and what to do when they're missing.
4
+ **Run what the repo declares.** This file is a lookup table, not a script to run top to
5
+ bottom.
6
+
7
+ ## PHP
8
+
9
+ | Intent | Command | Requires |
10
+ | --- | --- | --- |
11
+ | Everything | `composer run lint` | `scripts.lint` in `composer.json` |
12
+ | Syntax only | `composer run php-lint` | `php-parallel-lint` |
13
+ | Coding standards | `composer run phpcs` (or `phpcs --standard=phpcs.xml.dist`) | `phpcs.xml.dist` |
14
+ | Auto-fix standards | `composer run phpcbf` | same |
15
+ | Formatting check | `composer run fixer:test` | `.php-cs-fixer.dist.php` |
16
+ | Auto-fix formatting | `composer run fixer` | same |
17
+ | Static analysis | `composer run phpstan` | `phpstan.neon(.dist)` |
18
+ | Unit tests | `composer run phpunit` | `phpunit.xml(.dist)` |
19
+ | Lint + tests | `composer run test` | both |
20
+
21
+ The house `scripts.lint` composes `php-lint` → `phpcs` → `fixer:test` → `phpstan`, in that
22
+ order — cheapest failure first. Reproduce that order when running tools individually.
23
+
24
+ **PHPCS with no `phpcs.xml.dist`:** skip it and report it. Do not fall back to a global
25
+ standard — the result won't match CI, and a WordPress-standard run against a non-WordPress
26
+ codebase produces hundreds of meaningless violations.
27
+
28
+ **PHPStan baselines:** `phpstan-baseline.neon` records accepted debt. New errors go in the
29
+ code's fix, never in the baseline, unless the user explicitly asks to re-baseline.
30
+
31
+ ## JavaScript / CSS
32
+
33
+ | Intent | Command | Notes |
34
+ | --- | --- | --- |
35
+ | Pre-commit equivalent | `npm run lint:check` | Where defined, this is `format` + `lint:js` |
36
+ | JS/TS | `npm run lint:js` | `wp-scripts lint-js` on WordPress projects |
37
+ | CSS/SCSS | `npm run lint:css` | `wp-scripts lint-style` |
38
+ | Format | `npm run format` | Prettier via `wp-scripts format` |
39
+ | Unit tests | `npm run test:unit` | |
40
+ | E2E | `npm run test:e2e` | Playwright; needs a running environment |
41
+ | Build | `npm run build` | Verify the change compiles before pushing |
42
+
43
+ **Nested workspaces.** Site repos build per package — e.g. a theme and a functionality
44
+ plugin each with their own `package.json`, sometimes an inner `src/`. Root scripts often
45
+ delegate (`npm run build --prefix themes/<theme>`). Lint a file in the workspace that owns
46
+ it; running the root script may silently skip it.
47
+
48
+ **Non-WordPress projects** (Workers, Astro, API services) use `eslint.config.mjs` +
49
+ `.prettierrc` directly. Same rule: run the declared `package.json` script.
50
+
51
+ ## Pre-commit hooks
52
+
53
+ `husky` + `lint-staged` are the house pre-commit setup (`"prepare": "husky"`).
54
+
55
+ - `lint-staged.config.js` is the source of truth for what runs on commit. When it exists,
56
+ **run the same commands it would run** — that's exactly what the hook will do.
57
+ - Config files are often excluded from linting there. Don't "fix" a config file the
58
+ lint-staged rules deliberately skip.
59
+ - A blocked commit means the gate found something real. Fix the code; never `--no-verify`.
60
+
61
+ ## The house script set
62
+
63
+ When a repo is missing these, propose adding them — matching the repo's existing tooling,
64
+ and only with the user's approval:
65
+
66
+ ```jsonc
67
+ // package.json
68
+ "scripts": {
69
+ "format": "wp-scripts format",
70
+ "lint:js": "wp-scripts lint-js",
71
+ "lint:css": "wp-scripts lint-style",
72
+ "lint:check": "npm run format && npm run lint:js",
73
+ "prepare": "husky"
74
+ }
75
+ ```
76
+
77
+ ```jsonc
78
+ // composer.json
79
+ "scripts": {
80
+ "lint": ["@php-lint", "@phpcs", "@fixer:test", "@phpstan"],
81
+ "phpcs": "phpcs --standard=phpcs.xml.dist",
82
+ "phpcbf": "phpcbf --standard=phpcs.xml.dist",
83
+ "phpstan": "phpstan analyse"
84
+ }
85
+ ```
86
+
87
+ Adding a script that references a tool the repo doesn't require is worse than having no
88
+ script — it fails for everyone. Check `require-dev` first, and add the dependency in the
89
+ same change or not at all.
90
+
91
+ ## Local environments
92
+
93
+ **WordPress Studio is the default** for new Linchpin work. Repos still carrying wp-env or
94
+ LocalWP config predate that switch — those files are not evidence that the project is being
95
+ run that way today. Detect, and when both exist, ask which the user is on:
96
+
97
+ | Signal | Environment | Start |
98
+ | --- | --- | --- |
99
+ | Site registered in Studio | **WordPress Studio** (default) | [`wp-studio-cli`](../../wp-studio-cli/SKILL.md) — MCP `site_start`, else `studio site start` |
100
+ | `.wp-env.json` | wp-env (Docker) — legacy | `npx @wordpress/env start` |
101
+ | `.linchpin.json` → `wordpress.environments` | LocalWP or a declared path — legacy | Already running; use the path |
102
+
103
+ Unit tests and lint don't need any of this. Only reach for an environment when running E2E
104
+ or a check that boots WordPress.
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: safety-hooks
3
+ description: Turn on enforced guardrails for risky work — a PreToolUse hook that makes destructive commands (wp db drop, search-replace without --dry-run, DROP TABLE, git push --force, rm -rf) require confirmation, and an optional edit boundary that blocks writes outside one directory. Use when touching production or a client database, debugging a live site, working in an unfamiliar repo, or when asked for careful mode, safety mode, or to lock down edits. Claude Code only.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Safety hooks
8
+
9
+ Every other skill in this library *asks* an agent not to do something dangerous. This one
10
+ **enforces it**, by registering a `PreToolUse` hook that inspects each command before it runs
11
+ and makes the destructive ones require confirmation.
12
+
13
+ That matters most where our work is riskiest: client databases, production WordPress, and
14
+ `wp search-replace` — a single command that rewrites a live database in place.
15
+
16
+ ## When to use
17
+
18
+ - About to touch production or staging, or any client database.
19
+ - Debugging a live site, or working in a repo you don't know well.
20
+ - Handing a session to someone less familiar with the project.
21
+ - Asked for "careful mode", "safety mode", or "only edit this folder".
22
+
23
+ **Not this skill:** the prose rules about what's safe on a server —
24
+ [`wp-pressable`](../wp-pressable/SKILL.md). Pre-commit checks —
25
+ [`quality-gates`](../quality-gates/SKILL.md).
26
+
27
+ ## Owns
28
+
29
+ Canonical for: the enforced guardrail mechanism, the destructive-command list, and the edit
30
+ boundary. Other skills state judgment rules; this one blocks actions.
31
+
32
+ ## Portability exception
33
+
34
+ This is the **one deliberately Claude-Code-only skill** in the library. Hooks are a Claude
35
+ Code feature; Copilot, Codex, and Cursor ignore the `hooks` frontmatter and the skill
36
+ degrades to documentation. That trade is worth making because the protection is real, and
37
+ because nothing else here depends on it — see
38
+ [`write-a-linchpin-skill`](../write-a-linchpin-skill/SKILL.md).
39
+
40
+ ## Turning it on
41
+
42
+ **For the session** — invoking this skill registers the hooks below; they last until the
43
+ conversation ends.
44
+
45
+ **Permanently** — copy the same entries into `~/.claude/settings.json` (global) or the
46
+ project's `.claude/settings.json`:
47
+
48
+ ```jsonc
49
+ {
50
+ "hooks": {
51
+ "PreToolUse": [
52
+ {
53
+ "matcher": "Bash",
54
+ "hooks": [{
55
+ "type": "command",
56
+ "command": "bash -c 'f=.claude/skills/safety-hooks/scripts/check-destructive.sh; [ -f \"$f\" ] || f=$HOME/.claude/skills/safety-hooks/scripts/check-destructive.sh; bash \"$f\"'"
57
+ }]
58
+ },
59
+ {
60
+ "matcher": "Edit|Write",
61
+ "hooks": [{
62
+ "type": "command",
63
+ "command": "bash -c 'f=.claude/skills/safety-hooks/scripts/check-edit-boundary.sh; [ -f \"$f\" ] || f=$HOME/.claude/skills/safety-hooks/scripts/check-edit-boundary.sh; bash \"$f\"'"
64
+ }]
65
+ }
66
+ ]
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Setting an edit boundary
72
+
73
+ The boundary is opt-in and off by default. To scope edits to one directory — useful while
74
+ debugging, so unrelated code can't be "helpfully" fixed:
75
+
76
+ ```bash
77
+ mkdir -p "${LINCHPIN_SAFETY_DIR:-$HOME/.claude/.linchpin-safety}"
78
+ cd <directory-to-restrict-to> && pwd > "${LINCHPIN_SAFETY_DIR:-$HOME/.claude/.linchpin-safety}/edit-boundary.txt"
79
+ ```
80
+
81
+ To clear it:
82
+
83
+ ```bash
84
+ rm -f "${LINCHPIN_SAFETY_DIR:-$HOME/.claude/.linchpin-safety}/edit-boundary.txt"
85
+ ```
86
+
87
+ Ask the user which directory rather than guessing, and confirm the resolved absolute path
88
+ back to them.
89
+
90
+ ## What triggers a confirmation
91
+
92
+ | Area | Patterns |
93
+ | --- | --- |
94
+ | WordPress | `wp db drop`/`reset`/`import`, `wp search-replace` without `--dry-run`, `wp site empty`, `wp post/user/term/comment delete`, `wp plugin uninstall`, changing `home`/`siteurl` |
95
+ | Databases | `DROP TABLE`/`DATABASE`, `TRUNCATE`, `DELETE FROM` with no `WHERE` |
96
+ | Git | `push --force`, `reset --hard`, `checkout .`/`restore .`, `clean -fd`, `branch -D` |
97
+ | Filesystem & infra | `rm -rf`, `kubectl delete`, `docker rm -f`, `docker system prune`, `ssh` running `wp`/`mysql` remotely |
98
+
99
+ Allowed without prompting: removing `node_modules`, `vendor`, `dist`, `build`, `.next`,
100
+ `.turbo`, `.cache`, `coverage`, `__pycache__`, and any `search-replace` carrying `--dry-run`.
101
+
102
+ ## Guardrails
103
+
104
+ - **This is a speed bump, not a sandbox.** It fails *open* — if `python3` is missing or
105
+ parsing fails, commands proceed. Bash can still write anywhere via `sed` or redirects.
106
+ Never describe it to a client or teammate as a security control.
107
+ - **Never disable the hook to get a command through.** Confirm the command deliberately, or
108
+ change the command.
109
+ - **Never widen the safe-exception list to silence a prompt** you found annoying — that list
110
+ is why the prompts stay meaningful.
111
+ - **Never set an edit boundary without telling the user** what it is; a blocked edit is
112
+ confusing when the reason is invisible.
113
+ - A confirmation prompt is a moment to re-read the command, especially the environment it
114
+ points at. Approving reflexively is the failure this exists to prevent.
115
+
116
+ ## Done
117
+
118
+ - [ ] Hooks registered — session-scoped, or written into settings for permanence.
119
+ - [ ] The user knows which protections are active and how to clear them.
120
+ - [ ] Edit boundary, if set, was chosen by the user and echoed back as an absolute path.
121
+ - [ ] No hook was bypassed or weakened to let a specific command through.
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse hook for Bash: warn before destructive commands.
3
+ #
4
+ # Reads the tool-call JSON on stdin and, when the command matches a destructive
5
+ # pattern, returns permissionDecision "ask" so the user confirms before it runs.
6
+ # Anything else produces no output, which lets the command proceed normally.
7
+ #
8
+ # Fails OPEN by design: this is a safety net, not a security boundary. If parsing
9
+ # breaks or python3 is missing, work continues rather than the session wedging.
10
+ # Bash can always modify files another way (sed, redirects), so treat this as a
11
+ # speed bump on autopilot, not a sandbox.
12
+
13
+ set -uo pipefail
14
+
15
+ INPUT=$(cat 2>/dev/null || true)
16
+ [ -z "$INPUT" ] && exit 0
17
+ command -v python3 >/dev/null 2>&1 || exit 0
18
+
19
+ CMD=$(printf '%s' "$INPUT" | python3 -c '
20
+ import json, sys
21
+ try:
22
+ data = json.load(sys.stdin)
23
+ except Exception:
24
+ sys.exit(0)
25
+ print((data.get("tool_input") or {}).get("command", ""))
26
+ ' 2>/dev/null || true)
27
+
28
+ [ -z "$CMD" ] && exit 0
29
+
30
+ # Build artifacts and dependency directories are safe to blow away.
31
+ if printf '%s' "$CMD" | grep -qE 'rm +(-[a-zA-Z]+ +)*(node_modules|vendor|dist|build|build-tools|\.next|\.turbo|\.cache|coverage|__pycache__)/?( |$)'; then
32
+ exit 0
33
+ fi
34
+ # An explicit dry run is the safe form of search-replace.
35
+ if printf '%s' "$CMD" | grep -q 'search-replace' && printf '%s' "$CMD" | grep -q -- '--dry-run'; then
36
+ exit 0
37
+ fi
38
+
39
+ REASON=""
40
+ match() { printf '%s' "$CMD" | grep -qiE "$1"; }
41
+
42
+ # --- WordPress / WP-CLI ------------------------------------------------------
43
+ if match 'wp +db +(drop|reset)'; then REASON="\`wp db drop/reset\` destroys the entire database."
44
+ elif match 'wp +db +import'; then REASON="\`wp db import\` overwrites the current database."
45
+ elif match 'wp +search-replace'; then REASON="\`wp search-replace\` rewrites the database in place. Run it with --dry-run first."
46
+ elif match 'wp +site +empty'; then REASON="\`wp site empty\` deletes all site content."
47
+ elif match 'wp +(post|user|term|comment) +delete'; then REASON="This deletes content permanently, especially with --force."
48
+ elif match 'wp +plugin +uninstall'; then REASON="\`wp plugin uninstall\` removes the plugin and its data."
49
+ elif match 'wp +option +(delete|update) +(home|siteurl)'; then REASON="Changing home/siteurl can take the site offline."
50
+ # --- Databases ---------------------------------------------------------------
51
+ elif match 'drop +(table|database)'; then REASON="DROP removes the table or database and its data."
52
+ elif match 'truncate +(table +)?[a-z_]'; then REASON="TRUNCATE empties the table irreversibly."
53
+ elif match 'delete +from +[a-z_]+ *(;|$)'; then REASON="DELETE FROM with no WHERE clause removes every row."
54
+ # --- Git ---------------------------------------------------------------------
55
+ elif match 'git +push +.*(--force|-f)( |$)'; then REASON="Force-push rewrites remote history and can destroy others' commits. Prefer --force-with-lease."
56
+ elif match 'git +reset +--hard'; then REASON="\`git reset --hard\` discards uncommitted work permanently."
57
+ elif match 'git +(checkout|restore) +\.( |$)';then REASON="This discards all uncommitted changes in the working tree."
58
+ elif match 'git +clean +-[a-z]*[fd]'; then REASON="\`git clean\` deletes untracked files, including ones never committed."
59
+ elif match 'git +branch +-D'; then REASON="Force-deleting a branch can lose unmerged commits."
60
+ # --- Filesystem / infra ------------------------------------------------------
61
+ elif match 'rm +(-[a-zA-Z]+ +)*-?[a-zA-Z]*r[a-zA-Z]*f|rm +-rf|rm +-fr'; then REASON="Recursive force delete."
62
+ elif match 'kubectl +delete'; then REASON="This removes live Kubernetes resources."
63
+ elif match 'docker +(rm +-f|system +prune)'; then REASON="This removes containers, images, or volumes."
64
+ elif match 'ssh .*(wp |mysql )'; then REASON="This runs a data-affecting command on a remote server."
65
+ fi
66
+
67
+ [ -z "$REASON" ] && exit 0
68
+
69
+ python3 -c '
70
+ import json, sys
71
+ reason = sys.argv[1]
72
+ print(json.dumps({
73
+ "hookSpecificOutput": {
74
+ "hookEventName": "PreToolUse",
75
+ "permissionDecision": "ask",
76
+ "permissionDecisionReason": "Destructive command: " + reason + " Confirm you intend to run this, and that it is pointed at the environment you think it is."
77
+ }
78
+ }))
79
+ ' "$REASON"
80
+ exit 0
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse hook for Edit/Write: block edits outside an allowed directory.
3
+ #
4
+ # Reads the tool-call JSON on stdin and denies the operation when the target file
5
+ # is outside the boundary recorded in the state file. With no state file, every
6
+ # edit is allowed — the boundary is opt-in per session.
7
+ #
8
+ # State file: $LINCHPIN_SAFETY_DIR/edit-boundary.txt (default ~/.claude/.linchpin-safety/)
9
+ #
10
+ # Fails OPEN: if parsing breaks, the edit proceeds. This prevents accidental edits
11
+ # to unrelated code; it is not a security boundary, since Bash can still write
12
+ # anywhere.
13
+
14
+ set -uo pipefail
15
+
16
+ STATE_DIR="${LINCHPIN_SAFETY_DIR:-$HOME/.claude/.linchpin-safety}"
17
+ BOUNDARY_FILE="$STATE_DIR/edit-boundary.txt"
18
+
19
+ [ -f "$BOUNDARY_FILE" ] || exit 0
20
+ BOUNDARY=$(cat "$BOUNDARY_FILE" 2>/dev/null || true)
21
+ [ -z "$BOUNDARY" ] && exit 0
22
+ # Trailing slash keeps /src from matching /src-old.
23
+ BOUNDARY="${BOUNDARY%/}/"
24
+
25
+ INPUT=$(cat 2>/dev/null || true)
26
+ [ -z "$INPUT" ] && exit 0
27
+ command -v python3 >/dev/null 2>&1 || exit 0
28
+
29
+ FILE_PATH=$(printf '%s' "$INPUT" | python3 -c '
30
+ import json, sys
31
+ try:
32
+ data = json.load(sys.stdin)
33
+ except Exception:
34
+ sys.exit(0)
35
+ print((data.get("tool_input") or {}).get("file_path", ""))
36
+ ' 2>/dev/null || true)
37
+
38
+ [ -z "$FILE_PATH" ] && exit 0
39
+
40
+ # Resolve relative paths against the working directory so the comparison is fair.
41
+ case "$FILE_PATH" in
42
+ /*) ABS="$FILE_PATH" ;;
43
+ *) ABS="$PWD/$FILE_PATH" ;;
44
+ esac
45
+
46
+ case "$ABS" in
47
+ "$BOUNDARY"*) exit 0 ;;
48
+ esac
49
+
50
+ python3 -c '
51
+ import json, sys
52
+ path, boundary = sys.argv[1], sys.argv[2]
53
+ print(json.dumps({
54
+ "hookSpecificOutput": {
55
+ "hookEventName": "PreToolUse",
56
+ "permissionDecision": "deny",
57
+ "permissionDecisionReason": (
58
+ "Edit boundary active: edits are restricted to " + boundary +
59
+ " but this targets " + path +
60
+ ". Run /safety-hooks to change or clear the boundary if this edit is intended."
61
+ )
62
+ }
63
+ }))
64
+ ' "$ABS" "$BOUNDARY"
65
+ exit 0
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: support-triage
3
+ description: Run a client support request end to end — clarify what was actually asked, reproduce it, judge urgency and whether it fits the support agreement, fix and verify it, then close the loop with the requester in their language. Use when handling a support ticket, when a client reports something broken, when triaging a support queue, or when a request might be out of scope. Baseline workflow, refined with the team over time.
4
+ version: 0.1.0
5
+ ---
6
+
7
+ # Support triage
8
+
9
+ Support work is judged on two things clients actually notice: **was it understood, and were
10
+ they told what happened.** The technical fix is usually the easy part.
11
+
12
+ The two failure modes to design against: fixing the wrong thing because the report was taken
13
+ literally, and doing three hours of unscoped work because nobody stopped to ask whether it
14
+ was covered.
15
+
16
+ ## When to use
17
+
18
+ - A client support request needs handling.
19
+ - Something broke on a live client site.
20
+ - Triaging a queue of open support requests.
21
+ - A request looks like it might exceed the support agreement.
22
+
23
+ **Not this skill:** deciding what kind of engagement the work belongs to —
24
+ [`engagement-types`](../engagement-types/SKILL.md). Root-causing a stubborn bug —
25
+ [`investigate`](../investigate/SKILL.md). Server operations —
26
+ [`wp-pressable`](../wp-pressable/SKILL.md).
27
+
28
+ ## Owns
29
+
30
+ Canonical for: support intake, urgency assessment, the in-scope decision, and closing the
31
+ loop with the requester.
32
+
33
+ ## Procedure
34
+
35
+ 1. **Read the request for the underlying need.** Clients describe symptoms and often propose
36
+ solutions ("can you make the button bigger" may mean "nobody is finding the form"). Restate
37
+ what you think they want and what outcome would count as fixed.
38
+ → You can state the actual problem, not just the requested change.
39
+ 2. **Identify the site and environment.** On multi-site accounts this is required, never
40
+ inferred ([`engagement-types`](../engagement-types/SKILL.md)). Confirm production URL
41
+ versus staging. → The exact target is named in the task.
42
+ 3. **Reproduce it** before theorizing — same page, same role, same browser where it matters.
43
+ If it doesn't reproduce, that's information: ask for a screenshot, the URL, the browser,
44
+ or the account used. → Confirmed reproduction, or a specific question back to the client.
45
+ 4. **Assess urgency honestly**, from user impact rather than from the tone of the request:
46
+
47
+ | Level | Means | Response |
48
+ | --- | --- | --- |
49
+ | **Site down / data at risk** | Nobody can use it, or something is leaking | Drop other work, tell the client you're on it now |
50
+ | **Blocking** | A core task is impossible, no workaround | Same or next business day |
51
+ | **Degraded** | Works with a workaround, or affects some users | Scheduled into the queue |
52
+ | **Request** | Change, addition, or question | Queued and estimated |
53
+
54
+ → A stated level the client would recognize as fair.
55
+ 5. **Decide in-scope or not — before doing the work.** If it fits the agreement, proceed. If
56
+ it's really a small project, say so *now*, with a rough estimate, and let the client decide.
57
+ → Either work has started, or a scoping question is with the client.
58
+ 6. **Fix it properly.** Code changes go through the repo and the deploy pipeline; content and
59
+ template overrides are server-side database work
60
+ ([`wp-pressable`](../wp-pressable/SKILL.md)). Never patch files directly on a server —
61
+ the next deploy erases it and the bug returns looking like a regression.
62
+ → Fix is in the right layer and will survive a deploy.
63
+ 7. **Verify on the environment the client saw it on**, flushing caches first. Screenshot the
64
+ result. → Evidence the specific reported case now works.
65
+ 8. **Close the loop in the client's language.** What was wrong, what you did, anything they
66
+ should know or do. No jargon, no commit hashes. Then update the task
67
+ ([`task-tracking`](../task-tracking/SKILL.md)) with the technical detail for the team.
68
+ → The client knows it's resolved; the task carries the internal record.
69
+
70
+ ## Recurring symptoms
71
+
72
+ | Report | Check first |
73
+ | --- | --- |
74
+ | "The site is down" | Is it actually down, or slow, or DNS/SSL? Confirm before escalating |
75
+ | "My change didn't save" or "isn't showing" | Caching first — object, page, browser — then the edit itself |
76
+ | "It broke and we didn't change anything" | Recent deploy, plugin auto-update, or an expiring credential or certificate |
77
+ | "It looks wrong" | Get a screenshot and their browser; often browser-specific or a stale cache |
78
+ | "Emails aren't arriving" | Deliverability and the sending service before the form code |
79
+ | "Can you just quickly…" | Scope check — quick to describe is not quick to build |
80
+
81
+ ## Guardrails
82
+
83
+ - **Never work outside the agreement without approval.** Doing unbilled work "to be nice"
84
+ sets an expectation the whole team inherits.
85
+ - **Never make an undiscussed change while you're in there.** Unrequested improvements on a
86
+ client site are unreviewed changes.
87
+ - **Never edit files directly on the server** — it's overwritten by the next deploy.
88
+ - **Never change production data without a backup** and explicit confirmation
89
+ ([`safety-hooks`](../safety-hooks/SKILL.md) will prompt on the dangerous commands).
90
+ - **Never guess at what a client meant** on anything ambiguous and destructive — ask.
91
+ - **Never report it fixed without verifying on their environment**, cache cleared.
92
+ - **Never leave a request silent.** Even "I'm looking at it, here's what I know" is a
93
+ response; silence is the complaint clients actually make.
94
+
95
+ ## Done
96
+
97
+ - [ ] Underlying need restated and confirmed, not just the literal request.
98
+ - [ ] Site and environment identified explicitly.
99
+ - [ ] Reproduced, or a specific clarifying question sent.
100
+ - [ ] Urgency assessed on impact, and scope decided before the work began.
101
+ - [ ] Fix in the correct layer, surviving the next deploy.
102
+ - [ ] Verified on the client's environment with caches cleared, with evidence.
103
+ - [ ] Client told what happened in plain language; task updated with the technical record.