@catalyst-cloud/cli 0.13.0 → 0.14.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 (149) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +27 -30
  3. package/dist/args.js +299 -61
  4. package/dist/ask.js +30 -18
  5. package/dist/capabilities.js +7 -0
  6. package/dist/cli.js +125 -20
  7. package/dist/config.js +3 -1
  8. package/dist/env/draft.js +134 -0
  9. package/dist/env/legacy.js +54 -0
  10. package/dist/env/parse-env.js +50 -0
  11. package/dist/env/settings-toml.js +66 -0
  12. package/dist/env.js +126 -26
  13. package/dist/environment.js +1 -1
  14. package/dist/legacy.js +224 -0
  15. package/dist/onboard-consent.js +65 -0
  16. package/dist/onboard.js +836 -0
  17. package/dist/project.js +191 -0
  18. package/dist/ready.js +11 -1
  19. package/dist/release.js +1 -1
  20. package/dist/repo.js +297 -0
  21. package/dist/runtime-verb.js +1 -1
  22. package/dist/secret.js +25 -4
  23. package/dist/skill-shape.js +21 -13
  24. package/dist/skills.js +44 -4
  25. package/dist/transport.js +26 -1
  26. package/dist/var.js +118 -0
  27. package/package.json +6 -4
  28. package/skills/catalyst-github/SKILL.md +7 -7
  29. package/skills/catalyst-github/references/is-it-mergeable.md +8 -2
  30. package/skills/catalyst-github/references/what-a-pr-accumulates.md +3 -3
  31. package/skills/catalyst-github/scripts/is-it-mergeable.mjs +3 -3
  32. package/skills/catalyst-github/scripts/lib/cli.mjs +9 -7
  33. package/skills/catalyst-github/scripts/lib/credential.mjs +2 -2
  34. package/skills/catalyst-github/scripts/read-pr.mjs +2 -2
  35. package/skills/catalyst-linear/SKILL.md +17 -19
  36. package/skills/catalyst-linear/agents/openai.yaml +1 -1
  37. package/skills/catalyst-linear/agents/portability.yaml +1 -1
  38. package/skills/catalyst-linear/references/reading-a-ticket.md +9 -26
  39. package/skills/catalyst-linear/references/what-a-ticket-accumulates.md +2 -2
  40. package/skills/catalyst-linear/references/writing-to-linear.md +7 -9
  41. package/skills/catalyst-linear/scripts/comment.mjs +1 -1
  42. package/skills/catalyst-linear/scripts/create-ticket.mjs +1 -1
  43. package/skills/catalyst-linear/scripts/label.mjs +1 -1
  44. package/skills/catalyst-linear/scripts/lib/cli.mjs +9 -7
  45. package/skills/catalyst-linear/scripts/lib/credential.mjs +2 -2
  46. package/skills/catalyst-linear/scripts/move.mjs +1 -1
  47. package/skills/catalyst-linear/scripts/read-ticket.mjs +1 -1
  48. package/skills/catalyst-linear/scripts/search.mjs +1 -1
  49. package/skills/catalyst-onboard/SKILL.md +37 -57
  50. package/skills/catalyst-onboard/agents/openai.yaml +2 -2
  51. package/skills/catalyst-onboard/agents/portability.yaml +1 -1
  52. package/skills/catalyst-onboard/references/connecting-this-machine.md +35 -0
  53. package/skills/catalyst-onboard/references/declaring-a-repository.md +18 -21
  54. package/skills/catalyst-onboard/references/local-sync.md +19 -7
  55. package/skills/catalyst-onboard/references/reading-ready.md +79 -0
  56. package/skills/catalyst-onboard/references/replacing-a-credential.md +8 -33
  57. package/skills/catalyst-onboard/references/repository-agent-setup.md +23 -0
  58. package/skills/catalyst-onboard/references/skill-sources.md +13 -19
  59. package/skills/catalyst-onboard/references/the-one-path.md +18 -74
  60. package/skills/catalyst-onboard/references/what-a-phase-needs.md +21 -28
  61. package/skills/catalyst-onboard/references/what-the-browser-owns.md +20 -37
  62. package/skills/{catalyst-setup → catalyst-onboard}/scripts/check.mjs +10 -9
  63. package/skills/catalyst-onboard/scripts/lib/cli.mjs +6 -4
  64. package/skills/catalyst-onboard/scripts/lib/credential.mjs +2 -2
  65. package/skills/catalyst-onboard/scripts/lib/project-coverage.mjs +53 -0
  66. package/skills/catalyst-onboard/scripts/where-am-i.mjs +929 -184
  67. package/skills/run-this-project/SKILL.md +23 -25
  68. package/skills/run-this-project/agents/openai.yaml +1 -1
  69. package/skills/run-this-project/agents/portability.yaml +1 -1
  70. package/skills/run-this-project/references/making-work-ready.md +20 -37
  71. package/skills/run-this-project/references/reacting-to-events.md +25 -48
  72. package/skills/run-this-project/references/stalls-and-escalation.md +20 -42
  73. package/skills/run-this-project/scripts/lib/cli.mjs +7 -5
  74. package/skills/run-this-project/scripts/lib/credential.mjs +2 -2
  75. package/skills/run-this-project/scripts/scope-status.mjs +0 -0
  76. package/skills/run-this-project/scripts/watch-scope.mjs +1 -1
  77. package/skills/unstick/SKILL.md +12 -12
  78. package/skills/unstick/agents/openai.yaml +1 -1
  79. package/skills/unstick/agents/portability.yaml +1 -1
  80. package/skills/unstick/references/playbook.md +13 -13
  81. package/skills/unstick/scripts/lib/cli.mjs +6 -4
  82. package/skills/unstick/scripts/lib/credential.mjs +2 -2
  83. package/skills/unstick/scripts/unstick.mjs +1 -1
  84. package/skills/what-needs-me/SKILL.md +12 -14
  85. package/skills/what-needs-me/agents/openai.yaml +2 -2
  86. package/skills/what-needs-me/agents/portability.yaml +1 -1
  87. package/skills/what-needs-me/references/raising-a-decision.md +8 -16
  88. package/skills/what-needs-me/references/reading-the-inbox.md +6 -13
  89. package/skills/what-needs-me/references/settling-an-answer.md +6 -12
  90. package/skills/what-needs-me/scripts/inbox.mjs +14 -13
  91. package/skills/what-needs-me/scripts/lib/cli.mjs +6 -4
  92. package/skills/what-needs-me/scripts/lib/credential.mjs +2 -2
  93. package/skills/what-needs-me/scripts/raise.mjs +1 -1
  94. package/skills/what-needs-me/scripts/settle.mjs +1 -1
  95. package/skills/what-this-repo-needs/SKILL.md +10 -19
  96. package/skills/what-this-repo-needs/references/what-each-group-means.md +14 -44
  97. package/skills/what-this-repo-needs/scripts/check.mjs +13 -14
  98. package/skills/what-this-repo-needs/scripts/inventory.mjs +2 -2
  99. package/skills/what-this-repo-needs/scripts/lib/cli.mjs +11 -9
  100. package/skills/what-this-repo-needs/scripts/lib/credential.mjs +2 -2
  101. package/skills/whats-happening/SKILL.md +20 -30
  102. package/skills/whats-happening/agents/openai.yaml +1 -1
  103. package/skills/whats-happening/references/coding-accounts.md +11 -0
  104. package/skills/whats-happening/references/routing-work.md +4 -33
  105. package/skills/whats-happening/references/settings-and-where-they-live.md +17 -0
  106. package/skills/whats-happening/references/stages-and-mapping.md +19 -0
  107. package/skills/whats-happening/references/status-reply.md +11 -31
  108. package/skills/whats-happening/references/the-ladder.md +20 -0
  109. package/skills/whats-happening/references/what-runs-next.md +13 -0
  110. package/skills/whats-happening/references/when-a-phase-fails.md +21 -0
  111. package/skills/whats-happening/references/why-is-it-stuck.md +38 -62
  112. package/skills/whats-happening/scripts/explain.mjs +1 -1
  113. package/skills/whats-happening/scripts/lib/cli.mjs +7 -5
  114. package/skills/whats-happening/scripts/lib/credential.mjs +2 -2
  115. package/skills/{how-catalyst-works → whats-happening}/scripts/show-my-map.mjs +18 -32
  116. package/skills/whats-happening/scripts/snapshot.mjs +10 -6
  117. package/dist/env/declaration-rules.js +0 -163
  118. package/skills/catalyst-onboard/references/choosing-a-coding-account.md +0 -52
  119. package/skills/catalyst-onboard/references/who-fixes-what.md +0 -64
  120. package/skills/catalyst-setup/SKILL.md +0 -36
  121. package/skills/catalyst-setup/agents/openai.yaml +0 -6
  122. package/skills/catalyst-setup/agents/portability.yaml +0 -4
  123. package/skills/catalyst-setup/references/what-each-check-means.md +0 -88
  124. package/skills/catalyst-setup/scripts/lib/cli.mjs +0 -103
  125. package/skills/catalyst-setup/scripts/lib/credential.mjs +0 -29
  126. package/skills/catalyst-setup/scripts/replica-status.mjs +0 -46
  127. package/skills/connect-me/SKILL.md +0 -63
  128. package/skills/connect-me/agents/openai.yaml +0 -6
  129. package/skills/connect-me/agents/portability.yaml +0 -5
  130. package/skills/connect-me/references/keeping-the-replica-running.md +0 -88
  131. package/skills/connect-me/scripts/lib/cli.mjs +0 -185
  132. package/skills/connect-me/scripts/lib/credential.mjs +0 -29
  133. package/skills/connect-me/scripts/verify-connection.mjs +0 -68
  134. package/skills/how-catalyst-works/SKILL.md +0 -45
  135. package/skills/how-catalyst-works/agents/openai.yaml +0 -6
  136. package/skills/how-catalyst-works/agents/portability.yaml +0 -4
  137. package/skills/how-catalyst-works/references/coding-accounts.md +0 -51
  138. package/skills/how-catalyst-works/references/settings-and-where-they-live.md +0 -20
  139. package/skills/how-catalyst-works/references/stages-and-mapping.md +0 -56
  140. package/skills/how-catalyst-works/references/the-ladder.md +0 -41
  141. package/skills/how-catalyst-works/references/what-catalyst-is.md +0 -30
  142. package/skills/how-catalyst-works/references/what-runs-next.md +0 -95
  143. package/skills/how-catalyst-works/references/when-a-phase-fails.md +0 -57
  144. package/skills/how-catalyst-works/scripts/explain-ticket.mjs +0 -41
  145. package/skills/how-catalyst-works/scripts/lib/cli.mjs +0 -164
  146. package/skills/how-catalyst-works/scripts/lib/credential.mjs +0 -29
  147. package/skills/how-catalyst-works/scripts/whats-running.mjs +0 -65
  148. package/skills/whats-happening/references/reading-the-board.md +0 -43
  149. package/skills/whats-happening/references/reprioritising.md +0 -37
@@ -1,185 +0,0 @@
1
- #!/usr/bin/env node
2
- // lib/cli.mjs — the one way a skill script reaches Catalyst Cloud: by spawning the catalyst-skills
3
- // CLI. The CLI holds the SDK and the key; this file holds neither. It reads
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`.
6
- //
7
- // Exit codes every script built on this file shares: 2 = this machine is not connected, 1 = the
8
- // script's own check failed, 0 = fine.
9
- import { spawn } from "node:child_process";
10
- import { existsSync, readFileSync } from "node:fs";
11
- import { join } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
- import { CONNECT_COMMAND, hasCredential } from "./credential.mjs";
14
-
15
- export const PACKAGE_NAME = "@catalyst-cloud/catalyst-skills";
16
- export const NOT_CONFIGURED_EXIT = 2;
17
- export const CHECK_FAILED_EXIT = 1;
18
-
19
- export function homeDir() {
20
- return process.env.CATALYST_SKILLS_HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? "/";
21
- }
22
-
23
- export function configDir() {
24
- return join(homeDir(), ".config", "catalyst-cloud");
25
- }
26
-
27
- export function configPath() {
28
- return join(configDir(), "customer.json");
29
- }
30
-
31
- /** The stored config, or null when the file is absent or unreadable. Never throws. */
32
- export function loadCustomerConfig() {
33
- const path = configPath();
34
- if (!existsSync(path)) return null;
35
- try {
36
- const parsed = JSON.parse(readFileSync(path, "utf8"));
37
- if (!hasCredential(parsed) || typeof parsed.account !== "string") return null;
38
- return parsed;
39
- } catch {
40
- return null;
41
- }
42
- }
43
-
44
- /** Print the one not-connected line and exit 2. */
45
- export function requireConfigured() {
46
- const cfg = loadCustomerConfig();
47
- if (cfg) return cfg;
48
- console.error(`not connected: ${configPath()} is missing or unreadable — run: ${CONNECT_COMMAND}`);
49
- process.exit(NOT_CONFIGURED_EXIT);
50
- }
51
-
52
- /** The command and argument prefix that reaches the CLI on this machine. */
53
- export function cliCommand(cfg = loadCustomerConfig()) {
54
- if (cfg && typeof cfg.cliPath === "string" && existsSync(cfg.cliPath)) {
55
- return { command: process.execPath, prefix: [cfg.cliPath], via: `node ${cfg.cliPath}` };
56
- }
57
- const npx = process.platform === "win32" ? "npx.cmd" : "npx";
58
- return { command: npx, prefix: [PACKAGE_NAME], via: `npx ${PACKAGE_NAME}` };
59
- }
60
-
61
- /**
62
- * Run one CLI verb and capture its output. Resolves `{code, stdout, stderr, notConfigured}`;
63
- * `notConfigured` is true when the CLI itself said the machine is not connected.
64
- */
65
- export function runCli(args, { stdin } = {}) {
66
- const { command, prefix } = cliCommand();
67
- return new Promise((resolve, reject) => {
68
- const child = spawn(command, [...prefix, ...args], {
69
- stdio: [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
70
- env: process.env,
71
- shell: process.platform === "win32",
72
- });
73
- let stdout = "";
74
- let stderr = "";
75
- child.stdout.on("data", (d) => (stdout += String(d)));
76
- child.stderr.on("data", (d) => (stderr += String(d)));
77
- child.on("error", reject);
78
- child.on("close", (code) => {
79
- resolve({ code: code ?? 1, stdout, stderr, notConfigured: /not (joined|connected)/i.test(stderr) || /not (joined|connected)/i.test(stdout) });
80
- });
81
- if (stdin !== undefined) child.stdin.end(stdin);
82
- });
83
- }
84
-
85
- /**
86
- * Run one CLI verb with the terminal attached, so a long-running verb such as `watch` streams
87
- * straight through. Resolves with the exit code; SIGINT and SIGTERM are forwarded to the child.
88
- */
89
- export function execCli(args) {
90
- const { command, prefix } = cliCommand();
91
- return new Promise((resolve, reject) => {
92
- const child = spawn(command, [...prefix, ...args], { stdio: "inherit", env: process.env, shell: process.platform === "win32" });
93
- const forward = (sig) => () => {
94
- try {
95
- child.kill(sig);
96
- } catch {
97
- // already gone
98
- }
99
- };
100
- const onInt = forward("SIGINT");
101
- const onTerm = forward("SIGTERM");
102
- process.on("SIGINT", onInt);
103
- process.on("SIGTERM", onTerm);
104
- child.on("error", reject);
105
- child.on("close", (code) => {
106
- process.off("SIGINT", onInt);
107
- process.off("SIGTERM", onTerm);
108
- resolve(code ?? 1);
109
- });
110
- });
111
- }
112
-
113
- /** Parse the CLI's --json output; a parse failure names the first line of what came back. */
114
- export function parseJson(stdout) {
115
- try {
116
- return JSON.parse(stdout);
117
- } catch {
118
- throw new Error(`the CLI did not answer with JSON: ${stdout.split("\n")[0] ?? "(empty)"}`);
119
- }
120
- }
121
-
122
- /** Exit 2 with the CLI's own not-connected line when a call reports it; otherwise return the result. */
123
- export function guard(result) {
124
- if (result.notConfigured) {
125
- process.stderr.write(result.stderr || result.stdout);
126
- process.exit(NOT_CONFIGURED_EXIT);
127
- }
128
- return result;
129
- }
130
-
131
- /** A tiny flag parser: `--name value`, `--name=value`, `--flag`, repeatable names collected as arrays. */
132
- export function parseFlags(argv, { values = [], repeat = [], booleans = [] } = {}) {
133
- const flags = {};
134
- const positionals = [];
135
- for (let i = 0; i < argv.length; i++) {
136
- const a = argv[i];
137
- if (a === "--") {
138
- positionals.push(...argv.slice(i + 1));
139
- break;
140
- }
141
- if (!a.startsWith("--")) {
142
- positionals.push(a);
143
- continue;
144
- }
145
- let name = a.slice(2);
146
- let value;
147
- const eq = name.indexOf("=");
148
- if (eq !== -1) {
149
- value = name.slice(eq + 1);
150
- name = name.slice(0, eq);
151
- }
152
- if (booleans.includes(name)) {
153
- flags[name] = true;
154
- continue;
155
- }
156
- if (!values.includes(name) && !repeat.includes(name)) {
157
- console.error(`unknown option: --${name} (try --help)`);
158
- process.exit(CHECK_FAILED_EXIT);
159
- }
160
- if (value === undefined) {
161
- value = argv[++i];
162
- if (value === undefined) {
163
- console.error(`--${name} needs a value`);
164
- process.exit(CHECK_FAILED_EXIT);
165
- }
166
- }
167
- if (repeat.includes(name)) flags[name] = [...(flags[name] ?? []), value];
168
- else flags[name] = value;
169
- }
170
- return { flags, positionals };
171
- }
172
-
173
- export function wantsHelp(argv) {
174
- return argv.includes("--help") || argv.includes("-h");
175
- }
176
-
177
- const HELP = `lib/cli.mjs — shared helper; not a command.
178
-
179
- Resolves the catalyst-skills CLI (the path login recorded in ${configPath()}, else npx ${PACKAGE_NAME})
180
- and runs one verb for the script that imports it. Run any sibling script with --help instead.`;
181
-
182
- if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
183
- console.log(HELP);
184
- process.exit(0);
185
- }
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env node
2
- // lib/credential.mjs — is this machine connected? The ONE place a skill script decides it, vendored
3
- // byte-identical into every skill's scripts/lib/ from skill-lib/credential.mjs at the package root
4
- // (`npm run skill-lib:sync`; a test fails on any drift). Skills install one directory at a time, so
5
- // each carries its own copy. This file is a library — run a sibling script with --help for usage.
6
- //
7
- // customer.json carries exactly one credential: a personal key (`key`), or the keyless login's
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
10
- // itself. A new credential kind lands here, once.
11
-
12
- /** The command that connects this machine, as every not-connected line names it. */
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)";
15
-
16
- /** True when `cfg` (parsed customer.json) holds a usable credential of either kind. Never throws. */
17
- export function hasCredential(cfg) {
18
- if (cfg === null || typeof cfg !== "object") return false;
19
- const key = cfg["key"];
20
- if (typeof key === "string" && key !== "") return true;
21
- const login = cfg["auth"];
22
- return (
23
- login !== null &&
24
- typeof login === "object" &&
25
- login["kind"] === "oauth" &&
26
- typeof login["refreshToken"] === "string" &&
27
- login["refreshToken"] !== ""
28
- );
29
- }
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env node
2
- // verify-connection.mjs — did the connect step land? One line each for the tenant, the contract version and the
3
- // replica, from the CLI's own verbs. The machine is connected when `status` names a tenant.
4
- import { CHECK_FAILED_EXIT, cliCommand, loadCustomerConfig, parseFlags, runCli, wantsHelp } from "./lib/cli.mjs";
5
-
6
- const HELP = `Usage: node scripts/verify-connection.mjs [--json]
7
-
8
- Runs catalyst-skills status, contract --path contractVersion, and replica status, and prints one
9
- line for each: the tenant this machine is connected to, the cached contract version, and the replica
10
- verdict. Nothing is written.
11
-
12
- Options:
13
- --json one JSON document instead of three lines
14
- --help this text
15
-
16
- Exit codes: 1 when status reports the machine is not connected (or the CLI could not be run);
17
- 0 otherwise, whatever the replica verdict is, because the replica is optional.`;
18
-
19
- const argv = process.argv.slice(2);
20
- if (wantsHelp(argv)) {
21
- console.log(HELP);
22
- process.exit(0);
23
- }
24
- const { flags, positionals } = parseFlags(argv, { booleans: ["json"] });
25
- if (positionals.length > 0) {
26
- console.error(`unexpected argument: ${positionals[0]} (see --help)`);
27
- process.exit(CHECK_FAILED_EXIT);
28
- }
29
-
30
- const cfg = loadCustomerConfig();
31
- const { via } = cliCommand(cfg);
32
- const out = { cli: via, connected: false, tenant: null, contractVersion: null, replica: null };
33
-
34
- let status;
35
- try {
36
- status = await runCli(["status"]);
37
- } catch (err) {
38
- console.error(`could not run the CLI (${via}): ${err instanceof Error ? err.message : String(err)}`);
39
- process.exit(CHECK_FAILED_EXIT);
40
- }
41
- const tenantLine = status.stdout.split("\n").find((l) => l.startsWith("Tenant:"));
42
- out.connected = status.code === 0 && Boolean(tenantLine) && !status.notConfigured;
43
- out.tenant = tenantLine ? tenantLine.slice("Tenant:".length).trim() : null;
44
-
45
- if (out.connected) {
46
- const contract = await runCli(["contract", "--path", "contractVersion"]);
47
- out.contractVersion = contract.code === 0 ? contract.stdout.trim() : null;
48
- out.contractError = contract.code === 0 ? undefined : (contract.stderr || contract.stdout).trim().split("\n").at(-1);
49
- const replica = await runCli(["replica", "status", "--json"]);
50
- try {
51
- const parsed = JSON.parse(replica.stdout);
52
- out.replica = { verdict: parsed.verdict, exitCode: replica.code, cursor: parsed.cursor ?? null, reasons: parsed.reasons ?? [] };
53
- } catch {
54
- out.replica = { verdict: "unknown", exitCode: replica.code, line: (replica.stdout || replica.stderr).trim() };
55
- }
56
- }
57
-
58
- if (flags.json) {
59
- console.log(JSON.stringify(out));
60
- } else if (!out.connected) {
61
- console.log(`not connected (${via}): ${(status.stdout || status.stderr).trim().split("\n")[0] ?? "status printed nothing"}`);
62
- } else {
63
- console.log(`tenant: ${out.tenant}`);
64
- console.log(out.contractVersion ? `contract: version ${out.contractVersion} cached` : `contract: not cached (${out.contractError ?? "unknown reason"}) — run: catalyst-skills contract --refresh`);
65
- const r = out.replica;
66
- console.log(`replica: ${r.verdict}${r.cursor !== null && r.cursor !== undefined ? ` (cursor ${r.cursor})` : ""}${r.reasons && r.reasons.length ? ` — ${r.reasons.join("; ")}` : ""}${r.verdict === "absent" ? " — optional; start it with: catalyst-skills replica start --detach" : ""}`);
67
- }
68
- process.exit(out.connected ? 0 : CHECK_FAILED_EXIT);
@@ -1,45 +0,0 @@
1
- ---
2
- name: how-catalyst-works
3
- description: >-
4
- How Catalyst Cloud runs a ticket on the customer's own tenant, as facts an agent loads on demand: the eight-phase ladder and what each phase produces, the eleven board slots and this team's live stage map, what happens when a phase fails (retry, backoff, Remediate, park), how the queue is ordered and routed and every reason a ticket is excluded, the coding-account model, and which settings screen governs which rule. Use when a person asks "how does this work?", "why did it do that?", "why is this stuck?", "what runs next?", "how does it prioritise?" or "what does this setting do, where is it set?". Read-only; its scripts explain one ticket's eligibility in plain English, show what is running and queued, and print the tenant's stage map and thresholds straight from the contract.
5
- allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
6
- ---
7
- <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.13.0 — written in this repository for customer tenants -->
8
-
9
- # How Catalyst works
10
-
11
- You explain the machine. A person asks why Catalyst did something, what it will do next, or how it decides; you answer from the tenant's live contract and eligibility explainer, and from the invariants in the references below. You never guess a stage name, a label, a threshold or a route: the scripts print the live values.
12
-
13
- Every tenant-specific fact (stage names and ids, label ids, the ladder keying, the thresholds, the merge policy) comes from `GET /api/v1/agent/contract`, cached per session by the `catalyst-skills` CLI. The references restate only what does not vary per tenant.
14
-
15
- ## Run first
16
-
17
- Run each with `--help` before reading anything else; the scripts are black boxes, not reading material.
18
-
19
- - `node scripts/explain-ticket.mjs <ticket>` — why one ticket is or is not about to run, as one paragraph plus the raw eligibility row.
20
- - `node scripts/whats-running.mjs [--queue] [--accounts] [--team <key>]` — fleet activity, the agent roster and lease attributions; with `--queue` the dispatch order; with `--accounts` the coding-account line.
21
- - `node scripts/show-my-map.mjs [--team <key>]` — this tenant's slot-to-stage map, ladder and live thresholds.
22
-
23
- Exit codes: 0 answered, 1 not found or a usage error, 2 this machine is not connected or the cloud refused (the one line printed says which; connect first if it names the login).
24
-
25
- ## Load on demand
26
-
27
- | when | read |
28
- | -- | -- |
29
- | "what is Catalyst Cloud?", "what changes for me?" | `references/what-catalyst-is.md` |
30
- | "what are the phases, what does each produce, when is a ticket Done?" | `references/the-ladder.md` |
31
- | "which column is which, why does nothing dispatch, what is a slot?" | `references/stages-and-mapping.md` |
32
- | a phase FAILED, a card went to Remediate, a ticket is parked or on hold | `references/when-a-phase-fails.md` |
33
- | "what runs next, why not this one, what does this exclusion reason mean?", "why does nothing start while nothing runs?" (the WIP limit) | `references/what-runs-next.md` |
34
- | "why is nothing running", walls, quarantine, which provider ran a phase | `references/coding-accounts.md` |
35
- | "what does this setting do, where is it set, who can change it?" | `references/settings-and-where-they-live.md` |
36
-
37
- ## Rules
38
-
39
- - **Print, never recall.** A stage name, label, threshold or route in your answer must have come from a script's output in this session.
40
- - **A setting has a page.** Name the screen, its route and the rule from `references/settings-and-where-they-live.md`; never answer a settings question from memory, and never invent a route.
41
- - **A reason is a layer.** Translate an exclusion reason through `references/what-runs-next.md`; name what releases it and who can do that (a clock, a comment, a push, an operator).
42
- - **Unknown is not absent.** An `unknown` verdict from the explainer means the cloud could not look; report it as inconclusive.
43
- - **System causes are one alert.** A provider outage, the runner image breaker or a paused repository holds many tickets for one reason; never escalate it ticket by ticket.
44
- - **Say what a key cannot see.** A few PR facts are not mirrored (labels, the reviewer's reaction) and a park is released only by an operator; the scripts say so and name the settings page. Do not fill the gap with a guess.
45
- - **Depth lives elsewhere.** What a ticket accumulates in Linear is `catalyst-linear`; what a PR accumulates and whether it is mergeable is `catalyst-github`; raising a decision is `what-needs-me`.
@@ -1,6 +0,0 @@
1
- interface:
2
- display_name: "How Catalyst Works"
3
- short_description: "Explain how Catalyst Cloud runs a ticket: the ladder, your stage map, failures, what runs next, coding accounts"
4
- default_prompt: "Use $how-catalyst-works to explain why Catalyst did that, or what it will run next."
5
- policy:
6
- allow_implicit_invocation: true
@@ -1,4 +0,0 @@
1
- identity: { pack: catalyst-cloud-skills, skill: how-catalyst-works }
2
- effects: []
3
- invocation: implicit
4
- exposure: [catalog]
@@ -1,51 +0,0 @@
1
- # Coding accounts: slots, windows, walls, and what a key cannot read yet
2
-
3
- This reference restates the model, which does not vary per tenant. Your tenant's actual accounts — provider, harness, declared and observed state, usage windows, walls, quarantine — are readable with your key: `node scripts/whats-running.mjs --accounts` prints the live rows (`catalyst-skills accounts`). No credential or email ever rides in that read; the settings page (`/settings/coding-accounts`) is where an admin enrols, pauses or removes one.
4
-
5
- ## What a slot is
6
-
7
- A slot is one enrolled coding-account credential: a subscription or token that runs phases. A tenant may enrol several accounts of the same provider under the same email, because identity is the credential, not the email; the email is a display field. A revoked slot leaves the roster.
8
-
9
- ## Providers and harnesses
10
-
11
- | Provider | Harness | Credential |
12
- | -- | -- | -- |
13
- | Claude | the Claude CLI | its own subscription |
14
- | Codex | Codex | its own subscription |
15
- | GLM | the Claude CLI | its own token |
16
- | Qwen | the Claude CLI | its own token |
17
- | GLM via OpenCode | OpenCode | the same GLM enrolment |
18
- | Qwen via OpenCode | OpenCode | the same Qwen enrolment |
19
-
20
- The harness is a property of the catalog entry, never a routing input. Which provider a phase runs on is the routing decision in `references/what-runs-next.md`; the routing rows, not the slot, name the model.
21
-
22
- ## Two state axes
23
-
24
- - **Declared**: `active` or `disabled`. The operator sets it.
25
- - **Observed**: `healthy`, `degraded` or `unknown`. The poller sets it from what the provider reports.
26
-
27
- Two more lifecycle facts sit beside them: **quarantined** (system-set, on a credential conflict or an authentication mismatch; Replace credential on the account's page clears it, and a tenant owner or admin can do that, so an operator is not required) and **revoked** (operator-set).
28
-
29
- ## Windows, walls, headroom
30
-
31
- - Subscriptions meter usage over a **5-hour** window and a **7-day** window, each with a used percentage and a reset time.
32
- - A **wall** is the window limit a session can die at mid-run. Before a slot is granted, a fit gate projects the phase's expected burn against the remaining headroom with a safety margin; a slot that would hit the wall is not offered.
33
- - **Headroom** per provider is advisory: it counts eligible and degraded slots and the best remaining percentage, and it can never promise what a reservation would refuse because both read the same eligibility predicate.
34
- - Holds: a Claude slot can serve several phases at once up to a per-account cap; a Codex slot serves one at a time because its refresh tokens are single-use.
35
- - Slot choice orders by usage band, then live-hold count, then used percent, then the latest reset, so bursts spread across accounts rather than stacking on one.
36
-
37
- A poller refreshes an active slot's usage every 5 minutes and a disabled one daily. Separately, vendor status pages are polled for provider health; that signal says nothing about your own accounts' windows, walls or quarantine.
38
-
39
- ## What the settings page shows
40
-
41
- Your tenant's coding-accounts page lists each slot with a five-value status, first match wins: expired-or-revoked, walled, active, attested (healthy and active, but the provider is unobserved), unobserved. A drilldown shows the windows, the holds and the history.
42
-
43
- ## What a key cannot read yet, and what to say
44
-
45
- Coding-account status (provider, declared and observed state, window percentages and resets, walls, quarantine, live holds) is served only behind the tenant's admin gate. So when a human asks "why is nothing running?":
46
-
47
- 1. Run `node scripts/explain-ticket.mjs <ticket>` for a stuck ticket. `routing_unavailable` with a detail naming a slot or provider, or `no_eligible_account_slot` in the routing block, points at accounts.
48
- 2. Read the live rows with `catalyst-skills accounts` and say what they show; an empty list means no account is enrolled, and the script prints the settings link. Do not guess a wall or a quarantine from silence — a wall is the `walled` field, a quarantine is `quarantined` with its reason.
49
- 3. Treat it as ONE fleet-level cause for every ticket it holds, never as a per-ticket escalation.
50
-
51
- The customer session never holds or mints a coding-account credential; phases run in the cloud on the enrolled accounts, and repository access is a per-phase installation token.
@@ -1,20 +0,0 @@
1
- # Settings and where they live
2
-
3
- This reference restates rules that do not vary per tenant: what a setting does, the screen it lives on, and who can change it. Every live value — whether a connection is up, which repositories are registered, the current merge policy — comes from the contract or a script; every route below is prefixed with `<their cloud>` because the host is the tenant's own, never a literal you invent.
4
-
5
- | what | where it is set | the rule | who can set it |
6
- | -- | -- | -- | -- |
7
- | **Connecting Linear** | `<their cloud>/settings/connections` | Needs an admin or owner browser session; the tenant comes from that session, never from a URL parameter. | owner or admin |
8
- | **One workspace, one account** | `<their cloud>/settings/connections` | A Linear workspace binds to exactly one account and an account to exactly one workspace; a second tenant for the same workspace is refused and stores nothing. That is a decision to raise, not a retry. | owner or admin |
9
- | **Installing the GitHub App** | `<their cloud>/settings/connections`, but the admin's own personal GitHub grant comes first at `<their cloud>/settings/profile` | A refusal naming a missing GitHub identity, or an installation that is not yours, means "grant your personal GitHub first", never "the App is broken". | owner or admin |
10
- | **Uninstalling is safe either way** | `<their cloud>/settings/connections` | Removing the App on GitHub self-heals the registry; unlinking in Catalyst uninstalls upstream best-effort. Neither needs a support ticket. | owner or admin |
11
- | **One team, one repository** | `<their cloud>/settings/repositories` | One Linear team maps to at most one repository per account, and one GitHub repository to at most one, case-insensitively. | owner or admin |
12
- | **Neither half can be re-pointed** | `<their cloud>/settings/repositories` | A repository is exactly one team plus one GitHub repo; the only editable field is its name. A monorepo across two teams, or one team across two repos, cannot be modelled today — do not promise it. | owner or admin |
13
- | **An archived repository still holds both keys** | `<their cloud>/settings/repositories` | Re-registering the same team-plus-repo pair is refused until the archived entry is restored. | owner or admin |
14
- | **Merge policy** | `<their cloud>/settings/repositories/$repoId/merging` | The effective policy and where it came from are served on the contract's `merge` block; this is the page that changes it (`catalyst-github`'s mergeability read never sets it). | owner or admin |
15
- | **Reviewers** | `<their cloud>/settings/repositories/$repoId/code-reviews` | "Post review requests as" names one connected member; a member cannot volunteer or refuse. A strict merge policy with no reviewer configured never earns the merge label — configure a reviewer, or change the policy on the merging page. | owner or admin |
16
- | **The merge queue is assumed, and this bundle cannot see it** | (no settings page; the repository's own merge automation — a Mergify configuration, GitHub's native merge queue, or whatever else that repository runs) | The merge phase never merges: its one write is the ready label. Without a queue consuming that label the label is still the cloud's attestation, and the merge itself is whatever the repository's own setup does with it. Nothing here detects or repairs that, and nothing here knows which queue a tenant runs; say that by name rather than guessing, and never name a vendor the tenant has not named first. | whoever administers the GitHub repository |
17
- | **The runner cap** | `<their cloud>/settings/repositories` | Default 20 running phases per repository; a paused repository resolves to zero without losing the stored value. The tenant page is display-only — lowering it is an operator action. See `references/what-runs-next.md`'s concurrency section. | operator (the page only displays it) |
18
- | **Secrets are write-only; environment variables are readable** | `<their cloud>/settings/secrets` and `<their cloud>/settings/environment`, plus the per-repository sections under `<their cloud>/settings/repositories` | A name is a secret **or** an environment variable, never both; a repository-scoped value wins over a tenant-scoped one; an environment variable may reference a vault secret by name, expanded only when the container is built. | owner or admin |
19
- | **"My secrets are not in the container"** | `<their cloud>/settings/repositories` (the team the repository is registered under) | When a repository was registered without naming its team, nothing reaches a runner, and the failure is silent — the symptom is a phase whose build cannot find its environment. The fix is an admin or operator action, not a retry; raise it as an ask. Name the symptom and the page, never the registry table behind it. | owner or admin |
20
- | **Routing has no write route and no UI** | none — there is no settings page for this | Stage defaults are global and the per-tenant precedence table is empty, so an empty precedence always resolves to the default provider. A Codex-first pipeline is not something to promise today. See `references/coding-accounts.md` and `references/what-runs-next.md`'s routing decision. | nobody — not configurable yet |
@@ -1,56 +0,0 @@
1
- # Stages and mapping: eleven slots, five that matter, and how to read this team's map
2
-
3
- This reference restates invariants (the slot vocabulary, which slots are load-bearing, the allowed state types, the three mapping modes). Everything about YOUR team — which Linear stage each slot maps to, its name, its type, whether it still exists — is live on the contract under `teams[].stages`. Read it with `node scripts/show-my-map.mjs [--team <key>]`; never quote a stage name or id from memory.
4
-
5
- ## The eleven slots, in pipeline order
6
-
7
- `dispatch`, `intake`, `research`, `plan`, `implement`, `remediate`, `verify`, `review`, `pr`, `done`, `canceled`
8
-
9
- A slot is Catalyst's name for a position on the board. A stage is the Linear workflow state your team actually has. Mapping is the per-team table that binds each slot to one stage.
10
-
11
- ## Five slots are load-bearing
12
-
13
- | Slot | Why it matters | Allowed Linear state types |
14
- | -- | -- | -- |
15
- | `dispatch` | Moving a card here is how work is dispatched. Nothing is offered from any other column. | `unstarted`, `backlog` |
16
- | `intake` | Where a never-seen ticket lands for the optional intake pass. | `unstarted`, `backlog` (never Linear's reserved triage type) |
17
- | `pr` | Where a card must sit for `merge` to be offered. | `started` |
18
- | `done` | Written by the merge webhook. | `completed` |
19
- | `canceled` | Terminal; a card here is never work. | `canceled` |
20
-
21
- An absent or wrongly-typed mapping on one of these five is a distinct silent failure: the ladder simply never moves. The other six slots are informational — a wrong value costs a warning in readiness, not a stall. That asymmetry is why "map my stages" is a five-field decision.
22
-
23
- ## Three mapping modes
24
-
25
- The contract's `teams[].workflowMode` reports which one a team is in:
26
-
27
- - **adopted**: Catalyst created its recommended stage set for the team, one stage per slot, with verify and review sharing one validation stage.
28
- - **mapped**: the team kept its existing stages and a human chose which stage fills each slot.
29
- - **mixed**: some slots adopted, some hand-chosen.
30
-
31
- `teams[].gitAutomation` is a stored consent for a feature that is not built yet (Catalyst managing a team's Linear git automations); nothing reads it, so `off` never stops work and turning it on would start none. What decides whether a team's tickets start is the mapping above: until dispatch, pr, done and canceled each point at a live stage, Catalyst starts nothing in that team, and `explain` says so by name.
32
-
33
- Stage mappings live in the app, at Settings → Linear teams. The cloud reads each repository's `.catalyst/catalyst.toml`, but that file has no stage-mapping section, so a mapping written there changes nothing. An older `.catalyst/config.json` is not imported.
34
-
35
- ## The state id is the authority; names are display
36
-
37
- Each mapped stage carries a `stateId`, a display `name`, a `type`, `stateStillExists` and a `source` (how the mapping was chosen). Only the id is a lookup key. A Linear-to-Linear import can preserve every human-readable name while re-minting every state id, and then a name-based lookup points at nothing. So:
38
-
39
- - Move cards by slot (`catalyst-linear`'s move script does this) and let the CLI resolve the id from the contract. Never move a card "to Todo" by name.
40
- - `stateStillExists: false` means the mapped state is provably gone; the map needs fixing in your tenant settings before that slot can be written to. The CLI refuses such a move rather than guessing.
41
- - The stage names the contract shows come from the mirror's live view of Linear, not from a stored snapshot, so they are current at read time.
42
-
43
- ## How to read the printed map
44
-
45
- `node scripts/show-my-map.mjs` prints, per team, one row per slot: the slot, the stage name (or `(unmapped)`, or `(state gone)`), the state type, whether the state still exists, and the source. Load-bearing slots are starred. Then the team's ask, hold and release labels with `(absent)` where the workspace has no such label, then the ladder (phases, keying, intake on or off) and the live thresholds.
46
-
47
- Reading it for a question:
48
-
49
- - "Why does nothing dispatch?" — is `dispatch` mapped, does its state still exist, and is the card actually in that stage? `node scripts/explain-ticket.mjs <ticket>` names `not_at_dispatch_stage` when the card is elsewhere.
50
- - "Why is merge not running?" — is the card in the `pr` slot's stage?
51
- - "Why did the card go to Remediate?" — `remediate` is mapped, so a failed phase moved it there (see `references/when-a-phase-fails.md`); if `remediate` is unmapped the same episode shows up as the hold label from `teams[].labels.hold` instead.
52
- - "The team's Backlog is not in the list" — correct. Backlog is not a slot. Parking a card is a move to the team's backlog-type state, which the CLI resolves from the team's live workflow states rather than from the map.
53
-
54
- ## Readiness over the mapping
55
-
56
- The contract carries a readiness vector per team (`teams[].readiness`), including whether every mapped state exists, whether the mapping is total, whether the types are compatible, whether the labels are present, whether writes land and whether the webhook covers the team. A check the cloud could not run is `unknown`, never `pass`. `catalyst-setup` reads this vector; this skill only points at it.
@@ -1,41 +0,0 @@
1
- # The ladder: eight phases, what each one produces
2
-
3
- This reference restates invariants. The phase order and what each phase leaves behind do not vary per tenant. The only live values are whether intake is switched on for your tenant and which keying the advance table uses; both are on the contract under `ladder` (print them with `node scripts/show-my-map.mjs`).
4
-
5
- ## The phases, in order
6
-
7
- | Phase | What it produces | Notes |
8
- | -- | -- | -- |
9
- | `intake` | nothing projected (a classification pass) | Optional head. Offered only to a ticket that has never entered the ladder, and only when the contract says `ladder.intakeEnabled` is true. |
10
- | `research` | `research.md` | The first ladder phase. Offered when the card sits in the team's dispatch slot. |
11
- | `plan` | `plan.md` | |
12
- | `implement` | `implement.md` | Creates the ticket branch (named exactly the ticket identifier) and opens a draft pull request. |
13
- | `validate` | `validation.md` | Its success moves the card to the team's review stage; verify and review are two slots that both land on that stage. |
14
- | `pr` | `pr.md` | The first line is the pull-request title, then a blank line, then the body. The phase rebases onto the default branch, force-pushes, writes the title and body, and marks the PR ready for review. |
15
- | `remediate` | `remediation.json` | An interrupt, not a step forward. It repairs a phase that failed and never advances the card past the PR slot. |
16
- | `merge` | a receipt, no artifact | Applies the queue-ready label once its evidence gate passes. It writes no board state. |
17
-
18
- Every artifact-bearing phase (all but `intake` and `merge`) projects its artifact into Linear as a document attached to the ticket, with a short link comment; `catalyst-linear` describes those shapes.
19
-
20
- ## What moves the card
21
-
22
- An advance table maps a phase outcome to a card move: research done moves the card from the dispatch slot to the research slot, plan done from research to plan, implement done from plan to implement, validate done from implement to review, pr done from review to pr. The table is served on the contract as `ladder.advance` so you can read the exact rows; do not restate them from memory, because the tenant's keying decides whether a stage names the phase that just finished (trailing, the default) or the phase still ahead (leading).
23
-
24
- Three rows never move the card:
25
-
26
- - A **failed** phase writes no board state. The ticket stays where it was, retryable, and the failure handling in `references/when-a-phase-fails.md` takes over.
27
- - **`remediate` done** writes nothing by itself; the card restores to its exact pre-failure stage once a remediation round succeeds.
28
- - **`merge` done** writes nothing. The receipt only ever meant "the queue-ready label was applied".
29
-
30
- ## Done is written by the merge, never by a phase
31
-
32
- The terminal move to the done slot is keyed to the real pull-request-merged event from GitHub, not to any phase receipt. When the PR merges, the mirror's own ingest path moves the ticket to Done within a few seconds, with no session and no human sweep, and a bounded periodic sweep backstops a dropped webhook. So:
33
-
34
- - A ticket still not Done a minute after its PR merged is a finding, not a chore to hand-close.
35
- - Done is not the same as live. A change to something that is deployed separately is inert until that deployment happens; the ticket state says the code merged, nothing more.
36
-
37
- ## How to read a ticket's position on the ladder
38
-
39
- 1. The card's stage tells you which phase last finished (under trailing keying) — read the team's map with `node scripts/show-my-map.mjs --team <key>` to translate a stage name into a slot.
40
- 2. The phase-outcome comments on the ticket tell you which attempts ran and how they ended; the document attachments are the artifacts themselves.
41
- 3. `node scripts/explain-ticket.mjs <ticket>` tells you what the cloud will run next, or why it will not.
@@ -1,30 +0,0 @@
1
- # What Catalyst Cloud is
2
-
3
- Explain it in this person's terms: their repositories, their tickets, how they work today. Use their examples. Keep the facts exact; the wording is yours.
4
-
5
- ## The short version
6
-
7
- They keep filing tickets in Linear. Moving a card into the team's dispatch column starts the work. Catalyst Cloud then takes the ticket through research, a plan, the implementation, validation, a pull request and the merge.
8
-
9
- Each phase runs in a cloud container, on a coding account the tenant enrolled. Each phase leaves a document and an outcome comment on the ticket, so the ticket is the record.
10
-
11
- Their laptop stops being where coding sessions run. There are no local sessions, no home server, and no test runners or headless browsers competing for the machine. They install two skill packs and a small CLI that holds their sign-in.
12
-
13
- Each container is set up for the repository: the same skills, MCP servers, environment variable names and secrets. They declare the names once; the values stay in the app.
14
-
15
- Agents react to events instead of polling: a ticket edited, a pull request opened, a review landing, a check failing. The `run-this-project` skill explains the watch.
16
-
17
- A decision only they can make arrives as a ticket in their own Linear. It has options, a default if they stay silent, and a list of what it holds. Everything else keeps moving.
18
-
19
- ## What changes in their day
20
-
21
- - They start work by moving a card into the dispatch column. Nothing else starts a ticket. See `references/stages-and-mapping.md`.
22
- - They write the ticket for a reader who cannot see their chat: an outcome in the title, what done looks like, a priority, no open blocker.
23
- - They never move a card into a working stage by hand, and they never close a ticket. The merge writes Done. See `references/the-ladder.md`.
24
- - A draft pull request appears that they did not open. A later phase marks it ready. A green pull request with no open review threads merges through the queue. A hold label keeps it out.
25
- - They answer asks instead of being paged. One stuck ticket or a provider outage is never an ask.
26
- - A comment they post gets an eyes reaction and a reply in its thread.
27
-
28
- ## Why it is different
29
-
30
- Every item above has a mechanism in another reference. Retries and repair rounds are in `references/when-a-phase-fails.md`. An unknown verdict means "could not look": see `references/what-runs-next.md`. One outage is one alert, not one per ticket.