@clipboard-health/groundcrew 4.40.0 → 4.42.0

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.
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Groundcrew-owned `crew-clearance-ensure` command: a thin shim that ships on
4
+ // PATH with `crew` and dispatches to clearance's `clearance-ensure` entrypoint,
5
+ // passing args, stdio, and exit code straight through.
6
+
7
+ import { createRequire } from "node:module";
8
+ import path from "node:path";
9
+ import { pathToFileURL } from "node:url";
10
+
11
+ const require = createRequire(import.meta.url);
12
+
13
+ // Resolve clearance wherever npm placed it (nested under groundcrew or hoisted
14
+ // to a top-level node_modules). Clearance's `exports` map exposes only `.` and
15
+ // `./package.json`, so we locate the package root through `package.json`, then
16
+ // import the bin by absolute file URL, which is not subject to the `exports`
17
+ // gate. Reading the path from `bin["clearance-ensure"]` keeps the shim correct
18
+ // if clearance relocates the file in a future version.
19
+ const packageJsonPath = require.resolve("@clipboard-health/clearance/package.json");
20
+ const packageJson = require("@clipboard-health/clearance/package.json");
21
+ const ensureRelativePath = packageJson.bin["clearance-ensure"];
22
+ const ensurePath = path.resolve(path.dirname(packageJsonPath), ensureRelativePath);
23
+
24
+ await import(pathToFileURL(ensurePath).href);
@@ -49,6 +49,16 @@ export default {
49
49
  // graft repo add ~/dev/owner/monorepo
50
50
  // graft alias add billing services/billing libs/common
51
51
  // `crew doctor` does not parse or validate these shell templates.
52
+ //
53
+ // An object entry can also set a per-repo prepareWorktree hook without a
54
+ // committed `.groundcrew/config.json` — handy for third-party repos you
55
+ // don't want to add groundcrew files to. It beats `defaults.hooks` below
56
+ // but still yields to a repo-committed `.groundcrew/config.json`:
57
+ //
58
+ // {
59
+ // name: "other-org/their-repo",
60
+ // hooks: { prepareWorktree: "uv sync --dev --frozen" },
61
+ // },
52
62
  },
53
63
  agents: {
54
64
  default: "claude",
@@ -78,8 +88,10 @@ export default {
78
88
  },
79
89
  // Repo-preparation hook: runs after each worktree is created and before the
80
90
  // agent launches. The default below is a no-op placeholder. Replace it with
81
- // your repo's setup, e.g. "npm ci" or "uv sync --dev --frozen". A repo-local
82
- // `.groundcrew/config.json` hooks.prepareWorktree overrides this per repo.
91
+ // your repo's setup, e.g. "npm ci" or "uv sync --dev --frozen". This is the
92
+ // lowest-priority layer: a per-repo `knownRepositories[].hooks.prepareWorktree`
93
+ // (above) overrides it, and a repo-committed `.groundcrew/config.json`
94
+ // `hooks.prepareWorktree` overrides both.
83
95
  defaults: {
84
96
  hooks: {
85
97
  prepareWorktree: "true",
@@ -109,6 +121,9 @@ export default {
109
121
  // {
110
122
  // kind: "shell",
111
123
  // name: "jira",
124
+ // // Open local task-store directories for read/write inside the
125
+ // // safehouse/srt sandbox when this source owns the launched task.
126
+ // sandboxWritePaths: ["~/plans"],
112
127
  // commands: {
113
128
  // verify: "jira me",
114
129
  // fetch: "~/.config/groundcrew/jira-fetch.sh",
@@ -1 +1 @@
1
- {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAyBnE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CA4If;AAgJD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAkDf"}
1
+ {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAyBnE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CAmJf;AAgJD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAkDf"}
@@ -78,8 +78,13 @@ export async function setupWorkspace(config, options, runOptions = {}) {
78
78
  workspaceContinuationInstruction: renderWorkspaceContinuationInstruction(accessHint),
79
79
  });
80
80
  promptDir = stagedPrompt.directory;
81
+ const perRepoHooks = config.workspace.repositories.find((entry) => entry.name === repository)?.hooks;
81
82
  const prepareWorktreeCommand = resolvePrepareWorktreeCommand({
82
83
  worktreeDir: launchDir,
84
+ // Spread-conditional rather than a direct assignment: under
85
+ // exactOptionalPropertyTypes an optional field can't take an explicit
86
+ // `undefined`, and the lookup yields undefined for repos with no hooks.
87
+ ...(perRepoHooks === undefined ? {} : { perRepoHooks }),
83
88
  defaultHooks: config.defaults.hooks,
84
89
  });
85
90
  const secretsFile = prepareWorktreeCommand === undefined ? undefined : stageBuildSecrets(promptDir);
@@ -89,6 +89,7 @@ export declare const shellValidateOutputSchema: z.ZodArray<z.ZodString>;
89
89
  export declare const shellAdapterConfigSchema: z.ZodObject<{
90
90
  kind: z.ZodLiteral<"shell">;
91
91
  name: z.ZodString;
92
+ sandboxWritePaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
92
93
  commands: z.ZodObject<{
93
94
  verify: z.ZodOptional<z.ZodString>;
94
95
  listTasks: z.ZodOptional<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAA4B,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,yBAAsB,CAAC;AAE7D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkEnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAA4B,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,yBAAsB,CAAC;AAE7D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8EnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -47,6 +47,18 @@ export const shellAdapterConfigSchema = z.object({
47
47
  name: z
48
48
  .string()
49
49
  .regex(/^[a-z][a-z0-9-]*$/, "name must be kebab-case (lowercase letters, digits, hyphens)"),
50
+ /**
51
+ * Local directories the sandbox opens for **read + write** while an agent
52
+ * works a task owned by this source. When the task id is source-qualified
53
+ * (a `<source>:` prefix), the grant is scoped to that source, so a task from
54
+ * another source does not receive these paths; an unqualified task id grants
55
+ * every eligible source's declared paths. `~` expands at config load. Use for
56
+ * an external task store the agent must read and update in place (e.g. a
57
+ * plan-keeper `~/plans` tree).
58
+ */
59
+ sandboxWritePaths: z
60
+ .array(z.string().min(1, "sandboxWritePaths entries must be non-empty"))
61
+ .optional(),
50
62
  commands: z
51
63
  .object({
52
64
  verify: z.string().optional(),
@@ -165,6 +165,14 @@ export interface KnownRepository {
165
165
  * is unchanged. Relative, no `..`.
166
166
  */
167
167
  workdir?: string;
168
+ /**
169
+ * Per-repo operator hooks, reusing the same `HookCommands` shape that
170
+ * `defaults.hooks` and the in-repo `.groundcrew/config.json` use. Slots
171
+ * between the repo-committed file (wins) and `defaults.hooks` (fallback) in
172
+ * the `prepareWorktree` cascade, so an operator can set the hook for a repo
173
+ * they don't want to (or can't) commit a `.groundcrew/config.json` into.
174
+ */
175
+ hooks?: HookCommands;
168
176
  }
169
177
  export interface Config {
170
178
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -137,6 +137,9 @@ function rethrowWithConfigPath({ error, filepath }) {
137
137
  function isNonEmptyString(value) {
138
138
  return typeof value === "string" && value.length > 0;
139
139
  }
140
+ function isStringArray(value) {
141
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
142
+ }
140
143
  function requireString(value, configKey) {
141
144
  if (!isNonEmptyString(value)) {
142
145
  fail(`${configKey} must be a non-empty string (got ${JSON.stringify(value)})`);
@@ -461,6 +464,12 @@ function normalizeSources(raw) {
461
464
  : {}),
462
465
  });
463
466
  }
467
+ else if (kind === "shell" && isStringArray(entry["sandboxWritePaths"])) {
468
+ expanded.push({
469
+ ...entry,
470
+ sandboxWritePaths: entry["sandboxWritePaths"].map((value) => expandHome(value)),
471
+ });
472
+ }
464
473
  else {
465
474
  expanded.push(entry);
466
475
  }
@@ -500,6 +509,9 @@ function normalizeKnownRepository(entry, index) {
500
509
  if (workdir !== undefined) {
501
510
  recipe.workdir = workdir;
502
511
  }
512
+ if (entry.hooks !== undefined) {
513
+ recipe.hooks = normalizeHookCommands(entry.hooks, `${label}.hooks`);
514
+ }
503
515
  return recipe;
504
516
  }
505
517
  /**
@@ -1,6 +1,13 @@
1
1
  import type { HookCommands } from "./config.ts";
2
2
  interface ResolvePrepareWorktreeCommandArguments {
3
3
  worktreeDir: string;
4
+ /**
5
+ * Per-repo operator hooks from this repo's `knownRepositories[]` entry in
6
+ * `crew.config.ts`. Slots between the repo-committed file and the global
7
+ * `defaults.hooks` so an operator can set the hook for a repo they don't
8
+ * want to (or can't) commit a `.groundcrew/config.json` into.
9
+ */
10
+ perRepoHooks?: HookCommands;
4
11
  defaultHooks: HookCommands;
5
12
  }
6
13
  export declare function resolvePrepareWorktreeCommand(arguments_: ResolvePrepareWorktreeCommandArguments): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"repositoryHooks.d.ts","sourceRoot":"","sources":["../../src/lib/repositoryHooks.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,UAAU,sCAAsC;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,sCAAsC,GACjD,MAAM,GAAG,SAAS,CAGpB"}
1
+ {"version":3,"file":"repositoryHooks.d.ts","sourceRoot":"","sources":["../../src/lib/repositoryHooks.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,UAAU,sCAAsC;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,EAAE,YAAY,CAAC;CAC5B;AAMD,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,sCAAsC,GACjD,MAAM,GAAG,SAAS,CAOpB"}
@@ -1,9 +1,15 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  const REPOSITORY_CONFIG_RELATIVE_PATH = ".groundcrew/config.json";
4
+ // Flat precedence cascade, highest priority first: the repo-committed
5
+ // `.groundcrew/config.json` wins (closest to the code), then the per-repo
6
+ // operator layer, then the global `defaults.hooks` fallback. All three reuse
7
+ // the same `HookCommands` shape so this stays a plain `?? ?? ??`.
4
8
  export function resolvePrepareWorktreeCommand(arguments_) {
5
9
  const repositoryConfig = readRepositoryConfig(arguments_.worktreeDir);
6
- return repositoryConfig?.hooks.prepareWorktree ?? arguments_.defaultHooks.prepareWorktree;
10
+ return (repositoryConfig?.hooks.prepareWorktree ??
11
+ arguments_.perRepoHooks?.prepareWorktree ??
12
+ arguments_.defaultHooks.prepareWorktree);
7
13
  }
8
14
  function readRepositoryConfig(worktreeDir) {
9
15
  const configPath = path.join(worktreeDir, REPOSITORY_CONFIG_RELATIVE_PATH);
@@ -1,4 +1,11 @@
1
1
  import type { ResolvedConfig } from "./config.ts";
2
+ /**
3
+ * Local directories the sandbox must open (read + write) for a task. Covers two
4
+ * source kinds: a `todo-txt` source's completion-writeback target (the todo
5
+ * file's parent dir plus its tasks dir) and a `shell` source's declared
6
+ * `sandboxWritePaths`. Scoped to the source that owns the task by the task-id
7
+ * prefix; an un-prefixed id grants every eligible source's paths.
8
+ */
2
9
  export declare function taskSourceWritePathsForCompletion(input: {
3
10
  config: Pick<ResolvedConfig, "sources">;
4
11
  taskId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"taskSourceFilesystem.d.ts","sourceRoot":"","sources":["../../src/lib/taskSourceFilesystem.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAUlD,wBAAgB,iCAAiC,CAAC,KAAK,EAAE;IACvD,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,SAAS,MAAM,EAAE,CAwBpB"}
1
+ {"version":3,"file":"taskSourceFilesystem.d.ts","sourceRoot":"","sources":["../../src/lib/taskSourceFilesystem.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAUlD;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAAC,KAAK,EAAE;IACvD,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,SAAS,MAAM,EAAE,CAsCpB"}
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { z } from "zod";
3
+ import { shellAdapterConfigSchema } from "./adapters/shell/schema.js";
3
4
  import { todoTxtAdapterConfigSchema } from "./adapters/todo-txt/schema.js";
4
5
  const sourceSelectorShape = z.looseObject({
5
6
  kind: z.string().optional(),
@@ -7,23 +8,45 @@ const sourceSelectorShape = z.looseObject({
7
8
  enabled: z.boolean().optional(),
8
9
  });
9
10
  const DEFAULT_TODO_SOURCE_NAME = "todo";
11
+ /**
12
+ * Local directories the sandbox must open (read + write) for a task. Covers two
13
+ * source kinds: a `todo-txt` source's completion-writeback target (the todo
14
+ * file's parent dir plus its tasks dir) and a `shell` source's declared
15
+ * `sandboxWritePaths`. Scoped to the source that owns the task by the task-id
16
+ * prefix; an un-prefixed id grants every eligible source's paths.
17
+ */
10
18
  export function taskSourceWritePathsForCompletion(input) {
11
19
  const targetSourceName = sourceNameFromTaskId(input.taskId);
12
20
  const paths = [];
13
21
  for (const rawSource of input.config.sources) {
14
22
  const selector = sourceSelectorShape.parse(rawSource);
15
- if (selector.enabled === false || selector.kind !== "todo-txt") {
23
+ if (selector.enabled === false) {
16
24
  continue;
17
25
  }
26
+ if (selector.kind !== "todo-txt" && selector.kind !== "shell") {
27
+ continue;
28
+ }
29
+ // Only the canonical task-list source has an implicit default name; shell
30
+ // sources carry a schema-required name, so the fallback never applies there.
18
31
  const sourceName = selector.name ?? DEFAULT_TODO_SOURCE_NAME;
19
32
  if (targetSourceName !== undefined && sourceName !== targetSourceName) {
20
33
  continue;
21
34
  }
22
- const source = todoTxtAdapterConfigSchema.parse(rawSource);
23
- // Completion writeback writes the todo file plus lock/tmp siblings, so the
24
- // sandbox grant must cover the todo file's parent directory.
25
- paths.push(resolveForWorker(input.workingDir, path.dirname(source.todoPath)));
26
- paths.push(resolveForWorker(input.workingDir, source.tasksDir));
35
+ if (selector.kind === "todo-txt") {
36
+ const source = todoTxtAdapterConfigSchema.parse(rawSource);
37
+ // Completion writeback writes the todo file plus lock/tmp siblings, so the
38
+ // sandbox grant must cover the todo file's parent directory.
39
+ paths.push(resolveForWorker(input.workingDir, path.dirname(source.todoPath)));
40
+ paths.push(resolveForWorker(input.workingDir, source.tasksDir));
41
+ }
42
+ else {
43
+ // shell: open the directories the source declares it reads/writes in place
44
+ // (e.g. an external plan store) for read + write under the sandbox.
45
+ const source = shellAdapterConfigSchema.parse(rawSource);
46
+ for (const writePath of source.sandboxWritePaths ?? []) {
47
+ paths.push(resolveForWorker(input.workingDir, writePath));
48
+ }
49
+ }
27
50
  }
28
51
  return [...new Set(paths)];
29
52
  }
@@ -62,6 +62,7 @@ workspace: {
62
62
  - **`provision`** marks the entry as scripted. **`provision.create`** runs in place of `git worktree add`; **`provision.remove`** runs in place of `git worktree remove`. Both are required — a `provision` block missing either is rejected at config load. `projectDirOverride` (the per-repo source-directory override for native entries) cannot be combined with `provision`; a scripted entry has no groundcrew-managed clone, so it is rejected at config load.
63
63
  - Both templates run on the host via `sh -c` with the working directory set to the worktree root (`worktreeDir`, defaulting to `projectDir`). They interpolate `${branch}`, `${dir}`, `${baseRef}` (`<remote>/<defaultBranch>`), `${repo}`, and `${task}`; each value is shell-quoted.
64
64
  - **`workdir`** (optional) is a relative subdirectory of the worktree. When set, the agent's working directory, the `prepareWorktree` hook, and the `.groundcrew/config.json` lookup all re-root to `<worktree>/<workdir>` — use it when a sparse checkout materializes a monorepo whose project lives in a subdirectory (e.g. `uv sync` must run there). The worktree root is unchanged: it is still what create/remove/list operate on, and a sandboxed agent keeps full read/write access to the whole checkout. `workdir` must be relative with no `..` segments; if it is missing after checkout, `crew` fails fast.
65
+ - **`hooks`** (optional) sets per-repo operator hooks for this entry, reusing the same `HookCommands` shape as `defaults.hooks` and the in-repo `.groundcrew/config.json`. Today that is `hooks.prepareWorktree`. It slots between the repo-committed file (wins) and `defaults.hooks` (fallback) — see [Default Hooks](#default-hooks). You must use the object form to attach `hooks` (string entries only carry the repo name), but unlike `provision`, adding `hooks` does not make the entry scripted; the worktree is still created natively.
65
66
  - A dirty scripted worktree is still protected from data loss: `crew cleanup` refuses to run `remove` unless you pass `--force`. Orphan and branch cleanup are delegated to your `remove` template, since groundcrew does not track the scripted clone.
66
67
 
67
68
  Set `graft` (or whatever tool your templates call) up once, outside groundcrew:
@@ -238,9 +239,15 @@ This keeps package defaults portable while letting your private config reference
238
239
 
239
240
  ## Default Hooks
240
241
 
241
- Repo-local `.groundcrew/config.json` is the preferred place for
242
- `hooks.prepareWorktree`. To provide a fallback for repos that do not define one,
243
- set `defaults.hooks.prepareWorktree`:
242
+ `hooks.prepareWorktree` resolves through three layers, highest priority first:
243
+
244
+ 1. **`.groundcrew/config.json` `hooks.prepareWorktree`** — committed in the repo, authored by its maintainers. Wins.
245
+ 2. **`workspace.knownRepositories[].hooks.prepareWorktree`** — per-repo, set by the operator in `crew.config.ts`.
246
+ 3. **`defaults.hooks.prepareWorktree`** — global operator fallback in `crew.config.ts`.
247
+
248
+ Repo-local `.groundcrew/config.json` is the preferred place for the hook. To
249
+ provide a global fallback for repos that do not define one, set
250
+ `defaults.hooks.prepareWorktree`:
244
251
 
245
252
  ```ts
246
253
  export default {
@@ -252,34 +259,61 @@ export default {
252
259
  };
253
260
  ```
254
261
 
262
+ For a repo you can't (or don't want to) commit a `.groundcrew/config.json` into
263
+ — a third-party repo, say — set the hook for just that repo on its
264
+ `knownRepositories` entry. It beats `defaults.hooks` but still yields to a
265
+ committed `.groundcrew/config.json`:
266
+
267
+ ```ts
268
+ export default {
269
+ workspace: {
270
+ projectDir: "~/dev",
271
+ knownRepositories: [
272
+ "your-org/your-repo",
273
+ {
274
+ name: "other-org/their-repo",
275
+ hooks: { prepareWorktree: "uv sync --dev --frozen" },
276
+ },
277
+ ],
278
+ },
279
+ };
280
+ ```
281
+
282
+ The per-repo `hooks` reuse the same container and `prepareWorktree` contract as
283
+ the other two layers. This is the additive hook seam: `hooks.*` runs _around_ the
284
+ worktree lifecycle, distinct from `provision.*`, which _replaces_ the git
285
+ worktree porcelain (`create`/`remove`).
286
+
255
287
  See [Prepare Worktree Hooks](./setup-hooks.md) for the repo-local config shape
256
288
  and hook contract.
257
289
 
258
290
  ## Full Reference
259
291
 
260
- | Key | Default | What it does |
261
- | ---------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
262
- | `sources` | `[]` | Additional pluggable task sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. Declare `{ kind: "linear", team: "ENG", statuses: { ... } }` to configure Linear task creation's default team and/or override Linear status names used for `in-progress` / `in-review` disambiguation. Disable the implicit Linear source with `{ kind: "linear", enabled: false }` (no API key required) — useful for shell-only setups. |
263
- | `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
264
- | `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
265
- | `git.branchPrefix` | OS username | Prefix groundcrew puts before the task id when naming a worktree branch (`<branchPrefix>-<task>`). Must be a slash-free slug of letters, digits, `.`, `_`, or `-`. Defaults to the OS account username. Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. Prefer a per-user config for personal prefixes — a committed `git.branchPrefix` gives every contributor the same branch prefix. |
266
- | `workspace.projectDir` | **required** | Parent dir for cloned repos and the default task worktree root. |
267
- | `workspace.worktreeDir` | optional | Parent dir for task worktrees. When unset, worktrees are created under `workspace.projectDir`. Changing this only affects worktrees discovered under the new root; clean up existing worktrees before switching it, or temporarily unset it when you need `crew cleanup` to find old worktrees. |
268
- | `workspace.knownRepositories` | **required** | Repos searched for in task descriptions to infer where work belongs. Entries can be strings under `workspace.projectDir` or `{ name, projectDirOverride }` objects when a repo clone lives under a different parent dir. A task labeled for groundcrew (`agent-*`) fails fast when no known repo appears; unlabeled tasks are ignored. |
269
- | `defaults.hooks.prepareWorktree` | optional | Fallback repo-preparation command used only when the worktree does not define `.groundcrew/config.json` `hooks.prepareWorktree`. The hook runs after worktree creation and before the agent starts. Repo-local config wins. |
270
- | `orchestrator.maximumInProgress` | `4` | Cap on in-progress tasks at once for this `crew` instance. |
271
- | `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
272
- | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. An agent whose codexbar session window exceeds this percentage is skipped that tick. Agents are also skipped when codexbar reports weekly usage over the current weekly paced budget. |
273
- | `agents.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew start <TASK>` for unlabeled tasks. `crew run` ignores unlabeled tasks and does not apply this default. Must exist in `agents.definitions`. If you enable only `codex`, set `default: "codex"`. |
274
- | `agents.definitions` | **required** | Enabled launch profile set. Built-in keys (`claude`, `codex`) can use `{}` to opt into the shipped preset. Custom profile names must provide `cmd` and `color`; use custom profiles such as `claude-fable` and `claude-opus` to select model-specific commands per task. |
275
- | `agents.definitions.<name>.cmd` | preset for built-ins | Shell command launched for the agent. Required for custom profiles. Runs in the worktree through the resolved `local.runner`. `{{worktree}}` is replaced before launch; `{{sandbox}}` expands to the sbx sandbox name under the sdx runner and an empty string otherwise. |
276
- | `agents.definitions.<name>.color` | preset for built-ins | Color for the workspace status pill (cmux only; tmux and zellij silently drop it). Required for custom profiles. |
277
- | `agents.definitions.<name>.usage` | preset for built-ins | If set, codexbar usage is fetched for this agent and gated by `sessionLimitPercentage` plus the weekly paced budget when codexbar exposes a weekly window. When `usage.codexbar.source` is omitted, groundcrew uses `oauth` for Codex/Claude on macOS, `auto` for other macOS providers, and `cli` elsewhere. Set to `{ disabled: true }` to disable usage gating while keeping the agent enabled. |
278
- | `agents.definitions.<name>.sandbox` | optional | Docker Sandboxes binding for the agent. Required at launch when `local.runner` resolves to `sdx`. Field: `agent` (required sbx agent name). Groundcrew assumes the `groundcrew-<agent>` sandbox already exists. |
279
- | `agents.definitions.<name>.preLaunch` | optional | Host-only shell snippet run before the agent exec and outside Safehouse/sdx. Exports survive into the launch shell; under the default `safehouse` runner they are only forwarded to the agent when listed via `preLaunchEnv` or when `cmd` includes its own `safehouse --env-pass=NAMES`. `{{worktree}}` is substituted. A non-zero exit aborts launch. Not supported when `local.runner` resolves to `sdx` in v1. |
280
- | `agents.definitions.<name>.preLaunchEnv` | optional | Companion to `preLaunch`: list of env var names to append to groundcrew's `safehouse-clearance` `--env-pass=` flag, so `preLaunch` exports reach the agent without overriding `cmd` and losing the project's egress allowlist. Each entry must match `[A-Za-z_][A-Za-z0-9_]*`. Under `runner: "none"` exports already inherit and `preLaunchEnv` is a no-op. An empty array is a uniform no-op in every runner; a non-empty list is rejected when `cmd` already starts with `safehouse` or when `runner` resolves to `sdx`. |
281
- | `prompts.initial` | unattended template | First message sent to the agent: the execution wrapper around each task. The task description is the task-specific prompt. Placeholders: `{{task}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override only to change the execution contract for every task, such as team-wide review rules or tool conventions. Mutually exclusive with `prompts.promptFile`. |
282
- | `prompts.promptFile` | optional | Path to a UTF-8 file whose contents become `prompts.initial`, read at load time. Resolved relative to the config file's directory; `~` is expanded and absolute paths are used as-is. The JSON-friendly alternative to inlining a large prompt or `readFileSync`. Mutually exclusive with `prompts.initial`. |
283
- | `workspaceKind` | `"auto"` | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"`, `"tmux"`, or `"zellij"` to fail loudly when the chosen backend is missing. |
284
- | `local.runner` | `"auto"` | Local isolation backend. `"auto"` uses `safehouse` on macOS and `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly. |
285
- | `logging.file` | XDG state path | Append-mode log file. `log()` / `logEvent()` tee here in addition to stdout. Defaults to `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log`. |
292
+ | Key | Default | What it does |
293
+ | ----------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
294
+ | `sources` | `[]` | Additional pluggable task sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. Declare `{ kind: "linear", team: "ENG", statuses: { ... } }` to configure Linear task creation's default team and/or override Linear status names used for `in-progress` / `in-review` disambiguation. Disable the implicit Linear source with `{ kind: "linear", enabled: false }` (no API key required) — useful for shell-only setups. |
295
+ | `sources[].sandboxWritePaths` | optional | Shell sources only. Local directories that the `safehouse` and `srt` sandbox runners open for read and write while an agent works a task from that source. Use this when a shell-backed task store lives outside the worktree, such as `~/plans`. Paths may be absolute, relative to the agent working directory, or `~`-prefixed. Not supported by the `sdx` runner. |
296
+ | `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
297
+ | `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
298
+ | `git.branchPrefix` | OS username | Prefix groundcrew puts before the task id when naming a worktree branch (`<branchPrefix>-<task>`). Must be a slash-free slug of letters, digits, `.`, `_`, or `-`. Defaults to the OS account username. Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. Prefer a per-user config for personal prefixes — a committed `git.branchPrefix` gives every contributor the same branch prefix. |
299
+ | `workspace.projectDir` | **required** | Parent dir for cloned repos and the default task worktree root. |
300
+ | `workspace.worktreeDir` | optional | Parent dir for task worktrees. When unset, worktrees are created under `workspace.projectDir`. Changing this only affects worktrees discovered under the new root; clean up existing worktrees before switching it, or temporarily unset it when you need `crew cleanup` to find old worktrees. |
301
+ | `workspace.knownRepositories` | **required** | Repos searched for in task descriptions to infer where work belongs. Entries can be strings under `workspace.projectDir` or `{ name, projectDirOverride }` objects when a repo clone lives under a different parent dir. A task labeled for groundcrew (`agent-*`) fails fast when no known repo appears; unlabeled tasks are ignored. |
302
+ | `workspace.knownRepositories[].hooks.prepareWorktree` | optional | Per-repo operator hook. Used when the repo has no committed `.groundcrew/config.json` `hooks.prepareWorktree`; beats `defaults.hooks.prepareWorktree`. Set it on a repo's object entry for repos you can't or don't want to commit groundcrew files into. Reuses the same `hooks` container and hook contract as the other two layers. |
303
+ | `defaults.hooks.prepareWorktree` | optional | Global fallback repo-preparation command used only when neither the worktree's `.groundcrew/config.json` `hooks.prepareWorktree` nor the per-repo `knownRepositories[].hooks.prepareWorktree` is set. The hook runs after worktree creation and before the agent starts. Repo-local config and the per-repo layer both win. |
304
+ | `orchestrator.maximumInProgress` | `4` | Cap on in-progress tasks at once for this `crew` instance. |
305
+ | `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
306
+ | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. An agent whose codexbar session window exceeds this percentage is skipped that tick. Agents are also skipped when codexbar reports weekly usage over the current weekly paced budget. |
307
+ | `agents.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew start <TASK>` for unlabeled tasks. `crew run` ignores unlabeled tasks and does not apply this default. Must exist in `agents.definitions`. If you enable only `codex`, set `default: "codex"`. |
308
+ | `agents.definitions` | **required** | Enabled launch profile set. Built-in keys (`claude`, `codex`) can use `{}` to opt into the shipped preset. Custom profile names must provide `cmd` and `color`; use custom profiles such as `claude-fable` and `claude-opus` to select model-specific commands per task. |
309
+ | `agents.definitions.<name>.cmd` | preset for built-ins | Shell command launched for the agent. Required for custom profiles. Runs in the worktree through the resolved `local.runner`. `{{worktree}}` is replaced before launch; `{{sandbox}}` expands to the sbx sandbox name under the sdx runner and an empty string otherwise. |
310
+ | `agents.definitions.<name>.color` | preset for built-ins | Color for the workspace status pill (cmux only; tmux and zellij silently drop it). Required for custom profiles. |
311
+ | `agents.definitions.<name>.usage` | preset for built-ins | If set, codexbar usage is fetched for this agent and gated by `sessionLimitPercentage` plus the weekly paced budget when codexbar exposes a weekly window. When `usage.codexbar.source` is omitted, groundcrew uses `oauth` for Codex/Claude on macOS, `auto` for other macOS providers, and `cli` elsewhere. Set to `{ disabled: true }` to disable usage gating while keeping the agent enabled. |
312
+ | `agents.definitions.<name>.sandbox` | optional | Docker Sandboxes binding for the agent. Required at launch when `local.runner` resolves to `sdx`. Field: `agent` (required sbx agent name). Groundcrew assumes the `groundcrew-<agent>` sandbox already exists. |
313
+ | `agents.definitions.<name>.preLaunch` | optional | Host-only shell snippet run before the agent exec and outside Safehouse/sdx. Exports survive into the launch shell; under the default `safehouse` runner they are only forwarded to the agent when listed via `preLaunchEnv` or when `cmd` includes its own `safehouse --env-pass=NAMES`. `{{worktree}}` is substituted. A non-zero exit aborts launch. Not supported when `local.runner` resolves to `sdx` in v1. |
314
+ | `agents.definitions.<name>.preLaunchEnv` | optional | Companion to `preLaunch`: list of env var names to append to groundcrew's `safehouse-clearance` `--env-pass=` flag, so `preLaunch` exports reach the agent without overriding `cmd` and losing the project's egress allowlist. Each entry must match `[A-Za-z_][A-Za-z0-9_]*`. Under `runner: "none"` exports already inherit and `preLaunchEnv` is a no-op. An empty array is a uniform no-op in every runner; a non-empty list is rejected when `cmd` already starts with `safehouse` or when `runner` resolves to `sdx`. |
315
+ | `prompts.initial` | unattended template | First message sent to the agent: the execution wrapper around each task. The task description is the task-specific prompt. Placeholders: `{{task}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override only to change the execution contract for every task, such as team-wide review rules or tool conventions. Mutually exclusive with `prompts.promptFile`. |
316
+ | `prompts.promptFile` | optional | Path to a UTF-8 file whose contents become `prompts.initial`, read at load time. Resolved relative to the config file's directory; `~` is expanded and absolute paths are used as-is. The JSON-friendly alternative to inlining a large prompt or `readFileSync`. Mutually exclusive with `prompts.initial`. |
317
+ | `workspaceKind` | `"auto"` | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"`, `"tmux"`, or `"zellij"` to fail loudly when the chosen backend is missing. |
318
+ | `local.runner` | `"auto"` | Local isolation backend. `"auto"` uses `safehouse` on macOS and `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly. |
319
+ | `logging.file` | XDG state path | Append-mode log file. `log()` / `logEvent()` tee here in addition to stdout. Defaults to `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log`. |
package/docs/runners.md CHANGED
@@ -33,7 +33,7 @@ crew run --watch
33
33
 
34
34
  Watch `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.log` for `DENY` lines and add only the domains your agents actually need.
35
35
 
36
- `@clipboard-health/clearance` is pulled in transitively when you install groundcrew and provides the `clearance` / `clearance-ensure` bins used by Safehouse runs. See the [clearance README](https://github.com/ClipboardHealth/core-utils/tree/main/packages/clearance) for proxy env vars, log paths, and DNS rules.
36
+ `@clipboard-health/clearance` is pulled in transitively when you install groundcrew, but npm links its `clearance` / `clearance-ensure` bins only into the nested `node_modules/.bin/`, never onto your shell `PATH`. So groundcrew exposes its own first-class `crew-clearance-ensure` command (installed alongside `crew`) that dispatches straight to clearance's `clearance-ensure` entrypoint, forwarding all args, stdio, and exit code unchanged. See the [clearance README](https://github.com/ClipboardHealth/core-utils/tree/main/packages/clearance) for proxy env vars, log paths, and DNS rules.
37
37
 
38
38
  ## srt (Anthropic sandbox-runtime)
39
39
 
@@ -22,7 +22,8 @@ onboarding in this hook: no prompts, global installs, auth setup, runtime
22
22
  manager bootstrap (`nvm`, `pyenv`, `rustup`, `mise`, `asdf`), db seeds, husky,
23
23
  pre-commit, or local package linking.
24
24
 
25
- The hook runs from the repo root under every runner:
25
+ The hook runs from the worktree root unless the repo entry sets `workdir`; in
26
+ that case it runs from that subdirectory. Under every runner:
26
27
 
27
28
  - `safehouse`: inside a profile-neutral Safehouse wrap before the agent wrap.
28
29
  - `sdx`: inside the Docker Sandbox before the agent command.
@@ -32,7 +33,21 @@ Hook failures are advisory. Groundcrew logs the non-zero exit and still launches
32
33
  the agent so a flaky package registry or stale lockfile does not block the
33
34
  session.
34
35
 
35
- ## Defaults
36
+ ## Precedence
37
+
38
+ `prepareWorktree` resolves through three layers, highest priority first:
39
+
40
+ | Layer | Where | Author |
41
+ | ------------------------------------------------- | -------------------------- | ---------------- |
42
+ | `.groundcrew/config.json` `hooks.prepareWorktree` | committed in the repo | repo maintainers |
43
+ | `knownRepositories[].hooks.prepareWorktree` | `crew.config.ts`, per-repo | operator |
44
+ | `defaults.hooks.prepareWorktree` | `crew.config.ts`, global | operator |
45
+
46
+ The repo-committed file wins (it travels with the code and stays in sync with
47
+ the repo's build), then the per-repo operator layer, then the global default. A
48
+ repo with none of the three set skips the phase.
49
+
50
+ ### Global default
36
51
 
37
52
  For repos without local config, set a fallback in `crew.config.ts`:
38
53
 
@@ -51,6 +66,34 @@ Repo-local `.groundcrew/config.json` wins for that hook. A repo-local file
51
66
  without `hooks.prepareWorktree` still falls back to the `crew.config.ts`
52
67
  default.
53
68
 
69
+ ### Per-repo operator hook
70
+
71
+ When you can't (or don't want to) commit a `.groundcrew/config.json` into a repo
72
+ — a third-party repo, or one where adding groundcrew files is undesirable — set
73
+ the hook for just that repo from your own `crew.config.ts`:
74
+
75
+ ```ts
76
+ export default {
77
+ workspace: {
78
+ projectDir: "~/dev",
79
+ knownRepositories: [
80
+ "your-org/your-repo",
81
+ {
82
+ name: "other-org/their-repo",
83
+ hooks: {
84
+ prepareWorktree: "uv sync --dev --frozen",
85
+ },
86
+ },
87
+ ],
88
+ },
89
+ // ...
90
+ };
91
+ ```
92
+
93
+ This reuses the same `hooks` container and `prepareWorktree` contract as the
94
+ other two layers. It beats `defaults.hooks` but still yields to a committed
95
+ `.groundcrew/config.json` in that repo.
96
+
54
97
  ## Examples
55
98
 
56
99
  Python with uv:
@@ -18,6 +18,7 @@ export default {
18
18
  markInReview: "jira issue move ${id} 'In Review'",
19
19
  markDone: "jira issue move ${id} 'Done'",
20
20
  },
21
+ sandboxWritePaths: ["~/plans"],
21
22
  timeouts: { listTasks: 60_000, markInReview: 15_000, markDone: 15_000 },
22
23
  },
23
24
  ],
@@ -46,6 +47,13 @@ prompt tells them to run `GROUNDCREW_COMPLETE` only when it is set, the
46
47
  requested work is complete, no PR is needed, and any dirty worktree state is
47
48
  expected or explicitly allowed.
48
49
 
50
+ Set `sandboxWritePaths` when the shell source owns local files the agent must
51
+ read and update in place, such as a plan directory. Each path may be absolute,
52
+ relative to the agent working directory, or `~`-prefixed. Under the `safehouse`
53
+ and `srt` runners, groundcrew opens those directories for read and write only
54
+ for tasks from that source; an unqualified task id grants every eligible
55
+ source's declared paths. The `sdx` runner does not mount these host paths.
56
+
49
57
  ```json
50
58
  [
51
59
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.40.0",
3
+ "version": "4.42.0",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",
@@ -18,7 +18,8 @@
18
18
  "url": "git+https://github.com/ClipboardHealth/groundcrew.git"
19
19
  },
20
20
  "bin": {
21
- "crew": "./bin/run.js"
21
+ "crew": "./bin/run.js",
22
+ "crew-clearance-ensure": "./bin/crewClearanceEnsure.js"
22
23
  },
23
24
  "files": [
24
25
  "README.md",
@@ -71,7 +72,7 @@
71
72
  "@anthropic-ai/sandbox-runtime": "0.0.54",
72
73
  "@clipboard-health/clearance": "1.3.2",
73
74
  "@linear/sdk": "86.0.0",
74
- "cosmiconfig": "9.0.1",
75
+ "cosmiconfig": "9.0.2",
75
76
  "tslib": "2.8.1",
76
77
  "zod": "4.4.3"
77
78
  },
@@ -81,7 +82,7 @@
81
82
  "@nx/js": "22.7.5",
82
83
  "@tsconfig/node24": "24.0.4",
83
84
  "@tsconfig/strictest": "2.0.8",
84
- "@types/node": "25.9.2",
85
+ "@types/node": "25.9.3",
85
86
  "@typescript/native-preview": "7.0.0-dev.20260608.1",
86
87
  "@vitest/coverage-v8": "4.1.8",
87
88
  "cspell": "10.0.1",
@@ -92,7 +93,7 @@
92
93
  "lint-staged": "17.0.7",
93
94
  "markdownlint-cli2": "0.22.1",
94
95
  "nx": "22.7.5",
95
- "oxfmt": "0.53.0",
96
+ "oxfmt": "0.54.0",
96
97
  "oxlint": "1.68.0",
97
98
  "oxlint-tsgolint": "0.23.0",
98
99
  "syncpack": "15.3.1",