@catalyst-cloud/cli 0.13.0 → 0.13.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 (137) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +26 -29
  3. package/dist/args.js +16 -0
  4. package/dist/ask.js +30 -18
  5. package/dist/capabilities.js +5 -0
  6. package/dist/cli.js +18 -5
  7. package/dist/environment.js +1 -1
  8. package/dist/legacy.js +224 -0
  9. package/dist/project.js +129 -0
  10. package/dist/release.js +1 -1
  11. package/dist/repo.js +297 -0
  12. package/dist/runtime-verb.js +1 -1
  13. package/dist/secret.js +1 -1
  14. package/dist/skill-shape.js +21 -13
  15. package/dist/skills.js +44 -4
  16. package/dist/transport.js +26 -1
  17. package/package.json +5 -4
  18. package/skills/catalyst-github/SKILL.md +7 -7
  19. package/skills/catalyst-github/references/is-it-mergeable.md +8 -2
  20. package/skills/catalyst-github/references/what-a-pr-accumulates.md +3 -3
  21. package/skills/catalyst-github/scripts/is-it-mergeable.mjs +3 -3
  22. package/skills/catalyst-github/scripts/lib/cli.mjs +9 -7
  23. package/skills/catalyst-github/scripts/lib/credential.mjs +2 -2
  24. package/skills/catalyst-github/scripts/read-pr.mjs +2 -2
  25. package/skills/catalyst-linear/SKILL.md +17 -19
  26. package/skills/catalyst-linear/agents/openai.yaml +1 -1
  27. package/skills/catalyst-linear/agents/portability.yaml +1 -1
  28. package/skills/catalyst-linear/references/reading-a-ticket.md +9 -26
  29. package/skills/catalyst-linear/references/what-a-ticket-accumulates.md +2 -2
  30. package/skills/catalyst-linear/references/writing-to-linear.md +7 -9
  31. package/skills/catalyst-linear/scripts/comment.mjs +1 -1
  32. package/skills/catalyst-linear/scripts/create-ticket.mjs +1 -1
  33. package/skills/catalyst-linear/scripts/label.mjs +1 -1
  34. package/skills/catalyst-linear/scripts/lib/cli.mjs +9 -7
  35. package/skills/catalyst-linear/scripts/lib/credential.mjs +2 -2
  36. package/skills/catalyst-linear/scripts/move.mjs +1 -1
  37. package/skills/catalyst-linear/scripts/read-ticket.mjs +1 -1
  38. package/skills/catalyst-linear/scripts/search.mjs +1 -1
  39. package/skills/catalyst-onboard/SKILL.md +37 -57
  40. package/skills/catalyst-onboard/agents/openai.yaml +2 -2
  41. package/skills/catalyst-onboard/agents/portability.yaml +1 -1
  42. package/skills/catalyst-onboard/references/connecting-this-machine.md +35 -0
  43. package/skills/catalyst-onboard/references/declaring-a-repository.md +7 -17
  44. package/skills/catalyst-onboard/references/local-sync.md +19 -7
  45. package/skills/catalyst-onboard/references/reading-ready.md +79 -0
  46. package/skills/catalyst-onboard/references/replacing-a-credential.md +8 -33
  47. package/skills/catalyst-onboard/references/repository-agent-setup.md +23 -0
  48. package/skills/catalyst-onboard/references/skill-sources.md +13 -19
  49. package/skills/catalyst-onboard/references/the-one-path.md +18 -74
  50. package/skills/catalyst-onboard/references/what-a-phase-needs.md +21 -28
  51. package/skills/catalyst-onboard/references/what-the-browser-owns.md +20 -37
  52. package/skills/{catalyst-setup → catalyst-onboard}/scripts/check.mjs +10 -9
  53. package/skills/catalyst-onboard/scripts/lib/cli.mjs +6 -4
  54. package/skills/catalyst-onboard/scripts/lib/credential.mjs +2 -2
  55. package/skills/catalyst-onboard/scripts/where-am-i.mjs +60 -4
  56. package/skills/run-this-project/SKILL.md +23 -25
  57. package/skills/run-this-project/agents/openai.yaml +1 -1
  58. package/skills/run-this-project/agents/portability.yaml +1 -1
  59. package/skills/run-this-project/references/making-work-ready.md +20 -37
  60. package/skills/run-this-project/references/reacting-to-events.md +25 -48
  61. package/skills/run-this-project/references/stalls-and-escalation.md +20 -42
  62. package/skills/run-this-project/scripts/lib/cli.mjs +7 -5
  63. package/skills/run-this-project/scripts/lib/credential.mjs +2 -2
  64. package/skills/run-this-project/scripts/scope-status.mjs +0 -0
  65. package/skills/run-this-project/scripts/watch-scope.mjs +1 -1
  66. package/skills/unstick/SKILL.md +12 -12
  67. package/skills/unstick/agents/openai.yaml +1 -1
  68. package/skills/unstick/agents/portability.yaml +1 -1
  69. package/skills/unstick/references/playbook.md +13 -13
  70. package/skills/unstick/scripts/lib/cli.mjs +6 -4
  71. package/skills/unstick/scripts/lib/credential.mjs +2 -2
  72. package/skills/unstick/scripts/unstick.mjs +1 -1
  73. package/skills/what-needs-me/SKILL.md +12 -14
  74. package/skills/what-needs-me/agents/openai.yaml +2 -2
  75. package/skills/what-needs-me/agents/portability.yaml +1 -1
  76. package/skills/what-needs-me/references/raising-a-decision.md +8 -16
  77. package/skills/what-needs-me/references/reading-the-inbox.md +6 -13
  78. package/skills/what-needs-me/references/settling-an-answer.md +6 -12
  79. package/skills/what-needs-me/scripts/inbox.mjs +14 -13
  80. package/skills/what-needs-me/scripts/lib/cli.mjs +6 -4
  81. package/skills/what-needs-me/scripts/lib/credential.mjs +2 -2
  82. package/skills/what-needs-me/scripts/raise.mjs +1 -1
  83. package/skills/what-needs-me/scripts/settle.mjs +1 -1
  84. package/skills/what-this-repo-needs/SKILL.md +10 -19
  85. package/skills/what-this-repo-needs/references/what-each-group-means.md +14 -44
  86. package/skills/what-this-repo-needs/scripts/check.mjs +2 -2
  87. package/skills/what-this-repo-needs/scripts/inventory.mjs +2 -2
  88. package/skills/what-this-repo-needs/scripts/lib/cli.mjs +11 -9
  89. package/skills/what-this-repo-needs/scripts/lib/credential.mjs +2 -2
  90. package/skills/whats-happening/SKILL.md +27 -21
  91. package/skills/whats-happening/agents/openai.yaml +1 -1
  92. package/skills/whats-happening/references/coding-accounts.md +24 -0
  93. package/skills/whats-happening/references/routing-work.md +11 -29
  94. package/skills/whats-happening/references/settings-and-where-they-live.md +20 -0
  95. package/skills/whats-happening/references/stages-and-mapping.md +25 -0
  96. package/skills/whats-happening/references/status-reply.md +21 -23
  97. package/skills/whats-happening/references/the-ladder.md +30 -0
  98. package/skills/whats-happening/references/what-runs-next.md +32 -0
  99. package/skills/whats-happening/references/when-a-phase-fails.md +27 -0
  100. package/skills/whats-happening/references/why-is-it-stuck.md +30 -44
  101. package/skills/whats-happening/scripts/explain.mjs +1 -1
  102. package/skills/whats-happening/scripts/lib/cli.mjs +7 -5
  103. package/skills/whats-happening/scripts/lib/credential.mjs +2 -2
  104. package/skills/{how-catalyst-works → whats-happening}/scripts/show-my-map.mjs +18 -32
  105. package/skills/whats-happening/scripts/snapshot.mjs +10 -6
  106. package/skills/catalyst-onboard/references/choosing-a-coding-account.md +0 -52
  107. package/skills/catalyst-onboard/references/who-fixes-what.md +0 -64
  108. package/skills/catalyst-setup/SKILL.md +0 -36
  109. package/skills/catalyst-setup/agents/openai.yaml +0 -6
  110. package/skills/catalyst-setup/agents/portability.yaml +0 -4
  111. package/skills/catalyst-setup/references/what-each-check-means.md +0 -88
  112. package/skills/catalyst-setup/scripts/lib/cli.mjs +0 -103
  113. package/skills/catalyst-setup/scripts/lib/credential.mjs +0 -29
  114. package/skills/catalyst-setup/scripts/replica-status.mjs +0 -46
  115. package/skills/connect-me/SKILL.md +0 -63
  116. package/skills/connect-me/agents/openai.yaml +0 -6
  117. package/skills/connect-me/agents/portability.yaml +0 -5
  118. package/skills/connect-me/references/keeping-the-replica-running.md +0 -88
  119. package/skills/connect-me/scripts/lib/cli.mjs +0 -185
  120. package/skills/connect-me/scripts/lib/credential.mjs +0 -29
  121. package/skills/connect-me/scripts/verify-connection.mjs +0 -68
  122. package/skills/how-catalyst-works/SKILL.md +0 -45
  123. package/skills/how-catalyst-works/agents/openai.yaml +0 -6
  124. package/skills/how-catalyst-works/agents/portability.yaml +0 -4
  125. package/skills/how-catalyst-works/references/coding-accounts.md +0 -51
  126. package/skills/how-catalyst-works/references/settings-and-where-they-live.md +0 -20
  127. package/skills/how-catalyst-works/references/stages-and-mapping.md +0 -56
  128. package/skills/how-catalyst-works/references/the-ladder.md +0 -41
  129. package/skills/how-catalyst-works/references/what-catalyst-is.md +0 -30
  130. package/skills/how-catalyst-works/references/what-runs-next.md +0 -95
  131. package/skills/how-catalyst-works/references/when-a-phase-fails.md +0 -57
  132. package/skills/how-catalyst-works/scripts/explain-ticket.mjs +0 -41
  133. package/skills/how-catalyst-works/scripts/lib/cli.mjs +0 -164
  134. package/skills/how-catalyst-works/scripts/lib/credential.mjs +0 -29
  135. package/skills/how-catalyst-works/scripts/whats-running.mjs +0 -65
  136. package/skills/whats-happening/references/reading-the-board.md +0 -43
  137. package/skills/whats-happening/references/reprioritising.md +0 -37
@@ -14,6 +14,7 @@ import { runLocalSync } from "./local-sync.mjs";
14
14
  const SPEC = {
15
15
  next: { help: "print only the single next step" },
16
16
  json: { help: "one JSON document instead of the report" },
17
+ repo: { value: true, help: "a checkout of a mapped repository on this machine: its agent setup (AGENTS.md block, portable layout) is read and reported, never changed" },
17
18
  };
18
19
  const NOTES = [
19
20
  "Reads, in this order: `status` (machine), `ready --json` (machine checks and project checks, kept apart),",
@@ -22,6 +23,8 @@ const NOTES = [
22
23
  "`contract --path codingAccounts` and `accounts --json` (coding accounts, and which one needs a new credential), and each",
23
24
  "project's hosts_current check with its fixedWhere (host), and each project's environment_declared check with its per-repository notes (repository declarations). The teams read is refreshed, so a mapping just saved reads back mapped.",
24
25
  "Reads `capabilities --json` once: when the installed CLI can run a step for this person's role (`team check`, `team map`), the next step is that command; otherwise the page, with who can.",
26
+ "Reads `legacy --json` when the CLI has the verb: leftovers of the old local Catalyst runtime become a machine line and a note; removing them is the person's yes.",
27
+ "With --repo <path>, reads `repo agent-setup <path> --json` (a read; nothing changes) and adds a `repository agent setup` part that never blocks: the Catalyst block in AGENTS.md and the portable layout, with the writes named for the person to approve.",
25
28
  "Also runs `gh repo view <owner>/thoughts` for each registered repository's owner, as a note: it shows the repository exists, never that the GitHub App can reach it.",
26
29
  "When the tenant serves a contract version this CLI refuses, runs `npm view @catalyst-cloud/cli version` once to say whether a newer CLI is published.",
27
30
  "Writes nothing and changes nothing. Runs before this machine is connected — that is one of the states it reports.",
@@ -29,7 +32,7 @@ const NOTES = [
29
32
 
30
33
  const { help, flags, positionals } = parseFlags(process.argv.slice(2), SPEC);
31
34
  if (help) {
32
- printHelp("node scripts/where-am-i.mjs [--next] [--json]", SPEC, NOTES);
35
+ printHelp("node scripts/where-am-i.mjs [--next] [--json] [--repo <path>]", SPEC, NOTES);
33
36
  process.exit(0);
34
37
  }
35
38
  if (positionals.length > 0) {
@@ -155,6 +158,19 @@ if (connected) {
155
158
  const capDoc = tryJson(runCli(["capabilities", "--json"]).stdout);
156
159
  capabilities = Array.isArray(capDoc?.capabilities) ? capDoc.capabilities : null;
157
160
  }
161
+ // Leftovers of the old local Catalyst runtime (its plugin, jobs, commands, state), from the CLI's own
162
+ // fixed list. Reported on the machine part and as a note; removal is the person's yes, never this script's.
163
+ let legacyNote = null;
164
+ if (verbAvailable("legacy")) {
165
+ const legacyDoc = tryJson(runCli(["legacy", "--json"]).stdout);
166
+ const found = Array.isArray(legacyDoc?.found) ? legacyDoc.found : [];
167
+ if (found.length > 0) {
168
+ const machine = parts.find((p) => p.part === "machine");
169
+ const names = found.map((f) => `${f.kind} ${f.name}`);
170
+ machine?.lines.push(`note leftovers of the old local Catalyst runtime (${found.length}): ${names.slice(0, 6).join(", ")}${found.length > 6 ? ", …" : ""}; removing them is strongly recommended: catalyst legacy --remove (the data folders are a separate yes, --data)`);
171
+ legacyNote = `this machine still carries ${found.length} piece${found.length === 1 ? "" : "s"} of the old local Catalyst runtime (${names.slice(0, 4).join(", ")}${found.length > 4 ? ", …" : ""}); offer catalyst legacy --remove once, strongly recommended, then --data as a separate question`;
172
+ }
173
+ }
158
174
 
159
175
  // ── person ────────────────────────────────────────────────────────────────────────────────────────
160
176
  if (!connected) {
@@ -593,6 +609,45 @@ if (!connected) {
593
609
  }
594
610
  }
595
611
 
612
+ // ── repository agent setup (only with --repo) ─────────────────────────────────────────────────────
613
+ // A checkout the person named. Read through the CLI's own verb, never by this script opening files:
614
+ // the verb is the one place that knows the block's words and the portable layout. Never blocking:
615
+ // a repository that is not portable still runs; this is an offer, and the writes wait for a yes.
616
+ let repoNote = null;
617
+ let repoNext = null;
618
+ if (typeof flags.repo === "string" && flags.repo !== "") {
619
+ const REPO_INSTRUMENT = `repo agent-setup ${flags.repo} --json`;
620
+ if (!connected) {
621
+ add("repository agent setup", REPO_INSTRUMENT, "unreadable", ["not readable until this machine is connected"], null, null, false);
622
+ } else if (!verbAvailable("repo agent-setup")) {
623
+ add("repository agent setup", REPO_INSTRUMENT, "unreadable", ["this CLI has no repo agent-setup verb; update the CLI to read a checkout's agent setup"], null, null, false).next =
624
+ "update the CLI (it lacks repo agent-setup); the checkout's agent setup cannot be read until then (does not block anything)";
625
+ } else {
626
+ const res = runCli(["repo", "agent-setup", flags.repo, "--json"]);
627
+ const doc = tryJson(res.stdout);
628
+ if (doc === null) {
629
+ add("repository agent setup", REPO_INSTRUMENT, "unreadable", [`the checkout could not be read (${(res.stderr || res.stdout).trim().split("\n")[0] || "no output"})`], null, null, false).next =
630
+ `run this again with the checkout's real path; ${flags.repo} could not be read`;
631
+ } else {
632
+ const block = doc.agentsMd?.block ?? "unknown";
633
+ const verdict = doc.verdict ?? "unknown";
634
+ const ok = verdict === "portable" && block === "current";
635
+ const lines = [
636
+ `AGENTS.md: ${doc.agentsMd?.present ? `present; Catalyst block ${block}` : "absent"}`,
637
+ `CLAUDE.md: ${doc.claudeMd?.present ? (doc.claudeMd.importsAgentsMd ? "imports AGENTS.md" : `${doc.claudeMd.otherLines ?? "?"} lines of its own guidance, no @AGENTS.md import`) : "absent"}`,
638
+ `layout: ${verdict}${Array.isArray(doc.plan) && doc.plan.length ? ` — ${doc.plan.join("; ")}` : ""}${Array.isArray(doc.blockers) && doc.blockers.length ? ` — by hand: ${doc.blockers.join("; ")}` : ""}`,
639
+ ];
640
+ const offers = [];
641
+ if (block !== "current") offers.push(`the Catalyst block for AGENTS.md (catalyst repo agents-block ${flags.repo} --write)`);
642
+ if (verdict === "convertible") offers.push(`the portable layout (catalyst repo agent-setup ${flags.repo} --apply)`);
643
+ if (verdict === "needs_hand_merge") offers.push("a hand merge first (the layout line names what)");
644
+ repoNext = ok ? null : `say in one clause what the checkout holds, then offer ${offers.join(" and ")}, each after a yes, on a branch for the pull request the person opens`;
645
+ add("repository agent setup", REPO_INSTRUMENT, ok ? "ok" : "unfinished", lines, ok ? null : "you, after the person's yes, in their checkout; the pull request is theirs", ok ? null : `catalyst repo agent-setup ${flags.repo}`, false);
646
+ if (!ok) repoNote = `repository ${flags.repo}: Catalyst block ${block}, layout ${verdict} — offer ${offers.join(" and ")} after a yes (references/repository-agent-setup.md)`;
647
+ }
648
+ }
649
+ }
650
+
596
651
  // ── the single next step ──────────────────────────────────────────────────────────────────────────
597
652
  // The machine's next action is not one sentence: an unconnected machine needs the login, and a
598
653
  // connected one needs whatever check failed — and `ready` already printed that check's own fix, so
@@ -620,6 +675,7 @@ const NEXT = {
620
675
  repositories: "install the GitHub App on the Connections page, granting it the repository you want worked; registering that repository on the Repositories page, attached to the project, is the step right after",
621
676
  "coding accounts": accountsNext,
622
677
  "repository declarations": declNext,
678
+ "repository agent setup": repoNext ?? "read the checkout's agent setup again",
623
679
  host: "connect a Catalyst host",
624
680
  };
625
681
  // The parts are read in the order their data allows and reported in the order a person can act on
@@ -627,7 +683,7 @@ const NEXT = {
627
683
  // workspace's Linear integration, the project, the GitHub App with its repository, the person's own
628
684
  // connected accounts (personal GitHub after the repository proves the App), the repository's
629
685
  // declaration, and last the host. `--next` is the first unfinished part in this order.
630
- const ORDER = ["machine", "coding accounts", "account", "projects", "repositories", "person", "repository declarations", "host"];
686
+ const ORDER = ["machine", "coding accounts", "account", "projects", "repositories", "person", "repository declarations", "repository agent setup", "host"];
631
687
  parts.sort((a, b) => ORDER.indexOf(a.part) - ORDER.indexOf(b.part));
632
688
  // A personal Linear grant cannot start until the tenant's Linear workspace exists. Once that account
633
689
  // connection is present, a missing personal grant becomes the next member step before project setup.
@@ -680,9 +736,9 @@ if (contractMismatch !== null && stuck !== null) {
680
736
  const finished = parts.every((p) => p.verdict === "ok");
681
737
 
682
738
  if (flags.json) {
683
- console.log(JSON.stringify({ cli: via, connected, cloud, personalConnections, parts, localSync, next, finished, notes: retireNote === null ? [] : [retireNote] }));
739
+ console.log(JSON.stringify({ cli: via, connected, cloud, personalConnections, parts, localSync, next, finished, notes: [retireNote, repoNote, legacyNote].filter((n) => n !== null) }));
684
740
  } else if (flags.next) {
685
- process.on("exit", () => { if (retireNote !== null) console.log(`note: ${retireNote}`); });
741
+ process.on("exit", () => { for (const n of [retireNote, repoNote, legacyNote]) if (n !== null) console.log(`note: ${n}`); });
686
742
  if (next === null) console.log("nothing left: every part is finished, a coding account is enrolled and the host check passes. Move one card into the project's start stage (usually Todo).");
687
743
  else console.log(`${next.part}: ${next.action}${next.blocking ? "" : " (does not block the steps below)"}${next.owner ? ` — who: ${next.owner}` : ""}${next.where ? ` — ${next.where.startsWith("http") ? "where" : "do"}: ${next.where}` : ""}`);
688
744
  } else {
@@ -1,45 +1,43 @@
1
1
  ---
2
2
  name: run-this-project
3
3
  description: >-
4
- Own one Catalyst Cloud project end to end until it closes. Use when the person says "run this project for me", "own this until it ships", "keep this moving", or hands you a project id or a set of tickets to drive. Subscribes to the tenant stream for the scope through the catalyst-skills CLI, reacts to each change in the same turn, makes tickets ready and moves them to dispatch, parks what should stop, chases stalls, escalates inward, and keeps one status summary current. Writes to Linear as the app actor; never polls.
5
- disable-model-invocation: true
6
- allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
4
+ Own one Catalyst Cloud project end to end until it closes. Use when the person says "run this project for me", "own this until it ships", "keep this moving", or hands you a project id or a set of tickets to drive. Subscribes to the account's event stream for the scope through the catalyst CLI, reacts to each change in the same turn, makes tickets ready and moves them to dispatch, parks what should stop, chases stalls, escalates inward, and keeps one status summary current. Writes to Linear as the app actor; never polls.
5
+ allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
7
6
  ---
8
- <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.13.0 — written in this repository for customer tenants -->
7
+ <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.13.1 — written in this repository for customer accounts -->
9
8
 
10
9
  # Run this project
11
10
 
12
- You are the steward: the single-threaded owner of ONE project or ticket set, from now until it closes. The person says "run this project for me" and hands you a scope. Catalyst does the phases; you make work ready and visible, react to what happens, unblock, and keep one status summary the person can read. You hold no authority over other stewards and you are not the desk; `whats-happening` is the desk.
11
+ You are the steward: the single-threaded owner of ONE project or ticket set until it closes. Catalyst runs the phases; you make work ready and visible, react to what happens, unblock, and keep one status summary the person can read. `whats-happening` is the desk; other stewards are peers.
13
12
 
14
13
  ## Run first
15
14
 
16
- Scripts are run, never read. Each prints its own `--help`.
15
+ Scripts are run, never read. Each prints `--help`, and exits 2 when this machine is not connected (`catalyst login`), 1 when its own check fails.
17
16
 
18
- 1. `node scripts/scope-status.mjs --project <id>` (or `--team <key>`): tickets by stage in the contract's order, what is running and queued in scope, and stalls against the local policy. Run it once to take the scope, and again after any resync.
19
- 2. `node scripts/watch-scope.mjs --project <id>`: the subscription. In Claude Code, arm a monitor on it and react to each printed line in the same turn. In a harness with no monitor, add `--exec <command>` so a reaction still runs per frame.
20
- 3. `node scripts/make-ready.mjs <ticket>` to dispatch; `--park` to stop; `--note <why>` to record it.
21
- 4. `catalyst-skills explain <ticket>` whenever a ticket is not moving: one paragraph naming the reason and what releases it.
22
-
23
- Every script exits 2 when this machine is not connected (run `catalyst-skills login`), 1 when its own check fails.
17
+ 1. `node scripts/scope-status.mjs --project <id>` (or `--team <key>`): tickets by stage, what is running and queued in scope, and stalls against the local policy. Run it to take the scope, and again after a resync.
18
+ 2. `node scripts/watch-scope.mjs --project <id>`: the subscription. In Claude Code, arm a monitor on it and react to each printed line in the same turn; elsewhere add `--exec <command>` so a reaction runs per frame.
19
+ 3. `node scripts/make-ready.mjs <ticket>` dispatches; `--park` stops; `--note <why>` records it.
20
+ 4. `catalyst explain <ticket>` whenever a ticket is not moving.
24
21
 
25
22
  ## Load on demand
26
23
 
27
24
  | when | read |
28
25
  | -- | -- |
29
- | arming the watch, deciding what a frame means, a reaction failed, a resync line printed | `references/reacting-to-events.md` |
30
- | dispatching or parking a ticket, judging whether a phase actually ran, writing a ticket for Catalyst | `references/making-work-ready.md` |
31
- | something is not moving, tuning the stall policy, deciding whether a human needs to hear about it | `references/stalls-and-escalation.md` |
32
- | the exclusion vocabulary in depth, the ladder, this team's stage map | the `how-catalyst-works` skill |
26
+ | arming the watch, reading a frame, a failed reaction, a resync | `references/reacting-to-events.md` |
27
+ | dispatching or parking, judging whether a phase ran, writing a ticket for Catalyst | `references/making-work-ready.md` |
28
+ | something is not moving, tuning the stall policy, whether a human needs to hear | `references/stalls-and-escalation.md` |
29
+ | what an exclusion reason means, the ladder, the stage map | the `whats-happening` skill |
33
30
  | raising or settling a decision | the `what-needs-me` skill |
34
- | a PR's checks, review and merge legs | the `catalyst-github` skill |
31
+ | a PR's checks, review and merge | the `catalyst-github` skill |
35
32
 
36
33
  ## Rules
37
34
 
38
- - **React, never poll.** A loop that re-reads the API or the replica is a defect. The stream and the cursor file are the mechanism; a reaction that throws leaves the cursor so the frame is offered again.
39
- - **Dispatch is a card move.** The cloud takes work from the dispatch column and writes every later stage itself. Your two moves are into dispatch and into the backlog; never hand-move a card into a ladder stage.
40
- - **Evidence a phase ran is the outcome comment, the attached document and the agent session,** not the clock and not the card's column.
41
- - **Tenant facts come from the contract, live.** Never restate a stage name, label id, team id, threshold or template in prose; run `catalyst-skills contract --path <a.b.c>` when you need one.
42
- - **Escalate inward.** Instrument, then you, then the desk, then the human as an ask filed through `what-needs-me` with what it blocks. Decide the technical calls yourself; take the sane default and record it; a fleet or provider condition is one note, never one ask per ticket.
43
- - **Reply where the message arrived,** in-thread, tagged, as the app actor. Never post as the human, never answer someone else's ask. Bookkeeping records take the contract's marker (`catalyst-skills write comment --bookkeeping`).
44
- - **One status summary,** kept current after every reaction: in flight, blocked and on whom, closed, next, each line with a ticket id. Where a human decision is pending, the line carries the ask's id.
45
- - **Say what a key cannot see.** PR labels and reactions are not mirrored; the CLI says so by name and points at settings. A park or hold is released from the person's own login once its cause is fixed, through the `unstick` skill (`catalyst-skills release`). Execution history (`explain --history`) and coding-account status (`accounts`) ARE readable — read them rather than reconstructing them from comments.
35
+ - **React to the stream.** The watch and its cursor file are the mechanism, and a reaction that throws leaves the cursor so the frame is offered again. Re-reading the API or the replica in a loop is a defect.
36
+ - **Dispatch is a card move.** Your two moves are into dispatch and into the backlog; the cloud writes every ladder stage itself.
37
+ - **A phase ran when its outcome comment, attached document and agent session say so,** not when the clock or the column suggests it.
38
+ - **Account facts come from the contract, live** (`catalyst contract --path <a.b.c>`), never from prose.
39
+ - **Escalate inward:** instrument, you, the desk, then the human as an ask through `what-needs-me` naming what it blocks. Decide technical calls yourself and record the default; a fleet or provider condition is one note, never one ask per ticket.
40
+ - **Say what a write costs.** Every move and comment spends the contract's daily write budget as the app actor, and a move into dispatch starts paid work on a coding account. Read the verdict `make-ready.mjs` prints before the next move.
41
+ - **Reply where the message arrived,** in-thread, tagged, as the app actor. The human speaks for themselves, and each ask belongs to its own addressee. Bookkeeping records take the contract's marker (`catalyst write comment --bookkeeping`).
42
+ - **One status summary,** current after every reaction: in flight, blocked and on whom, closed, next, each line with a ticket id, and a pending decision's line with its ask id.
43
+ - **Releases belong to `unstick`.** Once the cause is fixed, the person's own login releases a park or most holds through the `unstick` skill (`catalyst release`); a round-threshold hold and a review that will not converge refuse it. PR labels and reactions are not mirrored; execution history (`explain --history`) and coding accounts (`catalyst accounts`) are readable.
@@ -3,4 +3,4 @@ interface:
3
3
  short_description: "Own one Catalyst project end to end: react to its events, make tickets ready, chase stalls, escalate inward"
4
4
  default_prompt: "Use $run-this-project to run project <id> for me until it closes."
5
5
  policy:
6
- allow_implicit_invocation: false
6
+ allow_implicit_invocation: true
@@ -1,5 +1,5 @@
1
1
  identity: { pack: catalyst-cloud-skills, skill: run-this-project }
2
2
  effects: [external-write]
3
3
  mutating: true
4
- invocation: explicit
4
+ invocation: implicit
5
5
  exposure: [catalog]
@@ -1,60 +1,43 @@
1
1
  # Making work ready
2
2
 
3
- This reference restates invariants of how Catalyst takes work. The tenant's own values (which Linear state is the dispatch column, which state is the backlog, the ask and release label ids, the round cap and the park threshold) are read live from `catalyst-skills contract`; the scripts here never name a stage.
4
-
5
- ## The steward's two moves
6
-
7
- Catalyst does not take work by being asked. It takes work by finding a card in the team's dispatch column and offering that card's next phase to a runner. So the steward's dispatch verb is a card move, and its stop verb is a card move:
3
+ Catalyst takes work by finding a card in the team's dispatch column and offering its next phase to a runner. So the steward's dispatch and stop verbs are both card moves:
8
4
 
9
5
  ```sh
10
- node scripts/make-ready.mjs ENG-41 # move into the dispatch column; the cloud offers the next phase
11
- node scripts/make-ready.mjs ENG-41 --park # move into the team's backlog-type state; nothing further is offered
6
+ node scripts/make-ready.mjs ENG-41 # into the dispatch column
7
+ node scripts/make-ready.mjs ENG-41 --park # into the team's backlog-type state
12
8
  node scripts/make-ready.mjs ENG-41 --park --note "waiting on the vendor's API change"
13
9
  ```
14
10
 
15
- Dispatch resolves the dispatch slot on the ticket's team from the contract. Parking resolves the team's first backlog-type state from its live workflow states, because the backlog is deliberately not one of the eleven slots. Those are the only two state moves you make. Every other stage move on a ticket in the ladder is the cloud's, written when a phase completes; moving a card forward by hand does not run a phase, it only confuses the advance table, and moving it backwards by hand does not undo one.
11
+ Dispatch resolves the team's dispatch slot from the contract; parking resolves the team's first backlog-type state from its live workflow, since the backlog is not a slot. Every other stage on a ticket in the ladder is the cloud's record of what ran: a hand move forward runs nothing, and a hand move back undoes nothing.
16
12
 
17
13
  ## What a dispatchable ticket carries
18
14
 
19
- Before the move, the ticket needs, in the record itself:
15
+ A phase agent reads the ticket, not your chat. Read it in full, then check it has:
20
16
 
21
- - A title that states the outcome. A phase agent reads the ticket, not your chat.
22
- - A description that says what done looks like: the behaviour, the constraints, the files or surfaces if you know them, acceptance criteria a validate phase can check.
23
- - The right team. The team key is the ticket prefix, and the team's stage map and labels are what the cloud will use.
24
- - A priority. Queue order inside a team is priority first, then creation time, then identifier, so an unset priority sorts last.
25
- - No live blocking relation. A ticket with an open blocker is excluded as `blocked` until the blocker closes.
26
- - No ask on it. A ticket that carries the ask label, or whose own text reads as a decision request (an ask-shaped title, lettered options, a "default if silent" line), is excluded as a question rather than work. If a real ticket trips the shape detector, a human applies the release label named in the contract's vocabulary; you can also rewrite the text so it reads as work.
27
- - Declared scope when the team enforces it: a ticket whose declared files overlap a ticket already in flight is held as `scope_overlap` for the implement phase.
17
+ - a title stating the outcome, and a description of what done looks like (behaviour, constraints, files or surfaces, acceptance criteria validate can check);
18
+ - the right team (the prefix; its stage map and labels are what the cloud uses) and a priority (unset sorts last);
19
+ - no live blocking relation, which excludes it as `blocked`;
20
+ - no ask: an ask label, or text that reads as a decision request, excludes it as a question. A human applies the contract's release label to a false positive; you can rewrite the text so it reads as work;
21
+ - declared scope when the team enforces it, and no file overlap with a ticket in flight (`scope_overlap`). Serialise two tickets that touch the same files.
28
22
 
29
- A ticket does not need a branch, a PR, or any artifact to be dispatched: the ladder creates those. A ticket that has never entered the ladder starts at intake when the tenant enables it, otherwise at research.
23
+ It needs no branch, PR or artifact; the ladder makes those, starting at intake when the account has it on, else research.
30
24
 
31
25
  ## After the move
32
26
 
33
- `make-ready.mjs` asks the eligibility explainer as soon as the move lands and prints the verdict. Read it as follows:
34
-
35
- - `offered` or `eligible` with a queue position: done; a runner will pick it up in the next dispatch pass, ordered by priority, then age, then identifier, with tickets already mid-ladder ahead of fresh ones.
36
- - An ordering that is stale or never published: the cloud re-derives the team's queue within a pass of the move; ask again in a minute with `catalyst-skills explain <ticket>`.
37
- - Any other exclusion reason: the paragraph names it and what releases it. The reasons and what unblocks each are in `how-catalyst-works` and in `whats-happening`'s "why is it stuck" reference.
27
+ `make-ready.mjs` prints the explainer's verdict. A queue position means a runner will pick it up in order. A stale or unpublished ordering is re-derived within a pass; ask again in a minute with `catalyst explain <ticket>`. Any other reason is in the `whats-happening` skill's `references/why-is-it-stuck.md`.
38
28
 
39
29
  ## Evidence a phase ran
40
30
 
41
- Do not infer progress from time passing. A phase leaves three kinds of evidence on the ticket, and a card move alone is not one of them:
42
-
43
- 1. **The outcome comment.** The cloud posts a card per phase: a completed phase names the phase, the attempt, and its artifact; a failed phase names the phase, the attempt, and the failure class, and may carry a park or hold block. A remediate round posts its own attempt card with the round number and the class it is repairing. These arrive as comment frames on the watch.
44
- 2. **The attachment and document.** Each artifact-bearing phase (research, plan, implement, validate, pr, remediate) is projected to a Linear document titled with the ticket, the phase, the attempt and the date, attached to the ticket, with a short link comment. Research and plan on a ticket with a project also appear as a project link.
45
- 3. **The agent session.** The ticket's agent session carries the ladder as its plan, with the phases before the current one completed, the current one in progress, and the rest pending; its activities are the phase start, the gate, artifacts written, the PR opened, and the report.
46
-
47
- The card's stage is the fourth, weakest signal: a completed phase moves the card to the stage the advance table names, a failed phase writes no stage at all, and Done is written only when the pull request actually merges, by the merge webhook, never by a phase. A card sitting in a stage tells you which phase last finished, not whether the next one is running; `scripts/scope-status.mjs` shows running and leased phases beside the stage for exactly that reason.
31
+ Time passing proves nothing. A phase leaves three kinds of evidence on the ticket:
48
32
 
49
- ## Parking, and what it does not do
33
+ 1. **The outcome comment.** Per phase: the phase, the attempt, and its artifact, or on failure the failure class and any park or hold block. A remediate round posts its own card with the round number. These arrive as comment frames.
34
+ 2. **The document.** Each artifact-bearing phase attaches a Linear document with a short link comment.
35
+ 3. **The agent session.** Its plan is the ladder, with the current phase in progress; its activities are the phase start, the gate, the artifacts, the PR, and the report.
50
36
 
51
- Parking is the lever that stops the cloud offering more rounds on a ticket: moved out of the dispatch column and the ladder's stages, the ticket is excluded at the next offer. It does not kill a phase that is already running under a lease; that container finishes its phase, posts its outcome, and the next offer finds the card parked. If you park a ticket the cloud has itself parked (three consecutive failures, or the remediate round cap), record why in a bookkeeping note. Releasing a cloud park is not a card move: once its cause is fixed, the `unstick` skill releases it with `catalyst-skills release <ticket>`, and only a refusal that names a person's action becomes an ask.
37
+ The card's stage is the weakest signal: a completed phase moves it, a failed one writes nothing, and Done is written only when the pull request merges. `scope-status.mjs` shows running and leased phases beside each stage for that reason.
52
38
 
53
- Un-parking is the same dispatch move again. The counted attempts and rounds do not reset when a card comes back; the contract's thresholds say how many remain.
39
+ ## Parking
54
40
 
55
- ## What you never do to make work ready
41
+ Parking stops the cloud offering further rounds: the card is excluded at the next offer. A phase already running under a lease finishes and posts its outcome first. Un-parking is the dispatch move again, and counted attempts and rounds carry over. A cloud park (three consecutive failures, or the round cap) is not a card move: once its cause is fixed, the `unstick` skill releases it with `catalyst release <ticket>`, and only a refusal that names a person's action becomes an ask. When you park a ticket the cloud already parked, record why in a bookkeeping note.
56
42
 
57
- - Never move a card into a research, plan, implement, validate, PR, done or canceled stage by hand to "skip ahead". The cloud reads those stages as the record of what ran.
58
- - Never remove an ask label or apply the release label yourself; a human decides whether a ticket is a question.
59
- - Never dispatch two tickets that touch the same files at once; serialise them or let the second one wait as `scope_overlap`.
60
- - Never dispatch a ticket you have not read in full.
43
+ Ask labels and the release label are a human's call; leave them as you find them.
@@ -1,76 +1,53 @@
1
1
  # Reacting to events
2
2
 
3
- This reference restates invariants of the Catalyst Cloud stream and the `watch` verb. Nothing here varies per tenant; the tenant facts a reaction needs (stage ids, label ids, the ask team) come from `catalyst-skills contract` at the moment you need them.
4
-
5
- ## The mechanism
6
-
7
- A steward never polls. It subscribes once to the tenant stream through the SDK's live client and reacts to each change as it arrives. The verb is `catalyst-skills watch`, and this skill's `scripts/watch-scope.mjs` is that verb with the scope checked up front:
3
+ A steward subscribes once to the account's event stream and reacts to each change as it arrives. The verb is `catalyst watch`; `scripts/watch-scope.mjs` is that verb with the scope checked up front:
8
4
 
9
5
  ```sh
10
- node scripts/watch-scope.mjs --project <id> # every ticket in the project
11
- node scripts/watch-scope.mjs --team <key> # every ticket on the team
6
+ node scripts/watch-scope.mjs --project <id>
7
+ node scripts/watch-scope.mjs --team <key>
12
8
  node scripts/watch-scope.mjs --ticket ENG-41 --ticket ENG-42
9
+ node scripts/watch-scope.mjs --project <id> --exec 'node my-reaction.mjs' # a harness with no monitor
13
10
  ```
14
11
 
15
- The stream itself is tenant-wide; scope is filtered on this machine from each frame's entity and row, and a row that only carries a ticket reference (a comment, a session, a pull request) is resolved to its project through the ticket's own record, cached for the life of the watch. The cost of an ignored frame is one JSON parse, so a wide scope is fine.
16
-
17
- **In Claude Code**, arm a monitor on the command above. Each line it prints is one applied change inside the scope, delivered into your session as an event. React to it in the same turn: read what changed, decide, act, and only then let the turn end.
18
-
19
- **In Codex, OpenCode, or any harness without a monitor**, pass `--exec`:
20
-
21
- ```sh
22
- node scripts/watch-scope.mjs --project <id> --exec 'node my-reaction.mjs'
23
- ```
24
-
25
- The command runs once per frame with the frame on stdin. A non-zero exit is a failed reaction (see the cursor rule). Nothing polls in either shape.
12
+ The stream is account-wide and scope is filtered on this machine, resolving comments, sessions and pull requests to their ticket's project; an ignored frame costs one JSON parse, so a wide scope is fine. In Claude Code, a monitor on the command delivers each printed line into your session. With `--exec`, the command runs once per frame with the frame on stdin, and a non-zero exit is a failed reaction.
26
13
 
27
14
  ## The frame
28
15
 
29
- One JSON object per line, exactly what the SDK delivers:
16
+ One JSON object per line, as the SDK delivers it:
30
17
 
31
18
  ```json
32
- {"type":"change","accountId":"<tenant>","seq":4182,"entity":"comments","entityId":"<id>","op":"upsert","row":{...}}
19
+ {"type":"change","accountId":"<account>","seq":4182,"entity":"comments","entityId":"<id>","op":"upsert","row":{...}}
33
20
  ```
34
21
 
35
- `seq` is the tenant-wide position. `entity` is one of the mirror's feed tables. `op` is `upsert` or `delete`. `row` is the mirrored row when the op is an upsert; a delete carries the id only. A frame for another tenant is refused by the CLI and never printed.
22
+ `seq` is the account-wide position, `entity` a feed table, `op` is `upsert` (with the row) or `delete` (the id only).
36
23
 
37
24
  ## Entities that matter to a scope
38
25
 
39
26
  | entity | what a change means | first reaction |
40
27
  | -- | -- | -- |
41
- | `issues` | a card moved, was retitled, re-prioritised, assigned, or created | if it moved into a stage the cloud owns, note the advance; if it moved out of the ladder by hand, ask why before touching it |
42
- | `comments` | someone or something wrote on a ticket | a human comment in your scope is answered in-thread by you, tagged; a cloud outcome card (phase complete, phase failed, remediate attempt, board health, merge wait) is your execution signal, read it |
43
- | `issue_labels`, `labels` | a label was added or removed | an ask label means a question, not work; a hold label means the cloud wants a human; the release label means a human cleared a false positive |
44
- | `relations` | a blocks relation appeared or went away | a new blocker on your ticket is a dependency to chase; a blocker closing may make a card dispatchable again |
45
- | `pull_requests`, `pr_events`, `pushes` | the branch and PR state changed | a PR going ready-for-review or merged is a milestone; a force-push after a resolved review thread is worth a look |
46
- | `check_runs`, `check_suites`, `commit_statuses` | CI moved | red on a required check after the queue label wakes an automatic remediation; you watch, you do not re-run it |
47
- | `reviews`, `pr_review_threads`, `pr_review_comments` | the reviewer spoke | unresolved threads block the merge; a clean pass is a reaction or a terse comment, not a review object |
48
- | `agent_sessions`, `agent_activities` | a phase started, wrote an artifact, opened a PR, reported | the plan on the session is the ladder itself; the current phase is the one in progress |
49
- | `fleet_activity`, `fleet_host_liveness` | a runner picked up or dropped a phase | a phase running is not a stall, whatever the clock says |
50
- | `fleet_anomalies` | the cloud raised a fleet-level alert | one alert covers every ticket it touches; never escalate it per ticket |
51
- | `workflow_states`, `team_workflow_mapping` | the tenant's stage map changed | refresh the contract (`catalyst-skills contract --refresh`) before the next state move |
52
- | `projects`, `cycles`, `initiatives` | your scope's container changed | update the status summary |
53
-
54
- Entities not listed still arrive when they are in scope; ignore what you do not need.
28
+ | `issues` | a card moved, was edited, re-prioritised, assigned or created | a move into a cloud-owned stage is an advance; a hand move out of the ladder, ask why before touching it |
29
+ | `comments` | someone or something wrote on a ticket | answer a human comment in-thread, tagged; read a cloud outcome card (phase complete or failed, remediate attempt, board health, merge wait) as your execution signal |
30
+ | `issue_labels`, `labels` | a label changed | an ask label means a question; a hold label means the cloud wants a human; the release label means a human cleared a false positive |
31
+ | `relations` | a blocker appeared or went away | chase a new blocker; a closed one may make a card dispatchable |
32
+ | `pull_requests`, `pr_events`, `pushes` | the branch or PR changed | ready-for-review and merged are milestones; a force-push after a resolved thread is worth a look |
33
+ | `check_runs`, `check_suites`, `commit_statuses` | CI moved | a red required check after the queue label wakes an automatic remediation; watch it |
34
+ | `reviews`, `pr_review_threads`, `pr_review_comments` | the reviewer spoke | unresolved threads block the merge; a clean pass is a reaction or a terse comment |
35
+ | `agent_sessions`, `agent_activities` | a phase started, wrote, opened a PR, reported | the session's plan is the ladder |
36
+ | `fleet_activity`, `fleet_host_liveness` | a runner picked up or dropped a phase | a running phase is not a stall |
37
+ | `fleet_anomalies` | a fleet-level alert | one alert covers every ticket it touches |
38
+ | `workflow_states`, `team_workflow_mapping` | the stage map changed | `catalyst contract --refresh` before the next move |
39
+ | `projects`, `cycles`, `initiatives` | the scope's container changed | update the summary |
55
40
 
56
41
  ## The same-turn rule
57
42
 
58
- A frame is reacted to in the turn it arrives, not batched for a later pass. The reaction is whatever the table above says plus anything the situation obviously needs, and it ends with the one status summary for the scope being current. If a reaction needs a human decision, file the ask through `what-needs-me` before proceeding on the default, in that same turn.
59
-
60
- ## The cursor rule
61
-
62
- The cursor file (`~/.config/catalyst-cloud/watch-cursor.json`, stamped with the tenant) advances only after the reaction returns. A reaction that throws, or an `--exec` command that exits non-zero, leaves the cursor at the last good frame; the CLI closes the socket and the SDK's reconnect replays from that cursor, so the failed frame is offered again. Delivery is therefore at-least-once: make reactions safe to repeat (check before you write, prefer idempotent writes such as a label add over a fresh comment).
63
-
64
- Two consequences. First, a crash mid-reaction replays, never loses. Second, a reaction that keeps failing keeps the watch pinned on one frame; fix the reaction rather than skipping the frame, or restart with `--from head` and re-read the scope with `scripts/scope-status.mjs` to catch up on what you missed.
65
-
66
- ## What to do on resync
43
+ React to a frame in the turn it arrives, and end the turn with the status summary current. A reaction that needs a human decision files the ask through `what-needs-me` in that same turn, before proceeding on the default.
67
44
 
68
- When the tenant tells the stream it can no longer replay from your cursor, the CLI prints one line on standard error, `[watch] resync: cursor moved to head <n>`, and continues from the head. No rows are copied. Anything that happened between your old cursor and the head is not replayed, so a resync is your cue to run `scripts/scope-status.mjs` once and reconcile the summary against the live state.
45
+ ## The cursor
69
46
 
70
- ## Starting a watch on an existing project
47
+ The cursor file (`~/.config/catalyst-cloud/watch-cursor.json`) advances only after a reaction returns. A reaction that throws, or an `--exec` that exits non-zero, leaves it at the last good frame, and the reconnect replays from there. Delivery is at-least-once, so make reactions safe to repeat: check before writing, and prefer idempotent writes such as a label add. A reaction that keeps failing pins the watch on one frame; fix the reaction, or restart with `--from head` and re-read the scope with `scripts/scope-status.mjs`.
71
48
 
72
- Start with `--from head` when you are picking up a project that has been running without a steward; the backlog of old frames is not worth replaying, and `scope-status.mjs` gives you the present state in one call. Start from the saved cursor (the default) when you are resuming your own watch after a break, so nothing that happened in between is lost.
49
+ A resync prints `[watch] resync: cursor moved to head <n>` on standard error and continues from the head without replaying the gap: run `scripts/scope-status.mjs` once and reconcile. Start with `--from head` when taking over a project that ran without a steward; resume from the saved cursor (the default) after your own break.
73
50
 
74
51
  ## The honest limit
75
52
 
76
- The stream carries the mirror's entity changes. It does not carry the relay ledger's phase failures, parks, remediate rounds or backoff timers as first-class frames. Between frames you learn those from the outcome comments the cloud posts on the ticket, which do arrive as `comments` frames: a phase-failed card names the phase, the attempt and the failure class; a remediate-attempt card names the round; a board-health comment names a stall the cloud itself noticed. For the current verdict on any ticket, ask the explainer: `catalyst-skills explain <ticket>` turns the exclusion reason and the last failure into one paragraph. `catalyst-skills explain --history <ticket>` prints the per-phase attempt ledger, the remediation rounds against the cap, and any park with what releases it.
53
+ The stream carries the mirror's entity changes, not the relay ledger's failures, parks, rounds or backoff timers as frames. Those arrive as the cloud's outcome comments, and `catalyst explain <ticket>` gives the current verdict; `--history` adds the attempts, rounds against the cap, and any park with what releases it.
@@ -1,63 +1,41 @@
1
1
  # Stalls and escalation
2
2
 
3
- The stall thresholds here are **local policy**, set once by the human who owns the tenant and read by `scripts/scope-status.mjs`. They are not cloud values and the cloud does not enforce them; the cloud's own enforcement numbers (retry backoff, the park threshold, the remediate round cap, the write budget) are on the contract and printed by `how-catalyst-works`. The chase order and the escalation rule below are invariants.
3
+ The stall thresholds are **local judgment**, set once by the project's owner and read by `scripts/scope-status.mjs`. The cloud publishes no stall policy and enforces none of these numbers; what it does about a failing ticket is in the contract's `thresholds`.
4
4
 
5
5
  ## The policy file
6
6
 
7
- `assets/stall-policy.json` ships the defaults: minutes a ticket may sit in each slot with no update and nothing running or leased on it before it counts as stalled. To change them, copy that file to `~/.config/catalyst-cloud/stall-policy.json` and edit it there; the script prefers the local copy and prints which file it used. Pass `--stall-policy <file>` to use another.
8
-
9
- Two slots deserve thought when tuning. The dispatch slot's threshold is how long a card may wait to be picked up before you ask why nothing took it; on a busy tenant with a full queue that is capacity, not a stall, and the answer is to wait or re-prioritise. The PR slot's threshold is how long a card may wait for merge evidence; reviews and CI take real time, so set it generously.
7
+ `assets/stall-policy.json` ships the defaults: minutes a ticket may sit in each slot, with no update and nothing running or leased, before it counts as stalled. To change them, copy it to `~/.config/catalyst-cloud/stall-policy.json` and edit the copy; the script prefers it and prints which file it used (`--stall-policy <file>` picks another). On a busy account a waiting dispatch card is capacity, not a stall, and reviews and CI take real time, so set the dispatch and PR thresholds generously.
10
8
 
11
9
  ## What counts as stuck
12
10
 
13
- A ticket is stuck when all of these hold at once: it is in an active slot (not done, not canceled), nothing is running or leased on it, its last update is older than the slot's threshold, and the eligibility explainer does not name a reason that is simply waiting out a clock. `scope-status.mjs` checks the first three and hands you the fourth as a command to run.
14
-
15
- A running phase is never a stall, however long it has run; the cloud's own timeout will fail it and the outcome card will say so. A ticket in retry backoff is waiting out a rung of two, five or fifteen minutes and is not a stall either.
11
+ All at once: an active slot, nothing running or leased, a last update older than the slot's threshold, and an explainer reason that is not a clock. `scope-status.mjs` checks the first three and hands you the fourth as a command. A running phase is never a stall; its own timeout fails it and the outcome card says so.
16
12
 
17
13
  ## The chase order
18
14
 
19
- Run `catalyst-skills explain <ticket>` first, and let its reason pick the row:
20
-
21
- | the explainer says | what it means | your move |
22
- | -- | -- | -- |
23
- | offered, with a position | the queue has it; a runner is the bottleneck | wait; if every ticket waits, it is capacity, see below |
24
- | lease held, intake lease held | a container has it | not a stall; wait for the outcome card |
25
- | retry backoff | a pre-branch or infrastructure failure is retrying in place | wait out the rung; three in a row parks it, and then it is a real stall |
26
- | blocked | a live blocks relation | chase the blocker: is it dispatchable, is it an ask nobody answered, is it done but still open |
27
- | ask ticket, ask shape suspected | the ticket is a question | route it to `what-needs-me`; if it is really work, a human applies the release label |
28
- | not at dispatch stage | somebody moved the card, or it never entered | read the card's history; re-dispatch with `make-ready.mjs` if it should run |
29
- | phase parked, cooling down, remediate parked | the cloud parked it after repeated failure or the round cap | read the last outcome card for the class; once its cause is fixed, the `unstick` skill releases it (`catalyst-skills release <ticket> --because <what changed>`); if the fix is a decision, file an ask |
30
- | no change hold | a remediate round changed nothing | a human comment on the ticket, or a new push to the branch, releases it; say what should change |
31
- | validate class spent, stale failure episode | the repair budget for this failure is used, or the ladder moved on | read the outcome cards; usually a decision about the approach, so an ask |
32
- | waiting on | a merge-gate failure with no automatic repair | read the merge-wait comment and the PR's three legs through `catalyst-github` |
33
- | branch missing, branch gone, no branch to remediate | the branch the phase needs does not exist | a hand-deleted branch is a human question; a never-created one means implement has not run, so check the earlier phases |
34
- | environment check required, running, failed, expired, hash mismatch | the repository's environment gate | a repository setting; the tenant admin resolves it in settings, one ask for the repository, not per ticket |
35
- | scope overlap | another in-flight ticket owns the files | wait for it, or re-order by priority |
36
- | routing unavailable, no eligible slot, runner image breaker, repo paused | a fleet or provider condition | one fleet note, never per ticket; see below |
37
- | pr merged, pipeline complete, ticket terminal | it is finished | close the loop in the summary; if the card is not Done a minute after the merge, that is a finding, not a chore |
38
-
39
- Where the explainer names a reason not in this table, it prints the raw reason; read it as spelled and consult `how-catalyst-works`.
40
-
41
- ## Capacity and fleet conditions are one note, not many asks
15
+ Run `catalyst explain <ticket>` and look the reason up in the `whats-happening` skill's `references/why-is-it-stuck.md`. For you as the owner:
42
16
 
43
- When several tickets in scope are offered and nothing picks them up, or the explainer names a routing, slot, provider or image condition, the cause is shared: coding-account headroom, a provider outage, a paused repository, a poisoned runner image. Write one line in the status summary naming the condition and the tickets it holds. Do not file an ask per ticket. `catalyst-skills accounts` reads coding-account status (state, usage windows, walls, quarantine); enrolling, pausing or removing an account is the settings page, not you.
17
+ - **It releases itself:** wait for the outcome card.
18
+ - **The person, with their own login, acts:** do it when the move is yours (re-dispatch, a comment that clears a no-change or validate-budget hold, a question routed to `what-needs-me`), and file an ask when it is theirs.
19
+ - **A park or hold a release clears:** once its cause is fixed, the `unstick` skill releases it (`catalyst release <ticket> --because <what changed>`). A round-threshold hold and a review that will not converge refuse that release; the table says what clears each.
20
+ - **An owner or admin, in settings, or an operator:** one ask per repository or setting.
21
+ - **Finished:** close the loop in the summary.
44
22
 
45
- ## Escalate inward, never outward
23
+ Quote a reason the table does not know as the explainer spelled it.
46
24
 
47
- The order is instrument, then you (the steward), then the desk (`whats-happening`), then the human, and the human only ever sees an ask. An instrument or a script that pages a human directly is a defect. So is a stall report that reaches the human as a bare label, a board row, or a chat aside with no ask behind it.
25
+ ## Shared causes are one note
48
26
 
49
- Before anything reaches the human, answer three questions:
27
+ When several tickets are offered and nothing takes them, or the explainer names a routing, slot, provider or image condition, the cause is shared (coding-account headroom, a provider outage, a paused repository, a bad runner image). Write one summary line naming the condition and the tickets it holds. `catalyst accounts` reads coding-account status; enrolling or pausing one is the settings page.
50
28
 
51
- 1. **Can I decide this myself?** Which approach, retry or abandon, rebase or re-cut, re-order two tickets: yours. Decide, record the decision in a bookkeeping note on the ticket, and move on.
52
- 2. **Does this need to block at all?** If a sane default exists, take it, record it, and file the ask anyway so the human can overrule; the work does not wait.
53
- 3. **Who else can move this?** Another steward, the desk, a repository owner. Pulling in a peer is preferred over pulling in the human.
29
+ ## Escalate inward
54
30
 
55
- Only a genuine product, priority or approval decision, or an action only a human can physically take (close a person's own pull request, read a review that will not converge, resolve a repository setting, apply the release label, answer a merge policy question), survives to become an ask.
31
+ The order is instrument, you, the desk (`whats-happening`), then the human, who only ever sees an ask. Before anything reaches the human:
56
32
 
57
- ## When a stall becomes an ask
33
+ 1. **Can I decide this myself?** Approach, retry or abandon, rebase or re-cut, ordering: yours. Decide, record it in a bookkeeping note, move on.
34
+ 2. **Does it need to block?** With a sane default, take it, record it, and file the ask anyway so the human can overrule.
35
+ 3. **Who else can move it?** A peer steward, the desk or a repository owner comes before the human.
58
36
 
59
- File it through `what-needs-me`, never by hand and never as the human. The ask carries the question as its title, the options, the default that fires if silent, and what it blocks, with the stalled ticket named in the blocks list so the human's inbox ranks it by what it holds. Search for an existing ask about the same decision first and attach the new ticket to it rather than filing a second one; two asks for one decision split its urgency and sink both. Once filed, proceed on the default and say so in the ticket.
37
+ What survives is a product, priority or approval decision, or an action only a human can take: close their own pull request, read a review that will not converge, change a repository setting, apply the release label, answer a merge-policy question.
60
38
 
61
- ## Reply where the message arrived
39
+ ## Filing the ask
62
40
 
63
- A human comment inside your scope is answered by you, in that thread, tagged, as the app actor. A question only a human can decide becomes an ask; you do not answer it, and you never post as the human. A bookkeeping record (a state move, a decision you took, a chain summary) carries the contract's bookkeeping marker as a prefix so it wakes nothing; the `--bookkeeping` flag on `catalyst-skills write comment` adds it.
41
+ File it through `what-needs-me`, as the app actor: the question as its title, the options, the default if silent, and the stalled ticket in its blocks list so the inbox ranks it by what it holds. Search for an existing ask on the same decision first and attach to it, because two asks split one decision's urgency. Then proceed on the default and say so on the ticket.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- // lib/cli.mjs — the one way a skill script reaches Catalyst Cloud: by spawning the catalyst-skills
2
+ // lib/cli.mjs — the one way a skill script reaches Catalyst Cloud: by spawning the catalyst
3
3
  // CLI. The CLI holds the SDK and the key; this file holds neither. It reads
4
4
  // ~/.config/catalyst-cloud/customer.json (under CATALYST_SKILLS_HOME when set, else HOME) for the
5
- // CLI path that login recorded and falls back to `npx @catalyst-cloud/catalyst-skills`.
5
+ // CLI path that login recorded and falls back to `npx -p @catalyst-cloud/cli catalyst`.
6
6
  //
7
7
  // Exit codes every script built on this file shares: 2 = this machine is not connected, 1 = the
8
8
  // script's own check failed, 0 = fine.
@@ -12,7 +12,9 @@ import { join } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { CONNECT_COMMAND, hasCredential } from "./credential.mjs";
14
14
 
15
- export const PACKAGE_NAME = "@catalyst-cloud/catalyst-skills";
15
+ export const PACKAGE_NAME = "@catalyst-cloud/cli";
16
+ /** The package ships two commands, so npx needs the one to run named: `npx -p <package> catalyst`. */
17
+ export const NPX_ARGS = ["-p", PACKAGE_NAME, "catalyst"];
16
18
  export const NOT_CONFIGURED_EXIT = 2;
17
19
  export const CHECK_FAILED_EXIT = 1;
18
20
 
@@ -55,7 +57,7 @@ export function cliCommand(cfg = loadCustomerConfig()) {
55
57
  return { command: process.execPath, prefix: [cfg.cliPath], via: `node ${cfg.cliPath}` };
56
58
  }
57
59
  const npx = process.platform === "win32" ? "npx.cmd" : "npx";
58
- return { command: npx, prefix: [PACKAGE_NAME], via: `npx ${PACKAGE_NAME}` };
60
+ return { command: npx, prefix: NPX_ARGS, via: `npx -p ${PACKAGE_NAME} catalyst` };
59
61
  }
60
62
 
61
63
  /**
@@ -176,7 +178,7 @@ export function wantsHelp(argv) {
176
178
 
177
179
  const HELP = `lib/cli.mjs — shared helper; not a command.
178
180
 
179
- Resolves the catalyst-skills CLI (the path login recorded in ${configPath()}, else npx ${PACKAGE_NAME})
181
+ Resolves the catalyst CLI (the path login recorded in ${configPath()}, else npx -p ${PACKAGE_NAME} catalyst)
180
182
  and runs one verb for the script that imports it. Run any sibling script with --help instead.`;
181
183
 
182
184
  if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
@@ -6,12 +6,12 @@
6
6
  //
7
7
  // customer.json carries exactly one credential: a personal key (`key`), or the keyless login's
8
8
  // session (`auth`, the recommended rail). A script never reads either for its value: it spawns the
9
- // catalyst-skills CLI, which authenticates with whichever is present and refreshes a login's token
9
+ // catalyst CLI, which authenticates with whichever is present and refreshes a login's token
10
10
  // itself. A new credential kind lands here, once.
11
11
 
12
12
  /** The command that connects this machine, as every not-connected line names it. */
13
13
  export const CONNECT_COMMAND =
14
- "npx @catalyst-cloud/catalyst-skills login (or, with a personal key: CATALYST_CLOUD_TOKEN=<your personal key> npx @catalyst-cloud/catalyst-skills login)";
14
+ "npx -p @catalyst-cloud/cli catalyst login (or, with a personal key: CATALYST_CLOUD_TOKEN=<your personal key> npx -p @catalyst-cloud/cli catalyst login)";
15
15
 
16
16
  /** True when `cfg` (parsed customer.json) holds a usable credential of either kind. Never throws. */
17
17
  export function hasCredential(cfg) {