Compare commits
2
Commits
main
..
fa680ff2e4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa680ff2e4 | ||
|
|
72c20701b7 |
@@ -6,6 +6,3 @@
|
|||||||
# Python
|
# Python
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
|
||||||
# dev-tickets CLI dependencies, installed by install-packages.sh (npm ci)
|
|
||||||
home/.local/lib/dev-tickets/node_modules/
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
[submodule "mattpocock-skills"]
|
|
||||||
path = mattpocock-skills
|
|
||||||
url = ssh://git@git.grosinger.net:22322/tgrosinger/mattpocock-skills.git
|
|
||||||
@@ -6,7 +6,3 @@
|
|||||||
|
|
||||||
`run-stow.sh` just links the dotfiles from this repo into their correct locations.
|
`run-stow.sh` just links the dotfiles from this repo into their correct locations.
|
||||||
|
|
||||||
## Docs
|
|
||||||
|
|
||||||
[`docs/claude-resource-limits.md`](docs/claude-resource-limits.md) — why `dev-claude` launches through `systemd-run` into `claude.slice`, and what breaks if that wrapper is simplified.
|
|
||||||
|
|
||||||
|
|||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
podman run --rm -i \
|
||||||
|
-v ${HOME}/Music:/downloads:z \
|
||||||
|
--userns keep-id:uid=1000,gid=1000 \
|
||||||
|
--entrypoint scdl \
|
||||||
|
yt-dlp:latest $@
|
||||||
|
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
podman run --rm -i \
|
||||||
|
-v ${HOME}/Music:/downloads:z \
|
||||||
|
--userns keep-id:uid=1000,gid=1000 \
|
||||||
|
--entrypoint spotdl \
|
||||||
|
yt-dlp:latest $@
|
||||||
|
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
podman run --rm \
|
||||||
|
-v ${HOME}/Videos/youtube:/downloads:z \
|
||||||
|
--userns keep-id:uid=1000,gid=1000 \
|
||||||
|
yt-dlp:latest -S 'res:1080' $@
|
||||||
|
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
podman run --rm \
|
||||||
|
-v ${HOME}/Videos/youtube:/downloads:z \
|
||||||
|
--userns keep-id:uid=1000,gid=1000 \
|
||||||
|
yt-dlp:latest --embed-metadata -o "%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s" -S 'res:1080' $@
|
||||||
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
# Claude Code resource limits
|
|
||||||
|
|
||||||
## The problem this solves
|
|
||||||
|
|
||||||
Runaway subprocess trees under Claude Code — parallel vitest workers, `tsc`, eslint — periodically locked this machine up hard enough that rebooting was the only way out.
|
|
||||||
|
|
||||||
The cause is **not** CPU starvation. With 16 cores the scheduler timeslices through a busy load fine. It is memory exhaustion driving **zram thrash**: swap on this machine is 8 GiB of zram *only*, so swapping consumes physical RAM and burns CPU compressing, which frees less memory, which swaps harder. The kernel OOM killer fires far too late to save the interactive session, and sway stops being scheduled long enough to accept input.
|
|
||||||
|
|
||||||
At the time this was set up, nothing on the box caught that spiral:
|
|
||||||
|
|
||||||
| Mechanism | State |
|
|
||||||
| --- | --- |
|
|
||||||
| `kernel.sysrq` | `16` — only `sync` permitted, so `Alt+SysRq+F` was blocked |
|
|
||||||
| `systemd-oomd` | inactive and disabled |
|
|
||||||
| `earlyoom` | not installed |
|
|
||||||
| `sshd` | inactive — no remote rescue path |
|
|
||||||
|
|
||||||
There was no escape hatch, which is why rebooting was the only recovery.
|
|
||||||
|
|
||||||
## The fix
|
|
||||||
|
|
||||||
Every Claude session launched through `dev-claude` runs inside a transient systemd scope placed in **`claude.slice`**, which carries a hard memory ceiling. A runaway tree now hits that ceiling and is OOM-killed *inside its own cgroup*, while sway and everything else keep running.
|
|
||||||
|
|
||||||
This needs no root: `cpu memory pids` are already delegated to the user manager (`Delegate=yes` on `user@1000.service`).
|
|
||||||
|
|
||||||
### Files involved
|
|
||||||
|
|
||||||
| File | Role |
|
|
||||||
| --- | --- |
|
|
||||||
| `home/.config/systemd/user/claude.slice` | The limits themselves |
|
|
||||||
| `home/.config/fish/functions/dev-claude.fish` | Places each session into the slice |
|
|
||||||
|
|
||||||
### The launcher
|
|
||||||
|
|
||||||
`dev-claude.fish` builds a launch prefix once and uses it at all four launch sites:
|
|
||||||
|
|
||||||
```fish
|
|
||||||
set -l launch /usr/bin/env claude
|
|
||||||
if command -q systemd-run
|
|
||||||
set launch systemd-run --user --scope --quiet --collect --slice=claude.slice -- /usr/bin/env claude
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Because `systemd-run --scope` **execs** rather than forks, the resulting process tree and command line are byte-identical to running `claude` directly. The only difference is which cgroup it lands in.
|
|
||||||
|
|
||||||
## Three non-obvious things
|
|
||||||
|
|
||||||
These are the parts that will look wrong later and get "cleaned up". Don't.
|
|
||||||
|
|
||||||
### 1. `/usr/bin/env` is load-bearing
|
|
||||||
|
|
||||||
`systemd-run` resolves its target through `PATH` and **rewrites `argv[0]` to an absolute path**:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ systemd-run --user --scope --quiet -- argvprobe.sh
|
|
||||||
argv0=[/tmp/argvprobe.sh] # not "argvprobe.sh"
|
|
||||||
```
|
|
||||||
|
|
||||||
`~/.tmux.conf` sets `@resurrect-processes '"claude->dev-claude *"'`, and tmux-resurrect matches that with an **anchored** regex — `scripts/process_restore_helpers.sh`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
if [[ "$pane_full_command" =~ (^${match} ) ]] || [[ "$pane_full_command" =~ (^${match}$) ]]; then
|
|
||||||
```
|
|
||||||
|
|
||||||
So a bare `systemd-run … -- claude` makes the saved command `/home/tgrosinger/.local/bin/claude --session-id …`, which fails `^claude ` and **silently stops restoring Claude panes** — breaking the exact thing `dev-claude` exists to provide. There is no error; restore just quietly does nothing.
|
|
||||||
|
|
||||||
Interposing `env` fixes it, because `env` re-execs its target with `argv[0]` set to the bare name:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ systemd-run --user --scope --quiet -- /usr/bin/env sleep 137
|
|
||||||
cmdline: [sleep 137] # bare argv[0], as if run directly
|
|
||||||
ppid: bash # systemd-run exec'd; no extra process in the tree
|
|
||||||
```
|
|
||||||
|
|
||||||
`env` also does the PATH lookup that `command claude` used to do here, so a fish function or alias named `claude` still cannot shadow the real binary.
|
|
||||||
|
|
||||||
### 2. `dev-claude` must not `exec`
|
|
||||||
|
|
||||||
This one bit once already: for months every Claude pane was saved, matched nothing, and restored nothing — silently.
|
|
||||||
|
|
||||||
tmux-resurrect's `ps` save strategy does not read the pane process. It reads the pane process's **children** (`save_command_strategies/ps.sh`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ps -ao "ppid,args" | sed "s/^ *//" | grep "^${PANE_PID}" | cut -d' ' -f2-
|
|
||||||
```
|
|
||||||
|
|
||||||
`exec` makes Claude *become* the pane pid, so that grep returns Claude's own subprocesses instead of Claude. The saved `pane_full_command` came out as `socat UNIX-LISTEN:/tmp/claude-http-….sock …` — or empty, when nothing happened to be running — never `claude --session-id …`. Both fail `^claude `, so `@resurrect-processes` never fires.
|
|
||||||
|
|
||||||
Running Claude as a plain foreground child keeps fish at the pane pid, which is exactly where resurrect looks:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ fish -c 'echo $fish_pid; /usr/bin/env sleep 3' # no exec
|
|
||||||
286 285 fish -c …
|
|
||||||
317 286 sleep 3
|
|
||||||
-> resurrect saves: [sleep 3]
|
|
||||||
|
|
||||||
$ fish -c 'echo $fish_pid; exec /usr/bin/env sleep 3'
|
|
||||||
328 285 sleep 3
|
|
||||||
-> resurrect saves: []
|
|
||||||
```
|
|
||||||
|
|
||||||
The cost is that the pane returns to a fish prompt when Claude exits rather than closing. That is the intended trade — a `dev` layout does not collapse when a session ends.
|
|
||||||
|
|
||||||
### 3. `command` cannot be used in the fallback
|
|
||||||
|
|
||||||
The natural fallback is `set -l launch command claude`. While `dev-claude` still used `exec`, fish rejected it outright:
|
|
||||||
|
|
||||||
```
|
|
||||||
fish: The expanded command is a keyword.
|
|
||||||
set -l launch command echo; exec $launch ok
|
|
||||||
^~~~~~^
|
|
||||||
```
|
|
||||||
|
|
||||||
`exec` will not accept an expansion that begins with a builtin. That specific constraint is gone with `exec`, but `/usr/bin/env claude` stays in both branches: it gives the same shadowing guarantee, and keeping the two branches identical means the saved command line does not depend on whether `systemd-run` was found.
|
|
||||||
|
|
||||||
## Limits, and why each one
|
|
||||||
|
|
||||||
Set on the **slice**, not on each scope, so all concurrent sessions share one budget. Per-scope limits would let four sessions claim 4 × 14 GiB and defeat the point.
|
|
||||||
|
|
||||||
| Setting | Value | Reason |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `MemoryHigh` | `10G` | Soft ceiling — throttle and reclaim first, so there's back pressure before anything dies |
|
|
||||||
| `MemoryMax` | `14G` | Hard ceiling — OOM-kill inside the cgroup. Leaves ~16 GiB of 30 GiB for everything else, which normally sits near 13 GiB |
|
|
||||||
| `MemorySwapMax` | `2G` | The zram-specific piece. Uncapped, the slice can push 8 GiB into zram, which itself occupies physical RAM — that's the thrash spiral |
|
|
||||||
| `CPUWeight` | `50` | Only bites under contention, so nothing slows down when the box is idle |
|
|
||||||
| `CPUQuota` | `1200%` | Reserves ~4 of 16 cores for sway and the compositor unconditionally |
|
|
||||||
| `TasksMax` | `2048` | Bounds pid exhaustion from a runaway worker pool |
|
|
||||||
|
|
||||||
## Operating it
|
|
||||||
|
|
||||||
Kill every Claude session at once, without touching anything else:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl --user kill claude.slice
|
|
||||||
```
|
|
||||||
|
|
||||||
Watch live usage against the caps:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemd-cgtop --user
|
|
||||||
systemctl --user status claude.slice
|
|
||||||
```
|
|
||||||
|
|
||||||
Tune without restarting running sessions (edit the unit file to persist):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl --user set-property claude.slice MemoryMax=16G
|
|
||||||
```
|
|
||||||
|
|
||||||
After editing the unit file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl --user daemon-reload
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verifying it actually works
|
|
||||||
|
|
||||||
**The check that matters.** If the unit file is missing or `daemon-reload` was skipped, systemd **silently auto-creates `claude.slice` with no limits** — sessions launch fine, appear healthy, and are completely unbounded. There is no warning. Always confirm the real value:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/claude.slice/memory.max
|
|
||||||
# must print 15032385536, NOT "max"
|
|
||||||
```
|
|
||||||
|
|
||||||
Confirm a running session is actually inside the slice:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemd-cgls --user-unit claude.slice
|
|
||||||
```
|
|
||||||
|
|
||||||
Confirm tmux-resurrect is unaffected — this is the string its `ps` save strategy reads. It must be non-empty, must name `claude` and not one of its subprocesses, and must show a **bare** `claude`, not an absolute path:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ps -ao ppid,args | grep "^$(tmux display -p '#{pane_pid}') "
|
|
||||||
# claude --session-id <uuid>
|
|
||||||
```
|
|
||||||
|
|
||||||
Then check what actually landed on disk, which is the thing restore reads:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
grep -a claude ~/.local/share/tmux/resurrect/last | cut -f11
|
|
||||||
# :claude --session-id <uuid>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing containment safely
|
|
||||||
|
|
||||||
Do **not** force an OOM inside `claude.slice` to test it. The kernel picks the largest consumer in the cgroup, which is very likely to be a real session rather than the test process. Use an unrelated top-level slice instead — note that systemd treats `-` as a hierarchy separator, so a name like `claude-test.slice` would nest *inside* `claude.slice` and hit the same problem:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemd-run --user --scope --quiet --collect --slice=capcheck.slice \
|
|
||||||
-p MemoryMax=512M -p MemorySwapMax=0 \
|
|
||||||
-- python3 -c "b=bytearray()
|
|
||||||
while True: b.extend(bytes(1<<20))"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expect `Killed`, with the desktop unaffected.
|
|
||||||
|
|
||||||
## Known gap
|
|
||||||
|
|
||||||
Typing plain `claude` instead of `dev-claude` **bypasses the cap entirely**. Closing that would mean adding a `claude` fish function delegating to `dev-claude` — safe from recursion, since `env` resolves the binary through `PATH` and never re-enters fish.
|
|
||||||
|
|
||||||
## Deliberately not done here
|
|
||||||
|
|
||||||
Independent of this, and all requiring root:
|
|
||||||
|
|
||||||
- `kernel.sysrq=1`, restoring `Alt+SysRq+F` as an in-kernel escape hatch that works even when userspace is fully starved
|
|
||||||
- `earlyoom` (packaged in Fedora), which would also catch non-Claude runaways
|
|
||||||
- enabling `sshd` for remote rescue from another device
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
---
|
|
||||||
name: brainstorming
|
|
||||||
description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation."
|
|
||||||
source: Modified from https://github.com/obra/superpowers/blob/main/skills/brainstorming/SKILL.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Brainstorming Ideas Into Designs
|
|
||||||
|
|
||||||
Help turn ideas into fully formed designs and specs through natural collaborative dialogue.
|
|
||||||
|
|
||||||
Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval.
|
|
||||||
|
|
||||||
<HARD-GATE>
|
|
||||||
Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity.
|
|
||||||
</HARD-GATE>
|
|
||||||
|
|
||||||
## Anti-Pattern: "This Is Too Simple To Need A Design"
|
|
||||||
|
|
||||||
Every project goes through this process. A todo list, a single-function utility, a config change — all of them. "Simple" projects are where unexamined assumptions cause the most wasted work. The design can be short (a few sentences for truly simple projects), but you MUST present it and get approval.
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
You MUST create a task for each of these items and complete them in order:
|
|
||||||
|
|
||||||
1. **Explore project context** — check files, docs, recent commits
|
|
||||||
3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
|
|
||||||
4. **Propose 2-3 approaches** — with trade-offs and your recommendation
|
|
||||||
5. **Present design** — in sections scaled to their complexity, get user approval after each section
|
|
||||||
6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit
|
|
||||||
7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below)
|
|
||||||
8. **User reviews written spec** — ask user to review the spec file before proceeding
|
|
||||||
9. **Transition to implementation** — invoke skills such as grill-with-docs or to-prd to create implementation plan
|
|
||||||
|
|
||||||
## Process Flow
|
|
||||||
|
|
||||||
```dot
|
|
||||||
digraph brainstorming {
|
|
||||||
"Explore project context" [shape=box];
|
|
||||||
"Visual questions ahead?" [shape=diamond];
|
|
||||||
"Ask clarifying questions" [shape=box];
|
|
||||||
"Propose 2-3 approaches" [shape=box];
|
|
||||||
"Present design sections" [shape=box];
|
|
||||||
"User approves design?" [shape=diamond];
|
|
||||||
"Write design doc" [shape=box];
|
|
||||||
"Spec self-review\n(fix inline)" [shape=box];
|
|
||||||
"User reviews spec?" [shape=diamond];
|
|
||||||
"Transition to implementation" [shape=doublecircle];
|
|
||||||
|
|
||||||
"Explore project context" -> "Visual questions ahead?";
|
|
||||||
"Visual questions ahead?" -> "Ask clarifying questions" [label="no"];
|
|
||||||
"Ask clarifying questions" -> "Propose 2-3 approaches";
|
|
||||||
"Propose 2-3 approaches" -> "Present design sections";
|
|
||||||
"Present design sections" -> "User approves design?";
|
|
||||||
"User approves design?" -> "Present design sections" [label="no, revise"];
|
|
||||||
"User approves design?" -> "Write design doc" [label="yes"];
|
|
||||||
"Write design doc" -> "Spec self-review\n(fix inline)";
|
|
||||||
"Spec self-review\n(fix inline)" -> "User reviews spec?";
|
|
||||||
"User reviews spec?" -> "Write design doc" [label="changes requested"];
|
|
||||||
"User reviews spec?" -> "Transition to implementation" [label="approved"];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**The terminal state is writing a plan.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skills you invoke after brainstorming are grill-with-docs or to-prd.
|
|
||||||
|
|
||||||
## The Process
|
|
||||||
|
|
||||||
**Understanding the idea:**
|
|
||||||
|
|
||||||
- Check out the current project state first (files, docs, recent commits)
|
|
||||||
- Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first.
|
|
||||||
- If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle.
|
|
||||||
- For appropriately-scoped projects, ask questions one at a time to refine the idea
|
|
||||||
- Prefer multiple choice questions when possible, but open-ended is fine too
|
|
||||||
- Only one question per message - if a topic needs more exploration, break it into multiple questions
|
|
||||||
- Focus on understanding: purpose, constraints, success criteria
|
|
||||||
|
|
||||||
**Exploring approaches:**
|
|
||||||
|
|
||||||
- Propose 2-3 different approaches with trade-offs
|
|
||||||
- Present options conversationally with your recommendation and reasoning
|
|
||||||
- Lead with your recommended option and explain why
|
|
||||||
|
|
||||||
**Presenting the design:**
|
|
||||||
|
|
||||||
- Once you believe you understand what you're building, present the design
|
|
||||||
- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced
|
|
||||||
- Ask after each section whether it looks right so far
|
|
||||||
- Cover: architecture, components, data flow, error handling, testing
|
|
||||||
- Be ready to go back and clarify if something doesn't make sense
|
|
||||||
|
|
||||||
**Design for isolation and clarity:**
|
|
||||||
|
|
||||||
- Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently
|
|
||||||
- For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on?
|
|
||||||
- Can someone understand what a unit does without reading its internals? Can you change the internals without breaking consumers? If not, the boundaries need work.
|
|
||||||
- Smaller, well-bounded units are also easier for you to work with - you reason better about code you can hold in context at once, and your edits are more reliable when files are focused. When a file grows large, that's often a signal that it's doing too much.
|
|
||||||
|
|
||||||
**Working in existing codebases:**
|
|
||||||
|
|
||||||
- Explore the current structure before proposing changes. Follow existing patterns.
|
|
||||||
- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in.
|
|
||||||
- Don't propose unrelated refactoring. Stay focused on what serves the current goal.
|
|
||||||
|
|
||||||
## After the Design
|
|
||||||
|
|
||||||
**Documentation:**
|
|
||||||
|
|
||||||
- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`
|
|
||||||
- (User preferences for spec location override this default)
|
|
||||||
- Use elements-of-style:writing-clearly-and-concisely skill if available
|
|
||||||
- Commit the design document to git
|
|
||||||
|
|
||||||
**Spec Self-Review:**
|
|
||||||
After writing the spec document, look at it with fresh eyes:
|
|
||||||
|
|
||||||
1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them.
|
|
||||||
2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions?
|
|
||||||
3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition?
|
|
||||||
4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit.
|
|
||||||
|
|
||||||
Fix any issues inline. No need to re-review — just fix and move on.
|
|
||||||
|
|
||||||
**User Review Gate:**
|
|
||||||
After the spec review loop passes, ask the user to review the written spec before proceeding:
|
|
||||||
|
|
||||||
> "Spec written and committed to `<path>`. Please review it and let me know if you want to make any changes before we start writing out the implementation plan."
|
|
||||||
|
|
||||||
Wait for the user's response. If they request changes, make them and re-run the spec review loop. Only proceed once the user approves.
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
|
|
||||||
- Invoke the grill-with-docs skill to create a detailed implementation plan
|
|
||||||
- Do NOT invoke any other skill. grill-with-docs is the next step.
|
|
||||||
|
|
||||||
## Key Principles
|
|
||||||
|
|
||||||
- **One question at a time** - Don't overwhelm with multiple questions
|
|
||||||
- **Multiple choice preferred** - Easier to answer than open-ended when possible
|
|
||||||
- **YAGNI ruthlessly** - Remove unnecessary features from all designs
|
|
||||||
- **Explore alternatives** - Always propose 2-3 approaches before settling
|
|
||||||
- **Incremental validation** - Present design, get approval before moving on
|
|
||||||
- **Be flexible** - Go back and clarify when something doesn't make sense
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/codebase-design
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
---
|
|
||||||
name: daily-note-cleanup
|
|
||||||
description: Tidy a day's Obsidian daily note — create that day's note and the next day's from the template when missing, fill in the weather, and other cleanup tasks. Use when asked to clean up, tidy, or process yesterday's or a given day's daily note, or on a scheduled end-of-day run.
|
|
||||||
---
|
|
||||||
|
|
||||||
# daily-note-cleanup — tidy a day's daily note
|
|
||||||
|
|
||||||
The subject is one daily note: **yesterday's by default**, or a date the user names. Every step below reads and edits that note, plus the **next day's** note as the one place carried-forward work lands. Nothing here touches the vault's other notes.
|
|
||||||
|
|
||||||
The notes live at `<vault>/daily/<YYYY-MM-DD>.md`. The template is `<vault>/templates/daily.md`; its only placeholder is `{{date}}`.
|
|
||||||
|
|
||||||
Do the steps in order. Each edits the note in place — touch only what a step names and leave prose, ordering, and embeds exactly as they are. The vault syncs live from other devices, so a wholesale rewrite is a sync conflict waiting to happen: make targeted edits.
|
|
||||||
|
|
||||||
## 1. Resolve the two dates
|
|
||||||
|
|
||||||
```
|
|
||||||
date -d "yesterday" +%F # subject day, when the user names none
|
|
||||||
date -d "<subject> +1 day" +%F # the next day
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Create either note if it is missing
|
|
||||||
|
|
||||||
For a note that does not exist, read `<vault>/templates/daily.md`, replace `{{date}}` with that note's own date, and write it to `<vault>/daily/<date>.md`. A note that already exists is left untouched here — it may already hold a day's writing.
|
|
||||||
|
|
||||||
## 3. Carry pending tasks forward
|
|
||||||
|
|
||||||
Under the subject note's `## Work`, find the `Pending tasks:` line and the bullet list right under it. The block runs from that label to the next header or the next non-bullet, non-blank line. Move the whole block — label and bullets — into the **next day's** note under its `## Work`:
|
|
||||||
|
|
||||||
- Fresh next-day note: drop the block straight under `## Work`.
|
|
||||||
- Next-day `## Work` already carries a `Pending tasks:` list: append the bullets to it, don't add a second label.
|
|
||||||
|
|
||||||
Then delete the block from the subject note. If that empties `## Work`, step 6 removes the header.
|
|
||||||
|
|
||||||
No `Pending tasks:` under `## Work` means nothing to carry — skip.
|
|
||||||
|
|
||||||
## 4. Fill in the weather
|
|
||||||
|
|
||||||
If the subject note's frontmatter has no weather keys (`temp_high` and the rest), invoke the **weather** skill for the subject date; it owns reading the station and writing those keys. Already present — the scheduled run often fills yesterday — and you skip it. If weather refuses the day (a station gap, partial coverage, or a date that hasn't ended), leave the keys absent and move on.
|
|
||||||
|
|
||||||
## 5. Remove empty frontmatter fields
|
|
||||||
|
|
||||||
Delete any frontmatter line whose value is blank — `work_mood:` with nothing after the colon. Keep every field that has a value. If no frontmatter fields remain, remove the `---` fences.
|
|
||||||
|
|
||||||
## 6. Remove empty headers
|
|
||||||
|
|
||||||
Work from the deepest headers outward. Remove a header when nothing lives under it — everything from it to the next same-or-higher header is blank — and no sub-header of it survives. Removing an empty leaf can empty its parent, so re-check the parent.
|
|
||||||
|
|
||||||
Two kinds of header stay even when they look bare:
|
|
||||||
|
|
||||||
- **Embeds are content.** A header whose body is only `![[...]]` embed links stays (`## Articles`, `## Tasks`).
|
|
||||||
- **Structure is content.** A header with a surviving sub-header stays, even when its own lines are blank (`## Notes` over a `### ...` that has bullets).
|
|
||||||
|
|
||||||
Never remove the `# <date>` title. Collapse the blank lines a removed section leaves behind to a single blank between the headers that remain.
|
|
||||||
|
|
||||||
## What it looks like
|
|
||||||
|
|
||||||
A subject note the morning after, with empty template sections, blank moods, no weather yet, and a pending list under Work:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
work_mood:
|
|
||||||
general_mood:
|
|
||||||
weight:
|
|
||||||
---
|
|
||||||
# 2026-08-27
|
|
||||||
|
|
||||||
## Articles
|
|
||||||
|
|
||||||
![[Articles.base#Added Today]]
|
|
||||||
|
|
||||||
## Timeline
|
|
||||||
|
|
||||||
|
|
||||||
## Journal
|
|
||||||
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
|
|
||||||
## Work
|
|
||||||
|
|
||||||
|
|
||||||
Pending tasks:
|
|
||||||
|
|
||||||
- Deploy the account server again.
|
|
||||||
- Refactor ReviewRequestTypes.
|
|
||||||
|
|
||||||
## Tasks
|
|
||||||
|
|
||||||
![[Daily Note Tasks.base#Completed Today]]
|
|
||||||
```
|
|
||||||
|
|
||||||
After — moods removed, weather filled (step 4 succeeded here), the pending block carried into 2026-08-28, and Timeline/Journal/Notes/Work gone (Work emptied by the carry) while Articles and Tasks stay on their embeds:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
temp_high: 78.1
|
|
||||||
temp_low: 55.6
|
|
||||||
gdd_base50: 14.4
|
|
||||||
solar_energy_kwh_m2: 7.02
|
|
||||||
---
|
|
||||||
# 2026-08-27
|
|
||||||
|
|
||||||
## Articles
|
|
||||||
|
|
||||||
![[Articles.base#Added Today]]
|
|
||||||
|
|
||||||
## Tasks
|
|
||||||
|
|
||||||
![[Daily Note Tasks.base#Completed Today]]
|
|
||||||
```
|
|
||||||
|
|
||||||
The next day's note gains, under its `## Work`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Work
|
|
||||||
|
|
||||||
Pending tasks:
|
|
||||||
|
|
||||||
- Deploy the account server again.
|
|
||||||
- Refactor ReviewRequestTypes.
|
|
||||||
```
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
# Why dev-tickets is shaped this way
|
|
||||||
|
|
||||||
`SKILL.md` is the what and the how. This is the why — the constraints that forced each choice, the alternatives rejected, and the vault settings the model quietly depends on. Read it before changing the model; most of the obvious simplifications have already been tried on paper and don't survive contact with the tooling.
|
|
||||||
|
|
||||||
Designed 2026-08-10, replacing per-repo `.scratch/` directories. Revised 2026-08-28: the model is unchanged, but the mechanics moved from `tn` + `obsidian` to the file-based `ticket` CLI — see [2026-08-28: file-based on every host](#2026-08-28-file-based-on-every-host). The sections between here and there describe the constraints as they stood on the desktop with Obsidian running; where they talk about `tn` or `obsidian`, that is history.
|
|
||||||
|
|
||||||
## The goal
|
|
||||||
|
|
||||||
Everything about a piece of work — the idea, the planning, the spec, and the implementation tickets — in one place, queryable, with real dependency edges instead of prose cross-references like "that is ticket 03".
|
|
||||||
|
|
||||||
## The constraints that shaped it
|
|
||||||
|
|
||||||
These are properties of the tools, not preferences. Most of the design falls out of them.
|
|
||||||
|
|
||||||
**Grouping rides on `projects`, not on folders.** `path` is filterable, so directory layout *could* scope a query — but archiving moves a note to `TaskNotes/Archive/` and changes its path, while its `projects` edges survive. Folders organise notes for a human; `projects` is what a query can trust.
|
|
||||||
|
|
||||||
**`tn update` cannot write a task's body.** There is no `details` flag on update, so a body can only be set at creation — which `tn create --details-file` now does. Editing an existing body still goes through `obsidian`.
|
|
||||||
|
|
||||||
**Three CLI gaps were closed rather than worked around** (2026-08-11, effort `tn API feature gaps`): `--add-tags`/`--add-contexts`/`--add-projects` were silent no-ops that returned 200 with the task unchanged; `blockedBy` was invisible to both `tn update` and `--filter`; and `PUT {"blockedBy": []}` could not clear the field, because the deletion pass fired only on a literal `undefined` that JSON cannot express. The plugin gained `POST /api/tasks/:id/{tags,contexts,projects,dependencies}` and the CLI was wired to them. The two-CLI split below survives this, but shrank to what `tn` genuinely does not model.
|
|
||||||
|
|
||||||
**Subtasks are `projects`.** There is no parent field anywhere in TaskNotes. A task is a subtask of whatever its `projects` array wikilinks to, and the parent can be any note — including one that doesn't exist. The vault's own `TaskNotes/Views/relationships.base` defines its Subtasks view exactly this way.
|
|
||||||
|
|
||||||
**`isBlocked` is derived.** The plugin maintains a dependency index and computes `isBlocked` from `blockedBy` plus the live status of each blocker, exposing it as a read-only field on every task. Storing a `blocked` status alongside it would be a second source of truth that drifts.
|
|
||||||
|
|
||||||
## Choices, and what lost
|
|
||||||
|
|
||||||
**Vault over `.scratch/`.** `.scratch/` worked from a plain filesystem with no daemon; the vault needs Obsidian running. That's a real availability regression, accepted because a single location for idea → planning → implementation was the whole point.
|
|
||||||
|
|
||||||
Moving creation to `tn create` (2026-08-11) deepened it. Tickets used to be written straight to disk, so a stopped Obsidian cost only status updates; now it costs creation as well. Reading a ticket never depended on Obsidian and still doesn't — the notes are plain markdown on disk. The trade was accepted because hand-authoring had to reproduce the plugin's `fieldMapping` (`scheduled` → `date_scheduled`) by hand, and silently produced a note the plugin then rewrote.
|
|
||||||
|
|
||||||
**The spec and the planning map are one note.** They were briefly separate — a plain "effort note" holding the wayfinder map, with the spec as a task beneath it — because a map isn't naturally a task. That split existed only to give the map a non-task home, and it cost a query: with tickets two levels down, no single `projects:contains` reached the whole effort. Collapsing them is also closer to the truth, since the map's Destination is what graduates into the spec. The cost is that decision and implementation tickets become siblings with no structural distinction, which `ticket_type` now carries instead.
|
|
||||||
|
|
||||||
**Rejected: dual-parenting.** Giving implementation tickets both `[[Spec]]` and `[[Effort]]` would make one query span everything, but it overloads `projects` to mean both "hierarchy parent" and "effort membership" — the effort's Subtasks view would flatten the spec and every ticket into one list.
|
|
||||||
|
|
||||||
**Rejected: a tag per repo and per effort.** `tags:contains:"effort/network-isolation"` scopes at any depth and would have worked, but it creates unbounded tag vocabulary that duplicates the directory path. The `[[repo]]` wikilink gets the same result with the mechanism already in use.
|
|
||||||
|
|
||||||
**Repo project notes are optional** (revised 2026-08-11). They were originally specified as one plain note per repo. Two findings demoted them. Grouping never needed the note: `projects` matches on the frontmatter value, so a dangling `[[repo]]` queries identically to a resolved one — `[[tasknotes-cli]]` had been running that way unnoticed. And the documented `projects: []` frontmatter was inert, since the Subtasks filter opens with `file.hasTag("task")` and a project note deliberately has no such tag, so nothing ever read it.
|
|
||||||
|
|
||||||
What survives is narrower and worth keeping: the note's *path* is what `relationships.base` compares `this.file` against, so a repo with no note has no page to view its efforts on. That makes the note a landing page, created on demand, with a free-form body. It still must not be a task — one there would show up in `tn list` as something to do.
|
|
||||||
|
|
||||||
**The link binds without a note.** Making the note optional does not make an unmatched `[[repo]]` inert: Obsidian resolves case-insensitively across the whole vault, so `[[obsidian]]` attaches the repo's tickets to a personal `Obsidian.md` at the vault root and lists them in *its* Subtasks view. Queries stay correct throughout, which is why it goes unnoticed. Title collisions are checked by looking, with `-iname`.
|
|
||||||
|
|
||||||
**Tags for routing, a field for type.** The routing tags sit on the note as tags; `ticket_type` is a registered user field instead, because TaskNotes rewrites frontmatter on update and an unregistered key isn't guaranteed to survive the round-trip.
|
|
||||||
|
|
||||||
**Actor and stall are two axes, not one** (revised 2026-08-11). The original vocabulary was `needs-info` / `ready-for-agent` / `ready-for-human` — three mutually exclusive values answering *who acts next*. That framing made every value transient: claiming or closing a ticket should logically have cleared or rewritten the tag, and none of the transitions were written down. The first six real tickets all reached `Done` still carrying `ready-for-agent`, which under that reading is drift.
|
|
||||||
|
|
||||||
Renaming the actor values to `agent-step` / `human-step` changes the question the tag answers from "who is queued next" to "whose step is this", which is **durable** — true at creation and still true after completion, where it becomes a record of who did the work. That makes the observed behaviour correct rather than sloppy, and it costs nothing to maintain.
|
|
||||||
|
|
||||||
`needs-info` does not belong on that axis: it is not an actor, it is a stall. It became an orthogonal flag, so `agent-step` + `needs-info` reads as "agent work, currently stalled on a question" — a state the old mutually-exclusive vocabulary could not express at all without lying about who owned the ticket.
|
|
||||||
|
|
||||||
The one maintained rule is that the actor tag is **corrected at close** if the other actor did the work. Pure write-once was considered and rejected: it makes `agent-step AND status:Done` mean "was scoped for an agent" rather than "an agent shipped it", and the second is the question worth being able to ask.
|
|
||||||
|
|
||||||
The full transition table is in `SKILL.md`; it exists because its absence was the single largest source of uncertainty in the model.
|
|
||||||
|
|
||||||
**`setup` instead of wayfinder's `task`.** Upstream's fourth type is `task`, which collides twice here: with `implementation` (both mean "do work") and with the `task` tag that makes a note a task at all. `setup` names what the type is for — groundwork whose result later tickets depend on.
|
|
||||||
|
|
||||||
**Statuses collapse, nothing is archived.** Upstream ran two deliberately separate vocabularies: `blocked`/`needs-info`/`ready-for-agent`/`ready-for-human`/`wont-do`/`complete` for implementation tickets, and `claimed`/`resolved`/`out-of-scope` for decision tickets. Both fold onto the same four TaskNotes statuses. Archiving would have been the natural home for `wont-do`, but it moves the file to `TaskNotes/Archive/` and breaks the directory layout — hence the `Wont Do` status.
|
|
||||||
|
|
||||||
**Rejected: `curl`.** `PUT /api/tasks/:id` has no validation and accepts `blockedBy` and `projects` directly, which made it the obvious escape hatch until the Obsidian CLI turned out to cover the same ground with no auth token and no JSON assembly.
|
|
||||||
|
|
||||||
**Rejected: MCP.** The plugin ships an MCP server, currently disabled. Its `create_task` and `update_task` tools validate through a zod schema that has no `blockedBy` key, so an extra field is *stripped* before the handler sees it — strictly worse than the unvalidated HTTP route.
|
|
||||||
|
|
||||||
## Vault settings this depends on
|
|
||||||
|
|
||||||
Invisible from `SKILL.md`, and the model breaks quietly without them.
|
|
||||||
|
|
||||||
- **mdbase export enabled** — TaskNotes maintains `mdbase.yaml` and `_types/task.md` in the vault root. `ticket` opens the vault through that schema and refuses to run without a `task` type; the completed statuses (`tn_completed_values`) come from it, not from code.
|
|
||||||
- **Status `Wont Do`** — `isCompleted: true` so it drops off the frontier and `ticket` treats it as completed, `excludeFromCycle: true`, `autoArchive: false`.
|
|
||||||
- **Status `Done` with `autoArchive: false`** — changed from `true` for this system. Auto-archiving would move completed tickets out of their effort directory. Note this is global: personal completed tasks no longer move to `TaskNotes/Archive/` either.
|
|
||||||
- **User fields** `ticket_type`, `git_worktree`, `git_branch`, `agent_session` — all `text`, hidden from the creation and edit modals via `modalFieldsConfig`, since they're agent-written. Registering them is what puts them in `_types/task.md`, which is the only reason a file-based writer can be schema-faithful.
|
|
||||||
- **`taskIdentificationMethod: tag`** with `taskTag: task` and empty `excludedFolders` — this is what lets tickets live under `TaskNotes/Dev/` instead of `tasksFolder`. Switching identification to folder-based would orphan every dev ticket at once.
|
|
||||||
|
|
||||||
## Confirmed against a running vault
|
|
||||||
|
|
||||||
The three behaviours derived from source at design time were observed on 2026-08-11:
|
|
||||||
|
|
||||||
1. `tn list --json` does include `isBlocked` on every task, so the frontier query works as written.
|
|
||||||
2. `tn update --add-tags` was a silent no-op, exactly as suspected — and is now fixed.
|
|
||||||
3. `obsidian property:set` and `obsidian eval` behave as the app bundle suggested.
|
|
||||||
|
|
||||||
One behaviour was *not* anticipated: `obsidian append` writes only at end of file, so a `## Notes` entry lands under the trailing `## As Built` heading instead. `SKILL.md` gives the working form.
|
|
||||||
|
|
||||||
Probing `POST /api/tasks` the same day settled how creation works. The endpoint accepts `details` and `customProperties` — which is what made `tn create` able to replace hand-authoring — but **silently ignores** `folder`, `folderPath`, and `path`, filing every new note into `tasksFolder` regardless. `tn create --folder` therefore creates and then moves. Obsidian's watcher re-indexes the move immediately, and wikilinks resolve by basename, so nothing breaks; the note is seconds old and has no inbound links yet.
|
|
||||||
|
|
||||||
## Deferred
|
|
||||||
|
|
||||||
- The four live `.scratch/` directories (`atribot`, `claude-review`, `gnucash-visualizations`, `pinball-datasette`) stay where they are; new efforts use this system. Completed work isn't worth migrating, and the in-flight `atribot` tickets carry prose cross-references that need rewriting by hand.
|
|
||||||
- Making `POST /api/tasks` honour a folder would let `tn create --folder` drop its post-create move. The move is a workaround for a server-side gap, and it is the one step that touches the vault outside the plugin.
|
|
||||||
- `tn update` has no `--prop`, so custom properties still need `obsidian property:set` after creation. Adding it would retire the second CLI from the ticket workflow entirely.
|
|
||||||
|
|
||||||
## 2026-08-28: file-based on every host
|
|
||||||
|
|
||||||
### The constraint that changed
|
|
||||||
|
|
||||||
The skill assumed the desktop: a hardcoded vault path under `~/Documents`, `tn` (which needs the TaskNotes HTTP API inside a running Obsidian) and `obsidian` (which needs the desktop app). On tachi none of that exists — the vault is the obsync container's headless-sync mirror at another path, with no Obsidian process — so a session there could not create, claim, annotate, or close a ticket at all. The "vault over `.scratch/`" trade above had accepted needing Obsidian; a second host made that unacceptable.
|
|
||||||
|
|
||||||
### What replaced it
|
|
||||||
|
|
||||||
One file-based tool, `ticket`, over `@callumalpass/mdbase` — the same library and `_types/task.md` schema that TaskNotes' mdbase export already maintains in the vault. It reads and writes the markdown directly, so it behaves identically wherever the vault is on disk and whether or not Obsidian is running. The only per-host fact is the vault path, recorded once with `ticket vault --set` in `~/.config/mdbase-tasknotes/config.json` — the same file and key `mtn` would use, so the two could never disagree — and resolved as flag → `MDBASE_TASKNOTES_PATH` → that file, with no current-directory fallback. The model, the shape, the routing tags and the transition table are unchanged; only the mechanics moved, and the verbs enforce the rules the prose used to ask agents to remember (needs-info cleared at close, actor corrected at close, blockers must exist, efforts refuse to close over open tickets).
|
|
||||||
|
|
||||||
The package lives in dotfiles at `~/.local/lib/dev-tickets` (plain ESM JavaScript, `node --test`, mdbase the only dependency). There is no shell shim: the entry point's shebang is `#!/usr/bin/env -S MISE_NODE_VERSION=lts node`, which runs it under mise's lts Node from inside any repo regardless of that repo's pin, needs only mise's shims directory on PATH, and resolves `node_modules` from the file's real path so the stow symlink is transparent.
|
|
||||||
|
|
||||||
### Facts measured against mdbase 0.2.2 and the live vault
|
|
||||||
|
|
||||||
- mdbase's `update` rewrites the entire frontmatter in its own style: single-quoted wikilinks *and* dates where TaskNotes double-quotes and leaves them bare, trailing blank lines dropped, type defaults applied on read but never written. All valid YAML; TaskNotes rewrites in its own style when it next touches the note. `priority` is therefore written explicitly, and keys are emitted in TaskNotes' order, so tool-written notes look native.
|
|
||||||
- mdbase does not stamp the type's `generated:` fields for a v0.2 type. The tool stamps `date_created` / `date_modified` in TaskNotes' local-offset shape itself.
|
|
||||||
- mdbase's concurrency check covers only its own stat→write window inside `update()`. The tool records the mtime when it reads, compares in mdbase's `preWriteHook`, and refuses the write (exit 3) if the file changed — no retry; every verb is idempotent and re-running is the recovery. On tachi that guard is the only coordination with the atribot agent container sharing the vault copy, deliberately.
|
|
||||||
- `in` in `--where` errors with `invalid_expression` through the library; the earlier belief that it "silently returns nothing" was `mtn` swallowing the error.
|
|
||||||
- On the live vault (3,859 notes, 247 tasks): open ~50 ms, all tasks ~0.6 s, one effort ~20 ms.
|
|
||||||
- `.mdbase/cache.sqlite` in the vault root is a local artifact; it never appears in the sync log, while `mdbase.yaml` does.
|
|
||||||
- Vault-wide, case-insensitive title uniqueness is now enforced by `create`, not checked by hand; an ambiguous blocker (two notes with the same title) is treated like an unresolvable one — warned about and ignored — which surfaces the collision instead of guessing which note Obsidian would pick.
|
|
||||||
|
|
||||||
### What lost
|
|
||||||
|
|
||||||
- **A second host-side sync device, writes through the container, and ACLs.** The mirror at `/tachi/docker/atribot/vault` became user-owned instead; ZFS there is mounted `noacl`, so ACLs were never on the table.
|
|
||||||
- **`mtn` (mdbase-tasknotes) as the tool.** Its `create` is natural-language only and files into the default tasks folder; its `update` cannot touch projects, blockers, custom properties, or the body; its JSON carries no `isBlocked`. A wrapper was needed regardless, so the wrapper is the whole tool. It is not installed even as the writer of the config file — `ticket vault --set` covers that — and nothing is contributed upstream.
|
|
||||||
- **A shell shim** (the `tn` pattern). The shebang does the same job with no file to maintain; `tn` already ran off-pin (`installs/node/lts` was not the version pinned in `config.toml`), so the shim bought nothing the shebang does not.
|
|
||||||
- **`--folder` and `--project` on create.** The tool owns `TaskNotes/Dev/<repo>/<effort>/`, so `--repo` / `--effort` derive both, plus the effort's contexts for a ticket, and the effort/ticket flag validation falls out of that.
|
|
||||||
- **Auto-retry on a concurrent modification.** A retry that silently re-applied `close` over someone else's edit is exactly the clobber the guard exists to prevent.
|
|
||||||
- **Cascading effort close.** Refusing while children are open makes the human decide what to abandon.
|
|
||||||
- **An effort-agnostic frontier.** An agent always works one effort; the wider question is a `list --where`.
|
|
||||||
- **A `CONTEXT.md` and a `docs/adr/`.** `SKILL.md`'s Vocabulary is the glossary and this README is the decision log; a second copy of either would drift.
|
|
||||||
- **Detecting stale `In Progress` claims** from crashed sessions. Still a `list --where` question, not a model change.
|
|
||||||
|
|
||||||
Acceptance was dogfooding: this effort's own tickets were hand-written (nothing existed yet to write them), and each lifecycle step moved to `ticket` the moment the verb existed.
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
---
|
|
||||||
name: dev-tickets
|
|
||||||
description: Storage model for development tickets, specs, and planning maps, which live in the Obsidian vault as TaskNotes tasks and are read and written with the `ticket` CLI. Use when creating an effort or spec, writing implementation or decision tickets, finding the frontier, or claiming, annotating, and closing a ticket — and whenever /to-spec, /to-tickets, /wayfinder, or an implementor reads or writes them.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Dev tickets
|
|
||||||
|
|
||||||
Tickets, specs, and planning maps live in the Obsidian vault as TaskNotes tasks. They are read and written with **`ticket`**, a file-based CLI that works on the markdown directly — no running Obsidian, no HTTP API — so it behaves the same on every host. This skill owns the storage model: the shape on disk, the metadata, and the verbs that read and write it. The skills that produce tickets (`/to-spec`, `/to-tickets`, `/wayfinder`) own their own process and defer here for everything below.
|
|
||||||
|
|
||||||
**Where the vault is** is what `ticket vault` prints. It is configured once per host, and the skill never names a path:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ticket vault --set <vault path> # once per host; refuses a directory that is not the vault
|
|
||||||
```
|
|
||||||
|
|
||||||
If `ticket` says no vault is configured, ask the user for the path and run that. If `ticket` itself is missing, the dotfiles install script installs it (`npm ci` inside `~/.local/lib/dev-tickets`); the launcher prints the exact command when its dependencies are absent.
|
|
||||||
|
|
||||||
## Vocabulary
|
|
||||||
|
|
||||||
- **Vault** — the Obsidian vault, wherever it is on this host. The user-facing word for where tickets live.
|
|
||||||
- **Collection** — the vault as mdbase sees it: `mdbase.yaml` plus the `_types/task.md` schema that TaskNotes maintains. Used only when talking about the tool's mechanics.
|
|
||||||
- **Repo project** — the `[[repo]]` wikilink every effort in a repo shares. A note at that title is **optional**; see [Repo project notes](#repo-project-notes).
|
|
||||||
- **Effort** — one feature or change. A task whose body is the planning map, which graduates into the spec.
|
|
||||||
- **Decision ticket** — a subtask that answers a question. `/wayfinder`'s unit.
|
|
||||||
- **Implementation ticket** — a subtask that builds something. `/to-tickets`' unit.
|
|
||||||
- **Frontier** — the tickets that are open and not blocked.
|
|
||||||
- **Title** — a note's filename stem. It is the note's identity (every edge is a wikilink to it) and is never a frontmatter key.
|
|
||||||
- **Record** — what `--json` returns for a note: `path`, `title`, the derived `isBlocked`, then the frontmatter as it is on disk. `show --json` adds `body`.
|
|
||||||
|
|
||||||
## Shape
|
|
||||||
|
|
||||||
```
|
|
||||||
TaskNotes/Dev/atribot/
|
|
||||||
├── atribot.md ← repo project note; optional, NOT a task
|
|
||||||
└── Network isolation/
|
|
||||||
├── Network isolation.md ← effort task
|
|
||||||
├── Should obsync share a namespace.md ← decision ticket
|
|
||||||
└── Split obsync into its own container.md ← implementation ticket
|
|
||||||
```
|
|
||||||
|
|
||||||
One directory per repo, one per effort. Every note in an effort directory sits directly in it — the hierarchy is expressed by `projects`, not by nesting. `ticket create` owns this shape: it derives the directory and the parent link from `--repo` or `--effort`, so a note cannot be filed in the wrong place.
|
|
||||||
|
|
||||||
**A title is an identity.** Every parent and blocking edge is a `[[wikilink]]` to a title, so titles are globally unique across the vault and a published note is never renamed. Give each one a name that reads on its own — `Split obsync into its own container`, not `02-split-container`.
|
|
||||||
|
|
||||||
Uniqueness is **case-insensitive and vault-wide**, and it binds whether or not you created a note for the title: `projects: [[obsidian]]` would attach every ticket in that repo to a personal `Obsidian.md` anywhere in the vault. `ticket create` refuses a title that any note already carries, and `ticket show <title>` is the pre-check when choosing one (exit 2 means the title is free).
|
|
||||||
|
|
||||||
## Frontmatter
|
|
||||||
|
|
||||||
`ticket create` writes every one of these in a single call. The middle column names what sets it.
|
|
||||||
|
|
||||||
| Field | Set with | Values |
|
|
||||||
|---|---|---|
|
|
||||||
| `status` | `--status` at creation, then `claim` / `close` | `Open` · `In Progress` (claimed) · `Done` (complete, resolved) · `Wont Do` (abandoned, out of scope) |
|
|
||||||
| `contexts` | `--context` on an effort; tickets inherit the effort's | from the repo's `CLAUDE.local.md` |
|
|
||||||
| `projects` | derived: `--repo R` → `[[R]]`, `--effort E` → `[[E]]` | effort → `[[repo]]` · ticket → `[[effort]]` |
|
|
||||||
| actor tag | `--actor agent\|human` at creation; `close --actor` to correct it | `agent-step` · `human-step` — exactly one, durable, survives completion |
|
|
||||||
| `needs-info` | `stall` / `unstall` | tag, present only while stalled on an unanswered question |
|
|
||||||
| `blockedBy` | `--blocked-by` (repeatable) at creation; `set blockedBy+=` / `-=` later | list of `{uid, reltype}`; `reltype` is `FINISHTOSTART`; every blocker must resolve to an existing task |
|
|
||||||
| `ticket_type` | `--type` | `research` · `prototype` · `grilling` · `setup` · `implementation` |
|
|
||||||
| `git_branch` | `set git_branch=…` on the effort | effort tasks only; omit entirely when on main/master |
|
|
||||||
| `git_worktree` | `set git_worktree=…` on the effort | effort tasks only; omit entirely when the main worktree |
|
|
||||||
| `agent_session` | `claim` | `$CLAUDE_CODE_SESSION_ID`, overwritten each session that works the ticket |
|
|
||||||
|
|
||||||
The tool writes the rest itself: the `task` tag, `priority` (the vault's default), `date_created` and `date_modified` (bumped on every write), and `date_completed` at close. It never writes `date_scheduled` or a `title` key.
|
|
||||||
|
|
||||||
A ticket on disk reads:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
---
|
|
||||||
status: Open
|
|
||||||
priority: 2-Normal
|
|
||||||
contexts:
|
|
||||||
- Obsidian
|
|
||||||
projects:
|
|
||||||
- "[[Network isolation]]"
|
|
||||||
date_created: 2026-08-10T09:12:44.118-07:00
|
|
||||||
date_modified: 2026-08-10T09:12:44.118-07:00
|
|
||||||
blockedBy:
|
|
||||||
- uid: "[[Verify internal network publish]]"
|
|
||||||
reltype: FINISHTOSTART
|
|
||||||
tags:
|
|
||||||
- task
|
|
||||||
- agent-step
|
|
||||||
ticket_type: implementation
|
|
||||||
agent_session: 444e4dcb-de76-4f6b-8c39-7d08d6aa9f5f
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
Quoting varies by writer — `ticket` single-quotes wikilinks and dates, TaskNotes double-quotes and leaves them bare — and both are valid YAML; do not "fix" it.
|
|
||||||
|
|
||||||
`blocked` is not a status. `isBlocked` is derived from `blockedBy` and the live status of each blocker (TaskNotes does the same on the desktop), so blocking is recorded once, in `blockedBy`, and read back from `isBlocked` in every record. A blocker that does not resolve warns and does not block.
|
|
||||||
|
|
||||||
`ticket_type: setup` is groundwork that unblocks a decision — provisioning access, moving data — whose *result* later tickets depend on. It corresponds to `/wayfinder`'s "task" type.
|
|
||||||
|
|
||||||
## Body
|
|
||||||
|
|
||||||
Effort task — the map first, and the spec replacing `## Destination` once the way is clear. `/wayfinder` and `/to-spec` each own their own section content.
|
|
||||||
|
|
||||||
Ticket — `## Question` for a decision ticket, `## What to build` plus acceptance checkboxes for an implementation ticket. A resolved decision ticket gains `## Answer`.
|
|
||||||
|
|
||||||
Every effort and ticket task takes the same two trailing sections, which `ticket create` appends when the body lacks them:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
### [[2026-08-10]]
|
|
||||||
|
|
||||||
Squid rejects CONNECT to the registry on first run; the allowlist needs the CDN host too, not just the API host.
|
|
||||||
|
|
||||||
## As Built
|
|
||||||
```
|
|
||||||
|
|
||||||
`## Notes` is a running log — hurdles, surprises, design changes — appended **while the work happens** with `ticket note`, under a sub-heading wikilinking the current date.
|
|
||||||
|
|
||||||
`## As Built` is written at completion with `ticket as-built`, and only when the implementation materially deviated from what the ticket asked for.
|
|
||||||
|
|
||||||
## Repo configuration
|
|
||||||
|
|
||||||
Each repo's `CLAUDE.local.md` carries the context to stamp on its notes:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Dev tickets
|
|
||||||
|
|
||||||
- context: Obsidian
|
|
||||||
- repo: atribot # optional; defaults to the directory name
|
|
||||||
```
|
|
||||||
|
|
||||||
When a repo has no such block, ask the user for the context and offer to write the block. A guessed context files the effort under the wrong area and stays wrong.
|
|
||||||
|
|
||||||
## Creating
|
|
||||||
|
|
||||||
An effort names its repo and context; the tool files it at `TaskNotes/Dev/<repo>/<title>/<title>.md` with `projects: [[repo]]` and no actor tag:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ticket create --repo atribot --title 'Network isolation' --context Obsidian \
|
|
||||||
--set git_branch=feat/network-isolation \
|
|
||||||
--body-file - <<'BODY'
|
|
||||||
## Destination
|
|
||||||
|
|
||||||
...
|
|
||||||
BODY
|
|
||||||
```
|
|
||||||
|
|
||||||
A ticket names its effort; the tool files it beside the effort note with `projects: [[effort]]`, the effort's contexts, the actor tag, and `ticket_type`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ticket create --effort 'Network isolation' --title 'Split obsync into its own container' \
|
|
||||||
--type implementation --actor agent \
|
|
||||||
--blocked-by 'Verify internal network publish' --blocked-by 'Provision the registry mirror' \
|
|
||||||
--body-file - <<'BODY'
|
|
||||||
## What to build
|
|
||||||
|
|
||||||
- [ ] Move the compose service into its own namespace
|
|
||||||
BODY
|
|
||||||
```
|
|
||||||
|
|
||||||
`--type` and `--actor` are required for a ticket and refused for an effort; `--context` is the reverse. `--blocked-by` accepts bare or `[[bracketed]]` titles and every one must exist — a typo'd blocker would never block, so it is an error instead. Create in dependency order. `--set k=v` adds any other field (`git_worktree`, `timeEstimate`); `--json` prints the record.
|
|
||||||
|
|
||||||
Creation refuses — and writes nothing — when the target file exists or any note in the vault has the same title case-insensitively (exit 2). Resolving a collision is the user's choice.
|
|
||||||
|
|
||||||
### Repo project notes
|
|
||||||
|
|
||||||
Optional, and there is no need to create one. `projects: [[atribot]]` groups the repo's efforts whether or not a note by that name exists — queries match on the frontmatter value, so a dangling link queries exactly like a resolved one.
|
|
||||||
|
|
||||||
Create one only to give a repo a landing page. Its single job is to be the note Obsidian's Subtasks view renders on: `TaskNotes/Views/relationships.base` matches a task's `projects` against `this.file`, so with no note there is no page to see the effort list on. Nothing reads the note's own frontmatter — it is not a task, so TaskNotes skips it — which makes the body free-form. A description and the repo path is enough:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
Matrix bot and its companion services, deployed as compose stacks.
|
|
||||||
|
|
||||||
Repo: `/home/tgrosinger/code/atribot`
|
|
||||||
```
|
|
||||||
|
|
||||||
`ticket` only makes tasks. Write the file directly at `TaskNotes/Dev/<repo>/<repo>.md`.
|
|
||||||
|
|
||||||
## Routing tags
|
|
||||||
|
|
||||||
Two independent axes, neither of which is a status. `status` carries the lifecycle; these carry *who does this step* and *whether it can proceed*.
|
|
||||||
|
|
||||||
**Actor — `agent-step` or `human-step`.** Exactly one on every ticket, from creation onward. On an open ticket it names the intended next actor; on a closed ticket it records the actor who actually completed the work. It is durable: claiming and closing do not remove it, so a `Done` ticket still records whose step it was. Effort tasks carry no actor tag; only tickets do.
|
|
||||||
|
|
||||||
**Stall — `needs-info`.** Transient and orthogonal to the actor. It means the assigned actor cannot proceed without information from someone else; it does not reassign the ticket. Thus `agent-step` + `needs-info` means *agent work awaiting a human answer*, while `human-step` + `needs-info` means *human work awaiting information from another person or source*. Add it the moment work stalls, remove it when the question is answered.
|
|
||||||
|
|
||||||
The actor usually follows from `ticket_type`, and `/wayfinder` calls the same distinction HITL vs AFK:
|
|
||||||
|
|
||||||
| `ticket_type` | actor | why |
|
|
||||||
|---|---|---|
|
|
||||||
| `research` · `implementation` | `agent-step` | an agent drives it alone |
|
|
||||||
| `grilling` · `prototype` | `human-step` | resolves only through live exchange with the user |
|
|
||||||
| `setup` | either | depends on whether the agent can do the work itself |
|
|
||||||
|
|
||||||
`setup` is the case that must be decided per ticket — which is exactly why the actor is a tag and not derived from `ticket_type` at query time.
|
|
||||||
|
|
||||||
| Transition | `status` | actor tag | `needs-info` | verb |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| Created | `Open` | set, exactly one | absent | `create --actor` |
|
|
||||||
| Claimed | → `In Progress` | unchanged | unchanged | `claim` |
|
|
||||||
| Stalled on a question | unchanged | unchanged | **add** | `stall` |
|
|
||||||
| Question answered | unchanged | unchanged | **remove** | `unstall` |
|
|
||||||
| Reassigned to the other actor | unchanged | **swap** | unchanged | `set tags+=… tags-=…` |
|
|
||||||
| Completed | → `Done` | **swap if the other actor did the work** | must be absent | `close [--actor]` |
|
|
||||||
| Abandoned | → `Wont Do` | unchanged | remove if present | `close --wont-do` |
|
|
||||||
|
|
||||||
The verbs enforce the two rules that used to be easy to forget: `close` always clears `needs-info`, and `close --actor human` (or `agent`) corrects the actor tag when the other actor finished the work — which is what keeps `agent-step AND status:Done` a truthful record of what agents actually shipped. `set tags+=needs-info` and friends still work as an escape hatch, with a warning naming the verb that would have done it properly.
|
|
||||||
|
|
||||||
## Working a ticket
|
|
||||||
|
|
||||||
1. **Claim it** before any work, so a concurrent session skips it:
|
|
||||||
```sh
|
|
||||||
ticket claim 'Split obsync into its own container'
|
|
||||||
```
|
|
||||||
The session id comes from `$CLAUDE_CODE_SESSION_ID` (or `--session`). `claim` refuses a ticket that is blocked or already `In Progress` under another session; `--force` overrides both.
|
|
||||||
2. **Record the checkout** on the *effort* task. When the branch is not `main` or `master`, set `git_branch`. When working in a linked worktree rather than the repository's main worktree, also set `git_worktree` to the absolute root returned by `git rev-parse --show-toplevel`:
|
|
||||||
```sh
|
|
||||||
ticket set 'Network isolation' git_branch=feat/network-isolation git_worktree=/home/tgrosinger/code/atribot-ni
|
|
||||||
```
|
|
||||||
Omit `git_branch` on `main`/`master`, and omit `git_worktree` in the main worktree (`ticket set '<effort>' git_worktree=` removes it). If the checkout changes later, update both fields.
|
|
||||||
3. **Append notes as you go**; each lands under today's `### [[YYYY-MM-DD]]` heading (reused within a day), above `## As Built`:
|
|
||||||
```sh
|
|
||||||
ticket note 'Split obsync into its own container' 'Squid rejects CONNECT to the registry on first run.'
|
|
||||||
ticket note 'Split obsync into its own container' --file - # longer text on stdin
|
|
||||||
```
|
|
||||||
4. **Stall it** if you hit a question only the user can answer — the tag and the logged question are one write:
|
|
||||||
```sh
|
|
||||||
ticket stall 'Split obsync into its own container' 'Should the registry mirror share the proxy namespace?'
|
|
||||||
ticket unstall 'Split obsync into its own container' 'Yes — same namespace.'
|
|
||||||
```
|
|
||||||
Leave `status` at `In Progress` — the ticket is still yours, it just cannot proceed.
|
|
||||||
5. **Close it.** Write `## As Built` first if the implementation deviated, then:
|
|
||||||
```sh
|
|
||||||
ticket as-built 'Split obsync into its own container' 'Kept the socket; a namespace split was not needed.'
|
|
||||||
ticket close 'Split obsync into its own container' # Done
|
|
||||||
ticket close 'Split obsync into its own container' --actor human # the human finished it
|
|
||||||
ticket close 'Split obsync into its own container' --wont-do # abandoned
|
|
||||||
```
|
|
||||||
`close` stamps `date_completed` and clears `needs-info`. Closing an **effort** is refused while any of its tickets is still open — close or `--wont-do` them first; it never cascades.
|
|
||||||
|
|
||||||
Closing a ticket unblocks its dependents automatically; there is nothing to update on them.
|
|
||||||
|
|
||||||
To resume a ticket: `cd` to its effort's `git_worktree`, then `claude --resume <agent_session>`. When `git_worktree` is absent, use the repository's main worktree: the repo project note records its path when that note exists; otherwise resume from an existing checkout of that repo and use its root from `git rev-parse --show-toplevel`.
|
|
||||||
|
|
||||||
## Querying
|
|
||||||
|
|
||||||
Every reference (`REF`, `EFFORT`) is a vault-relative path or a title — bare or `[[bracketed]]`, case-insensitive. An ambiguous title is refused with the candidates listed.
|
|
||||||
|
|
||||||
The **frontier** of one effort — open and unblocked — and the **agent frontier**, what an agent may pick up right now (also `agent-step` and not stalled):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ticket frontier 'Network isolation'
|
|
||||||
ticket frontier 'Network isolation' --actor agent --json
|
|
||||||
```
|
|
||||||
|
|
||||||
**Awaiting a human** — both axes reach the human, so the inbox is their union, minus completed work, across the whole vault:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ticket inbox
|
|
||||||
```
|
|
||||||
|
|
||||||
Everything else is `list`, scoped to `TaskNotes/Dev/` unless `--all`; the filters AND together:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ticket list --effort 'Network isolation' # every ticket of the effort, any status
|
|
||||||
ticket list --repo atribot --open # the repo's open efforts
|
|
||||||
ticket list --open --where 'status == "In Progress"'
|
|
||||||
ticket list --all --where 'tags.contains("human-step") && exists("date_completed")'
|
|
||||||
```
|
|
||||||
|
|
||||||
`--where` is mdbase's expression language, not CEL: `==`, `!=`, `&&`, `||`, `!`, `.contains("x")`, `exists("field")`, `file.name`, indexing. `in` and CEL macros (`exists(t, …)`) are rejected as invalid expressions — the tool reports the error rather than returning nothing.
|
|
||||||
|
|
||||||
Text output is one row per note — status (with `blocked` / `needs-info` flags), actor, title, path. `--json` prints records; pipe to `jq`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ticket frontier 'Network isolation' --actor agent --json | jq -r '.[0].path'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cheatsheet
|
|
||||||
|
|
||||||
```
|
|
||||||
ticket vault [--set PATH] print the vault path, or record it for this host
|
|
||||||
ticket show REF print a note verbatim (--json: its record with body)
|
|
||||||
ticket list [--effort E] [--repo R] [--open] [--where EXPR] [--all] tasks under TaskNotes/Dev (or the whole vault with --all)
|
|
||||||
ticket frontier EFFORT [--actor agent|human] the effort's tickets that are Open and unblocked
|
|
||||||
ticket inbox human-step or needs-info, not completed, vault-wide
|
|
||||||
ticket create --repo R --title T --context C [--set k=v] [--body-file F|-]
|
|
||||||
ticket create --effort E --title T --type TYPE --actor agent|human [--blocked-by B]... [--body-file F|-]
|
|
||||||
ticket claim REF [--session ID] [--force] In Progress under this session
|
|
||||||
ticket note REF TEXT|--file F [--date YYYY-MM-DD] append to ## Notes, above ## As Built
|
|
||||||
ticket as-built REF TEXT|--file F append under ## As Built
|
|
||||||
ticket stall REF TEXT|--file F add needs-info and log the question
|
|
||||||
ticket unstall REF [TEXT|--file F] clear needs-info, optionally logging the answer
|
|
||||||
ticket close REF [--wont-do] [--actor agent|human] Done (or Wont Do), date_completed, needs-info cleared
|
|
||||||
ticket set REF k=v k= k+=v k-=v ... edit frontmatter; += / -= on tags, contexts, projects, blockedBy
|
|
||||||
global: --vault PATH --json --help
|
|
||||||
```
|
|
||||||
|
|
||||||
Exit codes: `1` usage or error, `2` refused (collision, blocked, ambiguous, not found, open children, not a task), `3` the file changed between read and write — nothing was written; re-run the command.
|
|
||||||
|
|
||||||
## Guardrails
|
|
||||||
|
|
||||||
- **Create a new ticket rather than renaming a published one.** Every inbound `projects` and `blockedBy` edge points at the title.
|
|
||||||
- **Body by hand is fine; frontmatter goes through `ticket`.** `set` and the verbs stamp `date_modified`, refuse non-tasks, keep the schema's types, and refuse to clobber a file another writer changed underneath them. A direct frontmatter edit does none of that.
|
|
||||||
- **There is no delete.** `ticket` never removes a note, and neither should you without the user's explicit say-so.
|
|
||||||
- `ticket` does not talk to Obsidian. When Obsidian is running on the same host it picks the change up like any external edit; when it is not, sync carries it.
|
|
||||||
|
|
||||||
Why the model is shaped this way, and what was rejected on the way here: [README.md](./README.md).
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/diagnosing-bugs
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/domain-modeling
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/grill-with-docs
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/productivity/grilling
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/productivity/handoff
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/implement
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/improve-codebase-architecture
|
|
||||||
@@ -1,644 +0,0 @@
|
|||||||
---
|
|
||||||
name: json-canvas
|
|
||||||
description: Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating visual canvases, mind maps, flowcharts, or when the user mentions Canvas files in Obsidian.
|
|
||||||
source: https://github.com/kepano/obsidian-skills/blob/main/skills/json-canvas/SKILL.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# JSON Canvas Skill
|
|
||||||
|
|
||||||
This skill enables Claude Code to create and edit valid JSON Canvas files (`.canvas`) used in Obsidian and other applications.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
JSON Canvas is an open file format for infinite canvas data. Canvas files use the `.canvas` extension and contain valid JSON following the [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/).
|
|
||||||
|
|
||||||
## File Structure
|
|
||||||
|
|
||||||
A canvas file contains two top-level arrays:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [],
|
|
||||||
"edges": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `nodes` (optional): Array of node objects
|
|
||||||
- `edges` (optional): Array of edge objects connecting nodes
|
|
||||||
|
|
||||||
## Nodes
|
|
||||||
|
|
||||||
Nodes are objects placed on the canvas. There are four node types:
|
|
||||||
- `text` - Text content with Markdown
|
|
||||||
- `file` - Reference to files/attachments
|
|
||||||
- `link` - External URL
|
|
||||||
- `group` - Visual container for other nodes
|
|
||||||
|
|
||||||
### Z-Index Ordering
|
|
||||||
|
|
||||||
Nodes are ordered by z-index in the array:
|
|
||||||
- First node = bottom layer (displayed below others)
|
|
||||||
- Last node = top layer (displayed above others)
|
|
||||||
|
|
||||||
### Generic Node Attributes
|
|
||||||
|
|
||||||
All nodes share these attributes:
|
|
||||||
|
|
||||||
| Attribute | Required | Type | Description |
|
|
||||||
|-----------|----------|------|-------------|
|
|
||||||
| `id` | Yes | string | Unique identifier for the node |
|
|
||||||
| `type` | Yes | string | Node type: `text`, `file`, `link`, or `group` |
|
|
||||||
| `x` | Yes | integer | X position in pixels |
|
|
||||||
| `y` | Yes | integer | Y position in pixels |
|
|
||||||
| `width` | Yes | integer | Width in pixels |
|
|
||||||
| `height` | Yes | integer | Height in pixels |
|
|
||||||
| `color` | No | canvasColor | Node color (see Color section) |
|
|
||||||
|
|
||||||
### Text Nodes
|
|
||||||
|
|
||||||
Text nodes contain Markdown content.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "6f0ad84f44ce9c17",
|
|
||||||
"type": "text",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 400,
|
|
||||||
"height": 200,
|
|
||||||
"text": "# Hello World\n\nThis is **Markdown** content."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Attribute | Required | Type | Description |
|
|
||||||
|-----------|----------|------|-------------|
|
|
||||||
| `text` | Yes | string | Plain text with Markdown syntax |
|
|
||||||
|
|
||||||
### File Nodes
|
|
||||||
|
|
||||||
File nodes reference files or attachments (images, videos, PDFs, notes, etc.).
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "a1b2c3d4e5f67890",
|
|
||||||
"type": "file",
|
|
||||||
"x": 500,
|
|
||||||
"y": 0,
|
|
||||||
"width": 400,
|
|
||||||
"height": 300,
|
|
||||||
"file": "Attachments/diagram.png"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "b2c3d4e5f6789012",
|
|
||||||
"type": "file",
|
|
||||||
"x": 500,
|
|
||||||
"y": 400,
|
|
||||||
"width": 400,
|
|
||||||
"height": 300,
|
|
||||||
"file": "Notes/Project Overview.md",
|
|
||||||
"subpath": "#Implementation"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Attribute | Required | Type | Description |
|
|
||||||
|-----------|----------|------|-------------|
|
|
||||||
| `file` | Yes | string | Path to file within the system |
|
|
||||||
| `subpath` | No | string | Link to heading or block (starts with `#`) |
|
|
||||||
|
|
||||||
### Link Nodes
|
|
||||||
|
|
||||||
Link nodes display external URLs.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "c3d4e5f678901234",
|
|
||||||
"type": "link",
|
|
||||||
"x": 1000,
|
|
||||||
"y": 0,
|
|
||||||
"width": 400,
|
|
||||||
"height": 200,
|
|
||||||
"url": "https://obsidian.md"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Attribute | Required | Type | Description |
|
|
||||||
|-----------|----------|------|-------------|
|
|
||||||
| `url` | Yes | string | External URL |
|
|
||||||
|
|
||||||
### Group Nodes
|
|
||||||
|
|
||||||
Group nodes are visual containers for organizing other nodes.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "d4e5f6789012345a",
|
|
||||||
"type": "group",
|
|
||||||
"x": -50,
|
|
||||||
"y": -50,
|
|
||||||
"width": 1000,
|
|
||||||
"height": 600,
|
|
||||||
"label": "Project Overview",
|
|
||||||
"color": "4"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "e5f67890123456ab",
|
|
||||||
"type": "group",
|
|
||||||
"x": 0,
|
|
||||||
"y": 700,
|
|
||||||
"width": 800,
|
|
||||||
"height": 500,
|
|
||||||
"label": "Resources",
|
|
||||||
"background": "Attachments/background.png",
|
|
||||||
"backgroundStyle": "cover"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Attribute | Required | Type | Description |
|
|
||||||
|-----------|----------|------|-------------|
|
|
||||||
| `label` | No | string | Text label for the group |
|
|
||||||
| `background` | No | string | Path to background image |
|
|
||||||
| `backgroundStyle` | No | string | Background rendering style |
|
|
||||||
|
|
||||||
#### Background Styles
|
|
||||||
|
|
||||||
| Value | Description |
|
|
||||||
|-------|-------------|
|
|
||||||
| `cover` | Fills entire width and height of node |
|
|
||||||
| `ratio` | Maintains aspect ratio of background image |
|
|
||||||
| `repeat` | Repeats image as pattern in both directions |
|
|
||||||
|
|
||||||
## Edges
|
|
||||||
|
|
||||||
Edges are lines connecting nodes.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "f67890123456789a",
|
|
||||||
"fromNode": "6f0ad84f44ce9c17",
|
|
||||||
"toNode": "a1b2c3d4e5f67890"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "0123456789abcdef",
|
|
||||||
"fromNode": "6f0ad84f44ce9c17",
|
|
||||||
"fromSide": "right",
|
|
||||||
"fromEnd": "none",
|
|
||||||
"toNode": "b2c3d4e5f6789012",
|
|
||||||
"toSide": "left",
|
|
||||||
"toEnd": "arrow",
|
|
||||||
"color": "1",
|
|
||||||
"label": "leads to"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Attribute | Required | Type | Default | Description |
|
|
||||||
|-----------|----------|------|---------|-------------|
|
|
||||||
| `id` | Yes | string | - | Unique identifier for the edge |
|
|
||||||
| `fromNode` | Yes | string | - | Node ID where connection starts |
|
|
||||||
| `fromSide` | No | string | - | Side where edge starts |
|
|
||||||
| `fromEnd` | No | string | `none` | Shape at edge start |
|
|
||||||
| `toNode` | Yes | string | - | Node ID where connection ends |
|
|
||||||
| `toSide` | No | string | - | Side where edge ends |
|
|
||||||
| `toEnd` | No | string | `arrow` | Shape at edge end |
|
|
||||||
| `color` | No | canvasColor | - | Line color |
|
|
||||||
| `label` | No | string | - | Text label for the edge |
|
|
||||||
|
|
||||||
### Side Values
|
|
||||||
|
|
||||||
| Value | Description |
|
|
||||||
|-------|-------------|
|
|
||||||
| `top` | Top edge of node |
|
|
||||||
| `right` | Right edge of node |
|
|
||||||
| `bottom` | Bottom edge of node |
|
|
||||||
| `left` | Left edge of node |
|
|
||||||
|
|
||||||
### End Shapes
|
|
||||||
|
|
||||||
| Value | Description |
|
|
||||||
|-------|-------------|
|
|
||||||
| `none` | No endpoint shape |
|
|
||||||
| `arrow` | Arrow endpoint |
|
|
||||||
|
|
||||||
## Colors
|
|
||||||
|
|
||||||
The `canvasColor` type can be specified in two ways:
|
|
||||||
|
|
||||||
### Hex Colors
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"color": "#FF0000"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Preset Colors
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"color": "1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Preset | Color |
|
|
||||||
|--------|-------|
|
|
||||||
| `"1"` | Red |
|
|
||||||
| `"2"` | Orange |
|
|
||||||
| `"3"` | Yellow |
|
|
||||||
| `"4"` | Green |
|
|
||||||
| `"5"` | Cyan |
|
|
||||||
| `"6"` | Purple |
|
|
||||||
|
|
||||||
Note: Specific color values for presets are intentionally undefined, allowing applications to use their own brand colors.
|
|
||||||
|
|
||||||
## Complete Examples
|
|
||||||
|
|
||||||
### Simple Canvas with Text and Connections
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "8a9b0c1d2e3f4a5b",
|
|
||||||
"type": "text",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 300,
|
|
||||||
"height": 150,
|
|
||||||
"text": "# Main Idea\n\nThis is the central concept."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "1a2b3c4d5e6f7a8b",
|
|
||||||
"type": "text",
|
|
||||||
"x": 400,
|
|
||||||
"y": -100,
|
|
||||||
"width": 250,
|
|
||||||
"height": 100,
|
|
||||||
"text": "## Supporting Point A\n\nDetails here."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2b3c4d5e6f7a8b9c",
|
|
||||||
"type": "text",
|
|
||||||
"x": 400,
|
|
||||||
"y": 100,
|
|
||||||
"width": 250,
|
|
||||||
"height": 100,
|
|
||||||
"text": "## Supporting Point B\n\nMore details."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"id": "3c4d5e6f7a8b9c0d",
|
|
||||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
|
||||||
"fromSide": "right",
|
|
||||||
"toNode": "1a2b3c4d5e6f7a8b",
|
|
||||||
"toSide": "left"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4d5e6f7a8b9c0d1e",
|
|
||||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
|
||||||
"fromSide": "right",
|
|
||||||
"toNode": "2b3c4d5e6f7a8b9c",
|
|
||||||
"toSide": "left"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Board with Groups
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "5e6f7a8b9c0d1e2f",
|
|
||||||
"type": "group",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 300,
|
|
||||||
"height": 500,
|
|
||||||
"label": "To Do",
|
|
||||||
"color": "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6f7a8b9c0d1e2f3a",
|
|
||||||
"type": "group",
|
|
||||||
"x": 350,
|
|
||||||
"y": 0,
|
|
||||||
"width": 300,
|
|
||||||
"height": 500,
|
|
||||||
"label": "In Progress",
|
|
||||||
"color": "3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7a8b9c0d1e2f3a4b",
|
|
||||||
"type": "group",
|
|
||||||
"x": 700,
|
|
||||||
"y": 0,
|
|
||||||
"width": 300,
|
|
||||||
"height": 500,
|
|
||||||
"label": "Done",
|
|
||||||
"color": "4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8b9c0d1e2f3a4b5c",
|
|
||||||
"type": "text",
|
|
||||||
"x": 20,
|
|
||||||
"y": 50,
|
|
||||||
"width": 260,
|
|
||||||
"height": 80,
|
|
||||||
"text": "## Task 1\n\nImplement feature X"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9c0d1e2f3a4b5c6d",
|
|
||||||
"type": "text",
|
|
||||||
"x": 370,
|
|
||||||
"y": 50,
|
|
||||||
"width": 260,
|
|
||||||
"height": 80,
|
|
||||||
"text": "## Task 2\n\nReview PR #123",
|
|
||||||
"color": "2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0d1e2f3a4b5c6d7e",
|
|
||||||
"type": "text",
|
|
||||||
"x": 720,
|
|
||||||
"y": 50,
|
|
||||||
"width": 260,
|
|
||||||
"height": 80,
|
|
||||||
"text": "## Task 3\n\n~~Setup CI/CD~~"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"edges": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Research Canvas with Files and Links
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "1e2f3a4b5c6d7e8f",
|
|
||||||
"type": "text",
|
|
||||||
"x": 300,
|
|
||||||
"y": 200,
|
|
||||||
"width": 400,
|
|
||||||
"height": 200,
|
|
||||||
"text": "# Research Topic\n\n## Key Questions\n\n- How does X affect Y?\n- What are the implications?",
|
|
||||||
"color": "5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2f3a4b5c6d7e8f9a",
|
|
||||||
"type": "file",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"width": 250,
|
|
||||||
"height": 150,
|
|
||||||
"file": "Literature/Paper A.pdf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3a4b5c6d7e8f9a0b",
|
|
||||||
"type": "file",
|
|
||||||
"x": 0,
|
|
||||||
"y": 200,
|
|
||||||
"width": 250,
|
|
||||||
"height": 150,
|
|
||||||
"file": "Notes/Meeting Notes.md",
|
|
||||||
"subpath": "#Key Insights"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4b5c6d7e8f9a0b1c",
|
|
||||||
"type": "link",
|
|
||||||
"x": 0,
|
|
||||||
"y": 400,
|
|
||||||
"width": 250,
|
|
||||||
"height": 100,
|
|
||||||
"url": "https://example.com/research"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "5c6d7e8f9a0b1c2d",
|
|
||||||
"type": "file",
|
|
||||||
"x": 750,
|
|
||||||
"y": 150,
|
|
||||||
"width": 300,
|
|
||||||
"height": 250,
|
|
||||||
"file": "Attachments/diagram.png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"id": "6d7e8f9a0b1c2d3e",
|
|
||||||
"fromNode": "2f3a4b5c6d7e8f9a",
|
|
||||||
"fromSide": "right",
|
|
||||||
"toNode": "1e2f3a4b5c6d7e8f",
|
|
||||||
"toSide": "left",
|
|
||||||
"label": "supports"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7e8f9a0b1c2d3e4f",
|
|
||||||
"fromNode": "3a4b5c6d7e8f9a0b",
|
|
||||||
"fromSide": "right",
|
|
||||||
"toNode": "1e2f3a4b5c6d7e8f",
|
|
||||||
"toSide": "left",
|
|
||||||
"label": "informs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8f9a0b1c2d3e4f5a",
|
|
||||||
"fromNode": "4b5c6d7e8f9a0b1c",
|
|
||||||
"fromSide": "right",
|
|
||||||
"toNode": "1e2f3a4b5c6d7e8f",
|
|
||||||
"toSide": "left",
|
|
||||||
"toEnd": "arrow",
|
|
||||||
"color": "6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9a0b1c2d3e4f5a6b",
|
|
||||||
"fromNode": "1e2f3a4b5c6d7e8f",
|
|
||||||
"fromSide": "right",
|
|
||||||
"toNode": "5c6d7e8f9a0b1c2d",
|
|
||||||
"toSide": "left",
|
|
||||||
"label": "visualized by"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Flowchart
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "a0b1c2d3e4f5a6b7",
|
|
||||||
"type": "text",
|
|
||||||
"x": 200,
|
|
||||||
"y": 0,
|
|
||||||
"width": 150,
|
|
||||||
"height": 60,
|
|
||||||
"text": "**Start**",
|
|
||||||
"color": "4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "b1c2d3e4f5a6b7c8",
|
|
||||||
"type": "text",
|
|
||||||
"x": 200,
|
|
||||||
"y": 100,
|
|
||||||
"width": 150,
|
|
||||||
"height": 60,
|
|
||||||
"text": "Step 1:\nGather data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "c2d3e4f5a6b7c8d9",
|
|
||||||
"type": "text",
|
|
||||||
"x": 200,
|
|
||||||
"y": 200,
|
|
||||||
"width": 150,
|
|
||||||
"height": 80,
|
|
||||||
"text": "**Decision**\n\nIs data valid?",
|
|
||||||
"color": "3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "d3e4f5a6b7c8d9e0",
|
|
||||||
"type": "text",
|
|
||||||
"x": 400,
|
|
||||||
"y": 200,
|
|
||||||
"width": 150,
|
|
||||||
"height": 60,
|
|
||||||
"text": "Process data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e4f5a6b7c8d9e0f1",
|
|
||||||
"type": "text",
|
|
||||||
"x": 0,
|
|
||||||
"y": 200,
|
|
||||||
"width": 150,
|
|
||||||
"height": 60,
|
|
||||||
"text": "Request new data",
|
|
||||||
"color": "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "f5a6b7c8d9e0f1a2",
|
|
||||||
"type": "text",
|
|
||||||
"x": 400,
|
|
||||||
"y": 320,
|
|
||||||
"width": 150,
|
|
||||||
"height": 60,
|
|
||||||
"text": "**End**",
|
|
||||||
"color": "4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"id": "a6b7c8d9e0f1a2b3",
|
|
||||||
"fromNode": "a0b1c2d3e4f5a6b7",
|
|
||||||
"fromSide": "bottom",
|
|
||||||
"toNode": "b1c2d3e4f5a6b7c8",
|
|
||||||
"toSide": "top"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "b7c8d9e0f1a2b3c4",
|
|
||||||
"fromNode": "b1c2d3e4f5a6b7c8",
|
|
||||||
"fromSide": "bottom",
|
|
||||||
"toNode": "c2d3e4f5a6b7c8d9",
|
|
||||||
"toSide": "top"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "c8d9e0f1a2b3c4d5",
|
|
||||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
|
||||||
"fromSide": "right",
|
|
||||||
"toNode": "d3e4f5a6b7c8d9e0",
|
|
||||||
"toSide": "left",
|
|
||||||
"label": "Yes",
|
|
||||||
"color": "4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "d9e0f1a2b3c4d5e6",
|
|
||||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
|
||||||
"fromSide": "left",
|
|
||||||
"toNode": "e4f5a6b7c8d9e0f1",
|
|
||||||
"toSide": "right",
|
|
||||||
"label": "No",
|
|
||||||
"color": "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e0f1a2b3c4d5e6f7",
|
|
||||||
"fromNode": "e4f5a6b7c8d9e0f1",
|
|
||||||
"fromSide": "top",
|
|
||||||
"fromEnd": "none",
|
|
||||||
"toNode": "b1c2d3e4f5a6b7c8",
|
|
||||||
"toSide": "left",
|
|
||||||
"toEnd": "arrow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "f1a2b3c4d5e6f7a8",
|
|
||||||
"fromNode": "d3e4f5a6b7c8d9e0",
|
|
||||||
"fromSide": "bottom",
|
|
||||||
"toNode": "f5a6b7c8d9e0f1a2",
|
|
||||||
"toSide": "top"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ID Generation
|
|
||||||
|
|
||||||
Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"id": "6f0ad84f44ce9c17"
|
|
||||||
"id": "a3b2c1d0e9f8g7h6"
|
|
||||||
"id": "1234567890abcdef"
|
|
||||||
```
|
|
||||||
|
|
||||||
This format is a 16-character lowercase hex string (64-bit random value).
|
|
||||||
|
|
||||||
## Layout Guidelines
|
|
||||||
|
|
||||||
### Positioning
|
|
||||||
|
|
||||||
- Coordinates can be negative (canvas extends infinitely)
|
|
||||||
- `x` increases to the right
|
|
||||||
- `y` increases downward
|
|
||||||
- Position refers to top-left corner of node
|
|
||||||
|
|
||||||
### Recommended Sizes
|
|
||||||
|
|
||||||
| Node Type | Suggested Width | Suggested Height |
|
|
||||||
|-----------|-----------------|------------------|
|
|
||||||
| Small text | 200-300 | 80-150 |
|
|
||||||
| Medium text | 300-450 | 150-300 |
|
|
||||||
| Large text | 400-600 | 300-500 |
|
|
||||||
| File preview | 300-500 | 200-400 |
|
|
||||||
| Link preview | 250-400 | 100-200 |
|
|
||||||
| Group | Varies | Varies |
|
|
||||||
|
|
||||||
### Spacing
|
|
||||||
|
|
||||||
- Leave 20-50px padding inside groups
|
|
||||||
- Space nodes 50-100px apart for readability
|
|
||||||
- Align nodes to grid (multiples of 10 or 20) for cleaner layouts
|
|
||||||
|
|
||||||
## Validation Rules
|
|
||||||
|
|
||||||
1. All `id` values must be unique across nodes and edges
|
|
||||||
2. `fromNode` and `toNode` must reference existing node IDs
|
|
||||||
3. Required fields must be present for each node type
|
|
||||||
4. `type` must be one of: `text`, `file`, `link`, `group`
|
|
||||||
5. `backgroundStyle` must be one of: `cover`, `ratio`, `repeat`
|
|
||||||
6. `fromSide`, `toSide` must be one of: `top`, `right`, `bottom`, `left`
|
|
||||||
7. `fromEnd`, `toEnd` must be one of: `none`, `arrow`
|
|
||||||
8. Color presets must be `"1"` through `"6"` or valid hex color
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/)
|
|
||||||
- [JSON Canvas GitHub](https://github.com/obsidianmd/jsoncanvas)
|
|
||||||
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
---
|
|
||||||
name: magpie-review
|
|
||||||
description: Run a multi-AI adversarial code review using the `magpie` CLI. Multiple AI models independently review the changes, debate findings, and a verifier audits each issue against the actual code. Use whenever the user asks for a "magpie review", a "multi-AI review", an "adversarial review", a "second opinion review", or wants magpie to look at local uncommitted changes, the current branch, or a GitHub PR. Also use when the user wants to `magpie discuss` a topic or do a whole-repo review.
|
|
||||||
---
|
|
||||||
|
|
||||||
# magpie-review
|
|
||||||
|
|
||||||
Wraps the `magpie` CLI to run adversarial multi-AI code reviews. Magpie spawns several reviewer models (Claude Code, Codex, Gemini, etc.) that independently review a change, debate across rounds, then a verifier audits each reported issue against the actual code.
|
|
||||||
|
|
||||||
## When to pick which mode
|
|
||||||
|
|
||||||
Magpie supports three review targets — pick based on what the user is reviewing:
|
|
||||||
|
|
||||||
| User's intent | Command |
|
|
||||||
|---|---|
|
|
||||||
| Review work I haven't committed yet (staged + unstaged) | `magpie review --local` |
|
|
||||||
| Review the commits on my current branch vs a base | `magpie review --branch [base]` (base defaults to `main`) |
|
|
||||||
| Review a GitHub PR | `magpie review <pr-number>` or `magpie review <pr-url>` |
|
|
||||||
| Review specific files only | `magpie review --files <path> [path...]` |
|
|
||||||
| Review the entire repository | `magpie review --repo` |
|
|
||||||
| Discuss a topic / design question | `magpie discuss "<topic>"` or `magpie discuss <path-to-file.md>` |
|
|
||||||
|
|
||||||
If the user is ambiguous (e.g. "review my changes"), check `git status` and `git log @{u}..HEAD` to figure out whether they mean uncommitted, committed-but-unpushed, or already-pushed work — then pick the matching mode rather than guessing.
|
|
||||||
|
|
||||||
## Running a review
|
|
||||||
|
|
||||||
Always run `magpie` from inside the target repo's working tree. For PR mode it uses the `origin` remote to find the repo by default.
|
|
||||||
|
|
||||||
### Common flags
|
|
||||||
|
|
||||||
These apply to both `review` and `discuss` unless noted:
|
|
||||||
|
|
||||||
- `-i, --interactive` — Pause between turns for Q&A. Use when the user wants to drive the review themselves.
|
|
||||||
- `-a, --all` — Use every configured reviewer without an interactive picker. Good for non-interactive/batch runs.
|
|
||||||
- `--reviewers <ids>` — Comma-separated reviewer IDs (e.g. `claude-code,gemini-cli`) when you want a specific subset.
|
|
||||||
- `-r, --rounds <n>` — Cap the debate rounds (default 5).
|
|
||||||
- `--no-converge` — Disable early-stop on consensus. Use when you want the full debate even if reviewers agree quickly.
|
|
||||||
- `-o, --output <file>` and `-f, --format <markdown|json>` — Save results to a file.
|
|
||||||
- `--fail-fast` — Abort the whole flow if any reviewer fails. Default is resilient (continues with surviving reviewers). Use fail-fast when debugging provider/auth issues or when the user wants a guarantee every reviewer participated.
|
|
||||||
- `--plan-only` — Generate the review plan without running reviewers. Useful for a quick preview of what magpie *would* do.
|
|
||||||
|
|
||||||
### `review`-only flags worth knowing
|
|
||||||
|
|
||||||
- `--skip-context` — Skip the context-gathering phase (call chains, related PRs). Faster, less informed.
|
|
||||||
- `--no-post` — Skip the post-debate GitHub comment-posting flow. Use in non-interactive contexts where you just want the review output, not the interactive post-each-issue loop.
|
|
||||||
- `--no-conclusion` — Skip the final summarizer. Useful for bot/CI use.
|
|
||||||
- `--git-remote <remote>` — Override the remote used for PR-URL detection (default `origin`).
|
|
||||||
- `--reanalyze` — Bypass the analyzer cache and re-analyze from scratch.
|
|
||||||
|
|
||||||
### Repo-mode flags (with `--repo`)
|
|
||||||
|
|
||||||
- `--path <subdir>` — Limit the repo review to a subdirectory.
|
|
||||||
- `--ignore <patterns...>` — Skip matching paths.
|
|
||||||
- `--quick` — Architecture overview only.
|
|
||||||
- `--deep` — Full analysis, no prompts.
|
|
||||||
- `--list-sessions` / `--session <id>` / `--export <file>` — Manage long-running repo review sessions (they persist so you can pause and resume).
|
|
||||||
|
|
||||||
## Interactive vs non-interactive
|
|
||||||
|
|
||||||
Magpie's default flow includes interactive prompts (reviewer selection, per-issue post/edit/skip after the debate). When you (Claude) are invoking magpie programmatically on the user's behalf:
|
|
||||||
|
|
||||||
- Prefer `-a` (or `--reviewers`) to skip the reviewer-selection prompt.
|
|
||||||
- Prefer `--no-post` to skip the per-issue posting loop — the user can still read the review output.
|
|
||||||
- If you want a clean machine-readable result, add `-f json -o <file>`.
|
|
||||||
|
|
||||||
When the user wants to drive the review themselves, hand the command back to them to run (e.g. via `! magpie review ...`) so they get the interactive UX rather than running it through a tool call.
|
|
||||||
|
|
||||||
## Running it as a long task (it takes minutes)
|
|
||||||
|
|
||||||
A full review runs several models across multiple debate rounds, so it takes minutes. Run it as a background `Bash` task with `run_in_background: true` and save the output (`-f markdown -o <file>`). The background task auto-notifies you when it completes — that completion notification is all you need; read the saved output file then.
|
|
||||||
|
|
||||||
- **Do not add a separate `Monitor` on the same output file.** The background task already notifies on completion, so a monitor watching the same file is redundant. Only add a `Monitor` if you genuinely need streamed interim progress, and even then it is usually unnecessary for a fire-and-forget review.
|
|
||||||
- **Never copy the output path by hand.** Pass the same explicit `-o <file>` path you chose (e.g. `/tmp/magpie-review.md`) to your follow-up `Read` — don't transcribe the long auto-generated task-output path from the completion notification, which is easy to typo.
|
|
||||||
- **Don't suppress stderr** (`2>/dev/null`) on magpie or on any watcher command. If something fails — bad path, auth error, missing reviewer — you want to see why, not a bare non-zero exit. Use `2>&1 | tee <log>` if you want both a saved log and visible errors.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
**User: "Have magpie look at what I'm working on right now."**
|
|
||||||
They likely mean uncommitted work. Run:
|
|
||||||
```
|
|
||||||
magpie review --local
|
|
||||||
```
|
|
||||||
|
|
||||||
**User: "Get a magpie review on this branch before I push."**
|
|
||||||
Current branch vs main:
|
|
||||||
```
|
|
||||||
magpie review --branch
|
|
||||||
```
|
|
||||||
|
|
||||||
**User: "Run magpie on PR 4521."**
|
|
||||||
```
|
|
||||||
magpie review 4521
|
|
||||||
```
|
|
||||||
|
|
||||||
**User: "Use magpie to review just the changes to `src/auth/`."**
|
|
||||||
Pick the files mode:
|
|
||||||
```
|
|
||||||
magpie review --files src/auth/login.ts src/auth/session.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
**User: "Get a fast magpie sanity check on PR 4521 — I just want the output, don't post anything."**
|
|
||||||
```
|
|
||||||
magpie review 4521 -a --no-post --skip-context
|
|
||||||
```
|
|
||||||
|
|
||||||
**User: "Have magpie debate whether we should adopt tRPC."**
|
|
||||||
```
|
|
||||||
magpie discuss "Should we adopt tRPC for our internal APIs?"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration notes
|
|
||||||
|
|
||||||
- Magpie reads `~/.magpie/config.yaml` for providers, reviewers, analyzer, summarizer, and the context-gatherer config.
|
|
||||||
- CLI providers (`claude-code`, `codex-cli`, `gemini-cli`, `qwen-code`, `opencode-cli`) use the user's existing subscriptions/logins — no API keys needed and they're the recommended choice.
|
|
||||||
- If the user hasn't run `magpie init` yet, suggest `magpie init` (interactive) or `magpie init -y` (defaults) before the first review.
|
|
||||||
- If `magpie` is not on PATH, the project at `/home/tgrosinger/code/magpie` may need `npm install && npm run build && npm link` from its root.
|
|
||||||
|
|
||||||
## When *not* to use this skill
|
|
||||||
|
|
||||||
- The user is asking how magpie itself is implemented or wants to modify magpie's source — that's a normal code task in the magpie repo, not an invocation of this skill.
|
|
||||||
- The user wants a single-model review (just Claude reviewing the diff). Use the built-in `/code-review` skill instead.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/code-review
|
|
||||||
@@ -1,620 +0,0 @@
|
|||||||
---
|
|
||||||
name: obsidian-bases
|
|
||||||
description: Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.
|
|
||||||
source: https://github.com/kepano/obsidian-skills/blob/main/skills/obsidian-bases/SKILL.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Obsidian Bases Skill
|
|
||||||
|
|
||||||
This skill enables Claude Code to create and edit valid Obsidian Bases (`.base` files) including views, filters, formulas, and all related configurations.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Obsidian Bases are YAML-based files that define dynamic views of notes in an Obsidian vault. A Base file can contain multiple views, global filters, formulas, property configurations, and custom summaries.
|
|
||||||
|
|
||||||
## File Format
|
|
||||||
|
|
||||||
Base files use the `.base` extension and contain valid YAML. They can also be embedded in Markdown code blocks.
|
|
||||||
|
|
||||||
## Complete Schema
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Global filters apply to ALL views in the base
|
|
||||||
filters:
|
|
||||||
# Can be a single filter string
|
|
||||||
# OR a recursive filter object with and/or/not
|
|
||||||
and: []
|
|
||||||
or: []
|
|
||||||
not: []
|
|
||||||
|
|
||||||
# Define formula properties that can be used across all views
|
|
||||||
formulas:
|
|
||||||
formula_name: 'expression'
|
|
||||||
|
|
||||||
# Configure display names and settings for properties
|
|
||||||
properties:
|
|
||||||
property_name:
|
|
||||||
displayName: "Display Name"
|
|
||||||
formula.formula_name:
|
|
||||||
displayName: "Formula Display Name"
|
|
||||||
file.ext:
|
|
||||||
displayName: "Extension"
|
|
||||||
|
|
||||||
# Define custom summary formulas
|
|
||||||
summaries:
|
|
||||||
custom_summary_name: 'values.mean().round(3)'
|
|
||||||
|
|
||||||
# Define one or more views
|
|
||||||
views:
|
|
||||||
- type: table | cards | list | map
|
|
||||||
name: "View Name"
|
|
||||||
limit: 10 # Optional: limit results
|
|
||||||
groupBy: # Optional: group results
|
|
||||||
property: property_name
|
|
||||||
direction: ASC | DESC
|
|
||||||
filters: # View-specific filters
|
|
||||||
and: []
|
|
||||||
order: # Properties to display in order
|
|
||||||
- file.name
|
|
||||||
- property_name
|
|
||||||
- formula.formula_name
|
|
||||||
summaries: # Map properties to summary formulas
|
|
||||||
property_name: Average
|
|
||||||
```
|
|
||||||
|
|
||||||
## Filter Syntax
|
|
||||||
|
|
||||||
Filters narrow down results. They can be applied globally or per-view.
|
|
||||||
|
|
||||||
### Filter Structure
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Single filter
|
|
||||||
filters: 'status == "done"'
|
|
||||||
|
|
||||||
# AND - all conditions must be true
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- 'status == "done"'
|
|
||||||
- 'priority > 3'
|
|
||||||
|
|
||||||
# OR - any condition can be true
|
|
||||||
filters:
|
|
||||||
or:
|
|
||||||
- 'file.hasTag("book")'
|
|
||||||
- 'file.hasTag("article")'
|
|
||||||
|
|
||||||
# NOT - exclude matching items
|
|
||||||
filters:
|
|
||||||
not:
|
|
||||||
- 'file.hasTag("archived")'
|
|
||||||
|
|
||||||
# Nested filters
|
|
||||||
filters:
|
|
||||||
or:
|
|
||||||
- file.hasTag("tag")
|
|
||||||
- and:
|
|
||||||
- file.hasTag("book")
|
|
||||||
- file.hasLink("Textbook")
|
|
||||||
- not:
|
|
||||||
- file.hasTag("book")
|
|
||||||
- file.inFolder("Required Reading")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Filter Operators
|
|
||||||
|
|
||||||
| Operator | Description |
|
|
||||||
|----------|-------------|
|
|
||||||
| `==` | equals |
|
|
||||||
| `!=` | not equal |
|
|
||||||
| `>` | greater than |
|
|
||||||
| `<` | less than |
|
|
||||||
| `>=` | greater than or equal |
|
|
||||||
| `<=` | less than or equal |
|
|
||||||
| `&&` | logical and |
|
|
||||||
| `\|\|` | logical or |
|
|
||||||
| <code>!</code> | logical not |
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### Three Types of Properties
|
|
||||||
|
|
||||||
1. **Note properties** - From frontmatter: `note.author` or just `author`
|
|
||||||
2. **File properties** - File metadata: `file.name`, `file.mtime`, etc.
|
|
||||||
3. **Formula properties** - Computed values: `formula.my_formula`
|
|
||||||
|
|
||||||
### File Properties Reference
|
|
||||||
|
|
||||||
| Property | Type | Description |
|
|
||||||
|----------|------|-------------|
|
|
||||||
| `file.name` | String | File name |
|
|
||||||
| `file.basename` | String | File name without extension |
|
|
||||||
| `file.path` | String | Full path to file |
|
|
||||||
| `file.folder` | String | Parent folder path |
|
|
||||||
| `file.ext` | String | File extension |
|
|
||||||
| `file.size` | Number | File size in bytes |
|
|
||||||
| `file.ctime` | Date | Created time |
|
|
||||||
| `file.mtime` | Date | Modified time |
|
|
||||||
| `file.tags` | List | All tags in file |
|
|
||||||
| `file.links` | List | Internal links in file |
|
|
||||||
| `file.backlinks` | List | Files linking to this file |
|
|
||||||
| `file.embeds` | List | Embeds in the note |
|
|
||||||
| `file.properties` | Object | All frontmatter properties |
|
|
||||||
|
|
||||||
### The `this` Keyword
|
|
||||||
|
|
||||||
- In main content area: refers to the base file itself
|
|
||||||
- When embedded: refers to the embedding file
|
|
||||||
- In sidebar: refers to the active file in main content
|
|
||||||
|
|
||||||
## Formula Syntax
|
|
||||||
|
|
||||||
Formulas compute values from properties. Defined in the `formulas` section.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
formulas:
|
|
||||||
# Simple arithmetic
|
|
||||||
total: "price * quantity"
|
|
||||||
|
|
||||||
# Conditional logic
|
|
||||||
status_icon: 'if(done, "✅", "⏳")'
|
|
||||||
|
|
||||||
# String formatting
|
|
||||||
formatted_price: 'if(price, price.toFixed(2) + " dollars")'
|
|
||||||
|
|
||||||
# Date formatting
|
|
||||||
created: 'file.ctime.format("YYYY-MM-DD")'
|
|
||||||
|
|
||||||
# Complex expressions
|
|
||||||
days_old: '((now() - file.ctime) / 86400000).round(0)'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Functions Reference
|
|
||||||
|
|
||||||
### Global Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `date()` | `date(string): date` | Parse string to date. Format: `YYYY-MM-DD HH:mm:ss` |
|
|
||||||
| `duration()` | `duration(string): duration` | Parse duration string |
|
|
||||||
| `now()` | `now(): date` | Current date and time |
|
|
||||||
| `today()` | `today(): date` | Current date (time = 00:00:00) |
|
|
||||||
| `if()` | `if(condition, trueResult, falseResult?)` | Conditional |
|
|
||||||
| `min()` | `min(n1, n2, ...): number` | Smallest number |
|
|
||||||
| `max()` | `max(n1, n2, ...): number` | Largest number |
|
|
||||||
| `number()` | `number(any): number` | Convert to number |
|
|
||||||
| `link()` | `link(path, display?): Link` | Create a link |
|
|
||||||
| `list()` | `list(element): List` | Wrap in list if not already |
|
|
||||||
| `file()` | `file(path): file` | Get file object |
|
|
||||||
| `image()` | `image(path): image` | Create image for rendering |
|
|
||||||
| `icon()` | `icon(name): icon` | Lucide icon by name |
|
|
||||||
| `html()` | `html(string): html` | Render as HTML |
|
|
||||||
| `escapeHTML()` | `escapeHTML(string): string` | Escape HTML characters |
|
|
||||||
|
|
||||||
### Any Type Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `isTruthy()` | `any.isTruthy(): boolean` | Coerce to boolean |
|
|
||||||
| `isType()` | `any.isType(type): boolean` | Check type |
|
|
||||||
| `toString()` | `any.toString(): string` | Convert to string |
|
|
||||||
|
|
||||||
### Date Functions & Fields
|
|
||||||
|
|
||||||
**Fields:** `date.year`, `date.month`, `date.day`, `date.hour`, `date.minute`, `date.second`, `date.millisecond`
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `date()` | `date.date(): date` | Remove time portion |
|
|
||||||
| `format()` | `date.format(string): string` | Format with Moment.js pattern |
|
|
||||||
| `time()` | `date.time(): string` | Get time as string |
|
|
||||||
| `relative()` | `date.relative(): string` | Human-readable relative time |
|
|
||||||
| `isEmpty()` | `date.isEmpty(): boolean` | Always false for dates |
|
|
||||||
|
|
||||||
### Date Arithmetic
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Duration units: y/year/years, M/month/months, d/day/days,
|
|
||||||
# w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds
|
|
||||||
|
|
||||||
# Add/subtract durations
|
|
||||||
"date + \"1M\"" # Add 1 month
|
|
||||||
"date - \"2h\"" # Subtract 2 hours
|
|
||||||
"now() + \"1 day\"" # Tomorrow
|
|
||||||
"today() + \"7d\"" # A week from today
|
|
||||||
|
|
||||||
# Subtract dates for millisecond difference
|
|
||||||
"now() - file.ctime"
|
|
||||||
|
|
||||||
# Complex duration arithmetic
|
|
||||||
"now() + (duration('1d') * 2)"
|
|
||||||
```
|
|
||||||
|
|
||||||
### String Functions
|
|
||||||
|
|
||||||
**Field:** `string.length`
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `contains()` | `string.contains(value): boolean` | Check substring |
|
|
||||||
| `containsAll()` | `string.containsAll(...values): boolean` | All substrings present |
|
|
||||||
| `containsAny()` | `string.containsAny(...values): boolean` | Any substring present |
|
|
||||||
| `startsWith()` | `string.startsWith(query): boolean` | Starts with query |
|
|
||||||
| `endsWith()` | `string.endsWith(query): boolean` | Ends with query |
|
|
||||||
| `isEmpty()` | `string.isEmpty(): boolean` | Empty or not present |
|
|
||||||
| `lower()` | `string.lower(): string` | To lowercase |
|
|
||||||
| `title()` | `string.title(): string` | To Title Case |
|
|
||||||
| `trim()` | `string.trim(): string` | Remove whitespace |
|
|
||||||
| `replace()` | `string.replace(pattern, replacement): string` | Replace pattern |
|
|
||||||
| `repeat()` | `string.repeat(count): string` | Repeat string |
|
|
||||||
| `reverse()` | `string.reverse(): string` | Reverse string |
|
|
||||||
| `slice()` | `string.slice(start, end?): string` | Substring |
|
|
||||||
| `split()` | `string.split(separator, n?): list` | Split to list |
|
|
||||||
|
|
||||||
### Number Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `abs()` | `number.abs(): number` | Absolute value |
|
|
||||||
| `ceil()` | `number.ceil(): number` | Round up |
|
|
||||||
| `floor()` | `number.floor(): number` | Round down |
|
|
||||||
| `round()` | `number.round(digits?): number` | Round to digits |
|
|
||||||
| `toFixed()` | `number.toFixed(precision): string` | Fixed-point notation |
|
|
||||||
| `isEmpty()` | `number.isEmpty(): boolean` | Not present |
|
|
||||||
|
|
||||||
### List Functions
|
|
||||||
|
|
||||||
**Field:** `list.length`
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `contains()` | `list.contains(value): boolean` | Element exists |
|
|
||||||
| `containsAll()` | `list.containsAll(...values): boolean` | All elements exist |
|
|
||||||
| `containsAny()` | `list.containsAny(...values): boolean` | Any element exists |
|
|
||||||
| `filter()` | `list.filter(expression): list` | Filter by condition (uses `value`, `index`) |
|
|
||||||
| `map()` | `list.map(expression): list` | Transform elements (uses `value`, `index`) |
|
|
||||||
| `reduce()` | `list.reduce(expression, initial): any` | Reduce to single value (uses `value`, `index`, `acc`) |
|
|
||||||
| `flat()` | `list.flat(): list` | Flatten nested lists |
|
|
||||||
| `join()` | `list.join(separator): string` | Join to string |
|
|
||||||
| `reverse()` | `list.reverse(): list` | Reverse order |
|
|
||||||
| `slice()` | `list.slice(start, end?): list` | Sublist |
|
|
||||||
| `sort()` | `list.sort(): list` | Sort ascending |
|
|
||||||
| `unique()` | `list.unique(): list` | Remove duplicates |
|
|
||||||
| `isEmpty()` | `list.isEmpty(): boolean` | No elements |
|
|
||||||
|
|
||||||
### File Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `asLink()` | `file.asLink(display?): Link` | Convert to link |
|
|
||||||
| `hasLink()` | `file.hasLink(otherFile): boolean` | Has link to file |
|
|
||||||
| `hasTag()` | `file.hasTag(...tags): boolean` | Has any of the tags |
|
|
||||||
| `hasProperty()` | `file.hasProperty(name): boolean` | Has property |
|
|
||||||
| `inFolder()` | `file.inFolder(folder): boolean` | In folder or subfolder |
|
|
||||||
|
|
||||||
### Link Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `asFile()` | `link.asFile(): file` | Get file object |
|
|
||||||
| `linksTo()` | `link.linksTo(file): boolean` | Links to file |
|
|
||||||
|
|
||||||
### Object Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `isEmpty()` | `object.isEmpty(): boolean` | No properties |
|
|
||||||
| `keys()` | `object.keys(): list` | List of keys |
|
|
||||||
| `values()` | `object.values(): list` | List of values |
|
|
||||||
|
|
||||||
### Regular Expression Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `matches()` | `regexp.matches(string): boolean` | Test if matches |
|
|
||||||
|
|
||||||
## View Types
|
|
||||||
|
|
||||||
### Table View
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
views:
|
|
||||||
- type: table
|
|
||||||
name: "My Table"
|
|
||||||
order:
|
|
||||||
- file.name
|
|
||||||
- status
|
|
||||||
- due_date
|
|
||||||
summaries:
|
|
||||||
price: Sum
|
|
||||||
count: Average
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cards View
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
views:
|
|
||||||
- type: cards
|
|
||||||
name: "Gallery"
|
|
||||||
order:
|
|
||||||
- file.name
|
|
||||||
- cover_image
|
|
||||||
- description
|
|
||||||
```
|
|
||||||
|
|
||||||
### List View
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
views:
|
|
||||||
- type: list
|
|
||||||
name: "Simple List"
|
|
||||||
order:
|
|
||||||
- file.name
|
|
||||||
- status
|
|
||||||
```
|
|
||||||
|
|
||||||
### Map View
|
|
||||||
|
|
||||||
Requires latitude/longitude properties and the Maps plugin.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
views:
|
|
||||||
- type: map
|
|
||||||
name: "Locations"
|
|
||||||
# Map-specific settings for lat/lng properties
|
|
||||||
```
|
|
||||||
|
|
||||||
## Default Summary Formulas
|
|
||||||
|
|
||||||
| Name | Input Type | Description |
|
|
||||||
|------|------------|-------------|
|
|
||||||
| `Average` | Number | Mathematical mean |
|
|
||||||
| `Min` | Number | Smallest number |
|
|
||||||
| `Max` | Number | Largest number |
|
|
||||||
| `Sum` | Number | Sum of all numbers |
|
|
||||||
| `Range` | Number | Max - Min |
|
|
||||||
| `Median` | Number | Mathematical median |
|
|
||||||
| `Stddev` | Number | Standard deviation |
|
|
||||||
| `Earliest` | Date | Earliest date |
|
|
||||||
| `Latest` | Date | Latest date |
|
|
||||||
| `Range` | Date | Latest - Earliest |
|
|
||||||
| `Checked` | Boolean | Count of true values |
|
|
||||||
| `Unchecked` | Boolean | Count of false values |
|
|
||||||
| `Empty` | Any | Count of empty values |
|
|
||||||
| `Filled` | Any | Count of non-empty values |
|
|
||||||
| `Unique` | Any | Count of unique values |
|
|
||||||
|
|
||||||
## Complete Examples
|
|
||||||
|
|
||||||
### Task Tracker Base
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- file.hasTag("task")
|
|
||||||
- 'file.ext == "md"'
|
|
||||||
|
|
||||||
formulas:
|
|
||||||
days_until_due: 'if(due, ((date(due) - today()) / 86400000).round(0), "")'
|
|
||||||
is_overdue: 'if(due, date(due) < today() && status != "done", false)'
|
|
||||||
priority_label: 'if(priority == 1, "🔴 High", if(priority == 2, "🟡 Medium", "🟢 Low"))'
|
|
||||||
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
displayName: Status
|
|
||||||
formula.days_until_due:
|
|
||||||
displayName: "Days Until Due"
|
|
||||||
formula.priority_label:
|
|
||||||
displayName: Priority
|
|
||||||
|
|
||||||
views:
|
|
||||||
- type: table
|
|
||||||
name: "Active Tasks"
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- 'status != "done"'
|
|
||||||
order:
|
|
||||||
- file.name
|
|
||||||
- status
|
|
||||||
- formula.priority_label
|
|
||||||
- due
|
|
||||||
- formula.days_until_due
|
|
||||||
groupBy:
|
|
||||||
property: status
|
|
||||||
direction: ASC
|
|
||||||
summaries:
|
|
||||||
formula.days_until_due: Average
|
|
||||||
|
|
||||||
- type: table
|
|
||||||
name: "Completed"
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- 'status == "done"'
|
|
||||||
order:
|
|
||||||
- file.name
|
|
||||||
- completed_date
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reading List Base
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
filters:
|
|
||||||
or:
|
|
||||||
- file.hasTag("book")
|
|
||||||
- file.hasTag("article")
|
|
||||||
|
|
||||||
formulas:
|
|
||||||
reading_time: 'if(pages, (pages * 2).toString() + " min", "")'
|
|
||||||
status_icon: 'if(status == "reading", "📖", if(status == "done", "✅", "📚"))'
|
|
||||||
year_read: 'if(finished_date, date(finished_date).year, "")'
|
|
||||||
|
|
||||||
properties:
|
|
||||||
author:
|
|
||||||
displayName: Author
|
|
||||||
formula.status_icon:
|
|
||||||
displayName: ""
|
|
||||||
formula.reading_time:
|
|
||||||
displayName: "Est. Time"
|
|
||||||
|
|
||||||
views:
|
|
||||||
- type: cards
|
|
||||||
name: "Library"
|
|
||||||
order:
|
|
||||||
- cover
|
|
||||||
- file.name
|
|
||||||
- author
|
|
||||||
- formula.status_icon
|
|
||||||
filters:
|
|
||||||
not:
|
|
||||||
- 'status == "dropped"'
|
|
||||||
|
|
||||||
- type: table
|
|
||||||
name: "Reading List"
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- 'status == "to-read"'
|
|
||||||
order:
|
|
||||||
- file.name
|
|
||||||
- author
|
|
||||||
- pages
|
|
||||||
- formula.reading_time
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Notes Base
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- file.inFolder("Projects")
|
|
||||||
- 'file.ext == "md"'
|
|
||||||
|
|
||||||
formulas:
|
|
||||||
last_updated: 'file.mtime.relative()'
|
|
||||||
link_count: 'file.links.length'
|
|
||||||
|
|
||||||
summaries:
|
|
||||||
avgLinks: 'values.filter(value.isType("number")).mean().round(1)'
|
|
||||||
|
|
||||||
properties:
|
|
||||||
formula.last_updated:
|
|
||||||
displayName: "Updated"
|
|
||||||
formula.link_count:
|
|
||||||
displayName: "Links"
|
|
||||||
|
|
||||||
views:
|
|
||||||
- type: table
|
|
||||||
name: "All Projects"
|
|
||||||
order:
|
|
||||||
- file.name
|
|
||||||
- status
|
|
||||||
- formula.last_updated
|
|
||||||
- formula.link_count
|
|
||||||
summaries:
|
|
||||||
formula.link_count: avgLinks
|
|
||||||
groupBy:
|
|
||||||
property: status
|
|
||||||
direction: ASC
|
|
||||||
|
|
||||||
- type: list
|
|
||||||
name: "Quick List"
|
|
||||||
order:
|
|
||||||
- file.name
|
|
||||||
- status
|
|
||||||
```
|
|
||||||
|
|
||||||
### Daily Notes Index
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- file.inFolder("Daily Notes")
|
|
||||||
- '/^\d{4}-\d{2}-\d{2}$/.matches(file.basename)'
|
|
||||||
|
|
||||||
formulas:
|
|
||||||
word_estimate: '(file.size / 5).round(0)'
|
|
||||||
day_of_week: 'date(file.basename).format("dddd")'
|
|
||||||
|
|
||||||
properties:
|
|
||||||
formula.day_of_week:
|
|
||||||
displayName: "Day"
|
|
||||||
formula.word_estimate:
|
|
||||||
displayName: "~Words"
|
|
||||||
|
|
||||||
views:
|
|
||||||
- type: table
|
|
||||||
name: "Recent Notes"
|
|
||||||
limit: 30
|
|
||||||
order:
|
|
||||||
- file.name
|
|
||||||
- formula.day_of_week
|
|
||||||
- formula.word_estimate
|
|
||||||
- file.mtime
|
|
||||||
```
|
|
||||||
|
|
||||||
## Embedding Bases
|
|
||||||
|
|
||||||
Embed in Markdown files:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
![[MyBase.base]]
|
|
||||||
|
|
||||||
<!-- Specific view -->
|
|
||||||
![[MyBase.base#View Name]]
|
|
||||||
```
|
|
||||||
|
|
||||||
## YAML Quoting Rules
|
|
||||||
|
|
||||||
- Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'`
|
|
||||||
- Use double quotes for simple strings: `"My View Name"`
|
|
||||||
- Escape nested quotes properly in complex expressions
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Filter by Tag
|
|
||||||
```yaml
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- file.hasTag("project")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Filter by Folder
|
|
||||||
```yaml
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- file.inFolder("Notes")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Filter by Date Range
|
|
||||||
```yaml
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- 'file.mtime > now() - "7d"'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Filter by Property Value
|
|
||||||
```yaml
|
|
||||||
filters:
|
|
||||||
and:
|
|
||||||
- 'status == "active"'
|
|
||||||
- 'priority >= 3'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Combine Multiple Conditions
|
|
||||||
```yaml
|
|
||||||
filters:
|
|
||||||
or:
|
|
||||||
- and:
|
|
||||||
- file.hasTag("important")
|
|
||||||
- 'status != "done"'
|
|
||||||
- and:
|
|
||||||
- 'priority == 1'
|
|
||||||
- 'due != ""'
|
|
||||||
```
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Bases Syntax](https://help.obsidian.md/bases/syntax)
|
|
||||||
- [Functions](https://help.obsidian.md/bases/functions)
|
|
||||||
- [Views](https://help.obsidian.md/bases/views)
|
|
||||||
- [Formulas](https://help.obsidian.md/formulas)
|
|
||||||
|
|
||||||
@@ -1,622 +0,0 @@
|
|||||||
---
|
|
||||||
name: obsidian-markdown
|
|
||||||
description: Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, or when the user mentions wikilinks, callouts, frontmatter, tags, embeds, or Obsidian notes.
|
|
||||||
source: https://github.com/kepano/obsidian-skills/blob/main/skills/obsidian-markdown/SKILL.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Obsidian Flavored Markdown Skill
|
|
||||||
|
|
||||||
This skill enables Claude Code to create and edit valid Obsidian Flavored Markdown, including all Obsidian-specific syntax extensions.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Obsidian uses a combination of Markdown flavors:
|
|
||||||
- [CommonMark](https://commonmark.org/)
|
|
||||||
- [GitHub Flavored Markdown](https://github.github.com/gfm/)
|
|
||||||
- [LaTeX](https://www.latex-project.org/) for math
|
|
||||||
- Obsidian-specific extensions (wikilinks, callouts, embeds, etc.)
|
|
||||||
|
|
||||||
## Basic Formatting
|
|
||||||
|
|
||||||
### Paragraphs and Line Breaks
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
This is a paragraph.
|
|
||||||
|
|
||||||
This is another paragraph (blank line between creates separate paragraphs).
|
|
||||||
|
|
||||||
For a line break within a paragraph, add two spaces at the end
|
|
||||||
or use Shift+Enter.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Headings
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Heading 1
|
|
||||||
## Heading 2
|
|
||||||
### Heading 3
|
|
||||||
#### Heading 4
|
|
||||||
##### Heading 5
|
|
||||||
###### Heading 6
|
|
||||||
```
|
|
||||||
|
|
||||||
### Text Formatting
|
|
||||||
|
|
||||||
| Style | Syntax | Example | Output |
|
|
||||||
|-------|--------|---------|--------|
|
|
||||||
| Bold | `**text**` or `__text__` | `**Bold**` | **Bold** |
|
|
||||||
| Italic | `*text*` or `_text_` | `*Italic*` | *Italic* |
|
|
||||||
| Bold + Italic | `***text***` | `***Both***` | ***Both*** |
|
|
||||||
| Strikethrough | `~~text~~` | `~~Striked~~` | ~~Striked~~ |
|
|
||||||
| Highlight | `==text==` | `==Highlighted==` | ==Highlighted== |
|
|
||||||
| Inline code | `` `code` `` | `` `code` `` | `code` |
|
|
||||||
|
|
||||||
### Escaping Formatting
|
|
||||||
|
|
||||||
Use backslash to escape special characters:
|
|
||||||
```markdown
|
|
||||||
\*This won't be italic\*
|
|
||||||
\#This won't be a heading
|
|
||||||
1\. This won't be a list item
|
|
||||||
```
|
|
||||||
|
|
||||||
Common characters to escape: `\*`, `\_`, `\#`, `` \` ``, `\|`, `\~`
|
|
||||||
|
|
||||||
## Internal Links (Wikilinks)
|
|
||||||
|
|
||||||
### Basic Links
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
[[Note Name]]
|
|
||||||
[[Note Name.md]]
|
|
||||||
[[Note Name|Display Text]]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Link to Headings
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
[[Note Name#Heading]]
|
|
||||||
[[Note Name#Heading|Custom Text]]
|
|
||||||
[[#Heading in same note]]
|
|
||||||
[[##Search all headings in vault]]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Link to Blocks
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
[[Note Name#^block-id]]
|
|
||||||
[[Note Name#^block-id|Custom Text]]
|
|
||||||
```
|
|
||||||
|
|
||||||
Define a block ID by adding `^block-id` at the end of a paragraph:
|
|
||||||
```markdown
|
|
||||||
This is a paragraph that can be linked to. ^my-block-id
|
|
||||||
```
|
|
||||||
|
|
||||||
For lists and quotes, add the block ID on a separate line:
|
|
||||||
```markdown
|
|
||||||
> This is a quote
|
|
||||||
> With multiple lines
|
|
||||||
|
|
||||||
^quote-id
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search Links
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
[[##heading]] Search for headings containing "heading"
|
|
||||||
[[^^block]] Search for blocks containing "block"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Markdown-Style Links
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
[Display Text](Note%20Name.md)
|
|
||||||
[Display Text](Note%20Name.md#Heading)
|
|
||||||
[Display Text](https://example.com)
|
|
||||||
[Note](obsidian://open?vault=VaultName&file=Note.md)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: Spaces must be URL-encoded as `%20` in Markdown links.
|
|
||||||
|
|
||||||
## Embeds
|
|
||||||
|
|
||||||
### Embed Notes
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
![[Note Name]]
|
|
||||||
![[Note Name#Heading]]
|
|
||||||
![[Note Name#^block-id]]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Embed Images
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
![[image.png]]
|
|
||||||
![[image.png|640x480]] Width x Height
|
|
||||||
![[image.png|300]] Width only (maintains aspect ratio)
|
|
||||||
```
|
|
||||||
|
|
||||||
### External Images
|
|
||||||
|
|
||||||
```markdown
|
|
||||||

|
|
||||||

|
|
||||||
```
|
|
||||||
|
|
||||||
### Embed Audio
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
![[audio.mp3]]
|
|
||||||
![[audio.ogg]]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Embed PDF
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
![[document.pdf]]
|
|
||||||
![[document.pdf#page=3]]
|
|
||||||
![[document.pdf#height=400]]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Embed Lists
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
![[Note#^list-id]]
|
|
||||||
```
|
|
||||||
|
|
||||||
Where the list has been defined with a block ID:
|
|
||||||
```markdown
|
|
||||||
- Item 1
|
|
||||||
- Item 2
|
|
||||||
- Item 3
|
|
||||||
|
|
||||||
^list-id
|
|
||||||
```
|
|
||||||
|
|
||||||
### Embed Search Results
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
```query
|
|
||||||
tag:#project status:done
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
## Callouts
|
|
||||||
|
|
||||||
### Basic Callout
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
> [!note]
|
|
||||||
> This is a note callout.
|
|
||||||
|
|
||||||
> [!info] Custom Title
|
|
||||||
> This callout has a custom title.
|
|
||||||
|
|
||||||
> [!tip] Title Only
|
|
||||||
```
|
|
||||||
|
|
||||||
### Foldable Callouts
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
> [!faq]- Collapsed by default
|
|
||||||
> This content is hidden until expanded.
|
|
||||||
|
|
||||||
> [!faq]+ Expanded by default
|
|
||||||
> This content is visible but can be collapsed.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nested Callouts
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
> [!question] Outer callout
|
|
||||||
> > [!note] Inner callout
|
|
||||||
> > Nested content
|
|
||||||
```
|
|
||||||
|
|
||||||
### Supported Callout Types
|
|
||||||
|
|
||||||
| Type | Aliases | Description |
|
|
||||||
|------|---------|-------------|
|
|
||||||
| `note` | - | Blue, pencil icon |
|
|
||||||
| `abstract` | `summary`, `tldr` | Teal, clipboard icon |
|
|
||||||
| `info` | - | Blue, info icon |
|
|
||||||
| `todo` | - | Blue, checkbox icon |
|
|
||||||
| `tip` | `hint`, `important` | Cyan, flame icon |
|
|
||||||
| `success` | `check`, `done` | Green, checkmark icon |
|
|
||||||
| `question` | `help`, `faq` | Yellow, question mark |
|
|
||||||
| `warning` | `caution`, `attention` | Orange, warning icon |
|
|
||||||
| `failure` | `fail`, `missing` | Red, X icon |
|
|
||||||
| `danger` | `error` | Red, zap icon |
|
|
||||||
| `bug` | - | Red, bug icon |
|
|
||||||
| `example` | - | Purple, list icon |
|
|
||||||
| `quote` | `cite` | Gray, quote icon |
|
|
||||||
|
|
||||||
### Custom Callouts (CSS)
|
|
||||||
|
|
||||||
```css
|
|
||||||
.callout[data-callout="custom-type"] {
|
|
||||||
--callout-color: 255, 0, 0;
|
|
||||||
--callout-icon: lucide-alert-circle;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Lists
|
|
||||||
|
|
||||||
### Unordered Lists
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
- Item 1
|
|
||||||
- Item 2
|
|
||||||
- Nested item
|
|
||||||
- Another nested
|
|
||||||
- Item 3
|
|
||||||
|
|
||||||
* Also works with asterisks
|
|
||||||
+ Or plus signs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ordered Lists
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
1. First item
|
|
||||||
2. Second item
|
|
||||||
1. Nested numbered
|
|
||||||
2. Another nested
|
|
||||||
3. Third item
|
|
||||||
|
|
||||||
1) Alternative syntax
|
|
||||||
2) With parentheses
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task Lists
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
- [ ] Incomplete task
|
|
||||||
- [x] Completed task
|
|
||||||
- [ ] Task with sub-tasks
|
|
||||||
- [ ] Subtask 1
|
|
||||||
- [x] Subtask 2
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quotes
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
> This is a blockquote.
|
|
||||||
> It can span multiple lines.
|
|
||||||
>
|
|
||||||
> And include multiple paragraphs.
|
|
||||||
>
|
|
||||||
> > Nested quotes work too.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code
|
|
||||||
|
|
||||||
### Inline Code
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
Use `backticks` for inline code.
|
|
||||||
Use double backticks for ``code with a ` backtick inside``.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Code Blocks
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
```
|
|
||||||
Plain code block
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Syntax highlighted code block
|
|
||||||
function hello() {
|
|
||||||
console.log("Hello, world!");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Python example
|
|
||||||
def greet(name):
|
|
||||||
print(f"Hello, {name}!")
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
### Nesting Code Blocks
|
|
||||||
|
|
||||||
Use more backticks or tildes for the outer block:
|
|
||||||
|
|
||||||
`````markdown
|
|
||||||
````markdown
|
|
||||||
Here's how to create a code block:
|
|
||||||
```js
|
|
||||||
console.log("Hello")
|
|
||||||
```
|
|
||||||
````
|
|
||||||
`````
|
|
||||||
|
|
||||||
## Tables
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
| Header 1 | Header 2 | Header 3 |
|
|
||||||
|----------|----------|----------|
|
|
||||||
| Cell 1 | Cell 2 | Cell 3 |
|
|
||||||
| Cell 4 | Cell 5 | Cell 6 |
|
|
||||||
```
|
|
||||||
|
|
||||||
### Alignment
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
| Left | Center | Right |
|
|
||||||
|:---------|:--------:|---------:|
|
|
||||||
| Left | Center | Right |
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using Pipes in Tables
|
|
||||||
|
|
||||||
Escape pipes with backslash:
|
|
||||||
```markdown
|
|
||||||
| Column 1 | Column 2 |
|
|
||||||
|----------|----------|
|
|
||||||
| [[Link\|Display]] | ![[Image\|100]] |
|
|
||||||
```
|
|
||||||
|
|
||||||
## Math (LaTeX)
|
|
||||||
|
|
||||||
### Inline Math
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
This is inline math: $e^{i\pi} + 1 = 0$
|
|
||||||
```
|
|
||||||
|
|
||||||
### Block Math
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
$$
|
|
||||||
\begin{vmatrix}
|
|
||||||
a & b \\
|
|
||||||
c & d
|
|
||||||
\end{vmatrix} = ad - bc
|
|
||||||
$$
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Math Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
$x^2$ Superscript
|
|
||||||
$x_i$ Subscript
|
|
||||||
$\frac{a}{b}$ Fraction
|
|
||||||
$\sqrt{x}$ Square root
|
|
||||||
$\sum_{i=1}^{n}$ Summation
|
|
||||||
$\int_a^b$ Integral
|
|
||||||
$\alpha, \beta$ Greek letters
|
|
||||||
```
|
|
||||||
|
|
||||||
## Diagrams (Mermaid)
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[Start] --> B{Decision}
|
|
||||||
B -->|Yes| C[Do this]
|
|
||||||
B -->|No| D[Do that]
|
|
||||||
C --> E[End]
|
|
||||||
D --> E
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
### Sequence Diagrams
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
Alice->>Bob: Hello Bob
|
|
||||||
Bob-->>Alice: Hi Alice
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
### Linking in Diagrams
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[Biology]
|
|
||||||
B[Chemistry]
|
|
||||||
A --> B
|
|
||||||
class A,B internal-link;
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
## Footnotes
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
This sentence has a footnote[^1].
|
|
||||||
|
|
||||||
[^1]: This is the footnote content.
|
|
||||||
|
|
||||||
You can also use named footnotes[^note].
|
|
||||||
|
|
||||||
[^note]: Named footnotes still appear as numbers.
|
|
||||||
|
|
||||||
Inline footnotes are also supported.^[This is an inline footnote.]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Comments
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
This is visible %%but this is hidden%% text.
|
|
||||||
|
|
||||||
%%
|
|
||||||
This entire block is hidden.
|
|
||||||
It won't appear in reading view.
|
|
||||||
%%
|
|
||||||
```
|
|
||||||
|
|
||||||
## Horizontal Rules
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
***
|
|
||||||
___
|
|
||||||
- - -
|
|
||||||
* * *
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties (Frontmatter)
|
|
||||||
|
|
||||||
Properties use YAML frontmatter at the start of a note:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
---
|
|
||||||
title: My Note Title
|
|
||||||
date: 2024-01-15
|
|
||||||
tags:
|
|
||||||
- project
|
|
||||||
- important
|
|
||||||
aliases:
|
|
||||||
- My Note
|
|
||||||
- Alternative Name
|
|
||||||
cssclasses:
|
|
||||||
- custom-class
|
|
||||||
status: in-progress
|
|
||||||
rating: 4.5
|
|
||||||
completed: false
|
|
||||||
due: 2024-02-01T14:30:00
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
### Property Types
|
|
||||||
|
|
||||||
| Type | Example |
|
|
||||||
|------|---------|
|
|
||||||
| Text | `title: My Title` |
|
|
||||||
| Number | `rating: 4.5` |
|
|
||||||
| Checkbox | `completed: true` |
|
|
||||||
| Date | `date: 2024-01-15` |
|
|
||||||
| Date & Time | `due: 2024-01-15T14:30:00` |
|
|
||||||
| List | `tags: [one, two]` or YAML list |
|
|
||||||
| Links | `related: "[[Other Note]]"` |
|
|
||||||
|
|
||||||
### Default Properties
|
|
||||||
|
|
||||||
- `tags` - Note tags
|
|
||||||
- `aliases` - Alternative names for the note
|
|
||||||
- `cssclasses` - CSS classes applied to the note
|
|
||||||
|
|
||||||
## Tags
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
#tag
|
|
||||||
#nested/tag
|
|
||||||
#tag-with-dashes
|
|
||||||
#tag_with_underscores
|
|
||||||
|
|
||||||
In frontmatter:
|
|
||||||
---
|
|
||||||
tags:
|
|
||||||
- tag1
|
|
||||||
- nested/tag2
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
Tags can contain:
|
|
||||||
- Letters (any language)
|
|
||||||
- Numbers (not as first character)
|
|
||||||
- Underscores `_`
|
|
||||||
- Hyphens `-`
|
|
||||||
- Forward slashes `/` (for nesting)
|
|
||||||
|
|
||||||
## HTML Content
|
|
||||||
|
|
||||||
Obsidian supports HTML within Markdown:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
<div class="custom-container">
|
|
||||||
<span style="color: red;">Colored text</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Click to expand</summary>
|
|
||||||
Hidden content here.
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<kbd>Ctrl</kbd> + <kbd>C</kbd>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complete Example
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
---
|
|
||||||
title: Project Alpha
|
|
||||||
date: 2024-01-15
|
|
||||||
tags:
|
|
||||||
- project
|
|
||||||
- active
|
|
||||||
status: in-progress
|
|
||||||
priority: high
|
|
||||||
---
|
|
||||||
|
|
||||||
# Project Alpha
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This project aims to [[improve workflow]] using modern techniques.
|
|
||||||
|
|
||||||
> [!important] Key Deadline
|
|
||||||
> The first milestone is due on ==January 30th==.
|
|
||||||
|
|
||||||
## Tasks
|
|
||||||
|
|
||||||
- [x] Initial planning
|
|
||||||
- [x] Resource allocation
|
|
||||||
- [ ] Development phase
|
|
||||||
- [ ] Backend implementation
|
|
||||||
- [ ] Frontend design
|
|
||||||
- [ ] Testing
|
|
||||||
- [ ] Deployment
|
|
||||||
|
|
||||||
## Technical Notes
|
|
||||||
|
|
||||||
The main algorithm uses the formula $O(n \log n)$ for sorting.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def process_data(items):
|
|
||||||
return sorted(items, key=lambda x: x.priority)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
A[Input] --> B[Process]
|
|
||||||
B --> C[Output]
|
|
||||||
B --> D[Cache]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Related Documents
|
|
||||||
|
|
||||||
- ![[Meeting Notes 2024-01-10#Decisions]]
|
|
||||||
- [[Budget Allocation|Budget]]
|
|
||||||
- [[Team Members]]
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
For more details, see the official documentation[^1].
|
|
||||||
|
|
||||||
[^1]: https://example.com/docs
|
|
||||||
|
|
||||||
%%
|
|
||||||
Internal notes:
|
|
||||||
- Review with team on Friday
|
|
||||||
- Consider alternative approaches
|
|
||||||
%%
|
|
||||||
````
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Basic formatting syntax](https://help.obsidian.md/syntax)
|
|
||||||
- [Advanced formatting syntax](https://help.obsidian.md/advanced-syntax)
|
|
||||||
- [Obsidian Flavored Markdown](https://help.obsidian.md/obsidian-flavored-markdown)
|
|
||||||
- [Internal links](https://help.obsidian.md/links)
|
|
||||||
- [Embed files](https://help.obsidian.md/embeds)
|
|
||||||
- [Callouts](https://help.obsidian.md/callouts)
|
|
||||||
- [Properties](https://help.obsidian.md/properties)
|
|
||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
---
|
|
||||||
name: process-compose
|
|
||||||
description: Inspect a running process-compose project read-only. Use when the user asks what services/processes are running, whether a service is up or healthy, what state a process is in, or to read a service's recent log output — or mentions process-compose (pc) by name.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Inspecting process-compose
|
|
||||||
|
|
||||||
The `process-compose` CLI is a thin **client**. It does not read your config or
|
|
||||||
processes directly — it queries a process-compose **server** that is already
|
|
||||||
running (the one started by `process-compose up`). Every command below talks to
|
|
||||||
that server over TCP, default `localhost:8080`.
|
|
||||||
|
|
||||||
This skill is **read-only**: never start, stop, restart, or scale processes.
|
|
||||||
|
|
||||||
## Connecting
|
|
||||||
|
|
||||||
- Default target is `localhost:8080`. If a project runs on another port, pass
|
|
||||||
`-p PORT` (or set `PC_PORT_NUM`); the port is whatever that project's
|
|
||||||
`process-compose.yaml` / launch command set.
|
|
||||||
- A "connection refused" error means **no server is running on that port**, not
|
|
||||||
a bad command. Report that the project isn't up rather than retrying variants.
|
|
||||||
- `pc` is a fish abbreviation for `process-compose` and exists only in an
|
|
||||||
interactive fish shell. In scripts and Bash calls use the full `process-compose`.
|
|
||||||
|
|
||||||
## Read commands
|
|
||||||
|
|
||||||
List every process with its status (one line each):
|
|
||||||
|
|
||||||
```
|
|
||||||
process-compose process list -o wide
|
|
||||||
```
|
|
||||||
|
|
||||||
Add `-o json` when you need to parse fields (status, health, pid, restarts, exit
|
|
||||||
code) rather than display them.
|
|
||||||
|
|
||||||
Full state of one process:
|
|
||||||
|
|
||||||
```
|
|
||||||
process-compose process get NAME -o json
|
|
||||||
```
|
|
||||||
|
|
||||||
Recent log lines for a process (tail the last N — adjust the number to the need):
|
|
||||||
|
|
||||||
```
|
|
||||||
process-compose process logs NAME -n 100
|
|
||||||
```
|
|
||||||
|
|
||||||
Multiple processes: comma-separate them (`proc1,proc2`). A whole namespace:
|
|
||||||
`-N NAMESPACE`.
|
|
||||||
|
|
||||||
Whole-project state (is everything ready):
|
|
||||||
|
|
||||||
```
|
|
||||||
process-compose project state
|
|
||||||
```
|
|
||||||
|
|
||||||
## Never do
|
|
||||||
|
|
||||||
- **No TUI.** Bare `process-compose`, `up`, and `attach` launch the interactive
|
|
||||||
full-screen TUI and hang a non-interactive shell. Always use a subcommand.
|
|
||||||
- **No `-f` / `--follow`** on `logs` — it streams forever and blocks. Use `-n` to
|
|
||||||
pull a finite tail instead.
|
|
||||||
- **No mutations** — `start`, `stop`, `restart`, `scale`, `down` are out of scope
|
|
||||||
for this skill.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/prototype
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/research
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/resolving-merge-conflicts
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
---
|
|
||||||
name: rewrite-comments
|
|
||||||
description: Inspect and rewrite unclear source-code comments after tracing the surrounding behavior and contracts. Use when asked to review, improve, clean up, or rewrite comments selected by a Git commit, the current working tree, or a named function or method; function requests may target comments on the declaration, inside its body, or both.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Rewrite Comments
|
|
||||||
|
|
||||||
Rewrite comments only after understanding the code well enough to preserve its meaning. Make edits in the current working tree; never rewrite Git history unless explicitly requested.
|
|
||||||
|
|
||||||
## Resolve the target
|
|
||||||
|
|
||||||
Determine the scope from the request:
|
|
||||||
|
|
||||||
- **Commit:** Inspect the commit diff and the complete current versions of the code regions it changes. Treat comments added or modified by the commit, plus comments attached to or inside changed declarations, as candidates. If the relevant code no longer exists in the current tree, report that instead of editing an older revision or history.
|
|
||||||
- **Working tree:** Inspect staged, unstaged, and untracked changes. Treat comments in or attached to changed declarations as candidates, prioritizing comments changed in the diff.
|
|
||||||
- **Function:** Locate the named function or method. Include comments documenting the declaration and comments within its body by default. Honor `on`, `above`, or `documentation` as declaration-only, and `within`, `inside`, or `body` as body-only.
|
|
||||||
|
|
||||||
Use repository-aware search and language structure where available. Account for overloads, methods with the same name, generated code, and renamed files. Ask for clarification only when multiple plausible targets remain and choosing one would materially change the edits.
|
|
||||||
|
|
||||||
Do not broaden the edit scope merely because nearby comments could also be improved. Read outside the scope freely to establish context.
|
|
||||||
|
|
||||||
## Build context before editing
|
|
||||||
|
|
||||||
Read the full enclosing declaration and enough neighboring code to understand it. Trace relevant definitions and call sites until the important behavior is supported by evidence. Establish, as applicable:
|
|
||||||
|
|
||||||
- why the code exists and which callers depend on it;
|
|
||||||
- accepted inputs, returned values, mutations, side effects, and failure behavior;
|
|
||||||
- ordering, lifecycle, concurrency, caching, security, and performance constraints;
|
|
||||||
- invariants, edge cases, compatibility requirements, and intentionally surprising choices;
|
|
||||||
- types, tests, interfaces, domain documentation, and architectural decisions that define the contract.
|
|
||||||
|
|
||||||
Prefer direct evidence from code, tests, and repository documentation. Do not invent intent. If a comment makes a claim that cannot be verified, either make it narrower and factual or remove it.
|
|
||||||
|
|
||||||
Stop exploring when the comment's purpose and every retained behavioral claim can be explained from evidence. Avoid tracing unrelated parts of the system.
|
|
||||||
|
|
||||||
## Judge each candidate
|
|
||||||
|
|
||||||
Keep an accurate comment when it already adds durable information. Rewrite or remove a comment when it is unclear, redundant, misleading, stale, speculative, or coupled to incidental implementation details.
|
|
||||||
|
|
||||||
Prioritize comments that capture:
|
|
||||||
|
|
||||||
- hidden contracts and invariants;
|
|
||||||
- non-obvious reasons and tradeoffs;
|
|
||||||
- caller-visible edge cases or failure behavior;
|
|
||||||
- constraints imposed by another subsystem, API, format, or compatibility promise;
|
|
||||||
- deliberate deviations from the obvious implementation.
|
|
||||||
|
|
||||||
Avoid comments that:
|
|
||||||
|
|
||||||
- narrate syntax or restate names and types;
|
|
||||||
- describe mechanics that are immediately apparent from the next few lines;
|
|
||||||
- duplicate the type system or stable API shape without adding a contract;
|
|
||||||
- predict future work without an actionable, repository-standard marker;
|
|
||||||
- mention transient details, line positions, counts, or internal steps likely to drift;
|
|
||||||
- preserve a confident explanation unsupported by the code.
|
|
||||||
|
|
||||||
Retain required legal notices, tool directives, generated-file markers, suppression comments, and structured documentation tags unless the task explicitly includes them and changing them is safe.
|
|
||||||
|
|
||||||
## Rewrite
|
|
||||||
|
|
||||||
Edit only comments unless the user explicitly requests code changes. Preserve behavior, public API, formatting conventions, comment style, and documentation syntax.
|
|
||||||
|
|
||||||
Write concise, direct comments at the narrowest useful location. Explain `why`, `must`, `unless`, or `despite` when those ideas matter. State contracts in terms of observable behavior rather than current implementation. Include inputs and outputs only when their semantics, ownership, units, normalization, sentinel values, or failure modes are not already obvious from code and types.
|
|
||||||
|
|
||||||
Delete a comment when removal is clearer than a rewrite. Do not add comments merely to replace every removed one, and do not churn wording without a meaningful clarity or correctness improvement.
|
|
||||||
|
|
||||||
## Verify and report
|
|
||||||
|
|
||||||
Review the final diff and confirm that edits are limited to the resolved scope and do not alter executable code. Re-read each edited comment against its code and callers. Run repository-required checks for changed files and any broader checks explicitly required by repository instructions; treat failures as blocking.
|
|
||||||
|
|
||||||
Summarize which comments changed and the hidden behavior or contract they now clarify. Mention comments intentionally removed, unresolved ambiguity, unverifiable claims, and checks that could not be run.
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: "Rewrite Comments"
|
|
||||||
short_description: "Rewrite code comments around selected changes"
|
|
||||||
default_prompt: "Use $rewrite-comments to inspect and improve comments in my working tree."
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
---
|
|
||||||
name: senior-code-review
|
|
||||||
description: Perform a thorough, read-only review of a pull request, branch, commit, working-tree diff, or named files. Use when asked to review code.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Senior Code Review
|
|
||||||
|
|
||||||
Perform a high-recall review followed by a high-precision verification pass. Behave like a senior engineer who must understand the local system before judging the change.
|
|
||||||
|
|
||||||
## Operating contract
|
|
||||||
|
|
||||||
- Treat the review as read-only. Do not edit files, apply fixes, change git state, commit, or post comments unless the user explicitly asks in a separate instruction.
|
|
||||||
- Review the change, not the author's process. Do not excuse a defect because tests pass or because the implementation was difficult.
|
|
||||||
- Prefer concrete, falsifiable findings over generic advice. Do not report style preferences unless they violate repository conventions or materially harm comprehension.
|
|
||||||
- Keep distinct findings separate; combine repeated instances of one root cause.
|
|
||||||
- Investigate enough surrounding code to determine whether a suspected problem is real. The diff is an index, not the full review surface.
|
|
||||||
- Report no findings when none survive verification. Never invent issues to make a review look thorough.
|
|
||||||
|
|
||||||
Read [references/review-lenses.md](references/review-lenses.md) before reviewing. When independent subagents are available, also read [references/reviewer-briefs.md](references/reviewer-briefs.md).
|
|
||||||
|
|
||||||
## 1. Establish scope
|
|
||||||
|
|
||||||
Use the user's explicit target when supplied. Otherwise choose the first applicable scope:
|
|
||||||
|
|
||||||
1. Staged, unstaged, and relevant untracked changes when the working tree has changes.
|
|
||||||
2. The current branch against its merge base with the default or upstream branch.
|
|
||||||
3. The most recent commit when no other change exists.
|
|
||||||
|
|
||||||
Resolve the base and head before reviewing. Inspect the diff summary, changed paths, commits, and rename/binary status. Stop with a concise explanation if the target is invalid or the diff is empty.
|
|
||||||
|
|
||||||
Exclude generated, vendored, lock, and snapshot files from line-by-line review when appropriate, but inspect their semantic effects when dependency, schema, API, or snapshot changes are relevant.
|
|
||||||
|
|
||||||
## 2. Build codebase context
|
|
||||||
|
|
||||||
Before judging implementation details:
|
|
||||||
|
|
||||||
1. Read the applicable repository instructions, including root and path-scoped `AGENTS.md`, `CLAUDE.md`, `REVIEW.md`, `CONTRIBUTING.md`, and relevant engineering or architecture documents.
|
|
||||||
2. Determine the intended behavior from the user's request, PR or issue text when available, commit messages, specifications, and tests. State any important requirement that remains unavailable.
|
|
||||||
3. Read each changed source and test file as a whole when feasible, not only the edited hunks.
|
|
||||||
4. Trace affected entry points, callers, callees, types, data flows, configuration, and persistence boundaries far enough to understand observable behavior.
|
|
||||||
5. Search for sibling implementations, established helpers, test conventions, error-handling patterns, and architectural precedents.
|
|
||||||
6. Use focused git history or blame when a surprising invariant, compatibility shim, or architectural choice may be intentional.
|
|
||||||
|
|
||||||
Produce a short internal context map: change intent, affected behavior, relevant invariants, architectural boundaries, and verification mechanisms. Share it with review subagents; do not clutter the final response with it unless it explains a finding.
|
|
||||||
|
|
||||||
## 3. Inspect tests before implementation
|
|
||||||
|
|
||||||
Read changed and nearby tests first. Infer the promised behavior, then challenge the tests:
|
|
||||||
|
|
||||||
- Would a plausible broken implementation still pass?
|
|
||||||
- Do assertions observe externally meaningful behavior or merely repeat mocks, fixtures, implementation details, or values constructed by the test itself?
|
|
||||||
- Do mocks preserve the real contract, ordering, failure modes, and data shape?
|
|
||||||
- Does a regression test fail without the fix for the intended reason?
|
|
||||||
- Are negative, boundary, authorization, error, concurrency, and integration paths covered where risk warrants them?
|
|
||||||
- Can the test pass vacuously because code was not called, an async result was not awaited, an exception was swallowed, or the assertion is too broad?
|
|
||||||
|
|
||||||
Do not equate coverage with correctness. Treat missing tests as a finding only when you can name the meaningful behavior or regression that remains unprotected.
|
|
||||||
|
|
||||||
## 4. Run independent review passes
|
|
||||||
|
|
||||||
For a non-trivial change, use four independent review seats in parallel when the host supports subagents:
|
|
||||||
|
|
||||||
1. Correctness and unintended behavior.
|
|
||||||
2. Test validity and verification gaps.
|
|
||||||
3. Security and defensive coding.
|
|
||||||
4. Architecture, abstractions, and maintainability.
|
|
||||||
|
|
||||||
Give every seat the same scope, intent, context map, changed-file list, and read-only constraint. Keep their reasoning isolated. Use the exact contracts in `references/reviewer-briefs.md` and allow them to inspect the repository rather than pasting an enormous diff into each prompt.
|
|
||||||
|
|
||||||
If subagents are unavailable or the change is very small, perform the four passes sequentially yourself. Finish one lens before starting the next so concerns do not collapse into a shallow general scan.
|
|
||||||
|
|
||||||
Add focused passes for performance, concurrency, migrations, compatibility, observability, accessibility, or dependency risk when the changed surfaces make them relevant.
|
|
||||||
|
|
||||||
## 5. Verify candidate findings
|
|
||||||
|
|
||||||
Collect candidate findings from every pass, deduplicate by root cause, then challenge them with a fresh verification pass. Use an independent verifier subagent when available.
|
|
||||||
|
|
||||||
Keep a bug, security, or test finding only when all applicable statements are true:
|
|
||||||
|
|
||||||
- The finding identifies a precise changed or directly affected location.
|
|
||||||
- The reviewer read the surrounding implementation, callers, guards, types, and relevant tests.
|
|
||||||
- A concrete input, state, or execution path leads to a specific bad outcome.
|
|
||||||
- Existing validation, framework behavior, or another layer does not already prevent it.
|
|
||||||
- The issue is introduced by the change or is directly relevant to safely merging it. Clearly label significant pre-existing issues.
|
|
||||||
- The severity matches realistic impact and likelihood.
|
|
||||||
- The proposed direction addresses the root cause without creating a larger problem.
|
|
||||||
|
|
||||||
Keep an architecture or maintainability finding only when it identifies a concrete cost such as an invalid dependency direction, duplicated source of truth, leaky boundary, misleading abstraction, excessive coupling, hidden invariant, or materially harder future change. Explain the tradeoff and label non-blocking improvements as suggestions.
|
|
||||||
|
|
||||||
Require at least 80% confidence for reported defects. Drop refuted, purely speculative, or unactionable candidates. When uncertainty is important and cannot be resolved, state the missing evidence rather than asserting a defect.
|
|
||||||
|
|
||||||
## 6. Report findings
|
|
||||||
|
|
||||||
Lead with findings ordered by severity and then by confidence. Use these levels:
|
|
||||||
|
|
||||||
- `P0 Critical`: immediate security compromise, data loss, corruption, or broad outage.
|
|
||||||
- `P1 High`: concrete bug, security vulnerability, broken contract, or invalid test that should block merge.
|
|
||||||
- `P2 Medium`: real but narrower defect, meaningful defensive-coding gap, or structural problem likely to cause defects.
|
|
||||||
- `P3 Suggestion`: non-blocking architectural or maintainability improvement with a concrete benefit.
|
|
||||||
|
|
||||||
Format each finding as:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
### [P1][correctness] Short imperative title
|
|
||||||
`path/to/file.ext:line`
|
|
||||||
|
|
||||||
- Evidence: What the code does and the surrounding fact that makes it wrong.
|
|
||||||
- Failure scenario: The triggering input/state and observable outcome.
|
|
||||||
- Recommendation: The smallest sound direction for fixing the root cause.
|
|
||||||
- Confidence: 92%
|
|
||||||
```
|
|
||||||
|
|
||||||
For test findings, name the mutation or broken implementation that would still pass. For security findings, name the trust boundary, attacker-controlled input or capability, and impact. For architecture findings, name the boundary or invariant and compare the proposed structure with the current cost.
|
|
||||||
|
|
||||||
After findings, include:
|
|
||||||
|
|
||||||
- `Verdict`: `Do not merge`, `Merge after fixes`, or `Ready to merge`.
|
|
||||||
- `Review coverage`: the scope and lenses actually inspected.
|
|
||||||
- `Verification gaps`: commands, runtime checks, specifications, or environment access that were unavailable.
|
|
||||||
|
|
||||||
If no finding survives verification, say `No verified findings.` and still list material verification gaps. Do not add praise, a diff summary, or generic best-practice advice unless it helps the user act on the review.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: Senior Code Review
|
|
||||||
short_description: Thorough, evidence-backed code review
|
|
||||||
default_prompt: Use $senior-code-review to perform a thorough, read-only review
|
|
||||||
of the current changes.
|
|
||||||
icon_small: assets/icon.svg
|
|
||||||
icon_large: assets/icon.svg
|
|
||||||
policy:
|
|
||||||
products:
|
|
||||||
- chatgpt
|
|
||||||
- codex
|
|
||||||
- api
|
|
||||||
- atlas
|
|
||||||
allow_implicit_invocation: true
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M3 13.5C3 12.1193 4.11929 11 5.5 11H9V21H5.5C4.11929 21 3 19.8807 3 18.5V13.5Z" fill="#FFD400"/>
|
|
||||||
<path d="M15 7H18.5C19.8807 7 21 8.11929 21 9.5V18.5C21 19.8807 19.8807 21 18.5 21H15V7Z" fill="#F75858"/>
|
|
||||||
<path d="M9 5.5C9 4.11929 10.1193 3 11.5 3H12.5C13.8807 3 15 4.11929 15 5.5V21H9V5.5Z" fill="#FFA43D"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 420 B |
@@ -1,73 +0,0 @@
|
|||||||
# Review lenses
|
|
||||||
|
|
||||||
Use every core lens. Expand only the checks relevant to the changed surfaces.
|
|
||||||
|
|
||||||
## Contents
|
|
||||||
|
|
||||||
- Correctness and unintended behavior
|
|
||||||
- Test validity
|
|
||||||
- Security and defensive coding
|
|
||||||
- Architecture and maintainability
|
|
||||||
- Conditional lenses
|
|
||||||
|
|
||||||
## Correctness and unintended behavior
|
|
||||||
|
|
||||||
- Trace happy, error, empty, null, boundary, retry, cancellation, and partial-failure paths.
|
|
||||||
- Check state transitions, ordering, idempotency, caching, invalidation, and cleanup.
|
|
||||||
- Look for off-by-one errors, stale state, wrong defaults, lossy conversions, timezone or locale mistakes, and mismatched units.
|
|
||||||
- Check async ownership, missing awaits, races, deadlocks, double execution, resource leaks, and work after cancellation.
|
|
||||||
- Compare types and runtime values across boundaries; do not assume static types validate external data.
|
|
||||||
- Check compatibility with existing callers, serialized formats, database rows, events, CLI flags, environment variables, and public APIs.
|
|
||||||
- Follow error values and exceptions. Flag swallowed failures, misleading fallbacks, partial writes, and success reported before durable completion.
|
|
||||||
- Verify deletions and refactors leave no live caller, stale branch, duplicate path, or behavior split between old and new implementations.
|
|
||||||
- Distinguish behavior newly broken by the change from a nearby pre-existing issue.
|
|
||||||
|
|
||||||
## Test validity
|
|
||||||
|
|
||||||
- Identify the behavior each test claims to protect and the production branch that implements it.
|
|
||||||
- Mentally mutate or remove the production behavior. Determine whether the test fails for the intended reason.
|
|
||||||
- Detect tautological assertions, assertions derived from the same value under test, snapshots accepted without semantic inspection, and tests with no meaningful assertion.
|
|
||||||
- Check that the exercised path runs: awaited promises, consumed generators, subscribed streams, flushed transactions, rendered components, and invoked callbacks.
|
|
||||||
- Treat mocks as contracts. Compare mock methods, data, ordering, errors, and side effects with the real dependency.
|
|
||||||
- Flag tests that verify implementation details while missing observable outcomes.
|
|
||||||
- Check false positives from broad exception assertions, loose matchers, unconditional waits, retries that mask failure, or fixtures that bypass the behavior.
|
|
||||||
- For bug fixes, require a regression test that would fail on the old behavior when practical.
|
|
||||||
- Seek high-risk gaps: permissions, tenant isolation, malformed input, boundaries, failure recovery, concurrency, migrations, and cross-component integration.
|
|
||||||
- Do not demand a test for trivial wiring already proven by stronger coverage.
|
|
||||||
|
|
||||||
## Security and defensive coding
|
|
||||||
|
|
||||||
- Identify trust boundaries and attacker-controlled input before applying a checklist.
|
|
||||||
- Check authentication, authorization, object ownership, tenant isolation, and confused-deputy paths at the point of action.
|
|
||||||
- Validate and canonicalize input at boundaries; encode output for its destination context.
|
|
||||||
- Look for injection into SQL, commands, templates, HTML, URLs, headers, logs, paths, deserializers, and dynamic code.
|
|
||||||
- Check secrets, credentials, tokens, personal data, and sensitive metadata in source, logs, errors, caches, telemetry, URLs, and client-visible responses.
|
|
||||||
- Review cryptography, randomness, signature verification, token lifetime, replay resistance, secure comparison, and key handling when touched.
|
|
||||||
- Check filesystem traversal, unsafe temporary files, archive extraction, symlink behavior, permissions, and cleanup.
|
|
||||||
- Review SSRF, redirect validation, request smuggling surfaces, unbounded downloads, parser limits, and denial-of-service opportunities for network-facing changes.
|
|
||||||
- Check dependency provenance, pinned integrity, install or build scripts, dangerous configuration defaults, and privilege expansion.
|
|
||||||
- Prefer fail-closed behavior where authorization or integrity is uncertain, without turning recoverable availability failures into outages.
|
|
||||||
- Consider abuse cases and defense in depth; report only issues supported by a realistic capability and impact.
|
|
||||||
|
|
||||||
## Architecture and maintainability
|
|
||||||
|
|
||||||
- Compare the change with existing architectural boundaries and dependency direction.
|
|
||||||
- Check separation of orchestration, domain logic, persistence, transport, and presentation.
|
|
||||||
- Identify feature logic leaking into shared infrastructure or transport details leaking into the domain.
|
|
||||||
- Look for duplicated sources of truth, parallel implementations, inconsistent policy enforcement, and abstractions that conceal rather than remove complexity.
|
|
||||||
- Challenge both under-abstraction and premature abstraction. Require a named invariant or repeated variation before adding a general layer.
|
|
||||||
- Check cohesion, coupling, fan-in/fan-out, circular dependencies, global state, hidden temporal coupling, and unclear resource ownership.
|
|
||||||
- Prefer explicit invariants, narrow interfaces, meaningful types, and canonical helpers over casts, optional fields, catch-all objects, and silent fallbacks.
|
|
||||||
- Judge readability through control flow, naming, locality, concept count, and how much unrelated context a maintainer must retain.
|
|
||||||
- Check whether the design supports likely change without optimizing for hypothetical futures.
|
|
||||||
- When suggesting restructuring, name the move and why it lowers risk: split responsibility, invert dependency, centralize policy, remove pass-through abstraction, model state explicitly, or reuse a canonical path.
|
|
||||||
- Avoid requesting large refactors unless the current change creates or materially worsens the problem.
|
|
||||||
|
|
||||||
## Conditional lenses
|
|
||||||
|
|
||||||
- Performance: complexity, repeated I/O, N+1 access, allocations, hot-path blocking, pagination, cache correctness, and backpressure.
|
|
||||||
- Data changes: forward/backward compatibility, transactionality, rollbacks, backfills, mixed-version deployment, and irreversible loss.
|
|
||||||
- Distributed systems: retries, duplicate delivery, ordering, idempotency, clock assumptions, split brain, and partial availability.
|
|
||||||
- UI and accessibility: loading/error/empty states, keyboard and focus behavior, semantics, contrast, responsive behavior, and hydration.
|
|
||||||
- Observability: actionable errors, stable structured fields, correlation, sensitive-data hygiene, metrics on failure paths, and diagnosable fallbacks.
|
|
||||||
- Dependencies and configuration: default changes, environment parity, least privilege, reproducibility, version compatibility, and safe rollout.
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# Reviewer briefs
|
|
||||||
|
|
||||||
Use these briefs for isolated review seats. Replace placeholders with the established scope and context. Every reviewer must remain read-only and inspect repository files directly.
|
|
||||||
|
|
||||||
## Shared preamble
|
|
||||||
|
|
||||||
```text
|
|
||||||
Act as an independent senior code reviewer. Do not edit files or change git state.
|
|
||||||
|
|
||||||
Review target: {TARGET}
|
|
||||||
Intent and requirements: {INTENT}
|
|
||||||
Changed files: {CHANGED_FILES}
|
|
||||||
Relevant repository guidance: {GUIDANCE}
|
|
||||||
Context map and invariants: {CONTEXT_MAP}
|
|
||||||
|
|
||||||
Read the relevant diff, complete changed files, surrounding code, and tests. Trace callers or dependencies when needed. Return candidate findings only. For every candidate include file and line, concrete evidence, failure or maintenance scenario, severity, recommendation, and confidence. Do not report generic advice or repeat repository tooling. Mark pre-existing issues explicitly.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Correctness seat
|
|
||||||
|
|
||||||
```text
|
|
||||||
Focus on runtime correctness and unintended behavior. Trace affected flows through callers, boundaries, state transitions, error paths, compatibility contracts, async behavior, cleanup, and edge cases. Find concrete cases where the implementation produces the wrong observable outcome. Challenge assumptions made by types, frameworks, and happy-path tests. Ignore cosmetic style and unproven hypotheticals.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test seat
|
|
||||||
|
|
||||||
```text
|
|
||||||
Focus on whether the tests genuinely prove the promised behavior. Read tests before implementation. For each suspicious test, name a realistic broken production mutation that would still pass. Check vacuous execution, weak or tautological assertions, unrealistic mocks, missing awaits, swallowed failures, implementation-detail coupling, and high-risk behavioral gaps. Do not equate coverage with correctness or demand low-value tests.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security seat
|
|
||||||
|
|
||||||
```text
|
|
||||||
Focus on security and defensive coding. Identify trust boundaries, attacker-controlled inputs, sensitive assets, and privileges first. Examine authentication, authorization, isolation, injection, data exposure, unsafe parsing or filesystem/network behavior, cryptography, dependency risk, resource limits, and failure posture. Report only findings with a realistic attacker capability, path, and impact; include relevant defense-in-depth gaps when concrete.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture seat
|
|
||||||
|
|
||||||
```text
|
|
||||||
Focus on architecture, abstractions, readability, and maintainability in the context of this repository. Compare with existing patterns and boundaries. Look for invalid dependency direction, duplicated truth, leaky layers, hidden invariants, excessive coupling, unclear ownership, premature or missing abstractions, and complexity merely relocated. Suggest a specific structural move and explain its benefit and tradeoff. Separate merge-blocking structural regressions from optional improvements.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification seat
|
|
||||||
|
|
||||||
```text
|
|
||||||
Act as an adversarial verifier, not another finder. Given the repository, review target, requirements, and candidate findings, verify each candidate independently.
|
|
||||||
|
|
||||||
For each candidate:
|
|
||||||
1. Read the cited code and enough surrounding callers, guards, types, tests, and framework behavior to understand it.
|
|
||||||
2. Reproduce or reason through the exact input/state and outcome.
|
|
||||||
3. Decide confirmed, uncertain, or refuted.
|
|
||||||
4. Check whether the change introduced it or merely exposed a pre-existing issue.
|
|
||||||
5. Correct severity and recommendation.
|
|
||||||
6. Merge duplicates that share a root cause.
|
|
||||||
|
|
||||||
Return only confirmed findings at 80% confidence or higher, plus unresolved uncertainties where missing evidence is itself important. Preserve concrete non-blocking architecture suggestions, clearly labeled as suggestions. Do not accept a finding because multiple reviewers repeated it.
|
|
||||||
```
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
---
|
|
||||||
name: show-me
|
|
||||||
description: Help the user understand the current topic visually with concise diagrams, code-shape sketches, and focused HTML artifacts.
|
|
||||||
source: https://github.com/humanlayer/skills/blob/main/plugins/show-me/skills/show-me/SKILL.md
|
|
||||||
---
|
|
||||||
|
|
||||||
Help the user understand the current topic of conversation visually. Skip the preamble and keep prose brief. Pick the smallest view that makes the key point clear.
|
|
||||||
|
|
||||||
- Show logic or an algorithm as pseudocode:
|
|
||||||
|
|
||||||
```text
|
|
||||||
on(save)
|
|
||||||
if content is unchanged
|
|
||||||
return cached result
|
|
||||||
write new content
|
|
||||||
return fresh result
|
|
||||||
```
|
|
||||||
|
|
||||||
- Show runtime control flow as a call tree:
|
|
||||||
|
|
||||||
```text
|
|
||||||
submitForm
|
|
||||||
createSession
|
|
||||||
persistPrompt
|
|
||||||
launchAgent
|
|
||||||
navigateToSession
|
|
||||||
```
|
|
||||||
|
|
||||||
- Show UI structure as a component tree, including state and module boundaries that matter:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<SessionPage> (apps/example/src/routes/session.tsx)
|
|
||||||
useSessionEvents()
|
|
||||||
<SessionToolbar>
|
|
||||||
<RunSkillButton> (packages/ui)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Show file responsibility or a broad refactor as a shallow file tree:
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/
|
|
||||||
├── commands/ # parses user actions
|
|
||||||
├── sessions/ # owns session state
|
|
||||||
└── transport/ # sends API requests
|
|
||||||
```
|
|
||||||
|
|
||||||
- Show component interaction, control flow, or data flow with Mermaid:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant User
|
|
||||||
participant UI
|
|
||||||
participant Daemon
|
|
||||||
User->>UI: choose command
|
|
||||||
UI->>Daemon: send expanded prompt
|
|
||||||
Daemon-->>UI: stream result
|
|
||||||
```
|
|
||||||
|
|
||||||
- Use `diff` when the point is what changes and the surrounding shape already exists. Match the diff shape to the topic.
|
|
||||||
|
|
||||||
For a component change:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
<SessionPage>
|
|
||||||
useSessionEvents()
|
|
||||||
<SessionToolbar>
|
|
||||||
+ <RunSkillButton />
|
|
||||||
<SessionTimeline>
|
|
||||||
+ <SkillResultCard />
|
|
||||||
```
|
|
||||||
|
|
||||||
For a file-layout change:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
src/
|
|
||||||
├── commands/
|
|
||||||
+│ └── show-me.ts # expands the slash command
|
|
||||||
├── sessions/
|
|
||||||
-└── transport.ts
|
|
||||||
+└── transport/
|
|
||||||
+ ├── client.ts
|
|
||||||
+ └── stream.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
For a call-tree or call-stack change:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
submitForm
|
|
||||||
createSession
|
|
||||||
persistPrompt
|
|
||||||
+ expandSkillMention
|
|
||||||
launchAgent
|
|
||||||
- navigateToSession
|
|
||||||
+ navigateToSession
|
|
||||||
+ subscribeToEvents
|
|
||||||
```
|
|
||||||
|
|
||||||
For a state or control-flow change:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
on(save)
|
|
||||||
- write content
|
|
||||||
+ if content is unchanged
|
|
||||||
+ return cached result
|
|
||||||
+ write new content
|
|
||||||
+ invalidate cache
|
|
||||||
```
|
|
||||||
|
|
||||||
- Show the whole block when most of it is new, when omitted context would hide ownership or order, or when the user needs a copyable target shape:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function expandSkill(command: string): string {
|
|
||||||
const skillName = command.slice(1)
|
|
||||||
return `use the ${skillName} skill`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- For a visual UI, layout, state comparison, or concept too dense for Mermaid, write one focused HTML file — a diagram, an infographic, or a short slide deck, whichever fits the point. Match the product's colors, type, spacing, and components; use real labels and data; support desktop and mobile. Then open it for the user:
|
|
||||||
|
|
||||||
```
|
|
||||||
Bash(open path/to/show-me-{description}.html)
|
|
||||||
```
|
|
||||||
|
|
||||||
### guidance
|
|
||||||
|
|
||||||
Place each visual next to the short text it supports. Keep only the calls, files, props, states, and boundaries needed to answer the user's current question or the options to resolve the current discussion point.
|
|
||||||
|
|
||||||
You may use one of these, you may use several, it is unlikely you will use all of them. Use your judgement and don't overwhelm the user.
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
---
|
|
||||||
name: simplify-code
|
|
||||||
description: Simplify recently changed code without changing behavior. Use when asked to clean up, clarify, streamline, refactor, or review an uncommitted diff, staged changes, changes against a Git ref or branch, the last commit, or specified changed files. Restrict edits to changed lines, follow repository guidance, preserve public APIs and semantics, and verify the result with the project's tests and checks.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Simplify Code
|
|
||||||
|
|
||||||
Improve the readability and maintainability of a Git-scoped change while preserving its behavior. Treat the existing diff as the edit boundary, not as permission to refactor the surrounding code.
|
|
||||||
|
|
||||||
## Resolve the scope
|
|
||||||
|
|
||||||
1. Find the repository root with `git rev-parse --show-toplevel` and work from it.
|
|
||||||
2. Read applicable repository instructions such as `AGENTS.md` and `CLAUDE.md`, including any nearer files that govern the changed paths.
|
|
||||||
3. Preserve unrelated worktree changes. Never reset, restore, discard, stage, or commit changes unless the user explicitly asks.
|
|
||||||
4. Select one base scope from the request:
|
|
||||||
- Default, including staged and unstaged tracked changes: `git diff HEAD`
|
|
||||||
- Staged only: `git diff --cached`
|
|
||||||
- Against a ref or branch: `git diff <ref>`
|
|
||||||
- Last commit: `git diff HEAD~1..HEAD`
|
|
||||||
5. If the user names files, append `-- <paths...>` and exclude every other path.
|
|
||||||
6. List statuses with the matching `git diff --name-status` command. For renames or copies, use the destination path.
|
|
||||||
7. If the default scope is empty and the request refers generally to recent changes, fall back to `HEAD~1..HEAD`. State in the final summary that the last commit was used. Do not use this fallback for an explicitly staged, ref, or file-scoped request.
|
|
||||||
8. If the resulting scope is empty, report that there is nothing to simplify and stop.
|
|
||||||
|
|
||||||
Use `git diff --unified=0 --no-ext-diff <scope> -- <path>` to identify current-file line ranges from each hunk's `+start,count` coordinates. Treat an omitted count as one line and a zero count as a deletion-only hunk with no current lines. Treat an added file as fully in scope. If ranges touch or overlap, treat them as one range.
|
|
||||||
|
|
||||||
Do not hand-edit binary files, generated output, minified files, vendored code, lockfiles, or snapshots unless the user specifically includes them and the repository expects manual edits. Report skipped files briefly.
|
|
||||||
|
|
||||||
## Simplify within the boundary
|
|
||||||
|
|
||||||
Inspect surrounding code for context, but modify only the changed current-file ranges. Work one file at a time so line movement remains manageable. Recalculate that file's diff after editing and confirm no simplification edit escaped the original changed regions.
|
|
||||||
|
|
||||||
Prioritize concrete improvements:
|
|
||||||
|
|
||||||
- Reduce unnecessary nesting, branching, duplication, and intermediate state.
|
|
||||||
- Replace unclear names when every required reference can be changed inside the allowed scope.
|
|
||||||
- Consolidate logic that is already one concern; keep distinct concerns separate.
|
|
||||||
- Remove dead or redundant code and comments that merely narrate syntax.
|
|
||||||
- Retain comments that explain intent, constraints, business rules, or non-obvious behavior.
|
|
||||||
- Prefer straightforward control flow over clever expressions; avoid nested ternaries.
|
|
||||||
- Follow established project patterns instead of introducing a new abstraction or style.
|
|
||||||
|
|
||||||
Preserve observable behavior, error handling, side effects, ordering, concurrency behavior, types, serialization formats, and public APIs. Do not add features or broaden the task. Do not remove a useful abstraction merely to reduce line count.
|
|
||||||
|
|
||||||
If an improvement requires touching unchanged code, leave the code alone and mention the opportunity in the final summary. If no worthwhile in-scope improvement exists, make no edit to that region.
|
|
||||||
|
|
||||||
## Verify
|
|
||||||
|
|
||||||
1. Review the final diff for every edited file and run `git diff --check` with the same scope where applicable.
|
|
||||||
2. Run the narrowest relevant formatter, linter, type checker, and tests first, using repository-provided commands.
|
|
||||||
3. Run the broader project verification suite when practical.
|
|
||||||
4. If verification fails, determine whether the failure was caused by the simplification. Fix in-scope regressions; otherwise preserve the failure output and report it accurately.
|
|
||||||
5. Reconfirm that behavior and public interfaces did not change and that unrelated files remain untouched.
|
|
||||||
|
|
||||||
## Report
|
|
||||||
|
|
||||||
Summarize:
|
|
||||||
|
|
||||||
- the Git scope reviewed;
|
|
||||||
- the simplifications made and why they improve clarity;
|
|
||||||
- checks and tests run, with their results;
|
|
||||||
- skipped files, pre-existing failures, or worthwhile out-of-scope improvements.
|
|
||||||
|
|
||||||
Do not claim a simplification when the code was already clear enough to leave unchanged.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
interface:
|
|
||||||
display_name: Simplify Code
|
|
||||||
short_description: Simplify changed code without changing behavior
|
|
||||||
default_prompt: Use $simplify-code to simplify my recent code changes while preserving
|
|
||||||
behavior.
|
|
||||||
icon_small: assets/icon.svg
|
|
||||||
icon_large: assets/icon.svg
|
|
||||||
policy:
|
|
||||||
products:
|
|
||||||
- chatgpt
|
|
||||||
- codex
|
|
||||||
- api
|
|
||||||
- atlas
|
|
||||||
allow_implicit_invocation: true
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M19.58 4.38996C18.05 2.86996 15.68 2.88996 14.18 4.38996L5.26999 13.33C4.25999 14.34 3.84999 14.92 3.59999 16.26L3.06999 19.56C2.88999 20.47 3.50999 21.09 4.43999 20.93L7.74999 20.39C9.06999 20.18 9.64999 19.76 10.66 18.75L19.58 9.82996C21.1 8.30996 21.12 5.93996 19.58 4.40996V4.38996Z" fill="#FEBD08"/>
|
|
||||||
<path d="M19.58 9.81997C21.1 8.29997 21.12 5.92997 19.58 4.39997C18.05 2.87997 15.68 2.89997 14.18 4.39997L13.77 4.80997L19.18 10.22L19.58 9.81997Z" fill="#FF928C"/>
|
|
||||||
<path d="M13.7694 4.81308L12.3552 6.22729L17.7646 11.6367L19.1788 10.2224L13.7694 4.81308Z" fill="#D9D9D9"/>
|
|
||||||
<path opacity="0.5" d="M12.36 6.22998L5.26998 13.34C4.25998 14.35 3.84998 14.93 3.59998 16.27L3.06998 19.57C2.97998 20.02 3.08998 20.4 3.32998 20.65L15.05 8.92998L12.36 6.22998Z" fill="white"/>
|
|
||||||
<path d="M4.60001 14.05C4.06001 14.69 3.79001 15.27 3.60001 16.26L3.51001 16.81L7.18001 20.48L7.75001 20.39C8.74001 20.23 9.31001 19.96 9.95001 19.41L4.60001 14.05Z" fill="#FFDDBC"/>
|
|
||||||
<path d="M3.50999 16.8101L3.06999 19.5601C2.88999 20.4701 3.50999 21.0901 4.43999 20.9301L7.17999 20.4801L3.50999 16.8101Z" fill="#4D4D4D"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/tdd
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/productivity/teach
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/productivity/to-questionnaire
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/to-spec
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/to-tickets
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/productivity/wait-what
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/wayfinder
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
---
|
|
||||||
name: weather
|
|
||||||
description: Record a day's high temperature, low temperature, rainfall, growing degree days, solar energy, and freezing exposure in that day's daily note, read from the home weather station. Use whenever the user asks to log, add, backfill, or look up the weather for a date — including when they say only "yesterday's weather" or "add the weather to my daily note", when they ask about growing degree days, GDD, accumulated heat, solar energy, frost or freezing hours, and including the scheduled run that fills in the day that just ended.
|
|
||||||
---
|
|
||||||
|
|
||||||
# weather — a day's weather in the daily note
|
|
||||||
|
|
||||||
Shell the `weather-day` CLI from in this skill's `scripts` directory with the bash tool, then write its values into the daily note's frontmatter. Nothing here needs a key or a login — the station is public.
|
|
||||||
|
|
||||||
## Getting the numbers
|
|
||||||
|
|
||||||
```
|
|
||||||
weather-day [YYYY-MM-DD] # no argument: yesterday
|
|
||||||
```
|
|
||||||
|
|
||||||
One JSON object on stdout:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "date": "2026-08-18", "station_id": 173994,
|
|
||||||
"temp_high": 72.3, "temp_low": 54.1, "rainfall": 0.02,
|
|
||||||
"gdd_base50": 11.4, "solar_energy_kwh_m2": 6.83,
|
|
||||||
"observations": 287, "coverage_hours": 23.9 }
|
|
||||||
```
|
|
||||||
|
|
||||||
On a day that went below freezing, two more keys appear between `solar_energy_kwh_m2` and `observations`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"hours_below_freezing": 7.2, "freezing_degree_hours": 31.8,
|
|
||||||
```
|
|
||||||
|
|
||||||
| key | units | what it is |
|
|
||||||
|---|---|---|
|
|
||||||
| `temp_high`, `temp_low` | °F | the day's extremes |
|
|
||||||
| `rainfall` | inches | total for the day |
|
|
||||||
| `gdd_base50` | °F-days | growing degree days, base 50°F |
|
|
||||||
| `solar_energy_kwh_m2` | kWh/m² | total solar energy on a horizontal surface |
|
|
||||||
| `hours_below_freezing` | hours | time spent under 32°F |
|
|
||||||
| `freezing_degree_hours` | °F-hours | how far under 32°F, integrated over that time |
|
|
||||||
|
|
||||||
**Everything from `date` through `freezing_degree_hours` is a frontmatter key** — copy the values across unchanged rather than reformatting or re-rounding them. `observations` and `coverage_hours` are diagnostics; they stay out of the note.
|
|
||||||
|
|
||||||
Three of them are written only when they have something to say. **`rainfall` is omitted from the note when it is `0`** — most days here are dry, and a `rainfall: 0` on four notes in five is noise that makes the days it did rain harder to spot. `hours_below_freezing` and `freezing_degree_hours` are omitted by `weather-day` itself on a day that never went below 32°F, so they are simply not there to copy. Either way the key's absence is the record: a dry day, a day with no frost.
|
|
||||||
|
|
||||||
Note the difference in where the omission happens. `weather-day` always prints `rainfall`, `0` included — that is a measurement, and the CLI reports it. Dropping it is this skill's decision about the note, so do not expect the JSON to be missing it.
|
|
||||||
|
|
||||||
**`gdd_base50` is one day's accumulation, not a running total.** It is the number you *sum* to answer a question — days from planting to harvest, from transplanting to first bloom, from fruit set to ripe. A single day's value on its own says very little. When the user asks "how many GDD since I planted the tomatoes", add up the `gdd_base50` of each daily note from that date forward; say how many days you summed and flag any in the range that have no value rather than treating a missing day as zero.
|
|
||||||
|
|
||||||
Any failure exits non-zero with a message on stderr. Report it and stop; do not write a partial or guessed value into the note. That includes the station going quiet mid-day: the totals are integrals over the whole day, so rather than draw a straight line across an hour or more of missing readings, `weather-day` refuses the date outright. There is no partial answer to salvage and nothing to write — say which stretch is missing and leave the note alone.
|
|
||||||
|
|
||||||
## Check the coverage before writing
|
|
||||||
|
|
||||||
`coverage_hours` is the span from the first observation to the last. A full day is ~24 (23 or 25 across a DST change).
|
|
||||||
|
|
||||||
`observations` is a count, not a duration, and a low-looking one is not by itself a problem: the API answers a day-long range on a 5-minute grid, so a complete day is around 288, not the ~1440 a per-minute feed would give. Judge the day by `coverage_hours`.
|
|
||||||
|
|
||||||
**If `coverage_hours` is under 20, do not write to the note.** Tell the user the day is only partly covered and give them the numbers to judge. A high or low computed from part of a day looks entirely normal in the frontmatter and stays wrong forever. It usually means the station was offline for a stretch, or the date asked for is today and the day hasn't finished.
|
|
||||||
|
|
||||||
A hole *inside* the day is a hard failure rather than a low `coverage_hours`, so a number that gets here has no outage longer than an hour in it. What `coverage_hours` still catches is a day the station started or stopped reporting partway through — and for the totals that is worse than for the extremes: `gdd_base50` and `solar_energy_kwh_m2` are missing the hours that never arrived, so they come out low, plausibly, and stay that way in every season sum afterwards.
|
|
||||||
|
|
||||||
## Writing it into the note
|
|
||||||
|
|
||||||
The note is `<vault>/daily/<date>.md` — the date exactly as `weather-day` echoed it back, and the absolute path, always.
|
|
||||||
|
|
||||||
**If the note exists:** add or update only the keys you are writing. Every other key keeps its value and its position, and the body is untouched — the vault syncs live from other devices, so a rewritten note is a sync conflict waiting to happen. Add new keys at the end of the frontmatter block.
|
|
||||||
|
|
||||||
**If the note does not exist:** create it with just those keys and the date as an H1.
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
temp_high: 72.3
|
|
||||||
temp_low: 54.1
|
|
||||||
rainfall: 0.02
|
|
||||||
gdd_base50: 11.4
|
|
||||||
solar_energy_kwh_m2: 6.83
|
|
||||||
---
|
|
||||||
# 2026-08-18
|
|
||||||
```
|
|
||||||
|
|
||||||
A dry day with no frost is the common shape, and has neither `rainfall` nor the freezing pair:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
temp_high: 78.1
|
|
||||||
temp_low: 55.6
|
|
||||||
gdd_base50: 14.4
|
|
||||||
solar_energy_kwh_m2: 7.02
|
|
||||||
---
|
|
||||||
# 2026-08-19
|
|
||||||
```
|
|
||||||
|
|
||||||
**Any of those keys you are not writing, delete from the note if it is already there** — `rainfall` when the day was dry, `hours_below_freezing` and `freezing_degree_hours` when it never froze. Absence is the record, so a value left behind from an earlier run reports rain or a frost that did not happen, in exactly the format a real one would take. This is the one case where this skill removes rather than only adds, and it reaches only those three keys — never anything else in the frontmatter.
|
|
||||||
|
|
||||||
Re-running a date is otherwise safe: the same keys get the same values again.
|
|
||||||
|
|
||||||
## Reading it back
|
|
||||||
|
|
||||||
To answer "what was the weather on X" for a day already recorded, read the note's frontmatter — that is faster than a station call and it is the recorded value. Call `weather-day` when the note has no weather keys yet, or when the user wants the station's answer rather than the note's.
|
|
||||||
|
|
||||||
**A missing `rainfall` on a note that carries the other weather keys means zero, not unknown.** The same goes for the freezing pair. So when totalling rain over a month or frost hours over a winter, a note with `temp_high` but no `rainfall` is a recorded dry day: count it as `0` and as a day you have data for. Reading it as a gap gets the total right by luck and everything around it wrong — it reports most of a dry July as unmeasured, and sends you re-running `weather-day` over days that were never missing. A note with *none* of the weather keys is the genuinely unrecorded case, and that one is worth saying out loud or filling in before answering.
|
|
||||||
|
|
||||||
`gdd_base50` is different — it is written on every recorded day, including a `0`. A daily note missing it has not been filled in, and should be reported as a hole rather than summed as zero.
|
|
||||||
|
|
||||||
## A different station
|
|
||||||
|
|
||||||
`STATION_ID` is a constant at the top of the `weather-day` script, in this skill's directory. Changing it needs an image rebuild; say so rather than trying to override it at the command line.
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# High/low temperature, total rainfall, growing degree days, solar energy, and
|
|
||||||
# freezing exposure for one local day, read from a public Tempest (WeatherFlow)
|
|
||||||
# station. Prints one JSON object on stdout.
|
|
||||||
# Usage: weather-day [YYYY-MM-DD] (default: yesterday)
|
|
||||||
#
|
|
||||||
# The station is public, so the key below is WeatherFlow's own web-app key rather
|
|
||||||
# than a secret — it is what tempestwx.com sends, works for any public station, and
|
|
||||||
# is documented as the default in the weather-stats repo's .env.example. Nothing
|
|
||||||
# here belongs in .env: swap the two constants and rebuild to point at a different
|
|
||||||
# station.
|
|
||||||
#
|
|
||||||
# Reaches swd.weatherflow.com, which must be on the `acl allowed` line in
|
|
||||||
# config/squid.conf or every call here fails with a 403 from the proxy. That
|
|
||||||
# coupling cannot be asserted at build time — squid.conf is mounted into the squid
|
|
||||||
# sidecar and never exists inside this image.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
STATION_ID=173994
|
|
||||||
API_KEY=6bff2f89-84ab-463c-886e-fc0f443da4cf
|
|
||||||
API_BASE=https://swd.weatherflow.com/swd/rest
|
|
||||||
BUILD=169
|
|
||||||
|
|
||||||
# Everything below the high/low is an integral, and an integral fails quietly: a
|
|
||||||
# wrong high looks wrong, a wrong degree-day total looks like a number. So this
|
|
||||||
# one constant guards both ways a day's samples can misrepresent the day.
|
|
||||||
#
|
|
||||||
# As the *fetch pad*, it is how far either side of midnight the observations query
|
|
||||||
# reaches, so the samples bracketing each boundary exist and the day can be
|
|
||||||
# interpolated to its true edges instead of truncated to the first and last
|
|
||||||
# reading inside it.
|
|
||||||
#
|
|
||||||
# As the *gap limit*, it is the widest hole between consecutive samples the
|
|
||||||
# integrals will cross. This endpoint answers a day-long range on a 5-minute grid
|
|
||||||
# — 2026-08-18 came back as 287 observations spanning 23.8 h, which is 300.0 s
|
|
||||||
# apart — so an hour-long hole is a dozen consecutive reports missing, an outage
|
|
||||||
# rather than a few dropped ones. A straight line drawn across one, through a
|
|
||||||
# summer afternoon especially, moves the day's totals a long way while looking
|
|
||||||
# entirely ordinary in the note. Better to refuse the day.
|
|
||||||
#
|
|
||||||
# Measured rather than assumed, and worth re-measuring before leaning on it: the
|
|
||||||
# station itself reports far more often than this, so the 5 minutes is the API's
|
|
||||||
# resolution for a range this wide, not the hardware's.
|
|
||||||
#
|
|
||||||
# One number for both on purpose: a neighbouring observation further out than the
|
|
||||||
# pad is one the gap limit would refuse to interpolate across anyway, so widening
|
|
||||||
# either alone buys nothing.
|
|
||||||
MAX_GAP_SECONDS=3600
|
|
||||||
|
|
||||||
# Both headers are load-bearing, not politeness, and the key is why: it was lifted
|
|
||||||
# from the tempestwx.com JavaScript bundle, and WeatherFlow gates it on the request
|
|
||||||
# resembling that web app. Sending neither returns 401, observed — these are the
|
|
||||||
# two values the working weather-stats client sends on every call
|
|
||||||
# (src/lib/server/collectors/tempest-api.ts), and `build` is part of the same act.
|
|
||||||
# Drop any of the three and expect the 401 back.
|
|
||||||
ORIGIN=https://tempestwx.com
|
|
||||||
USER_AGENT='Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0'
|
|
||||||
|
|
||||||
if [ $# -gt 1 ]; then
|
|
||||||
echo 'usage: weather-day [YYYY-MM-DD] (default: yesterday)' >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
day=${1:-$(date -d yesterday +%F)}
|
|
||||||
|
|
||||||
# Rejected here rather than passed through: `date -d` accepts a great deal that is
|
|
||||||
# not a calendar date ("now", "next friday", "1 day ago"), and any of them would
|
|
||||||
# produce a plausible-looking result filed under a nonsense `date` key.
|
|
||||||
if ! [[ $day =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
|
|
||||||
echo "weather-day: '$day' is not a date in YYYY-MM-DD form" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
if ! date -d "$day" >/dev/null 2>&1; then
|
|
||||||
echo "weather-day: '$day' is not a real calendar date" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# The container's TZ decides where the day starts, which is what makes this the
|
|
||||||
# user's day rather than UTC's. `+ 1 day` is calendar arithmetic rather than
|
|
||||||
# +86400, so a DST transition gives a 23- or 25-hour day instead of one shifted by
|
|
||||||
# an hour — verified at both 2026 transitions.
|
|
||||||
#
|
|
||||||
# The bare date, with no `00:00:00`, is load-bearing. GNU date parses a `+N` that
|
|
||||||
# follows a *time* as a numeric timezone, so `"$day 00:00:00 + 1 day"` yields
|
|
||||||
# 16:00 the same day and silently loses a third of it.
|
|
||||||
start=$(date -d "$day" +%s)
|
|
||||||
end=$(date -d "$day + 1 day" +%s)
|
|
||||||
|
|
||||||
# Shared by both calls: check the HTTP status before handing the body to jq, so a
|
|
||||||
# proxy denial or an API outage reports itself instead of surfacing as "no
|
|
||||||
# observations" for a day the station was up.
|
|
||||||
tempest_get() {
|
|
||||||
local endpoint=$1 resp code json
|
|
||||||
resp=$(curl -sS -w $'\n%{http_code}' -G "${API_BASE}/${endpoint}" \
|
|
||||||
-A "${USER_AGENT}" \
|
|
||||||
-H "Origin: ${ORIGIN}" \
|
|
||||||
--data-urlencode "api_key=${API_KEY}" \
|
|
||||||
--data-urlencode "build=${BUILD}" \
|
|
||||||
"${@:2}")
|
|
||||||
code=${resp##*$'\n'}
|
|
||||||
json=${resp%$'\n'*}
|
|
||||||
|
|
||||||
if [ "$code" != "200" ]; then
|
|
||||||
echo "weather-day: ${endpoint} failed (HTTP $code)" >&2
|
|
||||||
echo " $json" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
printf '%s' "$json"
|
|
||||||
}
|
|
||||||
|
|
||||||
# A station has one ST (Tempest) device and usually an HB (hub) alongside it; only
|
|
||||||
# the ST reports weather.
|
|
||||||
locations=$(tempest_get "locations/${STATION_ID}" --data-urlencode 'include_arbitrary_locations=true')
|
|
||||||
device_id=$(printf '%s' "$locations" | jq -r '
|
|
||||||
if .status.status_code != 0 then
|
|
||||||
"ERR:\(.status.status_message)"
|
|
||||||
else
|
|
||||||
(.locations[0].devices[]? | select(.device_type == "ST") | .device_id) // "ERR:no ST device"
|
|
||||||
end' | head -1)
|
|
||||||
|
|
||||||
case "$device_id" in
|
|
||||||
ERR:*)
|
|
||||||
echo "weather-day: station ${STATION_ID}: ${device_id#ERR:}" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
''|*[!0-9]*)
|
|
||||||
echo "weather-day: station ${STATION_ID}: could not resolve a Tempest device id" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Deliberately wider than the day on both sides — MAX_GAP_SECONDS of padding — so
|
|
||||||
# the observations either side of each midnight come back and the boundaries can
|
|
||||||
# be interpolated. Without them the first integral segment would start at whenever
|
|
||||||
# the station happened to report after 00:00 and the day would be quietly short.
|
|
||||||
#
|
|
||||||
# The padding is *only* for interpolation. The jq filter below re-asserts the
|
|
||||||
# half-open window itself, so temp_high, temp_low, rainfall, observations and
|
|
||||||
# coverage_hours still describe the requested day and nothing else — which also
|
|
||||||
# makes time_end's inclusivity on the API side stop mattering here.
|
|
||||||
observations=$(tempest_get "observations/device/${device_id}" \
|
|
||||||
--data-urlencode "time_start=$((start - MAX_GAP_SECONDS))" \
|
|
||||||
--data-urlencode "time_end=$((end + MAX_GAP_SECONDS))")
|
|
||||||
|
|
||||||
# Positional obs_st layout, per the OBS_IDX table in weather-stats
|
|
||||||
# (src/lib/server/collectors/tempest-api.ts): [0] epoch, [7] air temperature in C,
|
|
||||||
# [11] solar radiation in W/m², [12] precipitation in mm accumulated over the
|
|
||||||
# report interval.
|
|
||||||
#
|
|
||||||
# Rainfall is the sum of [12] rather than the station's own
|
|
||||||
# precip_accum_local_day, which resets at the station's midnight — summing keeps
|
|
||||||
# the total tied to the window actually requested.
|
|
||||||
#
|
|
||||||
# The four derived numbers are integrals over the day rather than statistics over
|
|
||||||
# the samples, and the difference is not academic. The spacing between reports is
|
|
||||||
# nothing this code should assume — it is whatever the API returns for the range
|
|
||||||
# asked for, and reports go missing — so a per-sample average silently weights a
|
|
||||||
# stretch the station was chatty the same as a stretch it was quiet; and clipping
|
|
||||||
# each *sample* at a threshold charges a whole interval to whichever side its
|
|
||||||
# endpoints landed on. Both errors are invisible in the output and both compound
|
|
||||||
# when a season of these gets summed. So: piecewise-linear between real
|
|
||||||
# timestamps, thresholds crossed at the exact instant the line crosses them.
|
|
||||||
printf '%s' "$observations" | jq \
|
|
||||||
--arg date "$day" \
|
|
||||||
--argjson station "$STATION_ID" \
|
|
||||||
--argjson start "$start" \
|
|
||||||
--argjson end "$end" \
|
|
||||||
--argjson maxgap "$MAX_GAP_SECONDS" '
|
|
||||||
def mag: if . < 0 then - . else . end;
|
|
||||||
def fahrenheit: . * 9 / 5 + 32;
|
|
||||||
def r1: . * 10 | round / 10;
|
|
||||||
def r2: . * 100 | round / 100;
|
|
||||||
|
|
||||||
# Every def below takes a series: [epoch, value] pairs, ascending, one per
|
|
||||||
# observation that actually carried the field.
|
|
||||||
|
|
||||||
# The value of the segment [$a,$b] at time $t.
|
|
||||||
def at($a; $b; $t): $a[1] + ($b[1] - $a[1]) * ($t - $a[0]) / ($b[0] - $a[0]);
|
|
||||||
|
|
||||||
# $p clipped to the day, with the two boundary values interpolated from the
|
|
||||||
# observations just outside it — which is what the padded fetch above is for.
|
|
||||||
# A neighbour further out than $maxgap is not used: that is a hole, not a
|
|
||||||
# boundary, so the series just starts (or ends) at the nearest real sample and
|
|
||||||
# coverage_hours is left to report the short day.
|
|
||||||
def day_series($p):
|
|
||||||
[$p[] | select(.[0] >= $start and .[0] < $end)] as $in
|
|
||||||
| if ($in | length) == 0 then []
|
|
||||||
else
|
|
||||||
([$p[] | select(.[0] < $start)] | last) as $pre
|
|
||||||
| ([$p[] | select(.[0] >= $end)] | first) as $post
|
|
||||||
| (if $pre != null and $in[0][0] > $start and ($in[0][0] - $pre[0]) <= $maxgap
|
|
||||||
then [[$start, at($pre; $in[0]; $start)]] else [] end)
|
|
||||||
+ $in
|
|
||||||
+ (if $post != null and $in[-1][0] < $end and ($post[0] - $in[-1][0]) <= $maxgap
|
|
||||||
then [[$end, at($in[-1]; $post; $end)]] else [] end)
|
|
||||||
end;
|
|
||||||
|
|
||||||
# ∫max(v,0)·dt in value-hours. A segment whose endpoints straddle zero
|
|
||||||
# contributes only the triangle on the positive side of the crossing, and
|
|
||||||
# solving for that crossing is the entire reason this is not a trapezoid sum:
|
|
||||||
# a trapezoid of the clipped endpoints charges the whole interval to the
|
|
||||||
# positive side, which is how an hour at 31°F becomes an hour of thaw.
|
|
||||||
def positive_area($s):
|
|
||||||
reduce range(1; $s | length) as $i (0;
|
|
||||||
$s[$i - 1][1] as $a
|
|
||||||
| $s[$i][1] as $b
|
|
||||||
| (($s[$i][0] - $s[$i - 1][0]) / 3600) as $h
|
|
||||||
| . + (if $a >= 0 and $b >= 0 then ($a + $b) / 2 * $h
|
|
||||||
elif $a <= 0 and $b <= 0 then 0
|
|
||||||
else (([$a, 0] | max) as $pa
|
|
||||||
| ([$b, 0] | max) as $pb
|
|
||||||
| ($pa * $pa + $pb * $pb) / (2 * (($a - $b) | mag)) * $h)
|
|
||||||
end));
|
|
||||||
|
|
||||||
# Hours for which the interpolated value is strictly positive, same crossing.
|
|
||||||
def positive_hours($s):
|
|
||||||
reduce range(1; $s | length) as $i (0;
|
|
||||||
$s[$i - 1][1] as $a
|
|
||||||
| $s[$i][1] as $b
|
|
||||||
| (($s[$i][0] - $s[$i - 1][0]) / 3600) as $h
|
|
||||||
| . + (if $a > 0 and $b > 0 then $h
|
|
||||||
elif $a <= 0 and $b <= 0 then 0
|
|
||||||
else $h * (([$a, 0] | max) + ([$b, 0] | max)) / (($a - $b) | mag)
|
|
||||||
end));
|
|
||||||
|
|
||||||
# The widest interval between consecutive samples, as [from, to, seconds].
|
|
||||||
def widest($s):
|
|
||||||
[range(1; $s | length) | [$s[. - 1][0], $s[.][0], ($s[.][0] - $s[. - 1][0])]]
|
|
||||||
| max_by(.[2]);
|
|
||||||
|
|
||||||
# Refuse rather than draw a straight line across an outage. See MAX_GAP_SECONDS.
|
|
||||||
def no_gaps($s; $what):
|
|
||||||
widest($s) as $g
|
|
||||||
| if $g != null and $g[2] > $maxgap then
|
|
||||||
"weather-day: \($what) for \($date) stops for \($g[2] / 3600 | r1) h (\($g[0] | localtime | strftime("%H:%M")) to \($g[1] | localtime | strftime("%H:%M"))) — a day total interpolated across a hole that size would look entirely normal and be wrong\n" | halt_error(1)
|
|
||||||
else . end;
|
|
||||||
|
|
||||||
if .status.status_code != 0 then
|
|
||||||
"weather-day: device observations: \(.status.status_message)\n" | halt_error(1)
|
|
||||||
else . end
|
|
||||||
# Sorted and one-per-timestamp because the integrals below walk consecutive
|
|
||||||
# pairs; the API answers in order, but nothing here should depend on that.
|
|
||||||
| ([.obs[]? | select(type == "array" and (.[0] | type) == "number")] | unique_by(.[0])) as $all
|
|
||||||
| [$all[] | select(.[0] >= $start and .[0] < $end)] as $o
|
|
||||||
| if ($o | length) == 0 then
|
|
||||||
"weather-day: no observations for \($date) — station offline, or the date is outside its history\n" | halt_error(1)
|
|
||||||
else . end
|
|
||||||
| [$o[] | .[7] | numbers] as $temps
|
|
||||||
| [$o[] | .[12] | numbers] as $precip
|
|
||||||
| (if ($temps | length) == 0 then
|
|
||||||
"weather-day: observations for \($date) carry no temperature readings\n" | halt_error(1)
|
|
||||||
else . end)
|
|
||||||
| day_series([$all[] | select((.[7] | type) == "number") | [.[0], (.[7] | fahrenheit)]]) as $tempF
|
|
||||||
| day_series([$all[] | select((.[11] | type) == "number") | [.[0], .[11]]]) as $solar
|
|
||||||
# A dead pyranometer integrates to a perfectly plausible 0.0 that would be
|
|
||||||
# written into a note and summed forever, so it is an error, not a zero. Zero
|
|
||||||
# readings are different, and stay zero.
|
|
||||||
| (if ($solar | length) == 0 then
|
|
||||||
"weather-day: observations for \($date) carry no solar radiation readings\n" | halt_error(1)
|
|
||||||
else . end)
|
|
||||||
| no_gaps($tempF; "temperature")
|
|
||||||
| no_gaps($solar; "solar radiation")
|
|
||||||
# Omitted, not zeroed, on a day that never froze: absent says "no exposure",
|
|
||||||
# whereas 0.0 is also what a broken calculation says. Tested on the series
|
|
||||||
# minimum rather than the rounded temp_low, so a midnight boundary that
|
|
||||||
# interpolates just under 32°F counts — it is inside the day.
|
|
||||||
| (if ([$tempF[] | .[1]] | min) < 32 then
|
|
||||||
([$tempF[] | [.[0], (32 - .[1])]] as $freezing
|
|
||||||
| { hours_below_freezing: (positive_hours($freezing) | r1),
|
|
||||||
freezing_degree_hours: (positive_area($freezing) | r1) })
|
|
||||||
else {} end) as $cold
|
|
||||||
| {
|
|
||||||
date: $date,
|
|
||||||
station_id: $station,
|
|
||||||
temp_high: (($temps | max) | fahrenheit | r1),
|
|
||||||
temp_low: (($temps | min) | fahrenheit | r1),
|
|
||||||
rainfall: (($precip | add // 0) / 25.4 | r2),
|
|
||||||
# Degree-days, so the °F-hours above the base divided by 24 — one day at a
|
|
||||||
# steady 60°F is 10, not 240. A daily figure, meant to be summed.
|
|
||||||
gdd_base50: (positive_area([$tempF[] | [.[0], (.[1] - 50)]]) / 24 | r1),
|
|
||||||
# W/m² integrated over hours is Wh/m². positive_area rather than a plain
|
|
||||||
# trapezoid because irradiance cannot be negative, and a pyranometer reading
|
|
||||||
# a little below zero on a clear night should contribute nothing, not debt.
|
|
||||||
solar_energy_kwh_m2: (positive_area($solar) / 1000 | r2)
|
|
||||||
}
|
|
||||||
+ $cold
|
|
||||||
+ {
|
|
||||||
observations: ($o | length),
|
|
||||||
coverage_hours: ((($o | last | .[0]) - ($o | first | .[0])) / 3600 | r1)
|
|
||||||
}'
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/engineering/wizard
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../mattpocock-skills/skills/productivity/writing-for-agents
|
|
||||||
+4
-11
@@ -64,11 +64,7 @@ fi
|
|||||||
alias tmux="tmux -2"
|
alias tmux="tmux -2"
|
||||||
alias grep="grep --color=auto"
|
alias grep="grep --color=auto"
|
||||||
|
|
||||||
if command -v brew >/dev/null 2>&1; then
|
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||||
eval "$(brew shellenv)"
|
|
||||||
elif [[ -x /home/linuxbrew/.linuxbrew/bin/brew ]]; then
|
|
||||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fix gpg signing
|
# Fix gpg signing
|
||||||
# https://github.com/keybase/keybase-issues/issues/2798
|
# https://github.com/keybase/keybase-issues/issues/2798
|
||||||
@@ -82,20 +78,17 @@ export TERM="xterm-256color"
|
|||||||
export EDITOR=$(which nvim)
|
export EDITOR=$(which nvim)
|
||||||
|
|
||||||
# Adding applications to path
|
# Adding applications to path
|
||||||
if [[ -d ${HOME}/.local/bin ]] && [[ ":${PATH}:" != *":${HOME}/.local/bin:"* ]]; then
|
if [[ -d ${HOME}/.dotfiles/bin/linux ]]; then
|
||||||
export PATH=${HOME}/.local/bin:${PATH}
|
export PATH=$PATH:${HOME}/.dotfiles/bin/linux
|
||||||
fi
|
fi
|
||||||
if [[ -d ${HOME}/bin ]]; then
|
if [[ -d ${HOME}/bin ]]; then
|
||||||
export PATH=$PATH:${HOME}/bin
|
export PATH=$PATH:${HOME}/bin
|
||||||
fi
|
fi
|
||||||
|
|
||||||
eval "$(mise activate bash)"
|
|
||||||
|
|
||||||
# Enable atuin
|
# Enable atuin
|
||||||
# https://docs.atuin.sh
|
# https://docs.atuin.sh
|
||||||
if command -v atuin >/dev/null 2>&1; then
|
eval "$(atuin init bash)"
|
||||||
eval "$(atuin init bash)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# fzf
|
# fzf
|
||||||
#eval "$(fzf --bash)"
|
#eval "$(fzf --bash)"
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Source: https://github.com/mattpocock/skills
|
|
||||||
|
|
||||||
INPUT=$(cat)
|
|
||||||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
|
|
||||||
|
|
||||||
DANGEROUS_PATTERNS=(
|
|
||||||
"git push"
|
|
||||||
"git reset --hard"
|
|
||||||
"git clean -fd"
|
|
||||||
"git clean -f"
|
|
||||||
"git branch -D"
|
|
||||||
"git checkout \."
|
|
||||||
"git restore \."
|
|
||||||
"push --force"
|
|
||||||
"reset --hard"
|
|
||||||
)
|
|
||||||
|
|
||||||
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
|
|
||||||
if echo "$COMMAND" | grep -qE "$pattern"; then
|
|
||||||
echo "BLOCKED: '$COMMAND' matches dangerous pattern '$pattern'. The user has prevented you from doing this." >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Blocks Bash commands that delete or destroy files.
|
|
||||||
# Companion to CLAUDE.md's "never delete files" rule.
|
|
||||||
|
|
||||||
block() {
|
|
||||||
printf 'BLOCKED: %s\n' "$1" >&2
|
|
||||||
exit 2
|
|
||||||
}
|
|
||||||
|
|
||||||
command -v jq >/dev/null 2>&1 || block "jq is required to validate Bash commands."
|
|
||||||
command -v realpath >/dev/null 2>&1 || block "realpath is required to validate deletion paths."
|
|
||||||
command -v stat >/dev/null 2>&1 || block "stat is required to validate the temporary directory."
|
|
||||||
|
|
||||||
INPUT=$(cat) || block "could not read hook input."
|
|
||||||
COMMAND=$(printf '%s' "$INPUT" | jq -er '.tool_input.command | select(type == "string")') ||
|
|
||||||
block "hook input does not contain a valid tool_input.command string."
|
|
||||||
|
|
||||||
# Do not trust TMPDIR: when it is unset, `$TMPDIR/path` expands to `/path`.
|
|
||||||
# This fixed per-user directory is the only place where deletion is permitted.
|
|
||||||
TMP_ROOT="/tmp/claude-$(id -u)"
|
|
||||||
|
|
||||||
# True only for one command in one of these exact forms:
|
|
||||||
# rm -- /tmp/claude-UID/path [...]
|
|
||||||
# rm -f|-r|-rf|-fr -- /tmp/claude-UID/path [...]
|
|
||||||
#
|
|
||||||
# Paths are deliberately restricted to an unquoted, expansion-free character
|
|
||||||
# set. The filesystem checks reject an escaping canonical path and any symlink
|
|
||||||
# that already exists in the path. This is defense against accidental deletion,
|
|
||||||
# not a race-free security boundary against a process changing paths concurrently.
|
|
||||||
confined_to_tmp() {
|
|
||||||
local cmd="$1" option_part path_part path canonical component partial mode uid
|
|
||||||
local -a paths components
|
|
||||||
|
|
||||||
uid=$(id -u) || return 1
|
|
||||||
[[ -d "$TMP_ROOT" && ! -L "$TMP_ROOT" ]] || return 1
|
|
||||||
[[ "$(realpath -e -- "$TMP_ROOT" 2>/dev/null)" == "$TMP_ROOT" ]] || return 1
|
|
||||||
[[ "$(stat -c '%u' -- "$TMP_ROOT" 2>/dev/null)" == "$uid" ]] || return 1
|
|
||||||
mode=$(stat -c '%a' -- "$TMP_ROOT" 2>/dev/null) || return 1
|
|
||||||
(( (8#$mode & 0022) == 0 )) || return 1
|
|
||||||
|
|
||||||
if [[ "$cmd" =~ ^rm\ (--|-f\ --|-r\ --|-rf\ --|-fr\ --)\ (/tmp/claude-[0-9]+/[A-Za-z0-9._/+,=:@%-]+(\ /tmp/claude-[0-9]+/[A-Za-z0-9._/+,=:@%-]+)*)$ ]]; then
|
|
||||||
option_part="${BASH_REMATCH[1]}"
|
|
||||||
path_part="${BASH_REMATCH[2]}"
|
|
||||||
else
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# The regex excludes shell syntax and whitespace within paths, so this split
|
|
||||||
# does not attempt to interpret arbitrary Bash source.
|
|
||||||
read -r -a paths <<< "$path_part"
|
|
||||||
((${#paths[@]} > 0)) || return 1
|
|
||||||
|
|
||||||
for path in "${paths[@]}"; do
|
|
||||||
[[ "$path" == "$TMP_ROOT"/?* ]] || return 1
|
|
||||||
[[ "$path" != */./* && "$path" != */../* && "$path" != */. && "$path" != */.. ]] || return 1
|
|
||||||
|
|
||||||
canonical=$(realpath -m -- "$path" 2>/dev/null) || return 1
|
|
||||||
[[ "$canonical" == "$TMP_ROOT"/?* ]] || return 1
|
|
||||||
|
|
||||||
# realpath catches symlink escapes. Reject in-tree symlinks too, since rm
|
|
||||||
# must not traverse a link whose target happens to currently be in the tree.
|
|
||||||
partial=""
|
|
||||||
IFS='/' read -r -a components <<< "$path"
|
|
||||||
for component in "${components[@]}"; do
|
|
||||||
[[ -n "$component" ]] || continue
|
|
||||||
partial="$partial/$component"
|
|
||||||
[[ ! -L "$partial" ]] || return 1
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
# Keep the variable used so shellcheck documents that only allowlisted option
|
|
||||||
# spellings can reach this point.
|
|
||||||
[[ -n "$option_part" ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
confined_to_tmp "$COMMAND" && exit 0
|
|
||||||
|
|
||||||
# These patterns are a backstop for ordinary Bash spellings, not a parser.
|
|
||||||
# Include quotes and backslashes between letters to catch forms such as r""m
|
|
||||||
# and r\m that Bash resolves to rm.
|
|
||||||
DANGEROUS_PATTERNS=(
|
|
||||||
'\br["'"'"'\\]*m\b'
|
|
||||||
'\bu["'"'"'\\]*n["'"'"'\\]*l["'"'"'\\]*i["'"'"'\\]*n["'"'"'\\]*k\b'
|
|
||||||
'\br["'"'"'\\]*m["'"'"'\\]*d["'"'"'\\]*i["'"'"'\\]*r\b'
|
|
||||||
'\bshred\b'
|
|
||||||
'\btruncate\b'
|
|
||||||
'\bfind\b.*-delete\b'
|
|
||||||
'\btrash\b'
|
|
||||||
)
|
|
||||||
|
|
||||||
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
|
|
||||||
if printf '%s\n' "$COMMAND" | grep -qE "$pattern"; then
|
|
||||||
block "'$COMMAND' is a deletion command. Only literal 'rm [-f|-r|-rf|-fr] -- $TMP_ROOT/path' commands without variables, quotes, globs, shell operators, or symlinks are allowed."
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
{
|
|
||||||
"attribution": {
|
|
||||||
"commit": "",
|
|
||||||
"pr": ""
|
|
||||||
},
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(npx tsc:*)",
|
|
||||||
"Bash(pnpm run build:*)",
|
|
||||||
"Bash(pnpm tsc:*)",
|
|
||||||
"Bash(pnpm build:*)",
|
|
||||||
"Bash(pnpm test:*)",
|
|
||||||
"Bash(pnpm install:*)",
|
|
||||||
"Bash(git diff:*)"
|
|
||||||
],
|
|
||||||
"deny": [
|
|
||||||
"Bash(git push *)",
|
|
||||||
"Bash(ssh *)",
|
|
||||||
"Bash(ssh)",
|
|
||||||
"Bash(scp *)",
|
|
||||||
"Bash(shred:*)",
|
|
||||||
"Bash(truncate:*)",
|
|
||||||
"Bash(trash:*)",
|
|
||||||
"CronCreate",
|
|
||||||
"CronDelete",
|
|
||||||
"CronList",
|
|
||||||
"PushNotification",
|
|
||||||
"NotebookEdit",
|
|
||||||
"DesignSync"
|
|
||||||
],
|
|
||||||
"defaultMode": "auto"
|
|
||||||
},
|
|
||||||
"model": "claude-opus-4-8",
|
|
||||||
"disableClaudeAiConnectors": true,
|
|
||||||
"hooks": {
|
|
||||||
"PostToolUse": [
|
|
||||||
{
|
|
||||||
"matcher": "Bash",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "atuin hook claude-code"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"PostToolUseFailure": [
|
|
||||||
{
|
|
||||||
"matcher": "Bash",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "atuin hook claude-code"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"PreToolUse": [
|
|
||||||
{
|
|
||||||
"matcher": "Bash",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "~/.claude/hooks/block-dangerous-git.sh"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "~/.claude/hooks/block-file-deletion.sh"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matcher": "Bash",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "atuin hook claude-code"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"disableWorkflows": true,
|
|
||||||
"disableArtifact": true,
|
|
||||||
"statusLine": {
|
|
||||||
"type": "command",
|
|
||||||
"command": "bash /home/tgrosinger/.claude/statusline-command.sh"
|
|
||||||
},
|
|
||||||
"enabledPlugins": {
|
|
||||||
"claude-md-management@claude-plugins-official": true,
|
|
||||||
"code-simplifier@claude-plugins-official": true,
|
|
||||||
"frontend-design@claude-plugins-official": true,
|
|
||||||
"gopls-lsp@claude-plugins-official": true,
|
|
||||||
"pr-review-toolkit@claude-plugins-official": true,
|
|
||||||
"security-guidance@claude-plugins-official": true,
|
|
||||||
"skill-creator@claude-plugins-official": true,
|
|
||||||
"typescript-lsp@claude-plugins-official": true
|
|
||||||
},
|
|
||||||
"sandbox": {
|
|
||||||
"enabled": true,
|
|
||||||
"autoAllowBashIfSandboxed": true,
|
|
||||||
"allowUnsandboxedCommands": false,
|
|
||||||
"network": {
|
|
||||||
"allowedDomains": [
|
|
||||||
"github.com",
|
|
||||||
"*.github.com",
|
|
||||||
"*.githubusercontent.com",
|
|
||||||
"registry.npmjs.org",
|
|
||||||
"proxy.golang.org",
|
|
||||||
"sum.golang.org",
|
|
||||||
"cache.nixos.org",
|
|
||||||
"nodejs.org",
|
|
||||||
"go.dev",
|
|
||||||
"dl.google.com",
|
|
||||||
"mise.en.dev"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"filesystem": {
|
|
||||||
"allowWrite": [
|
|
||||||
"~/.local/share/pnpm",
|
|
||||||
"~/.cache/pnpm",
|
|
||||||
"~/Documents/Atrium",
|
|
||||||
"/tachi/docker/atribot/vault"
|
|
||||||
],
|
|
||||||
"denyRead": [
|
|
||||||
"~/.ssh",
|
|
||||||
"~/.config/Signal",
|
|
||||||
"~/Documents",
|
|
||||||
"/tachi/backups",
|
|
||||||
"/tachi/documents",
|
|
||||||
"/tachi/docker"
|
|
||||||
],
|
|
||||||
"allowRead": [
|
|
||||||
"~/Documents/Atrium",
|
|
||||||
"/tachi/docker/atribot/vault"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"excludedCommands": [
|
|
||||||
"git push *",
|
|
||||||
"brew *"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"spinnerVerbs": {
|
|
||||||
"mode": "replace",
|
|
||||||
"verbs": [
|
|
||||||
"Thinking"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"effortLevel": "xhigh",
|
|
||||||
"tui": "fullscreen",
|
|
||||||
"voice": {
|
|
||||||
"enabled": false,
|
|
||||||
"mode": "hold"
|
|
||||||
},
|
|
||||||
"prefersReducedMotion": true,
|
|
||||||
"autoMemoryEnabled": true,
|
|
||||||
"autoDreamEnabled": true,
|
|
||||||
"theme": "light",
|
|
||||||
"editorMode": "vim",
|
|
||||||
"remoteControlAtStartup": false,
|
|
||||||
"agentPushNotifEnabled": true,
|
|
||||||
"skipAutoPermissionPrompt": true
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../.agents/skills
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
input=$(cat)
|
|
||||||
|
|
||||||
# Context-window fill from the latest message's usage in the transcript.
|
|
||||||
transcript=$(echo "$input" | jq -r '.transcript_path // ""')
|
|
||||||
model_id=$(echo "$input" | jq -r '.model.id // ""')
|
|
||||||
case "$model_id" in
|
|
||||||
*1m*|*"[1m]"*) limit=1000000 ;;
|
|
||||||
*) limit=200000 ;;
|
|
||||||
esac
|
|
||||||
used=""
|
|
||||||
if [ -n "$transcript" ] && [ -f "$transcript" ]; then
|
|
||||||
used=$(jq -rs '[.[] | select(.message.usage != null) | .message.usage
|
|
||||||
| (.input_tokens // 0) + (.cache_read_input_tokens // 0)
|
|
||||||
+ (.cache_creation_input_tokens // 0) + (.output_tokens // 0)]
|
|
||||||
| last // ""' "$transcript" 2>/dev/null)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# No usage recorded yet (e.g. fresh session before the first turn).
|
|
||||||
if [ -z "$used" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
pct=$(awk -v u="$used" -v l="$limit" 'BEGIN{ printf "%d", (l>0? u*100/l : 0) }')
|
|
||||||
used_h=$(awk -v n="$used" 'BEGIN{ if(n>=1000000) printf "%.1fM", n/1000000; else printf "%.1fk", n/1000 }')
|
|
||||||
limit_h=$(awk -v n="$limit" 'BEGIN{ if(n>=1000000) printf "%gM", n/1000000; else printf "%gk", n/1000 }')
|
|
||||||
if [ "$used" -ge 200000 ]; then ctx_color='1;31'
|
|
||||||
elif [ "$used" -ge 100000 ]; then ctx_color='1;33'
|
|
||||||
else ctx_color='1;90'; fi
|
|
||||||
|
|
||||||
printf '\033[%sm%s/%s (%d%%)\033[0m' "$ctx_color" "$used_h" "$limit_h" "$pct"
|
|
||||||
@@ -282,6 +282,5 @@
|
|||||||
"editor.inlayHints.fontFamily": "Sriracha",
|
"editor.inlayHints.fontFamily": "Sriracha",
|
||||||
"github.copilot.nextEditSuggestions.enabled": true,
|
"github.copilot.nextEditSuggestions.enabled": true,
|
||||||
"gitlens.ai.model": "vscode",
|
"gitlens.ai.model": "vscode",
|
||||||
"gitlens.ai.vscode.model": "copilot:gpt-4.1",
|
"gitlens.ai.vscode.model": "copilot:gpt-4.1"
|
||||||
"workbench.startupEditor": "none"
|
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,3 @@ normal = { family = "JetBrainsMonoNerdFontMono", style = "Regular" }
|
|||||||
bold = { family = "JetBrainsMonoNerdFontMono", style = "Bold" }
|
bold = { family = "JetBrainsMonoNerdFontMono", style = "Bold" }
|
||||||
italic = { family = "JetBrainsMonoNerdFontMono", style = "Italic" }
|
italic = { family = "JetBrainsMonoNerdFontMono", style = "Italic" }
|
||||||
bold_italic = { family = "JetBrainsMonoNerdFontMono", style = "BoldItalic" }
|
bold_italic = { family = "JetBrainsMonoNerdFontMono", style = "BoldItalic" }
|
||||||
|
|
||||||
[[keyboard.bindings]]
|
|
||||||
key = "Return"
|
|
||||||
mods = "Shift"
|
|
||||||
chars = "\u001b\r"
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ workspaces = true
|
|||||||
keymap_mode = "auto"
|
keymap_mode = "auto"
|
||||||
enter_accept = true
|
enter_accept = true
|
||||||
|
|
||||||
sync_address = "https://atuin.i.grosinger.net"
|
|
||||||
|
|
||||||
history_filter = [
|
history_filter = [
|
||||||
"^cd$",
|
"^cd$",
|
||||||
"^lg$",
|
"^lg$",
|
||||||
@@ -20,9 +18,4 @@ history_filter = [
|
|||||||
## The "workspace" mode is skipped when not in a workspace or workspaces = false.
|
## The "workspace" mode is skipped when not in a workspace or workspaces = false.
|
||||||
## Default filter mode can be overridden with the filter_mode setting.
|
## Default filter mode can be overridden with the filter_mode setting.
|
||||||
#filters = [ "global", "host", "session", "session-preload", "workspace", "directory" ]
|
#filters = [ "global", "host", "session", "session-preload", "workspace", "directory" ]
|
||||||
filters = [ "global", "host", "workspace", "directory" ]
|
filters = [ "global", "workspace", "directory" ]
|
||||||
|
|
||||||
[tmux]
|
|
||||||
enabled = true
|
|
||||||
width = "80%"
|
|
||||||
height = "60%"
|
|
||||||
|
|||||||
@@ -1,286 +0,0 @@
|
|||||||
#? Config file for btop v.1.4.7
|
|
||||||
|
|
||||||
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
|
|
||||||
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
|
|
||||||
color_theme = "/home/tgrosinger/.config/btop/themes/catppuccin_latte.theme"
|
|
||||||
|
|
||||||
#* If the theme set background should be shown, set to False if you want terminal background transparency.
|
|
||||||
theme_background = true
|
|
||||||
|
|
||||||
#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.
|
|
||||||
truecolor = true
|
|
||||||
|
|
||||||
#* Set to true to force tty mode regardless if a real tty has been detected or not.
|
|
||||||
#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols.
|
|
||||||
force_tty = false
|
|
||||||
|
|
||||||
#* Option to disable presets. Either the default preset, custom presets, or all presets.
|
|
||||||
#* "Off" All presets are enabled.
|
|
||||||
#* "Default" preset is disabled.#* "Custom" presets are disabled.#* "All" presets are disabled.
|
|
||||||
disable_presets = "Off"
|
|
||||||
|
|
||||||
#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.
|
|
||||||
#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box.
|
|
||||||
#* Use whitespace " " as separator between different presets.
|
|
||||||
#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty"
|
|
||||||
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"
|
|
||||||
|
|
||||||
#* Set to True to enable "h,j,k,l,g,G" keys for directional control in lists.
|
|
||||||
#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift.
|
|
||||||
vim_keys = false
|
|
||||||
|
|
||||||
#* Disable all mouse events.
|
|
||||||
disable_mouse = false
|
|
||||||
|
|
||||||
#* Rounded corners on boxes, is ignored if TTY mode is ON.
|
|
||||||
rounded_corners = true
|
|
||||||
|
|
||||||
#* Use terminal synchronized output sequences to reduce flickering on supported terminals.
|
|
||||||
terminal_sync = true
|
|
||||||
|
|
||||||
#* Default symbols to use for graph creation, "braille", "block" or "tty".
|
|
||||||
#* "braille" offers the highest resolution but might not be included in all fonts.
|
|
||||||
#* "block" has half the resolution of braille but uses more common characters.
|
|
||||||
#* "tty" uses only 3 different symbols but will work with most fonts and should work in a real TTY.
|
|
||||||
#* Note that "tty" only has half the horizontal resolution of the other two, so will show a shorter historical view.
|
|
||||||
graph_symbol = "braille"
|
|
||||||
|
|
||||||
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
|
|
||||||
graph_symbol_cpu = "default"
|
|
||||||
|
|
||||||
# Graph symbol to use for graphs in gpu box, "default", "braille", "block" or "tty".
|
|
||||||
graph_symbol_gpu = "default"
|
|
||||||
|
|
||||||
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
|
|
||||||
graph_symbol_mem = "default"
|
|
||||||
|
|
||||||
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
|
|
||||||
graph_symbol_net = "default"
|
|
||||||
|
|
||||||
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
|
|
||||||
graph_symbol_proc = "default"
|
|
||||||
|
|
||||||
#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace.
|
|
||||||
shown_boxes = "cpu mem net proc"
|
|
||||||
|
|
||||||
#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs.
|
|
||||||
update_ms = 2000
|
|
||||||
|
|
||||||
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct",
|
|
||||||
#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly.
|
|
||||||
proc_sorting = "cpu lazy"
|
|
||||||
|
|
||||||
#* Reverse sorting order, True or False.
|
|
||||||
proc_reversed = false
|
|
||||||
|
|
||||||
#* Show processes as a tree.
|
|
||||||
proc_tree = true
|
|
||||||
|
|
||||||
#* Use the cpu graph colors in the process list.
|
|
||||||
proc_colors = true
|
|
||||||
|
|
||||||
#* Use a darkening gradient in the process list.
|
|
||||||
proc_gradient = true
|
|
||||||
|
|
||||||
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.
|
|
||||||
proc_per_core = true
|
|
||||||
|
|
||||||
#* Show process memory as bytes instead of percent.
|
|
||||||
proc_mem_bytes = true
|
|
||||||
|
|
||||||
#* Show cpu graph for each process.
|
|
||||||
proc_cpu_graphs = true
|
|
||||||
|
|
||||||
#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)
|
|
||||||
proc_info_smaps = false
|
|
||||||
|
|
||||||
#* Show proc box on left side of screen instead of right.
|
|
||||||
proc_left = false
|
|
||||||
|
|
||||||
#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).
|
|
||||||
proc_filter_kernel = false
|
|
||||||
|
|
||||||
#* Should the process list follow the selected process when detailed view is open.
|
|
||||||
proc_follow_detailed = true
|
|
||||||
|
|
||||||
#* In tree-view, always accumulate child process resources in the parent process.
|
|
||||||
proc_aggregate = false
|
|
||||||
|
|
||||||
#* Should cpu and memory usage display be preserved for dead processes when paused.
|
|
||||||
keep_dead_proc_usage = false
|
|
||||||
|
|
||||||
#* Sets the CPU stat shown in upper half of the CPU graph, "total" is always available.
|
|
||||||
#* Select from a list of detected attributes from the options menu.
|
|
||||||
cpu_graph_upper = "Auto"
|
|
||||||
|
|
||||||
#* Sets the CPU stat shown in lower half of the CPU graph, "total" is always available.
|
|
||||||
#* Select from a list of detected attributes from the options menu.
|
|
||||||
cpu_graph_lower = "Auto"
|
|
||||||
|
|
||||||
#* If gpu info should be shown in the cpu box. Available values = "Auto", "On" and "Off".
|
|
||||||
show_gpu_info = "Auto"
|
|
||||||
|
|
||||||
#* Toggles if the lower CPU graph should be inverted.
|
|
||||||
cpu_invert_lower = true
|
|
||||||
|
|
||||||
#* Set to True to completely disable the lower CPU graph.
|
|
||||||
cpu_single_graph = false
|
|
||||||
|
|
||||||
#* Show cpu box at bottom of screen instead of top.
|
|
||||||
cpu_bottom = false
|
|
||||||
|
|
||||||
#* Shows the system uptime in the CPU box.
|
|
||||||
show_uptime = true
|
|
||||||
|
|
||||||
#* Shows the CPU package current power consumption in watts. Requires running `make setcap` or `make setuid` or running with sudo.
|
|
||||||
show_cpu_watts = true
|
|
||||||
|
|
||||||
#* Show cpu temperature.
|
|
||||||
check_temp = true
|
|
||||||
|
|
||||||
#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.
|
|
||||||
cpu_sensor = "Auto"
|
|
||||||
|
|
||||||
#* Show temperatures for cpu cores also if check_temp is True and sensors has been found.
|
|
||||||
show_coretemp = true
|
|
||||||
|
|
||||||
#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.
|
|
||||||
#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.
|
|
||||||
#* Format "x:y" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.
|
|
||||||
#* Example: "4:0 5:1 6:3"
|
|
||||||
cpu_core_map = ""
|
|
||||||
|
|
||||||
#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine".
|
|
||||||
temp_scale = "celsius"
|
|
||||||
|
|
||||||
#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024.
|
|
||||||
base_10_sizes = false
|
|
||||||
|
|
||||||
#* Show CPU frequency.
|
|
||||||
show_cpu_freq = true
|
|
||||||
|
|
||||||
#* How to calculate CPU frequency, available values: "first", "range", "lowest", "highest" and "average".
|
|
||||||
freq_mode = "first"
|
|
||||||
|
|
||||||
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.
|
|
||||||
#* Special formatting: /host = hostname | /user = username | /uptime = system uptime
|
|
||||||
clock_format = "%X"
|
|
||||||
|
|
||||||
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.
|
|
||||||
background_update = true
|
|
||||||
|
|
||||||
#* Custom cpu model name, empty string to disable.
|
|
||||||
custom_cpu_name = ""
|
|
||||||
|
|
||||||
#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace " ".
|
|
||||||
#* Only disks matching the filter will be shown. Prepend exclude= to only show disks not matching the filter. Examples: disk_filter="/boot /home/user", disks_filter="exclude=/boot /home/user"
|
|
||||||
disks_filter = ""
|
|
||||||
|
|
||||||
#* Show graphs instead of meters for memory values.
|
|
||||||
mem_graphs = true
|
|
||||||
|
|
||||||
#* Show mem box below net box instead of above.
|
|
||||||
mem_below_net = false
|
|
||||||
|
|
||||||
#* Count ZFS ARC in cached and available memory.
|
|
||||||
zfs_arc_cached = true
|
|
||||||
|
|
||||||
#* If swap memory should be shown in memory box.
|
|
||||||
show_swap = true
|
|
||||||
|
|
||||||
#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.
|
|
||||||
swap_disk = true
|
|
||||||
|
|
||||||
#* If mem box should be split to also show disks info.
|
|
||||||
show_disks = true
|
|
||||||
|
|
||||||
#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.
|
|
||||||
only_physical = true
|
|
||||||
|
|
||||||
#* Read disks list from /etc/fstab. This also disables only_physical.
|
|
||||||
use_fstab = true
|
|
||||||
|
|
||||||
#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)
|
|
||||||
zfs_hide_datasets = false
|
|
||||||
|
|
||||||
#* Set to true to show available disk space for privileged users.
|
|
||||||
disk_free_priv = false
|
|
||||||
|
|
||||||
#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view.
|
|
||||||
show_io_stat = true
|
|
||||||
|
|
||||||
#* Toggles io mode for disks, showing big graphs for disk read/write speeds.
|
|
||||||
io_mode = false
|
|
||||||
|
|
||||||
#* Set to True to show combined read/write io graphs in io mode.
|
|
||||||
io_graph_combined = false
|
|
||||||
|
|
||||||
#* Set the top speed for the io graphs in MiB/s (100 by default), use format "mountpoint:speed" separate disks with whitespace " ".
|
|
||||||
#* Example: "/mnt/media:100 /:20 /boot:1".
|
|
||||||
io_graph_speeds = ""
|
|
||||||
|
|
||||||
#* Swap the positions of the upload and download speed graphs. When true, upload will be on top.
|
|
||||||
swap_upload_download = false
|
|
||||||
|
|
||||||
#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.
|
|
||||||
net_download = 100
|
|
||||||
|
|
||||||
net_upload = 100
|
|
||||||
|
|
||||||
#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.
|
|
||||||
net_auto = true
|
|
||||||
|
|
||||||
#* Sync the auto scaling for download and upload to whichever currently has the highest scale.
|
|
||||||
net_sync = true
|
|
||||||
|
|
||||||
#* Starts with the Network Interface specified here.
|
|
||||||
net_iface = ""
|
|
||||||
|
|
||||||
#* "True" shows bitrates in base 10 (Kbps, Mbps). "False" shows bitrates in binary sizes (Kibps, Mibps, etc.). "Auto" uses base_10_sizes.
|
|
||||||
base_10_bitrate = "Auto"
|
|
||||||
|
|
||||||
#* Show battery stats in top right if battery is present.
|
|
||||||
show_battery = true
|
|
||||||
|
|
||||||
#* Which battery to use if multiple are present. "Auto" for auto detection.
|
|
||||||
selected_battery = "Auto"
|
|
||||||
|
|
||||||
#* Show power stats of battery next to charge indicator.
|
|
||||||
show_battery_watts = true
|
|
||||||
|
|
||||||
#* Set loglevel for "~/.local/state/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG".
|
|
||||||
#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info.
|
|
||||||
log_level = "WARNING"
|
|
||||||
|
|
||||||
#* Automatically save current settings to config file on exit.
|
|
||||||
save_config_on_exit = true
|
|
||||||
|
|
||||||
#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards.
|
|
||||||
nvml_measure_pcie_speeds = true
|
|
||||||
|
|
||||||
#* Measure PCIe throughput on AMD cards, may impact performance on certain cards.
|
|
||||||
rsmi_measure_pcie_speeds = true
|
|
||||||
|
|
||||||
#* Horizontally mirror the GPU graph.
|
|
||||||
gpu_mirror_graph = true
|
|
||||||
|
|
||||||
#* Set which GPU vendors to show. Available values are "nvidia amd intel apple"
|
|
||||||
shown_gpus = "nvidia amd intel"
|
|
||||||
|
|
||||||
#* Custom gpu0 model name, empty string to disable.
|
|
||||||
custom_gpu_name0 = ""
|
|
||||||
|
|
||||||
#* Custom gpu1 model name, empty string to disable.
|
|
||||||
custom_gpu_name1 = ""
|
|
||||||
|
|
||||||
#* Custom gpu2 model name, empty string to disable.
|
|
||||||
custom_gpu_name2 = ""
|
|
||||||
|
|
||||||
#* Custom gpu3 model name, empty string to disable.
|
|
||||||
custom_gpu_name3 = ""
|
|
||||||
|
|
||||||
#* Custom gpu4 model name, empty string to disable.
|
|
||||||
custom_gpu_name4 = ""
|
|
||||||
|
|
||||||
#* Custom gpu5 model name, empty string to disable.
|
|
||||||
custom_gpu_name5 = ""
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
# Main background, empty for terminal default, need to be empty if you want transparent background
|
|
||||||
theme[main_bg]="#eff1f5"
|
|
||||||
|
|
||||||
# Main text color
|
|
||||||
theme[main_fg]="#4c4f69"
|
|
||||||
|
|
||||||
# Title color for boxes
|
|
||||||
theme[title]="#4c4f69"
|
|
||||||
|
|
||||||
# Highlight color for keyboard shortcuts
|
|
||||||
theme[hi_fg]="#1e66f5"
|
|
||||||
|
|
||||||
# Background color of selected item in processes box
|
|
||||||
theme[selected_bg]="#bcc0cc"
|
|
||||||
|
|
||||||
# Foreground color of selected item in processes box
|
|
||||||
theme[selected_fg]="#1e66f5"
|
|
||||||
|
|
||||||
# Color of inactive/disabled text
|
|
||||||
theme[inactive_fg]="#8c8fa1"
|
|
||||||
|
|
||||||
# Color of text appearing on top of graphs, i.e uptime and current network graph scaling
|
|
||||||
theme[graph_text]="#dc8a78"
|
|
||||||
|
|
||||||
# Background color of the percentage meters
|
|
||||||
theme[meter_bg]="#bcc0cc"
|
|
||||||
|
|
||||||
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
|
|
||||||
theme[proc_misc]="#dc8a78"
|
|
||||||
|
|
||||||
# CPU, Memory, Network, Proc box outline colors
|
|
||||||
theme[cpu_box]="#8839ef" #Mauve
|
|
||||||
theme[mem_box]="#40a02b" #Green
|
|
||||||
theme[net_box]="#e64553" #Maroon
|
|
||||||
theme[proc_box]="#1e66f5" #Blue
|
|
||||||
|
|
||||||
# Box divider line and small boxes line color
|
|
||||||
theme[div_line]="#9ca0b0"
|
|
||||||
|
|
||||||
# Temperature graph color (Green -> Yellow -> Red)
|
|
||||||
theme[temp_start]="#40a02b"
|
|
||||||
theme[temp_mid]="#df8e1d"
|
|
||||||
theme[temp_end]="#d20f39"
|
|
||||||
|
|
||||||
# CPU graph colors (Teal -> Lavender)
|
|
||||||
theme[cpu_start]="#179299"
|
|
||||||
theme[cpu_mid]="#209fb5"
|
|
||||||
theme[cpu_end]="#7287fd"
|
|
||||||
|
|
||||||
# Mem/Disk free meter (Mauve -> Lavender -> Blue)
|
|
||||||
theme[free_start]="#8839ef"
|
|
||||||
theme[free_mid]="#7287fd"
|
|
||||||
theme[free_end]="#1e66f5"
|
|
||||||
|
|
||||||
# Mem/Disk cached meter (Sapphire -> Lavender)
|
|
||||||
theme[cached_start]="#209fb5"
|
|
||||||
theme[cached_mid]="#1e66f5"
|
|
||||||
theme[cached_end]="#7287fd"
|
|
||||||
|
|
||||||
# Mem/Disk available meter (Peach -> Red)
|
|
||||||
theme[available_start]="#fe640b"
|
|
||||||
theme[available_mid]="#e64553"
|
|
||||||
theme[available_end]="#d20f39"
|
|
||||||
|
|
||||||
# Mem/Disk used meter (Green -> Sky)
|
|
||||||
theme[used_start]="#40a02b"
|
|
||||||
theme[used_mid]="#179299"
|
|
||||||
theme[used_end]="#04a5e5"
|
|
||||||
|
|
||||||
# Download graph colors (Peach -> Red)
|
|
||||||
theme[download_start]="#fe640b"
|
|
||||||
theme[download_mid]="#e64553"
|
|
||||||
theme[download_end]="#d20f39"
|
|
||||||
|
|
||||||
# Upload graph colors (Green -> Sky)
|
|
||||||
theme[upload_start]="#40a02b"
|
|
||||||
theme[upload_mid]="#179299"
|
|
||||||
theme[upload_end]="#04a5e5"
|
|
||||||
|
|
||||||
# Process box color gradient for threads, mem and cpu usage (Sapphire -> Mauve)
|
|
||||||
theme[process_start]="#209fb5"
|
|
||||||
theme[process_mid]="#7287fd"
|
|
||||||
theme[process_end]="#8839ef"
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
[delta "catppuccin-latte"]
|
|
||||||
blame-palette = "#eff1f5 #e6e9ef #dce0e8 #ccd0da #bcc0cc"
|
|
||||||
commit-decoration-style = "#9ca0b0" bold box ul
|
|
||||||
light = true
|
|
||||||
file-decoration-style = "#9ca0b0"
|
|
||||||
file-style = "#4c4f69"
|
|
||||||
hunk-header-decoration-style = "#9ca0b0" box ul
|
|
||||||
hunk-header-file-style = bold
|
|
||||||
hunk-header-line-number-style = bold "#6c6f85"
|
|
||||||
hunk-header-style = file line-number syntax
|
|
||||||
line-numbers-left-style = "#9ca0b0"
|
|
||||||
line-numbers-minus-style = bold "#d20f39"
|
|
||||||
line-numbers-plus-style = bold "#40a02b"
|
|
||||||
line-numbers-right-style = "#9ca0b0"
|
|
||||||
line-numbers-zero-style = "#9ca0b0"
|
|
||||||
# 35% red 65% base
|
|
||||||
minus-emph-style = bold syntax "#e5a2b3"
|
|
||||||
# 20% red 80% base
|
|
||||||
minus-style = syntax "#e9c4cf"
|
|
||||||
# 35% green 65% base
|
|
||||||
plus-emph-style = bold syntax "#b2d5ae"
|
|
||||||
# 20% green 80% base
|
|
||||||
plus-style = syntax "#cce1cd"
|
|
||||||
map-styles = \
|
|
||||||
bold purple => syntax "#cbb1f2", \
|
|
||||||
bold blue => syntax "#a6c1f5", \
|
|
||||||
bold cyan => syntax "#9dd7ef", \
|
|
||||||
bold yellow => syntax "#eacfa9"
|
|
||||||
# Should match the name of the bat theme
|
|
||||||
syntax-theme = Catppuccin Latte
|
|
||||||
|
|
||||||
[delta "catppuccin-frappe"]
|
|
||||||
blame-palette = "#303446 #292c3c #232634 #414559 #51576d"
|
|
||||||
commit-decoration-style = "#737994" bold box ul
|
|
||||||
dark = true
|
|
||||||
file-decoration-style = "#737994"
|
|
||||||
file-style = "#c6d0f5"
|
|
||||||
hunk-header-decoration-style = "#737994" box ul
|
|
||||||
hunk-header-file-style = bold
|
|
||||||
hunk-header-line-number-style = bold "#a5adce"
|
|
||||||
hunk-header-style = file line-number syntax
|
|
||||||
line-numbers-left-style = "#737994"
|
|
||||||
line-numbers-minus-style = bold "#e78284"
|
|
||||||
line-numbers-plus-style = bold "#a6d189"
|
|
||||||
line-numbers-right-style = "#737994"
|
|
||||||
line-numbers-zero-style = "#737994"
|
|
||||||
# 35% red 65% base
|
|
||||||
minus-emph-style = bold syntax "#704f5c"
|
|
||||||
# 20% red 80% base
|
|
||||||
minus-style = syntax "#544452"
|
|
||||||
# 35% green 65% base
|
|
||||||
plus-emph-style = bold syntax "#596b5e"
|
|
||||||
# 20% green 80% base
|
|
||||||
plus-style = syntax "#475453"
|
|
||||||
map-styles = \
|
|
||||||
bold purple => syntax "#66597e", \
|
|
||||||
bold blue => syntax "#505d81", \
|
|
||||||
bold cyan => syntax "#546b7a", \
|
|
||||||
bold yellow => syntax "#6f6860"
|
|
||||||
# Should match the name of the bat theme
|
|
||||||
syntax-theme = Catppuccin Frappe
|
|
||||||
|
|
||||||
[delta "catppuccin-macchiato"]
|
|
||||||
blame-palette = "#24273a #1e2030 #181926 #363a4f #494d64"
|
|
||||||
commit-decoration-style = "#6e738d" bold box ul
|
|
||||||
dark = true
|
|
||||||
file-decoration-style = "#6e738d"
|
|
||||||
file-style = "#cad3f5"
|
|
||||||
hunk-header-decoration-style = "#6e738d" box ul
|
|
||||||
hunk-header-file-style = bold
|
|
||||||
hunk-header-line-number-style = bold "#a5adcb"
|
|
||||||
hunk-header-style = file line-number syntax
|
|
||||||
line-numbers-left-style = "#6e738d"
|
|
||||||
line-numbers-minus-style = bold "#ed8796"
|
|
||||||
line-numbers-plus-style = bold "#a6da95"
|
|
||||||
line-numbers-right-style = "#6e738d"
|
|
||||||
line-numbers-zero-style = "#6e738d"
|
|
||||||
# 35% red 65% base
|
|
||||||
minus-emph-style = bold syntax "#6a485a"
|
|
||||||
# 20% red 80% base
|
|
||||||
minus-style = syntax "#4c3a4c"
|
|
||||||
# 35% green 65% base
|
|
||||||
plus-emph-style = bold syntax "#51655a"
|
|
||||||
# 20% green 80% base
|
|
||||||
plus-style = syntax "#3e4b4c"
|
|
||||||
map-styles = \
|
|
||||||
bold purple => syntax "#5c517c", \
|
|
||||||
bold blue => syntax "#47557b", \
|
|
||||||
bold cyan => syntax "#4a6475", \
|
|
||||||
bold yellow => syntax "#6a635d"
|
|
||||||
# Should match the name of the bat theme
|
|
||||||
syntax-theme = Catppuccin Macchiato
|
|
||||||
|
|
||||||
[delta "catppuccin-mocha"]
|
|
||||||
blame-palette = "#1e1e2e #181825 #11111b #313244 #45475a"
|
|
||||||
commit-decoration-style = "#6c7086" bold box ul
|
|
||||||
dark = true
|
|
||||||
file-decoration-style = "#6c7086"
|
|
||||||
file-style = "#cdd6f4"
|
|
||||||
hunk-header-decoration-style = "#6c7086" box ul
|
|
||||||
hunk-header-file-style = bold
|
|
||||||
hunk-header-line-number-style = bold "#a6adc8"
|
|
||||||
hunk-header-style = file line-number syntax
|
|
||||||
line-numbers-left-style = "#6c7086"
|
|
||||||
line-numbers-minus-style = bold "#f38ba8"
|
|
||||||
line-numbers-plus-style = bold "#a6e3a1"
|
|
||||||
line-numbers-right-style = "#6c7086"
|
|
||||||
line-numbers-zero-style = "#6c7086"
|
|
||||||
# 35% red 65% base
|
|
||||||
minus-emph-style = bold syntax "#694559"
|
|
||||||
# 20% red 80% base
|
|
||||||
minus-style = syntax "#493447"
|
|
||||||
# 35% green 65% base
|
|
||||||
plus-emph-style = bold syntax "#4e6356"
|
|
||||||
# 20% green 80% base
|
|
||||||
plus-style = syntax "#394545"
|
|
||||||
map-styles = \
|
|
||||||
bold purple => syntax "#5b4e74", \
|
|
||||||
bold blue => syntax "#445375", \
|
|
||||||
bold cyan => syntax "#446170", \
|
|
||||||
bold yellow => syntax "#6b635b"
|
|
||||||
# Should match the name of the bat theme
|
|
||||||
syntax-theme = Catppuccin Mocha
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
complete -c dev-switch -f -a '(git worktree list --porcelain 2>/dev/null | string match "worktree *" | string replace "worktree " "" | while read -l p; basename $p; end)'
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
function __dev_wt_pr_numbers
|
|
||||||
# Open PRs in the current repo: number<TAB>title. gh resolves the repo from
|
|
||||||
# the remote, so this only yields anything inside a GitHub checkout.
|
|
||||||
gh pr list --json number,title --jq '.[] | "\(.number)\t\(.title)"' 2>/dev/null
|
|
||||||
end
|
|
||||||
|
|
||||||
function __dev_wt_pr_branch
|
|
||||||
# Second arg is the branch; default to the head branch of the PR already typed.
|
|
||||||
set -l tokens (commandline -opc)
|
|
||||||
test (count $tokens) -ge 2; or return
|
|
||||||
gh pr view $tokens[2] --json headRefName --jq .headRefName 2>/dev/null
|
|
||||||
end
|
|
||||||
|
|
||||||
complete -c dev-wt-pr -f -n __fish_is_first_arg -a '(__dev_wt_pr_numbers)'
|
|
||||||
complete -c dev-wt-pr -f -n 'test (count (commandline -opc)) -eq 2' -a '(__dev_wt_pr_branch)'
|
|
||||||
complete -c dev-wt-pr -n 'test (count (commandline -opc)) -eq 3' -a '(__fish_complete_directories)' -d 'Repo path'
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
function __dev_wt_completions
|
|
||||||
# Existing worktrees: dev-wt matches by directory basename or branch name,
|
|
||||||
# so offer both. Track branches that already have a worktree to avoid
|
|
||||||
# duplicating them in the "create new" list below.
|
|
||||||
set -l wt_branches
|
|
||||||
set -l path ""
|
|
||||||
for line in (git worktree list --porcelain 2>/dev/null)
|
|
||||||
if string match -q "worktree *" -- $line
|
|
||||||
set path (string replace "worktree " "" -- $line)
|
|
||||||
printf '%s\tworktree\n' (basename $path)
|
|
||||||
else if string match -q "branch *" -- $line
|
|
||||||
set -l branch (string replace "branch refs/heads/" "" -- $line)
|
|
||||||
set -a wt_branches $branch
|
|
||||||
printf '%s\tworktree branch\n' $branch
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Remaining local branches: selecting one creates a new worktree for it.
|
|
||||||
for branch in (git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null)
|
|
||||||
if not contains -- $branch $wt_branches
|
|
||||||
printf '%s\tbranch\n' $branch
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
complete -c dev-wt -f -n __fish_is_first_arg -a '(__dev_wt_completions)'
|
|
||||||
complete -c dev-wt -s C -l repo -r -a '(__fish_complete_directories)' -d 'Operate on the repo at this path'
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
complete -c pandoc -f -a '(string match -r ".*\\.md\$" -- (ls))'
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
# fish completion for process-compose -*- shell-script -*-
|
|
||||||
|
|
||||||
function __process_compose_debug
|
|
||||||
set -l file "$BASH_COMP_DEBUG_FILE"
|
|
||||||
if test -n "$file"
|
|
||||||
echo "$argv" >> $file
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function __process_compose_perform_completion
|
|
||||||
__process_compose_debug "Starting __process_compose_perform_completion"
|
|
||||||
|
|
||||||
# Extract all args except the last one
|
|
||||||
set -l args (commandline -opc)
|
|
||||||
# Extract the last arg and escape it in case it is a space
|
|
||||||
set -l lastArg (string escape -- (commandline -ct))
|
|
||||||
|
|
||||||
__process_compose_debug "args: $args"
|
|
||||||
__process_compose_debug "last arg: $lastArg"
|
|
||||||
|
|
||||||
# Disable ActiveHelp which is not supported for fish shell
|
|
||||||
set -l requestComp "PROCESS_COMPOSE_ACTIVE_HELP=0 $args[1] __complete $args[2..-1] $lastArg"
|
|
||||||
|
|
||||||
__process_compose_debug "Calling $requestComp"
|
|
||||||
set -l results (eval $requestComp 2> /dev/null)
|
|
||||||
|
|
||||||
# Some programs may output extra empty lines after the directive.
|
|
||||||
# Let's ignore them or else it will break completion.
|
|
||||||
# Ref: https://github.com/spf13/cobra/issues/1279
|
|
||||||
for line in $results[-1..1]
|
|
||||||
if test (string trim -- $line) = ""
|
|
||||||
# Found an empty line, remove it
|
|
||||||
set results $results[1..-2]
|
|
||||||
else
|
|
||||||
# Found non-empty line, we have our proper output
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l comps $results[1..-2]
|
|
||||||
set -l directiveLine $results[-1]
|
|
||||||
|
|
||||||
# For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)
|
|
||||||
# completions must be prefixed with the flag
|
|
||||||
set -l flagPrefix (string match -r -- '-.*=' "$lastArg")
|
|
||||||
|
|
||||||
__process_compose_debug "Comps: $comps"
|
|
||||||
__process_compose_debug "DirectiveLine: $directiveLine"
|
|
||||||
__process_compose_debug "flagPrefix: $flagPrefix"
|
|
||||||
|
|
||||||
for comp in $comps
|
|
||||||
printf "%s%s\n" "$flagPrefix" "$comp"
|
|
||||||
end
|
|
||||||
|
|
||||||
printf "%s\n" "$directiveLine"
|
|
||||||
end
|
|
||||||
|
|
||||||
# this function limits calls to __process_compose_perform_completion, by caching the result behind $__process_compose_perform_completion_once_result
|
|
||||||
function __process_compose_perform_completion_once
|
|
||||||
__process_compose_debug "Starting __process_compose_perform_completion_once"
|
|
||||||
|
|
||||||
if test -n "$__process_compose_perform_completion_once_result"
|
|
||||||
__process_compose_debug "Seems like a valid result already exists, skipping __process_compose_perform_completion"
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
set --global __process_compose_perform_completion_once_result (__process_compose_perform_completion)
|
|
||||||
if test -z "$__process_compose_perform_completion_once_result"
|
|
||||||
__process_compose_debug "No completions, probably due to a failure"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
__process_compose_debug "Performed completions and set __process_compose_perform_completion_once_result"
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
# this function is used to clear the $__process_compose_perform_completion_once_result variable after completions are run
|
|
||||||
function __process_compose_clear_perform_completion_once_result
|
|
||||||
__process_compose_debug ""
|
|
||||||
__process_compose_debug "========= clearing previously set __process_compose_perform_completion_once_result variable =========="
|
|
||||||
set --erase __process_compose_perform_completion_once_result
|
|
||||||
__process_compose_debug "Successfully erased the variable __process_compose_perform_completion_once_result"
|
|
||||||
end
|
|
||||||
|
|
||||||
function __process_compose_requires_order_preservation
|
|
||||||
__process_compose_debug ""
|
|
||||||
__process_compose_debug "========= checking if order preservation is required =========="
|
|
||||||
|
|
||||||
__process_compose_perform_completion_once
|
|
||||||
if test -z "$__process_compose_perform_completion_once_result"
|
|
||||||
__process_compose_debug "Error determining if order preservation is required"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l directive (string sub --start 2 $__process_compose_perform_completion_once_result[-1])
|
|
||||||
__process_compose_debug "Directive is: $directive"
|
|
||||||
|
|
||||||
set -l shellCompDirectiveKeepOrder 32
|
|
||||||
set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) % 2)
|
|
||||||
__process_compose_debug "Keeporder is: $keeporder"
|
|
||||||
|
|
||||||
if test $keeporder -ne 0
|
|
||||||
__process_compose_debug "This does require order preservation"
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
__process_compose_debug "This doesn't require order preservation"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# This function does two things:
|
|
||||||
# - Obtain the completions and store them in the global __process_compose_comp_results
|
|
||||||
# - Return false if file completion should be performed
|
|
||||||
function __process_compose_prepare_completions
|
|
||||||
__process_compose_debug ""
|
|
||||||
__process_compose_debug "========= starting completion logic =========="
|
|
||||||
|
|
||||||
# Start fresh
|
|
||||||
set --erase __process_compose_comp_results
|
|
||||||
|
|
||||||
__process_compose_perform_completion_once
|
|
||||||
__process_compose_debug "Completion results: $__process_compose_perform_completion_once_result"
|
|
||||||
|
|
||||||
if test -z "$__process_compose_perform_completion_once_result"
|
|
||||||
__process_compose_debug "No completion, probably due to a failure"
|
|
||||||
# Might as well do file completion, in case it helps
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l directive (string sub --start 2 $__process_compose_perform_completion_once_result[-1])
|
|
||||||
set --global __process_compose_comp_results $__process_compose_perform_completion_once_result[1..-2]
|
|
||||||
|
|
||||||
__process_compose_debug "Completions are: $__process_compose_comp_results"
|
|
||||||
__process_compose_debug "Directive is: $directive"
|
|
||||||
|
|
||||||
set -l shellCompDirectiveError 1
|
|
||||||
set -l shellCompDirectiveNoSpace 2
|
|
||||||
set -l shellCompDirectiveNoFileComp 4
|
|
||||||
set -l shellCompDirectiveFilterFileExt 8
|
|
||||||
set -l shellCompDirectiveFilterDirs 16
|
|
||||||
|
|
||||||
if test -z "$directive"
|
|
||||||
set directive 0
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2)
|
|
||||||
if test $compErr -eq 1
|
|
||||||
__process_compose_debug "Received error directive: aborting."
|
|
||||||
# Might as well do file completion, in case it helps
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2)
|
|
||||||
set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2)
|
|
||||||
if test $filefilter -eq 1; or test $dirfilter -eq 1
|
|
||||||
__process_compose_debug "File extension filtering or directory filtering not supported"
|
|
||||||
# Do full file completion instead
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2)
|
|
||||||
set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2)
|
|
||||||
|
|
||||||
__process_compose_debug "nospace: $nospace, nofiles: $nofiles"
|
|
||||||
|
|
||||||
# If we want to prevent a space, or if file completion is NOT disabled,
|
|
||||||
# we need to count the number of valid completions.
|
|
||||||
# To do so, we will filter on prefix as the completions we have received
|
|
||||||
# may not already be filtered so as to allow fish to match on different
|
|
||||||
# criteria than the prefix.
|
|
||||||
if test $nospace -ne 0; or test $nofiles -eq 0
|
|
||||||
set -l prefix (commandline -t | string escape --style=regex)
|
|
||||||
__process_compose_debug "prefix: $prefix"
|
|
||||||
|
|
||||||
set -l completions (string match -r -- "^$prefix.*" $__process_compose_comp_results)
|
|
||||||
set --global __process_compose_comp_results $completions
|
|
||||||
__process_compose_debug "Filtered completions are: $__process_compose_comp_results"
|
|
||||||
|
|
||||||
# Important not to quote the variable for count to work
|
|
||||||
set -l numComps (count $__process_compose_comp_results)
|
|
||||||
__process_compose_debug "numComps: $numComps"
|
|
||||||
|
|
||||||
if test $numComps -eq 1; and test $nospace -ne 0
|
|
||||||
# We must first split on \t to get rid of the descriptions to be
|
|
||||||
# able to check what the actual completion will be.
|
|
||||||
# We don't need descriptions anyway since there is only a single
|
|
||||||
# real completion which the shell will expand immediately.
|
|
||||||
set -l split (string split --max 1 \t $__process_compose_comp_results[1])
|
|
||||||
|
|
||||||
# Fish won't add a space if the completion ends with any
|
|
||||||
# of the following characters: @=/:.,
|
|
||||||
set -l lastChar (string sub -s -1 -- $split)
|
|
||||||
if not string match -r -q "[@=/:.,]" -- "$lastChar"
|
|
||||||
# In other cases, to support the "nospace" directive we trick the shell
|
|
||||||
# by outputting an extra, longer completion.
|
|
||||||
__process_compose_debug "Adding second completion to perform nospace directive"
|
|
||||||
set --global __process_compose_comp_results $split[1] $split[1].
|
|
||||||
__process_compose_debug "Completions are now: $__process_compose_comp_results"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if test $numComps -eq 0; and test $nofiles -eq 0
|
|
||||||
# To be consistent with bash and zsh, we only trigger file
|
|
||||||
# completion when there are no other completions
|
|
||||||
__process_compose_debug "Requesting file completion"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
|
|
||||||
# so we can properly delete any completions provided by another script.
|
|
||||||
# Only do this if the program can be found, or else fish may print some errors; besides,
|
|
||||||
# the existing completions will only be loaded if the program can be found.
|
|
||||||
if type -q "process-compose"
|
|
||||||
# The space after the program name is essential to trigger completion for the program
|
|
||||||
# and not completion of the program name itself.
|
|
||||||
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
|
|
||||||
complete --do-complete "process-compose " > /dev/null 2>&1
|
|
||||||
end
|
|
||||||
|
|
||||||
# Remove any pre-existing completions for the program since we will be handling all of them.
|
|
||||||
complete -c process-compose -e
|
|
||||||
|
|
||||||
# this will get called after the two calls below and clear the $__process_compose_perform_completion_once_result global
|
|
||||||
complete -c process-compose -n '__process_compose_clear_perform_completion_once_result'
|
|
||||||
# The call to __process_compose_prepare_completions will setup __process_compose_comp_results
|
|
||||||
# which provides the program's completion choices.
|
|
||||||
# If this doesn't require order preservation, we don't use the -k flag
|
|
||||||
complete -c process-compose -n 'not __process_compose_requires_order_preservation && __process_compose_prepare_completions' -f -a '$__process_compose_comp_results'
|
|
||||||
# otherwise we use the -k flag
|
|
||||||
complete -k -c process-compose -n '__process_compose_requires_order_preservation && __process_compose_prepare_completions' -f -a '$__process_compose_comp_results'
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Gradle needs a JDK it can actually run on. Fedora 43's default is JDK 25,
|
|
||||||
# which Gradle 8.13 rejects with "Unsupported class file major version 69".
|
|
||||||
# $HOME is shared with the host, so scope this to the distrobox via CONTAINER_ID.
|
|
||||||
if set -q CONTAINER_ID; and test -x /usr/lib/jvm/java-21-openjdk/bin/javac
|
|
||||||
set -gx JAVA_HOME /usr/lib/jvm/java-21-openjdk
|
|
||||||
end
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
function __dev_repo_name
|
|
||||||
# Use the main worktree (first entry) for consistent naming across worktrees
|
|
||||||
set -l lines (git worktree list --porcelain | string replace -rf "^worktree " "")
|
|
||||||
basename $lines[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
function __dev_clean_shell -d "Build a clean login shell command, unsetting devbox vars so they re-source"
|
|
||||||
# Print one word per line; callers capture with (__dev_clean_shell).
|
|
||||||
echo env
|
|
||||||
for var in (set --names --export | string match 'DEVBOX_*')
|
|
||||||
echo -- -u
|
|
||||||
echo -- $var
|
|
||||||
end
|
|
||||||
echo -- (command -s fish)
|
|
||||||
echo -- --login
|
|
||||||
end
|
|
||||||
|
|
||||||
function __dev_create_session -a session dir
|
|
||||||
if test -z "$dir"
|
|
||||||
set dir (pwd)
|
|
||||||
end
|
|
||||||
if not tmux has-session -t "$session" 2>/dev/null
|
|
||||||
# Clean login shell so the new directory re-sources its environment.
|
|
||||||
set -l shell (__dev_clean_shell)
|
|
||||||
|
|
||||||
tmux new-session -d -s "$session" -c "$dir" -n dev $shell
|
|
||||||
|
|
||||||
# Ensure that new panes in this session also start with the clean environment.
|
|
||||||
tmux set-option -t "$session" default-command (string join ' ' -- $shell)
|
|
||||||
|
|
||||||
set -l left (tmux display-message -t "$session:dev" -p '#{pane_id}')
|
|
||||||
tmux split-window -h -t "$left" -c "$dir" $shell
|
|
||||||
set -l right (tmux display-message -t "$session:dev" -p '#{pane_id}')
|
|
||||||
tmux split-window -v -t "$right" -c "$dir" $shell
|
|
||||||
set -l bottom_right (tmux display-message -t "$session:dev" -p '#{pane_id}')
|
|
||||||
tmux send-keys -t "$left" 'vim' Enter
|
|
||||||
tmux send-keys -t "$right" 'dev-claude' Enter
|
|
||||||
|
|
||||||
tmux select-pane -t "$bottom_right"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function __dev_attach_session -a session
|
|
||||||
if test -z "$TMUX"
|
|
||||||
tmux attach-session -t "$session"
|
|
||||||
else
|
|
||||||
set -l current_session (tmux display-message -p '#S')
|
|
||||||
if test "$current_session" != "$session"
|
|
||||||
tmux switch-client -t "$session"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function __dev_wt_session_name -a repo branch
|
|
||||||
set -l name "$repo-"(string replace -a '/' '-' -- $branch | string replace -a '\\' '-')
|
|
||||||
string replace -a '.' '-' -- $name
|
|
||||||
end
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# This file was created by fish when upgrading to version 4.3, to migrate
|
|
||||||
# the 'fish_key_bindings' variable from its old default scope (universal)
|
|
||||||
# to its new default scope (global). We recommend you delete this file
|
|
||||||
# and configure key bindings in ~/.config/fish/config.fish if needed.
|
|
||||||
|
|
||||||
set --global fish_key_bindings fish_vi_key_bindings
|
|
||||||
|
|
||||||
# Prior to version 4.3, fish shipped an event handler that runs
|
|
||||||
# `set --universal fish_key_bindings fish_default_key_bindings`
|
|
||||||
# whenever the fish_key_bindings variable is erased.
|
|
||||||
# This means that as long as any fish < 4.3 is still running on this system,
|
|
||||||
# we cannot complete the migration.
|
|
||||||
# As a workaround, erase the universal variable at every shell startup.
|
|
||||||
set --erase --universal fish_key_bindings
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# This file was created by fish when upgrading to version 4.3, to migrate
|
|
||||||
# theme variables from universal to global scope.
|
|
||||||
# Don't edit this file, as it will be written by the web-config tool (`fish_config`).
|
|
||||||
# To customize your theme, delete this file and see
|
|
||||||
# help interactive#syntax-highlighting
|
|
||||||
# or
|
|
||||||
# man fish-interactive | less +/^SYNTAX.HIGHLIGHTING
|
|
||||||
# for appropriate commands to add to ~/.config/fish/config.fish instead.
|
|
||||||
# See also the release notes for fish 4.3.0 (run `help relnotes`).
|
|
||||||
|
|
||||||
set --global fish_color_autosuggestion 9ca0b0
|
|
||||||
set --global fish_color_cancel d20f39
|
|
||||||
set --global fish_color_command 1e66f5
|
|
||||||
set --global fish_color_comment 8c8fa1
|
|
||||||
set --global fish_color_cwd df8e1d
|
|
||||||
set --global fish_color_cwd_root red
|
|
||||||
set --global fish_color_end fe640b
|
|
||||||
set --global fish_color_error d20f39
|
|
||||||
set --global fish_color_escape e64553
|
|
||||||
set --global fish_color_gray 9ca0b0
|
|
||||||
set --global fish_color_history_current --bold
|
|
||||||
set --global fish_color_host 1e66f5
|
|
||||||
set --global fish_color_host_remote 40a02b
|
|
||||||
set --global fish_color_keyword 8839ef
|
|
||||||
set --global fish_color_normal 4c4f69
|
|
||||||
set --global fish_color_operator ea76cb
|
|
||||||
set --global fish_color_option 40a02b
|
|
||||||
set --global fish_color_param dd7878
|
|
||||||
set --global fish_color_quote 40a02b
|
|
||||||
set --global fish_color_redirection ea76cb
|
|
||||||
set --global fish_color_search_match --background=ccd0da
|
|
||||||
set --global fish_color_selection --background=ccd0da
|
|
||||||
set --global fish_color_status d20f39
|
|
||||||
set --global fish_color_user 179299
|
|
||||||
set --global fish_color_valid_path --underline
|
|
||||||
set --global fish_pager_color_background
|
|
||||||
set --global fish_pager_color_completion 4c4f69
|
|
||||||
set --global fish_pager_color_description 9ca0b0
|
|
||||||
set --global fish_pager_color_prefix ea76cb
|
|
||||||
set --global fish_pager_color_progress 9ca0b0
|
|
||||||
set --global fish_pager_color_secondary_background
|
|
||||||
set --global fish_pager_color_secondary_completion
|
|
||||||
set --global fish_pager_color_secondary_description
|
|
||||||
set --global fish_pager_color_secondary_prefix
|
|
||||||
set --global fish_pager_color_selected_background
|
|
||||||
set --global fish_pager_color_selected_completion
|
|
||||||
set --global fish_pager_color_selected_description
|
|
||||||
set --global fish_pager_color_selected_prefix
|
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
# Mise shims so non-interactive shells (scripts, editor spawns) find tools.
|
|
||||||
# Interactive sessions get exact versions from `mise activate` below.
|
|
||||||
fish_add_path --global ~/.local/bin ~/.local/share/mise/shims ~/go/bin
|
|
||||||
|
|
||||||
if test -d ~/code/yt-dlp
|
|
||||||
fish_add_path --global ~/code/yt-dlp
|
|
||||||
end
|
|
||||||
|
|
||||||
if status is-interactive
|
if status is-interactive
|
||||||
# Commands to run in interactive sessions can go here
|
# Commands to run in interactive sessions can go here
|
||||||
|
|
||||||
@@ -13,37 +5,30 @@ if status is-interactive
|
|||||||
# https://fishshell.com/docs/current/cmds/fish_greeting.html
|
# https://fishshell.com/docs/current/cmds/fish_greeting.html
|
||||||
set -g fish_greeting
|
set -g fish_greeting
|
||||||
|
|
||||||
if command -q brew
|
if test -f /home/linuxbrew/.linuxbrew/bin/brew
|
||||||
brew shellenv | source
|
|
||||||
else if test -x /home/linuxbrew/.linuxbrew/bin/brew
|
|
||||||
/home/linuxbrew/.linuxbrew/bin/brew shellenv | source
|
/home/linuxbrew/.linuxbrew/bin/brew shellenv | source
|
||||||
end
|
end
|
||||||
if command -q atuin
|
if test -f $(which atuin)
|
||||||
atuin init fish | source
|
atuin init fish | source
|
||||||
end
|
end
|
||||||
|
|
||||||
# Override globals
|
# Override globals
|
||||||
set -gx EDITOR nvim
|
set -gx EDITOR nvim
|
||||||
|
|
||||||
|
# Enable autoenv
|
||||||
|
source ~/.config/fish/functions/activate.fish
|
||||||
|
|
||||||
# Aliases
|
# Aliases
|
||||||
alias vim="nvim"
|
alias vim="nvim"
|
||||||
|
alias dc="podman compose"
|
||||||
alias lg="lazygit"
|
alias lg="lazygit"
|
||||||
alias la="eza --long --header --git --group --time-style long-iso -a"
|
alias la="eza --long --header --git --group --time-style long-iso -a"
|
||||||
alias record='wf-recorder -g "$(slurp)"'
|
alias record='wf-recorder -g "$(slurp)"'
|
||||||
alias claer="clear"
|
|
||||||
|
|
||||||
# Set a location to store variables that should not be tracked in git.
|
|
||||||
if test -f ~/.local/fish_env.fish
|
|
||||||
source ~/.local/fish_env.fish
|
|
||||||
end
|
|
||||||
|
|
||||||
# Abbreviations
|
# Abbreviations
|
||||||
# https://fishshell.com/docs/current/cmds/abbr.html
|
# https://fishshell.com/docs/current/cmds/abbr.html
|
||||||
abbr --add --position command pc process-compose
|
abbr --add --position command ds devbox services
|
||||||
|
|
||||||
# Per-directory toolchains and global tools.
|
# Set Path
|
||||||
# https://mise.jdx.dev/getting-started.html
|
fish_add_path -p /home/tgrosinger/.dotfiles/bin/linux
|
||||||
if type -q mise
|
|
||||||
mise activate fish | source
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,43 @@
|
|||||||
# This file contains fish universal variable definitions.
|
# This file contains fish universal variable definitions.
|
||||||
# VERSION: 3.0
|
# VERSION: 3.0
|
||||||
SETUVAR __fish_initialized:4300
|
SETUVAR __fish_initialized:3800
|
||||||
|
SETUVAR fish_color_autosuggestion:9ca0b0
|
||||||
|
SETUVAR fish_color_cancel:d20f39
|
||||||
|
SETUVAR fish_color_command:1e66f5
|
||||||
|
SETUVAR fish_color_comment:8c8fa1
|
||||||
|
SETUVAR fish_color_cwd:df8e1d
|
||||||
|
SETUVAR fish_color_cwd_root:red
|
||||||
|
SETUVAR fish_color_end:fe640b
|
||||||
|
SETUVAR fish_color_error:d20f39
|
||||||
|
SETUVAR fish_color_escape:e64553
|
||||||
|
SETUVAR fish_color_gray:9ca0b0
|
||||||
|
SETUVAR fish_color_history_current:\x2d\x2dbold
|
||||||
|
SETUVAR fish_color_host:1e66f5
|
||||||
|
SETUVAR fish_color_host_remote:40a02b
|
||||||
|
SETUVAR fish_color_keyword:8839ef
|
||||||
|
SETUVAR fish_color_normal:4c4f69
|
||||||
|
SETUVAR fish_color_operator:ea76cb
|
||||||
|
SETUVAR fish_color_option:40a02b
|
||||||
|
SETUVAR fish_color_param:dd7878
|
||||||
|
SETUVAR fish_color_quote:40a02b
|
||||||
|
SETUVAR fish_color_redirection:ea76cb
|
||||||
|
SETUVAR fish_color_search_match:\x2d\x2dbackground\x3dccd0da
|
||||||
|
SETUVAR fish_color_selection:\x2d\x2dbackground\x3dccd0da
|
||||||
|
SETUVAR fish_color_status:d20f39
|
||||||
|
SETUVAR fish_color_user:179299
|
||||||
|
SETUVAR fish_color_valid_path:\x2d\x2dunderline
|
||||||
|
SETUVAR fish_key_bindings:fish_vi_key_bindings
|
||||||
|
SETUVAR fish_pager_color_background:\x1d
|
||||||
|
SETUVAR fish_pager_color_completion:4c4f69
|
||||||
|
SETUVAR fish_pager_color_description:9ca0b0
|
||||||
|
SETUVAR fish_pager_color_prefix:ea76cb
|
||||||
|
SETUVAR fish_pager_color_progress:9ca0b0
|
||||||
|
SETUVAR fish_pager_color_secondary_background:\x1d
|
||||||
|
SETUVAR fish_pager_color_secondary_completion:\x1d
|
||||||
|
SETUVAR fish_pager_color_secondary_description:\x1d
|
||||||
|
SETUVAR fish_pager_color_secondary_prefix:\x1d
|
||||||
|
SETUVAR fish_pager_color_selected_background:\x1d
|
||||||
|
SETUVAR fish_pager_color_selected_completion:\x1d
|
||||||
|
SETUVAR fish_pager_color_selected_description:\x1d
|
||||||
|
SETUVAR fish_pager_color_selected_prefix:\x1d
|
||||||
|
SETUVAR fish_user_paths:/home/tgrosinger/\x2edotfiles/bin/linux
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env fish
|
||||||
|
|
||||||
|
#
|
||||||
|
# Autoenv for fish shell.
|
||||||
|
# Based on, but heavily modified from:
|
||||||
|
# https://github.com/loopbit/autoenv_fish
|
||||||
|
#
|
||||||
|
|
||||||
|
set AUTOENV_AUTH_FILE ~/.autoenv_authorized
|
||||||
|
if [ -z "$AUTOENV_ENV_FILENAME" ]
|
||||||
|
set AUTOENV_ENV_FILENAME ".env"
|
||||||
|
end
|
||||||
|
|
||||||
|
# probe to see if we have access to a shasum command, otherwise disable autoenv
|
||||||
|
if which gsha1sum 2>/dev/null >&2
|
||||||
|
# Okay
|
||||||
|
else if which sha1sum 2>/dev/null >&2
|
||||||
|
# Okay
|
||||||
|
else if which shasum 2>/dev/null >&2
|
||||||
|
# Okay
|
||||||
|
else
|
||||||
|
echo "Autoenv cannot locate a compatible shasum binary; not enabling"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# This function will be automatically called on directory change
|
||||||
|
# without needing to override the `cd` command.
|
||||||
|
function autoenv_init --on-variable PWD
|
||||||
|
set defIFS $IFS
|
||||||
|
set IFS (echo -en "\n\b")
|
||||||
|
|
||||||
|
set target $argv[1]
|
||||||
|
set home (dirname $HOME)
|
||||||
|
set search_dir $PWD
|
||||||
|
|
||||||
|
while [ $search_dir != / -a $search_dir != "$home" ]
|
||||||
|
set file "$search_dir/$AUTOENV_ENV_FILENAME"
|
||||||
|
if [ -e $file ]
|
||||||
|
set files $files $file
|
||||||
|
end
|
||||||
|
set search_dir (dirname $search_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
set numerator (count $files)
|
||||||
|
if [ $numerator -gt 0 ]
|
||||||
|
for x in (seq $numerator)
|
||||||
|
set envfile $files[$x]
|
||||||
|
autoenv_check_authz_and_run "$envfile"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set IFS $defIFS
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_run
|
||||||
|
set file "(realpath "$argv[1]")"
|
||||||
|
autoenv_check_authz_and_run "$file"
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_env
|
||||||
|
builtin echo "autoenv:" "$argv[1]"
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_printf
|
||||||
|
builtin printf "autoenv: "
|
||||||
|
builtin printf "$argv[1]"
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_indent
|
||||||
|
cat -e $argv[1] | sed 's/.*/autoenv: &/'
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_hashline
|
||||||
|
# typeset envfile hash
|
||||||
|
set envfile $argv[1]
|
||||||
|
set hash (shasum "$envfile" | cut -d' ' -f 1)
|
||||||
|
echo "$envfile:$hash"
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_check_authz
|
||||||
|
# typeset envfile hash
|
||||||
|
set envfile $argv[1]
|
||||||
|
set hash (autoenv_hashline "$envfile")
|
||||||
|
touch $AUTOENV_AUTH_FILE
|
||||||
|
grep -Gq "$hash" $AUTOENV_AUTH_FILE
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_check_authz_and_run
|
||||||
|
set envfile $argv[1]
|
||||||
|
if autoenv_check_authz "$envfile"
|
||||||
|
autoenv_source "$envfile"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
if [ -z $MC_SID ] #make sure mc is not running
|
||||||
|
autoenv_env
|
||||||
|
autoenv_env "WARNING:"
|
||||||
|
autoenv_env "This is the first time you are about to source $envfile":
|
||||||
|
autoenv_env
|
||||||
|
autoenv_env " --- (begin contents) ---------------------------------------"
|
||||||
|
autoenv_indent "$envfile"
|
||||||
|
autoenv_env " --- (end contents) -----------------------------------------"
|
||||||
|
autoenv_env
|
||||||
|
autoenv_printf "Are you sure you want to allow this? (y/N) \n"
|
||||||
|
read answer
|
||||||
|
if [ $answer = y -o $answer = Y ]
|
||||||
|
autoenv_authorize_env "$envfile"
|
||||||
|
autoenv_source "$envfile"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_deauthorize_env
|
||||||
|
#typeset envfile
|
||||||
|
set envfile $argv[1]
|
||||||
|
cp "$AUTOENV_AUTH_FILE" "$AUTOENV_AUTH_FILE.tmp"
|
||||||
|
grep -Gv "$envfile:" "$AUTOENV_AUTH_FILE.tmp" >$AUTOENV_AUTH_FILE
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_authorize_env
|
||||||
|
#typeset envfile
|
||||||
|
set envfile $argv[1]
|
||||||
|
autoenv_deauthorize_env "$envfile"
|
||||||
|
autoenv_hashline "$envfile" >>$AUTOENV_AUTH_FILE
|
||||||
|
end
|
||||||
|
|
||||||
|
function autoenv_source
|
||||||
|
#TODO: Why are global vars not being passed to sourced script?
|
||||||
|
set -g AUTOENV_CUR_FILE $argv[1]
|
||||||
|
set -g AUTOENV_CUR_DIR (dirname $argv[1])
|
||||||
|
source "$argv[1]"
|
||||||
|
#set -e AUTOENV_CUR_FILE
|
||||||
|
#set -e AUTOENV_CUR_DIR
|
||||||
|
end
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
function aiq -d "Ask Codex for a fish shell command"
|
|
||||||
if test (count $argv) -eq 0
|
|
||||||
echo "Usage: aiq <what you want to do>" >&2
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if not command -q codex
|
|
||||||
echo "aiq: codex is not installed or is not in PATH" >&2
|
|
||||||
return 127
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l need (string join ' ' -- $argv)
|
|
||||||
set -l prompt "You are suggesting a shell command for the fish shell. Do not run the command or modify any files. The user needs to: $need
|
|
||||||
|
|
||||||
Output the suggested command first. Follow it with additional details only when they are very important, or when there is relevant optional configuration the user should know about. Keep the response concise and do not wrap the command in backticks."
|
|
||||||
|
|
||||||
# Codex writes progress and run metadata to stderr; keep this helper focused
|
|
||||||
# on the final answer, but surface stderr when the request fails.
|
|
||||||
set -l errors (mktemp); or return 1
|
|
||||||
command codex exec --ephemeral --sandbox read-only --skip-git-repo-check "$prompt" 2>$errors
|
|
||||||
set -l result $status
|
|
||||||
|
|
||||||
if test $result -ne 0
|
|
||||||
command cat $errors >&2
|
|
||||||
end
|
|
||||||
command rm -f $errors
|
|
||||||
|
|
||||||
return $result
|
|
||||||
end
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
function del-wt -d "Remove current git worktree and cd into the main worktree"
|
|
||||||
set -l main_wt (git worktree list --porcelain | head -1 | string replace "worktree " "")
|
|
||||||
set -l current (git rev-parse --show-toplevel 2>/dev/null)
|
|
||||||
cd "$main_wt"; or return 1
|
|
||||||
git worktree remove "$current" # git handles dirty/main-worktree guards itself
|
|
||||||
end
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
function dev-claude -d "Start Claude with a durable session id so tmux-resurrect can restore the conversation"
|
|
||||||
# tmux-resurrect saves each pane's command line verbatim and replays it on
|
|
||||||
# restore. Pinning the session id at launch is what lets a restored pane
|
|
||||||
# rejoin its exact conversation; cwd alone can't disambiguate, since several
|
|
||||||
# Claude sessions often share one worktree.
|
|
||||||
|
|
||||||
# Run Claude inside a transient scope in claude.slice so a runaway subprocess
|
|
||||||
# tree hits that slice's memory cap and is killed there, instead of thrashing
|
|
||||||
# the whole machine into a lockup. See ~/.dotfiles/docs/claude-resource-limits.md
|
|
||||||
#
|
|
||||||
# The /usr/bin/env shim is load-bearing, in two ways:
|
|
||||||
# 1. systemd-run resolves its target through PATH and rewrites argv[0] to an
|
|
||||||
# absolute path. tmux-resurrect matches panes with an anchored "^claude "
|
|
||||||
# regex, so without the shim the saved command becomes an absolute path,
|
|
||||||
# stops matching, and pane restore silently dies. env re-execs with argv[0]
|
|
||||||
# as a bare "claude", keeping the saved command byte-identical.
|
|
||||||
# 2. env does the PATH lookup that "command" would do here, so a fish
|
|
||||||
# function or alias named claude still can't shadow the real binary.
|
|
||||||
set -l launch /usr/bin/env claude
|
|
||||||
if command -q systemd-run
|
|
||||||
set launch systemd-run --user --scope --quiet --collect --slice=claude.slice -- /usr/bin/env claude
|
|
||||||
end
|
|
||||||
|
|
||||||
# Never exec here. tmux-resurrect's ps save strategy reads the *children* of
|
|
||||||
# the pane pid (`ps -ao ppid,args | grep "^$pane_pid"`), so exec'ing would make
|
|
||||||
# claude the pane pid itself and the saved command would be one of claude's own
|
|
||||||
# subprocesses — restore then silently does nothing. Keeping fish as the pane
|
|
||||||
# process is what puts "claude --session-id <uuid>" where resurrect looks.
|
|
||||||
# Claude exiting drops back to a prompt rather than closing the pane.
|
|
||||||
|
|
||||||
# Restore path: resurrect replays the id we launched with. Claude rejects
|
|
||||||
# --session-id for an id that already exists, so switch to --resume once a
|
|
||||||
# transcript for it is on disk.
|
|
||||||
if test (count $argv) -ge 2
|
|
||||||
and contains -- $argv[1] --session-id --resume
|
|
||||||
and string match -qir '^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$' -- $argv[2]
|
|
||||||
set -l id $argv[2]
|
|
||||||
set -l transcript (find $HOME/.claude/projects -maxdepth 2 -name "$id.jsonl" -print -quit)
|
|
||||||
if test -n "$transcript"
|
|
||||||
$launch --resume $id $argv[3..]
|
|
||||||
return
|
|
||||||
end
|
|
||||||
$launch --session-id $id $argv[3..]
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
# An explicit session flag means the caller is choosing the conversation.
|
|
||||||
for flag in -c --continue -r --resume --session-id
|
|
||||||
if contains -- $flag $argv
|
|
||||||
$launch $argv
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
$launch --session-id (uuidgen) $argv
|
|
||||||
end
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
function dev-wt-pr -d "Open a GitHub PR in a worktree in a new tmux window (Octo review + Claude)" -a pr_number branch repo_path
|
|
||||||
if test -z "$pr_number"
|
|
||||||
echo "Usage: dev-wt-pr <pr-number> [branch] [repo-path]"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# Opens the PR in a new window of the current tmux session, so it must be run
|
|
||||||
# from inside tmux. Running outside tmux is unsupported for now.
|
|
||||||
if test -z "$TMUX"
|
|
||||||
echo "dev-wt-pr must be run from inside a tmux session"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# gh-dash passes the repo path; default to the current directory otherwise.
|
|
||||||
if test -n "$repo_path"
|
|
||||||
cd "$repo_path"; or return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l repo (__dev_repo_name 2>/dev/null)
|
|
||||||
if test -z "$repo"
|
|
||||||
echo "Not in a git repository"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# gh-dash passes the branch to save a lookup; manual callers can omit it and
|
|
||||||
# we resolve it from the PR number here.
|
|
||||||
if test -z "$branch"
|
|
||||||
set branch (gh pr view $pr_number --json headRefName --jq .headRefName 2>/dev/null)
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l main_wt_path (git worktree list --porcelain | head -1 | string replace "worktree " "")
|
|
||||||
set -l parent_dir (dirname "$main_wt_path")
|
|
||||||
# Key the worktree on the PR number to stay unique and avoid slashes in
|
|
||||||
# branch names producing nested directories.
|
|
||||||
set -l wt_path "$parent_dir/$repo-pr-$pr_number"
|
|
||||||
|
|
||||||
# Create the worktree if it doesn't already exist.
|
|
||||||
if not test -d "$wt_path"
|
|
||||||
echo "Creating worktree for PR #$pr_number at $wt_path..."
|
|
||||||
git worktree add --detach "$wt_path"; or return 1
|
|
||||||
|
|
||||||
# gh pr checkout handles both same-repo and fork PRs uniformly.
|
|
||||||
if not fish -c "cd '$wt_path'; and gh pr checkout $pr_number"
|
|
||||||
echo "Failed to check out PR #$pr_number"
|
|
||||||
git worktree remove --force "$wt_path"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# Run repo-specific setup hook if present (matches dev-wt).
|
|
||||||
set -l setup_hook "$main_wt_path/.worktree-setup.sh"
|
|
||||||
if test -x "$setup_hook"
|
|
||||||
echo "Running worktree setup hook..."
|
|
||||||
bash "$setup_hook" "$wt_path" "$main_wt_path" "$pr_number"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Window name like "123-fix-login"; flatten any slashes from the branch name.
|
|
||||||
set -l window_name "$pr_number-"(string replace -a '/' '-' -- $branch)
|
|
||||||
|
|
||||||
# Reuse the window if it already exists (e.g. re-pressing the shortcut).
|
|
||||||
if tmux list-windows -F '#{window_name}' 2>/dev/null | string match -q -- "$window_name"
|
|
||||||
tmux select-window -t "$window_name"
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
# Clean login shell so the worktree re-sources its environment.
|
|
||||||
set -l shell (__dev_clean_shell)
|
|
||||||
|
|
||||||
set -l win (tmux new-window -P -F '#{window_id}' -n "$window_name" -c "$wt_path" $shell)
|
|
||||||
tmux send-keys -t "$win" 'nvim -c "Octo review start"' Enter
|
|
||||||
|
|
||||||
set -l right (tmux split-window -h -P -F '#{pane_id}' -t "$win" -c "$wt_path" $shell)
|
|
||||||
tmux send-keys -t "$right" 'dev-claude' Enter
|
|
||||||
|
|
||||||
tmux select-window -t "$win"
|
|
||||||
end
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
function dev-wt -d "Switch to a tmux dev session for a worktree, creating it if needed"
|
|
||||||
argparse 'C/repo=' -- $argv
|
|
||||||
or return 1
|
|
||||||
|
|
||||||
set -l name $argv[1]
|
|
||||||
|
|
||||||
# With -C/--repo, behave exactly as if invoked from within that directory by
|
|
||||||
# cd-ing there for the duration and restoring the caller's cwd afterward.
|
|
||||||
if set -q _flag_repo
|
|
||||||
if not test -d "$_flag_repo"
|
|
||||||
echo "Not a directory: $_flag_repo"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
set -l prev_pwd $PWD
|
|
||||||
cd "$_flag_repo"; or return 1
|
|
||||||
__dev_wt_impl $name
|
|
||||||
set -l rc $status
|
|
||||||
cd "$prev_pwd"
|
|
||||||
return $rc
|
|
||||||
end
|
|
||||||
|
|
||||||
__dev_wt_impl $name
|
|
||||||
end
|
|
||||||
|
|
||||||
function __dev_wt_impl -a name
|
|
||||||
set -l repo (__dev_repo_name 2>/dev/null)
|
|
||||||
if test -z "$repo"
|
|
||||||
echo "Not in a git repository"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# No argument: switch to a session for the main repo (not a worktree)
|
|
||||||
if test -z "$name"
|
|
||||||
set -l main_wt_path (git worktree list --porcelain | head -1 | string replace "worktree " "")
|
|
||||||
set -l session (string replace -a '.' '-' -- $repo)
|
|
||||||
__dev_create_session $session $main_wt_path
|
|
||||||
__dev_attach_session $session
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l wt_path ""
|
|
||||||
set -l wt_branch ""
|
|
||||||
|
|
||||||
# Search for worktree by directory basename or branch name
|
|
||||||
set -l current_path ""
|
|
||||||
set -l current_branch ""
|
|
||||||
for line in (git worktree list --porcelain)
|
|
||||||
if string match -q "worktree *" -- $line
|
|
||||||
set current_path (string replace "worktree " "" -- $line)
|
|
||||||
set current_branch ""
|
|
||||||
else if string match -q "branch *" -- $line
|
|
||||||
set current_branch (string replace "branch refs/heads/" "" -- $line)
|
|
||||||
if test (basename "$current_path") = "$name"; or test "$current_branch" = "$name"
|
|
||||||
set wt_path $current_path
|
|
||||||
set wt_branch $current_branch
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# If worktree not found, create it
|
|
||||||
if test -z "$wt_path"
|
|
||||||
set -l main_wt_path (git worktree list --porcelain | head -1 | string replace "worktree " "")
|
|
||||||
set -l parent_dir (dirname "$main_wt_path")
|
|
||||||
set wt_path "$parent_dir/$repo-$name"
|
|
||||||
set wt_branch "$name"
|
|
||||||
|
|
||||||
if test -d "$wt_path"
|
|
||||||
echo "Error: directory already exists: $wt_path"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# Create branch from main if it doesn't exist
|
|
||||||
if not git rev-parse --verify "$wt_branch" >/dev/null 2>&1
|
|
||||||
echo "Creating branch '$wt_branch' from main..."
|
|
||||||
git branch "$wt_branch" main
|
|
||||||
end
|
|
||||||
|
|
||||||
echo "Creating worktree at $wt_path..."
|
|
||||||
git worktree add "$wt_path" "$wt_branch"
|
|
||||||
|
|
||||||
# Run repo-specific setup hook if present
|
|
||||||
set -l setup_hook "$main_wt_path/.worktree-setup.sh"
|
|
||||||
if test -x "$setup_hook"
|
|
||||||
echo "Running worktree setup hook..."
|
|
||||||
bash "$setup_hook" "$wt_path" "$main_wt_path" "$wt_branch"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -z "$wt_branch"
|
|
||||||
echo "Worktree '$name' has no branch (detached HEAD)"
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
set -l session (__dev_wt_session_name $repo $wt_branch)
|
|
||||||
__dev_create_session $session $wt_path
|
|
||||||
__dev_attach_session $session
|
|
||||||
end
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
function dev -d "Create and attach to a tmux dev session for the current directory"
|
|
||||||
set -l session (basename (pwd) | string replace -a '.' '-')
|
|
||||||
__dev_create_session $session
|
|
||||||
__dev_attach_session $session
|
|
||||||
end
|
|
||||||
@@ -6,7 +6,6 @@ function fish_prompt
|
|||||||
set -l red (set_color -o red)
|
set -l red (set_color -o red)
|
||||||
set -l green (set_color -o green)
|
set -l green (set_color -o green)
|
||||||
set -l blue (set_color -o blue)
|
set -l blue (set_color -o blue)
|
||||||
set -l magenta (set_color -o magenta)
|
|
||||||
set -l normal (set_color normal)
|
set -l normal (set_color normal)
|
||||||
|
|
||||||
set -l arrow_color "$green"
|
set -l arrow_color "$green"
|
||||||
@@ -21,11 +20,5 @@ function fish_prompt
|
|||||||
|
|
||||||
set -l cwd $cyan(prompt_pwd | path basename)
|
set -l cwd $cyan(prompt_pwd | path basename)
|
||||||
|
|
||||||
# distrobox exports CONTAINER_ID inside the container
|
echo -n -s $arrow ' '$cwd $repo_info $normal ' '
|
||||||
set -l box ""
|
|
||||||
if set -q CONTAINER_ID
|
|
||||||
set box $magenta"📦$CONTAINER_ID "
|
|
||||||
end
|
|
||||||
|
|
||||||
echo -n -s $arrow ' ' $box $cwd $repo_info $normal ' '
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
function pandoc -d "Convert a markdown file to docx using pandoc in podman" -a input
|
|
||||||
set -l output (string replace -r '\.md$' '.docx' $input)
|
|
||||||
|
|
||||||
podman run --rm -v "$(pwd):/data:z" \
|
|
||||||
--userns keep-id:uid=1000,gid=1000 \
|
|
||||||
pandoc/core:3.8-alpine --from markdown --to docx -o "/data/$output" "/data/$input"
|
|
||||||
end
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
function record -d "Capture a screen recording with no audio"
|
|
||||||
wf-recorder -c libopenh264 --no-audio -g "$(slurp)" -f output.mp4
|
|
||||||
end
|
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
function reencode -d "Re-encode a video to VBR with no audio" -a input
|
|
||||||
set -l name (string replace -r '\.[^.]+$' '' $input)
|
|
||||||
set -l ext (string replace -r '.*\.' '' $input)
|
|
||||||
set -l output "$name-reenc.$ext"
|
|
||||||
|
|
||||||
ffmpeg -i $input -c:v libopenh264 -crf 23 -preset slow -an $output
|
|
||||||
end
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
function togif -d "Convert a video to a high-quality gif" -a input
|
|
||||||
set -l name (string replace -r '\.[^.]+$' '' $input)
|
|
||||||
set -l output "$name.gif"
|
|
||||||
set -l palette (mktemp --suffix .png)
|
|
||||||
|
|
||||||
ffmpeg -i $input -vf "fps=15,palettegen" -y $palette
|
|
||||||
ffmpeg -i $input -i $palette -lavfi "fps=15 [x]; [x][1:v] paletteuse" -y $output
|
|
||||||
|
|
||||||
rm -f $palette
|
|
||||||
end
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
function tomov -d "Convert a video to a GitHub-uploadable .mov" -a input
|
|
||||||
set -l name (string replace -r '\.[^.]+$' '' $input)
|
|
||||||
set -l output "$name.mov"
|
|
||||||
|
|
||||||
ffmpeg -i $input -c:v libopenh264 -crf 23 -preset slow -pix_fmt yuv420p -an $output
|
|
||||||
end
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
prSections:
|
|
||||||
- title: My Pull Requests
|
|
||||||
filters: is:open author:@me
|
|
||||||
- title: Needs My Review
|
|
||||||
filters: is:open review-requested:@me
|
|
||||||
- title: Involved
|
|
||||||
filters: is:open involves:@me -author:@me
|
|
||||||
issuesSections:
|
|
||||||
- title: My Issues
|
|
||||||
filters: is:open author:@me
|
|
||||||
- title: Assigned
|
|
||||||
filters: is:open assignee:@me
|
|
||||||
- title: Involved
|
|
||||||
filters: is:open involves:@me -author:@me
|
|
||||||
notificationsSections:
|
|
||||||
- title: All
|
|
||||||
filters: ""
|
|
||||||
- title: Created
|
|
||||||
filters: reason:author
|
|
||||||
- title: Participating
|
|
||||||
filters: reason:participating
|
|
||||||
- title: Mentioned
|
|
||||||
filters: reason:mention
|
|
||||||
- title: Review Requested
|
|
||||||
filters: reason:review-requested
|
|
||||||
- title: Assigned
|
|
||||||
filters: reason:assign
|
|
||||||
- title: Subscribed
|
|
||||||
filters: reason:subscribed
|
|
||||||
- title: Team Mentioned
|
|
||||||
filters: reason:team-mention
|
|
||||||
repo:
|
|
||||||
branchesRefetchIntervalSeconds: 30
|
|
||||||
prsRefetchIntervalSeconds: 60
|
|
||||||
defaults:
|
|
||||||
preview:
|
|
||||||
open: true
|
|
||||||
width: 0.45
|
|
||||||
height: 0.6
|
|
||||||
position: auto
|
|
||||||
prsLimit: 20
|
|
||||||
prApproveComment: LGTM
|
|
||||||
issuesLimit: 20
|
|
||||||
notificationsLimit: 20
|
|
||||||
view: prs
|
|
||||||
layout:
|
|
||||||
prs:
|
|
||||||
updatedAt:
|
|
||||||
width: 5
|
|
||||||
createdAt:
|
|
||||||
width: 5
|
|
||||||
repo:
|
|
||||||
width: 20
|
|
||||||
author:
|
|
||||||
width: 15
|
|
||||||
authorIcon:
|
|
||||||
hidden: false
|
|
||||||
labels:
|
|
||||||
width: 22
|
|
||||||
hidden: true
|
|
||||||
assignees:
|
|
||||||
width: 20
|
|
||||||
hidden: true
|
|
||||||
base:
|
|
||||||
width: 15
|
|
||||||
hidden: true
|
|
||||||
lines:
|
|
||||||
width: 15
|
|
||||||
issues:
|
|
||||||
updatedAt:
|
|
||||||
width: 5
|
|
||||||
createdAt:
|
|
||||||
width: 5
|
|
||||||
repo:
|
|
||||||
width: 15
|
|
||||||
creator:
|
|
||||||
width: 10
|
|
||||||
creatorIcon:
|
|
||||||
hidden: false
|
|
||||||
assignees:
|
|
||||||
width: 20
|
|
||||||
hidden: true
|
|
||||||
refetchIntervalMinutes: 30
|
|
||||||
keybindings:
|
|
||||||
prs:
|
|
||||||
- key: C
|
|
||||||
name: review in worktree
|
|
||||||
command: >
|
|
||||||
fish -c "dev-wt-pr {{.PrNumber}} {{.HeadRefName}} {{.RepoPath}}"
|
|
||||||
universal:
|
|
||||||
- key: g
|
|
||||||
name: lazygit
|
|
||||||
command: >
|
|
||||||
cd {{.RepoPath}}; lazygit
|
|
||||||
repoPaths: {}
|
|
||||||
theme:
|
|
||||||
colors:
|
|
||||||
text:
|
|
||||||
primary: "#4c4f69"
|
|
||||||
secondary: "#179299"
|
|
||||||
inverted: "#dce0e8"
|
|
||||||
faint: "#5c5f77"
|
|
||||||
warning: "#df8e1d"
|
|
||||||
success: "#40a02b"
|
|
||||||
error: "#d20f39"
|
|
||||||
background:
|
|
||||||
selected: "#ccd0da"
|
|
||||||
border:
|
|
||||||
primary: "#179299"
|
|
||||||
secondary: "#bcc0cc"
|
|
||||||
faint: "#ccd0da"
|
|
||||||
ui:
|
|
||||||
sectionsShowCount: true
|
|
||||||
table:
|
|
||||||
showSeparator: true
|
|
||||||
compact: false
|
|
||||||
pager:
|
|
||||||
diff: hunk
|
|
||||||
confirmQuit: false
|
|
||||||
showAuthorIcons: true
|
|
||||||
smartFilteringAtLaunch: true
|
|
||||||
includeReadNotifications: true
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
theme = Catppuccin Latte
|
|
||||||
font-family = JetBrainsMono Nerd Font Mono
|
|
||||||
|
|
||||||
# https://pi.dev/docs/latest/terminal-setup
|
|
||||||
keybind = alt+backspace=text:\x1b\x7f
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
theme = "catppuccin-latte"
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"lastSeenCliVersion": "0.14.1"
|
|
||||||
}
|
|
||||||
@@ -7,9 +7,6 @@ promptToReturnFromSubprocess: false # removes "press enter to return to lazygit"
|
|||||||
notARepository: 'skip'
|
notARepository: 'skip'
|
||||||
git:
|
git:
|
||||||
autoForwardBranches: "none"
|
autoForwardBranches: "none"
|
||||||
diffRenderers:
|
|
||||||
- command: delta --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"
|
|
||||||
- command: delta --paging=never --line-numbers --no-gitconfig --light
|
|
||||||
os:
|
os:
|
||||||
editPreset: "nvim"
|
editPreset: "nvim"
|
||||||
gui:
|
gui:
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
[settings]
|
|
||||||
|
|
||||||
# Pin exact versions, URLs, and checksums in mise.lock so a tampered
|
|
||||||
# re-release of the same version can't slip in silently.
|
|
||||||
lockfile = true
|
|
||||||
|
|
||||||
# Block the asdf plugin backend (arbitrary community shell scripts) in favor
|
|
||||||
# of aqua/ubi/core backends, which verify checksums and signatures. Remove if
|
|
||||||
# a tool you need exists only as an asdf plugin.
|
|
||||||
disable_backends = ["asdf", "ubi"]
|
|
||||||
|
|
||||||
# Never install versions less than 2 weeks old.
|
|
||||||
minimum_release_age = "14d"
|
|
||||||
|
|
||||||
[tools]
|
|
||||||
# Short names resolve to the aqua backend (checksum + signature verified)
|
|
||||||
"aqua:F1bonacc1/process-compose" = "1.120.0"
|
|
||||||
"aqua:neovim/neovim" = "0.12.4"
|
|
||||||
"github:Satty-org/Satty" = "0.20.1"
|
|
||||||
"npm:@anthropic-ai/sandbox-runtime" = "0.0.67"
|
|
||||||
"npm:@earendil-works/pi-coding-agent" = "0.83.0"
|
|
||||||
"npm:typescript" = "7.0.2"
|
|
||||||
"npm:typescript-language-server" = "5.3.0"
|
|
||||||
bat = "0.26.1"
|
|
||||||
btop = "1.4.7"
|
|
||||||
delta = "0.19.2"
|
|
||||||
dive = "0.13.1"
|
|
||||||
eza = "0.23.5"
|
|
||||||
fd = "10.4.2"
|
|
||||||
gh = "2.97.0"
|
|
||||||
jq = "1.8.2"
|
|
||||||
lazygit = "0.64.1"
|
|
||||||
node = "24.18.1"
|
|
||||||
opencode = "1.15.11"
|
|
||||||
pnpm = "11.18.0"
|
|
||||||
restic = "0.19.1"
|
|
||||||
ripgrep = "15.2.0"
|
|
||||||
tmux = "3.7b"
|
|
||||||
uv = "0.12.0"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
// markdownlint-cli2 base config used by nvim-lint (see lua/plugins/markdown.lua).
|
|
||||||
"config": {
|
|
||||||
// Disable line-length errors; long lines are soft-wrapped on purpose.
|
|
||||||
"MD013": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +1,38 @@
|
|||||||
{
|
{
|
||||||
"LazyVim": { "branch": "main", "commit": "c10948c50b18fae7f256433afdef09e432410480" },
|
"LazyVim": { "branch": "main", "commit": "28db03f958d58dfff3c647ce28fdc1cb88ac158d" },
|
||||||
"SchemaStore.nvim": { "branch": "main", "commit": "d34c58439271f5e73ed79c2b8d1a09731c4525f1" },
|
"SchemaStore.nvim": { "branch": "main", "commit": "5f2299987a1937612c910f00db39156bab6a6b35" },
|
||||||
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
|
"blink.cmp": { "branch": "main", "commit": "b19413d214068f316c78978b08264ed1c41830ec" },
|
||||||
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
"catppuccin": { "branch": "main", "commit": "ce4a8e0d5267e67056f9f4dcf6cb1d0933c8ca00" },
|
||||||
"catppuccin": { "branch": "main", "commit": "edefef779ab08ce1a4a404713e3012b0d202bd35" },
|
"conform.nvim": { "branch": "master", "commit": "4993e07fac6679d0a5005aa7499e0bad2bd39f19" },
|
||||||
"claude-review.nvim": { "branch": "main", "commit": "899a33a04d26b6a75384dda176e212eee75a2532" },
|
"flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" },
|
||||||
"conform.nvim": { "branch": "master", "commit": "016802de402556da54c36bd7359b441266b01cdd" },
|
"focus.nvim": { "branch": "master", "commit": "26a755c363284547196ceb258a83f92608d7979b" },
|
||||||
"diffview.nvim": { "branch": "main", "commit": "43e60bca414e4991ed10118e59f809fb03bbeddd" },
|
"friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" },
|
||||||
"flash.nvim": { "branch": "main", "commit": "5f0f270fdc7c5b0c21d903ee85b9cb06f2ac636a" },
|
"gitsigns.nvim": { "branch": "main", "commit": "5813e4878748805f1518cee7abb50fd7205a3a48" },
|
||||||
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
|
"grug-far.nvim": { "branch": "main", "commit": "b58b2d65863f4ebad88b10a1ddd519e5380466e0" },
|
||||||
"gitsigns.nvim": { "branch": "main", "commit": "42d6aed4e94e0f0bbced16bbdcc42f57673bd75e" },
|
|
||||||
"grug-far.nvim": { "branch": "main", "commit": "11595bf747edc270bce2069d1020502ad4ae56cf" },
|
|
||||||
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||||
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
|
"lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" },
|
||||||
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
|
"lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" },
|
||||||
"markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
|
"markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
|
||||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "40276c4df7e6bdce6801d6c035c6227f9115a855" },
|
"mason-lspconfig.nvim": { "branch": "main", "commit": "7d527c76c43f46294de9c19d39c5a86317809b4b" },
|
||||||
"mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
|
"mason.nvim": { "branch": "main", "commit": "57e5a8addb8c71fb063ee4acda466c7cf6ad2800" },
|
||||||
"mini.ai": { "branch": "main", "commit": "25248c6aa002391936a6200f12d1466015987133" },
|
"mini.ai": { "branch": "main", "commit": "bfb26d9072670c3aaefab0f53024b2f3729c8083" },
|
||||||
"mini.diff": { "branch": "main", "commit": "626b8a5b93874c4d05ca25aedec56cfff0b378fb" },
|
"mini.icons": { "branch": "main", "commit": "ff2e4f1d29f659cc2bad0f9256f2f6195c6b2428" },
|
||||||
"mini.icons": { "branch": "main", "commit": "98faae31e9be1cc054ae63485e58ceb185efcad0" },
|
"mini.pairs": { "branch": "main", "commit": "472ec50092a3314ec285d2db2baa48602d71fe93" },
|
||||||
"mini.pairs": { "branch": "main", "commit": "b1c5a726921b7a8c9321e9a7a208aa0571de5810" },
|
"mini.surround": { "branch": "main", "commit": "88c52297ed3e69ecf9f8652837888ecc727a28ee" },
|
||||||
"mini.surround": { "branch": "main", "commit": "8d5d0c5aa92449368ac251e85451d79d8f69d296" },
|
|
||||||
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
|
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
|
||||||
"nui.nvim": { "branch": "main", "commit": "10fc361835c856ba4233ef5ea135b919bf3dce97" },
|
"nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
|
||||||
"nvim-ansible": { "branch": "main", "commit": "c7f595d568b588942d4d0c37b5cd6cae3764a148" },
|
"nvim-lint": { "branch": "master", "commit": "d1118791070d090777398792a73032a0ca5c79ff" },
|
||||||
"nvim-lint": { "branch": "master", "commit": "3d55c8f67c6ae5c15e1042571e107c7a3d5c5f4e" },
|
"nvim-lspconfig": { "branch": "master", "commit": "b2441c9374699685991959f50e5e6293c509e501" },
|
||||||
"nvim-lspconfig": { "branch": "master", "commit": "16286347bdba1333c7d124d9de9fe6630731b2b2" },
|
"nvim-treesitter": { "branch": "main", "commit": "17885756e63df73ed90db62e4630f744ceda6514" },
|
||||||
"nvim-treesitter": { "branch": "main", "commit": "19071296d3d643b48615ee574a20e8a03ac40872" },
|
"nvim-treesitter-textobjects": { "branch": "main", "commit": "63c4dce4a56312ef1bdeafd16bdefa008fcc950a" },
|
||||||
"nvim-treesitter-textobjects": { "branch": "main", "commit": "898ee307df58f854d11cd7edd06472574d48014e" },
|
"nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" },
|
||||||
"nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
|
|
||||||
"octo.nvim": { "branch": "master", "commit": "af2411604b51cb4a0f3e2de50b1b7cacc2581c48" },
|
|
||||||
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
|
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
|
||||||
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
|
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
|
||||||
"render-markdown.nvim": { "branch": "main", "commit": "4663eb3ecd538bd5062628fb6d95bbe6bdca78f6" },
|
"render-markdown.nvim": { "branch": "main", "commit": "6e0e8902dac70fecbdd8ce557d142062a621ec38" },
|
||||||
"sidekick.nvim": { "branch": "main", "commit": "208e1c5b8170c01fd1d07df0139322a76479b235" },
|
"snacks.nvim": { "branch": "main", "commit": "fe7cfe9800a182274d0f868a74b7263b8c0c020b" },
|
||||||
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
|
|
||||||
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
|
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
|
||||||
"tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" },
|
|
||||||
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
||||||
"ts-comments.nvim": { "branch": "main", "commit": "a59d6092213447450191122c9346f309161504cb" },
|
"ts-comments.nvim": { "branch": "main", "commit": "123a9fb12e7229342f807ec9e6de478b1102b041" },
|
||||||
"vim-tmux-navigator": { "branch": "master", "commit": "e41c431a0c7b7388ae7ba341f01a0d217eb3a432" },
|
"vim-tmux-navigator": { "branch": "master", "commit": "c45243dc1f32ac6bcf6068e5300f3b2b237e576a" },
|
||||||
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user