@catalyst-cloud/cli 0.9.5 → 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.
- package/CHANGELOG.md +28 -0
- package/README.md +28 -29
- package/dist/args.js +19 -1
- package/dist/ask.js +30 -18
- package/dist/capabilities.js +5 -0
- package/dist/cli.js +24 -6
- package/dist/env/declaration-rules.js +163 -0
- package/dist/env/group.js +51 -0
- package/dist/env/inventory.js +102 -0
- package/dist/env/render.js +39 -0
- package/dist/env/scan-env-example.js +62 -0
- package/dist/env/scan-source.js +43 -0
- package/dist/env/scan-workflow.js +123 -0
- package/dist/env/scan-wrangler.js +120 -0
- package/dist/env/types.js +1 -0
- package/dist/env/walk.js +87 -0
- package/dist/env.js +76 -0
- package/dist/environment.js +1 -1
- package/dist/legacy.js +224 -0
- package/dist/project.js +129 -0
- package/dist/release.js +1 -1
- package/dist/repo.js +297 -0
- package/dist/runtime-verb.js +1 -1
- package/dist/secret.js +1 -1
- package/dist/skill-shape.js +21 -13
- package/dist/skills.js +44 -4
- package/dist/transport.js +26 -1
- package/package.json +7 -6
- package/skills/catalyst-github/SKILL.md +7 -7
- package/skills/catalyst-github/references/is-it-mergeable.md +8 -2
- package/skills/catalyst-github/references/what-a-pr-accumulates.md +3 -3
- package/skills/catalyst-github/scripts/is-it-mergeable.mjs +3 -3
- package/skills/catalyst-github/scripts/lib/cli.mjs +9 -7
- package/skills/catalyst-github/scripts/lib/credential.mjs +2 -2
- package/skills/catalyst-github/scripts/read-pr.mjs +2 -2
- package/skills/catalyst-linear/SKILL.md +17 -19
- package/skills/catalyst-linear/agents/openai.yaml +1 -1
- package/skills/catalyst-linear/agents/portability.yaml +1 -1
- package/skills/catalyst-linear/references/reading-a-ticket.md +9 -26
- package/skills/catalyst-linear/references/what-a-ticket-accumulates.md +2 -2
- package/skills/catalyst-linear/references/writing-to-linear.md +7 -9
- package/skills/catalyst-linear/scripts/comment.mjs +1 -1
- package/skills/catalyst-linear/scripts/create-ticket.mjs +1 -1
- package/skills/catalyst-linear/scripts/label.mjs +1 -1
- package/skills/catalyst-linear/scripts/lib/cli.mjs +9 -7
- package/skills/catalyst-linear/scripts/lib/credential.mjs +2 -2
- package/skills/catalyst-linear/scripts/move.mjs +1 -1
- package/skills/catalyst-linear/scripts/read-ticket.mjs +1 -1
- package/skills/catalyst-linear/scripts/search.mjs +1 -1
- package/skills/catalyst-onboard/SKILL.md +37 -57
- package/skills/catalyst-onboard/agents/openai.yaml +2 -2
- package/skills/catalyst-onboard/agents/portability.yaml +1 -1
- package/skills/catalyst-onboard/references/connecting-this-machine.md +35 -0
- package/skills/catalyst-onboard/references/declaring-a-repository.md +7 -17
- package/skills/catalyst-onboard/references/local-sync.md +19 -7
- package/skills/catalyst-onboard/references/reading-ready.md +79 -0
- package/skills/catalyst-onboard/references/replacing-a-credential.md +8 -33
- package/skills/catalyst-onboard/references/repository-agent-setup.md +23 -0
- package/skills/catalyst-onboard/references/skill-sources.md +13 -19
- package/skills/catalyst-onboard/references/the-one-path.md +18 -74
- package/skills/catalyst-onboard/references/what-a-phase-needs.md +21 -28
- package/skills/catalyst-onboard/references/what-the-browser-owns.md +20 -37
- package/skills/{catalyst-setup → catalyst-onboard}/scripts/check.mjs +10 -9
- package/skills/catalyst-onboard/scripts/lib/cli.mjs +6 -4
- package/skills/catalyst-onboard/scripts/lib/credential.mjs +2 -2
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +60 -4
- package/skills/run-this-project/SKILL.md +23 -25
- package/skills/run-this-project/agents/openai.yaml +1 -1
- package/skills/run-this-project/agents/portability.yaml +1 -1
- package/skills/run-this-project/references/making-work-ready.md +20 -37
- package/skills/run-this-project/references/reacting-to-events.md +25 -48
- package/skills/run-this-project/references/stalls-and-escalation.md +20 -42
- package/skills/run-this-project/scripts/lib/cli.mjs +7 -5
- package/skills/run-this-project/scripts/lib/credential.mjs +2 -2
- package/skills/run-this-project/scripts/scope-status.mjs +0 -0
- package/skills/run-this-project/scripts/watch-scope.mjs +1 -1
- package/skills/unstick/SKILL.md +12 -12
- package/skills/unstick/agents/openai.yaml +1 -1
- package/skills/unstick/agents/portability.yaml +1 -1
- package/skills/unstick/references/playbook.md +13 -13
- package/skills/unstick/scripts/lib/cli.mjs +6 -4
- package/skills/unstick/scripts/lib/credential.mjs +2 -2
- package/skills/unstick/scripts/unstick.mjs +1 -1
- package/skills/what-needs-me/SKILL.md +12 -14
- package/skills/what-needs-me/agents/openai.yaml +2 -2
- package/skills/what-needs-me/agents/portability.yaml +1 -1
- package/skills/what-needs-me/references/raising-a-decision.md +8 -16
- package/skills/what-needs-me/references/reading-the-inbox.md +9 -12
- package/skills/what-needs-me/references/settling-an-answer.md +6 -12
- package/skills/what-needs-me/scripts/inbox.mjs +14 -13
- package/skills/what-needs-me/scripts/lib/cli.mjs +6 -4
- package/skills/what-needs-me/scripts/lib/credential.mjs +2 -2
- package/skills/what-needs-me/scripts/raise.mjs +1 -1
- package/skills/what-needs-me/scripts/settle.mjs +1 -1
- package/skills/what-this-repo-needs/SKILL.md +31 -0
- package/skills/what-this-repo-needs/agents/openai.yaml +6 -0
- package/skills/what-this-repo-needs/agents/portability.yaml +4 -0
- package/skills/what-this-repo-needs/references/what-each-group-means.md +38 -0
- package/skills/what-this-repo-needs/scripts/check.mjs +64 -0
- package/skills/what-this-repo-needs/scripts/inventory.mjs +61 -0
- package/skills/{catalyst-setup → what-this-repo-needs}/scripts/lib/cli.mjs +35 -22
- package/skills/{catalyst-setup → what-this-repo-needs}/scripts/lib/credential.mjs +2 -2
- package/skills/whats-happening/SKILL.md +27 -21
- package/skills/whats-happening/agents/openai.yaml +1 -1
- package/skills/whats-happening/references/coding-accounts.md +24 -0
- package/skills/whats-happening/references/routing-work.md +11 -29
- package/skills/whats-happening/references/settings-and-where-they-live.md +20 -0
- package/skills/whats-happening/references/stages-and-mapping.md +25 -0
- package/skills/whats-happening/references/status-reply.md +21 -21
- package/skills/whats-happening/references/the-ladder.md +30 -0
- package/skills/whats-happening/references/what-runs-next.md +32 -0
- package/skills/whats-happening/references/when-a-phase-fails.md +27 -0
- package/skills/whats-happening/references/why-is-it-stuck.md +33 -40
- package/skills/whats-happening/scripts/explain.mjs +1 -1
- package/skills/whats-happening/scripts/lib/cli.mjs +7 -5
- package/skills/whats-happening/scripts/lib/credential.mjs +2 -2
- package/skills/{how-catalyst-works → whats-happening}/scripts/show-my-map.mjs +18 -32
- package/skills/whats-happening/scripts/snapshot.mjs +10 -6
- package/skills/catalyst-onboard/references/choosing-a-coding-account.md +0 -52
- package/skills/catalyst-onboard/references/who-fixes-what.md +0 -64
- package/skills/catalyst-setup/SKILL.md +0 -36
- package/skills/catalyst-setup/agents/openai.yaml +0 -6
- package/skills/catalyst-setup/agents/portability.yaml +0 -4
- package/skills/catalyst-setup/references/what-each-check-means.md +0 -88
- package/skills/catalyst-setup/scripts/replica-status.mjs +0 -46
- package/skills/connect-me/SKILL.md +0 -63
- package/skills/connect-me/agents/openai.yaml +0 -6
- package/skills/connect-me/agents/portability.yaml +0 -5
- package/skills/connect-me/references/keeping-the-replica-running.md +0 -88
- package/skills/connect-me/scripts/lib/cli.mjs +0 -185
- package/skills/connect-me/scripts/lib/credential.mjs +0 -29
- package/skills/connect-me/scripts/verify-connection.mjs +0 -68
- package/skills/how-catalyst-works/SKILL.md +0 -43
- package/skills/how-catalyst-works/agents/openai.yaml +0 -6
- package/skills/how-catalyst-works/agents/portability.yaml +0 -4
- package/skills/how-catalyst-works/references/coding-accounts.md +0 -51
- package/skills/how-catalyst-works/references/stages-and-mapping.md +0 -56
- package/skills/how-catalyst-works/references/the-ladder.md +0 -41
- package/skills/how-catalyst-works/references/what-catalyst-is.md +0 -30
- package/skills/how-catalyst-works/references/what-runs-next.md +0 -88
- package/skills/how-catalyst-works/references/when-a-phase-fails.md +0 -57
- package/skills/how-catalyst-works/scripts/explain-ticket.mjs +0 -41
- package/skills/how-catalyst-works/scripts/lib/cli.mjs +0 -164
- package/skills/how-catalyst-works/scripts/lib/credential.mjs +0 -29
- package/skills/how-catalyst-works/scripts/whats-running.mjs +0 -65
- package/skills/whats-happening/references/reading-the-board.md +0 -43
- package/skills/whats-happening/references/reprioritising.md +0 -37
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check.mjs — validates a catalyst.env.json file offline, with the same rules this bundle vendors
|
|
3
|
+
// from the cloud's own validator. Never reads or prints a value. Offline: no login, no network —
|
|
4
|
+
// wraps `catalyst env check`.
|
|
5
|
+
import { runCliOffline } from "./lib/cli.mjs";
|
|
6
|
+
|
|
7
|
+
const HELP = `Usage: node scripts/check.mjs <file> [--json]
|
|
8
|
+
|
|
9
|
+
Validates a catalyst.env.json file against this bundle's offline copy of the cloud's rules. Prints
|
|
10
|
+
"valid" or the refusal reasons — never a value. Exit 0 valid, 1 invalid or unreadable.
|
|
11
|
+
|
|
12
|
+
--json prints {state, errors}. This is a local, offline check: no login, no network required.`;
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
16
|
+
console.log(HELP);
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
const json = args.includes("--json");
|
|
20
|
+
const file = args.find((a) => a !== "--json");
|
|
21
|
+
if (!file) {
|
|
22
|
+
console.error("env check needs a file: node scripts/check.mjs <path to catalyst.env.json>");
|
|
23
|
+
console.log(HELP);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const res = runCliOffline(["env", "check", file, ...(json ? ["--json"] : [])]);
|
|
28
|
+
if (res.stdout) process.stdout.write(res.stdout);
|
|
29
|
+
if (res.stderr) process.stderr.write(res.stderr);
|
|
30
|
+
await finish(res.code);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ⛔ NEVER `process.exit(code)` STRAIGHT AFTER A WRITE — the same rule `bin/launch.js`
|
|
34
|
+
* states in full, reintroduced here at validate attempt 29 (M-3). When this script's own stdout is a
|
|
35
|
+
* PIPE — which is how an agent harness runs it — writes are asynchronous, so `process.exit` severs
|
|
36
|
+
* whatever is still in flight AND STILL REPORTS THE ORIGINAL EXIT CODE. Measured: a `--json` body of
|
|
37
|
+
* 385,633 bytes arrived as 65,536 bytes of invalid JSON at exit 0. Writing to a FILE hides it,
|
|
38
|
+
* because a file gives stdout a synchronous write path.
|
|
39
|
+
*
|
|
40
|
+
* So: park the code on `process.exitCode`, wait for both streams to drain, and only then exit.
|
|
41
|
+
*/
|
|
42
|
+
function drained(stream) {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
if (!stream || stream.destroyed || stream.writableEnded) return resolve();
|
|
45
|
+
let settled = false;
|
|
46
|
+
const done = () => {
|
|
47
|
+
if (settled) return;
|
|
48
|
+
settled = true;
|
|
49
|
+
resolve();
|
|
50
|
+
};
|
|
51
|
+
stream.once("error", done);
|
|
52
|
+
try {
|
|
53
|
+
stream.write("", done);
|
|
54
|
+
} catch {
|
|
55
|
+
done();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function finish(code) {
|
|
61
|
+
process.exitCode = code;
|
|
62
|
+
await Promise.all([drained(process.stdout), drained(process.stderr)]);
|
|
63
|
+
process.exit(code);
|
|
64
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// inventory.mjs — lists the environment variable NAMES this repository needs, grouped build/test,
|
|
3
|
+
// deploy-only and bindings, each with where it was found, what uses it, and where a local value
|
|
4
|
+
// would come from. Never reads or prints a value. Offline: no login, no network — wraps
|
|
5
|
+
// `catalyst env inventory`.
|
|
6
|
+
import { runCliOffline } from "./lib/cli.mjs";
|
|
7
|
+
|
|
8
|
+
const HELP = `Usage: node scripts/inventory.mjs [path] [--json]
|
|
9
|
+
|
|
10
|
+
Scans a repository (default: the current directory) for the environment variable names it needs and
|
|
11
|
+
prints them in three groups: build/test, deploy-only, bindings. For each name: where it was found
|
|
12
|
+
(file:line), what uses it, and where a local value would come from. Never reads or prints a value.
|
|
13
|
+
|
|
14
|
+
--json prints the same data as JSON. This is a local, offline scan: no login, no network required.`;
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
18
|
+
console.log(HELP);
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
const json = args.includes("--json");
|
|
22
|
+
const path = args.find((a) => a !== "--json") ?? ".";
|
|
23
|
+
|
|
24
|
+
const res = runCliOffline(["env", "inventory", path, ...(json ? ["--json"] : [])]);
|
|
25
|
+
if (res.stdout) process.stdout.write(res.stdout);
|
|
26
|
+
if (res.stderr) process.stderr.write(res.stderr);
|
|
27
|
+
await finish(res.code);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ⛔ NEVER `process.exit(code)` STRAIGHT AFTER A WRITE — the same rule `bin/launch.js`
|
|
31
|
+
* states in full, reintroduced here at validate attempt 29 (M-3). When this script's own stdout is a
|
|
32
|
+
* PIPE — which is how an agent harness runs it — writes are asynchronous, so `process.exit` severs
|
|
33
|
+
* whatever is still in flight AND STILL REPORTS THE ORIGINAL EXIT CODE. Measured: a `--json` body of
|
|
34
|
+
* 385,633 bytes arrived as 65,536 bytes of invalid JSON at exit 0. Writing to a FILE hides it,
|
|
35
|
+
* because a file gives stdout a synchronous write path.
|
|
36
|
+
*
|
|
37
|
+
* So: park the code on `process.exitCode`, wait for both streams to drain, and only then exit.
|
|
38
|
+
*/
|
|
39
|
+
function drained(stream) {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
if (!stream || stream.destroyed || stream.writableEnded) return resolve();
|
|
42
|
+
let settled = false;
|
|
43
|
+
const done = () => {
|
|
44
|
+
if (settled) return;
|
|
45
|
+
settled = true;
|
|
46
|
+
resolve();
|
|
47
|
+
};
|
|
48
|
+
stream.once("error", done);
|
|
49
|
+
try {
|
|
50
|
+
stream.write("", done);
|
|
51
|
+
} catch {
|
|
52
|
+
done();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function finish(code) {
|
|
58
|
+
process.exitCode = code;
|
|
59
|
+
await Promise.all([drained(process.stdout), drained(process.stderr)]);
|
|
60
|
+
process.exit(code);
|
|
61
|
+
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// lib/cli.mjs — the one way a skill script reaches the Catalyst Cloud SDK and API: by spawning the
|
|
3
|
-
// catalyst
|
|
3
|
+
// catalyst CLI this bundle installed. It reads customer.json to find that CLI and nothing
|
|
4
4
|
// else; it never holds the key itself. This file is a library — run a sibling script with
|
|
5
|
-
// --help for usage.
|
|
6
|
-
//
|
|
5
|
+
// --help for usage. Per-skill (skills install one directory at a time, so nothing shared outside the
|
|
6
|
+
// skill would ever be installed) — see runCliOffline below for the one way this skill's copy differs.
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { CONNECT_COMMAND, hasCredential } from "./credential.mjs";
|
|
12
12
|
|
|
13
|
-
export const PACKAGE = "@catalyst-cloud/
|
|
13
|
+
export const PACKAGE = "@catalyst-cloud/cli";
|
|
14
|
+
/** The package ships two commands, so npx needs the one to run named: `npx -p <package> catalyst`. */
|
|
15
|
+
export const NPX_ARGS = ["-p", PACKAGE, "catalyst"];
|
|
14
16
|
export const CONNECT_HINT = `this machine is not connected to a tenant yet — run: ${CONNECT_COMMAND}`;
|
|
15
17
|
|
|
16
18
|
/** ~/.config/catalyst-cloud/customer.json, honouring CATALYST_SKILLS_HOME before HOME. */
|
|
@@ -41,15 +43,38 @@ export function requireCustomerConfig() {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
|
-
* Run one catalyst
|
|
45
|
-
* connect step recorded in customer.json; falls back to `npx @catalyst-cloud/catalyst
|
|
46
|
+
* Run one catalyst verb and return {code, stdout, stderr}. Spawns the CLI whose path the
|
|
47
|
+
* connect step recorded in customer.json; falls back to `npx -p @catalyst-cloud/cli catalyst` when
|
|
46
48
|
* no path is recorded or it no longer exists. Exits 2 when the machine is not connected.
|
|
47
49
|
*/
|
|
48
50
|
export function runCli(args, opts = {}) {
|
|
49
51
|
const cfg = requireCustomerConfig();
|
|
50
|
-
|
|
52
|
+
return spawnCli(cfg, args, opts);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run `catalyst env inventory` / `env check` without requiring a credential at all. Unlike
|
|
57
|
+
* every other verb this launcher can reach, `env inventory` and `env check` are LOCAL and OFFLINE —
|
|
58
|
+
* they never call the cloud — so gating them on `requireCustomerConfig` would break the feature they
|
|
59
|
+
* exist to provide. Still resolves the recorded `cliPath` when a customer.json happens to exist (so
|
|
60
|
+
* an already-connected machine reuses the same installed CLI), and still falls back to
|
|
61
|
+
* `npx -p @catalyst-cloud/cli catalyst` otherwise.
|
|
62
|
+
*/
|
|
63
|
+
export function runCliOffline(args, opts = {}) {
|
|
64
|
+
let cfg = null;
|
|
65
|
+
try {
|
|
66
|
+
const path = configPath();
|
|
67
|
+
if (existsSync(path)) cfg = JSON.parse(readFileSync(path, "utf8"));
|
|
68
|
+
} catch {
|
|
69
|
+
cfg = null;
|
|
70
|
+
}
|
|
71
|
+
return spawnCli(cfg, args, opts);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function spawnCli(cfg, args, opts) {
|
|
75
|
+
const recorded = Boolean(cfg) && typeof cfg.cliPath === "string" && existsSync(cfg.cliPath);
|
|
51
76
|
const command = recorded ? process.execPath : process.platform === "win32" ? "npx.cmd" : "npx";
|
|
52
|
-
const argv = recorded ? [cfg.cliPath, ...args] : [
|
|
77
|
+
const argv = recorded ? [cfg.cliPath, ...args] : [...NPX_ARGS, ...args];
|
|
53
78
|
const res = spawnSync(command, argv, {
|
|
54
79
|
encoding: "utf8",
|
|
55
80
|
input: opts.stdin,
|
|
@@ -58,7 +83,7 @@ export function runCli(args, opts = {}) {
|
|
|
58
83
|
shell: !recorded && process.platform === "win32",
|
|
59
84
|
});
|
|
60
85
|
if (res.error) {
|
|
61
|
-
console.error(`could not run ${recorded ? cfg.cliPath : `npx ${PACKAGE}`}: ${res.error.message}`);
|
|
86
|
+
console.error(`could not run ${recorded ? cfg.cliPath : `npx -p ${PACKAGE} catalyst`}: ${res.error.message}`);
|
|
62
87
|
process.exit(2);
|
|
63
88
|
}
|
|
64
89
|
return { code: res.status ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
@@ -72,7 +97,7 @@ export function runCli(args, opts = {}) {
|
|
|
72
97
|
export function runCliOrExit(args, opts = {}) {
|
|
73
98
|
const res = runCli(args, opts);
|
|
74
99
|
if (res.code === 0) return res;
|
|
75
|
-
const why = res.stderr.trim() || res.stdout.trim() || `catalyst
|
|
100
|
+
const why = res.stderr.trim() || res.stdout.trim() || `catalyst ${args.join(" ")} exited ${res.code}`;
|
|
76
101
|
console.error(why);
|
|
77
102
|
process.exit(res.code === 2 ? 2 : 1);
|
|
78
103
|
}
|
|
@@ -86,18 +111,6 @@ export function parseJson(stdout) {
|
|
|
86
111
|
}
|
|
87
112
|
}
|
|
88
113
|
|
|
89
|
-
/** Forward the `source: replica|api (...)` line a query verb prints, so the answer names its source. */
|
|
90
|
-
export function forwardSourceLine(res) {
|
|
91
|
-
for (const line of res.stderr.split("\n")) {
|
|
92
|
-
if (line.startsWith("source:")) console.error(line);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** True for a ticket identifier such as ABC-123; false for a GitHub node id or anything else. */
|
|
97
|
-
export function looksLikeTicket(s) {
|
|
98
|
-
return /^[A-Za-z][A-Za-z0-9]*-\d+$/.test(s);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
114
|
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
102
115
|
console.log("lib/cli.mjs is a library used by the scripts beside it; run any of those with --help for usage.");
|
|
103
116
|
}
|
|
@@ -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
|
|
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
|
|
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) {
|
|
@@ -1,43 +1,49 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: whats-happening
|
|
3
3
|
description: >-
|
|
4
|
-
The desk for a Catalyst Cloud
|
|
5
|
-
allowed-tools: Bash(catalyst
|
|
4
|
+
The desk for a Catalyst Cloud account, and the facts behind it. Use when the person asks "what's happening?", "where are we?", "why is that stuck?", "what closed?", "what's next?", "how does this work?", "why did it do that?", "how does it prioritise?" or "what does this setting do, where is it set?", or asks for something to be done rather than known. Reads the contract, what is running and queued, the eligibility explainer, the coding accounts and the open asks through the catalyst CLI, and answers in one reply with ticket ids. Explains the ladder, the stage map, failures, parks and holds, the queue order and every reason a ticket is excluded. Routes work to a project owner and decisions to what-needs-me. Read-only: never composes a URL, never polls, never answers as the human.
|
|
5
|
+
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.13.1 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# What's happening
|
|
10
10
|
|
|
11
|
-
You are the one desk the person talks to about their Catalyst Cloud
|
|
11
|
+
You are the one desk the person talks to about their Catalyst Cloud account: where things stand, why something is stuck, what closed, what is next, and how Catalyst works. Answer each in one reply from their own account's data.
|
|
12
|
+
|
|
13
|
+
Every account-specific fact (stage names and ids, labels, the ladder keying, thresholds, merge policy) comes from the contract, `GET /api/v1/agent/contract`; the references hold only what is the same for every account.
|
|
12
14
|
|
|
13
15
|
## Run first
|
|
14
16
|
|
|
15
|
-
Scripts are run, never read
|
|
17
|
+
Scripts are run, never read; each prints `--help`. Exit 2 means this machine is not connected (`catalyst-onboard`), exit 1 that the check itself failed.
|
|
16
18
|
|
|
17
|
-
- `node scripts/snapshot.mjs
|
|
18
|
-
- `node scripts/explain.mjs
|
|
19
|
+
- `node scripts/snapshot.mjs`: the trimmed contract, what is running, the queue, the ranked open asks, and the replica verdict. `--board` adds open tickets by stage, `--accounts` the coding accounts, `--team K` narrows.
|
|
20
|
+
- `node scripts/explain.mjs <ticket>`: why one ticket is or is not about to run. `--history` adds attempts, rounds, holds and past releases.
|
|
21
|
+
- `node scripts/show-my-map.mjs [--team K]`: this account's slot-to-stage map, labels, ladder and live thresholds.
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
One ticket's comments and relations are the `catalyst-linear` skill; a pull request's checks and threads, `catalyst-github`; readiness ("am I set up?"), `catalyst-onboard`.
|
|
21
24
|
|
|
22
25
|
## Load on demand
|
|
23
26
|
|
|
24
27
|
| when | read |
|
|
25
28
|
| -- | -- |
|
|
26
|
-
| writing any status answer | `references/status-reply.md` (schema: `assets/status-reply.json`) |
|
|
27
|
-
| the board looks wrong, a column is long, or you must decide whether something is stuck | `references/reading-the-board.md` |
|
|
29
|
+
| writing any status answer, or deciding whether something is stuck | `references/status-reply.md` (schema: `assets/status-reply.json`) |
|
|
28
30
|
| `explain` returned a reason and the person wants the next action | `references/why-is-it-stuck.md` |
|
|
29
|
-
| "do this one first",
|
|
31
|
+
| "what runs next?", "do this one first", the WIP limit, capacity, routing | `references/what-runs-next.md` |
|
|
32
|
+
| a phase failed, a card went to Remediate, a ticket is parked or on hold | `references/when-a-phase-fails.md` |
|
|
30
33
|
| the person asks for work to be done, or a decision surfaces | `references/routing-work.md` |
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
| "what is Catalyst Cloud?", the phases, what each produces, when a ticket is Done | `references/the-ladder.md` |
|
|
35
|
+
| "which column is which, why does nothing dispatch, what is a slot?" | `references/stages-and-mapping.md` |
|
|
36
|
+
| "why is nothing running", walls, quarantine, which provider ran a phase | `references/coding-accounts.md` |
|
|
37
|
+
| "what does this setting do, where is it set, who can change it?" | `references/settings-and-where-they-live.md` |
|
|
33
38
|
|
|
34
39
|
## Rules
|
|
35
40
|
|
|
36
|
-
- **Their
|
|
37
|
-
- **
|
|
38
|
-
- **One reply, ticket ids on every line, source named
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
41
|
+
- **Their account, as them.** Every read goes through the CLI with the person's own login; "you" in a reply means the connected person.
|
|
42
|
+
- **Print, never recall.** A stage name, label, threshold or route in your answer comes from a script's output in this session. A settings answer names the screen, its route and the rule from `references/settings-and-where-they-live.md`.
|
|
43
|
+
- **One reply, ticket ids on every line, source named** (`references/status-reply.md`).
|
|
44
|
+
- **A reason is a row** in `references/why-is-it-stuck.md`: name what releases it and who can. Unknown is not absent; report it as inconclusive.
|
|
45
|
+
- **Parks and holds have their own release.** Once the cause is fixed, the person's own login releases a park, a no-change hold or a validate-budget hold through the `unstick` skill (`catalyst release`). A round-threshold hold and a review that will not converge refuse that release; `references/when-a-phase-fails.md` says what clears each.
|
|
46
|
+
- **Say what a key cannot see.** PR labels and reactions are not mirrored; repeat the settings URL the CLI prints. Coding accounts and execution history are readable, so read them.
|
|
47
|
+
- **System causes are one alert.** A provider outage, the runner image breaker or a paused repository is said once, for every ticket it holds.
|
|
48
|
+
- **One snapshot per question.** Waiting on a change is the project owner's job (`run-this-project` subscribes to the stream).
|
|
49
|
+
- **You route; the owner acts** (`references/routing-work.md`). The human answers for themselves: a decision is an ask through `what-needs-me`, filed before anyone proceeds on its default.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "What's happening"
|
|
3
|
-
short_description: "Where your Catalyst work stands: in flight, blocked and on whom, closed,
|
|
3
|
+
short_description: "Where your Catalyst work stands and how Catalyst decides: in flight, blocked and on whom, closed, what runs next, and why"
|
|
4
4
|
default_prompt: "Use $whats-happening to tell me where my work stands and why anything is stuck."
|
|
5
5
|
policy:
|
|
6
6
|
allow_implicit_invocation: true
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Coding accounts: slots, windows, walls
|
|
2
|
+
|
|
3
|
+
The person's own rows print with `node scripts/snapshot.mjs --accounts`, with no credential or email. An owner or admin enrols, pauses or removes one at `<their cloud>/settings/coding-accounts`.
|
|
4
|
+
|
|
5
|
+
## Slots and providers
|
|
6
|
+
|
|
7
|
+
A slot is one enrolled credential that runs phases; identity is the credential, so one email may hold several. Claude, GLM and Qwen run under the Claude CLI, Codex under Codex, and GLM and Qwen also under OpenCode. The routing rows (`references/what-runs-next.md`), not the harness, name the model.
|
|
8
|
+
|
|
9
|
+
## State
|
|
10
|
+
|
|
11
|
+
- **Declared:** `active` or `disabled`, set by the operator.
|
|
12
|
+
- **Observed:** `healthy`, `degraded` or `unknown`, set by the poller (every 5 minutes for an active slot, daily for a disabled one).
|
|
13
|
+
- **Quarantined:** system-set on a credential conflict or an authentication mismatch. Replace credential on the account's page clears it, and a workspace owner or admin can do that.
|
|
14
|
+
|
|
15
|
+
## Windows and walls
|
|
16
|
+
|
|
17
|
+
Subscriptions meter usage over a **5-hour** and a **7-day** window. A **wall** is the limit a session can die at mid-run, so a slot whose remaining headroom cannot fit the phase's projected burn is not offered. A Claude slot serves several phases at once up to a cap; a Codex slot serves one, because its refresh tokens are single-use. Vendor status pages say nothing about the person's own windows.
|
|
18
|
+
|
|
19
|
+
## "Why is nothing running?"
|
|
20
|
+
|
|
21
|
+
1. `node scripts/explain.mjs <ticket>`: `routing_unavailable` naming a slot or provider, or `no_eligible_account_slot` in the routing block, points at accounts.
|
|
22
|
+
2. `node scripts/snapshot.mjs --accounts`: an empty list means none is enrolled; a wall is the `walled` field, a quarantine `quarantined` with its reason. Read them; silence proves nothing.
|
|
23
|
+
3. Report it as one fleet-level cause for every ticket it holds.
|
|
24
|
+
|
|
@@ -1,36 +1,18 @@
|
|
|
1
1
|
# Routing work
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Your first sentence says which of three things the message is:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
1. **A question** ("where is X, why is Y stuck, what closed?"): answer it in the one-reply shape (`references/status-reply.md`).
|
|
6
|
+
2. **A request for work** ("get X done, own this until it closes"): turn it into a project with an owner, in one pass, as below.
|
|
7
|
+
3. **A decision only they can make**: raise it through `what-needs-me` before anyone proceeds on the default. `what-needs-me` owns what is and is not an ask.
|
|
6
8
|
|
|
7
|
-
A
|
|
9
|
+
A request containing a decision is both; do and say both.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
2. **A request for work.** "Get X done. Ship the Y change. Own this until it closes." Turn it into a project with an owner, in one pass, as below.
|
|
11
|
-
3. **A decision only they can make**, surfaced by you or by the cloud. That is never answered here; it goes to `what-needs-me`.
|
|
11
|
+
## A request becomes a project with an owner
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
1. **Find the existing home** with `catalyst query projects` and `catalyst query search <terms>`. A request that fits an open project is a ticket there, never a duplicate project.
|
|
14
|
+
2. **Scaffold when there is none:** the outcome in a sentence, the first ticket or two (through `catalyst-linear`), and the owner. The person creates a project in Linear; name it for them.
|
|
15
|
+
3. **Name the owner:** a `run-this-project` session for that project, or a person. Give the one command that starts the owner session; long-running work belongs there, not at the desk.
|
|
16
|
+
4. **Say what you did:** the identifiers created, the owner, and the next thing the person will see. Cite an identifier only after the create call returned it; a guessed number is usually a real, unrelated ticket.
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
You hold no authority over project owners. Your job is to make the work visible and hand it to a single-threaded owner, then get out of the way.
|
|
18
|
-
|
|
19
|
-
1. **Find the existing home first.** `catalyst-skills query projects` and `query search <terms>`. A request that fits an open project is a ticket in that project, not a new project. Duplicate projects split one goal's status across two places.
|
|
20
|
-
2. **Scaffold when there is no home.** State the outcome in one sentence, the first ticket or two, and who owns it. Tickets are created through the `catalyst-linear` skill (the app actor, the team key from the contract); a project itself is created by the person in Linear, so name what you want it called and ask them to create it if none fits.
|
|
21
|
-
3. **Name the owner.** The owner is a `run-this-project` session for that project, or a person. Say which, in the reply. Tell the person the one command that starts the owner session, and do not start long-running work inside this session: the desk answers questions; the owner reacts to events.
|
|
22
|
-
4. **Say what you did.** The reply ends with the identifiers created, the owner named, and the next thing the person will see.
|
|
23
|
-
|
|
24
|
-
Cite an identifier only after the create call returned it. A guessed number is usually a real, unrelated ticket.
|
|
25
|
-
|
|
26
|
-
## What goes to what-needs-me
|
|
27
|
-
|
|
28
|
-
Anything that gates active work on a choice only the human can make: a product call, a priority call between two things that cannot both go first, an approval, or an action only they can physically take (a click in settings, a credential). It is raised as an ask through the `what-needs-me` skill with the question, the options, the default that fires if they stay silent, and what it blocks, and it is raised **before** anyone proceeds on the default.
|
|
29
|
-
|
|
30
|
-
Not an ask: brainstorming, a design back-and-forth, a question the person asked first, a retry-or-abandon call an owner can make, a provider outage (that is one status line, not a per-ticket question).
|
|
31
|
-
|
|
32
|
-
## Three rules that bind this skill
|
|
33
|
-
|
|
34
|
-
- **Never answer as the human.** You do not pick an option on an open ask, close one, or post in their voice. When their answer arrives in chat, it is recorded on the ask through `what-needs-me` so the record is complete, and it is recorded as the app actor, never as them.
|
|
35
|
-
- **Escalate inward, never outward.** An instrument reports to the project owner; the owner asks the desk; the desk asks the human, as an ask. A single stuck ticket is never a page to the human. A system-level failure (provider down, out of capacity, rate-limited) is one line in the status reply, not a question per ticket.
|
|
36
|
-
- **One door.** If the person needs a second place to look after your reply, add the missing block to the reply next time rather than pointing them at a dashboard.
|
|
18
|
+
The human answers for themselves: pick no option on an open ask, close none, and post nothing in their voice. An answer they give in chat is recorded on the ask through `what-needs-me`, as the app actor. A system-level failure is one line in the status reply, not a question per ticket.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Settings and where they live
|
|
2
|
+
|
|
3
|
+
Live values come from the contract or a script. Prefix every route with the person's own `<their cloud>`, never an invented host. Unless a row says otherwise, an owner or admin sets it.
|
|
4
|
+
|
|
5
|
+
| what | where | the rule |
|
|
6
|
+
| -- | -- | -- |
|
|
7
|
+
| **Connecting Linear** | `<their cloud>/settings/connections` | Needs an owner or admin browser session, which also decides the account. |
|
|
8
|
+
| **One workspace, one account** | `<their cloud>/settings/connections` | A Linear workspace binds to exactly one account, and the reverse; a second account for the same workspace is refused. Raise it as a decision, not a retry. |
|
|
9
|
+
| **Installing the GitHub App** | `<their cloud>/settings/connections`, after `<their cloud>/settings/profile` | The admin's personal GitHub grant comes first: a refusal naming a missing GitHub identity or someone else's installation means "grant your personal GitHub first". |
|
|
10
|
+
| **Uninstalling** | `<their cloud>/settings/connections` | Removing the App on GitHub self-heals the registry; unlinking in Catalyst uninstalls upstream. Neither needs support. |
|
|
11
|
+
| **One team, one repository** | `<their cloud>/settings/repositories` | A Linear team maps to at most one repository per account, and a GitHub repository to at most one, case-insensitively. |
|
|
12
|
+
| **Neither half can be re-pointed** | `<their cloud>/settings/repositories` | A repository is one team plus one GitHub repo; the only editable field is its name, so a monorepo across two teams cannot be modelled. |
|
|
13
|
+
| **An archived repository holds both keys** | `<their cloud>/settings/repositories` | Re-registering the same team-plus-repo pair is refused until the archived entry is restored. |
|
|
14
|
+
| **Merge policy** | `<their cloud>/settings/repositories/$repoId/merging` | The effective policy and its source are on the contract's `merge` block; this page changes it (`catalyst-github` only reads it). |
|
|
15
|
+
| **Reviewers** | `<their cloud>/settings/repositories/$repoId/code-reviews` | "Post review requests as" names one connected member. A strict merge policy with no reviewer never earns the merge label: configure one, or change the policy. |
|
|
16
|
+
| **The merge queue** | no settings page; the repository's own merge automation | The merge phase's one write is the ready label; the merge itself is whatever the repository's own setup does with it. This bundle cannot see which queue runs; say so, naming no vendor the person has not named. The GitHub repository's administrator owns it. |
|
|
17
|
+
| **The runner cap** | `<their cloud>/settings/repositories` | 20 running phases per repository by default, which an operator changes; the page only displays it, and a paused repository resolves to zero. See `references/what-runs-next.md`. |
|
|
18
|
+
| **Secrets and environment variables** | `<their cloud>/settings/secrets`, `<their cloud>/settings/environment`, and each repository's sections | Secrets are write-only; variables are readable. A name is one or the other, never both; a repository value wins over an account one; a variable may reference a vault secret by name, expanded only in the container. |
|
|
19
|
+
| **"My secrets are not in the container"** | `<their cloud>/settings/repositories` | When a repository was registered without its team, nothing reaches a runner, and the failure is silent: a phase's build cannot find its environment. Raise the fix, an admin or operator action, as an ask. |
|
|
20
|
+
| **Routing** | none: there is no settings page for this | Stage defaults are global and the per-account table is empty, so routing always resolves to the default provider. A Codex-first pipeline is not something to promise (`references/what-runs-next.md`). |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Stages and mapping
|
|
2
|
+
|
|
3
|
+
A **slot** is Catalyst's name for a position on the board; a **stage** is the Linear workflow state a team has; the **mapping** binds each slot to one stage per team. Every team's live map is on the contract under `teams[].stages`; `node scripts/show-my-map.mjs [--team <key>]` prints it with the load-bearing slots starred.
|
|
4
|
+
|
|
5
|
+
The slots, in pipeline order: `dispatch`, `intake`, `research`, `plan`, `implement`, `remediate`, `verify`, `review`, `pr`, `done`, `canceled`.
|
|
6
|
+
|
|
7
|
+
## Five slots are load-bearing
|
|
8
|
+
|
|
9
|
+
| slot | why it matters | allowed state types |
|
|
10
|
+
| -- | -- | -- |
|
|
11
|
+
| `dispatch` | moving a card here dispatches work; nothing is offered from any other column | `unstarted`, `backlog` |
|
|
12
|
+
| `intake` | where a never-seen ticket lands for the optional intake pass | `unstarted`, `backlog`, never Linear's triage type |
|
|
13
|
+
| `pr` | a card must sit here for `merge` to be offered | `started` |
|
|
14
|
+
| `done` | written by the merge webhook | `completed` |
|
|
15
|
+
| `canceled` | terminal; never work | `canceled` |
|
|
16
|
+
|
|
17
|
+
An absent or wrongly typed mapping on one of these stops the ladder silently (on the others it costs a readiness warning): until dispatch, pr, done and canceled each point at a live stage, Catalyst starts nothing in that team, and `explain` says so. `teams[].gitAutomation` is a stored consent for a feature not built yet; nothing reads it, so its value neither stops nor starts work.
|
|
18
|
+
|
|
19
|
+
`teams[].workflowMode` is **adopted** (Catalyst created the stages), **mapped** (a human chose each) or **mixed**. Mappings live at `<their cloud>/settings/linear-teams`; `.catalyst/catalyst.toml` has no stage section.
|
|
20
|
+
|
|
21
|
+
## The state id is the authority
|
|
22
|
+
|
|
23
|
+
Only a stage's `stateId` is a lookup key, because a Linear import can keep every name while re-minting every id. Move cards by slot (`catalyst-linear` does) and let the CLI resolve the id. `stateStillExists: false` means the team needs re-mapping (`catalyst team map <KEY>`) before that slot can be written; the CLI refuses the move.
|
|
24
|
+
|
|
25
|
+
Backlog is not a slot: parking a card moves it to the team's backlog-type state, which the CLI resolves from the live workflow. Readiness over the mapping, and who fixes each check, is the `catalyst-onboard` skill.
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
# The one-reply shape
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One question gets one reply, complete enough that the person needs no second surface. Every line names a ticket identifier, and a fact you could not read is named as unreadable, with where it lives.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Open with one clause: the snapshot's `takenAt` and its source (a fresh replica's cursor, or the API and why). Then the blocks, in order:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
1. **In flight.** From the snapshot's `running` block: identifier, the phase running, how long.
|
|
8
|
+
2. **Blocked, and on whom.** The queue's excluded rows, with `explain` on the ones the person cares about. Every line carries the reason in the person's words and who releases it: the person, a role, the cloud itself, or a clock.
|
|
9
|
+
3. **Waiting on the human.** `waitingOnHuman` in the snapshot, ranked by what each ask holds: identifier, the question, what it releases. `what-needs-me` owns the detail.
|
|
10
|
+
4. **Closed.** What reached the done slot in the window asked about (`query issues` by the done-slot stage name, or the change feed); with no window read, "since my last reply".
|
|
11
|
+
5. **Next.** The snapshot's `queue` block in the cloud's order, with each ticket's next phase.
|
|
12
|
+
6. **Cannot see**, only when non-empty: PR labels and reactions (not mirrored), each with the URL the CLI printed. A flow number goes here by name: cycle time, throughput, or how long pull requests have been open are not computed by anything a key reads, so say they are not computed rather than counting ticket dates.
|
|
8
13
|
|
|
9
|
-
##
|
|
14
|
+
## Writing the lines
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
4. **Closed.** What reached the done slot in the window the person asked about (or since your last reply). Source: `query issues` filtered by the team's done-slot stage name from the contract, or the change feed for a time window. When you did not read a window, say "since my last reply" and mean it.
|
|
15
|
-
5. **Next.** What the queue picks up next, in the cloud's order, with the phase each will run. Source: the snapshot's `queue` block. Do not reorder it to what you think should be next; the levers are in `references/reprioritising.md`.
|
|
16
|
+
- Identifier first, then the stage as the contract spells it, then one clause: `KEY-123 · <stage name> · implement running 14 min`.
|
|
17
|
+
- Age in human units from `updated_at` or the lease start, judged against the phase's natural duration.
|
|
18
|
+
- A reason is its translation from `references/why-is-it-stuck.md`. One ticket in flight and nothing blocked is a three-line reply.
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
## Reading the board
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
`node scripts/snapshot.mjs --board` groups open tickets by stage in slot order; read it by slot, never by name. A long dispatch column with nothing in flight is a capacity or eligibility question: `explain` the first row. A card that has not moved is running, retrying in place, or in remediate, which is an interrupt to count separately, never progress. An ask-labelled ticket belongs in the waiting-on-human block; a local-lane ticket is in flight elsewhere. The ticket's comments (`catalyst-linear`) and `catalyst explain --history <ticket>` must agree.
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
## What counts as stuck
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
A ticket is **stuck** only when all three hold:
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- No adjectives about health. "Stuck" has a definition (`references/reading-the-board.md`); use it only when it applies.
|
|
28
|
+
1. Nothing is offered for it: the queue excludes it, or `explain` gives a reason rather than a position.
|
|
29
|
+
2. The reason does not release itself (`references/why-is-it-stuck.md` marks which do).
|
|
30
|
+
3. No one is acting on the release: no open ask names it, and no human comment or push landed after the failure.
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
Everything else is **waiting**, and the reply says on what; calling a wait "stuck" sends the human to fix what the cloud is handling.
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
- It never answers an ask, proposes a default on the human's behalf inside the status reply, or moves anything. Routing a request is `references/routing-work.md`; a decision is the `what-needs-me` skill.
|
|
34
|
-
- It never pads a short answer. When one ticket is in flight and nothing is blocked, the reply is three lines.
|
|
34
|
+
The reply answers no ask, proposes no default for the human, and moves nothing: requests go to `references/routing-work.md`, decisions to `what-needs-me`.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# What Catalyst is, and the ladder
|
|
2
|
+
|
|
3
|
+
## In the person's terms
|
|
4
|
+
|
|
5
|
+
Use their repositories and tickets as examples. Moving a card into the team's dispatch column starts the work, and nothing else does. Each phase runs in a cloud container set up for the repository, on a coding account their workspace enrolled, and leaves a document and an outcome comment on the ticket, so the ticket is the record. Their laptop runs no coding sessions, and agents react to events rather than polling.
|
|
6
|
+
|
|
7
|
+
What changes in their day: they write each ticket for a reader who cannot see their chat (an outcome title, what done looks like, a priority, no open blocker). They leave working stages and Done to Catalyst. A draft pull request appears that a later phase marks ready, and a green one with no open threads merges through the queue unless a hold label keeps it out. A decision only they can make arrives as an ask in their own Linear, with options, a silent default and what it holds. A comment they post gets an eyes reaction and a threaded reply.
|
|
8
|
+
|
|
9
|
+
## The phases
|
|
10
|
+
|
|
11
|
+
The contract's `ladder` block (`node scripts/show-my-map.mjs`) says whether intake is on and which keying applies.
|
|
12
|
+
|
|
13
|
+
| phase | produces | notes |
|
|
14
|
+
| -- | -- | -- |
|
|
15
|
+
| `intake` | nothing (a classification pass) | optional, for a new ticket, when `ladder.intakeEnabled` |
|
|
16
|
+
| `research` | `research.md` | offered from the dispatch slot |
|
|
17
|
+
| `plan` | `plan.md` | |
|
|
18
|
+
| `implement` | `implement.md` | creates the branch, named exactly the ticket identifier, and opens a draft pull request |
|
|
19
|
+
| `validate` | `validation.md` | success moves the card to the review stage (verify and review share it) |
|
|
20
|
+
| `pr` | `pr.md` | first line is the PR title, then the body; rebases, force-pushes, marks the PR ready |
|
|
21
|
+
| `remediate` | `remediation.json` | an interrupt that repairs a failed phase; never advances past the PR slot |
|
|
22
|
+
| `merge` | a receipt | applies the queue-ready label once its evidence gate passes |
|
|
23
|
+
|
|
24
|
+
## What moves the card
|
|
25
|
+
|
|
26
|
+
The contract's `ladder.advance` maps each completed phase to a card move; its keying decides whether a stage names the phase just finished (trailing, the default) or the one ahead (leading). A failed phase, `remediate` and `merge` move nothing (`references/when-a-phase-fails.md`).
|
|
27
|
+
|
|
28
|
+
**Done is written by the merge.** GitHub's merged event moves the ticket to Done within seconds, with a sweep behind a dropped webhook, so a ticket not Done a minute after its PR merged is a finding, not a chore to hand-close. Done means merged; a separately deployed change is live only once deployed.
|
|
29
|
+
|
|
30
|
+
To place a ticket, its stage says which phase last finished (`node scripts/show-my-map.mjs --team <key>` translates it) and `explain` says what runs next.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# What runs next: order, capacity, routing, and the person's levers
|
|
2
|
+
|
|
3
|
+
## Queue order
|
|
4
|
+
|
|
5
|
+
The cloud derives each team's queue from its tickets within seconds of any board change. Cards in the dispatch slot rank by Linear **priority** (urgent first), then **created time**, then **identifier**. Tickets already **mid-ladder** join the same set and sort ahead by phases completed, so a ticket near the end goes first; that count orders, and never makes an ineligible ticket eligible.
|
|
6
|
+
|
|
7
|
+
## Capacity
|
|
8
|
+
|
|
9
|
+
- Each repository has a concurrency cap of 20 running phases by default, which an operator raises or lowers; the account's own settings page displays it. A paused repository resolves to zero and keeps the stored value for resuming.
|
|
10
|
+
- Dispatch splits slots across teams so none starves, and comment-wake work shares the cap.
|
|
11
|
+
|
|
12
|
+
## The work-in-progress limit
|
|
13
|
+
|
|
14
|
+
A project (one Linear team across its repositories) at its limit starts no new ticket: its own setting, else an account-wide value an operator set, else 12. `0` holds every new start. Members read it, its source and the count with `catalyst project wip-limit get --team <KEY>`; an owner or admin runs `set <n>` (or `set default`).
|
|
15
|
+
|
|
16
|
+
It holds only first phases (`explain` prints `wip_limit`, e.g. "14 tickets in progress, at or above its WIP limit of 12"); a started ticket keeps getting every later phase. In progress counts tickets, not containers: every live ticket past the dispatch column (or granted a first phase), blocked, parked and waiting ones included; never-started, backlog and local-lane tickets are not counted. A fleet idle at the limit means the tickets in progress are all waiting; unstick those (`unstick`, `catalyst-github`, `what-needs-me`).
|
|
17
|
+
|
|
18
|
+
## Routing
|
|
19
|
+
|
|
20
|
+
A phase about to start takes the first route that passes, in order: capability match, a configured model, the provider available, an eligible coding-account slot with headroom (when the route needs one), and resolvable parameters. With no survivor, the routing block reads `no_eligible_account_slot` when a candidate was skipped on capacity, else `routing_unavailable`. Stage defaults are global and there is no routing setting yet, so a Codex-first pipeline is not something to promise. Slots are in `references/coding-accounts.md`.
|
|
21
|
+
|
|
22
|
+
## The person's levers
|
|
23
|
+
|
|
24
|
+
1. **Priority on the ticket** reorders the queue: the lever for "do this one first".
|
|
25
|
+
2. **The dispatch column.** Moving a card there starts work; moving it to a backlog-type state stops work, and further rounds, without cancelling. Describe the move and let the person or the project owner (`run-this-project`) make it.
|
|
26
|
+
3. **Holds.** A hold label from the contract's `merge.prLabels` keeps a mergeable pull request out of the queue until removed. An ask's blocking relation holds every ticket it names. The contract's release label frees a ticket the ask-shape detector flagged by mistake.
|
|
27
|
+
|
|
28
|
+
No lever interrupts a running phase. A failed phase needs no re-queueing, and moving its card by hand usually restarts its count. Priority is the fleet's order, not the person's urgency: an urgent ticket blocked on an ask still waits for the ask.
|
|
29
|
+
|
|
30
|
+
## "Why is that one not next?"
|
|
31
|
+
|
|
32
|
+
Run `explain` on it. A position means capacity bounds it: say how many are ahead and whether anything runs at all; if nothing runs anywhere, read the coding accounts first. A reason goes to `references/why-is-it-stuck.md`. A ticket missing from the explainer is on another team, terminal, or unknown; check the identifier.
|