@catalyst-cloud/catalyst-skills 0.5.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 -178
- package/bin/catalyst-skills.js +3 -49
- package/bin/catalyst.js +3 -0
- package/package.json +8 -29
- package/CHANGELOG.md +0 -41
- package/LICENSE +0 -21
- package/dist/args.js +0 -261
- package/dist/ask.js +0 -158
- package/dist/browser.js +0 -20
- package/dist/cli.js +0 -356
- package/dist/config.js +0 -148
- package/dist/contract-types.js +0 -4
- package/dist/contract.js +0 -178
- package/dist/detach.js +0 -10
- package/dist/errors.js +0 -27
- package/dist/events.js +0 -106
- package/dist/execution.js +0 -369
- package/dist/oauth.js +0 -300
- package/dist/prompt.js +0 -35
- package/dist/query.js +0 -225
- package/dist/ready.js +0 -163
- package/dist/release.js +0 -142
- package/dist/replica.js +0 -325
- package/dist/sdk.js +0 -26
- package/dist/skill-shape.js +0 -179
- package/dist/skills.js +0 -78
- package/dist/transport.js +0 -205
- package/dist/ts-deps-loader.js +0 -68
- 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 -50
- 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-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 -66
- 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 -145
|
@@ -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,82 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// lib/pull.mjs — resolve "a ticket or a PR id" to one mirrored pull-request detail, the way both
|
|
3
|
-
// scripts in this skill need it. A ticket resolves through its own record's linked pull requests
|
|
4
|
-
// (the mirror links a PR to the ticket its text names), then the chosen PR's detail is read by node
|
|
5
|
-
// id. A library: run read-pr.mjs or is-it-mergeable.mjs with --help for usage.
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { forwardSourceLine, looksLikeTicket, parseJson, runCli, runCliOrExit } from "./cli.mjs";
|
|
8
|
-
|
|
9
|
-
export function truthy(v) {
|
|
10
|
-
return v === true || v === 1 || v === "1" || v === "true";
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function readIssue(ticket, extra = []) {
|
|
14
|
-
const res = runCli(["query", "issue", ticket, ...extra, "--json"]);
|
|
15
|
-
forwardSourceLine(res);
|
|
16
|
-
if (res.code !== 0) {
|
|
17
|
-
console.error(res.stderr.trim() || `${ticket}: not found`);
|
|
18
|
-
process.exit(res.code === 2 ? 2 : 1);
|
|
19
|
-
}
|
|
20
|
-
const issue = parseJson(res.stdout);
|
|
21
|
-
if (!issue || typeof issue !== "object") {
|
|
22
|
-
console.error(`${ticket}: the ticket read did not return JSON`);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
return issue;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Every PR that names the ticket, newest number first. Exits 1 when the ticket is unknown. A source
|
|
30
|
-
* whose ticket detail carries no `linked_pulls` field at all (a replica built by an SDK that predates
|
|
31
|
-
* the ticket-to-PR link) is not evidence of "no PR": the ticket is re-read from the API, which is
|
|
32
|
-
* origin-fresh and carries the field, and the second source line says so. An empty list from a source
|
|
33
|
-
* that has the field is a real "no PR yet".
|
|
34
|
-
*/
|
|
35
|
-
export function linkedPulls(ticket) {
|
|
36
|
-
let issue = readIssue(ticket);
|
|
37
|
-
if (issue.linked_pulls === undefined) {
|
|
38
|
-
console.error("note: this source carries no linked pull requests on a ticket; re-reading the ticket from the API");
|
|
39
|
-
issue = readIssue(ticket, ["--source", "api"]);
|
|
40
|
-
}
|
|
41
|
-
const pulls = Array.isArray(issue.linked_pulls) ? issue.linked_pulls : [];
|
|
42
|
-
return { issue, pulls: [...pulls].sort((a, b) => Number(b.number ?? 0) - Number(a.number ?? 0)) };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** The PR that matters for a ticket: an open one first, else the merged one, else the newest. */
|
|
46
|
-
export function pickPull(pulls) {
|
|
47
|
-
const open = pulls.find((p) => String(p.state ?? "").toLowerCase() === "open" && !truthy(p.merged));
|
|
48
|
-
if (open) return open;
|
|
49
|
-
const merged = pulls.find((p) => truthy(p.merged));
|
|
50
|
-
return merged ?? pulls[0] ?? null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** One PR's detail by node id: checks, reviews, commit statuses, mergeable state, linked ticket. */
|
|
54
|
-
export function pullDetail(nodeId) {
|
|
55
|
-
const res = runCliOrExit(["query", "pull", nodeId, "--json"]);
|
|
56
|
-
forwardSourceLine(res);
|
|
57
|
-
const detail = parseJson(res.stdout);
|
|
58
|
-
if (!detail || typeof detail !== "object") {
|
|
59
|
-
console.error(`${nodeId}: the pull read did not return JSON`);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
return detail;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Ticket or node id → {ticket, detail, linked}. Exits 1 when a ticket has no PR yet. */
|
|
66
|
-
export function resolvePull(arg) {
|
|
67
|
-
if (looksLikeTicket(arg)) {
|
|
68
|
-
const { pulls } = linkedPulls(arg);
|
|
69
|
-
const chosen = pickPull(pulls);
|
|
70
|
-
if (!chosen) {
|
|
71
|
-
console.error(`${arg}: no pull request names this ticket yet — the implement phase opens one when it has a branch`);
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
return { ticket: arg, detail: pullDetail(String(chosen.node_id)), linked: pulls };
|
|
75
|
-
}
|
|
76
|
-
const detail = pullDetail(arg);
|
|
77
|
-
return { ticket: detail.linear_issue_identifier ? String(detail.linear_issue_identifier) : null, detail, linked: [] };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
81
|
-
console.log("lib/pull.mjs is a library used by read-pr.mjs and is-it-mergeable.mjs; run those with --help for usage.");
|
|
82
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// read-pr.mjs — one ticket's pull request (or one PR by node id) as the mirror holds it: state,
|
|
3
|
-
// branch, head, base, linked ticket and its stage, GitHub's own mergeable verdict, every check,
|
|
4
|
-
// review and commit status. Reads through `catalyst-skills query`; composes no URL.
|
|
5
|
-
import { linkedPulls, resolvePull, truthy } from "./lib/pull.mjs";
|
|
6
|
-
|
|
7
|
-
const HELP = `Usage: node scripts/read-pr.mjs <ticket | pr-node-id> [--all] [--json]
|
|
8
|
-
|
|
9
|
-
<ticket> a ticket identifier such as ABC-123: its open PR is shown (else the merged one, else the newest)
|
|
10
|
-
<pr-node-id> a GitHub pull-request node id, as printed by "catalyst-skills query pulls"
|
|
11
|
-
--all for a ticket: list every PR that names it instead of one detail
|
|
12
|
-
--json print the raw detail document instead of the summary
|
|
13
|
-
|
|
14
|
-
Exit 0 shown, 1 not found or no PR yet, 2 this machine is not connected to a tenant.
|
|
15
|
-
The first stderr line names the source the CLI read from (a fresh replica, or the API).`;
|
|
16
|
-
|
|
17
|
-
const args = process.argv.slice(2);
|
|
18
|
-
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
19
|
-
console.log(HELP);
|
|
20
|
-
process.exit(0);
|
|
21
|
-
}
|
|
22
|
-
const json = args.includes("--json");
|
|
23
|
-
const all = args.includes("--all");
|
|
24
|
-
const target = args.find((a) => !a.startsWith("--"));
|
|
25
|
-
if (!target) {
|
|
26
|
-
console.error("read-pr needs a ticket or a PR node id");
|
|
27
|
-
console.log(HELP);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function fmt(v, fallback = "?") {
|
|
32
|
-
return v === null || v === undefined || v === "" ? fallback : String(v);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function when(ms) {
|
|
36
|
-
const n = Number(ms);
|
|
37
|
-
return Number.isFinite(n) && n > 0 ? new Date(n).toISOString() : fmt(ms, "");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (all) {
|
|
41
|
-
if (!/^[A-Za-z][A-Za-z0-9]*-\d+$/.test(target)) {
|
|
42
|
-
console.error("--all takes a ticket identifier");
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
const { pulls } = linkedPulls(target);
|
|
46
|
-
if (json) {
|
|
47
|
-
console.log(JSON.stringify(pulls));
|
|
48
|
-
process.exit(0);
|
|
49
|
-
}
|
|
50
|
-
if (pulls.length === 0) {
|
|
51
|
-
console.log(`${target}: no pull request names this ticket yet`);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
for (const p of pulls) {
|
|
55
|
-
const flags = [truthy(p.draft) ? "draft" : null, truthy(p.merged) ? "merged" : null].filter(Boolean).join(" ");
|
|
56
|
-
console.log(`${fmt(p.repo_id)}#${fmt(p.number)} ${fmt(p.state)}${flags ? ` ${flags}` : ""} ${fmt(p.title, "")} [${fmt(p.node_id, "")}]`);
|
|
57
|
-
}
|
|
58
|
-
process.exit(0);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const { ticket, detail } = resolvePull(target);
|
|
62
|
-
if (json) {
|
|
63
|
-
console.log(JSON.stringify(detail));
|
|
64
|
-
process.exit(0);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const checks = Array.isArray(detail.checks) ? detail.checks : [];
|
|
68
|
-
const reviews = Array.isArray(detail.reviews) ? detail.reviews : [];
|
|
69
|
-
const statuses = Array.isArray(detail.commit_statuses) ? detail.commit_statuses : [];
|
|
70
|
-
const state = [fmt(detail.state), truthy(detail.draft) ? "draft" : null, truthy(detail.merged) ? `merged ${when(detail.merged_at)}` : null].filter(Boolean).join(", ");
|
|
71
|
-
|
|
72
|
-
console.log(`${fmt(detail.repo_id)}#${fmt(detail.number)} ${fmt(detail.title, "(no title yet)")}`);
|
|
73
|
-
console.log(`state: ${state} by ${fmt(detail.author_login)} opened ${when(detail.created_at)} updated ${when(detail.updated_at)}`);
|
|
74
|
-
console.log(`branch: ${fmt(detail.head_ref)} → ${fmt(detail.base_ref)} head ${fmt(detail.head_sha).slice(0, 12)}`);
|
|
75
|
-
console.log(`ticket: ${fmt(ticket ?? detail.linear_issue_identifier, "none named")}${detail.linked_issue_state ? ` (stage: ${detail.linked_issue_state})` : ""}`);
|
|
76
|
-
console.log(`github says: mergeable=${fmt(detail.mergeable, "unknown")} state=${fmt(detail.mergeable_state, "unknown")} auto-merge=${truthy(detail.auto_merge) ? "on" : "off"}`);
|
|
77
|
-
if (detail.blocked_on_ask && typeof detail.blocked_on_ask === "object") {
|
|
78
|
-
const b = detail.blocked_on_ask;
|
|
79
|
-
console.log(`blocked on an ask: ${fmt(b.identifier ?? b.id, "")} ${fmt(b.title, "")}`.trim());
|
|
80
|
-
}
|
|
81
|
-
console.log(`node id: ${fmt(detail.node_id)}`);
|
|
82
|
-
|
|
83
|
-
console.log(`checks (${checks.length}):`);
|
|
84
|
-
if (checks.length === 0) console.log(" none reported at this head yet");
|
|
85
|
-
for (const c of checks) console.log(` ${fmt(c.status)}/${fmt(c.conclusion, "-")} ${fmt(c.name)}`);
|
|
86
|
-
|
|
87
|
-
if (statuses.length > 0) {
|
|
88
|
-
console.log(`commit statuses (${statuses.length}):`);
|
|
89
|
-
for (const s of statuses) console.log(` ${fmt(s.state)} ${fmt(s.context)}`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
console.log(`reviews (${reviews.length}):`);
|
|
93
|
-
if (reviews.length === 0) console.log(" none mirrored; a reviewer's clean pass is a reaction, which this read does not carry");
|
|
94
|
-
for (const r of reviews) console.log(` ${fmt(r.state)} ${fmt(r.reviewer_name, fmt(r.reviewer_id))} ${when(r.submitted_at)}`);
|
|
95
|
-
|
|
96
|
-
console.log("not mirrored here: PR labels (holds, queue attestation) and the reviewer's reaction — see references/what-a-pr-accumulates.md");
|
|
97
|
-
process.exit(0);
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: catalyst-linear
|
|
3
|
-
description: >-
|
|
4
|
-
Catalyst's view of Linear on the customer's own tenant. Reads a ticket with its comments, relations, labels, linked pull requests and agent sessions inline, from the local replica when it is fresh and the origin-fresh API otherwise, always naming the source; searches tickets, PRs, projects and initiatives; writes comments, card moves, labels and new tickets through the tenant's agent proxy as the app actor, with every route, stage id, label id and marker read from the tenant contract. Knows what a ticket accumulates as Catalyst works it (phase-outcome comments, the document per phase, the agent session, the labels, the single blocks relation, the bookkeeping marker, the eyes acknowledgement). Use when a person says "show me the ticket", "what did Catalyst write on it", "comment on it", "move it", "label it" or "file a ticket". Not for raising a decision for a human (what-needs-me) and not for pull requests (catalyst-github).
|
|
5
|
-
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
6
|
-
disable-model-invocation: true
|
|
7
|
-
---
|
|
8
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills — written in this repository for customer tenants -->
|
|
9
|
-
|
|
10
|
-
# Catalyst Linear
|
|
11
|
-
|
|
12
|
-
You read tickets and write to them on the customer's tenant, as Catalyst. Reads come from the replica when it is fresh, else from the API, and every read names its source. Writes go through the tenant's agent proxy as the app actor, with routes, stage ids, label ids and the bookkeeping marker resolved from the tenant contract by the `catalyst-skills` CLI. You never compose a URL, never name a stage by its display name, and never quote a label id or marker from memory.
|
|
13
|
-
|
|
14
|
-
## Run first
|
|
15
|
-
|
|
16
|
-
Run each with `--help` first; scripts are executed, never read.
|
|
17
|
-
|
|
18
|
-
- `node scripts/read-ticket.mjs <ticket> [--comments]` — one ticket, everything inline, source line on stderr.
|
|
19
|
-
- `node scripts/search.mjs <terms>` — tickets, PRs, projects, initiatives matching the terms.
|
|
20
|
-
- `node scripts/comment.mjs <ticket> --body <text> [--parent <id>] [--bookkeeping]` — a comment as the app actor; a machine record takes `--bookkeeping`.
|
|
21
|
-
- `node scripts/move.mjs <ticket> --slot <slot>` — a card move by slot (`--state-type backlog` parks).
|
|
22
|
-
- `node scripts/label.mjs <ticket> --add <name> --remove <name>` — labels, resolved through the contract.
|
|
23
|
-
- `node scripts/create-ticket.mjs --team <key> --title <text>` — a new ticket; cite its identifier only after it prints.
|
|
24
|
-
|
|
25
|
-
Exit codes: 0 done, 1 not found or a usage error, 2 this machine is not connected or the write was refused (budget spent, slot unmapped, label absent; the one line printed says which).
|
|
26
|
-
|
|
27
|
-
## Load on demand
|
|
28
|
-
|
|
29
|
-
| when | read |
|
|
30
|
-
| -- | -- |
|
|
31
|
-
| "what did Catalyst write on this ticket?", a comment shape, a label, the marker, the 👀 | `references/what-a-ticket-accumulates.md` |
|
|
32
|
-
| before any read you will report on; "is this current?"; citing; searching | `references/reading-a-ticket.md` |
|
|
33
|
-
| before any comment, move, label or new ticket; identity, budget, slots, ids | `references/writing-to-linear.md` |
|
|
34
|
-
|
|
35
|
-
## Rules
|
|
36
|
-
|
|
37
|
-
- **Read before acting.** Never summarise a ticket from its title; read the description and the thread. Cite identifiers and comment ids only after a script printed them.
|
|
38
|
-
- **Name the source.** Quote the source line when freshness matters; a stale replica is never read silently, because the CLI falls back to the API and says so.
|
|
39
|
-
- **Reply where it arrived, as Catalyst.** Thread under the comment you answer (`--parent`); never post as the human.
|
|
40
|
-
- **Records carry the marker.** A merge note, a state-move log, a chain summary: `--bookkeeping`, so it never reads as a human turn. A real question is not bookkeeping.
|
|
41
|
-
- **Move by slot, label by contract.** A stage name is display; the id is the authority, and the CLI resolves both. Moving to `dispatch` dispatches; the backlog-type state parks.
|
|
42
|
-
- **A decision is an ask, not a ticket.** Raise it through `what-needs-me` with options, a default and what it blocks; a question filed as a ticket is held out of dispatch by shape.
|
|
43
|
-
- **One write per need, no loops.** Writes spend a daily budget the contract names; batch, and report a refusal rather than retrying.
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
interface:
|
|
2
|
-
display_name: "Catalyst Linear"
|
|
3
|
-
short_description: "Read a ticket and what Catalyst wrote on it; comment, move, label and file tickets as the app actor"
|
|
4
|
-
default_prompt: "Use $catalyst-linear to show me the ticket, its history, and what Catalyst wrote on it."
|
|
5
|
-
policy:
|
|
6
|
-
allow_implicit_invocation: false
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Reading a ticket: freshness first, then the row
|
|
2
|
-
|
|
3
|
-
This reference restates invariants about how a read is made and reported. Nothing in it is a tenant value.
|
|
4
|
-
|
|
5
|
-
## Freshness first
|
|
6
|
-
|
|
7
|
-
Every `query` verb, and therefore `node scripts/read-ticket.mjs` and `node scripts/search.mjs`, prints one stderr line before the answer:
|
|
8
|
-
|
|
9
|
-
- `source: replica (cursor N)` — the local replica was fresh (its writer heartbeat is young and its cursor non-empty), so the answer is local and cheap. N is the position it had applied.
|
|
10
|
-
- `source: api (replica stale)`, `source: api (replica absent)` or `source: api (replica not configured)` — the answer came from the tenant API, which is origin-fresh: it reflects the mirror's latest ingest, not a cached copy.
|
|
11
|
-
- `source: api (<verb> is api-only)` — search, cycles, pull detail and change feeds are never served from the replica.
|
|
12
|
-
|
|
13
|
-
Read that line every time. A fresh replica is a preference; the API is the fallback and it is never wrong to read it. What is wrong is silently reading a stale replica, and the CLI does not let that happen: a stale replica falls back to the API and says so. Quote the source in your answer when the freshness of a fact matters ("as of the replica at cursor N" or "from the API just now").
|
|
14
|
-
|
|
15
|
-
`--source replica` or `--source api` forces one. Forcing the replica when it is absent or stale is refused.
|
|
16
|
-
|
|
17
|
-
## Then the row
|
|
18
|
-
|
|
19
|
-
One ticket read returns the whole record: the fields, the description, `labels[]`, `relations[]`, `linked_pulls[]`, `comments[]`, `activity[]` and `agent_sessions[]` inline, plus the project, cycle, team, delegate and parent fields. One call answers "what is this ticket, what happened on it, what is it waiting on, which PR is it". There is no second call to make for the comments.
|
|
20
|
-
|
|
21
|
-
The summary the script prints puts the counts first and the description last; `--comments` prints every comment with its id, author, time and whether the author is a bot. A comment with a `reply-to` marker is threaded under another; reply under the same parent when you answer it.
|
|
22
|
-
|
|
23
|
-
## Reading Catalyst's own writes
|
|
24
|
-
|
|
25
|
-
The comment shapes in `references/what-a-ticket-accumulates.md` tell you which comments are the cloud's. To answer "what happened to this ticket":
|
|
26
|
-
|
|
27
|
-
1. Phase-outcome comments, newest first, give the attempts and their results.
|
|
28
|
-
2. Remediate-attempt comments give the failure class each round repaired.
|
|
29
|
-
3. The projection-link comments name the documents; open the document (it is attached to the ticket) to read what a phase actually concluded. A fallback comment carries the body inline instead.
|
|
30
|
-
4. `linked_pulls[]` names the PR; `catalyst-github` reads it.
|
|
31
|
-
5. For "what will it do next", leave this skill: `how-catalyst-works` explains the eligibility row.
|
|
32
|
-
|
|
33
|
-
## When to read a transcript
|
|
34
|
-
|
|
35
|
-
A phase's transcript (the full session log) exists in the cloud per ticket, but this bundle has no verb for it yet. Read the artifact document first; it is the phase's own account of what it did. Reach for the transcript only when the document leaves the question open, and say that the bundle cannot fetch it so the human opens it from the ticket's attachments.
|
|
36
|
-
|
|
37
|
-
## What the ledger adds to the comments
|
|
38
|
-
|
|
39
|
-
Per-ticket execution history beyond the comments (the attempt ledger, the remediation round count against the cap, park state and what releases it) is `catalyst-skills explain --history <ticket>`, read from the cloud's own relay ledger. Do not reconstruct a round count from comments and present it as the cap's count; read it.
|
|
40
|
-
|
|
41
|
-
## How to cite
|
|
42
|
-
|
|
43
|
-
- Cite a ticket by its identifier (`KEY-123`), never by title alone, and only after you read it back from a script's output.
|
|
44
|
-
- Cite a comment by its id when you refer to it or reply under it.
|
|
45
|
-
- Cite a document by the title the projection comment gave it.
|
|
46
|
-
- Never summarise a ticket from its title. Read the description and the thread.
|
|
47
|
-
|
|
48
|
-
## Searching and listing
|
|
49
|
-
|
|
50
|
-
`node scripts/search.mjs <terms>` matches ticket identifiers and titles, PR titles, project and initiative names and returns a few of each. It is the only search; a list read with a filter is not a search and will hand back the ordinary first page, which reads as a false "not found". Lists (`catalyst-skills query issues --team <key>`, `query projects`, `query cycles`) are for a board view, not for finding one ticket.
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# What a ticket accumulates as Catalyst works it
|
|
2
|
-
|
|
3
|
-
This reference restates invariants: the shapes of what the cloud writes onto a ticket. The one live value it needs — the bookkeeping marker and the label names — is on the contract under `vocabulary` and `teams[].labels`; print them with `catalyst-skills contract --path vocabulary` and `catalyst-skills contract --path teams`. Never quote a marker or a label id from memory.
|
|
4
|
-
|
|
5
|
-
## Comments the cloud posts
|
|
6
|
-
|
|
7
|
-
| Kind | When | How it opens |
|
|
8
|
-
| -- | -- | -- |
|
|
9
|
-
| Phase outcome | every phase attempt ends | `✅ **Phase complete**` or `🛑 **Phase FAILED**`, then `**Phase**`, an optional `**Attempt**`, `**Artifact**`, a quoted summary, and a park, hold or failure block when there is one; a footer names the event that produced it |
|
|
10
|
-
| Remediate attempt | every remediation round ends | `🔧 **Remediate attempt N — SUCCEEDED**` or `FAILED`, then `**Class**` (the failure class being repaired) and optionally `**Repairing**` and `**Artifact**` |
|
|
11
|
-
| Projection link | an artifact document was created in Linear | `**<phase>** · attempt N — [<title>](<document url>)`, then the storage key and the thoughts-repository path |
|
|
12
|
-
| Projection fallback | Linear refused a document | `> ⚠️ **Fallback comment.** Linear refused a document for this artifact: <reason>` followed by the artifact body fenced inline |
|
|
13
|
-
| Board health | a ticket sat in an active stage with no sign of life | `**Catalyst Cloud · board health**`, what stalled, and "Action taken" |
|
|
14
|
-
| Merge wait | a hold at PR or merge named a cause | says what the merge is waiting on |
|
|
15
|
-
| Ask reply | a human answered an ask | a threaded reply under the answer |
|
|
16
|
-
|
|
17
|
-
Every one of these is posted by the app actor and is skipped by the comment-wake trigger; none of them wakes an agent.
|
|
18
|
-
|
|
19
|
-
## One document per phase, attached
|
|
20
|
-
|
|
21
|
-
Each artifact-bearing phase (research, plan, implement, validate, pr, remediate) projects its artifact into Linear as a document at a deterministic id, titled `<TICKET> · <phase> · attempt <n> · <YYYY-MM-DD>`, attached to the ticket, and announced by the projection-link comment above. Research and plan documents on a ticket that belongs to a project are also linked from the project. The same body is committed into the tenant's thoughts repository when one is configured. Reading the document is how you read what a phase concluded; the phase-outcome comment only summarises it.
|
|
22
|
-
|
|
23
|
-
## The agent session
|
|
24
|
-
|
|
25
|
-
A ticket being worked grows a Linear agent session whose **plan** is the ladder itself: phases before the current one completed, the current one in progress, later ones pending. A remediation round is an interrupt with no ladder position and anchors on the phase it interrupted. Activities record a phase starting, the gate running, an artifact being written, a PR being opened, and a terminal report. A session in a pending, active or awaiting-input status is reused; a stale, complete or errored one is replaced by a fresh session. Emission is best-effort: a failure to narrate never blocks a phase.
|
|
26
|
-
|
|
27
|
-
## Labels
|
|
28
|
-
|
|
29
|
-
| Role (contract name) | Who applies it | Meaning |
|
|
30
|
-
| -- | -- | -- |
|
|
31
|
-
| ask marker (`vocabulary.askMarkerLabel`, listed under `teams[].labels.ask`) | the cloud, atomically when it files an ask; or a human filing one by hand | this ticket is a question for a human, never work |
|
|
32
|
-
| ask kind family (`vocabulary.askLabelPrefix`, and per-phase `vocabulary.askPhaseLabelPrefix`) | the cloud, or a human | what kind of ask, and which phase raised it |
|
|
33
|
-
| release (`vocabulary.releaseLabel`, listed under `teams[].labels.release`) | **a human only** | releases a ticket the shape detector flagged as an ask; a human-applied ask-kind label outranks it |
|
|
34
|
-
| hold (listed under `teams[].labels.hold`) | the cloud | the marker a team gets instead of a Remediate state move when it has no remediate stage mapped |
|
|
35
|
-
| a local-lane marker | the cloud | a worker outside the cloud holds this ticket; removed at that lane's terminal step |
|
|
36
|
-
|
|
37
|
-
`(absent)` beside a label in `teams[].labels` means the workspace has no such label yet; the CLI refuses to apply a name it cannot resolve.
|
|
38
|
-
|
|
39
|
-
## Relations
|
|
40
|
-
|
|
41
|
-
`blocks` is the only relation Catalyst creates. It is written when an ask is raised, so the ask blocks every ticket waiting on the answer, and it is the signal that ranks the human's inbox: an ask with nothing to block is refused unless the caller declares nothing is blocked, because such an ask would never surface. No other relation type is written by the cloud.
|
|
42
|
-
|
|
43
|
-
## The bookkeeping marker
|
|
44
|
-
|
|
45
|
-
A comment that is a machine RECORD (a merge note, a state-move log, a chain summary) rather than a turn in a conversation must begin with the bookkeeping marker from `vocabulary.bookkeeping.marker`. Rules the contract states alongside it: it is a prefix only, never a substring; ASCII case-insensitive; leading whitespace is trimmed first. A marked comment never wakes an agent and never counts as "something happened" for the no-change and validate holds.
|
|
46
|
-
|
|
47
|
-
It matters on the human-attributed path: a record posted with a personal identity is wire-identical to the human typing it and would wake an agent to reply to its own writeup. A comment posted as the app actor already carries a bot actor and is already skipped; `--bookkeeping` on the comment script is still correct there, and harmless.
|
|
48
|
-
|
|
49
|
-
A real question that merely mentions the word is not bookkeeping and still wakes, deliberately.
|
|
50
|
-
|
|
51
|
-
## The eyes acknowledgement
|
|
52
|
-
|
|
53
|
-
When a genuine human comment lands (not a bot, not agent-authored, not an automation signature, not bookkeeping), the cloud reacts with 👀 and records a durable wake; the record is written even if the reaction fails. When a bot or agent reply observed at ingest is threaded under that comment, the 👀 is removed and the wake resolves. No live session is required for either half. So: reply in-thread, under the comment you are answering, or the acknowledgement never clears.
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Writing to Linear: identity, budget, slots, labels
|
|
2
|
-
|
|
3
|
-
This reference restates the write mechanism. The live values it depends on — the route table, the daily write budget, the stage map, the label ids — are read by the CLI from the contract (`routes`, `thresholds`, `teams[].stages`, `teams[].labels`) on every write. Nothing here is a literal to copy.
|
|
4
|
-
|
|
5
|
-
## Every write is one script
|
|
6
|
-
|
|
7
|
-
| Want | Run |
|
|
8
|
-
| -- | -- |
|
|
9
|
-
| a comment, threaded or top-level | `node scripts/comment.mjs <ticket> --body <text> [--parent <commentId>] [--bookkeeping]` |
|
|
10
|
-
| a card move | `node scripts/move.mjs <ticket> --slot <slot>` or `--state-type backlog` |
|
|
11
|
-
| a label on or off | `node scripts/label.mjs <ticket> --add <name> --remove <name>` |
|
|
12
|
-
| a new ticket | `node scripts/create-ticket.mjs --team <key> --title <text>` |
|
|
13
|
-
| a decision for a human | not here: the `what-needs-me` skill raises an ask with options, a default and what it blocks |
|
|
14
|
-
|
|
15
|
-
Each script wraps one `catalyst-skills write` verb, which posts to the route the contract names, as the tenant's app actor, with the personal key this machine connected with — so the write carries the person's identity for attribution, and an ask names them. No script composes a URL, and none needs a Linear credential of its own.
|
|
16
|
-
|
|
17
|
-
## App actor versus personal identity
|
|
18
|
-
|
|
19
|
-
Every write goes out as the tenant's Catalyst app actor by default. That identity is what the cloud's own comment-wake trigger recognises as "not a human", so an app-actor comment never wakes an agent to reply to it, never needs the bookkeeping marker to be safe, and reads to the human as Catalyst speaking. `--as-user` switches a comment or a new ticket to the personal identity behind the key; use it only when the human asked for that attribution, and then mark any machine record with `--bookkeeping`, because on that path the record is indistinguishable from the human typing it.
|
|
20
|
-
|
|
21
|
-
Reply where the message arrived: a comment inside a thread is answered with `--parent <commentId>`, in that thread, and never as a new ticket. Never post as the human; you speak as Catalyst, or as yourself by role.
|
|
22
|
-
|
|
23
|
-
## The daily write budget
|
|
24
|
-
|
|
25
|
-
Every write route spends one unit of a per-host daily budget the contract publishes as `thresholds.hostDailyWriteBudget`; a label call spends one unit whatever the label count; reads spend none. When the budget is spent, the CLI refuses with exit 2 and a line that names the budget and the retry time. Do not retry in a loop; report it, and batch what you can into fewer writes (one comment, not five).
|
|
26
|
-
|
|
27
|
-
## State moves are by slot, never by name
|
|
28
|
-
|
|
29
|
-
`--slot <slot>` names one of the eleven slots; the CLI resolves it to this team's live state id from the contract. It refuses when the slot is unmapped for the team, and when the mapped state no longer exists in Linear (the map needs fixing in tenant settings first). Moving to `dispatch` is how work is dispatched; nothing is offered from any other column. Backlog is not a slot: `--state-type backlog` resolves the team's first backlog-type state from its live workflow states, and moving a card there parks it and stops remediation rounds. A move never fabricates a state id, and a move by stage name is not offered because names are display fields that survive re-imports while ids do not.
|
|
30
|
-
|
|
31
|
-
## Labels are by contract id
|
|
32
|
-
|
|
33
|
-
A name the contract lists for the team (the ask marker, the hold label, the release label) resolves to that team's preferred id; when the workspace has no such label the CLI refuses rather than inventing one. Any other value is sent as a label id as given, so a label outside Catalyst's own set needs its id, which the ticket record's `labels[]` shows.
|
|
34
|
-
|
|
35
|
-
Two labels are a human's, not yours: the release label (which frees a ticket the shape detector held) is applied by a human; the hold labels on a pull request are `catalyst-github`'s subject.
|
|
36
|
-
|
|
37
|
-
## A new ticket takes the team key
|
|
38
|
-
|
|
39
|
-
`create-ticket.mjs --team <key>` names the team by the prefix its identifiers carry; the CLI resolves the team id from the contract and the cloud fences the write to your tenant on the team, not only on the credential. Cite the identifier only after the script prints it; a guessed number is usually a real, unrelated ticket. Do not file a question for a human this way: a ticket whose text reads as a decision request is held out of dispatch by shape until someone releases it, and it would never reach the human's inbox with what it blocks.
|
|
40
|
-
|
|
41
|
-
## What is never offered
|
|
42
|
-
|
|
43
|
-
Delegation (assigning a ticket to an agent) is an operator action, not a tenant write. Answering an ask on the human's behalf is not a write you make; the human answers, and `what-needs-me` records the acceptance. Editing another actor's comment is not available.
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// comment.mjs — post a comment on a ticket as the tenant's app actor through the agent proxy.
|
|
3
|
-
// A bookkeeping comment (a machine record, not a turn in a conversation) takes --bookkeeping, which
|
|
4
|
-
// prefixes the marker the contract's vocabulary names.
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
6
|
-
import { exitOnFailure, parseFlags, parseJson, relayStderr, runCli, usage, wantsHelp } from "./lib/cli.mjs";
|
|
7
|
-
|
|
8
|
-
const HELP = `Usage: node scripts/comment.mjs <ticket> (--body <text> | --stdin) [--parent <commentId>] [--bookkeeping] [--as-user] [--json]
|
|
9
|
-
|
|
10
|
-
Posts one comment on a ticket. Wraps: catalyst-skills write comment. Spends one unit of the tenant's
|
|
11
|
-
daily write budget.
|
|
12
|
-
|
|
13
|
-
<ticket> the Linear identifier, e.g. KEY-123
|
|
14
|
-
--body <text> the comment body
|
|
15
|
-
--stdin read the body from stdin instead
|
|
16
|
-
--parent <commentId> reply under this comment (reply where the message arrived)
|
|
17
|
-
--bookkeeping this is a machine record: prefix the contract's bookkeeping marker so it
|
|
18
|
-
never reads as a human turn
|
|
19
|
-
--as-user post with the personal identity instead of the app actor (rare; then a
|
|
20
|
-
record needs --bookkeeping to avoid waking an agent)
|
|
21
|
-
--json print the CLI's JSON result
|
|
22
|
-
|
|
23
|
-
Exit 0 posted, 1 on a usage error, 2 when this machine is not connected to a tenant or the write
|
|
24
|
-
was refused (the daily budget is spent, the ticket is unknown, the route is missing; the line
|
|
25
|
-
says which).`;
|
|
26
|
-
|
|
27
|
-
const argv = process.argv.slice(2);
|
|
28
|
-
if (wantsHelp(argv)) {
|
|
29
|
-
console.log(HELP);
|
|
30
|
-
process.exit(0);
|
|
31
|
-
}
|
|
32
|
-
const { flags, positionals } = parseFlags(argv, {
|
|
33
|
-
bool: ["stdin", "bookkeeping", "as-user", "json"],
|
|
34
|
-
value: ["body", "parent"],
|
|
35
|
-
});
|
|
36
|
-
const ticket = positionals[0];
|
|
37
|
-
if (!ticket) usage("comment needs a ticket identifier (see --help)");
|
|
38
|
-
if (!flags.body && !flags.stdin) usage("comment needs --body <text> or --stdin (see --help)");
|
|
39
|
-
if (flags.body && flags.stdin) usage("give --body or --stdin, not both");
|
|
40
|
-
|
|
41
|
-
let stdin;
|
|
42
|
-
const args = ["write", "comment", ticket, "--json"];
|
|
43
|
-
if (flags.stdin) {
|
|
44
|
-
stdin = readFileSync(0, "utf8");
|
|
45
|
-
if (!stdin.trim()) usage("stdin was empty");
|
|
46
|
-
args.push("--stdin");
|
|
47
|
-
} else {
|
|
48
|
-
args.push("--body", flags.body);
|
|
49
|
-
}
|
|
50
|
-
if (flags.parent) args.push("--parent", flags.parent);
|
|
51
|
-
if (flags.bookkeeping) args.push("--bookkeeping");
|
|
52
|
-
if (flags["as-user"]) args.push("--as-user");
|
|
53
|
-
|
|
54
|
-
const r = runCli(args, { stdin });
|
|
55
|
-
exitOnFailure(r);
|
|
56
|
-
relayStderr(r);
|
|
57
|
-
const result = parseJson(r.stdout);
|
|
58
|
-
if (flags.json) console.log(JSON.stringify(result ?? {}));
|
|
59
|
-
else console.log(`comment posted on ${ticket}${result && result.id ? ` (${result.id})` : ""}`);
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// create-ticket.mjs — file a new ticket on a team as the app actor. The team is named by its key;
|
|
3
|
-
// the CLI resolves the team id from the contract. A decision for a human is NOT a ticket filed here:
|
|
4
|
-
// that is an ask, raised through the what-needs-me skill so it carries options, a default and blocks.
|
|
5
|
-
import { exitOnFailure, parseFlags, parseJson, relayStderr, runCli, usage, wantsHelp } from "./lib/cli.mjs";
|
|
6
|
-
|
|
7
|
-
const HELP = `Usage: node scripts/create-ticket.mjs --team <key> --title <text> [--label <name|id>]... [--priority <0-4>] [--as-user] [--json]
|
|
8
|
-
|
|
9
|
-
Creates one ticket. Wraps: catalyst-skills write create. Spends one unit of the daily write budget.
|
|
10
|
-
|
|
11
|
-
--team <key> the team key (the prefix of its ticket identifiers)
|
|
12
|
-
--title <text> the ticket title
|
|
13
|
-
--label <name|id> a label to apply (repeatable); Catalyst label names resolve via the contract
|
|
14
|
-
--priority <0-4> Linear priority: 0 none, 1 urgent, 2 high, 3 medium, 4 low
|
|
15
|
-
--as-user create with the personal identity instead of the app actor (rare)
|
|
16
|
-
--json print the CLI's JSON result (carries the new identifier)
|
|
17
|
-
|
|
18
|
-
Never cite the new ticket's identifier until this script has printed it. Do not use this to ask a
|
|
19
|
-
human a question: raise an ask instead (what-needs-me), or the question will look like work.
|
|
20
|
-
|
|
21
|
-
Exit 0 created, 1 on a usage error, 2 when this machine is not connected to a tenant or the write
|
|
22
|
-
was refused (an unknown team key, the daily budget spent; the line says which).`;
|
|
23
|
-
|
|
24
|
-
const argv = process.argv.slice(2);
|
|
25
|
-
if (wantsHelp(argv)) {
|
|
26
|
-
console.log(HELP);
|
|
27
|
-
process.exit(0);
|
|
28
|
-
}
|
|
29
|
-
const { flags } = parseFlags(argv, { bool: ["as-user", "json"], value: ["team", "title", "priority"], repeat: ["label"] });
|
|
30
|
-
if (!flags.team || !flags.title) usage("create-ticket needs --team <key> and --title <text> (see --help)");
|
|
31
|
-
|
|
32
|
-
const args = ["write", "create", "--team", flags.team, "--title", flags.title, "--json"];
|
|
33
|
-
for (const l of flags.label ?? []) args.push("--label", l);
|
|
34
|
-
if (flags.priority !== undefined) args.push("--priority", flags.priority);
|
|
35
|
-
if (flags["as-user"]) args.push("--as-user");
|
|
36
|
-
const r = runCli(args);
|
|
37
|
-
exitOnFailure(r);
|
|
38
|
-
relayStderr(r);
|
|
39
|
-
const result = parseJson(r.stdout);
|
|
40
|
-
if (flags.json) console.log(JSON.stringify(result ?? {}));
|
|
41
|
-
else {
|
|
42
|
-
const ident = result && (result.identifier ?? result.id);
|
|
43
|
-
console.log(`created${ident ? ` ${ident}` : ""} on team ${flags.team}: ${flags.title}`);
|
|
44
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// label.mjs — add or remove labels on a ticket. A Catalyst label name (ask, hold, release) resolves
|
|
3
|
-
// to the id the contract lists for this team; anything else is passed through as a label id.
|
|
4
|
-
import { exitOnFailure, parseFlags, parseJson, relayStderr, runCli, usage, wantsHelp } from "./lib/cli.mjs";
|
|
5
|
-
|
|
6
|
-
const HELP = `Usage: node scripts/label.mjs <ticket> [--add <name|id>]... [--remove <name|id>]... [--json]
|
|
7
|
-
|
|
8
|
-
Adds and/or removes labels. Wraps: catalyst-skills write label. One write-budget unit per call
|
|
9
|
-
direction (add, remove), whatever the label count.
|
|
10
|
-
|
|
11
|
-
<ticket> the Linear identifier, e.g. KEY-123
|
|
12
|
-
--add <name|id> a label to add (repeatable)
|
|
13
|
-
--remove <name|id> a label to remove (repeatable)
|
|
14
|
-
--json print the CLI's JSON result
|
|
15
|
-
|
|
16
|
-
Names the contract knows (the ask marker, the hold label, the release label) resolve to this team's
|
|
17
|
-
label id; a name the workspace lacks is refused rather than guessed. Any other value is sent as a
|
|
18
|
-
label id unchanged.
|
|
19
|
-
|
|
20
|
-
Exit 0 written, 1 on a usage error, 2 when this machine is not connected to a tenant or the write
|
|
21
|
-
was refused (a Catalyst label the workspace lacks, the daily budget spent; the line says which).`;
|
|
22
|
-
|
|
23
|
-
const argv = process.argv.slice(2);
|
|
24
|
-
if (wantsHelp(argv)) {
|
|
25
|
-
console.log(HELP);
|
|
26
|
-
process.exit(0);
|
|
27
|
-
}
|
|
28
|
-
const { flags, positionals } = parseFlags(argv, { bool: ["json"], repeat: ["add", "remove"] });
|
|
29
|
-
const ticket = positionals[0];
|
|
30
|
-
if (!ticket) usage("label needs a ticket identifier (see --help)");
|
|
31
|
-
const add = flags.add ?? [];
|
|
32
|
-
const remove = flags.remove ?? [];
|
|
33
|
-
if (add.length === 0 && remove.length === 0) usage("label needs --add and/or --remove (see --help)");
|
|
34
|
-
|
|
35
|
-
const args = ["write", "label", ticket, "--json"];
|
|
36
|
-
for (const a of add) args.push("--add", a);
|
|
37
|
-
for (const x of remove) args.push("--remove", x);
|
|
38
|
-
const r = runCli(args);
|
|
39
|
-
exitOnFailure(r);
|
|
40
|
-
relayStderr(r);
|
|
41
|
-
const result = parseJson(r.stdout);
|
|
42
|
-
if (flags.json) console.log(JSON.stringify(result ?? {}));
|
|
43
|
-
else {
|
|
44
|
-
const bits = [];
|
|
45
|
-
if (add.length) bits.push(`added ${add.join(", ")}`);
|
|
46
|
-
if (remove.length) bits.push(`removed ${remove.join(", ")}`);
|
|
47
|
-
console.log(`${ticket}: ${bits.join("; ")}`);
|
|
48
|
-
}
|