@tablation/crew 0.64.0 → 0.66.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 (2) hide show
  1. package/dist/cli.js +85 -7
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1449,6 +1449,7 @@ var HOOK_NAMES = [
1449
1449
  "ports",
1450
1450
  "isolate",
1451
1451
  "handoff",
1452
+ "ci_status",
1452
1453
  "version",
1453
1454
  "bump",
1454
1455
  "pr",
@@ -2792,7 +2793,8 @@ var HOOK_PURPOSE = [
2792
2793
  ["ports", "this worktree's port assignments; prints KEY=value overrides to export"],
2793
2794
  ["handoff", "leave the operator able to open what you built; put its output in your progress comment"],
2794
2795
  ["test", "run the suite"],
2795
- ["build", "build it"]
2796
+ ["build", "build it"],
2797
+ ["ci_status", "after you push and before you hand off: exit 0 means CI is green for your branch; on failure fix and push again, and say in your progress comment if it is still pending"]
2796
2798
  ];
2797
2799
  function hookTable(repo) {
2798
2800
  const declared = HOOK_PURPOSE.filter(([name]) => repo.hooks[name]);
@@ -13027,6 +13029,62 @@ function configuredRepos(text, route, base) {
13027
13029
  const dir = m.get("dir");
13028
13030
  return dir ? [{ name: basename7(String(dir)), dir: expandDir(String(dir), base) }] : [];
13029
13031
  }
13032
+ function routeSetsHooks(text, route, name) {
13033
+ const m = routeMapOf(parseDocument(text), route);
13034
+ const repos = m.get("repos", true);
13035
+ let target = m;
13036
+ if (isMap(repos)) target = repos.get(name, true);
13037
+ if (!isMap(target)) return false;
13038
+ const hooks = target.get("hooks", true);
13039
+ return isMap(hooks) && hooks.items.length > 0;
13040
+ }
13041
+ function listRepoRows(i) {
13042
+ const checkouts = configuredRepos(i.text, i.route, i.base);
13043
+ const claimed = /* @__PURE__ */ new Set();
13044
+ const hooksSourceOf = (c) => i.exists(c.dir) && i.repoHasHooks(c.dir) ? "repo" : routeSetsHooks(i.text, i.route, c.name) ? "route" : "none";
13045
+ const originFor = (c, remote) => {
13046
+ const o = i.originOf(c.dir) ?? null;
13047
+ if (!o) return null;
13048
+ return remote !== null && normalizeRemote(o) === normalizeRemote(remote) ? null : o;
13049
+ };
13050
+ const rows = [];
13051
+ const names = i.resolved?.repoNames ?? {};
13052
+ const remotes = i.resolved?.repoRemotes ?? {};
13053
+ for (const [id, name] of Object.entries(names)) {
13054
+ const remote = remotes[id] ?? null;
13055
+ const want = normalizeRemote(remote);
13056
+ let idx = checkouts.findIndex((c2, n) => !claimed.has(n) && want !== void 0 && normalizeRemote(i.originOf(c2.dir)) === want);
13057
+ if (idx < 0) idx = checkouts.findIndex((c2, n) => !claimed.has(n) && c2.name === name);
13058
+ const c = idx >= 0 ? checkouts[idx] : void 0;
13059
+ if (idx >= 0) claimed.add(idx);
13060
+ rows.push({
13061
+ name,
13062
+ remote,
13063
+ repoRowId: id,
13064
+ attached: c !== void 0,
13065
+ dir: c?.dir ?? null,
13066
+ exists: c ? i.exists(c.dir) : false,
13067
+ origin: c ? originFor(c, remote) : null,
13068
+ matchedRow: c ? name : null,
13069
+ hooksSource: c ? hooksSourceOf(c) : "none"
13070
+ });
13071
+ }
13072
+ checkouts.forEach((c, n) => {
13073
+ if (claimed.has(n)) return;
13074
+ rows.push({
13075
+ name: c.name,
13076
+ remote: null,
13077
+ repoRowId: null,
13078
+ attached: true,
13079
+ dir: c.dir,
13080
+ exists: i.exists(c.dir),
13081
+ origin: originFor(c, null),
13082
+ matchedRow: null,
13083
+ hooksSource: hooksSourceOf(c)
13084
+ });
13085
+ });
13086
+ return rows;
13087
+ }
13030
13088
  function planRepoAdd(i) {
13031
13089
  const doc = parseDocument(i.text);
13032
13090
  if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
@@ -13225,11 +13283,11 @@ function detectRecipes(f) {
13225
13283
  {
13226
13284
  id: "github-actions-ci-status",
13227
13285
  title: "Check CI status before hand-off",
13228
- description: "Ask GitHub whether the latest run on the branch passed before a ticket is handed to QA. The repo spec has no hook for this yet, so it is reported but cannot be written.",
13286
+ description: "Ask GitHub whether the latest run on the branch passed before a ticket is handed to QA (`hooks.ci_status`).",
13229
13287
  evidence,
13230
- hooks: {},
13231
- check: `gh run list --branch "$CREW_BRANCH" --limit 1 --json conclusion -q '.[0].conclusion == "success"'`,
13232
- writable: false,
13288
+ // `gh` prints the conclusion and exits 0 either way, so the test is on the text.
13289
+ hooks: { ci_status: `test "$(gh run list --branch "$CREW_BRANCH" --limit 1 --json conclusion -q '.[0].conclusion')" = success` },
13290
+ writable: true,
13233
13291
  defaultOn: false
13234
13292
  }
13235
13293
  ];
@@ -15416,7 +15474,7 @@ function usage() {
15416
15474
  crew repos add [ROUTE] [NAME] [PATH] attach a local checkout to a route in crew.yaml (--dry-run previews)
15417
15475
  crew repos clone [ROUTE] NAME clone a Repos row's remote into the route directory and attach it (--dir PATH, --dry-run, --json)
15418
15476
  crew repos hooks [ROUTE] NAME suggest test/build hooks for a checkout (--agent, --write route|repo, --recipes, --json)
15419
- crew repos list ROUTE the checkouts a route has, with the tracker's Repos row each matches
15477
+ crew repos list ROUTE the checkouts a route has, with the tracker's Repos row each matches (--json: every Repos row too)
15420
15478
  crew logbook list [route] recent Agent Log entries, filterable by --role/--ticket
15421
15479
  crew logbook show [route] ID [--prompt] one entry; --prompt reconstructs and verifies its prompt
15422
15480
  crew passengers [route] sync Host Passengers containers/tunnels once; what the passengers unit invokes
@@ -17104,7 +17162,7 @@ ${changed2.map((c) => ` - ${c}
17104
17162
  case "repos": {
17105
17163
  const sub = positional[1];
17106
17164
  const usage2 = () => {
17107
- 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>\ncrew repos hooks [workspace/project] <name> [--suggest] [--agent] [--write route|repo] [--recipes ID,ID] [--json]\n");
17165
+ 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] [--json]\n");
17108
17166
  process.exit(2);
17109
17167
  };
17110
17168
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
@@ -17254,6 +17312,26 @@ ${changed2.map((c) => ` - ${c}
17254
17312
  const base = dirname19(cfg.configFile);
17255
17313
  try {
17256
17314
  if (sub === "list") {
17315
+ if (flag("json")) {
17316
+ const rows = listRepoRows({
17317
+ text,
17318
+ route: routeName,
17319
+ base,
17320
+ resolved: resolvedRemotes,
17321
+ originOf,
17322
+ exists: (d) => existsSync31(d),
17323
+ repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
17324
+ try {
17325
+ return Object.keys(parseRepoConfig(readFileSync25(resolvePath(d, f), "utf8"), f).hooks).length > 0;
17326
+ } catch {
17327
+ return false;
17328
+ }
17329
+ })
17330
+ });
17331
+ process.stdout.write(`${JSON.stringify({ route: routeName, repos: rows })}
17332
+ `);
17333
+ break;
17334
+ }
17257
17335
  const repos = configuredRepos(text, routeName, base);
17258
17336
  if (repos.length === 0) process.stdout.write(`route ${routeName} has no checkout configured \u2014 \`crew repos add ${routeName} <path>\`
17259
17337
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.64.0",
3
+ "version": "0.66.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",