Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
01e2d58
feat(question): a decision post can carry the number the chat reply g…
genesiscz Oct 18, 2026
c7f1a92
feat(skills, babysit): tmp-reclaim --own-session reclaims the running…
genesiscz Oct 18, 2026
aa7bc0f
docs(skills, babysit): Martin's 2026-10-18 rules: round 3 fixes only …
genesiscz Oct 18, 2026
ce3c083
fix(question): a decision the agent got answered in chat moves to ack…
genesiscz Oct 21, 2026
dab09f1
fix(question): an item that carries the post's wait, or a source, nam…
genesiscz Oct 21, 2026
57fb24b
feat(tools): every failed tool run lands in a day's failures log with…
genesiscz Oct 22, 2026
f7da1c0
feat(tools): tools genesis-tools logs lists, groups and shows failed …
genesiscz Oct 22, 2026
9ff042d
docs(tools): the failures log and tools genesis-tools logs are where …
genesiscz Oct 22, 2026
d58ffba
fix(tools): a failure's message is the clack error line or the JSON e…
genesiscz Oct 22, 2026
534805e
fix(tools): the failure message pick compiles under the dashboard tsc…
genesiscz Oct 22, 2026
fe8c00b
chore(tools): TypeScript 7.0 side by side, its native tsc as @typescr…
genesiscz Oct 22, 2026
fe46a9c
refactor(tools): the CLI helpers and the failure watcher no longer im…
genesiscz Oct 22, 2026
c16b113
feat(ci): the typecheck runs incremental and only for the projects a …
genesiscz Oct 22, 2026
b61160f
refactor(tools): router-link and the link gate no longer import the r…
genesiscz Oct 22, 2026
7710f6e
refactor(tools): the app link strings and schemeLink live in app-link…
genesiscz Oct 22, 2026
1f85100
refactor(macos): notifications.ts keeps terminal-notifier and osascri…
genesiscz Oct 22, 2026
844c1af
refactor(tools): json2md's rebuild command and the notification say c…
genesiscz Oct 22, 2026
6675b66
feat(question): agents record chat answers and supersede decisions wi…
genesiscz Oct 22, 2026
ffa333e
feat(GenesisKit): decision cards take an own answer with the full com…
genesiscz Oct 22, 2026
2859803
refactor(tools): the transclude grammar, recheck line and cmd allow-l…
genesiscz Oct 22, 2026
158e428
fix(tools): shared modules find their own folder and load sibling mod…
genesiscz Oct 22, 2026
6900011
fix(ci): catch a dashboard that renders a server error at commit, lau…
genesiscz Oct 22, 2026
b037ff4
feat(tools): genesis-md links open only where an app handles them; op…
genesiscz Oct 22, 2026
5c1e669
fix(tools): genesis-tools logs names its follow-up commands through t…
genesiscz Oct 22, 2026
6fdf6ef
fix(tools): #559 followup - review fixes, rounds 1 and 2: pre-push ti…
genesiscz Oct 22, 2026
c15e51c
feat(tools): #559 followup - the tools wrapper records a spawned tool…
genesiscz Oct 22, 2026
2b6b6c1
fix(GenesisKit, GenesisHub, widgets): #559 followup - an alert peek g…
genesiscz Oct 22, 2026
7d50ea0
feat(GenesisHub, tools): #559 followup - the app launcher records a t…
genesiscz Oct 22, 2026
c862283
fix(skills, babysit): tmp-reclaim --own-session skips a plain file un…
genesiscz Oct 22, 2026
0ea516a
fix(tools): #559 followup - review fixes, rounds 3 and 4: the failure…
genesiscz Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude/rules/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ Loaded by Claude Code when you read, write or edit a file under `.github/`, `scr
- Use `git grep -P` in anything CI runs, never `rg`. Git is always present and searches tracked files only.
- **Lint the diff, not the tree, and not with `biome --changed`.** Under CI's depth-1 checkout it finds zero files, and biome exits 1 on "No files were processed". `scripts/ci/lint-changed.sh` passes `git diff --name-only --diff-filter=ACMR <base> HEAD` to biome with an `[ -f ]` test per path. The base is `github.event.pull_request.base.sha`, which already is the merge base, so no history fetch is needed.
- **Git runs the `.githooks/` scripts as config hooks** (`hook.genesis-tools-<name>`, through `/bin/sh -c`), set by `scripts/git/install-hooks.ts` from package.json `prepare`, never through `core.hooksPath`. Git executes a hook-folder file directly, macOS checks the first execution of every new file in syspolicyd, and a worktree's hooks are new files: on 2026-10-14 a stuck syspolicyd froze every such first execution at `_dyld_start`, and a rebase hung 41 minutes. A config hook runs only while `core.hooksPath` is unset, so `git -c core.hooksPath=/dev/null …` still turns every hook off. A new hook needs a `#!` line (its interpreter becomes the command) and one `bun install`. Check what runs with `git hook list <event>`.
- **A dashboard that does not render fails the push.** `scripts/ci/dashboard-smoke.ts` boots each Vite dashboard a
push can reach (all of them when `src/utils/` changes) and fetches `/`; a 5xx or no answer fails it with the
server's own error from its log. Pre-push runs it after the typecheck, about 5 s for all eight. CI does not: the
dashboards read this Mac's config. It exists because `tools clarity ui` answered 500 for a day (2026-10-22) while
tsc, the tests and its own launch passed; a launch now also reports a 5xx page and exits 1 (`reportBrokenDashboard`).
- **`lint-rules.ts` and `placeholder-check.sh` scan tracked files only** (`git ls-files`). Run them after the last commit, or after `git add` of every new file. A run before staging proves nothing about a new file.
5 changes: 3 additions & 2 deletions .claude/rules/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ When creating a new tool and writing helper functions, check if the utility is *
- `src/utils/string.ts` - String utilities (glob matching, ANSI stripping)
- `src/utils/cli/executor.ts` - CLI helpers: `suggestCommand()`, `isInteractive()`, `buildCommand()`, `Executor`, `enhanceHelp()`
- `src/utils/storage/storage.ts` - Config & cache management
- `src/utils/fs/module-dir.ts` - `moduleDir(import.meta)` (this module's folder) and `requireSibling(import.meta, name)` (a lazy load beside it). Bun-only `import.meta.dir`/`.dirname` and a bare `require()` break every Vite dashboard that loads the module; lint-rules `no-bun-only-import-meta` and `no-bare-require-in-utils` fail them
- `src/utils/storage/root.ts` - `toolDataDir(tool, ...)` / `genesisToolsDir(...)`: every `~/.genesis-tools/...` path (honours `GENESIS_TOOLS_HOME`, no logger import); `genesisToolsDirIn(home, ...)` for an explicit sandbox or fixture home. Never hand-join any home with `".genesis-tools"`: lint-rules `no-hand-joined-genesis-tools` fails it outside tests and `plugins/**`
- `src/utils/async.ts` - Async helpers (concurrency, retry, etc.)
- `src/utils/json-schema.ts` - JSON schema inference: `inferSchema()`, `formatSchema(value, "skeleton"|"typescript"|"schema")`
Expand Down Expand Up @@ -109,7 +110,7 @@ When spawning tools from shell hooks, always background (`&`) — 86ms Bun start

## Debugging & Logging

- **Triage from logs first.** When any tool misbehaves, the FIRST step is to read `~/.genesis-tools/logs/<today>.log` (and recent days) and `rg` for the tool name / error string — *before* forming hypotheses or reproducing. Logs are day-stamped pino JSON. This bug (`sqlite-vec extension failed to load`) was in the logs for weeks before it was triaged; checking them first collapses hours of guessing into one `rg`.
- **Triage from logs first.** When any tool misbehaves, the FIRST step is to read `~/.genesis-tools/logs/<today>.log` (and recent days) and `rg` for the tool name / error string — *before* forming hypotheses or reproducing. Logs are day-stamped pino JSON. This bug (`sqlite-vec extension failed to load`) was in the logs for weeks before it was triaged; checking them first collapses hours of guessing into one `rg`. `tools genesis-tools logs failures` lists the failed runs (each with its stderr tail and caller), `logs day --level warn --tool <name>` filters the day log through rg, and `logs profile` lists slow timers.
- **Days older than a week are compressed.** The daily pass (started by the logger once a day, `src/utils/logger/daily-trigger.ts`) stores `<date>.log` and `<date>-profiling.log` older than 7 days as `<name>.zst`. Search one with `rg -z <pattern> <file>.zst` (rg runs the `zstd` CLI) or `tools compress cat <file>.zst | rg <pattern>` (no CLI needed); `rg -z` also reads plain files, so `rg -z <pattern> ~/.genesis-tools/logs/2026-10-0*` covers both. Code that reads day logs goes through `dayLogPath` / `listDayLogs` (`@genesiscz/utils/logger/day-logs`) and the readers in `@genesiscz/utils/fs/read-lines`, which open either form; never build `<date>.log` by hand. Details: `.claude/rules/compress.md`.
- **Profiling lines live in their own file:** `~/.genesis-tools/logs/<date>-profiling.log` (e.g. `2026-09-03-profiling.log`), one `[profile:<scope>] <label> <ms>` line per measurement plus a `── <title> ──` summary table per scope. Enable per run with `PROFILE=1`, `PROFILE=*` or `PROFILE=<scope,scope>` (scope names in `src/utils/profile/scopes.ts`); an env `PROFILE` also echoes to stderr, `PROFILE_TO_STDERR=0` silences that. Persistent profiling comes from `tools config profiling`.
- **To ADD a measurement, use the shared profiler, never a hand-rolled `performance.now()` pair.** `import { profiler } from "@genesiscz/utils/profile"`, then one module-level `const prof = profiler.scope("<scope>")` (see `src/du/lib/engine.ts:21`). Verbs: `prof.measure(label, fn)` / `prof.measureAsync(label, fn)` wrap a call, `prof.start(label)` returns a stop function, `prof.section(label)` works with `using`, `prof.mark(label)` records a point in time, `prof.summary(title)` prints the table at the end. A disabled scope is a no-op, so a timer may sit in a hot path; put only work you would not otherwise do behind `prof.enabled`, and hide per-file or per-item timers behind `profiler.detail === "all"` so `--detail phases` stays readable. **A new scope name must be added to `PROFILER_SCOPE_NAMES` in `src/utils/profile/scopes.ts`**, or `--scopes` cannot offer it and `PROFILE=<name>` is a guess.
Expand All @@ -125,7 +126,7 @@ Two cleanly separated layers (the 2026-05 logger+out overhaul):
- **`logger` — diagnostics.** `import { logger } from "@genesiscz/utils/logger"` (named only; there is **no** default export and no `consoleLog` — they were removed). Writes to the day-stamped file **always** (debug+), and to the console **on stderr**, gated by level. `logger.*` is **never** the result channel. Global `-v` promotes file-only `logger.debug` to the console; `-vv` → trace (only on tools that opted into `--trace`).
- **`out` — user-facing.** `import { out } from "@genesiscz/utils/logger"` (or `const { log, out } = logger.scoped("comp")`). clack-shaped. **`out.result(data)` / `out.print(raw)` are the ONLY writers to stdout** (the machine result). `out.log.*` / spinners / notes / prompts → stderr. Never emit a serialized result via `logger.*` — that is `out.result()`'s job (CI guard enforces this).
- **`const { log, out } = logger.scoped("comp")`:** `log.*` = logger-only (diagnostics); `log.out.*` / `log.tee.*` = both (component-tagged single mirror); destructured `out.*` = only-out (no logger mirror).
- **Every commander entrypoint** ends with `await runTool(program, { tool })` (from `@genesiscz/utils/cli`) — it owns `-v`/`--readme`/help registration, console-level resolution, and the `{tool}` log binding, then `parseAsync`. The subprocess **spawner** is `execTool` (renamed from the old `runTool`).
- **Every commander entrypoint** ends with `await runTool(program, { tool })` (from `@genesiscz/utils/cli`) — it owns `-v`/`--readme`/help registration, console-level resolution, and the `{tool}` log binding, then `parseAsync`. It also records each failed run into the failures log (`src/utils/cli/failures/`); an entry point without commander calls `watchToolRun({ tool })` itself and `noteUsageError(message)` when its own parser refuses the command line. The subprocess **spawner** is `execTool` (renamed from the old `runTool`).
- **`scripts/ci/logging-guard.sh`** enforces this convention repo-wide in CI: no default/extension/relative-path/any-name import of the logger module (root `./tools` and `scripts/` included — not just `src/`), no bare `logger.*(SafeJSON.stringify(…))` result dumps, no reintroduced transitional shims, and that the browser-client isolation test exists. Browser-client trees never value-importing `@genesiscz/utils/logger` is authoritatively enforced by `src/logger/client-isolation.test.ts`.
- **`@genesiscz/utils/cli/ui` — high-density CLI status.** For tools that emit many short status lines per command (e.g. `tools stash`), clack's `│ ◆ ●` box-drawing is the wrong texture. Import `{ ui }` from `@genesiscz/utils/cli/ui` to get plain stderr writes with chalk decoration (`ui.ok/info/warn/err/dim/header/kv/section/raw`). Use this INSTEAD of `out.log.*` for high-density status; keep `out.log.*` for clack-shaped task lifecycles. `out.print()` / `out.result()` are still the only writers to stdout for machine-readable output.
- **CLI inventory tables (port-style) — prefer this for multi-column human output.** Canonical helpers live in `@genesiscz/utils/table` (not reimplemented per tool). Reference UIs: `tools port`, `tools ai-proxy models`, `tools macos swap`.
Expand Down
Loading
Loading