Compare commits
2 Commits
6f6f4204ee
...
b233fe41e7
| Author | SHA1 | Date | |
|---|---|---|---|
| b233fe41e7 | |||
| 47241b8421 |
119
.claude/skills/improve-codebase/HTML-REPORT.md
Normal file
119
.claude/skills/improve-codebase/HTML-REPORT.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# HTML Report Format
|
||||||
|
|
||||||
|
The architectural review is rendered as a single self-contained HTML file in the OS temp directory. Tailwind and Mermaid both come from CDNs. Mermaid handles graph-shaped diagrams reliably; hand-built divs and inline SVG handle the more editorial visuals (mass diagrams, cross-sections). Mix the two — don't lean on Mermaid for everything, it'll start to look generic.
|
||||||
|
|
||||||
|
## Scaffold
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Architecture review — {{repository name}}</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
||||||
|
mermaid.initialize({ startOnLoad: true, theme: "neutral", securityLevel: "loose" });
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/* small custom layer for things Tailwind doesn't cover cleanly:
|
||||||
|
dashed seam lines, hand-drawn-feeling arrow heads, etc. */
|
||||||
|
.seam { stroke-dasharray: 4 4; }
|
||||||
|
.leak { stroke: #dc2626; }
|
||||||
|
.deep { background: linear-gradient(135deg, #0f172a, #1e293b); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-stone-50 text-slate-900 font-sans">
|
||||||
|
<main class="max-w-5xl mx-auto px-6 py-12 space-y-12">
|
||||||
|
<header>...</header>
|
||||||
|
<section id="candidates" class="space-y-10">...</section>
|
||||||
|
<section id="top-recommendation">...</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Header
|
||||||
|
|
||||||
|
Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph — straight into the candidates.
|
||||||
|
|
||||||
|
## Candidate card
|
||||||
|
|
||||||
|
The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms (from the `/codebase-design` skill) without ceremony.
|
||||||
|
|
||||||
|
Each candidate is one `<article>`:
|
||||||
|
|
||||||
|
- **Title** — short, names the deepening (e.g. "Collapse the Order intake pipeline").
|
||||||
|
- **Badge row** — recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`).
|
||||||
|
- **Files** — monospaced list, `font-mono text-sm`.
|
||||||
|
- **Before / After diagram** — the centrepiece. Two columns, side by side. See patterns below.
|
||||||
|
- **Problem** — one sentence. What hurts.
|
||||||
|
- **Solution** — one sentence. What changes.
|
||||||
|
- **Wins** — bullets, ≤6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers".
|
||||||
|
- **ADR callout** (if applicable) — one line in an amber-tinted box.
|
||||||
|
|
||||||
|
No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram.
|
||||||
|
|
||||||
|
## Diagram patterns
|
||||||
|
|
||||||
|
Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same — variety is part of the point.
|
||||||
|
|
||||||
|
### Mermaid graph (the workhorse for dependencies / call flow)
|
||||||
|
|
||||||
|
Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1."
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||||||
|
<pre class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
A[OrderHandler] --> B[OrderValidator]
|
||||||
|
B --> C[OrderRepo]
|
||||||
|
C -.leak.-> D[PricingClient]
|
||||||
|
classDef leak stroke:#dc2626,stroke-width:2px;
|
||||||
|
class C,D leak
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hand-built boxes-and-arrows (when Mermaid's layout fights you)
|
||||||
|
|
||||||
|
Modules as `<div>`s with borders and labels. Arrows as inline SVG `<line>` or `<path>` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals — Mermaid won't render that with the right weight.
|
||||||
|
|
||||||
|
### Cross-section (good for layered shallowness)
|
||||||
|
|
||||||
|
Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility.
|
||||||
|
|
||||||
|
### Mass diagram (good for "interface as wide as implementation")
|
||||||
|
|
||||||
|
Two rectangles per module — one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep).
|
||||||
|
|
||||||
|
### Call-graph collapse
|
||||||
|
|
||||||
|
Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it.
|
||||||
|
|
||||||
|
## Style guidance
|
||||||
|
|
||||||
|
- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate).
|
||||||
|
- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings.
|
||||||
|
- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling.
|
||||||
|
- Use `text-xs uppercase tracking-wider` for module labels inside diagrams — they should read as schematic, not as UI.
|
||||||
|
- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static — no app code, no interactivity beyond Mermaid's own rendering.
|
||||||
|
|
||||||
|
## Top recommendation section
|
||||||
|
|
||||||
|
One larger card. Candidate name, one sentence on why, anchor link to its card. That's it.
|
||||||
|
|
||||||
|
## Tone
|
||||||
|
|
||||||
|
Plain English, concise — but the architectural nouns and verbs come straight from the `/codebase-design` glossary, terms and exclusions alike. Concision is not an excuse to drift.
|
||||||
|
|
||||||
|
**Phrasings that fit the style:**
|
||||||
|
|
||||||
|
- "Order intake module is shallow — interface nearly matches the implementation."
|
||||||
|
- "Pricing leaks across the seam."
|
||||||
|
- "Deepen: one interface, one place to test."
|
||||||
|
- "Two adapters justify the seam: HTTP in prod, in-memory in tests."
|
||||||
|
|
||||||
|
**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"* — those terms aren't in the glossary and don't earn their place.
|
||||||
|
|
||||||
|
No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in the `/codebase-design` glossary, reach for one that is before inventing a new one.
|
||||||
68
.claude/skills/improve-codebase/SKILL.md
Normal file
68
.claude/skills/improve-codebase/SKILL.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
name: improve-codebase
|
||||||
|
description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
|
||||||
|
disable-model-invocation: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Improve Codebase
|
||||||
|
|
||||||
|
Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability.
|
||||||
|
|
||||||
|
This command is _informed_ by the project's domain model and built on a shared design vocabulary:
|
||||||
|
|
||||||
|
- Run the `/codebase-design` skill for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use its terms exactly in every suggestion, per its glossary.
|
||||||
|
- The domain language in `.claude/CONTEXT.md` gives names to good seams; ADRs in `.claude/adr/` record decisions this command should not re-litigate.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
### 1. Explore
|
||||||
|
|
||||||
|
Read the project's domain glossary (`.claude/CONTEXT.md`) and any ADRs in the area you're touching first.
|
||||||
|
|
||||||
|
Then use the Agent tool with `subagent_type=Explore` to walk every top-level module or directory in scope (the whole repository, or the area the user pointed you to) — even if only briefly for the ones that turn out clean. Within each, judge friction organically rather than against a rigid checklist:
|
||||||
|
|
||||||
|
- Where does understanding one concept require bouncing between many small modules?
|
||||||
|
- Where are modules **shallow** — interface nearly as complex as the implementation?
|
||||||
|
- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)?
|
||||||
|
- Where do tightly-coupled modules leak across their seams?
|
||||||
|
- Which parts of the codebase are untested, or hard to test through their current interface?
|
||||||
|
|
||||||
|
Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want.
|
||||||
|
|
||||||
|
Zero candidates is a legitimate outcome for a genuinely clean area — but it has to follow from having looked, not from stopping early.
|
||||||
|
|
||||||
|
### 2. Present candidates as an HTML report
|
||||||
|
|
||||||
|
Write a self-contained HTML file to the OS temp directory so nothing lands in the repository. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `<tmpdir>/architecture-review-<timestamp>.html` so each run gets a fresh file. Open it for the user — `xdg-open <path>` on Linux, `open <path>` on macOS, `start <path>` on Windows. Treat the open as best-effort: it's a no-op in a headless/sandboxed environment with no display server, so report the absolute path regardless of whether the open succeeded.
|
||||||
|
|
||||||
|
The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals — use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual.
|
||||||
|
|
||||||
|
For each candidate, render a card with:
|
||||||
|
|
||||||
|
- **Files** — which files/modules are involved
|
||||||
|
- **Problem** — why the current architecture is causing friction
|
||||||
|
- **Solution** — plain English description of what would change
|
||||||
|
- **Benefits** — explained in terms of locality and leverage, and how tests would improve
|
||||||
|
- **Before / After diagram** — side-by-side, custom-drawn, illustrating the shallowness and the deepening
|
||||||
|
- **Recommendation strength** — one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge
|
||||||
|
|
||||||
|
End the report with a **Top recommendation** section: which candidate you'd tackle first and why.
|
||||||
|
|
||||||
|
**Use `.claude/CONTEXT.md` vocabulary for the domain, and the `/codebase-design` vocabulary for the architecture.** If `.claude/CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service."
|
||||||
|
|
||||||
|
**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids.
|
||||||
|
|
||||||
|
See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance.
|
||||||
|
|
||||||
|
Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?"
|
||||||
|
|
||||||
|
### 3. Grilling loop
|
||||||
|
|
||||||
|
Once the user picks a candidate, run `/grill` to walk the design tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive.
|
||||||
|
|
||||||
|
Side effects happen inline as decisions crystallize — run `/domain-modeling` to keep the domain model current as you go, even if `.claude/CONTEXT.md` doesn't exist yet:
|
||||||
|
|
||||||
|
- **Naming a deepened module after a concept not in `.claude/CONTEXT.md`?** Add the term to `.claude/CONTEXT.md`. Create the file lazily if it doesn't exist.
|
||||||
|
- **Sharpening a fuzzy term during the conversation?** Update `.claude/CONTEXT.md` right there.
|
||||||
|
- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones.
|
||||||
|
- **Want to explore alternative interfaces for the deepened module?** Run the `/codebase-design` skill and use its design-it-twice parallel sub-agent pattern.
|
||||||
152
.claude/skills/library/nbdev/SKILL.md
Normal file
152
.claude/skills/library/nbdev/SKILL.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
name: nbdev
|
||||||
|
description: nbdev conventions for notebooks — directives, cell structure, docments, tests, execution. Use for any .ipynb operation — including reads — in an nbdev project.
|
||||||
|
---
|
||||||
|
|
||||||
|
# nbdev
|
||||||
|
|
||||||
|
## Tool Preference
|
||||||
|
|
||||||
|
- Use the **Jupyter MCP** for all `.ipynb` operations — read, edit, insert, delete, execute
|
||||||
|
- Do **not** use the built-in `NotebookEdit` tool; it writes cell source as a single JSON string which breaks standard Jupyter formatting and produces noisy diffs
|
||||||
|
- Re-read the notebook before editing if it may have changed since your last read — cell indices/IDs can shift under concurrent edits (e.g. via JupyterLab's real-time collaboration), and editing by a stale index can hit the wrong cell
|
||||||
|
|
||||||
|
## nbdev Directives
|
||||||
|
|
||||||
|
Directives are comments at the top of a cell that control how nbdev processes it:
|
||||||
|
|
||||||
|
- `#| export` — include this cell in the exported Python module and in the docs
|
||||||
|
- `#| hide` — exclude this cell from both the module and the docs
|
||||||
|
- `#| hide_input` — show cell output in docs but hide the source code
|
||||||
|
- `#| default_exp module_name` — set which module this notebook exports to (second cell)
|
||||||
|
- `#| exporti` — export to module but do not show in docs (for internal helpers)
|
||||||
|
- `#| eval: false` — include in docs but do not execute during `nbdev-test`
|
||||||
|
|
||||||
|
Imports needed only for tests or examples should **not** be exported.
|
||||||
|
|
||||||
|
Never hand-edit the exported `.py` module files — they're build artifacts regenerated from the notebook by `nbdev_export`. All edits go through the source notebook in `nbs/`.
|
||||||
|
|
||||||
|
## Notebook Structure
|
||||||
|
|
||||||
|
Every notebook must follow this structure:
|
||||||
|
|
||||||
|
**Cell 1 — Markdown frontmatter:**
|
||||||
|
```markdown
|
||||||
|
# Module Title
|
||||||
|
|
||||||
|
> A one-line description of what this module does
|
||||||
|
```
|
||||||
|
The H1 becomes the page title in docs. The blockquote becomes the subtitle.
|
||||||
|
|
||||||
|
**Cell 2 — Default export:**
|
||||||
|
```python
|
||||||
|
#| default_exp module_name
|
||||||
|
```
|
||||||
|
|
||||||
|
**Body cells** — alternating between exported code, demonstrations, and markdown explanations (see Cell Structure below).
|
||||||
|
|
||||||
|
**Last cell:**
|
||||||
|
```python
|
||||||
|
#| hide
|
||||||
|
import nbdev; nbdev.nbdev_export()
|
||||||
|
```
|
||||||
|
|
||||||
|
Before declaring any notebook task complete, restart the kernel and run all cells top-to-bottom to verify it is fully reproducible.
|
||||||
|
|
||||||
|
## Cell Structure
|
||||||
|
|
||||||
|
Keep cells short. Each exported function gets its own cell, immediately followed by a demonstration. Do not write long functions with comments interspersed — split them into small separate cells with explanations and working examples after each.
|
||||||
|
|
||||||
|
The pattern per concept:
|
||||||
|
|
||||||
|
1. *(Optional)* A markdown cell explaining what comes next
|
||||||
|
2. A `#| export` code cell with the function
|
||||||
|
3. One or more plain code cells demonstrating usage
|
||||||
|
4. Assertions that double as tests
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
#| export
|
||||||
|
def slugify(text: str) -> str:
|
||||||
|
"Convert text to a URL-safe slug"
|
||||||
|
return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
slug = slugify("Hello, World!")
|
||||||
|
assert slug == "hello-world"
|
||||||
|
slug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docstrings and Parameter Documentation
|
||||||
|
|
||||||
|
Keep docstrings short — a single-line summary is sufficient for most functions. Elaborate in separate markdown or code cells below, where you can use real examples.
|
||||||
|
|
||||||
|
Use **docments** (inline parameter comments) instead of verbose docstring parameter sections:
|
||||||
|
|
||||||
|
```python
|
||||||
|
#| export
|
||||||
|
def greet(
|
||||||
|
name: str, # Person to greet
|
||||||
|
greeting: str="Hi", # Greeting word to use
|
||||||
|
) -> str: # The composed greeting
|
||||||
|
"Compose a greeting for name"
|
||||||
|
return f"{greeting}, {name}!"
|
||||||
|
```
|
||||||
|
|
||||||
|
This renders as a clean parameter table in the docs automatically — no need to repeat type information in the docstring body.
|
||||||
|
|
||||||
|
Use backticks around symbol names in docstrings and markdown — nbdev automatically converts these to hyperlinks to the relevant reference page.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- **Prefer composition**: write small functions that do one thing well
|
||||||
|
- Each exported function should be focused enough to fit naturally in a single notebook cell — one cell, one idea
|
||||||
|
- Use type hints on all exported functions
|
||||||
|
- Avoid classes unless state is genuinely needed — prefer functions that take and return data
|
||||||
|
- If you do write a class, use `fastcore`'s `@patch` decorator to define each method in its own cell, immediately followed by a demonstration. This avoids long class definitions and keeps examples close to the code
|
||||||
|
|
||||||
|
When a class is needed, document its methods with `show_doc`:
|
||||||
|
```python
|
||||||
|
from nbdev.showdoc import show_doc
|
||||||
|
show_doc(MyClass.my_method)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Every code cell is run as a test by nbdev unless explicitly marked otherwise — any exception fails the test.
|
||||||
|
|
||||||
|
- Turn demonstrations into tests by adding `assert` statements
|
||||||
|
- Use `fastcore.test` helpers for better error messages:
|
||||||
|
```python
|
||||||
|
from fastcore.test import test_eq, test_fail
|
||||||
|
test_eq(slugify("Hello World"), "hello-world")
|
||||||
|
```
|
||||||
|
- Document expected error cases with `test_fail`:
|
||||||
|
```python
|
||||||
|
test_fail(lambda: slugify(""), contains="empty")
|
||||||
|
```
|
||||||
|
- Each test/demo cell should import what it needs directly — don't rely on a name imported in a later cell just because it happened to be in scope during a prior run
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
- Always execute cells after writing them to verify they work
|
||||||
|
- If a cell errors, read the full traceback before attempting a fix — do not guess
|
||||||
|
- When installing packages, use `%pip install` inside the notebook (not `!pip install`) so they install into the running kernel
|
||||||
|
- Use autoreload at the top of notebooks that import from other modules in the project:
|
||||||
|
```python
|
||||||
|
%load_ext autoreload
|
||||||
|
%autoreload 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- Use H2 (`##`) markdown cells to group related symbols within a notebook
|
||||||
|
- Use H4 (`####`) markdown cells to split long explanations within a symbol's section (notes, examples, edge cases, etc.)
|
||||||
|
- Add rich representations to classes via `_repr_markdown_` where it aids understanding
|
||||||
|
- Include real code examples, plots, and diagrams — notebooks support rich output, use it
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
- Never print secrets, tokens, passwords, or API keys into cell output — notebook outputs get committed to git and published in docs, unlike transient script output
|
||||||
|
- Prefer summaries over dumping large data structures (`.head()`, `len()`, `[:5]`, etc.)
|
||||||
|
- Large outputs consume context window — keep them concise
|
||||||
37
.claude/skills/remove-skills/SKILL.md
Normal file
37
.claude/skills/remove-skills/SKILL.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: remove-skills
|
||||||
|
description: Remove one or more previously added library skills from the current project.
|
||||||
|
disable-model-invocation: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Removes a skill that [`setup-skills`](../setup-skills/SKILL.md) previously
|
||||||
|
copied into the current project, deleting both its files and its entry in
|
||||||
|
`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md)
|
||||||
|
for its schema).
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell
|
||||||
|
the user there's nothing installed to remove and stop.
|
||||||
|
|
||||||
|
2. Determine which skill(s) to remove:
|
||||||
|
- If the user's invocation already named a specific skill, use that —
|
||||||
|
if it isn't in the lockfile, say so and stop.
|
||||||
|
- Otherwise, list every skill currently in the lockfile and ask the
|
||||||
|
user to pick one (or more).
|
||||||
|
|
||||||
|
3. For each skill to remove, compute its current hash
|
||||||
|
(`~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/<name>`)
|
||||||
|
and compare it to the hash stored in the lockfile:
|
||||||
|
- If it matches (never modified since it was installed), delete
|
||||||
|
`.claude/skills/<name>/` and remove its lockfile entry immediately —
|
||||||
|
no confirmation needed, since nothing of the user's is being lost.
|
||||||
|
- If it differs (locally customized), tell the user it has local
|
||||||
|
changes that will be permanently lost and ask for confirmation
|
||||||
|
before deleting. If they decline, leave that skill installed and
|
||||||
|
move on to the next.
|
||||||
|
|
||||||
|
4. Finish with a summary of what was removed and what was left in place.
|
||||||
|
|
||||||
|
Done when every skill to remove has been either deleted (with its lockfile
|
||||||
|
entry removed) or explicitly left in place with a stated reason.
|
||||||
53
.claude/skills/setup-skills/LOCKFILE.md
Normal file
53
.claude/skills/setup-skills/LOCKFILE.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Skills Lockfile
|
||||||
|
|
||||||
|
`.claude/skills-lock.yaml`, at the root of a project, tracks which library
|
||||||
|
skills (from `~/.claude/skills/library/`) have been copied into that
|
||||||
|
project's `.claude/skills/`, so [`setup-skills`](SKILL.md),
|
||||||
|
[`update-skills`](../update-skills/SKILL.md), and
|
||||||
|
[`remove-skills`](../remove-skills/SKILL.md) all agree on what's installed
|
||||||
|
without re-deriving it from the filesystem.
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
A YAML list of entries, one per installed skill:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: nbdev
|
||||||
|
hash: 3f2a9b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a
|
||||||
|
- name: terraform-conventions
|
||||||
|
hash: 9c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a3f2a9b
|
||||||
|
```
|
||||||
|
|
||||||
|
- `name` — matches both the skill's directory name in the library
|
||||||
|
(`skills/library/<name>`) and its copied directory name in the project
|
||||||
|
(`.claude/skills/<name>`).
|
||||||
|
- `hash` — the output of `hash-dir.sh` run against that one skill's
|
||||||
|
directory contents, recorded at the moment it was last copied or
|
||||||
|
confirmed up to date. Never a hash of anything else — not the whole
|
||||||
|
project, not the whole library, just that one skill's own directory
|
||||||
|
tree.
|
||||||
|
|
||||||
|
## What a mismatch means
|
||||||
|
|
||||||
|
To classify a skill's state, compare three values: the lockfile's stored
|
||||||
|
`hash`, `hash-dir.sh` on the project's current copy
|
||||||
|
(`.claude/skills/<name>`), and `hash-dir.sh` on the library's current
|
||||||
|
source (`~/.claude/skills/library/<name>`).
|
||||||
|
|
||||||
|
| stored vs. project copy | stored vs. library source | meaning |
|
||||||
|
|--------------------------|----------------------------|--------------------------------------|
|
||||||
|
| match | match | nothing to do |
|
||||||
|
| match | differs | library moved on — safe to update |
|
||||||
|
| differs | match | project customized on purpose — leave it |
|
||||||
|
| differs | differs | conflict — report, don't touch |
|
||||||
|
|
||||||
|
## Writing to the lockfile
|
||||||
|
|
||||||
|
- Adding a skill: append a new `{name, hash}` entry.
|
||||||
|
- Applying a safe update: overwrite that entry's `hash` in place with the
|
||||||
|
library's current hash.
|
||||||
|
- Removing a skill: delete its entry entirely.
|
||||||
|
|
||||||
|
Never reorder or restructure existing entries beyond what an add, update,
|
||||||
|
or remove requires — this file is meant to diff cleanly in a project's
|
||||||
|
git history.
|
||||||
46
.claude/skills/setup-skills/SKILL.md
Normal file
46
.claude/skills/setup-skills/SKILL.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
name: setup-skills
|
||||||
|
description: Add relevant skills from the shared skills library to the current project.
|
||||||
|
disable-model-invocation: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Adds opt-in, project-specific skills from `~/.claude/skills/library/` into
|
||||||
|
the current project's `.claude/skills/`, tracked in
|
||||||
|
`.claude/skills-lock.yaml` (see [LOCKFILE.md](LOCKFILE.md) for its schema).
|
||||||
|
Only ever adds — checking already-installed skills for updates is
|
||||||
|
[`update-skills`](../update-skills/SKILL.md)'s job, not this one's.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Read `.claude/skills-lock.yaml` in the current project, if it exists.
|
||||||
|
Note every skill name already listed — these are already installed and
|
||||||
|
must not be re-proposed.
|
||||||
|
|
||||||
|
2. List every skill under `~/.claude/skills/library/*/SKILL.md` and read
|
||||||
|
each one's `name` and `description`.
|
||||||
|
|
||||||
|
3. Inspect the current project (file tree, manifests like
|
||||||
|
`pyproject.toml`/`package.json`, file extensions present, etc.) and
|
||||||
|
judge which library skills — excluding ones already installed — seem
|
||||||
|
relevant, the same way you'd reason about any unfamiliar codebase.
|
||||||
|
Propose that shortlist to the user with your reasoning, one line per
|
||||||
|
skill. If the user asks to see the full catalog instead, list every
|
||||||
|
library skill (minus already-installed ones) with its description.
|
||||||
|
|
||||||
|
4. Let the user confirm, adjust, or pick freely from the full list.
|
||||||
|
|
||||||
|
5. For each confirmed skill:
|
||||||
|
- If `.claude/skills/<name>/` already exists in the project and is
|
||||||
|
*not* in the lockfile, skip it and tell the user why (a same-named
|
||||||
|
skill already lives there and isn't tracked — remove or rename it
|
||||||
|
first if they want the library version).
|
||||||
|
- Otherwise, copy `~/.claude/skills/library/<name>/` to
|
||||||
|
`.claude/skills/<name>/` in the project, run
|
||||||
|
`~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/<name>`,
|
||||||
|
and append `{name, hash: <output>}` to `.claude/skills-lock.yaml`
|
||||||
|
(create the file, an empty YAML list, if it doesn't exist yet).
|
||||||
|
|
||||||
|
6. Report what was added and what was skipped, and why.
|
||||||
|
|
||||||
|
Done when every confirmed skill is either copied and recorded in the
|
||||||
|
lockfile, or explicitly skipped with a stated reason.
|
||||||
23
.claude/skills/setup-skills/hash-dir.sh
Executable file
23
.claude/skills/setup-skills/hash-dir.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Deterministic recursive hash of a directory's file contents.
|
||||||
|
#
|
||||||
|
# Hashes relative paths, not absolute ones, so two directories with
|
||||||
|
# identical contents hash identically regardless of where they live on
|
||||||
|
# disk (needed to compare a project's copied skill against the library
|
||||||
|
# source it was copied from).
|
||||||
|
#
|
||||||
|
# Usage: hash-dir.sh <directory>
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
echo "Usage: hash-dir.sh <directory>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dir="$1"
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
echo "Not a directory: $dir" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "$dir" && find . -type f -print0 | sort -z | xargs -0 -r sha256sum) | sha256sum | awk '{print $1}'
|
||||||
52
.claude/skills/update-skills/SKILL.md
Normal file
52
.claude/skills/update-skills/SKILL.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
name: update-skills
|
||||||
|
description: Check the current project's installed library skills for upstream changes and apply the safe ones.
|
||||||
|
disable-model-invocation: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Compares every skill listed in the current project's
|
||||||
|
`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md)
|
||||||
|
for its schema) against both the project's own copy and the current
|
||||||
|
library source, and decides what to do about each one. Never installs a
|
||||||
|
skill that isn't already there — that's
|
||||||
|
[`setup-skills`](../setup-skills/SKILL.md)'s job.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell
|
||||||
|
the user there's nothing to check and stop.
|
||||||
|
|
||||||
|
2. For each `{name, hash}` entry, compute:
|
||||||
|
- `project_hash`: `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/<name>`
|
||||||
|
- `library_hash`: `~/.claude/skills/setup-skills/hash-dir.sh ~/.claude/skills/library/<name>`
|
||||||
|
|
||||||
|
If either path is missing entirely, report that anomaly for this skill
|
||||||
|
(don't try to classify it) and move on to the next entry.
|
||||||
|
|
||||||
|
3. Classify each entry against the table in
|
||||||
|
[LOCKFILE.md](../setup-skills/LOCKFILE.md#what-a-mismatch-means),
|
||||||
|
using `project_hash` in place of "project copy" and `library_hash` in
|
||||||
|
place of "library source". The two outcomes that need action below are
|
||||||
|
**safe update** (stored matches project, differs from library) and
|
||||||
|
**conflict** (stored differs from both). "Locally customized" needs no
|
||||||
|
message beyond the summary.
|
||||||
|
|
||||||
|
4. If there are any safe updates, list them by name and ask for one
|
||||||
|
confirmation to apply all of them — unless the user's invocation
|
||||||
|
already included an explicit go-ahead argument (e.g. `-y`, `yes`), in
|
||||||
|
which case apply them without asking. Applying means: delete
|
||||||
|
`.claude/skills/<name>/` entirely and copy
|
||||||
|
`~/.claude/skills/library/<name>/` in its place, so no file the project
|
||||||
|
copy had but the library no longer has can survive — then recompute its
|
||||||
|
hash and overwrite that entry's `hash` in `.claude/skills-lock.yaml` in
|
||||||
|
place.
|
||||||
|
|
||||||
|
5. For every conflict, report it and show a recursive diff between the
|
||||||
|
project's copy and the library's current version
|
||||||
|
(`diff -ru .claude/skills/<name> ~/.claude/skills/library/<name>`).
|
||||||
|
Do not modify the project's copy or the lockfile entry for a
|
||||||
|
conflicted skill under any circumstances — surfacing it is the whole
|
||||||
|
job here.
|
||||||
|
|
||||||
|
6. Finish with a summary: updated, left alone (customized), conflicted,
|
||||||
|
already current, and any anomalies from step 2.
|
||||||
Reference in New Issue
Block a user