From a134b7fa933fb393bbf384bfdd793d941e85d9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Jane=C5=BEi=C4=8D?= Date: Mon, 2 Nov 2026 10:37:19 +0100 Subject: [PATCH] chore(claude): update lead skill --- claude/agents/builder-opus.md | 2 ++ claude/agents/integrator.md | 4 +++- claude/settings.json | 4 ++-- claude/skills/builder/SKILL.md | 2 +- claude/skills/lead/SKILL.md | 8 ++++---- claude/skills/lead/notes.md | 2 +- claude/skills/lead/notes.py | 36 +++++++++++++++++++++++++++++----- 7 files changed, 44 insertions(+), 14 deletions(-) diff --git a/claude/agents/builder-opus.md b/claude/agents/builder-opus.md index e27aaaf..3c10218 100644 --- a/claude/agents/builder-opus.md +++ b/claude/agents/builder-opus.md @@ -5,6 +5,8 @@ model: opus skills: - ground-rules - builder +experimental: + cacheTtl: 1h --- You are a builder; follow the preloaded builder instructions and the brief in your prompt. diff --git a/claude/agents/integrator.md b/claude/agents/integrator.md index 5f56816..4f8c7b2 100644 --- a/claude/agents/integrator.md +++ b/claude/agents/integrator.md @@ -3,6 +3,8 @@ name: integrator description: Owns one unit of work in Matej's projects - builds it on a branch, alone or with builder agents in parallel, reviews and merges their parts, runs every check, and hands back one branch that already works. Use when the lead delegates implementation from a brief. skills: - ground-rules +experimental: + cacheTtl: 1h --- You are the integrator for one unit of work. The lead that briefed you talks to Matej; you don't, so when the brief leaves a choice, take the simplest option that works, keep going, and list it under "decided along the way". @@ -11,7 +13,7 @@ Read `~/.config/claude/CLAUDE.md`, the project's AGENTS.md and the house-rule sk You own the result: a branch that works, lint and format clean, every suite green the way CI runs it, UI looked at in a browser, reviewed for bugs. Parts that don't step on each other go to builder agents in parallel, at most 2–3 at a time; read each part's diff before you merge it. Run code-review and simplify once, on your integrated branch, not per part. A fix-up after a builder has replied goes to a fresh builder with a short brief naming its branch. Never merge into the main branch, push or sign. Remove every branch, worktree, container and server you or your builders created once merged or unneeded, and nothing else. -Briefs to builders carry none of the ground rules: the builder agents preload the ground-rules skill. +Briefs to builders carry none of the ground rules: the builder agents preload the ground-rules skill. Each builder brief names the contracts its part shares with others (names, payload fields) and ends with your agent id, for its questions (the lead sends you your id after spawning you). ## Reply diff --git a/claude/settings.json b/claude/settings.json index f9c6907..168e380 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -24,10 +24,10 @@ } }, "effortLevel": "high", - "subagentPromptCacheTtl": "1h", "tui": "default", "skipDangerousModePermissionPrompt": true, "theme": "dark", "voiceEnabled": true, - "mcpServers": {} + "mcpServers": {}, + "idleCompaction": false } diff --git a/claude/skills/builder/SKILL.md b/claude/skills/builder/SKILL.md index e9b93fe..08a99ca 100644 --- a/claude/skills/builder/SKILL.md +++ b/claude/skills/builder/SKILL.md @@ -6,7 +6,7 @@ user-invocable: false # Builder -You build one part of a unit of work that an integrator owns, from its brief. Stay inside the brief: anything outside it is a leftover in your reply, not a change on your branch. When the brief leaves a choice, take the simplest option and list it under "decided along the way". +You build one part of a unit of work that an integrator owns, from its brief. Stay inside the brief: anything outside it is a leftover in your reply, not a change on your branch. When the brief leaves a choice, take the simplest option and list it under "decided along the way"; when it depends on another part or on context the brief left out, ask your integrator instead, by SendMessage to the id the brief names, and `main` only if that gets no answer or it's the lead's call. Read `~/.config/claude/CLAUDE.md`, the project's AGENTS.md and the house-rule skills it points to before writing code. Work on the branch and worktree the brief names; a few logical commits, never sign, merge or push. Run the checks the brief lists, only for what you touched, and stop your own servers and containers before replying. diff --git a/claude/skills/lead/SKILL.md b/claude/skills/lead/SKILL.md index fed3f56..32ecd87 100644 --- a/claude/skills/lead/SKILL.md +++ b/claude/skills/lead/SKILL.md @@ -31,12 +31,12 @@ The pair types carry their model in their frontmatter; never pass a `model` when - Discuss, lock, relay. Only locked decisions go to agents, in the words agreed. When a relay replaces an earlier one, say what it replaces, without restating the brief; when unsure what Matej meant, ask first. - Agents keep going while the next point is discussed; new locked items join the open branch. -- Agents decide what the brief doesn't settle the simple way and list it as "decided along the way". Only the lead asks Matej. +- Agents decide what the brief doesn't settle the simple way and list it as "decided along the way". Only the lead asks Matej. The lead relays a builder's question to its integrator rather than answering it. - Once an agent has replied, it is done. A fix-up, rename or check goes to a fresh agent with a short brief naming the branch; a message to a running agent is only for something inside the brief it is still on. ## Spawning -Use the agent types above, not a general-purpose agent, so Matej sees who is doing what. The label is set once, at spawn: make it the unit of work ("graph redesign", "signing agent commits"), never its first step. +Use the agent types above, not a general-purpose agent, so Matej sees who is doing what. The label is set once, at spawn: make it the unit of work ("graph redesign", "signing agent commits"), never its first step. Right after spawning an integrator, SendMessage it its own agent id: integrators have no name, so its builders can't find it otherwise. ## A brief carries @@ -63,7 +63,7 @@ A change to this skill, the agent definitions or the notes conventions is kept w - Leftovers: `followups/`, by area, at close-out. `questions/` is Matej's own. - Visual design to discuss: the Design canvas (an Artifact), kept current; old frames removed, not piled up. - Prototypes and finished work: branches. The repo's `.git` is on Matej's host, so he can `git worktree add --detach` any branch. -- Bulk files for Matej (screenshots, logs): the host handoff mount once it exists; never the project repo, never bulk in `.notes/`. +- Bulk files for Matej (screenshots, logs): `$SANDBOX_VM_STATE/-/`, with the full paths in the reply; never the project repo, never bulk in `.notes/`. ## Notes @@ -72,7 +72,7 @@ Plans, decisions, work handoffs, follow-ups and Matej's questions live in the pr ## Ownership and clean-up - Everything a unit of work creates carries its work id: branches `/` (see Git), worktrees under `$SANDBOX_VM_STATE/wt//`, containers `-`. Agents remove what they created and nothing else. -- The lead keeps `made.md` in the work item: each branch, worktree, container and handoff folder, with its state (open, merged, prototype); whether a branch is in main, `notes.py status` reads from git. +- The lead keeps `made.md` in the work item: each branch, worktree, container and state folder, with its state (open, merged, prototype); whether a branch is in main, `notes.py status` reads from git. - Close-out removes the list, then checks nothing under the work id is left. ## Interruptions diff --git a/claude/skills/lead/notes.md b/claude/skills/lead/notes.md index 2ddace2..8fbd798 100644 --- a/claude/skills/lead/notes.md +++ b/claude/skills/lead/notes.md @@ -97,7 +97,7 @@ Settling: check the repo first, fold the outcome into a doc or work item if wort ## Other -`other/out/` holds documents for someone else, written for that reader, and the few small files that go with them; no ids, no status line. HTML there follows the doc skeleton with `../../notes.css`; the eyebrow names the reader and date (`for the team · 2026-10-14`). What gets sent is the generated `_out/`; look at it before handing it over, link it from the work item's `## Your move` (the index doesn't list `other/`), then `commit "other: "`. Bulk output for a person (screenshots, builds, logs) goes to the host handoff mount, not `.notes/`. +`other/out/` holds documents for someone else, written for that reader, and the few small files that go with them; no ids, no status line. HTML there follows the doc skeleton with `../../notes.css`; the eyebrow names the reader and date (`for the team · 2026-10-14`). What gets sent is the generated `_out/`; look at it before handing it over, link it from the work item's `## Your move` (the index doesn't list `other/`), then `commit "other: "`. Bulk output for a person (screenshots, builds, logs) stays in `$SANDBOX_VM_STATE`, named by full path, not `.notes/`. ## Never diff --git a/claude/skills/lead/notes.py b/claude/skills/lead/notes.py index 7c41521..ddad48d 100755 --- a/claude/skills/lead/notes.py +++ b/claude/skills/lead/notes.py @@ -1034,7 +1034,26 @@ def file_reply(notes: Path, work: str, part: str, ref: str, checked: str) -> Non # serve: every project's notes on one port -SKIP_DIRS = {"node_modules", "target", "dist", "build", "vendor", "__pycache__"} +SKIP_DIRS = { + "node_modules", + "target", + "dist", + "build", + "vendor", + "__pycache__", + "cache", +} +# browser profiles under ~/.config: thousands of directories, never a project +SKIP_PATHS = {"config/chromium", "config/google-chrome", "config/BraveSoftware"} + + +def is_claude_config(d: Path) -> bool: + """claude code's config dir: its subdirectories are transcripts, sessions and skills""" + return ( + d.name.lstrip(".") == "claude" + and (d / "settings.json").is_file() + and (d / "projects").is_dir() + ) def find_notes(roots: list[Path], depth: int = 6) -> dict[str, Path]: @@ -1049,14 +1068,18 @@ def find_notes(roots: list[Path], depth: int = 6) -> dict[str, Path]: # a project's subdirectories are its code, even when the project is a root return # a repo's own subdirectories are its code, not more projects - if left == 0 or ((d / ".git").exists() and d != root): + if left == 0 or ((d / ".git").exists() and d != root) or is_claude_config(d): return try: children = sorted(d.iterdir()) except OSError: return for c in children: - skip = c.name.startswith(".") or c.name in SKIP_DIRS + skip = ( + c.name.startswith(".") + or c.name in SKIP_DIRS + or f"{d.name.lstrip('.')}/{c.name}" in SKIP_PATHS + ) if c.is_dir() and not c.is_symlink() and not skip: walk(c, root, left - 1) @@ -1298,8 +1321,10 @@ def serve(roots: list[Path], host: str, port: int) -> None: page = (notes / "index.html").read_text() page = page.replace("", "" + search_form("search"), 1) return self.send_text(page, "text/html; charset=utf-8") + # self.path stays the full request path, so the base class's redirect of a + # directory without its trailing slash keeps the project prefix self.serve_from = notes - self.path = "/" + rest + self.serve_prefix = prefix return super().do_GET() def do_POST(self) -> None: @@ -1351,7 +1376,8 @@ def serve(roots: list[Path], host: str, port: int) -> None: def translate_path(self, path: str) -> str: base = self.serve_from.resolve() - target = (base / unquote(urlsplit(path).path).lstrip("/")).resolve() + rest = unquote(urlsplit(path).path)[len(self.serve_prefix) + 1 :] + target = (base / rest.lstrip("/")).resolve() return str(target if target.is_relative_to(base) else base / "__outside__") def log_message(self, format: str, *args) -> None: