@detergent-software/conductor 1.4.0-dev.1 → 1.4.0-dev.10
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 +91 -0
- package/README.md +80 -17
- package/build/brief-cli.js +105 -23
- package/build/brief-cli.js.map +1 -1
- package/build/brief.js +107 -9
- package/build/brief.js.map +1 -1
- package/build/broker.js +40 -14
- package/build/broker.js.map +1 -1
- package/build/cli.js +49 -18
- package/build/cli.js.map +1 -1
- package/build/code-host-cli.js +46 -2
- package/build/code-host-cli.js.map +1 -1
- package/build/code-host.js +2 -0
- package/build/code-host.js.map +1 -1
- package/build/conditions.js +22 -13
- package/build/conditions.js.map +1 -1
- package/build/daemon.js +64 -16
- package/build/daemon.js.map +1 -1
- package/build/follow-ups.js +72 -0
- package/build/follow-ups.js.map +1 -0
- package/build/help.js +712 -208
- package/build/help.js.map +1 -1
- package/build/in-process.js +30 -0
- package/build/in-process.js.map +1 -0
- package/build/inbox.js +113 -11
- package/build/inbox.js.map +1 -1
- package/build/lib/providers/ado/code-host.js +8 -5
- package/build/lib/providers/ado/code-host.js.map +1 -1
- package/build/lib/providers/ado/pipelines.js +16 -10
- package/build/lib/providers/ado/pipelines.js.map +1 -1
- package/build/lib/providers/github/actions.js +11 -0
- package/build/lib/providers/github/actions.js.map +1 -1
- package/build/lib/providers/github/code-host.js +17 -0
- package/build/lib/providers/github/code-host.js.map +1 -1
- package/build/lib/providers/index.js +1 -1
- package/build/lib/providers/index.js.map +1 -1
- package/build/lib/providers/verdicts/pr.js +14 -12
- package/build/lib/providers/verdicts/pr.js.map +1 -1
- package/build/manager.js +8 -3
- package/build/manager.js.map +1 -1
- package/build/note.js +95 -14
- package/build/note.js.map +1 -1
- package/build/notes.js +104 -16
- package/build/notes.js.map +1 -1
- package/build/notify.js +2 -1
- package/build/notify.js.map +1 -1
- package/build/package-info.js +12 -0
- package/build/package-info.js.map +1 -0
- package/build/profile.js +64 -13
- package/build/profile.js.map +1 -1
- package/build/project-check-cli.js +85 -0
- package/build/project-check-cli.js.map +1 -0
- package/build/project-check.js +362 -0
- package/build/project-check.js.map +1 -0
- package/build/resolve.js +11 -3
- package/build/resolve.js.map +1 -1
- package/build/rule-actions.js +9 -0
- package/build/rule-actions.js.map +1 -1
- package/build/rules-service.js +28 -19
- package/build/rules-service.js.map +1 -1
- package/build/runs.js.map +1 -1
- package/build/scheduler.js +43 -1
- package/build/scheduler.js.map +1 -1
- package/build/server.js +76 -7
- package/build/server.js.map +1 -1
- package/build/store.js +27 -4
- package/build/store.js.map +1 -1
- package/build/tracker-writes.js +74 -0
- package/build/tracker-writes.js.map +1 -0
- package/build/trackers.js +20 -0
- package/build/trackers.js.map +1 -1
- package/build/watchers/ado.js +7 -1
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/boards-writes.js +182 -34
- package/build/watchers/boards-writes.js.map +1 -1
- package/build/watchers/boards.js +7 -2
- package/build/watchers/boards.js.map +1 -1
- package/build/watchers/github.js +20 -2
- package/build/watchers/github.js.map +1 -1
- package/build/watchers/index.js +32 -4
- package/build/watchers/index.js.map +1 -1
- package/build/watchers/issues-brief.js +48 -0
- package/build/watchers/issues-brief.js.map +1 -0
- package/build/watchers/issues-queue.js +85 -0
- package/build/watchers/issues-queue.js.map +1 -0
- package/build/watchers/issues-writes.js +233 -0
- package/build/watchers/issues-writes.js.map +1 -0
- package/build/watchers/issues.js +264 -0
- package/build/watchers/issues.js.map +1 -0
- package/build/watchers/linear-writes.js +198 -0
- package/build/watchers/linear-writes.js.map +1 -0
- package/build/watchers/source.js.map +1 -1
- package/build/worktrees.js +1 -0
- package/build/worktrees.js.map +1 -1
- package/mod/hooks/model.ts +11 -0
- package/mod/hooks/register.tsx +25 -3
- package/package.json +1 -1
package/build/profile.js
CHANGED
|
@@ -11,6 +11,12 @@ export const PROFILE_FILE = join('.claude', 'conductor.json');
|
|
|
11
11
|
const name = z.string().regex(/^[a-z][a-z0-9-]*$/, 'lowercase letters, digits and dashes, starting with a letter');
|
|
12
12
|
const text = z.string().trim().min(1);
|
|
13
13
|
const port = z.int().min(1).max(65535);
|
|
14
|
+
// The state each phase note moves an item to, over the tracker kind's defaults (tracker-writes.ts);
|
|
15
|
+
// null leaves it as it is.
|
|
16
|
+
const transitions = z
|
|
17
|
+
.strictObject({ landed: text.nullable(), 'pr-opened': text.nullable(), started: text.nullable() })
|
|
18
|
+
.partial()
|
|
19
|
+
.optional();
|
|
14
20
|
// A repo's .claude/conductor.json: what the conductor needs to know about a project, as data.
|
|
15
21
|
// Each adapter kind is a discriminated union, so ADO Boards (Cadence) arrives as one more variant.
|
|
16
22
|
const profileFields = z.strictObject({
|
|
@@ -71,6 +77,7 @@ const profileFields = z.strictObject({
|
|
|
71
77
|
// Done blocker has a comment containing `shipped` (the release skill's note). Off when absent.
|
|
72
78
|
releaseWait: z.strictObject({ label: text, shipped: text }).optional(),
|
|
73
79
|
team: text,
|
|
80
|
+
transitions,
|
|
74
81
|
// A new item a worker filed carries this label until Jason triages it: the queue never picks it,
|
|
75
82
|
// and the inbox says it is new (ATK-277). Off when absent.
|
|
76
83
|
triageLabel: text.optional(),
|
|
@@ -93,8 +100,39 @@ const profileFields = z.strictObject({
|
|
|
93
100
|
query: text,
|
|
94
101
|
// State names to types, over the built-in map for the Scrum, Agile, Basic and CMMI processes.
|
|
95
102
|
states: z.record(text, z.enum(['backlog', 'unstarted', 'started', 'completed', 'canceled'])).default({}),
|
|
103
|
+
transitions,
|
|
96
104
|
// As the Linear tracker's, a tag here.
|
|
97
105
|
triageLabel: text.optional(),
|
|
106
|
+
// The type of work item `conduct file` creates.
|
|
107
|
+
workItemType: text.default('Product Backlog Item'),
|
|
108
|
+
}),
|
|
109
|
+
// GitHub Issues (providers design note, section 5; ATK-458). Issue numbers are numbers alone, so
|
|
110
|
+
// `key` names them, as on Boards: PAR-12 is issue #12. Areas, the human and triage marks and the
|
|
111
|
+
// queue are labels; in `transitions`, `started` and `pr-opened` name the label each phase puts on
|
|
112
|
+
// an issue, and `landed` closes it as completed (null leaves it open).
|
|
113
|
+
z.strictObject({
|
|
114
|
+
// An Area is a label with this prefix (area:docs is the Area docs).
|
|
115
|
+
areaPrefix: text.default('area:'),
|
|
116
|
+
humanLabel: text,
|
|
117
|
+
key: z.string().regex(/^[A-Z]+$/, 'capital letters, such as PAR'),
|
|
118
|
+
kind: z.literal('github-issues'),
|
|
119
|
+
// The repo the issues live in; by default the code host's.
|
|
120
|
+
owner: z
|
|
121
|
+
.string()
|
|
122
|
+
.regex(/^[\w.-]+$/, 'a GitHub owner, such as JasonPaff')
|
|
123
|
+
.optional(),
|
|
124
|
+
// The queue's priority against other projects' (a Linear project's 1, urgent, to 4, low).
|
|
125
|
+
priority: z.int().min(1).max(4).default(3),
|
|
126
|
+
// The conducted queue: the open issues carrying this label. GitHub reads a comma in its label
|
|
127
|
+
// filter as "and", so a label can't have one.
|
|
128
|
+
queueLabel: text.refine((l) => !l.includes(','), 'one label, without a comma').default('conduct'),
|
|
129
|
+
repo: z
|
|
130
|
+
.string()
|
|
131
|
+
.regex(/^[\w.-]+$/, 'a GitHub repo name, without the owner')
|
|
132
|
+
.optional(),
|
|
133
|
+
transitions,
|
|
134
|
+
// As the Linear tracker's, a label here.
|
|
135
|
+
triageLabel: text.optional(),
|
|
98
136
|
}),
|
|
99
137
|
]),
|
|
100
138
|
// Skill trains (train.ts, ATK-285): the GitHub repo whose PRs carrying a `<work skill> · Train`
|
|
@@ -130,20 +168,32 @@ const profileFields = z.strictObject({
|
|
|
130
168
|
// attached to its background session.
|
|
131
169
|
workers: z.strictObject({ tab: z.boolean().default(false) }).prefault({}),
|
|
132
170
|
});
|
|
133
|
-
// ADO Boards takes its organization and project from an Azure DevOps code host
|
|
134
|
-
// one
|
|
171
|
+
// ADO Boards takes its organization and project from an Azure DevOps code host, and GitHub Issues
|
|
172
|
+
// its owner and repo from a GitHub one; beside a code host of the other kind each names them.
|
|
135
173
|
export const profileSchema = profileFields.superRefine((profile, ctx) => {
|
|
136
174
|
const { codeHost, tracker } = profile;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
175
|
+
const missing = (key, what) => ctx.addIssue({
|
|
176
|
+
code: 'custom',
|
|
177
|
+
message: `required beside a ${codeHost.kind} code host: ${what}`,
|
|
178
|
+
path: ['tracker', key],
|
|
179
|
+
});
|
|
180
|
+
if (tracker.kind === 'ado-boards' && codeHost.kind !== 'ado') {
|
|
181
|
+
for (const key of ['organization', 'project'])
|
|
182
|
+
if (tracker[key] === undefined)
|
|
183
|
+
missing(key, `the board's Azure DevOps ${key}`);
|
|
184
|
+
}
|
|
185
|
+
if (tracker.kind === 'github-issues' && codeHost.kind !== 'github') {
|
|
186
|
+
for (const key of ['owner', 'repo'])
|
|
187
|
+
if (tracker[key] === undefined)
|
|
188
|
+
missing(key, `the issues' GitHub ${key}`);
|
|
189
|
+
}
|
|
190
|
+
// One of them alone would pair with the code host's other half, naming a repo nobody meant.
|
|
191
|
+
if (tracker.kind === 'github-issues' && (tracker.owner === undefined) !== (tracker.repo === undefined)) {
|
|
192
|
+
ctx.addIssue({
|
|
193
|
+
code: 'custom',
|
|
194
|
+
message: 'set owner and repo together, or neither for the code host repo',
|
|
195
|
+
path: ['tracker', tracker.owner === undefined ? 'owner' : 'repo'],
|
|
196
|
+
});
|
|
147
197
|
}
|
|
148
198
|
});
|
|
149
199
|
// The profile's base ports by name, without maxSlot.
|
|
@@ -169,7 +219,8 @@ export function releasesOf(profile) {
|
|
|
169
219
|
pipeline,
|
|
170
220
|
}));
|
|
171
221
|
}
|
|
172
|
-
// The key prefix of the project's items: the Linear team (ATK-311), or the Boards
|
|
222
|
+
// The key prefix of the project's items: the Linear team (ATK-311), or the Boards or GitHub Issues
|
|
223
|
+
// key (CAD-1234, PAR-12).
|
|
173
224
|
export function trackerKey(profile) {
|
|
174
225
|
return profile.tracker.kind === 'linear' ? profile.tracker.team : profile.tracker.key;
|
|
175
226
|
}
|
package/build/profile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,oGAAoG;AACpG,mFAAmF;AACnF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC;AAC1B,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;AAE9D,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,8DAA8D,CAAC,CAAC;AACnH,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACtC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { broker } from "./broker.js";
|
|
4
|
+
import { callApi, NotRunningError } from "./client.js";
|
|
5
|
+
import { projectHosts } from "./code-host.js";
|
|
6
|
+
import { ConductorError } from "./errors.js";
|
|
7
|
+
import { localCredentials } from "./in-process.js";
|
|
8
|
+
import { checkFailed, checkLines, checkProject, checkReportSchema, noProject, } from "./project-check.js";
|
|
9
|
+
import { openStore } from "./store.js";
|
|
10
|
+
import { routedQueue } from "./trackers.js";
|
|
11
|
+
import { boardsQueue } from "./watchers/boards-queue.js";
|
|
12
|
+
import { issuesQueue } from "./watchers/issues-queue.js";
|
|
13
|
+
import { linearQueue } from "./watchers/linear-queue.js";
|
|
14
|
+
import { gitWorktrees } from "./worktrees.js";
|
|
15
|
+
// The worktree's create hook may run 10 minutes (pnpm install) and its remove 5, on top of the reads.
|
|
16
|
+
const CHECK_TIMEOUT_MS = 17 * 60_000;
|
|
17
|
+
const refused = z.object({ details: z.array(z.string()).optional(), error: z.string() });
|
|
18
|
+
// `conduct project check <name>`: through the daemon, which checks as its own accounts, else here
|
|
19
|
+
// as the machine's. Returns the exit code: 1 when a check failed, else 0.
|
|
20
|
+
export async function projectCheckCommand(paths, name, options, io) {
|
|
21
|
+
let report;
|
|
22
|
+
try {
|
|
23
|
+
report = await checkByDaemon(paths, name, options);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (!(error instanceof NotRunningError))
|
|
27
|
+
throw error;
|
|
28
|
+
io.err('checking here (the conductor is not running)');
|
|
29
|
+
report = await checkHere(paths, name, options, io);
|
|
30
|
+
}
|
|
31
|
+
if (options.json)
|
|
32
|
+
io.out(JSON.stringify(report, null, 2));
|
|
33
|
+
else
|
|
34
|
+
checkLines(report).forEach((line) => io.out(line));
|
|
35
|
+
return checkFailed(report) ? 1 : 0;
|
|
36
|
+
}
|
|
37
|
+
async function checkByDaemon(paths, name, options) {
|
|
38
|
+
const { body, status } = await callApi(paths, {
|
|
39
|
+
body: { worktree: options.worktree },
|
|
40
|
+
method: 'POST',
|
|
41
|
+
path: `/projects/${encodeURIComponent(name)}/check`,
|
|
42
|
+
timeoutMs: CHECK_TIMEOUT_MS,
|
|
43
|
+
});
|
|
44
|
+
// The daemon sends its headers before the checks run, so a check that broke is a 200 too.
|
|
45
|
+
const report = checkReportSchema.safeParse(body);
|
|
46
|
+
if (status === 200 && report.success)
|
|
47
|
+
return report.data;
|
|
48
|
+
const error = refused.safeParse(body).data;
|
|
49
|
+
// A daemon from before the check answers its router's `not_found`.
|
|
50
|
+
if (status === 404 && error?.error === 'not_found') {
|
|
51
|
+
throw new ConductorError('the running conductor has no project check', [
|
|
52
|
+
'restart it on this build: `conduct stop`, then `conduct start`',
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
throw new ConductorError(error?.error ?? `the API answered ${status}`, error?.details ?? []);
|
|
56
|
+
}
|
|
57
|
+
async function checkHere(paths, name, options, io) {
|
|
58
|
+
mkdirSync(paths.home, { recursive: true });
|
|
59
|
+
const store = openStore(paths.db);
|
|
60
|
+
try {
|
|
61
|
+
const project = store.listProjects().find((p) => p.name === name);
|
|
62
|
+
if (!project)
|
|
63
|
+
throw noProject(name, store.listProjects().map((p) => p.name));
|
|
64
|
+
return await checkProject(io.deps?.(store) ?? localDeps(paths, store), project, options);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
store.close();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// The checks' outside calls as the machine's accounts, the way the daemon makes them.
|
|
71
|
+
function localDeps(paths, store) {
|
|
72
|
+
const credentials = localCredentials(paths);
|
|
73
|
+
return {
|
|
74
|
+
credentials,
|
|
75
|
+
hosts: (project) => projectHosts(credentials, project),
|
|
76
|
+
ports: broker({ store }),
|
|
77
|
+
queue: routedQueue({
|
|
78
|
+
'ado-boards': boardsQueue({ credentials }),
|
|
79
|
+
'github-issues': issuesQueue({ credentials }),
|
|
80
|
+
linear: linearQueue({ credentials }),
|
|
81
|
+
}),
|
|
82
|
+
worktrees: gitWorktrees(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=project-check-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-check-cli.js","sourceRoot":"","sources":["../src/project-check-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { repoKey, repoOf } from "./code-host.js";
|
|
5
|
+
import { TRACKER_API } from "./conditions.js";
|
|
6
|
+
import { ConductorError } from "./errors.js";
|
|
7
|
+
import { answersTo, NotFound } from "./lib/providers/index.js";
|
|
8
|
+
import { PROFILE_FILE, PROTOCOL, readProfile, releasesOf } from "./profile.js";
|
|
9
|
+
import { ciRefusal } from "./runs.js";
|
|
10
|
+
// `conduct project check <name>` (ATK-490): whether a registered project can be conducted, checked
|
|
11
|
+
// before the first launch instead of found by a failed one. Each check reads what the launch and the
|
|
12
|
+
// watchers would use, as the project's accounts; only the worktree check writes, and it removes what
|
|
13
|
+
// it made. One result per check, in this order.
|
|
14
|
+
export const CHECKS = ['profile', 'tracker', 'code-host', 'ci', 'worktree', 'skills', 'ports', 'health'];
|
|
15
|
+
const HEALTH_TIMEOUT_MS = 15_000;
|
|
16
|
+
export const checkResultSchema = z.object({
|
|
17
|
+
check: z.enum(CHECKS),
|
|
18
|
+
// What a summary line can't hold: the fields that differ, each problem, a hook's output.
|
|
19
|
+
details: z.array(z.string()),
|
|
20
|
+
// What to do about a warn or a fail.
|
|
21
|
+
fix: z.string().nullable(),
|
|
22
|
+
// skip: the check couldn't run (an earlier one left nothing to check, or --no-worktree).
|
|
23
|
+
status: z.enum(['fail', 'ok', 'skip', 'warn']),
|
|
24
|
+
summary: z.string(),
|
|
25
|
+
});
|
|
26
|
+
export const checkReportSchema = z.object({ project: z.string(), results: z.array(checkResultSchema) });
|
|
27
|
+
const ok = (summary, details = []) => ({ details, fix: null, status: 'ok', summary });
|
|
28
|
+
const warn = (summary, fix, details = []) => ({
|
|
29
|
+
details,
|
|
30
|
+
fix,
|
|
31
|
+
status: 'warn',
|
|
32
|
+
summary,
|
|
33
|
+
});
|
|
34
|
+
const fail = (summary, fix, details = []) => ({
|
|
35
|
+
details: [...details],
|
|
36
|
+
fix,
|
|
37
|
+
status: 'fail',
|
|
38
|
+
summary,
|
|
39
|
+
});
|
|
40
|
+
const skip = (summary) => ({ details: [], fix: null, status: 'skip', summary });
|
|
41
|
+
// "2 fail, 1 warn" and the like, or that every check passed.
|
|
42
|
+
export function checkCounts(report) {
|
|
43
|
+
const counts = ['fail', 'warn', 'skip']
|
|
44
|
+
.map((status) => [status, report.results.filter((r) => r.status === status).length])
|
|
45
|
+
.filter(([, n]) => n > 0)
|
|
46
|
+
.map(([status, n]) => `${n} ${status}`);
|
|
47
|
+
return counts.length === 0 ? 'every check passed' : counts.join(', ');
|
|
48
|
+
}
|
|
49
|
+
export function checkFailed(report) {
|
|
50
|
+
return report.results.some((r) => r.status === 'fail');
|
|
51
|
+
}
|
|
52
|
+
// One line per check, its details and fix under it, then the counts.
|
|
53
|
+
export function checkLines(report) {
|
|
54
|
+
const width = Math.max(...CHECKS.map((c) => c.length));
|
|
55
|
+
const lines = [`project check ${report.project}`];
|
|
56
|
+
for (const r of report.results) {
|
|
57
|
+
lines.push(` ${r.status.padEnd(4)} ${r.check.padEnd(width)} ${r.summary}`);
|
|
58
|
+
const indent = ' '.repeat(width + 10);
|
|
59
|
+
for (const detail of r.details)
|
|
60
|
+
lines.push(`${indent}${detail}`);
|
|
61
|
+
if (r.fix !== null)
|
|
62
|
+
lines.push(`${indent}fix: ${r.fix}`);
|
|
63
|
+
}
|
|
64
|
+
lines.push(checkCounts(report));
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
|
67
|
+
// Runs every check, one after another. A check that throws is a fail of its own; the others still run.
|
|
68
|
+
export async function checkProject(deps, project, options) {
|
|
69
|
+
const { profile, result } = profileCheck(project);
|
|
70
|
+
const results = [{ check: 'profile', ...result }];
|
|
71
|
+
const steps = [
|
|
72
|
+
['tracker', (p) => trackerCheck(deps, p)],
|
|
73
|
+
['code-host', (p) => codeHostCheck(deps, p)],
|
|
74
|
+
['ci', (p) => ciCheck(deps, p)],
|
|
75
|
+
['worktree', (p) => worktreeCheck(deps, project.root, p, options)],
|
|
76
|
+
['skills', (p) => skillsCheck(deps, project.root, p)],
|
|
77
|
+
['ports', (p) => portsCheck(deps, p)],
|
|
78
|
+
['health', (p) => healthCheck(deps, p)],
|
|
79
|
+
];
|
|
80
|
+
for (const [check, run] of steps) {
|
|
81
|
+
if (profile === null) {
|
|
82
|
+
results.push({ check, ...skip('no profile to check: fix the profile first') });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
let outcome;
|
|
86
|
+
try {
|
|
87
|
+
outcome = await run(profile);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
outcome = fail(`the check failed: ${messageOf(error)}`, 'run it again; if it fails the same way, file it', [
|
|
91
|
+
...detailsOf(error),
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
results.push({ check, ...outcome });
|
|
95
|
+
}
|
|
96
|
+
return { project: project.name, results };
|
|
97
|
+
}
|
|
98
|
+
// The dotted paths at which two profiles differ, an array counting as one value.
|
|
99
|
+
export function differences(a, b, path = '') {
|
|
100
|
+
if (isRecord(a) && isRecord(b)) {
|
|
101
|
+
const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
|
|
102
|
+
return keys.flatMap((key) => differences(a[key], b[key], path === '' ? key : `${path}.${key}`));
|
|
103
|
+
}
|
|
104
|
+
return JSON.stringify(a) === JSON.stringify(b) ? [] : [path === '' ? '(the whole profile)' : path];
|
|
105
|
+
}
|
|
106
|
+
// The check of a name no project is registered under.
|
|
107
|
+
export function noProject(name, names) {
|
|
108
|
+
return new ConductorError(`no project ${name}`, [
|
|
109
|
+
names.length > 0 ? `registered: ${names.join(', ')}` : 'register one with `conduct project add <path>`',
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
// The profile on disk against this build's schema and the stored copy the daemon works from. The
|
|
113
|
+
// rest of the checks use the file when it reads (the daemon takes it on its next save or start),
|
|
114
|
+
// else the stored copy, else nothing.
|
|
115
|
+
export function profileCheck(project) {
|
|
116
|
+
let disk;
|
|
117
|
+
try {
|
|
118
|
+
disk = readProfile(project.root).profile;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
profile: project.profile,
|
|
123
|
+
result: fail(messageOf(error), `fix ${project.profilePath} and check again; until then the daemon keeps its stored copy`, detailsOf(error)),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (disk.name !== project.name) {
|
|
127
|
+
return {
|
|
128
|
+
profile: project.profile ?? disk,
|
|
129
|
+
result: fail(`${project.profilePath} renames the project to "${disk.name}"`, `put "name": "${project.name}" back: a project keeps the name it was registered under`),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (project.profile === null) {
|
|
133
|
+
return {
|
|
134
|
+
profile: disk,
|
|
135
|
+
result: warn(`${PROFILE_FILE} is valid, but the daemon's stored copy is one this build can't read (${project.problem ?? 'unknown'})`, 'the daemon takes the file when it next starts, or on the next save of the file'),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const changed = differences(project.profile, disk);
|
|
139
|
+
if (changed.length > 0) {
|
|
140
|
+
return {
|
|
141
|
+
profile: disk,
|
|
142
|
+
result: warn(`${PROFILE_FILE} is valid, but ${changed.length === 1 ? 'a field differs' : `${changed.length} fields differ`} from the daemon's stored copy`, 'the daemon takes the file on its next save, or when it next starts', changed),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return { profile: disk, result: ok(`${PROFILE_FILE} is valid (protocol ${PROTOCOL}) and matches the stored copy`) };
|
|
146
|
+
}
|
|
147
|
+
async function ciCheck(deps, profile) {
|
|
148
|
+
const repo = repoOf(profile);
|
|
149
|
+
const refusal = ciRefusal(profile, repo);
|
|
150
|
+
if (refusal !== null)
|
|
151
|
+
return fail(refusal, "set ci to the code host's own: ado-pipelines for Azure DevOps, github-actions for GitHub");
|
|
152
|
+
let pipelines;
|
|
153
|
+
try {
|
|
154
|
+
pipelines = await deps.hosts(profile.name)(repo).ci.pipelines();
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
return fail(`${profile.ci.kind} pipelines of ${repoKey(repo)} can't be listed: ${messageOf(error)}`, 'check that the account can read the pipelines', detailsOf(error));
|
|
158
|
+
}
|
|
159
|
+
const problems = [];
|
|
160
|
+
const resolved = [];
|
|
161
|
+
for (const release of releasesOf(profile)) {
|
|
162
|
+
const found = pipelines.filter((p) => answersTo(p, release.pipeline));
|
|
163
|
+
const [only] = found;
|
|
164
|
+
if (only && found.length === 1)
|
|
165
|
+
resolved.push(`${release.key} → ${only.file ?? only.name}`);
|
|
166
|
+
else if (found.length === 0)
|
|
167
|
+
problems.push(`release ${release.key}: no pipeline answers to "${release.pipeline}"`);
|
|
168
|
+
else
|
|
169
|
+
problems.push(`release ${release.key}: "${release.pipeline}" names ${found.map((p) => p.file ?? p.name).join(' and ')}`);
|
|
170
|
+
}
|
|
171
|
+
if (problems.length > 0) {
|
|
172
|
+
const known = pipelines.map((p) => p.file ?? p.name).sort();
|
|
173
|
+
return fail(problems.join('; '), `name each release's pipeline by its YAML file name, path or name, once; ${repoKey(repo)} has ${known.length > 0 ? known.join(', ') : 'none'}`);
|
|
174
|
+
}
|
|
175
|
+
const releases = resolved.length > 0 ? `; releases: ${resolved.join(', ')}` : '; no release pipelines named';
|
|
176
|
+
return ok(`${pipelines.length} ${profile.ci.kind} pipelines on ${repoKey(repo)}${releases}`);
|
|
177
|
+
}
|
|
178
|
+
async function codeHostCheck(deps, profile) {
|
|
179
|
+
const repo = repoOf(profile);
|
|
180
|
+
const { account, error } = await deps.credentials.probe(repo.host, profile.name);
|
|
181
|
+
if (error !== null)
|
|
182
|
+
return fail(`${account.label} doesn't answer: ${error}`, `sign in: ${account.command}`);
|
|
183
|
+
let tip;
|
|
184
|
+
try {
|
|
185
|
+
tip = await deps.hosts(profile.name)(repo).branchTip(profile.baseBranch);
|
|
186
|
+
}
|
|
187
|
+
catch (caught) {
|
|
188
|
+
if (caught instanceof NotFound) {
|
|
189
|
+
return fail(`${repoKey(repo)} is not there, or ${account.label} can't see it`, 'check codeHost: the organization, project and repository, or the owner and repo', [caught.message]);
|
|
190
|
+
}
|
|
191
|
+
return fail(`${repoKey(repo)} can't be read: ${messageOf(caught)}`, 'check again; if it persists, check the account', detailsOf(caught));
|
|
192
|
+
}
|
|
193
|
+
if (tip === null)
|
|
194
|
+
return fail(`${repoKey(repo)} has no branch ${profile.baseBranch}`, "set baseBranch to the repo's base");
|
|
195
|
+
return ok(`${repoKey(repo)} answers as ${account.label}; ${profile.baseBranch} is at ${tip.slice(0, 7)}`);
|
|
196
|
+
}
|
|
197
|
+
// A worker skill says which worker protocol it speaks in its SKILL.md ("worker protocol v1").
|
|
198
|
+
function declaresProtocol(skill) {
|
|
199
|
+
return new RegExp(`worker protocol v?${PROTOCOL}\\b`, 'i').test(skill);
|
|
200
|
+
}
|
|
201
|
+
function detailsOf(error) {
|
|
202
|
+
return error instanceof ConductorError ? error.details : [];
|
|
203
|
+
}
|
|
204
|
+
async function healthCheck(deps, profile) {
|
|
205
|
+
const urls = Object.entries(profile.health ?? {});
|
|
206
|
+
if (urls.length === 0)
|
|
207
|
+
return ok('no health URLs in the profile');
|
|
208
|
+
const fetcher = deps.fetch ?? globalThis.fetch;
|
|
209
|
+
const answers = await Promise.all(urls.map(async ([env, url]) => {
|
|
210
|
+
try {
|
|
211
|
+
const response = await fetcher(url, { signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS) });
|
|
212
|
+
await response.body?.cancel();
|
|
213
|
+
return response.ok ? null : `${env}: ${url} answered ${response.status}`;
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
return `${env}: ${url} didn't answer (${messageOf(error)})`;
|
|
217
|
+
}
|
|
218
|
+
}));
|
|
219
|
+
const problems = answers.filter((a) => a !== null);
|
|
220
|
+
if (problems.length > 0) {
|
|
221
|
+
return warn(`${problems.length} of ${urls.length} health URLs don't answer`, 'only `conduct wait health` needs them: check the environment, or its URL in health', problems);
|
|
222
|
+
}
|
|
223
|
+
return ok(`${urls.map(([env]) => env).join(', ')} answer`);
|
|
224
|
+
}
|
|
225
|
+
function isRecord(value) {
|
|
226
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
227
|
+
}
|
|
228
|
+
function messageOf(error) {
|
|
229
|
+
return error instanceof Error ? error.message : String(error);
|
|
230
|
+
}
|
|
231
|
+
async function portsCheck(deps, profile) {
|
|
232
|
+
try {
|
|
233
|
+
const { ports, slot } = await deps.ports.previewPorts(profile);
|
|
234
|
+
const named = Object.entries(ports)
|
|
235
|
+
.map(([name, port]) => `${name} ${port}`)
|
|
236
|
+
.join(', ');
|
|
237
|
+
return ok(`a new worktree gets slot ${slot} (${named}), of 1 to ${profile.ports.maxSlot}`);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (!(error instanceof ConductorError))
|
|
241
|
+
throw error;
|
|
242
|
+
return fail(error.message, error.details[0] ?? '`conduct ports` lists the assignments');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function readInstalledSkill(root, name) {
|
|
246
|
+
try {
|
|
247
|
+
return readFileSync(join(root, '.claude', 'skills', name, 'SKILL.md'), 'utf8');
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// The main checkout's skills, read-only: the worktree hooks copy them into each worker's worktree.
|
|
254
|
+
async function skillsCheck(deps, root, profile) {
|
|
255
|
+
const read = deps.readSkill ?? readInstalledSkill;
|
|
256
|
+
const { aliases, verify, work } = profile.skills;
|
|
257
|
+
const problems = [];
|
|
258
|
+
const installed = [];
|
|
259
|
+
const workSkill = read(root, work);
|
|
260
|
+
if (workSkill === null)
|
|
261
|
+
problems.push(`the work skill ${work} isn't installed`);
|
|
262
|
+
else {
|
|
263
|
+
installed.push(work);
|
|
264
|
+
if (!declaresProtocol(workSkill))
|
|
265
|
+
problems.push(`${work} doesn't declare worker protocol v${PROTOCOL}`);
|
|
266
|
+
}
|
|
267
|
+
if (read(root, verify) === null)
|
|
268
|
+
problems.push(`the verify skill ${verify} isn't installed`);
|
|
269
|
+
else
|
|
270
|
+
installed.push(verify);
|
|
271
|
+
const missing = aliases.filter((alias) => read(root, alias) === null);
|
|
272
|
+
// Only the installed ones: a missing one is said once, above.
|
|
273
|
+
const stale = installed.length > 0 ? await deps.worktrees.auditSkills(root, installed) : null;
|
|
274
|
+
// An audit that couldn't run carries atk's output, JSON over several lines: one line here.
|
|
275
|
+
if (stale !== null && stale.length > 0)
|
|
276
|
+
problems.push(`atk audit: ${stale.join(', ').replace(/\s+/g, ' ')}`);
|
|
277
|
+
if (problems.length > 0) {
|
|
278
|
+
return fail(problems.join('; '), `atk install in ${root}, or set skills.work and skills.verify to the installed skills' names`);
|
|
279
|
+
}
|
|
280
|
+
const audited = stale === null ? '' : ', matching .atk-lock.json';
|
|
281
|
+
const summary = `${work} (worker protocol v${PROTOCOL}) and ${verify} are installed${audited}`;
|
|
282
|
+
if (missing.length > 0) {
|
|
283
|
+
return warn(`${summary}; the alias ${missing.join(', ')} isn't`, 'notes under an alias still count; install it only for launchers that call it (atk install), or drop it from skills.aliases once no item in flight uses it');
|
|
284
|
+
}
|
|
285
|
+
return ok(`${summary}${aliases.length > 0 ? `, as is ${aliases.join(', ')}` : ''}`);
|
|
286
|
+
}
|
|
287
|
+
// A timestamp safe in a worktree and branch name.
|
|
288
|
+
// To the millisecond, so two checks started together get worktrees of their own.
|
|
289
|
+
function stamp(at) {
|
|
290
|
+
return at.toISOString().replace(/\D/g, '').slice(0, 17);
|
|
291
|
+
}
|
|
292
|
+
async function trackerCheck(deps, profile) {
|
|
293
|
+
const { tracker } = profile;
|
|
294
|
+
const { account, error } = await deps.credentials.probe(TRACKER_API[tracker.kind], profile.name);
|
|
295
|
+
if (error !== null)
|
|
296
|
+
return fail(`${account.label} doesn't answer: ${error}`, `sign in: ${account.command}`);
|
|
297
|
+
const where = tracker.kind === 'linear'
|
|
298
|
+
? `Linear team ${tracker.team}`
|
|
299
|
+
: tracker.kind === 'ado-boards'
|
|
300
|
+
? `the Boards query "${tracker.query}"`
|
|
301
|
+
: `the issues labelled ${tracker.queueLabel}`;
|
|
302
|
+
let snapshot;
|
|
303
|
+
try {
|
|
304
|
+
// In the daemon, within the budget its scheduler reads the tracker in.
|
|
305
|
+
snapshot = await deps.queue.read(profile, [], deps.budget?.(profile) ?? { take: () => true });
|
|
306
|
+
}
|
|
307
|
+
catch (caught) {
|
|
308
|
+
const fix = tracker.kind === 'linear'
|
|
309
|
+
? `check tracker.team: "${tracker.team}" must be the key of a Linear team ${account.label} can see`
|
|
310
|
+
: tracker.kind === 'ado-boards'
|
|
311
|
+
? `check tracker.query: "${tracker.query}" must be a saved WIQL query ${account.label} can run`
|
|
312
|
+
: `check the issues' repo (tracker.owner and repo, or the code host's) and that ${account.label} can read it`;
|
|
313
|
+
return fail(`${where} can't be read: ${messageOf(caught)}`, fix, detailsOf(caught));
|
|
314
|
+
}
|
|
315
|
+
const items = `${snapshot.issues.length} open ${snapshot.issues.length === 1 ? 'item' : 'items'}`;
|
|
316
|
+
if (tracker.kind !== 'linear')
|
|
317
|
+
return ok(`${where} lists ${items}, as ${account.label}`);
|
|
318
|
+
// Linear filters by the team's key, so a key no team has reads as a team with nothing.
|
|
319
|
+
if (snapshot.projects.length === 0 && snapshot.issues.length === 0) {
|
|
320
|
+
return fail(`${where} has no projects and no open items`, `check tracker.team: "${tracker.team}" must be the key of a Linear team ${account.label} can see`);
|
|
321
|
+
}
|
|
322
|
+
if (snapshot.areaLabels.length === 0) {
|
|
323
|
+
return warn(`${where} lists ${items}, but no label sits under the Area group "${tracker.areaLabelGroup}"`, "check tracker.areaLabelGroup: the scheduler keeps one item per Area, and with no Areas it can't tell them apart");
|
|
324
|
+
}
|
|
325
|
+
const prioritized = snapshot.projects.filter((p) => p.priority !== null && p.priority > 0).length;
|
|
326
|
+
if (prioritized === 0) {
|
|
327
|
+
return warn(`${where} lists ${items}, but none of its projects has a priority, so nothing is conducted`, 'give the Linear projects to conduct a priority');
|
|
328
|
+
}
|
|
329
|
+
return ok(`${where} lists ${items}, and ${prioritized} of its projects have a priority, as ${account.label}`);
|
|
330
|
+
}
|
|
331
|
+
// The launch's first two stages on a throwaway worktree (manager.ts launchWorker): the project's
|
|
332
|
+
// create hook, then the skills check. Whatever it made is removed, whichever way it went.
|
|
333
|
+
async function worktreeCheck(deps, root, profile, options) {
|
|
334
|
+
if (!options.worktree)
|
|
335
|
+
return skip('--no-worktree');
|
|
336
|
+
const name = `conduct-check-${stamp((deps.now ?? (() => new Date()))())}`;
|
|
337
|
+
let path = null;
|
|
338
|
+
let outcome;
|
|
339
|
+
try {
|
|
340
|
+
path = await deps.worktrees.create(root, name);
|
|
341
|
+
const problem = await deps.worktrees.checkSkills(path, [profile.skills.work, profile.skills.verify]);
|
|
342
|
+
outcome =
|
|
343
|
+
problem === null
|
|
344
|
+
? ok(`the worktree hooks created and removed ${name}, and its skills match`)
|
|
345
|
+
: fail(`a new worktree's skills: ${problem}`, `atk install in ${root}, then commit .atk-lock.json if it changed`);
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
outcome = fail(`the worktree hooks couldn't create a worktree: ${messageOf(error)}`, `the project needs the ATK worktree-kit bundle (/worktree-setup); its hook output is above. If the hook made the branch worktree-${name} first, delete it: git -C ${root} branch -D worktree-${name}`, detailsOf(error));
|
|
349
|
+
}
|
|
350
|
+
// A create that failed part way can leave its directory behind.
|
|
351
|
+
const left = path ?? join(root, '.claude', 'worktrees', name);
|
|
352
|
+
if (path !== null || existsSync(left)) {
|
|
353
|
+
try {
|
|
354
|
+
await deps.worktrees.remove(root, left);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
return fail(`${left} is still there: ${messageOf(error)}`, `remove it by hand: git -C ${root} worktree remove --force ${left}, then git -C ${root} branch -D worktree-${name}`, [`before that: ${outcome.status} ${outcome.summary}`, ...detailsOf(error)]);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return outcome;
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=project-check.js.map
|