Link Claude config and skills into ~/.claude
Map config/claude/CLAUDE.md and config/claude/skills into ~/.claude during dotfile linking. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
103
config/claude/CLAUDE.md
Normal file
103
config/claude/CLAUDE.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Core Behaviors
|
||||
|
||||
## Brevity (mandatory)
|
||||
Be concise.
|
||||
Short sentences only (8-10 words max).
|
||||
No filler, no preamble, no pleasantries.
|
||||
Tool first. Result first. No explain unless asked.
|
||||
Code stays normal. English gets compressed.
|
||||
|
||||
## Formatting
|
||||
|
||||
Output sounds human. Never AI-generated.
|
||||
Never use em-dashes or replacement hyphens.
|
||||
Avoid parenthetical clauses entirely.
|
||||
Hyphens map to standard grammar only.
|
||||
|
||||
## Skill Check (mandatory)
|
||||
|
||||
Before starting ANY non-trivial task, check available skills for relevance.
|
||||
If there is even a 1% chance a skill applies, invoke it before doing anything else.
|
||||
Never rationalize skipping a skill check with "this is simple enough" or "I already know how."
|
||||
|
||||
## Feedback
|
||||
Criticism is welcome.
|
||||
Please tell me when I am wrong or mistaken, or even when you think I might be wrong or mistaken.
|
||||
Please tell me if there is a better approach than the one I am taking.
|
||||
Please tell me if there is a relevant standard or convention that I appear to be unaware of.
|
||||
Be skeptical.
|
||||
|
||||
in short: fight me, bro.
|
||||
|
||||
# Development Behavior
|
||||
|
||||
## Core Development Principles
|
||||
- **Simplicity First**: Make every change as simple as possible. Impact minimal code.
|
||||
- **No Laziness**: Find root causes. No temporary fixes. Senior developer standards.
|
||||
- **Minimize Impact**: Changes should only touch what's necessary. Avoid introducing bugs.
|
||||
|
||||
## Testing Standards
|
||||
- Write unit tests for the **majority of critical and error branches** in every package
|
||||
- Every new package must have a `_test.go` file before the task is considered done
|
||||
- Test error paths explicitly: missing keys, wrong types, boundary conditions, empty inputs
|
||||
- Use table-driven tests for functions with multiple cases
|
||||
- Integration tests for `<command>` (requiring model files) go in `cmd/<command>/` directory and use `t.Skip` when file absent
|
||||
|
||||
## Implementation Methodology
|
||||
|
||||
### 1. Plan Mode Default
|
||||
- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
|
||||
- If something goes sideways, STOP and re-plan immediately - don't keep pushing
|
||||
- Use plan mode for verification steps, not just building
|
||||
- Write detailed specs upfront to reduce ambiguity
|
||||
|
||||
### 2. Subagent Strategy
|
||||
- Use subagents liberally to keep main context window clean
|
||||
- Offload research, exploration, and parallel analysis to subagents
|
||||
- For complex problems, throw more compute at it via subagents
|
||||
- One tack per subagent for focused execution
|
||||
|
||||
### 3. Self-Improvement Loop
|
||||
- After ANY correction from the user: update "tasks/lessons.md" with the pattern
|
||||
- Write rules for yourself that prevent the same mistake
|
||||
- Ruthlessly iterate on these lessons until mistake rate drops
|
||||
- Review lessons at session start for relevant project
|
||||
|
||||
### 4. Verification Before Done
|
||||
- Never mark a task complete without proving it works
|
||||
- Diff behavior between main and your changes when relevant
|
||||
- Ask yourself: "Would a staff engineer approve this?"
|
||||
- Run tests, check logs, demonstrate correctness
|
||||
|
||||
### 5. Demand Elegance (Balanced)
|
||||
- For non-trivial changes: pause and ask "is there a more elegant way?"
|
||||
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
|
||||
- Skip this for simple, obvious fixes - don't over-engineer
|
||||
- Challenge your own work before presenting it
|
||||
|
||||
### 6. Autonomous Bug Fizing
|
||||
- When given a bug report: just fix it. Don't ask for hand-holding
|
||||
- Point at logs, errors, failing tests - then resolve them
|
||||
- Zero context switching required from the user
|
||||
- Go fix failing CI tests without being told how
|
||||
|
||||
### 7. Tool Usage Hierarchy
|
||||
Always prefer specialized tools over bash commands:
|
||||
- Use `Read` instead of `cat/head/tail`
|
||||
- Use `Edit` instead of `sed/awk`
|
||||
- Use `Grep` instead of `grep` command
|
||||
- Use `Glob` instead of `find` for file patterns
|
||||
- Use `Task` with Explore agent for codebase exploration
|
||||
- Reserve bash for actual system commands (git, npm, docker, etc.)
|
||||
|
||||
|
||||
# Project management Behavior
|
||||
|
||||
## Task Management
|
||||
1. **Plan First**: Write plan to tasks/todo.md with checkable items
|
||||
2. **Verify Plan**: Check in before starting implementation
|
||||
3. **Track Progress**: Mark items complete as you go
|
||||
4. **Explain Changes**: High-level summary at each step
|
||||
5. **Document Results**: Add review section to "tasks/todo.md*
|
||||
6. **Capture Lessons**: Update "tasks/lessons.md" after corrections
|
||||
|
||||
122
config/claude/skills/digital-zettlekasten/SKILL.md
Normal file
122
config/claude/skills/digital-zettlekasten/SKILL.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
name: markdown-zettelkasten
|
||||
description: >
|
||||
Help users build and maintain a Zettelkasten (slip-box) knowledge system using plain markdown files.
|
||||
Use this skill whenever the user mentions Zettelkasten, slip-box, smart notes, permanent notes,
|
||||
literature notes, fleeting notes, or asks about note-taking systems, knowledge management,
|
||||
building a second brain, or connecting ideas across sources. Also trigger when the user wants to
|
||||
process reading notes, develop ideas from existing notes, or write from a collection of linked
|
||||
markdown notes — even if they don't use the word "Zettelkasten" explicitly. If someone asks
|
||||
how to organize their markdown notes for thinking and writing, this is the skill to use.
|
||||
---
|
||||
|
||||
# Markdown Zettelkasten
|
||||
|
||||
Help users create, process, connect, and write from a system of linked markdown notes based on the Zettelkasten method.
|
||||
|
||||
## When to Read the Reference
|
||||
|
||||
Before performing any of the operations below, read `references/zettelkasten-system-spec.md` for the full folder structure, note templates, naming conventions, linking syntax, and anti-pattern guidance. The reference contains the exact frontmatter schemas and file layouts to use.
|
||||
|
||||
## Core Operations
|
||||
|
||||
### 1. Initialize a Vault
|
||||
|
||||
When the user wants to set up a new Zettelkasten:
|
||||
|
||||
1. Read `references/zettelkasten-system-spec.md` § Folder Structure.
|
||||
2. Create the folder layout and all three template files.
|
||||
3. Create an empty `4-index/index.md` with the index frontmatter.
|
||||
4. Ask the user which markdown tool they use (Obsidian, Logseq, Foam, plain text) to determine link syntax — default to `[[wikilink]]` style.
|
||||
5. Briefly explain the purpose of each folder.
|
||||
|
||||
### 2. Create a Note
|
||||
|
||||
When the user wants to capture a thought or process something they've read:
|
||||
|
||||
1. Determine the note type (fleeting, literature, or permanent) based on context:
|
||||
- Quick idea, no source → fleeting
|
||||
- Reacting to a specific source → literature
|
||||
- Developed idea ready for the slip-box → permanent
|
||||
2. Use the appropriate template from the reference spec.
|
||||
3. Generate the correct filename using the naming convention for that type.
|
||||
4. For permanent notes: search existing notes for connections, suggest links, and add backlinks to related notes. Every permanent note must have at least one link.
|
||||
5. For fleeting notes: remind the user to process within 1–2 days.
|
||||
|
||||
### 3. Process the Inbox
|
||||
|
||||
When the user wants to review and clear their `0-inbox/`:
|
||||
|
||||
1. List all files in `0-inbox/`.
|
||||
2. For each fleeting note, present the content and ask: promote to permanent note, or delete?
|
||||
3. For notes being promoted:
|
||||
- Help the user write the idea in full sentences.
|
||||
- Search existing permanent notes for connections.
|
||||
- Suggest links (especially cross-domain connections — these are the most valuable).
|
||||
- Create the permanent note with proper frontmatter and links.
|
||||
- Add backlinks in the referenced existing notes.
|
||||
- Delete the original fleeting note.
|
||||
4. Report how many notes were processed, created, and deleted.
|
||||
|
||||
### 4. Process Literature Notes
|
||||
|
||||
When the user has created literature notes from reading and wants to extract permanent notes:
|
||||
|
||||
1. Read the literature note.
|
||||
2. For each idea worth developing, help the user create a permanent note:
|
||||
- Translate the idea into the user's own words and thinking context.
|
||||
- Set the `source` field to reference the literature note.
|
||||
- Find and create links to related permanent notes.
|
||||
3. The literature note stays in `1-literature/` — it is not deleted.
|
||||
|
||||
### 5. Suggest Connections
|
||||
|
||||
When the user adds a note or asks what connects to a given idea:
|
||||
|
||||
1. Search existing permanent notes for:
|
||||
- Shared tags or keywords.
|
||||
- Conceptual overlaps, especially across different domains.
|
||||
- Potential contradictions or qualifications.
|
||||
2. Present each suggestion as: "This might connect to [note title] because [brief reason]."
|
||||
3. For accepted connections, add links in both directions and annotate the relationship type (extension, cross-context, or contrast).
|
||||
|
||||
### 6. Update the Index
|
||||
|
||||
When a topic cluster has grown or the user asks to update navigation:
|
||||
|
||||
1. Check whether the relevant keyword already exists in `4-index/index.md`.
|
||||
2. If not, add a sparse entry pointing to 1–2 entry-point notes.
|
||||
3. If a topic has enough connected notes to need orientation, create or update an overview note in `4-index/` following the overview template in the reference spec.
|
||||
|
||||
### 7. Write from the Slip-Box
|
||||
|
||||
When the user wants to develop a topic into a written piece:
|
||||
|
||||
1. Help identify clusters — groups of densely connected permanent notes around a theme.
|
||||
2. Collect relevant notes into a project folder in `3-projects/`.
|
||||
3. Suggest an ordering that forms an argument or narrative.
|
||||
4. Identify gaps: missing steps, unsupported claims, unexplored counterpoints.
|
||||
5. Help the user translate notes into continuous prose — rewriting, building transitions, embedding in argument. Do not copy notes verbatim.
|
||||
|
||||
## Quality Checks
|
||||
|
||||
When reviewing a user's vault or a specific note, verify:
|
||||
|
||||
- **Permanent notes are atomic.** One idea per file. If a note covers multiple ideas, suggest splitting.
|
||||
- **Permanent notes use full sentences.** Bullet fragments are a sign the idea isn't developed yet.
|
||||
- **Every permanent note has links.** Flag orphan notes and help find connections.
|
||||
- **The inbox isn't accumulating.** If `0-inbox/` has notes older than a few days, prompt processing.
|
||||
- **No project bleed.** Outlines, drafts, and to-dos should be in `3-projects/`, not `2-permanent/`.
|
||||
- **Tags are sparse and contextual.** 2–3 per note maximum. Tags describe future usefulness, not current topic.
|
||||
- **No copy-pasted quotes in permanent notes.** Content must be in the user's own words.
|
||||
|
||||
## Principles to Keep in Mind
|
||||
|
||||
These come from the underlying method and should inform all guidance:
|
||||
|
||||
- The slip-box is a **thinking tool**, not an archive. The value is in connections and elaboration, not storage.
|
||||
- **Bottom-up over top-down.** Topics emerge from note clusters, not from predetermined categories. Resist the urge to pre-organize.
|
||||
- **Writing is thinking.** The act of writing a note in full sentences is where understanding happens. Skipping this step defeats the purpose.
|
||||
- **The system compounds.** Each note makes the next one easier and more connected. Early effort pays off exponentially.
|
||||
- **Constraints are features.** One idea per note, one format, one flat folder — these restrictions enable the recombination that generates insight.
|
||||
- **Don't force it.** If the user is stuck on one note or topic, encourage them to work on something else. The system supports parallel work across many projects.
|
||||
10
config/claude/skills/grill-me/SKILL.md
Normal file
10
config/claude/skills/grill-me/SKILL.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: grill-me
|
||||
description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
|
||||
---
|
||||
|
||||
Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
|
||||
|
||||
Ask the questions one at a time.
|
||||
|
||||
If a question can be answered by exploring the codebase, explore the codebase instead.
|
||||
15
config/claude/skills/handoff/SKILL.md
Normal file
15
config/claude/skills/handoff/SKILL.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: handoff
|
||||
description: Compact the current conversation into a handoff document for another agent to pick up.
|
||||
argument-hint: "What will the next session be used for?"
|
||||
---
|
||||
|
||||
Write a handoff document summarising the current conversation so a fresh agent can continue the work. Save to the temporary directory of the user's OS - not the current workspace.
|
||||
|
||||
Include a "suggested skills" section in the document, which suggests skills that the agent should invoke.
|
||||
|
||||
Do not duplicate content already captured in other artifacts (PRDs, plans, ADRs, issues, commits, diffs). Reference them by path or URL instead.
|
||||
|
||||
Redact any sensitive information, such as API keys, passwords, or personally identifiable information.
|
||||
|
||||
If the user passed arguments, treat them as a description of what the next session will focus on and tailor the doc accordingly.
|
||||
109
config/claude/skills/tdd/SKILL.md
Normal file
109
config/claude/skills/tdd/SKILL.md
Normal file
@@ -0,0 +1,109 @@
|
||||
---
|
||||
name: tdd
|
||||
description: Test-driven development with red-green-refactor loop. Use when user wants to build features or fix bugs using TDD, mentions "red-green-refactor", wants integration tests, or asks for test-first development.
|
||||
---
|
||||
|
||||
# Test-Driven Development
|
||||
|
||||
## Philosophy
|
||||
|
||||
**Core principle**: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.
|
||||
|
||||
**Good tests** are integration-style: they exercise real code paths through public APIs. They describe _what_ the system does, not _how_ it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure.
|
||||
|
||||
**Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.
|
||||
|
||||
See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines.
|
||||
|
||||
## Anti-Pattern: Horizontal Slices
|
||||
|
||||
**DO NOT write all tests first, then all implementation.** This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code."
|
||||
|
||||
This produces **crap tests**:
|
||||
|
||||
- Tests written in bulk test _imagined_ behavior, not _actual_ behavior
|
||||
- You end up testing the _shape_ of things (data structures, function signatures) rather than user-facing behavior
|
||||
- Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine
|
||||
- You outrun your headlights, committing to test structure before understanding the implementation
|
||||
|
||||
**Correct approach**: Vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle. Because you just wrote the code, you know exactly what behavior matters and how to verify it.
|
||||
|
||||
```
|
||||
WRONG (horizontal):
|
||||
RED: test1, test2, test3, test4, test5
|
||||
GREEN: impl1, impl2, impl3, impl4, impl5
|
||||
|
||||
RIGHT (vertical):
|
||||
RED→GREEN: test1→impl1
|
||||
RED→GREEN: test2→impl2
|
||||
RED→GREEN: test3→impl3
|
||||
...
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Planning
|
||||
|
||||
When exploring the codebase, use the project's domain glossary so that test names and interface vocabulary match the project's language, and respect ADRs in the area you're touching.
|
||||
|
||||
Before writing any code:
|
||||
|
||||
- [ ] Confirm with user what interface changes are needed
|
||||
- [ ] Confirm with user which behaviors to test (prioritize)
|
||||
- [ ] Identify opportunities for [deep modules](deep-modules.md) (small interface, deep implementation)
|
||||
- [ ] Design interfaces for [testability](interface-design.md)
|
||||
- [ ] List the behaviors to test (not implementation steps)
|
||||
- [ ] Get user approval on the plan
|
||||
|
||||
Ask: "What should the public interface look like? Which behaviors are most important to test?"
|
||||
|
||||
**You can't test everything.** Confirm with the user exactly which behaviors matter most. Focus testing effort on critical paths and complex logic, not every possible edge case.
|
||||
|
||||
### 2. Tracer Bullet
|
||||
|
||||
Write ONE test that confirms ONE thing about the system:
|
||||
|
||||
```
|
||||
RED: Write test for first behavior → test fails
|
||||
GREEN: Write minimal code to pass → test passes
|
||||
```
|
||||
|
||||
This is your tracer bullet - proves the path works end-to-end.
|
||||
|
||||
### 3. Incremental Loop
|
||||
|
||||
For each remaining behavior:
|
||||
|
||||
```
|
||||
RED: Write next test → fails
|
||||
GREEN: Minimal code to pass → passes
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- One test at a time
|
||||
- Only enough code to pass current test
|
||||
- Don't anticipate future tests
|
||||
- Keep tests focused on observable behavior
|
||||
|
||||
### 4. Refactor
|
||||
|
||||
After all tests pass, look for [refactor candidates](refactoring.md):
|
||||
|
||||
- [ ] Extract duplication
|
||||
- [ ] Deepen modules (move complexity behind simple interfaces)
|
||||
- [ ] Apply SOLID principles where natural
|
||||
- [ ] Consider what new code reveals about existing code
|
||||
- [ ] Run tests after each refactor step
|
||||
|
||||
**Never refactor while RED.** Get to GREEN first.
|
||||
|
||||
## Checklist Per Cycle
|
||||
|
||||
```
|
||||
[ ] Test describes behavior, not implementation
|
||||
[ ] Test uses public interface only
|
||||
[ ] Test would survive internal refactor
|
||||
[ ] Code is minimal for this test
|
||||
[ ] No speculative features added
|
||||
```
|
||||
33
config/claude/skills/tdd/deep-modules.md
Normal file
33
config/claude/skills/tdd/deep-modules.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Deep Modules
|
||||
|
||||
From "A Philosophy of Software Design":
|
||||
|
||||
**Deep module** = small interface + lots of implementation
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ Small Interface │ ← Few methods, simple params
|
||||
├─────────────────────┤
|
||||
│ │
|
||||
│ │
|
||||
│ Deep Implementation│ ← Complex logic hidden
|
||||
│ │
|
||||
│ │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
**Shallow module** = large interface + little implementation (avoid)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ Large Interface │ ← Many methods, complex params
|
||||
├─────────────────────────────────┤
|
||||
│ Thin Implementation │ ← Just passes through
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
When designing interfaces, ask:
|
||||
|
||||
- Can I reduce the number of methods?
|
||||
- Can I simplify the parameters?
|
||||
- Can I hide more complexity inside?
|
||||
31
config/claude/skills/tdd/interface-design.md
Normal file
31
config/claude/skills/tdd/interface-design.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Interface Design for Testability
|
||||
|
||||
Good interfaces make testing natural:
|
||||
|
||||
1. **Accept dependencies, don't create them**
|
||||
|
||||
```typescript
|
||||
// Testable
|
||||
function processOrder(order, paymentGateway) {}
|
||||
|
||||
// Hard to test
|
||||
function processOrder(order) {
|
||||
const gateway = new StripeGateway();
|
||||
}
|
||||
```
|
||||
|
||||
2. **Return results, don't produce side effects**
|
||||
|
||||
```typescript
|
||||
// Testable
|
||||
function calculateDiscount(cart): Discount {}
|
||||
|
||||
// Hard to test
|
||||
function applyDiscount(cart): void {
|
||||
cart.total -= discount;
|
||||
}
|
||||
```
|
||||
|
||||
3. **Small surface area**
|
||||
- Fewer methods = fewer tests needed
|
||||
- Fewer params = simpler test setup
|
||||
59
config/claude/skills/tdd/mocking.md
Normal file
59
config/claude/skills/tdd/mocking.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# When to Mock
|
||||
|
||||
Mock at **system boundaries** only:
|
||||
|
||||
- External APIs (payment, email, etc.)
|
||||
- Databases (sometimes - prefer test DB)
|
||||
- Time/randomness
|
||||
- File system (sometimes)
|
||||
|
||||
Don't mock:
|
||||
|
||||
- Your own classes/modules
|
||||
- Internal collaborators
|
||||
- Anything you control
|
||||
|
||||
## Designing for Mockability
|
||||
|
||||
At system boundaries, design interfaces that are easy to mock:
|
||||
|
||||
**1. Use dependency injection**
|
||||
|
||||
Pass external dependencies in rather than creating them internally:
|
||||
|
||||
```typescript
|
||||
// Easy to mock
|
||||
function processPayment(order, paymentClient) {
|
||||
return paymentClient.charge(order.total);
|
||||
}
|
||||
|
||||
// Hard to mock
|
||||
function processPayment(order) {
|
||||
const client = new StripeClient(process.env.STRIPE_KEY);
|
||||
return client.charge(order.total);
|
||||
}
|
||||
```
|
||||
|
||||
**2. Prefer SDK-style interfaces over generic fetchers**
|
||||
|
||||
Create specific functions for each external operation instead of one generic function with conditional logic:
|
||||
|
||||
```typescript
|
||||
// GOOD: Each function is independently mockable
|
||||
const api = {
|
||||
getUser: (id) => fetch(`/users/${id}`),
|
||||
getOrders: (userId) => fetch(`/users/${userId}/orders`),
|
||||
createOrder: (data) => fetch('/orders', { method: 'POST', body: data }),
|
||||
};
|
||||
|
||||
// BAD: Mocking requires conditional logic inside the mock
|
||||
const api = {
|
||||
fetch: (endpoint, options) => fetch(endpoint, options),
|
||||
};
|
||||
```
|
||||
|
||||
The SDK approach means:
|
||||
- Each mock returns one specific shape
|
||||
- No conditional logic in test setup
|
||||
- Easier to see which endpoints a test exercises
|
||||
- Type safety per endpoint
|
||||
10
config/claude/skills/tdd/refactoring.md
Normal file
10
config/claude/skills/tdd/refactoring.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Refactor Candidates
|
||||
|
||||
After TDD cycle, look for:
|
||||
|
||||
- **Duplication** → Extract function/class
|
||||
- **Long methods** → Break into private helpers (keep tests on public interface)
|
||||
- **Shallow modules** → Combine or deepen
|
||||
- **Feature envy** → Move logic to where data lives
|
||||
- **Primitive obsession** → Introduce value objects
|
||||
- **Existing code** the new code reveals as problematic
|
||||
61
config/claude/skills/tdd/tests.md
Normal file
61
config/claude/skills/tdd/tests.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Good and Bad Tests
|
||||
|
||||
## Good Tests
|
||||
|
||||
**Integration-style**: Test through real interfaces, not mocks of internal parts.
|
||||
|
||||
```typescript
|
||||
// GOOD: Tests observable behavior
|
||||
test("user can checkout with valid cart", async () => {
|
||||
const cart = createCart();
|
||||
cart.add(product);
|
||||
const result = await checkout(cart, paymentMethod);
|
||||
expect(result.status).toBe("confirmed");
|
||||
});
|
||||
```
|
||||
|
||||
Characteristics:
|
||||
|
||||
- Tests behavior users/callers care about
|
||||
- Uses public API only
|
||||
- Survives internal refactors
|
||||
- Describes WHAT, not HOW
|
||||
- One logical assertion per test
|
||||
|
||||
## Bad Tests
|
||||
|
||||
**Implementation-detail tests**: Coupled to internal structure.
|
||||
|
||||
```typescript
|
||||
// BAD: Tests implementation details
|
||||
test("checkout calls paymentService.process", async () => {
|
||||
const mockPayment = jest.mock(paymentService);
|
||||
await checkout(cart, payment);
|
||||
expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
|
||||
});
|
||||
```
|
||||
|
||||
Red flags:
|
||||
|
||||
- Mocking internal collaborators
|
||||
- Testing private methods
|
||||
- Asserting on call counts/order
|
||||
- Test breaks when refactoring without behavior change
|
||||
- Test name describes HOW not WHAT
|
||||
- Verifying through external means instead of interface
|
||||
|
||||
```typescript
|
||||
// BAD: Bypasses interface to verify
|
||||
test("createUser saves to database", async () => {
|
||||
await createUser({ name: "Alice" });
|
||||
const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
|
||||
expect(row).toBeDefined();
|
||||
});
|
||||
|
||||
// GOOD: Verifies through interface
|
||||
test("createUser makes user retrievable", async () => {
|
||||
const user = await createUser({ name: "Alice" });
|
||||
const retrieved = await getUser(user.id);
|
||||
expect(retrieved.name).toBe("Alice");
|
||||
});
|
||||
```
|
||||
138
config/claude/skills/webpage-summarize/SKILL.md
Normal file
138
config/claude/skills/webpage-summarize/SKILL.md
Normal file
@@ -0,0 +1,138 @@
|
||||
---
|
||||
name: web-readability
|
||||
description: Extract and summarize the main readable content from any web page URL using go-readability, a Go port of Mozilla's Readability.js. Use this skill whenever the user wants to pull article text from a website, extract clean content from a URL, summarize a web page, save a web page's main content as a markdown document, get metadata (title, author, excerpt) from a web page, or convert a cluttered web page into a clean readable summary. Also trigger when the user says things like "grab the content from this URL", "read this article", "extract text from this page", "save this web page", "summarize this link", "get the article from this link", or "pull the document from this site". By default, extracted content is summarized into a markdown document with a source URL reference. This is the preferred approach for fetching, cleaning, and summarizing web page content.
|
||||
---
|
||||
|
||||
# Web Readability
|
||||
|
||||
Extract clean, readable content from web pages using the `go-readability` CLI from [go-shiori/go-readability](https://github.com/go-shiori/go-readability). This is a Go port of Mozilla's Readability.js — the same engine behind Firefox Reader View. It strips ads, navigation, scripts, and clutter, returning just the main article content and metadata.
|
||||
|
||||
## Setup
|
||||
|
||||
Install the CLI (once per session, if not already present):
|
||||
|
||||
```bash
|
||||
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
|
||||
|
||||
# Install Go if missing
|
||||
if ! command -v go &> /dev/null; then
|
||||
cd /tmp && curl -sLO https://go.dev/dl/go1.23.8.linux-amd64.tar.gz \
|
||||
&& tar -C /usr/local -xzf go1.23.8.linux-amd64.tar.gz
|
||||
fi
|
||||
|
||||
# Install go-readability CLI if missing
|
||||
if ! command -v go-readability &> /dev/null; then
|
||||
go install github.com/go-shiori/go-readability/cmd/go-readability@latest
|
||||
fi
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```
|
||||
go-readability [flags] <source>
|
||||
```
|
||||
|
||||
The source can be a URL or a path to a local HTML file.
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-t, --text` | Output only the page's extracted plain text (no HTML) |
|
||||
| `-m, --metadata` | Output only the page's metadata as JSON (title, byline, excerpt, image, favicon) |
|
||||
| `-l, --http <addr>` | Start an HTTP server at the given address for batch use |
|
||||
| (no flags) | Output the cleaned HTML of the main article content |
|
||||
|
||||
### Output Modes
|
||||
|
||||
**Default (cleaned HTML):**
|
||||
Returns the article's main content as clean HTML with clutter removed. Useful when preserving links, formatting, and structure.
|
||||
|
||||
**Text only (`-t`):**
|
||||
Returns just the plain text content. Best for summarization, search indexing, or feeding into other text-processing tools.
|
||||
|
||||
**Metadata only (`-m`):**
|
||||
Returns JSON with: `title`, `byline`, `excerpt`, `image`, `favicon`. Use this for quick inspection or to decide whether to fetch the full content.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Extract article text for summarization
|
||||
```bash
|
||||
go-readability -t "https://example.com/article"
|
||||
```
|
||||
|
||||
### Get metadata to preview before extracting
|
||||
```bash
|
||||
go-readability -m "https://example.com/article"
|
||||
```
|
||||
|
||||
### Save cleaned HTML to a file
|
||||
```bash
|
||||
go-readability "https://example.com/article" > /home/claude/article.html
|
||||
```
|
||||
|
||||
### Save plain text to a file
|
||||
```bash
|
||||
go-readability -t "https://example.com/article" > /home/claude/article.txt
|
||||
```
|
||||
|
||||
### Extract from a local HTML file
|
||||
```bash
|
||||
go-readability -t /path/to/saved-page.html
|
||||
```
|
||||
|
||||
### Batch extraction
|
||||
```bash
|
||||
for url in "https://example.com/a" "https://example.com/b"; do
|
||||
echo "=== $url ==="
|
||||
go-readability -t "$url"
|
||||
echo
|
||||
done
|
||||
```
|
||||
|
||||
## Summarize to Markdown
|
||||
|
||||
After extracting content, always produce a markdown summary document unless the user explicitly asks for raw text or HTML only. Follow these steps:
|
||||
|
||||
1. Fetch metadata with `-m` to get the title, byline, and excerpt.
|
||||
2. Fetch full text with `-t`.
|
||||
3. Summarize the content and write a `.md` file using this template:
|
||||
|
||||
```markdown
|
||||
# {title}
|
||||
|
||||
**Source:** [{url}]({url})
|
||||
**Author:** {byline}
|
||||
|
||||
## Summary
|
||||
|
||||
{A concise summary of the article's main points, written by Claude. Aim for 3-8 paragraphs
|
||||
depending on the length and complexity of the source material. Capture the key arguments,
|
||||
findings, or narrative. Use subheadings if the article covers multiple distinct topics.}
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
{3-7 bullet points highlighting the most important facts, conclusions, or action items.}
|
||||
|
||||
---
|
||||
|
||||
*Extracted and summarized from [{title}]({url})*
|
||||
```
|
||||
|
||||
Omit the Author line if byline is empty. Adjust the summary length proportionally to the source — a short blog post gets a brief summary, a long-form investigation gets a more detailed one.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Ensure `go-readability` is installed (run the setup commands above).
|
||||
2. Always set PATH: `export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin`
|
||||
3. If the user provides a bare domain, prepend `https://`.
|
||||
4. Fetch metadata (`-m`) and text (`-t`) from the URL.
|
||||
5. Summarize the extracted text into a markdown document using the template above.
|
||||
6. Save the `.md` file to `/home/claude/` then copy to `/mnt/user-data/outputs/`.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Only works on pages that serve HTML. Will fail on direct PDF/image URLs.
|
||||
- Does not execute JavaScript, so JS-heavy SPAs may yield poor results.
|
||||
- Pages behind authentication will fail.
|
||||
- Some non-article pages (homepages, indexes) may not extract meaningful content.
|
||||
208
config/claude/skills/youtube-summarize/SKILL.md
Normal file
208
config/claude/skills/youtube-summarize/SKILL.md
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
name: youtube-transcript
|
||||
description: Use this skill whenever the user wants to pull, fetch, or read a YouTube video transcript or subtitles. Triggers include any mention of "YouTube transcript", "video subtitles", "captions", a YouTube URL with a request to get text/transcript, or asking what was said in a YouTube video. Also use when the user wants to summarize, analyze, or extract information from a YouTube video's spoken content.
|
||||
---
|
||||
|
||||
# YouTube Transcript Skill
|
||||
|
||||
Pull transcripts/subtitles from YouTube videos using the `youtube-transcript-api` Python library.
|
||||
|
||||
## Setup
|
||||
|
||||
Install the library first (once per session):
|
||||
|
||||
```bash
|
||||
pip install youtube-transcript-api --break-system-packages
|
||||
```
|
||||
|
||||
## Extracting the Video ID
|
||||
|
||||
YouTube URLs come in many formats. Extract the video ID from the user's input:
|
||||
|
||||
| URL Format | Video ID Location |
|
||||
|---|---|
|
||||
| `https://www.youtube.com/watch?v=VIDEO_ID` | `v` query parameter |
|
||||
| `https://youtu.be/VIDEO_ID` | path segment |
|
||||
| `https://www.youtube.com/embed/VIDEO_ID` | path segment |
|
||||
| `https://www.youtube.com/v/VIDEO_ID` | path segment |
|
||||
| `https://www.youtube.com/shorts/VIDEO_ID` | path segment |
|
||||
| Just a raw ID like `dQw4w9WgXcQ` | use directly |
|
||||
|
||||
Also strip any extra query params (like `&t=120`, `&list=...`, etc.) — you only need the 11-character video ID.
|
||||
|
||||
### Transcript Object Structure
|
||||
|
||||
`transcript.snippets` is a list of snippet objects, each with:
|
||||
- `text` — the spoken text
|
||||
- `start` — start time in seconds (float)
|
||||
- `duration` — duration in seconds (float)
|
||||
|
||||
## CLI Usage
|
||||
|
||||
The library also provides a CLI:
|
||||
|
||||
```bash
|
||||
# Basic fetch
|
||||
youtube_transcript_api VIDEO_ID
|
||||
|
||||
# With language preference
|
||||
youtube_transcript_api VIDEO_ID --languages en de
|
||||
|
||||
# JSON output
|
||||
youtube_transcript_api VIDEO_ID --format json
|
||||
|
||||
# Exclude auto-generated or manual subtitles
|
||||
youtube_transcript_api VIDEO_ID --exclude-generated
|
||||
youtube_transcript_api VIDEO_ID --exclude-manually-created
|
||||
|
||||
# List available transcripts
|
||||
youtube_transcript_api --list-transcripts VIDEO_ID
|
||||
|
||||
# Translate
|
||||
youtube_transcript_api VIDEO_ID --translate de
|
||||
```
|
||||
|
||||
## Environment Notes
|
||||
|
||||
This skill works in two environments:
|
||||
|
||||
### Local environments (Claude Code, local scripts) — preferred
|
||||
When running locally (e.g. via Claude Code on the user's machine), YouTube transcript fetching should **just work** since requests come from a residential IP. Simply install the library, fetch the transcript, and summarize. No special handling needed.
|
||||
|
||||
### Cloud environments (claude.ai, hosted containers)
|
||||
**YouTube blocks most cloud/server IPs.** If you get an `IpBlocked` or `RequestBlocked` error, this is expected.
|
||||
|
||||
When this happens:
|
||||
1. **Tell the user** the fetch failed because YouTube blocks cloud IPs.
|
||||
2. **Offer the CLI alternative**: Suggest the user run the command locally:
|
||||
```
|
||||
pip install youtube-transcript-api
|
||||
youtube_transcript_api VIDEO_ID --format json > transcript.json
|
||||
```
|
||||
Then they can upload the resulting file for processing.
|
||||
3. **Offer the proxy option**: The library supports proxies via `WebshareProxyConfig` or `GenericProxyConfig` if the user has proxy credentials.
|
||||
|
||||
## Output Handling
|
||||
|
||||
**Default behavior: Summarize the video with timestamped references.** After fetching or receiving a transcript, Claude should:
|
||||
|
||||
1. Keep the raw snippets with timestamps intact (do NOT flatten with `TextFormatter` for the working copy)
|
||||
2. **Summarize the content** — provide a concise, well-structured summary with clickable YouTube timestamp links for key quotes and moments
|
||||
3. Only save the raw transcript to a file if the user explicitly asks for it
|
||||
|
||||
### Summarization Approach
|
||||
|
||||
After obtaining the transcript (keep the raw snippets with timestamps — don't discard them):
|
||||
|
||||
- **Lead with a 2-3 sentence TL;DR** of the video's main point or thesis
|
||||
- **Follow with key topics/takeaways** covered in the video, in the order they appeared
|
||||
- **Include timestamped quotes and key moments** — for notable quotes, important data points, strong arguments, or memorable moments, include:
|
||||
- The quote or paraphrase
|
||||
- A clickable YouTube timestamp link in the format: `https://www.youtube.com/watch?v=VIDEO_ID&t=SECONDs`
|
||||
- Example: `[12:34](https://www.youtube.com/watch?v=VIDEO_ID&t=754s)` — "This is the exact quote from the speaker"
|
||||
- **Keep it proportional**: a 5-minute video gets a short paragraph; a 1-hour lecture gets a more detailed breakdown
|
||||
- If the transcript is very long (>50,000 chars), process it in chunks but still produce a unified summary
|
||||
|
||||
### Building Timestamp Links
|
||||
|
||||
YouTube supports linking to a specific time using the `&t=` parameter in seconds.
|
||||
|
||||
```python
|
||||
def make_yt_link(video_id: str, start_seconds: float) -> str:
|
||||
"""Generate a YouTube URL that jumps to a specific timestamp."""
|
||||
t = int(start_seconds)
|
||||
return f"https://www.youtube.com/watch?v={video_id}&t={t}s"
|
||||
|
||||
def format_timestamp(seconds: float) -> str:
|
||||
"""Convert seconds to HH:MM:SS or MM:SS display format."""
|
||||
h, remainder = divmod(int(seconds), 3600)
|
||||
m, s = divmod(remainder, 60)
|
||||
if h > 0:
|
||||
return f"{h}:{m:02d}:{s:02d}"
|
||||
return f"{m}:{s:02d}"
|
||||
|
||||
# Usage in summary:
|
||||
# [12:34](https://www.youtube.com/watch?v=VIDEO_ID&t=754s) — "Notable quote here"
|
||||
```
|
||||
|
||||
### Preserving Timestamps During Processing
|
||||
|
||||
Do NOT use `TextFormatter` for the working copy — it strips timestamps. Instead, work directly with the transcript snippets:
|
||||
|
||||
```python
|
||||
ytt_api = YouTubeTranscriptApi()
|
||||
transcript = ytt_api.fetch(video_id)
|
||||
|
||||
# Build full text for summarization context while retaining timestamps
|
||||
segments = []
|
||||
for snippet in transcript.snippets:
|
||||
segments.append({
|
||||
"text": snippet.text,
|
||||
"start": snippet.start,
|
||||
"link": make_yt_link(video_id, snippet.start)
|
||||
})
|
||||
|
||||
# Join into plain text for reading/summarization
|
||||
full_text = " ".join(s["text"] for s in segments)
|
||||
```
|
||||
|
||||
When summarizing, reference the `start` times from the snippets to generate accurate timestamp links for any quotes or key moments you call out.
|
||||
|
||||
### When to save files instead of summarizing
|
||||
|
||||
Only save raw transcript files when the user explicitly asks for:
|
||||
- "Give me the transcript" / "I want the full text"
|
||||
- "Save it as SRT/VTT/JSON"
|
||||
- "Export the captions"
|
||||
|
||||
In those cases, save to `/mnt/user-data/outputs/` in the requested format (`.txt`, `.srt`, `.vtt`, `.json`).
|
||||
|
||||
### Processing uploaded transcript files
|
||||
|
||||
If the user uploads a transcript file (because the cloud fetch failed and they ran the CLI locally):
|
||||
- Detect the format (JSON from `--format json`, or raw SRT/text)
|
||||
- Parse it into plain text
|
||||
- Summarize it using the same approach above
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
# If user uploaded JSON from: youtube_transcript_api VIDEO_ID --format json
|
||||
with open("transcript.json", "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# The JSON output is a list of dicts with "text", "start", "duration"
|
||||
full_text = " ".join(entry["text"] for entry in data)
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Common exceptions to catch:
|
||||
- `IpBlocked` / `RequestBlocked` — YouTube blocking cloud IPs (most common in hosted environments)
|
||||
- `TranscriptsDisabled` — Video has no transcripts/subtitles available
|
||||
- `NoTranscriptFound` — No transcript in the requested language
|
||||
- `VideoUnavailable` — Video doesn't exist or is private
|
||||
|
||||
```python
|
||||
from youtube_transcript_api._errors import (
|
||||
TranscriptsDisabled,
|
||||
NoTranscriptFound,
|
||||
VideoUnavailable,
|
||||
IpBlocked,
|
||||
RequestBlocked,
|
||||
)
|
||||
```
|
||||
|
||||
## Workflow Summary
|
||||
|
||||
1. Install `youtube-transcript-api` if not already installed: `pip install youtube-transcript-api`
|
||||
2. Parse the video ID from the user's URL/input
|
||||
3. Fetch the transcript using the Python API (keep raw snippets with timestamps)
|
||||
4. If `IpBlocked`/`RequestBlocked` (cloud environment), inform the user and offer the local CLI workaround
|
||||
5. **Summarize the video content** with:
|
||||
- TL;DR of the main point
|
||||
- Key takeaways in order
|
||||
- Clickable YouTube timestamp links for notable quotes and moments (e.g. `[12:34](https://www.youtube.com/watch?v=VIDEO_ID&t=754s)`)
|
||||
6. Only save raw transcript files if the user explicitly requests them
|
||||
|
||||
In Claude Code or other local environments, steps 1-3 should work without issue since YouTube won't block residential IPs. Just fetch and summarize.
|
||||
@@ -107,6 +107,12 @@ if is_linux; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Link Claude configuration
|
||||
log_info "Setting up Claude configuration"
|
||||
ensure_dir "$HOME/.claude"
|
||||
safe_symlink "$SCRIPT_DIR/claude/CLAUDE.md" "$HOME/.claude/CLAUDE.md"
|
||||
safe_symlink "$SCRIPT_DIR/claude/skills" "$HOME/.claude/skills"
|
||||
|
||||
# Install VSCode extensions
|
||||
if command_exists codium || command_exists code; then
|
||||
if [ -f "$SCRIPT_DIR/vscode-extensions.txt" ]; then
|
||||
|
||||
Reference in New Issue
Block a user