@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.
- package/CHANGELOG.md +4 -0
- package/README.md +2 -2
- package/dist/args.js +291 -69
- package/dist/capabilities.js +2 -0
- package/dist/cli.js +109 -17
- package/dist/config.js +3 -1
- package/dist/env/draft.js +134 -0
- package/dist/env/legacy.js +54 -0
- package/dist/env/parse-env.js +50 -0
- package/dist/env/settings-toml.js +66 -0
- package/dist/env.js +126 -26
- package/dist/onboard-consent.js +65 -0
- package/dist/onboard.js +836 -0
- package/dist/project.js +75 -13
- package/dist/ready.js +11 -1
- package/dist/secret.js +24 -3
- package/dist/var.js +118 -0
- package/package.json +2 -1
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +1 -1
- package/skills/catalyst-onboard/references/declaring-a-repository.md +19 -12
- package/skills/catalyst-onboard/references/reading-ready.md +1 -1
- package/skills/catalyst-onboard/references/the-one-path.md +3 -3
- package/skills/catalyst-onboard/references/what-the-browser-owns.md +2 -2
- package/skills/catalyst-onboard/scripts/lib/project-coverage.mjs +53 -0
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +884 -195
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +4 -4
- package/skills/what-this-repo-needs/scripts/check.mjs +12 -13
- package/skills/whats-happening/SKILL.md +19 -35
- package/skills/whats-happening/references/coding-accounts.md +6 -19
- package/skills/whats-happening/references/routing-work.md +4 -15
- package/skills/whats-happening/references/settings-and-where-they-live.md +15 -18
- package/skills/whats-happening/references/stages-and-mapping.md +12 -18
- package/skills/whats-happening/references/status-reply.md +11 -29
- package/skills/whats-happening/references/the-ladder.md +15 -25
- package/skills/whats-happening/references/what-runs-next.md +7 -26
- package/skills/whats-happening/references/when-a-phase-fails.md +16 -22
- package/skills/whats-happening/references/why-is-it-stuck.md +38 -48
- package/dist/env/declaration-rules.js +0 -163
|
@@ -1,56 +1,46 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
| reason |
|
|
10
|
-
| -- | -- |
|
|
11
|
-
| `runner_image_breaker` |
|
|
12
|
-
| `routing_unavailable` |
|
|
13
|
-
| `cooling_down`
|
|
14
|
-
| `wip_limit` |
|
|
15
|
-
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
| reason |
|
|
19
|
-
| -- | -- |
|
|
20
|
-
| `ask_ticket` |
|
|
21
|
-
| `ask_shape_suspected` |
|
|
22
|
-
| `blocked` |
|
|
23
|
-
| `not_at_dispatch_stage`
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
+
Finished, so no one acts: `ticket_terminal`, `pipeline_complete` and `pr_merged`; report them as closed.
|
|
47
41
|
|
|
48
|
-
|
|
42
|
+
## Inconclusive
|
|
49
43
|
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
}
|