@industry-theme/backlogmd-kanban-panel 1.2.8 → 1.2.10
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/panels.bundle.js +3 -2150
- package/dist/panels.bundle.js.map +1 -1
- package/package.json +3 -2
package/dist/panels.bundle.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __defNormalProp = (obj, key2, value) => key2 in obj ? __defProp(obj, key2, { enumerable: true, configurable: true, writable: true, value }) : obj[key2] = value;
|
|
3
|
-
var __publicField = (obj, key2, value) => __defNormalProp(obj, typeof key2 !== "symbol" ? key2 + "" : key2, value);
|
|
4
1
|
import * as React2 from "react";
|
|
5
2
|
import React2__default, { useLayoutEffect, useEffect, useRef, useMemo, useCallback, useState, createContext, memo, useReducer, useContext, forwardRef, cloneElement, createElement, useImperativeHandle } from "react";
|
|
6
3
|
import { unstable_batchedUpdates, createPortal } from "react-dom";
|
|
7
4
|
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
8
|
-
import {
|
|
5
|
+
import { DEFAULT_TASK_STATUSES, Core } from "@backlog-md/core";
|
|
6
|
+
import { trace, context, SpanStatusCode } from "@opentelemetry/api";
|
|
9
7
|
const canUseDOM = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
|
|
10
8
|
function isWindow(element2) {
|
|
11
9
|
const elementString = Object.prototype.toString.call(element2);
|
|
@@ -6338,2152 +6336,7 @@ function gt(o, e, t, r2) {
|
|
|
6338
6336
|
};
|
|
6339
6337
|
}, [o, e, t, r2]);
|
|
6340
6338
|
}
|
|
6341
|
-
const
|
|
6342
|
-
TODO: "To Do",
|
|
6343
|
-
IN_PROGRESS: "In Progress",
|
|
6344
|
-
DONE: "Done"
|
|
6345
|
-
};
|
|
6346
|
-
function parseTaskMarkdown(content2, filePath) {
|
|
6347
|
-
const { frontmatter, title, rawContent, acceptanceCriteria, description } = parseMarkdownContent(content2);
|
|
6348
|
-
const id = frontmatter.id || extractIdFromPath(filePath);
|
|
6349
|
-
const taskTitle = frontmatter.title || title || `Task ${id}`;
|
|
6350
|
-
return {
|
|
6351
|
-
id,
|
|
6352
|
-
title: taskTitle,
|
|
6353
|
-
status: frontmatter.status || "backlog",
|
|
6354
|
-
priority: frontmatter.priority,
|
|
6355
|
-
assignee: frontmatter.assignee || [],
|
|
6356
|
-
reporter: frontmatter.reporter,
|
|
6357
|
-
createdDate: frontmatter.createdDate || (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
6358
|
-
updatedDate: frontmatter.updatedDate,
|
|
6359
|
-
labels: frontmatter.labels || [],
|
|
6360
|
-
milestone: frontmatter.milestone,
|
|
6361
|
-
dependencies: frontmatter.dependencies || [],
|
|
6362
|
-
references: frontmatter.references || [],
|
|
6363
|
-
parentTaskId: frontmatter.parentTaskId,
|
|
6364
|
-
subtasks: frontmatter.subtasks,
|
|
6365
|
-
branch: frontmatter.branch,
|
|
6366
|
-
ordinal: frontmatter.ordinal,
|
|
6367
|
-
rawContent,
|
|
6368
|
-
description,
|
|
6369
|
-
acceptanceCriteriaItems: acceptanceCriteria,
|
|
6370
|
-
filePath
|
|
6371
|
-
};
|
|
6372
|
-
}
|
|
6373
|
-
function serializeTaskMarkdown(task) {
|
|
6374
|
-
const lines = [];
|
|
6375
|
-
lines.push("---");
|
|
6376
|
-
lines.push(`status: ${task.status}`);
|
|
6377
|
-
if (task.priority) {
|
|
6378
|
-
lines.push(`priority: ${task.priority}`);
|
|
6379
|
-
}
|
|
6380
|
-
if (task.assignee && task.assignee.length > 0) {
|
|
6381
|
-
lines.push(`assignee: [${task.assignee.join(", ")}]`);
|
|
6382
|
-
}
|
|
6383
|
-
if (task.reporter) {
|
|
6384
|
-
lines.push(`reporter: ${task.reporter}`);
|
|
6385
|
-
}
|
|
6386
|
-
if (task.labels && task.labels.length > 0) {
|
|
6387
|
-
lines.push(`labels: [${task.labels.join(", ")}]`);
|
|
6388
|
-
}
|
|
6389
|
-
if (task.milestone) {
|
|
6390
|
-
lines.push(`milestone: ${task.milestone}`);
|
|
6391
|
-
}
|
|
6392
|
-
if (task.dependencies && task.dependencies.length > 0) {
|
|
6393
|
-
lines.push(`dependencies: [${task.dependencies.join(", ")}]`);
|
|
6394
|
-
}
|
|
6395
|
-
if (task.references && task.references.length > 0) {
|
|
6396
|
-
lines.push(`references: [${task.references.join(", ")}]`);
|
|
6397
|
-
}
|
|
6398
|
-
if (task.parentTaskId) {
|
|
6399
|
-
lines.push(`parentTaskId: ${task.parentTaskId}`);
|
|
6400
|
-
}
|
|
6401
|
-
if (task.subtasks && task.subtasks.length > 0) {
|
|
6402
|
-
lines.push(`subtasks: [${task.subtasks.join(", ")}]`);
|
|
6403
|
-
}
|
|
6404
|
-
if (task.branch) {
|
|
6405
|
-
lines.push(`branch: ${task.branch}`);
|
|
6406
|
-
}
|
|
6407
|
-
if (task.ordinal !== void 0) {
|
|
6408
|
-
lines.push(`ordinal: ${task.ordinal}`);
|
|
6409
|
-
}
|
|
6410
|
-
if (task.createdDate) {
|
|
6411
|
-
lines.push(`createdDate: ${task.createdDate}`);
|
|
6412
|
-
}
|
|
6413
|
-
if (task.updatedDate) {
|
|
6414
|
-
lines.push(`updatedDate: ${task.updatedDate}`);
|
|
6415
|
-
}
|
|
6416
|
-
lines.push("---");
|
|
6417
|
-
lines.push("");
|
|
6418
|
-
lines.push(`# ${task.title}`);
|
|
6419
|
-
lines.push("");
|
|
6420
|
-
if (task.description) {
|
|
6421
|
-
lines.push(task.description);
|
|
6422
|
-
lines.push("");
|
|
6423
|
-
}
|
|
6424
|
-
if (task.acceptanceCriteriaItems && task.acceptanceCriteriaItems.length > 0) {
|
|
6425
|
-
lines.push("## Acceptance Criteria");
|
|
6426
|
-
lines.push("");
|
|
6427
|
-
for (const criterion of task.acceptanceCriteriaItems) {
|
|
6428
|
-
const checkbox = criterion.checked ? "[x]" : "[ ]";
|
|
6429
|
-
lines.push(`- ${checkbox} ${criterion.text}`);
|
|
6430
|
-
}
|
|
6431
|
-
lines.push("");
|
|
6432
|
-
}
|
|
6433
|
-
if (task.implementationPlan) {
|
|
6434
|
-
lines.push("## Implementation Plan");
|
|
6435
|
-
lines.push("");
|
|
6436
|
-
lines.push(task.implementationPlan);
|
|
6437
|
-
lines.push("");
|
|
6438
|
-
}
|
|
6439
|
-
if (task.implementationNotes) {
|
|
6440
|
-
lines.push("## Implementation Notes");
|
|
6441
|
-
lines.push("");
|
|
6442
|
-
lines.push(task.implementationNotes);
|
|
6443
|
-
lines.push("");
|
|
6444
|
-
}
|
|
6445
|
-
return lines.join("\n");
|
|
6446
|
-
}
|
|
6447
|
-
function parseMarkdownContent(content2) {
|
|
6448
|
-
let frontmatter = {};
|
|
6449
|
-
let remaining = content2;
|
|
6450
|
-
const frontmatterMatch = content2.match(/^---\n([\s\S]*?)\n---\n/);
|
|
6451
|
-
if (frontmatterMatch) {
|
|
6452
|
-
frontmatter = parseFrontmatter(frontmatterMatch[1]);
|
|
6453
|
-
remaining = content2.slice(frontmatterMatch[0].length);
|
|
6454
|
-
}
|
|
6455
|
-
let title = "";
|
|
6456
|
-
const titleMatch = remaining.match(/^#\s+(.+)$/m);
|
|
6457
|
-
if (titleMatch) {
|
|
6458
|
-
title = titleMatch[1].trim();
|
|
6459
|
-
}
|
|
6460
|
-
const acceptanceCriteria = parseAcceptanceCriteria(remaining);
|
|
6461
|
-
const rawContent = remaining.trim();
|
|
6462
|
-
const description = extractDescription(remaining, title);
|
|
6463
|
-
return { frontmatter, title, rawContent, description, acceptanceCriteria };
|
|
6464
|
-
}
|
|
6465
|
-
function parseFrontmatter(raw2) {
|
|
6466
|
-
const result = {};
|
|
6467
|
-
for (const line of raw2.split("\n")) {
|
|
6468
|
-
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
6469
|
-
if (!match)
|
|
6470
|
-
continue;
|
|
6471
|
-
const [, key2, value] = match;
|
|
6472
|
-
let cleanValue = value.trim();
|
|
6473
|
-
if (cleanValue.startsWith('"') && cleanValue.endsWith('"') || cleanValue.startsWith("'") && cleanValue.endsWith("'")) {
|
|
6474
|
-
cleanValue = cleanValue.slice(1, -1);
|
|
6475
|
-
}
|
|
6476
|
-
switch (key2) {
|
|
6477
|
-
case "id":
|
|
6478
|
-
case "title":
|
|
6479
|
-
result[key2] = cleanValue;
|
|
6480
|
-
break;
|
|
6481
|
-
case "status":
|
|
6482
|
-
result.status = cleanValue;
|
|
6483
|
-
break;
|
|
6484
|
-
case "priority":
|
|
6485
|
-
result.priority = cleanValue;
|
|
6486
|
-
break;
|
|
6487
|
-
case "ordinal":
|
|
6488
|
-
result.ordinal = parseInt(cleanValue, 10);
|
|
6489
|
-
break;
|
|
6490
|
-
case "reporter":
|
|
6491
|
-
case "milestone":
|
|
6492
|
-
case "parentTaskId":
|
|
6493
|
-
case "branch":
|
|
6494
|
-
case "createdDate":
|
|
6495
|
-
case "updatedDate":
|
|
6496
|
-
result[key2] = cleanValue;
|
|
6497
|
-
break;
|
|
6498
|
-
case "assignee":
|
|
6499
|
-
case "labels":
|
|
6500
|
-
case "dependencies":
|
|
6501
|
-
case "references":
|
|
6502
|
-
case "subtasks":
|
|
6503
|
-
result[key2] = parseArrayValue(value);
|
|
6504
|
-
break;
|
|
6505
|
-
}
|
|
6506
|
-
}
|
|
6507
|
-
return result;
|
|
6508
|
-
}
|
|
6509
|
-
function parseArrayValue(value) {
|
|
6510
|
-
const cleaned = value.replace(/^\[|\]$/g, "").trim();
|
|
6511
|
-
if (!cleaned)
|
|
6512
|
-
return [];
|
|
6513
|
-
return cleaned.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
6514
|
-
}
|
|
6515
|
-
function parseAcceptanceCriteria(content2) {
|
|
6516
|
-
const criteria = [];
|
|
6517
|
-
const checkboxPattern = /^-\s*\[([ xX])\]\s*(.+)$/gm;
|
|
6518
|
-
const matches = content2.matchAll(checkboxPattern);
|
|
6519
|
-
let index2 = 1;
|
|
6520
|
-
for (const match of matches) {
|
|
6521
|
-
criteria.push({
|
|
6522
|
-
index: index2++,
|
|
6523
|
-
checked: match[1].toLowerCase() === "x",
|
|
6524
|
-
text: match[2].trim()
|
|
6525
|
-
});
|
|
6526
|
-
}
|
|
6527
|
-
return criteria;
|
|
6528
|
-
}
|
|
6529
|
-
function extractDescription(content2, title) {
|
|
6530
|
-
const descriptionSection = extractSection(content2, "Description");
|
|
6531
|
-
if (descriptionSection) {
|
|
6532
|
-
return stripHtmlComments(descriptionSection).trim();
|
|
6533
|
-
}
|
|
6534
|
-
let body = content2;
|
|
6535
|
-
if (title) {
|
|
6536
|
-
body = body.replace(new RegExp(`^#\\s+${escapeRegex(title)}\\s*\\n?`, "m"), "");
|
|
6537
|
-
}
|
|
6538
|
-
const firstSectionMatch = body.match(/^##\s+/m);
|
|
6539
|
-
if (firstSectionMatch && firstSectionMatch.index !== void 0) {
|
|
6540
|
-
body = body.slice(0, firstSectionMatch.index);
|
|
6541
|
-
}
|
|
6542
|
-
body = stripHtmlComments(body);
|
|
6543
|
-
return body.trim();
|
|
6544
|
-
}
|
|
6545
|
-
function stripHtmlComments(content2) {
|
|
6546
|
-
return content2.replace(/<!--[\s\S]*?-->/g, "").trim();
|
|
6547
|
-
}
|
|
6548
|
-
function extractIdFromPath(filePath) {
|
|
6549
|
-
const filename = filePath.split("/").pop() || "";
|
|
6550
|
-
const match = filename.match(/^(?:task-)?(\d+(?:\.\d+)?)\s*-/);
|
|
6551
|
-
if (match) {
|
|
6552
|
-
return match[1];
|
|
6553
|
-
}
|
|
6554
|
-
return filename.replace(/\.md$/, "");
|
|
6555
|
-
}
|
|
6556
|
-
function extractTitleFromPath(filePath) {
|
|
6557
|
-
const filename = filePath.split("/").pop() || "";
|
|
6558
|
-
const match = filename.match(/^(?:task-)?\d+(?:\.\d+)?\s*-\s*(.+)\.md$/);
|
|
6559
|
-
if (match) {
|
|
6560
|
-
return match[1].trim();
|
|
6561
|
-
}
|
|
6562
|
-
return filename.replace(/\.md$/, "");
|
|
6563
|
-
}
|
|
6564
|
-
function extractSourceFromPath(filePath) {
|
|
6565
|
-
if (filePath.includes("/completed/") || filePath.includes("\\completed\\")) {
|
|
6566
|
-
return "completed";
|
|
6567
|
-
}
|
|
6568
|
-
return "tasks";
|
|
6569
|
-
}
|
|
6570
|
-
function extractTaskIndexFromPath(filePath) {
|
|
6571
|
-
return {
|
|
6572
|
-
id: extractIdFromPath(filePath),
|
|
6573
|
-
filePath,
|
|
6574
|
-
title: extractTitleFromPath(filePath),
|
|
6575
|
-
source: extractSourceFromPath(filePath)
|
|
6576
|
-
};
|
|
6577
|
-
}
|
|
6578
|
-
function escapeRegex(str) {
|
|
6579
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6580
|
-
}
|
|
6581
|
-
function extractSection(content2, sectionTitle) {
|
|
6582
|
-
var _a;
|
|
6583
|
-
const src = content2.replace(/\r\n/g, "\n");
|
|
6584
|
-
const regex2 = new RegExp(`## ${sectionTitle}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`, "i");
|
|
6585
|
-
const match = src.match(regex2);
|
|
6586
|
-
return (_a = match == null ? void 0 : match[1]) == null ? void 0 : _a.trim();
|
|
6587
|
-
}
|
|
6588
|
-
function parseMilestoneFrontmatter(raw2) {
|
|
6589
|
-
const result = {};
|
|
6590
|
-
for (const line of raw2.split("\n")) {
|
|
6591
|
-
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
6592
|
-
if (!match)
|
|
6593
|
-
continue;
|
|
6594
|
-
const [, key2, value] = match;
|
|
6595
|
-
let cleanValue = value.trim();
|
|
6596
|
-
if (cleanValue.startsWith('"') && cleanValue.endsWith('"') || cleanValue.startsWith("'") && cleanValue.endsWith("'")) {
|
|
6597
|
-
cleanValue = cleanValue.slice(1, -1);
|
|
6598
|
-
}
|
|
6599
|
-
switch (key2) {
|
|
6600
|
-
case "id":
|
|
6601
|
-
case "title":
|
|
6602
|
-
result[key2] = cleanValue;
|
|
6603
|
-
break;
|
|
6604
|
-
case "tasks":
|
|
6605
|
-
if (cleanValue.startsWith("[") && cleanValue.endsWith("]")) {
|
|
6606
|
-
result.tasks = cleanValue.slice(1, -1).split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
6607
|
-
}
|
|
6608
|
-
break;
|
|
6609
|
-
}
|
|
6610
|
-
}
|
|
6611
|
-
return result;
|
|
6612
|
-
}
|
|
6613
|
-
function parseMilestoneMarkdown(content2) {
|
|
6614
|
-
let frontmatter = {};
|
|
6615
|
-
let remaining = content2;
|
|
6616
|
-
const frontmatterMatch = content2.match(/^---\n([\s\S]*?)\n---\n/);
|
|
6617
|
-
if (frontmatterMatch) {
|
|
6618
|
-
frontmatter = parseMilestoneFrontmatter(frontmatterMatch[1]);
|
|
6619
|
-
remaining = content2.slice(frontmatterMatch[0].length);
|
|
6620
|
-
}
|
|
6621
|
-
const rawContent = remaining.trim();
|
|
6622
|
-
const description = extractSection(rawContent, "Description") || "";
|
|
6623
|
-
return {
|
|
6624
|
-
id: frontmatter.id || "",
|
|
6625
|
-
title: frontmatter.title || "",
|
|
6626
|
-
description,
|
|
6627
|
-
rawContent,
|
|
6628
|
-
tasks: frontmatter.tasks || []
|
|
6629
|
-
};
|
|
6630
|
-
}
|
|
6631
|
-
function serializeMilestoneMarkdown(milestone) {
|
|
6632
|
-
const lines = [];
|
|
6633
|
-
lines.push("---");
|
|
6634
|
-
lines.push(`id: ${milestone.id}`);
|
|
6635
|
-
const escapedTitle = milestone.title.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
6636
|
-
lines.push(`title: "${escapedTitle}"`);
|
|
6637
|
-
lines.push(`tasks: [${milestone.tasks.join(", ")}]`);
|
|
6638
|
-
lines.push("---");
|
|
6639
|
-
lines.push("");
|
|
6640
|
-
lines.push("## Description");
|
|
6641
|
-
lines.push("");
|
|
6642
|
-
lines.push(milestone.description || `Milestone: ${milestone.title}`);
|
|
6643
|
-
lines.push("");
|
|
6644
|
-
return lines.join("\n");
|
|
6645
|
-
}
|
|
6646
|
-
function getMilestoneFilename(id, title) {
|
|
6647
|
-
const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "-").toLowerCase().slice(0, 50);
|
|
6648
|
-
return `${id} - ${safeTitle}.md`;
|
|
6649
|
-
}
|
|
6650
|
-
function extractMilestoneIdFromFilename(filename) {
|
|
6651
|
-
const match = filename.match(/^(m-\d+)/);
|
|
6652
|
-
return match ? match[1] : null;
|
|
6653
|
-
}
|
|
6654
|
-
const version$1 = "0.3.13";
|
|
6655
|
-
const packageJson$1 = {
|
|
6656
|
-
version: version$1
|
|
6657
|
-
};
|
|
6658
|
-
const TRACER_NAME$1 = "@backlog-md/core";
|
|
6659
|
-
const TRACER_VERSION$1 = packageJson$1.version;
|
|
6660
|
-
function getTracer$1() {
|
|
6661
|
-
return trace.getTracer(TRACER_NAME$1, TRACER_VERSION$1);
|
|
6662
|
-
}
|
|
6663
|
-
const NO_MILESTONE_KEY = "__none";
|
|
6664
|
-
function normalizeMilestoneName(name2) {
|
|
6665
|
-
return name2.trim();
|
|
6666
|
-
}
|
|
6667
|
-
function milestoneKey(name2) {
|
|
6668
|
-
return normalizeMilestoneName(name2 ?? "").toLowerCase();
|
|
6669
|
-
}
|
|
6670
|
-
function isDoneStatus(status) {
|
|
6671
|
-
const normalized = (status ?? "").toLowerCase();
|
|
6672
|
-
return normalized.includes("done") || normalized.includes("complete");
|
|
6673
|
-
}
|
|
6674
|
-
function getMilestoneLabel(milestoneId, milestoneEntities) {
|
|
6675
|
-
if (!milestoneId) {
|
|
6676
|
-
return "Tasks without milestone";
|
|
6677
|
-
}
|
|
6678
|
-
const entity = milestoneEntities.find((m) => milestoneKey(m.id) === milestoneKey(milestoneId));
|
|
6679
|
-
return (entity == null ? void 0 : entity.title) || milestoneId;
|
|
6680
|
-
}
|
|
6681
|
-
function collectMilestoneIds(tasks, milestoneEntities) {
|
|
6682
|
-
const merged = [];
|
|
6683
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6684
|
-
const addMilestone = (value) => {
|
|
6685
|
-
const normalized = normalizeMilestoneName(value);
|
|
6686
|
-
if (!normalized)
|
|
6687
|
-
return;
|
|
6688
|
-
const key2 = milestoneKey(normalized);
|
|
6689
|
-
if (seen.has(key2))
|
|
6690
|
-
return;
|
|
6691
|
-
seen.add(key2);
|
|
6692
|
-
merged.push(normalized);
|
|
6693
|
-
};
|
|
6694
|
-
for (const entity of milestoneEntities) {
|
|
6695
|
-
addMilestone(entity.id);
|
|
6696
|
-
}
|
|
6697
|
-
for (const task of tasks) {
|
|
6698
|
-
addMilestone(task.milestone ?? "");
|
|
6699
|
-
}
|
|
6700
|
-
return merged;
|
|
6701
|
-
}
|
|
6702
|
-
function collectMilestones(tasks, configMilestones) {
|
|
6703
|
-
const merged = [];
|
|
6704
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6705
|
-
const addMilestone = (value) => {
|
|
6706
|
-
const normalized = normalizeMilestoneName(value);
|
|
6707
|
-
if (!normalized)
|
|
6708
|
-
return;
|
|
6709
|
-
const key2 = milestoneKey(normalized);
|
|
6710
|
-
if (seen.has(key2))
|
|
6711
|
-
return;
|
|
6712
|
-
seen.add(key2);
|
|
6713
|
-
merged.push(normalized);
|
|
6714
|
-
};
|
|
6715
|
-
for (const m of configMilestones) {
|
|
6716
|
-
addMilestone(m);
|
|
6717
|
-
}
|
|
6718
|
-
for (const task of tasks) {
|
|
6719
|
-
addMilestone(task.milestone ?? "");
|
|
6720
|
-
}
|
|
6721
|
-
return merged;
|
|
6722
|
-
}
|
|
6723
|
-
function createBucket(milestoneId, tasks, statuses, milestoneEntities, isNoMilestone) {
|
|
6724
|
-
const bucketMilestoneKey = milestoneKey(milestoneId);
|
|
6725
|
-
const bucketTasks = tasks.filter((task) => {
|
|
6726
|
-
const taskMilestoneKey = milestoneKey(task.milestone);
|
|
6727
|
-
return bucketMilestoneKey ? taskMilestoneKey === bucketMilestoneKey : !taskMilestoneKey;
|
|
6728
|
-
});
|
|
6729
|
-
const counts = {};
|
|
6730
|
-
for (const status of statuses) {
|
|
6731
|
-
counts[status] = 0;
|
|
6732
|
-
}
|
|
6733
|
-
for (const task of bucketTasks) {
|
|
6734
|
-
const status = task.status ?? "";
|
|
6735
|
-
counts[status] = (counts[status] ?? 0) + 1;
|
|
6736
|
-
}
|
|
6737
|
-
const doneCount = bucketTasks.filter((t) => isDoneStatus(t.status)).length;
|
|
6738
|
-
const progress = bucketTasks.length > 0 ? Math.round(doneCount / bucketTasks.length * 100) : 0;
|
|
6739
|
-
const key2 = bucketMilestoneKey ? bucketMilestoneKey : NO_MILESTONE_KEY;
|
|
6740
|
-
const label = getMilestoneLabel(milestoneId, milestoneEntities);
|
|
6741
|
-
return {
|
|
6742
|
-
key: key2,
|
|
6743
|
-
label,
|
|
6744
|
-
milestone: milestoneId,
|
|
6745
|
-
isNoMilestone,
|
|
6746
|
-
tasks: bucketTasks,
|
|
6747
|
-
statusCounts: counts,
|
|
6748
|
-
total: bucketTasks.length,
|
|
6749
|
-
doneCount,
|
|
6750
|
-
progress
|
|
6751
|
-
};
|
|
6752
|
-
}
|
|
6753
|
-
function buildMilestoneBuckets(tasks, milestoneEntities, statuses) {
|
|
6754
|
-
const allMilestoneIds = collectMilestoneIds(tasks, milestoneEntities);
|
|
6755
|
-
const buckets = [
|
|
6756
|
-
// "No milestone" bucket first
|
|
6757
|
-
createBucket(void 0, tasks, statuses, milestoneEntities, true),
|
|
6758
|
-
// Then each milestone bucket
|
|
6759
|
-
...allMilestoneIds.map((m) => createBucket(m, tasks, statuses, milestoneEntities, false))
|
|
6760
|
-
];
|
|
6761
|
-
return buckets;
|
|
6762
|
-
}
|
|
6763
|
-
function buildMilestoneBucketsFromConfig(tasks, configMilestones, statuses) {
|
|
6764
|
-
const milestoneEntities = configMilestones.map((id) => ({
|
|
6765
|
-
id,
|
|
6766
|
-
title: id,
|
|
6767
|
-
description: "",
|
|
6768
|
-
rawContent: "",
|
|
6769
|
-
tasks: []
|
|
6770
|
-
}));
|
|
6771
|
-
return buildMilestoneBuckets(tasks, milestoneEntities, statuses);
|
|
6772
|
-
}
|
|
6773
|
-
function groupTasksByMilestone(tasks, configMilestones, statuses) {
|
|
6774
|
-
const milestones = collectMilestones(tasks, configMilestones);
|
|
6775
|
-
const buckets = buildMilestoneBucketsFromConfig(tasks, configMilestones, statuses);
|
|
6776
|
-
return {
|
|
6777
|
-
milestones,
|
|
6778
|
-
buckets
|
|
6779
|
-
};
|
|
6780
|
-
}
|
|
6781
|
-
const PRIORITY_ORDER = {
|
|
6782
|
-
high: 0,
|
|
6783
|
-
medium: 1,
|
|
6784
|
-
low: 2
|
|
6785
|
-
};
|
|
6786
|
-
function sortTasksByTitle(tasks, direction = "asc") {
|
|
6787
|
-
return [...tasks].sort((a, b) => {
|
|
6788
|
-
const cmp = a.title.localeCompare(b.title);
|
|
6789
|
-
return direction === "asc" ? cmp : -cmp;
|
|
6790
|
-
});
|
|
6791
|
-
}
|
|
6792
|
-
function sortTasksBy(tasks, sortBy = "title", direction = "asc") {
|
|
6793
|
-
switch (sortBy) {
|
|
6794
|
-
case "title":
|
|
6795
|
-
return sortTasksByTitle(tasks, direction);
|
|
6796
|
-
case "createdDate":
|
|
6797
|
-
return [...tasks].sort((a, b) => {
|
|
6798
|
-
const cmp = a.createdDate.localeCompare(b.createdDate);
|
|
6799
|
-
return direction === "asc" ? cmp : -cmp;
|
|
6800
|
-
});
|
|
6801
|
-
default: {
|
|
6802
|
-
const sorted = sortTasks(tasks);
|
|
6803
|
-
return direction === "desc" ? sorted.reverse() : sorted;
|
|
6804
|
-
}
|
|
6805
|
-
}
|
|
6806
|
-
}
|
|
6807
|
-
function sortTasks(tasks) {
|
|
6808
|
-
return [...tasks].sort((a, b) => {
|
|
6809
|
-
if (a.ordinal !== void 0 && b.ordinal !== void 0) {
|
|
6810
|
-
return a.ordinal - b.ordinal;
|
|
6811
|
-
}
|
|
6812
|
-
if (a.ordinal !== void 0)
|
|
6813
|
-
return -1;
|
|
6814
|
-
if (b.ordinal !== void 0)
|
|
6815
|
-
return 1;
|
|
6816
|
-
const aPri = a.priority ? PRIORITY_ORDER[a.priority] : 3;
|
|
6817
|
-
const bPri = b.priority ? PRIORITY_ORDER[b.priority] : 3;
|
|
6818
|
-
if (aPri !== bPri)
|
|
6819
|
-
return aPri - bPri;
|
|
6820
|
-
return b.createdDate.localeCompare(a.createdDate);
|
|
6821
|
-
});
|
|
6822
|
-
}
|
|
6823
|
-
function groupTasksByStatus(tasks, statuses) {
|
|
6824
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
6825
|
-
for (const status of statuses) {
|
|
6826
|
-
grouped.set(status, []);
|
|
6827
|
-
}
|
|
6828
|
-
for (const task of tasks) {
|
|
6829
|
-
const list2 = grouped.get(task.status);
|
|
6830
|
-
if (list2) {
|
|
6831
|
-
list2.push(task);
|
|
6832
|
-
} else {
|
|
6833
|
-
grouped.set(task.status, [task]);
|
|
6834
|
-
}
|
|
6835
|
-
}
|
|
6836
|
-
for (const [status, statusTasks] of grouped) {
|
|
6837
|
-
grouped.set(status, sortTasks(statusTasks));
|
|
6838
|
-
}
|
|
6839
|
-
return grouped;
|
|
6840
|
-
}
|
|
6841
|
-
const DEFAULT_STATUSES = ["To Do", "In Progress", "Done"];
|
|
6842
|
-
function parseBacklogConfig(content2) {
|
|
6843
|
-
const config = {};
|
|
6844
|
-
const lines = content2.split("\n");
|
|
6845
|
-
for (const line of lines) {
|
|
6846
|
-
const trimmed = line.trim();
|
|
6847
|
-
if (!trimmed || trimmed.startsWith("#"))
|
|
6848
|
-
continue;
|
|
6849
|
-
const colonIndex = trimmed.indexOf(":");
|
|
6850
|
-
if (colonIndex === -1)
|
|
6851
|
-
continue;
|
|
6852
|
-
const key2 = trimmed.substring(0, colonIndex).trim();
|
|
6853
|
-
const value = trimmed.substring(colonIndex + 1).trim();
|
|
6854
|
-
switch (key2) {
|
|
6855
|
-
case "project_name":
|
|
6856
|
-
config.projectName = value.replace(/['"]/g, "");
|
|
6857
|
-
break;
|
|
6858
|
-
case "default_assignee":
|
|
6859
|
-
config.defaultAssignee = value.replace(/['"]/g, "");
|
|
6860
|
-
break;
|
|
6861
|
-
case "default_reporter":
|
|
6862
|
-
config.defaultReporter = value.replace(/['"]/g, "");
|
|
6863
|
-
break;
|
|
6864
|
-
case "default_status":
|
|
6865
|
-
config.defaultStatus = value.replace(/['"]/g, "");
|
|
6866
|
-
break;
|
|
6867
|
-
case "statuses":
|
|
6868
|
-
case "labels":
|
|
6869
|
-
case "milestones":
|
|
6870
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
6871
|
-
const arrayContent = value.slice(1, -1);
|
|
6872
|
-
config[key2] = arrayContent.split(",").map((item) => item.trim().replace(/['"]/g, "")).filter(Boolean);
|
|
6873
|
-
}
|
|
6874
|
-
break;
|
|
6875
|
-
case "date_format":
|
|
6876
|
-
config.dateFormat = value.replace(/['"]/g, "");
|
|
6877
|
-
break;
|
|
6878
|
-
case "max_column_width":
|
|
6879
|
-
config.maxColumnWidth = parseInt(value, 10);
|
|
6880
|
-
break;
|
|
6881
|
-
case "task_resolution_strategy":
|
|
6882
|
-
config.taskResolutionStrategy = value.replace(/['"]/g, "");
|
|
6883
|
-
break;
|
|
6884
|
-
case "default_editor":
|
|
6885
|
-
config.defaultEditor = value.replace(/["']/g, "");
|
|
6886
|
-
break;
|
|
6887
|
-
case "auto_open_browser":
|
|
6888
|
-
config.autoOpenBrowser = value.toLowerCase() === "true";
|
|
6889
|
-
break;
|
|
6890
|
-
case "default_port":
|
|
6891
|
-
config.defaultPort = parseInt(value, 10);
|
|
6892
|
-
break;
|
|
6893
|
-
case "remote_operations":
|
|
6894
|
-
config.remoteOperations = value.toLowerCase() === "true";
|
|
6895
|
-
break;
|
|
6896
|
-
case "auto_commit":
|
|
6897
|
-
config.autoCommit = value.toLowerCase() === "true";
|
|
6898
|
-
break;
|
|
6899
|
-
case "zero_padded_ids":
|
|
6900
|
-
config.zeroPaddedIds = parseInt(value, 10);
|
|
6901
|
-
break;
|
|
6902
|
-
case "timezone_preference":
|
|
6903
|
-
config.timezonePreference = value.replace(/['"]/g, "");
|
|
6904
|
-
break;
|
|
6905
|
-
case "include_date_time_in_dates":
|
|
6906
|
-
config.includeDateTimeInDates = value.toLowerCase() === "true";
|
|
6907
|
-
break;
|
|
6908
|
-
case "bypass_git_hooks":
|
|
6909
|
-
config.bypassGitHooks = value.toLowerCase() === "true";
|
|
6910
|
-
break;
|
|
6911
|
-
case "check_active_branches":
|
|
6912
|
-
config.checkActiveBranches = value.toLowerCase() === "true";
|
|
6913
|
-
break;
|
|
6914
|
-
case "active_branch_days":
|
|
6915
|
-
config.activeBranchDays = parseInt(value, 10);
|
|
6916
|
-
break;
|
|
6917
|
-
}
|
|
6918
|
-
}
|
|
6919
|
-
return {
|
|
6920
|
-
projectName: config.projectName || "Backlog",
|
|
6921
|
-
statuses: config.statuses || [...DEFAULT_STATUSES],
|
|
6922
|
-
labels: config.labels || [],
|
|
6923
|
-
milestones: config.milestones || [],
|
|
6924
|
-
defaultStatus: config.defaultStatus || DEFAULT_STATUSES[0],
|
|
6925
|
-
dateFormat: config.dateFormat || "YYYY-MM-DD",
|
|
6926
|
-
defaultAssignee: config.defaultAssignee,
|
|
6927
|
-
defaultReporter: config.defaultReporter,
|
|
6928
|
-
maxColumnWidth: config.maxColumnWidth,
|
|
6929
|
-
taskResolutionStrategy: config.taskResolutionStrategy,
|
|
6930
|
-
defaultEditor: config.defaultEditor,
|
|
6931
|
-
autoOpenBrowser: config.autoOpenBrowser,
|
|
6932
|
-
defaultPort: config.defaultPort,
|
|
6933
|
-
remoteOperations: config.remoteOperations,
|
|
6934
|
-
autoCommit: config.autoCommit,
|
|
6935
|
-
zeroPaddedIds: config.zeroPaddedIds,
|
|
6936
|
-
timezonePreference: config.timezonePreference,
|
|
6937
|
-
includeDateTimeInDates: config.includeDateTimeInDates,
|
|
6938
|
-
bypassGitHooks: config.bypassGitHooks,
|
|
6939
|
-
checkActiveBranches: config.checkActiveBranches,
|
|
6940
|
-
activeBranchDays: config.activeBranchDays
|
|
6941
|
-
};
|
|
6942
|
-
}
|
|
6943
|
-
function serializeBacklogConfig(config) {
|
|
6944
|
-
const lines = [];
|
|
6945
|
-
lines.push(`project_name: "${config.projectName}"`);
|
|
6946
|
-
if (config.defaultStatus) {
|
|
6947
|
-
lines.push(`default_status: "${config.defaultStatus}"`);
|
|
6948
|
-
}
|
|
6949
|
-
lines.push(`statuses: [${config.statuses.map((s2) => `"${s2}"`).join(", ")}]`);
|
|
6950
|
-
lines.push(`labels: [${(config.labels || []).map((l) => `"${l}"`).join(", ")}]`);
|
|
6951
|
-
lines.push(`milestones: [${(config.milestones || []).map((m) => `"${m}"`).join(", ")}]`);
|
|
6952
|
-
if (config.dateFormat) {
|
|
6953
|
-
lines.push(`date_format: "${config.dateFormat}"`);
|
|
6954
|
-
}
|
|
6955
|
-
if (config.defaultAssignee) {
|
|
6956
|
-
lines.push(`default_assignee: "${config.defaultAssignee}"`);
|
|
6957
|
-
}
|
|
6958
|
-
if (config.defaultReporter) {
|
|
6959
|
-
lines.push(`default_reporter: "${config.defaultReporter}"`);
|
|
6960
|
-
}
|
|
6961
|
-
if (config.defaultEditor) {
|
|
6962
|
-
lines.push(`default_editor: "${config.defaultEditor}"`);
|
|
6963
|
-
}
|
|
6964
|
-
if (typeof config.autoCommit === "boolean") {
|
|
6965
|
-
lines.push(`auto_commit: ${config.autoCommit}`);
|
|
6966
|
-
}
|
|
6967
|
-
if (typeof config.zeroPaddedIds === "number") {
|
|
6968
|
-
lines.push(`zero_padded_ids: ${config.zeroPaddedIds}`);
|
|
6969
|
-
}
|
|
6970
|
-
if (typeof config.autoOpenBrowser === "boolean") {
|
|
6971
|
-
lines.push(`auto_open_browser: ${config.autoOpenBrowser}`);
|
|
6972
|
-
}
|
|
6973
|
-
if (typeof config.defaultPort === "number") {
|
|
6974
|
-
lines.push(`default_port: ${config.defaultPort}`);
|
|
6975
|
-
}
|
|
6976
|
-
if (typeof config.remoteOperations === "boolean") {
|
|
6977
|
-
lines.push(`remote_operations: ${config.remoteOperations}`);
|
|
6978
|
-
}
|
|
6979
|
-
if (typeof config.bypassGitHooks === "boolean") {
|
|
6980
|
-
lines.push(`bypass_git_hooks: ${config.bypassGitHooks}`);
|
|
6981
|
-
}
|
|
6982
|
-
if (typeof config.checkActiveBranches === "boolean") {
|
|
6983
|
-
lines.push(`check_active_branches: ${config.checkActiveBranches}`);
|
|
6984
|
-
}
|
|
6985
|
-
if (typeof config.activeBranchDays === "number") {
|
|
6986
|
-
lines.push(`active_branch_days: ${config.activeBranchDays}`);
|
|
6987
|
-
}
|
|
6988
|
-
return `${lines.join("\n")}
|
|
6989
|
-
`;
|
|
6990
|
-
}
|
|
6991
|
-
class Core {
|
|
6992
|
-
constructor(options) {
|
|
6993
|
-
__publicField(this, "projectRoot");
|
|
6994
|
-
__publicField(this, "fs");
|
|
6995
|
-
__publicField(this, "tracer");
|
|
6996
|
-
__publicField(this, "config", null);
|
|
6997
|
-
__publicField(this, "tasks", /* @__PURE__ */ new Map());
|
|
6998
|
-
__publicField(this, "initialized", false);
|
|
6999
|
-
/** Lightweight task index for lazy loading (no file reads) */
|
|
7000
|
-
__publicField(this, "taskIndex", /* @__PURE__ */ new Map());
|
|
7001
|
-
__publicField(this, "lazyInitialized", false);
|
|
7002
|
-
this.projectRoot = options.projectRoot;
|
|
7003
|
-
this.fs = options.adapters.fs;
|
|
7004
|
-
this.tracer = getTracer$1();
|
|
7005
|
-
}
|
|
7006
|
-
/**
|
|
7007
|
-
* Check if projectRoot contains a valid Backlog.md project
|
|
7008
|
-
*/
|
|
7009
|
-
async isBacklogProject() {
|
|
7010
|
-
const configPath = this.fs.join(this.projectRoot, "backlog", "config.yml");
|
|
7011
|
-
return this.fs.exists(configPath);
|
|
7012
|
-
}
|
|
7013
|
-
/**
|
|
7014
|
-
* Initialize a new Backlog.md project in the projectRoot directory
|
|
7015
|
-
*
|
|
7016
|
-
* Creates the backlog/ directory and config.yml file.
|
|
7017
|
-
* Task directories (tasks/, completed/) are created lazily when needed.
|
|
7018
|
-
*
|
|
7019
|
-
* @param options - Optional configuration for the new project
|
|
7020
|
-
* @throws Error if project already exists
|
|
7021
|
-
*
|
|
7022
|
-
* @example
|
|
7023
|
-
* ```typescript
|
|
7024
|
-
* const core = new Core({ projectRoot: '/path/to/project', adapters: { fs } });
|
|
7025
|
-
*
|
|
7026
|
-
* // Initialize with defaults
|
|
7027
|
-
* await core.initProject();
|
|
7028
|
-
*
|
|
7029
|
-
* // Or with custom options
|
|
7030
|
-
* await core.initProject({
|
|
7031
|
-
* projectName: 'My Project',
|
|
7032
|
-
* statuses: ['Backlog', 'In Progress', 'Review', 'Done'],
|
|
7033
|
-
* labels: ['bug', 'feature', 'docs']
|
|
7034
|
-
* });
|
|
7035
|
-
* ```
|
|
7036
|
-
*/
|
|
7037
|
-
async initProject(options = {}) {
|
|
7038
|
-
if (await this.isBacklogProject()) {
|
|
7039
|
-
throw new Error(`Already a Backlog.md project: config.yml exists at ${this.fs.join(this.projectRoot, "backlog", "config.yml")}`);
|
|
7040
|
-
}
|
|
7041
|
-
const dirName = this.projectRoot.split("/").pop() || "Backlog";
|
|
7042
|
-
const projectName = options.projectName || dirName;
|
|
7043
|
-
const statuses = options.statuses || [
|
|
7044
|
-
DEFAULT_TASK_STATUSES.TODO,
|
|
7045
|
-
DEFAULT_TASK_STATUSES.IN_PROGRESS,
|
|
7046
|
-
DEFAULT_TASK_STATUSES.DONE
|
|
7047
|
-
];
|
|
7048
|
-
const config = {
|
|
7049
|
-
projectName,
|
|
7050
|
-
statuses,
|
|
7051
|
-
labels: options.labels || [],
|
|
7052
|
-
milestones: [],
|
|
7053
|
-
defaultStatus: options.defaultStatus || statuses[0],
|
|
7054
|
-
dateFormat: "YYYY-MM-DD"
|
|
7055
|
-
};
|
|
7056
|
-
const backlogDir = this.fs.join(this.projectRoot, "backlog");
|
|
7057
|
-
await this.fs.createDir(backlogDir, { recursive: true });
|
|
7058
|
-
const configPath = this.fs.join(backlogDir, "config.yml");
|
|
7059
|
-
const configContent = serializeBacklogConfig(config);
|
|
7060
|
-
await this.fs.writeFile(configPath, configContent);
|
|
7061
|
-
}
|
|
7062
|
-
/**
|
|
7063
|
-
* Initialize the Core instance
|
|
7064
|
-
*
|
|
7065
|
-
* Loads configuration and discovers all tasks.
|
|
7066
|
-
* Must be called before using other methods.
|
|
7067
|
-
*/
|
|
7068
|
-
async initialize() {
|
|
7069
|
-
if (this.initialized)
|
|
7070
|
-
return;
|
|
7071
|
-
return this.tracer.startActiveSpan("core.init", {
|
|
7072
|
-
attributes: {
|
|
7073
|
-
projectRoot: this.projectRoot,
|
|
7074
|
-
mode: "full"
|
|
7075
|
-
}
|
|
7076
|
-
}, async (span) => {
|
|
7077
|
-
var _a;
|
|
7078
|
-
const startTime = Date.now();
|
|
7079
|
-
try {
|
|
7080
|
-
span.addEvent("core.init.started", {
|
|
7081
|
-
projectRoot: this.projectRoot,
|
|
7082
|
-
mode: "full"
|
|
7083
|
-
});
|
|
7084
|
-
const configPath = this.fs.join(this.projectRoot, "backlog", "config.yml");
|
|
7085
|
-
const configExists = await this.fs.exists(configPath);
|
|
7086
|
-
if (!configExists) {
|
|
7087
|
-
const error = new Error(`Not a Backlog.md project: config.yml not found at ${configPath}`);
|
|
7088
|
-
span.addEvent("core.init.error", {
|
|
7089
|
-
"error.type": "ConfigNotFoundError",
|
|
7090
|
-
"error.message": error.message,
|
|
7091
|
-
stage: "config"
|
|
7092
|
-
});
|
|
7093
|
-
span.recordException(error);
|
|
7094
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
7095
|
-
throw error;
|
|
7096
|
-
}
|
|
7097
|
-
const configContent = await this.fs.readFile(configPath);
|
|
7098
|
-
this.config = parseBacklogConfig(configContent);
|
|
7099
|
-
span.addEvent("core.init.config.loaded", {
|
|
7100
|
-
projectName: this.config.projectName,
|
|
7101
|
-
statusCount: this.config.statuses.length,
|
|
7102
|
-
labelCount: ((_a = this.config.labels) == null ? void 0 : _a.length) ?? 0
|
|
7103
|
-
});
|
|
7104
|
-
let localTaskCount = 0;
|
|
7105
|
-
const tasksDir = this.fs.join(this.projectRoot, "backlog", "tasks");
|
|
7106
|
-
if (await this.fs.exists(tasksDir)) {
|
|
7107
|
-
await this.loadTasksFromDirectory(tasksDir, "local");
|
|
7108
|
-
localTaskCount = Array.from(this.tasks.values()).filter((t) => t.source === "local").length;
|
|
7109
|
-
}
|
|
7110
|
-
let completedTaskCount = 0;
|
|
7111
|
-
const completedDir = this.fs.join(this.projectRoot, "backlog", "completed");
|
|
7112
|
-
if (await this.fs.exists(completedDir)) {
|
|
7113
|
-
await this.loadTasksFromDirectory(completedDir, "completed");
|
|
7114
|
-
completedTaskCount = Array.from(this.tasks.values()).filter((t) => t.source === "completed").length;
|
|
7115
|
-
}
|
|
7116
|
-
const totalTaskCount = this.tasks.size;
|
|
7117
|
-
span.addEvent("core.init.tasks.loaded", {
|
|
7118
|
-
localTaskCount,
|
|
7119
|
-
completedTaskCount,
|
|
7120
|
-
totalTaskCount
|
|
7121
|
-
});
|
|
7122
|
-
this.initialized = true;
|
|
7123
|
-
const duration = Date.now() - startTime;
|
|
7124
|
-
span.addEvent("core.init.complete", {
|
|
7125
|
-
success: true,
|
|
7126
|
-
"duration.ms": duration,
|
|
7127
|
-
taskCount: totalTaskCount
|
|
7128
|
-
});
|
|
7129
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7130
|
-
} catch (error) {
|
|
7131
|
-
if (error instanceof Error) {
|
|
7132
|
-
span.recordException(error);
|
|
7133
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
7134
|
-
}
|
|
7135
|
-
throw error;
|
|
7136
|
-
} finally {
|
|
7137
|
-
span.end();
|
|
7138
|
-
}
|
|
7139
|
-
});
|
|
7140
|
-
}
|
|
7141
|
-
/**
|
|
7142
|
-
* Initialize with lazy loading (no file content reads)
|
|
7143
|
-
*
|
|
7144
|
-
* Only loads config and builds task index from file paths.
|
|
7145
|
-
* Task content is loaded on-demand via loadTask().
|
|
7146
|
-
* Use this for web/panel contexts where file reads are expensive.
|
|
7147
|
-
*
|
|
7148
|
-
* @param filePaths - Array of all file paths in the project
|
|
7149
|
-
*/
|
|
7150
|
-
async initializeLazy(filePaths) {
|
|
7151
|
-
if (this.lazyInitialized)
|
|
7152
|
-
return;
|
|
7153
|
-
return this.tracer.startActiveSpan("core.init", {
|
|
7154
|
-
attributes: {
|
|
7155
|
-
projectRoot: this.projectRoot,
|
|
7156
|
-
mode: "lazy",
|
|
7157
|
-
"input.filePathCount": filePaths.length
|
|
7158
|
-
}
|
|
7159
|
-
}, async (span) => {
|
|
7160
|
-
var _a;
|
|
7161
|
-
const startTime = Date.now();
|
|
7162
|
-
try {
|
|
7163
|
-
span.addEvent("core.init.started", {
|
|
7164
|
-
projectRoot: this.projectRoot,
|
|
7165
|
-
mode: "lazy"
|
|
7166
|
-
});
|
|
7167
|
-
const configPath = this.fs.join(this.projectRoot, "backlog", "config.yml");
|
|
7168
|
-
const configExists = await this.fs.exists(configPath);
|
|
7169
|
-
if (!configExists) {
|
|
7170
|
-
const error = new Error(`Not a Backlog.md project: config.yml not found at ${configPath}`);
|
|
7171
|
-
span.addEvent("core.init.error", {
|
|
7172
|
-
"error.type": "ConfigNotFoundError",
|
|
7173
|
-
"error.message": error.message,
|
|
7174
|
-
stage: "config"
|
|
7175
|
-
});
|
|
7176
|
-
span.recordException(error);
|
|
7177
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
7178
|
-
throw error;
|
|
7179
|
-
}
|
|
7180
|
-
const configContent = await this.fs.readFile(configPath);
|
|
7181
|
-
this.config = parseBacklogConfig(configContent);
|
|
7182
|
-
span.addEvent("core.init.config.loaded", {
|
|
7183
|
-
projectName: this.config.projectName,
|
|
7184
|
-
statusCount: this.config.statuses.length,
|
|
7185
|
-
labelCount: ((_a = this.config.labels) == null ? void 0 : _a.length) ?? 0
|
|
7186
|
-
});
|
|
7187
|
-
this.taskIndex.clear();
|
|
7188
|
-
let tasksIndexed = 0;
|
|
7189
|
-
let completedIndexed = 0;
|
|
7190
|
-
for (const filePath of filePaths) {
|
|
7191
|
-
if (!filePath.endsWith(".md"))
|
|
7192
|
-
continue;
|
|
7193
|
-
const isTaskFile = filePath.includes("backlog/tasks/") || filePath.includes("backlog\\tasks\\");
|
|
7194
|
-
const isCompletedFile = filePath.includes("backlog/completed/") || filePath.includes("backlog\\completed\\");
|
|
7195
|
-
if (!isTaskFile && !isCompletedFile)
|
|
7196
|
-
continue;
|
|
7197
|
-
if (filePath.endsWith("config.yml"))
|
|
7198
|
-
continue;
|
|
7199
|
-
const indexEntry = extractTaskIndexFromPath(filePath);
|
|
7200
|
-
this.taskIndex.set(indexEntry.id, indexEntry);
|
|
7201
|
-
if (isTaskFile)
|
|
7202
|
-
tasksIndexed++;
|
|
7203
|
-
if (isCompletedFile)
|
|
7204
|
-
completedIndexed++;
|
|
7205
|
-
}
|
|
7206
|
-
this.lazyInitialized = true;
|
|
7207
|
-
const duration = Date.now() - startTime;
|
|
7208
|
-
span.addEvent("core.init.complete", {
|
|
7209
|
-
success: true,
|
|
7210
|
-
"duration.ms": duration,
|
|
7211
|
-
tasksIndexed,
|
|
7212
|
-
completedIndexed,
|
|
7213
|
-
totalIndexed: this.taskIndex.size
|
|
7214
|
-
});
|
|
7215
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7216
|
-
} catch (error) {
|
|
7217
|
-
if (error instanceof Error) {
|
|
7218
|
-
span.recordException(error);
|
|
7219
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
7220
|
-
}
|
|
7221
|
-
throw error;
|
|
7222
|
-
} finally {
|
|
7223
|
-
span.end();
|
|
7224
|
-
}
|
|
7225
|
-
});
|
|
7226
|
-
}
|
|
7227
|
-
/**
|
|
7228
|
-
* Check if lazy initialization is complete
|
|
7229
|
-
*/
|
|
7230
|
-
isLazyInitialized() {
|
|
7231
|
-
return this.lazyInitialized;
|
|
7232
|
-
}
|
|
7233
|
-
/**
|
|
7234
|
-
* Get the task index (lightweight entries)
|
|
7235
|
-
*/
|
|
7236
|
-
getTaskIndex() {
|
|
7237
|
-
if (!this.lazyInitialized) {
|
|
7238
|
-
throw new Error("Core not lazy initialized. Call initializeLazy() first.");
|
|
7239
|
-
}
|
|
7240
|
-
return this.taskIndex;
|
|
7241
|
-
}
|
|
7242
|
-
/**
|
|
7243
|
-
* Load a single task by ID (on-demand loading)
|
|
7244
|
-
*
|
|
7245
|
-
* @param id - Task ID
|
|
7246
|
-
* @returns Task or undefined if not found
|
|
7247
|
-
*/
|
|
7248
|
-
async loadTask(id) {
|
|
7249
|
-
if (this.tasks.has(id)) {
|
|
7250
|
-
return this.tasks.get(id);
|
|
7251
|
-
}
|
|
7252
|
-
const indexEntry = this.taskIndex.get(id);
|
|
7253
|
-
if (!indexEntry) {
|
|
7254
|
-
return void 0;
|
|
7255
|
-
}
|
|
7256
|
-
try {
|
|
7257
|
-
const content2 = await this.fs.readFile(indexEntry.filePath);
|
|
7258
|
-
const task = parseTaskMarkdown(content2, indexEntry.filePath);
|
|
7259
|
-
task.source = indexEntry.source === "completed" ? "completed" : "local";
|
|
7260
|
-
this.tasks.set(task.id, task);
|
|
7261
|
-
return task;
|
|
7262
|
-
} catch (error) {
|
|
7263
|
-
console.warn(`Failed to load task ${id} from ${indexEntry.filePath}:`, error);
|
|
7264
|
-
return void 0;
|
|
7265
|
-
}
|
|
7266
|
-
}
|
|
7267
|
-
/**
|
|
7268
|
-
* Load multiple tasks by ID (on-demand loading)
|
|
7269
|
-
*
|
|
7270
|
-
* @param ids - Array of task IDs to load
|
|
7271
|
-
* @returns Array of loaded tasks (undefined entries filtered out)
|
|
7272
|
-
*/
|
|
7273
|
-
async loadTasks(ids2) {
|
|
7274
|
-
const tasks = await Promise.all(ids2.map((id) => this.loadTask(id)));
|
|
7275
|
-
return tasks.filter((t) => t !== void 0);
|
|
7276
|
-
}
|
|
7277
|
-
/**
|
|
7278
|
-
* Get tasks by source (tasks/completed) with pagination
|
|
7279
|
-
*
|
|
7280
|
-
* This is a lazy-loading alternative to getTasksByStatusPaginated().
|
|
7281
|
-
* Groups by directory (tasks/ or completed/) instead of status.
|
|
7282
|
-
* Only loads task content for items in the requested page.
|
|
7283
|
-
*
|
|
7284
|
-
* @param options - Per-source pagination options
|
|
7285
|
-
* @returns Paginated tasks grouped by source
|
|
7286
|
-
*/
|
|
7287
|
-
async getTasksBySourcePaginated(options) {
|
|
7288
|
-
if (!this.lazyInitialized) {
|
|
7289
|
-
throw new Error("Core not lazy initialized. Call initializeLazy() first.");
|
|
7290
|
-
}
|
|
7291
|
-
const tasksLimit = (options == null ? void 0 : options.tasksLimit) ?? 10;
|
|
7292
|
-
const completedLimit = (options == null ? void 0 : options.completedLimit) ?? 10;
|
|
7293
|
-
const offset = (options == null ? void 0 : options.offset) ?? 0;
|
|
7294
|
-
const tasksSortDirection = (options == null ? void 0 : options.tasksSortDirection) ?? "asc";
|
|
7295
|
-
const completedSortByIdDesc = (options == null ? void 0 : options.completedSortByIdDesc) ?? true;
|
|
7296
|
-
const sources = ["tasks", "completed"];
|
|
7297
|
-
const bySource = /* @__PURE__ */ new Map();
|
|
7298
|
-
for (const source2 of sources) {
|
|
7299
|
-
let entries = Array.from(this.taskIndex.values()).filter((e) => e.source === source2);
|
|
7300
|
-
const limit = source2 === "tasks" ? tasksLimit : completedLimit;
|
|
7301
|
-
entries = entries.sort((a, b) => {
|
|
7302
|
-
const aNum = parseInt(a.id.replace(/\D/g, ""), 10) || 0;
|
|
7303
|
-
const bNum = parseInt(b.id.replace(/\D/g, ""), 10) || 0;
|
|
7304
|
-
if (source2 === "completed") {
|
|
7305
|
-
return completedSortByIdDesc ? bNum - aNum : aNum - bNum;
|
|
7306
|
-
} else {
|
|
7307
|
-
return tasksSortDirection === "desc" ? bNum - aNum : aNum - bNum;
|
|
7308
|
-
}
|
|
7309
|
-
});
|
|
7310
|
-
const total = entries.length;
|
|
7311
|
-
const pageEntries = entries.slice(offset, offset + limit);
|
|
7312
|
-
const items = await this.loadTasks(pageEntries.map((e) => e.id));
|
|
7313
|
-
bySource.set(source2, {
|
|
7314
|
-
items,
|
|
7315
|
-
total,
|
|
7316
|
-
hasMore: offset + limit < total,
|
|
7317
|
-
offset,
|
|
7318
|
-
limit
|
|
7319
|
-
});
|
|
7320
|
-
}
|
|
7321
|
-
return {
|
|
7322
|
-
bySource,
|
|
7323
|
-
sources
|
|
7324
|
-
};
|
|
7325
|
-
}
|
|
7326
|
-
/**
|
|
7327
|
-
* Load more tasks for a specific source (lazy loading)
|
|
7328
|
-
*
|
|
7329
|
-
* @param source - Source to load more from ("tasks" or "completed")
|
|
7330
|
-
* @param currentOffset - Current offset (items already loaded)
|
|
7331
|
-
* @param options - Pagination options
|
|
7332
|
-
* @returns Paginated result for the source
|
|
7333
|
-
*/
|
|
7334
|
-
async loadMoreForSource(source2, currentOffset, options) {
|
|
7335
|
-
if (!this.lazyInitialized) {
|
|
7336
|
-
throw new Error("Core not lazy initialized. Call initializeLazy() first.");
|
|
7337
|
-
}
|
|
7338
|
-
const limit = (options == null ? void 0 : options.limit) ?? 10;
|
|
7339
|
-
const sortDirection = (options == null ? void 0 : options.sortDirection) ?? "asc";
|
|
7340
|
-
const completedSortByIdDesc = (options == null ? void 0 : options.completedSortByIdDesc) ?? true;
|
|
7341
|
-
let entries = Array.from(this.taskIndex.values()).filter((e) => e.source === source2);
|
|
7342
|
-
entries = entries.sort((a, b) => {
|
|
7343
|
-
const aNum = parseInt(a.id.replace(/\D/g, ""), 10) || 0;
|
|
7344
|
-
const bNum = parseInt(b.id.replace(/\D/g, ""), 10) || 0;
|
|
7345
|
-
if (source2 === "completed") {
|
|
7346
|
-
return completedSortByIdDesc ? bNum - aNum : aNum - bNum;
|
|
7347
|
-
} else {
|
|
7348
|
-
return sortDirection === "desc" ? bNum - aNum : aNum - bNum;
|
|
7349
|
-
}
|
|
7350
|
-
});
|
|
7351
|
-
const total = entries.length;
|
|
7352
|
-
const pageEntries = entries.slice(currentOffset, currentOffset + limit);
|
|
7353
|
-
const items = await this.loadTasks(pageEntries.map((e) => e.id));
|
|
7354
|
-
return {
|
|
7355
|
-
items,
|
|
7356
|
-
total,
|
|
7357
|
-
hasMore: currentOffset + limit < total,
|
|
7358
|
-
offset: currentOffset,
|
|
7359
|
-
limit
|
|
7360
|
-
};
|
|
7361
|
-
}
|
|
7362
|
-
/**
|
|
7363
|
-
* Get the loaded configuration
|
|
7364
|
-
*
|
|
7365
|
-
* @throws Error if not initialized
|
|
7366
|
-
*/
|
|
7367
|
-
getConfig() {
|
|
7368
|
-
this.ensureInitialized();
|
|
7369
|
-
return this.safeConfig;
|
|
7370
|
-
}
|
|
7371
|
-
/**
|
|
7372
|
-
* List all tasks, optionally filtered
|
|
7373
|
-
*
|
|
7374
|
-
* @param filter - Optional filter criteria
|
|
7375
|
-
* @returns Sorted array of tasks
|
|
7376
|
-
*/
|
|
7377
|
-
listTasks(filter) {
|
|
7378
|
-
this.ensureInitialized();
|
|
7379
|
-
let tasks = Array.from(this.tasks.values());
|
|
7380
|
-
if (filter == null ? void 0 : filter.status) {
|
|
7381
|
-
tasks = tasks.filter((t) => t.status === filter.status);
|
|
7382
|
-
}
|
|
7383
|
-
if (filter == null ? void 0 : filter.assignee) {
|
|
7384
|
-
const assignee = filter.assignee;
|
|
7385
|
-
tasks = tasks.filter((t) => t.assignee.includes(assignee));
|
|
7386
|
-
}
|
|
7387
|
-
if (filter == null ? void 0 : filter.priority) {
|
|
7388
|
-
tasks = tasks.filter((t) => t.priority === filter.priority);
|
|
7389
|
-
}
|
|
7390
|
-
if (filter == null ? void 0 : filter.milestone) {
|
|
7391
|
-
const filterKey = milestoneKey(filter.milestone);
|
|
7392
|
-
tasks = tasks.filter((t) => milestoneKey(t.milestone) === filterKey);
|
|
7393
|
-
}
|
|
7394
|
-
if ((filter == null ? void 0 : filter.labels) && filter.labels.length > 0) {
|
|
7395
|
-
tasks = tasks.filter((t) => {
|
|
7396
|
-
var _a;
|
|
7397
|
-
return (_a = filter.labels) == null ? void 0 : _a.some((label) => t.labels.includes(label));
|
|
7398
|
-
});
|
|
7399
|
-
}
|
|
7400
|
-
if (filter == null ? void 0 : filter.parentTaskId) {
|
|
7401
|
-
tasks = tasks.filter((t) => t.parentTaskId === filter.parentTaskId);
|
|
7402
|
-
}
|
|
7403
|
-
return sortTasks(tasks);
|
|
7404
|
-
}
|
|
7405
|
-
/**
|
|
7406
|
-
* Get tasks grouped by status
|
|
7407
|
-
*
|
|
7408
|
-
* This is the primary method for kanban-style displays.
|
|
7409
|
-
* Returns a Map with status as key and sorted tasks as value.
|
|
7410
|
-
* The Map preserves the order of statuses from config.
|
|
7411
|
-
*/
|
|
7412
|
-
getTasksByStatus() {
|
|
7413
|
-
this.ensureInitialized();
|
|
7414
|
-
const tasks = Array.from(this.tasks.values());
|
|
7415
|
-
return groupTasksByStatus(tasks, this.safeConfig.statuses);
|
|
7416
|
-
}
|
|
7417
|
-
/**
|
|
7418
|
-
* Get tasks grouped by milestone
|
|
7419
|
-
*
|
|
7420
|
-
* Returns a MilestoneSummary with:
|
|
7421
|
-
* - milestones: List of milestone IDs in display order
|
|
7422
|
-
* - buckets: Array of MilestoneBucket with tasks, progress, status counts
|
|
7423
|
-
*
|
|
7424
|
-
* The first bucket is always "Tasks without milestone".
|
|
7425
|
-
* Each bucket includes progress percentage based on done status.
|
|
7426
|
-
*
|
|
7427
|
-
* @example
|
|
7428
|
-
* ```typescript
|
|
7429
|
-
* const summary = core.getTasksByMilestone();
|
|
7430
|
-
* for (const bucket of summary.buckets) {
|
|
7431
|
-
* console.log(`${bucket.label}: ${bucket.progress}% complete`);
|
|
7432
|
-
* console.log(` ${bucket.doneCount}/${bucket.total} tasks done`);
|
|
7433
|
-
* }
|
|
7434
|
-
* ```
|
|
7435
|
-
*/
|
|
7436
|
-
getTasksByMilestone() {
|
|
7437
|
-
this.ensureInitialized();
|
|
7438
|
-
const tasks = Array.from(this.tasks.values());
|
|
7439
|
-
return groupTasksByMilestone(tasks, this.safeConfig.milestones, this.safeConfig.statuses);
|
|
7440
|
-
}
|
|
7441
|
-
// =========================================================================
|
|
7442
|
-
// Milestone CRUD Operations
|
|
7443
|
-
// =========================================================================
|
|
7444
|
-
/**
|
|
7445
|
-
* Get the milestones directory path
|
|
7446
|
-
*/
|
|
7447
|
-
getMilestonesDir() {
|
|
7448
|
-
return this.fs.join(this.projectRoot, "backlog", "milestones");
|
|
7449
|
-
}
|
|
7450
|
-
/**
|
|
7451
|
-
* List all milestones from the milestones directory
|
|
7452
|
-
*
|
|
7453
|
-
* @returns Array of Milestone objects sorted by ID
|
|
7454
|
-
*/
|
|
7455
|
-
async listMilestones() {
|
|
7456
|
-
const milestonesDir = this.getMilestonesDir();
|
|
7457
|
-
if (!await this.fs.exists(milestonesDir)) {
|
|
7458
|
-
return [];
|
|
7459
|
-
}
|
|
7460
|
-
const entries = await this.fs.readDir(milestonesDir);
|
|
7461
|
-
const milestones = [];
|
|
7462
|
-
for (const entry of entries) {
|
|
7463
|
-
if (!entry.endsWith(".md"))
|
|
7464
|
-
continue;
|
|
7465
|
-
if (entry.toLowerCase() === "readme.md")
|
|
7466
|
-
continue;
|
|
7467
|
-
const milestoneId = extractMilestoneIdFromFilename(entry);
|
|
7468
|
-
if (!milestoneId)
|
|
7469
|
-
continue;
|
|
7470
|
-
const filepath = this.fs.join(milestonesDir, entry);
|
|
7471
|
-
if (await this.fs.isDirectory(filepath))
|
|
7472
|
-
continue;
|
|
7473
|
-
try {
|
|
7474
|
-
const content2 = await this.fs.readFile(filepath);
|
|
7475
|
-
milestones.push(parseMilestoneMarkdown(content2));
|
|
7476
|
-
} catch (error) {
|
|
7477
|
-
console.warn(`Failed to parse milestone file ${filepath}:`, error);
|
|
7478
|
-
}
|
|
7479
|
-
}
|
|
7480
|
-
return milestones.sort((a, b) => a.id.localeCompare(b.id, void 0, { numeric: true }));
|
|
7481
|
-
}
|
|
7482
|
-
/**
|
|
7483
|
-
* Load a single milestone by ID
|
|
7484
|
-
*
|
|
7485
|
-
* @param id - Milestone ID (e.g., "m-0")
|
|
7486
|
-
* @returns Milestone or null if not found
|
|
7487
|
-
*/
|
|
7488
|
-
async loadMilestone(id) {
|
|
7489
|
-
const milestonesDir = this.getMilestonesDir();
|
|
7490
|
-
if (!await this.fs.exists(milestonesDir)) {
|
|
7491
|
-
return null;
|
|
7492
|
-
}
|
|
7493
|
-
const entries = await this.fs.readDir(milestonesDir);
|
|
7494
|
-
const milestoneFile = entries.find((entry) => {
|
|
7495
|
-
const fileId = extractMilestoneIdFromFilename(entry);
|
|
7496
|
-
return fileId === id;
|
|
7497
|
-
});
|
|
7498
|
-
if (!milestoneFile) {
|
|
7499
|
-
return null;
|
|
7500
|
-
}
|
|
7501
|
-
const filepath = this.fs.join(milestonesDir, milestoneFile);
|
|
7502
|
-
try {
|
|
7503
|
-
const content2 = await this.fs.readFile(filepath);
|
|
7504
|
-
return parseMilestoneMarkdown(content2);
|
|
7505
|
-
} catch {
|
|
7506
|
-
return null;
|
|
7507
|
-
}
|
|
7508
|
-
}
|
|
7509
|
-
/**
|
|
7510
|
-
* Create a new milestone
|
|
7511
|
-
*
|
|
7512
|
-
* @param input - Milestone creation input
|
|
7513
|
-
* @returns Created milestone
|
|
7514
|
-
*/
|
|
7515
|
-
async createMilestone(input) {
|
|
7516
|
-
const startTime = Date.now();
|
|
7517
|
-
const span = this.tracer.startSpan("milestone.create", {
|
|
7518
|
-
attributes: {
|
|
7519
|
-
"input.title": input.title,
|
|
7520
|
-
"input.hasDescription": input.description !== void 0
|
|
7521
|
-
}
|
|
7522
|
-
});
|
|
7523
|
-
return await context.with(trace.setSpan(context.active(), span), async () => {
|
|
7524
|
-
try {
|
|
7525
|
-
span.addEvent("milestone.create.started", {
|
|
7526
|
-
"input.title": input.title,
|
|
7527
|
-
"input.hasDescription": input.description !== void 0
|
|
7528
|
-
});
|
|
7529
|
-
const milestonesDir = this.getMilestonesDir();
|
|
7530
|
-
await this.fs.createDir(milestonesDir, { recursive: true });
|
|
7531
|
-
const entries = await this.fs.readDir(milestonesDir).catch(() => []);
|
|
7532
|
-
const existingIds = entries.map((f) => {
|
|
7533
|
-
const match = f.match(/^m-(\d+)/);
|
|
7534
|
-
return (match == null ? void 0 : match[1]) ? parseInt(match[1], 10) : -1;
|
|
7535
|
-
}).filter((id2) => id2 >= 0);
|
|
7536
|
-
const nextId = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 0;
|
|
7537
|
-
const id = `m-${nextId}`;
|
|
7538
|
-
const description = input.description || `Milestone: ${input.title}`;
|
|
7539
|
-
const tempMilestone = {
|
|
7540
|
-
id,
|
|
7541
|
-
title: input.title,
|
|
7542
|
-
description,
|
|
7543
|
-
rawContent: "",
|
|
7544
|
-
tasks: []
|
|
7545
|
-
};
|
|
7546
|
-
const content2 = serializeMilestoneMarkdown(tempMilestone);
|
|
7547
|
-
const milestone = {
|
|
7548
|
-
id,
|
|
7549
|
-
title: input.title,
|
|
7550
|
-
description,
|
|
7551
|
-
rawContent: content2,
|
|
7552
|
-
tasks: []
|
|
7553
|
-
};
|
|
7554
|
-
const filename = getMilestoneFilename(id, input.title);
|
|
7555
|
-
const filepath = this.fs.join(milestonesDir, filename);
|
|
7556
|
-
await this.fs.writeFile(filepath, content2);
|
|
7557
|
-
span.addEvent("milestone.create.complete", {
|
|
7558
|
-
"output.milestoneId": id,
|
|
7559
|
-
"duration.ms": Date.now() - startTime
|
|
7560
|
-
});
|
|
7561
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7562
|
-
span.end();
|
|
7563
|
-
return milestone;
|
|
7564
|
-
} catch (error) {
|
|
7565
|
-
span.addEvent("milestone.create.error", {
|
|
7566
|
-
"error.type": error instanceof Error ? error.name : "UnknownError",
|
|
7567
|
-
"error.message": error instanceof Error ? error.message : String(error)
|
|
7568
|
-
});
|
|
7569
|
-
span.setStatus({
|
|
7570
|
-
code: SpanStatusCode.ERROR,
|
|
7571
|
-
message: error instanceof Error ? error.message : String(error)
|
|
7572
|
-
});
|
|
7573
|
-
span.end();
|
|
7574
|
-
throw error;
|
|
7575
|
-
}
|
|
7576
|
-
});
|
|
7577
|
-
}
|
|
7578
|
-
/**
|
|
7579
|
-
* Update an existing milestone
|
|
7580
|
-
*
|
|
7581
|
-
* @param id - Milestone ID to update
|
|
7582
|
-
* @param input - Fields to update
|
|
7583
|
-
* @returns Updated milestone or null if not found
|
|
7584
|
-
*/
|
|
7585
|
-
async updateMilestone(id, input) {
|
|
7586
|
-
const startTime = Date.now();
|
|
7587
|
-
const span = this.tracer.startSpan("milestone.update", {
|
|
7588
|
-
attributes: {
|
|
7589
|
-
"input.milestoneId": id,
|
|
7590
|
-
"input.hasTitle": input.title !== void 0,
|
|
7591
|
-
"input.hasDescription": input.description !== void 0
|
|
7592
|
-
}
|
|
7593
|
-
});
|
|
7594
|
-
return await context.with(trace.setSpan(context.active(), span), async () => {
|
|
7595
|
-
try {
|
|
7596
|
-
span.addEvent("milestone.update.started", {
|
|
7597
|
-
"input.milestoneId": id,
|
|
7598
|
-
"input.hasTitle": input.title !== void 0,
|
|
7599
|
-
"input.hasDescription": input.description !== void 0
|
|
7600
|
-
});
|
|
7601
|
-
const existing = await this.loadMilestone(id);
|
|
7602
|
-
if (!existing) {
|
|
7603
|
-
span.addEvent("milestone.update.error", {
|
|
7604
|
-
"error.type": "NotFoundError",
|
|
7605
|
-
"error.message": "Milestone not found",
|
|
7606
|
-
"input.milestoneId": id
|
|
7607
|
-
});
|
|
7608
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7609
|
-
span.end();
|
|
7610
|
-
return null;
|
|
7611
|
-
}
|
|
7612
|
-
const milestonesDir = this.getMilestonesDir();
|
|
7613
|
-
const entries = await this.fs.readDir(milestonesDir);
|
|
7614
|
-
const currentFile = entries.find((entry) => {
|
|
7615
|
-
const fileId = extractMilestoneIdFromFilename(entry);
|
|
7616
|
-
return fileId === id;
|
|
7617
|
-
});
|
|
7618
|
-
if (!currentFile) {
|
|
7619
|
-
span.addEvent("milestone.update.error", {
|
|
7620
|
-
"error.type": "NotFoundError",
|
|
7621
|
-
"error.message": "Milestone file not found",
|
|
7622
|
-
"input.milestoneId": id
|
|
7623
|
-
});
|
|
7624
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7625
|
-
span.end();
|
|
7626
|
-
return null;
|
|
7627
|
-
}
|
|
7628
|
-
const newTitle = input.title ?? existing.title;
|
|
7629
|
-
const newDescription = input.description ?? existing.description;
|
|
7630
|
-
const titleChanged = input.title !== void 0 && input.title !== existing.title;
|
|
7631
|
-
const tempMilestone = {
|
|
7632
|
-
id: existing.id,
|
|
7633
|
-
title: newTitle,
|
|
7634
|
-
description: newDescription,
|
|
7635
|
-
rawContent: "",
|
|
7636
|
-
tasks: existing.tasks
|
|
7637
|
-
};
|
|
7638
|
-
const content2 = serializeMilestoneMarkdown(tempMilestone);
|
|
7639
|
-
const updated = {
|
|
7640
|
-
id: existing.id,
|
|
7641
|
-
title: newTitle,
|
|
7642
|
-
description: newDescription,
|
|
7643
|
-
rawContent: content2,
|
|
7644
|
-
tasks: existing.tasks
|
|
7645
|
-
};
|
|
7646
|
-
const oldPath = this.fs.join(milestonesDir, currentFile);
|
|
7647
|
-
await this.fs.deleteFile(oldPath);
|
|
7648
|
-
const newFilename = getMilestoneFilename(id, updated.title);
|
|
7649
|
-
const newPath = this.fs.join(milestonesDir, newFilename);
|
|
7650
|
-
await this.fs.writeFile(newPath, content2);
|
|
7651
|
-
span.addEvent("milestone.update.complete", {
|
|
7652
|
-
"output.milestoneId": id,
|
|
7653
|
-
"output.titleChanged": titleChanged,
|
|
7654
|
-
"duration.ms": Date.now() - startTime
|
|
7655
|
-
});
|
|
7656
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7657
|
-
span.end();
|
|
7658
|
-
return updated;
|
|
7659
|
-
} catch (error) {
|
|
7660
|
-
span.addEvent("milestone.update.error", {
|
|
7661
|
-
"error.type": error instanceof Error ? error.name : "UnknownError",
|
|
7662
|
-
"error.message": error instanceof Error ? error.message : String(error),
|
|
7663
|
-
"input.milestoneId": id
|
|
7664
|
-
});
|
|
7665
|
-
span.setStatus({
|
|
7666
|
-
code: SpanStatusCode.ERROR,
|
|
7667
|
-
message: error instanceof Error ? error.message : String(error)
|
|
7668
|
-
});
|
|
7669
|
-
span.end();
|
|
7670
|
-
throw error;
|
|
7671
|
-
}
|
|
7672
|
-
});
|
|
7673
|
-
}
|
|
7674
|
-
/**
|
|
7675
|
-
* Delete a milestone
|
|
7676
|
-
*
|
|
7677
|
-
* @param id - Milestone ID to delete
|
|
7678
|
-
* @returns true if deleted, false if not found
|
|
7679
|
-
*/
|
|
7680
|
-
async deleteMilestone(id) {
|
|
7681
|
-
const startTime = Date.now();
|
|
7682
|
-
const span = this.tracer.startSpan("milestone.delete", {
|
|
7683
|
-
attributes: {
|
|
7684
|
-
"input.milestoneId": id
|
|
7685
|
-
}
|
|
7686
|
-
});
|
|
7687
|
-
return await context.with(trace.setSpan(context.active(), span), async () => {
|
|
7688
|
-
try {
|
|
7689
|
-
span.addEvent("milestone.delete.started", {
|
|
7690
|
-
"input.milestoneId": id
|
|
7691
|
-
});
|
|
7692
|
-
const milestonesDir = this.getMilestonesDir();
|
|
7693
|
-
if (!await this.fs.exists(milestonesDir)) {
|
|
7694
|
-
span.addEvent("milestone.delete.error", {
|
|
7695
|
-
"error.type": "NotFoundError",
|
|
7696
|
-
"error.message": "Milestones directory not found",
|
|
7697
|
-
"input.milestoneId": id
|
|
7698
|
-
});
|
|
7699
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7700
|
-
span.end();
|
|
7701
|
-
return false;
|
|
7702
|
-
}
|
|
7703
|
-
const entries = await this.fs.readDir(milestonesDir);
|
|
7704
|
-
const milestoneFile = entries.find((entry) => {
|
|
7705
|
-
const fileId = extractMilestoneIdFromFilename(entry);
|
|
7706
|
-
return fileId === id;
|
|
7707
|
-
});
|
|
7708
|
-
if (!milestoneFile) {
|
|
7709
|
-
span.addEvent("milestone.delete.error", {
|
|
7710
|
-
"error.type": "NotFoundError",
|
|
7711
|
-
"error.message": "Milestone not found",
|
|
7712
|
-
"input.milestoneId": id
|
|
7713
|
-
});
|
|
7714
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7715
|
-
span.end();
|
|
7716
|
-
return false;
|
|
7717
|
-
}
|
|
7718
|
-
const filepath = this.fs.join(milestonesDir, milestoneFile);
|
|
7719
|
-
await this.fs.deleteFile(filepath);
|
|
7720
|
-
span.addEvent("milestone.delete.complete", {
|
|
7721
|
-
"output.milestoneId": id,
|
|
7722
|
-
"output.deleted": true,
|
|
7723
|
-
"duration.ms": Date.now() - startTime
|
|
7724
|
-
});
|
|
7725
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7726
|
-
span.end();
|
|
7727
|
-
return true;
|
|
7728
|
-
} catch (error) {
|
|
7729
|
-
span.addEvent("milestone.delete.error", {
|
|
7730
|
-
"error.type": error instanceof Error ? error.name : "UnknownError",
|
|
7731
|
-
"error.message": error instanceof Error ? error.message : String(error),
|
|
7732
|
-
"input.milestoneId": id
|
|
7733
|
-
});
|
|
7734
|
-
span.setStatus({
|
|
7735
|
-
code: SpanStatusCode.ERROR,
|
|
7736
|
-
message: error instanceof Error ? error.message : String(error)
|
|
7737
|
-
});
|
|
7738
|
-
span.end();
|
|
7739
|
-
throw error;
|
|
7740
|
-
}
|
|
7741
|
-
});
|
|
7742
|
-
}
|
|
7743
|
-
/**
|
|
7744
|
-
* Get a single task by ID
|
|
7745
|
-
*
|
|
7746
|
-
* @param id - Task ID
|
|
7747
|
-
* @returns Task or undefined if not found
|
|
7748
|
-
*/
|
|
7749
|
-
getTask(id) {
|
|
7750
|
-
this.ensureInitialized();
|
|
7751
|
-
return this.tasks.get(id);
|
|
7752
|
-
}
|
|
7753
|
-
/**
|
|
7754
|
-
* List tasks with pagination
|
|
7755
|
-
*
|
|
7756
|
-
* @param filter - Filter and pagination options
|
|
7757
|
-
* @returns Paginated result with tasks
|
|
7758
|
-
*/
|
|
7759
|
-
listTasksPaginated(filter) {
|
|
7760
|
-
this.ensureInitialized();
|
|
7761
|
-
let tasks = this.applyFilters(Array.from(this.tasks.values()), filter);
|
|
7762
|
-
const pagination = (filter == null ? void 0 : filter.pagination) ?? {};
|
|
7763
|
-
const sortBy = pagination.sortBy ?? "title";
|
|
7764
|
-
const sortDirection = pagination.sortDirection ?? "asc";
|
|
7765
|
-
tasks = sortTasksBy(tasks, sortBy, sortDirection);
|
|
7766
|
-
const limit = pagination.limit ?? 10;
|
|
7767
|
-
const offset = pagination.offset ?? 0;
|
|
7768
|
-
const total = tasks.length;
|
|
7769
|
-
const items = tasks.slice(offset, offset + limit);
|
|
7770
|
-
return {
|
|
7771
|
-
items,
|
|
7772
|
-
total,
|
|
7773
|
-
hasMore: offset + limit < total,
|
|
7774
|
-
offset,
|
|
7775
|
-
limit
|
|
7776
|
-
};
|
|
7777
|
-
}
|
|
7778
|
-
/**
|
|
7779
|
-
* Get tasks by status with pagination per column
|
|
7780
|
-
*
|
|
7781
|
-
* @param pagination - Pagination options (applied per status)
|
|
7782
|
-
* @returns Paginated tasks grouped by status
|
|
7783
|
-
*/
|
|
7784
|
-
getTasksByStatusPaginated(pagination) {
|
|
7785
|
-
this.ensureInitialized();
|
|
7786
|
-
const limit = (pagination == null ? void 0 : pagination.limit) ?? 10;
|
|
7787
|
-
const offset = (pagination == null ? void 0 : pagination.offset) ?? 0;
|
|
7788
|
-
const sortBy = (pagination == null ? void 0 : pagination.sortBy) ?? "title";
|
|
7789
|
-
const sortDirection = (pagination == null ? void 0 : pagination.sortDirection) ?? "asc";
|
|
7790
|
-
const byStatus = /* @__PURE__ */ new Map();
|
|
7791
|
-
const allGrouped = /* @__PURE__ */ new Map();
|
|
7792
|
-
for (const status of this.safeConfig.statuses) {
|
|
7793
|
-
allGrouped.set(status, []);
|
|
7794
|
-
}
|
|
7795
|
-
for (const task of this.tasks.values()) {
|
|
7796
|
-
const list2 = allGrouped.get(task.status);
|
|
7797
|
-
if (list2) {
|
|
7798
|
-
list2.push(task);
|
|
7799
|
-
} else {
|
|
7800
|
-
allGrouped.set(task.status, [task]);
|
|
7801
|
-
}
|
|
7802
|
-
}
|
|
7803
|
-
for (const status of this.safeConfig.statuses) {
|
|
7804
|
-
let tasks = allGrouped.get(status) ?? [];
|
|
7805
|
-
tasks = sortTasksBy(tasks, sortBy, sortDirection);
|
|
7806
|
-
const total = tasks.length;
|
|
7807
|
-
const items = tasks.slice(offset, offset + limit);
|
|
7808
|
-
byStatus.set(status, {
|
|
7809
|
-
items,
|
|
7810
|
-
total,
|
|
7811
|
-
hasMore: offset + limit < total,
|
|
7812
|
-
offset,
|
|
7813
|
-
limit
|
|
7814
|
-
});
|
|
7815
|
-
}
|
|
7816
|
-
return {
|
|
7817
|
-
byStatus,
|
|
7818
|
-
statuses: this.safeConfig.statuses
|
|
7819
|
-
};
|
|
7820
|
-
}
|
|
7821
|
-
/**
|
|
7822
|
-
* Load more tasks for a specific status
|
|
7823
|
-
*
|
|
7824
|
-
* @param status - Status column to load more from
|
|
7825
|
-
* @param currentOffset - Current offset (items already loaded)
|
|
7826
|
-
* @param pagination - Pagination options (limit, sortBy, sortDirection)
|
|
7827
|
-
* @returns Paginated result for the status
|
|
7828
|
-
*/
|
|
7829
|
-
loadMoreForStatus(status, currentOffset, pagination) {
|
|
7830
|
-
this.ensureInitialized();
|
|
7831
|
-
const limit = (pagination == null ? void 0 : pagination.limit) ?? 10;
|
|
7832
|
-
const sortBy = (pagination == null ? void 0 : pagination.sortBy) ?? "title";
|
|
7833
|
-
const sortDirection = (pagination == null ? void 0 : pagination.sortDirection) ?? "asc";
|
|
7834
|
-
let tasks = Array.from(this.tasks.values()).filter((t) => t.status === status);
|
|
7835
|
-
tasks = sortTasksBy(tasks, sortBy, sortDirection);
|
|
7836
|
-
const total = tasks.length;
|
|
7837
|
-
const items = tasks.slice(currentOffset, currentOffset + limit);
|
|
7838
|
-
return {
|
|
7839
|
-
items,
|
|
7840
|
-
total,
|
|
7841
|
-
hasMore: currentOffset + limit < total,
|
|
7842
|
-
offset: currentOffset,
|
|
7843
|
-
limit
|
|
7844
|
-
};
|
|
7845
|
-
}
|
|
7846
|
-
/**
|
|
7847
|
-
* Reload all tasks from disk
|
|
7848
|
-
*
|
|
7849
|
-
* Useful after external changes to task files.
|
|
7850
|
-
*/
|
|
7851
|
-
async reload() {
|
|
7852
|
-
this.tasks.clear();
|
|
7853
|
-
this.initialized = false;
|
|
7854
|
-
await this.initialize();
|
|
7855
|
-
}
|
|
7856
|
-
// --- Private methods ---
|
|
7857
|
-
ensureInitialized() {
|
|
7858
|
-
if (!this.initialized && !this.lazyInitialized || !this.config) {
|
|
7859
|
-
throw new Error("Core not initialized. Call initialize() or initializeLazy() first.");
|
|
7860
|
-
}
|
|
7861
|
-
}
|
|
7862
|
-
/**
|
|
7863
|
-
* Get config with type safety (use after ensureInitialized)
|
|
7864
|
-
*/
|
|
7865
|
-
get safeConfig() {
|
|
7866
|
-
return this.config;
|
|
7867
|
-
}
|
|
7868
|
-
applyFilters(tasks, filter) {
|
|
7869
|
-
if (!filter)
|
|
7870
|
-
return tasks;
|
|
7871
|
-
let result = tasks;
|
|
7872
|
-
if (filter.status) {
|
|
7873
|
-
result = result.filter((t) => t.status === filter.status);
|
|
7874
|
-
}
|
|
7875
|
-
if (filter.assignee) {
|
|
7876
|
-
const assignee = filter.assignee;
|
|
7877
|
-
result = result.filter((t) => t.assignee.includes(assignee));
|
|
7878
|
-
}
|
|
7879
|
-
if (filter.priority) {
|
|
7880
|
-
result = result.filter((t) => t.priority === filter.priority);
|
|
7881
|
-
}
|
|
7882
|
-
if (filter.milestone) {
|
|
7883
|
-
const filterKey = milestoneKey(filter.milestone);
|
|
7884
|
-
result = result.filter((t) => milestoneKey(t.milestone) === filterKey);
|
|
7885
|
-
}
|
|
7886
|
-
if (filter.labels && filter.labels.length > 0) {
|
|
7887
|
-
result = result.filter((t) => {
|
|
7888
|
-
var _a;
|
|
7889
|
-
return (_a = filter.labels) == null ? void 0 : _a.some((label) => t.labels.includes(label));
|
|
7890
|
-
});
|
|
7891
|
-
}
|
|
7892
|
-
if (filter.parentTaskId) {
|
|
7893
|
-
result = result.filter((t) => t.parentTaskId === filter.parentTaskId);
|
|
7894
|
-
}
|
|
7895
|
-
return result;
|
|
7896
|
-
}
|
|
7897
|
-
async loadTasksFromDirectory(dir, source2) {
|
|
7898
|
-
const entries = await this.fs.readDir(dir);
|
|
7899
|
-
for (const entry of entries) {
|
|
7900
|
-
const fullPath = this.fs.join(dir, entry);
|
|
7901
|
-
if (await this.fs.isDirectory(fullPath)) {
|
|
7902
|
-
continue;
|
|
7903
|
-
}
|
|
7904
|
-
if (!entry.endsWith(".md")) {
|
|
7905
|
-
continue;
|
|
7906
|
-
}
|
|
7907
|
-
try {
|
|
7908
|
-
const content2 = await this.fs.readFile(fullPath);
|
|
7909
|
-
const task = parseTaskMarkdown(content2, fullPath);
|
|
7910
|
-
task.source = source2;
|
|
7911
|
-
this.tasks.set(task.id, task);
|
|
7912
|
-
} catch (error) {
|
|
7913
|
-
console.warn(`Failed to parse task file ${fullPath}:`, error);
|
|
7914
|
-
}
|
|
7915
|
-
}
|
|
7916
|
-
}
|
|
7917
|
-
// =========================================================================
|
|
7918
|
-
// Milestone-Task Sync Helpers
|
|
7919
|
-
// =========================================================================
|
|
7920
|
-
/**
|
|
7921
|
-
* Add a task ID to a milestone's tasks array
|
|
7922
|
-
*
|
|
7923
|
-
* @param taskId - Task ID to add
|
|
7924
|
-
* @param milestoneId - Milestone ID to update
|
|
7925
|
-
*/
|
|
7926
|
-
async addTaskToMilestone(taskId, milestoneId) {
|
|
7927
|
-
const milestone = await this.loadMilestone(milestoneId);
|
|
7928
|
-
if (!milestone) {
|
|
7929
|
-
console.warn(`Milestone ${milestoneId} not found when adding task ${taskId}`);
|
|
7930
|
-
return;
|
|
7931
|
-
}
|
|
7932
|
-
if (milestone.tasks.includes(taskId)) {
|
|
7933
|
-
return;
|
|
7934
|
-
}
|
|
7935
|
-
const updatedMilestone = {
|
|
7936
|
-
...milestone,
|
|
7937
|
-
tasks: [...milestone.tasks, taskId]
|
|
7938
|
-
};
|
|
7939
|
-
await this.writeMilestoneFile(updatedMilestone);
|
|
7940
|
-
}
|
|
7941
|
-
/**
|
|
7942
|
-
* Remove a task ID from a milestone's tasks array
|
|
7943
|
-
*
|
|
7944
|
-
* @param taskId - Task ID to remove
|
|
7945
|
-
* @param milestoneId - Milestone ID to update
|
|
7946
|
-
*/
|
|
7947
|
-
async removeTaskFromMilestone(taskId, milestoneId) {
|
|
7948
|
-
const milestone = await this.loadMilestone(milestoneId);
|
|
7949
|
-
if (!milestone) {
|
|
7950
|
-
return;
|
|
7951
|
-
}
|
|
7952
|
-
if (!milestone.tasks.includes(taskId)) {
|
|
7953
|
-
return;
|
|
7954
|
-
}
|
|
7955
|
-
const updatedMilestone = {
|
|
7956
|
-
...milestone,
|
|
7957
|
-
tasks: milestone.tasks.filter((id) => id !== taskId)
|
|
7958
|
-
};
|
|
7959
|
-
await this.writeMilestoneFile(updatedMilestone);
|
|
7960
|
-
}
|
|
7961
|
-
/**
|
|
7962
|
-
* Write a milestone to disk
|
|
7963
|
-
*/
|
|
7964
|
-
async writeMilestoneFile(milestone) {
|
|
7965
|
-
const milestonesDir = this.getMilestonesDir();
|
|
7966
|
-
const entries = await this.fs.readDir(milestonesDir).catch(() => []);
|
|
7967
|
-
const currentFile = entries.find((entry) => {
|
|
7968
|
-
const fileId = extractMilestoneIdFromFilename(entry);
|
|
7969
|
-
return fileId === milestone.id;
|
|
7970
|
-
});
|
|
7971
|
-
if (currentFile) {
|
|
7972
|
-
const oldPath = this.fs.join(milestonesDir, currentFile);
|
|
7973
|
-
await this.fs.deleteFile(oldPath).catch(() => {
|
|
7974
|
-
});
|
|
7975
|
-
}
|
|
7976
|
-
const content2 = serializeMilestoneMarkdown(milestone);
|
|
7977
|
-
const filename = getMilestoneFilename(milestone.id, milestone.title);
|
|
7978
|
-
const filepath = this.fs.join(milestonesDir, filename);
|
|
7979
|
-
await this.fs.writeFile(filepath, content2);
|
|
7980
|
-
}
|
|
7981
|
-
/**
|
|
7982
|
-
* Get the tasks directory path
|
|
7983
|
-
*/
|
|
7984
|
-
getTasksDir() {
|
|
7985
|
-
return this.fs.join(this.projectRoot, "backlog", "tasks");
|
|
7986
|
-
}
|
|
7987
|
-
/**
|
|
7988
|
-
* Get the completed directory path
|
|
7989
|
-
*/
|
|
7990
|
-
getCompletedDir() {
|
|
7991
|
-
return this.fs.join(this.projectRoot, "backlog", "completed");
|
|
7992
|
-
}
|
|
7993
|
-
// =========================================================================
|
|
7994
|
-
// Task CRUD Operations
|
|
7995
|
-
// =========================================================================
|
|
7996
|
-
/**
|
|
7997
|
-
* Create a new task
|
|
7998
|
-
*
|
|
7999
|
-
* @param input - Task creation input
|
|
8000
|
-
* @returns Created task
|
|
8001
|
-
*/
|
|
8002
|
-
async createTask(input) {
|
|
8003
|
-
this.ensureInitialized();
|
|
8004
|
-
const startTime = Date.now();
|
|
8005
|
-
const span = this.tracer.startSpan("task.create", {
|
|
8006
|
-
attributes: {
|
|
8007
|
-
"input.title": input.title,
|
|
8008
|
-
"input.status": input.status,
|
|
8009
|
-
"input.milestoneId": input.milestone
|
|
8010
|
-
}
|
|
8011
|
-
});
|
|
8012
|
-
return await context.with(trace.setSpan(context.active(), span), async () => {
|
|
8013
|
-
var _a, _b, _c, _d, _e2;
|
|
8014
|
-
try {
|
|
8015
|
-
span.addEvent("task.create.started", {
|
|
8016
|
-
"input.title": input.title,
|
|
8017
|
-
"input.status": input.status,
|
|
8018
|
-
"input.milestoneId": input.milestone
|
|
8019
|
-
});
|
|
8020
|
-
const tasksDir = this.getTasksDir();
|
|
8021
|
-
await this.fs.createDir(tasksDir, { recursive: true });
|
|
8022
|
-
const existingIds = Array.from(this.lazyInitialized ? this.taskIndex.keys() : this.tasks.keys()).map((id) => parseInt(id.replace(/\D/g, ""), 10)).filter((n) => !Number.isNaN(n));
|
|
8023
|
-
const nextId = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 1;
|
|
8024
|
-
const taskId = String(nextId);
|
|
8025
|
-
const configStatuses = ((_a = this.config) == null ? void 0 : _a.statuses) || [
|
|
8026
|
-
DEFAULT_TASK_STATUSES.TODO,
|
|
8027
|
-
DEFAULT_TASK_STATUSES.IN_PROGRESS,
|
|
8028
|
-
DEFAULT_TASK_STATUSES.DONE
|
|
8029
|
-
];
|
|
8030
|
-
let status = input.status || ((_b = this.config) == null ? void 0 : _b.defaultStatus) || DEFAULT_TASK_STATUSES.TODO;
|
|
8031
|
-
if (!configStatuses.includes(status)) {
|
|
8032
|
-
console.warn(`Warning: Status "${status}" is not in configured statuses [${configStatuses.join(", ")}]. Using default status "${((_c = this.config) == null ? void 0 : _c.defaultStatus) || DEFAULT_TASK_STATUSES.TODO}" instead.`);
|
|
8033
|
-
status = ((_d = this.config) == null ? void 0 : _d.defaultStatus) || DEFAULT_TASK_STATUSES.TODO;
|
|
8034
|
-
}
|
|
8035
|
-
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8036
|
-
const task = {
|
|
8037
|
-
id: taskId,
|
|
8038
|
-
title: input.title,
|
|
8039
|
-
status,
|
|
8040
|
-
priority: input.priority,
|
|
8041
|
-
assignee: input.assignee || [],
|
|
8042
|
-
createdDate: now,
|
|
8043
|
-
labels: input.labels || [],
|
|
8044
|
-
milestone: input.milestone,
|
|
8045
|
-
dependencies: input.dependencies || [],
|
|
8046
|
-
references: input.references || [],
|
|
8047
|
-
parentTaskId: input.parentTaskId,
|
|
8048
|
-
description: input.description,
|
|
8049
|
-
implementationPlan: input.implementationPlan,
|
|
8050
|
-
implementationNotes: input.implementationNotes,
|
|
8051
|
-
acceptanceCriteriaItems: (_e2 = input.acceptanceCriteria) == null ? void 0 : _e2.map((ac, i) => ({
|
|
8052
|
-
index: i + 1,
|
|
8053
|
-
text: ac.text,
|
|
8054
|
-
checked: ac.checked || false
|
|
8055
|
-
})),
|
|
8056
|
-
rawContent: input.rawContent,
|
|
8057
|
-
source: "local"
|
|
8058
|
-
};
|
|
8059
|
-
const content2 = serializeTaskMarkdown(task);
|
|
8060
|
-
const safeTitle = input.title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").slice(0, 50);
|
|
8061
|
-
const filename = `${taskId} - ${safeTitle}.md`;
|
|
8062
|
-
const filepath = this.fs.join(tasksDir, filename);
|
|
8063
|
-
await this.fs.writeFile(filepath, content2);
|
|
8064
|
-
task.filePath = filepath;
|
|
8065
|
-
this.tasks.set(taskId, task);
|
|
8066
|
-
if (this.lazyInitialized) {
|
|
8067
|
-
const relativePath = filepath.replace(`${this.projectRoot}/`, "");
|
|
8068
|
-
this.taskIndex.set(taskId, {
|
|
8069
|
-
id: taskId,
|
|
8070
|
-
filePath: relativePath,
|
|
8071
|
-
title: task.title,
|
|
8072
|
-
source: "tasks"
|
|
8073
|
-
});
|
|
8074
|
-
}
|
|
8075
|
-
if (input.milestone) {
|
|
8076
|
-
await this.addTaskToMilestone(taskId, input.milestone);
|
|
8077
|
-
}
|
|
8078
|
-
span.addEvent("task.create.complete", {
|
|
8079
|
-
"output.taskId": taskId,
|
|
8080
|
-
"output.taskIndex": nextId,
|
|
8081
|
-
"duration.ms": Date.now() - startTime
|
|
8082
|
-
});
|
|
8083
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8084
|
-
span.end();
|
|
8085
|
-
return task;
|
|
8086
|
-
} catch (error) {
|
|
8087
|
-
span.addEvent("task.create.error", {
|
|
8088
|
-
"error.type": error instanceof Error ? error.name : "UnknownError",
|
|
8089
|
-
"error.message": error instanceof Error ? error.message : String(error)
|
|
8090
|
-
});
|
|
8091
|
-
span.setStatus({
|
|
8092
|
-
code: SpanStatusCode.ERROR,
|
|
8093
|
-
message: error instanceof Error ? error.message : String(error)
|
|
8094
|
-
});
|
|
8095
|
-
span.end();
|
|
8096
|
-
throw error;
|
|
8097
|
-
}
|
|
8098
|
-
});
|
|
8099
|
-
}
|
|
8100
|
-
/**
|
|
8101
|
-
* Update an existing task
|
|
8102
|
-
*
|
|
8103
|
-
* @param id - Task ID to update
|
|
8104
|
-
* @param input - Fields to update
|
|
8105
|
-
* @returns Updated task or null if not found
|
|
8106
|
-
*/
|
|
8107
|
-
async updateTask(id, input) {
|
|
8108
|
-
this.ensureInitialized();
|
|
8109
|
-
const startTime = Date.now();
|
|
8110
|
-
const span = this.tracer.startSpan("task.update", {
|
|
8111
|
-
attributes: {
|
|
8112
|
-
"input.taskId": id,
|
|
8113
|
-
"input.hasTitle": input.title !== void 0,
|
|
8114
|
-
"input.hasStatus": input.status !== void 0,
|
|
8115
|
-
"input.hasMilestone": input.milestone !== void 0
|
|
8116
|
-
}
|
|
8117
|
-
});
|
|
8118
|
-
return await context.with(trace.setSpan(context.active(), span), async () => {
|
|
8119
|
-
try {
|
|
8120
|
-
span.addEvent("task.update.started", {
|
|
8121
|
-
"input.taskId": id,
|
|
8122
|
-
"input.hasTitle": input.title !== void 0,
|
|
8123
|
-
"input.hasStatus": input.status !== void 0,
|
|
8124
|
-
"input.hasMilestone": input.milestone !== void 0
|
|
8125
|
-
});
|
|
8126
|
-
const existing = this.tasks.get(id);
|
|
8127
|
-
if (!existing) {
|
|
8128
|
-
span.addEvent("task.update.error", {
|
|
8129
|
-
"error.type": "NotFoundError",
|
|
8130
|
-
"error.message": "Task not found",
|
|
8131
|
-
"input.taskId": id
|
|
8132
|
-
});
|
|
8133
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8134
|
-
span.end();
|
|
8135
|
-
return null;
|
|
8136
|
-
}
|
|
8137
|
-
const oldMilestone = existing.milestone;
|
|
8138
|
-
const newMilestone = input.milestone === null ? void 0 : input.milestone !== void 0 ? input.milestone : oldMilestone;
|
|
8139
|
-
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8140
|
-
const updated = {
|
|
8141
|
-
...existing,
|
|
8142
|
-
title: input.title ?? existing.title,
|
|
8143
|
-
status: input.status ?? existing.status,
|
|
8144
|
-
priority: input.priority ?? existing.priority,
|
|
8145
|
-
milestone: newMilestone,
|
|
8146
|
-
updatedDate: now,
|
|
8147
|
-
description: input.description ?? existing.description,
|
|
8148
|
-
implementationPlan: input.clearImplementationPlan ? void 0 : input.implementationPlan ?? existing.implementationPlan,
|
|
8149
|
-
implementationNotes: input.clearImplementationNotes ? void 0 : input.implementationNotes ?? existing.implementationNotes,
|
|
8150
|
-
ordinal: input.ordinal ?? existing.ordinal,
|
|
8151
|
-
dependencies: input.dependencies ?? existing.dependencies,
|
|
8152
|
-
references: input.references ?? existing.references ?? []
|
|
8153
|
-
};
|
|
8154
|
-
if (input.labels) {
|
|
8155
|
-
updated.labels = input.labels;
|
|
8156
|
-
} else {
|
|
8157
|
-
if (input.addLabels) {
|
|
8158
|
-
updated.labels = [.../* @__PURE__ */ new Set([...updated.labels, ...input.addLabels])];
|
|
8159
|
-
}
|
|
8160
|
-
if (input.removeLabels) {
|
|
8161
|
-
updated.labels = updated.labels.filter((l) => {
|
|
8162
|
-
var _a;
|
|
8163
|
-
return !((_a = input.removeLabels) == null ? void 0 : _a.includes(l));
|
|
8164
|
-
});
|
|
8165
|
-
}
|
|
8166
|
-
}
|
|
8167
|
-
if (input.assignee) {
|
|
8168
|
-
updated.assignee = input.assignee;
|
|
8169
|
-
}
|
|
8170
|
-
if (input.addDependencies) {
|
|
8171
|
-
updated.dependencies = [.../* @__PURE__ */ new Set([...updated.dependencies, ...input.addDependencies])];
|
|
8172
|
-
}
|
|
8173
|
-
if (input.removeDependencies) {
|
|
8174
|
-
updated.dependencies = updated.dependencies.filter((d) => {
|
|
8175
|
-
var _a;
|
|
8176
|
-
return !((_a = input.removeDependencies) == null ? void 0 : _a.includes(d));
|
|
8177
|
-
});
|
|
8178
|
-
}
|
|
8179
|
-
if (input.addReferences) {
|
|
8180
|
-
updated.references = [
|
|
8181
|
-
.../* @__PURE__ */ new Set([...updated.references || [], ...input.addReferences])
|
|
8182
|
-
];
|
|
8183
|
-
}
|
|
8184
|
-
if (input.removeReferences) {
|
|
8185
|
-
updated.references = (updated.references || []).filter((r2) => {
|
|
8186
|
-
var _a;
|
|
8187
|
-
return !((_a = input.removeReferences) == null ? void 0 : _a.includes(r2));
|
|
8188
|
-
});
|
|
8189
|
-
}
|
|
8190
|
-
if (input.acceptanceCriteria) {
|
|
8191
|
-
updated.acceptanceCriteriaItems = input.acceptanceCriteria.map((ac, i) => ({
|
|
8192
|
-
index: i + 1,
|
|
8193
|
-
text: ac.text,
|
|
8194
|
-
checked: ac.checked || false
|
|
8195
|
-
}));
|
|
8196
|
-
}
|
|
8197
|
-
const content2 = serializeTaskMarkdown(updated);
|
|
8198
|
-
if (existing.filePath) {
|
|
8199
|
-
await this.fs.deleteFile(existing.filePath).catch(() => {
|
|
8200
|
-
});
|
|
8201
|
-
}
|
|
8202
|
-
const tasksDir = this.getTasksDir();
|
|
8203
|
-
const safeTitle = updated.title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").slice(0, 50);
|
|
8204
|
-
const filename = `${id} - ${safeTitle}.md`;
|
|
8205
|
-
const filepath = this.fs.join(tasksDir, filename);
|
|
8206
|
-
await this.fs.writeFile(filepath, content2);
|
|
8207
|
-
updated.filePath = filepath;
|
|
8208
|
-
this.tasks.set(id, updated);
|
|
8209
|
-
const milestoneChanged = milestoneKey(oldMilestone) !== milestoneKey(newMilestone);
|
|
8210
|
-
if (milestoneChanged) {
|
|
8211
|
-
if (oldMilestone) {
|
|
8212
|
-
await this.removeTaskFromMilestone(id, oldMilestone);
|
|
8213
|
-
}
|
|
8214
|
-
if (newMilestone) {
|
|
8215
|
-
await this.addTaskToMilestone(id, newMilestone);
|
|
8216
|
-
}
|
|
8217
|
-
}
|
|
8218
|
-
span.addEvent("task.update.complete", {
|
|
8219
|
-
"output.taskId": id,
|
|
8220
|
-
"output.statusChanged": input.status !== void 0,
|
|
8221
|
-
"duration.ms": Date.now() - startTime
|
|
8222
|
-
});
|
|
8223
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8224
|
-
span.end();
|
|
8225
|
-
return updated;
|
|
8226
|
-
} catch (error) {
|
|
8227
|
-
span.addEvent("task.update.error", {
|
|
8228
|
-
"error.type": error instanceof Error ? error.name : "UnknownError",
|
|
8229
|
-
"error.message": error instanceof Error ? error.message : String(error),
|
|
8230
|
-
"input.taskId": id
|
|
8231
|
-
});
|
|
8232
|
-
span.setStatus({
|
|
8233
|
-
code: SpanStatusCode.ERROR,
|
|
8234
|
-
message: error instanceof Error ? error.message : String(error)
|
|
8235
|
-
});
|
|
8236
|
-
span.end();
|
|
8237
|
-
throw error;
|
|
8238
|
-
}
|
|
8239
|
-
});
|
|
8240
|
-
}
|
|
8241
|
-
/**
|
|
8242
|
-
* Delete a task
|
|
8243
|
-
*
|
|
8244
|
-
* @param id - Task ID to delete
|
|
8245
|
-
* @returns true if deleted, false if not found
|
|
8246
|
-
*/
|
|
8247
|
-
async deleteTask(id) {
|
|
8248
|
-
this.ensureInitialized();
|
|
8249
|
-
const startTime = Date.now();
|
|
8250
|
-
const span = this.tracer.startSpan("task.delete", {
|
|
8251
|
-
attributes: { "input.taskId": id }
|
|
8252
|
-
});
|
|
8253
|
-
return await context.with(trace.setSpan(context.active(), span), async () => {
|
|
8254
|
-
try {
|
|
8255
|
-
span.addEvent("task.delete.started", { "input.taskId": id });
|
|
8256
|
-
const task = this.tasks.get(id);
|
|
8257
|
-
if (!task) {
|
|
8258
|
-
span.addEvent("task.delete.error", {
|
|
8259
|
-
"error.type": "NotFoundError",
|
|
8260
|
-
"error.message": "Task not found",
|
|
8261
|
-
"input.taskId": id
|
|
8262
|
-
});
|
|
8263
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8264
|
-
span.end();
|
|
8265
|
-
return false;
|
|
8266
|
-
}
|
|
8267
|
-
if (task.milestone) {
|
|
8268
|
-
await this.removeTaskFromMilestone(id, task.milestone);
|
|
8269
|
-
}
|
|
8270
|
-
if (task.filePath) {
|
|
8271
|
-
try {
|
|
8272
|
-
await this.fs.deleteFile(task.filePath);
|
|
8273
|
-
} catch {
|
|
8274
|
-
}
|
|
8275
|
-
}
|
|
8276
|
-
this.tasks.delete(id);
|
|
8277
|
-
span.addEvent("task.delete.complete", {
|
|
8278
|
-
"output.taskId": id,
|
|
8279
|
-
"output.deleted": true,
|
|
8280
|
-
"duration.ms": Date.now() - startTime
|
|
8281
|
-
});
|
|
8282
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8283
|
-
span.end();
|
|
8284
|
-
return true;
|
|
8285
|
-
} catch (error) {
|
|
8286
|
-
span.addEvent("task.delete.error", {
|
|
8287
|
-
"error.type": error instanceof Error ? error.name : "UnknownError",
|
|
8288
|
-
"error.message": error instanceof Error ? error.message : String(error),
|
|
8289
|
-
"input.taskId": id
|
|
8290
|
-
});
|
|
8291
|
-
span.setStatus({
|
|
8292
|
-
code: SpanStatusCode.ERROR,
|
|
8293
|
-
message: error instanceof Error ? error.message : String(error)
|
|
8294
|
-
});
|
|
8295
|
-
span.end();
|
|
8296
|
-
throw error;
|
|
8297
|
-
}
|
|
8298
|
-
});
|
|
8299
|
-
}
|
|
8300
|
-
/**
|
|
8301
|
-
* Archive a task (move from tasks/ to completed/)
|
|
8302
|
-
*
|
|
8303
|
-
* @param id - Task ID to archive
|
|
8304
|
-
* @returns Archived task or null if not found or already archived
|
|
8305
|
-
*/
|
|
8306
|
-
async archiveTask(id) {
|
|
8307
|
-
this.ensureInitialized();
|
|
8308
|
-
const startTime = Date.now();
|
|
8309
|
-
const span = this.tracer.startSpan("task.archive", {
|
|
8310
|
-
attributes: { "input.taskId": id }
|
|
8311
|
-
});
|
|
8312
|
-
return await context.with(trace.setSpan(context.active(), span), async () => {
|
|
8313
|
-
try {
|
|
8314
|
-
span.addEvent("task.archive.started", { "input.taskId": id });
|
|
8315
|
-
const task = this.tasks.get(id);
|
|
8316
|
-
if (!task) {
|
|
8317
|
-
span.addEvent("task.archive.error", {
|
|
8318
|
-
"error.type": "NotFoundError",
|
|
8319
|
-
"error.message": "Task not found",
|
|
8320
|
-
"input.taskId": id
|
|
8321
|
-
});
|
|
8322
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8323
|
-
span.end();
|
|
8324
|
-
return null;
|
|
8325
|
-
}
|
|
8326
|
-
if (task.source === "completed") {
|
|
8327
|
-
span.addEvent("task.archive.error", {
|
|
8328
|
-
"error.type": "InvalidStateError",
|
|
8329
|
-
"error.message": "Task already archived",
|
|
8330
|
-
"input.taskId": id
|
|
8331
|
-
});
|
|
8332
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8333
|
-
span.end();
|
|
8334
|
-
return null;
|
|
8335
|
-
}
|
|
8336
|
-
const completedDir = this.getCompletedDir();
|
|
8337
|
-
await this.fs.createDir(completedDir, { recursive: true });
|
|
8338
|
-
const safeTitle = task.title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").slice(0, 50);
|
|
8339
|
-
const filename = `${id} - ${safeTitle}.md`;
|
|
8340
|
-
const newFilepath = this.fs.join(completedDir, filename);
|
|
8341
|
-
if (task.filePath) {
|
|
8342
|
-
try {
|
|
8343
|
-
await this.fs.deleteFile(task.filePath);
|
|
8344
|
-
} catch {
|
|
8345
|
-
}
|
|
8346
|
-
}
|
|
8347
|
-
const archived = {
|
|
8348
|
-
...task,
|
|
8349
|
-
source: "completed",
|
|
8350
|
-
filePath: newFilepath
|
|
8351
|
-
};
|
|
8352
|
-
const content2 = serializeTaskMarkdown(archived);
|
|
8353
|
-
await this.fs.writeFile(newFilepath, content2);
|
|
8354
|
-
this.tasks.set(id, archived);
|
|
8355
|
-
if (this.lazyInitialized) {
|
|
8356
|
-
const entry = this.taskIndex.get(id);
|
|
8357
|
-
if (entry) {
|
|
8358
|
-
entry.source = "completed";
|
|
8359
|
-
entry.filePath = newFilepath;
|
|
8360
|
-
}
|
|
8361
|
-
}
|
|
8362
|
-
span.addEvent("task.archive.complete", {
|
|
8363
|
-
"output.taskId": id,
|
|
8364
|
-
"output.newPath": newFilepath,
|
|
8365
|
-
"duration.ms": Date.now() - startTime
|
|
8366
|
-
});
|
|
8367
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8368
|
-
span.end();
|
|
8369
|
-
return archived;
|
|
8370
|
-
} catch (error) {
|
|
8371
|
-
span.addEvent("task.archive.error", {
|
|
8372
|
-
"error.type": error instanceof Error ? error.name : "UnknownError",
|
|
8373
|
-
"error.message": error instanceof Error ? error.message : String(error),
|
|
8374
|
-
"input.taskId": id
|
|
8375
|
-
});
|
|
8376
|
-
span.setStatus({
|
|
8377
|
-
code: SpanStatusCode.ERROR,
|
|
8378
|
-
message: error instanceof Error ? error.message : String(error)
|
|
8379
|
-
});
|
|
8380
|
-
span.end();
|
|
8381
|
-
throw error;
|
|
8382
|
-
}
|
|
8383
|
-
});
|
|
8384
|
-
}
|
|
8385
|
-
/**
|
|
8386
|
-
* Restore a task (move from completed/ to tasks/)
|
|
8387
|
-
*
|
|
8388
|
-
* @param id - Task ID to restore
|
|
8389
|
-
* @returns Restored task or null if not found or not archived
|
|
8390
|
-
*/
|
|
8391
|
-
async restoreTask(id) {
|
|
8392
|
-
this.ensureInitialized();
|
|
8393
|
-
const startTime = Date.now();
|
|
8394
|
-
const span = this.tracer.startSpan("task.restore", {
|
|
8395
|
-
attributes: { "input.taskId": id }
|
|
8396
|
-
});
|
|
8397
|
-
return await context.with(trace.setSpan(context.active(), span), async () => {
|
|
8398
|
-
try {
|
|
8399
|
-
span.addEvent("task.restore.started", { "input.taskId": id });
|
|
8400
|
-
const task = this.tasks.get(id);
|
|
8401
|
-
if (!task) {
|
|
8402
|
-
span.addEvent("task.restore.error", {
|
|
8403
|
-
"error.type": "NotFoundError",
|
|
8404
|
-
"error.message": "Task not found",
|
|
8405
|
-
"input.taskId": id
|
|
8406
|
-
});
|
|
8407
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8408
|
-
span.end();
|
|
8409
|
-
return null;
|
|
8410
|
-
}
|
|
8411
|
-
if (task.source !== "completed") {
|
|
8412
|
-
span.addEvent("task.restore.error", {
|
|
8413
|
-
"error.type": "InvalidStateError",
|
|
8414
|
-
"error.message": "Task not in completed",
|
|
8415
|
-
"input.taskId": id
|
|
8416
|
-
});
|
|
8417
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8418
|
-
span.end();
|
|
8419
|
-
return null;
|
|
8420
|
-
}
|
|
8421
|
-
const tasksDir = this.getTasksDir();
|
|
8422
|
-
await this.fs.createDir(tasksDir, { recursive: true });
|
|
8423
|
-
const safeTitle = task.title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").slice(0, 50);
|
|
8424
|
-
const filename = `${id} - ${safeTitle}.md`;
|
|
8425
|
-
const newFilepath = this.fs.join(tasksDir, filename);
|
|
8426
|
-
if (task.filePath) {
|
|
8427
|
-
try {
|
|
8428
|
-
await this.fs.deleteFile(task.filePath);
|
|
8429
|
-
} catch {
|
|
8430
|
-
}
|
|
8431
|
-
}
|
|
8432
|
-
const restored = {
|
|
8433
|
-
...task,
|
|
8434
|
-
source: "local",
|
|
8435
|
-
filePath: newFilepath
|
|
8436
|
-
};
|
|
8437
|
-
const content2 = serializeTaskMarkdown(restored);
|
|
8438
|
-
await this.fs.writeFile(newFilepath, content2);
|
|
8439
|
-
this.tasks.set(id, restored);
|
|
8440
|
-
if (this.lazyInitialized) {
|
|
8441
|
-
const entry = this.taskIndex.get(id);
|
|
8442
|
-
if (entry) {
|
|
8443
|
-
entry.source = "tasks";
|
|
8444
|
-
entry.filePath = newFilepath;
|
|
8445
|
-
}
|
|
8446
|
-
}
|
|
8447
|
-
span.addEvent("task.restore.complete", {
|
|
8448
|
-
"output.taskId": id,
|
|
8449
|
-
"output.newPath": newFilepath,
|
|
8450
|
-
"duration.ms": Date.now() - startTime
|
|
8451
|
-
});
|
|
8452
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
8453
|
-
span.end();
|
|
8454
|
-
return restored;
|
|
8455
|
-
} catch (error) {
|
|
8456
|
-
span.addEvent("task.restore.error", {
|
|
8457
|
-
"error.type": error instanceof Error ? error.name : "UnknownError",
|
|
8458
|
-
"error.message": error instanceof Error ? error.message : String(error),
|
|
8459
|
-
"input.taskId": id
|
|
8460
|
-
});
|
|
8461
|
-
span.setStatus({
|
|
8462
|
-
code: SpanStatusCode.ERROR,
|
|
8463
|
-
message: error instanceof Error ? error.message : String(error)
|
|
8464
|
-
});
|
|
8465
|
-
span.end();
|
|
8466
|
-
throw error;
|
|
8467
|
-
}
|
|
8468
|
-
});
|
|
8469
|
-
}
|
|
8470
|
-
/**
|
|
8471
|
-
* Load specific tasks by their IDs (for lazy loading milestone tasks)
|
|
8472
|
-
*
|
|
8473
|
-
* @param ids - Array of task IDs to load
|
|
8474
|
-
* @returns Array of loaded tasks (missing tasks excluded)
|
|
8475
|
-
*/
|
|
8476
|
-
async loadTasksByIds(ids2) {
|
|
8477
|
-
if (this.initialized) {
|
|
8478
|
-
return ids2.map((id) => this.tasks.get(id)).filter((t) => t !== void 0);
|
|
8479
|
-
}
|
|
8480
|
-
if (this.lazyInitialized) {
|
|
8481
|
-
return this.loadTasks(ids2);
|
|
8482
|
-
}
|
|
8483
|
-
throw new Error("Core not initialized. Call initialize() or initializeLazy() first.");
|
|
8484
|
-
}
|
|
8485
|
-
}
|
|
8486
|
-
const version = "1.2.7";
|
|
6339
|
+
const version = "1.2.10";
|
|
8487
6340
|
const packageJson = {
|
|
8488
6341
|
version
|
|
8489
6342
|
};
|