@tablation/crew 0.72.3 → 0.73.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +42 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -263,7 +263,7 @@ crew inbox [--member NAME] your tickets across every workspace
263
263
  crew connect WS[/PROJECT] resolve a workspace/project's ids into the state tree
264
264
  crew repos add [ROUTE] [NAME] [PATH] attach a local checkout to a route in crew.yaml, creating its Repos row if missing (--dry-run previews)
265
265
  crew repos list ROUTE the checkouts a route has, and the tracker Repos row each matches
266
- crew repos hooks [ROUTE] NAME suggest test/build hooks for a checkout (--write route|repo writes them, --recipes, --hook KEY=CMD, --agent, --json)
266
+ crew repos hooks [ROUTE] NAME suggest test/build hooks for a checkout (--write route|repo [--replace] writes them, --clear route removes them, --recipes, --hook KEY=CMD, --agent, --json)
267
267
  crew install [--replace] write and load this platform's scheduler unit (macOS: clears a crew installed from another path)
268
268
  crew uninstall [--all] unload and remove it (--all: every crew launchd unit, whichever path it came from)
269
269
  ```
package/dist/cli.js CHANGED
@@ -8692,6 +8692,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
8692
8692
  "area",
8693
8693
  "base-url",
8694
8694
  "choose",
8695
+ "clear",
8695
8696
  "dir",
8696
8697
  "extra-path",
8697
8698
  "harness",
@@ -14363,7 +14364,7 @@ function repoDirOf(text, base, route, name) {
14363
14364
  }
14364
14365
  return hit.dir;
14365
14366
  }
14366
- function planRouteHooks(text, route, name, hooks) {
14367
+ function planRouteHooks(text, route, name, hooks, opts = {}) {
14367
14368
  const doc = parseDocument3(text);
14368
14369
  if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
14369
14370
  const m = routeMapOf(doc, route);
@@ -14388,15 +14389,34 @@ function planRouteHooks(text, route, name, hooks) {
14388
14389
  }
14389
14390
  const written = [];
14390
14391
  const kept = [];
14392
+ const replaced = [];
14391
14393
  for (const [k, v] of Object.entries(hooks)) {
14392
14394
  if (v === void 0) continue;
14393
- if (hooksMap.has(k)) kept.push(k);
14395
+ if (hooksMap.has(k) && !opts.replace) kept.push(k);
14394
14396
  else {
14397
+ if (hooksMap.has(k)) replaced.push(k);
14395
14398
  hooksMap.set(k, v);
14396
14399
  written.push(k);
14397
14400
  }
14398
14401
  }
14399
- return { text: String(doc), written, kept };
14402
+ return { text: String(doc), written, kept, replaced };
14403
+ }
14404
+ function clearRouteHooks(text, route, name) {
14405
+ const doc = parseDocument3(text);
14406
+ if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
14407
+ const m = routeMapOf(doc, route);
14408
+ const repos = m.get("repos", true);
14409
+ let target = m;
14410
+ if (isMap2(repos)) {
14411
+ const entry = repos.get(name, true);
14412
+ if (!isMap2(entry)) return { text, removed: [] };
14413
+ target = entry;
14414
+ }
14415
+ const current = target.get("hooks", true);
14416
+ if (!isMap2(current)) return { text, removed: [] };
14417
+ const removed = current.items.map((i) => String(i.key.value ?? i.key));
14418
+ target.delete("hooks");
14419
+ return { text: String(doc), removed };
14400
14420
  }
14401
14421
  function starterRepoConfig(hooks, release) {
14402
14422
  const lines = [
@@ -18358,7 +18378,7 @@ ${changed2.map((c) => ` - ${c}
18358
18378
  case "repos": {
18359
18379
  const sub = positional[1];
18360
18380
  const usage2 = () => {
18361
- process.stderr.write("crew repos add [workspace/project] [name] [path] [--name N] [--project P] [--dry-run] [--no-create]\ncrew repos clone [workspace/project] <name> [--dir PATH] [--dry-run] [--json]\ncrew repos list <workspace/project> [--json]\ncrew repos hooks [workspace/project] <name> [--suggest] [--agent] [--write route|repo] [--recipes ID,ID] [--hook KEY=CMD ...] [--json]\n");
18381
+ process.stderr.write("crew repos add [workspace/project] [name] [path] [--name N] [--project P] [--dry-run] [--no-create]\ncrew repos clone [workspace/project] <name> [--dir PATH] [--dry-run] [--json]\ncrew repos list <workspace/project> [--json]\ncrew repos hooks [workspace/project] <name> [--suggest] [--agent] [--write route|repo [--replace]] [--clear route] [--recipes ID,ID] [--hook KEY=CMD ...] [--json]\n");
18362
18382
  process.exit(2);
18363
18383
  };
18364
18384
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
@@ -18384,6 +18404,10 @@ ${changed2.map((c) => ` - ${c}
18384
18404
  const suggestion = suggestHooks(files);
18385
18405
  const write = value("write");
18386
18406
  if (write !== void 0 && write !== "route" && write !== "repo") throw new ReposError(`--write takes "route" or "repo" (got "${write}")`);
18407
+ const clear2 = value("clear");
18408
+ if (clear2 !== void 0 && clear2 !== "route") throw new ReposError(`--clear takes "route" (got "${clear2}")`);
18409
+ if (clear2 && (write || flag("suggest") || flag("agent"))) throw new ReposError("--clear cannot be combined with --write, --suggest or --agent");
18410
+ if (flag("replace") && write !== "route") throw new ReposError("--replace only applies with --write route");
18387
18411
  const recipeIds = (value("recipes") ?? "").split(",").map((x) => x.trim()).filter((x) => x && x !== "none");
18388
18412
  if (recipeIds.length > 0 && !write) throw new ReposError("--recipes only applies with --write route|repo");
18389
18413
  if (flag("agent") && write) throw new ReposError("--agent is read-only; drop --write (write the sniffed hooks, then re-run --agent to compare)");
@@ -18411,20 +18435,26 @@ ${changed2.map((c) => ` - ${c}
18411
18435
  const base2 = selectRecipes(suggestion, recipeIds);
18412
18436
  const picked = { ...base2, hooks: { ...base2.hooks, ...overrides } };
18413
18437
  let written;
18438
+ let cleared;
18439
+ if (clear2) {
18440
+ const plan = clearRouteHooks(configText, a.route, a.name);
18441
+ if (plan.removed.length > 0) writeFileSync23(cfg.configFile, plan.text);
18442
+ cleared = { file: cfg.configFile, removed: plan.removed };
18443
+ }
18414
18444
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
18415
18445
  if (write === "route") {
18416
- const plan = planRouteHooks(configText, a.route, a.name, picked.hooks);
18446
+ const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
18417
18447
  writeFileSync23(cfg.configFile, plan.text);
18418
- written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept };
18448
+ written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
18419
18449
  } else if (write === "repo") {
18420
18450
  const have = existingRepoConfig(files);
18421
18451
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
18422
18452
  const file = resolvePath(dir, ".crew.yaml");
18423
18453
  writeFileSync23(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
18424
- written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [] };
18454
+ written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
18425
18455
  }
18426
18456
  if (flag("json")) {
18427
- process.stdout.write(`${JSON.stringify({ route: a.route, repo: a.name, dir, ...suggestion, agent, agentError, written: written ?? null })}
18457
+ process.stdout.write(`${JSON.stringify({ route: a.route, repo: a.name, dir, ...suggestion, agent, agentError, written: written ?? null, cleared: cleared ?? null })}
18428
18458
  `);
18429
18459
  } else {
18430
18460
  if (suggestion.source) process.stdout.write(`${a.name}: proposed from ${suggestion.source}
@@ -18445,8 +18475,12 @@ ${changed2.map((c) => ` - ${c}
18445
18475
  }
18446
18476
  if (agentError) process.stderr.write(`crew repos: --agent failed (${agentError.reason}): ${agentError.message}
18447
18477
  `);
18478
+ if (cleared) process.stdout.write(cleared.removed.length ? `removed route hooks ${cleared.removed.join(", ")} from ${cleared.file}
18479
+ ` : "no route hooks to remove\n");
18448
18480
  if (written) {
18449
18481
  process.stdout.write(`wrote ${written.hooks.join(", ") || "nothing new"} to ${written.file}
18482
+ `);
18483
+ if (written.replaced.length) process.stdout.write(`replaced route hooks: ${written.replaced.join(", ")}
18450
18484
  `);
18451
18485
  if (written.kept.length) process.stdout.write(`kept existing route hooks: ${written.kept.join(", ")}
18452
18486
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.72.3",
3
+ "version": "0.73.0",
4
4
  "description": "A standing team of headless agents that picks work off a Tablation board, does it on a machine you control, and reports back.",
5
5
  "type": "module",
6
6
  "license": "MIT",