@claude-code-mastery/starter-kit 1.2.1 → 1.3.1
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.
- package/.claude/skills/accessibility/SKILL.md +66 -0
- package/.claude/skills/api-conventions/SKILL.md +1 -1
- package/.claude/skills/code-review/references/project-checks.md +1 -1
- package/.claude/skills/create-service/SKILL.md +3 -3
- package/.claude/skills/css-structure/SKILL.md +56 -0
- package/.claude/skills/dev-pitfalls/SKILL.md +118 -0
- package/.claude/skills/docker/SKILL.md +72 -0
- package/.claude/skills/docker-swarm/SKILL.md +104 -0
- package/.claude/skills/mdd-workflow/SKILL.md +82 -0
- package/.claude/skills/mongodb-backups/SKILL.md +69 -0
- package/.claude/skills/mongodb-replica-sets/SKILL.md +73 -0
- package/.claude/skills/nginx/SKILL.md +148 -0
- package/.claude/skills/nodejs/SKILL.md +121 -0
- package/.claude/skills/responsive-css/SKILL.md +128 -0
- package/.claude/skills/test-writer/SKILL.md +1 -1
- package/.claude/skills/waf/SKILL.md +63 -0
- package/.claude/skills/web-architecture/SKILL.md +38 -0
- package/.claude/skills/web-performance/SKILL.md +103 -0
- package/README.md +21 -7
- package/README.npm.md +21 -7
- package/bin/cli.js +16 -0
- package/package.json +1 -1
- /package/.claude/skills/{terminal-tui → tui-builder}/SKILL.md +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: accessibility
|
|
3
|
+
description: Accessibility and screen-reader support, built in from the start rather than bolted on. Claude treats a11y as an afterthought and reaches for clickable divs, skips alt text and labels, and removes focus rings. Use when building or editing any UI, page, or interactive component. Covers semantic HTML before ARIA, real buttons and links, accessible names, keyboard operability and focus management, headings and landmarks, contrast and motion, and how to actually test it. Most of it is free if you use the right element.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
- Building or editing any UI, page, form, or interactive component, treat a11y as a default, not a later pass
|
|
6
|
+
- Anything with buttons, links, forms, modals, menus, tabs, or icons
|
|
7
|
+
- About to use a clickable `<div>`, skip `alt` or a label, remove a focus outline, or disable zoom
|
|
8
|
+
- Dynamic content that updates without a full page load (toasts, async results, SPA route changes)
|
|
9
|
+
- Do NOT skip this for "internal" tools, accessibility is not optional there either
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Accessibility: Build It In, Don't Bolt It On
|
|
13
|
+
|
|
14
|
+
Accessibility is a current best practice, not a nice-to-have for later, and most of it costs nothing if you use the right elements from the start. Claude's defaults (clickable divs, missing labels, removed focus rings) are exactly what breaks screen readers and keyboards, and they're expensive to retrofit. Treat it as part of the build, like you would performance.
|
|
15
|
+
|
|
16
|
+
## Semantic HTML first, ARIA last
|
|
17
|
+
|
|
18
|
+
The native element gives you keyboard support, focus, the correct role, and screen-reader semantics for free. The first rule of ARIA is don't use ARIA when a native element already does the job, and bad ARIA is worse than none. Reach for the real element before reaching for a role.
|
|
19
|
+
|
|
20
|
+
## Use the real element, not a clickable div
|
|
21
|
+
|
|
22
|
+
`<button>` for actions (submit, toggle, open a menu), `<a href>` for navigation (it goes somewhere). A `<div onclick>` is none of these: it isn't focusable, doesn't fire on Enter or Space, and is announced as nothing. Recreating a button with `role`, `tabindex`, and keydown handlers is work you will get subtly wrong, the native `<button>` is already all of that. Same for `<nav>`, `<ul>`, `<table>`, `<label>`, `<input>`, use them.
|
|
23
|
+
|
|
24
|
+
## Every interactive thing needs an accessible name
|
|
25
|
+
|
|
26
|
+
- **Images:** a descriptive `alt`, or `alt=""` for purely decorative ones so screen readers skip them. Never omit the attribute, that makes the reader announce the filename.
|
|
27
|
+
- **Form fields:** a real `<label>` tied to the input (`for`/`id` or wrapping). A `placeholder` is not a label, it vanishes on input, has poor contrast, and isn't reliably announced.
|
|
28
|
+
- **Icon-only buttons** (hamburger, close X, search): an `aria-label`. This is the single most common missing name, an icon button with no text is silent to a screen reader.
|
|
29
|
+
|
|
30
|
+
## Keyboard and focus
|
|
31
|
+
|
|
32
|
+
Everything interactive must be reachable and operable by keyboard in a sensible order. Two things Claude breaks constantly:
|
|
33
|
+
|
|
34
|
+
- **Never remove the focus ring** (`outline: none`) without replacing it, a keyboard user then can't see where they are. Use `:focus-visible` to show a clear ring for keyboard users without it appearing on mouse clicks.
|
|
35
|
+
- **Manage focus on modals and route changes.** Prefer the native `<dialog>` with `showModal()`, it traps focus, closes on Escape, and restores focus to the trigger. If you build your own, replicate all of that. In a SPA, move focus to the new view's heading on navigation, or a screen-reader user never learns the page changed.
|
|
36
|
+
|
|
37
|
+
## Structure: headings, landmarks, and sectioning
|
|
38
|
+
|
|
39
|
+
Headings are the outline that screen readers and search engines navigate by, so get them right:
|
|
40
|
+
|
|
41
|
+
- **One `<h1>` per page**, the page's title. Then `<h2>`/`<h3>` in order, don't skip levels for visual size (style with CSS, keep the order logical).
|
|
42
|
+
- **Use real heading elements, not a styled `<span>` or `<div>`.** Something that just looks like a heading carries no meaning, assistive tech and crawlers don't see a heading at all. And don't wrap the heading text in a `<span>` to style it, style the heading element (or a class on it) directly, the heading's content should be the heading text.
|
|
43
|
+
- **Keep a heading next to the content it labels.** A heading describes the block that immediately follows it, so the heading and its paragraph belong together, heading first, grouped. A heading stranded away from its content, or with unrelated markup wedged between, loses the relationship that gives it meaning to both a reader and a crawler.
|
|
44
|
+
|
|
45
|
+
Then use the document-sectioning elements for what they mean, not as styled `<div>`s, so assistive tech can understand and navigate the page:
|
|
46
|
+
|
|
47
|
+
- **Landmarks** wrap the page's regions: `<header>` (banner), `<nav>`, `<main>` (one per page), `<aside>` (complementary), `<footer>` (contentinfo). Add a skip-to-content link to `<main>` so keyboard and screen-reader users can jump straight in instead of tabbing through the nav every time.
|
|
48
|
+
- **`<article>`** for a self-contained piece that stands on its own or repeats, a post, comment, card, product. It tells assistive tech "this is one discrete item," which is what makes a feed or list navigable.
|
|
49
|
+
- **`<section>`** for a thematic group, but only when it has a heading. A bare `<section>` is not a landmark and adds nothing, it becomes a navigable region only when you give it an accessible name (`aria-labelledby` pointing at its heading). If it's just a styling wrapper with no heading, use a `<div>`.
|
|
50
|
+
|
|
51
|
+
The rule both ways: reach for the element that describes the content, but overusing `<section>` and `<article>` as generic wrappers is as unhelpful as never using them.
|
|
52
|
+
|
|
53
|
+
## Don't lock people out: contrast, color, zoom, motion
|
|
54
|
+
|
|
55
|
+
- Text contrast at least 4.5:1 (3:1 for large text), and never convey meaning by color alone, pair it with text, an icon, or a pattern.
|
|
56
|
+
- Never disable zoom (`user-scalable=no` / `maximum-scale=1`), see responsive-css.
|
|
57
|
+
- Respect `prefers-reduced-motion` for animations and parallax.
|
|
58
|
+
- Announce dynamic changes (toasts, async results, validation) with an `aria-live` region, or they happen silently for a screen reader.
|
|
59
|
+
|
|
60
|
+
## Test it, don't assume it
|
|
61
|
+
|
|
62
|
+
Do a keyboard-only pass (Tab through everything, operate it with Enter/Space/Escape, watch the focus ring) and run an actual screen reader on the key flows. Automated tools (axe, Lighthouse) are worth running but catch only a fraction of issues, the keyboard and screen-reader pass is what finds the rest.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
This skill is built to grow. Add a rule when a real accessibility failure has a stable, defensible fix.
|
|
@@ -31,4 +31,4 @@ Every service is three layers, one direction: `server.ts` → `handlers/` → `a
|
|
|
31
31
|
- A service owns its domain and is reached through its interface. A package does not reach into another package's internals or its data. Call the owning service.
|
|
32
32
|
- Code two services both need is hoisted to a shared layer, never imported sideways from a sibling.
|
|
33
33
|
|
|
34
|
-
The test: routes in `server.ts` read request-in, handler-call, response-out. Logic sits in `handlers/`. Anything that leaves the process goes through `adapters/`, and the data adapter
|
|
34
|
+
The test: routes in `server.ts` read request-in, handler-call, response-out. Logic sits in `handlers/`. Anything that leaves the process goes through `adapters/`, and the data adapter uses StrictDB if installed, the native driver otherwise.
|
|
@@ -13,7 +13,7 @@ Apply these to every diff. They are this codebase's hard rules. A violation is C
|
|
|
13
13
|
## Data access (lives in the adapter layer)
|
|
14
14
|
|
|
15
15
|
- [ ] **No Mongoose.** Flag any Mongoose import, model, or schema.
|
|
16
|
-
- [ ] **Data access goes through the adapter.** Flag raw collection access outside `adapters/`. Inside the adapter, StrictDB
|
|
16
|
+
- [ ] **Data access goes through the adapter.** Flag raw collection access outside `adapters/`. Inside the adapter, StrictDB is preferred when installed and the native driver is fine when it isn't, the driver choice is not a violation; Mongoose and raw access in feature code are.
|
|
17
17
|
- [ ] **One shared `MongoClient`.** Flag `new MongoClient` inside a request handler or per-call path; the client is created once and reused. In serverless, it must be at module scope, not inside the handler.
|
|
18
18
|
- [ ] **No `_id` in a write body.** Flag any update or upsert payload containing `_id`; it belongs in the filter.
|
|
19
19
|
- [ ] **Type-safe `_id`.** Flag a query comparing a string against an `ObjectId` `_id`; it silently returns nothing. For `$in`, every element must be converted.
|
|
@@ -24,7 +24,7 @@ server.ts routes only, NEVER business logic
|
|
|
24
24
|
handlers/ business logic, one file per domain
|
|
25
25
|
│
|
|
26
26
|
▼
|
|
27
|
-
adapters/ external wrappers (database via StrictDB, APIs, queues)
|
|
27
|
+
adapters/ external wrappers (database via StrictDB or native driver, APIs, queues)
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
This matches the `api-conventions` skill. Keep them in sync: if the layering changes, change both.
|
|
@@ -39,7 +39,7 @@ packages/{name}/
|
|
|
39
39
|
│ │ └── index.ts
|
|
40
40
|
│ ├── adapters/ # external wrappers
|
|
41
41
|
│ │ ├── index.ts
|
|
42
|
-
│ │ └── db.ts #
|
|
42
|
+
│ │ └── db.ts # data adapter — StrictDB or native driver, the only place the driver lives
|
|
43
43
|
│ └── types.ts # TypeScript types
|
|
44
44
|
├── tests/
|
|
45
45
|
│ └── handlers.test.ts
|
|
@@ -121,7 +121,7 @@ app.listen(PORT, () => {
|
|
|
121
121
|
The data adapter is the only place the driver is touched. Use StrictDB if it's installed, otherwise the native MongoDB driver. Never Mongoose. Handlers import this, never the driver.
|
|
122
122
|
|
|
123
123
|
```typescript
|
|
124
|
-
// Wire to
|
|
124
|
+
// Wire to StrictDB if installed, otherwise the native MongoDB driver. The data boundary for the service.
|
|
125
125
|
// Rules enforced here (see the mongodb-rules skill):
|
|
126
126
|
// - StrictDB if installed, else the native driver; never Mongoose
|
|
127
127
|
// - reads are aggregation pipelines, not find()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: css-structure
|
|
3
|
+
description: Where CSS should live. Claude defaults to dumping a giant <style> block or scattering style="..." attributes instead of putting styles in a .css file and linking it. Use when building or editing a web page or component, or whenever about to write a style attribute or an embedded style block. Covers defaulting to an external stylesheet, why inline style attributes are a trap, and the few cases where inline is actually correct (dynamic values via custom properties, critical CSS, single-file artifacts, email).
|
|
4
|
+
when_to_use: |
|
|
5
|
+
- Building or editing a web page or component and deciding where the styles go
|
|
6
|
+
- About to write a `style="..."` attribute, a large embedded `<style>` block, or a per-element inline style object in JSX
|
|
7
|
+
- Reviewing or refactoring how a project's CSS is organized
|
|
8
|
+
- Do NOT use for native mobile styling or non-web code
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# CSS Structure: Where Styles Live
|
|
12
|
+
|
|
13
|
+
Claude's default is to pour everything into one HTML file, either a big `<style>` block or `style="..."` attributes on each element. In a real project that's the wrong default. Styles belong in a `.css` file.
|
|
14
|
+
|
|
15
|
+
## Default: an external stylesheet, linked
|
|
16
|
+
|
|
17
|
+
When building a page or component in a project that has a file structure, put the CSS in its own file and link it:
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<link rel="stylesheet" href="/styles/app.css" />
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
It's reusable across pages, cached separately by the browser, keeps the markup readable, and lets the cascade and media queries work. This is the default to reach for instead of an inline block, not the thing to do only when asked.
|
|
24
|
+
|
|
25
|
+
## Avoid scattered inline `style="..."` attributes
|
|
26
|
+
|
|
27
|
+
Inline style attributes are the worst of the three options, for concrete reasons, not style preference:
|
|
28
|
+
|
|
29
|
+
- **They can't be responsive or interactive.** No media queries, no `:hover`/`:focus`, no `::before`/`::after`, no keyframes. An inline-styled element literally cannot be made responsive (see responsive-css), so it fights everything that skill is about.
|
|
30
|
+
- **They have the highest specificity short of `!important`.** Overriding an inline style later means escalating to `!important`, which starts a specificity war.
|
|
31
|
+
- **No reuse, no caching, noisy markup.** The same declarations get repeated on every element, ship on every page load, and bury the HTML structure.
|
|
32
|
+
|
|
33
|
+
## A single `<style>` block is fine for a deliberately single-file deliverable
|
|
34
|
+
|
|
35
|
+
Don't over-correct into "never embed." One `<style>` block in the head is the right call for a genuinely single-file thing: a standalone demo, a self-contained artifact, a quick reproduction. Email HTML is the opposite special case, mail clients strip `<style>` and external sheets, so email actually requires inline attributes. The rule is about defaults: the moment there's a project with folders, move the CSS into a file rather than defaulting to one big file because it's easier to paste.
|
|
36
|
+
|
|
37
|
+
## When inline is actually correct: dynamic and critical
|
|
38
|
+
|
|
39
|
+
Two cases where a value belongs inline:
|
|
40
|
+
|
|
41
|
+
- **Runtime values.** A width, transform, or color computed from state (a progress bar, a drag position) can't live in a static sheet. Set a CSS custom property inline and consume it in the stylesheet, so only the value is inline and the styling stays in CSS:
|
|
42
|
+
```html
|
|
43
|
+
<div class="bar" style="--progress: 73%"></div>
|
|
44
|
+
```
|
|
45
|
+
```css
|
|
46
|
+
.bar::after { width: var(--progress); }
|
|
47
|
+
```
|
|
48
|
+
- **Critical CSS.** Deliberately inlining the above-the-fold styles in `<head>` for first paint, then loading the rest, is a real performance technique, not the same mistake as scattering `style=`. Which styles are critical and what to defer is a first-paint decision, see web-performance.
|
|
49
|
+
|
|
50
|
+
## In React/JSX
|
|
51
|
+
|
|
52
|
+
Same split: dynamic values through `style={{}}` or a CSS variable, everything static in CSS Modules, a stylesheet, or utility classes (Tailwind). A large inline style object hand-written on every element is the JSX version of the same anti-pattern.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
This skill is built to grow. Add a rule when a real styling-location decision has a stable, defensible answer.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev-pitfalls
|
|
3
|
+
description: Common pitfalls that are slow to troubleshoot and that tend to get "fixed" by going in circles, because the error message points nowhere near the actual cause. Use before and during git, project, and WSL work, checking a repo is initialized and node_modules is ignored, putting WSL projects on the right filesystem, opening a browser from WSL for auth, normalizing line endings, and matching import case. Check the layer here first instead of debugging the wrong one.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
- Starting git work in a directory (is it even a repo? is node_modules ignored?)
|
|
6
|
+
- A WSL project is slow, hot reload / file watchers miss changes, or chmod / ssh permissions misbehave
|
|
7
|
+
- A CLI needs to open a browser from WSL for an OAuth / SSO (Okta, Entra) login
|
|
8
|
+
- CRLF/LF symptoms: `^M`, `bad interpreter: /bin/bash^M`, a deploy failing with "expected string, got object" or unmarshal errors, YAML or shell scripts breaking on one platform only
|
|
9
|
+
- An import works locally but fails in CI (case sensitivity)
|
|
10
|
+
- Writing or editing Kubernetes, Helm, Terraform, or other structured config where a field name or path could be guessed wrong (`service.port` vs `spec.ports[0].port`)
|
|
11
|
+
- A cryptic error whose message misleads: `ENOSPC` (really the file-watcher limit), `exec format error` (really CPU arch mismatch), `dubious ownership` (really a UID mismatch), `EADDRINUSE` (really a leftover process), heap OOM
|
|
12
|
+
- Do NOT use for application-level config or secrets, this is dev-machine, repo, and project-state hygiene
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Common Dev Pitfalls (check the layer before debugging it)
|
|
16
|
+
|
|
17
|
+
These cost hours because the symptom points at the wrong thing, and they're easy to "fix" by going in circles. When something matches below, check the cause here before debugging the code.
|
|
18
|
+
|
|
19
|
+
## Check project state before acting
|
|
20
|
+
|
|
21
|
+
Claude's frequent miss is assuming project state instead of verifying it. Two checks, stated plainly to the user:
|
|
22
|
+
|
|
23
|
+
- **Is it even a git repo?** Before staging, committing, or diffing, confirm the repo exists (`git rev-parse --is-inside-work-tree`). If it doesn't, say so directly, "this directory isn't a git repository yet, run `git init` first", rather than running git commands that fail confusingly or silently acting on a parent repo.
|
|
24
|
+
- **Is `node_modules` ignored?** Before the first commit, confirm `.gitignore` exists and lists `node_modules/`. Create `.gitignore` BEFORE `npm install` so it is never tracked. If it isn't ignored yet, say so before anything gets committed.
|
|
25
|
+
|
|
26
|
+
**`.gitignore` only ignores UNtracked files.** If `node_modules` (or `.env`, or build output) was already committed, adding it to `.gitignore` changes nothing, git keeps tracking it, and this is a classic "my .gitignore isn't working" loop. Untrack it, then commit:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git rm -r --cached node_modules
|
|
30
|
+
git commit -m "Stop tracking node_modules"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Confirm a rule actually bites with `git check-ignore -v node_modules`. Committed `node_modules` bloats the repo and breaks CI, its platform-specific compiled binaries fail on Linux runners. Same fix for anything that "won't stop showing up" after you ignored it.
|
|
34
|
+
|
|
35
|
+
## WSL: put the project on the Linux filesystem, not /mnt/c
|
|
36
|
+
|
|
37
|
+
The single biggest WSL pitfall, and Claude rarely thinks to check it. A project under `/mnt/c` (any Windows drive) is reached from Linux over a 9P bridge, so:
|
|
38
|
+
|
|
39
|
+
- file operations run 10-100x slower, `npm install`, `git status`, and test runs crawl
|
|
40
|
+
- `inotify` is flaky across the bridge, so file watchers fire intermittently, hot reload and test watchers "work randomly" then silently stop, which becomes a circular debugging session if you chase it as a config problem
|
|
41
|
+
- Linux permission bits are only emulated, `chmod +x` can silently fail and ssh rejects keys as "bad permissions"
|
|
42
|
+
- editor integration breaks: Claude often can't see your active selection or the code you've highlighted, so it's working blind on what you're pointing at
|
|
43
|
+
|
|
44
|
+
Keep repos on the Linux side (`~/projects/...`, `~/code/...`) and open them with VS Code Remote-WSL. If a project sits on `/mnt/c` and shows any of the above, that location IS the bug, move it:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
mv /mnt/c/Users/<you>/projects/my-app ~/projects/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `/mnt/c` only when a file genuinely needs to live on the Windows side for a Windows GUI app.
|
|
51
|
+
|
|
52
|
+
**Don't chase this through settings.** The circular trap is treating intermittent watchers or a blind editor as a config problem and tweaking watcher polling, exclude globs, and a pile of VS Code settings. On a Windows machine showing these symptoms, check the path FIRST. If it's `/mnt/c`, the answer is almost always "you're on the Windows filesystem, open the folder in WSL (Remote-WSL) and move it to `~/`", say that before suggesting a single setting.
|
|
53
|
+
|
|
54
|
+
## Opening a browser from WSL (auth / SSO)
|
|
55
|
+
|
|
56
|
+
A CLI runs an OAuth/SSO login, tries to open a browser from WSL, and either nothing opens or it opens a browser that isn't signed into the corporate IdP (Okta, Entra), so the login fails. Why your own browser matters: the SSO session lives in your real Chrome profile, a fresh or other browser context doesn't have it.
|
|
57
|
+
|
|
58
|
+
**Default to launching your real Chrome** via an executable shim on PATH (`~/.local/bin/chrome`, `chmod +x`):
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
#!/bin/sh
|
|
62
|
+
exec "/mnt/c/Program Files/Google/Chrome/Application/chrome.exe" "$@"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
export BROWSER=chrome # in ~/.bashrc
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
That opens your actual browser, your bookmarks and your live Okta session, which is what you want about 99% of the time.
|
|
70
|
+
|
|
71
|
+
`wslview` (from the `wslu` package) is the alternative, but in a corporate setup the browser it routes to may not carry your IdP session, so the login page loads unauthenticated. Offer it as a fallback, not the default. If neither is configured, ask the user which they want (own Chrome vs wslview) before wiring it in.
|
|
72
|
+
|
|
73
|
+
**Check `$BROWSER` hasn't been hijacked.** `BROWSER` has two unrelated meanings: the desktop convention (which browser opens URLs, used by auth flows) and Playwright's test-runner convention, where `BROWSER=chromium` selects which browser to run tests in. If a Playwright project exports `BROWSER=chromium` globally (in `~/.bashrc` or a sourced `.env`) instead of setting it inline on the test command, it overrides your desktop browser, so auth flows either fail outright (no executable named `chromium` on PATH) or open Playwright's bundled Chromium, a clean isolated context with none of your sessions. Same class of problem as VS Code's built-in browser. Diagnose with `echo $BROWSER`: if it shows `chromium`, `firefox`, or `webkit`, that's the hijack. Keep `BROWSER=chrome` (your shim) for desktop and auth, and scope Playwright's to the command only: `BROWSER=chromium npx playwright test`.
|
|
74
|
+
|
|
75
|
+
**Use a shim, not a bash `alias`.** An alias exists only in interactive shells, so when a program execs `$BROWSER` it never sees the alias and the open silently fails, which is the exact programmatic case auth needs.
|
|
76
|
+
|
|
77
|
+
## Line endings: LF everywhere (CRLF is the silent killer)
|
|
78
|
+
|
|
79
|
+
Windows CRLF breaks things in ways that take forever to trace: YAML parsing wrong, shell scripts dying with `bad interpreter: /bin/bash^M`, Docker entrypoints not executing, diffs showing every line changed when nothing did. YAML and `.sh` scripts are the usual victims because a stray `\r` rides along invisibly.
|
|
80
|
+
|
|
81
|
+
**The symptom that wastes the most time: a deploy failing with a type error.** A Kubernetes apply (or any YAML-driven deploy) that throws "expected a string, got a map/object" or "cannot unmarshal object into ... string" usually has nothing wrong with the field it names. A stray `\r` from CRLF has broken how the parser reads the value, so a scalar gets read as a structure. The manifest looks correct because the `\r` is invisible, which is exactly why this eats hours: the error points at the data, not the line ending. Before debugging the field, the schema, or the value, check the file's line endings, normalize to LF and re-apply.
|
|
82
|
+
|
|
83
|
+
Make the repo LF-only with `.gitattributes` at the root:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
* text=auto eol=lf
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`text=auto` lets git detect text vs binary (binaries untouched); `eol=lf` forces LF in both the committed blob and the working tree, regardless of anyone's local `core.autocrlf`. For a repo that already committed CRLF, the attribute alone won't rewrite it, renormalize once: `git add --renormalize . && git commit -m "Normalize line endings to LF"`. Set `.editorconfig` `end_of_line = lf` so files are authored as LF too.
|
|
90
|
+
|
|
91
|
+
## Case sensitivity: Linux is strict, Windows and macOS usually aren't
|
|
92
|
+
|
|
93
|
+
`import './Button'` resolves on a case-insensitive Windows/macOS filesystem even when the file is `button.tsx`, then fails on Linux and CI with "module not found." Match import casing to the real filename exactly. The same trap hits two files differing only in case (`README.md` vs `Readme.md`): they coexist on Linux but collide or shadow each other on Windows/macOS.
|
|
94
|
+
|
|
95
|
+
## Don't guess config field names, look them up (especially Kubernetes)
|
|
96
|
+
|
|
97
|
+
Structured config (Kubernetes, Helm, Terraform, cloud CLIs) is a typed, nested schema of objects and arrays, not flat `KEY=VALUE`. Guessing a field path is how an hour disappears into `service.port` vs `spec.ports[0].port`. The exact path is one command away, so query it instead of guessing.
|
|
98
|
+
|
|
99
|
+
- **`kubectl explain <resource>.<path>`** returns the real schema, including whether a field is an object or an array. `kubectl explain service.spec.ports` shows `ports` is a list, so the value lives at `spec.ports[0].port`, not `spec.port`. Add `--recursive` for the full nested tree.
|
|
100
|
+
- **`kubectl get <resource> <name> -o yaml`** shows the actual structure and values of a live object. Read the path off that, then pull a value precisely with `-o jsonpath='{.spec.ports[0].port}'`.
|
|
101
|
+
- **`KEY=VALUE` is not how Kubernetes models things.** Env vars are a list of objects, `env: [{name: FOO, value: bar}]`, not a map `env: {FOO: bar}`. Ports, containers, and volumes are arrays too (`spec.containers[0].image`). Flat-config instincts break here.
|
|
102
|
+
- **Helm values are chart-specific.** `service.port` can be valid in one chart's `values.yaml` and meaningless in another, that structure is defined by the chart, not by Kubernetes. Run `helm show values <chart>` and read the real keys instead of carrying an assumed path between charts.
|
|
103
|
+
|
|
104
|
+
General rule: when a tool ships an introspection command (`kubectl explain`, `kubectl get -o yaml`, `helm show values`, `terraform state show`, `aws ... describe-*`, `gh api`), use it to get the exact names the first time. A two-second lookup beats an hour on a path that "looked right."
|
|
105
|
+
|
|
106
|
+
## Errors that lie about their cause
|
|
107
|
+
|
|
108
|
+
The worst time-sinks are errors whose message points at the wrong thing, so you debug what the text says for hours. When you see one of these, check the real cause first.
|
|
109
|
+
|
|
110
|
+
- **`ENOSPC: no space left on device`** from a dev server, `npm start`, Vite, nodemon, or VS Code is almost never disk space. It's the Linux inotify file-watcher limit (default ~8192), exhausted by a large project's file count. Confirm disk is fine with `df -h`, check the limit with `cat /proc/sys/fs/inotify/max_user_watches`, then raise it: `echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p`. Inside a container, set `CHOKIDAR_USEPOLLING=true` instead.
|
|
111
|
+
- **`exec format error`** (`exec /entrypoint.sh: exec format error`, or `standard_init_linux.go: ... exec format error`) means the binary's CPU architecture doesn't match the host: an arm64 image on an amd64 host or the reverse. Common now that dev laptops are ARM (Apple Silicon) while prod or CI is x86 (or ARM Graviton). Image metadata can claim the right arch while the binary inside doesn't, so trust `file <binary>` run inside the container over `docker inspect`. Fix: pull/run with `--platform linux/amd64`, or build multi-arch with `docker buildx build --platform linux/amd64,linux/arm64`. Second possible cause: CRLF in the entrypoint script (see line endings above), check that too.
|
|
112
|
+
- **`fatal: detected dubious ownership in repository`** is a UID mismatch, not corruption: the repo files are owned by a different user than the one running git. Classic on `/mnt/c` (Windows files look root-owned to WSL) and in containers (host UID does not match container UID). Git offers the band-aid (`git config --global --add safe.directory <path>`), but the durable fix is matching ownership: `chown -R $(whoami) <repo>`, moving the repo to `~/`, or running the container with `--user $(id -u):$(id -g)`.
|
|
113
|
+
- **`EADDRINUSE: address already in use`** is a leftover process holding the port, usually a dev server that didn't exit. Find and kill it: `lsof -t -i:3000 | xargs kill`, or use another port. Don't reconfigure the app.
|
|
114
|
+
- **`JavaScript heap out of memory`** is V8's default heap ceiling, not always a leak. For a heavy build, raise it: `NODE_OPTIONS=--max-old-space-size=4096`. If it recurs at low memory, then look for a real leak.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
This skill is built to grow. Each new pitfall is another short section: the symptom you actually see, the cause, and the fix that ends the loop.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docker
|
|
3
|
+
description: Production Docker best practices for writing Dockerfiles, Compose files, and Swarm stacks. Use whenever creating or editing a Dockerfile, a docker-compose / compose.yaml, or a stack file, or building and optimizing an image. Fixes the handful of things Claude reliably gets wrong: multi-stage builds, layer-cache ordering, exec-form ENTRYPOINT for correct signals and exit codes, init:true, keeping secrets and env out of the image, non-root users, and healthchecks. From production, not defaults.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
- Writing or editing a Dockerfile, a Compose file, or a Swarm stack file
|
|
6
|
+
- Building, slimming, or speeding up an image
|
|
7
|
+
- Debugging containers that won't stop gracefully, leave zombie processes, or ignore SIGTERM
|
|
8
|
+
- Deciding how config, env, and secrets reach a container
|
|
9
|
+
- Do NOT use for orchestration choices unrelated to packaging (cluster sizing, cloud provider selection)
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Docker: Production Image and Compose Rules
|
|
13
|
+
|
|
14
|
+
From production, not defaults. Claude's Dockerfiles tend to be wrong in the same few ways. Fix them here.
|
|
15
|
+
|
|
16
|
+
## Dockerfile
|
|
17
|
+
|
|
18
|
+
- **Multi-stage builds, always.** Build in a stage that has the compilers and dev dependencies, then `COPY --from=build` only the artifacts into a slim runtime stage. The runtime image carries no build tools, which cuts size hard (a real build went from ~2GB to ~200MB) and removes a pile of CVEs.
|
|
19
|
+
```dockerfile
|
|
20
|
+
FROM node:22 AS build
|
|
21
|
+
WORKDIR /app
|
|
22
|
+
COPY package*.json ./
|
|
23
|
+
RUN npm ci
|
|
24
|
+
COPY . .
|
|
25
|
+
RUN npm run build
|
|
26
|
+
|
|
27
|
+
FROM node:22-slim AS runtime
|
|
28
|
+
WORKDIR /app
|
|
29
|
+
COPY package*.json ./
|
|
30
|
+
RUN npm ci --omit=dev
|
|
31
|
+
COPY --from=build /app/dist ./dist
|
|
32
|
+
USER node
|
|
33
|
+
ENTRYPOINT ["node", "dist/server.js"]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- **Order layers by how often they change: stable first, source last.** Docker caches each layer and rebuilds every layer after the first one whose inputs changed. So copy the dependency manifest and install deps BEFORE copying source: `COPY package*.json ./` then `RUN npm ci` then `COPY . .`. Copy source first and a one-line code change reinstalls every dependency, every build.
|
|
37
|
+
|
|
38
|
+
- **The runtime command is `ENTRYPOINT`/`CMD` in exec form, never `RUN`.** `RUN` executes at build time; the container's process belongs in `ENTRYPOINT ["node","server.js"]` (a JSON array). Use exec form, not shell form (`ENTRYPOINT node server.js`): shell form runs your app under `/bin/sh -c`, so `sh` is PID 1, it swallows `SIGTERM`, and your app never shuts down gracefully (Docker waits out the grace period then SIGKILLs it) and its exit code is lost. Exec form makes your process PID 1 so signals and exit codes propagate. `ENTRYPOINT` for the executable, `CMD` for default args.
|
|
39
|
+
|
|
40
|
+
- **Pin base image versions.** `FROM node:22.3.0-slim`, not `node:latest`. `latest` makes builds non-reproducible and shifts under you silently. Pin a tag (or a digest for full reproducibility), and expose it as an `ARG` so it's easy to bump deliberately.
|
|
41
|
+
|
|
42
|
+
- **Add a `.dockerignore`.** Exclude `node_modules`, `.git`, `.env`, `dist`, and local junk. Without it the whole directory ships as build context (slow), busts the cache on unrelated changes, and can bake a stale `node_modules` or a secret file into the image.
|
|
43
|
+
|
|
44
|
+
- **Run as non-root.** Containers run as root by default. Add a `USER` (for example `USER node`) before the entrypoint so a container escape isn't root on the host.
|
|
45
|
+
|
|
46
|
+
- **Add a `HEALTHCHECK`.** Swarm and Compose use it to know a container is actually ready, which is what makes rolling updates and automatic rollback work. Without it, "running" only means the process started, not that it serves traffic.
|
|
47
|
+
|
|
48
|
+
- **Combine and clean in one `RUN`.** `apt-get update && apt-get install -y ... && rm -rf /var/lib/apt/lists/*` in a single layer. A separate `update` layer goes stale behind the cache, and the cleanup only shrinks the image if it happens in the same layer that added the files.
|
|
49
|
+
|
|
50
|
+
## Compose and Swarm
|
|
51
|
+
|
|
52
|
+
- **`init: true` on every service.** The one always missed. It runs a tiny init (tini) as PID 1 that forwards signals to your process and reaps zombie children. Without it, signal handling and zombie reaping become your app's problem and `docker stop` often hangs to the timeout. Pair it with an exec-form ENTRYPOINT.
|
|
53
|
+
|
|
54
|
+
- **Set resource `limits` AND `reservations`.** Reservations make the scheduler place the container only where the resources exist; limits cap it so a runaway container can't take down the node. Both, not one.
|
|
55
|
+
|
|
56
|
+
- **Configure rolling updates and rollback.** `update_config` with `parallelism: 1`, a `delay`, `order: stop-first` (or `start-first` for blue-green), and `failure_action: rollback`. Combined with a HEALTHCHECK, a bad deploy rolls itself back instead of taking the service down.
|
|
57
|
+
|
|
58
|
+
- **Reference services by name, never by IP.** Container IPs change on every restart, scale, and update. Use the service name and let Docker DNS resolve it (`backend-service:8080`). A hardcoded IP is a guaranteed future outage.
|
|
59
|
+
|
|
60
|
+
- **Substitute env with defaults.** `replicas: ${NGINX_REPLICAS:-2}` and `image: registry/app:${BUILD_VERSION:-latest}` keep one file working across environments through `.env`.
|
|
61
|
+
|
|
62
|
+
## Secrets and config
|
|
63
|
+
|
|
64
|
+
- **Never bake secrets into the image.** `ARG` and `ENV` values are stored in image layers and show up in `docker history`, so a secret written into the Dockerfile is a leaked secret. Use BuildKit build secrets (`RUN --mount=type=secret,id=...`) for build time, and Docker secrets (mounted at `/run/secrets/<name>`) or runtime env for run time. Docker secrets are encrypted at rest, never appear in `docker inspect`, and are scoped to the services that mount them.
|
|
65
|
+
|
|
66
|
+
- **Secrets for sensitive, configs for the rest.** Docker `secret` for certs, keys, and passwords (encrypted); Docker `config` for non-sensitive files like an IP blocklist (not encrypted, but versioned and injected the same way). Neither goes in the image, both are injected at deploy.
|
|
67
|
+
|
|
68
|
+
- **Log to stdout/stderr, not to files inside the container.** Write to `/dev/stdout` and `/dev/stderr` so Docker's logging driver and your aggregator collect them. Logging to a file inside the container hides the output and fills the writable layer.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
This skill is built to grow. Add a rule when a real Dockerfile or stack failure has a stable, defensible fix.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docker-swarm
|
|
3
|
+
description: Production Docker Swarm deployment rules: what changes when a compose file goes from a single node to a multi-node Swarm. Use when writing or reviewing a stack file, a deploy block, an overlay network, or anything deployed with docker stack deploy. Covers the directives Swarm silently ignores, why fixed IPs and bind mounts break, the deploy orchestration block, and the exit-code and healthcheck discipline that Swarm self-healing depends on. Complements the docker skill, which covers writing the image and compose file itself.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
- Writing or reviewing a stack file or a `deploy:` block, or running `docker stack deploy`
|
|
6
|
+
- Moving a compose file that works locally onto a multi-node Swarm
|
|
7
|
+
- A service behaves differently deployed than it did with `docker compose up`
|
|
8
|
+
- A rolling update hangs, a replica can't get an IP, or a service is "running" but dead
|
|
9
|
+
- Designing overlay networks, placement, rolling updates, or rollback
|
|
10
|
+
- Do NOT use for single-node Compose or image building, that is the docker skill
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Docker Swarm: Single Node to Multi-Node
|
|
14
|
+
|
|
15
|
+
Compose and Swarm read the same file and interpret it differently. The trap is that Swarm fails silently: the stack deploys, containers start, and something just doesn't work the way it did locally. Write the compose file for the multi-node world from day one and the single-node case still works.
|
|
16
|
+
|
|
17
|
+
## The mental model: a container is a process, not a computer
|
|
18
|
+
|
|
19
|
+
Swarm tears down and recreates containers constantly, on every update, node failure, scale, and rebalance. The new container has a new ID, a new IP, a new hostname, and a blank filesystem. Treat the container as disposable and keep all state outside it: a database or Redis for sessions, a named volume or object storage for files, env or Docker secrets/configs for configuration, stdout/stderr for logs. The test: if Swarm kills this container right now and starts a new one, does the app work identically? If not, state is leaking into the container.
|
|
20
|
+
|
|
21
|
+
## Directives Swarm silently ignores
|
|
22
|
+
|
|
23
|
+
`docker stack deploy` reads these and skips them with no error. If you relied on one locally, you debug for hours before realizing Swarm never read it.
|
|
24
|
+
|
|
25
|
+
`build` (Swarm only runs pre-built registry images), `container_name` (names collide with replicas), `depends_on` (no startup ordering, services start in parallel), `links`, `restart` (use `deploy.restart_policy`), `networks.ipv4_address` / `ipv6_address`, `network_mode`, `cap_add` / `cap_drop`, `devices`, `tmpfs`, `extra_hosts`, `sysctls`, `security_opt`, `cgroup_parent`, `userns_mode`. Capabilities, sysctls, and security options are set at the engine level on each node instead.
|
|
26
|
+
|
|
27
|
+
## Directives that change behavior in Swarm
|
|
28
|
+
|
|
29
|
+
- **`ports`** publish through the routing mesh: the port opens on every node, and traffic to any node is routed to a container wherever it runs. Publish the container port only (`- "61339"`) and let the mesh assign a host port, your reverse proxy reaches the service by name and never needs to know the host port.
|
|
30
|
+
- **`volumes`**: named volumes work everywhere; bind mounts to host paths break the moment a container is scheduled on a different node, because that path doesn't exist there. Use named volumes for data and Docker configs/secrets for files.
|
|
31
|
+
- **`networks`**: Compose defaults to `bridge` (single host). Swarm needs `overlay` (multi-host). A bridge network in a stack means services can't talk across nodes.
|
|
32
|
+
|
|
33
|
+
## Never hardcode an IP, the service name is the identity
|
|
34
|
+
|
|
35
|
+
Container IPs change on every restart, scale, and update. The service name is the one thing that never changes; Docker DNS resolves it to wherever the container currently lives. Hardcoding an IP breaks replicas, load balancing, and multi-node, but the worst case bites with a single replica during a routine rolling update: the new container needs an IP the dying old container hasn't released yet, and you can deadlock, the new one waits for the IP, the old one waits to be healthy, the update hangs, and rollback hits the same conflict. Connect by name (`mongodb://mongo:27017/mydb`) and none of it happens. A reverse proxy in the mesh needs a Docker-aware DNS resolver with a short TTL so it re-resolves names, see the nginx skill.
|
|
36
|
+
|
|
37
|
+
## The deploy block (Swarm-only orchestration)
|
|
38
|
+
|
|
39
|
+
These keys do nothing in plain Compose (except resource limits) but are what Swarm runs on:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
deploy:
|
|
43
|
+
mode: replicated
|
|
44
|
+
replicas: 6
|
|
45
|
+
placement:
|
|
46
|
+
max_replicas_per_node: 3 # 6 replicas then need 2+ nodes, spreads for HA
|
|
47
|
+
constraints:
|
|
48
|
+
- node.role == worker
|
|
49
|
+
update_config:
|
|
50
|
+
parallelism: 2
|
|
51
|
+
delay: 10s
|
|
52
|
+
order: start-first # start new before stopping old, or stop-first
|
|
53
|
+
failure_action: rollback # bad deploy rolls itself back
|
|
54
|
+
rollback_config:
|
|
55
|
+
parallelism: 2
|
|
56
|
+
delay: 10s
|
|
57
|
+
restart_policy:
|
|
58
|
+
condition: on-failure
|
|
59
|
+
delay: 5s
|
|
60
|
+
max_attempts: 3
|
|
61
|
+
window: 120s
|
|
62
|
+
resources:
|
|
63
|
+
limits: # cap so a runaway container can't starve the node
|
|
64
|
+
cpus: '0.50'
|
|
65
|
+
memory: 400M
|
|
66
|
+
reservations: # guarantee, scheduler places only where it fits
|
|
67
|
+
cpus: '0.20'
|
|
68
|
+
memory: 150M
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## No `depends_on`, so the app must retry
|
|
72
|
+
|
|
73
|
+
Swarm starts services in parallel with no ordering. The app must connect to its dependencies with retry and exponential backoff rather than assuming the database is up. This is good engineering anyway, databases restart and connections drop in any production system.
|
|
74
|
+
|
|
75
|
+
## Self-healing depends on you, get exit codes and healthchecks right
|
|
76
|
+
|
|
77
|
+
Swarm (like Kubernetes) is blind to a service it can't measure. The four failures that actually kill production are the same on both, and both depend entirely on you:
|
|
78
|
+
|
|
79
|
+
- **Exit codes.** `restart_policy: on-failure` only restarts on a non-zero exit. An app that crashes but calls `exit(0)` is "success" and stays dead. Exit non-zero on failure.
|
|
80
|
+
- **Meaningful healthchecks.** A `/health` that always returns 200 even when the database is down reports healthy while serving errors. The check must verify real dependencies. Without any healthcheck, Swarm treats a hung or deadlocked container as healthy and keeps routing traffic to it.
|
|
81
|
+
- **Warm-up.** Use `start_period` so a slow-starting container isn't killed before it's ready.
|
|
82
|
+
|
|
83
|
+
Write the healthcheck and set the exit codes; the orchestrator does exactly what you tell it and nothing you don't.
|
|
84
|
+
|
|
85
|
+
## Overlay network and stack workflow
|
|
86
|
+
|
|
87
|
+
Pre-create an encrypted overlay, and pick a subnet that won't clash with your cloud VPC or default Docker ranges:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
docker network create --opt encrypted --attachable --driver overlay \
|
|
91
|
+
--subnet 172.240.0.0/24 awsnet
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Reference it as `external: true` in the stack. Open Swarm ports between nodes: 2377/tcp (management), 7946/tcp+udp (node comms), 4789/udp (overlay). Note that `--opt encrypted` can fight cloud NAT, use internal VPC IPs when you enable it. Then build and push to a registry first (Swarm won't build), and deploy:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
docker stack deploy -c docker-compose.yaml mystack
|
|
98
|
+
docker stack services mystack
|
|
99
|
+
docker service ps mystack_app --no-trunc # full error text when a task won't start
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
This skill is built to grow. Add a rule when a real Swarm deployment surprise has a stable, defensible fix.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mdd-workflow
|
|
3
|
+
description: Use in any project that uses MDD (a `.mdd/` directory exists) when editing source code, building a feature, fixing a bug, requesting an audit, or editing `.mdd/docs` files. Keeps feature docs in sync the moment their controlled source files change, recommends the `/mdd` workflow for substantial tasks, and conforms to `.mdd` doc conventions. Installed globally, so it self-gates: it does nothing in projects that don't use MDD.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
- About to edit source code in a project that has a `.mdd/` directory (drift check)
|
|
6
|
+
- User asks for a feature, a real bug fix, a meaningful refactor, or an audit (recommend `/mdd`)
|
|
7
|
+
- Editing a `.mdd/docs/*.md` file directly, outside a `/mdd` run (conventions)
|
|
8
|
+
- Do NOT act at all if the project has no `.mdd/` directory and no `/mdd` command — stay silent
|
|
9
|
+
allowed-tools: "Read, Edit, Grep, Glob, Bash"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# MDD Workflow Companion
|
|
13
|
+
|
|
14
|
+
This skill complements the `/mdd` command. The command **owns** the workflow; this skill keeps things honest **between** command runs. Its single most valuable job is preventing documentation drift the moment it would happen, before it accumulates.
|
|
15
|
+
|
|
16
|
+
## Step 1 — Gate (do this first, every time)
|
|
17
|
+
|
|
18
|
+
Act only if **this project uses MDD**. Check for any of:
|
|
19
|
+
- a `.mdd/` directory in the project root, or
|
|
20
|
+
- a resolvable `/mdd` command (`.claude/mdd/`, `~/.claude/mdd/`, `.claude/commands/mdd.md`).
|
|
21
|
+
|
|
22
|
+
If none exist, **stop silently**. Do nothing, say nothing, never mention MDD. This skill is installed globally and will activate in projects that have never adopted MDD; in those projects it must be invisible.
|
|
23
|
+
|
|
24
|
+
Two hard rules that hold for everything below:
|
|
25
|
+
- **Never run the workflow yourself.** No branching, no `.mdd/` bootstrap, no phase execution. You recommend `/mdd` and let the user confirm. The command performs all side effects.
|
|
26
|
+
- **Silence is the default.** When you activate but there is nothing controlled to sync and nothing substantial to recommend, produce no MDD output at all. Never narrate the gate or the check.
|
|
27
|
+
|
|
28
|
+
## Step 2 — Job 1: Prevent doc drift when editing controlled code (priority)
|
|
29
|
+
|
|
30
|
+
Feature docs declare the source they own in frontmatter (`source_files`, plus `routes` and `models`). When that source is edited outside a `/mdd` run, the doc silently goes stale. Catch it at the edit, not weeks later in `/mdd scan`.
|
|
31
|
+
|
|
32
|
+
When you are about to edit (or have just edited) a source file in an MDD project:
|
|
33
|
+
|
|
34
|
+
1. **Cheap controlled-file check.** Grep `.mdd/docs/*.md` for the file's path in `source_files` (and `routes`/`models` if the change adds or alters a route or model):
|
|
35
|
+
```bash
|
|
36
|
+
grep -rl "<relative/path/to/file>" .mdd/docs/*.md
|
|
37
|
+
```
|
|
38
|
+
No match → the file is not controlled. Stay silent and proceed normally.
|
|
39
|
+
|
|
40
|
+
2. **If a doc controls the file**, name it and keep it in sync:
|
|
41
|
+
- Read `.mdd/docs/00-frontmatter-spec.md` first so any frontmatter edit conforms to the schema.
|
|
42
|
+
- Make the **lightweight, factual** updates inline: bump `last_synced` to today; add a new endpoint to `routes`; add a new model to `models`; append a genuinely new defect to `known_issues` (append-only — never remove entries).
|
|
43
|
+
- Tell the user in one line:
|
|
44
|
+
`📝 Edited <file>, controlled by .mdd/docs/<id>.md — synced last_synced and routes.`
|
|
45
|
+
- **If the change is structural** (behavior, business rules, contracts, or data flow changed, not just a field), don't try to re-derive the doc body yourself. Recommend the command that does a full resync:
|
|
46
|
+
`This change affects how <id> behaves. Run \`/mdd update <id>\` (or \`/mdd audit\`) for a full doc resync.`
|
|
47
|
+
|
|
48
|
+
3. **If the user is mid-flow and declines the sync**, don't block their work. Note it once so it isn't lost:
|
|
49
|
+
`Leaving .mdd/docs/<id>.md unsynced — \`/mdd scan\` will flag it later.`
|
|
50
|
+
|
|
51
|
+
This is real-time and pre-commit, which is precisely what `/mdd status` and `/mdd scan` cannot do (they detect drift retrospectively, from git history, only when invoked). Defer batch and historical drift detection to those commands; this skill catches the single edit as it lands.
|
|
52
|
+
|
|
53
|
+
## Step 3 — Job 2: Recommend the workflow for substantial tasks (recommend, never run)
|
|
54
|
+
|
|
55
|
+
When the user asks for work MDD is built for **and** it would genuinely benefit, offer the matching mode once, then defer to the user.
|
|
56
|
+
|
|
57
|
+
**Nudge when** the task is a new feature, a real (non-trivial) bug, a meaningful refactor, or an audit — and **especially** when it will touch code controlled by an existing feature doc that would drift if done ad-hoc.
|
|
58
|
+
|
|
59
|
+
**Do not nudge** for typos, formatting, renames, config tweaks, one-liners, or plain questions. Anything too small to deserve a doc is too small to nudge.
|
|
60
|
+
|
|
61
|
+
The offer is one line, in the user's words, and waits:
|
|
62
|
+
|
|
63
|
+
> You have MDD installed and this looks like a task that would benefit from it — `/mdd <feature>` documents it first, then builds against the doc. Want me to use it, or just proceed here?
|
|
64
|
+
|
|
65
|
+
Pick the right invocation: feature → `/mdd <feature-slug>`; bug → `/mdd bug`; audit → `/mdd audit`; existing-doc drift → `/mdd update <id>`.
|
|
66
|
+
|
|
67
|
+
**One offer per task.** If the user declines or says "just do it," proceed ad-hoc and don't ask again this task. Never run `/mdd` without an explicit yes — but Job 1 still applies, so keep any controlled doc in sync even while working ad-hoc.
|
|
68
|
+
|
|
69
|
+
## Step 4 — Job 3: Keep `.mdd/docs` conventions correct
|
|
70
|
+
|
|
71
|
+
When editing a `.mdd/docs/*.md` file directly (not through `/mdd`), read `.mdd/docs/00-frontmatter-spec.md` first and conform:
|
|
72
|
+
- All required frontmatter fields present and correctly typed.
|
|
73
|
+
- `satisfies_contracts` entries use the `from` / `function` / `when` / `status` (`pending`|`done`) / `verified_at` shape — the field is `verified_at`, not `verified`.
|
|
74
|
+
- `relates` is symmetric: if doc A relates B, ensure B relates A.
|
|
75
|
+
- `known_issues` is append-only.
|
|
76
|
+
- Tags are domain concepts, technology names, or feature names — never file paths or generic words.
|
|
77
|
+
|
|
78
|
+
## What this skill does NOT do
|
|
79
|
+
|
|
80
|
+
- It does not execute, branch, bootstrap, or run any MDD phase — `/mdd` does that.
|
|
81
|
+
- It does not re-derive doc bodies or do full resyncs — `/mdd update`, `/mdd audit`, and `/mdd scan` do that.
|
|
82
|
+
- It does not act, or mention MDD, in projects that don't use MDD.
|