@stringsync/splates 0.0.0-stage → 0.0.45

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.
Files changed (251) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -2
  3. package/acp-extensions.d.ts +82 -0
  4. package/add.d.ts +53 -0
  5. package/adherence.d.ts +49 -0
  6. package/age.d.ts +2 -0
  7. package/agent-connection.d.ts +171 -0
  8. package/agent-errors.d.ts +2 -0
  9. package/agent-process.d.ts +65 -0
  10. package/agent-stream.d.ts +91 -0
  11. package/agents.d.ts +15 -0
  12. package/artifact-room.d.ts +47 -0
  13. package/artifact-viewers.d.ts +6 -0
  14. package/artifact.d.ts +30 -0
  15. package/artifacts.d.ts +161 -0
  16. package/asked.d.ts +146 -0
  17. package/asked.js +7 -0
  18. package/assumed.d.ts +34 -0
  19. package/attach.d.ts +30 -0
  20. package/attachments.d.ts +43 -0
  21. package/baselines.d.ts +55 -0
  22. package/claim.d.ts +17 -0
  23. package/cloudflare-api.d.ts +25 -0
  24. package/cloudflare-error.d.ts +5 -0
  25. package/cloudflared-tunnel-provider.d.ts +27 -0
  26. package/codex-limits.d.ts +19 -0
  27. package/compaction.d.ts +17 -0
  28. package/config-source.d.ts +13 -0
  29. package/config.d.ts +98 -0
  30. package/config.js +7 -0
  31. package/conversation-archive.d.ts +6 -0
  32. package/conversation-background.d.ts +21 -0
  33. package/conversation-choice.d.ts +4 -0
  34. package/conversation-files.d.ts +51 -0
  35. package/conversation-given.d.ts +8 -0
  36. package/conversation-host.d.ts +102 -0
  37. package/conversation-listing.d.ts +15 -0
  38. package/conversation-log.d.ts +301 -0
  39. package/conversation-messages.d.ts +18 -0
  40. package/conversation-name.d.ts +28 -0
  41. package/conversation-options.d.ts +73 -0
  42. package/conversation-reference.d.ts +2 -0
  43. package/conversation-state.d.ts +472 -0
  44. package/conversation-turns.d.ts +115 -0
  45. package/conversation.d.ts +209 -0
  46. package/conversations.d.ts +140 -0
  47. package/coordinator-state.d.ts +28 -0
  48. package/creds.d.ts +123 -0
  49. package/darwin-temperature.d.ts +7 -0
  50. package/decided.d.ts +18 -0
  51. package/demo.d.ts +29 -0
  52. package/dev/api.d.ts +209 -0
  53. package/dev/assets.d.ts +24 -0
  54. package/dev/conversation-address.d.ts +47 -0
  55. package/dev/conversation-feed.d.ts +166 -0
  56. package/dev/live-stream.d.ts +29 -0
  57. package/dev/worker.d.ts +33 -0
  58. package/dev-access.d.ts +61 -0
  59. package/dev-artifacts.d.ts +22 -0
  60. package/dev-budgets.d.ts +18 -0
  61. package/dev-conversation-events.d.ts +39 -0
  62. package/dev-conversation-routes.d.ts +90 -0
  63. package/dev-creds.d.ts +17 -0
  64. package/dev-dictation-connection.d.ts +18 -0
  65. package/dev-dictation.d.ts +17 -0
  66. package/dev-http.d.ts +76 -0
  67. package/dev-open.d.ts +15 -0
  68. package/dev-options.d.ts +133 -0
  69. package/dev-outcomes.d.ts +21 -0
  70. package/dev-pages.d.ts +16 -0
  71. package/dev-picker.d.ts +57 -0
  72. package/dev-port-socket.d.ts +26 -0
  73. package/dev-ports.d.ts +11 -0
  74. package/dev-project-watch.d.ts +44 -0
  75. package/dev-project.d.ts +168 -0
  76. package/dev-push.d.ts +59 -0
  77. package/dev-restart.d.ts +141 -0
  78. package/dev-servers.d.ts +30 -0
  79. package/dev-setup-native.d.ts +23 -0
  80. package/dev-setup-opencode.d.ts +29 -0
  81. package/dev-setup.d.ts +16 -0
  82. package/dev-shell.d.ts +70 -0
  83. package/dev-slot.d.ts +28 -0
  84. package/dev-sockets.d.ts +13 -0
  85. package/dev-task-ports.d.ts +73 -0
  86. package/dev-tasks.d.ts +20 -0
  87. package/dev-terminal-connection.d.ts +19 -0
  88. package/dev-terminal.d.ts +42 -0
  89. package/dev-todos.d.ts +35 -0
  90. package/dev-transcriber.d.ts +15 -0
  91. package/dev-tunnel.d.ts +38 -0
  92. package/dev-versions.d.ts +53 -0
  93. package/dev-workers.d.ts +55 -0
  94. package/dev.d.ts +24 -0
  95. package/diff.d.ts +29 -0
  96. package/dispatched.d.ts +16 -0
  97. package/editors.d.ts +22 -0
  98. package/escalate.d.ts +27 -0
  99. package/events.d.ts +311 -0
  100. package/everywhere.d.ts +41 -0
  101. package/exhibit.d.ts +21 -0
  102. package/exit.d.ts +54 -0
  103. package/fact-todos.d.ts +18 -0
  104. package/fake-agent.d.ts +107 -0
  105. package/fake-agent.js +284 -0
  106. package/finding-facts.d.ts +38 -0
  107. package/findings.d.ts +84 -0
  108. package/fold.d.ts +43 -0
  109. package/follow.d.ts +41 -0
  110. package/format.d.ts +68 -0
  111. package/git.d.ts +112 -0
  112. package/handed-out.d.ts +79 -0
  113. package/handoff.d.ts +23 -0
  114. package/heavy-run.d.ts +12 -0
  115. package/heavy.d.ts +28 -0
  116. package/heavy.js +16 -0
  117. package/held.d.ts +22 -0
  118. package/index-0tfvy823.js +272 -0
  119. package/index-43pq33n4.js +157 -0
  120. package/index-4776671w.js +698 -0
  121. package/index-d4hjrzaq.js +464 -0
  122. package/index-gcfq7awr.js +740 -0
  123. package/index-k73tsw5g.js +101 -0
  124. package/index-kaa44k7q.js +59 -0
  125. package/index-vcv9m93e.js +1265 -0
  126. package/index-xmfmy729.js +249 -0
  127. package/index.d.ts +2 -0
  128. package/index.js +26928 -0
  129. package/init.d.ts +28 -0
  130. package/journal.d.ts +57 -0
  131. package/landed.d.ts +14 -0
  132. package/lane-todos.d.ts +44 -0
  133. package/lane.d.ts +55 -0
  134. package/lanes-from-tags.d.ts +17 -0
  135. package/lanes.d.ts +96 -0
  136. package/legacy-names.d.ts +70 -0
  137. package/legacy-ps.d.ts +31 -0
  138. package/limits.d.ts +32 -0
  139. package/list.d.ts +18 -0
  140. package/load-config.d.ts +44 -0
  141. package/log.d.ts +14 -0
  142. package/machine.d.ts +42 -0
  143. package/merge.d.ts +35 -0
  144. package/migrate-config.d.ts +31 -0
  145. package/models.d.ts +24 -0
  146. package/needs.d.ts +115 -0
  147. package/notices.d.ts +39 -0
  148. package/offered.d.ts +39 -0
  149. package/opencode-usage.d.ts +37 -0
  150. package/outcomes.d.ts +114 -0
  151. package/package.json +74 -5
  152. package/path.d.ts +13 -0
  153. package/plan.d.ts +236 -0
  154. package/port-forward.d.ts +22 -0
  155. package/port-pipe.d.ts +36 -0
  156. package/ports-list.d.ts +19 -0
  157. package/probe-runtime.d.ts +5 -0
  158. package/probe-runtime.js +13 -0
  159. package/project-reaches.d.ts +23 -0
  160. package/projects.d.ts +136 -0
  161. package/prompts.d.ts +128 -0
  162. package/pty.d.ts +39 -0
  163. package/pulse.d.ts +8 -0
  164. package/recommend.d.ts +31 -0
  165. package/release.d.ts +41 -0
  166. package/remote-process.d.ts +26 -0
  167. package/remote-pty.d.ts +29 -0
  168. package/remote-worker.d.ts +77 -0
  169. package/remove.d.ts +31 -0
  170. package/repository.d.ts +91 -0
  171. package/retry.d.ts +34 -0
  172. package/roster.d.ts +63 -0
  173. package/saved-ports-host.d.ts +25 -0
  174. package/saved-tunnel.d.ts +44 -0
  175. package/server-worker-socket.d.ts +21 -0
  176. package/setup-areas.d.ts +7 -0
  177. package/setup-opencode-model-error.d.ts +3 -0
  178. package/setup-opencode-models.d.ts +54 -0
  179. package/setup-opencode-recipe.d.ts +26 -0
  180. package/setup-opencode.d.ts +46 -0
  181. package/setup.d.ts +102 -0
  182. package/shared-browser.d.ts +50 -0
  183. package/shell.d.ts +17 -0
  184. package/show.d.ts +106 -0
  185. package/skill-checks.d.ts +73 -0
  186. package/skill-checks.js +10 -0
  187. package/skills.d.ts +16 -0
  188. package/slot-tunnel.d.ts +27 -0
  189. package/slots.d.ts +135 -0
  190. package/snapshot.d.ts +75 -0
  191. package/splates-artifact.d.ts +3 -0
  192. package/splates-creds.d.ts +3 -0
  193. package/splates-dev.d.ts +22 -0
  194. package/splates-flags.d.ts +34 -0
  195. package/splates-lane.d.ts +3 -0
  196. package/splates-projects.d.ts +3 -0
  197. package/splates-setup.d.ts +17 -0
  198. package/splates-slots.d.ts +3 -0
  199. package/splates-todo.d.ts +3 -0
  200. package/splates-tunnel.d.ts +3 -0
  201. package/splates-worker.d.ts +3 -0
  202. package/splates.d.ts +28 -0
  203. package/stats.d.ts +186 -0
  204. package/supervise.d.ts +50 -0
  205. package/system-changes.d.ts +67 -0
  206. package/system.d.ts +28 -0
  207. package/systems.d.ts +119 -0
  208. package/systems.js +21 -0
  209. package/table.d.ts +5 -0
  210. package/task-log.d.ts +35 -0
  211. package/task-ports.d.ts +25 -0
  212. package/tell.d.ts +47 -0
  213. package/todo-edit.d.ts +60 -0
  214. package/todo-file.d.ts +6 -0
  215. package/todo-lanes.d.ts +82 -0
  216. package/todo-list.d.ts +26 -0
  217. package/todo-take.d.ts +27 -0
  218. package/todo.d.ts +146 -0
  219. package/todos.d.ts +157 -0
  220. package/tool-calls.d.ts +18 -0
  221. package/tunnel-provider.d.ts +37 -0
  222. package/tunnel-provider.js +0 -0
  223. package/tunnel-remove.d.ts +28 -0
  224. package/tunnel-setup.d.ts +104 -0
  225. package/unix-worker-listener.d.ts +23 -0
  226. package/unix-worker-socket.d.ts +30 -0
  227. package/update.d.ts +29 -0
  228. package/update.js +10 -0
  229. package/vocabulary.d.ts +9 -0
  230. package/wait.d.ts +36 -0
  231. package/watch.d.ts +46 -0
  232. package/watched.d.ts +43 -0
  233. package/websocket-worker-socket.d.ts +18 -0
  234. package/worker-call-host.d.ts +51 -0
  235. package/worker-channel.d.ts +83 -0
  236. package/worker-connection.d.ts +24 -0
  237. package/worker-daemon.d.ts +64 -0
  238. package/worker-git.d.ts +18 -0
  239. package/worker-list.d.ts +49 -0
  240. package/worker-machine.d.ts +34 -0
  241. package/worker-port-host.d.ts +21 -0
  242. package/worker-process-host.d.ts +29 -0
  243. package/worker-protocol.d.ts +104 -0
  244. package/worker-pty-host.d.ts +34 -0
  245. package/worker-registry.d.ts +68 -0
  246. package/worker-replay.d.ts +25 -0
  247. package/worker-run.d.ts +20 -0
  248. package/worker-session.d.ts +189 -0
  249. package/worker-socket.d.ts +15 -0
  250. package/worktree-service.d.ts +113 -0
  251. package/worktree.d.ts +232 -0
@@ -0,0 +1,79 @@
1
+ import { type Resource } from "webappwiz/disposable";
2
+ import type { Logger } from "webappwiz/log";
3
+ import { Duration, type Timer, type WallClock } from "webappwiz/time";
4
+ import type { Conversations } from "./conversations.js";
5
+ import { Everywhere } from "./everywhere.js";
6
+ import { type SeenLaneTodos } from "./lane-todos.js";
7
+ import type { LaneRecord } from "./lanes.js";
8
+ import type { TodoState } from "./todo.js";
9
+ import type { WorktreeService } from "./worktree-service.js";
10
+ /**
11
+ * How often every handed-out agent is checked again, for a task that landed
12
+ * or went with no word to its conversation, as from another tree.
13
+ */
14
+ export declare const SWEEP: Duration;
15
+ /**
16
+ * Keeps the conversation that handed tasks out, a lane's orchestrator with
17
+ * `splates add --agent` or a relaying agent with `splates handoff --relay`, up
18
+ * to date on the agents it handed them to, so it never waits on one that
19
+ * has gone quiet, a restart included. An agent that is not at work, with its
20
+ * task still under way and not escalated, gets nothing more done unless that
21
+ * one acts, so that one is told `[SPLATES IDLE <task>]`, unless it has heard of
22
+ * the task since the agent last said anything, or, for one that answers to
23
+ * the person, the person took it in hand. An agent that answers to the one
24
+ * that handed it out is archived with that one once its task is gone, off the
25
+ * list. Each is checked as its turn ends, once the conversations' changes
26
+ * settle, as the project opens, and every `SWEEP`.
27
+ *
28
+ * A conversation idle `HEARTBEAT_IDLE` since its last turn, with an agent
29
+ * that answers to it still at work, is told `[SPLATES HEARTBEAT]`, so it checks
30
+ * on them rather than wait out a long task with no word to the person. Any
31
+ * turn it takes before then puts the next one off again. A lane's
32
+ * orchestrator gets none: its agents talk to the person themselves.
33
+ *
34
+ * A lane's orchestrator is told `[SPLATES TODO DONE #N]` as a todo in a lane
35
+ * it works leaves the list, and `[SPLATES TODO OPEN #N]` as one comes back
36
+ * open, however that came about, so the todos drive the lane rather than its
37
+ * agents reporting back. What changed while `splates server` was down is told
38
+ * once it is back.
39
+ *
40
+ * A lane's orchestrator whose lanes are all done, however their last todos
41
+ * landed, that sits idle on a Working or Waiting report, as though they were
42
+ * not, is told `[SPLATES LANES DONE]`, once since it last started lanes, so it
43
+ * ends on its Finished report.
44
+ */
45
+ export declare class HandedOut implements Resource {
46
+ #private;
47
+ private readonly service;
48
+ private readonly conversations;
49
+ private readonly everywhere;
50
+ private readonly lanes;
51
+ private readonly todos;
52
+ private readonly seen;
53
+ private readonly log;
54
+ private readonly timer;
55
+ private readonly clock;
56
+ private constructor();
57
+ /** Watches `conversations`, checking each handed-out agent at once. */
58
+ static watch({ service, conversations, everywhere, lanes, todos, seen, log, timer, clock, }: {
59
+ service: WorktreeService;
60
+ conversations: Conversations;
61
+ /**
62
+ * Every project's conversations, where the one that handed out a task
63
+ * here may be; this project's alone by default.
64
+ */
65
+ everywhere?: Everywhere;
66
+ /** The lanes on the board, a lane gone from it once it is done. */
67
+ lanes: () => Promise<LaneRecord[]>;
68
+ /** Every todo on the list. */
69
+ todos: () => Promise<TodoState[]>;
70
+ /** The lane todos as last seen, kept past a restart. */
71
+ seen: SeenLaneTodos;
72
+ log: Logger;
73
+ timer?: Timer;
74
+ clock?: WallClock;
75
+ }): HandedOut;
76
+ /** Checks every handed-out agent again. */
77
+ sweep(): Promise<void>;
78
+ dispose(): void;
79
+ }
package/handoff.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { type Logger } from "webappwiz/log";
2
+ import { type AgentPick, type RosterAgent } from "./roster.js";
3
+ import type { Dispatched } from "./worktree.js";
4
+ import type { WorktreeService } from "./worktree-service.js";
5
+ /**
6
+ * Hands a task under way to another agent, which `splates server` starts in its
7
+ * tree, told `[SPLATES HANDOFF <task>]`, to carry on from where its `SPLATES.md`
8
+ * says it stands. The new agent answers to the person, as the old one did:
9
+ * the task's caller goes, so nobody else tells it anything or hears from it,
10
+ * but for a lane's orchestrator that started the old one, which goes on
11
+ * hearing when it stops.
12
+ * With `relay`, for an agent that hands on work it judged another model fits
13
+ * better, the new agent answers to this conversation instead, as to a lane's
14
+ * orchestrator: it reports back here, and is told what the person says.
15
+ * Only the conversation working the task hands it off; one that answers to an
16
+ * orchestrator escalates instead, for the orchestrator to decide.
17
+ */
18
+ export declare function handoff({ service, log }: {
19
+ service: WorktreeService;
20
+ log: Logger;
21
+ }, task: string, to: Omit<Dispatched, "conversation" | "pending" | "handoff" | "relay">, agents: RosterAgent[], { relay }?: {
22
+ relay?: boolean;
23
+ }): Promise<AgentPick>;
package/heavy-run.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { Logger } from "webappwiz/log";
2
+ import type { Fs, HeavyCap, Ps } from "webappwiz/system";
3
+ /**
4
+ * Runs `argv` in one of the machine's heavy-job slots, waiting for one to
5
+ * come free first, and says how it exited. What an agent runs itself (a full
6
+ * test run, an install) passes through splates no other way.
7
+ */
8
+ export declare function runHeavy(ctx: {
9
+ fs: Fs;
10
+ ps: Ps;
11
+ log: Logger;
12
+ }, argv: string[], heavy?: HeavyCap): Promise<number>;
package/heavy.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { Logger } from "webappwiz/log";
2
+ import { type Fs, HeavyJobs, type Ps } from "webappwiz/system";
3
+ /**
4
+ * How many heavy jobs run at once on a machine when `SPLATES_HEAVY_JOBS` says
5
+ * nothing. One `splates dev test` takes about 7 cores and 7.3 GB, so two
6
+ * bound the spike near 15 GB on a 64 GB Mac running two dozen sessions.
7
+ */
8
+ export declare const HEAVY_JOBS_DEFAULT = 2;
9
+ /** Sets the cap for the machine, read by every splates that takes a slot. */
10
+ export declare const HEAVY_JOBS = "SPLATES_HEAVY_JOBS";
11
+ /**
12
+ * What runs in a slot finds this in its environment: a heavy job it starts,
13
+ * such as the test run of a `preMerge` hook, runs in that slot too.
14
+ */
15
+ export declare const HEAVY_HELD = "SPLATES_HEAVY";
16
+ /**
17
+ * This machine's heavy-job slots, under the home `splates server --slot` names its
18
+ * servers' machine by, else `SPLATES_HOME`, so every server on one machine
19
+ * shares one cap; capped by `SPLATES_HEAVY_JOBS`. `splates dev test` takes one
20
+ * here too.
21
+ */
22
+ export declare function heavyJobsOfMachine({ fs, ps, log, }: {
23
+ fs: Fs;
24
+ ps: Ps;
25
+ log: Logger;
26
+ }): HeavyJobs;
27
+ /** The cap `SPLATES_HEAVY_JOBS` names, or the default for none or nonsense. */
28
+ export declare function capOf(value: string | undefined): number;
package/heavy.js ADDED
@@ -0,0 +1,16 @@
1
+ import {
2
+ HEAVY_JOBS_DEFAULT2,
3
+ HEAVY_JOBS2,
4
+ HEAVY_HELD2,
5
+ heavyJobsOfMachine2,
6
+ capOf2
7
+ } from "./index-xmfmy729.js";
8
+ import"./index-43pq33n4.js";
9
+ import"./index-k73tsw5g.js";
10
+ export {
11
+ HEAVY_HELD2 as HEAVY_HELD,
12
+ HEAVY_JOBS2 as HEAVY_JOBS,
13
+ HEAVY_JOBS_DEFAULT2 as HEAVY_JOBS_DEFAULT,
14
+ capOf2 as capOf,
15
+ heavyJobsOfMachine2 as heavyJobsOfMachine
16
+ };
package/held.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { ProcessEntry } from "webappwiz/system";
2
+ /** A command a restart would kill: where it runs, and what it runs. */
3
+ export interface Held {
4
+ /** The agent that left it running, or `terminal` for a page's shell. */
5
+ where: string;
6
+ command: string;
7
+ }
8
+ /**
9
+ * What a restart would kill, from the process table: the job in front in
10
+ * each of the page's `shells`, and each command an idle agent in `agents`
11
+ * left running, in a shell under the agent leading a process group of its
12
+ * own, as an agent runs each command. Its MCP servers, and the shells and
13
+ * browsers they start in the agent's group, are not commands it ran, so they
14
+ * hold nothing.
15
+ */
16
+ export declare function heldBy(table: ProcessEntry[], { shells, agents, }: {
17
+ shells: number[];
18
+ agents: {
19
+ name: string;
20
+ pid: number;
21
+ }[];
22
+ }): Held[];
@@ -0,0 +1,272 @@
1
+ // skill-checks.ts
2
+ function judge2(transcript, end) {
3
+ return Object.fromEntries(CHECKS2.map((check) => [check.id, check.judge(transcript, end)]));
4
+ }
5
+ var CHECKS2 = [
6
+ {
7
+ id: "plan-first",
8
+ rule: "Writes SPLATES.md before the first edit",
9
+ judge: ({ beats }) => added(beats).flatMap(({ at, task }, index, all) => {
10
+ const until = all[index + 1]?.at ?? beats.length;
11
+ const span = beats.slice(at + 1, until);
12
+ const edit = span.findIndex((each) => each.kind === "edit" && each.paths.some((path) => !isPlan(path)));
13
+ if (edit === -1) {
14
+ return [];
15
+ }
16
+ const planned = span.slice(0, edit).some(writesPlan);
17
+ return [
18
+ {
19
+ ok: planned,
20
+ why: planned ? `${task}: SPLATES.md came first` : `${task}: edited before writing SPLATES.md`
21
+ }
22
+ ];
23
+ })
24
+ },
25
+ {
26
+ id: "report-title",
27
+ rule: "Titles a task's report by how it ended",
28
+ judge: ({ beats }) => endings(beats).map(({ verb, review, report }) => {
29
+ const title = titleOf(report);
30
+ const form = review ? TITLES.review : TITLES[verb];
31
+ return {
32
+ ok: form.test(title),
33
+ why: `${review ? "review" : verb}: ${title ? `"${title}"` : "no report"}`
34
+ };
35
+ })
36
+ },
37
+ {
38
+ id: "numbered-questions",
39
+ rule: "Asks two or more questions as a numbered list",
40
+ judge: ({ beats }) => turns(beats).flatMap(({ report }) => {
41
+ const asked = questionLines(report);
42
+ if (asked.length < 2) {
43
+ return [];
44
+ }
45
+ const loose = asked.filter((line) => !NUMBERED.test(line));
46
+ return [
47
+ {
48
+ ok: loose.length === 0,
49
+ why: loose.length === 0 ? `${asked.length} questions, all numbered` : `not numbered: "${loose[0]?.trim()}"`
50
+ }
51
+ ];
52
+ })
53
+ },
54
+ {
55
+ id: "next-todo",
56
+ rule: "Proposes the next todo after a merge or remove",
57
+ judge: ({ beats, handed }) => handed ? [] : endings(beats).filter(({ verb, output }) => verb === "remove" || verb === "merge" && PROPOSED.test(output)).map(({ verb, report }) => {
58
+ const ok = TODO_HEADING.test(report);
59
+ return {
60
+ ok,
61
+ why: `${verb}: ${ok ? "proposed one" : "proposed none"}`
62
+ };
63
+ })
64
+ },
65
+ {
66
+ id: "squashed",
67
+ rule: "Lands one commit with no attribution",
68
+ judge: (_, { landed }) => landed.map(({ task, commits }) => {
69
+ const attributed = commits.find((message) => ATTRIBUTION.some((pattern) => pattern.test(message)));
70
+ return {
71
+ ok: commits.length === 1 && attributed === undefined,
72
+ why: commits.length !== 1 ? `${task}: landed ${commits.length} commits` : attributed === undefined ? `${task}: one plain commit` : `${task}: attributed`
73
+ };
74
+ })
75
+ },
76
+ {
77
+ id: "setup-read-first",
78
+ rule: "Reads a setup step before acting on it",
79
+ judge: ({ beats }) => setups(beats).flatMap(({ step, span }) => {
80
+ const first = span.find((beat) => beat.kind === "edit" || beat.kind === "run" && splatesCalls2(beat.command).length > 0);
81
+ if (first === undefined) {
82
+ return [];
83
+ }
84
+ const ok = checks(first, step);
85
+ return [
86
+ {
87
+ ok,
88
+ why: ok ? `${step}: read it first` : `${step}: acted before reading it`
89
+ }
90
+ ];
91
+ })
92
+ },
93
+ {
94
+ id: "setup-checked",
95
+ rule: "Checks a setup step after each change",
96
+ judge: ({ beats }) => setups(beats).flatMap(({ step, span }) => turns(span).flatMap(({ beats: turn }) => {
97
+ const last = turn.findLastIndex(acts);
98
+ if (last === -1) {
99
+ return [];
100
+ }
101
+ const ok = turn.slice(last + 1).some((beat) => checks(beat, step));
102
+ return [
103
+ {
104
+ ok,
105
+ why: ok ? `${step}: checked after the last change` : `${step}: changed without checking after`
106
+ }
107
+ ];
108
+ }))
109
+ },
110
+ {
111
+ id: "setup-one-step",
112
+ rule: "Stays on the setup step it was asked about",
113
+ judge: ({ beats }) => setups(beats).map(({ step, span }) => {
114
+ const next = span.findIndex((beat, at) => at > 0 && beat.kind === "user");
115
+ const other = (next === -1 ? span : span.slice(0, next)).flatMap((beat) => beat.kind === "run" ? splatesCalls2(beat.command) : []).flatMap(({ verb, args }) => verb === "setup" ? args.filter((arg) => !arg.startsWith("-") && arg !== step) : [])[0];
116
+ return {
117
+ ok: other === undefined,
118
+ why: other === undefined ? `${step}: kept to it` : `${step}: went on to ${other} unasked`
119
+ };
120
+ })
121
+ }
122
+ ];
123
+ var SETUP = /^\s*\[(?:SPLATES|ARBOR) SETUP ([^\]\s]+)\]/;
124
+ function setups(beats) {
125
+ const asks = beats.flatMap((beat, at) => {
126
+ const step = beat.kind === "user" ? SETUP.exec(beat.text)?.[1] : undefined;
127
+ return step === undefined ? [] : [{ at, step }];
128
+ });
129
+ return asks.map(({ at, step }, index) => ({
130
+ step,
131
+ span: beats.slice(at, asks[index + 1]?.at ?? beats.length)
132
+ }));
133
+ }
134
+ function checks(beat, step) {
135
+ return beat.kind === "run" && splatesCalls2(beat.command).some(({ verb, args }) => verb === "setup" && args.includes(step));
136
+ }
137
+ function acts(beat) {
138
+ if (beat.kind === "edit") {
139
+ return true;
140
+ }
141
+ return beat.kind === "run" && splatesCalls2(beat.command).some(({ verb, args }) => verb !== "setup" && !(verb === "creds" && ["wait", "list"].includes(args[0] ?? "")));
142
+ }
143
+ var TITLES = {
144
+ merge: /^## ✅ Merged \S+ onto \S+$/,
145
+ remove: /^## 🛑 Removed \S+: .*[^.!?:]$/,
146
+ escalate: /^## ⚠️? Escalated \S+: .*[^.!?:]$/,
147
+ review: /^## 🔍 Ready to merge \S+ onto \S+$/
148
+ };
149
+ var TODO_HEADING = /^### 📝 .+ \[#\d+\]\s*$/m;
150
+ var PROPOSED = /splates add <task> --todo \d+/;
151
+ var NUMBERED = /^\s*\d+\.\s/;
152
+ var ATTRIBUTION = [/^co-authored-by:/im, /generated (with|by)/i, /🤖/u];
153
+ var SPLATES = /(?:^|[;&|(\n])\s*(?:[A-Z_][A-Z0-9_]*=\S*\s+)*(?:(?:bunx|npx)\s+)?(?:\S*\/)?splates(?:@\S+)?\s+([a-z]+)([^;&|\n]*)/g;
154
+ function splatesCalls2(command) {
155
+ return [...command.matchAll(SPLATES)].map((match) => ({
156
+ verb: match[1],
157
+ args: words(match[2])
158
+ }));
159
+ }
160
+ function words(text) {
161
+ return [...text.matchAll(/'([^']*)'|"((?:\\.|[^"\\])*)"|(\S+)/g)].map((match) => match[1] ?? match[2] ?? match[3] ?? "");
162
+ }
163
+ var DONE = /^(added|merged|removed|escalated) (?!todo \d)(\S+)/gm;
164
+ var VERBS = {
165
+ added: "add",
166
+ merged: "merge",
167
+ removed: "remove",
168
+ escalated: "escalate"
169
+ };
170
+ var COLOR = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
171
+ function events(beats) {
172
+ return beats.flatMap((beat, at) => {
173
+ if (beat.kind !== "run" || /\bSPLATES_HOME=/.test(beat.command)) {
174
+ return [];
175
+ }
176
+ const calls = splatesCalls2(beat.command);
177
+ const output = beat.output.replace(COLOR, "");
178
+ return [...output.matchAll(DONE)].flatMap((match) => {
179
+ const verb = VERBS[match[1]];
180
+ const task = match[2];
181
+ const call = calls.find((each) => each.verb === verb);
182
+ if (call === undefined) {
183
+ return [];
184
+ }
185
+ const named = calls.find((each) => each.verb === verb && each.args.includes(task));
186
+ const args = (named ?? call).args;
187
+ const base = args[args.indexOf("--base") + 1];
188
+ return [
189
+ {
190
+ at,
191
+ verb,
192
+ task,
193
+ own: !args.includes("--agent") && !(args.includes("--base") && base?.startsWith("task/")),
194
+ review: args.includes("--review"),
195
+ output
196
+ }
197
+ ];
198
+ });
199
+ });
200
+ }
201
+ function added(beats) {
202
+ return events(beats).filter((each) => each.verb === "add" && each.own);
203
+ }
204
+ function turns(beats) {
205
+ const found = [];
206
+ beats.forEach((beat, at) => {
207
+ if (beat.kind === "user" || found.length === 0) {
208
+ found.push({ from: at, beats: [], report: "" });
209
+ }
210
+ found.at(-1)?.beats.push(beat);
211
+ });
212
+ for (const turn of found) {
213
+ turn.report = said(turn.beats);
214
+ }
215
+ return found;
216
+ }
217
+ function said(beats) {
218
+ const last = beats.findLast((beat) => beat.kind === "say");
219
+ return last?.kind === "say" ? last.text : "";
220
+ }
221
+ function endings(beats) {
222
+ const all = events(beats);
223
+ return turns(beats).flatMap(({ from, beats: turn }) => {
224
+ const to = from + turn.length;
225
+ const inTurn = all.filter((each) => each.at >= from && each.at < to);
226
+ return inTurn.flatMap((each, index) => {
227
+ if (each.verb === "add") {
228
+ return [];
229
+ }
230
+ const next = inTurn[index + 1];
231
+ if (next !== undefined && next.verb !== "add") {
232
+ return [];
233
+ }
234
+ const until = next?.at ?? to;
235
+ return [
236
+ {
237
+ ...each,
238
+ verb: each.verb,
239
+ report: said(beats.slice(each.at + 1, until))
240
+ }
241
+ ];
242
+ });
243
+ });
244
+ }
245
+ function titleOf(report) {
246
+ return report.trim().split(`
247
+ `)[0]?.trim() ?? "";
248
+ }
249
+ function questionLines(text) {
250
+ let fenced = false;
251
+ return text.split(`
252
+ `).filter((line) => {
253
+ if (/^\s*(```|~~~)/.test(line)) {
254
+ fenced = !fenced;
255
+ return false;
256
+ }
257
+ const trimmed = line.trim().replace(/\**$/, "");
258
+ return !fenced && trimmed.endsWith("?") && !trimmed.startsWith("#") && !trimmed.startsWith(">");
259
+ });
260
+ }
261
+ function isPlan(path) {
262
+ return path === "SPLATES.md" || path.endsWith("/SPLATES.md");
263
+ }
264
+ var WRITES = />>?[^;&|\n]*SPLATES\.md|\btee\b[^;&|\n]*SPLATES\.md|\s-i\b|\.write(_text)?\(|open\([^)]*,\s*['"][wa]/;
265
+ function writesPlan(beat) {
266
+ if (beat.kind === "edit") {
267
+ return beat.paths.some(isPlan);
268
+ }
269
+ return beat.kind === "run" && beat.command.includes("SPLATES.md") && WRITES.test(beat.command);
270
+ }
271
+
272
+ export { judge2, CHECKS2, splatesCalls2 };
@@ -0,0 +1,157 @@
1
+ import {
2
+ LEGACY_DIR,
3
+ unmigrated
4
+ } from "./index-k73tsw5g.js";
5
+ import {
6
+ fail
7
+ } from "./index-kaa44k7q.js";
8
+
9
+ // roster.ts
10
+ import { homedir } from "node:os";
11
+ import { z } from "zod";
12
+ class Roster2 {
13
+ path;
14
+ deps;
15
+ constructor(path, deps) {
16
+ this.path = path;
17
+ this.deps = deps;
18
+ }
19
+ async seed() {
20
+ if (await this.deps.fs.exists(this.path)) {
21
+ return false;
22
+ }
23
+ await this.deps.fs.mkdir(this.path.slice(0, this.path.lastIndexOf("/")));
24
+ const tmp = `${this.path}.${crypto.randomUUID()}.tmp`;
25
+ await this.deps.fs.write(tmp, `${JSON.stringify({ format: FORMAT, agents: DEFAULT_AGENTS }, null, "\t")}
26
+ `);
27
+ await this.deps.fs.rename(tmp, this.path);
28
+ return true;
29
+ }
30
+ async agents() {
31
+ const raw = await this.deps.fs.read(this.path).catch(() => null);
32
+ if (raw === null) {
33
+ return DEFAULT_AGENTS;
34
+ }
35
+ let json;
36
+ try {
37
+ json = JSON.parse(raw);
38
+ } catch {
39
+ fail("usage", `${this.path} is not JSON: fix or delete it`, {
40
+ path: this.path
41
+ });
42
+ }
43
+ const format = json?.format;
44
+ if (typeof format === "number" && format > FORMAT) {
45
+ fail("outdated", [
46
+ `${this.path} is in format ${format}, written by a newer splates; this one understands up to ${FORMAT}, so it leaves it alone.`,
47
+ "Run `bunx @stringsync/splates@latest` instead."
48
+ ].join(`
49
+ `), { path: this.path, format, understood: FORMAT });
50
+ }
51
+ const parsed = schema.safeParse(json);
52
+ if (!parsed.success) {
53
+ const issue = parsed.error.issues[0];
54
+ fail("usage", `${this.path} is not a roster: ${issue?.path.join(".") || "the file"}: ${issue?.message}. Fix it, or delete it for the defaults`, { path: this.path });
55
+ }
56
+ const agents = parsed.data.agents;
57
+ const twice = agents.find((agent, i) => agents.findIndex((each) => each.id === agent.id) !== i);
58
+ if (twice !== undefined) {
59
+ fail("usage", `${this.path} lists agent '${twice.id}' twice: give each its own id`, { path: this.path, agent: twice.id });
60
+ }
61
+ return agents;
62
+ }
63
+ }
64
+ var FORMAT = 1;
65
+ var schema = z.object({
66
+ format: z.number().int().positive().optional(),
67
+ agents: z.array(z.object({
68
+ id: z.string().min(1),
69
+ name: z.string().min(1),
70
+ argv: z.array(z.string()).min(1),
71
+ models: z.array(z.object({
72
+ id: z.string().min(1),
73
+ name: z.string().min(1),
74
+ strengths: z.string(),
75
+ cost: z.string()
76
+ }))
77
+ }))
78
+ });
79
+ var DEFAULT_AGENTS = [
80
+ {
81
+ id: "claude",
82
+ name: "Claude",
83
+ argv: ["bunx", "@agentclientprotocol/claude-agent-acp"],
84
+ models: [
85
+ {
86
+ id: "fable",
87
+ name: "Fable 5.1",
88
+ strengths: "the toughest and longest-running work",
89
+ cost: "highest"
90
+ },
91
+ {
92
+ id: "opus",
93
+ name: "Opus 5.5",
94
+ strengths: "complex work and everyday tasks; Claude's default",
95
+ cost: "high"
96
+ },
97
+ {
98
+ id: "sonnet",
99
+ name: "Sonnet 5.5",
100
+ strengths: "simpler, well-specified tasks",
101
+ cost: "medium"
102
+ },
103
+ {
104
+ id: "haiku",
105
+ name: "Haiku 5.5",
106
+ strengths: "quick answers and mechanical edits",
107
+ cost: "low"
108
+ }
109
+ ]
110
+ },
111
+ {
112
+ id: "codex",
113
+ name: "Codex",
114
+ argv: ["bunx", "@agentclientprotocol/codex-acp"],
115
+ models: [
116
+ {
117
+ id: "gpt-6-astra",
118
+ name: "6 Astra",
119
+ strengths: "Codex's default, for complex work",
120
+ cost: "high"
121
+ },
122
+ {
123
+ id: "gpt-6-luna",
124
+ name: "6 Luna",
125
+ strengths: "easier tasks, fast",
126
+ cost: "low"
127
+ }
128
+ ]
129
+ }
130
+ ];
131
+ function modelsOf(agent) {
132
+ return "models" in agent ? agent.models : [];
133
+ }
134
+ function pickLabel(pick) {
135
+ return pick.model === undefined ? pick.agent : `${pick.agent} (${pick.model})`;
136
+ }
137
+ function pickAgent(agents, agent, model, data = {}) {
138
+ const found = agents.find((each) => each.id === agent);
139
+ if (found === undefined) {
140
+ fail("usage", `no agent '${agent}' on the roster: use one of ${agents.map((each) => each.id).join(", ")} (\`splates agents\` lists them; ~/.splates/agents.json keeps them)`, { ...data, agent });
141
+ }
142
+ if (model === undefined || found.models.length === 0) {
143
+ return model === undefined ? { agent } : { agent, model };
144
+ }
145
+ const wanted = model.toLowerCase();
146
+ const picked = found.models.find((each) => each.id === model || agent !== "opencode" && !model.includes("/") && !each.id.includes("/") && (each.id.toLowerCase() === wanted || each.name.toLowerCase() === wanted));
147
+ if (picked === undefined) {
148
+ fail("usage", `${found.name} has no model '${model}' on the roster: use one of ${found.models.map((each) => each.id).join(", ")}`, { ...data, agent, model });
149
+ }
150
+ return { agent, model: picked.id };
151
+ }
152
+ function splatesHome(ps) {
153
+ const home = ps.env("HOME") ?? homedir();
154
+ return ps.env("SPLATES_HOME") ?? unmigrated(`${home}/.splates`, `${home}/.${LEGACY_DIR}`);
155
+ }
156
+
157
+ export { Roster2, DEFAULT_AGENTS, modelsOf, pickLabel, pickAgent, splatesHome };