catherd-cli 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "catherd",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Herds coding agents: Claude plans and verifies, Codex, opencode or headless Claude Code workers write the code, and Jev picks the model and effort for each piece.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "47vigen",
|
package/plugin/bin/catherd-mcp
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# catherd's MCP launcher (spec 1.1 §12): the plugin's .mcp.json runs it. The release stamps VERSION
|
|
3
3
|
# (scripts/stamp-plugin-version.mjs), so the plugin always starts the server it was released with.
|
|
4
|
-
VERSION="1.1.
|
|
4
|
+
VERSION="1.1.1"
|
|
5
5
|
|
|
6
6
|
# a global catherd at this version (catherd init installs one): start it, no resolve at all
|
|
7
7
|
if command -v catherd >/dev/null 2>&1 && [ "$(catherd --version 2>/dev/null)" = "$VERSION" ]; then
|
|
@@ -23,7 +23,7 @@ Your job:
|
|
|
23
23
|
|
|
24
24
|
The catherd server keeps `state.md` true: it rewrites it on every dispatch, climb and landing.
|
|
25
25
|
|
|
26
|
-
**Your first call is `status()`.** Its `version` must be the one this plugin pins, `catherd-cli@1.1.
|
|
26
|
+
**Your first call is `status()`.** Its `version` must be the one this plugin pins, `catherd-cli@1.1.1`. If it differs, stop and tell the user to restart Claude Code so the plugin and its server match.
|
|
27
27
|
|
|
28
28
|
**You never edit product files.** Every line of code, tests or docs comes from a role, including a one-line fix.
|
|
29
29
|
|
|
@@ -274,7 +274,7 @@ The brief is the `brief` text you pass to `dispatch` (catherd writes it to the d
|
|
|
274
274
|
4. For a worker:
|
|
275
275
|
- "Read `<R>/lanes/Mx.Ly.md`": decisions, signatures, data shapes;
|
|
276
276
|
- its fast check (targeted tests, lint and type check), to run until it passes;
|
|
277
|
-
- "Run the full suite only if this brief says so", and "Wrap any full build or full test suite in `bunx catherd-cli@1.1.
|
|
277
|
+
- "Run the full suite only if this brief says so", and "Wrap any full build or full test suite in `bunx catherd-cli@1.1.1 lock -- <command>`": other lanes share the machine.
|
|
278
278
|
5. For a reviewer: the A-lines and the changed files, with new files read in full. It reports every finding as `BLOCKER|BUG|NIT file:line — problem — fix`, covering:
|
|
279
279
|
- unmet A-lines and edge cases;
|
|
280
280
|
- code or abstractions nobody needs;
|
package/src/adapters/access.ts
CHANGED
|
@@ -38,8 +38,63 @@ function realOr(p: string): string {
|
|
|
38
38
|
/** The temp dir by its real path (macOS: /var/folders/… is /private/var/folders/…); sandboxes compare real paths. */
|
|
39
39
|
export const realTmpdir = (): string => realOr(tmpdir());
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
export
|
|
41
|
+
/** Lines a toolchain prints for a query (`go env GOCACHE GOMODCACHE`, `pnpm store path`); none when it is missing or fails. */
|
|
42
|
+
export type ToolQuery = (bin: string, args: string[]) => string[];
|
|
43
|
+
|
|
44
|
+
const queryTool: ToolQuery = (bin, args) => {
|
|
45
|
+
try {
|
|
46
|
+
const r = Bun.spawnSync([bin, ...args], { stdout: "pipe", stderr: "ignore", timeout: 5_000 });
|
|
47
|
+
return r.exitCode === 0
|
|
48
|
+
? r.stdout
|
|
49
|
+
.toString()
|
|
50
|
+
.split("\n")
|
|
51
|
+
.map((l) => l.trim())
|
|
52
|
+
.filter((l) => l.startsWith("/"))
|
|
53
|
+
: [];
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let asked: string[] | null = null;
|
|
60
|
+
/** Go's and pnpm's effective cache settings (`go env -w` and `store-dir` live outside the env), asked once per process. */
|
|
61
|
+
const effectiveCaches = (query: ToolQuery): string[] =>
|
|
62
|
+
query === queryTool
|
|
63
|
+
? (asked ??= [...query("go", ["env", "GOCACHE", "GOMODCACHE"]), ...query("pnpm", ["store", "path"])])
|
|
64
|
+
: [...query("go", ["env", "GOCACHE", "GOMODCACHE"]), ...query("pnpm", ["store", "path"])];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The toolchain caches a worker's checks write, where they exist: Go's build and module caches and the pnpm store
|
|
68
|
+
* (as the tools themselves report them, else their defaults), Bun's install cache and npm's cache. Without them a
|
|
69
|
+
* sandboxed `go vet` fails or starts cold in every lane.
|
|
70
|
+
*/
|
|
71
|
+
export function toolchainCaches(
|
|
72
|
+
env = process.env,
|
|
73
|
+
home = env.HOME || homedir(),
|
|
74
|
+
query: ToolQuery = queryTool,
|
|
75
|
+
): string[] {
|
|
76
|
+
const mac = process.platform === "darwin";
|
|
77
|
+
const cache = env.XDG_CACHE_HOME || join(home, ".cache");
|
|
78
|
+
const data = env.XDG_DATA_HOME || join(home, ".local", "share");
|
|
79
|
+
const gopath = (env.GOPATH || join(home, "go")).split(":")[0] as string;
|
|
80
|
+
const paths = [
|
|
81
|
+
...effectiveCaches(query),
|
|
82
|
+
env.GOCACHE || (mac ? join(home, "Library", "Caches", "go-build") : join(cache, "go-build")),
|
|
83
|
+
env.GOMODCACHE || join(gopath, "pkg", "mod"),
|
|
84
|
+
env.npm_config_store_dir || (mac ? join(home, "Library", "pnpm", "store") : join(data, "pnpm", "store")),
|
|
85
|
+
env.BUN_INSTALL_CACHE_DIR || join(home, ".bun", "install", "cache"),
|
|
86
|
+
env.npm_config_cache || join(home, ".npm"),
|
|
87
|
+
];
|
|
88
|
+
return [...new Set(paths.filter((p) => existsSync(p)).map(realOr))];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The directories a workspace-write worker writes besides the repo: the heavy-lock dir, the temp dir and the
|
|
93
|
+
* toolchain caches that exist, real paths.
|
|
94
|
+
*/
|
|
95
|
+
export const writableRoots = (): string[] => [
|
|
96
|
+
...new Set([realOr(locksDir()), realTmpdir(), ...toolchainCaches()]),
|
|
97
|
+
];
|
|
43
98
|
|
|
44
99
|
/** The usual local Docker sockets, in the order they are tried (OrbStack and Docker Desktop link /var/run/docker.sock). */
|
|
45
100
|
export const dockerSocketCandidates = (home = homedir()): string[] => [
|
package/src/entry/tui/run.tsx
CHANGED
|
@@ -10,6 +10,16 @@ import { liveEffects } from "./effects.ts";
|
|
|
10
10
|
import { fixtureEffects } from "./fixtures.ts";
|
|
11
11
|
import { createAppKeymap } from "./providers/keymap.tsx";
|
|
12
12
|
import { Providers } from "./providers/root.tsx";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* OpenTUI's SystemClock counts from process start (performance.now), but the pages compare its time with the
|
|
16
|
+
* wall-clock timestamps catherd writes (admittedAt, createdAt, lastActivity), so it must be epoch milliseconds.
|
|
17
|
+
*/
|
|
18
|
+
export class WallClock extends SystemClock {
|
|
19
|
+
override now(): number {
|
|
20
|
+
return Date.now();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
13
23
|
import { initialState, type Tab } from "./state.ts";
|
|
14
24
|
import { detectUi, type ThemeMode } from "./theme.ts";
|
|
15
25
|
import { App } from "./views/app.tsx";
|
|
@@ -72,7 +82,7 @@ export async function openTui(o: TuiOptions): Promise<number> {
|
|
|
72
82
|
keybinds={keybinds}
|
|
73
83
|
keymap={createAppKeymap(renderer)}
|
|
74
84
|
effects={effects}
|
|
75
|
-
clock={new
|
|
85
|
+
clock={new WallClock()}
|
|
76
86
|
initial={initialState(o.tab ?? "status")}
|
|
77
87
|
copy={(text) => renderer.copyToClipboardOSC52(text)}
|
|
78
88
|
onExit={(code, kept) => {
|