@clipboard-health/groundcrew 4.26.1 → 4.27.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.
@@ -18,5 +18,5 @@ export interface NormalizeOptions {
18
18
  updatedAt: string;
19
19
  }
20
20
  export declare function normalizeToIssue(options: NormalizeOptions): Issue | undefined;
21
- export declare function isActiveForFetch(parsed: ParsedTodoLine): boolean;
21
+ export declare function isActiveForFetch(parsed: ParsedTodoLine, todayIsoDate: string): boolean;
22
22
  //# sourceMappingURL=normalizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/normalizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsC,KAAK,KAAK,EAAiB,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAA8C,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9F,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAkED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,KAAK,GAAG,SAAS,CAqD7E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAShE"}
1
+ {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/normalizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsC,KAAK,KAAK,EAAiB,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAkED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,KAAK,GAAG,SAAS,CAqD7E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAYtF"}
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { AGENT_ANY } from "../../config.js";
3
3
  import { toCanonicalId } from "../../taskSource.js";
4
- import { getMetadataAll, getMetadataFirst, hashLine } from "./parser.js";
4
+ import { DATE_RE, getMetadataAll, getMetadataFirst, hashLine, } from "./parser.js";
5
5
  function derivedCanonicalStatus(parsed) {
6
6
  if (parsed.completed) {
7
7
  return "done";
@@ -90,7 +90,7 @@ export function normalizeToIssue(options) {
90
90
  sourceRef,
91
91
  };
92
92
  }
93
- export function isActiveForFetch(parsed) {
93
+ export function isActiveForFetch(parsed, todayIsoDate) {
94
94
  if (parsed.completed) {
95
95
  return false;
96
96
  }
@@ -98,5 +98,28 @@ export function isActiveForFetch(parsed) {
98
98
  return false;
99
99
  }
100
100
  const statusValue = getMetadataFirst(parsed, "status");
101
- return statusValue === "todo" || statusValue === "in-progress" || statusValue === "in-review";
101
+ if (statusValue === "todo") {
102
+ return !isDeferredByThreshold(parsed, todayIsoDate);
103
+ }
104
+ return statusValue === "in-progress" || statusValue === "in-review";
105
+ }
106
+ // Per todo.txt convention, t: (threshold) hides a task until that date.
107
+ // Only not-yet-started tasks defer; in-progress/in-review work must stay
108
+ // visible so the orchestrator keeps tracking it. Malformed t: values are
109
+ // surfaced by validate() and do not block dispatch.
110
+ function isDeferredByThreshold(parsed, todayIsoDate) {
111
+ const threshold = getMetadataFirst(parsed, "t");
112
+ if (threshold === undefined || !DATE_RE.test(threshold) || !isCalendarDate(threshold)) {
113
+ return false;
114
+ }
115
+ // ISO YYYY-MM-DD dates order lexicographically
116
+ return threshold > todayIsoDate;
117
+ }
118
+ // DATE_RE is format-only; non-calendar values like 2026-99-99 would compare
119
+ // greater than any real date and defer the task forever.
120
+ function isCalendarDate(value) {
121
+ const monthIndex = Number(value.slice(5, 7)) - 1;
122
+ const day = Number(value.slice(8, 10));
123
+ const date = new Date(Date.UTC(Number(value.slice(0, 4)), monthIndex, day));
124
+ return date.getUTCMonth() === monthIndex && date.getUTCDate() === day;
102
125
  }
@@ -12,6 +12,7 @@ export interface ParsedTodoLine {
12
12
  /** True when the final meaningful whitespace-delimited token is a `status:X` field. */
13
13
  readonly isStatusFinalToken: boolean;
14
14
  }
15
+ export declare const DATE_RE: RegExp;
15
16
  export declare function hashLine(raw: string): string;
16
17
  export declare function parseAllLines(fileContent: string): (ParsedTodoLine | null)[];
17
18
  export declare function getMetadataFirst(parsed: ParsedTodoLine, key: string): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/parser.ts"],"names":[],"mappings":"AAEA,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,uFAAuF;IACvF,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;CACtC;AAQD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5C;AA6ED,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,CAQ5E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE5E"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/parser.ts"],"names":[],"mappings":"AAEA,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,uFAAuF;IACvF,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;CACtC;AAED,eAAO,MAAM,OAAO,QAAwB,CAAC;AAM7C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5C;AA6ED,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,CAQ5E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE5E"}
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
2
+ export const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
3
3
  const KEY_VALUE_RE = /^(?<key>[a-zA-Z][a-zA-Z0-9-]*):(?<value>\S+)$/;
4
4
  const PRIORITY_RE = /^\((?<priority>[A-Z])\) /;
5
5
  const PROJECT_RE = /^\+\S+$/;
@@ -1 +1 @@
1
- {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA0RxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACtB,UAAU,CAqJZ"}
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA6RxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACtB,UAAU,CAsJZ"}
@@ -5,9 +5,8 @@ import { AGENT_ANY } from "../../config.js";
5
5
  import { toCanonicalId, } from "../../taskSource.js";
6
6
  import { readEnvironmentVariable } from "../../util.js";
7
7
  import { isActiveForFetch, normalizeToIssue } from "./normalizer.js";
8
- import { getMetadataFirst, parseAllLines } from "./parser.js";
8
+ import { DATE_RE, getMetadataFirst, parseAllLines } from "./parser.js";
9
9
  import { copyPromptFile, updateTaskStatus, validateTodoFile, withLock } from "./writeback.js";
10
- const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
11
10
  const RECURRENCE_RE = /^\+?\d+[dwmy]$/;
12
11
  function readPromptFile(promptPath) {
13
12
  try {
@@ -95,7 +94,7 @@ function metadataToken(key, value) {
95
94
  assertToken(`${key}: value`, value);
96
95
  return `${key}:${value}`;
97
96
  }
98
- function datePartFor(timeZone, now) {
97
+ function isoDateFor(timeZone, now) {
99
98
  const parts = new Intl.DateTimeFormat("en-CA", {
100
99
  timeZone,
101
100
  year: "numeric",
@@ -109,7 +108,10 @@ function datePartFor(timeZone, now) {
109
108
  if (year === undefined || month === undefined || day === undefined) {
110
109
  throw new Error(`todo-txt: could not format date in timezone "${timeZone}"`);
111
110
  }
112
- return `${year}${month}${day}`;
111
+ return `${year}-${month}-${day}`;
112
+ }
113
+ function datePartFor(timeZone, now) {
114
+ return isoDateFor(timeZone, now).replaceAll("-", "");
113
115
  }
114
116
  /* v8 ignore next @preserve -- Covered in source tests; full-suite V8 coverage remaps this helper inconsistently. */
115
117
  function nextGeneratedId(config, parsedAll) {
@@ -256,6 +258,7 @@ export function createTodoTxtTaskSource(config, context) {
256
258
  ]);
257
259
  function listTasks() {
258
260
  const updatedAt = fileUpdatedAt(todoPath);
261
+ const todayIsoDate = isoDateFor(config.timezone, new Date());
259
262
  const { parsedAll } = readAndParseTodo(todoPath);
260
263
  const issues = [];
261
264
  for (let i = 0; i < parsedAll.length; i++) {
@@ -263,7 +266,7 @@ export function createTodoTxtTaskSource(config, context) {
263
266
  if (parsed === null || parsed === undefined) {
264
267
  continue;
265
268
  }
266
- if (!isActiveForFetch(parsed)) {
269
+ if (!isActiveForFetch(parsed, todayIsoDate)) {
267
270
  continue;
268
271
  }
269
272
  const issue = buildIssue({
@@ -1 +1 @@
1
- {"version":3,"file":"writeback.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/writeback.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAoKD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,gBAAgB,CAAC;IACtB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQxF;AAED,KAAK,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAC;AA6E3D,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,cAAc,GACxB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAsElC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQrE;AAgGD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAChC,MAAM,EAAE,CA0CV"}
1
+ {"version":3,"file":"writeback.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/writeback.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAkKD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,gBAAgB,CAAC;IACtB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQxF;AAED,KAAK,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAC;AAgF3D,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,cAAc,GACxB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAsElC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQrE;AAgGD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAChC,MAAM,EAAE,CA0CV"}
@@ -1,7 +1,6 @@
1
1
  import { closeSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
2
2
  import path from "node:path";
3
- import { hashLine, parseAllLines } from "./parser.js";
4
- const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
3
+ import { DATE_RE, hashLine, parseAllLines } from "./parser.js";
5
4
  function isoDate(date) {
6
5
  return date.toISOString().slice(0, 10);
7
6
  }
@@ -181,9 +180,11 @@ function buildRecurResult(parsed, parsedAll, originalLine, ref, completionDateSt
181
180
  const newDue = oldDue === undefined ? undefined : advanceDate(dueBase, rec);
182
181
  // t: always advances from its own current value by the same period
183
182
  const newT = oldT === undefined ? undefined : advanceDate(oldT, rec);
184
- // Compute new date for id advancement
185
- /* v8 ignore next @preserve -- newDue undefined when no due: field; rare edge case */
186
- const newDateForId = newDue === undefined ? now : new Date(`${newDue}T00:00:00Z`);
183
+ // Compute new date for id advancement: prefer due:, then t:, so ids stay
184
+ // schedule-aligned for t:-only recurring tasks
185
+ const newScheduleDate = newDue ?? newT;
186
+ /* v8 ignore next @preserve -- rec: without due: or t: is unusual; id falls back to completion date */
187
+ const newDateForId = newScheduleDate === undefined ? now : new Date(`${newScheduleDate}T00:00:00Z`);
187
188
  const baseNewId = advanceId(ref.id, newDateForId);
188
189
  const newId = buildUniqueId(baseNewId, existingIds);
189
190
  const newTodoLine = buildRecurringLine(originalLine, ref.id, newId, oldDue, newDue, oldT, newT);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.26.1",
3
+ "version": "4.27.0",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",