@tablation/crew 0.6.0 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +48 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -207,7 +207,7 @@ crew ports [route] which checkout owns which ports, and what is up
207
207
  crew reap [route] kill servers left behind by removed worktrees
208
208
  crew drop [route] NNN remove a merged ticket's worktree and branch
209
209
  crew sync [route] fast-forward the checkout and its worktrees from the remote
210
- crew pause|resume [route] [R] pause everything, or one role
210
+ crew pause|resume [route] [R|release] pause everything, one role, or releases only (passengers keep running)
211
211
  crew log [route] tail the log
212
212
  crew inbox [--member NAME] your tickets across every workspace
213
213
  crew connect WS[/PROJECT] resolve a workspace/project's ids into the state tree
package/dist/cli.js CHANGED
@@ -1219,6 +1219,31 @@ var State = class _State {
1219
1219
  resume(role) {
1220
1220
  rmSync(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
1221
1221
  }
1222
+ /**
1223
+ * The release phase (merge, deploy, release) has its own sentinel, one
1224
+ * level below the whole-crew pause: `crew pause release` stops shipping
1225
+ * while agents keep working.
1226
+ */
1227
+ isReleasePaused() {
1228
+ return existsSync3(this.path(".release-paused"));
1229
+ }
1230
+ setReleasePaused(paused) {
1231
+ if (paused) writeFileSync(this.path(".release-paused"), "");
1232
+ else rmSync(this.path(".release-paused"), { force: true });
1233
+ }
1234
+ /**
1235
+ * Why a release cycle must not run right now, or null when it may. Every
1236
+ * release entry point checks this first — the release timer, `crew release`,
1237
+ * `crew merge`, `crew deploy` and the inline release a `run` cycle used to
1238
+ * do — so a paused ship cannot merge, deploy, or file release alerts.
1239
+ * A release already in flight when the pause lands finishes; only new
1240
+ * cycles are refused.
1241
+ */
1242
+ releaseSkipReason() {
1243
+ if (this.isPaused()) return "the crew is paused";
1244
+ if (this.isReleasePaused()) return "releases are paused";
1245
+ return null;
1246
+ }
1222
1247
  /**
1223
1248
  * Take an exclusive lock, or report who holds it.
1224
1249
  *
@@ -8525,6 +8550,7 @@ var SpawnBreaker = class {
8525
8550
  }
8526
8551
  };
8527
8552
  async function runOnePass(o) {
8553
+ if (o.state.isPaused()) return { pending: [], started: [], skipped: [] };
8528
8554
  await hydrateApiKeys([o.route], o.getSessionStore);
8529
8555
  const emit = o.newEmitter();
8530
8556
  const decide = o.decide ?? decideCycle;
@@ -8852,7 +8878,8 @@ function usage() {
8852
8878
  crew rotate-passenger-url [route] force a Host Passengers tunnel to reconnect with a fresh public URL
8853
8879
  crew unassign [route] NNN hand back a session's ticket \u2014 clears assignee, next cycle picks it up
8854
8880
  crew sync [route] fast-forward the checkout and its worktrees from the remote
8855
- crew pause|resume [route] [R] pause everything, or one role
8881
+ crew pause|resume [route] [R|release] pause everything, one role, or releases only (merge/deploy/release; passengers keeps running).
8882
+ resume with no argument clears only the whole-crew pause; a role or release pause needs its own resume
8856
8883
  crew log [route] tail the log
8857
8884
  crew inbox [--member NAME] your tickets across every workspace (or a colleague's)
8858
8885
  crew connect resolve a workspace's ids into a crew.yaml block
@@ -8953,6 +8980,11 @@ if (command === "passenger-sync-daemon") {
8953
8980
  urlFor: (slug) => publicUrlFor(slug, relayHost, cfg.ship.relayHttpPort, cfg.ship.relayPublicDomain),
8954
8981
  warn: (m) => emit.warn(m, { step: "passengers" })
8955
8982
  };
8983
+ }, releaseRefused = function() {
8984
+ const why = state.releaseSkipReason();
8985
+ if (!why) return false;
8986
+ emit.emit(`${why} \u2014 release skipped`, { step: "release" });
8987
+ return true;
8956
8988
  }, reportUnplaceable = function(c, unplaceable, verified, emit2) {
8957
8989
  if (unplaceableReported.has(c.route)) return;
8958
8990
  unplaceableReported.add(c.route);
@@ -8961,7 +8993,7 @@ if (command === "passenger-sync-daemon") {
8961
8993
  else emit2.emit(note.message, { step: "release" });
8962
8994
  }
8963
8995
  };
8964
- requireArmed2 = requireArmed, parseTicketNumber2 = parseTicketNumber, passengerApiKeys2 = passengerApiKeys, endpointApiFor2 = endpointApiFor, endpointReconcileDeps2 = endpointReconcileDeps, reportUnplaceable2 = reportUnplaceable;
8996
+ requireArmed2 = requireArmed, parseTicketNumber2 = parseTicketNumber, passengerApiKeys2 = passengerApiKeys, endpointApiFor2 = endpointApiFor, endpointReconcileDeps2 = endpointReconcileDeps, releaseRefused2 = releaseRefused, reportUnplaceable2 = reportUnplaceable;
8965
8997
  let cfg;
8966
8998
  let firstRun;
8967
8999
  try {
@@ -9003,7 +9035,7 @@ if (command === "passenger-sync-daemon") {
9003
9035
  const releaseFleetWide = ["merge", "deploy", "release"].includes(command) && flag("fleet");
9004
9036
  const DAEMON_SUBCOMMANDS = /* @__PURE__ */ new Set(["start", "stop", "status", "restart"]);
9005
9037
  const named = command === "daemon" && DAEMON_SUBCOMMANDS.has(positional[1] ?? "") ? positional[2] : positional[1];
9006
- const fleetWide = command === "inbox" || command === "connect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || releaseFleetWide || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1;
9038
+ const fleetWide = command === "inbox" || command === "connect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || releaseFleetWide || command === "pause" || command === "resume" || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1;
9007
9039
  let route;
9008
9040
  try {
9009
9041
  route = fleetWide ? cfg.routes[0] : findRoute(cfg, named);
@@ -9093,6 +9125,7 @@ if (command === "passenger-sync-daemon") {
9093
9125
  }
9094
9126
  }
9095
9127
  async function releaseFleet(opts = {}) {
9128
+ if (releaseRefused()) return;
9096
9129
  const summaries = [];
9097
9130
  if (!dryRun) state.releaseHeartbeat().touch();
9098
9131
  for (const c of cfg.routes) {
@@ -9103,6 +9136,7 @@ if (command === "passenger-sync-daemon") {
9103
9136
  if (!dryRun) state.releaseHeartbeat().touch();
9104
9137
  }
9105
9138
  async function releaseTargets(opts = {}) {
9139
+ if (releaseRefused()) return;
9106
9140
  const only = value("repo");
9107
9141
  const targets = reposOf(route);
9108
9142
  const chosen = only ? targets.filter((t) => t.name === only) : targets;
@@ -11022,6 +11056,12 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
11022
11056
  }
11023
11057
  case "pause":
11024
11058
  case "resume": {
11059
+ if (positional[1] === "release" || positional[2] === "release") {
11060
+ state.setReleasePaused(command === "pause");
11061
+ process.stdout.write(`${command}d releases
11062
+ `);
11063
+ break;
11064
+ }
11025
11065
  const role = positional[2] ?? (["dev", "design", "qa"].includes(positional[1] ?? "") ? positional[1] : void 0);
11026
11066
  if (command === "pause") state.pause(role);
11027
11067
  else state.resume(role);
@@ -11113,6 +11153,9 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
11113
11153
  },
11114
11154
  crew: {
11115
11155
  paused: state.isPaused(),
11156
+ // The release timer honors the whole-crew pause too, so this is
11157
+ // true whenever releases are stopped for either reason.
11158
+ releasePaused: state.releaseSkipReason() !== null,
11116
11159
  roles: ROLE_NAMES.map((r) => ({ name: r, label: ROLE_LABEL[r], paused: state.isRolePaused(r) })),
11117
11160
  seats: seats2
11118
11161
  },
@@ -11156,6 +11199,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
11156
11199
  route: ${route.route} -> ${route.dir}
11157
11200
  enabled: ${route.enabled}
11158
11201
  crew: ${state.isPaused() ? "paused" : "active"}
11202
+ release: ${state.releaseSkipReason() ? "paused" : "active"}
11159
11203
  watermark: ${state.watermark()}
11160
11204
  `
11161
11205
  );
@@ -11357,4 +11401,5 @@ var parseTicketNumber2;
11357
11401
  var passengerApiKeys2;
11358
11402
  var endpointApiFor2;
11359
11403
  var endpointReconcileDeps2;
11404
+ var releaseRefused2;
11360
11405
  var reportUnplaceable2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
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",