clawvault 2.4.6 → 2.4.7
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/bin/clawvault.js +5 -0
- package/bin/command-registration.test.js +1 -1
- package/bin/help-contract.test.js +1 -0
- package/bin/register-config-route-commands.test.js +8 -1
- package/bin/register-core-commands.js +3 -3
- package/bin/register-project-commands.js +209 -0
- package/bin/register-project-commands.test.js +201 -0
- package/bin/register-query-commands.js +40 -0
- package/bin/register-task-commands.js +2 -18
- package/bin/register-task-commands.test.js +3 -4
- package/bin/test-helpers/cli-command-fixtures.js +5 -0
- package/dist/{chunk-3PJIGGWV.js → chunk-2CDEETQN.js} +1 -0
- package/dist/{chunk-FD2ZA65C.js → chunk-2RK2AG32.js} +5 -5
- package/dist/chunk-5GZFTAL7.js +340 -0
- package/dist/{chunk-P2ZH6AN5.js → chunk-6RQPD7X6.js} +3 -4
- package/dist/{chunk-HNMFXFYP.js → chunk-7OHQFMJK.js} +2 -1
- package/dist/{chunk-FKQJB6XC.js → chunk-C3PF7WBA.js} +2 -2
- package/dist/{chunk-JXY6T5R7.js → chunk-FW465EEA.js} +1 -1
- package/dist/{chunk-BI6SGGZP.js → chunk-G3OQJ2NQ.js} +1 -1
- package/dist/chunk-GSD4ALSI.js +724 -0
- package/dist/{chunk-6QLRSPLZ.js → chunk-IOALNTAN.js} +268 -47
- package/dist/chunk-ITPEXLHA.js +528 -0
- package/dist/{chunk-LLN5SPGL.js → chunk-J5EMBUPK.js} +1 -1
- package/dist/chunk-K3CDT7IH.js +122 -0
- package/dist/{chunk-AHGUJG76.js → chunk-KCCHROBR.js} +13 -69
- package/dist/{chunk-JTO7NZLS.js → chunk-LMCC5OC7.js} +2 -2
- package/dist/{chunk-QALB2V3E.js → chunk-MQUJNOHK.js} +1 -1
- package/dist/{chunk-H6WQUUNK.js → chunk-TMZMN7OS.js} +334 -457
- package/dist/{chunk-HVTTYDCJ.js → chunk-VR5NE7PZ.js} +1 -1
- package/dist/{chunk-22WE3J4F.js → chunk-WIICLBNF.js} +35 -4
- package/dist/chunk-YCVDVI5B.js +273 -0
- package/dist/{chunk-NAMFB7ZA.js → chunk-Z2XBWN7A.js} +0 -2
- package/dist/commands/archive.js +3 -3
- package/dist/commands/backlog.js +1 -1
- package/dist/commands/blocked.js +1 -1
- package/dist/commands/canvas.d.ts +1 -14
- package/dist/commands/canvas.js +123 -1543
- package/dist/commands/context.js +5 -6
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/inject.d.ts +2 -0
- package/dist/commands/inject.js +14 -0
- package/dist/commands/kanban.js +2 -2
- package/dist/commands/migrate-observations.js +2 -2
- package/dist/commands/observe.js +8 -6
- package/dist/commands/project.d.ts +85 -0
- package/dist/commands/project.js +411 -0
- package/dist/commands/rebuild.js +7 -5
- package/dist/commands/reflect.js +5 -4
- package/dist/commands/replay.js +10 -7
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sleep.d.ts +1 -1
- package/dist/commands/sleep.js +11 -8
- package/dist/commands/status.js +2 -2
- package/dist/commands/task.d.ts +2 -2
- package/dist/commands/task.js +11 -301
- package/dist/commands/wake.d.ts +1 -1
- package/dist/commands/wake.js +4 -4
- package/dist/index.d.ts +75 -107
- package/dist/index.js +78 -36
- package/dist/inject-x65KXWPk.d.ts +137 -0
- package/dist/lib/project-utils.d.ts +97 -0
- package/dist/lib/project-utils.js +19 -0
- package/dist/lib/task-utils.d.ts +8 -3
- package/dist/lib/task-utils.js +1 -1
- package/dist/{types-DMU3SuAV.d.ts → types-jjuYN2Xn.d.ts} +1 -1
- package/package.json +2 -2
- package/dist/chunk-L3DJ36BZ.js +0 -40
- package/dist/chunk-UMMCYTJV.js +0 -105
|
@@ -1,33 +1,182 @@
|
|
|
1
1
|
// src/lib/task-utils.ts
|
|
2
|
+
import * as fs2 from "fs";
|
|
3
|
+
import * as path2 from "path";
|
|
4
|
+
import matter from "gray-matter";
|
|
5
|
+
|
|
6
|
+
// src/lib/transition-ledger.ts
|
|
2
7
|
import * as fs from "fs";
|
|
3
8
|
import * as path from "path";
|
|
4
|
-
|
|
9
|
+
var REGRESSION_PAIRS = [
|
|
10
|
+
["done", "open"],
|
|
11
|
+
["done", "blocked"],
|
|
12
|
+
["in-progress", "blocked"]
|
|
13
|
+
];
|
|
14
|
+
function isRegression(from, to) {
|
|
15
|
+
return REGRESSION_PAIRS.some(([f, t]) => f === from && t === to);
|
|
16
|
+
}
|
|
17
|
+
function getLedgerDir(vaultPath) {
|
|
18
|
+
return path.join(path.resolve(vaultPath), "ledger", "transitions");
|
|
19
|
+
}
|
|
20
|
+
function getTodayLedgerPath(vaultPath) {
|
|
21
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
22
|
+
return path.join(getLedgerDir(vaultPath), `${date}.jsonl`);
|
|
23
|
+
}
|
|
24
|
+
var RETRYABLE_APPEND_CODES = /* @__PURE__ */ new Set(["ENOENT", "EAGAIN", "EBUSY"]);
|
|
25
|
+
var MAX_APPEND_RETRIES = 2;
|
|
26
|
+
function asErrno(error) {
|
|
27
|
+
if (!error || typeof error !== "object") {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
function formatLedgerWriteError(filePath, error) {
|
|
33
|
+
const errno = asErrno(error);
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
+
if (errno?.code === "ENOSPC") {
|
|
36
|
+
return new Error(`Failed to write transition ledger at ${filePath}: no space left on device.`);
|
|
37
|
+
}
|
|
38
|
+
if (errno?.code === "EACCES" || errno?.code === "EPERM") {
|
|
39
|
+
return new Error(`Failed to write transition ledger at ${filePath}: permission denied.`);
|
|
40
|
+
}
|
|
41
|
+
return new Error(`Failed to write transition ledger at ${filePath}: ${message}`);
|
|
42
|
+
}
|
|
43
|
+
function appendTransition(vaultPath, event) {
|
|
44
|
+
const ledgerDir = getLedgerDir(vaultPath);
|
|
45
|
+
try {
|
|
46
|
+
fs.mkdirSync(ledgerDir, { recursive: true });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw formatLedgerWriteError(ledgerDir, error);
|
|
49
|
+
}
|
|
50
|
+
const filePath = getTodayLedgerPath(vaultPath);
|
|
51
|
+
const payload = JSON.stringify(event) + "\n";
|
|
52
|
+
for (let attempt = 0; attempt <= MAX_APPEND_RETRIES; attempt += 1) {
|
|
53
|
+
try {
|
|
54
|
+
fs.appendFileSync(filePath, payload);
|
|
55
|
+
return;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const errno = asErrno(error);
|
|
58
|
+
const code = errno?.code;
|
|
59
|
+
if (code === "ENOENT") {
|
|
60
|
+
try {
|
|
61
|
+
fs.mkdirSync(ledgerDir, { recursive: true });
|
|
62
|
+
} catch (mkdirError) {
|
|
63
|
+
throw formatLedgerWriteError(filePath, mkdirError);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (code && RETRYABLE_APPEND_CODES.has(code) && attempt < MAX_APPEND_RETRIES) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
throw formatLedgerWriteError(filePath, error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function buildTransitionEvent(taskId, fromStatus, toStatus, options = {}) {
|
|
74
|
+
const agentId = process.env.OPENCLAW_AGENT_ID || "manual";
|
|
75
|
+
const costTokensRaw = process.env.OPENCLAW_TOKEN_ESTIMATE;
|
|
76
|
+
const costTokens = costTokensRaw ? parseInt(costTokensRaw, 10) : null;
|
|
77
|
+
return {
|
|
78
|
+
task_id: taskId,
|
|
79
|
+
agent_id: agentId,
|
|
80
|
+
from_status: fromStatus,
|
|
81
|
+
to_status: toStatus,
|
|
82
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
83
|
+
confidence: options.confidence ?? (agentId === "manual" ? 1 : 1),
|
|
84
|
+
cost_tokens: costTokens !== null && !isNaN(costTokens) ? costTokens : null,
|
|
85
|
+
reason: options.reason || null
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function readAllTransitions(vaultPath) {
|
|
89
|
+
const ledgerDir = getLedgerDir(vaultPath);
|
|
90
|
+
if (!fs.existsSync(ledgerDir)) return [];
|
|
91
|
+
let files = [];
|
|
92
|
+
try {
|
|
93
|
+
files = fs.readdirSync(ledgerDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const events = [];
|
|
98
|
+
for (const file of files) {
|
|
99
|
+
let lines = [];
|
|
100
|
+
try {
|
|
101
|
+
lines = fs.readFileSync(path.join(ledgerDir, file), "utf-8").split("\n").filter((l) => l.trim());
|
|
102
|
+
} catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
for (const line of lines) {
|
|
106
|
+
try {
|
|
107
|
+
events.push(JSON.parse(line));
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return events;
|
|
113
|
+
}
|
|
114
|
+
function queryTransitions(vaultPath, filters = {}) {
|
|
115
|
+
let events = readAllTransitions(vaultPath);
|
|
116
|
+
if (filters.taskId) {
|
|
117
|
+
events = events.filter((e) => e.task_id === filters.taskId);
|
|
118
|
+
}
|
|
119
|
+
if (filters.agent) {
|
|
120
|
+
events = events.filter((e) => e.agent_id === filters.agent);
|
|
121
|
+
}
|
|
122
|
+
if (filters.failed) {
|
|
123
|
+
events = events.filter((e) => isRegression(e.from_status, e.to_status));
|
|
124
|
+
}
|
|
125
|
+
return events;
|
|
126
|
+
}
|
|
127
|
+
function countBlockedTransitions(vaultPath, taskId) {
|
|
128
|
+
const events = readAllTransitions(vaultPath);
|
|
129
|
+
return events.filter((e) => e.task_id === taskId && e.to_status === "blocked").length;
|
|
130
|
+
}
|
|
131
|
+
function formatTransitionsTable(events) {
|
|
132
|
+
if (events.length === 0) return "No transitions found.\n";
|
|
133
|
+
const headers = ["TIMESTAMP", "TASK", "FROM\u2192TO", "AGENT", "REASON"];
|
|
134
|
+
const widths = [20, 20, 24, 16, 30];
|
|
135
|
+
let output = headers.map((h, i) => h.padEnd(widths[i])).join(" ") + "\n";
|
|
136
|
+
output += "-".repeat(widths.reduce((a, b) => a + b + 2, 0)) + "\n";
|
|
137
|
+
for (const e of events) {
|
|
138
|
+
const ts = e.timestamp.replace("T", " ").slice(0, 19);
|
|
139
|
+
const taskId = e.task_id.length > widths[1] ? e.task_id.slice(0, widths[1] - 3) + "..." : e.task_id;
|
|
140
|
+
const transition = `${e.from_status} \u2192 ${e.to_status}`;
|
|
141
|
+
const reason = e.reason ? e.reason.length > widths[4] ? e.reason.slice(0, widths[4] - 3) + "..." : e.reason : "-";
|
|
142
|
+
output += [
|
|
143
|
+
ts.padEnd(widths[0]),
|
|
144
|
+
taskId.padEnd(widths[1]),
|
|
145
|
+
transition.padEnd(widths[2]),
|
|
146
|
+
e.agent_id.padEnd(widths[3]),
|
|
147
|
+
reason
|
|
148
|
+
].join(" ") + "\n";
|
|
149
|
+
}
|
|
150
|
+
return output;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/lib/task-utils.ts
|
|
5
154
|
function slugify(text) {
|
|
6
155
|
return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").trim();
|
|
7
156
|
}
|
|
8
157
|
function getTasksDir(vaultPath) {
|
|
9
|
-
return
|
|
158
|
+
return path2.join(path2.resolve(vaultPath), "tasks");
|
|
10
159
|
}
|
|
11
160
|
function getBacklogDir(vaultPath) {
|
|
12
|
-
return
|
|
161
|
+
return path2.join(path2.resolve(vaultPath), "backlog");
|
|
13
162
|
}
|
|
14
163
|
function ensureTasksDir(vaultPath) {
|
|
15
164
|
const tasksDir = getTasksDir(vaultPath);
|
|
16
|
-
if (!
|
|
17
|
-
|
|
165
|
+
if (!fs2.existsSync(tasksDir)) {
|
|
166
|
+
fs2.mkdirSync(tasksDir, { recursive: true });
|
|
18
167
|
}
|
|
19
168
|
}
|
|
20
169
|
function ensureBacklogDir(vaultPath) {
|
|
21
170
|
const backlogDir = getBacklogDir(vaultPath);
|
|
22
|
-
if (!
|
|
23
|
-
|
|
171
|
+
if (!fs2.existsSync(backlogDir)) {
|
|
172
|
+
fs2.mkdirSync(backlogDir, { recursive: true });
|
|
24
173
|
}
|
|
25
174
|
}
|
|
26
175
|
function getTaskPath(vaultPath, slug) {
|
|
27
|
-
return
|
|
176
|
+
return path2.join(getTasksDir(vaultPath), `${slug}.md`);
|
|
28
177
|
}
|
|
29
178
|
function getBacklogPath(vaultPath, slug) {
|
|
30
|
-
return
|
|
179
|
+
return path2.join(getBacklogDir(vaultPath), `${slug}.md`);
|
|
31
180
|
}
|
|
32
181
|
function extractTitle(content) {
|
|
33
182
|
const match = content.match(/^#\s+(.+)$/m);
|
|
@@ -43,13 +192,76 @@ function startOfToday() {
|
|
|
43
192
|
const now = /* @__PURE__ */ new Date();
|
|
44
193
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
|
45
194
|
}
|
|
195
|
+
var VALID_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
196
|
+
"open",
|
|
197
|
+
"in-progress",
|
|
198
|
+
"blocked",
|
|
199
|
+
"done"
|
|
200
|
+
]);
|
|
201
|
+
function isTaskStatus(value) {
|
|
202
|
+
return typeof value === "string" && VALID_TASK_STATUSES.has(value);
|
|
203
|
+
}
|
|
204
|
+
function persistTaskFrontmatter(task, frontmatter) {
|
|
205
|
+
fs2.writeFileSync(task.path, matter.stringify(task.content, frontmatter));
|
|
206
|
+
}
|
|
207
|
+
function resolveStatusTransition(previousStatus, nextStatus) {
|
|
208
|
+
if (!isTaskStatus(previousStatus) || !isTaskStatus(nextStatus)) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (previousStatus === nextStatus) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return { fromStatus: previousStatus, toStatus: nextStatus };
|
|
215
|
+
}
|
|
216
|
+
function logStatusTransition({
|
|
217
|
+
vaultPath,
|
|
218
|
+
task,
|
|
219
|
+
fromStatus,
|
|
220
|
+
toStatus,
|
|
221
|
+
frontmatter,
|
|
222
|
+
options
|
|
223
|
+
}) {
|
|
224
|
+
const normalizedReason = typeof options.reason === "string" ? options.reason.trim() : "";
|
|
225
|
+
const reason = normalizedReason || (isRegression(fromStatus, toStatus) ? `regression: ${fromStatus} -> ${toStatus}` : void 0);
|
|
226
|
+
const event = buildTransitionEvent(task.slug, fromStatus, toStatus, {
|
|
227
|
+
confidence: options.confidence,
|
|
228
|
+
reason
|
|
229
|
+
});
|
|
230
|
+
try {
|
|
231
|
+
appendTransition(vaultPath, event);
|
|
232
|
+
} catch {
|
|
233
|
+
return frontmatter;
|
|
234
|
+
}
|
|
235
|
+
if (toStatus !== "blocked" || frontmatter.escalation) {
|
|
236
|
+
return frontmatter;
|
|
237
|
+
}
|
|
238
|
+
let blockedCount = 0;
|
|
239
|
+
try {
|
|
240
|
+
blockedCount = countBlockedTransitions(vaultPath, task.slug);
|
|
241
|
+
} catch {
|
|
242
|
+
return frontmatter;
|
|
243
|
+
}
|
|
244
|
+
if (blockedCount < 3) {
|
|
245
|
+
return frontmatter;
|
|
246
|
+
}
|
|
247
|
+
const escalatedFrontmatter = {
|
|
248
|
+
...frontmatter,
|
|
249
|
+
escalation: true
|
|
250
|
+
};
|
|
251
|
+
try {
|
|
252
|
+
persistTaskFrontmatter(task, escalatedFrontmatter);
|
|
253
|
+
return escalatedFrontmatter;
|
|
254
|
+
} catch {
|
|
255
|
+
return frontmatter;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
46
258
|
function readTask(vaultPath, slug) {
|
|
47
259
|
const taskPath = getTaskPath(vaultPath, slug);
|
|
48
|
-
if (!
|
|
260
|
+
if (!fs2.existsSync(taskPath)) {
|
|
49
261
|
return null;
|
|
50
262
|
}
|
|
51
263
|
try {
|
|
52
|
-
const raw =
|
|
264
|
+
const raw = fs2.readFileSync(taskPath, "utf-8");
|
|
53
265
|
const { data, content } = matter(raw);
|
|
54
266
|
const title = extractTitle(content) || slug;
|
|
55
267
|
return {
|
|
@@ -65,11 +277,11 @@ function readTask(vaultPath, slug) {
|
|
|
65
277
|
}
|
|
66
278
|
function readBacklogItem(vaultPath, slug) {
|
|
67
279
|
const backlogPath = getBacklogPath(vaultPath, slug);
|
|
68
|
-
if (!
|
|
280
|
+
if (!fs2.existsSync(backlogPath)) {
|
|
69
281
|
return null;
|
|
70
282
|
}
|
|
71
283
|
try {
|
|
72
|
-
const raw =
|
|
284
|
+
const raw = fs2.readFileSync(backlogPath, "utf-8");
|
|
73
285
|
const { data, content } = matter(raw);
|
|
74
286
|
const title = extractTitle(content) || slug;
|
|
75
287
|
return {
|
|
@@ -85,11 +297,11 @@ function readBacklogItem(vaultPath, slug) {
|
|
|
85
297
|
}
|
|
86
298
|
function listTasks(vaultPath, filters) {
|
|
87
299
|
const tasksDir = getTasksDir(vaultPath);
|
|
88
|
-
if (!
|
|
300
|
+
if (!fs2.existsSync(tasksDir)) {
|
|
89
301
|
return [];
|
|
90
302
|
}
|
|
91
303
|
const tasks = [];
|
|
92
|
-
const entries =
|
|
304
|
+
const entries = fs2.readdirSync(tasksDir, { withFileTypes: true });
|
|
93
305
|
const today = startOfToday();
|
|
94
306
|
for (const entry of entries) {
|
|
95
307
|
if (!entry.isFile() || !entry.name.endsWith(".md")) {
|
|
@@ -145,11 +357,11 @@ function listTasks(vaultPath, filters) {
|
|
|
145
357
|
}
|
|
146
358
|
function listBacklogItems(vaultPath, filters) {
|
|
147
359
|
const backlogDir = getBacklogDir(vaultPath);
|
|
148
|
-
if (!
|
|
360
|
+
if (!fs2.existsSync(backlogDir)) {
|
|
149
361
|
return [];
|
|
150
362
|
}
|
|
151
363
|
const items = [];
|
|
152
|
-
const entries =
|
|
364
|
+
const entries = fs2.readdirSync(backlogDir, { withFileTypes: true });
|
|
153
365
|
for (const entry of entries) {
|
|
154
366
|
if (!entry.isFile() || !entry.name.endsWith(".md")) {
|
|
155
367
|
continue;
|
|
@@ -171,7 +383,7 @@ function createTask(vaultPath, title, options = {}) {
|
|
|
171
383
|
ensureTasksDir(vaultPath);
|
|
172
384
|
const slug = slugify(title);
|
|
173
385
|
const taskPath = getTaskPath(vaultPath, slug);
|
|
174
|
-
if (
|
|
386
|
+
if (fs2.existsSync(taskPath)) {
|
|
175
387
|
throw new Error(`Task already exists: ${slug}`);
|
|
176
388
|
}
|
|
177
389
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -206,7 +418,7 @@ ${options.content}
|
|
|
206
418
|
`;
|
|
207
419
|
}
|
|
208
420
|
const fileContent = matter.stringify(content, frontmatter);
|
|
209
|
-
|
|
421
|
+
fs2.writeFileSync(taskPath, fileContent);
|
|
210
422
|
return {
|
|
211
423
|
slug,
|
|
212
424
|
title,
|
|
@@ -215,13 +427,17 @@ ${options.content}
|
|
|
215
427
|
path: taskPath
|
|
216
428
|
};
|
|
217
429
|
}
|
|
218
|
-
function updateTask(vaultPath, slug, updates) {
|
|
430
|
+
function updateTask(vaultPath, slug, updates, options = {}) {
|
|
219
431
|
const task = readTask(vaultPath, slug);
|
|
220
432
|
if (!task) {
|
|
221
433
|
throw new Error(`Task not found: ${slug}`);
|
|
222
434
|
}
|
|
435
|
+
if (updates.status !== void 0 && !isTaskStatus(updates.status)) {
|
|
436
|
+
throw new Error(`Invalid task status: ${String(updates.status)}`);
|
|
437
|
+
}
|
|
438
|
+
const previousStatus = task.frontmatter.status;
|
|
223
439
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
224
|
-
|
|
440
|
+
let newFrontmatter = {
|
|
225
441
|
...task.frontmatter,
|
|
226
442
|
updated: now
|
|
227
443
|
};
|
|
@@ -348,41 +564,39 @@ function updateTask(vaultPath, slug, updates) {
|
|
|
348
564
|
} else {
|
|
349
565
|
newFrontmatter.blocked_by = updates.blocked_by;
|
|
350
566
|
}
|
|
351
|
-
} else if (updates.status && updates.status !== "blocked") {
|
|
567
|
+
} else if (updates.status !== void 0 && updates.status !== "blocked") {
|
|
352
568
|
delete newFrontmatter.blocked_by;
|
|
353
569
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
570
|
+
persistTaskFrontmatter(task, newFrontmatter);
|
|
571
|
+
const transition = options.skipTransition ? null : resolveStatusTransition(previousStatus, newFrontmatter.status);
|
|
572
|
+
if (transition) {
|
|
573
|
+
const confidence = options.confidence ?? (typeof updates.confidence === "number" ? updates.confidence : void 0);
|
|
574
|
+
const reason = options.reason ?? updates.reason ?? null;
|
|
575
|
+
newFrontmatter = logStatusTransition({
|
|
576
|
+
vaultPath,
|
|
577
|
+
task,
|
|
578
|
+
fromStatus: transition.fromStatus,
|
|
579
|
+
toStatus: transition.toStatus,
|
|
580
|
+
frontmatter: newFrontmatter,
|
|
581
|
+
options: {
|
|
582
|
+
confidence,
|
|
583
|
+
reason
|
|
584
|
+
}
|
|
585
|
+
});
|
|
365
586
|
}
|
|
366
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
367
|
-
const newFrontmatter = {
|
|
368
|
-
...task.frontmatter,
|
|
369
|
-
status: "done",
|
|
370
|
-
updated: now,
|
|
371
|
-
completed: now
|
|
372
|
-
};
|
|
373
|
-
delete newFrontmatter.blocked_by;
|
|
374
|
-
const fileContent = matter.stringify(task.content, newFrontmatter);
|
|
375
|
-
fs.writeFileSync(task.path, fileContent);
|
|
376
587
|
return {
|
|
377
588
|
...task,
|
|
378
589
|
frontmatter: newFrontmatter
|
|
379
590
|
};
|
|
380
591
|
}
|
|
592
|
+
function completeTask(vaultPath, slug, options = {}) {
|
|
593
|
+
return updateTask(vaultPath, slug, { status: "done" }, options);
|
|
594
|
+
}
|
|
381
595
|
function createBacklogItem(vaultPath, title, options = {}) {
|
|
382
596
|
ensureBacklogDir(vaultPath);
|
|
383
597
|
const slug = slugify(title);
|
|
384
598
|
const backlogPath = getBacklogPath(vaultPath, slug);
|
|
385
|
-
if (
|
|
599
|
+
if (fs2.existsSync(backlogPath)) {
|
|
386
600
|
throw new Error(`Backlog item already exists: ${slug}`);
|
|
387
601
|
}
|
|
388
602
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -408,7 +622,7 @@ ${options.content}
|
|
|
408
622
|
`;
|
|
409
623
|
}
|
|
410
624
|
const fileContent = matter.stringify(content, frontmatter);
|
|
411
|
-
|
|
625
|
+
fs2.writeFileSync(backlogPath, fileContent);
|
|
412
626
|
return {
|
|
413
627
|
slug,
|
|
414
628
|
title,
|
|
@@ -430,7 +644,7 @@ function updateBacklogItem(vaultPath, slug, updates) {
|
|
|
430
644
|
if (updates.tags !== void 0) newFrontmatter.tags = updates.tags;
|
|
431
645
|
if (updates.lastSeen !== void 0) newFrontmatter.lastSeen = updates.lastSeen;
|
|
432
646
|
const fileContent = matter.stringify(backlogItem.content, newFrontmatter);
|
|
433
|
-
|
|
647
|
+
fs2.writeFileSync(backlogItem.path, fileContent);
|
|
434
648
|
return {
|
|
435
649
|
...backlogItem,
|
|
436
650
|
frontmatter: newFrontmatter
|
|
@@ -450,7 +664,7 @@ function promoteBacklogItem(vaultPath, slug, options = {}) {
|
|
|
450
664
|
// Remove title from content
|
|
451
665
|
tags: backlogItem.frontmatter.tags
|
|
452
666
|
});
|
|
453
|
-
|
|
667
|
+
fs2.unlinkSync(backlogItem.path);
|
|
454
668
|
return task;
|
|
455
669
|
}
|
|
456
670
|
function getBlockedTasks(vaultPath, project) {
|
|
@@ -509,6 +723,13 @@ function getStatusDisplay(status) {
|
|
|
509
723
|
}
|
|
510
724
|
|
|
511
725
|
export {
|
|
726
|
+
isRegression,
|
|
727
|
+
appendTransition,
|
|
728
|
+
buildTransitionEvent,
|
|
729
|
+
readAllTransitions,
|
|
730
|
+
queryTransitions,
|
|
731
|
+
countBlockedTransitions,
|
|
732
|
+
formatTransitionsTable,
|
|
512
733
|
slugify,
|
|
513
734
|
getTasksDir,
|
|
514
735
|
getBacklogDir,
|