@catalyst-cloud/catalyst-skills 0.7.0 → 0.8.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.
- package/README.md +11 -182
- package/bin/catalyst-skills.js +3 -49
- package/bin/catalyst.js +3 -0
- package/package.json +8 -31
- package/CHANGELOG.md +0 -65
- package/LICENSE +0 -21
- package/dist/args.js +0 -273
- package/dist/ask.js +0 -161
- package/dist/browser.js +0 -20
- package/dist/cli.js +0 -361
- package/dist/config.js +0 -152
- package/dist/contract-types.js +0 -4
- package/dist/contract.js +0 -184
- package/dist/detach.js +0 -10
- package/dist/environment.js +0 -207
- package/dist/errors.js +0 -27
- package/dist/events.js +0 -106
- package/dist/execution.js +0 -451
- package/dist/oauth.js +0 -300
- package/dist/pagination.js +0 -76
- package/dist/prompt.js +0 -35
- package/dist/published.js +0 -75
- package/dist/query.js +0 -248
- package/dist/ready.js +0 -254
- package/dist/release.js +0 -142
- package/dist/replica.js +0 -585
- package/dist/sdk.js +0 -26
- package/dist/semver.js +0 -18
- package/dist/skill-shape.js +0 -189
- package/dist/skills.js +0 -129
- package/dist/transport.js +0 -205
- package/dist/ts-deps-loader.js +0 -113
- package/dist/watch/consumer.js +0 -141
- package/dist/watch/cursor-file.js +0 -62
- package/dist/watch.js +0 -175
- package/dist/write.js +0 -224
- package/skills/catalyst-github/SKILL.md +0 -35
- package/skills/catalyst-github/agents/openai.yaml +0 -6
- package/skills/catalyst-github/agents/portability.yaml +0 -4
- package/skills/catalyst-github/references/is-it-mergeable.md +0 -57
- package/skills/catalyst-github/references/what-a-pr-accumulates.md +0 -61
- package/skills/catalyst-github/scripts/is-it-mergeable.mjs +0 -124
- package/skills/catalyst-github/scripts/lib/cli.mjs +0 -103
- package/skills/catalyst-github/scripts/lib/credential.mjs +0 -29
- package/skills/catalyst-github/scripts/lib/pull.mjs +0 -82
- package/skills/catalyst-github/scripts/read-pr.mjs +0 -97
- package/skills/catalyst-linear/SKILL.md +0 -43
- package/skills/catalyst-linear/agents/openai.yaml +0 -6
- package/skills/catalyst-linear/agents/portability.yaml +0 -5
- package/skills/catalyst-linear/references/reading-a-ticket.md +0 -52
- package/skills/catalyst-linear/references/what-a-ticket-accumulates.md +0 -53
- package/skills/catalyst-linear/references/writing-to-linear.md +0 -43
- package/skills/catalyst-linear/scripts/comment.mjs +0 -59
- package/skills/catalyst-linear/scripts/create-ticket.mjs +0 -44
- package/skills/catalyst-linear/scripts/label.mjs +0 -48
- package/skills/catalyst-linear/scripts/lib/cli.mjs +0 -164
- package/skills/catalyst-linear/scripts/lib/credential.mjs +0 -29
- package/skills/catalyst-linear/scripts/move.mjs +0 -41
- package/skills/catalyst-linear/scripts/read-ticket.mjs +0 -93
- package/skills/catalyst-linear/scripts/search.mjs +0 -49
- package/skills/catalyst-onboard/SKILL.md +0 -49
- package/skills/catalyst-onboard/agents/openai.yaml +0 -6
- package/skills/catalyst-onboard/agents/portability.yaml +0 -5
- package/skills/catalyst-onboard/references/the-one-path.md +0 -143
- package/skills/catalyst-onboard/references/what-the-browser-owns.md +0 -50
- package/skills/catalyst-onboard/references/who-fixes-what.md +0 -41
- package/skills/catalyst-onboard/scripts/lib/cli.mjs +0 -117
- package/skills/catalyst-onboard/scripts/lib/credential.mjs +0 -29
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +0 -245
- package/skills/catalyst-setup/SKILL.md +0 -32
- 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 -78
- package/skills/catalyst-setup/scripts/check.mjs +0 -75
- package/skills/catalyst-setup/scripts/lib/cli.mjs +0 -103
- package/skills/catalyst-setup/scripts/lib/credential.mjs +0 -29
- 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 -42
- 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 -54
- package/skills/how-catalyst-works/references/the-ladder.md +0 -41
- package/skills/how-catalyst-works/references/what-runs-next.md +0 -77
- 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/show-my-map.mjs +0 -94
- package/skills/how-catalyst-works/scripts/whats-running.mjs +0 -65
- package/skills/run-this-project/SKILL.md +0 -45
- package/skills/run-this-project/agents/openai.yaml +0 -6
- package/skills/run-this-project/agents/portability.yaml +0 -5
- package/skills/run-this-project/assets/stall-policy.json +0 -15
- package/skills/run-this-project/references/making-work-ready.md +0 -60
- package/skills/run-this-project/references/reacting-to-events.md +0 -76
- package/skills/run-this-project/references/stalls-and-escalation.md +0 -63
- package/skills/run-this-project/scripts/lib/cli.mjs +0 -185
- package/skills/run-this-project/scripts/lib/credential.mjs +0 -29
- package/skills/run-this-project/scripts/make-ready.mjs +0 -64
- package/skills/run-this-project/scripts/scope-status.mjs +0 -0
- package/skills/run-this-project/scripts/watch-scope.mjs +0 -61
- package/skills/unstick/SKILL.md +0 -41
- package/skills/unstick/agents/openai.yaml +0 -6
- package/skills/unstick/agents/portability.yaml +0 -5
- package/skills/unstick/references/playbook.md +0 -51
- package/skills/unstick/scripts/lib/cli.mjs +0 -135
- package/skills/unstick/scripts/lib/credential.mjs +0 -29
- package/skills/unstick/scripts/unstick.mjs +0 -57
- package/skills/what-needs-me/SKILL.md +0 -41
- package/skills/what-needs-me/agents/openai.yaml +0 -6
- package/skills/what-needs-me/agents/portability.yaml +0 -5
- package/skills/what-needs-me/references/raising-a-decision.md +0 -41
- package/skills/what-needs-me/references/reading-the-inbox.md +0 -38
- package/skills/what-needs-me/references/settling-an-answer.md +0 -37
- package/skills/what-needs-me/scripts/inbox.mjs +0 -56
- package/skills/what-needs-me/scripts/lib/cli.mjs +0 -135
- package/skills/what-needs-me/scripts/lib/credential.mjs +0 -29
- package/skills/what-needs-me/scripts/raise.mjs +0 -53
- package/skills/what-needs-me/scripts/settle.mjs +0 -73
- package/skills/whats-happening/SKILL.md +0 -43
- package/skills/whats-happening/agents/openai.yaml +0 -6
- package/skills/whats-happening/agents/portability.yaml +0 -4
- package/skills/whats-happening/assets/status-reply.json +0 -77
- package/skills/whats-happening/references/reading-the-board.md +0 -43
- package/skills/whats-happening/references/reprioritising.md +0 -37
- package/skills/whats-happening/references/routing-work.md +0 -36
- package/skills/whats-happening/references/status-reply.md +0 -34
- package/skills/whats-happening/references/why-is-it-stuck.md +0 -62
- package/skills/whats-happening/scripts/explain.mjs +0 -28
- package/skills/whats-happening/scripts/lib/cli.mjs +0 -135
- package/skills/whats-happening/scripts/lib/credential.mjs +0 -29
- package/skills/whats-happening/scripts/snapshot.mjs +0 -149
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: connect-me
|
|
3
|
-
description: >-
|
|
4
|
-
Connect this machine to the person's Catalyst Cloud tenant as themselves — keyless (a browser device-code login) or with their own personal key — cache the tenant contract, and verify. Use when a person is getting started, when their config is missing or broken, when their login expired or they rotated their key, when a skill script exits 2 saying the machine is not connected, or when they ask which tenant this machine belongs to. The login names the tenant and the person, so nobody types an account id. Installing the skills is not this skill's job; the agent's own install command did that.
|
|
5
|
-
disable-model-invocation: true
|
|
6
|
-
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
7
|
-
---
|
|
8
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.7.0 — written in this repository for customer tenants -->
|
|
9
|
-
|
|
10
|
-
# Connect me
|
|
11
|
-
|
|
12
|
-
Connecting is one command. Keyless is the preferred rail — with no key, `login` opens a browser device-code flow and logs the person in as themselves:
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
catalyst-skills login
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
It prints a short code and a URL; the person approves in a browser (or from a phone, on a machine with no browser) and this machine is connected as them. The short-lived session refreshes silently on every request, so they stay connected for months.
|
|
19
|
-
|
|
20
|
-
A **personal key** still works for a script or an unattended shell — the environment form keeps it out of shell history; `--key <personal-key>` is the third form:
|
|
21
|
-
|
|
22
|
-
```sh
|
|
23
|
-
CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst-skills login
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
`npx @catalyst-cloud/catalyst-skills login` works when the package is not installed globally. A non-default cloud is pinned with `CATALYST_CLOUD_BASE_URL` or `--base-url`.
|
|
27
|
-
|
|
28
|
-
**This skill never installs skills.** You are already reading one, so the person's agent installed the set with its own command. Connecting is only about the credential and the contract.
|
|
29
|
-
|
|
30
|
-
**What it writes.** `~/.config/catalyst-cloud/customer.json` (mode 0600) holding the credential — a keyless login session (`auth`), or a personal key — the tenant it resolved, the person it resolved (`user`: id, label, role, Linear user id), and the absolute path of this CLI so skill scripts can spawn it; `~/.config/catalyst-cloud/contract.json`, the tenant contract with its ETag. A keyless session's token rotates on its own and the file is rewritten atomically each time.
|
|
31
|
-
|
|
32
|
-
**The replica is optional and one command away.** Nothing has to be running for reads, writes, asks or explanations; the API is origin-fresh. `catalyst-skills replica start --detach` starts the local replica for cheap repeated reads and ad hoc SQL; `login --start-replica` does the same at the end. Every skill checks `catalyst-skills replica status` first and falls back to the API when the replica is absent or stale, saying so.
|
|
33
|
-
|
|
34
|
-
## What you do
|
|
35
|
-
|
|
36
|
-
1. Prefer keyless: run `catalyst-skills login` with no key and have the person approve the code in their browser (or from a phone). Nothing to mint, nothing to paste, nothing to keep out of the transcript. A **personal key** is the fallback for a script or unattended shell — they mint it at Settings → API keys (every active member can; no admin is needed) and hand it to the login command via `CATALYST_CLOUD_TOKEN`; it is shown once, so never put it in a file, a ticket, or this conversation beyond that one command. Either way, do not ask an admin for the tenant's **account key** (`ctc_acct_`, Settings → Account keys): that is a host credential — runners, daemons, the host replica — and a person connected with it has no name on anything their agent writes, and "what needs me" cannot mean them. If they connect with one anyway, login says so on stderr and still works.
|
|
37
|
-
2. Run the login command. Its output names the tenant (`Connected to <name> (<slug>)`), the person (`Connected as <label> (<role>)`), the config path, and the cached contract version. If the person line says their Linear identity is not matched yet, tell them: an admin matches it in Settings → Members, and until then "what needs me" shows everyone's asks.
|
|
38
|
-
3. Verify with `node scripts/verify-connection.mjs`: one line each for the tenant, the contract version, and the replica; exit 1 when the machine is not connected.
|
|
39
|
-
4. Run `catalyst-skills ready` and read the verdict to them.
|
|
40
|
-
5. Offer the replica: `catalyst-skills replica start --detach`, then `catalyst-skills replica status`. For keeping it alive across a reboot, load `references/keeping-the-replica-running.md`.
|
|
41
|
-
6. If login fails: a keyless session that reports "your login expired or was revoked" needs one fresh `catalyst-skills login`, never a retry loop (it means the session was revoked or lapsed past the inactivity window — routine expiry refreshes silently and never surfaces). A `401` on the key rail means a stale, mistyped or revoked key — mint a new one at Settings → API keys, never a retry loop; a network error names the URL, check `--base-url`. A `403` on the contract naming an older cloud means the cloud has not yet deployed personal-key access: update the cloud, or connect with the account key until it has. If login succeeds but prints a line naming two contract versions, the tenant serves a contract outside this bundle's range: update the bundle before using the other skills.
|
|
42
|
-
|
|
43
|
-
## The verbs a session runs first
|
|
44
|
-
|
|
45
|
-
Every skill session opens with these, in this order, before doing anything else:
|
|
46
|
-
|
|
47
|
-
1. `catalyst-skills status` to confirm the tenant this machine belongs to and where the config and contract live.
|
|
48
|
-
2. `catalyst-skills contract` to load the tenant contract (cached per its own policy; `--refresh` forces a revalidation; `--path teams.0.stages` prints one sub-document).
|
|
49
|
-
3. `catalyst-skills replica status` to learn the read source: exit 0 fresh, 1 stale, 2 not configured, 3 absent.
|
|
50
|
-
4. `catalyst-skills ready` for the full verdict: Node, config, contract, CLI path, skills, SDK, replica, and the tenant's own readiness checks with who can fix each.
|
|
51
|
-
|
|
52
|
-
## Load on demand
|
|
53
|
-
|
|
54
|
-
| when | read |
|
|
55
|
-
| -- | -- |
|
|
56
|
-
| the person wants the replica writer to survive a reboot, or asks what it stores and whether anything needs cleaning | `references/keeping-the-replica-running.md` |
|
|
57
|
-
| the full readiness vector and what each check means | the `catalyst-setup` skill |
|
|
58
|
-
|
|
59
|
-
## Rules
|
|
60
|
-
|
|
61
|
-
- **The credential is a secret.** Keyless is safest — nothing to handle. A key goes into the login command or `CATALYST_CLOUD_TOKEN` and nowhere else: not into tickets, transcripts, or any file you write besides the one login writes; and never read back the `auth` block a keyless session stores.
|
|
62
|
-
- **Never guess a tenant.** There is no `--account`; the credential is the only tenant selector, on purpose. If the cloud names no tenant for the login, stop and say so.
|
|
63
|
-
- **Read the update notice.** After a bundle update, the next command prints one line with the changelog entry and what to run; read it to the person if they are watching.
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
interface:
|
|
2
|
-
display_name: "Connect me"
|
|
3
|
-
short_description: "Connect this machine to your Catalyst Cloud tenant with your own personal key, cache the tenant contract, and verify"
|
|
4
|
-
default_prompt: "Use $connect-me to connect this machine to my Catalyst Cloud tenant."
|
|
5
|
-
policy:
|
|
6
|
-
allow_implicit_invocation: false
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Keeping the replica running
|
|
2
|
-
|
|
3
|
-
This reference restates invariants of the local replica the Catalyst Cloud SDK manages. Paths are the CLI's defaults; the replica file can be moved with `--db` and the config records it.
|
|
4
|
-
|
|
5
|
-
## The supported path is the plain command
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
catalyst-skills replica start --detach # start in the background, write a pidfile, return
|
|
9
|
-
catalyst-skills replica status # 0 fresh, 1 stale, 2 not configured, 3 absent; one line either way
|
|
10
|
-
catalyst-skills replica status --probe # also compare the local cursor with the cloud head
|
|
11
|
-
catalyst-skills replica stop # signal the pidfile's process
|
|
12
|
-
catalyst-skills replica start # foreground, Ctrl-C to stop
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
It is a Node process, not a service. Node 22 or newer with its built-in SQLite module runs it the same on macOS, Linux and Windows, and the bundle never requires a daemon, because a required service is the first thing that breaks on a laptop. The writer is not a prerequisite for any skill; a fresh one is a preference. A skill must never refuse to work because the replica is down, and must never silently read a stale one; `replica status` is the one exit code that settles both.
|
|
16
|
-
|
|
17
|
-
## What it holds on disk, and why nothing rotates
|
|
18
|
-
|
|
19
|
-
Under `~/.config/catalyst-cloud/` by default:
|
|
20
|
-
|
|
21
|
-
| file | what it is |
|
|
22
|
-
| -- | -- |
|
|
23
|
-
| `replica.db` | one SQLite file: the tenant's mirrored tables, kept current by upserts and deletes from the stream |
|
|
24
|
-
| `replica.db.writer.lock` | the writer's lock, with a heartbeat the status check reads |
|
|
25
|
-
| `replica.db.pid` | the background writer's process id, written by `--detach` |
|
|
26
|
-
| a `sync_meta` row inside the database | the stream cursor, so a restart resumes where it stopped |
|
|
27
|
-
| `watch-cursor.json` | the events-only cursor for `catalyst-skills watch`, stamped with the tenant; a few bytes |
|
|
28
|
-
|
|
29
|
-
The replica is upserts and deletes into one file, so it neither grows without bound nor needs pruning; the cursor is a row. There is no directory of old files to clean and nothing to rotate. If the writer logs, it logs to standard error and the shell decides where that goes; nothing under the config directory is a log.
|
|
30
|
-
|
|
31
|
-
The first start seeds the file from the cloud's snapshot (one full copy of the tables) and then follows the stream; a restart resumes from the saved cursor. When the cloud can no longer replay from that cursor it tells the writer to re-seed, which the writer does on its own.
|
|
32
|
-
|
|
33
|
-
## Freshness, as the status check judges it
|
|
34
|
-
|
|
35
|
-
`replica status` needs no network. It answers fresh when the pidfile's process is alive, the writer lock's heartbeat is younger than the staleness threshold (15 seconds by default; `--stale-ms` overrides), and the cursor row is non-empty. Anything else is stale (exit 1) with the reasons listed, absent (exit 3) when there is no file, or not configured (exit 2) when the machine is not connected. `--probe` adds the one network call, fetching the cloud head to print how far behind the local cursor is. Every read verb prints `source: replica (cursor N)` or `source: api (replica stale|absent|not configured)` on standard error, so the answer always names where it came from.
|
|
36
|
-
|
|
37
|
-
## Surviving a reboot
|
|
38
|
-
|
|
39
|
-
Optional. The plain command is the supported path; these are for people who want the writer back after a restart. Each example assumes a global install (`npm install -g @catalyst-cloud/catalyst-skills`); substitute the absolute path `catalyst-skills status` prints as the CLI path if you prefer. Run the writer in the foreground under the supervisor (no `--detach`), so the supervisor owns the process.
|
|
40
|
-
|
|
41
|
-
### macOS, launchd
|
|
42
|
-
|
|
43
|
-
Save as `~/Library/LaunchAgents/dev.catalystcloud.replica.plist`, then `launchctl load ~/Library/LaunchAgents/dev.catalystcloud.replica.plist`:
|
|
44
|
-
|
|
45
|
-
```xml
|
|
46
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
47
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
48
|
-
<plist version="1.0"><dict>
|
|
49
|
-
<key>Label</key><string>dev.catalystcloud.replica</string>
|
|
50
|
-
<key>ProgramArguments</key><array>
|
|
51
|
-
<string>/usr/local/bin/catalyst-skills</string><string>replica</string><string>start</string>
|
|
52
|
-
</array>
|
|
53
|
-
<key>RunAtLoad</key><true/>
|
|
54
|
-
<key>KeepAlive</key><true/>
|
|
55
|
-
<key>StandardErrorPath</key><string>/tmp/catalyst-cloud-replica.err</string>
|
|
56
|
-
</dict></plist>
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Check the path to the binary with `which catalyst-skills`; Homebrew Node installs under `/opt/homebrew/bin`. `launchctl unload` the same file to stop it.
|
|
60
|
-
|
|
61
|
-
### Linux, systemd user unit
|
|
62
|
-
|
|
63
|
-
Save as `~/.config/systemd/user/catalyst-cloud-replica.service`, then `systemctl --user daemon-reload && systemctl --user enable --now catalyst-cloud-replica`:
|
|
64
|
-
|
|
65
|
-
```ini
|
|
66
|
-
[Unit]
|
|
67
|
-
Description=Catalyst Cloud local replica
|
|
68
|
-
After=network-online.target
|
|
69
|
-
|
|
70
|
-
[Service]
|
|
71
|
-
ExecStart=/usr/bin/env catalyst-skills replica start
|
|
72
|
-
Restart=on-failure
|
|
73
|
-
RestartSec=5
|
|
74
|
-
|
|
75
|
-
[Install]
|
|
76
|
-
WantedBy=default.target
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
For a writer that should run while nobody is logged in, `loginctl enable-linger $USER` once. `journalctl --user -u catalyst-cloud-replica` shows its standard error.
|
|
80
|
-
|
|
81
|
-
### Windows
|
|
82
|
-
|
|
83
|
-
There is no service wrapper in the bundle. Task Scheduler runs `catalyst-skills replica start` at logon with "Run whether user is logged on or not" and "If the task fails, restart every 1 minute"; or run `catalyst-skills replica start --detach` from a shell after logging in, which is the plain path and works the same as elsewhere.
|
|
84
|
-
|
|
85
|
-
## Two things that look like problems and are not
|
|
86
|
-
|
|
87
|
-
- **Two writers.** The writer lock guards the file, so a second writer on the same file is refused rather than allowed to corrupt it. Stop the first (`replica stop`, or the supervisor) before starting another or moving the file.
|
|
88
|
-
- **A stale verdict right after start.** The first start seeds the whole snapshot before it goes live; `status` reads stale until the cursor row appears. Ask again once `replica start` has printed its live line, or watch `status --probe` count the lag down.
|
|
@@ -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,42 +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, and the coding-account model. Use when a person asks "how does this work?", "why did it do that?", "why is this stuck?", "what runs next?" or "how does it prioritise?". 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.7.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 are the phases, what does each produce, when is a ticket Done?" | `references/the-ladder.md` |
|
|
30
|
-
| "which column is which, why does nothing dispatch, what is a slot?" | `references/stages-and-mapping.md` |
|
|
31
|
-
| a phase FAILED, a card went to Remediate, a ticket is parked or on hold | `references/when-a-phase-fails.md` |
|
|
32
|
-
| "what runs next, why not this one, what does this exclusion reason mean?" | `references/what-runs-next.md` |
|
|
33
|
-
| "why is nothing running", walls, quarantine, which provider ran a phase | `references/coding-accounts.md` |
|
|
34
|
-
|
|
35
|
-
## Rules
|
|
36
|
-
|
|
37
|
-
- **Print, never recall.** A stage name, label, threshold or route in your answer must have come from a script's output in this session.
|
|
38
|
-
- **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).
|
|
39
|
-
- **Unknown is not absent.** An `unknown` verdict from the explainer means the cloud could not look; report it as inconclusive.
|
|
40
|
-
- **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.
|
|
41
|
-
- **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.
|
|
42
|
-
- **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,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; sticky, only an operator clears it) 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,54 +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
|
-
## The state id is the authority; names are display
|
|
34
|
-
|
|
35
|
-
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:
|
|
36
|
-
|
|
37
|
-
- 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.
|
|
38
|
-
- `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.
|
|
39
|
-
- 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.
|
|
40
|
-
|
|
41
|
-
## How to read the printed map
|
|
42
|
-
|
|
43
|
-
`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.
|
|
44
|
-
|
|
45
|
-
Reading it for a question:
|
|
46
|
-
|
|
47
|
-
- "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.
|
|
48
|
-
- "Why is merge not running?" — is the card in the `pr` slot's stage?
|
|
49
|
-
- "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.
|
|
50
|
-
- "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.
|
|
51
|
-
|
|
52
|
-
## Readiness over the mapping
|
|
53
|
-
|
|
54
|
-
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.
|