@nickmeriano/task 0.11.0 → 0.12.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 (73) hide show
  1. package/README.md +175 -1
  2. package/dist/board/github.d.ts +68 -0
  3. package/dist/board/github.d.ts.map +1 -0
  4. package/dist/board/github.js +112 -0
  5. package/dist/board/github.js.map +1 -0
  6. package/dist/board/handler.d.ts +50 -0
  7. package/dist/board/handler.d.ts.map +1 -0
  8. package/dist/board/handler.js +183 -0
  9. package/dist/board/handler.js.map +1 -0
  10. package/dist/board/handler.test.d.ts +11 -0
  11. package/dist/board/handler.test.d.ts.map +1 -0
  12. package/dist/board/handler.test.js +229 -0
  13. package/dist/board/handler.test.js.map +1 -0
  14. package/dist/board/pages-function.d.ts +23 -0
  15. package/dist/board/pages-function.d.ts.map +1 -0
  16. package/dist/board/pages-function.js +34 -0
  17. package/dist/board/pages-function.js.map +1 -0
  18. package/dist/board/source.d.ts +118 -0
  19. package/dist/board/source.d.ts.map +1 -0
  20. package/dist/board/source.js +333 -0
  21. package/dist/board/source.js.map +1 -0
  22. package/dist/board/source.test.d.ts +12 -0
  23. package/dist/board/source.test.d.ts.map +1 -0
  24. package/dist/board/source.test.js +165 -0
  25. package/dist/board/source.test.js.map +1 -0
  26. package/dist/board/tar.d.ts +28 -0
  27. package/dist/board/tar.d.ts.map +1 -0
  28. package/dist/board/tar.js +188 -0
  29. package/dist/board/tar.js.map +1 -0
  30. package/dist/board/tar.test.d.ts +9 -0
  31. package/dist/board/tar.test.d.ts.map +1 -0
  32. package/dist/board/tar.test.js +110 -0
  33. package/dist/board/tar.test.js.map +1 -0
  34. package/dist/cli.js +37 -2
  35. package/dist/cli.js.map +1 -1
  36. package/dist/export.d.ts +31 -0
  37. package/dist/export.d.ts.map +1 -1
  38. package/dist/export.js +61 -1
  39. package/dist/export.js.map +1 -1
  40. package/dist/export.test.js +80 -1
  41. package/dist/export.test.js.map +1 -1
  42. package/dist/functions/board.js +709 -0
  43. package/dist/git-serve.d.ts +14 -1
  44. package/dist/git-serve.d.ts.map +1 -1
  45. package/dist/git-serve.js +30 -2
  46. package/dist/git-serve.js.map +1 -1
  47. package/dist/git-serve.test.d.ts +1 -0
  48. package/dist/git-serve.test.d.ts.map +1 -1
  49. package/dist/git-serve.test.js +36 -1
  50. package/dist/git-serve.test.js.map +1 -1
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +6 -0
  54. package/dist/index.js.map +1 -1
  55. package/package.json +3 -3
  56. package/skill/SKILL.md +10 -3
  57. package/src/board/github.ts +151 -0
  58. package/src/board/handler.test.ts +276 -0
  59. package/src/board/handler.ts +228 -0
  60. package/src/board/pages-function.ts +42 -0
  61. package/src/board/source.test.ts +203 -0
  62. package/src/board/source.ts +422 -0
  63. package/src/board/tar.test.ts +128 -0
  64. package/src/board/tar.ts +199 -0
  65. package/src/cli.ts +36 -2
  66. package/src/export.test.ts +108 -1
  67. package/src/export.ts +79 -1
  68. package/src/git-serve.test.ts +41 -1
  69. package/src/git-serve.ts +30 -2
  70. package/src/index.ts +10 -0
  71. package/ui/dist/assets/index-B8M_DaOt.js +229 -0
  72. package/ui/dist/index.html +1 -1
  73. package/ui/dist/assets/index-mJmm4sWq.js +0 -229
@@ -0,0 +1,333 @@
1
+ /**
2
+ * A repository, read as boards.
3
+ *
4
+ * This is `findBoards` + `FileStore`, re-expressed against a git tree instead
5
+ * of a filesystem, and producing byte-identical API payloads. That
6
+ * equivalence is the whole point: the hosted board and an exported board's
7
+ * live proxy run the *same* SPA as `task serve`, so anything that differs
8
+ * here shows up as a broken screen rather than a type error. The shapes below
9
+ * are pinned to `server.ts` — change one and change both — and the ticket
10
+ * format itself is not restated: parsing comes from `ticket-doc.ts`, the one
11
+ * definition every server shares, in its lenient mode (a hand-edited file
12
+ * that no longer parses becomes a degraded ticket or a skipped comment, never
13
+ * a broken board).
14
+ *
15
+ * Read-only by construction. There is no code here that could write, which is
16
+ * how "the board can't edit your repo" stays true no matter what the routing
17
+ * layer does.
18
+ *
19
+ * Cache-free on purpose. A snapshot is immutable per commit, so *where* to
20
+ * keep one is a deployment question — the hosted worker layers isolate memory
21
+ * and KV over `snapshotAt`, the live proxy keeps a bounded `SnapshotMemory` —
22
+ * and this module answers only "what is at this commit".
23
+ */
24
+ import { parseAskLenient, parseCommentLenient, parseGoalLenient, parseTicketLenient, } from "../ticket-doc.js";
25
+ import { isTicketKey } from "../id.js";
26
+ import { fetchRepoArchive, headCommit } from "./github.js";
27
+ export { STATUSES } from "../types.js";
28
+ const TASK_DIR = ".task";
29
+ const CONFIG_FILE = "config.json";
30
+ const TICKETS_DIR = "tickets";
31
+ const TICKET_FILE = "ticket.md";
32
+ const COMMENTS_DIR = "comments";
33
+ const ASKS_DIR = "asks";
34
+ const GOALS_DIR = "goals";
35
+ /** Same ceiling as the CLI's `findBoards`, for the same reason. */
36
+ const MAX_DEPTH = 6;
37
+ export class BoardSourceError extends Error {
38
+ status;
39
+ /**
40
+ * Machine-readable cause, sent beside the message so the SPA can choose a
41
+ * screen rather than infer one from a status code. Optional: most of these
42
+ * are one of a kind and the message is the whole story.
43
+ */
44
+ reason;
45
+ constructor(status, message, reason = null) {
46
+ super(message);
47
+ this.name = "BoardSourceError";
48
+ this.status = status;
49
+ this.reason = reason;
50
+ }
51
+ }
52
+ function str(value, fallback = "") {
53
+ return typeof value === "string" ? value : fallback;
54
+ }
55
+ function num(value, fallback = 0) {
56
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
57
+ }
58
+ /** `.task/config.json`, defensively — it's a file anyone can hand-edit. */
59
+ function parseConfig(bytes, fallbackName) {
60
+ try {
61
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
62
+ if (parsed && typeof parsed === "object") {
63
+ const config = parsed;
64
+ return {
65
+ name: str(config.name, fallbackName),
66
+ prefix: str(config.prefix, "TASK").toUpperCase(),
67
+ version: num(config.version, 1),
68
+ };
69
+ }
70
+ }
71
+ catch {
72
+ // fall through
73
+ }
74
+ return { name: fallbackName, prefix: "TASK", version: 1 };
75
+ }
76
+ /**
77
+ * Board directories, from a flat list of repo paths.
78
+ *
79
+ * The CLI finds a board by the presence of `.task/config.json`; so does this.
80
+ * Depth is capped the same way, and the ordering — "." first, then
81
+ * lexicographic — is copied so the default board is the same one you'd get
82
+ * locally.
83
+ */
84
+ export function findBoardDirs(paths) {
85
+ const marker = `${TASK_DIR}/${CONFIG_FILE}`;
86
+ const dirs = [];
87
+ for (const path of paths) {
88
+ // The directory has to be `.task` itself, not merely end in it — a bare
89
+ // suffix match would read `x.task/config.json` as a board at `x`.
90
+ if (path !== marker && !path.endsWith(`/${marker}`))
91
+ continue;
92
+ const dir = path.slice(0, -(TASK_DIR.length + CONFIG_FILE.length + 2));
93
+ const id = dir === "" ? "." : dir.replace(/\/$/, "");
94
+ if (id !== "." && id.split("/").length > MAX_DEPTH)
95
+ continue;
96
+ dirs.push(id);
97
+ }
98
+ dirs.sort((a, b) => (a === "." ? -1 : b === "." ? 1 : a < b ? -1 : 1));
99
+ return dirs;
100
+ }
101
+ /** `projects/phone/web` → "phone web"-ish fallback name, matching `task init`. */
102
+ function nameFromDir(id) {
103
+ if (id === ".")
104
+ return "tasks";
105
+ return id.split("/").pop() ?? "tasks";
106
+ }
107
+ /**
108
+ * One board from its ticket files. `files` is keyed by path relative to the
109
+ * board's `.task/` — `tickets/12/ticket.md`, `tickets/12/comments/<stem>.md` —
110
+ * which is how `snapshotAt` fetches them and how the test feeds them from
111
+ * disk.
112
+ */
113
+ export function readTicketsBoard(id, configBytes, files) {
114
+ const config = parseConfig(configBytes, nameFromDir(id));
115
+ const decoder = new TextDecoder();
116
+ const docs = new Map();
117
+ const comments = new Map();
118
+ const asks = new Map();
119
+ const goals = [];
120
+ for (const [path, bytes] of files) {
121
+ // Goals: one file per goal, archived ones one level down. Old branches
122
+ // simply have no goals/ — the list stays empty and every reader tolerates
123
+ // a ticket's goal ref not resolving.
124
+ const goalMatch = new RegExp(`^${GOALS_DIR}/(archive/)?([^/]+)\\.md$`).exec(path);
125
+ if (goalMatch) {
126
+ if (goalMatch[2].startsWith("."))
127
+ continue;
128
+ const doc = parseGoalLenient(decoder.decode(bytes));
129
+ if (doc) {
130
+ goals.push({
131
+ slug: goalMatch[2],
132
+ title: doc.title,
133
+ description: doc.description,
134
+ createdAt: doc.createdAt,
135
+ updatedAt: doc.updatedAt,
136
+ ...(goalMatch[1] ? { archived: true } : {}),
137
+ });
138
+ }
139
+ continue;
140
+ }
141
+ const ticketMatch = new RegExp(`^${TICKETS_DIR}/([^/]+)/${TICKET_FILE}$`).exec(path);
142
+ if (ticketMatch && isTicketKey(ticketMatch[1])) {
143
+ const doc = parseTicketLenient(decoder.decode(bytes));
144
+ if (doc)
145
+ docs.set(ticketMatch[1], doc);
146
+ continue;
147
+ }
148
+ const commentMatch = new RegExp(`^${TICKETS_DIR}/([^/]+)/${COMMENTS_DIR}/([^/]+)\\.md$`).exec(path);
149
+ if (commentMatch && isTicketKey(commentMatch[1]) && !commentMatch[2].startsWith(".")) {
150
+ const doc = parseCommentLenient(decoder.decode(bytes));
151
+ if (!doc)
152
+ continue;
153
+ const taskKey = commentMatch[1];
154
+ const list = comments.get(taskKey) ?? [];
155
+ list.push({
156
+ id: commentMatch[2],
157
+ taskId: `${config.prefix}-${taskKey}`,
158
+ author: doc.author,
159
+ body: doc.body,
160
+ createdAt: doc.createdAt,
161
+ });
162
+ comments.set(taskKey, list);
163
+ continue;
164
+ }
165
+ const askMatch = new RegExp(`^${TICKETS_DIR}/([^/]+)/${ASKS_DIR}/([^/]+)\\.md$`).exec(path);
166
+ if (askMatch && isTicketKey(askMatch[1]) && !askMatch[2].startsWith(".")) {
167
+ const doc = parseAskLenient(decoder.decode(bytes));
168
+ if (!doc)
169
+ continue;
170
+ const taskKey = askMatch[1];
171
+ const list = asks.get(taskKey) ?? [];
172
+ // Ordinal assigned after the sort below — 0 is a placeholder.
173
+ list.push({
174
+ id: askMatch[2],
175
+ taskId: `${config.prefix}-${taskKey}`,
176
+ ordinal: 0,
177
+ text: doc.body,
178
+ author: doc.author,
179
+ createdAt: doc.createdAt,
180
+ resolvedAt: doc.resolvedAt,
181
+ resolvedBy: doc.resolvedBy,
182
+ });
183
+ asks.set(taskKey, list);
184
+ }
185
+ }
186
+ // Comments whose ticket file is missing or unreadable would count against a
187
+ // ticket that isn't drawn — drop them, and order the rest the CLI's way.
188
+ for (const taskKey of comments.keys()) {
189
+ if (!docs.has(taskKey))
190
+ comments.delete(taskKey);
191
+ }
192
+ for (const list of comments.values()) {
193
+ list.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
194
+ }
195
+ // Asks: same orphan rule, and ordinals are creation-order positions — the
196
+ // exact numbering the CLI's `asks()` derives.
197
+ for (const taskKey of asks.keys()) {
198
+ if (!docs.has(taskKey))
199
+ asks.delete(taskKey);
200
+ }
201
+ for (const list of asks.values()) {
202
+ list.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
203
+ list.forEach((ask, index) => (ask.ordinal = index + 1));
204
+ }
205
+ // `blocks` is derived from the other tickets' stored `blocked_by` — the
206
+ // relation lives on one side only, so the two views can't disagree.
207
+ const blocks = new Map();
208
+ for (const [key, doc] of docs) {
209
+ for (const blocker of doc.blockedBy) {
210
+ blocks.set(blocker, [...(blocks.get(blocker) ?? []), key]);
211
+ }
212
+ }
213
+ for (const list of blocks.values())
214
+ list.sort((a, b) => a.localeCompare(b));
215
+ const tasks = [...docs.entries()]
216
+ .map(([key, doc]) => ({
217
+ id: `${config.prefix}-${key}`,
218
+ key,
219
+ title: doc.title,
220
+ description: doc.description,
221
+ status: doc.status,
222
+ tags: doc.tags,
223
+ goal: doc.goal,
224
+ needsHuman: (asks.get(key) ?? []).some((a) => !a.resolvedAt),
225
+ asks: asks.get(key) ?? [],
226
+ blocks: blocks.get(key) ?? [],
227
+ blockedBy: doc.blockedBy,
228
+ prs: doc.prs,
229
+ position: doc.position,
230
+ createdAt: doc.createdAt,
231
+ updatedAt: doc.updatedAt,
232
+ commentCount: comments.get(key)?.length ?? 0,
233
+ }))
234
+ .sort((a, b) => a.position - b.position ||
235
+ a.createdAt.localeCompare(b.createdAt) ||
236
+ a.key.localeCompare(b.key));
237
+ goals.sort((a, b) => Number(a.archived === true) - Number(b.archived === true) || a.slug.localeCompare(b.slug));
238
+ return { info: { id, name: config.name, prefix: config.prefix }, tasks, comments, goals };
239
+ }
240
+ /**
241
+ * Every board in a repository at a known commit, ready to serve.
242
+ *
243
+ * One request for the whole repository: every path, plus bytes for anything
244
+ * under a .task/ directory. (Earlier shapes — a tree listing plus per-file or
245
+ * batched blob reads — scaled subrequests with board size and tripped
246
+ * Workers' 50-subrequest ceiling; a tarball is one request regardless.)
247
+ *
248
+ * `ref` is only for the error message: told a branch, "this repository has
249
+ * no .task directory" is both wrong and confusing — the repository plainly
250
+ * has one, you were just looking at it — and `task init` is not the fix for
251
+ * previewing a branch that forked before the boards existed.
252
+ */
253
+ export async function snapshotAt(token, owner, repo, commit, ref = "HEAD") {
254
+ const { paths, files } = await fetchRepoArchive(token, owner, repo, commit.sha, (path) => path.startsWith(`${TASK_DIR}/`) || path.includes(`/${TASK_DIR}/`));
255
+ const dirs = findBoardDirs(paths);
256
+ if (dirs.length === 0) {
257
+ throw new BoardSourceError(404, ref === "HEAD"
258
+ ? "no .task directory in this repository — run `task init` and commit it"
259
+ : `no .task directory on ${ref}`, "no-boards");
260
+ }
261
+ const data = new Map();
262
+ for (const id of dirs) {
263
+ const base = id === "." ? TASK_DIR : `${id}/${TASK_DIR}`;
264
+ const configBytes = files.get(`${base}/${CONFIG_FILE}`);
265
+ if (!configBytes)
266
+ continue;
267
+ const prefix = `${base}/`;
268
+ const ticketPaths = paths.filter((path) => (path.startsWith(`${prefix}${TICKETS_DIR}/`) || path.startsWith(`${prefix}${GOALS_DIR}/`)) &&
269
+ path.endsWith(".md"));
270
+ // A config with no committed tickets can be two very different things: a
271
+ // real (empty) text board, or a pre-0.6 board whose state is a SQLite
272
+ // database this app no longer reads. The config's version says which —
273
+ // skip the legacy one rather than render an empty board that lies.
274
+ if (ticketPaths.length === 0 && parseConfig(configBytes, "").version < 2)
275
+ continue;
276
+ const tickets = new Map();
277
+ for (const path of ticketPaths) {
278
+ const bytes = files.get(path);
279
+ if (bytes)
280
+ tickets.set(path.slice(prefix.length), bytes);
281
+ }
282
+ const board = readTicketsBoard(id, configBytes, tickets);
283
+ data.set(board.info.id, board);
284
+ }
285
+ if (data.size === 0) {
286
+ throw new BoardSourceError(404, "found .task/config.json but no committed tickets — are .task/tickets/ gitignored, or is this a pre-0.6 SQLite board that still needs `task migrate` (@nickmeriano/task@0.6)?");
287
+ }
288
+ return {
289
+ commit,
290
+ boards: [...data.values()].map((board) => board.info),
291
+ data,
292
+ truncated: false,
293
+ };
294
+ }
295
+ /**
296
+ * Every board at `ref`, cache-free: "what is HEAD" plus one tarball. Callers
297
+ * that serve more than one view put a cache between the two — see
298
+ * `SnapshotMemory` — because the second request is the expensive one and a
299
+ * commit never changes.
300
+ */
301
+ export async function loadSnapshot(token, owner, repo, ref) {
302
+ const commit = await headCommit(token, owner, repo, ref);
303
+ return snapshotAt(token, owner, repo, commit, ref);
304
+ }
305
+ /**
306
+ * Parsed snapshots, keyed by `owner/repo@sha`, bounded.
307
+ *
308
+ * A commit is immutable, so this never serves anything stale: the only lookup
309
+ * that goes to GitHub on a warm instance is "what is HEAD right now", and a
310
+ * new commit is a new key. Bounded because an isolate's memory is not ours to
311
+ * fill; `Map` iterates in insertion order, so the first key is the oldest.
312
+ */
313
+ export class SnapshotMemory {
314
+ snapshots = new Map();
315
+ max;
316
+ constructor(max = 24) {
317
+ this.max = max;
318
+ }
319
+ get(key) {
320
+ return this.snapshots.get(key);
321
+ }
322
+ remember(key, snapshot) {
323
+ this.snapshots.set(key, snapshot);
324
+ while (this.snapshots.size > this.max) {
325
+ const oldest = this.snapshots.keys().next().value;
326
+ if (oldest === undefined)
327
+ break;
328
+ this.snapshots.delete(oldest);
329
+ }
330
+ return snapshot;
331
+ }
332
+ }
333
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/board/source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The hosted board and an exported board's live proxy serve the same SPA as
3
+ * `task serve`, so their payloads have to be the same payloads — not
4
+ * "compatible", the same. This diffs them against the CLI's own `FileStore`.
5
+ *
6
+ * That is the test that actually protects the design: if someone adds a field
7
+ * to the CLI's `toTask` and not here, the hosted board silently loses it, and
8
+ * the only symptom is a piece of the UI that renders blank for one deployment
9
+ * and not the other.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=source.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.test.d.ts","sourceRoot":"","sources":["../../src/board/source.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * The hosted board and an exported board's live proxy serve the same SPA as
3
+ * `task serve`, so their payloads have to be the same payloads — not
4
+ * "compatible", the same. This diffs them against the CLI's own `FileStore`.
5
+ *
6
+ * That is the test that actually protects the design: if someone adds a field
7
+ * to the CLI's `toTask` and not here, the hosted board silently loses it, and
8
+ * the only symptom is a piece of the UI that renders blank for one deployment
9
+ * and not the other.
10
+ */
11
+ import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { fileURLToPath } from "node:url";
14
+ import { join } from "node:path";
15
+ import assert from "node:assert/strict";
16
+ import { test } from "node:test";
17
+ import { FileStore, initProject } from "../file-store.js";
18
+ import { findBoardDirs, readTicketsBoard } from "./source.js";
19
+ const REPO_ROOT = fileURLToPath(new URL("../../../../..", import.meta.url));
20
+ /** Every board this repo actually commits, as `findBoards` would order them. */
21
+ const BOARDS = [".", "projects/inspo", "projects/phone/web", "projects/portal", "projects/task/cli"];
22
+ function tempRoot() {
23
+ const dir = mkdtempSync(join(tmpdir(), "board-source-test-"));
24
+ process.on("exit", () => rmSync(dir, { recursive: true, force: true }));
25
+ return dir;
26
+ }
27
+ /** A board's `.task/` read off disk into the path→bytes map the worker sees. */
28
+ function ticketFiles(taskDir) {
29
+ const files = new Map();
30
+ for (const dir of ["goals", "goals/archive"]) {
31
+ const goalsDir = join(taskDir, dir);
32
+ if (!existsSync(goalsDir))
33
+ continue;
34
+ for (const entry of readdirSync(goalsDir, { withFileTypes: true })) {
35
+ if (!entry.isFile() || !entry.name.endsWith(".md"))
36
+ continue;
37
+ files.set(`${dir}/${entry.name}`, new Uint8Array(readFileSync(join(goalsDir, entry.name))));
38
+ }
39
+ }
40
+ const ticketsDir = join(taskDir, "tickets");
41
+ if (!existsSync(ticketsDir))
42
+ return files;
43
+ for (const number of readdirSync(ticketsDir)) {
44
+ const ticket = join(ticketsDir, number, "ticket.md");
45
+ if (existsSync(ticket)) {
46
+ files.set(`tickets/${number}/ticket.md`, new Uint8Array(readFileSync(ticket)));
47
+ }
48
+ for (const kind of ["comments", "asks"]) {
49
+ const dir = join(ticketsDir, number, kind);
50
+ if (!existsSync(dir))
51
+ continue;
52
+ for (const entry of readdirSync(dir)) {
53
+ files.set(`tickets/${number}/${kind}/${entry}`, new Uint8Array(readFileSync(join(dir, entry))));
54
+ }
55
+ }
56
+ }
57
+ return files;
58
+ }
59
+ function loadTicketsBoard(id, root) {
60
+ const taskDir = join(root, ".task");
61
+ return readTicketsBoard(id, new Uint8Array(readFileSync(join(taskDir, "config.json"))), ticketFiles(taskDir));
62
+ }
63
+ /** `GET /api/tasks` + per-task comments, straight from the CLI store. */
64
+ function expectedPayloads(store) {
65
+ const counts = store.commentCounts();
66
+ const tasks = store.list().map((task) => ({
67
+ ...task,
68
+ commentCount: counts.get(task.key) ?? 0,
69
+ }));
70
+ return { tasks, comments: new Map(tasks.map((t) => [t.key, store.comments(t.key)])) };
71
+ }
72
+ function assertSamePayloads(id, hosted, store) {
73
+ assert.equal(hosted.info.name, store.config.name, `${id}: project name`);
74
+ assert.equal(hosted.info.prefix, store.config.prefix, `${id}: prefix`);
75
+ const expected = expectedPayloads(store);
76
+ assert.equal(hosted.tasks.length, expected.tasks.length, `${id}: task count`);
77
+ for (let i = 0; i < expected.tasks.length; i++) {
78
+ assert.deepEqual(hosted.tasks[i], expected.tasks[i], `${id}: task ${expected.tasks[i].id}`);
79
+ }
80
+ for (const task of expected.tasks) {
81
+ assert.deepEqual(hosted.comments.get(task.key) ?? [], expected.comments.get(task.key), `${id}: comments on ${task.id}`);
82
+ }
83
+ assert.deepEqual(hosted.goals, [...store.goals(), ...store.goals(true)], `${id}: goals`);
84
+ }
85
+ test("finds the same boards the CLI would, in the same order", () => {
86
+ // The shape a recursive git-tree listing arrives in, plus the noise it
87
+ // really contains: nested boards, unrelated files, and a `.task` path that
88
+ // is a file rather than a board.
89
+ const paths = [
90
+ "README.md",
91
+ ".task/config.json",
92
+ ".task/tickets/1/ticket.md",
93
+ "projects/phone/web/.task/config.json",
94
+ "projects/task/cli/.task/config.json",
95
+ "docs/.taskrc",
96
+ "vendor/thing/.task/config.json.bak",
97
+ // A directory that merely *ends* in `.task` is not a board directory.
98
+ "lib/x.task/config.json",
99
+ ];
100
+ assert.deepEqual(findBoardDirs(paths), [".", "projects/phone/web", "projects/task/cli"]);
101
+ });
102
+ test("ignores boards nested deeper than the CLI would walk", () => {
103
+ const deep = "a/b/c/d/e/f/g/.task/config.json";
104
+ assert.deepEqual(findBoardDirs([deep]), []);
105
+ assert.deepEqual(findBoardDirs(["a/b/c/.task/config.json"]), ["a/b/c"]);
106
+ });
107
+ test("text boards produce the same payloads the CLI's FileStore does", () => {
108
+ const root = tempRoot();
109
+ const store = initProject(root, { name: "fixture board", prefix: "FIX" });
110
+ store.createGoal({ title: "Launch", description: "the shared why" });
111
+ const one = store.create({
112
+ title: "Every field exercised",
113
+ description: "A *body* with\n\n# an inner heading\n\nand `code`.",
114
+ tags: ["api", "infra"],
115
+ goal: "launch",
116
+ prs: ["https://github.com/x/y/pull/3"],
117
+ });
118
+ store.addAsk(one.key, "Approve the launch window", "claude");
119
+ const two = store.create({ title: "Mover", status: "backlog" });
120
+ store.create({ title: "Plain" });
121
+ store.update(two.key, { status: "in_progress" }); // lands on top → negative position
122
+ store.link(two.key, "blocked_by", one.key);
123
+ store.addComment(one.key, "first, with **markdown**", "nick");
124
+ store.addComment(one.key, "second", "claude");
125
+ assertSamePayloads("fixture", loadTicketsBoard("fixture", root), store);
126
+ });
127
+ test("this repo's committed boards read as text, same as the CLI reads them", () => {
128
+ for (const id of BOARDS) {
129
+ const root = id === "." ? REPO_ROOT : join(REPO_ROOT, id);
130
+ assertSamePayloads(id, loadTicketsBoard(id, root), new FileStore(root));
131
+ }
132
+ });
133
+ test("every committed board is non-empty and self-consistent", () => {
134
+ // A smoke check that the fixtures above are actually exercising something —
135
+ // boards that all happened to be empty would make the diff vacuous.
136
+ const loaded = BOARDS.map((id) => loadTicketsBoard(id, id === "." ? REPO_ROOT : join(REPO_ROOT, id)));
137
+ const total = loaded.reduce((sum, board) => sum + board.tasks.length, 0);
138
+ assert.ok(total > 10, `expected the repo's boards to hold real tasks, found ${total}`);
139
+ for (const board of loaded) {
140
+ for (const task of board.tasks) {
141
+ assert.match(task.id, /^[A-Z0-9]+-[a-z0-9]+$/, `${board.info.id}: display id`);
142
+ assert.ok(Array.isArray(task.tags), `${board.info.id}: tags parsed`);
143
+ assert.equal(typeof task.needsHuman, "boolean", `${board.info.id}: needsHuman`);
144
+ }
145
+ }
146
+ });
147
+ test("a broken ticket degrades instead of breaking the board", () => {
148
+ const config = new TextEncoder().encode(JSON.stringify({ name: "rough", prefix: "RGH", version: 2 }));
149
+ const encode = (s) => new TextEncoder().encode(s);
150
+ const board = readTicketsBoard("rough", config, new Map([
151
+ ["tickets/bad2c/ticket.md", encode("---\nstatus: todoo\nposition: not-a-number\n---\n\nno heading")],
152
+ ["tickets/gone3/ticket.md", encode("not even frontmatter")],
153
+ ["tickets/fine4/ticket.md", encode("---\nstatus: todo\nposition: 1\ncreated: c\nupdated: u\n---\n\n# Fine\n")],
154
+ // A comment on a ticket that didn't parse must not surface anywhere.
155
+ ["tickets/gone3/comments/2026-08-15T120000-x.md", encode("---\nauthor: \"x\"\ncreated: c\n---\n\nhi")],
156
+ // A pre-TAS-42 numeric directory is not a ticket to this reader at all.
157
+ ["tickets/42/ticket.md", encode("---\nstatus: todo\nposition: 1\ncreated: c\nupdated: u\n---\n\n# Old format\n")],
158
+ ]));
159
+ assert.deepEqual(board.tasks.map((t) => [t.key, t.status, t.title]), [
160
+ ["bad2c", "backlog", ""],
161
+ ["fine4", "todo", "Fine"],
162
+ ]);
163
+ assert.equal(board.comments.size, 0);
164
+ });
165
+ //# sourceMappingURL=source.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.test.js","sourceRoot":"","sources":["../../src/board/source.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A minimal streaming tar reader — just enough to walk a GitHub repository
3
+ * tarball. One pass, bounded memory: every entry's *path* is recorded, but
4
+ * bytes are kept only for the paths the caller's `keep` predicate selects;
5
+ * everything else is discarded chunk by chunk as it streams through, so a big
6
+ * repository costs no more memory than its boards.
7
+ *
8
+ * Understands what GitHub's tarballs actually contain: ustar headers with the
9
+ * split `prefix` field, a leading `pax_global_header`, and pax extended
10
+ * headers (`x`) or GNU longname entries (`L`) for paths longer than the
11
+ * header's 100 bytes. Checksums are not verified — the bytes come off a TLS
12
+ * connection from GitHub, not a tape from 1988.
13
+ */
14
+ export declare class TarError extends Error {
15
+ }
16
+ export interface TarContents {
17
+ /** Every regular file's path, root directory stripped. */
18
+ paths: string[];
19
+ /** Bytes of the paths `keep` selected. */
20
+ files: Map<string, Uint8Array>;
21
+ }
22
+ /**
23
+ * Walk a (decompressed) tar stream. Paths are reported with the archive's
24
+ * root directory stripped — GitHub tarballs wrap everything in
25
+ * `<owner>-<repo>-<sha>/` — matching what a git tree listing would say.
26
+ */
27
+ export declare function readTar(stream: ReadableStream<Uint8Array>, keep: (path: string) => boolean): Promise<TarContents>;
28
+ //# sourceMappingURL=tar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tar.d.ts","sourceRoot":"","sources":["../../src/board/tar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,qBAAa,QAAS,SAAQ,KAAK;CAAG;AAoGtC,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,0CAA0C;IAC1C,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CAC/B;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAC9B,OAAO,CAAC,WAAW,CAAC,CAqEtB"}