@detergent-software/conductor 3.0.0-dev.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +482 -0
- package/README.md +275 -125
- package/build/brief-cli.js +5 -12
- package/build/brief-cli.js.map +1 -1
- package/build/brief.js +4 -3
- package/build/brief.js.map +1 -1
- package/build/broker-cli.js +42 -15
- package/build/broker-cli.js.map +1 -1
- package/build/broker.js +1 -1
- package/build/broker.js.map +1 -1
- package/build/cli.js +221 -63
- package/build/cli.js.map +1 -1
- package/build/clones.js +92 -0
- package/build/clones.js.map +1 -0
- package/build/code-host-cli.js +172 -40
- package/build/code-host-cli.js.map +1 -1
- package/build/code-host-service.js +35 -2
- package/build/code-host-service.js.map +1 -1
- package/build/code-host.js +138 -16
- package/build/code-host.js.map +1 -1
- package/build/conditions.js +33 -16
- package/build/conditions.js.map +1 -1
- package/build/config.js +58 -28
- package/build/config.js.map +1 -1
- package/build/console.js +17 -27
- package/build/console.js.map +1 -1
- package/build/credentials.js +86 -54
- package/build/credentials.js.map +1 -1
- package/build/daemon.js +54 -65
- package/build/daemon.js.map +1 -1
- package/build/file-lock.js +155 -40
- package/build/file-lock.js.map +1 -1
- package/build/follow-ups.js +2 -2
- package/build/follow-ups.js.map +1 -1
- package/build/help.js +241 -83
- package/build/help.js.map +1 -1
- package/build/history.js +10 -4
- package/build/history.js.map +1 -1
- package/build/host/credential-manager.js +109 -0
- package/build/host/credential-manager.js.map +1 -0
- package/build/host/keychain.js +44 -0
- package/build/host/keychain.js.map +1 -0
- package/build/host/launchd.js +118 -0
- package/build/host/launchd.js.map +1 -0
- package/build/host/libsecret.js +40 -0
- package/build/host/libsecret.js.map +1 -0
- package/build/host/macos-terminal.js +63 -0
- package/build/host/macos-terminal.js.map +1 -0
- package/build/host/platform.js +4 -0
- package/build/host/platform.js.map +1 -0
- package/build/host/secrets.js +46 -0
- package/build/host/secrets.js.map +1 -0
- package/build/host/shell.js +26 -0
- package/build/host/shell.js.map +1 -0
- package/build/host/startup.js +26 -0
- package/build/host/startup.js.map +1 -0
- package/build/host/systemd.js +91 -0
- package/build/host/systemd.js.map +1 -0
- package/build/host/task-scheduler.js +78 -0
- package/build/host/task-scheduler.js.map +1 -0
- package/build/host/terminal.js +46 -0
- package/build/host/terminal.js.map +1 -0
- package/build/host/tmux.js +39 -0
- package/build/host/tmux.js.map +1 -0
- package/build/host/windows-terminal.js +46 -0
- package/build/host/windows-terminal.js.map +1 -0
- package/build/in-process.js +6 -5
- package/build/in-process.js.map +1 -1
- package/build/inbox.js +57 -39
- package/build/inbox.js.map +1 -1
- package/build/init.js +509 -0
- package/build/init.js.map +1 -0
- package/build/landing.js +107 -10
- package/build/landing.js.map +1 -1
- package/build/lib/providers/ado/code-host.js +5 -1
- package/build/lib/providers/ado/code-host.js.map +1 -1
- package/build/lib/providers/ado/pipelines.js +138 -26
- package/build/lib/providers/ado/pipelines.js.map +1 -1
- package/build/lib/providers/ado/trigger.js +5 -0
- package/build/lib/providers/ado/trigger.js.map +1 -1
- package/build/lib/providers/ado/writes.js +17 -2
- package/build/lib/providers/ado/writes.js.map +1 -1
- package/build/lib/providers/github/actions.js +90 -3
- package/build/lib/providers/github/actions.js.map +1 -1
- package/build/lib/providers/github/code-host.js +2 -0
- package/build/lib/providers/github/code-host.js.map +1 -1
- package/build/lib/providers/github/writes.js +25 -2
- package/build/lib/providers/github/writes.js.map +1 -1
- package/build/lib/providers/index.js +3 -2
- package/build/lib/providers/index.js.map +1 -1
- package/build/lib/providers/npm/stage.js +14 -1
- package/build/lib/providers/npm/stage.js.map +1 -1
- package/build/lib/providers/timing.js +10 -0
- package/build/lib/providers/timing.js.map +1 -0
- package/build/lib/providers/verdicts/pr.js +47 -6
- package/build/lib/providers/verdicts/pr.js.map +1 -1
- package/build/lib/providers/verdicts/runs.js +12 -2
- package/build/lib/providers/verdicts/runs.js.map +1 -1
- package/build/logins.js +13 -6
- package/build/logins.js.map +1 -1
- package/build/manager.js +121 -55
- package/build/manager.js.map +1 -1
- package/build/note.js +5 -15
- package/build/note.js.map +1 -1
- package/build/notes.js +130 -23
- package/build/notes.js.map +1 -1
- package/build/notify.js +104 -42
- package/build/notify.js.map +1 -1
- package/build/npm-reads.js +151 -22
- package/build/npm-reads.js.map +1 -1
- package/build/ownership.js +2 -2
- package/build/paths.js +44 -1
- package/build/paths.js.map +1 -1
- package/build/plugin.js +4 -2
- package/build/plugin.js.map +1 -1
- package/build/profile-fields.js +153 -0
- package/build/profile-fields.js.map +1 -0
- package/build/profile-layers.js +264 -0
- package/build/profile-layers.js.map +1 -0
- package/build/profile.js +107 -51
- package/build/profile.js.map +1 -1
- package/build/project-check-cli.js +17 -19
- package/build/project-check-cli.js.map +1 -1
- package/build/project-check.js +166 -104
- package/build/project-check.js.map +1 -1
- package/build/protocol.js +5 -21
- package/build/protocol.js.map +1 -1
- package/build/pushover.js +5 -5
- package/build/pushover.js.map +1 -1
- package/build/queue.js +4 -4
- package/build/queue.js.map +1 -1
- package/build/registry.js +195 -36
- package/build/registry.js.map +1 -1
- package/build/release-kinds.js +34 -0
- package/build/release-kinds.js.map +1 -0
- package/build/rule-actions.js +8 -6
- package/build/rule-actions.js.map +1 -1
- package/build/rules-service.js +104 -47
- package/build/rules-service.js.map +1 -1
- package/build/rules.js +23 -23
- package/build/rules.js.map +1 -1
- package/build/run.js +20 -7
- package/build/run.js.map +1 -1
- package/build/runs.js +218 -68
- package/build/runs.js.map +1 -1
- package/build/scheduler.js +89 -13
- package/build/scheduler.js.map +1 -1
- package/build/search.js +2 -1
- package/build/search.js.map +1 -1
- package/build/server.js +51 -14
- package/build/server.js.map +1 -1
- package/build/spend.js +1 -1
- package/build/spend.js.map +1 -1
- package/build/store.js +79 -20
- package/build/store.js.map +1 -1
- package/build/supervise.js +1 -1
- package/build/supervise.js.map +1 -1
- package/build/timeline.js +104 -0
- package/build/timeline.js.map +1 -0
- package/build/tracker-reads-cli.js +4 -5
- package/build/tracker-reads-cli.js.map +1 -1
- package/build/trackers.js +56 -10
- package/build/trackers.js.map +1 -1
- package/build/train-cli.js +6 -5
- package/build/train-cli.js.map +1 -1
- package/build/train.js +2 -2
- package/build/train.js.map +1 -1
- package/build/upgrade.js +8 -9
- package/build/upgrade.js.map +1 -1
- package/build/wait.js +10 -10
- package/build/wait.js.map +1 -1
- package/build/watchers/ado.js +42 -19
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/github.js +18 -4
- package/build/watchers/github.js.map +1 -1
- package/build/watchers/index.js +2 -2
- package/build/watchers/index.js.map +1 -1
- package/build/watchers/linear-queue.js +47 -14
- package/build/watchers/linear-queue.js.map +1 -1
- package/build/watchers/linear-writes.js +26 -12
- package/build/watchers/linear-writes.js.map +1 -1
- package/build/watchers/linear.js +2 -2
- package/build/watchers/linear.js.map +1 -1
- package/build/watchers/source.js +5 -1
- package/build/watchers/source.js.map +1 -1
- package/build/webhook.js +80 -0
- package/build/webhook.js.map +1 -0
- package/build/why.js +1 -1
- package/build/why.js.map +1 -1
- package/build/worktrees.js +56 -93
- package/build/worktrees.js.map +1 -1
- package/package.json +61 -61
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/model.ts +46 -6
- package/plugin/hooks/register.tsx +41 -2
- package/plugin/skills/conductor-console/SKILL.md +26 -27
- package/plugin/skills/work-item/SKILL.md +58 -54
- package/plugin/skills/work-item/trackers/ado-boards.md +2 -2
- package/plugin/skills/work-item/trackers/github-issues.md +1 -1
- package/plugin/skills/work-item/trackers/linear.md +2 -2
- package/plugin/types/index.d.ts +1 -1
- package/protocol/PROTOCOL.md +3 -9
- package/build/checkout-sync.js +0 -249
- package/build/checkout-sync.js.map +0 -1
- package/build/credential.js +0 -66
- package/build/credential.js.map +0 -1
- package/build/startup.js +0 -92
- package/build/startup.js.map +0 -1
- package/build/tab.js +0 -32
- package/build/tab.js.map +0 -1
|
@@ -9,32 +9,33 @@ import { ConductorError } from "./errors.js";
|
|
|
9
9
|
import { localCredentials } from "./in-process.js";
|
|
10
10
|
import { checkFailed, checkLines, checkProject, checkReportSchema, noProject, } from "./project-check.js";
|
|
11
11
|
import { openStore } from "./store.js";
|
|
12
|
-
import {
|
|
13
|
-
import { boardsQueue } from "./watchers/boards-queue.js";
|
|
14
|
-
import { issuesQueue } from "./watchers/issues-queue.js";
|
|
15
|
-
import { linearQueue } from "./watchers/linear-queue.js";
|
|
12
|
+
import { projectTracker } from "./trackers.js";
|
|
16
13
|
import { gitWorktrees } from "./worktrees.js";
|
|
17
|
-
// The worktree's
|
|
14
|
+
// The worktree's setup may run 10 minutes (pnpm install) and its remove 5, on top of the reads.
|
|
18
15
|
const CHECK_TIMEOUT_MS = 17 * 60_000;
|
|
19
16
|
const refused = z.object({ details: z.array(z.string()).optional(), error: z.string() });
|
|
20
17
|
// `conduct project check <name>`: through the daemon, which checks as its own accounts, else here
|
|
21
18
|
// as the machine's. Returns the exit code: 1 when a check failed, else 0.
|
|
22
19
|
export async function projectCheckCommand(paths, name, options, io) {
|
|
23
|
-
|
|
20
|
+
const report = await projectCheckReport(paths, name, options, io);
|
|
21
|
+
if (options.json)
|
|
22
|
+
io.out(JSON.stringify(report, null, 2));
|
|
23
|
+
else
|
|
24
|
+
checkLines(report).forEach((line) => io.out(line));
|
|
25
|
+
return checkFailed(report) ? 1 : 0;
|
|
26
|
+
}
|
|
27
|
+
// The check's report, from the daemon or made here, for a caller that prints it its own way
|
|
28
|
+
// (`conduct init`).
|
|
29
|
+
export async function projectCheckReport(paths, name, options, io) {
|
|
24
30
|
try {
|
|
25
|
-
|
|
31
|
+
return await checkByDaemon(paths, name, options);
|
|
26
32
|
}
|
|
27
33
|
catch (error) {
|
|
28
34
|
if (!(error instanceof NotRunningError))
|
|
29
35
|
throw error;
|
|
30
36
|
io.err('checking here (the conductor is not running)');
|
|
31
|
-
|
|
37
|
+
return checkHere(paths, name, options, io);
|
|
32
38
|
}
|
|
33
|
-
if (options.json)
|
|
34
|
-
io.out(JSON.stringify(report, null, 2));
|
|
35
|
-
else
|
|
36
|
-
checkLines(report).forEach((line) => io.out(line));
|
|
37
|
-
return checkFailed(report) ? 1 : 0;
|
|
38
39
|
}
|
|
39
40
|
async function checkByDaemon(paths, name, options) {
|
|
40
41
|
const { body, status } = await callApi(paths, {
|
|
@@ -75,13 +76,10 @@ function localDeps(paths, store) {
|
|
|
75
76
|
return {
|
|
76
77
|
claude: claudeCli(),
|
|
77
78
|
credentials,
|
|
78
|
-
hosts: (
|
|
79
|
+
hosts: (profile) => projectHosts(credentials, profile.name, { profile }),
|
|
80
|
+
paths,
|
|
79
81
|
ports: broker({ pool: loadConfig(paths.config).ports, store }),
|
|
80
|
-
queue:
|
|
81
|
-
'ado-boards': boardsQueue({ credentials }),
|
|
82
|
-
'github-issues': issuesQueue({ credentials }),
|
|
83
|
-
linear: linearQueue({ credentials }),
|
|
84
|
-
}),
|
|
82
|
+
queue: projectTracker({ credentials }).queue,
|
|
85
83
|
worktrees: gitWorktrees(),
|
|
86
84
|
};
|
|
87
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-check-cli.js","sourceRoot":"","sources":["../src/project-check-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,WAAW,EACX,UAAU,EAEV,YAAY,EAEZ,iBAAiB,EACjB,SAAS,GAEV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAc,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"project-check-cli.js","sourceRoot":"","sources":["../src/project-check-cli.ts"],"names":[],"mappings":"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"}
|
package/build/project-check.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
1
|
import { join } from 'node:path';
|
|
2
|
+
import { gt } from 'semver';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { portEnv } from "./broker.js";
|
|
5
5
|
import { runGit } from "./checkout.js";
|
|
@@ -8,11 +8,17 @@ import { repoKey, repoOf } from "./code-host.js";
|
|
|
8
8
|
import { TRACKER_API } from "./conditions.js";
|
|
9
9
|
import { ConductorError } from "./errors.js";
|
|
10
10
|
import { answersTo, NotFound } from "./lib/providers/index.js";
|
|
11
|
+
import { projectPaths } from "./paths.js";
|
|
11
12
|
import { pluginSkill, pluginSource, projectSkills, readPluginSkill } from "./plugin.js";
|
|
12
|
-
import {
|
|
13
|
+
import { minimumCheck } from "./profile-fields.js";
|
|
14
|
+
import { projectProfile } from "./profile-layers.js";
|
|
15
|
+
import { adoOrganization } from "./profile.js";
|
|
13
16
|
import { PROTOCOL } from "./protocol.js";
|
|
17
|
+
import { registeredByCheckout } from "./registry.js";
|
|
18
|
+
import { RELEASE_KINDS } from "./release-kinds.js";
|
|
14
19
|
import { ciRefusal } from "./runs.js";
|
|
15
|
-
import {
|
|
20
|
+
import { labelQueue } from "./watchers/linear-queue.js";
|
|
21
|
+
import { BRANCH_PREFIX, SESSION_SETTINGS } from "./worktrees.js";
|
|
16
22
|
// `conduct project check <name>` (ATK-490): whether a registered project can be conducted, checked
|
|
17
23
|
// before the first launch instead of found by a failed one. Each check reads what the launch and the
|
|
18
24
|
// watchers would use, as the project's accounts; only the worktree check writes, and it removes what
|
|
@@ -29,6 +35,9 @@ export const CHECKS = [
|
|
|
29
35
|
'health',
|
|
30
36
|
];
|
|
31
37
|
const HEALTH_TIMEOUT_MS = 15_000;
|
|
38
|
+
// How the worktree check's summary starts when a skill the profile names is missing after setup:
|
|
39
|
+
// `conduct init` reads it to point at the verify skill's maker.
|
|
40
|
+
export const MISSING_SKILLS = "a new worktree's skills:";
|
|
32
41
|
const SOURCE_WORDS = {
|
|
33
42
|
'cli-login': 'CLI login',
|
|
34
43
|
'machine-token': "the machine's token",
|
|
@@ -99,23 +108,26 @@ export async function checkProject(given, project, options) {
|
|
|
99
108
|
const deps = {
|
|
100
109
|
...given,
|
|
101
110
|
credentials: {
|
|
102
|
-
|
|
111
|
+
...given.credentials,
|
|
112
|
+
probe: (provider, name, options) => {
|
|
103
113
|
const key = `${provider}:${name}`;
|
|
104
|
-
const known = probes.get(key) ?? given.credentials.probe(provider, name);
|
|
114
|
+
const known = probes.get(key) ?? given.credentials.probe(provider, name, options);
|
|
105
115
|
probes.set(key, known);
|
|
106
116
|
return known;
|
|
107
117
|
},
|
|
108
118
|
},
|
|
109
119
|
};
|
|
110
|
-
const
|
|
120
|
+
const resolve = deps.resolveProfile ?? ((name) => projectProfile(deps.paths, name));
|
|
121
|
+
const { profile, result } = profileCheck(project, deps.paths, () => resolve(project.name));
|
|
111
122
|
const results = [{ check: 'profile', ...result }];
|
|
123
|
+
const { clone, worktrees: dir } = projectPaths(deps.paths, project.name);
|
|
112
124
|
const steps = [
|
|
113
125
|
['auth', (p) => authCheck(deps, p)],
|
|
114
126
|
['tracker', (p) => trackerCheck(deps, p)],
|
|
115
127
|
['code-host', (p) => codeHostCheck(deps, p)],
|
|
116
128
|
['ci', (p) => ciCheck(deps, p)],
|
|
117
|
-
['worktree', (p) => worktreeCheck(deps,
|
|
118
|
-
['skills', (p) => skillsCheck(deps,
|
|
129
|
+
['worktree', (p) => worktreeCheck(deps, { clone, dir }, p, options)],
|
|
130
|
+
['skills', (p) => skillsCheck(deps, p, options)],
|
|
119
131
|
['ports', (p) => portsCheck(deps, p)],
|
|
120
132
|
['health', (p) => healthCheck(deps, p)],
|
|
121
133
|
];
|
|
@@ -155,50 +167,63 @@ export function differences(a, b, path = '') {
|
|
|
155
167
|
// The check of a name no project is registered under.
|
|
156
168
|
export function noProject(name, names) {
|
|
157
169
|
return new ConductorError(`no project ${name}`, [
|
|
158
|
-
names.length > 0 ? `registered: ${names.join(', ')}` : 'register one with `conduct project add <path>`',
|
|
170
|
+
names.length > 0 ? `registered: ${names.join(', ')}` : 'register one with `conduct project add <url | path>`',
|
|
159
171
|
]);
|
|
160
172
|
}
|
|
161
|
-
// The profile
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
173
|
+
// The profile as its layers give it now (the machine file over the repo's at origin/<base>) against
|
|
174
|
+
// this build's schema and the stored copy the daemon works from, and each file's `conductor` minimum
|
|
175
|
+
// against the fields it writes (ATK-580). The rest of the checks use the resolved profile when it
|
|
176
|
+
// reads (the daemon takes it on its next start, save or fetch), else the stored copy, else nothing.
|
|
177
|
+
export function profileCheck(project, paths, resolve) {
|
|
178
|
+
if (registeredByCheckout(paths, project)) {
|
|
179
|
+
return {
|
|
180
|
+
profile: null,
|
|
181
|
+
result: fail(`${project.name} is registered by the checkout ${project.clone}, which the conductor no longer works in`, `run \`conduct project add "${project.clone}"\`: it clones the checkout's origin into ${projectPaths(paths, project.name).clone} and keeps the registration`),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
let layered;
|
|
166
185
|
try {
|
|
167
|
-
|
|
186
|
+
layered = resolve();
|
|
168
187
|
}
|
|
169
188
|
catch (error) {
|
|
170
189
|
return {
|
|
171
190
|
profile: project.profile,
|
|
172
|
-
result: fail(messageOf(error), `fix ${project.
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
if (disk.name !== project.name) {
|
|
176
|
-
return {
|
|
177
|
-
profile: project.profile ?? disk,
|
|
178
|
-
result: fail(`${project.profilePath} renames the project to "${disk.name}"`, `put "name": "${project.name}" back: a project keeps the name it was registered under. Or rename it: \`conduct project remove ${project.name}\`, then \`conduct project add "${project.root}"\``),
|
|
191
|
+
result: fail(messageOf(error), `fix the profile (\`conduct profile ${project.name}\` names its files) and check again; until then the daemon keeps its stored copy`, detailsOf(error)),
|
|
179
192
|
};
|
|
180
193
|
}
|
|
194
|
+
const { layers, profile: now } = layered;
|
|
195
|
+
const minimum = combined([layers.machine, layers.repo].flatMap((layer) => layer === null ? [] : [minimumCheck(JSON.stringify(layer.raw), layer.label)]));
|
|
196
|
+
const files = [layers.machine, layers.repo].flatMap((l) => (l === null ? [] : [l.label])).join(' over ');
|
|
197
|
+
const moved = [layers.machine, layers.repo].flatMap((l) => l !== null && Object.hasOwn(l.raw, 'workers') ? [l.label] : []);
|
|
198
|
+
const checked = (outcome) => withMovedWorkers(withMinimum(outcome, minimum), moved);
|
|
181
199
|
if (project.profile === null) {
|
|
182
200
|
return {
|
|
183
|
-
profile:
|
|
184
|
-
result: warn(
|
|
201
|
+
profile: now,
|
|
202
|
+
result: checked(warn(`the profile (${files}) is valid, but the daemon's stored copy is one this build can't read (${project.problem ?? 'unknown'})`, 'the daemon takes the profile when it next starts, or after the next save or fetch')),
|
|
185
203
|
};
|
|
186
204
|
}
|
|
187
|
-
const changed = differences(project.profile,
|
|
205
|
+
const changed = differences(project.profile, now);
|
|
188
206
|
if (changed.length > 0) {
|
|
189
207
|
return {
|
|
190
|
-
profile:
|
|
191
|
-
result: warn(
|
|
208
|
+
profile: now,
|
|
209
|
+
result: checked(warn(`the profile (${files}) is valid, but ${changed.length === 1 ? 'a field differs' : `${changed.length} fields differ`} from the daemon's stored copy`, 'the daemon takes it after the next save of the machine profile or fetch of the clone, or when it next starts', changed)),
|
|
192
210
|
};
|
|
193
211
|
}
|
|
194
|
-
return {
|
|
212
|
+
return {
|
|
213
|
+
profile: now,
|
|
214
|
+
result: checked(ok(`the profile (${files}) is valid (protocol ${PROTOCOL}) and matches the stored copy`)),
|
|
215
|
+
};
|
|
195
216
|
}
|
|
196
217
|
// Which credential each provider the project uses signs in with (credentials.ts: a project token,
|
|
197
218
|
// then the machine's token, then the CLI login), and for each that isn't the project's own token, how
|
|
198
219
|
// to give it one scoped to the project. Advice only: any source that works passes.
|
|
199
220
|
async function authCheck(deps, profile) {
|
|
200
|
-
|
|
201
|
-
const
|
|
221
|
+
// Azure Pipelines that build a GitHub repo sign in to Azure DevOps too.
|
|
222
|
+
const pipelines = profile.ci.kind === 'ado-pipelines' ? ['ado'] : [];
|
|
223
|
+
const used = [...new Set([repoOf(profile).host, ...pipelines, TRACKER_API[profile.tracker.kind]])];
|
|
224
|
+
// The checked profile's ADO organization, which the stored one may not have yet.
|
|
225
|
+
const organization = adoOrganization(profile);
|
|
226
|
+
const probed = await Promise.all(used.map((provider) => deps.credentials.probe(provider, profile.name, { organization })));
|
|
202
227
|
const failing = probed.find((p) => p.error !== null);
|
|
203
228
|
// No source: nothing was found at all (Linear with no key, which has no CLI login).
|
|
204
229
|
const words = (source) => (source === null ? 'no credential' : SOURCE_WORDS[source]);
|
|
@@ -220,8 +245,7 @@ async function authCheck(deps, profile) {
|
|
|
220
245
|
const scoped = wide.map((provider) => {
|
|
221
246
|
const target = `conductor-${profile.name}-${provider}`;
|
|
222
247
|
const key = provider === 'linear' ? 'linear.credential' : `${provider}.token`;
|
|
223
|
-
|
|
224
|
-
return `for ${provider}, ${SCOPES[provider]}: cmdkey /generic:${target} /user:${user} /pass, then set projects.${profile.name}.${key} to "${target}" in config.json`;
|
|
248
|
+
return `for ${provider}, ${SCOPES[provider]}: ${deps.credentials.saveCommand(provider, target)}, then set projects.${profile.name}.${key} to "${target}" in config.json`;
|
|
225
249
|
});
|
|
226
250
|
return {
|
|
227
251
|
details: [...details, ...scoped],
|
|
@@ -233,11 +257,12 @@ async function authCheck(deps, profile) {
|
|
|
233
257
|
async function ciCheck(deps, profile) {
|
|
234
258
|
const repo = repoOf(profile);
|
|
235
259
|
const refusal = ciRefusal(profile, repo);
|
|
236
|
-
if (refusal !== null)
|
|
237
|
-
return fail(refusal,
|
|
260
|
+
if (refusal !== null) {
|
|
261
|
+
return fail(refusal, 'set ci to ado-pipelines for an Azure DevOps repo; for a GitHub repo, github-actions, or ado-pipelines with the organization and project whose pipelines build it');
|
|
262
|
+
}
|
|
238
263
|
let pipelines;
|
|
239
264
|
try {
|
|
240
|
-
pipelines = await deps.hosts(profile
|
|
265
|
+
pipelines = await deps.hosts(profile)(repo).ci.pipelines();
|
|
241
266
|
}
|
|
242
267
|
catch (error) {
|
|
243
268
|
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));
|
|
@@ -259,16 +284,24 @@ async function ciCheck(deps, profile) {
|
|
|
259
284
|
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'}`);
|
|
260
285
|
}
|
|
261
286
|
const releases = resolved.length > 0 ? `; releases: ${resolved.join(', ')}` : '; no release pipelines named';
|
|
262
|
-
|
|
287
|
+
const found = `${pipelines.length} ${profile.ci.kind} pipelines on ${repoKey(repo)}${releases}`;
|
|
288
|
+
// Each release waits on a person at its kind's gate (release-kinds.ts), which `gates` should name.
|
|
289
|
+
const ungated = profile.releases.filter((r) => !profile.gates.includes(RELEASE_KINDS[r.kind].gate));
|
|
290
|
+
if (ungated.length > 0) {
|
|
291
|
+
return warn(`${found}, but gates leaves out ${ungated.map((r) => `release ${r.key}'s ${RELEASE_KINDS[r.kind].gate}`).join(', ')}`, `add ${[...new Set(ungated.map((r) => RELEASE_KINDS[r.kind].gate))].join(', ')} to the profile's gates`);
|
|
292
|
+
}
|
|
293
|
+
return ok(found);
|
|
263
294
|
}
|
|
264
295
|
async function codeHostCheck(deps, profile) {
|
|
265
296
|
const repo = repoOf(profile);
|
|
266
|
-
const { account, error } = await deps.credentials.probe(repo.host, profile.name
|
|
297
|
+
const { account, error } = await deps.credentials.probe(repo.host, profile.name, {
|
|
298
|
+
organization: adoOrganization(profile),
|
|
299
|
+
});
|
|
267
300
|
if (error !== null)
|
|
268
301
|
return fail(`${account.label} doesn't answer: ${error}`, `sign in: ${account.command}`);
|
|
269
302
|
let tip;
|
|
270
303
|
try {
|
|
271
|
-
tip = await deps.hosts(profile
|
|
304
|
+
tip = await deps.hosts(profile)(repo).branchTip(profile.baseBranch);
|
|
272
305
|
}
|
|
273
306
|
catch (caught) {
|
|
274
307
|
if (caught instanceof NotFound) {
|
|
@@ -280,6 +313,14 @@ async function codeHostCheck(deps, profile) {
|
|
|
280
313
|
return fail(`${repoKey(repo)} has no branch ${profile.baseBranch}`, "set baseBranch to the repo's base");
|
|
281
314
|
return ok(`${repoKey(repo)} answers as ${account.label}; ${profile.baseBranch} is at ${tip.slice(0, 7)}`);
|
|
282
315
|
}
|
|
316
|
+
// The layers' minimum checks as one: every file's problems, and the newest version that clears them.
|
|
317
|
+
function combined(checks) {
|
|
318
|
+
let needs = null;
|
|
319
|
+
for (const check of checks)
|
|
320
|
+
if (check.needs !== null && (needs === null || gt(check.needs, needs)))
|
|
321
|
+
needs = check.needs;
|
|
322
|
+
return { needs, problems: checks.flatMap((c) => c.problems) };
|
|
323
|
+
}
|
|
283
324
|
function detailsOf(error) {
|
|
284
325
|
return error instanceof ConductorError ? error.details : [];
|
|
285
326
|
}
|
|
@@ -330,61 +371,38 @@ async function portsCheck(deps, profile) {
|
|
|
330
371
|
return fail(error.message, error.details[0] ?? '`conduct ports` lists the assignments');
|
|
331
372
|
}
|
|
332
373
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return null;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
// The main checkout's skills, read-only: the worktree hooks copy them into each worker's worktree.
|
|
342
|
-
async function skillsCheck(deps, root, profile) {
|
|
343
|
-
const read = deps.readSkill ?? readInstalledSkill;
|
|
344
|
-
const { aliases, verify, work } = profile.skills;
|
|
374
|
+
// The skills the profile names, read where a session finds them before any worktree exists: the
|
|
375
|
+
// conductor plugin's (conductor:<skill>), with the work skill's declared protocol. The project's
|
|
376
|
+
// own skills arrive through `setup` in each new worktree, so the worktree check looks for those.
|
|
377
|
+
async function skillsCheck(deps, profile, options) {
|
|
378
|
+
const { verify, work } = profile.skills;
|
|
345
379
|
const problems = [];
|
|
346
|
-
const installed = [];
|
|
347
|
-
// The conductor plugin's skills (conductor:<skill>) come with the package, not the checkout: read
|
|
348
|
-
// from its plugin and left out of the audit.
|
|
349
380
|
const plugin = deps.pluginDir ?? pluginSource();
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
installed.push(work);
|
|
361
|
-
const declared = declaredProtocol(workSkill);
|
|
381
|
+
const fromPlugin = [work, verify].filter((name) => pluginSkill(name) !== null);
|
|
382
|
+
for (const name of fromPlugin) {
|
|
383
|
+
const text = readPluginSkill(plugin, pluginSkill(name) ?? name);
|
|
384
|
+
if (text === null) {
|
|
385
|
+
problems.push(`${name} isn't in the conductor plugin`);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (name !== work)
|
|
389
|
+
continue;
|
|
390
|
+
const declared = declaredProtocol(text);
|
|
362
391
|
if (declared === null)
|
|
363
392
|
problems.push(`${work} declares no worker protocol (protocol: ${PROTOCOL} in its frontmatter)`);
|
|
364
393
|
else if (declared !== PROTOCOL)
|
|
365
394
|
problems.push(`${work} speaks worker protocol ${declared}; this conductor speaks ${PROTOCOL}`);
|
|
366
395
|
}
|
|
367
|
-
if (
|
|
368
|
-
problems.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (stale !== null && stale.length > 0)
|
|
378
|
-
problems.push(`atk audit: ${stale.join(', ').replace(/\s+/g, ' ')}`);
|
|
379
|
-
if (problems.length > 0) {
|
|
380
|
-
return fail(problems.join('; '), `atk install in ${root}, or set skills.work and skills.verify to the installed skills' names`);
|
|
381
|
-
}
|
|
382
|
-
const audited = stale === null ? '' : ', matching .atk-lock.json';
|
|
383
|
-
const summary = `${work} (worker protocol ${PROTOCOL}) and ${verify} are installed${audited}`;
|
|
384
|
-
if (missing.length > 0) {
|
|
385
|
-
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');
|
|
386
|
-
}
|
|
387
|
-
return ok(`${summary}${checkAliases && aliases.length > 0 ? `, as is ${aliases.join(', ')}` : ''}`);
|
|
396
|
+
if (problems.length > 0)
|
|
397
|
+
return fail(problems.join('; '), 'upgrade the conductor, or set skills.work and skills.verify to skills it has');
|
|
398
|
+
const own = [work, verify].filter((name) => pluginSkill(name) === null);
|
|
399
|
+
const plugged = fromPlugin.length > 0
|
|
400
|
+
? `${fromPlugin.join(' and ')} ${fromPlugin.length === 1 ? 'comes' : 'come'} with the conductor plugin`
|
|
401
|
+
: '';
|
|
402
|
+
const theirs = own.length === 0
|
|
403
|
+
? ''
|
|
404
|
+
: `${own.join(' and ')} ${own.length === 1 ? 'comes' : 'come'} from the project's setup: ${options.worktree ? 'the worktree check looked for' : '--no-worktree, so nothing looked for'} ${own.length === 1 ? 'it' : 'them'}`;
|
|
405
|
+
return ok([plugged, theirs].filter((s) => s !== '').join('; '));
|
|
388
406
|
}
|
|
389
407
|
// A timestamp safe in a worktree and branch name.
|
|
390
408
|
// To the millisecond, so two checks started together get worktrees of their own.
|
|
@@ -393,7 +411,9 @@ function stamp(at) {
|
|
|
393
411
|
}
|
|
394
412
|
async function trackerCheck(deps, profile) {
|
|
395
413
|
const { tracker } = profile;
|
|
396
|
-
const { account, error } = await deps.credentials.probe(TRACKER_API[tracker.kind], profile.name
|
|
414
|
+
const { account, error } = await deps.credentials.probe(TRACKER_API[tracker.kind], profile.name, {
|
|
415
|
+
organization: adoOrganization(profile),
|
|
416
|
+
});
|
|
397
417
|
if (error !== null)
|
|
398
418
|
return fail(`${account.label} doesn't answer: ${error}`, `sign in: ${account.command}`);
|
|
399
419
|
const where = tracker.kind === 'linear'
|
|
@@ -417,38 +437,80 @@ async function trackerCheck(deps, profile) {
|
|
|
417
437
|
const items = `${snapshot.issues.length} open ${snapshot.issues.length === 1 ? 'item' : 'items'}`;
|
|
418
438
|
if (tracker.kind !== 'linear')
|
|
419
439
|
return ok(`${where} lists ${items}, as ${account.label}`);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
440
|
+
const queue = labelQueue(tracker);
|
|
441
|
+
// Linear filters by the team's key, so a key no team has reads as a team with nothing. A label
|
|
442
|
+
// queue reads no projects.
|
|
443
|
+
if ((queue !== null || snapshot.projects.length === 0) && snapshot.issues.length === 0) {
|
|
444
|
+
return fail(`${where} has ${queue ? '' : 'no projects and '}no open items`, `check tracker.team: "${tracker.team}" must be the key of a Linear team ${account.label} can see`);
|
|
445
|
+
}
|
|
446
|
+
// Nothing conducted first: it matters more than the Areas.
|
|
447
|
+
const queued = queue ? snapshot.issues.filter((i) => i.trackerProject === queue.name).length : 0;
|
|
427
448
|
const prioritized = snapshot.projects.filter((p) => p.priority !== null && p.priority > 0).length;
|
|
428
|
-
if (
|
|
449
|
+
if (queue && queued === 0) {
|
|
450
|
+
return warn(`${where} lists ${items}, but none carries the queue label ${queue.label}, so nothing is conducted`, `label the Linear issues to conduct ${queue.label}, or check tracker.queue.label`);
|
|
451
|
+
}
|
|
452
|
+
if (!queue && prioritized === 0) {
|
|
429
453
|
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');
|
|
430
454
|
}
|
|
431
|
-
|
|
455
|
+
// No Area group means the team has no Areas (tracker.areaLabelGroup: null).
|
|
456
|
+
if (tracker.areaLabelGroup !== null && snapshot.areaLabels.length === 0) {
|
|
457
|
+
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 (null if the team has none)");
|
|
458
|
+
}
|
|
459
|
+
return ok(queue
|
|
460
|
+
? `${where} lists ${items}, and ${queued} carry the queue label ${queue.label}, as ${account.label}`
|
|
461
|
+
: `${where} lists ${items}, and ${prioritized} of its projects have a priority, as ${account.label}`);
|
|
462
|
+
}
|
|
463
|
+
// A profile that writes a field newer than its `conductor` minimum reads here, but an older conductor
|
|
464
|
+
// would refuse it with the fields it doesn't know instead of the one line that says to upgrade: a
|
|
465
|
+
// warn, beside whatever else the profile check found.
|
|
466
|
+
function withMinimum(outcome, { needs, problems }) {
|
|
467
|
+
if (problems.length === 0)
|
|
468
|
+
return outcome;
|
|
469
|
+
const below = `its "conductor" minimum is below ${problems.length === 1 ? 'a field' : `${problems.length} fields`} it writes`;
|
|
470
|
+
const fix = needs === null
|
|
471
|
+
? "the conductor's profile-fields.ts records no version for these fields: file it against the conductor"
|
|
472
|
+
: `raise "conductor" to ${needs}, the oldest conductor that reads every field the file writes`;
|
|
473
|
+
return {
|
|
474
|
+
details: [...outcome.details, ...problems],
|
|
475
|
+
fix: outcome.fix === null ? fix : `${outcome.fix}; ${fix}`,
|
|
476
|
+
status: 'warn',
|
|
477
|
+
summary: outcome.status === 'ok' ? `${outcome.summary}, but ${below}` : `${outcome.summary}; ${below}`,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
// A profile file that still sets `workers`, which moved to the machine's config.json (ATK-529): it
|
|
481
|
+
// is read and ignored, so a warn, beside whatever else the profile check found.
|
|
482
|
+
function withMovedWorkers(outcome, files) {
|
|
483
|
+
if (files.length === 0)
|
|
484
|
+
return outcome;
|
|
485
|
+
const moved = `${files.join(' and ')} ${files.length === 1 ? 'sets' : 'set'} "workers", which moved to the machine's config.json and is ignored here`;
|
|
486
|
+
const fix = 'set "workers": { "tab": true } in ~/.conductor/config.json on each machine that wants a tab per launch, and drop "workers" from the profile';
|
|
487
|
+
return {
|
|
488
|
+
details: outcome.details,
|
|
489
|
+
fix: outcome.fix === null ? fix : `${outcome.fix}; ${fix}`,
|
|
490
|
+
status: 'warn',
|
|
491
|
+
summary: outcome.status === 'ok' ? `${outcome.summary}, but ${moved}` : `${outcome.summary}; ${moved}`,
|
|
492
|
+
};
|
|
432
493
|
}
|
|
433
494
|
// The launch's worktree stages on a throwaway worktree (manager.ts launchWorker): `git worktree add`
|
|
434
495
|
// from origin/<base>, the local files and setup, with ports as the broker would give them, then the
|
|
435
496
|
// skills check and the trust probe a launch's `claude --bg` meets (ATK-571). Whatever it made is
|
|
436
497
|
// removed, whichever way it went.
|
|
437
|
-
async function worktreeCheck(deps,
|
|
498
|
+
async function worktreeCheck(deps, { clone, dir }, profile, options) {
|
|
438
499
|
if (!options.worktree)
|
|
439
500
|
return skip('--no-worktree');
|
|
440
501
|
const name = `conduct-check-${stamp((deps.now ?? (() => new Date()))())}`;
|
|
441
|
-
const left =
|
|
502
|
+
const left = join(dir, name);
|
|
442
503
|
// The ports check reports a pool with too few free; setup then runs without them.
|
|
443
504
|
const env = portEnv(await deps.ports.previewPorts(profile.ports).catch(() => ({})));
|
|
444
505
|
let outcome;
|
|
445
506
|
try {
|
|
446
507
|
const path = await deps.worktrees.create({
|
|
447
508
|
base: profile.baseBranch,
|
|
509
|
+
clone,
|
|
448
510
|
env: { ...env, CONDUCTOR_ITEM: name, CONDUCTOR_PROJECT: profile.name, CONDUCTOR_WORKTREE: left },
|
|
449
511
|
...(profile.localFilesFrom === undefined ? {} : { localFilesFrom: profile.localFilesFrom }),
|
|
450
512
|
name,
|
|
451
|
-
|
|
513
|
+
path: left,
|
|
452
514
|
sessionEnv: env,
|
|
453
515
|
setup: profile.setup,
|
|
454
516
|
});
|
|
@@ -457,7 +519,7 @@ async function worktreeCheck(deps, root, profile, options) {
|
|
|
457
519
|
const ignored = await (deps.ignores ?? gitIgnores)(path, SESSION_SETTINGS);
|
|
458
520
|
const made = `created ${name} from origin/${profile.baseBranch}${profile.setup.length > 0 ? ` and ran its ${profile.setup.length} setup ${profile.setup.length === 1 ? 'command' : 'commands'}` : ''}, then removed it`;
|
|
459
521
|
if (problem !== null) {
|
|
460
|
-
outcome = fail(
|
|
522
|
+
outcome = fail(`${MISSING_SKILLS} ${problem}`, "add what installs them to the profile's setup, or install them in ~/.claude/skills");
|
|
461
523
|
}
|
|
462
524
|
else {
|
|
463
525
|
// Before the remove below: the probe's session is gone before its directory is.
|
|
@@ -470,9 +532,9 @@ async function worktreeCheck(deps, root, profile, options) {
|
|
|
470
532
|
}
|
|
471
533
|
outcome =
|
|
472
534
|
'error' in trust
|
|
473
|
-
? fail(`claude --bg couldn't start a session in a new worktree: ${messageOf(trust.error)}`, 'a launch fails the same way: run `claude --bg` in a worktree of the
|
|
535
|
+
? fail(`claude --bg couldn't start a session in a new worktree: ${messageOf(trust.error)}`, 'a launch fails the same way: run `claude --bg` in a worktree of the clone to see why', detailsOf(trust.error))
|
|
474
536
|
: !trust.trusted
|
|
475
|
-
? fail(`Claude Code doesn't trust ${
|
|
537
|
+
? fail(`Claude Code doesn't trust ${clone}, so no worker can start in its worktrees`, `${trustStep(clone)}, then check again`)
|
|
476
538
|
: trust.removeError !== null
|
|
477
539
|
? warn(`${made}, but the trust probe's idle session ${trust.session} is still there`, `remove it: claude rm ${trust.session}`, [trust.removeError])
|
|
478
540
|
: !ignored && Object.keys(env).length > 0
|
|
@@ -486,10 +548,10 @@ async function worktreeCheck(deps, root, profile, options) {
|
|
|
486
548
|
// A create that failed part way can leave its directory or branch behind; with neither, this
|
|
487
549
|
// removes nothing.
|
|
488
550
|
try {
|
|
489
|
-
await deps.worktrees.remove(
|
|
551
|
+
await deps.worktrees.remove(left, { deleteBranch: true, repo: clone });
|
|
490
552
|
}
|
|
491
553
|
catch (error) {
|
|
492
|
-
return fail(`${left} is still there: ${messageOf(error)}`, `remove it by hand: delete the directory, then git -C ${
|
|
554
|
+
return fail(`${left} is still there: ${messageOf(error)}`, `remove it by hand: delete the directory, then git -C ${clone} worktree prune and git -C ${clone} branch -D ${BRANCH_PREFIX}${name}`, [
|
|
493
555
|
`before that: ${outcome.status} ${outcome.summary}`,
|
|
494
556
|
...outcome.details,
|
|
495
557
|
...(outcome.fix === null ? [] : [`its fix: ${outcome.fix}`]),
|