@stringsync/splates 0.0.58 → 0.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/blockers.d.ts +32 -0
- package/index.js +423 -214
- package/package.json +3 -3
- package/ship.d.ts +3 -0
package/README.md
CHANGED
|
@@ -281,6 +281,16 @@ better or worse, and say what to change when one stands out. Fixed rules over
|
|
|
281
281
|
the counts make those calls, not a model, so the same outcomes always read
|
|
282
282
|
the same.
|
|
283
283
|
|
|
284
|
+
### Kimi Code
|
|
285
|
+
|
|
286
|
+
Kimi Code is on the default roster beside Claude and Codex, started as
|
|
287
|
+
`kimi acp`, Kimi's own ACP server. Its installer leaves `kimi` off the PATH,
|
|
288
|
+
so splates runs `KIMI_PATH` when set, else `kimi` on the PATH, else
|
|
289
|
+
`~/.kimi-code/bin/kimi`. A roster written before Kimi joined gains it once,
|
|
290
|
+
when the server starts on a machine with Kimi installed; take it off and it
|
|
291
|
+
stays off. Its models go by their full ids, such as `kimi-code/k3.1-pro`.
|
|
292
|
+
Sign in first with `kimi login`: splates never signs in for it.
|
|
293
|
+
|
|
284
294
|
### Optional OpenCode ACP agent
|
|
285
295
|
|
|
286
296
|
Open **Settings → Setup** and visit the optional OpenCode steps. Check this
|
package/blockers.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ElsewhereBlocker, ElsewhereBoards } from "@stringsync/splates-server/board";
|
|
2
|
+
/**
|
|
3
|
+
* Reads `--blocked-by` and `--remove-blocked-by` in the project at `here`:
|
|
4
|
+
* a number names a task here, `webappwiz#404` or `webappwiz #404` one of
|
|
5
|
+
* another project, as `boards` finds it, and this project's own slug a task
|
|
6
|
+
* here. A task of another project that `--blocked-by` names has to be open
|
|
7
|
+
* there; one `--remove-blocked-by` names need not.
|
|
8
|
+
*/
|
|
9
|
+
export declare function blockerFlags({ boards, here, blockedBy, removeBlockedBy, }: BlockerFlagsOptions): Promise<BlockerFlags>;
|
|
10
|
+
/** What `blockerFlags` reads, and where. */
|
|
11
|
+
export interface BlockerFlagsOptions {
|
|
12
|
+
boards: ElsewhereBoards;
|
|
13
|
+
/** This project's main tree. */
|
|
14
|
+
here: string;
|
|
15
|
+
/** `--blocked-by`, as given. */
|
|
16
|
+
blockedBy?: readonly string[];
|
|
17
|
+
/** `--remove-blocked-by`, as given. */
|
|
18
|
+
removeBlockedBy?: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/** The tasks the flags name, here and in other projects. */
|
|
21
|
+
export interface BlockerFlags {
|
|
22
|
+
blockedBy: number[];
|
|
23
|
+
blockedElsewhere: ElsewhereBlocker[];
|
|
24
|
+
/** Those no longer to block it. */
|
|
25
|
+
unblocked: {
|
|
26
|
+
ids: number[];
|
|
27
|
+
elsewhere: {
|
|
28
|
+
project: string;
|
|
29
|
+
id: number;
|
|
30
|
+
}[];
|
|
31
|
+
};
|
|
32
|
+
}
|
package/index.js
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
|
4
|
+
var __asyncDispose = Symbol.asyncDispose || /* @__PURE__ */ Symbol.for("Symbol.asyncDispose");
|
|
5
|
+
var __using = (stack, value, async) => {
|
|
6
|
+
if (value != null) {
|
|
7
|
+
if (typeof value !== "object" && typeof value !== "function")
|
|
8
|
+
throw TypeError('Object expected to be assigned to "using" declaration');
|
|
9
|
+
var dispose;
|
|
10
|
+
if (async)
|
|
11
|
+
dispose = value[__asyncDispose];
|
|
12
|
+
if (dispose === undefined)
|
|
13
|
+
dispose = value[__dispose];
|
|
14
|
+
if (typeof dispose !== "function")
|
|
15
|
+
throw TypeError("Object not disposable");
|
|
16
|
+
stack.push([async, dispose, value]);
|
|
17
|
+
} else if (async) {
|
|
18
|
+
stack.push([async]);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
};
|
|
22
|
+
var __callDispose = (stack, error, hasError) => {
|
|
23
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
24
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
25
|
+
}, fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e), next = (it) => {
|
|
26
|
+
while (it = stack.pop()) {
|
|
27
|
+
try {
|
|
28
|
+
var result = it[1] && it[1].call(it[2]);
|
|
29
|
+
if (it[0])
|
|
30
|
+
return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
31
|
+
} catch (e) {
|
|
32
|
+
fail(e);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (hasError)
|
|
36
|
+
throw error;
|
|
37
|
+
};
|
|
38
|
+
return next();
|
|
39
|
+
};
|
|
3
40
|
|
|
4
41
|
// index.ts
|
|
5
42
|
import { basename as basename2 } from "path";
|
|
@@ -48,9 +85,9 @@ async function handOffDev(argv, cwd) {
|
|
|
48
85
|
// splates.ts
|
|
49
86
|
import { agents } from "@stringsync/splates-server/agents";
|
|
50
87
|
import { attach } from "@stringsync/splates-server/artifacts";
|
|
51
|
-
import { commaList as
|
|
88
|
+
import { commaList as commaList6 } from "@stringsync/splates-server/comma-list";
|
|
52
89
|
import { refuseGroomer as refuseGroomer4 } from "@stringsync/splates-server/conversations";
|
|
53
|
-
import { exits, fail as
|
|
90
|
+
import { exits, fail as fail10 } from "@stringsync/splates-server/exit";
|
|
54
91
|
import { DEFAULT_COUNT, log as showLog } from "@stringsync/splates-server/log";
|
|
55
92
|
import { merge } from "@stringsync/splates-server/merge";
|
|
56
93
|
import { portsLs } from "@stringsync/splates-server/ports";
|
|
@@ -59,11 +96,13 @@ import {
|
|
|
59
96
|
MIGRATING,
|
|
60
97
|
MIGRATION_LIMIT,
|
|
61
98
|
migrateRecords,
|
|
62
|
-
|
|
99
|
+
OpenBoards as OpenBoards2,
|
|
100
|
+
repository as repository11
|
|
63
101
|
} from "@stringsync/splates-server/records";
|
|
64
102
|
import {
|
|
65
103
|
newest,
|
|
66
104
|
recordsFirst,
|
|
105
|
+
refreshHere,
|
|
67
106
|
rosterOf as rosterOf2,
|
|
68
107
|
seeded as seeded2,
|
|
69
108
|
update
|
|
@@ -85,9 +124,9 @@ import {
|
|
|
85
124
|
untilAfter as untilAfter3,
|
|
86
125
|
wait
|
|
87
126
|
} from "@stringsync/splates-server/tasks";
|
|
88
|
-
import { cli as
|
|
127
|
+
import { cli as cli16 } from "webappwiz/cmd";
|
|
89
128
|
import { Duration as Duration3 } from "webappwiz/time";
|
|
90
|
-
import { z as
|
|
129
|
+
import { z as z17 } from "zod";
|
|
91
130
|
|
|
92
131
|
// artifact.ts
|
|
93
132
|
import { basename, resolve as resolve2 } from "node:path";
|
|
@@ -244,9 +283,87 @@ splatesArtifact.command("remove").use(repository({ needs: "own" })).description(
|
|
|
244
283
|
splatesArtifact.command("list").use(repository({ needs: "own" })).description("the artifacts this conversation lists, and their current version").option(...conversation).action(async (opts, ctx) => listArtifacts(ctx, await conversationOf(ctx.service, opts.conversation)));
|
|
245
284
|
splatesArtifact.command("show").use(repository({ needs: "own" })).description("every version of an artifact, and which is current").arg("name", z2.string(), { description: "the artifact's name" }).option(...conversation).action(async (opts, ctx) => listArtifacts(ctx, await conversationOf(ctx.service, opts.conversation), opts.name));
|
|
246
285
|
|
|
286
|
+
// blockers.ts
|
|
287
|
+
import { commaList as commaList3 } from "@stringsync/splates-server/comma-list";
|
|
288
|
+
import { fail as fail3 } from "@stringsync/splates-server/exit";
|
|
289
|
+
async function blockerFlags({
|
|
290
|
+
boards,
|
|
291
|
+
here,
|
|
292
|
+
blockedBy = [],
|
|
293
|
+
removeBlockedBy = []
|
|
294
|
+
}) {
|
|
295
|
+
const ids = [];
|
|
296
|
+
const blockedElsewhere = [];
|
|
297
|
+
for (const named of await namedIn(boards, here, blockedBy)) {
|
|
298
|
+
if (named.project === null) {
|
|
299
|
+
ids.push(named.id);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const found = await boards.find(named.project, named.id);
|
|
303
|
+
if (found === null) {
|
|
304
|
+
fail3("not_found", `the tasks of ${named.written} cannot be read: is ${named.slug} set up with \`splates init\`?`, { blockedBy: named.written });
|
|
305
|
+
}
|
|
306
|
+
if (found.status === "merged" || found.status === "removed") {
|
|
307
|
+
fail3("not_found", `${named.written} is ${found.status === "merged" ? `merged, as ${found.commit}` : "gone, removed without merging"}: nothing waits on it any more`, { blockedBy: named.written });
|
|
308
|
+
}
|
|
309
|
+
blockedElsewhere.push({
|
|
310
|
+
project: named.project,
|
|
311
|
+
id: named.id,
|
|
312
|
+
subject: found.subject
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const unblocked = { ids: [], elsewhere: [] };
|
|
316
|
+
for (const named of await namedIn(boards, here, removeBlockedBy)) {
|
|
317
|
+
if (named.project === null) {
|
|
318
|
+
unblocked.ids.push(named.id);
|
|
319
|
+
} else {
|
|
320
|
+
unblocked.elsewhere.push({ project: named.project, id: named.id });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return { blockedBy: ids, blockedElsewhere, unblocked };
|
|
324
|
+
}
|
|
325
|
+
function id(digits) {
|
|
326
|
+
const parsed = Number(digits);
|
|
327
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
328
|
+
fail3("usage", `'${digits}' is not a task number`, { number: digits });
|
|
329
|
+
}
|
|
330
|
+
return parsed;
|
|
331
|
+
}
|
|
332
|
+
async function namedIn(boards, here, raw) {
|
|
333
|
+
const named = [];
|
|
334
|
+
for (const value of commaList3(raw.join(","))) {
|
|
335
|
+
const local = /^#?(\d+)$/.exec(value);
|
|
336
|
+
if (local !== null) {
|
|
337
|
+
named.push({
|
|
338
|
+
project: null,
|
|
339
|
+
slug: "",
|
|
340
|
+
id: id(local[1]),
|
|
341
|
+
written: value
|
|
342
|
+
});
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const other = /^#?([\w.-]+)(?:\s+task)?\s*#(\d+)$/i.exec(value);
|
|
346
|
+
if (other === null) {
|
|
347
|
+
fail3("usage", `'${value}' is not a task: pass its number, or <project>#<number> for one of another project`, { blockedBy: value });
|
|
348
|
+
}
|
|
349
|
+
const slug = other[1];
|
|
350
|
+
const root = await boards.root(slug);
|
|
351
|
+
if (root === null) {
|
|
352
|
+
fail3("not_found", `no splates project '${slug}' on this machine: \`splates projects\` lists them`, { blockedBy: value });
|
|
353
|
+
}
|
|
354
|
+
named.push({
|
|
355
|
+
project: root === here ? null : root,
|
|
356
|
+
slug,
|
|
357
|
+
id: id(other[2]),
|
|
358
|
+
written: `${slug} #${other[2]}`
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return named;
|
|
362
|
+
}
|
|
363
|
+
|
|
247
364
|
// creds.ts
|
|
248
365
|
import { DEFAULT_TIMEOUT, PROVIDERS } from "@stringsync/splates-server/creds";
|
|
249
|
-
import { fail as
|
|
366
|
+
import { fail as fail4 } from "@stringsync/splates-server/exit";
|
|
250
367
|
import { repository as repository2 } from "@stringsync/splates-server/records";
|
|
251
368
|
import { untilAfter } from "@stringsync/splates-server/tasks";
|
|
252
369
|
import { cli as cli2 } from "webappwiz/cmd";
|
|
@@ -296,7 +413,7 @@ splatesCreds.command("wait").use(repository2({ needs: "own" })).description("blo
|
|
|
296
413
|
await ctx.journal.waiting("creds wait", null, { name: opts.name, until: untilAfter(timeout) }, async () => {
|
|
297
414
|
const ended = await ctx.creds.wait(opts.name, { timeout });
|
|
298
415
|
if (ended === "dismissed") {
|
|
299
|
-
|
|
416
|
+
fail4("dismissed", `${opts.name} was dismissed: carry on without it, or ask the person why`, { name: opts.name });
|
|
300
417
|
}
|
|
301
418
|
});
|
|
302
419
|
ctx.log.info(`${color.green("saved")} ${opts.name}`);
|
|
@@ -328,7 +445,7 @@ import {
|
|
|
328
445
|
inspectBatch,
|
|
329
446
|
writeImported
|
|
330
447
|
} from "@stringsync/splates-server/evals";
|
|
331
|
-
import { fail as
|
|
448
|
+
import { fail as fail5 } from "@stringsync/splates-server/exit";
|
|
332
449
|
import { cli as cli3 } from "webappwiz/cmd";
|
|
333
450
|
import { color as color2 } from "webappwiz/log";
|
|
334
451
|
import { z as z4 } from "zod";
|
|
@@ -344,7 +461,7 @@ splatesEvals.command("import").description("turn a framework's results into a sp
|
|
|
344
461
|
const file = resolve3(ctx.ps.cwd(), opts.file);
|
|
345
462
|
const dir = resolve3(ctx.ps.cwd(), opts["batch-dir"]);
|
|
346
463
|
if (!await ctx.fs.exists(file)) {
|
|
347
|
-
|
|
464
|
+
fail5("not_found", `no file at ${file}`, { file });
|
|
348
465
|
}
|
|
349
466
|
const imported = inspectBatch(await inspectLog(ctx, file), opts["pass-at"]);
|
|
350
467
|
await writeImported(dir, imported);
|
|
@@ -358,12 +475,12 @@ async function inspectLog(ctx, file) {
|
|
|
358
475
|
return log;
|
|
359
476
|
}
|
|
360
477
|
} catch {}
|
|
361
|
-
return
|
|
478
|
+
return fail5("usage", `${file} is no Inspect eval log`, { file });
|
|
362
479
|
}
|
|
363
480
|
async function dumped(ctx, file) {
|
|
364
|
-
const { exitCode, stdout, stderr } = await ctx.ps.spawnCapture(["inspect", "log", "dump", file]).catch(() =>
|
|
481
|
+
const { exitCode, stdout, stderr } = await ctx.ps.spawnCapture(["inspect", "log", "dump", file]).catch(() => fail5("usage", "`inspect` is not on PATH: activate the environment Inspect is installed in, or pass a log written with --log-format json"));
|
|
365
482
|
if (exitCode !== 0) {
|
|
366
|
-
|
|
483
|
+
fail5("usage", `inspect log dump ${file} failed: ${stderr.trim()}`, {
|
|
367
484
|
file
|
|
368
485
|
});
|
|
369
486
|
}
|
|
@@ -371,8 +488,8 @@ async function dumped(ctx, file) {
|
|
|
371
488
|
}
|
|
372
489
|
|
|
373
490
|
// hook.ts
|
|
374
|
-
import { commaList as
|
|
375
|
-
import { fail as
|
|
491
|
+
import { commaList as commaList4 } from "@stringsync/splates-server/comma-list";
|
|
492
|
+
import { fail as fail6 } from "@stringsync/splates-server/exit";
|
|
376
493
|
import {
|
|
377
494
|
HookStore,
|
|
378
495
|
hookAdd,
|
|
@@ -425,13 +542,13 @@ splatesHook.command("add").use(repository3({ needs: "tasks" })).description("run
|
|
|
425
542
|
let hook;
|
|
426
543
|
if (opts.run !== "") {
|
|
427
544
|
if (pick != null || opts.prompt !== "") {
|
|
428
|
-
|
|
545
|
+
fail6("usage", "--run goes alone: a shell command runs no agent", {});
|
|
429
546
|
}
|
|
430
547
|
hook = { kind: "run", command: opts.run };
|
|
431
548
|
} else if (pick == null) {
|
|
432
|
-
|
|
549
|
+
fail6("usage", "say what runs: --agent with --prompt, or --run", {});
|
|
433
550
|
} else if (opts.prompt === "") {
|
|
434
|
-
|
|
551
|
+
fail6("usage", "an agent hook needs --prompt: what to tell it", {});
|
|
435
552
|
} else {
|
|
436
553
|
hook = { kind: "agent", prompt: opts.prompt, ...pick };
|
|
437
554
|
}
|
|
@@ -471,7 +588,7 @@ splatesHook.command("edit").use(repository3({ needs: "tasks" })).description("ch
|
|
|
471
588
|
const { id, agent, model, effort, ...change } = opts;
|
|
472
589
|
const pick = await assigneeIn(ctx, agent, model, effort);
|
|
473
590
|
if (pick === null) {
|
|
474
|
-
|
|
591
|
+
fail6("usage", "an agent automation needs an agent: name one, or --run", {});
|
|
475
592
|
}
|
|
476
593
|
await hookEdit({ hooks: storeOf(ctx), log: ctx.log }, id, {
|
|
477
594
|
...change,
|
|
@@ -501,24 +618,24 @@ splatesHook.command("groomer").use(repository3({ needs: "tasks" })).description(
|
|
|
501
618
|
}).action(async (opts, ctx) => {
|
|
502
619
|
const pick = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
503
620
|
if (opts.off === (pick != null)) {
|
|
504
|
-
|
|
621
|
+
fail6("usage", "say how it runs with --agent, or turn it off with --off", {});
|
|
505
622
|
}
|
|
506
623
|
await hookGroomer({ hooks: storeOf(ctx), log: ctx.log }, pick ?? null);
|
|
507
624
|
});
|
|
508
625
|
function triggerOf(opts) {
|
|
509
626
|
const given = [opts.event, opts.every, opts.cron].filter((each) => each !== undefined && each !== "");
|
|
510
627
|
if (given.length !== 1) {
|
|
511
|
-
|
|
628
|
+
fail6("usage", "say what sets it off: a task event, --every or --cron, one of them", {});
|
|
512
629
|
}
|
|
513
630
|
if (opts.event === undefined || opts.event === "") {
|
|
514
631
|
if (opts.stage !== "" || opts.lane !== "") {
|
|
515
|
-
|
|
632
|
+
fail6("usage", "--stage and --lane narrow a task event, not a schedule", {});
|
|
516
633
|
}
|
|
517
634
|
return opts.every !== "" ? { every: opts.every } : { cron: opts.cron };
|
|
518
635
|
}
|
|
519
636
|
const filter = {};
|
|
520
637
|
if (opts.stage !== "") {
|
|
521
|
-
filter.stages = stagesNamed(
|
|
638
|
+
filter.stages = stagesNamed(commaList4(opts.stage));
|
|
522
639
|
}
|
|
523
640
|
if (opts.lane !== "") {
|
|
524
641
|
filter.lanes = lanesNamed(opts.lane);
|
|
@@ -532,10 +649,10 @@ function storeOf(ctx) {
|
|
|
532
649
|
|
|
533
650
|
// lane.ts
|
|
534
651
|
import {
|
|
535
|
-
APPROVAL,
|
|
536
652
|
BACKLOG_LANE as BACKLOG_LANE2,
|
|
537
653
|
laneAdd,
|
|
538
654
|
laneList,
|
|
655
|
+
laneQueue,
|
|
539
656
|
laneRemove,
|
|
540
657
|
laneShow,
|
|
541
658
|
laneUpdate,
|
|
@@ -544,7 +661,10 @@ import {
|
|
|
544
661
|
} from "@stringsync/splates-server/board";
|
|
545
662
|
import { refuseGroomer } from "@stringsync/splates-server/conversations";
|
|
546
663
|
import { UNTRIAGED } from "@stringsync/splates-server/plans";
|
|
547
|
-
import {
|
|
664
|
+
import {
|
|
665
|
+
refuseAgentQueue,
|
|
666
|
+
sayUnqueuedBlockers
|
|
667
|
+
} from "@stringsync/splates-server/queue";
|
|
548
668
|
import { repository as repository4 } from "@stringsync/splates-server/records";
|
|
549
669
|
import { cli as cli5 } from "webappwiz/cmd";
|
|
550
670
|
import { z as z6 } from "zod";
|
|
@@ -552,7 +672,7 @@ var laneNumber = z6.union([
|
|
|
552
672
|
z6.literal("backlog").transform(() => BACKLOG_LANE2),
|
|
553
673
|
z6.coerce.number().int().nonnegative()
|
|
554
674
|
]);
|
|
555
|
-
var splatesLane = cli5("lane").description("lanes: named groups of tasks, for you to see related work together on the board; queue
|
|
675
|
+
var splatesLane = cli5("lane").description("lanes: named groups of tasks, for you to see related work together on the board; queue all its tasks at once with `splates lane queue`. Backlog (lane 0) is permanent and holds unmatched deferred work. Untriaged is groomed into lanes and cannot be queued as a lane");
|
|
556
676
|
splatesLane.command("list").use(repository4({ needs: "read" })).description("every lane: its name, its queued tasks in order, the started tasks on it, its next task, and what blocks that in other lanes").option("json", z6.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => laneList(ctx, { json: opts.json }));
|
|
557
677
|
splatesLane.command("show").use(repository4({ needs: "read" })).description("one lane step by step, and what its agent does next: start the next task, or wait for the one blocking it in another lane to land first; `untriaged` lists the tasks in no lane, to place in lanes").arg("lane", z6.union([z6.literal(UNTRIAGED).transform(() => null), laneNumber], {
|
|
558
678
|
error: "pass a lane's number, backlog, or untriaged"
|
|
@@ -572,27 +692,19 @@ splatesLane.command("update").use(repository4({ needs: "tasks" })).description("
|
|
|
572
692
|
const change = { name: opts.name, position: opts.position };
|
|
573
693
|
await ctx.journal.record("lane update", null, () => laneUpdate(ctx, opts.lane, change), (record) => laneUpdated(opts.lane, record, change));
|
|
574
694
|
});
|
|
575
|
-
splatesLane.command("queue").use(repository4({ needs: "tasks" })).description("queue a lane
|
|
695
|
+
splatesLane.command("queue").use(repository4({ needs: "tasks" })).description("queue every task in a lane now, once: the queue starts each once nothing it is blocked by is open; one blocked by a task outside the queue waits queued until that is queued too, as the command names; a task put in the lane later is queued on its own. Untriaged cannot be queued as a lane").arg("lane", laneNumber, {
|
|
576
696
|
description: "lane number"
|
|
577
697
|
}).action(async (opts, ctx) => {
|
|
578
698
|
refuseAgentQueue(ctx.service, "lane queue");
|
|
579
699
|
await refuseGroomer(ctx.service, "lane queue");
|
|
580
|
-
const
|
|
581
|
-
await
|
|
700
|
+
const queued = await laneQueue(ctx, opts.lane, true);
|
|
701
|
+
await sayUnqueuedBlockers(ctx, queued.map((task) => task.id));
|
|
582
702
|
});
|
|
583
|
-
splatesLane.command("
|
|
584
|
-
description: "lane number"
|
|
585
|
-
}).action(async (opts, ctx) => {
|
|
586
|
-
refuseAgentQueue(ctx.service, "lane approve");
|
|
587
|
-
const change = { approve: APPROVAL };
|
|
588
|
-
await ctx.journal.record("lane update", null, () => laneUpdate(ctx, opts.lane, change), (record) => laneUpdated(opts.lane, record, change));
|
|
589
|
-
});
|
|
590
|
-
splatesLane.command("unqueue").use(repository4({ needs: "tasks" })).description("stop queueing a lane whole, taking out of the queue each of its tasks that has yet to start; those under way go on").arg("lane", laneNumber, {
|
|
703
|
+
splatesLane.command("unqueue").use(repository4({ needs: "tasks" })).description("take each of a lane's queued tasks out of the queue; those under way go on").arg("lane", laneNumber, {
|
|
591
704
|
description: "lane number"
|
|
592
705
|
}).action(async (opts, ctx) => {
|
|
593
706
|
refuseAgentQueue(ctx.service, "lane unqueue");
|
|
594
|
-
|
|
595
|
-
await ctx.journal.record("lane update", null, () => laneUpdate(ctx, opts.lane, change), (record) => laneUpdated(opts.lane, record, change));
|
|
707
|
+
await laneQueue(ctx, opts.lane, false);
|
|
596
708
|
});
|
|
597
709
|
splatesLane.command("remove").use(repository4({ needs: "tasks" })).description("remove a lane and its tasks, kept to restore; a lane whose last task merges goes on its own; agents may remove an empty lane; for one containing tasks, ask with a permission card").arg("lane", laneNumber, {
|
|
598
710
|
description: "lane number"
|
|
@@ -672,7 +784,7 @@ for (const command of ["pause", "resume"]) {
|
|
|
672
784
|
import { dirname as dirname2 } from "node:path";
|
|
673
785
|
import { loadConfig } from "@stringsync/splates-server/config";
|
|
674
786
|
import { SYSTEM_STORES as SYSTEM_STORES2 } from "@stringsync/splates-server/creds";
|
|
675
|
-
import { fail as
|
|
787
|
+
import { fail as fail7 } from "@stringsync/splates-server/exit";
|
|
676
788
|
import { Projects } from "@stringsync/splates-server/projects";
|
|
677
789
|
import {
|
|
678
790
|
FORMAT,
|
|
@@ -714,7 +826,7 @@ splatesReplica.command("restore").description("with `splates server` stopped, re
|
|
|
714
826
|
]);
|
|
715
827
|
const config = await loadConfig(root, { fs: ctx.fs });
|
|
716
828
|
if (config.replica === null) {
|
|
717
|
-
|
|
829
|
+
fail7("usage", "no replica: name one as `replica` in splates.config.ts first");
|
|
718
830
|
}
|
|
719
831
|
const replica = openReplica(config.replica, {
|
|
720
832
|
root,
|
|
@@ -741,7 +853,7 @@ splatesReplica.command("restore").description("with `splates server` stopped, re
|
|
|
741
853
|
].join(`
|
|
742
854
|
`));
|
|
743
855
|
} catch (error) {
|
|
744
|
-
|
|
856
|
+
fail7("usage", error instanceof Error ? error.message : String(error));
|
|
745
857
|
}
|
|
746
858
|
});
|
|
747
859
|
splatesReplica.command("retire").use(repository7({ needs: "own" })).description("after the server moved elsewhere (stop it and check `splates replica status` says caught up first), leave this project's records aside as state.db.moved, so a splates run here finds no stale records and says where the server went").arg("to", z9.string(), {
|
|
@@ -762,7 +874,7 @@ async function mainTree(ctx) {
|
|
|
762
874
|
"--git-common-dir"
|
|
763
875
|
]);
|
|
764
876
|
if (exitCode !== 0) {
|
|
765
|
-
|
|
877
|
+
fail7("usage", `not a git repository: ${ctx.ps.cwd()}`);
|
|
766
878
|
}
|
|
767
879
|
return dirname2(stdout.trim());
|
|
768
880
|
}
|
|
@@ -807,7 +919,7 @@ splatesServer.command("run").description("serve every splates project on this ma
|
|
|
807
919
|
// setup.ts
|
|
808
920
|
import { dirname as dirname3 } from "node:path";
|
|
809
921
|
import { SYSTEM_STORES as SYSTEM_STORES3 } from "@stringsync/splates-server/creds";
|
|
810
|
-
import { fail as
|
|
922
|
+
import { fail as fail8 } from "@stringsync/splates-server/exit";
|
|
811
923
|
import { splatesHome as splatesHome2 } from "@stringsync/splates-server/models";
|
|
812
924
|
import { Projects as Projects2 } from "@stringsync/splates-server/projects";
|
|
813
925
|
import {
|
|
@@ -823,15 +935,15 @@ import { fetchWorkers } from "@stringsync/splates-server/workers";
|
|
|
823
935
|
import { color as color3 } from "webappwiz/log";
|
|
824
936
|
async function setup(deps, opts) {
|
|
825
937
|
if (opts.step === "" && (opts.skip || opts.unskip || opts.undo)) {
|
|
826
|
-
|
|
938
|
+
fail8("usage", "--skip and --unskip name a step, --undo an area", {});
|
|
827
939
|
}
|
|
828
940
|
if (opts.undo && (opts.skip || opts.unskip)) {
|
|
829
|
-
|
|
941
|
+
fail8("usage", "--undo names an area, not a step to skip", {
|
|
830
942
|
area: opts.step
|
|
831
943
|
});
|
|
832
944
|
}
|
|
833
945
|
if (opts.skip && opts.unskip) {
|
|
834
|
-
|
|
946
|
+
fail8("usage", "--skip or --unskip, not both", { step: opts.step });
|
|
835
947
|
}
|
|
836
948
|
const machine = await setupOf(deps);
|
|
837
949
|
const { log } = deps;
|
|
@@ -976,12 +1088,38 @@ function printUndo(log, plan) {
|
|
|
976
1088
|
log.info(color3.dim("the Setup page's Undo does it, after the user confirms"));
|
|
977
1089
|
}
|
|
978
1090
|
|
|
979
|
-
//
|
|
980
|
-
import {
|
|
1091
|
+
// ship.ts
|
|
1092
|
+
import { repository as repository8 } from "@stringsync/splates-server/records";
|
|
1093
|
+
import { DEFAULT_PORT as DEFAULT_PORT2 } from "@stringsync/splates-server/server";
|
|
981
1094
|
import { cli as cli10 } from "webappwiz/cmd";
|
|
982
1095
|
import { z as z11 } from "zod";
|
|
983
|
-
var
|
|
984
|
-
|
|
1096
|
+
var splatesShip = cli10("ship").description("open the Ships page with a project picked").fallback("run");
|
|
1097
|
+
splatesShip.command("run").use(repository8({ needs: "read" })).arg("project", z11.string(), {
|
|
1098
|
+
default: "",
|
|
1099
|
+
description: "the registered project to show"
|
|
1100
|
+
}).option("dev", z11.string(), {
|
|
1101
|
+
default: "",
|
|
1102
|
+
description: "the splates server URL"
|
|
1103
|
+
}).action((opts, ctx) => {
|
|
1104
|
+
const page = new URL("/", opts.dev || ctx.ps.env("SPLATES_DEV") || `http://localhost:${DEFAULT_PORT2}`);
|
|
1105
|
+
page.hash = "ships";
|
|
1106
|
+
if (opts.project !== "") {
|
|
1107
|
+
page.searchParams.set("ship", opts.project);
|
|
1108
|
+
}
|
|
1109
|
+
ctx.log.info(`Open ship plan: ${page}`);
|
|
1110
|
+
const local = new URL(page);
|
|
1111
|
+
local.hostname = "localhost";
|
|
1112
|
+
local.protocol = "http:";
|
|
1113
|
+
local.port = page.hostname === "localhost" || page.hostname === "127.0.0.1" ? page.port : String(DEFAULT_PORT2);
|
|
1114
|
+
ctx.log.info(`Local: ${local}`);
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// slots.ts
|
|
1118
|
+
import { Slots } from "@stringsync/splates-server/ports";
|
|
1119
|
+
import { cli as cli11 } from "webappwiz/cmd";
|
|
1120
|
+
import { z as z12 } from "zod";
|
|
1121
|
+
var splatesSlots = cli11("slots").description("the words `splates server --slot` hands out on this machine, each held by the tree that claimed it: a task's worktree holds its word until the task merges or is removed, which stops its server and gives the word back");
|
|
1122
|
+
splatesSlots.command("list").description("print each claimed word, the tree holding it, its server's pid while one runs, and whether it is orphaned: its tree gone, or a splates repository's main tree, which every agent shares").option("json", z12.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => {
|
|
985
1123
|
const held = await Slots.ofMachine(ctx).status();
|
|
986
1124
|
if (opts.json) {
|
|
987
1125
|
ctx.log.info(JSON.stringify(held.map(({ slot, tree, pid, orphaned }) => ({
|
|
@@ -1024,19 +1162,19 @@ import {
|
|
|
1024
1162
|
tasksNamed as tasksNamed2,
|
|
1025
1163
|
taskUpdate
|
|
1026
1164
|
} from "@stringsync/splates-server/board";
|
|
1027
|
-
import { commaList as
|
|
1165
|
+
import { commaList as commaList5 } from "@stringsync/splates-server/comma-list";
|
|
1028
1166
|
import { refuseGroomer as refuseGroomer3 } from "@stringsync/splates-server/conversations";
|
|
1029
|
-
import { fail as
|
|
1030
|
-
import { repository as
|
|
1167
|
+
import { fail as fail9 } from "@stringsync/splates-server/exit";
|
|
1168
|
+
import { OpenBoards, repository as repository9 } from "@stringsync/splates-server/records";
|
|
1031
1169
|
import {
|
|
1032
1170
|
DEFAULT_TIMEOUT as DEFAULT_TIMEOUT2,
|
|
1033
1171
|
taskWait,
|
|
1034
1172
|
untilAfter as untilAfter2
|
|
1035
1173
|
} from "@stringsync/splates-server/tasks";
|
|
1036
|
-
import { cli as
|
|
1174
|
+
import { cli as cli12 } from "webappwiz/cmd";
|
|
1037
1175
|
import { Duration as Duration2 } from "webappwiz/time";
|
|
1038
|
-
import { z as
|
|
1039
|
-
var splatesTask =
|
|
1176
|
+
import { z as z13 } from "zod";
|
|
1177
|
+
var splatesTask = cli12("task").description("the numbered tasks: defer work for later instead of growing the task you are in, and `splates add <slug> --task <n>` starts one; `merge` recommends the next one when a task lands. The person puts tasks in `splates queue`, for workers to start");
|
|
1040
1178
|
async function projectFrom(ctx) {
|
|
1041
1179
|
if (ctx.elsewhere === null) {
|
|
1042
1180
|
return;
|
|
@@ -1044,62 +1182,77 @@ async function projectFrom(ctx) {
|
|
|
1044
1182
|
return (await ctx.projects.list()).find((each) => each.root === ctx.git.root)?.slug;
|
|
1045
1183
|
}
|
|
1046
1184
|
function adding(name, description, ready) {
|
|
1047
|
-
splatesTask.command(name).use(
|
|
1185
|
+
splatesTask.command(name).use(repository9({ needs: "queue" })).description(description).arg("subject", z13.string(), {
|
|
1048
1186
|
description: "what is left to do, in a line"
|
|
1049
|
-
}).arg("text",
|
|
1187
|
+
}).arg("text", z13.string(), {
|
|
1050
1188
|
default: "",
|
|
1051
1189
|
description: "whatever more there is to say about it, in markdown"
|
|
1052
|
-
}).option("slug",
|
|
1190
|
+
}).option("slug", z13.string(), {
|
|
1053
1191
|
description: "a short name for the task that takes it (lowercase-with-dashes), naming its branch, worktree and conversation: a few words for what it changes, not the subject cut short"
|
|
1054
1192
|
}).option("position", position, {
|
|
1055
1193
|
description: "where it goes in the list, 1 at the top, pushing those from there down (default: the bottom)"
|
|
1056
|
-
}).repeated("file",
|
|
1194
|
+
}).repeated("file", z13.string(), {
|
|
1057
1195
|
description: "files to attach, comma separated or the flag repeated: copied under .git/splates/tasks/<n>/"
|
|
1058
|
-
}).repeated("blocked-by",
|
|
1059
|
-
description: "
|
|
1060
|
-
}).option("lane",
|
|
1196
|
+
}).repeated("blocked-by", z13.string(), {
|
|
1197
|
+
description: "the tasks blocking it, comma separated or the flag repeated: a number for one here, <project>#<number> for one of another project (`webappwiz#404`); each has to merge before this one starts"
|
|
1198
|
+
}).option("lane", z13.string(), {
|
|
1061
1199
|
default: "",
|
|
1062
1200
|
description: "the lane it goes in, at the bottom: a lane's number, `backlog` for the permanent catch-all, or `new` for one of its own (default: none, untriaged)"
|
|
1063
|
-
}).option("agent",
|
|
1201
|
+
}).option("agent", z13.string(), {
|
|
1064
1202
|
default: "",
|
|
1065
1203
|
description: "the agent on the roster (`splates agents`) that should work it, which Start on the `splates server` board offers first"
|
|
1066
|
-
}).option("model",
|
|
1204
|
+
}).option("model", z13.string(), {
|
|
1067
1205
|
default: "",
|
|
1068
1206
|
description: "with --agent: the model, by its id or name on the roster"
|
|
1069
|
-
}).option("effort",
|
|
1207
|
+
}).option("effort", z13.string(), {
|
|
1070
1208
|
default: "",
|
|
1071
1209
|
description: "with --agent: the thinking level, one the agent offers (default: the agent's own)"
|
|
1072
1210
|
}).action(async (opts, ctx) => {
|
|
1073
|
-
|
|
1074
|
-
|
|
1211
|
+
let __stack = [];
|
|
1212
|
+
try {
|
|
1213
|
+
if (ctx.elsewhere !== null && opts.lane !== "") {
|
|
1214
|
+
fail9("not_granted", "a task from another project goes in its untriaged lane, for whoever orchestrates it there to place: leave --lane off", { subject: opts.subject });
|
|
1215
|
+
}
|
|
1216
|
+
const from = await here(ctx);
|
|
1217
|
+
const assignee = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
1218
|
+
const origin = ctx.elsewhere === null ? undefined : {
|
|
1219
|
+
project: ctx.elsewhere,
|
|
1220
|
+
task: ctx.project === undefined ? null : ctx.service.taskFor(await ctx.git.currentBranch(ctx.ps.cwd()))
|
|
1221
|
+
};
|
|
1222
|
+
const project = await projectFrom(ctx);
|
|
1223
|
+
const boards = __using(__stack, new OpenBoards(ctx, ctx.projects), 0);
|
|
1224
|
+
const { blockedBy, blockedElsewhere } = await blockerFlags({
|
|
1225
|
+
boards,
|
|
1226
|
+
here: ctx.git.root,
|
|
1227
|
+
blockedBy: opts["blocked-by"]
|
|
1228
|
+
});
|
|
1229
|
+
await ctx.journal.record("task add", from, () => taskAdd(ctx, opts.subject, from, {
|
|
1230
|
+
slug: opts.slug,
|
|
1231
|
+
text: opts.text,
|
|
1232
|
+
position: opts.position,
|
|
1233
|
+
files: commaList5(opts.file.join(",")),
|
|
1234
|
+
blockedBy,
|
|
1235
|
+
blockedElsewhere,
|
|
1236
|
+
lane: laneIn(opts.lane) ?? null,
|
|
1237
|
+
assignee,
|
|
1238
|
+
origin,
|
|
1239
|
+
ready,
|
|
1240
|
+
project,
|
|
1241
|
+
boards
|
|
1242
|
+
}), addedFacts);
|
|
1243
|
+
} catch (_catch) {
|
|
1244
|
+
var _err = _catch, _hasErr = 1;
|
|
1245
|
+
} finally {
|
|
1246
|
+
__callDispose(__stack, _err, _hasErr);
|
|
1075
1247
|
}
|
|
1076
|
-
const from = await here(ctx);
|
|
1077
|
-
const assignee = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
1078
|
-
const origin = ctx.elsewhere === null ? undefined : {
|
|
1079
|
-
project: ctx.elsewhere,
|
|
1080
|
-
task: ctx.project === undefined ? null : ctx.service.taskFor(await ctx.git.currentBranch(ctx.ps.cwd()))
|
|
1081
|
-
};
|
|
1082
|
-
const project = await projectFrom(ctx);
|
|
1083
|
-
await ctx.journal.record("task add", from, () => taskAdd(ctx, opts.subject, from, {
|
|
1084
|
-
slug: opts.slug,
|
|
1085
|
-
text: opts.text,
|
|
1086
|
-
position: opts.position,
|
|
1087
|
-
files: commaList4(opts.file.join(",")),
|
|
1088
|
-
blockedBy: taskIds(opts["blocked-by"].join(",")),
|
|
1089
|
-
lane: laneIn(opts.lane) ?? null,
|
|
1090
|
-
assignee,
|
|
1091
|
-
origin,
|
|
1092
|
-
ready,
|
|
1093
|
-
project
|
|
1094
|
-
}), addedFacts);
|
|
1095
1248
|
});
|
|
1096
1249
|
}
|
|
1097
1250
|
adding("add", "queue a task to do later and move on, groomed, so it goes in ready: you checked no task already asks it, and linked what blocks it; run from a worktree, it records the task it came up in", true);
|
|
1098
1251
|
adding("defer", "queue a task as it is, rough, in the backlog for whoever grooms it: to check for one that already asks it, link what blocks it, and mark it ready; run from a worktree, it records the task it came up in", false);
|
|
1099
|
-
splatesTask.command("list").use(
|
|
1252
|
+
splatesTask.command("list").use(repository9({ needs: "read" })).description("every queued task, top of the list first, with the task it came from and the started one that took it up").option("json", z13.boolean(), { default: false, description: "emit JSON" }).option("open", z13.boolean(), {
|
|
1100
1253
|
default: false,
|
|
1101
1254
|
description: "only tasks no started one has taken up: the ones free to pick up"
|
|
1102
|
-
}).option("ready",
|
|
1255
|
+
}).option("ready", z13.boolean(), {
|
|
1103
1256
|
default: false,
|
|
1104
1257
|
description: "only tasks free to start now: none has taken them, and nothing blocking them is still open"
|
|
1105
1258
|
}).action(async (opts, ctx) => taskList(ctx, {
|
|
@@ -1108,77 +1261,96 @@ splatesTask.command("list").use(repository8({ needs: "read" })).description("eve
|
|
|
1108
1261
|
ready: opts.ready,
|
|
1109
1262
|
project: await projectFrom(ctx)
|
|
1110
1263
|
}));
|
|
1111
|
-
splatesTask.command("show").use(
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1264
|
+
splatesTask.command("show").use(repository9({ needs: "read" })).description("one task in full: where it stands, its files, and its detail").arg("id", z13.coerce.number().int().positive(), { description: "task number" }).option("json", z13.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => {
|
|
1265
|
+
let __stack = [];
|
|
1266
|
+
try {
|
|
1267
|
+
const boards = __using(__stack, new OpenBoards(ctx, ctx.projects), 0);
|
|
1268
|
+
await taskShow(ctx, opts.id, {
|
|
1269
|
+
json: opts.json,
|
|
1270
|
+
project: await projectFrom(ctx),
|
|
1271
|
+
others: (await ctx.projects.list()).filter((project) => project.root !== ctx.git.root).map((project) => project.slug),
|
|
1272
|
+
boards,
|
|
1273
|
+
root: ctx.git.root
|
|
1274
|
+
});
|
|
1275
|
+
} catch (_catch) {
|
|
1276
|
+
var _err = _catch, _hasErr = 1;
|
|
1277
|
+
} finally {
|
|
1278
|
+
__callDispose(__stack, _err, _hasErr);
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
splatesTask.command("update").use(repository9({ needs: "tasks" })).description("say what a queued task is in other words, move it up or down the list, or attach and drop files; it keeps its number").arg("id", z13.coerce.number().int().positive(), { description: "task number" }).arg("text", z13.string(), {
|
|
1117
1282
|
default: "",
|
|
1118
1283
|
description: "whatever more there is to say about it, in markdown; leave out to keep the detail"
|
|
1119
|
-
}).option("subject",
|
|
1284
|
+
}).option("subject", z13.string(), {
|
|
1120
1285
|
default: "",
|
|
1121
1286
|
description: "what is left to do, in a line; leave out to keep it"
|
|
1122
|
-
}).option("slug",
|
|
1287
|
+
}).option("slug", z13.string(), {
|
|
1123
1288
|
default: "",
|
|
1124
1289
|
description: "a new name for the task that takes it (lowercase-with-dashes); leave out to keep it"
|
|
1125
1290
|
}).option("position", position, {
|
|
1126
1291
|
description: "where to move it in the list, 1 at the top; the others close up around it"
|
|
1127
|
-
}).repeated("file",
|
|
1292
|
+
}).repeated("file", z13.string(), {
|
|
1128
1293
|
description: "files to attach, comma separated or the flag repeated"
|
|
1129
|
-
}).repeated("remove-file",
|
|
1294
|
+
}).repeated("remove-file", z13.string(), {
|
|
1130
1295
|
description: "attached files to drop, comma separated or the flag repeated, by path or stored name"
|
|
1131
|
-
}).repeated("blocked-by",
|
|
1132
|
-
description: "
|
|
1133
|
-
}).repeated("remove-blocked-by",
|
|
1134
|
-
description: "
|
|
1135
|
-
}).option("lane",
|
|
1296
|
+
}).repeated("blocked-by", z13.string(), {
|
|
1297
|
+
description: "more tasks blocking it, comma separated or the flag repeated: a number for one here, <project>#<number> for one of another project (`webappwiz#404`); each has to merge before this one starts; refused when that makes a loop"
|
|
1298
|
+
}).repeated("remove-blocked-by", z13.string(), {
|
|
1299
|
+
description: "tasks no longer blocking it, as --blocked-by names them, comma separated or the flag repeated"
|
|
1300
|
+
}).option("lane", z13.string(), {
|
|
1136
1301
|
default: "",
|
|
1137
1302
|
description: "move it to a lane, at the bottom: a lane's number, `backlog` for the permanent catch-all, `new` for one of its own, or `none` for untriaged"
|
|
1138
|
-
}).option("agent",
|
|
1303
|
+
}).option("agent", z13.string(), {
|
|
1139
1304
|
default: "",
|
|
1140
1305
|
description: "the agent on the roster (`splates agents`) that should work it, which Start on the `splates server` board offers first, or `none`"
|
|
1141
|
-
}).option("model",
|
|
1306
|
+
}).option("model", z13.string(), {
|
|
1142
1307
|
default: "",
|
|
1143
1308
|
description: "with --agent: the model, by its id or name on the roster"
|
|
1144
|
-
}).option("effort",
|
|
1309
|
+
}).option("effort", z13.string(), {
|
|
1145
1310
|
default: "",
|
|
1146
1311
|
description: "with --agent: the thinking level, one the agent offers (default: the agent's own)"
|
|
1147
|
-
}).option("ready",
|
|
1312
|
+
}).option("ready", z13.boolean(), {
|
|
1148
1313
|
default: false,
|
|
1149
1314
|
description: "mark it groomed, moving it from the backlog to ready: no task already asks it, and what blocks it is linked"
|
|
1150
|
-
}).option("manual", z12.boolean(), {
|
|
1151
|
-
default: false,
|
|
1152
|
-
description: "queue it by hand only: a queued lane passes over it, and it starts once you `splates queue add` it"
|
|
1153
|
-
}).option("auto", z12.boolean(), {
|
|
1154
|
-
default: false,
|
|
1155
|
-
description: "let a queued lane queue it again, undoing --manual"
|
|
1156
1315
|
}).action(async (opts, ctx) => {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1316
|
+
let __stack = [];
|
|
1317
|
+
try {
|
|
1318
|
+
await refuseGroomer3(ctx.service, "task update", await ctx.tasks.find(opts.id));
|
|
1319
|
+
const assignee = await assigneeIn(ctx, opts.agent, opts.model, opts.effort);
|
|
1320
|
+
const boards = __using(__stack, new OpenBoards(ctx, ctx.projects), 0);
|
|
1321
|
+
const { blockedBy, blockedElsewhere, unblocked } = await blockerFlags({
|
|
1322
|
+
boards,
|
|
1323
|
+
here: ctx.git.root,
|
|
1324
|
+
blockedBy: opts["blocked-by"],
|
|
1325
|
+
removeBlockedBy: opts["remove-blocked-by"]
|
|
1326
|
+
});
|
|
1327
|
+
await recordUpdate(ctx.journal, ctx.tasks, opts.id, () => taskUpdate(ctx, opts.id, {
|
|
1328
|
+
subject: opts.subject,
|
|
1329
|
+
slug: opts.slug,
|
|
1330
|
+
text: opts.text,
|
|
1331
|
+
position: opts.position,
|
|
1332
|
+
files: commaList5(opts.file.join(",")),
|
|
1333
|
+
removeFiles: commaList5(opts["remove-file"].join(",")),
|
|
1334
|
+
blockedBy,
|
|
1335
|
+
removeBlockedBy: unblocked.ids,
|
|
1336
|
+
blockedElsewhere,
|
|
1337
|
+
removeElsewhere: unblocked.elsewhere,
|
|
1338
|
+
lane: laneIn(opts.lane),
|
|
1339
|
+
assignee,
|
|
1340
|
+
ready: opts.ready,
|
|
1341
|
+
boards
|
|
1342
|
+
}));
|
|
1343
|
+
} catch (_catch) {
|
|
1344
|
+
var _err = _catch, _hasErr = 1;
|
|
1345
|
+
} finally {
|
|
1346
|
+
__callDispose(__stack, _err, _hasErr);
|
|
1347
|
+
}
|
|
1176
1348
|
});
|
|
1177
|
-
splatesTask.command("remove").use(
|
|
1349
|
+
splatesTask.command("remove").use(repository9({ needs: "tasks" })).description("drop a queued task that was done some other way or no longer applies").arg("id", z13.coerce.number().int().positive(), { description: "task number" }).action(async (opts, ctx) => {
|
|
1178
1350
|
await refuseGroomer3(ctx.service, "task remove", await ctx.tasks.find(opts.id));
|
|
1179
1351
|
await ctx.journal.record("task remove", null, () => taskRemove(ctx, opts.id), (removed) => ({ tasks: tasksNamed2([removed]) }));
|
|
1180
1352
|
});
|
|
1181
|
-
splatesTask.command("wait").use(
|
|
1353
|
+
splatesTask.command("wait").use(repository9({ needs: "read" })).description("block until a task leaves the queue, which it does when the task that took it merges: for a task blocked by one another lane is on; ends early when that task waits on a person to answer its questions, and gives up with `timeout`").arg("id", z13.coerce.number().int().positive(), { description: "task number" }).option("timeout-secs", z13.coerce.number(), {
|
|
1182
1354
|
default: DEFAULT_TIMEOUT2.secs,
|
|
1183
1355
|
description: "how long to wait before giving up"
|
|
1184
1356
|
}).action(async (opts, ctx) => {
|
|
@@ -1192,25 +1364,25 @@ splatesTask.command("wait").use(repository8({ needs: "read" })).description("blo
|
|
|
1192
1364
|
});
|
|
1193
1365
|
|
|
1194
1366
|
// tunnel.ts
|
|
1195
|
-
import { repository as
|
|
1367
|
+
import { repository as repository10 } from "@stringsync/splates-server/records";
|
|
1196
1368
|
import { savedTunnelOf } from "@stringsync/splates-server/server";
|
|
1197
1369
|
import {
|
|
1198
1370
|
API_TOKEN,
|
|
1199
1371
|
tunnelRemove,
|
|
1200
1372
|
tunnelSetup
|
|
1201
1373
|
} from "@stringsync/splates-server/tunnel";
|
|
1202
|
-
import { cli as
|
|
1203
|
-
import { z as
|
|
1204
|
-
var splatesTunnel =
|
|
1205
|
-
splatesTunnel.command("setup").use(
|
|
1374
|
+
import { cli as cli13 } from "webappwiz/cmd";
|
|
1375
|
+
import { z as z14 } from "zod";
|
|
1376
|
+
var splatesTunnel = cli13("tunnel").description("reach `splates server` from elsewhere: a Cloudflare tunnel with Cloudflare Access in front, set up once per machine");
|
|
1377
|
+
splatesTunnel.command("setup").use(repository10({ needs: "own" })).description(`make a Cloudflare tunnel to \`splates server\`, the DNS record routing <host> to it, and an Access application letting in only --email, through the API with the token kept as the cred ${API_TOKEN}, then save them for this machine, so a bare \`splates server\` comes up tunneled; run again, it changes only what is not so already. Without that token it requests it on the Creds page of \`splates server\`, with how to make it, and exits \`requested\`: \`splates creds wait ${API_TOKEN}\`, then run it again`).arg("host", z14.string(), {
|
|
1206
1378
|
description: "the public host name, in a zone of the account, as in splates.example.com"
|
|
1207
|
-
}).option("email",
|
|
1379
|
+
}).option("email", z14.string(), {
|
|
1208
1380
|
default: "",
|
|
1209
1381
|
description: "comma-separated email addresses Access lets in"
|
|
1210
|
-
}).option("name",
|
|
1382
|
+
}).option("name", z14.string(), {
|
|
1211
1383
|
default: "splates",
|
|
1212
1384
|
description: "what the tunnel, the Access application and its policy are called"
|
|
1213
|
-
}).option("team",
|
|
1385
|
+
}).option("team", z14.string(), {
|
|
1214
1386
|
default: "",
|
|
1215
1387
|
description: "the Zero Trust team to make, as in <team>.cloudflareaccess.com, when the account has none yet"
|
|
1216
1388
|
}).action(async (opts, ctx) => ctx.journal.record("tunnel setup", null, async () => tunnelSetup({
|
|
@@ -1221,7 +1393,7 @@ splatesTunnel.command("setup").use(repository9({ needs: "own" })).description(`m
|
|
|
1221
1393
|
log: ctx.log,
|
|
1222
1394
|
fetch: ctx.fetch
|
|
1223
1395
|
})));
|
|
1224
|
-
splatesTunnel.command("status").description("print the tunnel `splates tunnel setup` saved for this machine, which a bare `splates server` runs, and whether its token is in the system's secret store").option("json",
|
|
1396
|
+
splatesTunnel.command("status").description("print the tunnel `splates tunnel setup` saved for this machine, which a bare `splates server` runs, and whether its token is in the system's secret store").option("json", z14.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => {
|
|
1225
1397
|
const saved = savedTunnelOf(ctx);
|
|
1226
1398
|
const record = await saved.read();
|
|
1227
1399
|
const token = record === null ? false : await saved.token() !== undefined;
|
|
@@ -1242,7 +1414,7 @@ splatesTunnel.command("status").description("print the tunnel `splates tunnel se
|
|
|
1242
1414
|
].join(`
|
|
1243
1415
|
`));
|
|
1244
1416
|
});
|
|
1245
|
-
splatesTunnel.command("remove").use(
|
|
1417
|
+
splatesTunnel.command("remove").use(repository10({ needs: "own" })).description(`delete what \`splates tunnel setup\` and \`splates server --slot\` made in Cloudflare by what they saved: each tunnel, the DNS record routing to it, the Access applications and their policy, then forget the saved files and the tunnels' tokens; the API token kept as ${API_TOKEN} stays. Run again after a failure, it finishes what is left`).action(async (_opts, ctx) => ctx.journal.record("tunnel remove", null, async () => tunnelRemove({
|
|
1246
1418
|
saved: savedTunnelOf(ctx),
|
|
1247
1419
|
slots: slotTunnelsOf(ctx),
|
|
1248
1420
|
creds: ctx.creds,
|
|
@@ -1261,20 +1433,20 @@ import {
|
|
|
1261
1433
|
removeWorker,
|
|
1262
1434
|
workerRun
|
|
1263
1435
|
} from "@stringsync/splates-server/workers";
|
|
1264
|
-
import { cli as
|
|
1436
|
+
import { cli as cli15 } from "webappwiz/cmd";
|
|
1265
1437
|
import { CompositeLogger } from "webappwiz/log";
|
|
1266
|
-
import { z as
|
|
1438
|
+
import { z as z16 } from "zod";
|
|
1267
1439
|
|
|
1268
1440
|
// waiting.ts
|
|
1269
1441
|
import { listQueue, moveQueued } from "@stringsync/splates-server/workers";
|
|
1270
|
-
import { cli as
|
|
1271
|
-
import { z as
|
|
1442
|
+
import { cli as cli14 } from "webappwiz/cmd";
|
|
1443
|
+
import { z as z15 } from "zod";
|
|
1272
1444
|
var DEV = {
|
|
1273
1445
|
default: "",
|
|
1274
1446
|
description: "the `splates server` to ask, a URL such as http://localhost:4310; by default the one an agent runs under, from SPLATES_DEV"
|
|
1275
1447
|
};
|
|
1276
|
-
var splatesWaiting =
|
|
1277
|
-
splatesWaiting.command("list").description("list the agents waiting to start, first in line first: position, project, task, conversation, how long each has waited and why (paused, no worker up, or worker draining)").option("dev",
|
|
1448
|
+
var splatesWaiting = cli14("queue").description("the agents waiting to start on a worker of the running `splates server`: nothing caps how many agents run, so one waits only while what splates starts on its own (the queue, automations, hooks, the groomer) is paused, or while no worker it can run on is up, first come, first served; none is ever failed for waiting. An agent you start waits only while no worker is up");
|
|
1449
|
+
splatesWaiting.command("list").description("list the agents waiting to start, first in line first: position, project, task, conversation, how long each has waited and why (paused, no worker up, or worker draining)").option("dev", z15.string(), DEV).option("json", z15.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => listQueue({
|
|
1278
1450
|
dev: opts.dev,
|
|
1279
1451
|
json: opts.json,
|
|
1280
1452
|
log: ctx.log,
|
|
@@ -1282,11 +1454,11 @@ splatesWaiting.command("list").description("list the agents waiting to start, fi
|
|
|
1282
1454
|
ps: ctx.ps,
|
|
1283
1455
|
send: ctx.fetch
|
|
1284
1456
|
}));
|
|
1285
|
-
splatesWaiting.command("move").description("move an agent waiting to start to another place in line, from 1 for the next to start, then list the queue").arg("which",
|
|
1457
|
+
splatesWaiting.command("move").description("move an agent waiting to start to another place in line, from 1 for the next to start, then list the queue").arg("which", z15.string(), {
|
|
1286
1458
|
description: "the task it works, or its conversation's id"
|
|
1287
|
-
}).arg("position",
|
|
1459
|
+
}).arg("position", z15.coerce.number().int(), {
|
|
1288
1460
|
description: "where in line to put it, from 1"
|
|
1289
|
-
}).option("dev",
|
|
1461
|
+
}).option("dev", z15.string(), DEV).option("json", z15.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => moveQueued({
|
|
1290
1462
|
which: opts.which,
|
|
1291
1463
|
position: opts.position,
|
|
1292
1464
|
dev: opts.dev,
|
|
@@ -1298,20 +1470,20 @@ splatesWaiting.command("move").description("move an agent waiting to start to an
|
|
|
1298
1470
|
}));
|
|
1299
1471
|
|
|
1300
1472
|
// worker.ts
|
|
1301
|
-
var splatesWorker =
|
|
1302
|
-
splatesWorker.command("run").description("run this machine’s worker until stopped, connecting over its private Unix socket and reconnecting when the server restarts").option("server",
|
|
1473
|
+
var splatesWorker = cli15("worker").description("this machine’s worker, which runs agents, terminals and process reads for `splates server` over its Unix socket");
|
|
1474
|
+
splatesWorker.command("run").description("run this machine’s worker until stopped, connecting over its private Unix socket and reconnecting when the server restarts").option("server", z16.string(), {
|
|
1303
1475
|
default: "",
|
|
1304
1476
|
description: "the `splates server` to dial: unix:<path>; by default this machine's"
|
|
1305
|
-
}).option("coordinator",
|
|
1477
|
+
}).option("coordinator", z16.string(), {
|
|
1306
1478
|
default: "",
|
|
1307
1479
|
description: "the old name for --server, still read for workers installed before it, and passed to a build older than it"
|
|
1308
|
-
}).option("slots",
|
|
1480
|
+
}).option("slots", z16.coerce.number().int().min(0), {
|
|
1309
1481
|
default: 0,
|
|
1310
1482
|
description: "ignored: nothing caps how many agents a worker runs; still read, since a `splates server` started before it was dropped passes it"
|
|
1311
|
-
}).option("id",
|
|
1483
|
+
}).option("id", z16.literal("local"), {
|
|
1312
1484
|
default: "local",
|
|
1313
1485
|
description: "what the server knows this worker by, kept across restarts so it picks up where it was; by default local"
|
|
1314
|
-
}).option("quiet",
|
|
1486
|
+
}).option("quiet", z16.boolean(), {
|
|
1315
1487
|
default: false,
|
|
1316
1488
|
description: "say only what went wrong, not each time it joins or loses its server, as the worker `splates server` starts beside itself does; all of it still goes to worker.log in ~/.splates, the one before it kept as worker.log.1"
|
|
1317
1489
|
}).action((opts, ctx) => workerRun({
|
|
@@ -1321,10 +1493,10 @@ splatesWorker.command("run").description("run this machine’s worker until stop
|
|
|
1321
1493
|
ps: ctx.ps,
|
|
1322
1494
|
fs: ctx.fs
|
|
1323
1495
|
}));
|
|
1324
|
-
splatesWorker.command("list").description("list the workers the running `splates server` has: platform, build, protocol, health with when it was last heard, and tasks, then any it refused for speaking another protocol").option("dev",
|
|
1496
|
+
splatesWorker.command("list").description("list the workers the running `splates server` has: platform, build, protocol, health with when it was last heard, and tasks, then any it refused for speaking another protocol").option("dev", z16.string(), {
|
|
1325
1497
|
default: "",
|
|
1326
1498
|
description: "the `splates server` to ask, a URL such as http://localhost:4310; by default the one an agent runs under, from SPLATES_DEV"
|
|
1327
|
-
}).option("json",
|
|
1499
|
+
}).option("json", z16.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => listWorkers({
|
|
1328
1500
|
dev: opts.dev,
|
|
1329
1501
|
json: opts.json,
|
|
1330
1502
|
log: ctx.log,
|
|
@@ -1336,12 +1508,12 @@ var dev = {
|
|
|
1336
1508
|
default: "",
|
|
1337
1509
|
description: "the `splates server` to ask, a URL such as http://localhost:4310; by default the one an agent runs under, from SPLATES_DEV"
|
|
1338
1510
|
};
|
|
1339
|
-
splatesWorker.command("drain").description("take no new agents on a worker, letting those on it run to their end, but for one asking you something in a task, which saves its task and lets its slot go, its question kept for you to answer and carry it on elsewhere; --undo takes new ones again").arg("id",
|
|
1511
|
+
splatesWorker.command("drain").description("take no new agents on a worker, letting those on it run to their end, but for one asking you something in a task, which saves its task and lets its slot go, its question kept for you to answer and carry it on elsewhere; --undo takes new ones again").arg("id", z16.string(), {
|
|
1340
1512
|
description: "the worker, as `splates worker list` names it"
|
|
1341
|
-
}).option("undo",
|
|
1513
|
+
}).option("undo", z16.boolean(), {
|
|
1342
1514
|
default: false,
|
|
1343
1515
|
description: "take new agents again"
|
|
1344
|
-
}).option("dev",
|
|
1516
|
+
}).option("dev", z16.string(), dev).action((opts, ctx) => drainWorker({
|
|
1345
1517
|
id: opts.id,
|
|
1346
1518
|
undo: opts.undo,
|
|
1347
1519
|
dev: opts.dev,
|
|
@@ -1350,12 +1522,12 @@ splatesWorker.command("drain").description("take no new agents on a worker, lett
|
|
|
1350
1522
|
ps: ctx.ps,
|
|
1351
1523
|
send: ctx.fetch
|
|
1352
1524
|
}));
|
|
1353
|
-
splatesWorker.command("remove").description("stop this machine’s worker taking agents, letting its agents run to their end; resume it with `splates worker drain local --undo`").arg("id",
|
|
1525
|
+
splatesWorker.command("remove").description("stop this machine’s worker taking agents, letting its agents run to their end; resume it with `splates worker drain local --undo`").arg("id", z16.string(), {
|
|
1354
1526
|
description: "the worker, as `splates worker list` names it"
|
|
1355
|
-
}).option("force",
|
|
1527
|
+
}).option("force", z16.boolean(), {
|
|
1356
1528
|
default: false,
|
|
1357
1529
|
description: "end the agents on it now rather than wait for them"
|
|
1358
|
-
}).option("dev",
|
|
1530
|
+
}).option("dev", z16.string(), dev).action((opts, ctx) => removeWorker({
|
|
1359
1531
|
id: opts.id,
|
|
1360
1532
|
force: opts.force,
|
|
1361
1533
|
dev: opts.dev,
|
|
@@ -1387,15 +1559,15 @@ function projectArgs(argv) {
|
|
|
1387
1559
|
}
|
|
1388
1560
|
return { argv };
|
|
1389
1561
|
}
|
|
1390
|
-
var splates =
|
|
1391
|
-
splates.command("init").use(
|
|
1562
|
+
var splates = cli16("splates").use(exits());
|
|
1563
|
+
splates.command("init").use(repository11({ creates: true, needs: "own" })).description("make this repository a splates project: set up its records under .git/splates when it has none, install the splates skill of this release with `bunx skills add`, and register it in ~/.splates/projects.json, so `splates server` serves it beside every other; run again, it changes only what is not so already").action((_opts, ctx) => init(ctx));
|
|
1392
1564
|
splates.command("migrate").description(`move this repository's records under .git/splates to this splates's format, as \`splates server\` does as it starts: backed up first, the steps run with ${MIGRATION_LIMIT.human()} to finish, and the backup put back if one fails or runs over, with why kept in .git/splates-migration.json; it tries again past a failure kept there, which the server and every other command leave alone. Run it only while no older \`splates server\` serves the repository, which would refuse the records once moved; \`splates update\` restarts one first`).action(async (_opts, ctx) => {
|
|
1393
1565
|
const format = await migrateRecords(ctx);
|
|
1394
1566
|
if (ctx.ps.env(MIGRATING) === undefined) {
|
|
1395
1567
|
ctx.log.info(`splates's records are in format ${format}`);
|
|
1396
1568
|
}
|
|
1397
1569
|
});
|
|
1398
|
-
splates.command("update").use(recordsFirst()).use(
|
|
1570
|
+
splates.command("update").use(recordsFirst()).use(repository11({ needs: "own" })).description("bring this repository up to the newest splates on npm: run `update` on that release when it is newer than this one, which moves the records under .git/splates to its format, as `splates migrate` does, unless an older `splates server` serves the repository, which it asks to restart onto that release once its agents have checkpointed, migrating them as it starts; then installs its splates skill with `bunx skills add` over the copy there, leaving one that links to your own; commit what changed").action(async (_opts, ctx) => {
|
|
1399
1571
|
const code = await update({
|
|
1400
1572
|
root: ctx.git.root,
|
|
1401
1573
|
fs: ctx.fs,
|
|
@@ -1407,42 +1579,43 @@ splates.command("update").use(recordsFirst()).use(repository10({ needs: "own" })
|
|
|
1407
1579
|
ctx.ps.exit(code);
|
|
1408
1580
|
}
|
|
1409
1581
|
});
|
|
1410
|
-
splates.command("
|
|
1411
|
-
splates.command("
|
|
1582
|
+
splates.command("refresh").description("bring the tree you stand in, a task's worktree included, up to this splates: install its splates skill with `bunx skills add` over the copy there and move what an older config holds to where it reads it, leaving the records and any `splates server` alone; what a project runs once a ship's bump task has installed a newer splates, as splates's `afterBump` names; commit what changed").action((_opts, ctx) => refreshHere(ctx));
|
|
1583
|
+
splates.command("forget").use(repository11({ needs: "own" })).description("take this repository off ~/.splates/projects.json, so `splates server` no longer offers it; its records stay, and `splates init` brings it back").action((_opts, ctx) => forget(ctx));
|
|
1584
|
+
splates.command("add").use(repository11({ needs: "tasks" })).description("start a task: a new one, or with --task one from the queue; create branch task/<slug>, a worktree at ../<repo>-splates.noindex/<slug> and a state record").arg("slug", z17.string(), {
|
|
1412
1585
|
description: "the task's name, for its branch and worktree (lowercase-with-dashes)"
|
|
1413
|
-
}).option("base",
|
|
1586
|
+
}).option("base", z17.string(), {
|
|
1414
1587
|
default: "",
|
|
1415
1588
|
description: "branch this task starts from and merges onto (default: trunk); `task/<other>` stacks this task on that one and lands the work in its worktree"
|
|
1416
|
-
}).option("task",
|
|
1589
|
+
}).option("task", z17.string(), {
|
|
1417
1590
|
default: "",
|
|
1418
1591
|
description: "start this queued task by number: its words become the plan's Goal, merging removes it, and removing this task puts it back where it was"
|
|
1419
|
-
}).option("goal",
|
|
1592
|
+
}).option("goal", z17.string(), {
|
|
1420
1593
|
default: "",
|
|
1421
1594
|
description: "one or two lines on what done means, written under `## Goal` in the task's SPLATES.md; required unless --task seeds the Goal"
|
|
1422
|
-
}).option("agent",
|
|
1595
|
+
}).option("agent", z17.string(), {
|
|
1423
1596
|
default: "",
|
|
1424
1597
|
description: "how `splates server`'s queue hands the task to an agent on the roster (`splates agents`), which it starts in the worktree; refused from an agent, which starts no agents and queues work with `splates task add` instead"
|
|
1425
|
-
}).option("model",
|
|
1598
|
+
}).option("model", z17.string(), {
|
|
1426
1599
|
default: "",
|
|
1427
1600
|
description: "with --agent: the model it works on, by its id or name on the roster (default: the agent's own)"
|
|
1428
|
-
}).option("effort",
|
|
1601
|
+
}).option("effort", z17.string(), {
|
|
1429
1602
|
default: "",
|
|
1430
1603
|
description: "with --agent: the thinking level it works at, one the agent offers (default: the agent's own)"
|
|
1431
|
-
}).option("prompt",
|
|
1604
|
+
}).option("prompt", z17.string(), {
|
|
1432
1605
|
default: "",
|
|
1433
1606
|
description: "with --agent: what to tell the agent beyond claiming the task"
|
|
1434
|
-
}).option("held",
|
|
1607
|
+
}).option("held", z17.string(), {
|
|
1435
1608
|
default: "",
|
|
1436
1609
|
description: "with --agent: the conversation `splates server`'s queue held for the queued task, which takes the task rather than a new one"
|
|
1437
1610
|
}).action(async (opts, ctx) => {
|
|
1438
1611
|
await refuseGroomer4(ctx.service, "add");
|
|
1439
1612
|
if (opts.agent === "" && (opts.model !== "" || opts.effort !== "" || opts.prompt !== "" || opts.held !== "")) {
|
|
1440
|
-
|
|
1613
|
+
fail10("usage", "--model, --effort, --prompt and --held go with --agent", {
|
|
1441
1614
|
task: opts.slug
|
|
1442
1615
|
});
|
|
1443
1616
|
}
|
|
1444
1617
|
if (opts.goal.trim() === "" && opts.task === "") {
|
|
1445
|
-
|
|
1618
|
+
fail10("usage", `say what done means: \`splates add ${opts.slug} --goal "<one or two lines>"\`, or start queued tasks with --task`, { task: opts.slug });
|
|
1446
1619
|
}
|
|
1447
1620
|
const dispatch = opts.agent === "" ? undefined : {
|
|
1448
1621
|
agent: opts.agent,
|
|
@@ -1476,11 +1649,11 @@ splates.command("add").use(repository10({ needs: "tasks" })).description("start
|
|
|
1476
1649
|
await serveProject(ctx);
|
|
1477
1650
|
}
|
|
1478
1651
|
});
|
|
1479
|
-
splates.command("claim").use(
|
|
1652
|
+
splates.command("claim").use(repository11({ needs: "own" })).description("resume an existing task: take ownership of its worktree and print its path, status and any half-finished rebase; refuses while another agent holds the lease, but takes a stale one silently, so `splates show` first if the tree may not be abandoned").arg("task", z17.string(), { description: "task name" }).action(async (opts, ctx) => {
|
|
1480
1653
|
await ctx.journal.record("claim", opts.task, () => claim(ctx, opts.task), (claimed) => claimed);
|
|
1481
1654
|
await follow(ctx, opts.task);
|
|
1482
1655
|
});
|
|
1483
|
-
splates.command("merge").use(
|
|
1656
|
+
splates.command("merge").use(repository11({ needs: "own" })).description("land this worktree's branch on its base (trunk unless created with --base): rebase onto it, run tests on the rebased code, fast-forward it in whichever worktree has it checked out, then discard the worktree, branch and record (linear history, never a merge commit, no flag to skip tests); requires committed work, refusing a dirty worktree").option("message", z17.string(), {
|
|
1484
1657
|
default: "",
|
|
1485
1658
|
description: "squash the task's commits into one with this message before landing, saying what the base gains in at least 5 characters"
|
|
1486
1659
|
}).action(async (opts, ctx) => {
|
|
@@ -1495,39 +1668,75 @@ splates.command("merge").use(repository10({ needs: "own" })).description("land t
|
|
|
1495
1668
|
await follow(ctx, null, { from: task });
|
|
1496
1669
|
}
|
|
1497
1670
|
});
|
|
1498
|
-
splates.command("remove").use(
|
|
1671
|
+
splates.command("remove").use(repository11({ needs: "tasks" })).description("discard a task: worktree, branch and board tasks; cheap and encouraged, since redoing a task against current trunk often beats a hard rebase").arg("task", z17.string(), { description: "task name" }).option("force", z17.boolean(), {
|
|
1499
1672
|
default: false,
|
|
1500
1673
|
description: "discard even when another agent holds the lease"
|
|
1501
|
-
}).option("reason",
|
|
1502
|
-
description: "why, for the Stats page: nothing (nothing to change), moot (no longer needed), superseded (done some other way), redo (better redone against current trunk), blocked (set aside until what it waits on lands, so back on the list)"
|
|
1674
|
+
}).option("reason", z17.optional(z17.enum(REMOVE_REASONS)), {
|
|
1675
|
+
description: "why, for the Stats page: nothing (nothing to change), moot (no longer needed), superseded (done some other way), redo (better redone against current trunk), blocked (set aside until what it waits on lands, so back on the list, which takes --why and at least one blocker)"
|
|
1676
|
+
}).option("why", z17.string(), {
|
|
1677
|
+
default: "",
|
|
1678
|
+
description: "with --reason blocked: what you found that stops the work, in a sentence or two, for the person and whoever takes it next"
|
|
1679
|
+
}).repeated("blocked-by", z17.string(), {
|
|
1680
|
+
description: "with --reason blocked: the tasks it waits on to merge, comma separated or the flag repeated: a number for one here, <project>#<number> for one of another project"
|
|
1681
|
+
}).repeated("ask", z17.string(), {
|
|
1682
|
+
description: "with --reason blocked: a question only the person can answer, in the skill's question format; repeat the flag for each"
|
|
1683
|
+
}).repeated("waiting-on", z17.string(), {
|
|
1684
|
+
description: "with --reason blocked: something outside splates it waits on, such as a key or an upstream release; repeat the flag for each"
|
|
1685
|
+
}).option("detail", z17.string(), {
|
|
1686
|
+
default: "",
|
|
1687
|
+
description: "with --reason blocked: the task's detail rewritten for whoever takes it next: why it stopped, what you found, the steps done and left, and where any pushed work is"
|
|
1503
1688
|
}).action(async (opts, ctx) => {
|
|
1504
|
-
|
|
1505
|
-
|
|
1689
|
+
let __stack = [];
|
|
1690
|
+
try {
|
|
1691
|
+
const boards = __using(__stack, new OpenBoards2(ctx, ctx.projects), 0);
|
|
1692
|
+
const { blockedBy, blockedElsewhere } = await blockerFlags({
|
|
1693
|
+
boards,
|
|
1694
|
+
here: ctx.git.root,
|
|
1695
|
+
blockedBy: opts["blocked-by"]
|
|
1696
|
+
});
|
|
1697
|
+
await ctx.journal.record("remove", opts.task, () => remove(ctx, opts.task, {
|
|
1698
|
+
force: opts.force,
|
|
1699
|
+
reason: opts.reason,
|
|
1700
|
+
deferral: {
|
|
1701
|
+
why: opts.why,
|
|
1702
|
+
blockedBy,
|
|
1703
|
+
blockedElsewhere,
|
|
1704
|
+
questions: opts.ask,
|
|
1705
|
+
outside: opts["waiting-on"],
|
|
1706
|
+
detail: opts.detail === "" ? undefined : opts.detail
|
|
1707
|
+
}
|
|
1708
|
+
}), ({ released }) => ({ why: opts.reason ?? null, released }));
|
|
1709
|
+
await follow(ctx, null, { from: opts.task });
|
|
1710
|
+
} catch (_catch) {
|
|
1711
|
+
var _err = _catch, _hasErr = 1;
|
|
1712
|
+
} finally {
|
|
1713
|
+
__callDispose(__stack, _err, _hasErr);
|
|
1714
|
+
}
|
|
1506
1715
|
});
|
|
1507
|
-
splates.command("list").use(
|
|
1716
|
+
splates.command("list").use(repository11({ needs: "read" })).description("list every started task: its number, name, status, lease (held: an agent is on it now; stale: gone quiet, normal for a task mid-edit; none), commits ahead of trunk, age; `splates task list` lists the queue").option("json", z17.boolean(), { default: false, description: "emit JSON" }).option("files", z17.boolean(), {
|
|
1508
1717
|
default: false,
|
|
1509
1718
|
description: "add each task's Goal and changed files (committed or not), to check for overlap before starting"
|
|
1510
1719
|
}).action((opts, ctx) => list(ctx, { json: opts.json, files: opts.files }));
|
|
1511
|
-
splates.command("show").use(
|
|
1512
|
-
splates.command("wait").use(
|
|
1720
|
+
splates.command("show").use(repository11({ needs: "read" })).description("read one task without touching it: everything `list` shows for it, plus the SPLATES.md its agent left at the worktree root; takes no lease, so it cannot knock that agent off its own tree; for a task no longer live, the newest finished one of that name: how it ended, its base, the commit it landed as, its conversation and the SPLATES.md it ended on").arg("task", z17.string(), { description: "task name" }).option("json", z17.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => show(ctx, opts.task, { json: opts.json }));
|
|
1721
|
+
splates.command("wait").use(repository11({ needs: "read" })).description("block until a task stops moving: waiting on a person to answer its questions, or gone (merged or removed), or broken; takes no lease, and gives up with `timeout` rather than waiting forever").arg("task", z17.string(), { description: "task name" }).option("timeout-secs", z17.coerce.number(), {
|
|
1513
1722
|
default: DEFAULT_TIMEOUT3.secs,
|
|
1514
1723
|
description: "how long to wait before giving up"
|
|
1515
1724
|
}).action((opts, ctx) => {
|
|
1516
1725
|
const timeout = Duration3.secs(opts["timeout-secs"]);
|
|
1517
1726
|
return ctx.journal.waiting("wait", opts.task, { until: untilAfter3(timeout) }, () => wait(ctx, opts.task, { timeout }));
|
|
1518
1727
|
});
|
|
1519
|
-
splates.command("log").use(
|
|
1728
|
+
splates.command("log").use(repository11({ needs: "read" })).description("show what has been done here recently: one line per add, claim, merge, remove, retry, queued task and more, with how it ended; outlives the tasks themselves").option("count", z17.coerce.number(), {
|
|
1520
1729
|
default: DEFAULT_COUNT,
|
|
1521
1730
|
description: "how many entries to show"
|
|
1522
|
-
}).option("json",
|
|
1731
|
+
}).option("json", z17.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => showLog(ctx, { count: opts.count, json: opts.json }));
|
|
1523
1732
|
splates.mount("server", splatesServer);
|
|
1524
|
-
splates.command("ports").description("list the ports tasks serve, found by the running `splates server` as an editor finds ports to forward: each port a process in a task's worktree, or under its agent, listens on, with its link through the ports host `splates server --ports-host` saved, behind Cloudflare Access, and its link on this machine; give the user both").arg("task",
|
|
1733
|
+
splates.command("ports").description("list the ports tasks serve, found by the running `splates server` as an editor finds ports to forward: each port a process in a task's worktree, or under its agent, listens on, with its link through the ports host `splates server --ports-host` saved, behind Cloudflare Access, and its link on this machine; give the user both").arg("task", z17.string(), {
|
|
1525
1734
|
default: "",
|
|
1526
1735
|
description: "only this task's ports; every task's by default"
|
|
1527
|
-
}).option("dev",
|
|
1736
|
+
}).option("dev", z17.string(), {
|
|
1528
1737
|
default: "",
|
|
1529
1738
|
description: "the `splates server` to ask, a URL such as http://localhost:4310; by default the one an agent runs under, from SPLATES_DEV, else this machine's on its default port"
|
|
1530
|
-
}).option("json",
|
|
1739
|
+
}).option("json", z17.boolean(), { default: false, description: "emit JSON" }).action((opts, ctx) => portsLs({
|
|
1531
1740
|
task: opts.task,
|
|
1532
1741
|
dev: opts.dev,
|
|
1533
1742
|
json: opts.json,
|
|
@@ -1535,50 +1744,50 @@ splates.command("ports").description("list the ports tasks serve, found by the r
|
|
|
1535
1744
|
ps: ctx.ps,
|
|
1536
1745
|
send: ctx.fetch
|
|
1537
1746
|
}));
|
|
1538
|
-
splates.command("agents").description("print the roster of agents splates can start, kept for every project in ~/.splates/agents.json: each agent's launch command and models, with what each model is good at and what it costs, and the thinking levels `--effort` takes, as the agent offered them when last started, for choosing whom to delegate to; writes the defaults (Claude, Codex) there first when there is none, and `splates server` starts conversations with those it lists").option("json",
|
|
1747
|
+
splates.command("agents").description("print the roster of agents splates can start, kept for every project in ~/.splates/agents.json: each agent's launch command and models, with what each model is good at and what it costs, and the thinking levels `--effort` takes, as the agent offered them when last started, for choosing whom to delegate to; writes the defaults (Claude, Codex) there first when there is none, and `splates server` starts conversations with those it lists").option("json", z17.boolean(), { default: false, description: "emit JSON" }).action(async (opts, ctx) => agents({
|
|
1539
1748
|
roster: rosterOf2(ctx),
|
|
1540
1749
|
offered: await offeredOf(ctx).all(),
|
|
1541
1750
|
log: ctx.log
|
|
1542
1751
|
}, { json: opts.json }));
|
|
1543
|
-
splates.command("setup").description("set splates up on this machine, one step at a time: with no step, each area's steps, checked from real state, and the one to do next; with a step, why it matters, the one thing the user does and what the agent does. Skips are kept in ~/.splates/setup.json, so a skipped step stops asking").arg("step",
|
|
1752
|
+
splates.command("setup").description("set splates up on this machine, one step at a time: with no step, each area's steps, checked from real state, and the one to do next; with a step, why it matters, the one thing the user does and what the agent does. Skips are kept in ~/.splates/setup.json, so a skipped step stops asking").arg("step", z17.string(), {
|
|
1544
1753
|
default: "",
|
|
1545
1754
|
description: "a step's id; omit for every area's progress"
|
|
1546
|
-
}).option("json",
|
|
1755
|
+
}).option("json", z17.boolean(), { default: false, description: "emit JSON" }).option("skip", z17.boolean(), {
|
|
1547
1756
|
default: false,
|
|
1548
1757
|
description: "with a step: stop asking for it"
|
|
1549
|
-
}).option("unskip",
|
|
1758
|
+
}).option("unskip", z17.boolean(), {
|
|
1550
1759
|
default: false,
|
|
1551
1760
|
description: "with a step: ask for it again"
|
|
1552
|
-
}).option("undo",
|
|
1761
|
+
}).option("undo", z17.boolean(), {
|
|
1553
1762
|
default: false,
|
|
1554
1763
|
description: "with an area's id in place of a step: print what undoing it would remove, the creds it forgets and keeps, and what only the user can remove; the Setup page's Undo does it, after the user confirms"
|
|
1555
1764
|
}).action((opts, ctx) => setup(ctx, opts));
|
|
1556
|
-
splates.command("path").use(
|
|
1765
|
+
splates.command("path").use(repository11({ needs: "read" })).description("print a task's worktree path, or the main tree with no task; names another agent's tree without taking its lease").arg("task", z17.string(), {
|
|
1557
1766
|
default: "",
|
|
1558
1767
|
description: "task name; omit for the main tree"
|
|
1559
1768
|
}).action((opts, ctx) => path(ctx, opts.task || undefined));
|
|
1560
|
-
splates.command("checkpoint").use(
|
|
1769
|
+
splates.command("checkpoint").use(repository11({ needs: "own" })).description("save this task to the central remote (`remote` in splates.config.ts) before a stop: commit what is uncommitted as a `WIP: checkpoint` commit, to squash before merging, and push the branch as `splates/<task>`, so a worker that dies loses nothing; with no remote configured, leaves the tree as it is").action(async (_opts, ctx) => {
|
|
1561
1770
|
await checkpoint(ctx, ctx.ps.cwd());
|
|
1562
1771
|
});
|
|
1563
|
-
splates.command("attach").use(
|
|
1772
|
+
splates.command("attach").use(repository11({ needs: "own" })).description("store screenshots and recordings for this task's questions to show: prints the path each was stored at, under .git/splates, which the `splates server` page shows inline; from a conversation (its $SPLATES_CONVERSATION, or --conversation) they stay with it past the task, otherwise they go when the task merges or is removed; those the task's final SPLATES.md shows are copied to keep with its finished record. A task holds at most attachmentCapacity bytes; with no files, lists what it holds. A file the user will come back for on its own is an artifact instead (`splates artifact add`)").arg("files", z17.string(), {
|
|
1564
1773
|
default: "",
|
|
1565
1774
|
description: "files to store, comma separated"
|
|
1566
|
-
}).option("task",
|
|
1775
|
+
}).option("task", z17.string(), {
|
|
1567
1776
|
default: "",
|
|
1568
1777
|
description: "task name, when run outside its worktree"
|
|
1569
|
-
}).option("remove",
|
|
1778
|
+
}).option("remove", z17.string(), {
|
|
1570
1779
|
default: "",
|
|
1571
1780
|
description: "files it holds to drop first, comma separated, by path or stored name"
|
|
1572
|
-
}).option("conversation",
|
|
1781
|
+
}).option("conversation", z17.string(), {
|
|
1573
1782
|
default: "",
|
|
1574
1783
|
description: "conversation to keep the files with, when $SPLATES_CONVERSATION is not set"
|
|
1575
|
-
}).action(async (opts, ctx) => ctx.journal.record("attach", opts.task || await here(ctx), () => attach(ctx,
|
|
1784
|
+
}).action(async (opts, ctx) => ctx.journal.record("attach", opts.task || await here(ctx), () => attach(ctx, commaList6(opts.files), ctx.ps.cwd(), {
|
|
1576
1785
|
task: opts.task || undefined,
|
|
1577
|
-
remove:
|
|
1786
|
+
remove: commaList6(opts.remove),
|
|
1578
1787
|
conversation: opts.conversation || undefined
|
|
1579
|
-
}), (stored) => ({ stored, removed:
|
|
1580
|
-
splates.command("retry").use(
|
|
1581
|
-
splates.mount("artifact", splatesArtifact).mount("task", splatesTask).mount("lane", splatesLane).mount("projects", splatesProjects).mount("creds", splatesCreds).mount("tunnel", splatesTunnel).mount("slots", splatesSlots).mount("worker", splatesWorker).mount("queue", splatesQueue).mount("hook", splatesHook).mount("replica", splatesReplica).mount("evals", splatesEvals);
|
|
1788
|
+
}), (stored) => ({ stored, removed: commaList6(opts.remove) })));
|
|
1789
|
+
splates.command("retry").use(repository11({ needs: "own" })).description("give a task that spent its merge attempts another mergeRetryCount of them; the way out of `budget_exhausted` that is not remove and redo, for a person to run once they have seen the tree").arg("task", z17.string(), { description: "task name" }).action((opts, ctx) => recordRetry(ctx, opts.task));
|
|
1790
|
+
splates.mount("artifact", splatesArtifact).mount("task", splatesTask).mount("lane", splatesLane).mount("ship", splatesShip).mount("projects", splatesProjects).mount("creds", splatesCreds).mount("tunnel", splatesTunnel).mount("slots", splatesSlots).mount("worker", splatesWorker).mount("queue", splatesQueue).mount("hook", splatesHook).mount("replica", splatesReplica).mount("evals", splatesEvals);
|
|
1582
1791
|
|
|
1583
1792
|
// index.ts
|
|
1584
1793
|
if (basename2(process.argv[1] ?? "") === LEGACY) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stringsync/splates",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.60",
|
|
4
4
|
"description": "Runs several AI coding agents on one repository at once, each in its own git worktree",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "stringsync LLC",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@stringsync/splates-server": "^0.0.
|
|
21
|
-
"webappwiz": "0.0.
|
|
20
|
+
"@stringsync/splates-server": "^0.0.60",
|
|
21
|
+
"webappwiz": "0.0.54",
|
|
22
22
|
"zod": "^4.4.3"
|
|
23
23
|
},
|
|
24
24
|
"main": "./index.js",
|
package/ship.d.ts
ADDED