@nickmeriano/task 0.8.0 → 0.9.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 (114) hide show
  1. package/README.md +65 -22
  2. package/dist/asks.test.d.ts +17 -0
  3. package/dist/asks.test.d.ts.map +1 -0
  4. package/dist/asks.test.js +278 -0
  5. package/dist/asks.test.js.map +1 -0
  6. package/dist/check.d.ts +3 -2
  7. package/dist/check.d.ts.map +1 -1
  8. package/dist/check.js +99 -60
  9. package/dist/check.js.map +1 -1
  10. package/dist/check.test.js +99 -60
  11. package/dist/check.test.js.map +1 -1
  12. package/dist/claim-io.d.ts +73 -0
  13. package/dist/claim-io.d.ts.map +1 -0
  14. package/dist/claim-io.js +344 -0
  15. package/dist/claim-io.js.map +1 -0
  16. package/dist/claim.d.ts +21 -22
  17. package/dist/claim.d.ts.map +1 -1
  18. package/dist/claim.js +58 -83
  19. package/dist/claim.js.map +1 -1
  20. package/dist/claim.test.d.ts +2 -2
  21. package/dist/claim.test.js +111 -94
  22. package/dist/claim.test.js.map +1 -1
  23. package/dist/cli.js +241 -64
  24. package/dist/cli.js.map +1 -1
  25. package/dist/file-store.d.ts +70 -24
  26. package/dist/file-store.d.ts.map +1 -1
  27. package/dist/file-store.js +319 -141
  28. package/dist/file-store.js.map +1 -1
  29. package/dist/git-serve.d.ts +183 -0
  30. package/dist/git-serve.d.ts.map +1 -0
  31. package/dist/git-serve.js +503 -0
  32. package/dist/git-serve.js.map +1 -0
  33. package/dist/git-serve.test.d.ts +16 -0
  34. package/dist/git-serve.test.d.ts.map +1 -0
  35. package/dist/git-serve.test.js +183 -0
  36. package/dist/git-serve.test.js.map +1 -0
  37. package/dist/git.d.ts +65 -0
  38. package/dist/git.d.ts.map +1 -0
  39. package/dist/git.js +114 -0
  40. package/dist/git.js.map +1 -0
  41. package/dist/id.d.ts +39 -0
  42. package/dist/id.d.ts.map +1 -0
  43. package/dist/id.js +67 -0
  44. package/dist/id.js.map +1 -0
  45. package/dist/inbox.d.ts +41 -0
  46. package/dist/inbox.d.ts.map +1 -0
  47. package/dist/inbox.js +56 -0
  48. package/dist/inbox.js.map +1 -0
  49. package/dist/overview.d.ts +9 -0
  50. package/dist/overview.d.ts.map +1 -1
  51. package/dist/overview.js +19 -11
  52. package/dist/overview.js.map +1 -1
  53. package/dist/overview.test.js +6 -5
  54. package/dist/overview.test.js.map +1 -1
  55. package/dist/promote.test.d.ts +3 -2
  56. package/dist/promote.test.d.ts.map +1 -1
  57. package/dist/promote.test.js +29 -31
  58. package/dist/promote.test.js.map +1 -1
  59. package/dist/publish.d.ts +2 -17
  60. package/dist/publish.d.ts.map +1 -1
  61. package/dist/publish.js +4 -49
  62. package/dist/publish.js.map +1 -1
  63. package/dist/search.d.ts +3 -1
  64. package/dist/search.d.ts.map +1 -1
  65. package/dist/search.js +15 -2
  66. package/dist/search.js.map +1 -1
  67. package/dist/search.test.js +9 -9
  68. package/dist/search.test.js.map +1 -1
  69. package/dist/server.d.ts.map +1 -1
  70. package/dist/server.js +218 -21
  71. package/dist/server.js.map +1 -1
  72. package/dist/store.d.ts +28 -14
  73. package/dist/store.d.ts.map +1 -1
  74. package/dist/store.js.map +1 -1
  75. package/dist/store.test.js +88 -75
  76. package/dist/store.test.js.map +1 -1
  77. package/dist/ticket-doc.d.ts +26 -10
  78. package/dist/ticket-doc.d.ts.map +1 -1
  79. package/dist/ticket-doc.js +71 -27
  80. package/dist/ticket-doc.js.map +1 -1
  81. package/dist/types.d.ts +51 -17
  82. package/dist/types.d.ts.map +1 -1
  83. package/dist/types.js.map +1 -1
  84. package/package.json +1 -1
  85. package/skill/SKILL.md +70 -23
  86. package/src/asks.test.ts +355 -0
  87. package/src/check.test.ts +129 -72
  88. package/src/check.ts +101 -58
  89. package/src/claim-io.ts +401 -0
  90. package/src/claim.test.ts +141 -99
  91. package/src/claim.ts +69 -96
  92. package/src/cli.ts +251 -63
  93. package/src/file-store.ts +357 -145
  94. package/src/git-serve.test.ts +240 -0
  95. package/src/git-serve.ts +595 -0
  96. package/src/git.ts +141 -0
  97. package/src/id.ts +68 -0
  98. package/src/inbox.ts +77 -0
  99. package/src/overview.test.ts +6 -5
  100. package/src/overview.ts +25 -10
  101. package/src/promote.test.ts +40 -28
  102. package/src/publish.ts +6 -53
  103. package/src/search.test.ts +11 -11
  104. package/src/search.ts +17 -3
  105. package/src/server.ts +221 -19
  106. package/src/store.test.ts +95 -77
  107. package/src/store.ts +28 -13
  108. package/src/ticket-doc.ts +93 -30
  109. package/src/types.ts +52 -17
  110. package/ui/dist/assets/index-BjsorZOU.js +229 -0
  111. package/ui/dist/assets/index-CoKCUYic.css +1 -0
  112. package/ui/dist/index.html +2 -2
  113. package/ui/dist/assets/index-BJmOsOdR.js +0 -229
  114. package/ui/dist/assets/index-BoqQlqSU.css +0 -1
package/src/store.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs"
2
2
  import { dirname, join, relative, sep } from "node:path"
3
3
  import type {
4
+ Ask,
4
5
  Comment,
5
6
  Goal,
6
7
  GoalInput,
@@ -29,18 +30,24 @@ export interface Store {
29
30
  readonly taskDir: string
30
31
  readonly config: ProjectConfig
31
32
  close(): void
32
- displayId(number: number): string
33
- parseId(ref: string): number
33
+ displayId(key: string): string
34
+ /**
35
+ * Resolve a ref — "TAS-x7k4m", "x7k4m", or a unique key prefix like "x7" —
36
+ * to a ticket key, git-style: any unambiguous prefix of an existing key
37
+ * works, an ambiguous one throws naming the candidates, and an unknown one
38
+ * comes back verbatim so the caller's lookup produces the "no such task".
39
+ */
40
+ parseId(ref: string): string
34
41
  list(filter?: TaskFilter): Task[]
35
- get(number: number): Task | null
42
+ get(key: string): Task | null
36
43
  create(input: TaskInput): Task
37
- update(number: number, patch: TaskPatch): Task
38
- link(number: number, relation: "blocks" | "blocked_by", target: number): Task
39
- unlink(number: number, relation: "blocks" | "blocked_by", target: number): Task
40
- delete(number: number): void
44
+ update(key: string, patch: TaskPatch): Task
45
+ link(key: string, relation: "blocks" | "blocked_by", target: string): Task
46
+ unlink(key: string, relation: "blocks" | "blocked_by", target: string): Task
47
+ delete(key: string): void
41
48
  /** Move a done/canceled ticket to `.task/archive/`, out of the board's hot path. */
42
- archive(number: number): Task
43
- unarchive(number: number): Task
49
+ archive(key: string): Task
50
+ unarchive(key: string): Task
44
51
  /** Live goals, in slug order — pass `true` to list `.task/goals/archive/` instead. */
45
52
  goals(archived?: boolean): Goal[]
46
53
  /** Live or archived — a ticket's `goal:` ref should resolve either way. */
@@ -52,10 +59,18 @@ export interface Store {
52
59
  unarchiveGoal(slug: string): Goal
53
60
  /** Refused while any live task references the goal, whatever its status. */
54
61
  deleteGoal(slug: string): void
55
- comments(number: number): Comment[]
56
- addComment(number: number, body: string, author?: string): Comment
57
- deleteComment(number: number, commentId: string): Comment
58
- commentCounts(): Map<number, number>
62
+ comments(key: string): Comment[]
63
+ addComment(key: string, body: string, author?: string): Comment
64
+ deleteComment(key: string, commentId: string): Comment
65
+ commentCounts(): Map<string, number>
66
+ /** Every ask on a ticket, open and resolved, in creation (= ordinal) order. */
67
+ asks(key: string): Ask[]
68
+ /** Refused on done/canceled tickets — a finished ticket can't start owing again. */
69
+ addAsk(key: string, text: string, author?: string): Ask
70
+ /** `ref` is an ordinal ("2") or the ask's id. */
71
+ resolveAsk(key: string, ref: string, resolvedBy?: string): Ask
72
+ reopenAsk(key: string, ref: string): Ask
73
+ deleteAsk(key: string, ref: string): Ask
59
74
  }
60
75
 
61
76
  /**
package/src/ticket-doc.ts CHANGED
@@ -25,9 +25,8 @@ export interface TicketDoc {
25
25
  tags: string[]
26
26
  /** Slug of the goal this ticket belongs to — at most one, by design. */
27
27
  goal: string | null
28
- needsHuman: boolean
29
- /** Task numbers blocking this one — the single stored side of the relation. */
30
- blockedBy: number[]
28
+ /** Keys of tasks blocking this one — the single stored side of the relation. */
29
+ blockedBy: string[]
31
30
  prs: string[]
32
31
  position: number
33
32
  createdAt: string
@@ -41,18 +40,11 @@ export interface TicketDoc {
41
40
  extras?: [string, string][]
42
41
  }
43
42
 
44
- /**
45
- * The frontmatter keys a ticket file may carry; anything else is an extra.
46
- * `milestone` is the pre-0.8 spelling of `goal`: still *read* (as the goal, so
47
- * old boards keep rendering) but never written — `task check` flags it and
48
- * `--fix` rewrites the file, which is the whole migration.
49
- */
43
+ /** The frontmatter keys a ticket file may carry; anything else is an extra. */
50
44
  export const TICKET_FIELDS = [
51
45
  "status",
52
46
  "tags",
53
47
  "goal",
54
- "milestone",
55
- "needs_human",
56
48
  "blocked_by",
57
49
  "prs",
58
50
  "position",
@@ -69,6 +61,22 @@ export interface CommentDoc {
69
61
  body: string
70
62
  }
71
63
 
64
+ /** The frontmatter keys an ask file may carry. */
65
+ export const ASK_FIELDS = ["author", "created", "resolved", "resolved_by"] as const
66
+
67
+ /**
68
+ * One ask on disk — `tickets/<n>/asks/<stem>.md`, the comment layout plus a
69
+ * resolution: `resolved`/`resolved_by` appear when the ask is closed and are
70
+ * simply absent while it's open, so an open ask's bytes never churn.
71
+ */
72
+ export interface AskDoc {
73
+ author: string
74
+ createdAt: string
75
+ resolvedAt: string | null
76
+ resolvedBy: string | null
77
+ body: string
78
+ }
79
+
72
80
  /** A parse that names the file it failed in — these files invite hand-editing. */
73
81
  export class TicketParseError extends Error {}
74
82
 
@@ -144,13 +152,17 @@ function stringList(raw: RawDoc, key: string, where: string): string[] {
144
152
  throw new TicketParseError(`${where}: ${key} should be a list of strings, e.g. ["a", "b"]`)
145
153
  }
146
154
 
147
- function numberList(raw: RawDoc, key: string, where: string): number[] {
155
+ function keyList(raw: RawDoc, key: string, where: string): string[] {
148
156
  const value = field(raw, key)
149
157
  if (value === undefined) return []
150
- if (Array.isArray(value) && value.every((v) => typeof v === "number" && Number.isInteger(v))) {
151
- return value as number[]
158
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
159
+ return value as string[]
152
160
  }
153
- throw new TicketParseError(`${where}: ${key} should be a list of task numbers, e.g. [3, 7]`)
161
+ // A list of bare numbers is a pre-migration file (ids were sequential
162
+ // integers before TAS-42) — name the one edit that fixes it.
163
+ throw new TicketParseError(
164
+ `${where}: ${key} should be a list of ticket keys, e.g. ["x7k4m", "q3v8d"] — numeric entries are the pre-TAS-42 id format, which is no longer read`,
165
+ )
154
166
  }
155
167
 
156
168
  // ── Tickets ──────────────────────────────────────────────────────────────────
@@ -177,11 +189,18 @@ export function parseTicket(text: string, where: string): TicketDoc {
177
189
  `${where}: invalid status "${status}" — one of: ${STATUSES.join(", ")}`,
178
190
  )
179
191
  }
180
- const goal = field(raw, "goal") ?? field(raw, "milestone")
192
+ // The pre-0.8 `milestone:` spelling is not honored (TAS-41: no compat), and
193
+ // reading past it would silently drop the ticket's goal — refuse by name
194
+ // instead, so the one edit that fixes the file is in the error.
195
+ if (raw.fields.has("milestone")) {
196
+ throw new TicketParseError(
197
+ `${where}: \`milestone:\` is the pre-0.8 spelling and is no longer read — rename the key to \`goal:\``,
198
+ )
199
+ }
200
+ const goal = field(raw, "goal")
181
201
  if (goal !== undefined && goal !== null && typeof goal !== "string") {
182
202
  throw new TicketParseError(`${where}: goal should be a string`)
183
203
  }
184
- const needsHuman = field(raw, "needs_human")
185
204
  const position = field(raw, "position")
186
205
  if (position !== undefined && typeof position !== "number") {
187
206
  throw new TicketParseError(`${where}: position should be a number`)
@@ -201,8 +220,7 @@ export function parseTicket(text: string, where: string): TicketDoc {
201
220
  status,
202
221
  tags: stringList(raw, "tags", where),
203
222
  goal: typeof goal === "string" && goal !== "" ? goal : null,
204
- needsHuman: needsHuman === true,
205
- blockedBy: numberList(raw, "blocked_by", where),
223
+ blockedBy: keyList(raw, "blocked_by", where),
206
224
  prs: stringList(raw, "prs", where),
207
225
  position: typeof position === "number" ? position : 0,
208
226
  createdAt: stringField(raw, "created", where, ""),
@@ -226,7 +244,6 @@ export function serializeTicket(doc: TicketDoc): string {
226
244
  const lines = ["---", `status: ${doc.status}`]
227
245
  if (doc.tags.length) lines.push(`tags: ${list(doc.tags)}`)
228
246
  if (doc.goal) lines.push(`goal: ${JSON.stringify(doc.goal)}`)
229
- if (doc.needsHuman) lines.push(`needs_human: true`)
230
247
  if (doc.blockedBy.length) lines.push(`blocked_by: ${list(doc.blockedBy)}`)
231
248
  if (doc.prs.length) lines.push(`prs: ${list(doc.prs)}`)
232
249
  lines.push(`position: ${doc.position}`)
@@ -339,6 +356,53 @@ export function serializeComment(doc: CommentDoc): string {
339
356
  return `${lines.join("\n")}\n`
340
357
  }
341
358
 
359
+ // ── Asks ─────────────────────────────────────────────────────────────────────
360
+
361
+ export function parseAsk(text: string, where: string): AskDoc {
362
+ const raw = splitRaw(text, where)!
363
+ const resolvedAt = field(raw, "resolved")
364
+ if (resolvedAt !== undefined && typeof resolvedAt !== "string") {
365
+ throw new TicketParseError(`${where}: resolved should be a timestamp string`)
366
+ }
367
+ return {
368
+ author: stringField(raw, "author", where, ""),
369
+ createdAt: stringField(raw, "created", where, ""),
370
+ resolvedAt: resolvedAt ?? null,
371
+ resolvedBy: resolvedAt === undefined ? null : stringField(raw, "resolved_by", where, ""),
372
+ body: raw.body,
373
+ }
374
+ }
375
+
376
+ export function serializeAsk(doc: AskDoc): string {
377
+ const lines = ["---"]
378
+ if (doc.author) lines.push(`author: ${JSON.stringify(doc.author)}`)
379
+ lines.push(`created: ${doc.createdAt}`)
380
+ if (doc.resolvedAt) {
381
+ lines.push(`resolved: ${doc.resolvedAt}`)
382
+ if (doc.resolvedBy) lines.push(`resolved_by: ${JSON.stringify(doc.resolvedBy)}`)
383
+ }
384
+ lines.push("---", "", doc.body)
385
+ return `${lines.join("\n")}\n`
386
+ }
387
+
388
+ /**
389
+ * An ask, forgivingly — null when there's no frontmatter at all. The lenient
390
+ * read matters more here than for comments: the inbox reads ask files straight
391
+ * off claim branches, which are exactly the bytes a strict parser can't refuse.
392
+ */
393
+ export function parseAskLenient(text: string): AskDoc | null {
394
+ const raw = splitRaw(text, null)
395
+ if (!raw) return null
396
+ const resolvedAt = field(raw, "resolved")
397
+ return {
398
+ author: lenientString(field(raw, "author")),
399
+ createdAt: lenientString(field(raw, "created")),
400
+ resolvedAt: typeof resolvedAt === "string" && resolvedAt !== "" ? resolvedAt : null,
401
+ resolvedBy: lenientString(field(raw, "resolved_by")) || null,
402
+ body: raw.body,
403
+ }
404
+ }
405
+
342
406
  // ── Lenient parsing ──────────────────────────────────────────────────────────
343
407
  //
344
408
  // The hosted board's read of the same format. The CLI is strict — it wrote the
@@ -352,10 +416,10 @@ function lenientStrings(value: unknown): string[] {
352
416
  return Array.isArray(value) ? value.filter((v): v is string => typeof v === "string") : []
353
417
  }
354
418
 
355
- function lenientNumbers(value: unknown): number[] {
356
- return Array.isArray(value)
357
- ? value.filter((v): v is number => typeof v === "number" && Number.isInteger(v))
358
- : []
419
+ function lenientKeys(value: unknown): string[] {
420
+ // Numeric entries (the pre-TAS-42 id format) are dropped, not coerced: a
421
+ // rendered old branch degrades to unlinked tickets, never to wrong links.
422
+ return Array.isArray(value) ? value.filter((v): v is string => typeof v === "string") : []
359
423
  }
360
424
 
361
425
  function lenientString(value: unknown): string {
@@ -367,10 +431,10 @@ export function parseTicketLenient(text: string): TicketDoc | null {
367
431
  const raw = splitRaw(text, null)
368
432
  if (!raw) return null
369
433
  const status = field(raw, "status")
370
- // The pre-0.8 `milestone` key still reads as the goal the lenient parser
371
- // exists precisely for old bytes (hand-edits, and here: old branches the
372
- // hosted board previews).
373
- const goal = field(raw, "goal") ?? field(raw, "milestone")
434
+ // No `milestone:` read here either on the old branches the hosted board
435
+ // previews, a pre-0.8 ticket degrades to goalless, the same as any other
436
+ // retired field. History renders; it doesn't get reinterpreted.
437
+ const goal = field(raw, "goal")
374
438
  const position = field(raw, "position")
375
439
  const { title, description } = splitTitle(raw.body)
376
440
  return {
@@ -379,8 +443,7 @@ export function parseTicketLenient(text: string): TicketDoc | null {
379
443
  status: typeof status === "string" && isStatus(status) ? status : "backlog",
380
444
  tags: lenientStrings(field(raw, "tags")),
381
445
  goal: typeof goal === "string" && goal !== "" ? goal : null,
382
- needsHuman: field(raw, "needs_human") === true,
383
- blockedBy: [...new Set(lenientNumbers(field(raw, "blocked_by")))].sort((a, b) => a - b),
446
+ blockedBy: [...new Set(lenientKeys(field(raw, "blocked_by")))].sort(),
384
447
  prs: lenientStrings(field(raw, "prs")),
385
448
  position: typeof position === "number" && Number.isFinite(position) ? position : 0,
386
449
  createdAt: lenientString(field(raw, "created")),
package/src/types.ts CHANGED
@@ -9,10 +9,42 @@ export const STATUSES = [
9
9
  ] as const
10
10
  export type Status = (typeof STATUSES)[number]
11
11
 
12
+ /**
13
+ * An ask: one thing a person owes this ticket before it can be done. Stored
14
+ * like a comment — one file per ask under `tickets/<n>/asks/` — so a worker on
15
+ * a claim branch can add one without ever conflicting with main. The ask is
16
+ * the primitive everything human-owed funnels through: `needsHuman` is derived
17
+ * from open asks, `task inbox` is the list of them, and `done` is gated on
18
+ * zero remaining.
19
+ */
20
+ export interface Ask {
21
+ /** Stable opaque id — the ask's filename stem (e.g. "2026-08-27T104501-claude"). */
22
+ id: string
23
+ taskId: string
24
+ /**
25
+ * 1-based position in creation order across the ticket's asks, resolved and
26
+ * open alike — the short handle `task resolve <id> <ordinal>` takes.
27
+ */
28
+ ordinal: number
29
+ text: string
30
+ author: string
31
+ createdAt: string
32
+ /** Both null while the ask is open. */
33
+ resolvedAt: string | null
34
+ resolvedBy: string | null
35
+ }
36
+
12
37
  export interface Task {
13
- /** Display id — `<prefix>-<number>`, e.g. "PHO-12". */
38
+ /** Display id — `<prefix>-<key>`, e.g. "PHO-x7k4m". */
14
39
  id: string
15
- number: number
40
+ /**
41
+ * The ticket's key: 5 random lowercase characters minted at creation (see
42
+ * id.ts), doubling as its directory name under `.task/tickets/`. Random on
43
+ * purpose — allocation must be safe across branches that have never seen
44
+ * each other — and opaque on purpose: creation order lives in `createdAt`,
45
+ * board order in `position`.
46
+ */
47
+ key: string
16
48
  title: string
17
49
  description: string
18
50
  status: Status
@@ -23,15 +55,20 @@ export interface Task {
23
55
  * wants to live in two goals is a tag.
24
56
  */
25
57
  goal: string | null
26
- /** This ticket can't be finished by an agent alone. */
58
+ /**
59
+ * Derived, never stored: true while any ask is open. There is no flag to
60
+ * set — a flag with no stated reason is the failure mode asks exist to end.
61
+ */
27
62
  needsHuman: boolean
63
+ /** Every ask on this ticket, open and resolved, in creation order. */
64
+ asks: Ask[]
28
65
  /**
29
- * Task numbers this one blocks / is blocked by. One relation, two views:
66
+ * Keys of tasks this one blocks / is blocked by. One relation, two views:
30
67
  * a single `(blocker, blocked)` row shows up in the blocker's `blocks` and
31
68
  * the blocked task's `blockedBy`, so the mirror can never drift.
32
69
  */
33
- blocks: number[]
34
- blockedBy: number[]
70
+ blocks: string[]
71
+ blockedBy: string[]
35
72
  /** Pull request URLs attached to this task. */
36
73
  prs: string[]
37
74
  /** Sort key within a status column; smaller sorts first. */
@@ -77,13 +114,13 @@ export interface ClaimsConfig {
77
114
 
78
115
  export interface ProjectConfig {
79
116
  name: string
80
- /** Uppercase id prefix, e.g. "PHO" → PHO-1, PHO-2, … */
117
+ /** Uppercase id prefix, e.g. "PHO" → PHO-x7k4m, PHO-q3v8d, … */
81
118
  prefix: string
82
119
  version: number
83
120
  /**
84
- * Pre-0.8 spelling of `claims.branchPrefix`, still read (the shared lock
85
- * convention must not strand in-flight claims across a version bump).
86
- * `task check` nags about it; `--fix` migrates it.
121
+ * Pre-0.8 spelling of `claims.branchPrefix`. Never honored typed only so
122
+ * `claimNamespace` can refuse it by name instead of silently defaulting the
123
+ * board's committed lock convention out from under its workers.
87
124
  */
88
125
  claimPrefix?: string
89
126
  claims?: ClaimsConfig
@@ -131,7 +168,7 @@ export interface TaskFilter {
131
168
  /** Any-of: a task matches if it carries at least one of these tags. */
132
169
  tags?: string[]
133
170
  goal?: string
134
- /** undefined = don't filter on it at all. */
171
+ /** Matches the *derived* flag — open asks, nothing else. undefined = don't filter. */
135
172
  needsHuman?: boolean
136
173
  /** true = list the archive instead of the board. */
137
174
  archived?: boolean
@@ -143,9 +180,8 @@ export interface TaskInput {
143
180
  status?: Status
144
181
  tags?: string[]
145
182
  goal?: string | null
146
- needsHuman?: boolean
147
- blocks?: number[]
148
- blockedBy?: number[]
183
+ blocks?: string[]
184
+ blockedBy?: string[]
149
185
  prs?: string[]
150
186
  }
151
187
 
@@ -155,13 +191,12 @@ export interface TaskPatch {
155
191
  status?: Status
156
192
  tags?: string[]
157
193
  goal?: string | null
158
- needsHuman?: boolean
159
194
  /**
160
195
  * Full replacement, like `tags` — the store reconciles the relation table
161
196
  * to match. Writing either side updates the other task's mirror view.
162
197
  */
163
- blocks?: number[]
164
- blockedBy?: number[]
198
+ blocks?: string[]
199
+ blockedBy?: string[]
165
200
  prs?: string[]
166
201
  /** Explicit board position (used by drag-and-drop in the UI). */
167
202
  position?: number