@ai-setting/roy-task-plugin 0.7.1 → 0.7.2

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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `findOpenTask` — the Stop hook's "is a task still open?" lookup.
3
+ *
4
+ * Extracted from `stop-check.mjs` so it can be unit-tested without spawning a
5
+ * hook process (Task #4624, CV finding).
6
+ *
7
+ * ## Why the shape matters
8
+ *
9
+ * `TaskBackend.listTasks` is a *paged view*: it applies the status filter in
10
+ * memory, then slices `[offset, offset+limit)`. It promises neither an order
11
+ * nor that one page holds every match. So "take one page, then filter in the
12
+ * caller" silently misses an open task that sits behind a page of newer
13
+ * completed tasks — the exact regression this module exists to prevent.
14
+ */
15
+
16
+ /**
17
+ * The statuses that count as "still open".
18
+ *
19
+ * Ordered by how recently a task is likely to have been touched; the search
20
+ * does not depend on the order because it scans every open status.
21
+ */
22
+ export const OPEN_STATUSES = ["active", "todo"];
23
+
24
+ /** Page size used while draining the open statuses. */
25
+ const PAGE_SIZE = 500;
26
+
27
+ /** Read an update timestamp from either roy shape (`updated_at` / `updatedAt`). */
28
+ export function updatedAtOf(task) {
29
+ const raw = task?.updatedAt ?? task?.updated_at ?? 0;
30
+ if (typeof raw === "number") return raw;
31
+ const parsed = Date.parse(String(raw));
32
+ return Number.isNaN(parsed) ? 0 : parsed;
33
+ }
34
+
35
+ /**
36
+ * The most recently updated task that is still `todo` / `active`, or `null`.
37
+ *
38
+ * @param {{ listTasks: (options?: object) => Promise<{ tasks?: any[], total?: number }> }} backend
39
+ */
40
+ export async function findOpenTask(backend) {
41
+ let newest = null;
42
+ let newestAt = Number.NEGATIVE_INFINITY;
43
+
44
+ for (const status of OPEN_STATUSES) {
45
+ let offset = 0;
46
+ for (;;) {
47
+ const page = await backend.listTasks({ status, limit: PAGE_SIZE, offset });
48
+ const tasks = page?.tasks ?? [];
49
+
50
+ for (const task of tasks) {
51
+ if (task.status !== status) continue;
52
+ const at = updatedAtOf(task);
53
+ if (at > newestAt || (at === newestAt && newest && task.id > newest.id)) {
54
+ newest = task;
55
+ newestAt = at;
56
+ }
57
+ }
58
+
59
+ offset += tasks.length;
60
+ const total = page?.total ?? offset;
61
+ if (tasks.length === 0 || offset >= total) break;
62
+ }
63
+ }
64
+
65
+ return newest;
66
+ }
@@ -39,6 +39,7 @@
39
39
  */
40
40
 
41
41
  import { createServer, defaultTaskDbPath, BridgeError } from "../mcp/bridge.mjs";
42
+ import { findOpenTask } from "./find-open-task.mjs";
42
43
 
43
44
  /** Read stdin fully, with a hard cap so a huge transcript cannot stall us. */
44
45
  async function readStdin(limitBytes = 2 * 1024 * 1024) {
@@ -131,26 +132,6 @@ function parseTranscript(raw) {
131
132
  return entries;
132
133
  }
133
134
 
134
- /** The most recently updated task that is still open. */
135
- /**
136
- * Find the most recently touched task that is still open.
137
- *
138
- * Task #4619 — this used to be a hand-written SQL query against roy's own
139
- * database (`SELECT id, title, status FROM task WHERE status IN ('todo',
140
- * 'active') ORDER BY updated_at DESC LIMIT 1`). The *query* is task
141
- * semantics, so it belongs to core, not to the plugin.
142
- *
143
- * It is now expressed against core's `TaskBackend`, which owns the status
144
- * vocabulary and the ordering.
145
- */
146
- async function findOpenTask(backend) {
147
- const result = await backend.listTasks({ limit: 200 });
148
- const open = result.tasks
149
- .filter((t) => t.status === "todo" || t.status === "active")
150
- .sort((a, b) => (b.updatedAt ?? b.updated_at ?? 0) - (a.updatedAt ?? a.updated_at ?? 0));
151
- return open[0] ?? null;
152
- }
153
-
154
135
  function approve() {
155
136
  process.stdout.write(JSON.stringify({ decision: "approve" }));
156
137
  process.exit(0);
package/dist/cli.js CHANGED
@@ -2484,7 +2484,7 @@ function formatStatus(report) {
2484
2484
  }
2485
2485
 
2486
2486
  // ../../../packages/cli/src/main.ts
2487
- var VERSION = "0.7.1";
2487
+ var VERSION = "0.7.2";
2488
2488
  async function main(argv) {
2489
2489
  const parsed = parseArgs(argv);
2490
2490
  switch (parsed.command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-setting/roy-task-plugin",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {