@catalyst-cloud/cli 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +2 -2
  3. package/dist/args.js +291 -69
  4. package/dist/capabilities.js +2 -0
  5. package/dist/cli.js +109 -17
  6. package/dist/config.js +3 -1
  7. package/dist/env/draft.js +134 -0
  8. package/dist/env/legacy.js +54 -0
  9. package/dist/env/parse-env.js +50 -0
  10. package/dist/env/settings-toml.js +66 -0
  11. package/dist/env.js +126 -26
  12. package/dist/onboard-consent.js +65 -0
  13. package/dist/onboard.js +836 -0
  14. package/dist/project.js +75 -13
  15. package/dist/ready.js +11 -1
  16. package/dist/secret.js +24 -3
  17. package/dist/var.js +118 -0
  18. package/package.json +2 -1
  19. package/skills/catalyst-github/SKILL.md +1 -1
  20. package/skills/catalyst-linear/SKILL.md +1 -1
  21. package/skills/catalyst-onboard/SKILL.md +1 -1
  22. package/skills/catalyst-onboard/references/declaring-a-repository.md +19 -12
  23. package/skills/catalyst-onboard/references/reading-ready.md +1 -1
  24. package/skills/catalyst-onboard/references/the-one-path.md +3 -3
  25. package/skills/catalyst-onboard/references/what-the-browser-owns.md +2 -2
  26. package/skills/catalyst-onboard/scripts/lib/project-coverage.mjs +53 -0
  27. package/skills/catalyst-onboard/scripts/where-am-i.mjs +884 -195
  28. package/skills/run-this-project/SKILL.md +1 -1
  29. package/skills/unstick/SKILL.md +1 -1
  30. package/skills/what-needs-me/SKILL.md +1 -1
  31. package/skills/what-this-repo-needs/SKILL.md +4 -4
  32. package/skills/what-this-repo-needs/scripts/check.mjs +12 -13
  33. package/skills/whats-happening/SKILL.md +19 -35
  34. package/skills/whats-happening/references/coding-accounts.md +6 -19
  35. package/skills/whats-happening/references/routing-work.md +4 -15
  36. package/skills/whats-happening/references/settings-and-where-they-live.md +15 -18
  37. package/skills/whats-happening/references/stages-and-mapping.md +12 -18
  38. package/skills/whats-happening/references/status-reply.md +11 -29
  39. package/skills/whats-happening/references/the-ladder.md +15 -25
  40. package/skills/whats-happening/references/what-runs-next.md +7 -26
  41. package/skills/whats-happening/references/when-a-phase-fails.md +16 -22
  42. package/skills/whats-happening/references/why-is-it-stuck.md +38 -48
  43. package/dist/env/declaration-rules.js +0 -163
@@ -1,56 +1,46 @@
1
- # Why is it stuck: every reason, and who acts
2
-
3
- The pack's one reason table; other skills point here. Match the reason `node scripts/explain.mjs <ticket>` prints to a row. "No one acts" is an honest answer. A reason missing here is printed as the cloud spelled it: report it verbatim and say the bundle does not know it. The mechanism is in `references/when-a-phase-fails.md`.
4
-
5
- ## Reasons that release themselves
6
-
7
- `retry_backoff` (a retry rung, `thresholds.retryBackoffMs`), `lease_held`, `intake_lease_held` and `later_phase_lease_held` (a live container holds this or a later phase), `environment_check_running`, `claim_storm` (claimed too often in the last hour), `repo_at_capacity` (every runner seat busy), `stale_failure_episode` (the round would repair a phase already passed, so it is dropped), and `no_branch_to_remediate` / `branch_missing` (the cloud's budgeted release): each clears on its own, and no one acts.
8
-
9
- | reason | what it means, and what clears it | who acts |
10
- | -- | -- | -- |
11
- | `runner_image_breaker` | fleet-wide: the live runner image fails every phase at startup; the image pin moves; one alert per account | an operator |
12
- | `routing_unavailable` | refused at kickoff: no route, no eligible slot, or the provider down (the detail says which); clears on recovery | usually no one acts; if it persists, a workspace owner or admin, in settings, checks coding accounts (`references/coding-accounts.md`) |
13
- | `cooling_down` (with a named callback) | parked on a condition the cloud watches; the callback in `detail` fires | no one acts; if the callback stalls, the person, with their own login, releases it through `unstick` |
14
- | `wip_limit` | the project is at its work-in-progress limit, so no first phase; a ticket in progress finishing clears it | no one acts on this ticket; the in-progress tickets it waits on each have their own row |
15
-
16
- ## Reasons that need a human
17
-
18
- | reason | what it means | who acts and how |
19
- | -- | -- | -- |
20
- | `ask_ticket` | it carries an ask label; a question is never work | the person, with their own login, answers the ask (`what-needs-me`) |
21
- | `ask_shape_suspected` | its text reads as a decision request with no ask label | the person, with their own login, answers it as an ask or applies the contract's release label |
22
- | `blocked` | a live blocking relation holds it | the blocker's owner closes it; for an ask, the person, with their own login |
23
- | `not_at_dispatch_stage` | the card is not in the dispatch column | the person, with their own login, moves it to the dispatch slot |
24
- | `not_at_pr_stage` | merge is next but the card is off the PR column | the person, with their own login, returns it to the pr slot and finds the automation that bounced it |
25
- | `no_change_hold` | a repair round changed nothing | the person, with their own login, comments or pushes; `unstick` can also release it |
26
- | `waiting_on` | a merge-gate failure the cloud cannot repair holds it at PR | the person, with their own login, reads the merge-wait comment: usually a hold label or an owed review (`catalyst-github`) |
27
- | `externally_claimed` | a worker outside the cloud holds it | that worker, or the person, with their own login, removes the local-lane label |
28
- | `environment_check_required` / `environment_check_failed` / `environment_check_expired` / `environment_check_hash_mismatch` | the repository's environment check has not run, failed, aged out, or predates a change | a workspace owner or admin, in settings, runs, fixes or reruns it |
29
- | `repo_paused` | an operator paused the repository | an operator resumes it |
30
- | `scope_overlap` | its declared file scope meets a ticket in flight | the person, with their own login, picks which goes first, or waits |
31
- | `human_owned_pr` | a person's own pull request holds the ticket | the person, with their own login, closes, merges or hands over that pull request, which only they may close. The release command refuses it |
32
- | `review_not_converging` | validate and repair cycles are not converging (review convergence hold) | the person, with their own login, reads the findings (`catalyst-github`) and comments. The release command refuses it; the hold table in `references/when-a-phase-fails.md` says what clears it |
33
- | `round_threshold` | the lifetime repair budget is spent (round-threshold hold) | the person, with their own login, answers the ask the cloud raised or pushes a fix; point at that ask rather than raising another. The release command refuses it; see the hold table |
1
+ # Eligibility reasons
2
+
3
+ Use explain.mjs; unknown reasons stay verbatim. Holds: when-a-phase-fails.md.
4
+
5
+ ## Automatic release
6
+
7
+ Automatic waits, where no one acts: `retry_backoff`, `lease_held`, `intake_lease_held`, `later_phase_lease_held`, `environment_check_running`, `claim_storm`, `repo_at_capacity`, `stale_failure_episode`, `no_branch_to_remediate` or `branch_missing`. These cover backoff, leases, environment checks, hourly claim throttling, occupied seats, obsolete repair and budgeted missing-branch release.
8
+
9
+ | reason | release and actor |
10
+ | -- | -- |
11
+ | `runner_image_breaker` | an operator moves faulty startup image pin; one fleet alert |
12
+ | `routing_unavailable` | kickoff route/slot/provider failure; recovery clears it; persistence needs a workspace owner or admin, in settings |
13
+ | `cooling_down` with callback | cloud watches detail's condition; no one acts unless stalled, then own-login release through unstick |
14
+ | `wip_limit` | no one acts on this ticket; project at limit; started work must finish; inspect those tickets' own reasons |
15
+
16
+ ## Human action
17
+
18
+ | reason | meaning and action |
19
+ | -- | -- |
20
+ | `ask_ticket` | the person, with their own login: labelled question, never work; answer through what-needs-me |
21
+ | `ask_shape_suspected` | the person, with their own login: unlabelled decision text; answer or apply contract release label |
22
+ | `blocked` | blocker owner closes it; for an ask, the person, with their own login, answers |
23
+ | `not_at_dispatch_stage` / `not_at_pr_stage` | the person, with their own login: restore dispatch/PR slot respectively; for PR, fix bouncing automation |
24
+ | `no_change_hold` | the person, with their own login: unchanged repair; comment/push/unstick |
25
+ | `waiting_on` | the person, with their own login: unrepairable PR merge gate; read merge-wait comment for hold/review owed, using catalyst-github |
26
+ | `externally_claimed` | the person, with their own login: outside worker owns it; worker/person removes local-lane label |
27
+ | `environment_check_required` / `environment_check_failed` / `environment_check_expired` / `environment_check_hash_mismatch` | absent/failed/aged/changed environment check; a workspace owner or admin, in settings, runs or fixes it |
28
+ | `repo_paused` | an operator resumes repository |
29
+ | `scope_overlap` | the person, with their own login: declared files overlap running work; choose order or wait |
30
+ | `human_owned_pr` | the person, with their own login: only its human owner closes, merges or hands it over; release refused |
31
+ | `review_not_converging` | the person, with their own login: read findings through catalyst-github, comment; release refused |
32
+ | `round_threshold` | the person, with their own login: lifetime repair budget spent; answer existing ask or push fix, never duplicate ask; release refused |
34
33
 
35
34
  ## Reasons a release clears once the cause is fixed
36
35
 
37
- The desk does not release. Route these to the `unstick` skill, which reads the history, previews `catalyst release <ticket>`, and runs it or names what a person must do first.
36
+ After a fix, the person, with their own login, routes `phase_parked`, `cooling_down` from failures/round cap, `remediate_parked`, `validate_class_spent` and `branch_gone` to `unstick`. Read history, preview release; recreate deleted branches or drop tickets. Validate-budget also clears by push/comment/ask answer.
38
37
 
39
- | reason | what it means | note | who acts |
40
- | -- | -- | -- | -- |
41
- | `phase_parked` / `cooling_down` (repeated failures, or the round cap spent) | the phase is parked | `explain --history` names the park | the person, with their own login |
42
- | `remediate_parked` | the repair phase itself is parked | as above | the person, with their own login |
43
- | `validate_class_spent` | the validate-budget hold | a push, comment or ask answer also clears it (the hold table) | the person, with their own login |
44
- | `branch_gone` | a branch that existed was deleted | recreate it, or drop the ticket | the person, with their own login |
38
+ ## Finished
45
39
 
46
- ## Reasons that are not problems
40
+ Finished, so no one acts: `ticket_terminal`, `pipeline_complete` and `pr_merged`; report them as closed.
47
41
 
48
- `ticket_terminal`, `pipeline_complete` and `pr_merged` describe finished work; no one acts. Report them as closed.
42
+ ## Inconclusive
49
43
 
50
- ## When the cloud could not judge
44
+ unknown with `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` or `branch_snapshot_unknown` means unreadable, never absent. Usually recovered next pass. `workflow_mapping_unknown` does not clear without a saved mapping; nor does accompanying `ordering_never_published`. Remap with `catalyst team map <KEY>` as owner/admin; other persistent unknowns need catalyst-onboard.
51
45
 
52
- A status of `unknown` with `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` or `branch_snapshot_unknown` means "I could not look", never "it is not there": report it as inconclusive; most clear on the next pass. `workflow_mapping_unknown` (and `ordering_never_published` with it) does not clear by itself when the team has no saved mapping: a workspace owner or admin maps it (`catalyst team map <KEY>`). If another persists, read readiness (`catalyst-onboard`).
53
-
54
- ## The one advisory
55
-
56
- `human_addressed_unlabeled_ask_suspect` gates nothing: a human-assigned ticket may be an unlabelled ask. List it under waiting-on-human with a question mark.
46
+ `human_addressed_unlabeled_ask_suspect` is advisory: list the human-assigned possible ask under waiting-on-human with a question mark.
@@ -1,163 +0,0 @@
1
- // declaration-rules.ts — a vendored, dated copy of catalyst-cloud's rules for a repo-scope
2
- // `catalyst.env.json`, applied by `validateRepoEnvironmentDeclaration`
3
- // (packages/environment/src/contract.ts:416-493 on catalyst-cloud `main`; the ticket cites line 488,
4
- // which falls inside that range). That file lives in the PRIVATE `coalesce-labs/catalyst-cloud`
5
- // monorepo — no sibling checkout, no npm publication (its one customer, `apps/cli`, is itself
6
- // "private": true) and no network reachability from this session. Everything below is vendored
7
- // secondhand from two independent, dated thoughts-pool research passes (2026-09-10 and 2026-09-17)
8
- // that agreed with each other, following the same idiom this repository's own
9
- // `test/skills-content.test.ts` already uses to vendor catalyst-cloud's `READINESS_CHECK_IDS` (see
10
- // that file's `TEAM_CHECK_IDS` and its provenance comment): dated, cited, paired with a test, never
11
- // hand-copied and trusted on its own.
12
- //
13
- // What a parity test built against THIS copy can, and cannot, catch: it can catch this copy DRIFTING
14
- // FROM ITSELF — a rule dropped, loosened or tightened by a future edit here. It CANNOT notice a new
15
- // rule landing upstream, because nothing in this repository imports the real function — there is no
16
- // publish channel for it. The one fixture pinned by a real, run command transcript rather than by
17
- // prose (`valid-empty-provenance.json`, in `test/fixtures/env-declaration/`) is the strongest anchor
18
- // this vendored copy has; the rest are pinned by the two agreeing research passes.
19
- const MAX_ENV_REFS = 128;
20
- const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
21
- // ⛔ A PREFIX ONLY COUNTS AT A TOKEN BOUNDARY (validate attempt 29, CR-6). These were matched with
22
- // `text.includes(p)`, and "sk-" is a SUBSTRING of "task-", "risk-" and "disk-" — so a declaration
23
- // whose setup step was `["npm","run","task-build"]` was refused as containing secret material. Every
24
- // prefix here is identifier-shaped, so a match is real only at the start of the text or after a
25
- // character that cannot be part of a token; PEM armour, which is not identifier-shaped and carries
26
- // its own unmistakable delimiter, keeps a plain substring match of its own.
27
- // `gho_`, `sk_live_` and `AIza` are in the 2026-09-10 pass's list of upstream's prefixes
28
- // (thoughts: repos/catalyst/shared/research/2026-09-10-CTC-2020-laptop-environment-inventory.md:65),
29
- // and `ghs_` is pinned as an upstream entry by the 2026-09-27 CTC-3378 plan v2 (:238). Validate
30
- // attempt 31 found the first three missing here (and noted `ghs_`), so `env check` said `valid`
31
- // where upstream's validator refuses.
32
- // That upstream list ends "and others": this copy still cannot claim to hold every entry.
33
- const KNOWN_SECRET_PREFIXES = ["ghp_", "gho_", "ghs_", "github_pat_", "sk-", "sk_live_", "AKIA", "AIza", "xoxb-", "xoxp-"];
34
- const PEM_ARMOUR = "-----BEGIN";
35
- const KNOWN_SECRET_PREFIX_RE = new RegExp(`(?:^|[^A-Za-z0-9_])(?:${KNOWN_SECRET_PREFIXES.map(escapeForRegExp).join("|")})`);
36
- function escapeForRegExp(literal) {
37
- return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
38
- }
39
- const SECRET_ASSIGNMENT_RE = /\b([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(\S{4,})/g;
40
- const SECRET_NAME_RE = /(_TOKEN|_SECRET|_KEY|_PASSWORD|_CREDENTIAL)$|^(TOKEN|SECRET|PASSWORD)$/i;
41
- const REQUIRED_ARRAY_FIELDS = ["toolchains", "systemPackages", "setup", "verify", "services", "environment", "agentAssets", "provenance"];
42
- const TOP_LEVEL_FIELDS = new Set(["version", ...REQUIRED_ARRAY_FIELDS]);
43
- /** Empty array = valid. Never reads, and never returns, a value — only field names and paths. */
44
- export function validateDeclaration(doc) {
45
- if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
46
- return ["the declaration must be a JSON object"];
47
- }
48
- const rec = doc;
49
- const errors = [];
50
- for (const key of Object.keys(rec))
51
- if (!TOP_LEVEL_FIELDS.has(key))
52
- errors.push(`unknown key "${key}"`);
53
- if (rec.version !== 1)
54
- errors.push("version must be 1");
55
- for (const field of REQUIRED_ARRAY_FIELDS)
56
- if (!Array.isArray(rec[field]))
57
- errors.push(`${field} must be an array`);
58
- // The rules below assume the shape above holds; a missing/wrong-typed field is reported on its own.
59
- if (errors.length > 0)
60
- return errors;
61
- const environment = rec.environment;
62
- if (environment.length > MAX_ENV_REFS)
63
- errors.push(`environment has ${environment.length} entries; at most ${MAX_ENV_REFS}`);
64
- for (const [i, e] of environment.entries()) {
65
- if (typeof e !== "object" || e === null) {
66
- errors.push(`environment[${i}] must be an object`);
67
- continue;
68
- }
69
- const rec2 = e;
70
- if (typeof rec2.name !== "string" || !ENV_NAME_RE.test(rec2.name))
71
- errors.push(`environment[${i}].name is not a valid name`);
72
- if (typeof rec2.required !== "boolean")
73
- errors.push(`environment[${i}].required must be a boolean`);
74
- if (!Array.isArray(rec2.provenanceIds))
75
- errors.push(`environment[${i}].provenanceIds must be an array`);
76
- }
77
- for (const field of ["setup", "verify"]) {
78
- for (const [i, s] of rec[field].entries()) {
79
- if (typeof s !== "object" || s === null) {
80
- errors.push(`${field}[${i}] must be an object`);
81
- continue;
82
- }
83
- const rec2 = s;
84
- const command = rec2.command;
85
- if (!Array.isArray(command) || command.length === 0 || !command.every((c) => typeof c === "string")) {
86
- errors.push(`${field}[${i}].command must be an argv array (a bare shell string is refused; ["sh","-lc", …] is the explicit escape)`);
87
- }
88
- if (typeof rec2.timeoutSeconds !== "number")
89
- errors.push(`${field}[${i}].timeoutSeconds must be a number`);
90
- if (!Array.isArray(rec2.provenanceIds))
91
- errors.push(`${field}[${i}].provenanceIds must be an array`);
92
- }
93
- }
94
- // C-2: services / provenance / agentAssets had no element check at all, so a non-object element
95
- // reached the free-text sweep below and threw instead of being reported. The rule is the same one
96
- // `environment` and `setup`/`verify` already apply: an element that is not an object is an error.
97
- for (const field of ["services", "provenance", "agentAssets"]) {
98
- for (const [i, e] of rec[field].entries()) {
99
- if (typeof e !== "object" || e === null || Array.isArray(e))
100
- errors.push(`${field}[${i}] must be an object`);
101
- }
102
- }
103
- // assertNoSecretMaterial, LAST — a value-SHAPE refusal over every free-text field (argv strings,
104
- // service image names, provenance explanations, agent-asset references), never a classifier over
105
- // environment[].name: there is nothing to classify there. The refusal names the FIELD, never the
106
- // matched text, so this function never prints what it found.
107
- for (const { path, text } of collectFreeText(rec)) {
108
- if (text.includes(PEM_ARMOUR) || KNOWN_SECRET_PREFIX_RE.test(text)) {
109
- errors.push(`${path} appears to contain secret material (matches a known credential prefix)`);
110
- continue;
111
- }
112
- SECRET_ASSIGNMENT_RE.lastIndex = 0;
113
- let m;
114
- while ((m = SECRET_ASSIGNMENT_RE.exec(text))) {
115
- if (SECRET_NAME_RE.test(m[1])) {
116
- errors.push(`${path} appears to contain secret material (an assignment shaped like a secret name)`);
117
- break;
118
- }
119
- }
120
- }
121
- return errors;
122
- }
123
- function collectFreeText(rec) {
124
- const out = [];
125
- for (const field of ["setup", "verify"]) {
126
- for (const [i, s] of rec[field].entries()) {
127
- const rec2 = asRecord(s);
128
- if (rec2 === null)
129
- continue;
130
- if (Array.isArray(rec2.command)) {
131
- for (const c of rec2.command)
132
- if (typeof c === "string")
133
- out.push({ path: `${field}[${i}].command`, text: c });
134
- }
135
- }
136
- }
137
- for (const [i, s] of rec.services.entries()) {
138
- const rec2 = asRecord(s);
139
- if (rec2 === null)
140
- continue;
141
- if (typeof rec2.image === "string")
142
- out.push({ path: `services[${i}].image`, text: rec2.image });
143
- }
144
- for (const [i, p] of rec.provenance.entries()) {
145
- const rec2 = asRecord(p);
146
- if (rec2 === null)
147
- continue;
148
- if (typeof rec2.explanation === "string")
149
- out.push({ path: `provenance[${i}].explanation`, text: rec2.explanation });
150
- }
151
- for (const [i, a] of rec.agentAssets.entries()) {
152
- const rec2 = asRecord(a);
153
- if (rec2 === null)
154
- continue;
155
- if (typeof rec2.reference === "string")
156
- out.push({ path: `agentAssets[${i}].reference`, text: rec2.reference });
157
- }
158
- return out;
159
- }
160
- /** An element the caller may safely read properties off, or null — a non-object is reported above. */
161
- function asRecord(value) {
162
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
163
- }