@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.
Files changed (140) hide show
  1. package/README.md +11 -182
  2. package/bin/catalyst-skills.js +3 -49
  3. package/bin/catalyst.js +3 -0
  4. package/package.json +8 -31
  5. package/CHANGELOG.md +0 -65
  6. package/LICENSE +0 -21
  7. package/dist/args.js +0 -273
  8. package/dist/ask.js +0 -161
  9. package/dist/browser.js +0 -20
  10. package/dist/cli.js +0 -361
  11. package/dist/config.js +0 -152
  12. package/dist/contract-types.js +0 -4
  13. package/dist/contract.js +0 -184
  14. package/dist/detach.js +0 -10
  15. package/dist/environment.js +0 -207
  16. package/dist/errors.js +0 -27
  17. package/dist/events.js +0 -106
  18. package/dist/execution.js +0 -451
  19. package/dist/oauth.js +0 -300
  20. package/dist/pagination.js +0 -76
  21. package/dist/prompt.js +0 -35
  22. package/dist/published.js +0 -75
  23. package/dist/query.js +0 -248
  24. package/dist/ready.js +0 -254
  25. package/dist/release.js +0 -142
  26. package/dist/replica.js +0 -585
  27. package/dist/sdk.js +0 -26
  28. package/dist/semver.js +0 -18
  29. package/dist/skill-shape.js +0 -189
  30. package/dist/skills.js +0 -129
  31. package/dist/transport.js +0 -205
  32. package/dist/ts-deps-loader.js +0 -113
  33. package/dist/watch/consumer.js +0 -141
  34. package/dist/watch/cursor-file.js +0 -62
  35. package/dist/watch.js +0 -175
  36. package/dist/write.js +0 -224
  37. package/skills/catalyst-github/SKILL.md +0 -35
  38. package/skills/catalyst-github/agents/openai.yaml +0 -6
  39. package/skills/catalyst-github/agents/portability.yaml +0 -4
  40. package/skills/catalyst-github/references/is-it-mergeable.md +0 -57
  41. package/skills/catalyst-github/references/what-a-pr-accumulates.md +0 -61
  42. package/skills/catalyst-github/scripts/is-it-mergeable.mjs +0 -124
  43. package/skills/catalyst-github/scripts/lib/cli.mjs +0 -103
  44. package/skills/catalyst-github/scripts/lib/credential.mjs +0 -29
  45. package/skills/catalyst-github/scripts/lib/pull.mjs +0 -82
  46. package/skills/catalyst-github/scripts/read-pr.mjs +0 -97
  47. package/skills/catalyst-linear/SKILL.md +0 -43
  48. package/skills/catalyst-linear/agents/openai.yaml +0 -6
  49. package/skills/catalyst-linear/agents/portability.yaml +0 -5
  50. package/skills/catalyst-linear/references/reading-a-ticket.md +0 -52
  51. package/skills/catalyst-linear/references/what-a-ticket-accumulates.md +0 -53
  52. package/skills/catalyst-linear/references/writing-to-linear.md +0 -43
  53. package/skills/catalyst-linear/scripts/comment.mjs +0 -59
  54. package/skills/catalyst-linear/scripts/create-ticket.mjs +0 -44
  55. package/skills/catalyst-linear/scripts/label.mjs +0 -48
  56. package/skills/catalyst-linear/scripts/lib/cli.mjs +0 -164
  57. package/skills/catalyst-linear/scripts/lib/credential.mjs +0 -29
  58. package/skills/catalyst-linear/scripts/move.mjs +0 -41
  59. package/skills/catalyst-linear/scripts/read-ticket.mjs +0 -93
  60. package/skills/catalyst-linear/scripts/search.mjs +0 -49
  61. package/skills/catalyst-onboard/SKILL.md +0 -49
  62. package/skills/catalyst-onboard/agents/openai.yaml +0 -6
  63. package/skills/catalyst-onboard/agents/portability.yaml +0 -5
  64. package/skills/catalyst-onboard/references/the-one-path.md +0 -143
  65. package/skills/catalyst-onboard/references/what-the-browser-owns.md +0 -50
  66. package/skills/catalyst-onboard/references/who-fixes-what.md +0 -41
  67. package/skills/catalyst-onboard/scripts/lib/cli.mjs +0 -117
  68. package/skills/catalyst-onboard/scripts/lib/credential.mjs +0 -29
  69. package/skills/catalyst-onboard/scripts/where-am-i.mjs +0 -245
  70. package/skills/catalyst-setup/SKILL.md +0 -32
  71. package/skills/catalyst-setup/agents/openai.yaml +0 -6
  72. package/skills/catalyst-setup/agents/portability.yaml +0 -4
  73. package/skills/catalyst-setup/references/what-each-check-means.md +0 -78
  74. package/skills/catalyst-setup/scripts/check.mjs +0 -75
  75. package/skills/catalyst-setup/scripts/lib/cli.mjs +0 -103
  76. package/skills/catalyst-setup/scripts/lib/credential.mjs +0 -29
  77. package/skills/catalyst-setup/scripts/replica-status.mjs +0 -46
  78. package/skills/connect-me/SKILL.md +0 -63
  79. package/skills/connect-me/agents/openai.yaml +0 -6
  80. package/skills/connect-me/agents/portability.yaml +0 -5
  81. package/skills/connect-me/references/keeping-the-replica-running.md +0 -88
  82. package/skills/connect-me/scripts/lib/cli.mjs +0 -185
  83. package/skills/connect-me/scripts/lib/credential.mjs +0 -29
  84. package/skills/connect-me/scripts/verify-connection.mjs +0 -68
  85. package/skills/how-catalyst-works/SKILL.md +0 -42
  86. package/skills/how-catalyst-works/agents/openai.yaml +0 -6
  87. package/skills/how-catalyst-works/agents/portability.yaml +0 -4
  88. package/skills/how-catalyst-works/references/coding-accounts.md +0 -51
  89. package/skills/how-catalyst-works/references/stages-and-mapping.md +0 -54
  90. package/skills/how-catalyst-works/references/the-ladder.md +0 -41
  91. package/skills/how-catalyst-works/references/what-runs-next.md +0 -77
  92. package/skills/how-catalyst-works/references/when-a-phase-fails.md +0 -57
  93. package/skills/how-catalyst-works/scripts/explain-ticket.mjs +0 -41
  94. package/skills/how-catalyst-works/scripts/lib/cli.mjs +0 -164
  95. package/skills/how-catalyst-works/scripts/lib/credential.mjs +0 -29
  96. package/skills/how-catalyst-works/scripts/show-my-map.mjs +0 -94
  97. package/skills/how-catalyst-works/scripts/whats-running.mjs +0 -65
  98. package/skills/run-this-project/SKILL.md +0 -45
  99. package/skills/run-this-project/agents/openai.yaml +0 -6
  100. package/skills/run-this-project/agents/portability.yaml +0 -5
  101. package/skills/run-this-project/assets/stall-policy.json +0 -15
  102. package/skills/run-this-project/references/making-work-ready.md +0 -60
  103. package/skills/run-this-project/references/reacting-to-events.md +0 -76
  104. package/skills/run-this-project/references/stalls-and-escalation.md +0 -63
  105. package/skills/run-this-project/scripts/lib/cli.mjs +0 -185
  106. package/skills/run-this-project/scripts/lib/credential.mjs +0 -29
  107. package/skills/run-this-project/scripts/make-ready.mjs +0 -64
  108. package/skills/run-this-project/scripts/scope-status.mjs +0 -0
  109. package/skills/run-this-project/scripts/watch-scope.mjs +0 -61
  110. package/skills/unstick/SKILL.md +0 -41
  111. package/skills/unstick/agents/openai.yaml +0 -6
  112. package/skills/unstick/agents/portability.yaml +0 -5
  113. package/skills/unstick/references/playbook.md +0 -51
  114. package/skills/unstick/scripts/lib/cli.mjs +0 -135
  115. package/skills/unstick/scripts/lib/credential.mjs +0 -29
  116. package/skills/unstick/scripts/unstick.mjs +0 -57
  117. package/skills/what-needs-me/SKILL.md +0 -41
  118. package/skills/what-needs-me/agents/openai.yaml +0 -6
  119. package/skills/what-needs-me/agents/portability.yaml +0 -5
  120. package/skills/what-needs-me/references/raising-a-decision.md +0 -41
  121. package/skills/what-needs-me/references/reading-the-inbox.md +0 -38
  122. package/skills/what-needs-me/references/settling-an-answer.md +0 -37
  123. package/skills/what-needs-me/scripts/inbox.mjs +0 -56
  124. package/skills/what-needs-me/scripts/lib/cli.mjs +0 -135
  125. package/skills/what-needs-me/scripts/lib/credential.mjs +0 -29
  126. package/skills/what-needs-me/scripts/raise.mjs +0 -53
  127. package/skills/what-needs-me/scripts/settle.mjs +0 -73
  128. package/skills/whats-happening/SKILL.md +0 -43
  129. package/skills/whats-happening/agents/openai.yaml +0 -6
  130. package/skills/whats-happening/agents/portability.yaml +0 -4
  131. package/skills/whats-happening/assets/status-reply.json +0 -77
  132. package/skills/whats-happening/references/reading-the-board.md +0 -43
  133. package/skills/whats-happening/references/reprioritising.md +0 -37
  134. package/skills/whats-happening/references/routing-work.md +0 -36
  135. package/skills/whats-happening/references/status-reply.md +0 -34
  136. package/skills/whats-happening/references/why-is-it-stuck.md +0 -62
  137. package/skills/whats-happening/scripts/explain.mjs +0 -28
  138. package/skills/whats-happening/scripts/lib/cli.mjs +0 -135
  139. package/skills/whats-happening/scripts/lib/credential.mjs +0 -29
  140. package/skills/whats-happening/scripts/snapshot.mjs +0 -149
@@ -1,41 +0,0 @@
1
- # The ladder: eight phases, what each one produces
2
-
3
- This reference restates invariants. The phase order and what each phase leaves behind do not vary per tenant. The only live values are whether intake is switched on for your tenant and which keying the advance table uses; both are on the contract under `ladder` (print them with `node scripts/show-my-map.mjs`).
4
-
5
- ## The phases, in order
6
-
7
- | Phase | What it produces | Notes |
8
- | -- | -- | -- |
9
- | `intake` | nothing projected (a classification pass) | Optional head. Offered only to a ticket that has never entered the ladder, and only when the contract says `ladder.intakeEnabled` is true. |
10
- | `research` | `research.md` | The first ladder phase. Offered when the card sits in the team's dispatch slot. |
11
- | `plan` | `plan.md` | |
12
- | `implement` | `implement.md` | Creates the ticket branch (named exactly the ticket identifier) and opens a draft pull request. |
13
- | `validate` | `validation.md` | Its success moves the card to the team's review stage; verify and review are two slots that both land on that stage. |
14
- | `pr` | `pr.md` | The first line is the pull-request title, then a blank line, then the body. The phase rebases onto the default branch, force-pushes, writes the title and body, and marks the PR ready for review. |
15
- | `remediate` | `remediation.json` | An interrupt, not a step forward. It repairs a phase that failed and never advances the card past the PR slot. |
16
- | `merge` | a receipt, no artifact | Applies the queue-ready label once its evidence gate passes. It writes no board state. |
17
-
18
- Every artifact-bearing phase (all but `intake` and `merge`) projects its artifact into Linear as a document attached to the ticket, with a short link comment; `catalyst-linear` describes those shapes.
19
-
20
- ## What moves the card
21
-
22
- An advance table maps a phase outcome to a card move: research done moves the card from the dispatch slot to the research slot, plan done from research to plan, implement done from plan to implement, validate done from implement to review, pr done from review to pr. The table is served on the contract as `ladder.advance` so you can read the exact rows; do not restate them from memory, because the tenant's keying decides whether a stage names the phase that just finished (trailing, the default) or the phase still ahead (leading).
23
-
24
- Three rows never move the card:
25
-
26
- - A **failed** phase writes no board state. The ticket stays where it was, retryable, and the failure handling in `references/when-a-phase-fails.md` takes over.
27
- - **`remediate` done** writes nothing by itself; the card restores to its exact pre-failure stage once a remediation round succeeds.
28
- - **`merge` done** writes nothing. The receipt only ever meant "the queue-ready label was applied".
29
-
30
- ## Done is written by the merge, never by a phase
31
-
32
- The terminal move to the done slot is keyed to the real pull-request-merged event from GitHub, not to any phase receipt. When the PR merges, the mirror's own ingest path moves the ticket to Done within a few seconds, with no session and no human sweep, and a bounded periodic sweep backstops a dropped webhook. So:
33
-
34
- - A ticket still not Done a minute after its PR merged is a finding, not a chore to hand-close.
35
- - Done is not the same as live. A change to something that is deployed separately is inert until that deployment happens; the ticket state says the code merged, nothing more.
36
-
37
- ## How to read a ticket's position on the ladder
38
-
39
- 1. The card's stage tells you which phase last finished (under trailing keying) — read the team's map with `node scripts/show-my-map.mjs --team <key>` to translate a stage name into a slot.
40
- 2. The phase-outcome comments on the ticket tell you which attempts ran and how they ended; the document attachments are the artifacts themselves.
41
- 3. `node scripts/explain-ticket.mjs <ticket>` tells you what the cloud will run next, or why it will not.
@@ -1,77 +0,0 @@
1
- # What runs next: queue order, concurrency, routing, and every exclusion reason
2
-
3
- This reference restates invariants. The live queue is `node scripts/whats-running.mjs --queue [--team <key>]`; one ticket's verdict is `node scripts/explain-ticket.mjs <ticket>`. The reason phrases below match the CLI's own table, so a reason you see printed can be looked up here.
4
-
5
- ## Queue order
6
-
7
- Each team's dispatch queue is derived by the tenant's own store, continuously, from the board:
8
-
9
- 1. Queued tickets (cards in the dispatch slot) rank by **priority ascending, then created time ascending, then identifier ascending**.
10
- 2. Mid-ladder tickets (any ticket with a phase already completed) join the candidate set and order **by completed-phase count descending**, so a ticket close to the end goes before one that just started. That count is an ordering surrogate only; it never decides membership or eligibility.
11
-
12
- The queue re-derives within seconds of any board change and at the top of every periodic pass as a backstop. `dispatch_queue.source` reads `self-derived` when the tenant store built it itself, which is the normal case.
13
-
14
- ## Concurrency
15
-
16
- - Each repository has a concurrency cap (default 20 running phases). An operator can raise or lower it; a paused repository resolves to a cap of zero without touching the stored value, so resuming restores it.
17
- - Dispatch buckets by repository and splits slots across teams so no team starves another. A slot that is running, might be running, or is restarting is never free capacity.
18
- - Comment-wake work (an agent answering a human comment) shares the same cap as ladder work.
19
-
20
- ## The routing decision, in five checks
21
-
22
- When a phase is about to start, a route is chosen per candidate, in order, and the first survivor wins:
23
-
24
- 1. capability match (can this route run this phase);
25
- 2. a model is configured;
26
- 3. the provider is available;
27
- 4. if the candidate needs a coding-account slot: an eligible slot exists and the headroom floor is met;
28
- 5. degraded, equivalent or stage-default parameters resolve.
29
-
30
- No survivor is `no_eligible_account_slot` when any candidate was skipped on capacity, else `routing_unavailable` (a misconfiguration). See `references/coding-accounts.md` for what a slot is.
31
-
32
- ## Every exclusion reason, one line each
33
-
34
- | Reason | Meaning |
35
- | -- | -- |
36
- | `ticket_terminal` | the ticket is in a done or canceled state |
37
- | `pipeline_complete` | every phase has already completed |
38
- | `not_at_dispatch_stage` | the card is not in the team's dispatch column; move it there to dispatch |
39
- | `not_at_pr_stage` | merge is next but the card is not in the PR column (waived when a tenant automation bounced it off PR and the bounce was recorded) |
40
- | `blocked` | a live blocking relation; the blocker closes first |
41
- | `cooling_down` | the offered phase is parked; an operator or a callback releases it, not a clock |
42
- | `lease_held` | a live container already holds this phase |
43
- | `intake_lease_held` | a later phase is offered while an intake container still holds the ticket |
44
- | `ask_ticket` | it carries an ask label; a question is never work |
45
- | `ask_shape_suspected` | its own text reads as a decision request; a human releases it with the release label from the contract's `vocabulary` |
46
- | `externally_claimed` | a worker outside the cloud claimed it |
47
- | `environment_check_required` | the repository's environment check has not run |
48
- | `environment_check_running` | the environment check is in flight |
49
- | `environment_check_failed` | the environment check failed |
50
- | `environment_check_expired` | the environment verdict aged out |
51
- | `environment_check_hash_mismatch` | the environment changed since the verdict |
52
- | `scope_overlap` | its declared file scope intersects a ticket in flight (implement only; enforced once a team activates a scope policy) |
53
- | `waiting_on` | a merge-gate failure with no remediable cause holds the card at PR |
54
- | `branch_missing` | the ticket branch has never been seen |
55
- | `branch_gone` | the ticket branch was deleted |
56
- | `pr_merged` | its PR merged and no other PR is open |
57
- | `no_change_hold` | a remediate round changed nothing; a human comment or a new push releases it |
58
- | `validate_class_spent` | this validate failure already spent its one repair round |
59
- | `stale_failure_episode` | the ladder advanced after the failure, so the round would repair a phase already passed |
60
- | `runner_image_breaker` | fleet-wide: the live runner image fails every phase at startup; clears when the pin moves |
61
- | `no_branch_to_remediate` | a remediate round is queued on a ticket with no branch |
62
- | `retry_backoff` | retrying in place, waiting out its 2/5/15-minute rung |
63
- | `routing_unavailable` | claimed then refused at kickoff: no route, no eligible slot, or the provider is unavailable; the detail names which |
64
- | `repo_paused` | an operator paused the repository |
65
- | `remediate_parked` | the remediate phase is parked, so the failing phase has nowhere to be repaired |
66
-
67
- ## The unknowns (the evaluator fails closed)
68
-
69
- When the cloud cannot answer, it says so rather than guessing: `ordering_never_published`, `ordering_stale`, `workflow_mapping_unknown`, `ticket_unknown`, `dependency_snapshot_unknown`, `blocker_unknown`, `label_snapshot_unknown`, `prior_artifact_unknown`, `scope_unknown`, `scope_occupancy_unknown`, `branch_snapshot_unknown`. An unknown is "I could not look", never "it is not there"; report it as inconclusive. The exception is a team with no saved stage mapping: `workflow_mapping_unknown` never clears on its own there, `explain` names the missing stages, and the fix is a tenant owner or admin mapping the team.
70
-
71
- One advisory gates nothing: `human_addressed_unlabeled_ask_suspect` (assigned to a human with no delegate; possibly an unlabelled ask).
72
-
73
- ## The three levers a human has
74
-
75
- 1. **Priority** on the ticket reorders the queue.
76
- 2. **The dispatch column**: moving a card into the dispatch slot dispatches it; moving it to the team's backlog-type state parks it and stops rounds.
77
- 3. **Holds**: the hold labels on the pull request (from the contract's `merge.prLabels`) keep a green PR out of the merge queue; a human comment on the ticket clears the validate hold and the no-change hold.
@@ -1,57 +0,0 @@
1
- # When a phase fails: four layers, in order
2
-
3
- This reference restates the mechanism, which does not vary per tenant. The numbers quoted are the fleet defaults; the live values are served on the contract under `thresholds` and `node scripts/show-my-map.mjs` prints them. Quote the printed values, not these.
4
-
5
- A failed phase writes no board state on its own: the ticket stays at its current stage, retryable. What happens next is decided by four stacked layers.
6
-
7
- ## Layer 1: retry in place
8
-
9
- Some failures are not the ticket's fault, so the SAME phase is simply re-offered and no remediation round is minted:
10
-
11
- - the phase failed before any branch existed (research, plan or implement on a ticket whose own record shows no branch);
12
- - an infrastructure failure at any phase: a vendor 5xx, a dropped stream, a refused setup, an environment gap, a phase timeout, an unwritable workspace.
13
-
14
- Nothing is written for the retry; the ordinary dispatch path re-offers the phase. A remediation round on a branchless ticket could only fail to find the branch while spending a counted attempt, which is why this layer exists.
15
-
16
- ## Layer 2: backoff
17
-
18
- Every retry-in-place, and a routing refusal at kickoff, waits out a rung of a backoff ladder before the phase is re-offered: 2 minutes, then 5, then 15, indexed by the consecutive-failure count and clamped at the last rung. While waiting, the eligibility explainer shows `retry_backoff` (or `routing_unavailable` with a kickoff detail). `node scripts/explain-ticket.mjs <ticket>` prints it. The live ladder is `thresholds.retryBackoffMs`.
19
-
20
- ## Layer 3: move to Remediate
21
-
22
- Any failure that is not retry-in-place moves the card to the team's remediate stage and queues a remediation round. The round is an interrupt phase (`remediate`) that repairs what failed; when a round succeeds, the card restores to its exact pre-failure stage. A team with no remediate stage mapped gets the hold label from `teams[].labels.hold` instead of a state move.
23
-
24
- Rounds are capped: after `thresholds.remediateRoundCap` rounds (default 3) in one failure episode, no further round is queued. A remediation round that itself fails never queues another round. A round that changes nothing puts the ticket on a no-change hold, released by a human comment on the ticket or a new push, never by a clock. A validate failure with the same fingerprint spends only one repair round per episode.
25
-
26
- Two structural cases the round machinery refuses: a `remediate` round is never offered on a ticket with no recorded branch (`no_branch_to_remediate`), and one dispatched anyway is cancelled at the clone.
27
-
28
- ## Layer 4: park
29
-
30
- After `thresholds.parkAfterConsecutiveFailures` consecutive failures (default 3) the phase is parked. A parked phase shows as `cooling_down` in the explainer; the ticket's `remediate` phase being parked shows as `remediate_parked`.
31
-
32
- Which parks release themselves:
33
-
34
- | Park | Releases by |
35
- | -- | -- |
36
- | repeated failure | a release from the person's own login once its cause is fixed (`catalyst-skills release`), not a clock |
37
- | remediate round cap reached | the same release, which buys one more round, not a clock |
38
- | missing branch | its own budgeted release loop |
39
- | rebase conflict | its own budgeted release loop |
40
-
41
- A person's own key releases a park, and every other hold that a retry can fix, in one step: `catalyst-skills release <ticket> --because <what changed>`. The cloud releases every governor holding the ticket or releases nothing and names what a person must do instead (a person's own pull request, a review that will not converge, the lifetime repair budget). It refuses a cause it cannot see change unless the caller says what changed. The `unstick` skill runs that loop. When a ticket is parked, name the failure class the explainer shows and whether its cause is fixed.
42
-
43
- ## Two holds that are not failures
44
-
45
- - **Validate budget hold**: a validate failure that has spent its repair round holds the card; a human comment on the ticket clears it.
46
- - **Runner image breaker**: fleet-wide, not per ticket. When three distinct tickets fail the same startup class on the live runner image, dispatch pauses for the affected tickets, one anomaly alert is raised per tenant, and dispatch resumes automatically when the image pin moves. The explainer shows `runner_image_breaker`. Never escalate this one ticket at a time.
47
-
48
- ## What a human sees on the ticket
49
-
50
- Each attempt posts a phase-outcome comment (complete or FAILED, with phase, attempt, artifact, a summary and any park or hold block) and each remediation round posts a remediate-attempt comment naming the failure class it is repairing. `catalyst-linear` describes the shapes. The attempt ledger, the round count against the cap and the park history are readable: `catalyst-skills explain --history <ticket>` prints them from the cloud's own relay ledger.
51
-
52
- ## Rule of thumb for answering "why is it stuck?"
53
-
54
- 1. Run `node scripts/explain-ticket.mjs <ticket>`; the reason names the layer.
55
- 2. `retry_backoff` or `routing_unavailable`: wait; it retries itself. Say when.
56
- 3. `cooling_down`, `remediate_parked`, `no_change_hold`, `validate_class_spent`: name what releases it (a release once the cause is fixed, a comment, a push) and who can do that.
57
- 4. A system-level cause (provider down, runner image breaker, repo paused) is ONE alert, never a per-ticket escalation.
@@ -1,41 +0,0 @@
1
- #!/usr/bin/env node
2
- // explain-ticket.mjs — why one ticket is, or is not, about to run: the cloud's own eligibility row
3
- // for it, rendered as one paragraph, plus the raw row so the failure block is not lost.
4
- import { exitOnFailure, parseFlags, parseJson, relayStderr, runCli, usage, wantsHelp } from "./lib/cli.mjs";
5
-
6
- const HELP = `Usage: node scripts/explain-ticket.mjs <ticket> [--json]
7
-
8
- Asks Catalyst Cloud for the ticket's eligibility row (position, status, the exclusion reason in plain
9
- English, the last failure, advisories) and prints it as one paragraph. Wraps: catalyst-skills explain.
10
-
11
- <ticket> the Linear identifier, e.g. KEY-123
12
- --json print the CLI's JSON document instead of the paragraph
13
-
14
- Exit 0 explained (even when the ticket is excluded), 1 when the ticket is unknown to the mirror or
15
- a usage error, 2 when this machine is not connected to a tenant or the cloud refused the read
16
- (the line says which).`;
17
-
18
- const argv = process.argv.slice(2);
19
- if (wantsHelp(argv)) {
20
- console.log(HELP);
21
- process.exit(0);
22
- }
23
- const { flags, positionals } = parseFlags(argv, { bool: ["json"] });
24
- const ticket = positionals[0];
25
- if (!ticket) usage("explain-ticket needs a ticket identifier (see --help)");
26
-
27
- const r = runCli(["explain", ticket, "--json"]);
28
- exitOnFailure(r);
29
- relayStderr(r);
30
- const doc = parseJson(r.stdout);
31
- if (!doc || typeof doc.explanation !== "string") {
32
- console.log(r.stdout.trimEnd());
33
- process.exit(1);
34
- }
35
- if (flags.json) {
36
- console.log(JSON.stringify(doc));
37
- } else {
38
- console.log(doc.explanation);
39
- if (doc.row) console.log(`row: ${JSON.stringify(doc.row)}`);
40
- }
41
- process.exit(doc.row ? 0 : 1);
@@ -1,164 +0,0 @@
1
- #!/usr/bin/env node
2
- // lib/cli.mjs — the one way a skill script reaches Catalyst Cloud: it spawns the catalyst-skills CLI
3
- // this machine connected with (the path recorded in customer.json, else npx) and hands back its
4
- // output. Scripts import it; a person runs it with --help to see what it does. No dependencies.
5
- import { spawnSync } from "node:child_process";
6
- import { existsSync, readFileSync } from "node:fs";
7
- import { join } from "node:path";
8
- import { pathToFileURL } from "node:url";
9
- import { CONNECT_COMMAND, hasCredential } from "./credential.mjs";
10
-
11
- export const PACKAGE = "@catalyst-cloud/catalyst-skills";
12
- export const CONNECT_HINT = CONNECT_COMMAND;
13
-
14
- /** ~/.config/catalyst-cloud/customer.json, honouring CATALYST_SKILLS_HOME (used by tests) over HOME. */
15
- export function configPath() {
16
- const home = process.env.CATALYST_SKILLS_HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? "";
17
- return join(home, ".config", "catalyst-cloud", "customer.json");
18
- }
19
-
20
- /** The config, or a one-line reason it could not be read. Never throws. */
21
- export function loadConfig() {
22
- const path = configPath();
23
- if (!existsSync(path)) return { ok: false, reason: `no config at ${path}` };
24
- try {
25
- const cfg = JSON.parse(readFileSync(path, "utf8"));
26
- if (!hasCredential(cfg) || typeof cfg.account !== "string") {
27
- return { ok: false, reason: `${path} is missing a key or login, or the account` };
28
- }
29
- return { ok: true, cfg, path };
30
- } catch (err) {
31
- return { ok: false, reason: `${path} is unreadable: ${err instanceof Error ? err.message : String(err)}` };
32
- }
33
- }
34
-
35
- /** Exit 2 with the one line every script prints when this machine is not connected to a tenant. */
36
- export function notConfigured(reason) {
37
- console.error(`not connected to a Catalyst Cloud tenant (${reason}) — run: ${CONNECT_HINT}`);
38
- process.exit(2);
39
- }
40
-
41
- /**
42
- * Run one catalyst-skills verb. Returns { code, stdout, stderr }. The CLI is `node <cliPath>` when
43
- * the config recorded one that still exists, else `npx @catalyst-cloud/catalyst-skills`.
44
- * Exits 2 (not configured) before spawning anything when the config is absent or unreadable.
45
- */
46
- export function runCli(args, { stdin } = {}) {
47
- const loaded = loadConfig();
48
- if (!loaded.ok) notConfigured(loaded.reason);
49
- const { cfg } = loaded;
50
- let cmd;
51
- let argv;
52
- let shell = false;
53
- if (typeof cfg.cliPath === "string" && existsSync(cfg.cliPath)) {
54
- cmd = process.execPath;
55
- argv = [cfg.cliPath, ...args];
56
- } else {
57
- cmd = process.platform === "win32" ? "npx.cmd" : "npx";
58
- argv = [PACKAGE, ...args];
59
- shell = process.platform === "win32";
60
- }
61
- const r = spawnSync(cmd, argv, {
62
- encoding: "utf8",
63
- input: stdin,
64
- env: process.env,
65
- shell,
66
- maxBuffer: 64 * 1024 * 1024,
67
- });
68
- if (r.error) {
69
- console.error(`could not run ${cmd}: ${r.error.message} — re-run ${CONNECT_HINT} to record the CLI path`);
70
- process.exit(2);
71
- }
72
- return { code: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
73
- }
74
-
75
- /** Print the CLI's stderr (the source line, the contract line, any refusal) on our stderr. */
76
- export function relayStderr(r) {
77
- const text = r.stderr.trimEnd();
78
- if (text) console.error(text);
79
- }
80
-
81
- /** A non-zero CLI result ends the script with the same code, its stdout shown so nothing is lost. */
82
- export function exitOnFailure(r) {
83
- if (r.code === 0) return;
84
- relayStderr(r);
85
- const out = r.stdout.trimEnd();
86
- if (out) console.log(out);
87
- process.exit(r.code);
88
- }
89
-
90
- export function parseJson(text) {
91
- try {
92
- return JSON.parse(text.trim());
93
- } catch {
94
- return null;
95
- }
96
- }
97
-
98
- /**
99
- * A small flag parser: `spec.value` names flags that take a value, `spec.bool` boolean flags,
100
- * `spec.repeat` value flags that may repeat (collected into arrays). Unknown flags are a usage error.
101
- */
102
- export function parseFlags(argv, spec = {}) {
103
- const value = new Set(spec.value ?? []);
104
- const bool = new Set(spec.bool ?? []);
105
- const repeat = new Set(spec.repeat ?? []);
106
- const flags = {};
107
- const positionals = [];
108
- for (let i = 0; i < argv.length; i++) {
109
- const a = argv[i];
110
- if (a === "--") {
111
- positionals.push(...argv.slice(i + 1));
112
- break;
113
- }
114
- if (!a.startsWith("--")) {
115
- positionals.push(a);
116
- continue;
117
- }
118
- let name = a.slice(2);
119
- let inline;
120
- const eq = name.indexOf("=");
121
- if (eq !== -1) {
122
- inline = name.slice(eq + 1);
123
- name = name.slice(0, eq);
124
- }
125
- if (bool.has(name)) {
126
- flags[name] = true;
127
- } else if (value.has(name) || repeat.has(name)) {
128
- const v = inline ?? argv[++i];
129
- if (v === undefined || v === "") usage(`--${name} needs a value`);
130
- if (repeat.has(name)) (flags[name] ??= []).push(v);
131
- else flags[name] = v;
132
- } else {
133
- usage(`unknown option --${name}`);
134
- }
135
- }
136
- return { flags, positionals };
137
- }
138
-
139
- export function usage(message) {
140
- console.error(message);
141
- process.exit(1);
142
- }
143
-
144
- export function wantsHelp(argv) {
145
- return argv.includes("--help") || argv.includes("-h");
146
- }
147
-
148
- const runDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
149
- if (runDirectly) {
150
- if (wantsHelp(process.argv.slice(2)) || process.argv.length <= 2) {
151
- console.log(
152
- [
153
- "lib/cli.mjs — shared helper for this skill's scripts (not a command of its own)",
154
- "",
155
- "Reads ~/.config/catalyst-cloud/customer.json and runs the catalyst-skills CLI recorded there",
156
- `(or npx ${PACKAGE} when no path is recorded). Exit 2 when the machine is not connected.`,
157
- "",
158
- `Connect first with: ${CONNECT_HINT}`,
159
- `Config in use: ${configPath()}`,
160
- ].join("\n"),
161
- );
162
- process.exit(0);
163
- }
164
- }
@@ -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,94 +0,0 @@
1
- #!/usr/bin/env node
2
- // show-my-map.mjs — this tenant's stage map, ladder and thresholds, straight from the contract:
3
- // per team, every slot with the Linear stage it maps to, its type, whether that state still exists
4
- // and how the mapping was chosen; then the ladder and the live failure thresholds.
5
- import { exitOnFailure, parseFlags, parseJson, relayStderr, runCli, wantsHelp } from "./lib/cli.mjs";
6
-
7
- const HELP = `Usage: node scripts/show-my-map.mjs [--team <key>] [--json]
8
-
9
- Prints the tenant contract's stage map, ladder and thresholds. Nothing here is guessed: every value is
10
- read from the contract Catalyst Cloud serves for your tenant. Wraps: catalyst-skills contract --path.
11
-
12
- --team <key> only this team
13
- --json one JSON document: { slots, teams, ladder, thresholds }
14
- --help this text
15
-
16
- A slot marked * is load-bearing: dispatch, intake, pr, done and canceled must be mapped for the
17
- ladder to move at all; the others are informational. "(unmapped)" means the team has no stage for
18
- that slot; "(state gone)" means the mapped Linear state no longer exists and needs re-mapping in
19
- settings.
20
-
21
- Exit 0, 1 on a usage error or an unknown team, 2 when this machine is not connected to a tenant or
22
- the contract could not be read (the line says which).`;
23
-
24
- const LOAD_BEARING = new Set(["dispatch", "intake", "pr", "done", "canceled"]);
25
-
26
- const argv = process.argv.slice(2);
27
- if (wantsHelp(argv)) {
28
- console.log(HELP);
29
- process.exit(0);
30
- }
31
- const { flags } = parseFlags(argv, { bool: ["json"], value: ["team"] });
32
-
33
- function contractPath(path) {
34
- const r = runCli(["contract", "--path", path, "--json"]);
35
- exitOnFailure(r);
36
- const v = parseJson(r.stdout);
37
- if (v === null || v === undefined) {
38
- relayStderr(r);
39
- console.error(`contract --path ${path} returned nothing readable`);
40
- process.exit(1);
41
- }
42
- return { value: v, stderr: r.stderr };
43
- }
44
-
45
- const slotsRead = contractPath("slots");
46
- if (slotsRead.stderr.trim()) console.error(slotsRead.stderr.trimEnd());
47
- const slots = slotsRead.value;
48
- let teams = contractPath("teams").value;
49
- const ladder = contractPath("ladder").value;
50
- const thresholds = contractPath("thresholds").value;
51
-
52
- if (flags.team) {
53
- const want = flags.team.toUpperCase();
54
- teams = teams.filter((t) => String(t.key ?? "").toUpperCase() === want);
55
- if (teams.length === 0) {
56
- console.error(`no team with key ${flags.team} on this tenant's contract`);
57
- process.exit(1);
58
- }
59
- }
60
-
61
- if (flags.json) {
62
- console.log(JSON.stringify({ slots, teams, ladder, thresholds }));
63
- process.exit(0);
64
- }
65
-
66
- const pad = (s, n) => String(s).padEnd(n);
67
- for (const team of teams) {
68
- console.log(`== team ${team.key ?? "(no key)"} — ${team.name ?? team.id} — workflow ${team.workflowMode}, git automation ${team.gitAutomation}, readiness ${team.readiness?.status ?? "unknown"}`);
69
- console.log(` ${pad("slot", 12)}${pad("stage", 22)}${pad("type", 12)}${pad("exists", 8)}source`);
70
- for (const slot of slots) {
71
- const s = team.stages?.[slot];
72
- const mark = LOAD_BEARING.has(slot) ? "*" : " ";
73
- if (!s) {
74
- console.log(` ${mark}${pad(slot, 12)}(unmapped)`);
75
- continue;
76
- }
77
- const name = s.stateStillExists ? (s.name ?? "(unnamed)") : "(state gone)";
78
- console.log(` ${mark}${pad(slot, 12)}${pad(name, 22)}${pad(s.type ?? "-", 12)}${pad(s.stateStillExists ? "yes" : "NO", 8)}${s.source}`);
79
- }
80
- const labels = team.labels ?? {};
81
- const describe = (list) => (list ?? []).map((l) => `${l.name}${l.preferredId ? "" : " (absent)"}`).join(", ") || "none";
82
- console.log(` labels: ask ${describe(labels.ask)}; hold ${describe(labels.hold)}; release ${describe(labels.release)}`);
83
- }
84
-
85
- console.log("== ladder");
86
- console.log(` phases: ${(ladder.phases ?? []).join(" → ")}`);
87
- console.log(` keying: ${ladder.keying} (${ladder.keyingScope}); intake ${ladder.intakeEnabled ? "enabled" : "off"}`);
88
-
89
- console.log("== thresholds (live, fleet-wide)");
90
- const minutes = (ms) => `${Math.round(ms / 60000)} min`;
91
- console.log(` park after ${thresholds.parkAfterConsecutiveFailures} consecutive failures`);
92
- console.log(` remediate round cap ${thresholds.remediateRoundCap} (escalated budget ${thresholds.remediateEscalatedRoundBudget}, rewind budget ${thresholds.remediateRewindBudget})`);
93
- console.log(` retry backoff ${(thresholds.retryBackoffMs ?? []).map(minutes).join(", ")}`);
94
- console.log(` write budget ${thresholds.hostDailyWriteBudget} per host per day`);
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env node
2
- // whats-running.mjs — what Catalyst is executing right now (fleet activity, the agent roster, lease
3
- // attributions), optionally the dispatch queue and the coding-account line.
4
- import { exitOnFailure, parseFlags, parseJson, relayStderr, runCli, wantsHelp } from "./lib/cli.mjs";
5
-
6
- const HELP = `Usage: node scripts/whats-running.mjs [--queue] [--accounts] [--team <key>] [--json]
7
-
8
- Prints what is executing on your tenant. Wraps: catalyst-skills running, queue, accounts.
9
-
10
- --queue also print the dispatch queue (what runs next, in order)
11
- --team <key> with --queue: one team's queue
12
- --accounts also print the coding-account line (provider, state, windows — never a credential)
13
- --json one JSON document: { running, queue?, accounts? }
14
- --help this text
15
-
16
- Exit 0, 1 on a usage error, 2 when this machine is not connected to a tenant or the cloud refused
17
- a read (the line says which).`;
18
-
19
- const argv = process.argv.slice(2);
20
- if (wantsHelp(argv)) {
21
- console.log(HELP);
22
- process.exit(0);
23
- }
24
- const { flags } = parseFlags(argv, { bool: ["queue", "accounts", "json"], value: ["team"] });
25
-
26
- const out = {};
27
- const running = runCli(["running", "--json"]);
28
- exitOnFailure(running);
29
- relayStderr(running);
30
- out.running = parseJson(running.stdout) ?? running.stdout.trimEnd();
31
-
32
- if (flags.queue) {
33
- const args = ["queue", "--json"];
34
- if (flags.team) args.push("--team", flags.team);
35
- const queue = runCli(args);
36
- exitOnFailure(queue);
37
- relayStderr(queue);
38
- out.queue = parseJson(queue.stdout) ?? queue.stdout.trimEnd();
39
- }
40
-
41
- if (flags.accounts) {
42
- const accounts = runCli(["accounts"]);
43
- exitOnFailure(accounts);
44
- relayStderr(accounts);
45
- out.accounts = accounts.stdout.trimEnd();
46
- }
47
-
48
- if (flags.json) {
49
- console.log(JSON.stringify(out));
50
- process.exit(0);
51
- }
52
-
53
- const section = (title, value) => {
54
- console.log(`== ${title}`);
55
- console.log(typeof value === "string" ? value : JSON.stringify(value, null, 2));
56
- };
57
- if (out.running && typeof out.running === "object") {
58
- section("fleet activity", out.running.fleetActivity ?? out.running);
59
- if (out.running.agentRoster !== undefined) section("agent roster", out.running.agentRoster);
60
- if (out.running.leaseAttributions !== undefined) section("lease attributions", out.running.leaseAttributions);
61
- } else {
62
- section("running", out.running);
63
- }
64
- if (out.queue !== undefined) section("dispatch queue", out.queue);
65
- if (out.accounts !== undefined) section("coding accounts", out.accounts);
@@ -1,45 +0,0 @@
1
- ---
2
- name: run-this-project
3
- description: >-
4
- Own one Catalyst Cloud project end to end until it closes. Use when the person says "run this project for me", "own this until it ships", "keep this moving", or hands you a project id or a set of tickets to drive. Subscribes to the tenant stream for the scope through the catalyst-skills CLI, reacts to each change in the same turn, makes tickets ready and moves them to dispatch, parks what should stop, chases stalls, escalates inward, and keeps one status summary current. Writes to Linear as the app actor; never polls.
5
- disable-model-invocation: true
6
- allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
7
- ---
8
- <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.7.0 — written in this repository for customer tenants -->
9
-
10
- # Run this project
11
-
12
- You are the steward: the single-threaded owner of ONE project or ticket set, from now until it closes. The person says "run this project for me" and hands you a scope. Catalyst does the phases; you make work ready and visible, react to what happens, unblock, and keep one status summary the person can read. You hold no authority over other stewards and you are not the desk; `whats-happening` is the desk.
13
-
14
- ## Run first
15
-
16
- Scripts are run, never read. Each prints its own `--help`.
17
-
18
- 1. `node scripts/scope-status.mjs --project <id>` (or `--team <key>`): tickets by stage in the contract's order, what is running and queued in scope, and stalls against the local policy. Run it once to take the scope, and again after any resync.
19
- 2. `node scripts/watch-scope.mjs --project <id>`: the subscription. In Claude Code, arm a monitor on it and react to each printed line in the same turn. In a harness with no monitor, add `--exec <command>` so a reaction still runs per frame.
20
- 3. `node scripts/make-ready.mjs <ticket>` to dispatch; `--park` to stop; `--note <why>` to record it.
21
- 4. `catalyst-skills explain <ticket>` whenever a ticket is not moving: one paragraph naming the reason and what releases it.
22
-
23
- Every script exits 2 when this machine is not connected (run `catalyst-skills login`), 1 when its own check fails.
24
-
25
- ## Load on demand
26
-
27
- | when | read |
28
- | -- | -- |
29
- | arming the watch, deciding what a frame means, a reaction failed, a resync line printed | `references/reacting-to-events.md` |
30
- | dispatching or parking a ticket, judging whether a phase actually ran, writing a ticket for Catalyst | `references/making-work-ready.md` |
31
- | something is not moving, tuning the stall policy, deciding whether a human needs to hear about it | `references/stalls-and-escalation.md` |
32
- | the exclusion vocabulary in depth, the ladder, this team's stage map | the `how-catalyst-works` skill |
33
- | raising or settling a decision | the `what-needs-me` skill |
34
- | a PR's checks, review and merge legs | the `catalyst-github` skill |
35
-
36
- ## Rules
37
-
38
- - **React, never poll.** A loop that re-reads the API or the replica is a defect. The stream and the cursor file are the mechanism; a reaction that throws leaves the cursor so the frame is offered again.
39
- - **Dispatch is a card move.** The cloud takes work from the dispatch column and writes every later stage itself. Your two moves are into dispatch and into the backlog; never hand-move a card into a ladder stage.
40
- - **Evidence a phase ran is the outcome comment, the attached document and the agent session,** not the clock and not the card's column.
41
- - **Tenant facts come from the contract, live.** Never restate a stage name, label id, team id, threshold or template in prose; run `catalyst-skills contract --path <a.b.c>` when you need one.
42
- - **Escalate inward.** Instrument, then you, then the desk, then the human as an ask filed through `what-needs-me` with what it blocks. Decide the technical calls yourself; take the sane default and record it; a fleet or provider condition is one note, never one ask per ticket.
43
- - **Reply where the message arrived,** in-thread, tagged, as the app actor. Never post as the human, never answer someone else's ask. Bookkeeping records take the contract's marker (`catalyst-skills write comment --bookkeeping`).
44
- - **One status summary,** kept current after every reaction: in flight, blocked and on whom, closed, next, each line with a ticket id. Where a human decision is pending, the line carries the ask's id.
45
- - **Say what a key cannot see.** PR labels and reactions are not mirrored; the CLI says so by name and points at settings. A park or hold is released from the person's own login once its cause is fixed, through the `unstick` skill (`catalyst-skills release`). Execution history (`explain --history`) and coding-account status (`accounts`) ARE readable — read them rather than reconstructing them from comments.
@@ -1,6 +0,0 @@
1
- interface:
2
- display_name: "Run this project"
3
- short_description: "Own one Catalyst project end to end: react to its events, make tickets ready, chase stalls, escalate inward"
4
- default_prompt: "Use $run-this-project to run project <id> for me until it closes."
5
- policy:
6
- allow_implicit_invocation: false
@@ -1,5 +0,0 @@
1
- identity: { pack: catalyst-cloud-skills, skill: run-this-project }
2
- effects: [external-write]
3
- mutating: true
4
- invocation: explicit
5
- exposure: [catalog]