Add new skills for TDD, issue management, PRD creation, and productivity tools
- Introduced TDD skills including deep modules, interface design, mocking, refactoring, and testing guidelines. - Added skills for breaking plans into GitHub issues and creating PRDs from conversation context. - Implemented productivity skills for scaffolding exercises, setting up pre-commit hooks, and managing notes in Obsidian. - Created a caveman communication mode for concise technical responses and a grilling technique for thorough plan discussions. - Developed a skill for writing new agent skills with structured templates and guidelines. - Included git guardrails to prevent dangerous git commands and a migration guide for using @total-typescript/shoehorn in tests.
This commit is contained in:
parent
3e3ca9b9fa
commit
62f43a1817
43 changed files with 97 additions and 65 deletions
3
skills/misc/README.md
Normal file
3
skills/misc/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Misc
|
||||
|
||||
Tools I keep around but rarely use.
|
||||
95
skills/misc/git-guardrails-claude-code/SKILL.md
Normal file
95
skills/misc/git-guardrails-claude-code/SKILL.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
name: git-guardrails-claude-code
|
||||
description: Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. Use when user wants to prevent destructive git operations, add git safety hooks, or block git push/reset in Claude Code.
|
||||
---
|
||||
|
||||
# Setup Git Guardrails
|
||||
|
||||
Sets up a PreToolUse hook that intercepts and blocks dangerous git commands before Claude executes them.
|
||||
|
||||
## What Gets Blocked
|
||||
|
||||
- `git push` (all variants including `--force`)
|
||||
- `git reset --hard`
|
||||
- `git clean -f` / `git clean -fd`
|
||||
- `git branch -D`
|
||||
- `git checkout .` / `git restore .`
|
||||
|
||||
When blocked, Claude sees a message telling it that it does not have authority to access these commands.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Ask scope
|
||||
|
||||
Ask the user: install for **this project only** (`.claude/settings.json`) or **all projects** (`~/.claude/settings.json`)?
|
||||
|
||||
### 2. Copy the hook script
|
||||
|
||||
The bundled script is at: [scripts/block-dangerous-git.sh](scripts/block-dangerous-git.sh)
|
||||
|
||||
Copy it to the target location based on scope:
|
||||
|
||||
- **Project**: `.claude/hooks/block-dangerous-git.sh`
|
||||
- **Global**: `~/.claude/hooks/block-dangerous-git.sh`
|
||||
|
||||
Make it executable with `chmod +x`.
|
||||
|
||||
### 3. Add hook to settings
|
||||
|
||||
Add to the appropriate settings file:
|
||||
|
||||
**Project** (`.claude/settings.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous-git.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Global** (`~/.claude/settings.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/block-dangerous-git.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the settings file already exists, merge the hook into existing `hooks.PreToolUse` array — don't overwrite other settings.
|
||||
|
||||
### 4. Ask about customization
|
||||
|
||||
Ask if user wants to add or remove any patterns from the blocked list. Edit the copied script accordingly.
|
||||
|
||||
### 5. Verify
|
||||
|
||||
Run a quick test:
|
||||
|
||||
```bash
|
||||
echo '{"tool_input":{"command":"git push origin main"}}' | <path-to-script>
|
||||
```
|
||||
|
||||
Should exit with code 2 and print a BLOCKED message to stderr.
|
||||
25
skills/misc/git-guardrails-claude-code/scripts/block-dangerous-git.sh
Executable file
25
skills/misc/git-guardrails-claude-code/scripts/block-dangerous-git.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash
|
||||
|
||||
INPUT=$(cat)
|
||||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
|
||||
|
||||
DANGEROUS_PATTERNS=(
|
||||
"git push"
|
||||
"git reset --hard"
|
||||
"git clean -fd"
|
||||
"git clean -f"
|
||||
"git branch -D"
|
||||
"git checkout \."
|
||||
"git restore \."
|
||||
"push --force"
|
||||
"reset --hard"
|
||||
)
|
||||
|
||||
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
|
||||
if echo "$COMMAND" | grep -qE "$pattern"; then
|
||||
echo "BLOCKED: '$COMMAND' matches dangerous pattern '$pattern'. The user has prevented you from doing this." >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
118
skills/misc/migrate-to-shoehorn/SKILL.md
Normal file
118
skills/misc/migrate-to-shoehorn/SKILL.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
name: migrate-to-shoehorn
|
||||
description: Migrate test files from `as` type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace `as` in tests, or needs partial test data.
|
||||
---
|
||||
|
||||
# Migrate to Shoehorn
|
||||
|
||||
## Why shoehorn?
|
||||
|
||||
`shoehorn` lets you pass partial data in tests while keeping TypeScript happy. It replaces `as` assertions with type-safe alternatives.
|
||||
|
||||
**Test code only.** Never use shoehorn in production code.
|
||||
|
||||
Problems with `as` in tests:
|
||||
|
||||
- Trained not to use it
|
||||
- Must manually specify target type
|
||||
- Double-as (`as unknown as Type`) for intentionally wrong data
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm i @total-typescript/shoehorn
|
||||
```
|
||||
|
||||
## Migration patterns
|
||||
|
||||
### Large objects with few needed properties
|
||||
|
||||
Before:
|
||||
|
||||
```ts
|
||||
type Request = {
|
||||
body: { id: string };
|
||||
headers: Record<string, string>;
|
||||
cookies: Record<string, string>;
|
||||
// ...20 more properties
|
||||
};
|
||||
|
||||
it("gets user by id", () => {
|
||||
// Only care about body.id but must fake entire Request
|
||||
getUser({
|
||||
body: { id: "123" },
|
||||
headers: {},
|
||||
cookies: {},
|
||||
// ...fake all 20 properties
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```ts
|
||||
import { fromPartial } from "@total-typescript/shoehorn";
|
||||
|
||||
it("gets user by id", () => {
|
||||
getUser(
|
||||
fromPartial({
|
||||
body: { id: "123" },
|
||||
}),
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### `as Type` → `fromPartial()`
|
||||
|
||||
Before:
|
||||
|
||||
```ts
|
||||
getUser({ body: { id: "123" } } as Request);
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```ts
|
||||
import { fromPartial } from "@total-typescript/shoehorn";
|
||||
|
||||
getUser(fromPartial({ body: { id: "123" } }));
|
||||
```
|
||||
|
||||
### `as unknown as Type` → `fromAny()`
|
||||
|
||||
Before:
|
||||
|
||||
```ts
|
||||
getUser({ body: { id: 123 } } as unknown as Request); // wrong type on purpose
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```ts
|
||||
import { fromAny } from "@total-typescript/shoehorn";
|
||||
|
||||
getUser(fromAny({ body: { id: 123 } }));
|
||||
```
|
||||
|
||||
## When to use each
|
||||
|
||||
| Function | Use case |
|
||||
| --------------- | -------------------------------------------------- |
|
||||
| `fromPartial()` | Pass partial data that still type-checks |
|
||||
| `fromAny()` | Pass intentionally wrong data (keeps autocomplete) |
|
||||
| `fromExact()` | Force full object (swap with fromPartial later) |
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Gather requirements** - ask user:
|
||||
- What test files have `as` assertions causing problems?
|
||||
- Are they dealing with large objects where only some properties matter?
|
||||
- Do they need to pass intentionally wrong data for error testing?
|
||||
|
||||
2. **Install and migrate**:
|
||||
- [ ] Install: `npm i @total-typescript/shoehorn`
|
||||
- [ ] Find test files with `as` assertions: `grep -r " as [A-Z]" --include="*.test.ts" --include="*.spec.ts"`
|
||||
- [ ] Replace `as Type` with `fromPartial()`
|
||||
- [ ] Replace `as unknown as Type` with `fromAny()`
|
||||
- [ ] Add imports from `@total-typescript/shoehorn`
|
||||
- [ ] Run type check to verify
|
||||
106
skills/misc/scaffold-exercises/SKILL.md
Normal file
106
skills/misc/scaffold-exercises/SKILL.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
name: scaffold-exercises
|
||||
description: Create exercise directory structures with sections, problems, solutions, and explainers that pass linting. Use when user wants to scaffold exercises, create exercise stubs, or set up a new course section.
|
||||
---
|
||||
|
||||
# Scaffold Exercises
|
||||
|
||||
Create exercise directory structures that pass `pnpm ai-hero-cli internal lint`, then commit with `git commit`.
|
||||
|
||||
## Directory naming
|
||||
|
||||
- **Sections**: `XX-section-name/` inside `exercises/` (e.g., `01-retrieval-skill-building`)
|
||||
- **Exercises**: `XX.YY-exercise-name/` inside a section (e.g., `01.03-retrieval-with-bm25`)
|
||||
- Section number = `XX`, exercise number = `XX.YY`
|
||||
- Names are dash-case (lowercase, hyphens)
|
||||
|
||||
## Exercise variants
|
||||
|
||||
Each exercise needs at least one of these subfolders:
|
||||
|
||||
- `problem/` - student workspace with TODOs
|
||||
- `solution/` - reference implementation
|
||||
- `explainer/` - conceptual material, no TODOs
|
||||
|
||||
When stubbing, default to `explainer/` unless the plan specifies otherwise.
|
||||
|
||||
## Required files
|
||||
|
||||
Each subfolder (`problem/`, `solution/`, `explainer/`) needs a `readme.md` that:
|
||||
|
||||
- Is **not empty** (must have real content, even a single title line works)
|
||||
- Has no broken links
|
||||
|
||||
When stubbing, create a minimal readme with a title and a description:
|
||||
|
||||
```md
|
||||
# Exercise Title
|
||||
|
||||
Description here
|
||||
```
|
||||
|
||||
If the subfolder has code, it also needs a `main.ts` (>1 line). But for stubs, a readme-only exercise is fine.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Parse the plan** - extract section names, exercise names, and variant types
|
||||
2. **Create directories** - `mkdir -p` for each path
|
||||
3. **Create stub readmes** - one `readme.md` per variant folder with a title
|
||||
4. **Run lint** - `pnpm ai-hero-cli internal lint` to validate
|
||||
5. **Fix any errors** - iterate until lint passes
|
||||
|
||||
## Lint rules summary
|
||||
|
||||
The linter (`pnpm ai-hero-cli internal lint`) checks:
|
||||
|
||||
- Each exercise has subfolders (`problem/`, `solution/`, `explainer/`)
|
||||
- At least one of `problem/`, `explainer/`, or `explainer.1/` exists
|
||||
- `readme.md` exists and is non-empty in the primary subfolder
|
||||
- No `.gitkeep` files
|
||||
- No `speaker-notes.md` files
|
||||
- No broken links in readmes
|
||||
- No `pnpm run exercise` commands in readmes
|
||||
- `main.ts` required per subfolder unless it's readme-only
|
||||
|
||||
## Moving/renaming exercises
|
||||
|
||||
When renumbering or moving exercises:
|
||||
|
||||
1. Use `git mv` (not `mv`) to rename directories - preserves git history
|
||||
2. Update the numeric prefix to maintain order
|
||||
3. Re-run lint after moves
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
git mv exercises/01-retrieval/01.03-embeddings exercises/01-retrieval/01.04-embeddings
|
||||
```
|
||||
|
||||
## Example: stubbing from a plan
|
||||
|
||||
Given a plan like:
|
||||
|
||||
```
|
||||
Section 05: Memory Skill Building
|
||||
- 05.01 Introduction to Memory
|
||||
- 05.02 Short-term Memory (explainer + problem + solution)
|
||||
- 05.03 Long-term Memory
|
||||
```
|
||||
|
||||
Create:
|
||||
|
||||
```bash
|
||||
mkdir -p exercises/05-memory-skill-building/05.01-introduction-to-memory/explainer
|
||||
mkdir -p exercises/05-memory-skill-building/05.02-short-term-memory/{explainer,problem,solution}
|
||||
mkdir -p exercises/05-memory-skill-building/05.03-long-term-memory/explainer
|
||||
```
|
||||
|
||||
Then create readme stubs:
|
||||
|
||||
```
|
||||
exercises/05-memory-skill-building/05.01-introduction-to-memory/explainer/readme.md -> "# Introduction to Memory"
|
||||
exercises/05-memory-skill-building/05.02-short-term-memory/explainer/readme.md -> "# Short-term Memory"
|
||||
exercises/05-memory-skill-building/05.02-short-term-memory/problem/readme.md -> "# Short-term Memory"
|
||||
exercises/05-memory-skill-building/05.02-short-term-memory/solution/readme.md -> "# Short-term Memory"
|
||||
exercises/05-memory-skill-building/05.03-long-term-memory/explainer/readme.md -> "# Long-term Memory"
|
||||
```
|
||||
91
skills/misc/setup-pre-commit/SKILL.md
Normal file
91
skills/misc/setup-pre-commit/SKILL.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
name: setup-pre-commit
|
||||
description: Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.
|
||||
---
|
||||
|
||||
# Setup Pre-Commit Hooks
|
||||
|
||||
## What This Sets Up
|
||||
|
||||
- **Husky** pre-commit hook
|
||||
- **lint-staged** running Prettier on all staged files
|
||||
- **Prettier** config (if missing)
|
||||
- **typecheck** and **test** scripts in the pre-commit hook
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Detect package manager
|
||||
|
||||
Check for `package-lock.json` (npm), `pnpm-lock.yaml` (pnpm), `yarn.lock` (yarn), `bun.lockb` (bun). Use whichever is present. Default to npm if unclear.
|
||||
|
||||
### 2. Install dependencies
|
||||
|
||||
Install as devDependencies:
|
||||
|
||||
```
|
||||
husky lint-staged prettier
|
||||
```
|
||||
|
||||
### 3. Initialize Husky
|
||||
|
||||
```bash
|
||||
npx husky init
|
||||
```
|
||||
|
||||
This creates `.husky/` dir and adds `prepare: "husky"` to package.json.
|
||||
|
||||
### 4. Create `.husky/pre-commit`
|
||||
|
||||
Write this file (no shebang needed for Husky v9+):
|
||||
|
||||
```
|
||||
npx lint-staged
|
||||
npm run typecheck
|
||||
npm run test
|
||||
```
|
||||
|
||||
**Adapt**: Replace `npm` with detected package manager. If repo has no `typecheck` or `test` script in package.json, omit those lines and tell the user.
|
||||
|
||||
### 5. Create `.lintstagedrc`
|
||||
|
||||
```json
|
||||
{
|
||||
"*": "prettier --ignore-unknown --write"
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Create `.prettierrc` (if missing)
|
||||
|
||||
Only create if no Prettier config exists. Use these defaults:
|
||||
|
||||
```json
|
||||
{
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 80,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"semi": true,
|
||||
"arrowParens": "always"
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Verify
|
||||
|
||||
- [ ] `.husky/pre-commit` exists and is executable
|
||||
- [ ] `.lintstagedrc` exists
|
||||
- [ ] `prepare` script in package.json is `"husky"`
|
||||
- [ ] `prettier` config exists
|
||||
- [ ] Run `npx lint-staged` to verify it works
|
||||
|
||||
### 8. Commit
|
||||
|
||||
Stage all changed/created files and commit with message: `Add pre-commit hooks (husky + lint-staged + prettier)`
|
||||
|
||||
This will run through the new pre-commit hooks — a good smoke test that everything works.
|
||||
|
||||
## Notes
|
||||
|
||||
- Husky v9+ doesn't need shebangs in hook files
|
||||
- `prettier --ignore-unknown` skips files Prettier can't parse (images, etc.)
|
||||
- The pre-commit runs lint-staged first (fast, staged-only), then full typecheck and tests
|
||||
Loading…
Add table
Add a link
Reference in a new issue