@detergent-software/conductor 0.0.0-stage → 1.0.0-dev.2
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 +31 -0
- package/README.md +131 -2
- package/build/claude.js +102 -0
- package/build/claude.js.map +1 -0
- package/build/cli.js +450 -0
- package/build/cli.js.map +1 -0
- package/build/client.js +64 -0
- package/build/client.js.map +1 -0
- package/build/conditions.js +198 -0
- package/build/conditions.js.map +1 -0
- package/build/config.js +74 -0
- package/build/config.js.map +1 -0
- package/build/daemon.js +127 -0
- package/build/daemon.js.map +1 -0
- package/build/errors.js +17 -0
- package/build/errors.js.map +1 -0
- package/build/jsonc.js +24 -0
- package/build/jsonc.js.map +1 -0
- package/build/lock.js +64 -0
- package/build/lock.js.map +1 -0
- package/build/log.js +43 -0
- package/build/log.js.map +1 -0
- package/build/manager.js +614 -0
- package/build/manager.js.map +1 -0
- package/build/paths.js +16 -0
- package/build/paths.js.map +1 -0
- package/build/profile.js +74 -0
- package/build/profile.js.map +1 -0
- package/build/registry.js +33 -0
- package/build/registry.js.map +1 -0
- package/build/resolve.js +28 -0
- package/build/resolve.js.map +1 -0
- package/build/run.js +52 -0
- package/build/run.js.map +1 -0
- package/build/server.js +227 -0
- package/build/server.js.map +1 -0
- package/build/spend.js +134 -0
- package/build/spend.js.map +1 -0
- package/build/startup.js +78 -0
- package/build/startup.js.map +1 -0
- package/build/store.js +528 -0
- package/build/store.js.map +1 -0
- package/build/tab.js +26 -0
- package/build/tab.js.map +1 -0
- package/build/token.js +58 -0
- package/build/token.js.map +1 -0
- package/build/upgrade.js +93 -0
- package/build/upgrade.js.map +1 -0
- package/build/wait.js +149 -0
- package/build/wait.js.map +1 -0
- package/build/watchers/ado.js +167 -0
- package/build/watchers/ado.js.map +1 -0
- package/build/watchers/github.js +82 -0
- package/build/watchers/github.js.map +1 -0
- package/build/watchers/health.js +42 -0
- package/build/watchers/health.js.map +1 -0
- package/build/watchers/index.js +226 -0
- package/build/watchers/index.js.map +1 -0
- package/build/watchers/linear-key.js +56 -0
- package/build/watchers/linear-key.js.map +1 -0
- package/build/watchers/linear.js +218 -0
- package/build/watchers/linear.js.map +1 -0
- package/build/watchers/npm.js +35 -0
- package/build/watchers/npm.js.map +1 -0
- package/build/watchers/source.js +94 -0
- package/build/watchers/source.js.map +1 -0
- package/build/worktrees.js +143 -0
- package/build/worktrees.js.map +1 -0
- package/package.json +56 -5
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { parseCondition, sourceOf } from "../conditions.js";
|
|
2
|
+
import { ConductorError } from "../errors.js";
|
|
3
|
+
import { resolveOpenWait } from "../resolve.js";
|
|
4
|
+
import { ACTIVE_STATUSES } from "../store.js";
|
|
5
|
+
import { adoSource } from "./ado.js";
|
|
6
|
+
import { githubSource } from "./github.js";
|
|
7
|
+
import { healthSource } from "./health.js";
|
|
8
|
+
import { linearSource } from "./linear.js";
|
|
9
|
+
import { npmSource } from "./npm.js";
|
|
10
|
+
import { rateBudget } from "./source.js";
|
|
11
|
+
// A source that fails waits twice as long each time, up to this.
|
|
12
|
+
const MAX_BACKOFF_MS = 10 * 60_000;
|
|
13
|
+
const TICK_MS = 1000;
|
|
14
|
+
export function defaultSources() {
|
|
15
|
+
return [adoSource(), githubSource(), healthSource(), linearSource(), npmSource()];
|
|
16
|
+
}
|
|
17
|
+
// The daemon's watchers: each API is polled only while some wait needs it, at its interval, within
|
|
18
|
+
// its budget. A problem with one wait is recorded once (`wait.check-failed`) and the wait stays
|
|
19
|
+
// open; a failing API backs off and is recorded once per spell (`watcher.failed`, then
|
|
20
|
+
// `watcher.recovered`). A `watcher.stats` event every `statsMinutes` records what each API was asked.
|
|
21
|
+
export function startWatchers(deps) {
|
|
22
|
+
const { config, log, store } = deps;
|
|
23
|
+
const now = deps.now ?? (() => new Date());
|
|
24
|
+
const sources = deps.sources ?? defaultSources();
|
|
25
|
+
const state = new Map(sources.map((s) => [
|
|
26
|
+
s,
|
|
27
|
+
{ budget: rateBudget(settings(config, s).perMinute, () => now().getTime()), failures: 0, nextAt: 0, passes: 0, running: null, totalFailures: 0, turn: 0 },
|
|
28
|
+
]));
|
|
29
|
+
// The last problem recorded per wait, so each is recorded once.
|
|
30
|
+
const problems = new Map();
|
|
31
|
+
let statsAt = now().getTime();
|
|
32
|
+
let timer;
|
|
33
|
+
let passing = null;
|
|
34
|
+
// Each open wait with a live worker, as its source sees it. A wait whose form no longer parses
|
|
35
|
+
// (written by another build) is reported once and left alone.
|
|
36
|
+
const openChecks = () => {
|
|
37
|
+
const profiles = new Map(store.listProjects().map((p) => [p.name, p.profile]));
|
|
38
|
+
const bySource = new Map();
|
|
39
|
+
for (const wait of store.openWaits()) {
|
|
40
|
+
const condition = parsed(wait);
|
|
41
|
+
if (condition === null || condition.kind === 'time')
|
|
42
|
+
continue;
|
|
43
|
+
const worker = store.getWorker(wait.worker);
|
|
44
|
+
if (!worker || !ACTIVE_STATUSES.includes(worker.status))
|
|
45
|
+
continue;
|
|
46
|
+
const source = sources.find((s) => s.name === sourceOf(condition));
|
|
47
|
+
if (!source)
|
|
48
|
+
continue;
|
|
49
|
+
const checks = bySource.get(source) ?? [];
|
|
50
|
+
checks.push({ condition, createdAt: wait.createdAt, id: wait.id, profile: profiles.get(worker.project) ?? null });
|
|
51
|
+
bySource.set(source, checks);
|
|
52
|
+
}
|
|
53
|
+
return bySource;
|
|
54
|
+
};
|
|
55
|
+
const parsed = (wait) => {
|
|
56
|
+
try {
|
|
57
|
+
return parseCondition(wait.condition);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
problem(wait.id, error instanceof ConductorError ? error.message : String(error));
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const problem = (id, error) => {
|
|
65
|
+
if (problems.get(id) === error)
|
|
66
|
+
return;
|
|
67
|
+
problems.set(id, error);
|
|
68
|
+
const wait = store.getWait(id);
|
|
69
|
+
const worker = wait ? store.getWorker(wait.worker) : null;
|
|
70
|
+
store.appendEvent({
|
|
71
|
+
data: { condition: wait?.condition ?? null, error, wait: id, worker: worker?.id ?? null },
|
|
72
|
+
kind: 'wait.check-failed',
|
|
73
|
+
project: worker?.project ?? null,
|
|
74
|
+
subject: worker?.item ?? null,
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
const checkSource = async (source, s, checks) => {
|
|
78
|
+
const interval = settings(config, source).intervalSeconds * 1000;
|
|
79
|
+
s.passes++;
|
|
80
|
+
// Each pass starts one wait further on, so when the budget runs out mid-pass the same waits
|
|
81
|
+
// aren't the ones left unchecked every time.
|
|
82
|
+
const turn = s.turn++ % checks.length;
|
|
83
|
+
const ordered = [...checks.slice(turn), ...checks.slice(0, turn)];
|
|
84
|
+
try {
|
|
85
|
+
const outcomes = await source.check(ordered, s.budget);
|
|
86
|
+
let resolved = 0;
|
|
87
|
+
for (const [id, outcome] of outcomes) {
|
|
88
|
+
if ('error' in outcome) {
|
|
89
|
+
problem(id, outcome.error);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const wait = store.getWait(id);
|
|
93
|
+
if (wait && resolveOpenWait(store, wait, outcome.resolution))
|
|
94
|
+
resolved++;
|
|
95
|
+
problems.delete(id);
|
|
96
|
+
}
|
|
97
|
+
if (s.failures > 0)
|
|
98
|
+
store.appendEvent({ data: { failures: s.failures, source: source.name }, kind: 'watcher.recovered' });
|
|
99
|
+
s.failures = 0;
|
|
100
|
+
s.nextAt = now().getTime() + interval;
|
|
101
|
+
if (resolved > 0)
|
|
102
|
+
deps.onResolved?.();
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
s.failures++;
|
|
106
|
+
s.totalFailures++;
|
|
107
|
+
const delay = Math.min(interval * 2 ** s.failures, MAX_BACKOFF_MS);
|
|
108
|
+
s.nextAt = now().getTime() + delay;
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
if (s.failures === 1) {
|
|
111
|
+
store.appendEvent({ data: { error: message, retrySeconds: delay / 1000, source: source.name }, kind: 'watcher.failed' });
|
|
112
|
+
}
|
|
113
|
+
log.log('warn', 'watcher failed', { error: message, failures: s.failures, source: source.name });
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const recordStats = (open) => {
|
|
117
|
+
const minutes = (now().getTime() - statsAt) / 60_000;
|
|
118
|
+
if (minutes < config.statsMinutes)
|
|
119
|
+
return;
|
|
120
|
+
statsAt = now().getTime();
|
|
121
|
+
const counts = {};
|
|
122
|
+
for (const [source, s] of state) {
|
|
123
|
+
counts[source.name] = { failures: s.totalFailures, passes: s.passes, waits: open.get(source)?.length ?? 0, ...s.budget.drain() };
|
|
124
|
+
s.passes = 0;
|
|
125
|
+
s.totalFailures = 0;
|
|
126
|
+
}
|
|
127
|
+
// A window in which nothing waited and nothing was asked leaves no event: the log is append-only.
|
|
128
|
+
if (Object.values(counts).every((c) => Object.values(c).every((n) => n === 0)))
|
|
129
|
+
return;
|
|
130
|
+
store.appendEvent({ data: { minutes: Math.round(minutes), sources: counts }, kind: 'watcher.stats' });
|
|
131
|
+
};
|
|
132
|
+
const passOnce = async () => {
|
|
133
|
+
const open = openChecks();
|
|
134
|
+
for (const id of problems.keys())
|
|
135
|
+
if (store.getWait(id)?.resolvedAt !== null)
|
|
136
|
+
problems.delete(id);
|
|
137
|
+
const started = [];
|
|
138
|
+
for (const [source, s] of state) {
|
|
139
|
+
const checks = open.get(source);
|
|
140
|
+
// Nothing waits: no request, and the next wait is checked at once.
|
|
141
|
+
if (!checks || checks.length === 0 || s.running || now().getTime() < s.nextAt)
|
|
142
|
+
continue;
|
|
143
|
+
s.running = checkSource(source, s, checks).finally(() => (s.running = null));
|
|
144
|
+
started.push(s.running);
|
|
145
|
+
}
|
|
146
|
+
await Promise.all(started);
|
|
147
|
+
recordStats(open);
|
|
148
|
+
};
|
|
149
|
+
const watchers = {
|
|
150
|
+
idle: async () => {
|
|
151
|
+
while (passing || [...state.values()].some((s) => s.running)) {
|
|
152
|
+
await Promise.allSettled([passing, ...[...state.values()].map((s) => s.running)]);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
// Never rejects: it runs on a timer.
|
|
156
|
+
pass: () => {
|
|
157
|
+
passing ??= passOnce()
|
|
158
|
+
.catch((error) => log.log('error', 'watcher pass failed', { error: String(error) }))
|
|
159
|
+
.finally(() => (passing = null));
|
|
160
|
+
return passing;
|
|
161
|
+
},
|
|
162
|
+
start: () => {
|
|
163
|
+
timer = setInterval(() => void watchers.pass(), TICK_MS);
|
|
164
|
+
timer.unref();
|
|
165
|
+
},
|
|
166
|
+
stop: async () => {
|
|
167
|
+
clearInterval(timer);
|
|
168
|
+
await watchers.idle();
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
return watchers;
|
|
172
|
+
}
|
|
173
|
+
// `conduct wait` without a daemon: checks the condition itself until it holds (its resolution) or
|
|
174
|
+
// the time is up (null). A problem with the wait itself, such as a PR that doesn't exist, is
|
|
175
|
+
// thrown, since waiting longer can't help.
|
|
176
|
+
export async function waitLocally(options) {
|
|
177
|
+
const { condition, profile, timeoutMs } = options;
|
|
178
|
+
const now = options.now ?? (() => new Date());
|
|
179
|
+
const sleep = options.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
|
|
180
|
+
const deadline = now().getTime() + timeoutMs;
|
|
181
|
+
const interval = options.intervalMs ?? 30_000;
|
|
182
|
+
const source = condition.kind === 'time' ? null : (options.sources ?? defaultSources()).find((s) => s.name === sourceOf(condition));
|
|
183
|
+
if (condition.kind !== 'time' && !source)
|
|
184
|
+
throw new ConductorError(`no source checks ${sourceOf(condition)} conditions`);
|
|
185
|
+
const budget = rateBudget(options.perMinute ?? 30, () => now().getTime());
|
|
186
|
+
const check = { condition, createdAt: now().toISOString(), id: 1, profile };
|
|
187
|
+
let said = null;
|
|
188
|
+
let failures = 0;
|
|
189
|
+
for (;;) {
|
|
190
|
+
let delay = interval;
|
|
191
|
+
if (condition.kind === 'time') {
|
|
192
|
+
const at = Date.parse(condition.at);
|
|
193
|
+
if (now().getTime() >= at)
|
|
194
|
+
return `time ${condition.at} reached`;
|
|
195
|
+
delay = Math.min(interval, at - now().getTime());
|
|
196
|
+
}
|
|
197
|
+
else if (source) {
|
|
198
|
+
try {
|
|
199
|
+
const outcome = (await source.check([check], budget)).get(check.id);
|
|
200
|
+
if (outcome && 'resolution' in outcome)
|
|
201
|
+
return outcome.resolution;
|
|
202
|
+
if (outcome)
|
|
203
|
+
throw new ConductorError(`can't wait on ${condition.kind}: ${outcome.error}`);
|
|
204
|
+
failures = 0;
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (error instanceof ConductorError)
|
|
208
|
+
throw error;
|
|
209
|
+
failures++;
|
|
210
|
+
delay = Math.min(interval * 2 ** failures, MAX_BACKOFF_MS);
|
|
211
|
+
const line = `${source.name} failed: ${error instanceof Error ? error.message : String(error)}; trying again`;
|
|
212
|
+
if (line !== said)
|
|
213
|
+
options.report?.(line);
|
|
214
|
+
said = line;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const left = deadline - now().getTime();
|
|
218
|
+
if (left <= 0)
|
|
219
|
+
return null;
|
|
220
|
+
await sleep(Math.min(delay, left));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function settings(config, source) {
|
|
224
|
+
return config[source.name];
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/watchers/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { run } from "../run.js";
|
|
2
|
+
// The Linear API key the conductor reads with (ATK-272, ported from conduct-atk's linear-key.mjs):
|
|
3
|
+
// LINEAR_API_KEY, else the Windows generic credential `atk-conductor-linear` (the password is the
|
|
4
|
+
// key). Node can't read Credential Manager and `cmdkey` can't print a secret, so PowerShell calls
|
|
5
|
+
// advapi32's CredRead. The key is only returned: never logged, stored or put on a command line.
|
|
6
|
+
export const LINEAR_CREDENTIAL = 'atk-conductor-linear';
|
|
7
|
+
const NOT_FOUND = 3;
|
|
8
|
+
// Win32 ERROR_NOT_FOUND: CredRead's "no such credential". Any other failure is a broken read.
|
|
9
|
+
const ERROR_NOT_FOUND = 1168;
|
|
10
|
+
const CRED_READ = `
|
|
11
|
+
$ErrorActionPreference = 'Stop'
|
|
12
|
+
try {
|
|
13
|
+
Add-Type -Namespace AtkConduct -Name Cred -MemberDefinition @'
|
|
14
|
+
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
15
|
+
public static extern bool CredRead(string target, int type, int flags, out System.IntPtr cred);
|
|
16
|
+
[DllImport("advapi32.dll")]
|
|
17
|
+
public static extern void CredFree(System.IntPtr cred);
|
|
18
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
19
|
+
public struct CREDENTIAL { public int Flags; public int Type; public string TargetName; public string Comment;
|
|
20
|
+
public long LastWritten; public int CredentialBlobSize; public System.IntPtr CredentialBlob; public int Persist;
|
|
21
|
+
public int AttributeCount; public System.IntPtr Attributes; public string TargetAlias; public string UserName; }
|
|
22
|
+
'@
|
|
23
|
+
$p = [IntPtr]::Zero
|
|
24
|
+
if (-not [AtkConduct.Cred]::CredRead($env:ATK_CONDUCT_CRED_TARGET, 1, 0, [ref]$p)) {
|
|
25
|
+
$code = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
|
|
26
|
+
if ($code -eq ${ERROR_NOT_FOUND}) { exit ${NOT_FOUND} }
|
|
27
|
+
[Console]::Error.WriteLine("CredRead failed with Win32 error $code"); exit 4
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
$c = [Runtime.InteropServices.Marshal]::PtrToStructure($p, [type][AtkConduct.Cred+CREDENTIAL])
|
|
31
|
+
[Console]::Out.Write([Runtime.InteropServices.Marshal]::PtrToStringUni($c.CredentialBlob, $c.CredentialBlobSize / 2))
|
|
32
|
+
} finally { [AtkConduct.Cred]::CredFree($p) }
|
|
33
|
+
} catch { [Console]::Error.WriteLine($_.Exception.Message); exit 4 }
|
|
34
|
+
`;
|
|
35
|
+
// ATK_CONDUCT_LINEAR_CREDENTIAL names another credential target, as in conduct-atk.
|
|
36
|
+
export async function readLinearKey(env = process.env) {
|
|
37
|
+
const fromEnv = env.LINEAR_API_KEY?.trim();
|
|
38
|
+
if (fromEnv)
|
|
39
|
+
return { key: fromEnv, source: 'LINEAR_API_KEY' };
|
|
40
|
+
const target = env.ATK_CONDUCT_LINEAR_CREDENTIAL?.trim() || LINEAR_CREDENTIAL;
|
|
41
|
+
if (process.platform !== 'win32')
|
|
42
|
+
return { key: null, reason: `no LINEAR_API_KEY (the ${target} credential is Windows-only)` };
|
|
43
|
+
const result = await run('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(CRED_READ, 'utf16le').toString('base64')], { env: { ...env, ATK_CONDUCT_CRED_TARGET: target }, timeoutMs: 30_000 });
|
|
44
|
+
const key = result.code === 0 ? result.stdout.trim() : '';
|
|
45
|
+
if (key)
|
|
46
|
+
return { key, source: `credential ${target}` };
|
|
47
|
+
if (result.code === NOT_FOUND)
|
|
48
|
+
return { key: null, reason: `no LINEAR_API_KEY and no credential ${target}` };
|
|
49
|
+
// Never echo stdout here: on a partial failure it could hold the key.
|
|
50
|
+
const line = result.stderr
|
|
51
|
+
.split(/\r?\n/)
|
|
52
|
+
.map((l) => l.trim())
|
|
53
|
+
.find((l) => l && !l.startsWith('#< CLIXML') && !l.startsWith('<Objs'));
|
|
54
|
+
return { key: null, reason: `no LINEAR_API_KEY, and the credential ${target} couldn't be read (${result.error ?? line ?? `exit ${result.code}`})` };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=linear-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-key.js","sourceRoot":"","sources":["../../src/watchers/linear-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,mGAAmG;AACnG,kGAAkG;AAClG,kGAAkG;AAClG,gGAAgG;AAChG,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAIxD,MAAM,SAAS,GAAG,CAAC,CAAC;AACpB,8FAA8F;AAC9F,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;kBAgBA,eAAe,YAAY,SAAS;;;;;;;;CAQrD,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAyB,OAAO,CAAC,GAAG;IACtE,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAI,OAAO;QAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,6BAA6B,EAAE,IAAI,EAAE,IAAI,iBAAiB,CAAC;IAC9E,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,0BAA0B,MAAM,8BAA8B,EAAE,CAAC;IAC/H,MAAM,MAAM,GAAG,MAAM,GAAG,CACtB,gBAAgB,EAChB,CAAC,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAC1G,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,uBAAuB,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CACxE,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1D,IAAI,GAAG;QAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,MAAM,EAAE,EAAE,CAAC;IACxD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,uCAAuC,MAAM,EAAE,EAAE,CAAC;IAC7G,sEAAsE;IACtE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM;SACvB,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1E,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,yCAAyC,MAAM,sBAAsB,MAAM,CAAC,KAAK,IAAI,IAAI,IAAI,QAAQ,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;AACtJ,CAAC"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ConductorError } from "../errors.js";
|
|
3
|
+
import { readLinearKey } from "./linear-key.js";
|
|
4
|
+
import { perWait, spend, untilSpent, WaitProblem } from "./source.js";
|
|
5
|
+
export const LINEAR_API = 'https://api.linear.app/graphql';
|
|
6
|
+
const PAGE = 100;
|
|
7
|
+
const TIMEOUT_MS = 20_000;
|
|
8
|
+
const OVERLAP_MS = 2 * 60_000;
|
|
9
|
+
// One request covers every waited item of a team: the issues (state, labels) and their comments
|
|
10
|
+
// (phase notes, `approve` / `revise:`). After the first pass both are filtered on `updatedAt`, so a
|
|
11
|
+
// quiet pass returns almost nothing. Comments are filtered on their own: a new comment doesn't
|
|
12
|
+
// change its issue's `updatedAt` (seen on ATK-310, 2026-10-10).
|
|
13
|
+
export const WAITS_QUERY = `query Waits($issues: IssueFilter!, $comments: CommentFilter!, $after: String) {
|
|
14
|
+
viewer { id }
|
|
15
|
+
issues(first: ${PAGE}, includeArchived: true, filter: $issues) {
|
|
16
|
+
nodes { identifier updatedAt state { name type } labels(first: 50) { nodes { name } } }
|
|
17
|
+
}
|
|
18
|
+
comments(first: ${PAGE}, after: $after, filter: $comments) {
|
|
19
|
+
pageInfo { hasNextPage endCursor }
|
|
20
|
+
nodes { id body createdAt updatedAt user { id } issue { identifier } }
|
|
21
|
+
}
|
|
22
|
+
}`;
|
|
23
|
+
const answer = z.object({
|
|
24
|
+
comments: z.object({
|
|
25
|
+
nodes: z.array(z.object({
|
|
26
|
+
body: z.string(),
|
|
27
|
+
createdAt: z.string(),
|
|
28
|
+
id: z.string(),
|
|
29
|
+
issue: z.object({ identifier: z.string() }).nullable(),
|
|
30
|
+
updatedAt: z.string(),
|
|
31
|
+
user: z.object({ id: z.string() }).nullable(),
|
|
32
|
+
})),
|
|
33
|
+
pageInfo: z.object({ endCursor: z.string().nullable(), hasNextPage: z.boolean() }),
|
|
34
|
+
}),
|
|
35
|
+
issues: z.object({
|
|
36
|
+
nodes: z.array(z.object({
|
|
37
|
+
identifier: z.string(),
|
|
38
|
+
labels: z.object({ nodes: z.array(z.object({ name: z.string() })) }),
|
|
39
|
+
state: z.object({ name: z.string(), type: z.string() }),
|
|
40
|
+
updatedAt: z.string(),
|
|
41
|
+
})),
|
|
42
|
+
}),
|
|
43
|
+
viewer: z.object({ id: z.string() }),
|
|
44
|
+
});
|
|
45
|
+
// The first `approve` / `revise: …` comment by `approverId` (the key's owner, Jason) after the
|
|
46
|
+
// newest `<skill> · Plan` note, as conduct-atk reads it. Notes carry the same author as Jason's
|
|
47
|
+
// comments, so the heading is what tells them apart.
|
|
48
|
+
export function approvalAfterPlan(comments, approverId, notePrefix) {
|
|
49
|
+
const sorted = [...comments].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
|
|
50
|
+
const plan = sorted.findLastIndex((c) => phaseOf(c.body, notePrefix) === 'Plan');
|
|
51
|
+
if (plan === -1 || approverId === null)
|
|
52
|
+
return null;
|
|
53
|
+
for (const c of sorted.slice(plan + 1)) {
|
|
54
|
+
if (c.authorId !== approverId || phaseOf(c.body, notePrefix) !== null)
|
|
55
|
+
continue;
|
|
56
|
+
const text = c.body.trim();
|
|
57
|
+
if (/^approved?\b/i.test(text))
|
|
58
|
+
return { at: c.createdAt, kind: 'approve' };
|
|
59
|
+
if (/^revise\s*:/i.test(text))
|
|
60
|
+
return { at: c.createdAt, kind: 'revise' };
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
// Linear: approval (`approve` / `revise:` after the newest Plan), and an item's state, labels and
|
|
65
|
+
// phase notes. The team is the item's key prefix (ATK-271 is team ATK). State lives in memory, so
|
|
66
|
+
// a restart reads each item in full once.
|
|
67
|
+
export function linearSource({ fetch: fetcher = globalThis.fetch, key = readLinearKey, url = LINEAR_API, } = {}) {
|
|
68
|
+
const teams = new Map();
|
|
69
|
+
let apiKey = null;
|
|
70
|
+
const post = async (variables) => {
|
|
71
|
+
if (apiKey === null) {
|
|
72
|
+
const found = await key();
|
|
73
|
+
// A local wait can't go on without a key, so it stops there; the daemon backs off.
|
|
74
|
+
if (found.key === null)
|
|
75
|
+
throw new ConductorError('no Linear API key', [found.reason]);
|
|
76
|
+
apiKey = found.key;
|
|
77
|
+
}
|
|
78
|
+
const secret = apiKey;
|
|
79
|
+
let response;
|
|
80
|
+
try {
|
|
81
|
+
response = await fetcher(url, {
|
|
82
|
+
body: JSON.stringify({ query: WAITS_QUERY, variables }),
|
|
83
|
+
headers: { authorization: secret, 'content-type': 'application/json' },
|
|
84
|
+
method: 'POST',
|
|
85
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
throw new Error(redact(`Linear API unreachable: ${error instanceof Error ? error.message : String(error)}`, secret));
|
|
90
|
+
}
|
|
91
|
+
const body = z
|
|
92
|
+
.looseObject({ data: z.unknown().optional(), errors: z.array(z.looseObject({ message: z.string() })).optional() })
|
|
93
|
+
.safeParse(await response.json().catch(() => null)).data;
|
|
94
|
+
if (!response.ok || body?.errors?.length || body?.data == null) {
|
|
95
|
+
if (response.status === 401)
|
|
96
|
+
apiKey = null; // read it again next pass: it may have been replaced
|
|
97
|
+
const reason = body?.errors?.map((e) => e.message).join('; ') || `HTTP ${response.status}`;
|
|
98
|
+
throw new Error(redact(`Linear API: ${reason}`, secret));
|
|
99
|
+
}
|
|
100
|
+
return answer.parse(body.data);
|
|
101
|
+
};
|
|
102
|
+
// Reads what changed for the team's waited items into its state.
|
|
103
|
+
const refresh = async (key, numbers, spendOne) => {
|
|
104
|
+
const team = teams.get(key) ?? { covered: new Set(), issues: new Map(), viewer: null, watermark: null };
|
|
105
|
+
teams.set(key, team);
|
|
106
|
+
const full = team.watermark === null || numbers.some((n) => !team.covered.has(n));
|
|
107
|
+
const scope = { number: { in: numbers }, team: { key: { eq: key } } };
|
|
108
|
+
// Asked with an overlap, so a write committed with an earlier timestamp than one already seen
|
|
109
|
+
// isn't skipped. What comes back twice replaces itself (comments are kept by id).
|
|
110
|
+
const since = full || team.watermark === null ? {} : { updatedAt: { gte: new Date(Date.parse(team.watermark) - OVERLAP_MS).toISOString() } };
|
|
111
|
+
const fresh = new Map();
|
|
112
|
+
let newest = team.watermark;
|
|
113
|
+
const later = (at) => {
|
|
114
|
+
if (newest === null || at > newest)
|
|
115
|
+
newest = at;
|
|
116
|
+
};
|
|
117
|
+
for (let after = null, more = true; more;) {
|
|
118
|
+
spendOne();
|
|
119
|
+
const data = await post({ after, comments: { issue: scope, ...since }, issues: { ...scope, ...since } });
|
|
120
|
+
team.viewer = data.viewer.id;
|
|
121
|
+
for (const node of data.issues.nodes) {
|
|
122
|
+
const issue = (full ? fresh.get(node.identifier) : team.issues.get(node.identifier)) ?? { comments: new Map(), labels: [], state: null };
|
|
123
|
+
issue.state = node.state;
|
|
124
|
+
issue.labels = node.labels.nodes.map((l) => l.name);
|
|
125
|
+
(full ? fresh : team.issues).set(node.identifier, issue);
|
|
126
|
+
later(node.updatedAt);
|
|
127
|
+
}
|
|
128
|
+
for (const node of data.comments.nodes) {
|
|
129
|
+
const id = node.issue?.identifier;
|
|
130
|
+
if (!id)
|
|
131
|
+
continue;
|
|
132
|
+
const target = full ? fresh : team.issues;
|
|
133
|
+
const issue = target.get(id) ?? { comments: new Map(), labels: [], state: null };
|
|
134
|
+
issue.comments.set(node.id, { authorId: node.user?.id ?? null, body: node.body, createdAt: node.createdAt });
|
|
135
|
+
target.set(id, issue);
|
|
136
|
+
later(node.updatedAt);
|
|
137
|
+
}
|
|
138
|
+
after = data.comments.pageInfo.endCursor;
|
|
139
|
+
more = data.comments.pageInfo.hasNextPage && after !== null;
|
|
140
|
+
}
|
|
141
|
+
if (full) {
|
|
142
|
+
team.issues = fresh;
|
|
143
|
+
team.covered = new Set(numbers);
|
|
144
|
+
}
|
|
145
|
+
team.watermark = newest;
|
|
146
|
+
return team;
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
check: async (waits, budget) => {
|
|
150
|
+
const outcomes = new Map();
|
|
151
|
+
const byTeam = new Map();
|
|
152
|
+
for (const wait of waits) {
|
|
153
|
+
const c = wait.condition;
|
|
154
|
+
if (c.kind !== 'approval' && c.kind !== 'item')
|
|
155
|
+
continue;
|
|
156
|
+
const [prefix = ''] = c.item.split('-');
|
|
157
|
+
const group = byTeam.get(prefix) ?? new Map();
|
|
158
|
+
group.set(wait.id, wait);
|
|
159
|
+
byTeam.set(prefix, group);
|
|
160
|
+
}
|
|
161
|
+
await untilSpent(async () => {
|
|
162
|
+
for (const [prefix, group] of byTeam) {
|
|
163
|
+
const numbers = [
|
|
164
|
+
...new Set([...group.values()].flatMap(({ condition: c }) => (c.kind === 'approval' || c.kind === 'item' ? [itemNumber(c.item)] : []))),
|
|
165
|
+
];
|
|
166
|
+
const team = await refresh(prefix, numbers, () => spend(budget));
|
|
167
|
+
for (const wait of group.values()) {
|
|
168
|
+
const c = wait.condition;
|
|
169
|
+
if (c.kind !== 'approval' && c.kind !== 'item')
|
|
170
|
+
continue;
|
|
171
|
+
await perWait(outcomes, wait.id, async () => {
|
|
172
|
+
const issue = team.issues.get(c.item);
|
|
173
|
+
if (!issue?.state)
|
|
174
|
+
throw new WaitProblem(`Linear has no issue ${c.item}`);
|
|
175
|
+
const prefixOfNotes = `${wait.profile?.skills.work ?? 'work-issue'} · `;
|
|
176
|
+
const comments = [...issue.comments.values()];
|
|
177
|
+
if (c.kind === 'approval') {
|
|
178
|
+
const found = approvalAfterPlan(comments, team.viewer, prefixOfNotes);
|
|
179
|
+
return found ? `approval ${c.item}: ${found.kind}` : null;
|
|
180
|
+
}
|
|
181
|
+
const value = c.value.toLowerCase();
|
|
182
|
+
if (c.field === 'state') {
|
|
183
|
+
const { name, type } = issue.state;
|
|
184
|
+
return name.toLowerCase() === value || type.toLowerCase() === value ? `item ${c.item} state ${name}` : null;
|
|
185
|
+
}
|
|
186
|
+
if (c.field === 'label') {
|
|
187
|
+
const label = issue.labels.find((l) => l.toLowerCase() === value);
|
|
188
|
+
return label ? `item ${c.item} labelled ${label}` : null;
|
|
189
|
+
}
|
|
190
|
+
const note = comments.map((x) => phaseOf(x.body, prefixOfNotes)).find((p) => p?.toLowerCase() === value);
|
|
191
|
+
return note ? `item ${c.item} note ${note}` : null;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
return outcomes;
|
|
197
|
+
},
|
|
198
|
+
name: 'linear',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// The phase of a `## <skill> · <Phase>[ · <kind>: …]` note's heading line, or null. "Plan approved
|
|
202
|
+
// (via …)" reads as "Plan approved".
|
|
203
|
+
export function phaseOf(body, notePrefix) {
|
|
204
|
+
const first = body.split(/\r?\n/).find((line) => line.trim() !== '') ?? '';
|
|
205
|
+
const heading = first.replace(/^[#*\s]+/, '').replace(/[*\s]+$/, '');
|
|
206
|
+
if (!heading.startsWith(notePrefix))
|
|
207
|
+
return null;
|
|
208
|
+
const rest = heading.slice(notePrefix.length);
|
|
209
|
+
const split = /^([^(·]+?) · (.+)$/.exec(rest);
|
|
210
|
+
return (split?.[1] ?? rest.replace(/\s*\(.*\)$/, '')).trim() || null;
|
|
211
|
+
}
|
|
212
|
+
function itemNumber(item) {
|
|
213
|
+
return Number(item.split('-')[1]);
|
|
214
|
+
}
|
|
215
|
+
function redact(text, secret) {
|
|
216
|
+
return text.split(secret).join('lin_api_…');
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=linear.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear.js","sourceRoot":"","sources":["../../src/watchers/linear.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getJson, perPass, perWait, untilSpent, WaitProblem } from "./source.js";
|
|
3
|
+
export const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
4
|
+
const distTags = z.record(z.string(), z.string());
|
|
5
|
+
// A version on a dist-tag, from the registry's dist-tags endpoint: a few bytes, read with no cache,
|
|
6
|
+
// so a stage approved a moment ago shows on the next pass. One request per package per pass.
|
|
7
|
+
export function npmSource({ fetch: fetcher = globalThis.fetch, registry = NPM_REGISTRY } = {}) {
|
|
8
|
+
return {
|
|
9
|
+
check: async (waits, budget) => {
|
|
10
|
+
const outcomes = new Map();
|
|
11
|
+
const ask = perPass(budget);
|
|
12
|
+
const tagsOf = (pkg) => ask(pkg, async () => {
|
|
13
|
+
const { body, status } = await getJson(fetcher, `${registry}/-/package/${pkg.replace('/', '%2f')}/dist-tags`, {
|
|
14
|
+
headers: { accept: 'application/json', 'cache-control': 'no-cache' },
|
|
15
|
+
});
|
|
16
|
+
if (status === 404)
|
|
17
|
+
throw new WaitProblem(`npm has no package ${pkg}`);
|
|
18
|
+
if (status !== 200)
|
|
19
|
+
throw new Error(`npm answered ${status} for ${pkg}'s dist-tags`);
|
|
20
|
+
return distTags.parse(body);
|
|
21
|
+
});
|
|
22
|
+
await untilSpent(async () => {
|
|
23
|
+
for (const wait of waits) {
|
|
24
|
+
const c = wait.condition;
|
|
25
|
+
if (c.kind !== 'npm')
|
|
26
|
+
continue;
|
|
27
|
+
await perWait(outcomes, wait.id, async () => (await tagsOf(c.package))[c.tag] === c.version ? `npm ${c.package}@${c.version} on ${c.tag}` : null);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return outcomes;
|
|
31
|
+
},
|
|
32
|
+
name: 'npm',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=npm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm.js","sourceRoot":"","sources":["../../src/watchers/npm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAgB,OAAO,EAAE,OAAO,EAAe,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE5G,MAAM,CAAC,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAEzD,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AAElD,oGAAoG;AACpG,6FAA6F;AAC7F,MAAM,UAAU,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,YAAY,EAAE,GAAG,EAAE;IAC3F,OAAO;QACL,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAC7B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;YAC5C,MAAM,GAAG,GAAG,OAAO,CAAyB,MAAM,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,CAAC,GAAW,EAAmC,EAAE,CAC9D,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,QAAQ,cAAc,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,YAAY,EAAE;oBAC5G,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,eAAe,EAAE,UAAU,EAAE;iBACrE,CAAC,CAAC;gBACH,IAAI,MAAM,KAAK,GAAG;oBAAE,MAAM,IAAI,WAAW,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAC;gBACvE,IAAI,MAAM,KAAK,GAAG;oBAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,MAAM,QAAQ,GAAG,cAAc,CAAC,CAAC;gBACrF,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC,CAAC,CAAC;YACL,MAAM,UAAU,CAAC,KAAK,IAAI,EAAE;gBAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;oBACzB,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK;wBAAE,SAAS;oBAC/B,MAAM,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE,CAC1C,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CACpG,CAAC;gBACJ,CAAC;YACH,CAAC,CAAC,CAAC;YACH,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,IAAI,EAAE,KAAK;KACZ,CAAC;AACJ,CAAC"}
|