@catalyst-cloud/cli 0.15.2 → 0.15.3
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 +7 -0
- package/README.md +2 -2
- package/dist/cli.js +5 -17
- package/dist/oauth.js +1 -1
- package/dist/onboard-accounts.js +67 -171
- package/dist/onboard-checklist.js +309 -0
- package/dist/onboard-existing.js +1 -1
- package/dist/onboard-first-ticket.js +199 -0
- package/dist/onboard-headless.js +2 -2
- package/dist/onboard-housekeeping.js +93 -0
- package/dist/onboard-next.js +108 -9
- package/dist/onboard-ready.js +61 -0
- package/dist/onboard-runtime.js +60 -27
- package/dist/onboard-standalone-copy.js +73 -66
- package/dist/onboard-ui.js +162 -89
- package/dist/onboard-values.js +47 -0
- package/dist/onboard.js +49 -13
- package/dist/ready-copy.js +87 -0
- package/dist/ready.js +1 -57
- package/dist/setup-onboard-copy.js +237 -41
- package/dist/setup-render.js +73 -16
- package/dist/setup.js +6 -3
- package/package.json +1 -1
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +3 -3
- package/skills/catalyst-onboard/references/replacing-a-credential.md +9 -15
- package/skills/catalyst-onboard/references/what-a-phase-needs.md +4 -11
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +1 -1
- package/skills/run-this-project/SKILL.md +2 -2
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +2 -2
- package/skills/whats-happening/references/coding-accounts.md +1 -1
- package/skills/whats-happening/scripts/snapshot.mjs +1 -1
package/dist/onboard.js
CHANGED
|
@@ -10,7 +10,7 @@ import { onboardJsonView, TIMED_OUT_REASONS } from "./setup-onboard-copy.js";
|
|
|
10
10
|
import { standalonePlan, standalonePlanNotes, } from "./onboard-standalone-copy.js";
|
|
11
11
|
import { CliError, UsageError } from "./errors.js";
|
|
12
12
|
import { onboardFileSnapshot } from "./onboard-file-snapshot.js";
|
|
13
|
-
import { onboardReasonText, onboardTeamKey, savedOnboardBaseUrl, } from "./onboard-next.js";
|
|
13
|
+
import { dispatchStageName, onboardReasonText, onboardTeamKey, savedOnboardBaseUrl, } from "./onboard-next.js";
|
|
14
14
|
import { parseBootstrapPlan, bootstrapPlanHash, bootstrapPlanLines, } from "./onboard-bootstrap.js";
|
|
15
15
|
export const ONBOARD_STEPS = [
|
|
16
16
|
"machine",
|
|
@@ -37,6 +37,14 @@ export const ONBOARD_STEPS = [
|
|
|
37
37
|
"first-ticket",
|
|
38
38
|
"ready",
|
|
39
39
|
];
|
|
40
|
+
function dailyUpdateChoice(value) {
|
|
41
|
+
const row = object(value);
|
|
42
|
+
if (row?.state === "on" || row?.state === "off")
|
|
43
|
+
return { state: row.state };
|
|
44
|
+
if (row?.state === "skipped" && row.reason === "no_scheduler")
|
|
45
|
+
return { state: "skipped", reason: "no_scheduler" };
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
40
48
|
export function onboardIdentityLines(identity) {
|
|
41
49
|
if (!identity)
|
|
42
50
|
return [
|
|
@@ -104,7 +112,8 @@ export const ONBOARD_DEPENDENCIES = {
|
|
|
104
112
|
projects: ["linear.team", "github.repos"],
|
|
105
113
|
accounts: ["signin"],
|
|
106
114
|
settings: ["projects"],
|
|
107
|
-
values
|
|
115
|
+
// The cloud's required-values check reads the registered repositories, not a local checkout.
|
|
116
|
+
values: ["projects"],
|
|
108
117
|
capacity: ["projects"],
|
|
109
118
|
// The join token names the selected team's pool.
|
|
110
119
|
runner: ["linear.team"],
|
|
@@ -127,7 +136,7 @@ const ADMIN_STEPS = new Set([
|
|
|
127
136
|
"github.repos",
|
|
128
137
|
"projects",
|
|
129
138
|
"settings",
|
|
130
|
-
|
|
139
|
+
// `values` is not here: its check only reads, so a member's run finds missing values too.
|
|
131
140
|
"capacity",
|
|
132
141
|
"runner",
|
|
133
142
|
"first-ticket",
|
|
@@ -147,9 +156,9 @@ export const ONBOARD_TITLES = {
|
|
|
147
156
|
"github.personal": "Connect your GitHub account",
|
|
148
157
|
"github.repos": "Choose repositories",
|
|
149
158
|
projects: "Register your projects",
|
|
150
|
-
accounts: "Check
|
|
159
|
+
accounts: "Check AI accounts",
|
|
151
160
|
settings: "Review repository settings",
|
|
152
|
-
values: "
|
|
161
|
+
values: "Check the repository's values",
|
|
153
162
|
capacity: "Check runner capacity",
|
|
154
163
|
runner: "Run Catalyst's work on this machine",
|
|
155
164
|
daemon: "Check optional local sync",
|
|
@@ -251,6 +260,10 @@ function normalizeStep(value, fallbackAt) {
|
|
|
251
260
|
"repository",
|
|
252
261
|
"installation",
|
|
253
262
|
"ticket",
|
|
263
|
+
// The team's dispatch stage name, as the contract gave it.
|
|
264
|
+
"stage",
|
|
265
|
+
// Admin-owned step ids a member's run found unfinished.
|
|
266
|
+
"admin",
|
|
254
267
|
"cursor",
|
|
255
268
|
"lag",
|
|
256
269
|
"heartbeatAgeMs",
|
|
@@ -288,6 +301,8 @@ function normalizeStep(value, fallbackAt) {
|
|
|
288
301
|
"org",
|
|
289
302
|
"url",
|
|
290
303
|
"actor",
|
|
304
|
+
// CTC-4716: which AI accounts the workspace may add, so the step's text offers only those.
|
|
305
|
+
"aiAccountKinds",
|
|
291
306
|
]) {
|
|
292
307
|
const item = evidence?.[key];
|
|
293
308
|
if (item === null ||
|
|
@@ -323,6 +338,7 @@ export function readOnboardJournal(path, cliVersion = "0.13.1", now = new Date()
|
|
|
323
338
|
: typeof value.exitCode === "number"
|
|
324
339
|
? value.exitCode
|
|
325
340
|
: null;
|
|
341
|
+
const dailyUpdate = dailyUpdateChoice(value.dailyUpdate);
|
|
326
342
|
return {
|
|
327
343
|
schema: 1,
|
|
328
344
|
runId: typeof value.runId === "string" ? value.runId : randomUUID(),
|
|
@@ -354,6 +370,7 @@ export function readOnboardJournal(path, cliVersion = "0.13.1", now = new Date()
|
|
|
354
370
|
...(typeof value.localSync === "boolean"
|
|
355
371
|
? { localSync: value.localSync }
|
|
356
372
|
: {}),
|
|
373
|
+
...(dailyUpdate ? { dailyUpdate } : {}),
|
|
357
374
|
operations: Object.fromEntries(Object.entries(object(value.operations) ?? {}).filter(([key, v]) => ONBOARD_STEPS.includes(key) &&
|
|
358
375
|
typeof v === "string" &&
|
|
359
376
|
/^[a-zA-Z0-9_.:-]{1,160}$/.test(v))),
|
|
@@ -573,7 +590,8 @@ function printPlan(ctx, journal, identity, options) {
|
|
|
573
590
|
for (const line of onboardIdentityLines(identity))
|
|
574
591
|
ctx.stdout(line);
|
|
575
592
|
let group;
|
|
576
|
-
|
|
593
|
+
// Beside --json and headless output: the numbers JSON actions carry (CTC-4680).
|
|
594
|
+
for (const row of standalonePlan(journal, options?.scope, options?.runner, "json")) {
|
|
577
595
|
if (row.group !== group) {
|
|
578
596
|
ctx.stdout("");
|
|
579
597
|
ctx.stdout(row.group);
|
|
@@ -629,14 +647,28 @@ export function stepSatisfied(step) {
|
|
|
629
647
|
return (step?.state === "done" ||
|
|
630
648
|
(step?.state === "skipped" &&
|
|
631
649
|
(step.id === "legacy" ||
|
|
650
|
+
// Optional: a computer without the daily update still runs work.
|
|
651
|
+
step.id === "housekeeping" ||
|
|
632
652
|
step.reason === "member_scope" ||
|
|
633
653
|
(step.id === "daemon" && step.reason === "local_sync_not_selected") ||
|
|
634
654
|
(step.id === "runner" && step.reason === "runner_not_selected") ||
|
|
655
|
+
(step.id === "first-ticket" && step.reason === "first_ticket_skipped") ||
|
|
635
656
|
(step.id === "linear.automations" &&
|
|
636
657
|
step.reason === "automation_management_unavailable"))));
|
|
637
658
|
}
|
|
659
|
+
/** Optional work this computer or checkout cannot do. Work runs without it, so its wait never holds
|
|
660
|
+
* a complete setup. */
|
|
661
|
+
const OPTIONAL_WAITS = new Set([
|
|
662
|
+
"settings_checkout_unverified",
|
|
663
|
+
"settings_approval_unverified",
|
|
664
|
+
]);
|
|
665
|
+
/** Satisfied, or an optional wait. What a complete setup needs of every step. */
|
|
666
|
+
export function stepSettled(step) {
|
|
667
|
+
return (stepSatisfied(step) ||
|
|
668
|
+
(step?.state === "waiting" && OPTIONAL_WAITS.has(step.reason ?? "")));
|
|
669
|
+
}
|
|
638
670
|
/** Steps whose wait does not hold a full run's exit code: work runs without them. A failure still
|
|
639
|
-
* fails the run, and the receipt's `complete` still needs them
|
|
671
|
+
* fails the run, and the receipt's `complete` still needs them settled. Never used for gating. */
|
|
640
672
|
export const ONBOARD_DEFERRED_STEPS = new Set([
|
|
641
673
|
"settings",
|
|
642
674
|
"values",
|
|
@@ -677,8 +709,11 @@ export function onboardNextActions(journal, baseUrl, only) {
|
|
|
677
709
|
.filter((step) => (!only || step.id === only) && !stepSatisfied(step))
|
|
678
710
|
.sort((a, b) => ONBOARD_STEPS.indexOf(a.id) - ONBOARD_STEPS.indexOf(b.id))
|
|
679
711
|
.map((step) => `${ONBOARD_TITLES[step.id]}: ${onboardStepAction(journal, step, baseUrl)}`);
|
|
680
|
-
|
|
681
|
-
|
|
712
|
+
// A waiting first ticket already says to move a ticket, in its own line.
|
|
713
|
+
const firstTicket = journalStep(journal, "first-ticket");
|
|
714
|
+
if (["projects", "accounts", "linear.adopt", "capacity"].every((id) => journalStep(journal, id)?.state === "done") &&
|
|
715
|
+
firstTicket?.state !== "waiting")
|
|
716
|
+
lines.push(`Move a ticket in ${onboardTeamKey(journal) ?? "<TEAM KEY>"} to ${dispatchStageName(firstTicket?.evidence?.stage)}; \`catalyst explain <ticket>\` says why it is or is not starting.`);
|
|
682
717
|
return lines;
|
|
683
718
|
}
|
|
684
719
|
function requireMatchingIdentity(journal, identity) {
|
|
@@ -727,9 +762,10 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
727
762
|
const only = typeof args.flags.only === "string"
|
|
728
763
|
? args.flags.only
|
|
729
764
|
: undefined;
|
|
765
|
+
// A member's values check reads the selected team, so without --team it waits on the same skip.
|
|
730
766
|
const memberScopeSkips = (id) => ADMIN_STEPS.has(id) ||
|
|
731
|
-
(id === "linear.team" &&
|
|
732
|
-
only !==
|
|
767
|
+
((id === "linear.team" || id === "values") &&
|
|
768
|
+
only !== id &&
|
|
733
769
|
typeof args.flags.team !== "string");
|
|
734
770
|
const resume = typeof args.flags["resume-from"] === "string"
|
|
735
771
|
? args.flags["resume-from"]
|
|
@@ -1069,7 +1105,7 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1069
1105
|
journal.complete =
|
|
1070
1106
|
!only &&
|
|
1071
1107
|
code === 0 &&
|
|
1072
|
-
ONBOARD_STEPS.every((id) =>
|
|
1108
|
+
ONBOARD_STEPS.every((id) => stepSettled(journalStep(journal, id)));
|
|
1073
1109
|
writeOnboardJournal(statePath, journal);
|
|
1074
1110
|
if (args.json)
|
|
1075
1111
|
ctx.stdout(JSON.stringify(journal));
|
|
@@ -1442,7 +1478,7 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1442
1478
|
journal.complete =
|
|
1443
1479
|
!only &&
|
|
1444
1480
|
code === 0 &&
|
|
1445
|
-
ONBOARD_STEPS.every((id) =>
|
|
1481
|
+
ONBOARD_STEPS.every((id) => stepSettled(journalStep(journal, id)));
|
|
1446
1482
|
const unfinished = journal.steps.filter((step) => !stepSatisfied(step));
|
|
1447
1483
|
if (!only &&
|
|
1448
1484
|
!refused &&
|
package/dist/ready-copy.js
CHANGED
|
@@ -65,6 +65,93 @@ function slotList(slots) {
|
|
|
65
65
|
function origin(baseUrl) {
|
|
66
66
|
return baseUrl.replace(/\/+$/, "");
|
|
67
67
|
}
|
|
68
|
+
const NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
69
|
+
const REPOSITORY = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}\/[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$/;
|
|
70
|
+
const identifiers = (value) => Array.isArray(value)
|
|
71
|
+
? value.filter((name) => typeof name === "string" && NAME.test(name))
|
|
72
|
+
: [];
|
|
73
|
+
const unresolvedFacts = (value) => Array.isArray(value)
|
|
74
|
+
? value.flatMap((row) => typeof row?.name === "string" && NAME.test(row.name) && identifiers(row.references).length
|
|
75
|
+
? [{ name: row.name, references: identifiers(row.references) }]
|
|
76
|
+
: [])
|
|
77
|
+
: [];
|
|
78
|
+
/** The declared names a failing check carries, kept in the receipt for the fix line: which variables
|
|
79
|
+
* need a value, which refer to a secret with no value, and the same for the team's other
|
|
80
|
+
* repositories. Identifiers and repository names only. */
|
|
81
|
+
export function valuesFacts(value) {
|
|
82
|
+
const row = (value ?? {});
|
|
83
|
+
return {
|
|
84
|
+
names: identifiers(row.names),
|
|
85
|
+
unresolved: unresolvedFacts(row.unresolved),
|
|
86
|
+
repos: Array.isArray(row.repos)
|
|
87
|
+
? row.repos.flatMap((note) => typeof note?.repo === "string" && REPOSITORY.test(note.repo)
|
|
88
|
+
? [{ repo: note.repo, names: identifiers(note.names), unresolved: unresolvedFacts(note.unresolved) }]
|
|
89
|
+
: [])
|
|
90
|
+
: [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Where the named variables get their values. Names only: nothing here reads or prints a value. */
|
|
94
|
+
function setValuesFix(baseUrl, names) {
|
|
95
|
+
return baseUrl
|
|
96
|
+
? `set ${names.join(", ")} on the repository's Environment page: open ${origin(baseUrl)}/settings/projects, then the project, then the repository`
|
|
97
|
+
: `set ${names.join(", ")} on the repository's Environment page in Catalyst's project settings`;
|
|
98
|
+
}
|
|
99
|
+
function nameList(v) {
|
|
100
|
+
return Array.isArray(v)
|
|
101
|
+
? v.filter((n) => typeof n === "string" && n.length > 0)
|
|
102
|
+
: [];
|
|
103
|
+
}
|
|
104
|
+
/** Entries whose name and references are both present; anything malformed is dropped, not guessed. */
|
|
105
|
+
function unresolvedList(v) {
|
|
106
|
+
if (!Array.isArray(v))
|
|
107
|
+
return [];
|
|
108
|
+
return v.flatMap((u) => {
|
|
109
|
+
if (typeof u !== "object" || u === null)
|
|
110
|
+
return [];
|
|
111
|
+
const { name, references } = u;
|
|
112
|
+
const refs = nameList(references);
|
|
113
|
+
return typeof name === "string" && name.length > 0 && refs.length > 0
|
|
114
|
+
? [{ name, references: refs }]
|
|
115
|
+
: [];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function unresolvedLine(u) {
|
|
119
|
+
const which = u.references.length === 1 ? "which has" : "which have";
|
|
120
|
+
return `${u.name} refers to ${u.references.join(", ")}, ${which} no value, so Catalyst can't start work there`;
|
|
121
|
+
}
|
|
122
|
+
/** A team check's fix line. A check that carries `names` (CTC-3561: `required_values`, contract
|
|
123
|
+
* 1.24.0) names them and where to set them. CTC-3606: it also names each `unresolved` variable and
|
|
124
|
+
* the reference with no value, and each other repository's missing names from `repos[]`. `unresolved`
|
|
125
|
+
* is a subset of `names` (the variable exists; its reference does not), so those are not told to be
|
|
126
|
+
* "set". Every field is optional so an older cloud still works. Everything printed is a declared
|
|
127
|
+
* identifier or a repository name, never a value, and nothing here reads a value. Any other check
|
|
128
|
+
* keeps the generic line. Shared with setup's values step, which has no web address when the
|
|
129
|
+
* saved login has none. */
|
|
130
|
+
export function valuesFix(base, c) {
|
|
131
|
+
const unresolved = unresolvedList(c.unresolved);
|
|
132
|
+
const unresolvedNames = new Set(unresolved.map((u) => u.name));
|
|
133
|
+
const names = nameList(c.names).filter((n) => !unresolvedNames.has(n));
|
|
134
|
+
const parts = [];
|
|
135
|
+
if (names.length > 0)
|
|
136
|
+
parts.push(setValuesFix(base, names));
|
|
137
|
+
for (const u of unresolved)
|
|
138
|
+
parts.push(unresolvedLine(u));
|
|
139
|
+
for (const note of Array.isArray(c.repos) ? c.repos : []) {
|
|
140
|
+
if (typeof note !== "object" ||
|
|
141
|
+
note === null ||
|
|
142
|
+
typeof note.repo !== "string" ||
|
|
143
|
+
note.repo.length === 0)
|
|
144
|
+
continue;
|
|
145
|
+
const repoUnresolved = unresolvedList(note.unresolved);
|
|
146
|
+
const skip = new Set(repoUnresolved.map((u) => u.name));
|
|
147
|
+
const missing = nameList(note.names).filter((n) => !skip.has(n));
|
|
148
|
+
if (missing.length > 0)
|
|
149
|
+
parts.push(`${note.repo} is missing ${missing.join(", ")}`);
|
|
150
|
+
for (const u of repoUnresolved)
|
|
151
|
+
parts.push(`in ${note.repo}, ${unresolvedLine(u)}`);
|
|
152
|
+
}
|
|
153
|
+
return parts.length === 0 ? null : parts.join(". ");
|
|
154
|
+
}
|
|
68
155
|
/** The team's own page in Catalyst settings, optionally one of its sub-pages (`map`, `adopt`). */
|
|
69
156
|
export function teamPage(baseUrl, key, sub) {
|
|
70
157
|
return `${origin(baseUrl)}/settings/linear-teams/${encodeURIComponent(key)}${sub ? `/${sub}` : ""}`;
|
package/dist/ready.js
CHANGED
|
@@ -15,7 +15,7 @@ import { FIRST_STAMPED_VERSION } from "./skill-shape.js";
|
|
|
15
15
|
import { installedBundleVersion } from "./skills.js";
|
|
16
16
|
import { onboardingReadyReport, observeCloudOnboarding, } from "./onboard-ready.js";
|
|
17
17
|
import { selectedOnboardTeam } from "./onboard-existing.js";
|
|
18
|
-
import { dispatchGateCopy, teamCheckCopy, teamPage, whoFixes } from "./ready-copy.js";
|
|
18
|
+
import { dispatchGateCopy, teamCheckCopy, teamPage, valuesFix, whoFixes } from "./ready-copy.js";
|
|
19
19
|
import { onboardStatePath, readOnboardJournal } from "./onboard.js";
|
|
20
20
|
export { semverOlder };
|
|
21
21
|
/** `ready`'s own wording for the replica. It never recommends adopting the replica — until the
|
|
@@ -112,62 +112,6 @@ export function catalystCommandCheck(env, platform = process.platform) {
|
|
|
112
112
|
"Keep using catalyst-skills on this machine, or put the npm global bin directory ahead of that one on PATH.",
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
|
-
function nameList(v) {
|
|
116
|
-
return Array.isArray(v)
|
|
117
|
-
? v.filter((n) => typeof n === "string" && n.length > 0)
|
|
118
|
-
: [];
|
|
119
|
-
}
|
|
120
|
-
/** Entries whose name and references are both present; anything malformed is dropped, not guessed. */
|
|
121
|
-
function unresolvedList(v) {
|
|
122
|
-
if (!Array.isArray(v))
|
|
123
|
-
return [];
|
|
124
|
-
return v.flatMap((u) => {
|
|
125
|
-
if (typeof u !== "object" || u === null)
|
|
126
|
-
return [];
|
|
127
|
-
const { name, references } = u;
|
|
128
|
-
const refs = nameList(references);
|
|
129
|
-
return typeof name === "string" && name.length > 0 && refs.length > 0
|
|
130
|
-
? [{ name, references: refs }]
|
|
131
|
-
: [];
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
function unresolvedLine(u) {
|
|
135
|
-
const which = u.references.length === 1 ? "which has" : "which have";
|
|
136
|
-
return `${u.name} refers to ${u.references.join(", ")}, ${which} no value, so Catalyst can't start work there`;
|
|
137
|
-
}
|
|
138
|
-
/** A team check's fix line. A check that carries `names` (CTC-3561: `required_values`, contract
|
|
139
|
-
* 1.24.0) names them and where to set them. CTC-3606: it also names each `unresolved` variable and
|
|
140
|
-
* the reference with no value, and each other repository's missing names from `repos[]`. `unresolved`
|
|
141
|
-
* is a subset of `names` (the variable exists; its reference does not), so those are not told to be
|
|
142
|
-
* "set". Every field is optional so an older cloud still works. Everything printed is a declared
|
|
143
|
-
* identifier or a repository name, never a value, and nothing here reads a value. Any other check
|
|
144
|
-
* keeps the generic line. */
|
|
145
|
-
function valuesFix(base, c) {
|
|
146
|
-
const unresolved = unresolvedList(c.unresolved);
|
|
147
|
-
const unresolvedNames = new Set(unresolved.map((u) => u.name));
|
|
148
|
-
const names = nameList(c.names).filter((n) => !unresolvedNames.has(n));
|
|
149
|
-
const parts = [];
|
|
150
|
-
if (names.length > 0) {
|
|
151
|
-
parts.push(`set ${names.join(", ")} on the repository's Environment page: open ${base}/settings/projects, then the project, then the repository`);
|
|
152
|
-
}
|
|
153
|
-
for (const u of unresolved)
|
|
154
|
-
parts.push(unresolvedLine(u));
|
|
155
|
-
for (const note of Array.isArray(c.repos) ? c.repos : []) {
|
|
156
|
-
if (typeof note !== "object" ||
|
|
157
|
-
note === null ||
|
|
158
|
-
typeof note.repo !== "string" ||
|
|
159
|
-
note.repo.length === 0)
|
|
160
|
-
continue;
|
|
161
|
-
const repoUnresolved = unresolvedList(note.unresolved);
|
|
162
|
-
const skip = new Set(repoUnresolved.map((u) => u.name));
|
|
163
|
-
const missing = nameList(note.names).filter((n) => !skip.has(n));
|
|
164
|
-
if (missing.length > 0)
|
|
165
|
-
parts.push(`${note.repo} is missing ${missing.join(", ")}`);
|
|
166
|
-
for (const u of repoUnresolved)
|
|
167
|
-
parts.push(`in ${note.repo}, ${unresolvedLine(u)}`);
|
|
168
|
-
}
|
|
169
|
-
return parts.length === 0 ? null : parts.join(". ");
|
|
170
|
-
}
|
|
171
115
|
export async function readyReport(ctx, deps) {
|
|
172
116
|
const checks = [];
|
|
173
117
|
const facts = deps.runtime ?? detectRuntime();
|