@jvittechs/j 1.0.57 → 1.0.59
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/dist/chunk-3MKBSVAL.js +43 -0
- package/dist/chunk-3MKBSVAL.js.map +1 -0
- package/dist/chunk-BMDRQFY7.js +247 -0
- package/dist/chunk-BMDRQFY7.js.map +1 -0
- package/dist/{chunk-FZBVI5AX.js → chunk-SDYQQ4ZY.js} +517 -17
- package/dist/chunk-SDYQQ4ZY.js.map +1 -0
- package/dist/cli.js +881 -776
- package/dist/cli.js.map +1 -1
- package/dist/show-FGCDTIU7.js +8 -0
- package/dist/{summary-4J2OCCSA.js → summary-FIUGSB65.js} +3 -2
- package/dist/summary-FIUGSB65.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-FZBVI5AX.js.map +0 -1
- /package/dist/{summary-4J2OCCSA.js.map → show-FGCDTIU7.js.map} +0 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SettingsService
|
|
3
|
+
} from "./chunk-BMDRQFY7.js";
|
|
4
|
+
|
|
1
5
|
// src/commands/tasks/summary.ts
|
|
2
6
|
import { Command } from "commander";
|
|
3
7
|
import chalk2 from "chalk";
|
|
4
8
|
import boxen from "boxen";
|
|
5
9
|
|
|
6
10
|
// src/services/task.service.ts
|
|
7
|
-
import { promises as
|
|
8
|
-
import { join, dirname } from "path";
|
|
11
|
+
import { promises as fs2, existsSync } from "fs";
|
|
12
|
+
import { join as join2, dirname } from "path";
|
|
9
13
|
import { execSync } from "child_process";
|
|
10
14
|
import chalk from "chalk";
|
|
11
15
|
|
|
@@ -13,6 +17,7 @@ import chalk from "chalk";
|
|
|
13
17
|
import { z } from "zod";
|
|
14
18
|
var TaskSchema = z.object({
|
|
15
19
|
id: z.string().regex(/^T-\d+$/),
|
|
20
|
+
type: z.enum(["feature", "bug", "plan", "task", "prd", "prompt"]).default("task"),
|
|
16
21
|
parent: z.string().default(""),
|
|
17
22
|
title: z.string().min(1).max(500),
|
|
18
23
|
status: z.enum(["todo", "in_progress", "done", "cancelled"]),
|
|
@@ -46,20 +51,427 @@ var PRIORITY_LABELS = {
|
|
|
46
51
|
3: "Low"
|
|
47
52
|
};
|
|
48
53
|
|
|
54
|
+
// src/services/config.service.ts
|
|
55
|
+
import { promises as fs } from "fs";
|
|
56
|
+
import { join } from "path";
|
|
57
|
+
import { homedir } from "os";
|
|
58
|
+
var ConfigService = class {
|
|
59
|
+
configDir;
|
|
60
|
+
configPath;
|
|
61
|
+
constructor() {
|
|
62
|
+
this.configDir = join(homedir(), ".jai1");
|
|
63
|
+
this.configPath = join(this.configDir, "config.json");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if config file exists
|
|
67
|
+
*/
|
|
68
|
+
async exists() {
|
|
69
|
+
try {
|
|
70
|
+
await fs.access(this.configPath);
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Load configuration from file
|
|
78
|
+
* @returns Config object or null if not found
|
|
79
|
+
*/
|
|
80
|
+
async load() {
|
|
81
|
+
if (!await this.exists()) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const content = await fs.readFile(this.configPath, "utf-8");
|
|
86
|
+
return JSON.parse(content);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Failed to load config: ${error instanceof Error ? error.message : String(error)}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Save configuration to file
|
|
95
|
+
* Creates directory if it doesn't exist
|
|
96
|
+
* Sets proper file permissions (600)
|
|
97
|
+
*/
|
|
98
|
+
async save(config) {
|
|
99
|
+
try {
|
|
100
|
+
await fs.mkdir(this.configDir, { recursive: true, mode: 448 });
|
|
101
|
+
await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), {
|
|
102
|
+
mode: 384
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Failed to save config: ${error instanceof Error ? error.message : String(error)}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get config file path
|
|
112
|
+
*/
|
|
113
|
+
getConfigPath() {
|
|
114
|
+
return this.configPath;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get config directory path
|
|
118
|
+
*/
|
|
119
|
+
getConfigDir() {
|
|
120
|
+
return this.configDir;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/services/cloud-task-provider.ts
|
|
125
|
+
function mapCloudToLocal(ct) {
|
|
126
|
+
return {
|
|
127
|
+
id: ct.task_id,
|
|
128
|
+
type: ct.type,
|
|
129
|
+
parent: ct.parent || "",
|
|
130
|
+
title: ct.title,
|
|
131
|
+
status: ct.status,
|
|
132
|
+
assigned_to: ct.assigned_to || "",
|
|
133
|
+
claimed_at: ct.claimed_at || "",
|
|
134
|
+
priority: ct.priority ?? 2,
|
|
135
|
+
depends_on: Array.isArray(ct.depends_on) ? ct.depends_on : parseJsonArray(ct.depends_on),
|
|
136
|
+
tags: Array.isArray(ct.tags) ? ct.tags : parseJsonArray(ct.tags),
|
|
137
|
+
branch: ct.branch || "",
|
|
138
|
+
notes: ct.notes || "",
|
|
139
|
+
created: ct.created_at?.split("T")[0] || ct.created_at || "",
|
|
140
|
+
updated: ct.updated_at?.split("T")[0] || ct.updated_at || ""
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function parseJsonArray(val) {
|
|
144
|
+
if (Array.isArray(val)) return val;
|
|
145
|
+
if (typeof val === "string") {
|
|
146
|
+
try {
|
|
147
|
+
return JSON.parse(val);
|
|
148
|
+
} catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
function mapLocalToCloud(task) {
|
|
155
|
+
return {
|
|
156
|
+
task_id: task.id,
|
|
157
|
+
title: task.title,
|
|
158
|
+
type: task.type || "task",
|
|
159
|
+
parent: task.parent || "",
|
|
160
|
+
status: task.status,
|
|
161
|
+
assigned_to: task.assigned_to || "",
|
|
162
|
+
claimed_at: task.claimed_at || "",
|
|
163
|
+
priority: task.priority ?? 2,
|
|
164
|
+
depends_on: task.depends_on || [],
|
|
165
|
+
tags: task.tags || [],
|
|
166
|
+
branch: task.branch || "",
|
|
167
|
+
notes: task.notes || ""
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
var CloudTaskProvider = class {
|
|
171
|
+
apiUrl;
|
|
172
|
+
accessKey;
|
|
173
|
+
projectId;
|
|
174
|
+
constructor(config, projectId) {
|
|
175
|
+
this.apiUrl = config.apiUrl.replace(/\/$/, "");
|
|
176
|
+
this.accessKey = config.accessKey;
|
|
177
|
+
this.projectId = projectId;
|
|
178
|
+
}
|
|
179
|
+
// ============================================
|
|
180
|
+
// HELPERS
|
|
181
|
+
// ============================================
|
|
182
|
+
headers() {
|
|
183
|
+
return {
|
|
184
|
+
"Content-Type": "application/json",
|
|
185
|
+
"JAI1-Access-Key": this.accessKey
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
async request(path, options = {}) {
|
|
189
|
+
const url = `${this.apiUrl}${path}`;
|
|
190
|
+
const res = await fetch(url, {
|
|
191
|
+
...options,
|
|
192
|
+
headers: { ...this.headers(), ...options.headers || {} }
|
|
193
|
+
});
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
let errMsg = `API error ${res.status}`;
|
|
196
|
+
try {
|
|
197
|
+
const body = await res.json();
|
|
198
|
+
if (body.error) errMsg = body.error;
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
throw new Error(errMsg);
|
|
202
|
+
}
|
|
203
|
+
const data = await res.json();
|
|
204
|
+
if (!data.success) throw new Error(data.error || "API returned failure");
|
|
205
|
+
return data.data;
|
|
206
|
+
}
|
|
207
|
+
// ============================================
|
|
208
|
+
// ENSURE PROJECT REGISTERED
|
|
209
|
+
// ============================================
|
|
210
|
+
async ensureProjectRegistered(repoUrl) {
|
|
211
|
+
const result = await this.request(
|
|
212
|
+
"/api/projects/register",
|
|
213
|
+
{
|
|
214
|
+
method: "POST",
|
|
215
|
+
body: JSON.stringify({ repo_url: repoUrl })
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
return result.project_id;
|
|
219
|
+
}
|
|
220
|
+
// ============================================
|
|
221
|
+
// CRUD
|
|
222
|
+
// ============================================
|
|
223
|
+
async readAll() {
|
|
224
|
+
const rows = await this.request(
|
|
225
|
+
`/api/tasks?projectId=${encodeURIComponent(this.projectId)}`
|
|
226
|
+
);
|
|
227
|
+
return rows.map(mapCloudToLocal);
|
|
228
|
+
}
|
|
229
|
+
async add(input) {
|
|
230
|
+
const all = await this.readAll().catch(() => []);
|
|
231
|
+
const maxNum = all.reduce((max, t) => {
|
|
232
|
+
const n = parseInt(t.id.replace("T-", ""), 10);
|
|
233
|
+
return isNaN(n) ? max : Math.max(max, n);
|
|
234
|
+
}, 0);
|
|
235
|
+
const taskId = `T-${String(maxNum + 1).padStart(3, "0")}`;
|
|
236
|
+
const row = await this.request("/api/tasks", {
|
|
237
|
+
method: "POST",
|
|
238
|
+
body: JSON.stringify({
|
|
239
|
+
task_id: taskId,
|
|
240
|
+
project_id: this.projectId,
|
|
241
|
+
title: input.title,
|
|
242
|
+
type: input.type || "task",
|
|
243
|
+
parent: input.parent || "",
|
|
244
|
+
priority: input.priority ?? 2,
|
|
245
|
+
depends_on: input.depends_on || [],
|
|
246
|
+
tags: input.tags || [],
|
|
247
|
+
branch: input.branch || "",
|
|
248
|
+
notes: input.notes || ""
|
|
249
|
+
})
|
|
250
|
+
});
|
|
251
|
+
return mapCloudToLocal(row);
|
|
252
|
+
}
|
|
253
|
+
async update(id, updates) {
|
|
254
|
+
const row = await this.request(
|
|
255
|
+
`/api/tasks/${encodeURIComponent(id)}?projectId=${encodeURIComponent(this.projectId)}`,
|
|
256
|
+
{
|
|
257
|
+
method: "PATCH",
|
|
258
|
+
body: JSON.stringify(updates)
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
return mapCloudToLocal(row);
|
|
262
|
+
}
|
|
263
|
+
async deleteTask(id) {
|
|
264
|
+
await this.request(
|
|
265
|
+
`/api/tasks/${encodeURIComponent(id)}?projectId=${encodeURIComponent(this.projectId)}`,
|
|
266
|
+
{ method: "DELETE" }
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
async deleteGroup(parent) {
|
|
270
|
+
await this.request(
|
|
271
|
+
`/api/tasks/group/${encodeURIComponent(parent)}?projectId=${encodeURIComponent(this.projectId)}`,
|
|
272
|
+
{ method: "DELETE" }
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
// ============================================
|
|
276
|
+
// QUERY
|
|
277
|
+
// ============================================
|
|
278
|
+
async list(options) {
|
|
279
|
+
const params = new URLSearchParams({ projectId: this.projectId });
|
|
280
|
+
if (options?.status) params.set("status", options.status);
|
|
281
|
+
if (options?.type) params.set("type", options.type);
|
|
282
|
+
if (options?.parent !== void 0) params.set("parent", options.parent);
|
|
283
|
+
const rows = await this.request(`/api/tasks?${params}`);
|
|
284
|
+
return rows.map(mapCloudToLocal);
|
|
285
|
+
}
|
|
286
|
+
async getById(id) {
|
|
287
|
+
try {
|
|
288
|
+
const row = await this.request(
|
|
289
|
+
`/api/tasks/${encodeURIComponent(id)}?projectId=${encodeURIComponent(this.projectId)}`
|
|
290
|
+
);
|
|
291
|
+
return mapCloudToLocal(row);
|
|
292
|
+
} catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async getParents() {
|
|
297
|
+
const parents = await this.request(
|
|
298
|
+
`/api/tasks/parents?projectId=${encodeURIComponent(this.projectId)}`
|
|
299
|
+
);
|
|
300
|
+
const result = [];
|
|
301
|
+
for (const parent of parents) {
|
|
302
|
+
const tasks = await this.list({ parent });
|
|
303
|
+
const info = {
|
|
304
|
+
name: parent,
|
|
305
|
+
total: tasks.length,
|
|
306
|
+
todo: tasks.filter((t) => t.status === "todo").length,
|
|
307
|
+
in_progress: tasks.filter((t) => t.status === "in_progress").length,
|
|
308
|
+
done: tasks.filter((t) => t.status === "done").length,
|
|
309
|
+
cancelled: tasks.filter((t) => t.status === "cancelled").length,
|
|
310
|
+
blocked: 0,
|
|
311
|
+
ready: 0,
|
|
312
|
+
status: "todo"
|
|
313
|
+
};
|
|
314
|
+
if (info.done + info.cancelled === info.total) info.status = "done";
|
|
315
|
+
else if (info.in_progress > 0) info.status = "in_progress";
|
|
316
|
+
else if (info.todo > 0) info.status = "ready";
|
|
317
|
+
result.push(info);
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
async getStats() {
|
|
322
|
+
const raw = await this.request(
|
|
323
|
+
`/api/tasks/stats?projectId=${encodeURIComponent(this.projectId)}`
|
|
324
|
+
);
|
|
325
|
+
const s = raw.by_status;
|
|
326
|
+
const total = Object.values(s).reduce((a, b) => a + b, 0);
|
|
327
|
+
return {
|
|
328
|
+
total,
|
|
329
|
+
todo: s["todo"] || 0,
|
|
330
|
+
in_progress: s["in_progress"] || 0,
|
|
331
|
+
done: s["done"] || 0,
|
|
332
|
+
cancelled: s["cancelled"] || 0
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
// ============================================
|
|
336
|
+
// WORKFLOW
|
|
337
|
+
// ============================================
|
|
338
|
+
async getReady() {
|
|
339
|
+
const rows = await this.request(
|
|
340
|
+
`/api/tasks/ready?projectId=${encodeURIComponent(this.projectId)}`
|
|
341
|
+
);
|
|
342
|
+
return rows.map(mapCloudToLocal);
|
|
343
|
+
}
|
|
344
|
+
async pick(id, agentId) {
|
|
345
|
+
const row = await this.request(
|
|
346
|
+
`/api/tasks/${encodeURIComponent(id)}/pick`,
|
|
347
|
+
{
|
|
348
|
+
method: "POST",
|
|
349
|
+
body: JSON.stringify({
|
|
350
|
+
project_id: this.projectId,
|
|
351
|
+
agent_id: agentId || "cli"
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
return mapCloudToLocal(row);
|
|
356
|
+
}
|
|
357
|
+
async markDone(id) {
|
|
358
|
+
const row = await this.request(
|
|
359
|
+
`/api/tasks/${encodeURIComponent(id)}/done`,
|
|
360
|
+
{
|
|
361
|
+
method: "POST",
|
|
362
|
+
body: JSON.stringify({ project_id: this.projectId })
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
return mapCloudToLocal(row);
|
|
366
|
+
}
|
|
367
|
+
async cancel(id) {
|
|
368
|
+
await this.request(`/api/tasks/${encodeURIComponent(id)}/cancel`, {
|
|
369
|
+
method: "POST",
|
|
370
|
+
body: JSON.stringify({ project_id: this.projectId })
|
|
371
|
+
});
|
|
372
|
+
const task = await this.getById(id);
|
|
373
|
+
return task;
|
|
374
|
+
}
|
|
375
|
+
// ============================================
|
|
376
|
+
// SYNC
|
|
377
|
+
// ============================================
|
|
378
|
+
async pull() {
|
|
379
|
+
return this.readAll();
|
|
380
|
+
}
|
|
381
|
+
async push(tasks) {
|
|
382
|
+
for (const task of tasks) {
|
|
383
|
+
const cloudFields = mapLocalToCloud(task);
|
|
384
|
+
try {
|
|
385
|
+
await this.request(
|
|
386
|
+
`/api/tasks/${encodeURIComponent(task.id)}?projectId=${encodeURIComponent(this.projectId)}`,
|
|
387
|
+
{ method: "PATCH", body: JSON.stringify(cloudFields) }
|
|
388
|
+
);
|
|
389
|
+
} catch {
|
|
390
|
+
try {
|
|
391
|
+
await this.request("/api/tasks", {
|
|
392
|
+
method: "POST",
|
|
393
|
+
body: JSON.stringify({
|
|
394
|
+
...cloudFields,
|
|
395
|
+
project_id: this.projectId
|
|
396
|
+
})
|
|
397
|
+
});
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
49
405
|
// src/services/task.service.ts
|
|
50
406
|
var TASKS_FILE = ".jai1/tasks.jsonl";
|
|
51
407
|
var SYNC_BRANCH = "jai1";
|
|
52
408
|
var TaskService = class {
|
|
53
409
|
tasksPath;
|
|
410
|
+
cwd;
|
|
411
|
+
_cloudProvider = null;
|
|
412
|
+
_providerReady;
|
|
54
413
|
constructor(cwd) {
|
|
55
|
-
this.
|
|
414
|
+
this.cwd = cwd || process.cwd();
|
|
415
|
+
this.tasksPath = join2(this.cwd, TASKS_FILE);
|
|
416
|
+
this._providerReady = this._initCloudProvider();
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Initialise cloud provider if settings.yaml has tasks.cloud = true.
|
|
420
|
+
* Auto-registers the project on the server on first use.
|
|
421
|
+
*/
|
|
422
|
+
async _initCloudProvider() {
|
|
423
|
+
try {
|
|
424
|
+
const settings = new SettingsService(this.cwd);
|
|
425
|
+
if (!settings.isTaskCloudEnabled()) return;
|
|
426
|
+
const config = await new ConfigService().load();
|
|
427
|
+
if (!config?.apiUrl || !config?.accessKey) return;
|
|
428
|
+
const repoUrl = settings.resolveGitRepoUrl();
|
|
429
|
+
if (!repoUrl) return;
|
|
430
|
+
let projectId = settings.getProjectId();
|
|
431
|
+
if (!projectId) return;
|
|
432
|
+
const provider = new CloudTaskProvider(config, projectId);
|
|
433
|
+
try {
|
|
434
|
+
const registeredId = await provider.ensureProjectRegistered(repoUrl);
|
|
435
|
+
if (registeredId && registeredId !== projectId) {
|
|
436
|
+
projectId = registeredId;
|
|
437
|
+
await settings.set("tasks.projectId", registeredId);
|
|
438
|
+
this._cloudProvider = new CloudTaskProvider(config, registeredId);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
this._cloudProvider = provider;
|
|
444
|
+
} catch {
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Wait for provider initialisation (call before any cloud operation)
|
|
449
|
+
*/
|
|
450
|
+
async waitForInit() {
|
|
451
|
+
await this._providerReady;
|
|
452
|
+
}
|
|
453
|
+
/** @internal alias kept for backward compat */
|
|
454
|
+
async ready() {
|
|
455
|
+
await this._providerReady;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Returns true when cloud mode is active
|
|
459
|
+
*/
|
|
460
|
+
get isCloud() {
|
|
461
|
+
return this._cloudProvider !== null;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Returns cloud provider if active
|
|
465
|
+
*/
|
|
466
|
+
get cloud() {
|
|
467
|
+
return this._cloudProvider;
|
|
56
468
|
}
|
|
57
469
|
/**
|
|
58
470
|
* Check if .jai1 directory exists in CWD.
|
|
59
471
|
* If not, print a helpful message and exit.
|
|
60
472
|
*/
|
|
61
473
|
static ensureJai1Dir(cwd) {
|
|
62
|
-
const dir =
|
|
474
|
+
const dir = join2(cwd || process.cwd(), ".jai1");
|
|
63
475
|
if (!existsSync(dir)) {
|
|
64
476
|
console.error(chalk.red("\u274C Th\u01B0 m\u1EE5c .jai1 kh\xF4ng t\u1ED3n t\u1EA1i trong project n\xE0y."));
|
|
65
477
|
console.error("");
|
|
@@ -76,12 +488,16 @@ var TaskService = class {
|
|
|
76
488
|
// READ
|
|
77
489
|
// ============================================
|
|
78
490
|
/**
|
|
79
|
-
* Read all tasks from JSONL file
|
|
491
|
+
* Read all tasks from JSONL file (or cloud API if cloud mode)
|
|
80
492
|
*/
|
|
81
493
|
async readAll() {
|
|
494
|
+
await this.ready();
|
|
495
|
+
if (this._cloudProvider) {
|
|
496
|
+
return this._cloudProvider.readAll();
|
|
497
|
+
}
|
|
82
498
|
try {
|
|
83
499
|
await this.ensureTasksFileNotDirectory();
|
|
84
|
-
const content = await
|
|
500
|
+
const content = await fs2.readFile(this.tasksPath, "utf-8");
|
|
85
501
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
86
502
|
return lines.map((line) => TaskSchema.parse(JSON.parse(line)));
|
|
87
503
|
} catch (error) {
|
|
@@ -95,6 +511,10 @@ var TaskService = class {
|
|
|
95
511
|
* Find task by ID
|
|
96
512
|
*/
|
|
97
513
|
async findById(id) {
|
|
514
|
+
await this.ready();
|
|
515
|
+
if (this._cloudProvider) {
|
|
516
|
+
return this._cloudProvider.getById(id);
|
|
517
|
+
}
|
|
98
518
|
const tasks = await this.readAll();
|
|
99
519
|
return tasks.find((t) => t.id === id) || null;
|
|
100
520
|
}
|
|
@@ -102,6 +522,17 @@ var TaskService = class {
|
|
|
102
522
|
* Filter tasks by criteria
|
|
103
523
|
*/
|
|
104
524
|
async filter(criteria) {
|
|
525
|
+
await this.ready();
|
|
526
|
+
if (this._cloudProvider) {
|
|
527
|
+
const listInput = {};
|
|
528
|
+
if (criteria.status) listInput.status = criteria.status;
|
|
529
|
+
if (criteria.parent) listInput.parent = criteria.parent;
|
|
530
|
+
const tasks2 = await this._cloudProvider.list(listInput);
|
|
531
|
+
if (criteria.assignee) {
|
|
532
|
+
return tasks2.filter((t) => t.assigned_to === criteria.assignee);
|
|
533
|
+
}
|
|
534
|
+
return tasks2;
|
|
535
|
+
}
|
|
105
536
|
const tasks = await this.readAll();
|
|
106
537
|
return tasks.filter((t) => {
|
|
107
538
|
if (criteria.status && t.status !== criteria.status) return false;
|
|
@@ -115,6 +546,12 @@ var TaskService = class {
|
|
|
115
546
|
* status=todo, all depends_on done or cancelled, assigned_to empty
|
|
116
547
|
*/
|
|
117
548
|
async getReady(parent) {
|
|
549
|
+
await this.ready();
|
|
550
|
+
if (this._cloudProvider) {
|
|
551
|
+
const ready = await this._cloudProvider.getReady();
|
|
552
|
+
if (parent) return ready.filter((t) => t.parent === parent);
|
|
553
|
+
return ready;
|
|
554
|
+
}
|
|
118
555
|
const tasks = await this.readAll();
|
|
119
556
|
const resolvedIds = new Set(
|
|
120
557
|
tasks.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
@@ -147,6 +584,11 @@ var TaskService = class {
|
|
|
147
584
|
* Get stats by status
|
|
148
585
|
*/
|
|
149
586
|
async getStats() {
|
|
587
|
+
await this.ready();
|
|
588
|
+
if (this._cloudProvider) {
|
|
589
|
+
const stats = await this._cloudProvider.getStats();
|
|
590
|
+
return { ...stats, blocked: 0 };
|
|
591
|
+
}
|
|
150
592
|
const tasks = await this.readAll();
|
|
151
593
|
const resolvedIds = new Set(
|
|
152
594
|
tasks.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
@@ -176,6 +618,12 @@ var TaskService = class {
|
|
|
176
618
|
* todo = otherwise (blocked or waiting)
|
|
177
619
|
*/
|
|
178
620
|
async getParents(statusFilter) {
|
|
621
|
+
await this.ready();
|
|
622
|
+
if (this._cloudProvider) {
|
|
623
|
+
const parents2 = await this._cloudProvider.getParents();
|
|
624
|
+
if (statusFilter) return parents2.filter((p) => p.status === statusFilter);
|
|
625
|
+
return parents2;
|
|
626
|
+
}
|
|
179
627
|
const tasks = await this.readAll();
|
|
180
628
|
const resolvedIds = new Set(
|
|
181
629
|
tasks.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
@@ -252,10 +700,24 @@ var TaskService = class {
|
|
|
252
700
|
* Add a new task
|
|
253
701
|
*/
|
|
254
702
|
async add(data) {
|
|
703
|
+
await this.ready();
|
|
704
|
+
if (this._cloudProvider) {
|
|
705
|
+
const input = {
|
|
706
|
+
title: data.title,
|
|
707
|
+
...data.type !== void 0 && { type: data.type },
|
|
708
|
+
...data.parent !== void 0 && { parent: data.parent },
|
|
709
|
+
...data.priority !== void 0 && { priority: data.priority },
|
|
710
|
+
...data.tags !== void 0 && { tags: data.tags },
|
|
711
|
+
...data.notes !== void 0 && { notes: data.notes },
|
|
712
|
+
...data.branch !== void 0 && { branch: data.branch }
|
|
713
|
+
};
|
|
714
|
+
return this._cloudProvider.add(input);
|
|
715
|
+
}
|
|
255
716
|
const id = await this.nextId();
|
|
256
717
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
257
718
|
const task = TaskSchema.parse({
|
|
258
719
|
id,
|
|
720
|
+
type: data.type || "task",
|
|
259
721
|
parent: data.parent || "",
|
|
260
722
|
title: data.title,
|
|
261
723
|
status: "todo",
|
|
@@ -266,8 +728,8 @@ var TaskService = class {
|
|
|
266
728
|
created: now,
|
|
267
729
|
updated: now,
|
|
268
730
|
tags: data.tags || [],
|
|
269
|
-
branch: "",
|
|
270
|
-
notes: ""
|
|
731
|
+
branch: data.branch || "",
|
|
732
|
+
notes: data.notes || ""
|
|
271
733
|
});
|
|
272
734
|
await this.appendTask(task);
|
|
273
735
|
return task;
|
|
@@ -276,6 +738,10 @@ var TaskService = class {
|
|
|
276
738
|
* Update a task by ID
|
|
277
739
|
*/
|
|
278
740
|
async update(id, changes) {
|
|
741
|
+
await this.ready();
|
|
742
|
+
if (this._cloudProvider) {
|
|
743
|
+
return this._cloudProvider.update(id, changes);
|
|
744
|
+
}
|
|
279
745
|
const tasks = await this.readAll();
|
|
280
746
|
const index = tasks.findIndex((t) => t.id === id);
|
|
281
747
|
if (index === -1) {
|
|
@@ -291,6 +757,7 @@ var TaskService = class {
|
|
|
291
757
|
* Add dependency: child depends on parent
|
|
292
758
|
*/
|
|
293
759
|
async addDependency(childId, parentId) {
|
|
760
|
+
await this.ready();
|
|
294
761
|
const tasks = await this.readAll();
|
|
295
762
|
const child = tasks.find((t) => t.id === childId);
|
|
296
763
|
const parent = tasks.find((t) => t.id === parentId);
|
|
@@ -311,6 +778,10 @@ var TaskService = class {
|
|
|
311
778
|
* Mark task as done
|
|
312
779
|
*/
|
|
313
780
|
async markDone(id) {
|
|
781
|
+
await this.ready();
|
|
782
|
+
if (this._cloudProvider) {
|
|
783
|
+
return this._cloudProvider.markDone(id);
|
|
784
|
+
}
|
|
314
785
|
return this.update(id, { status: "done" });
|
|
315
786
|
}
|
|
316
787
|
/**
|
|
@@ -324,6 +795,10 @@ var TaskService = class {
|
|
|
324
795
|
* Cancel a task: set status=cancelled, clear assignment
|
|
325
796
|
*/
|
|
326
797
|
async cancel(id) {
|
|
798
|
+
await this.ready();
|
|
799
|
+
if (this._cloudProvider) {
|
|
800
|
+
return this._cloudProvider.cancel(id);
|
|
801
|
+
}
|
|
327
802
|
return this.update(id, {
|
|
328
803
|
status: "cancelled",
|
|
329
804
|
assigned_to: "",
|
|
@@ -334,6 +809,10 @@ var TaskService = class {
|
|
|
334
809
|
* Delete a task completely and clean up depends_on references
|
|
335
810
|
*/
|
|
336
811
|
async deleteTask(id) {
|
|
812
|
+
await this.ready();
|
|
813
|
+
if (this._cloudProvider) {
|
|
814
|
+
return this._cloudProvider.deleteTask(id);
|
|
815
|
+
}
|
|
337
816
|
return this.deleteTasks([id]);
|
|
338
817
|
}
|
|
339
818
|
/**
|
|
@@ -341,6 +820,13 @@ var TaskService = class {
|
|
|
341
820
|
* Validates all IDs exist before deleting any.
|
|
342
821
|
*/
|
|
343
822
|
async deleteTasks(ids) {
|
|
823
|
+
await this.ready();
|
|
824
|
+
if (this._cloudProvider) {
|
|
825
|
+
for (const id of ids) {
|
|
826
|
+
await this._cloudProvider.deleteTask(id);
|
|
827
|
+
}
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
344
830
|
const tasks = await this.readAll();
|
|
345
831
|
const deleteSet = new Set(ids);
|
|
346
832
|
const notFound = ids.filter((id) => !tasks.find((t) => t.id === id));
|
|
@@ -363,6 +849,15 @@ var TaskService = class {
|
|
|
363
849
|
* Returns the list of deleted tasks for display purposes.
|
|
364
850
|
*/
|
|
365
851
|
async deleteGroup(parentName) {
|
|
852
|
+
await this.ready();
|
|
853
|
+
if (this._cloudProvider) {
|
|
854
|
+
const groupTasks2 = await this._cloudProvider.list({ parent: parentName });
|
|
855
|
+
if (groupTasks2.length === 0) {
|
|
856
|
+
throw new Error(`No tasks found in group: ${parentName}`);
|
|
857
|
+
}
|
|
858
|
+
await this._cloudProvider.deleteGroup(parentName);
|
|
859
|
+
return groupTasks2;
|
|
860
|
+
}
|
|
366
861
|
const tasks = await this.readAll();
|
|
367
862
|
const groupTasks = tasks.filter((t) => t.parent === parentName);
|
|
368
863
|
if (groupTasks.length === 0) {
|
|
@@ -376,6 +871,10 @@ var TaskService = class {
|
|
|
376
871
|
* Pick next task: claim for current user
|
|
377
872
|
*/
|
|
378
873
|
async pick(taskId) {
|
|
874
|
+
await this.ready();
|
|
875
|
+
if (this._cloudProvider) {
|
|
876
|
+
return this._cloudProvider.pick(taskId, this.getCurrentUser());
|
|
877
|
+
}
|
|
379
878
|
const username = this.getCurrentUser();
|
|
380
879
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
381
880
|
return this.update(taskId, {
|
|
@@ -469,9 +968,9 @@ var TaskService = class {
|
|
|
469
968
|
* - Dirty working tree → stash/unstash automatically
|
|
470
969
|
*/
|
|
471
970
|
async syncPush() {
|
|
472
|
-
const cwd =
|
|
971
|
+
const cwd = this.cwd;
|
|
473
972
|
const branch = SYNC_BRANCH;
|
|
474
|
-
const tasksFullPath =
|
|
973
|
+
const tasksFullPath = join2(cwd, TASKS_FILE);
|
|
475
974
|
if (!existsSync(tasksFullPath)) {
|
|
476
975
|
return;
|
|
477
976
|
}
|
|
@@ -535,7 +1034,7 @@ var TaskService = class {
|
|
|
535
1034
|
* - tasks.jsonl doesn't exist on remote branch → returns empty
|
|
536
1035
|
*/
|
|
537
1036
|
async syncPull() {
|
|
538
|
-
const cwd =
|
|
1037
|
+
const cwd = this.cwd;
|
|
539
1038
|
const branch = SYNC_BRANCH;
|
|
540
1039
|
let merged = 0;
|
|
541
1040
|
let conflicts = 0;
|
|
@@ -581,16 +1080,16 @@ var TaskService = class {
|
|
|
581
1080
|
async appendTask(task) {
|
|
582
1081
|
await this.ensureTasksFileNotDirectory();
|
|
583
1082
|
const dir = dirname(this.tasksPath);
|
|
584
|
-
await
|
|
1083
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
585
1084
|
const line = JSON.stringify(task) + "\n";
|
|
586
|
-
await
|
|
1085
|
+
await fs2.appendFile(this.tasksPath, line, "utf-8");
|
|
587
1086
|
}
|
|
588
1087
|
async writeAll(tasks) {
|
|
589
1088
|
await this.ensureTasksFileNotDirectory();
|
|
590
1089
|
const dir = dirname(this.tasksPath);
|
|
591
|
-
await
|
|
1090
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
592
1091
|
const content = tasks.map((t) => JSON.stringify(t)).join("\n") + "\n";
|
|
593
|
-
await
|
|
1092
|
+
await fs2.writeFile(this.tasksPath, content, "utf-8");
|
|
594
1093
|
}
|
|
595
1094
|
getCurrentUser() {
|
|
596
1095
|
try {
|
|
@@ -618,7 +1117,7 @@ var TaskService = class {
|
|
|
618
1117
|
}
|
|
619
1118
|
async ensureTasksFileNotDirectory() {
|
|
620
1119
|
try {
|
|
621
|
-
const stat = await
|
|
1120
|
+
const stat = await fs2.stat(this.tasksPath);
|
|
622
1121
|
if (stat.isDirectory()) {
|
|
623
1122
|
throw new Error(
|
|
624
1123
|
`Invalid tasks path: expected file at ${TASKS_FILE} but found a directory. Please remove the directory and re-run the command.`
|
|
@@ -685,6 +1184,7 @@ function createTaskSummaryCommand() {
|
|
|
685
1184
|
}
|
|
686
1185
|
|
|
687
1186
|
export {
|
|
1187
|
+
ConfigService,
|
|
688
1188
|
STATUS_ICONS,
|
|
689
1189
|
BLOCKED_ICON,
|
|
690
1190
|
PRIORITY_ICONS,
|
|
@@ -693,4 +1193,4 @@ export {
|
|
|
693
1193
|
handleTaskSummary,
|
|
694
1194
|
createTaskSummaryCommand
|
|
695
1195
|
};
|
|
696
|
-
//# sourceMappingURL=chunk-
|
|
1196
|
+
//# sourceMappingURL=chunk-SDYQQ4ZY.js.map
|