@alisio/plugin-swarm 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/.agents/agents/swarm-architect.md +20 -0
  2. package/.agents/agents/swarm-cleaner.md +20 -0
  3. package/.agents/agents/swarm-coder.md +20 -0
  4. package/.agents/agents/swarm-hardener.md +20 -0
  5. package/.agents/agents/swarm-lieutenant.md +20 -0
  6. package/.agents/agents/swarm-qa.md +20 -0
  7. package/.agents/agents/swarm-refactorer.md +20 -0
  8. package/.agents/agents/swarm-specifier.md +20 -0
  9. package/.agents/skills/swarm-architecture-rules/SKILL.md +43 -0
  10. package/.agents/skills/swarm-clarification/SKILL.md +42 -0
  11. package/.agents/skills/swarm-cleaner-metrics/SKILL.md +44 -0
  12. package/.agents/skills/swarm-engineering-constitution/SKILL.md +44 -0
  13. package/.agents/skills/swarm-gherkin-spec/SKILL.md +43 -0
  14. package/.agents/skills/swarm-handoff-protocol/SKILL.md +45 -0
  15. package/.agents/skills/swarm-lieutenant/SKILL.md +41 -0
  16. package/.agents/skills/swarm-mutation-hardening/SKILL.md +43 -0
  17. package/.agents/skills/swarm-qa-acceptance/SKILL.md +42 -0
  18. package/.agents/skills/swarm-tdd-slice/SKILL.md +44 -0
  19. package/.agents/skills/swarm-toolchain-profile/SKILL.md +41 -0
  20. package/LICENSE +21 -0
  21. package/README.es.md +337 -0
  22. package/README.md +322 -2
  23. package/assets/approval-flow.svg +1 -0
  24. package/assets/architecture.svg +1 -0
  25. package/assets/audit-states.svg +1 -0
  26. package/assets/dashboard.css +964 -0
  27. package/assets/dashboard.html +123 -0
  28. package/assets/dashboard.js +965 -0
  29. package/assets/dashboard.svg +1 -0
  30. package/assets/handoff-sequence.svg +1 -0
  31. package/assets/pack-pipelines.svg +1 -0
  32. package/assets/packs/four-pack/pack.json +18 -0
  33. package/assets/packs/six-pack/pack.json +22 -0
  34. package/assets/packs/two-pack/pack.json +14 -0
  35. package/assets/screenshots/dashboard-board-dark.png +0 -0
  36. package/assets/screenshots/dashboard-board-light.png +0 -0
  37. package/assets/screenshots/dashboard-documents-diff.png +0 -0
  38. package/assets/screenshots/dashboard-empty-state.png +0 -0
  39. package/assets/screenshots/dashboard-phone.png +0 -0
  40. package/assets/screenshots/dashboard-reject-dialog.png +0 -0
  41. package/assets/screenshots/dashboard-transcript-tail.png +0 -0
  42. package/assets/screenshots/dashboard-work-queue.png +0 -0
  43. package/assets/task-states.svg +1 -0
  44. package/assets/toolchains/node-ts.json +61 -0
  45. package/cover.svg +46 -0
  46. package/dist/adapters/child-session-runner.d.ts +36 -0
  47. package/dist/adapters/child-session-runner.d.ts.map +1 -0
  48. package/dist/adapters/child-session-runner.js +156 -0
  49. package/dist/adapters/child-session-runner.js.map +1 -0
  50. package/dist/adapters/fs-board-store.d.ts +11 -0
  51. package/dist/adapters/fs-board-store.d.ts.map +1 -0
  52. package/dist/adapters/fs-board-store.js +27 -0
  53. package/dist/adapters/fs-board-store.js.map +1 -0
  54. package/dist/adapters/fs-handoff-store.d.ts +36 -0
  55. package/dist/adapters/fs-handoff-store.d.ts.map +1 -0
  56. package/dist/adapters/fs-handoff-store.js +254 -0
  57. package/dist/adapters/fs-handoff-store.js.map +1 -0
  58. package/dist/adapters/fs-task-state-store.d.ts +16 -0
  59. package/dist/adapters/fs-task-state-store.d.ts.map +1 -0
  60. package/dist/adapters/fs-task-state-store.js +66 -0
  61. package/dist/adapters/fs-task-state-store.js.map +1 -0
  62. package/dist/adapters/git-worktree.d.ts +31 -0
  63. package/dist/adapters/git-worktree.d.ts.map +1 -0
  64. package/dist/adapters/git-worktree.js +209 -0
  65. package/dist/adapters/git-worktree.js.map +1 -0
  66. package/dist/adapters/process-exec.d.ts +36 -0
  67. package/dist/adapters/process-exec.d.ts.map +1 -0
  68. package/dist/adapters/process-exec.js +138 -0
  69. package/dist/adapters/process-exec.js.map +1 -0
  70. package/dist/adapters/process-gate-runner.d.ts +39 -0
  71. package/dist/adapters/process-gate-runner.d.ts.map +1 -0
  72. package/dist/adapters/process-gate-runner.js +242 -0
  73. package/dist/adapters/process-gate-runner.js.map +1 -0
  74. package/dist/adapters/unavailable-runner.d.ts +4 -0
  75. package/dist/adapters/unavailable-runner.d.ts.map +1 -0
  76. package/dist/adapters/unavailable-runner.js +12 -0
  77. package/dist/adapters/unavailable-runner.js.map +1 -0
  78. package/dist/app/audit.d.ts +23 -0
  79. package/dist/app/audit.d.ts.map +1 -0
  80. package/dist/app/audit.js +9 -0
  81. package/dist/app/audit.js.map +1 -0
  82. package/dist/app/budget.d.ts +29 -0
  83. package/dist/app/budget.d.ts.map +1 -0
  84. package/dist/app/budget.js +78 -0
  85. package/dist/app/budget.js.map +1 -0
  86. package/dist/app/chat-store.d.ts +18 -0
  87. package/dist/app/chat-store.d.ts.map +1 -0
  88. package/dist/app/chat-store.js +48 -0
  89. package/dist/app/chat-store.js.map +1 -0
  90. package/dist/app/documents.d.ts +33 -0
  91. package/dist/app/documents.d.ts.map +1 -0
  92. package/dist/app/documents.js +63 -0
  93. package/dist/app/documents.js.map +1 -0
  94. package/dist/app/forge.d.ts +106 -0
  95. package/dist/app/forge.d.ts.map +1 -0
  96. package/dist/app/forge.js +420 -0
  97. package/dist/app/forge.js.map +1 -0
  98. package/dist/app/operator.d.ts +46 -0
  99. package/dist/app/operator.d.ts.map +1 -0
  100. package/dist/app/operator.js +168 -0
  101. package/dist/app/operator.js.map +1 -0
  102. package/dist/app/pidfile.d.ts +18 -0
  103. package/dist/app/pidfile.d.ts.map +1 -0
  104. package/dist/app/pidfile.js +56 -0
  105. package/dist/app/pidfile.js.map +1 -0
  106. package/dist/app/project.d.ts +93 -0
  107. package/dist/app/project.d.ts.map +1 -0
  108. package/dist/app/project.js +291 -0
  109. package/dist/app/project.js.map +1 -0
  110. package/dist/app/prompts.d.ts +48 -0
  111. package/dist/app/prompts.d.ts.map +1 -0
  112. package/dist/app/prompts.js +88 -0
  113. package/dist/app/prompts.js.map +1 -0
  114. package/dist/app/pump.d.ts +144 -0
  115. package/dist/app/pump.d.ts.map +1 -0
  116. package/dist/app/pump.js +707 -0
  117. package/dist/app/pump.js.map +1 -0
  118. package/dist/app/services.d.ts +112 -0
  119. package/dist/app/services.d.ts.map +1 -0
  120. package/dist/app/services.js +323 -0
  121. package/dist/app/services.js.map +1 -0
  122. package/dist/app/state.d.ts +70 -0
  123. package/dist/app/state.d.ts.map +1 -0
  124. package/dist/app/state.js +96 -0
  125. package/dist/app/state.js.map +1 -0
  126. package/dist/args.d.ts +10 -0
  127. package/dist/args.d.ts.map +1 -0
  128. package/dist/args.js +33 -0
  129. package/dist/args.js.map +1 -0
  130. package/dist/coordinator.d.ts +80 -0
  131. package/dist/coordinator.d.ts.map +1 -0
  132. package/dist/coordinator.js +481 -0
  133. package/dist/coordinator.js.map +1 -0
  134. package/dist/dashboard/api.d.ts +28 -0
  135. package/dist/dashboard/api.d.ts.map +1 -0
  136. package/dist/dashboard/api.js +178 -0
  137. package/dist/dashboard/api.js.map +1 -0
  138. package/dist/dashboard/auth.d.ts +13 -0
  139. package/dist/dashboard/auth.d.ts.map +1 -0
  140. package/dist/dashboard/auth.js +36 -0
  141. package/dist/dashboard/auth.js.map +1 -0
  142. package/dist/dashboard/open.d.ts +7 -0
  143. package/dist/dashboard/open.d.ts.map +1 -0
  144. package/dist/dashboard/open.js +17 -0
  145. package/dist/dashboard/open.js.map +1 -0
  146. package/dist/dashboard/server.d.ts +28 -0
  147. package/dist/dashboard/server.d.ts.map +1 -0
  148. package/dist/dashboard/server.js +176 -0
  149. package/dist/dashboard/server.js.map +1 -0
  150. package/dist/doctor.d.ts +28 -0
  151. package/dist/doctor.d.ts.map +1 -0
  152. package/dist/doctor.js +108 -0
  153. package/dist/doctor.js.map +1 -0
  154. package/dist/domain/attention.d.ts +20 -0
  155. package/dist/domain/attention.d.ts.map +1 -0
  156. package/dist/domain/attention.js +41 -0
  157. package/dist/domain/attention.js.map +1 -0
  158. package/dist/domain/envelope.d.ts +37 -0
  159. package/dist/domain/envelope.d.ts.map +1 -0
  160. package/dist/domain/envelope.js +106 -0
  161. package/dist/domain/envelope.js.map +1 -0
  162. package/dist/domain/handoff.d.ts +57 -0
  163. package/dist/domain/handoff.d.ts.map +1 -0
  164. package/dist/domain/handoff.js +191 -0
  165. package/dist/domain/handoff.js.map +1 -0
  166. package/dist/domain/identifiers.d.ts +18 -0
  167. package/dist/domain/identifiers.d.ts.map +1 -0
  168. package/dist/domain/identifiers.js +70 -0
  169. package/dist/domain/identifiers.js.map +1 -0
  170. package/dist/domain/pack.d.ts +45 -0
  171. package/dist/domain/pack.d.ts.map +1 -0
  172. package/dist/domain/pack.js +207 -0
  173. package/dist/domain/pack.js.map +1 -0
  174. package/dist/domain/pipeline.d.ts +33 -0
  175. package/dist/domain/pipeline.d.ts.map +1 -0
  176. package/dist/domain/pipeline.js +50 -0
  177. package/dist/domain/pipeline.js.map +1 -0
  178. package/dist/domain/task.d.ts +26 -0
  179. package/dist/domain/task.d.ts.map +1 -0
  180. package/dist/domain/task.js +154 -0
  181. package/dist/domain/task.js.map +1 -0
  182. package/dist/domain/taskstate.d.ts +37 -0
  183. package/dist/domain/taskstate.d.ts.map +1 -0
  184. package/dist/domain/taskstate.js +98 -0
  185. package/dist/domain/taskstate.js.map +1 -0
  186. package/dist/gates/dry.d.ts +14 -0
  187. package/dist/gates/dry.d.ts.map +1 -0
  188. package/dist/gates/dry.js +71 -0
  189. package/dist/gates/dry.js.map +1 -0
  190. package/dist/gates/parsers.d.ts +34 -0
  191. package/dist/gates/parsers.d.ts.map +1 -0
  192. package/dist/gates/parsers.js +120 -0
  193. package/dist/gates/parsers.js.map +1 -0
  194. package/dist/index.d.ts +45 -0
  195. package/dist/index.d.ts.map +1 -0
  196. package/dist/index.js +207 -0
  197. package/dist/index.js.map +1 -0
  198. package/dist/options.d.ts +13 -0
  199. package/dist/options.d.ts.map +1 -0
  200. package/dist/options.js +27 -0
  201. package/dist/options.js.map +1 -0
  202. package/dist/panel.d.ts +5 -0
  203. package/dist/panel.d.ts.map +1 -0
  204. package/dist/panel.js +52 -0
  205. package/dist/panel.js.map +1 -0
  206. package/dist/ports/agent-runner.d.ts +51 -0
  207. package/dist/ports/agent-runner.d.ts.map +1 -0
  208. package/dist/ports/agent-runner.js +2 -0
  209. package/dist/ports/agent-runner.js.map +1 -0
  210. package/dist/ports/board-store.d.ts +7 -0
  211. package/dist/ports/board-store.d.ts.map +1 -0
  212. package/dist/ports/board-store.js +2 -0
  213. package/dist/ports/board-store.js.map +1 -0
  214. package/dist/ports/clock.d.ts +5 -0
  215. package/dist/ports/clock.d.ts.map +1 -0
  216. package/dist/ports/clock.js +2 -0
  217. package/dist/ports/clock.js.map +1 -0
  218. package/dist/ports/gate-runner.d.ts +34 -0
  219. package/dist/ports/gate-runner.d.ts.map +1 -0
  220. package/dist/ports/gate-runner.js +2 -0
  221. package/dist/ports/gate-runner.js.map +1 -0
  222. package/dist/ports/handoff-store.d.ts +39 -0
  223. package/dist/ports/handoff-store.d.ts.map +1 -0
  224. package/dist/ports/handoff-store.js +2 -0
  225. package/dist/ports/handoff-store.js.map +1 -0
  226. package/dist/ports/isolation.d.ts +43 -0
  227. package/dist/ports/isolation.d.ts.map +1 -0
  228. package/dist/ports/isolation.js +2 -0
  229. package/dist/ports/isolation.js.map +1 -0
  230. package/dist/ports/notifier.d.ts +28 -0
  231. package/dist/ports/notifier.d.ts.map +1 -0
  232. package/dist/ports/notifier.js +2 -0
  233. package/dist/ports/notifier.js.map +1 -0
  234. package/dist/ports/task-state-store.d.ts +10 -0
  235. package/dist/ports/task-state-store.d.ts.map +1 -0
  236. package/dist/ports/task-state-store.js +2 -0
  237. package/dist/ports/task-state-store.js.map +1 -0
  238. package/dist/refs.d.ts +10 -0
  239. package/dist/refs.d.ts.map +1 -0
  240. package/dist/refs.js +19 -0
  241. package/dist/refs.js.map +1 -0
  242. package/dist/resources.d.ts +55 -0
  243. package/dist/resources.d.ts.map +1 -0
  244. package/dist/resources.js +255 -0
  245. package/dist/resources.js.map +1 -0
  246. package/dist/status.d.ts +18 -0
  247. package/dist/status.d.ts.map +1 -0
  248. package/dist/status.js +60 -0
  249. package/dist/status.js.map +1 -0
  250. package/dist/storage.d.ts +18 -0
  251. package/dist/storage.d.ts.map +1 -0
  252. package/dist/storage.js +79 -0
  253. package/dist/storage.js.map +1 -0
  254. package/dist/toolchains/profile.d.ts +21 -0
  255. package/dist/toolchains/profile.d.ts.map +1 -0
  256. package/dist/toolchains/profile.js +60 -0
  257. package/dist/toolchains/profile.js.map +1 -0
  258. package/dist/toolchains/thresholds.d.ts +29 -0
  259. package/dist/toolchains/thresholds.d.ts.map +1 -0
  260. package/dist/toolchains/thresholds.js +45 -0
  261. package/dist/toolchains/thresholds.js.map +1 -0
  262. package/dist/version.d.ts +2 -0
  263. package/dist/version.d.ts.map +1 -0
  264. package/dist/version.js +3 -0
  265. package/dist/version.js.map +1 -0
  266. package/package.json +51 -3
@@ -0,0 +1,70 @@
1
+ /** Identifier rules (spec 4.1). Every identifier is untrusted input and validated at each boundary. */
2
+ export const projectNamePattern = /^[a-z0-9][a-z0-9-]{0,47}$/;
3
+ export const taskNamePattern = /^[a-z0-9][a-z0-9-]{0,47}$/;
4
+ export const rolePattern = /^[a-z][a-z0-9-]{0,23}$/;
5
+ export const taskIdPattern = /^\d{8}T\d{12}Z-[a-z0-9][a-z0-9-]{0,47}$/;
6
+ export const commitPattern = /^[0-9a-f]{10}$/;
7
+ export function validateProjectName(name) {
8
+ if (typeof name !== "string" || !projectNamePattern.test(name)) {
9
+ throw new Error("Invalid project name: use 1-48 lowercase letters, digits or hyphens, starting with a letter or digit");
10
+ }
11
+ return name;
12
+ }
13
+ export function validateTaskName(name) {
14
+ if (typeof name !== "string" || !taskNamePattern.test(name)) {
15
+ throw new Error("Invalid task name: use 1-48 lowercase letters, digits or hyphens, starting with a letter or digit");
16
+ }
17
+ return name;
18
+ }
19
+ export function validateRole(role) {
20
+ if (typeof role !== "string" || !rolePattern.test(role)) {
21
+ throw new Error("Invalid role: use 1-24 lowercase letters, digits or hyphens, starting with a letter");
22
+ }
23
+ return role;
24
+ }
25
+ export function validateTaskId(taskId) {
26
+ if (typeof taskId !== "string" || !taskIdPattern.test(taskId)) {
27
+ throw new Error("Invalid task id: expected YYYYMMDDTHHMMSSffffffZ-<slug>");
28
+ }
29
+ return taskId;
30
+ }
31
+ export function validateCommit(commit) {
32
+ if (typeof commit !== "string" || !commitPattern.test(commit)) {
33
+ throw new Error("Invalid commit: expected exactly 10 lowercase hexadecimal characters");
34
+ }
35
+ return commit;
36
+ }
37
+ /** Derive a task slug from free-form text. May return an empty string when nothing is usable. */
38
+ export function slugify(text) {
39
+ return text
40
+ .toLowerCase()
41
+ .replace(/[^a-z0-9]+/g, "-")
42
+ .replace(/^-+|-+$/g, "")
43
+ .slice(0, 48)
44
+ .replace(/-+$/g, "");
45
+ }
46
+ /** `YYYYMMDDTHHMMSSffffffZ-<slug>`; `sequence` (0-999) disambiguates ids minted in one millisecond. */
47
+ export function makeTaskId(now, slug, sequence = 0) {
48
+ validateTaskName(slug);
49
+ if (!Number.isInteger(sequence) || sequence < 0 || sequence > 999) {
50
+ throw new Error("Invalid sequence: expected an integer from 0 to 999");
51
+ }
52
+ const iso = now.toISOString(); // 2026-01-02T03:04:05.678Z
53
+ const stamp = `${iso.slice(0, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}T${iso.slice(11, 13)}${iso.slice(14, 16)}${iso.slice(17, 19)}`;
54
+ const micros = `${iso.slice(20, 23)}${String(sequence).padStart(3, "0")}`;
55
+ return `${stamp}${micros}Z-${slug}`;
56
+ }
57
+ /** A relative path made of plain segments: no absolute, drive, backslash, empty, `.` or `..` parts. */
58
+ export function assertRelativePath(path) {
59
+ if (typeof path !== "string" ||
60
+ !path ||
61
+ path.includes("\0") ||
62
+ path.includes("\\") ||
63
+ path.startsWith("/") ||
64
+ /^[A-Za-z]:/.test(path) ||
65
+ path.split("/").some((part) => part === "" || part === "." || part === "..")) {
66
+ throw new Error(`Unsafe relative path: ${JSON.stringify(path)}`);
67
+ }
68
+ return path;
69
+ }
70
+ //# sourceMappingURL=identifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifiers.js","sourceRoot":"","sources":["../../src/domain/identifiers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ export type Isolation = "master" | "worktree";
2
+ export type Receive = "task" | "batch";
3
+ export type Propagation = "forward-only" | "back-one" | "back-all";
4
+ export declare const gateNames: readonly ["tests-green", "test-first", "coverage", "crap", "dry", "mutation", "structure", "acceptance"];
5
+ export type GateName = (typeof gateNames)[number];
6
+ export interface PackRole {
7
+ id: string;
8
+ agent: string;
9
+ isolation: Isolation;
10
+ receive: Receive;
11
+ propagation: Propagation;
12
+ /** Earlier role that receives this role's `blocked` findings (QA rejection routing, spec 8.4). */
13
+ rejectTo?: string;
14
+ }
15
+ export interface Thresholds {
16
+ coverage: number;
17
+ complexity: number;
18
+ crap: number;
19
+ mutation: number;
20
+ }
21
+ export interface Limits {
22
+ maxBounces: number;
23
+ maxAuditRounds: number;
24
+ }
25
+ export interface Pack {
26
+ schemaVersion: 1;
27
+ name: string;
28
+ description: string;
29
+ toolchain: string;
30
+ approval?: {
31
+ after: string;
32
+ };
33
+ roles: PackRole[];
34
+ parallel?: string[][];
35
+ gates: Record<string, GateName[]>;
36
+ thresholds: Thresholds;
37
+ limits: Limits;
38
+ }
39
+ /** Our defaults where upstream states none (spec 8.4). Pack data overrides them. */
40
+ export declare const defaultThresholds: Thresholds;
41
+ export declare const defaultLimits: Limits;
42
+ export declare function parsePack(input: unknown): Pack;
43
+ export declare const roleIds: (pack: Pack) => string[];
44
+ export declare const findRole: (pack: Pack, id: string) => PackRole | undefined;
45
+ //# sourceMappingURL=pack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../src/domain/pack.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;AAC9C,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AACvC,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,UAAU,CAAC;AAEnE,eAAO,MAAM,SAAS,YACpB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,MAAM,EACN,KAAK,EACL,UAAU,EACV,WAAW,EACX,YAAY,CACJ,CAAC;AACX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,MAAM;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,IAAI;IACnB,aAAa,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClC,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,EAAE,UAAmE,CAAC;AACpG,eAAO,MAAM,aAAa,EAAE,MAA6C,CAAC;AAuF1E,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAkH9C;AAED,eAAO,MAAM,OAAO,SAAU,IAAI,KAAG,MAAM,EAAuC,CAAC;AACnF,eAAO,MAAM,QAAQ,SAAU,IAAI,MAAM,MAAM,KAAG,QAAQ,GAAG,SAClB,CAAC"}
@@ -0,0 +1,207 @@
1
+ import { validateRole } from "./identifiers.js";
2
+ export const gateNames = [
3
+ "tests-green",
4
+ "test-first",
5
+ "coverage",
6
+ "crap",
7
+ "dry",
8
+ "mutation",
9
+ "structure",
10
+ "acceptance",
11
+ ];
12
+ /** Our defaults where upstream states none (spec 8.4). Pack data overrides them. */
13
+ export const defaultThresholds = { coverage: 80, complexity: 6, crap: 8, mutation: 80 };
14
+ export const defaultLimits = { maxBounces: 2, maxAuditRounds: 3 };
15
+ const packNamePattern = /^[a-z0-9][a-z0-9-]{0,47}$/;
16
+ const toolchainPattern = /^[a-z][a-z0-9-]{0,31}$/;
17
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
18
+ function onlyKeys(value, allowed, label) {
19
+ for (const key of Object.keys(value)) {
20
+ if (!allowed.includes(key))
21
+ throw new Error(`Unknown key "${key}" in ${label}`);
22
+ }
23
+ }
24
+ function parseRole(value, index) {
25
+ if (!isRecord(value))
26
+ throw new Error(`Role ${index + 1} must be an object`);
27
+ onlyKeys(value, ["id", "agent", "isolation", "receive", "propagation", "rejectTo"], `role ${index + 1}`);
28
+ const id = validateRole(value.id);
29
+ const agent = validateRole(value.agent);
30
+ if (value.isolation !== "master" && value.isolation !== "worktree") {
31
+ throw new Error(`Role ${id}: isolation must be "master" or "worktree"`);
32
+ }
33
+ if (value.receive !== "task" && value.receive !== "batch") {
34
+ throw new Error(`Role ${id}: receive must be "task" or "batch"`);
35
+ }
36
+ if (value.propagation !== "forward-only" &&
37
+ value.propagation !== "back-one" &&
38
+ value.propagation !== "back-all") {
39
+ throw new Error(`Role ${id}: propagation must be "forward-only", "back-one" or "back-all"`);
40
+ }
41
+ const rejectTo = value.rejectTo === undefined ? undefined : validateRole(value.rejectTo);
42
+ return {
43
+ id,
44
+ agent,
45
+ isolation: value.isolation,
46
+ receive: value.receive,
47
+ propagation: value.propagation,
48
+ ...(rejectTo ? { rejectTo } : {}),
49
+ };
50
+ }
51
+ function parseThresholds(value) {
52
+ if (value === undefined)
53
+ return { ...defaultThresholds };
54
+ if (!isRecord(value))
55
+ throw new Error("Invalid threshold block: expected an object");
56
+ onlyKeys(value, Object.keys(defaultThresholds), "thresholds");
57
+ const result = { ...defaultThresholds };
58
+ for (const key of Object.keys(defaultThresholds)) {
59
+ const raw = value[key];
60
+ if (raw === undefined)
61
+ continue;
62
+ const percent = key === "coverage" || key === "mutation";
63
+ if (typeof raw !== "number" ||
64
+ !Number.isFinite(raw) ||
65
+ raw < 0 ||
66
+ (percent && raw > 100) ||
67
+ (!percent && raw === 0)) {
68
+ throw new Error(`Invalid threshold ${key}: ${percent ? "0-100" : "a positive number"}`);
69
+ }
70
+ result[key] = raw;
71
+ }
72
+ return result;
73
+ }
74
+ function parseLimits(value) {
75
+ if (value === undefined)
76
+ return { ...defaultLimits };
77
+ if (!isRecord(value))
78
+ throw new Error("Invalid limit block: expected an object");
79
+ onlyKeys(value, Object.keys(defaultLimits), "limits");
80
+ const result = { ...defaultLimits };
81
+ for (const key of Object.keys(defaultLimits)) {
82
+ const raw = value[key];
83
+ if (raw === undefined)
84
+ continue;
85
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 0 || raw > 20) {
86
+ throw new Error(`Invalid limit ${key}: expected an integer from 0 to 20`);
87
+ }
88
+ result[key] = raw;
89
+ }
90
+ return result;
91
+ }
92
+ export function parsePack(input) {
93
+ if (!isRecord(input))
94
+ throw new Error("A pack must be a JSON object");
95
+ onlyKeys(input, [
96
+ "schemaVersion",
97
+ "name",
98
+ "description",
99
+ "toolchain",
100
+ "approval",
101
+ "roles",
102
+ "parallel",
103
+ "gates",
104
+ "thresholds",
105
+ "limits",
106
+ ], "pack");
107
+ if (input.schemaVersion !== 1)
108
+ throw new Error("Unsupported pack schemaVersion (expected 1)");
109
+ if (typeof input.name !== "string" || !packNamePattern.test(input.name)) {
110
+ throw new Error("Invalid pack name: use 1-48 lowercase letters, digits or hyphens");
111
+ }
112
+ if (typeof input.description !== "string" || !input.description.trim()) {
113
+ throw new Error("A pack needs a description");
114
+ }
115
+ if (typeof input.toolchain !== "string" || !toolchainPattern.test(input.toolchain)) {
116
+ throw new Error("Invalid toolchain id in pack");
117
+ }
118
+ if (!Array.isArray(input.roles) || input.roles.length === 0) {
119
+ throw new Error("A pack needs at least one role");
120
+ }
121
+ const roles = input.roles.map(parseRole);
122
+ const ids = roles.map((role) => role.id);
123
+ if (new Set(ids).size !== ids.length)
124
+ throw new Error("Duplicate role id in pack");
125
+ const masters = roles.filter((role) => role.isolation === "master");
126
+ if (masters.length !== 1)
127
+ throw new Error("A pack needs exactly one master role");
128
+ if (roles[0]?.isolation !== "master") {
129
+ throw new Error("The master role must be the first role (the pipeline entry)");
130
+ }
131
+ for (const [index, entry] of roles.entries()) {
132
+ const target = entry.rejectTo === undefined ? 0 : ids.indexOf(entry.rejectTo);
133
+ if (entry.rejectTo !== undefined && (target < 0 || target >= index)) {
134
+ throw new Error(`Role ${entry.id}: rejectTo must name an earlier role of the pack`);
135
+ }
136
+ }
137
+ let approval;
138
+ if (input.approval !== undefined) {
139
+ if (!isRecord(input.approval))
140
+ throw new Error("Invalid approval block");
141
+ onlyKeys(input.approval, ["after"], "approval");
142
+ const after = input.approval.after;
143
+ const position = typeof after === "string" ? ids.indexOf(after) : -1;
144
+ if (position < 0 || position === ids.length - 1) {
145
+ throw new Error("Invalid approval: `after` must name a role that is not the last role");
146
+ }
147
+ approval = { after: after };
148
+ }
149
+ const gates = {};
150
+ if (input.gates !== undefined) {
151
+ if (!isRecord(input.gates))
152
+ throw new Error("Invalid gates block");
153
+ for (const [roleId, list] of Object.entries(input.gates)) {
154
+ if (!ids.includes(roleId))
155
+ throw new Error(`Gates reference unknown role "${roleId}"`);
156
+ if (!Array.isArray(list))
157
+ throw new Error(`Gates for ${roleId} must be an array`);
158
+ gates[roleId] = list.map((gate) => {
159
+ if (!gateNames.includes(gate)) {
160
+ throw new Error(`Unknown gate "${String(gate)}" for ${roleId}`);
161
+ }
162
+ return gate;
163
+ });
164
+ }
165
+ }
166
+ let parallel;
167
+ if (input.parallel !== undefined) {
168
+ if (!Array.isArray(input.parallel))
169
+ throw new Error("Invalid parallel stages");
170
+ parallel = input.parallel.map((stage) => {
171
+ if (!Array.isArray(stage) || stage.length < 2 || !stage.every((id) => ids.includes(id))) {
172
+ throw new Error("Invalid parallel stage: list two or more roles that exist in the pack");
173
+ }
174
+ const positions = stage.map((id) => ids.indexOf(id));
175
+ const sorted = [...positions].sort((a, b) => a - b);
176
+ const contiguous = sorted.every((position, i) => i === 0 || position === sorted[i - 1] + 1);
177
+ if (!contiguous || positions.some((p, i) => p !== sorted[i])) {
178
+ throw new Error("Invalid parallel stage: roles must be adjacent and in pipeline order");
179
+ }
180
+ if (stage.includes(ids[ids.length - 1])) {
181
+ throw new Error("Invalid parallel stage: it cannot contain the last role");
182
+ }
183
+ if (stage.includes(ids[0])) {
184
+ throw new Error("Invalid parallel stage: it cannot contain the master role");
185
+ }
186
+ return stage;
187
+ });
188
+ if (approval && parallel.some((stage) => stage.includes(approval?.after))) {
189
+ throw new Error("Invalid approval: the gate cannot sit inside a parallel stage");
190
+ }
191
+ }
192
+ return {
193
+ schemaVersion: 1,
194
+ name: input.name,
195
+ description: input.description,
196
+ toolchain: input.toolchain,
197
+ ...(approval ? { approval } : {}),
198
+ roles,
199
+ ...(parallel ? { parallel } : {}),
200
+ gates,
201
+ thresholds: parseThresholds(input.thresholds),
202
+ limits: parseLimits(input.limits),
203
+ };
204
+ }
205
+ export const roleIds = (pack) => pack.roles.map((role) => role.id);
206
+ export const findRole = (pack, id) => pack.roles.find((role) => role.id === id);
207
+ //# sourceMappingURL=pack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack.js","sourceRoot":"","sources":["../../src/domain/pack.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type Pack } from "./pack.js";
2
+ export interface Route {
3
+ /** The single role that continues the work, absent for a terminal handoff. */
4
+ forward: string | undefined;
5
+ /** Merge-only copies (`non-forwarding: true`): receivers merge the commit and do no work. */
6
+ mergeOnly: string[];
7
+ /** The last role's handoff: broadcast merge-only to every other role; the card is Done. */
8
+ terminal: boolean;
9
+ }
10
+ export declare const entryRole: (pack: Pack) => string;
11
+ export declare const laneOrder: (pack: Pack) => string[];
12
+ export declare const isTerminalRole: (pack: Pack, role: string) => boolean;
13
+ export declare const approvalRequiredAfter: (pack: Pack, role: string) => boolean;
14
+ /** Where a handoff released by `from` goes. `from` is a role id or the phantom sender. */
15
+ export declare function route(pack: Pack, from: string): Route;
16
+ export interface ReleasePlan {
17
+ /** Recipients of the released handoff (several when a parallel stage starts). */
18
+ to: string[];
19
+ /** The parallel stage the releasing role belongs to: its handoffs wait for the join. */
20
+ stage?: string[];
21
+ terminal: boolean;
22
+ }
23
+ /** The parallel stage that contains `role`, if any. */
24
+ export declare const stageOf: (pack: Pack, role: string) => string[] | undefined;
25
+ /** The stage whose handoffs the role directly after it receives together (the join target). */
26
+ export declare function joinSource(pack: Pack, role: string): string[] | undefined;
27
+ /**
28
+ * Where a role's released handoff goes, aware of parallel stages (spec 4.2 and 8.4): the role
29
+ * before a stage fans out to every stage role, and a stage role hands off to the role after the
30
+ * stage once all stage roles are done.
31
+ */
32
+ export declare function releasePlan(pack: Pack, from: string): ReleasePlan;
33
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/domain/pipeline.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAA0B,MAAM,WAAW,CAAC;AAE9D,MAAM,WAAW,KAAK;IACpB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,6FAA6F;IAC7F,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,2FAA2F;IAC3F,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,SAAS,SAAU,IAAI,KAAG,MACqD,CAAC;AAE7F,eAAO,MAAM,SAAS,SAAU,IAAI,KAAG,MAAM,EAAgC,CAAC;AAE9E,eAAO,MAAM,cAAc,SAAU,IAAI,QAAQ,MAAM,KAAG,OAAyC,CAAC;AAEpG,eAAO,MAAM,qBAAqB,SAAU,IAAI,QAAQ,MAAM,KAAG,OAClC,CAAC;AAEhC,0FAA0F;AAC1F,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAarD;AAED,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,uDAAuD;AACvD,eAAO,MAAM,OAAO,SAAU,IAAI,QAAQ,MAAM,KAAG,MAAM,EAAE,GAAG,SACR,CAAC;AAEvD,+FAA+F;AAC/F,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAIzE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAYjE"}
@@ -0,0 +1,50 @@
1
+ import { PHANTOM_SENDER } from "./handoff.js";
2
+ import { roleIds } from "./pack.js";
3
+ export const entryRole = (pack) => (pack.roles.find((role) => role.isolation === "master") ?? pack.roles[0]).id;
4
+ export const laneOrder = (pack) => [...roleIds(pack), "done"];
5
+ export const isTerminalRole = (pack, role) => pack.roles.at(-1)?.id === role;
6
+ export const approvalRequiredAfter = (pack, role) => pack.approval?.after === role;
7
+ /** Where a handoff released by `from` goes. `from` is a role id or the phantom sender. */
8
+ export function route(pack, from) {
9
+ if (from === PHANTOM_SENDER)
10
+ return { forward: entryRole(pack), mergeOnly: [], terminal: false };
11
+ const ids = roleIds(pack);
12
+ const index = ids.indexOf(from);
13
+ if (index < 0)
14
+ throw new Error(`Unknown role in routing: ${from}`);
15
+ if (isTerminalRole(pack, from)) {
16
+ return { forward: undefined, mergeOnly: ids.filter((id) => id !== from), terminal: true };
17
+ }
18
+ const propagation = pack.roles[index]?.propagation ?? "forward-only";
19
+ const earlier = ids.slice(0, index);
20
+ const mergeOnly = propagation === "back-all" ? earlier : propagation === "back-one" ? earlier.slice(-1) : [];
21
+ return { forward: ids[index + 1], mergeOnly, terminal: false };
22
+ }
23
+ /** The parallel stage that contains `role`, if any. */
24
+ export const stageOf = (pack, role) => pack.parallel?.find((stage) => stage.includes(role));
25
+ /** The stage whose handoffs the role directly after it receives together (the join target). */
26
+ export function joinSource(pack, role) {
27
+ const ids = roleIds(pack);
28
+ const index = ids.indexOf(role);
29
+ return pack.parallel?.find((stage) => ids.indexOf(stage.at(-1)) === index - 1);
30
+ }
31
+ /**
32
+ * Where a role's released handoff goes, aware of parallel stages (spec 4.2 and 8.4): the role
33
+ * before a stage fans out to every stage role, and a stage role hands off to the role after the
34
+ * stage once all stage roles are done.
35
+ */
36
+ export function releasePlan(pack, from) {
37
+ const base = route(pack, from);
38
+ if (base.terminal)
39
+ return { to: base.mergeOnly, terminal: true };
40
+ const ids = roleIds(pack);
41
+ const stage = stageOf(pack, from);
42
+ if (stage) {
43
+ const after = ids[ids.indexOf(stage.at(-1)) + 1];
44
+ return { to: [after], stage, terminal: false };
45
+ }
46
+ const next = base.forward;
47
+ const entering = pack.parallel?.find((candidate) => candidate[0] === next);
48
+ return { to: entering ? [...entering] : [next], terminal: false };
49
+ }
50
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/domain/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { type StoredHandoff } from "./handoff.js";
2
+ import { type Pack } from "./pack.js";
3
+ export declare const taskStatuses: readonly ["queued", "working", "waiting_approval", "rejected", "merging", "clarifying", "blocked", "done"];
4
+ export type TaskStatus = (typeof taskStatuses)[number];
5
+ export interface TaskCard {
6
+ name: string;
7
+ taskId: string;
8
+ /** A pack role id, or `done`. */
9
+ lane: string;
10
+ status: TaskStatus;
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ auditCount: number;
14
+ }
15
+ export interface Board {
16
+ schemaVersion: 1;
17
+ tasks: TaskCard[];
18
+ }
19
+ export declare const emptyBoard: () => Board;
20
+ export declare function validateBoard(value: unknown): Board;
21
+ /**
22
+ * Rebuild the board from handoff files (the source of truth, AD-11). Board-only statuses that files
23
+ * cannot express (clarifying, blocked, rejected) survive from `previous` while the lane is unchanged.
24
+ */
25
+ export declare function rebuildBoard(pack: Pack, records: StoredHandoff[], previous?: Board): Board;
26
+ //# sourceMappingURL=task.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/domain/task.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,KAAK,IAAI,EAAW,MAAM,WAAW,CAAC;AAE/C,eAAO,MAAM,YAAY,YACvB,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,YAAY,EACZ,SAAS,EACT,MAAM,CACE,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,KAAK;IACpB,aAAa,EAAE,CAAC,CAAC;IACjB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAKD,eAAO,MAAM,UAAU,QAAO,KAA0C,CAAC;AAOzE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAsCnD;AAmBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAkF1F"}
@@ -0,0 +1,154 @@
1
+ import { PHANTOM_SENDER, parseHandoffFileName, } from "./handoff.js";
2
+ import { validateRole, validateTaskId, validateTaskName } from "./identifiers.js";
3
+ import { roleIds } from "./pack.js";
4
+ export const taskStatuses = [
5
+ "queued",
6
+ "working",
7
+ "waiting_approval",
8
+ "rejected",
9
+ "merging",
10
+ "clarifying",
11
+ "blocked",
12
+ "done",
13
+ ];
14
+ /** Statuses only the coordinator knows: handoff files cannot express them. */
15
+ const boardOnlyStatuses = ["clarifying", "blocked", "rejected"];
16
+ export const emptyBoard = () => ({ schemaVersion: 1, tasks: [] });
17
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
18
+ const isIso = (value) => typeof value === "string" && !Number.isNaN(Date.parse(value));
19
+ export function validateBoard(value) {
20
+ if (!isRecord(value))
21
+ throw new Error("Invalid board: expected an object");
22
+ if (value.schemaVersion !== 1)
23
+ throw new Error("Unsupported board schemaVersion (expected 1)");
24
+ if (!Array.isArray(value.tasks))
25
+ throw new Error("Invalid board: tasks must be an array");
26
+ const seen = new Set();
27
+ const tasks = value.tasks.map((raw) => {
28
+ if (!isRecord(raw))
29
+ throw new Error("Invalid task card: expected an object");
30
+ validateTaskName(raw.name);
31
+ validateTaskId(raw.taskId);
32
+ if (raw.lane !== "done") {
33
+ try {
34
+ validateRole(raw.lane);
35
+ }
36
+ catch {
37
+ throw new Error("Invalid lane on task card");
38
+ }
39
+ }
40
+ if (!taskStatuses.includes(raw.status)) {
41
+ throw new Error(`Invalid status on task card: ${String(raw.status)}`);
42
+ }
43
+ if (!isIso(raw.createdAt) || !isIso(raw.updatedAt)) {
44
+ throw new Error("Invalid timestamps on task card");
45
+ }
46
+ if (!Number.isInteger(raw.auditCount) || raw.auditCount < 0) {
47
+ throw new Error("Invalid auditCount on task card");
48
+ }
49
+ if (seen.has(raw.taskId))
50
+ throw new Error("Duplicate task id on board");
51
+ seen.add(raw.taskId);
52
+ return {
53
+ name: raw.name,
54
+ taskId: raw.taskId,
55
+ lane: raw.lane,
56
+ status: raw.status,
57
+ createdAt: raw.createdAt,
58
+ updatedAt: raw.updatedAt,
59
+ auditCount: raw.auditCount,
60
+ };
61
+ });
62
+ return { schemaVersion: 1, tasks };
63
+ }
64
+ const stampOf = (record) => record.handoff.completedAt ??
65
+ record.handoff.dequeuedAt ??
66
+ record.handoff.enqueuedAt ??
67
+ record.handoff.createdAt;
68
+ const seqOf = (record) => parseHandoffFileName(record.fileName)?.seq ?? 0;
69
+ const byAge = (a, b) => a.handoff.createdAt.localeCompare(b.handoff.createdAt) || seqOf(a) - seqOf(b);
70
+ const inFlight = (kind) => kind === "audit_pending" ||
71
+ kind === "pending_approval" ||
72
+ kind === "join_pending" ||
73
+ kind === "failed";
74
+ /**
75
+ * Rebuild the board from handoff files (the source of truth, AD-11). Board-only statuses that files
76
+ * cannot express (clarifying, blocked, rejected) survive from `previous` while the lane is unchanged.
77
+ */
78
+ export function rebuildBoard(pack, records, previous) {
79
+ const roles = roleIds(pack);
80
+ const last = roles.at(-1);
81
+ const byTask = new Map();
82
+ for (const record of records) {
83
+ const list = byTask.get(record.handoff.taskId) ?? [];
84
+ list.push(record);
85
+ byTask.set(record.handoff.taskId, list);
86
+ }
87
+ const cards = [];
88
+ for (const [taskId, list] of byTask) {
89
+ list.sort(byAge);
90
+ const first = list[0];
91
+ const released = list.filter((r) => r.handoff.type === "git_handoff" &&
92
+ r.handoff.from !== PHANTOM_SENDER &&
93
+ !inFlight(r.location.kind));
94
+ const auditCount = new Set(released.map((r) => `${r.handoff.from}:${r.handoff.commit}`)).size;
95
+ const finished = released.some((r) => r.handoff.from === last);
96
+ const forwarding = list.filter((r) => (!r.handoff.nonForwarding || inFlight(r.location.kind)) &&
97
+ r.handoff.to.every((role) => roles.includes(role)));
98
+ const latest = forwarding.at(-1);
99
+ let lane;
100
+ let status;
101
+ if (finished) {
102
+ lane = "done";
103
+ status = "done";
104
+ }
105
+ else if (!latest) {
106
+ // Only parked terminal/merge-only copies exist: the owner is the sender still auditing.
107
+ const parked = list.filter((r) => inFlight(r.location.kind)).at(-1) ?? first;
108
+ lane = roles.includes(parked.handoff.from) ? parked.handoff.from : roles[0];
109
+ status = "working";
110
+ }
111
+ else {
112
+ const location = latest.location;
113
+ const sender = latest.handoff.from;
114
+ const target = latest.handoff.to[0];
115
+ if (location.kind === "audit_pending" || location.kind === "join_pending") {
116
+ lane = roles.includes(sender) ? sender : target;
117
+ status = "working";
118
+ }
119
+ else if (location.kind === "pending_approval") {
120
+ lane = roles.includes(sender) ? sender : target;
121
+ status = "waiting_approval";
122
+ }
123
+ else if (location.kind === "failed") {
124
+ lane = roles.includes(sender) ? sender : target;
125
+ status = "blocked";
126
+ }
127
+ else {
128
+ lane = target;
129
+ const copy = list.find((r) => r.handoff.id === latest.handoff.id &&
130
+ r.location.kind === "inbox" &&
131
+ r.location.role === target);
132
+ const box = copy?.location.kind === "inbox" ? copy.location.box : "new";
133
+ status = box === "new" ? "queued" : "working";
134
+ }
135
+ }
136
+ const before = previous?.tasks.find((card) => card.taskId === taskId);
137
+ if (before && before.lane === lane && boardOnlyStatuses.includes(before.status) && !finished) {
138
+ status = before.status;
139
+ }
140
+ const newest = list.reduce((acc, r) => (stampOf(r) > acc ? stampOf(r) : acc), first.handoff.createdAt);
141
+ cards.push({
142
+ name: first.handoff.task,
143
+ taskId,
144
+ lane,
145
+ status,
146
+ createdAt: first.handoff.createdAt,
147
+ updatedAt: before && before.updatedAt > newest ? before.updatedAt : newest,
148
+ auditCount: Math.max(auditCount, before?.lane === lane ? before.auditCount : 0),
149
+ });
150
+ }
151
+ cards.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.taskId.localeCompare(b.taskId));
152
+ return { schemaVersion: 1, tasks: cards };
153
+ }
154
+ //# sourceMappingURL=task.js.map