@clipboard-health/groundcrew 4.26.1 → 4.27.1
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.
- package/README.md +10 -8
- package/crew.config.example.ts +4 -4
- package/dist/lib/adapters/todo-txt/normalizer.d.ts +1 -1
- package/dist/lib/adapters/todo-txt/normalizer.d.ts.map +1 -1
- package/dist/lib/adapters/todo-txt/normalizer.js +26 -3
- package/dist/lib/adapters/todo-txt/parser.d.ts +1 -0
- package/dist/lib/adapters/todo-txt/parser.d.ts.map +1 -1
- package/dist/lib/adapters/todo-txt/parser.js +1 -1
- package/dist/lib/adapters/todo-txt/source.d.ts.map +1 -1
- package/dist/lib/adapters/todo-txt/source.js +8 -5
- package/dist/lib/adapters/todo-txt/writeback.d.ts.map +1 -1
- package/dist/lib/adapters/todo-txt/writeback.js +6 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ npm install -g @clipboard-health/groundcrew@latest
|
|
|
49
49
|
|
|
50
50
|
# 2. Scaffold a global config. Agents are sandboxed by default
|
|
51
51
|
# (Safehouse/Docker Sandboxes); add --runner none to run unsandboxed on the host.
|
|
52
|
-
crew init --global --project-dir ~/dev --repo OWNER/REPO --
|
|
52
|
+
crew init --global --project-dir ~/dev --repo OWNER/REPO --agent claude
|
|
53
53
|
|
|
54
54
|
# 3. Run the clone commands printed by `crew init`.
|
|
55
55
|
|
|
@@ -64,7 +64,7 @@ crew doctor
|
|
|
64
64
|
crew run --watch
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
`crew init --global` writes config to `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/`. Pass `--repo` more than once for multiple repos. `--
|
|
67
|
+
`crew init --global` writes config to `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/`. Pass `--repo` more than once for multiple repos. `--agent claude` or `--agent codex` chooses the single built-in agent preset to enable in the generated config.
|
|
68
68
|
|
|
69
69
|
## Task Pickup
|
|
70
70
|
|
|
@@ -72,8 +72,8 @@ crew run --watch
|
|
|
72
72
|
|
|
73
73
|
Linear works out of the box: assign tasks to yourself and add an `agent-*` label.
|
|
74
74
|
|
|
75
|
-
- `agent-claude`, `agent-codex`, or `agent-<name>` routes to that
|
|
76
|
-
- `agent-any` routes to the enabled
|
|
75
|
+
- `agent-claude`, `agent-codex`, or `agent-<name>` routes to that agent.
|
|
76
|
+
- `agent-any` routes to the enabled agent with the most session headroom, after skipping agents over their session limit or weekly paced budget.
|
|
77
77
|
- Tasks without an `agent-*` label are ignored by `crew run`; dispatch one manually with `crew start <TASK>`.
|
|
78
78
|
|
|
79
79
|
Groundcrew scans `workspace.knownRepositories` to infer which repo a task belongs to.
|
|
@@ -91,17 +91,19 @@ Write tasks as complete agent instructions: the goal, the context and constraint
|
|
|
91
91
|
```bash
|
|
92
92
|
crew init [--global | --local] [--force] [--dry-run] # create a crew.config.ts
|
|
93
93
|
[--project-dir <dir>] [--repo <repo>]...
|
|
94
|
-
[--runner <auto|safehouse|sdx|none>] [--
|
|
94
|
+
[--runner <auto|safehouse|sdx|none>] [--agent <claude|codex>]
|
|
95
95
|
crew doctor # check setup
|
|
96
|
+
crew source list|verify [<source>] # inspect configured task sources
|
|
96
97
|
crew task list [--source <name>] # list tasks across sources
|
|
97
98
|
crew task get <TASK> [--source <name>] [--prompt] # inspect one task or its prompt
|
|
98
99
|
crew task create "Title" --source <name> [--agent <name>] # create a source task
|
|
100
|
+
crew task validate [<source>] # validate task content
|
|
99
101
|
crew status [<TASK>] # inspect current state or one task
|
|
100
102
|
crew run [--watch] # one-shot or --watch forever
|
|
101
103
|
crew start <TASK> # provision + launch one task now
|
|
102
104
|
crew stop <TASK> [--reason <text>] # stop workspace, keep worktree
|
|
103
105
|
crew resume <TASK> # reopen a paused task
|
|
104
|
-
crew cleanup <TASK>
|
|
106
|
+
crew cleanup [--force] <TASK> # tear down every worktree for a task
|
|
105
107
|
crew upgrade [<version>] # reinstall crew globally through npm
|
|
106
108
|
```
|
|
107
109
|
|
|
@@ -109,7 +111,7 @@ See [command details](./docs/commands.md) for status output, doctor behavior, an
|
|
|
109
111
|
|
|
110
112
|
## Configuration
|
|
111
113
|
|
|
112
|
-
Workspace settings and at least one enabled
|
|
114
|
+
Workspace settings and at least one enabled agent are required; everything else has a default.
|
|
113
115
|
|
|
114
116
|
```ts
|
|
115
117
|
import type { Config } from "@clipboard-health/groundcrew";
|
|
@@ -122,7 +124,7 @@ export default {
|
|
|
122
124
|
// Strings live under projectDir; use { name, projectDirOverride } to override per repo.
|
|
123
125
|
knownRepositories: ["OWNER/REPO"],
|
|
124
126
|
},
|
|
125
|
-
|
|
127
|
+
agents: {
|
|
126
128
|
default: "claude",
|
|
127
129
|
definitions: {
|
|
128
130
|
claude: {},
|
package/crew.config.example.ts
CHANGED
|
@@ -27,9 +27,9 @@ export default {
|
|
|
27
27
|
// { name: "other-org/other-repo", projectDirOverride: "~/work" }
|
|
28
28
|
knownRepositories: ["your-org/your-repo"],
|
|
29
29
|
},
|
|
30
|
-
|
|
30
|
+
agents: {
|
|
31
31
|
default: "claude",
|
|
32
|
-
// `definitions` is the enabled
|
|
32
|
+
// `definitions` is the enabled agent set. Built-in keys can use `{}` to
|
|
33
33
|
// opt into the shipped command/color/usage preset. Add `codex: {}` if you
|
|
34
34
|
// want both shipped agents, or add a custom entry and tag tasks with
|
|
35
35
|
// `agent-<name>`.
|
|
@@ -99,7 +99,7 @@ export default {
|
|
|
99
99
|
// // it into the agent. Chain with `&&` so a failed mint aborts launch.
|
|
100
100
|
// preLaunch: "SESSION_TOKEN=$(your-mint-command) && export SESSION_TOKEN",
|
|
101
101
|
// preLaunchEnv: ["SESSION_TOKEN"],
|
|
102
|
-
// // Required for this
|
|
102
|
+
// // Required for this agent when `local.runner` resolves to `sdx`.
|
|
103
103
|
// sandbox: { agent: "claude" },
|
|
104
104
|
// },
|
|
105
105
|
//
|
|
@@ -110,7 +110,7 @@ export default {
|
|
|
110
110
|
// local: { runner: "auto" },
|
|
111
111
|
//
|
|
112
112
|
// // Groundcrew does not create or authenticate sdx sandboxes. For an sdx
|
|
113
|
-
// //
|
|
113
|
+
// // agent, create the matching sandbox yourself before first launch:
|
|
114
114
|
// // sbx create --name groundcrew-claude claude ~/dev/groundcrew
|
|
115
115
|
// // sbx exec -it groundcrew-claude claude auth login
|
|
116
116
|
// // sbx exec -it groundcrew-claude gh auth login
|
|
@@ -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,
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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
|
|
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}
|
|
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;
|
|
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
|
-
|
|
186
|
-
const
|
|
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