add commit, penpot-login, and clean-house skills
Track the project-local Claude Code skills that were living untracked in .claude/skills/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
931096cc17
commit
f90bcbf5f3
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "1.5.0",
|
||||
"hash": "sha256:8090b99552c828ce019aa3740d8a0acb4768a443704b4e935efa23b32e91128d",
|
||||
"installed_at": "2026-06-02T08:46:25Z"
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
---
|
||||
name: clean-house
|
||||
description: >
|
||||
Run a documentation-discipline pass over a DQR (Decisions / Questions /
|
||||
Rejected) markdown system. Audits decisions/, surfaces drift (broken
|
||||
anchor links, missing Q→D backlinks, sunset-shaped phrases without
|
||||
linked tickets, files over the split threshold, dead cross-references),
|
||||
batches findings by category, and uses AskUserQuestion to apply fixes
|
||||
interactively. Trigger this skill whenever the user asks to clean,
|
||||
tidy, audit, sweep, garden, or review the state of decisions/, the
|
||||
DQR system, the questions index, or the records under decisions/.
|
||||
Also trigger before a release, after a session that touched many
|
||||
records, or whenever the user mentions documentation drift, stale
|
||||
plans, or "house" / "clean-house" / "house cleaning". Imperative —
|
||||
apply fixes, not just report.
|
||||
---
|
||||
|
||||
# clean-house
|
||||
|
||||
A periodic gardening pass over `decisions/`. The DQR system is
|
||||
markdown-as-source-of-truth; `.pql/pql.db` is a derived index. This skill
|
||||
operates on the markdown files, batches drift by category, and uses
|
||||
`AskUserQuestion` to decide what to apply.
|
||||
|
||||
The skill is **imperative**. It really cleans. The verb-noun match is
|
||||
load-bearing — a `clean-house` that only produced a report would be
|
||||
misnamed. Mechanical fixes get a single batched approval, judgment-call
|
||||
findings get individual prompts, and a "stop asking" escape hatch is
|
||||
always present.
|
||||
|
||||
You — Claude — are the runtime. Read this file, read
|
||||
`references/rules.md`, follow the procedure below, call `pql` and `Edit`
|
||||
to do the work, call `AskUserQuestion` for the prompts.
|
||||
|
||||
## When to run
|
||||
|
||||
- Before a release.
|
||||
- After a session that touched many D / Q / R records.
|
||||
- When something feels stale and a sweep is wanted.
|
||||
- When `pql decisions validate` is green but `decisions/` still feels off.
|
||||
|
||||
This skill is **not** a replacement for `pql decisions validate`.
|
||||
Validate is fast, hot, and gates pre-push. clean-house is slow, cold,
|
||||
on-demand. Escalation path: rules that fire often here are candidates
|
||||
for promotion to `pql decisions validate`; rules that almost never fire
|
||||
stay here where false positives don't slow anyone down.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. **Gate on validate.** Run `pql decisions validate`. If it exits
|
||||
non-zero, stop and tell the user to fix validator findings first.
|
||||
Do not proceed.
|
||||
|
||||
2. **Load the rule catalog.** Read `references/rules.md`. Each rule
|
||||
names its detection, fix, and finding-id format.
|
||||
|
||||
3. **Probe conventions.** Read `decisions/.clean-house.yaml` if it
|
||||
exists; otherwise infer from the project. Conventions to resolve:
|
||||
- `heading_level`: 2 or 3 — the depth records use (`## D-N` vs
|
||||
`### D-N`). Probe by sampling the first 3 records' file
|
||||
locations and looking for the first matching ATX heading.
|
||||
- `backlink_phrasing`: the project's Q→D backlink phrase (default
|
||||
`Resolved → D-N`; some projects use `Resolved as D-N` or
|
||||
`→ D-N`).
|
||||
- `file_threshold`: default 350 (RULE-FILE-OVER-THRESHOLD).
|
||||
- `stale_open_q_days`: default 60 (RULE-STALE-OPEN-Q).
|
||||
|
||||
Pass the resolved conventions into each rule's detection. Cache
|
||||
the probe result for the run.
|
||||
|
||||
`decisions/.clean-house.yaml` shape (all keys optional):
|
||||
```yaml
|
||||
heading_level: 3
|
||||
backlink_phrasing: "Resolved → "
|
||||
file_threshold: 500
|
||||
stale_open_q_days: 90
|
||||
exclude_paths: ["legacy/", "drafts/"]
|
||||
```
|
||||
|
||||
4. **Sync the index.** Run `pql decisions sync` so the DB reflects
|
||||
the markdown.
|
||||
|
||||
5. **Walk decisions/.** Use `pql decisions list` to enumerate
|
||||
records, plus `pql decisions read <id>` per record when a rule
|
||||
needs the body. For file-level rules (size, sort), read the
|
||||
markdown files directly. **Filter out** any record whose
|
||||
`file_path` begins with `legacy/` (or any prefix in
|
||||
`exclude_paths` from the probe).
|
||||
|
||||
6. **Run each rule's detection.** Collect findings. Tag each with
|
||||
its rule ID, finding ID (per the rule's format), category
|
||||
(`mechanical` | `judgment`), and the record(s)/file(s) involved.
|
||||
|
||||
7. **Group findings by rule** and prompt. Honor any "skipped 3+
|
||||
times" findings from the skip ledger by escalating their prompt
|
||||
("skipped 3x — promote to manual decision, downgrade to
|
||||
summary-only, or keep asking?"):
|
||||
|
||||
- **Mechanical rules** — one prompt per rule, batched. Show the
|
||||
count and offer: apply all / show diff first / skip this rule /
|
||||
**stop asking** (global — see below).
|
||||
- **Judgment rules** — one prompt per finding. Options match the
|
||||
rule's `Fix` section, plus the same global **stop asking**.
|
||||
|
||||
**"Stop asking" semantics (global).** When the user picks "stop
|
||||
asking" on any prompt, immediately halt all further prompts in
|
||||
the run — mechanical batches not yet asked AND remaining
|
||||
judgment findings. Move all unprompted findings to the skip
|
||||
ledger marked `deferred-stop-asking`. Skip to step 9.
|
||||
|
||||
**"Show diff first" loop.** When the user picks "show diff
|
||||
first" on a mechanical batch, emit a unified diff of the staged
|
||||
edits (one block per file), then re-prompt the same question
|
||||
with the same options minus "show diff first" — preventing
|
||||
infinite loops while still letting the user inspect before
|
||||
committing.
|
||||
|
||||
8. **Apply approvals.** For mechanical rules use `Edit` to mutate
|
||||
the markdown directly. For judgment rules, follow the option the
|
||||
user picked (file a ticket via `pql ticket new`, mark superseded
|
||||
by editing the record, etc.).
|
||||
|
||||
9. **Update the skip ledger and history.** Both files live at
|
||||
`decisions/.clean-house-state.md`, gitignored. The file uses
|
||||
per-run section headers so it doubles as run history:
|
||||
|
||||
```markdown
|
||||
# clean-house run history
|
||||
|
||||
## 2026-05-06T13:42Z
|
||||
fired: RULE-ANCHOR-DRIFT(1) RULE-SUNSET-WITHOUT-TICKET(1)
|
||||
applied: RULE-ANCHOR-DRIFT(1) RULE-SUNSET-WITHOUT-TICKET(1)
|
||||
skipped:
|
||||
- RULE-ANCHOR-DRIFT D-8:#q-1-markdown-mirror-for-tickets
|
||||
|
||||
## 2026-05-04T11:10Z
|
||||
fired: ...
|
||||
```
|
||||
|
||||
Each section starts with a UTC ISO-8601 timestamp. `fired`
|
||||
counts findings detected; `applied` counts findings the user
|
||||
acted on; `skipped` lists `<rule-id> <finding-id>` pairs.
|
||||
|
||||
**Promotion-candidate computation.** Scan the most recent N=7
|
||||
sections (or all sections if fewer). A rule is a promotion
|
||||
candidate if it `fired` in ≥ 5 of those sections. Surface the
|
||||
candidates in the summary; do not auto-promote.
|
||||
|
||||
10. **Emit the summary** (see Output below).
|
||||
|
||||
## Question phrasing
|
||||
|
||||
Always include the rule ID in the prompt so the user learns what's
|
||||
being checked.
|
||||
|
||||
**Mechanical (batched):**
|
||||
> RULE-ANCHOR-DRIFT: 12 cross-reference anchors point to headings that
|
||||
> no longer exist (renamed or deleted). Apply all 12 fixes? Options:
|
||||
> apply all / show diff first / skip this rule / stop asking, summarize
|
||||
> the rest.
|
||||
|
||||
**Judgment (individual):**
|
||||
> RULE-SUNSET-WITHOUT-TICKET: D-59 mentions "must track dugite-native
|
||||
> releases for security updates" but has no linked T. Options: file T
|
||||
> now / draft a T description first / mark as already covered (add note
|
||||
> to D) / skip / stop asking.
|
||||
|
||||
Phrasing rules:
|
||||
|
||||
- **Always include "stop asking, summarize the rest."** Sessions
|
||||
without bandwidth for full review need an escape. Forcing answers
|
||||
to every question kills the tool.
|
||||
- **Always include "skip."** Skipped findings go to the ledger; three
|
||||
consecutive skips of the same finding promote the next prompt.
|
||||
- **Mechanical batches; judgment doesn't.** "Apply all 12 fixes?" is
|
||||
one decision against a clear delta. Twelve individual prompts defeat
|
||||
the point. Conversely, judgment findings are each separate work —
|
||||
presenting them as a batch hides cost.
|
||||
|
||||
## What this skill does NOT do
|
||||
|
||||
- Does not file tickets without asking.
|
||||
- Does not rewrite D-record body prose, only metadata fields and
|
||||
cross-reference links.
|
||||
- Does not touch records under `legacy/`.
|
||||
- Does not run `pql decisions validate` *as a fix* — only as a
|
||||
precondition gate.
|
||||
- Does not promote rules to `pql decisions validate` automatically.
|
||||
Promotion is a human decision; this skill only flags candidates
|
||||
("RULE-X has fired in 5 of the last 7 runs — consider promoting").
|
||||
|
||||
## Non-interactive context
|
||||
|
||||
If invoked without an interactive `AskUserQuestion` surface (a CI run,
|
||||
a batch script), refuse to apply judgment fixes. Apply mechanical
|
||||
fixes only if the user explicitly says so via the trigger phrase
|
||||
("auto-apply mechanical, skip judgment"); otherwise emit the summary
|
||||
and exit without mutating files.
|
||||
|
||||
## Output
|
||||
|
||||
At the end, emit a summary block:
|
||||
|
||||
```
|
||||
clean-house — sweep complete
|
||||
────────────────────────────
|
||||
Files scanned: 11
|
||||
Records parsed: 62 D, 23 Q, 11 R
|
||||
Conventions: heading_level=3 backlink="Resolved → "
|
||||
Mechanical fixes: 14 applied, 0 deferred
|
||||
Judgment findings: 3 acted on, 2 deferred (in state file)
|
||||
Promotion candidates: RULE-ANCHOR-DRIFT (5/7 runs)
|
||||
Touched files: decisions/architecture.md, decisions/questions.md
|
||||
```
|
||||
|
||||
If this is the first run (no prior history) or there are fewer
|
||||
than 5 prior runs, `Promotion candidates:` reports
|
||||
`(none — N/7 runs of history)` instead of decorative empty content.
|
||||
|
||||
The summary names the touched files so the next commit message can be
|
||||
honest about what changed. The skill does not commit on its own.
|
||||
|
||||
## Versioning
|
||||
|
||||
clean-house ships embedded in the pql binary; its version is the
|
||||
pql version (`pql --version`). To see what changed and when, read
|
||||
`CHANGELOG.md` or `git log internal/skill/clean-house/` in the pql
|
||||
repo. Keep this file and `references/rules.md` consistent with
|
||||
each other when iterating — they're the contract.
|
||||
@@ -0,0 +1,386 @@
|
||||
# clean-house rule catalog
|
||||
|
||||
The rules clean-house runs and the reasoning behind each. New rules
|
||||
land here as their own entry. Catalog churn — additions, refinements,
|
||||
retirements — is tracked in pql's `CHANGELOG.md` and `git log
|
||||
internal/skill/clean-house/`. When a rule is retired, leave a
|
||||
**Retired** stub (ID + one-line reason + retirement commit) so the
|
||||
trail of "we used to check X" is recoverable from this file alone.
|
||||
|
||||
## Reading a rule
|
||||
|
||||
Each entry below has:
|
||||
|
||||
- **ID** — Stable identifier (e.g. `RULE-ANCHOR-DRIFT`). Used in
|
||||
AskUserQuestion prompts and skip-ledger entries so the user learns
|
||||
what's being checked.
|
||||
- **Category** — `mechanical` or `judgment`. Drives whether findings
|
||||
batch into one prompt or each get their own.
|
||||
- **Finding ID** — Format string for the per-finding stable
|
||||
identifier. Skip-ledger entries are written as `<rule-id>
|
||||
<finding-id> <ISO-date>`; subsequent runs use the same format to
|
||||
recognize "same finding skipped 3 runs in a row" and escalate.
|
||||
Use only stable inputs (record IDs, file paths, slug strings, body
|
||||
hashes) — never line numbers or timestamps.
|
||||
- **Detection** — Concrete steps to find violations. Names the
|
||||
pql command or file primitive used.
|
||||
- **Fix** — Mechanical: deterministic action. Judgment: the prompt
|
||||
option list and what each option does.
|
||||
- **Why** — One paragraph on the failure mode this rule guards
|
||||
against. Future-you reading the rule benefits from the reasoning,
|
||||
not just the check.
|
||||
|
||||
---
|
||||
|
||||
## RULE-ANCHOR-DRIFT
|
||||
|
||||
**Category:** mechanical
|
||||
|
||||
**Finding ID:** `<source-record>:<link-target>` (e.g.
|
||||
`D-8:#q-1-markdown-mirror-for-tickets` or
|
||||
`D-8:questions.md#q-1`).
|
||||
|
||||
**Detection:**
|
||||
|
||||
Anchor-only markdown links (`[text](#slug)`) resolve against the
|
||||
**source file's full heading set**, not the body of a single record
|
||||
— record-level headings (`### D-N: …`) live as siblings in the
|
||||
file and are valid link targets. The previous detection (which used
|
||||
`pql decisions read`'s body-only `headings` array) missed these
|
||||
and produced false positives.
|
||||
|
||||
Per source body:
|
||||
|
||||
1. Open the source body's file (`<vault>/<file_path>` from
|
||||
`pql decisions list`) and extract every ATX heading. Build a
|
||||
slug index for the file using the GFM convention (lowercase,
|
||||
hyphenate spaces, drop punctuation, disambiguate duplicates with
|
||||
`-1`/`-2`). Cache per file — every record in that file uses the
|
||||
same index.
|
||||
2. For each `[text](target)` link in the body:
|
||||
- Anchor-only (`#slug`): check `slug` against the **source file's**
|
||||
index. Missing → flag.
|
||||
- Cross-file (`path.md#slug`): resolve `path.md` relative to the
|
||||
source file's directory; check `slug` against that file's
|
||||
index. Missing file or missing slug → flag.
|
||||
|
||||
**Fix:**
|
||||
|
||||
If the slug exists in a sibling file's index (same directory) and
|
||||
the link is anchor-only, rewrite to `path.md#slug` (the canonical
|
||||
cross-file form). If the slug exists in the source file's index
|
||||
with edit-distance ≤ 2 from the link target, rewrite to the
|
||||
matched slug. Otherwise downgrade to judgment ("no auto-fix; the
|
||||
heading was removed, not renamed") with options: drop the link /
|
||||
point elsewhere / mark as intentionally dangling.
|
||||
|
||||
**Fix:**
|
||||
|
||||
If exactly one heading with a closely-matching slug exists (slug
|
||||
edit-distance ≤ 2 or substring match), rewrite the link to point at
|
||||
that heading. If no plausible match exists, downgrade the finding to
|
||||
judgment ("no auto-fix; the heading was removed, not renamed") and
|
||||
prompt the user with options: drop the link / point elsewhere / mark
|
||||
as intentionally dangling.
|
||||
|
||||
**Why:**
|
||||
|
||||
When a heading is renamed for clarity, every cross-reference pointing
|
||||
at the old slug silently breaks. The rendered docs still look fine —
|
||||
the link just goes nowhere. Without periodic sweeps these decay
|
||||
indefinitely; the cost of the sweep is small compared to the cost of
|
||||
a reader following a dead link and losing trust in the index.
|
||||
|
||||
---
|
||||
|
||||
## RULE-MISSING-Q-BACKLINK
|
||||
|
||||
**Category:** mechanical
|
||||
|
||||
**Finding ID:** `<d-id>:<q-id>` (e.g. `D-7:Q-2`). Order is always
|
||||
D-first regardless of which side is missing the backlink.
|
||||
|
||||
**Detection:**
|
||||
|
||||
For each D record body, find lines of the shape `Resolves: Q-N`
|
||||
(plain text, typically in the metadata block at the top of the
|
||||
record). For each Q-N referenced, fetch that Q record's body via
|
||||
`pql decisions read Q-N` and check whether it contains a line of the
|
||||
shape `Resolved → D-N` (or equivalent backlink phrasing — match the
|
||||
project's own convention; default pattern is `Resolved → D-N`).
|
||||
|
||||
Also check the reverse direction: any Q record claiming
|
||||
`Resolved → D-N` whose D-N body lacks `Resolves: Q-N`.
|
||||
|
||||
**Fix:**
|
||||
|
||||
Insert the missing backlink in-place using `Edit`:
|
||||
|
||||
- Missing on the D side: add `Resolves: Q-N` to the D's metadata
|
||||
block (typically right after `Domain:` / `Status:` lines).
|
||||
- Missing on the Q side: append `Resolved → D-N` to the Q's status
|
||||
line (or in the conventional position for that project).
|
||||
|
||||
**Why:**
|
||||
|
||||
Bidirectional Q↔D links are the navigation backbone of the DQR
|
||||
system. When one side drifts, search-by-decision finds nothing for
|
||||
that question and search-by-question doesn't surface its resolution.
|
||||
The asymmetric state usually arises from a hand-edit on one record
|
||||
that forgot to update the other; the fix is purely mechanical because
|
||||
the correct content is already determined by the existing pointer
|
||||
in the other direction.
|
||||
|
||||
---
|
||||
|
||||
## RULE-RECORD-SORT
|
||||
|
||||
**Category:** mechanical
|
||||
|
||||
**Finding ID:** `<file-path>` (e.g. `decisions/architecture.md`).
|
||||
The whole file is one finding — sort applies to the file as a
|
||||
unit.
|
||||
|
||||
**Detection:**
|
||||
|
||||
For each `decisions/*.md` file, read it directly (no pql) and find
|
||||
all top-level `## D-N` / `## Q-N` / `## R-N` headings in order. Strip
|
||||
the prefix, parse the numeric suffix, and check whether the sequence
|
||||
is monotonically ascending within each ID family (D, Q, R kept
|
||||
separate — files commonly mix families).
|
||||
|
||||
Only flag a file if it is **otherwise tidy** — currently no out-of-
|
||||
order amendments interleaved with new records. The heuristic: if the
|
||||
sort would touch fewer than three records out of position, apply it;
|
||||
if more, downgrade to judgment ("this file looks deliberately
|
||||
arranged — confirm before reordering"). Three is a soft threshold; if
|
||||
the file's last commit message contains `WIP` or `do-not-sort`, skip.
|
||||
|
||||
**Fix:**
|
||||
|
||||
Reorder the records within the file so each ID family is ascending.
|
||||
Preserve everything else (headings between record blocks, any prose
|
||||
prelude/postlude). Use `Edit` with full block replacement, not in-
|
||||
place line shuffling — easier to verify the diff.
|
||||
|
||||
**Why:**
|
||||
|
||||
Records added at the bottom of a file are easy to write but hard to
|
||||
find. Sorted IDs let a reader find D-37 by jumping to the
|
||||
two-thirds mark of the file rather than scanning. The cost of the
|
||||
sort is one reordering pass; the benefit is paid back on every
|
||||
subsequent read.
|
||||
|
||||
---
|
||||
|
||||
## RULE-EOF-NORMALIZATION
|
||||
|
||||
**Category:** mechanical
|
||||
|
||||
**Finding ID:** `<file-path>` (one finding per file).
|
||||
|
||||
**Detection:**
|
||||
|
||||
For each `decisions/*.md` file (and any other markdown the skill
|
||||
touched during this run), read directly. Flag if:
|
||||
|
||||
- File does not end with exactly one `\n`.
|
||||
- Any line contains trailing whitespace before its `\n`.
|
||||
|
||||
**Fix:**
|
||||
|
||||
Trim trailing whitespace from each line. Ensure exactly one trailing
|
||||
newline at end of file. Use `Edit` only if a violation was found —
|
||||
this rule must not produce a no-op diff.
|
||||
|
||||
**Why:**
|
||||
|
||||
Editor and git config drift causes whitespace creep that's invisible
|
||||
in rendering but pollutes diffs (every record edit ends up touching
|
||||
unrelated lines). A periodic normalization keeps future diffs clean
|
||||
without forcing per-editor enforcement on every contributor.
|
||||
|
||||
---
|
||||
|
||||
## RULE-SUNSET-WITHOUT-TICKET
|
||||
|
||||
**Category:** judgment
|
||||
|
||||
**Finding ID:** `<record-id>:<phrase-hash-8>` where phrase-hash-8 is
|
||||
the first 8 hex chars of `sha256(<matched-phrase>)`. Lets the
|
||||
ledger distinguish two sunset phrases in the same record.
|
||||
|
||||
**Detection:**
|
||||
|
||||
For each D record body (via `pql decisions read <id>`), grep for
|
||||
sunset-shaped phrases. Default regex set:
|
||||
|
||||
```
|
||||
(?i)\b(delete|remove|sunset|kill[ -]?switch|tear[ -]?down) when\b
|
||||
(?i)\bmust (track|monitor|watch|follow)\b
|
||||
(?i)\brevisit (when|after|once)\b
|
||||
(?i)\b(deprecate|retire) (when|after|once)\b
|
||||
```
|
||||
|
||||
For each match, run `pql ticket list --decision <id>`. If no tickets
|
||||
exist, the D has work-shaped intent without a tracked T — flag.
|
||||
|
||||
**Fix (prompt options):**
|
||||
|
||||
- **File T now** — Run `pql ticket new task "<phrase>" --decision <id>`,
|
||||
then prompt the user for a description (or call `pql ticket refine
|
||||
write <T> --description ...` after creation).
|
||||
- **Draft a T description first** — Open an `AskUserQuestion` for the
|
||||
description, then file as above.
|
||||
- **Mark as already covered** — Add a note to the D body
|
||||
("Tracked under T-N") via `Edit`. Skill does not assert which T;
|
||||
user provides the ID.
|
||||
- **Skip** — Adds to the ledger.
|
||||
- **Stop asking** — Skip remaining sunset findings, summarize.
|
||||
|
||||
**Why:**
|
||||
|
||||
Sunset-shaped intent ("we'll handle X when Y happens") is the most
|
||||
common source of accumulated debt in a DQR system: the trigger
|
||||
condition arrives, nobody remembers the D, the work doesn't happen.
|
||||
Linking each sunset to a T is the simplest defense — a T is a
|
||||
backlog item that surfaces in `plan whatsnext` / `plan board`. The
|
||||
fix is judgment because the right action depends on whether the D's
|
||||
condition is still relevant, whether it's already covered, and what
|
||||
the right scope is for the resulting T.
|
||||
|
||||
---
|
||||
|
||||
## RULE-FILE-OVER-THRESHOLD
|
||||
|
||||
**Category:** judgment
|
||||
|
||||
**Finding ID:** `<file-path>` (one finding per file; threshold
|
||||
choice is judgment, not a per-line problem).
|
||||
|
||||
**Detection:**
|
||||
|
||||
For each `decisions/*.md` file, count lines (via `wc -l` or
|
||||
equivalent). Default threshold: **350 lines**. Configurable per
|
||||
project — read `decisions/.clean-house.yaml` if present, key
|
||||
`file_threshold`. Fall back to 350.
|
||||
|
||||
**Fix (prompt options):**
|
||||
|
||||
- **Split now (which axis?)** — Prompt for the split axis: by ID
|
||||
family (D/Q/R), by domain, by date range, custom. Then perform the
|
||||
split: create new file(s), move records, update any anchor links
|
||||
pointing into the moved records (run RULE-ANCHOR-DRIFT in apply
|
||||
mode against the affected files after the split).
|
||||
- **Accept and raise the threshold** — Update
|
||||
`decisions/.clean-house.yaml`'s `file_threshold` to the next
|
||||
reasonable round number above the current line count.
|
||||
- **Defer** — Skip until next run.
|
||||
|
||||
**Why:**
|
||||
|
||||
A single decisions file growing past ~350 lines is the point at which
|
||||
linear scanning starts to lose to grep, and where the cost of
|
||||
splitting (renaming anchors) is still small. Beyond ~600 lines the
|
||||
split cost compounds. The threshold is judgment because some projects
|
||||
deliberately keep one file per domain and accept the size; others
|
||||
split aggressively. The skill surfaces the question; it doesn't
|
||||
decide.
|
||||
|
||||
---
|
||||
|
||||
## RULE-DEAD-FILE-REFERENCE
|
||||
|
||||
**Category:** judgment
|
||||
|
||||
**Finding ID:** `<record-id>:<path-token>` (path token as written
|
||||
in the body, not resolved).
|
||||
|
||||
**Detection:**
|
||||
|
||||
For each D record body (via `pql decisions read <id>`), grep for
|
||||
path-shaped tokens. Default regex:
|
||||
|
||||
```
|
||||
\b([\w./-]+\.(md|go|py|sql|yaml|yml|toml))\b
|
||||
```
|
||||
|
||||
The first detection pass produced 6/6 false positives in
|
||||
real-world use; the regex is necessary but not sufficient. For
|
||||
each match, apply the filters below in order — if any matches,
|
||||
**skip without flagging**:
|
||||
|
||||
1. **Placeholder filter.** Token contains `T-NNN`, `D-NNN`,
|
||||
`Q-NNN`, `R-NNN`, `...`, `<`, `>`, or `*` — it's a pattern,
|
||||
not a real path.
|
||||
2. **Source-relative resolution.** Resolve the token against the
|
||||
source file's directory (`<vault>/<file_path>`'s dir). If
|
||||
`[ -e <resolved> ]`, the reference is live.
|
||||
3. **Repo-relative resolution.** If `[ -e <token> ]` from the
|
||||
repo root, the reference is live.
|
||||
4. **Basename-fallback.** Run `find <repo-root> -name <basename>`
|
||||
(where basename is the last path component). If exactly one
|
||||
match exists, treat the reference as live and emit a low-
|
||||
priority "consider rewriting to the absolute path" note (not a
|
||||
judgment finding). If multiple matches exist, do not flag —
|
||||
the token is too ambiguous.
|
||||
|
||||
Only after all four filters miss does the reference qualify as
|
||||
truly dead and warrant a judgment prompt.
|
||||
|
||||
**Fix (prompt options):**
|
||||
|
||||
- **Update reference** — Prompt for the new path; rewrite the
|
||||
reference via `Edit`. If the user types a path, validate it exists
|
||||
before applying.
|
||||
- **Mark superseded** — Add a note to the D ("This decision
|
||||
references files no longer present; the underlying constraint
|
||||
was retired by D-N") and prompt for the superseding D ID.
|
||||
- **Defer** — Skip until next run.
|
||||
|
||||
**Why:**
|
||||
|
||||
Decisions reference code paths to ground their reasoning in the
|
||||
codebase that motivated them. When the code moves or gets deleted,
|
||||
the D's reasoning becomes harder to verify. Dead refs are a signal
|
||||
that either the decision should be updated or the decision itself is
|
||||
no longer load-bearing — both are judgment calls the user needs to
|
||||
make.
|
||||
|
||||
---
|
||||
|
||||
## RULE-STALE-OPEN-Q
|
||||
|
||||
**Category:** judgment
|
||||
|
||||
**Finding ID:** `<q-id>` (one finding per Q-record; staleness
|
||||
threshold is global per run, so a Q is either stale or not).
|
||||
|
||||
**Detection:**
|
||||
|
||||
Run `pql decisions list --type question --status open`. For each
|
||||
returned record, check the `date` field. Default staleness threshold:
|
||||
**60 days** (configurable via `decisions/.clean-house.yaml` key
|
||||
`stale_open_q_days`). Flag any Q with `date` older than the
|
||||
threshold.
|
||||
|
||||
**Fix (prompt options):**
|
||||
|
||||
- **Still relevant** — Touch the Q's `date` field to today, optionally
|
||||
add a one-line "still open as of YYYY-MM-DD: <reason>" to the body.
|
||||
- **Nudge to load-bearing index** — If the project keeps a
|
||||
load-bearing Qs list (a curated index of unresolved questions
|
||||
affecting current work), prompt for inclusion and add the Q there.
|
||||
- **Mark withdrawn** — Change the Q's status to `withdrawn` (the DQR
|
||||
system's terminal state for "no longer worth answering"); the
|
||||
user supplies a one-line reason.
|
||||
- **Defer** — Skip until next run.
|
||||
|
||||
**Why:**
|
||||
|
||||
Open questions are useful when they're current; stale ones become
|
||||
ambient noise that drowns the signal of new questions. Most
|
||||
projects don't enforce explicit Q-lifecycle, so without a periodic
|
||||
prompt the open list grows forever. The right action depends on
|
||||
whether the question is still open in fact — not just in metadata —
|
||||
which only the user can confirm.
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: commit
|
||||
description: Create a well-formatted git commit with staged changes
|
||||
---
|
||||
|
||||
# Git Commit
|
||||
|
||||
Create a well-formatted commit with staged changes following best practices.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Run `git status --porcelain` to check for changes
|
||||
2. If no staged changes, show unstaged files and ask what to stage
|
||||
3. Run `git diff --cached` to review staged changes
|
||||
4. Generate a commit message following Conventional Commits format:
|
||||
- `feat:` new feature
|
||||
- `fix:` bug fix
|
||||
- `docs:` documentation
|
||||
- `refactor:` code restructuring
|
||||
- `test:` adding tests
|
||||
- `chore:` maintenance
|
||||
5. Create commit with the message, adding Co-Authored-By trailer
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
```
|
||||
<type>(<scope>): <short description>
|
||||
|
||||
<body - what and why, not how>
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Warn about large commits (>500 lines changed)
|
||||
- Suggest splitting large changes into smaller commits
|
||||
- Never skip pre-commit hooks unless explicitly requested
|
||||
|
||||
## Changelog
|
||||
|
||||
After committing, update `CHANGELOG.md` following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format:
|
||||
|
||||
1. Add entry under `[Unreleased]` section (create if not exists)
|
||||
2. Categorize changes:
|
||||
- `Added` - new features
|
||||
- `Changed` - changes to existing functionality
|
||||
- `Deprecated` - features marked for removal
|
||||
- `Removed` - removed features
|
||||
- `Fixed` - bug fixes
|
||||
- `Security` - security-related changes
|
||||
3. Write entries in imperative mood: "Add feature" not "Added feature"
|
||||
4. Reference issue numbers where applicable
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
name: penpot-login
|
||||
description: Connect to Penpot MCP - starts the MCP server, logs into penpot.schweitz.net, and installs/connects the plugin
|
||||
allowed-tools: Read, Bash, mcp__chrome-devtools__new_page, mcp__chrome-devtools__navigate_page, mcp__chrome-devtools__take_snapshot, mcp__chrome-devtools__take_screenshot, mcp__chrome-devtools__click, mcp__chrome-devtools__fill, mcp__chrome-devtools__press_key, mcp__chrome-devtools__list_pages
|
||||
---
|
||||
|
||||
# Penpot MCP Connection
|
||||
|
||||
This skill connects Claude to Penpot by:
|
||||
1. Starting the Penpot MCP server (if not running)
|
||||
2. Logging into penpot.schweitz.net via Authentik
|
||||
3. Installing and connecting the Penpot MCP plugin
|
||||
|
||||
## Step 1: Start MCP Server
|
||||
|
||||
Check if the MCP server is running, start it if not:
|
||||
|
||||
```bash
|
||||
# Check if server is running
|
||||
if ! curl -s http://localhost:9880/mcp > /dev/null 2>&1; then
|
||||
# Start the server in background
|
||||
cd ~/Projects/penpot-mcp && ./start-server.sh &
|
||||
sleep 3 # Wait for server to start
|
||||
fi
|
||||
```
|
||||
|
||||
Server endpoints when running:
|
||||
- MCP Server: http://localhost:9880/mcp
|
||||
- Plugin Server: http://localhost:9879/
|
||||
- WebSocket: ws://localhost:4402
|
||||
|
||||
## Step 2: Login Credentials
|
||||
|
||||
Read credentials from the project root file `claude-authentik-credentials.md`:
|
||||
!`cat claude-authentik-credentials.md`
|
||||
|
||||
## Step 3: Login Flow
|
||||
|
||||
1. **Navigate to Penpot**
|
||||
- Use `mcp__chrome-devtools__new_page` to go to `https://penpot.schweitz.net`
|
||||
|
||||
2. **Check if already logged in**
|
||||
- Take a snapshot
|
||||
- If you see "Projects" heading, you're logged in - skip to Step 4
|
||||
- If you see login page, continue with authentication
|
||||
|
||||
3. **Authenticate via Authentik** (if not logged in)
|
||||
- Click the OpenID button to redirect to Authentik
|
||||
- On auth.schweitz.net, fill username textbox
|
||||
- Click "Log in" button
|
||||
- Fill password textbox
|
||||
- Click "Continue" button
|
||||
- If redirected to Authentik user page instead of Penpot, navigate back to `https://penpot.schweitz.net`
|
||||
|
||||
## Step 4: Open Design File
|
||||
|
||||
1. **Navigate to the Clide project**
|
||||
- Double-click on the TUI file to open the workspace
|
||||
|
||||
## Step 5: Install/Connect Plugin
|
||||
|
||||
1. **Open Plugins menu**
|
||||
- Click the Plugins button (puzzle icon, keyboard shortcut: Cmd+Alt+P)
|
||||
|
||||
2. **Check if plugin is installed**
|
||||
- If "Penpot MCP Plugin" appears under "INSTALLED PLUGINS", click OPEN
|
||||
- Otherwise, install it first:
|
||||
|
||||
3. **Install plugin** (if not installed)
|
||||
- Fill the plugin URL textbox with: `http://localhost:9879/manifest.json`
|
||||
- Click INSTALL
|
||||
- Click ALLOW on the permissions dialog
|
||||
|
||||
4. **Connect to MCP server**
|
||||
- In the plugin UI, click "CONNECT TO MCP SERVER"
|
||||
- Verify it shows "Connected to MCP server"
|
||||
|
||||
## Step 6: Configure Claude Code MCP
|
||||
|
||||
Add the Penpot MCP server to Claude Code (if not already configured):
|
||||
|
||||
```bash
|
||||
claude mcp add penpot -t http http://localhost:9880/mcp
|
||||
```
|
||||
|
||||
**Important**: Use HTTP transport (`-t http`) with the `/mcp` endpoint. Do NOT use SSE transport - it causes "Server not initialized" errors.
|
||||
|
||||
## Step 7: Verify Connection
|
||||
|
||||
After connecting, restart the MCP connection in Claude Code:
|
||||
- Use `/mcp` command to reconnect the penpot server
|
||||
- The Penpot MCP tools (execute_code, export_shape, etc.) will then be available
|
||||
|
||||
Test with:
|
||||
```javascript
|
||||
mcp__penpot__execute_code(code="return penpot.currentPage.name;")
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Keep the Penpot plugin UI window open while using MCP tools
|
||||
- The MCP server must be running for the plugin to connect
|
||||
- If connection fails, check that the server is running on port 9880
|
||||
- Use HTTP transport (`-t http`), NOT SSE transport
|
||||
Reference in New Issue
Block a user