Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b20583e
fix(chrome-devtools): #511 followup - a launch whose first window fai…
genesiscz Oct 13, 2026
b71ee4d
fix(tmux, dev-dashboard): a new tmux pane drops test markers and agen…
genesiscz Oct 16, 2026
8fb5c8e
chore(tools): the rebased unified branch keeps the earlier three-way …
genesiscz Oct 17, 2026
4e22955
fix(ai): a Claude model that no longer accepts sampling gets no tempe…
genesiscz Oct 19, 2026
91ea1f6
fix(tools): domains --ai picks a subscription account before a paid o…
genesiscz Oct 19, 2026
f47f044
feat(ai, domains): auto account pick ranks subscription accounts by u…
genesiscz Oct 19, 2026
1c9294b
feat(ai, domains): auto account pick prefers Claude, then Codex, then…
genesiscz Oct 19, 2026
d49b0a6
fix(gitlab): review --receive and the skeleton list a plain MR commen…
genesiscz Oct 20, 2026
fb9add6
fix(ai): an embedder with an explicit provider (Ollama for mail searc…
genesiscz Oct 20, 2026
bfe1b8c
fix(macos): mail index reads and saves its embedder choice through th…
genesiscz Oct 20, 2026
676aca0
fix(azure-devops): history activity accepts a time on --from (WIQL as…
genesiscz Oct 20, 2026
7a6cdbb
fix(cmux, ai): cmux launch reads the default Claude account name from…
genesiscz Oct 20, 2026
dfd8b8e
feat(claude, cmux, handoff): tools claude run auto picks the account …
genesiscz Oct 20, 2026
7bec97e
fix(tools): tools scripts servers is read-only, since a registry load…
genesiscz Oct 21, 2026
910cd83
fix(browser-extension): the popup hides sections that have nothing to…
genesiscz Oct 21, 2026
d753220
feat(browser-extension): popup and options cards get the dashboards' …
genesiscz Oct 21, 2026
cf1ae59
fix(tools): the hub roster refresh no longer writes about 230 MB of d…
genesiscz Oct 21, 2026
308ab3a
fix(video): evidence frames above 100 samples extract again, since th…
genesiscz Oct 21, 2026
a05b806
fix(ai, usage): the usage tick builds session rows in a child it kill…
genesiscz Oct 21, 2026
fd14493
fix(ts): a quoted glob argument expands to its source files instead o…
genesiscz Oct 21, 2026
4f3701e
feat(azure-devops): post a work item's fields in one patch and read a…
genesiscz Oct 15, 2026
e90d666
refactor(azure-devops): rename workitem-post to workitem-update with …
genesiscz Oct 15, 2026
9dbf73a
feat(azure-devops): a global --config option and AZURE_DEVOPS_CONFIG …
genesiscz Oct 15, 2026
23ac638
feat(du): measure iCloud and File Provider content by stat only so no…
genesiscz Oct 22, 2026
bed3d00
feat(du): evict falls back to an osascript helper when no Swift compi…
genesiscz Oct 22, 2026
595f933
fix(tools): #558 followup - review fixes: azure-devops compares read-…
genesiscz Oct 22, 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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "genesis-tools",
"version": "1.0.113",
"version": "1.0.114",
"description": "Plugins for GenesisTools CLI development and management",
"owner": {
"name": "genesiscz",
Expand All @@ -11,7 +11,7 @@
{
"name": "genesis-tools",
"description": "Skills and utilities for working with GenesisTools CLI toolkit. Provides guidance for discovering, executing, and troubleshooting genesis tools with integrated workflow support.",
"version": "1.0.113",
"version": "1.0.114",
"author": {
"name": "GenesisTools"
},
Expand Down
85 changes: 85 additions & 0 deletions docs/benchmarks-du.md
Original file line number Diff line number Diff line change
Expand Up @@ -1322,3 +1322,88 @@ to the 512 cap, and a scratch directory registers nothing outside itself. `bun s
(with the two other touched suites) exited 0: 101 pass, 0 fail. The foreign check requires `foreign>=1`, `missing=0`
and `leaked=0`. `src/du/native/bench.sh` was not run. The change is the mount list for root `/`, and a bench must not
walk `/`.

## 2026-10-22 18:57 — Cloud-backed content is measured by stat only by default (dataless files, File Provider domains)

Opening a dataless file makes `fileproviderd` download it. Before this change the default scan pruned only
`~/Library/CloudStorage` and `~/Library/Mobile Documents` by name, so Desktop and Documents synced to iCloud (and a
scan rooted inside a provider folder) were walked with the normal open + `F_LOG2PHYS_EXT` path for every shared file.
`clonesize ~/Desktop --freeable` and the same for `~/Documents` each hit a 45 s timeout on this Mac.

What changed in `clonesize.c`:

- Every walk (scan, `--bigfiles`, `--index`) requests `ATTR_CMN_FLAGS` (bit 0x00040000, between MODTIME and FILEID),
so each entry layout gains a u32 at offset 52 and every later field moves by 4. The Bun engine's shim
(`cs_getattrbulk`) gains it at offset 36.
- A file is cloud-backed when it has `SF_DATALESS`, or when it sits under a File Provider domain root (the
`com.apple.file-provider-domain-id` xattr). Only the places a root can sit pay for a `getxattr`: children of a home
folder, of `.../Library/CloudStorage`, `.../Library/Mobile Documents`, and the scan root with its ancestors.
- Default: a cloud-backed file is counted from the listing (allocated size into `naive`, and in full into the unique
totals) and never opened. A dataless folder is counted and never entered. Each walk thread sets
`IOPOL_TYPE_VFS_MATERIALIZE_DATALESS_FILES` to off. `--include-cloud` lifts all of it.
- `--bigfiles` leaves dataless files out (`dataless_skipped`); `--index` flags a dataless folder `IDX_F_CLOUD` and
does not enter it; a new `--cloudfiles` mode lists the downloaded cloud files for `tools du downloaded`.

Which totals move: only trees that hold cloud-backed content. There, clone sharing between cloud-backed files is no
longer deduped (they count at their full allocation, as `du` does), and `files_opened` drops to 0 for them. Every
total on a tree without cloud-backed content is unchanged. Checked old binary against new, back to back, at the same
moment (the repo is a live tree, so the T1 numbers of the two bench runs below differ between runs):

| Target | old vs new |
|---|---|
| T1 (repo) `--json` | identical `files_listed` 331921, `files_opened` 115978, `naive` 10743083008, `unique` 8786771088, `unique_allocated` 9595559936, `shared` 1147523072 |
| T2 (node_modules) `--json` | identical, `naive` 1680945152, `unique` 1238311583 |
| T2 `--index --threads 8` | 784 rows identical once node ids (scheduling order) are removed |
| T1 `--bigfiles --min-bytes 1000000` | 1176 files, same paths, `dataless_skipped` 0 |

`src/du/native/bench.sh` (system CPU is the metric; the load average was 20 to 88 throughout, so wall time is noise):

| Mode | before (sys s) | after (sys s) |
|---|---|---|
| T1 flat | 33.48 | 32.98 |
| T1 depth2 | 30.44 | 30.31 |
| T2 flat | 7.91 | 9.30 |
| T2 depth2 | 7.08 | 6.66 |
| T1 cache-cold | 28.31 | 27.66 |
| T1 cache-warm | 17.86 | 17.51 |

Load: before 35.00 at start, 88.66 at end; after 20.09 at start, 69.52 at end. Old against new in one hyperfine run
(load 60): `--index` T2 2.66 s against 2.39 s system, `--bigfiles` T1 6.95 s against 5.16 s. No regression shows
above the noise; the flags attribute is one more u32 per entry in the same kernel call.

Cloud folders with the new binary (counts by lstat only: `find -x <dir> \( -flags +dataless -prune -type f -print \)`):

| Folder | wall | opened | dataless files before / after | local KB before / after |
|---|---|---|---|---|
| ~/Desktop (iCloud) | 0.01 s | 0 | 889 / 889 | 3840 / 3840 |
| ~/Documents (iCloud) | 0.10 s | 0 | 8949 / 8949 | 159140 / 159140 |
| ~/Library/CloudStorage/Dropbox | 0.23 s | 0 | 52753 / 52753 | 0 / 0 |
| Tresorit Drive mount root | 0.00 s | 0 | 24 dataless folders, none entered / 24 | — |

## 2026-10-22 19:27 — `tools du evict` without a Swift compiler

No scan code changed: the C engine, the Bun engine and their totals are untouched, so no benchmark was rerun.
`evict` now picks its helper: the compiled `cloudctl` when it exists or `swiftc` can build it, otherwise
`native/cloudctl.js` through `osascript -l JavaScript`. Developer tools are detected with `xcode-select -p`,
because `/usr/bin/swiftc` exists on every Mac and opens the "install the tools" dialog when they are missing.

Parity, status of every downloaded ~/Desktop file plus two control paths: 278 rows from each helper, 0 differing.
The JS helper must pass the keys as an `NSMutableArray`; a JS array bridges to an empty key list, and single-key
reads leave out the upload error that the batched Swift read returns.

| Check | Swift helper | osascript helper (helper moved aside, fake `xcode-select` exiting 2) |
|---|---|---|
| `evict ~/Desktop --all --top 1000`, dry run | 45 eligible (180 KB), 231 refused | 45 eligible (180 KB), 231 refused (194 not uploaded, 37 not iCloud) |
| `evict --apply` on a probe file created in iCloud Drive | — | evicted: 176 blocks → 0, flags `compressed,dataless` |

## 2026-10-22 21:52 — PR #558 review: the Bun planner counts dataless folders once, and `evict` has a deadline

No hot-loop code changed: the C engine is untouched, and in the Bun engine only the planning walk (a few hundred
directory listings on the main thread) moved, so no benchmark was rerun. One total moves, in the Bun engine only:
`cloud_backed.dataless_dirs` goes down to the C engine's count. The planner counted a dataless folder when it listed
its parent, so a frontier leaf was counted again by the worker that recursed it, and once more on every later
planning round. It now counts only the dataless folders of interior directories, which no worker lists.

The planning walk also runs with dataless materialization off, and a dataless scan root is never listed. The scan
then finds nothing, as the C engine's guarded open of that root does. `cloudctl` runs stop after 60 s plus 2 s per
path, and the call fails with that reason (an `evict` names that some files may already be evicted).
2 changes: 1 addition & 1 deletion plugins/genesis-tools/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genesis-tools",
"version": "1.0.113",
"version": "1.0.114",
"description": "Skills and utilities for working with GenesisTools CLI toolkit. Provides guidance for discovering, executing, and troubleshooting genesis tools with integrated workflow support.",
"author": {
"name": "GenesisTools"
Expand Down
20 changes: 19 additions & 1 deletion plugins/genesis-tools/skills/azure-devops/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ az login --allow-no-subscriptions --use-device-code # if Conditional Access b
tools azure-devops configure "https://dev.azure.com/MyOrg/MyProject/_workitems"
```

`configure` takes any Azure DevOps URL of the project and writes `.claude/azure/config.json`. The tool finds that file from the current directory up to three parent levels. A missing login or config stops the command with a message that names the missing step. Add `-v` for debug logging. `tools azure-devops --readme` prints the full README.
`configure` takes any Azure DevOps URL of the project and writes `.claude/azure/config.json`. The tool finds that file from the current directory up to three parent levels. To use a config file from any folder, pass `--config <path>` (a global option, valid before or after the subcommand) or set `AZURE_DEVOPS_CONFIG=<path>`. The order is `--config`, then `AZURE_DEVOPS_CONFIG`, then the search. An explicit path that is not a readable file stops the command with an error; it never falls back to the search. With an explicit config, the `tasks/` and `wiki/` folders live next to that file instead of in the working directory, and `configure` writes to that path. A missing login or config stops the command with a message that names the missing step. Add `-v` for debug logging. `tools azure-devops --readme` prints the full README.

## CLI Reference

Expand All @@ -46,6 +46,7 @@ tools azure-devops comment add <id> --file x.md # Post markdown (--text, --file
tools azure-devops comment edit <id> <commentId> --file x.md # Replace a comment's text
tools azure-devops comment delete <id> <commentId> --yes # --yes is required without a terminal (alias: rm)
tools azure-devops workitem-create # Create work item (alias: create)
tools azure-devops workitem-update <id> # Read multilineFieldsFormat; with --field, patch in one request (alias: update)
tools azure-devops timelog configure # Interactive: setup API key, user, allowed types
tools azure-devops timelog types # List available time types
tools azure-devops timelog list -w <id> # List time logs for work item (--day <date>, --from/--to, --user @me)
Expand Down Expand Up @@ -332,6 +333,23 @@ When user says "analyze workitem/task X" or "analyze tasks from query Y":
| "Get task 12345 with screenshots" | `tools azure-devops workitem 12345 --task-folders --images` |
| "Analyze bug with images" | Fetch with `--images` → Read images → Explore agent with visual context |

### Update fields

`workitem-update` (alias `update`) reads one work item, including `multilineFieldsFormat`, and can update fields in a single JSON Patch. Without `--confirm` it prints the patch and writes nothing. Field names are the caller's; the command hard-codes none.

```bash
tools azure-devops workitem-update 12345 --json
tools azure-devops workitem-update 12345 \
--field System.Description=@description.html \
--markdown System.Description \
--image-root ./drafts \
--reuse uploaded-images.json \
--rev 6 \
--confirm --json
```

`--field` repeats. A value that starts with `@` is a file path. `--markdown` repeats and switches that multiline field to Markdown when it is not already; the server cannot switch it back. `--reuse` is a JSON object `{ "<file name>": { "sha256": "<hex>", "url": "<attachment url>" } }`. A local `<img src>` whose file still has that sha256 keeps the URL and is not uploaded again. The patch always starts with `test /rev` (the live rev, or `--rev` when it still matches). After `--confirm`, the read-back treats these server rewrites as the same text: an emoji stored as `&#128279;`, a quoted integer attribute stored without quotes (`width="375"` as `width=375`; `width="48%"` keeps the quotes), and an image `src` replaced by its attachment URL. `--json` prints the result. A differing read-back exits 1.

## Creating Work Items

The `workitem-create` command (alias `create`) supports multiple modes for creating new work items.
Expand Down
2 changes: 1 addition & 1 deletion plugins/genesis-tools/skills/browser-router/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ A link carries only the session id, the number and one letter. Anything else doe

## Run Claude in a new cmux surface

`tools cmux launch --prompt <text>` prints the quoted command for a new cmux surface. With `--open` it opens the surface (`--surface new|split|workspace`) and runs the command there. `--account` defaults to the default Claude account. The cmux-claude preset passes `--open`. A raw link that starts an agent always asks; only a link minted on this machine (`handoff_post` adds one as `paste.runLink`) runs without the card, once.
`tools cmux launch --prompt <text>` prints the quoted command for a new cmux surface. With `--open` it opens the surface (`--surface new|split|workspace`) and runs the command there. `--account` defaults to the default Claude account; `--account auto` runs `tools claude run auto`, which picks the account with the most headroom when the surface starts. A handoff's run link is minted with `account=auto`. The cmux-claude preset passes `--open`. A raw link that starts an agent always asks; only a link minted on this machine (`handoff_post` adds one as `paste.runLink`) runs without the card, once.

```bash
tools browser-router route 'http://127.0.0.1:8787/add/:id' --run <program> --arg add --arg '{id}' --arg --qty --arg '{qty}' --notify 'Added' --approval allow
Expand Down
40 changes: 39 additions & 1 deletion src/ai/commands/usage/poll-daemon.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";
import type { AccountUsageSnapshot } from "@genesiscz/utils/ai/usage-poll/types";
import { anthropicRows, notifiableWindows } from "./poll-daemon";
import { anthropicRows, notifiableWindows, rowsOfAnswer, sessionRowsArgv } from "./poll-daemon";

/**
* The daemon fans one poll round out to three consumers with different rules: threshold
Expand Down Expand Up @@ -86,3 +86,41 @@ describe("anthropicRows", () => {
expect(rows.filter((r) => !r.stale)).toEqual([]);
});
});

describe("sessionRowsArgv", () => {
test("the child and the hub server get the same cache-safe command, providers included", () => {
expect(sessionRowsArgv({ hours: 24, minRows: 10 })).toEqual([
"ai",
"usage",
"sessions",
"--json",
"--fresh",
"--no-cache-write",
"--journal-only",
"--hours",
"24",
"--min",
"10",
]);
expect(sessionRowsArgv({ providers: ["claude", "codex"], limit: 5 })).toEqual([
"ai",
"usage",
"sessions",
"--json",
"--fresh",
"--no-cache-write",
"--journal-only",
"--provider",
"claude",
"codex",
"--limit",
"5",
]);
});

test("rowsOfAnswer reads the rows and refuses an answer without them", () => {
expect(rowsOfAnswer(`{"rows":[{"sessionId":"a"}],"fetchedAt":1}`)?.map((row) => row.sessionId)).toEqual(["a"]);
expect(rowsOfAnswer(`{"error":"busy"}`)).toBeNull();
expect(() => rowsOfAnswer("not json")).toThrow();
});
});
Loading
Loading