docs(skills): fix stale guidance in bundled skills (T-507)

The 2026-07-02 skill review (adversarially verified) found the bundled
skills teaching pre-refactor reality: whats-next pointed refinement
agents at the pre-governance/ decision paths, claimed gh doesn't exist,
and taught the hand-staged .pql/changelog flow the pre-commit hook
replaced; git-commit mandated the heredoc-in-substitution commit form
the repo moved away from and referenced sidecar-era paths; the clide
skill omitted the dock slot (D-87); testmode omitted the terminal
category; ui-design's glyph table taught the static-const icon API
removed in T-314, and its generator scanned for a pattern that no
longer matches anything.

All fixed against current repo state. The glyph table is regenerated
3-column (name-keyed, byName-first); tool/gen_phosphor_glyphs.dart
round-trips it byte-identically. git-commit also gains the
session-vs-pre-existing triage step (backported from the settled-reach
variant) since this worktree hosts concurrent Claude sessions.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-02 11:51:41 +02:00
co-authored by Claude
parent a5acb6acc7
commit b02740e87d
14 changed files with 1614 additions and 2575 deletions
@@ -1,5 +0,0 @@
{
"version": "1.5.0",
"hash": "sha256:8090b99552c828ce019aa3740d8a0acb4768a443704b4e935efa23b32e91128d",
"installed_at": "2026-06-02T08:46:25Z"
}
-229
View File
@@ -1,229 +0,0 @@
---
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.
@@ -1,386 +0,0 @@
# 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.
+11 -10
View File
@@ -4,10 +4,8 @@ description: >
Use when you are running inside the clide IDE and want to observe or drive
its live UI — panes, editor, files, git, readers, toasts, layout — through
the `clide` CLI, or to find out what commands clide exposes. clide is the
IDE hosting this session; it puts `clide` on your PATH and a per-workspace
socket in `CLIDE_SOCK`. Start with `clide capabilities` to enumerate the
live tool surface. Triggers: "what can clide do", "drive the clide UI",
"open this in clide", "show the user", "toast", or invoking /clide.
IDE hosting this session. Triggers: "what can clide do", "drive the clide
UI", "open this in clide", "show the user", "toast", or invoking /clide.
user-invocable: true
allowed-tools: Bash
---
@@ -15,9 +13,10 @@ allowed-tools: Bash
# Driving clide from the CLI
You are (often) running **inside clide** — a Flutter IDE that hosts this
Claude session. It exposes its whole UI surface as a `clide <subsystem> <verb>`
CLI on your PATH, talking to the running app over a per-workspace socket
(`CLIDE_SOCK`). Every UI action the user can take has a CLI verb, and every
Claude session. It puts `clide` on your PATH and exposes its whole UI surface
as a `clide <subsystem> <verb>` CLI, talking to the running app over a
per-workspace socket (`CLIDE_SOCK`). Every UI action the user can take has a
CLI verb, and every
verb's effect is observable — that is the parity contract (D-6). So you can
*see what the user sees* and *show the user what you mean*.
@@ -53,9 +52,11 @@ one). `clide <subsystem>` with no verb, or an unknown command, prints usage.
## Conventions
- **Slots:** the layout has three content slots — `sidebar` (left), `workspace`
(center, where Claude lives), `context` (right) — plus the bottom `statusbar`.
Many verbs take a slot.
- **Slots:** the layout has four content slots — `sidebar` (left), `workspace`
(center, where Claude lives), `context` (right), and `dock` (bottom —
Output/Problems panes, hidden by default; D-87) — plus the bottom
`statusbar`. Many verbs take a slot. The live list is whatever
`clide capabilities` reports.
- **Honest failures:** a drive verb with no live GUI returns a `toolError`
("no live UI to drive"), not a hang. JSON on stdout; exit code conveys
ok/usage/tool error.
-53
View File
@@ -1,53 +0,0 @@
---
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
+31 -21
View File
@@ -30,17 +30,24 @@ This repo uses [Conventional Commits 1.0](https://www.conventionalcommits.org/en
Default to **one logical change per commit**, even when a lot of work lands in the tree at once. When there's a pile of uncommitted or untracked files:
1. **Read `git status` + `git diff` first** — never stage the whole tree blind.
2. **Group by concern**, not by file location. Typical concerns to separate:
2. **Triage: session vs pre-existing.** Separate changes you made in this
session from changes that were already dirty (or that a concurrent session
is producing — this worktree can host several Claude sessions at once).
Check the conversation history — if you didn't touch a file, it's not yours
to stage. After grouping your own commits, surface any remaining modified
or untracked files to the user and ask whether they belong in a commit.
Never silently skip or silently include changes that aren't yours.
3. **Group by concern**, not by file location. Typical concerns to separate:
- **Bookkeeping** — `.gitignore`, editor/IDE config, lockfiles, `go.sum` / `pubspec.lock` updates.
- **Documentation** — `README.md`, `CLAUDE.md`, ADRs under `docs/ADRs/`, design notes under `docs/`.
- **Documentation** — `README.md`, `CLAUDE.md`, D-records under `governance/`, design notes under `docs/`.
- **General-purpose tooling / skills** — things that aren't project-specific (reusable skills, shared scripts).
- **Project-specific conventions** — this repo's own rules.
- **Feature or subsystem** — one cohesive change per commit; a sidecar change and an app change for the same feature can land together, but two unrelated features should split.
- **Layer changes** — Flutter app, sidecar CLI, sidecar daemon, IPC server, pql wrapper, canvas driver, git panel — separate concerns; prefer separate commits when the changes are independent.
3. **Sequence the commits** so each one is cleanly scoped, but don't obsess about whether each intermediate commit "works" — for scaffolding PRs it's fine if the full picture only snaps together at the end.
4. **Prefer many small focused commits over one large mixed one** — a reviewer can read, revert, or cherry-pick a focused commit; they can't do any of those to a blob.
5. **Use `git add <specific paths>`** — never `git add -A` or `git add .` when splitting, or you'll sweep in the next commit's work by accident.
6. **Verify between commits** with `git status` and `git log -1` to confirm the split landed as intended.
- **Feature or subsystem** — one cohesive change per commit; a CLI change and an app change for the same feature can land together, but two unrelated features should split.
- **Layer changes** — Flutter app, `clide` CLI (`native/clide-cli/`), IPC server, pql wrapper, canvas driver, git panel — separate concerns; prefer separate commits when the changes are independent.
4. **Sequence the commits** so each one is cleanly scoped, but don't obsess about whether each intermediate commit "works" — for scaffolding PRs it's fine if the full picture only snaps together at the end.
5. **Prefer many small focused commits over one large mixed one** — a reviewer can read, revert, or cherry-pick a focused commit; they can't do any of those to a blob.
6. **Use `git add <specific paths>`** — never `git add -A` or `git add .` when splitting, or you'll sweep in the next commit's work by accident.
7. **Verify between commits** with `git status` and `git log -1` to confirm the split landed as intended.
Corollary: if a commit's subject line needs the word "and" to be accurate, it probably should have been two commits.
@@ -113,29 +120,32 @@ Co-Authored-By: Claude <noreply@anthropic.com>
The model-identifier variant produced by the Claude Code harness (e.g. `Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>`) is also accepted — don't rewrite it if the harness emits that form.
## HEREDOC discipline
## Message-file discipline
Pass commit messages via HEREDOC so multi-line formatting survives:
Pass commit messages via a message file so multi-line formatting survives
and the command stays inside the permission allowlist (command substitution
defeats the `git commit` prefix match and triggers permission prompts):
1. Write the full message to a file in `/tmp` (never inside `.git/` — that
directory is git's own state) using the Write tool.
2. Commit with it:
```bash
git commit -m "$(cat <<'EOF'
short imperative summary
Optional longer body explaining why this change was needed,
wrapped at about 72 characters.
Co-Authored-By: Claude <noreply@anthropic.com>
EOF
)"
git commit -F /tmp/commit-msg.txt
```
Never pass multi-line messages via `-m "line1\nline2"` or multiple `-m` flags — Git's behavior differs between shells and quoting regimes and the trailer can end up in the wrong place.
Fallback only: the HEREDOC-in-substitution form
(`git commit -m "$(cat <<'EOF' … EOF)"`) works but prompts for permission —
use it only when writing a file is impossible. Never pass multi-line messages
via `-m "line1\nline2"` or multiple `-m` flags — Git's behavior differs
between shells and quoting regimes and the trailer can end up in the wrong
place.
## What not to commit
- `.env` and any `*.env.local` — see `.gitignore`.
- `.claude/settings.local.json` — user-specific Claude Code settings, ignored.
- Build artefacts: `sidecar/bin/`, `sidecar/dist/`, `build/` (Flutter output), `app/.dart_tool/` — gitignored.
- Build artefacts: `/build/` (Flutter output), `/.dart_tool/` — gitignored.
- SQLite index files (`*.sqlite`, `*.sqlite-wal`, `*.sqlite-shm`, `*.db`) — caches generated against local repos; must never land here. Gitignored defensively.
- Coverage / test output (`*.out`, `coverage.*`, `*.test`) — gitignored.
-104
View File
@@ -1,104 +0,0 @@
---
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
-203
View File
@@ -1,203 +0,0 @@
---
name: skill-create
description: >
Guidance for creating effective Claude Code skills (.skill packages).
Use when the user wants to create, build, design, or iterate on a skill —
including writing SKILL.md files, bundling scripts/references/assets,
initializing new skills, packaging skills, or improving existing ones.
Triggers on requests like "create a skill", "make a new skill",
"build a skill for X", "package this skill", or "improve my skill".
---
# Skill Creator
## About Skills
Skills are modular, self-contained packages that extend Claude's capabilities
by providing specialized knowledge, workflows, and tools. They transform Claude
from a general-purpose agent into a specialized agent equipped with procedural
knowledge that no model can fully possess.
### What Skills Provide
- **Specialized workflows** — Multi-step procedures for specific domains
- **Tool integrations** — Instructions for working with specific file formats or APIs
- **Domain expertise** — Company-specific knowledge, schemas, business logic
- **Bundled resources** — Scripts, references, and assets for complex and repetitive tasks
## Core Principles
### Concise is Key
The context window is a public good. Skills share it with everything else Claude
needs: system prompt, conversation history, other skills' metadata, and the
actual user request.
Default assumption: Claude is already very smart. Only add context Claude doesn't
already have. Challenge each piece of information: "Does Claude really need this
explanation?" and "Does this paragraph justify its token cost?"
Prefer concise examples over verbose explanations.
### Set Appropriate Degrees of Freedom
Match specificity to the task's fragility and variability:
- **High freedom** (text-based instructions): Multiple approaches valid, decisions
depend on context, heuristics guide the approach.
- **Medium freedom** (pseudocode or scripts with parameters): Preferred pattern
exists, some variation acceptable, configuration affects behavior.
- **Low freedom** (specific scripts, few parameters): Operations are fragile and
error-prone, consistency is critical, specific sequence must be followed.
Think of Claude as exploring a path: a narrow bridge with cliffs needs specific
guardrails (low freedom), while an open field allows many routes (high freedom).
## Anatomy of a Skill
```
skill-name/
├── SKILL.md (required)
│ ├── YAML frontmatter metadata (required)
│ │ ├── name: (required)
│ │ ├── description: (required)
│ │ └── compatibility: (optional, rarely needed)
│ └── Markdown instructions (required)
└── Bundled Resources (optional)
├── scripts/ - Executable code (Python/Bash/etc.)
├── references/ - Documentation loaded into context as needed
└── assets/ - Files used in output (templates, icons, fonts, etc.)
```
### SKILL.md (required)
- **Frontmatter (YAML)**: `name` and `description` fields (required). Only these
are read by Claude to determine when the skill triggers — be clear and
comprehensive. The `compatibility` field is for environment requirements but
most skills don't need it.
- **Body (Markdown)**: Instructions and guidance. Only loaded AFTER the skill
triggers.
### Bundled Resources (optional)
**Scripts (`scripts/`)** — Executable code for tasks requiring deterministic
reliability or that are repeatedly rewritten.
**References (`references/`)** — Documentation loaded as needed into context.
Keep SKILL.md lean; move detailed reference material, schemas, and examples here.
If files are large (>10k words), include grep search patterns in SKILL.md.
**Assets (`assets/`)** — Files used in output, not loaded into context (templates,
images, icons, boilerplate code, fonts).
### What to NOT Include
Do NOT create extraneous files like README.md, INSTALLATION_GUIDE.md,
QUICK_REFERENCE.md, CHANGELOG.md, etc. The skill should only contain information
needed for an AI agent to do the job.
## Progressive Disclosure
Skills use a three-level loading system:
1. **Metadata** (name + description) — Always in context (~100 words)
2. **SKILL.md body** — When skill triggers (<5k words)
3. **Bundled resources** — As needed (unlimited; scripts can run without reading)
Keep SKILL.md body under 500 lines. Split content into separate files when
approaching this limit. Reference split files from SKILL.md with clear
descriptions of when to read them.
### Disclosure Patterns
**Pattern 1: High-level guide with references** — Keep overview in SKILL.md,
link to detail files loaded only when needed.
**Pattern 2: Domain-specific organization** — Organize content by domain
(e.g., `references/finance.md`, `references/sales.md`) so only relevant content
is loaded.
**Pattern 3: Conditional details** — Show basic content, link to advanced
content loaded only when the user needs those features.
Guidelines:
- Avoid deeply nested references — keep one level deep from SKILL.md
- Structure longer reference files with a table of contents at the top
## Skill Creation Process
Follow these steps in order, skipping only with clear reason:
### Step 1: Understand the Skill with Concrete Examples
Skip only when usage patterns are already clearly understood.
Ask the user for concrete examples of how the skill will be used:
- "What functionality should the skill support?"
- "Can you give some examples of how this skill would be used?"
- "What would a user say that should trigger this skill?"
Avoid overwhelming users — start with the most important questions.
### Step 2: Plan the Reusable Skill Contents
Analyze each example by considering how to execute from scratch and identifying
what scripts, references, and assets would help with repeated execution.
Establish a list of reusable resources: scripts, references, and assets.
### Step 3: Initialize the Skill
Create the skill directory manually:
```
mkdir -p <output-directory>/<skill-name>
```
Then create `SKILL.md` with frontmatter and body. Add `scripts/`, `references/`,
and `assets/` subdirectories only as needed.
Skip if iterating on an existing skill.
### Step 4: Edit the Skill
Remember the skill is for another Claude instance to use. Include beneficial,
non-obvious information.
For design patterns, consult:
- `references/workflows.md` — Sequential workflows and conditional logic
- `references/output-patterns.md` — Template and example patterns
**Implementation order:**
1. Start with reusable resources (`scripts/`, `references/`, `assets/`)
2. Test added scripts by running them
3. Delete unused example files from initialization
4. Update SKILL.md
**Writing guidelines:** Always use imperative/infinitive form.
**Frontmatter:**
- `name`: The skill name — use **domain-action** naming: `{domain}-{action}`.
The domain is the system/area the skill operates on, the action is what it does.
Examples: `pr-review`, `sprint-plan`, `docs-search`, `git-commit`, `debt-scan`.
Multi-action wrappers (like `ticket`) can use the domain name alone.
The directory name must match the `name` field.
- `description`: Primary triggering mechanism. Include what the skill does AND
specific triggers/contexts. All "when to use" info goes here (not in body).
**Body:** Instructions for using the skill and its bundled resources.
### Step 5: Validate the Skill
Check the skill manually:
- Frontmatter has `name` and `description`
- SKILL.md body is under 500 lines
- No extraneous files (README.md, CHANGELOG.md, etc.)
- Scripts are executable and tested
- References are referenced from SKILL.md
### Step 6: Iterate
After real usage, notice struggles or inefficiencies, identify needed updates,
implement changes, and test again.
+6 -3
View File
@@ -6,9 +6,10 @@ description: >
lifecycle, theme loading, or native platform code. Also use when
the user says "run testmode", "test the app", "smoke test", or
"verify the build". Triggers on changes to: lib/kernel/src/toolchain.dart,
lib/src/daemon/dispatcher.dart, lib/src/ipc/, lib/extension/,
lib/kernel/src/theme/, lib/builtin/*/src/extension.dart,
linux/CMakeLists.txt, macos/, Makefile (run targets), lib/main.dart.
lib/src/daemon/dispatcher.dart, lib/src/ipc/, lib/src/pty/,
lib/builtin/terminal/, lib/extension/, lib/kernel/src/theme/,
lib/builtin/*/src/extension.dart, linux/CMakeLists.txt, macos/,
Makefile (run targets), lib/main.dart.
---
# Testmode harness
@@ -26,6 +27,7 @@ make run-testmode # all categories, 60s timeout
make run-testmode TESTMODE_CATEGORY=toolchain
make run-testmode TESTMODE_CATEGORY=ipc
make run-testmode TESTMODE_CATEGORY=extensions
make run-testmode TESTMODE_CATEGORY=terminal
make run-testmode TESTMODE_TIMEOUT=120 # longer timeout for slow builds
```
@@ -47,6 +49,7 @@ Exit code is non-zero when any test fails. The Makefile verifies
| IPC envelope, dispatcher, schema | `ipc` | Round-trip + error contract |
| Extension manifest, activate, contributions | `extensions` | Register + activate lifecycle |
| Theme YAML, loader, palette | `extensions` | Theme parse is in this category |
| PTY spawning, terminal pane, xterm wiring | `terminal` | Real PTY spawn + I/O round-trip |
| Platform config (CMakeLists, pbxproj, Makefile) | `all` | Full rebuild validates everything |
| Any doubt | `all` | ~30s, cheap insurance |
File diff suppressed because it is too large Load Diff
+17 -23
View File
@@ -3,27 +3,22 @@
Mirrors `assets/fonts/phosphor/codepoints.csv` (the full bundled glyph set)
into a readable, greppable markdown table at
`.claude/skills/ui-design/references/phosphor-glyphs.md`, flagging which
glyphs clide already defines in `lib/widgets/src/icons/phosphor.dart`.
`.claude/skills/ui-design/references/phosphor-glyphs.md`.
That table is also the source of truth for the `kPhosphorGlyphs` map — after
regenerating it, re-run `dart run tool/gen_phosphor_glyphs.dart` so
`lib/widgets/src/icons/phosphor_glyphs.g.dart` stays in sync.
Run from the repo root: python3 .claude/skills/ui-design/scripts/gen-phosphor-glyphs.py
"""
import csv
import pathlib
import re
ROOT = pathlib.Path(__file__).resolve().parents[4]
CSV = ROOT / "assets/fonts/phosphor/codepoints.csv"
DART = ROOT / "lib/widgets/src/icons/phosphor.dart"
OUT = ROOT / ".claude/skills/ui-design/references/phosphor-glyphs.md"
# Codepoint -> camelCase accessor already defined in PhosphorIcons.
defined = {}
for m in re.finditer(r"static const (\w+) = PhosphorIconPainter\((0x[0-9a-fA-F]+)\)", DART.read_text()):
defined[int(m.group(2), 16)] = m.group(1)
rows = list(csv.DictReader(CSV.open()))
n_def = sum(1 for r in rows if int(r["codepoint"], 16) in defined)
lines = [
"---",
@@ -37,22 +32,21 @@ lines = [
f"All **{len(rows)}** glyphs bundled in clide's Phosphor font "
"(`assets/fonts/phosphor/`, MIT). Generated from "
"`assets/fonts/phosphor/codepoints.csv` — **do not hand-edit**; regenerate with "
"`python3 .claude/skills/ui-design/scripts/gen-phosphor-glyphs.py`.",
"`python3 .claude/skills/ui-design/scripts/gen-phosphor-glyphs.py`, then re-run "
"`dart run tool/gen_phosphor_glyphs.dart` (this table is the source for the "
"generated `kPhosphorGlyphs` map).",
"",
f"The **In clide** column flags the **{n_def}** glyphs already wired into "
"`PhosphorIcons` (`lib/widgets/src/icons/phosphor.dart`) — reach for those first. "
"To use any other glyph, add a one-line `static const` to that class with the "
"codepoint below, then `ClideIcon(PhosphorIcons.<name>, size: 13)`. Keep additions "
"to icons we actually use — don't bulk-import.",
"Every glyph is available in code via `PhosphorIcons.byName('<kebab-name>')` "
"(T-314) — e.g. `ClideIcon(PhosphorIcons.byName('folder'), size: 13)`. Raw "
"codepoints never appear in feature code; they live only in the generated map. "
"An unknown name renders the `placeholder` glyph, and `phosphor_glyphs_test` "
"asserts every `byName('')` literal in `lib/` resolves.",
"",
"| Codepoint | Name (kebab) | Pascal | In clide |",
"|---|---|---|---|",
"| Codepoint | Name (kebab) | Pascal |",
"|---|---|---|",
]
for r in rows:
cp = r["codepoint"]
accessor = defined.get(int(cp, 16))
in_clide = f"`PhosphorIcons.{accessor}`" if accessor else ""
lines.append(f"| `{cp}` | {r['name']} | {r['pascal_name']} | {in_clide} |")
lines.append(f"| `{r['codepoint']}` | {r['name']} | {r['pascal_name']} |")
OUT.write_text("\n".join(lines) + "\n")
print(f"wrote {OUT.relative_to(ROOT)}{len(rows)} glyphs, {n_def} already defined")
print(f"wrote {OUT.relative_to(ROOT)}{len(rows)} glyphs")
+22 -22
View File
@@ -4,7 +4,8 @@ description: >
Surface the best batch of tickets to pick up next from pql. Walks the
initiative/epic tree, filters to unblocked work, refines context via
parallel agents (or `pql ticket refine` for empty descriptions), and
optionally activates the batch on a fresh branch. Use when the user
optionally activates the batch (directly on main — no topic branch by
default). Use when the user
says "what's next", "next batch", "pick up work", or invokes
/whats-next. NOT triggered by "what should we work on" in a design
context — that's a discussion, not a batch selection.
@@ -18,7 +19,8 @@ Dependency-driven batch selection against pql. Three steps:
batch selection → refinement review → batch activation.
Pql is the single source of truth for tickets and decisions in this repo
(see [pql skill](../pql/SKILL.md) and [`decisions/README.md`](../../../decisions/README.md)). Always run from the repo root.
(see the pql skill — user scope, installed via `pql init` — and
[`governance/README.md`](../../../governance/README.md)). Always run from the repo root.
## Step 0: Sync state
@@ -158,8 +160,8 @@ Agent({
Your job:
1. Run `pql decisions show <decision_ref> --with-refs --pretty` and
read the linked D/Q-record in decisions/<domain>.md.
2. Grep decisions/questions-*.md for related Q-records.
read the linked D/Q-record in governance/decisions/<domain>.md.
2. Grep governance/questions/<domain>.md for related Q-records.
3. Verify referenced files, classes, and APIs actually exist in the
current tree (Read/Grep). Flag dangling references.
4. Cross-check against CLAUDE.md guardrails (single process, CLI-first,
@@ -217,24 +219,22 @@ Batch transition (comma-separated IDs):
pql ticket status T-1,T-2,T-3 in_progress
```
**Then persist it.** Ticket mutations (status here, and any `ticket new` in
Step 2) land only in the gitignored `.pql/pql.db`. The post-checkout/post-merge
hooks rebuild that DB from the committed changelog on every branch switch — so
un-exported changes vanish silently the next time anyone switches branches. After
creating or transitioning tickets, always:
```bash
pql plan export # regenerates .pql/changelog/*.sql
git add .pql/changelog && git commit # durable; survives rebuild
```
See the [pql skill](../pql/SKILL.md#versioning-planning-state--data-loss-footgun-read-this).
**Then make sure it persists.** Ticket mutations (status here, and any
`ticket new` in Step 2) land only in the gitignored `.pql/pql.db`. The
pre-commit hook runs `pql plan export --stage` automatically — the changelog
is exported and staged on every commit, so never hand-run the export or
`git add .pql/changelog`. The rule is simpler: the turn must land at least
one commit (through the git-commit skill). A ticket-only turn with no commit
leaves the mutations in `pql.db` only, and the post-checkout/post-merge hooks
rebuild that DB from the committed changelog on the next branch switch —
silently dropping them. See the pql skill's "Versioning planning state"
section for the full mechanics.
### 3b. Branch? Default no.
Solo-dev flow on this repo — work lands directly on `main` (see recent
`git log`). Don't create a topic branch unless the user explicitly asks.
If they do, plain `git checkout -b` is fine; there is no `gh` CLI.
If they do, plain `git checkout -b` is fine.
### 3c. Spawn implementation agents (optional)
@@ -263,13 +263,13 @@ End with a tight summary:
## Anti-patterns
- Don't skip Step 0 — stale `pql.db` makes the rest of the skill lie.
- Don't leave ticket changes un-exported — `pql.db` is gitignored and the
post-checkout/post-merge hooks rebuild it from the committed changelog, so a
branch switch silently drops un-exported tickets. Always `pql plan export` +
commit `.pql/changelog/` after mutating tickets (Step 3a).
- Don't hand-export or hand-stage `.pql/changelog/` — the pre-commit hook does
both on every commit. The real footgun is a turn that mutates tickets but
never commits: `pql.db` is gitignored and gets rebuilt from the committed
changelog on branch switch, silently dropping un-committed mutations. Land
at least one commit per ticket-mutating turn (Step 3a).
- Don't activate a batch the user hasn't confirmed.
- Don't spawn refinement agents for tickets that have no description — use
`pql ticket refine` instead; it's cheaper and writes back through the
proper channel.
- Don't reach for `gh` — this system doesn't have it. Plain `git` only.
- Don't `cd` into subdirectories — run everything from the repo root.
+2
View File
@@ -0,0 +1,2 @@
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FJ4D0GAXM1FN42PJKG7FE34G', 'T-507', '2026-07-02 09:41:05.241', '2026-07-02 09:41:05.241', NULL, 'd9dec9b9ed45db65467318f53eb05157', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FJ4D1M6ESSSQ1ZERTH4V9YN4', 'T-508', '2026-07-02 09:41:14.425', '2026-07-02 09:41:14.425', NULL, '5389a964251dc22408c41ceac6954f4d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
+9
View File
@@ -3,3 +3,12 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
PROGRESS (2026-06-29): force-directed layout SOLVER built + tested (lib/src/graph/force_layout.dart, 6 dart-test cases, commit bcef3a6f) — deterministic Fruchterman-Reingold, Flutter-free. REMAINING (the bulk, multi-session): graph data model from pql backlinks/outlinks over the vault; CustomPaint rendering (nodes+edges); pan/zoom; hover-highlight-subgraph; click-opens-note; filter pane (tag/glob/depth); MultitabPane + context-panel slot (D-47) wiring; widget/golden tests. This is a full interactive Tier-5 pane, a fresh-session undertaking — only its solver core is done.
PROGRESS (2026-07-02): the graph rendering + interaction foundation is BUILT + tested (4 slices, all committed). (1) force-directed solver — force_layout.dart (bcef3a6f). (2) link-graph model — vault_graph.dart: VaultGraph.fromOutlinks, neighborhood, edgePairs (084bf842). (3) CustomPaint renderer — graph_painter.dart: edges/nodes/labels, hover-dim, fit transform (6bea1723). (4) interactive GraphView — graph_view.dart: hover-highlight + click-to-open, shared GraphViewport hit-testing (c529703b). The graph is a fully working WIDGET (renders + hover + click). REMAINING = the app-integration phase: pql link-data wiring (pql outlinks over the vault -> the file->outlinks map -> VaultGraph), the pane shell (async load + filter: tag/glob/depth + pan/zoom), and MultitabPane + context-panel slot (D-47) + GraphExtension registration.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 11:17:23', '2026-07-02 06:35:03.555', NULL, 'a46ad64b03bc1b4126e9882ed7b81a3a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2G2KHKT5CJYR0TK1WQGMD0', 'story', '06FHAX7FV5KWGZQ31617R63W94', 'Tier-5 graph view (builtin.graph)', 'Tier-5 graph VIEW, absorbed from the former T-7 into the unified canvas epic (T-317). Force-directed layout of the vault (or a filtered subset): nodes are notes, edges are wikilinks. Hover highlights the connected subgraph; click opens the note in the editor. Filter pane: tag include/exclude, file glob, depth-from-active. pql provides the link data (pql backlinks / pql outlinks); rendering is owned in-app (CustomPaint, own-the-rendering-stack). Lives in its own slot per D-47 (context panel), uses MultitabPane (T-83). DISTINCT from the in-card graph TEMPLATE (T-321): T-321 renders a graph passed into the conversation drawing card; this is the full interactive graph pane over the whole vault. Relates to the core renderer (T-318) but has its own force-directed layout.
PROGRESS (2026-06-29): force-directed layout SOLVER built + tested (lib/src/graph/force_layout.dart, 6 dart-test cases, commit bcef3a6f) — deterministic Fruchterman-Reingold, Flutter-free. REMAINING (the bulk, multi-session): graph data model from pql backlinks/outlinks over the vault; CustomPaint rendering (nodes+edges); pan/zoom; hover-highlight-subgraph; click-opens-note; filter pane (tag/glob/depth); MultitabPane + context-panel slot (D-47) wiring; widget/golden tests. This is a full interactive Tier-5 pane, a fresh-session undertaking — only its solver core is done.
PROGRESS (2026-07-02): the graph rendering + interaction foundation is BUILT + tested (4 slices, all committed). (1) force-directed solver — force_layout.dart (bcef3a6f). (2) link-graph model — vault_graph.dart: VaultGraph.fromOutlinks, neighborhood, edgePairs (084bf842). (3) CustomPaint renderer — graph_painter.dart: edges/nodes/labels, hover-dim, fit transform (6bea1723). (4) interactive GraphView — graph_view.dart: hover-highlight + click-to-open, shared GraphViewport hit-testing (c529703b). The graph is a fully working WIDGET (renders + hover + click). REMAINING = the app-integration phase: pql link-data wiring (pql outlinks over the vault -> the file->outlinks map -> VaultGraph), the pane shell (async load + filter: tag/glob/depth + pan/zoom), and MultitabPane + context-panel slot (D-47) + GraphExtension registration.
PROGRESS (2026-07-02, session 2): app-integration phase — 3 slices committed. (1) GraphController (graph_controller.dart, 3b67725b) — pql.files + per-file pql.outlinks over the vault -> VaultGraph; loading/error state; debounced files.changed refresh; 6 tests. (2) GraphPanel shell (graph_panel.dart, 6fae1bc4) — loading/empty/error states + GraphView + click-to-open via editor.open; 5 widget tests. (3) GraphExtension registration (extension.dart + builtin.graph i18n, a5acb6ac) — graph.view context-panel tab (D-47), activates after builtin.pql dep; 4 tests; CHANGELOG entry. The graph is now REACHABLE end-to-end (21 graph tests green). REMAINING (T-323 enrichment): pan/zoom on the view; filter controls (tag include/exclude, glob, depth-from-active).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 11:17:23', '2026-07-02 08:33:27.518', NULL, 'dc254ed9b53362c45d11ac23f600b57d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FJ4D0GAXM1FN42PJKG7FE34G', 'task', NULL, 'Skill cleanup: apply 2026-07-02 estate-review fixes to clide repo + user scope', 'Apply verified findings from the estate-wide skill review: delete commit/penpot-login/clean-house(project copy) skills; centralize d2-diagram/frame0-wireframe/skill-create to user scope de-contaminated; repoint whats-next (governance/, gh, dead links, step 3a); fix git-commit heredoc guidance + stale sidecar/ADR refs; fix clide skill dock slot + description trim; testmode terminal category; ui-design phosphor glyph reference post-T-314; rewrite skills README index.', 'backlog', 'medium', NULL, NULL, NULL, '2026-07-02 09:41:05.239', '2026-07-02 09:41:05.239', NULL, 'c5ddebbb4ffe8611329732087dd1cebb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FJ4D1M6ESSSQ1ZERTH4V9YN4', 'task', NULL, 'Distribute the clide skill per-workspace with an install marker', 'Estate review 2026-07-02: the clide skill is repo-agnostic and hand-copied into other repos (settled-reach), drifting. Long-term story: clide installs the skill into each workspace''s .claude/skills/ with a .pql-install.json-style marker (version + sha256) so staleness is detectable, following pql''s clean-house precedent. Until then the canonical copy lives in the clide repo.', 'backlog', 'medium', NULL, NULL, NULL, '2026-07-02 09:41:14.420', '2026-07-02 09:41:14.420', NULL, '698b529e4ea8e4417ddb3b9846b30c13', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);