@obsfx/trekker 1.7.2 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/index.js +568 -274
- package/package.json +13 -2
package/dist/index.js
CHANGED
|
@@ -455,7 +455,7 @@ var require_customParseFormat = __commonJS((exports, module) => {
|
|
|
455
455
|
});
|
|
456
456
|
|
|
457
457
|
// src/index.ts
|
|
458
|
-
import { Command as
|
|
458
|
+
import { Command as Command14 } from "commander";
|
|
459
459
|
|
|
460
460
|
// src/commands/init.ts
|
|
461
461
|
import { Command } from "commander";
|
|
@@ -493,8 +493,8 @@ var epics = sqliteTable("epics", {
|
|
|
493
493
|
projectId: text("project_id").notNull(),
|
|
494
494
|
title: text("title").notNull(),
|
|
495
495
|
description: text("description"),
|
|
496
|
-
status: text("status").notNull().default("todo"),
|
|
497
|
-
priority: integer("priority").notNull().default(2),
|
|
496
|
+
status: text("status").notNull().default("todo").$type(),
|
|
497
|
+
priority: integer("priority").notNull().default(2).$type(),
|
|
498
498
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
499
499
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
500
500
|
});
|
|
@@ -505,8 +505,8 @@ var tasks = sqliteTable("tasks", {
|
|
|
505
505
|
parentTaskId: text("parent_task_id"),
|
|
506
506
|
title: text("title").notNull(),
|
|
507
507
|
description: text("description"),
|
|
508
|
-
priority: integer("priority").notNull().default(2),
|
|
509
|
-
status: text("status").notNull().default("todo"),
|
|
508
|
+
priority: integer("priority").notNull().default(2).$type(),
|
|
509
|
+
status: text("status").notNull().default("todo").$type(),
|
|
510
510
|
tags: text("tags"),
|
|
511
511
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
512
512
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
@@ -628,14 +628,15 @@ function createDb(cwd = process.cwd()) {
|
|
|
628
628
|
const dbPath = getDbPath(cwd);
|
|
629
629
|
sqliteInstance = new Database(dbPath);
|
|
630
630
|
dbInstance = drizzle(sqliteInstance, { schema: exports_schema });
|
|
631
|
-
sqliteInstance.
|
|
631
|
+
sqliteInstance.run(`
|
|
632
632
|
CREATE TABLE IF NOT EXISTS projects (
|
|
633
633
|
id TEXT PRIMARY KEY,
|
|
634
634
|
name TEXT NOT NULL UNIQUE,
|
|
635
635
|
created_at INTEGER NOT NULL,
|
|
636
636
|
updated_at INTEGER NOT NULL
|
|
637
|
-
)
|
|
638
|
-
|
|
637
|
+
)
|
|
638
|
+
`);
|
|
639
|
+
sqliteInstance.run(`
|
|
639
640
|
CREATE TABLE IF NOT EXISTS epics (
|
|
640
641
|
id TEXT PRIMARY KEY,
|
|
641
642
|
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
@@ -645,8 +646,9 @@ function createDb(cwd = process.cwd()) {
|
|
|
645
646
|
priority INTEGER NOT NULL DEFAULT 2,
|
|
646
647
|
created_at INTEGER NOT NULL,
|
|
647
648
|
updated_at INTEGER NOT NULL
|
|
648
|
-
)
|
|
649
|
-
|
|
649
|
+
)
|
|
650
|
+
`);
|
|
651
|
+
sqliteInstance.run(`
|
|
650
652
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
651
653
|
id TEXT PRIMARY KEY,
|
|
652
654
|
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
@@ -659,8 +661,9 @@ function createDb(cwd = process.cwd()) {
|
|
|
659
661
|
tags TEXT,
|
|
660
662
|
created_at INTEGER NOT NULL,
|
|
661
663
|
updated_at INTEGER NOT NULL
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
+
)
|
|
665
|
+
`);
|
|
666
|
+
sqliteInstance.run(`
|
|
664
667
|
CREATE TABLE IF NOT EXISTS comments (
|
|
665
668
|
id TEXT PRIMARY KEY,
|
|
666
669
|
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
@@ -668,26 +671,26 @@ function createDb(cwd = process.cwd()) {
|
|
|
668
671
|
content TEXT NOT NULL,
|
|
669
672
|
created_at INTEGER NOT NULL,
|
|
670
673
|
updated_at INTEGER NOT NULL
|
|
671
|
-
)
|
|
672
|
-
|
|
674
|
+
)
|
|
675
|
+
`);
|
|
676
|
+
sqliteInstance.run(`
|
|
673
677
|
CREATE TABLE IF NOT EXISTS dependencies (
|
|
674
678
|
id TEXT PRIMARY KEY,
|
|
675
679
|
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
676
680
|
depends_on_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
677
681
|
created_at INTEGER NOT NULL
|
|
678
|
-
)
|
|
679
|
-
|
|
682
|
+
)
|
|
683
|
+
`);
|
|
684
|
+
sqliteInstance.run(`
|
|
680
685
|
CREATE TABLE IF NOT EXISTS id_counters (
|
|
681
686
|
entity_type TEXT PRIMARY KEY,
|
|
682
687
|
counter INTEGER NOT NULL DEFAULT 0
|
|
683
|
-
)
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
-- Events table for history/logbook
|
|
688
|
+
)
|
|
689
|
+
`);
|
|
690
|
+
sqliteInstance.run("INSERT OR IGNORE INTO id_counters (entity_type, counter) VALUES ('task', 0)");
|
|
691
|
+
sqliteInstance.run("INSERT OR IGNORE INTO id_counters (entity_type, counter) VALUES ('epic', 0)");
|
|
692
|
+
sqliteInstance.run("INSERT OR IGNORE INTO id_counters (entity_type, counter) VALUES ('comment', 0)");
|
|
693
|
+
sqliteInstance.run(`
|
|
691
694
|
CREATE TABLE IF NOT EXISTS events (
|
|
692
695
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
693
696
|
action TEXT NOT NULL,
|
|
@@ -696,12 +699,11 @@ function createDb(cwd = process.cwd()) {
|
|
|
696
699
|
snapshot TEXT,
|
|
697
700
|
changes TEXT,
|
|
698
701
|
created_at INTEGER NOT NULL
|
|
699
|
-
)
|
|
700
|
-
|
|
701
|
-
CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_id);
|
|
702
|
-
CREATE INDEX IF NOT EXISTS idx_events_type_action ON events(entity_type, action);
|
|
703
|
-
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at);
|
|
702
|
+
)
|
|
704
703
|
`);
|
|
704
|
+
sqliteInstance.run("CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_id)");
|
|
705
|
+
sqliteInstance.run("CREATE INDEX IF NOT EXISTS idx_events_type_action ON events(entity_type, action)");
|
|
706
|
+
sqliteInstance.run("CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at)");
|
|
705
707
|
createSearchIndex(sqliteInstance);
|
|
706
708
|
createHistoryTriggers(sqliteInstance);
|
|
707
709
|
return dbInstance;
|
|
@@ -989,19 +991,8 @@ function deleteDb(cwd = process.cwd()) {
|
|
|
989
991
|
import { eq, sql } from "drizzle-orm";
|
|
990
992
|
|
|
991
993
|
// src/types/index.ts
|
|
992
|
-
var TASK_STATUSES = [
|
|
993
|
-
|
|
994
|
-
"in_progress",
|
|
995
|
-
"completed",
|
|
996
|
-
"wont_fix",
|
|
997
|
-
"archived"
|
|
998
|
-
];
|
|
999
|
-
var EPIC_STATUSES = [
|
|
1000
|
-
"todo",
|
|
1001
|
-
"in_progress",
|
|
1002
|
-
"completed",
|
|
1003
|
-
"archived"
|
|
1004
|
-
];
|
|
994
|
+
var TASK_STATUSES = ["todo", "in_progress", "completed", "wont_fix", "archived"];
|
|
995
|
+
var EPIC_STATUSES = ["todo", "in_progress", "completed", "archived"];
|
|
1005
996
|
var DEFAULT_PRIORITY = 2;
|
|
1006
997
|
var DEFAULT_TASK_STATUS = "todo";
|
|
1007
998
|
var DEFAULT_EPIC_STATUS = "todo";
|
|
@@ -1011,13 +1002,7 @@ var PAGINATION_DEFAULTS = {
|
|
|
1011
1002
|
HISTORY_PAGE_SIZE: 50,
|
|
1012
1003
|
DEFAULT_PAGE: 1
|
|
1013
1004
|
};
|
|
1014
|
-
var VALID_SORT_FIELDS = [
|
|
1015
|
-
"created",
|
|
1016
|
-
"updated",
|
|
1017
|
-
"title",
|
|
1018
|
-
"priority",
|
|
1019
|
-
"status"
|
|
1020
|
-
];
|
|
1005
|
+
var VALID_SORT_FIELDS = ["created", "updated", "title", "priority", "status"];
|
|
1021
1006
|
var LIST_ENTITY_TYPES = ["epic", "task", "subtask"];
|
|
1022
1007
|
var SEARCH_ENTITY_TYPES = ["epic", "task", "subtask", "comment"];
|
|
1023
1008
|
var PREFIX_MAP = {
|
|
@@ -1545,6 +1530,19 @@ function resolveOptions(options) {
|
|
|
1545
1530
|
};
|
|
1546
1531
|
}
|
|
1547
1532
|
|
|
1533
|
+
// src/utils/constants.ts
|
|
1534
|
+
var STATUS_PAD_WIDTH = 11;
|
|
1535
|
+
var TYPE_PAD_WIDTH = 7;
|
|
1536
|
+
var JSON_INDENT = 2;
|
|
1537
|
+
var MS_PER_SECOND = 1000;
|
|
1538
|
+
var MAX_PRIORITY = 5;
|
|
1539
|
+
var ACTION_PAD_WIDTH = 6;
|
|
1540
|
+
var RADIX_DECIMAL = 10;
|
|
1541
|
+
var TRUNCATE_OFFSET = 3;
|
|
1542
|
+
var TIMESTAMP_SLICE_END = 19;
|
|
1543
|
+
var TRUNCATE_DEFAULT = 40;
|
|
1544
|
+
var TRUNCATE_CONTENT = 60;
|
|
1545
|
+
|
|
1548
1546
|
// src/utils/output.ts
|
|
1549
1547
|
var toonMode = false;
|
|
1550
1548
|
function setToonMode(enabled) {
|
|
@@ -1559,7 +1557,7 @@ function output(data) {
|
|
|
1559
1557
|
} else if (typeof data === "string") {
|
|
1560
1558
|
console.log(data);
|
|
1561
1559
|
} else {
|
|
1562
|
-
console.log(JSON.stringify(data, null,
|
|
1560
|
+
console.log(JSON.stringify(data, null, JSON_INDENT));
|
|
1563
1561
|
}
|
|
1564
1562
|
}
|
|
1565
1563
|
function success(message, data) {
|
|
@@ -1583,7 +1581,11 @@ function error(message, details) {
|
|
|
1583
1581
|
}
|
|
1584
1582
|
}
|
|
1585
1583
|
function handleCommandError(err) {
|
|
1586
|
-
|
|
1584
|
+
if (err instanceof Error) {
|
|
1585
|
+
error(err.message);
|
|
1586
|
+
} else {
|
|
1587
|
+
error(String(err));
|
|
1588
|
+
}
|
|
1587
1589
|
process.exit(1);
|
|
1588
1590
|
}
|
|
1589
1591
|
function handleNotFound(entityType, id) {
|
|
@@ -1593,12 +1595,12 @@ function handleNotFound(entityType, id) {
|
|
|
1593
1595
|
function outputResult(data, formatter, successMessage) {
|
|
1594
1596
|
if (isToonMode()) {
|
|
1595
1597
|
output(data);
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
console.log(formatter(data));
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (successMessage) {
|
|
1601
|
+
success(successMessage);
|
|
1601
1602
|
}
|
|
1603
|
+
console.log(formatter(data));
|
|
1602
1604
|
}
|
|
1603
1605
|
function info(message) {
|
|
1604
1606
|
if (!toonMode) {
|
|
@@ -1654,46 +1656,83 @@ function formatComment(comment) {
|
|
|
1654
1656
|
return lines.join(`
|
|
1655
1657
|
`);
|
|
1656
1658
|
}
|
|
1657
|
-
function
|
|
1658
|
-
if (
|
|
1659
|
-
|
|
1659
|
+
function formatDependencyList(dependencies2, direction) {
|
|
1660
|
+
if (dependencies2.length === 0) {
|
|
1661
|
+
if (direction === "depends_on") {
|
|
1662
|
+
return "No dependencies.";
|
|
1663
|
+
}
|
|
1664
|
+
return "Does not block any tasks.";
|
|
1660
1665
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
const parent = task.parentTaskId ? ` (subtask of ${task.parentTaskId})` : "";
|
|
1664
|
-
return `${task.id} | ${task.status.padEnd(11)} | P${task.priority} | ${task.title}${tags}${parent}`;
|
|
1665
|
-
});
|
|
1666
|
-
return lines.join(`
|
|
1666
|
+
if (direction === "depends_on") {
|
|
1667
|
+
return dependencies2.map((d) => ` \u2192 depends on ${d.dependsOnId}`).join(`
|
|
1667
1668
|
`);
|
|
1669
|
+
}
|
|
1670
|
+
return dependencies2.map((d) => ` \u2192 blocks ${d.taskId}`).join(`
|
|
1671
|
+
`);
|
|
1672
|
+
}
|
|
1673
|
+
function formatPaginationFooter(total, page, limit) {
|
|
1674
|
+
const totalPages = Math.ceil(total / limit);
|
|
1675
|
+
if (totalPages > 1) {
|
|
1676
|
+
return `
|
|
1677
|
+
Page ${page} of ${totalPages}`;
|
|
1678
|
+
}
|
|
1679
|
+
return "";
|
|
1668
1680
|
}
|
|
1669
|
-
function
|
|
1670
|
-
|
|
1671
|
-
|
|
1681
|
+
function formatPaginatedTaskList(result) {
|
|
1682
|
+
const lines = [];
|
|
1683
|
+
lines.push(`${result.total} task(s) (page ${result.page}, ${result.limit} per page)
|
|
1684
|
+
`);
|
|
1685
|
+
if (result.items.length === 0) {
|
|
1686
|
+
lines.push("No tasks found.");
|
|
1687
|
+
return lines.join(`
|
|
1688
|
+
`);
|
|
1672
1689
|
}
|
|
1673
|
-
const
|
|
1674
|
-
|
|
1675
|
-
|
|
1690
|
+
for (const task of result.items) {
|
|
1691
|
+
let tags = "";
|
|
1692
|
+
if (task.tags) {
|
|
1693
|
+
tags = ` [${task.tags}]`;
|
|
1694
|
+
}
|
|
1695
|
+
let parent = "";
|
|
1696
|
+
if (task.parentTaskId) {
|
|
1697
|
+
parent = ` (subtask of ${task.parentTaskId})`;
|
|
1698
|
+
}
|
|
1699
|
+
lines.push(`${task.id} | ${task.status.padEnd(STATUS_PAD_WIDTH)} | P${task.priority} | ${task.title}${tags}${parent}`);
|
|
1700
|
+
}
|
|
1701
|
+
lines.push(formatPaginationFooter(result.total, result.page, result.limit));
|
|
1676
1702
|
return lines.join(`
|
|
1677
1703
|
`);
|
|
1678
1704
|
}
|
|
1679
|
-
function
|
|
1680
|
-
|
|
1681
|
-
|
|
1705
|
+
function formatPaginatedEpicList(result) {
|
|
1706
|
+
const lines = [];
|
|
1707
|
+
lines.push(`${result.total} epic(s) (page ${result.page}, ${result.limit} per page)
|
|
1708
|
+
`);
|
|
1709
|
+
if (result.items.length === 0) {
|
|
1710
|
+
lines.push("No epics found.");
|
|
1711
|
+
return lines.join(`
|
|
1712
|
+
`);
|
|
1682
1713
|
}
|
|
1683
|
-
|
|
1714
|
+
for (const epic of result.items) {
|
|
1715
|
+
lines.push(`${epic.id} | ${epic.status.padEnd(STATUS_PAD_WIDTH)} | P${epic.priority} | ${epic.title}`);
|
|
1716
|
+
}
|
|
1717
|
+
lines.push(formatPaginationFooter(result.total, result.page, result.limit));
|
|
1718
|
+
return lines.join(`
|
|
1684
1719
|
`);
|
|
1685
1720
|
}
|
|
1686
|
-
function
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
}
|
|
1690
|
-
if (direction === "depends_on") {
|
|
1691
|
-
return dependencies2.map((d) => ` \u2192 depends on ${d.dependsOnId}`).join(`
|
|
1721
|
+
function formatPaginatedCommentList(result) {
|
|
1722
|
+
const lines = [];
|
|
1723
|
+
lines.push(`${result.total} comment(s) (page ${result.page}, ${result.limit} per page)
|
|
1692
1724
|
`);
|
|
1693
|
-
|
|
1694
|
-
|
|
1725
|
+
if (result.items.length === 0) {
|
|
1726
|
+
lines.push("No comments found.");
|
|
1727
|
+
return lines.join(`
|
|
1695
1728
|
`);
|
|
1696
1729
|
}
|
|
1730
|
+
for (const c of result.items) {
|
|
1731
|
+
lines.push(`[${c.id}] ${c.author}: ${c.content}`);
|
|
1732
|
+
}
|
|
1733
|
+
lines.push(formatPaginationFooter(result.total, result.page, result.limit));
|
|
1734
|
+
return lines.join(`
|
|
1735
|
+
`);
|
|
1697
1736
|
}
|
|
1698
1737
|
|
|
1699
1738
|
// src/commands/init.ts
|
|
@@ -1706,8 +1745,7 @@ var initCommand = new Command("init").description("Initialize Trekker in the cur
|
|
|
1706
1745
|
initProject();
|
|
1707
1746
|
success("Trekker initialized successfully.");
|
|
1708
1747
|
} catch (err) {
|
|
1709
|
-
|
|
1710
|
-
process.exit(1);
|
|
1748
|
+
handleCommandError(err);
|
|
1711
1749
|
}
|
|
1712
1750
|
});
|
|
1713
1751
|
|
|
@@ -1730,8 +1768,7 @@ var wipeCommand = new Command2("wipe").description("Delete all Trekker data in t
|
|
|
1730
1768
|
wipeProject();
|
|
1731
1769
|
success("Trekker data deleted successfully.");
|
|
1732
1770
|
} catch (err) {
|
|
1733
|
-
|
|
1734
|
-
process.exit(1);
|
|
1771
|
+
handleCommandError(err);
|
|
1735
1772
|
}
|
|
1736
1773
|
});
|
|
1737
1774
|
function confirm(prompt) {
|
|
@@ -1751,7 +1788,7 @@ function confirm(prompt) {
|
|
|
1751
1788
|
import { Command as Command3 } from "commander";
|
|
1752
1789
|
|
|
1753
1790
|
// src/services/epic.ts
|
|
1754
|
-
import { eq as eq2, and, isNull } from "drizzle-orm";
|
|
1791
|
+
import { eq as eq2, and, isNull, desc, sql as sql2 } from "drizzle-orm";
|
|
1755
1792
|
function createEpic(input) {
|
|
1756
1793
|
const db = getDb();
|
|
1757
1794
|
const project = db.select().from(projects).get();
|
|
@@ -1775,15 +1812,21 @@ function createEpic(input) {
|
|
|
1775
1812
|
}
|
|
1776
1813
|
function getEpic(id) {
|
|
1777
1814
|
const db = getDb();
|
|
1778
|
-
|
|
1779
|
-
return result;
|
|
1815
|
+
return db.select().from(epics).where(eq2(epics.id, id)).get();
|
|
1780
1816
|
}
|
|
1781
|
-
function listEpics(
|
|
1817
|
+
function listEpics(options) {
|
|
1782
1818
|
const db = getDb();
|
|
1783
|
-
|
|
1784
|
-
|
|
1819
|
+
const limit = options?.limit ?? PAGINATION_DEFAULTS.LIST_PAGE_SIZE;
|
|
1820
|
+
const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
|
|
1821
|
+
const offset = (page - 1) * limit;
|
|
1822
|
+
let where;
|
|
1823
|
+
if (options?.status) {
|
|
1824
|
+
where = eq2(epics.status, options.status);
|
|
1785
1825
|
}
|
|
1786
|
-
|
|
1826
|
+
const countRow = db.select({ count: sql2`count(*)` }).from(epics).where(where).get();
|
|
1827
|
+
const total = countRow?.count ?? 0;
|
|
1828
|
+
const items = db.select().from(epics).where(where).orderBy(desc(epics.createdAt)).limit(limit).offset(offset).all();
|
|
1829
|
+
return { total, page, limit, items };
|
|
1787
1830
|
}
|
|
1788
1831
|
function updateEpic(id, input) {
|
|
1789
1832
|
const db = getDb();
|
|
@@ -1794,16 +1837,24 @@ function updateEpic(id, input) {
|
|
|
1794
1837
|
const updates = {
|
|
1795
1838
|
updatedAt: new Date
|
|
1796
1839
|
};
|
|
1797
|
-
if (input.title !== undefined)
|
|
1840
|
+
if (input.title !== undefined) {
|
|
1798
1841
|
updates.title = input.title;
|
|
1799
|
-
|
|
1842
|
+
}
|
|
1843
|
+
if (input.description !== undefined) {
|
|
1800
1844
|
updates.description = input.description;
|
|
1801
|
-
|
|
1845
|
+
}
|
|
1846
|
+
if (input.status !== undefined) {
|
|
1802
1847
|
updates.status = input.status;
|
|
1803
|
-
|
|
1848
|
+
}
|
|
1849
|
+
if (input.priority !== undefined) {
|
|
1804
1850
|
updates.priority = input.priority;
|
|
1851
|
+
}
|
|
1805
1852
|
db.update(epics).set(updates).where(eq2(epics.id, id)).run();
|
|
1806
|
-
|
|
1853
|
+
const updated = getEpic(id);
|
|
1854
|
+
if (!updated) {
|
|
1855
|
+
throw new Error(`Epic not found after update: ${id}`);
|
|
1856
|
+
}
|
|
1857
|
+
return updated;
|
|
1807
1858
|
}
|
|
1808
1859
|
function deleteEpic(id) {
|
|
1809
1860
|
const db = getDb();
|
|
@@ -1847,36 +1898,41 @@ function completeEpic(id) {
|
|
|
1847
1898
|
}
|
|
1848
1899
|
|
|
1849
1900
|
// src/utils/validator.ts
|
|
1901
|
+
var TASK_STATUS_SET = new Set(TASK_STATUSES);
|
|
1902
|
+
var EPIC_STATUS_SET = new Set(EPIC_STATUSES);
|
|
1903
|
+
var LIST_ENTITY_TYPE_SET = new Set(LIST_ENTITY_TYPES);
|
|
1904
|
+
var SEARCH_ENTITY_TYPE_SET = new Set(SEARCH_ENTITY_TYPES);
|
|
1850
1905
|
function isValidTaskStatus(status) {
|
|
1851
|
-
return
|
|
1906
|
+
return TASK_STATUS_SET.has(status);
|
|
1852
1907
|
}
|
|
1853
1908
|
function isValidEpicStatus(status) {
|
|
1854
|
-
return
|
|
1909
|
+
return EPIC_STATUS_SET.has(status);
|
|
1855
1910
|
}
|
|
1856
1911
|
function isValidPriority(priority) {
|
|
1857
|
-
return Number.isInteger(priority) && priority >= 0 && priority <=
|
|
1912
|
+
return Number.isInteger(priority) && priority >= 0 && priority <= MAX_PRIORITY;
|
|
1858
1913
|
}
|
|
1859
1914
|
function parseStatus(status, type) {
|
|
1860
|
-
if (!status)
|
|
1915
|
+
if (!status) {
|
|
1861
1916
|
return;
|
|
1917
|
+
}
|
|
1862
1918
|
const normalizedStatus = status.toLowerCase().replace(/-/g, "_");
|
|
1863
1919
|
if (type === "task") {
|
|
1864
1920
|
if (!isValidTaskStatus(normalizedStatus)) {
|
|
1865
1921
|
throw new Error(`Invalid task status: ${status}. Valid values: ${TASK_STATUSES.join(", ")}`);
|
|
1866
1922
|
}
|
|
1867
1923
|
return normalizedStatus;
|
|
1868
|
-
} else {
|
|
1869
|
-
if (!isValidEpicStatus(normalizedStatus)) {
|
|
1870
|
-
throw new Error(`Invalid epic status: ${status}. Valid values: ${EPIC_STATUSES.join(", ")}`);
|
|
1871
|
-
}
|
|
1872
|
-
return normalizedStatus;
|
|
1873
1924
|
}
|
|
1925
|
+
if (!isValidEpicStatus(normalizedStatus)) {
|
|
1926
|
+
throw new Error(`Invalid epic status: ${status}. Valid values: ${EPIC_STATUSES.join(", ")}`);
|
|
1927
|
+
}
|
|
1928
|
+
return normalizedStatus;
|
|
1874
1929
|
}
|
|
1875
1930
|
function parsePriority(priority) {
|
|
1876
|
-
if (priority === undefined)
|
|
1931
|
+
if (priority === undefined) {
|
|
1877
1932
|
return;
|
|
1878
|
-
|
|
1879
|
-
|
|
1933
|
+
}
|
|
1934
|
+
const num = Number.parseInt(priority, RADIX_DECIMAL);
|
|
1935
|
+
if (Number.isNaN(num) || !isValidPriority(num)) {
|
|
1880
1936
|
throw new Error(`Invalid priority: ${priority}. Must be a number between 0 and 5.`);
|
|
1881
1937
|
}
|
|
1882
1938
|
return num;
|
|
@@ -1887,34 +1943,68 @@ function validateRequired(value, fieldName) {
|
|
|
1887
1943
|
}
|
|
1888
1944
|
}
|
|
1889
1945
|
function validatePagination(limit, page) {
|
|
1890
|
-
if (isNaN(limit) || limit < 1) {
|
|
1946
|
+
if (Number.isNaN(limit) || limit < 1) {
|
|
1891
1947
|
throw new Error("Invalid limit value");
|
|
1892
1948
|
}
|
|
1893
|
-
if (isNaN(page) || page < 1) {
|
|
1949
|
+
if (Number.isNaN(page) || page < 1) {
|
|
1894
1950
|
throw new Error("Invalid page value");
|
|
1895
1951
|
}
|
|
1896
1952
|
}
|
|
1953
|
+
function parsePaginationOptions(opts) {
|
|
1954
|
+
const limit = Number.parseInt(opts.limit, RADIX_DECIMAL);
|
|
1955
|
+
const page = Number.parseInt(opts.page, RADIX_DECIMAL);
|
|
1956
|
+
validatePagination(limit, page);
|
|
1957
|
+
return { limit, page };
|
|
1958
|
+
}
|
|
1897
1959
|
function validateListEntityTypes(types) {
|
|
1898
1960
|
for (const t of types) {
|
|
1899
|
-
if (!
|
|
1961
|
+
if (!LIST_ENTITY_TYPE_SET.has(t)) {
|
|
1900
1962
|
throw new Error(`Invalid type: ${t}. Valid types: ${LIST_ENTITY_TYPES.join(", ")}`);
|
|
1901
1963
|
}
|
|
1902
1964
|
}
|
|
1903
1965
|
}
|
|
1904
1966
|
function validateSearchEntityTypes(types) {
|
|
1905
1967
|
for (const t of types) {
|
|
1906
|
-
if (!
|
|
1968
|
+
if (!SEARCH_ENTITY_TYPE_SET.has(t)) {
|
|
1907
1969
|
throw new Error(`Invalid type: ${t}. Valid types: ${SEARCH_ENTITY_TYPES.join(", ")}`);
|
|
1908
1970
|
}
|
|
1909
1971
|
}
|
|
1910
1972
|
}
|
|
1911
1973
|
function validatePriorities(priorities) {
|
|
1912
1974
|
for (const p of priorities) {
|
|
1913
|
-
if (isNaN(p) || p < 0 || p >
|
|
1975
|
+
if (Number.isNaN(p) || p < 0 || p > MAX_PRIORITY) {
|
|
1914
1976
|
throw new Error(`Invalid priority: ${p}. Valid priorities: 0-5`);
|
|
1915
1977
|
}
|
|
1916
1978
|
}
|
|
1917
1979
|
}
|
|
1980
|
+
var VALID_HISTORY_TYPES = new Set([
|
|
1981
|
+
"epic",
|
|
1982
|
+
"task",
|
|
1983
|
+
"subtask",
|
|
1984
|
+
"comment",
|
|
1985
|
+
"dependency"
|
|
1986
|
+
]);
|
|
1987
|
+
var VALID_HISTORY_ACTIONS = new Set(["create", "update", "delete"]);
|
|
1988
|
+
function validateHistoryTypes(types) {
|
|
1989
|
+
for (const t of types) {
|
|
1990
|
+
if (!VALID_HISTORY_TYPES.has(t)) {
|
|
1991
|
+
throw new Error(`Invalid type: ${t}. Valid types: ${[...VALID_HISTORY_TYPES].join(", ")}`);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
function validateHistoryActions(actions) {
|
|
1996
|
+
for (const a of actions) {
|
|
1997
|
+
if (!VALID_HISTORY_ACTIONS.has(a)) {
|
|
1998
|
+
throw new Error(`Invalid action: ${a}. Valid actions: ${[...VALID_HISTORY_ACTIONS].join(", ")}`);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
function parseCommaSeparated(input) {
|
|
2003
|
+
if (!input) {
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
return input.split(",").map((s) => s.trim());
|
|
2007
|
+
}
|
|
1918
2008
|
|
|
1919
2009
|
// src/commands/epic.ts
|
|
1920
2010
|
var epicCommand = new Command3("epic").description("Manage epics");
|
|
@@ -1932,11 +2022,12 @@ epicCommand.command("create").description("Create a new epic").requiredOption("-
|
|
|
1932
2022
|
handleCommandError(err);
|
|
1933
2023
|
}
|
|
1934
2024
|
});
|
|
1935
|
-
epicCommand.command("list").description("List all epics").option("-s, --status <status>", "Filter by status").action((options) => {
|
|
2025
|
+
epicCommand.command("list").description("List all epics").option("-s, --status <status>", "Filter by status").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((options) => {
|
|
1936
2026
|
try {
|
|
1937
2027
|
const status = parseStatus(options.status, "epic");
|
|
1938
|
-
const
|
|
1939
|
-
|
|
2028
|
+
const { limit, page } = parsePaginationOptions(options);
|
|
2029
|
+
const result = listEpics({ status, limit, page });
|
|
2030
|
+
outputResult(result, formatPaginatedEpicList);
|
|
1940
2031
|
} catch (err) {
|
|
1941
2032
|
handleCommandError(err);
|
|
1942
2033
|
}
|
|
@@ -1944,8 +2035,9 @@ epicCommand.command("list").description("List all epics").option("-s, --status <
|
|
|
1944
2035
|
epicCommand.command("show <epic-id>").description("Show epic details").action((epicId) => {
|
|
1945
2036
|
try {
|
|
1946
2037
|
const epic = getEpic(epicId);
|
|
1947
|
-
if (!epic)
|
|
2038
|
+
if (!epic) {
|
|
1948
2039
|
return handleNotFound("Epic", epicId);
|
|
2040
|
+
}
|
|
1949
2041
|
outputResult(epic, formatEpic);
|
|
1950
2042
|
} catch (err) {
|
|
1951
2043
|
handleCommandError(err);
|
|
@@ -1990,7 +2082,7 @@ epicCommand.command("complete <epic-id>").description("Complete an epic and arch
|
|
|
1990
2082
|
import { Command as Command4 } from "commander";
|
|
1991
2083
|
|
|
1992
2084
|
// src/services/task.ts
|
|
1993
|
-
import { eq as eq3, and as and2, isNull as isNull2 } from "drizzle-orm";
|
|
2085
|
+
import { eq as eq3, and as and2, isNull as isNull2, desc as desc2, sql as sql3 } from "drizzle-orm";
|
|
1994
2086
|
function createTask(input) {
|
|
1995
2087
|
const db = getDb();
|
|
1996
2088
|
const project = db.select().from(projects).get();
|
|
@@ -2029,11 +2121,13 @@ function createTask(input) {
|
|
|
2029
2121
|
}
|
|
2030
2122
|
function getTask(id) {
|
|
2031
2123
|
const db = getDb();
|
|
2032
|
-
|
|
2033
|
-
return result;
|
|
2124
|
+
return db.select().from(tasks).where(eq3(tasks.id, id)).get();
|
|
2034
2125
|
}
|
|
2035
2126
|
function listTasks(options) {
|
|
2036
2127
|
const db = getDb();
|
|
2128
|
+
const limit = options?.limit ?? PAGINATION_DEFAULTS.LIST_PAGE_SIZE;
|
|
2129
|
+
const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
|
|
2130
|
+
const offset = (page - 1) * limit;
|
|
2037
2131
|
const conditions = [];
|
|
2038
2132
|
if (options?.status) {
|
|
2039
2133
|
conditions.push(eq3(tasks.status, options.status));
|
|
@@ -2046,14 +2140,25 @@ function listTasks(options) {
|
|
|
2046
2140
|
} else if (options?.parentTaskId) {
|
|
2047
2141
|
conditions.push(eq3(tasks.parentTaskId, options.parentTaskId));
|
|
2048
2142
|
}
|
|
2143
|
+
let where;
|
|
2049
2144
|
if (conditions.length > 0) {
|
|
2050
|
-
|
|
2145
|
+
where = and2(...conditions);
|
|
2051
2146
|
}
|
|
2052
|
-
|
|
2147
|
+
const countRow = db.select({ count: sql3`count(*)` }).from(tasks).where(where).get();
|
|
2148
|
+
const total = countRow?.count ?? 0;
|
|
2149
|
+
const items = db.select().from(tasks).where(where).orderBy(desc2(tasks.createdAt)).limit(limit).offset(offset).all();
|
|
2150
|
+
return { total, page, limit, items };
|
|
2053
2151
|
}
|
|
2054
|
-
function listSubtasks(parentTaskId) {
|
|
2152
|
+
function listSubtasks(parentTaskId, options) {
|
|
2055
2153
|
const db = getDb();
|
|
2056
|
-
|
|
2154
|
+
const limit = options?.limit ?? PAGINATION_DEFAULTS.LIST_PAGE_SIZE;
|
|
2155
|
+
const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
|
|
2156
|
+
const offset = (page - 1) * limit;
|
|
2157
|
+
const where = eq3(tasks.parentTaskId, parentTaskId);
|
|
2158
|
+
const countRow = db.select({ count: sql3`count(*)` }).from(tasks).where(where).get();
|
|
2159
|
+
const total = countRow?.count ?? 0;
|
|
2160
|
+
const items = db.select().from(tasks).where(where).orderBy(desc2(tasks.createdAt)).limit(limit).offset(offset).all();
|
|
2161
|
+
return { total, page, limit, items };
|
|
2057
2162
|
}
|
|
2058
2163
|
function updateTask(id, input) {
|
|
2059
2164
|
const db = getDb();
|
|
@@ -2070,20 +2175,30 @@ function updateTask(id, input) {
|
|
|
2070
2175
|
const updates = {
|
|
2071
2176
|
updatedAt: new Date
|
|
2072
2177
|
};
|
|
2073
|
-
if (input.title !== undefined)
|
|
2178
|
+
if (input.title !== undefined) {
|
|
2074
2179
|
updates.title = input.title;
|
|
2075
|
-
|
|
2180
|
+
}
|
|
2181
|
+
if (input.description !== undefined) {
|
|
2076
2182
|
updates.description = input.description;
|
|
2077
|
-
|
|
2183
|
+
}
|
|
2184
|
+
if (input.priority !== undefined) {
|
|
2078
2185
|
updates.priority = input.priority;
|
|
2079
|
-
|
|
2186
|
+
}
|
|
2187
|
+
if (input.status !== undefined) {
|
|
2080
2188
|
updates.status = input.status;
|
|
2081
|
-
|
|
2189
|
+
}
|
|
2190
|
+
if (input.tags !== undefined) {
|
|
2082
2191
|
updates.tags = input.tags;
|
|
2083
|
-
|
|
2192
|
+
}
|
|
2193
|
+
if (input.epicId !== undefined) {
|
|
2084
2194
|
updates.epicId = input.epicId;
|
|
2195
|
+
}
|
|
2085
2196
|
db.update(tasks).set(updates).where(eq3(tasks.id, id)).run();
|
|
2086
|
-
|
|
2197
|
+
const updated = getTask(id);
|
|
2198
|
+
if (!updated) {
|
|
2199
|
+
throw new Error(`Task not found after update: ${id}`);
|
|
2200
|
+
}
|
|
2201
|
+
return updated;
|
|
2087
2202
|
}
|
|
2088
2203
|
function deleteTask(id) {
|
|
2089
2204
|
const db = getDb();
|
|
@@ -2112,15 +2227,18 @@ taskCommand.command("create").description("Create a new task").requiredOption("-
|
|
|
2112
2227
|
handleCommandError(err);
|
|
2113
2228
|
}
|
|
2114
2229
|
});
|
|
2115
|
-
taskCommand.command("list").description("List all tasks").option("-s, --status <status>", "Filter by status").option("-e, --epic <epic-id>", "Filter by epic").action((options) => {
|
|
2230
|
+
taskCommand.command("list").description("List all tasks").option("-s, --status <status>", "Filter by status").option("-e, --epic <epic-id>", "Filter by epic").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((options) => {
|
|
2116
2231
|
try {
|
|
2117
2232
|
const status = parseStatus(options.status, "task");
|
|
2118
|
-
const
|
|
2233
|
+
const { limit, page } = parsePaginationOptions(options);
|
|
2234
|
+
const result = listTasks({
|
|
2119
2235
|
status,
|
|
2120
2236
|
epicId: options.epic,
|
|
2121
|
-
parentTaskId: null
|
|
2237
|
+
parentTaskId: null,
|
|
2238
|
+
limit,
|
|
2239
|
+
page
|
|
2122
2240
|
});
|
|
2123
|
-
outputResult(
|
|
2241
|
+
outputResult(result, formatPaginatedTaskList);
|
|
2124
2242
|
} catch (err) {
|
|
2125
2243
|
handleCommandError(err);
|
|
2126
2244
|
}
|
|
@@ -2128,8 +2246,9 @@ taskCommand.command("list").description("List all tasks").option("-s, --status <
|
|
|
2128
2246
|
taskCommand.command("show <task-id>").description("Show task details").action((taskId) => {
|
|
2129
2247
|
try {
|
|
2130
2248
|
const task = getTask(taskId);
|
|
2131
|
-
if (!task)
|
|
2249
|
+
if (!task) {
|
|
2132
2250
|
return handleNotFound("Task", taskId);
|
|
2251
|
+
}
|
|
2133
2252
|
outputResult(task, formatTask);
|
|
2134
2253
|
} catch (err) {
|
|
2135
2254
|
handleCommandError(err);
|
|
@@ -2138,16 +2257,21 @@ taskCommand.command("show <task-id>").description("Show task details").action((t
|
|
|
2138
2257
|
taskCommand.command("update <task-id>").description("Update a task").option("-t, --title <title>", "New title").option("-d, --description <description>", "New description").option("-p, --priority <priority>", "New priority (0-5)").option("-s, --status <status>", "New status").option("--tags <tags>", "New tags (comma-separated)").option("-e, --epic <epic-id>", "New epic ID").option("--no-epic", "Remove from epic").action((taskId, options) => {
|
|
2139
2258
|
try {
|
|
2140
2259
|
const updateInput = {};
|
|
2141
|
-
if (options.title !== undefined)
|
|
2260
|
+
if (options.title !== undefined) {
|
|
2142
2261
|
updateInput.title = options.title;
|
|
2143
|
-
|
|
2262
|
+
}
|
|
2263
|
+
if (options.description !== undefined) {
|
|
2144
2264
|
updateInput.description = options.description;
|
|
2145
|
-
|
|
2265
|
+
}
|
|
2266
|
+
if (options.priority !== undefined) {
|
|
2146
2267
|
updateInput.priority = parsePriority(options.priority);
|
|
2147
|
-
|
|
2268
|
+
}
|
|
2269
|
+
if (options.status !== undefined) {
|
|
2148
2270
|
updateInput.status = parseStatus(options.status, "task");
|
|
2149
|
-
|
|
2271
|
+
}
|
|
2272
|
+
if (options.tags !== undefined) {
|
|
2150
2273
|
updateInput.tags = options.tags;
|
|
2274
|
+
}
|
|
2151
2275
|
if (options.epic === false) {
|
|
2152
2276
|
updateInput.epicId = null;
|
|
2153
2277
|
} else if (options.epic !== undefined) {
|
|
@@ -2175,8 +2299,9 @@ subtaskCommand.command("create <parent-task-id>").description("Create a new subt
|
|
|
2175
2299
|
try {
|
|
2176
2300
|
validateRequired(options.title, "Title");
|
|
2177
2301
|
const parent = getTask(parentTaskId);
|
|
2178
|
-
if (!parent)
|
|
2302
|
+
if (!parent) {
|
|
2179
2303
|
return handleNotFound("Parent task", parentTaskId);
|
|
2304
|
+
}
|
|
2180
2305
|
const subtask = createTask({
|
|
2181
2306
|
title: options.title,
|
|
2182
2307
|
description: options.description,
|
|
@@ -2190,22 +2315,15 @@ subtaskCommand.command("create <parent-task-id>").description("Create a new subt
|
|
|
2190
2315
|
handleCommandError(err);
|
|
2191
2316
|
}
|
|
2192
2317
|
});
|
|
2193
|
-
subtaskCommand.command("list <parent-task-id>").description("List all subtasks of a task").action((parentTaskId) => {
|
|
2318
|
+
subtaskCommand.command("list <parent-task-id>").description("List all subtasks of a task").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((parentTaskId, options) => {
|
|
2194
2319
|
try {
|
|
2195
2320
|
const parent = getTask(parentTaskId);
|
|
2196
|
-
if (!parent)
|
|
2321
|
+
if (!parent) {
|
|
2197
2322
|
return handleNotFound("Parent task", parentTaskId);
|
|
2198
|
-
const subtasks = listSubtasks(parentTaskId);
|
|
2199
|
-
if (isToonMode()) {
|
|
2200
|
-
output(subtasks);
|
|
2201
|
-
} else {
|
|
2202
|
-
if (subtasks.length === 0) {
|
|
2203
|
-
console.log(`No subtasks for ${parentTaskId}`);
|
|
2204
|
-
} else {
|
|
2205
|
-
console.log(`Subtasks of ${parentTaskId}:`);
|
|
2206
|
-
console.log(formatTaskList(subtasks));
|
|
2207
|
-
}
|
|
2208
2323
|
}
|
|
2324
|
+
const { limit, page } = parsePaginationOptions(options);
|
|
2325
|
+
const result = listSubtasks(parentTaskId, { limit, page });
|
|
2326
|
+
outputResult(result, formatPaginatedTaskList);
|
|
2209
2327
|
} catch (err) {
|
|
2210
2328
|
handleCommandError(err);
|
|
2211
2329
|
}
|
|
@@ -2213,21 +2331,26 @@ subtaskCommand.command("list <parent-task-id>").description("List all subtasks o
|
|
|
2213
2331
|
subtaskCommand.command("update <subtask-id>").description("Update a subtask").option("-t, --title <title>", "New title").option("-d, --description <description>", "New description").option("-p, --priority <priority>", "New priority (0-5)").option("-s, --status <status>", "New status").action((subtaskId, options) => {
|
|
2214
2332
|
try {
|
|
2215
2333
|
const subtask = getTask(subtaskId);
|
|
2216
|
-
if (!subtask)
|
|
2334
|
+
if (!subtask) {
|
|
2217
2335
|
return handleNotFound("Subtask", subtaskId);
|
|
2336
|
+
}
|
|
2218
2337
|
if (!subtask.parentTaskId) {
|
|
2219
2338
|
error(`${subtaskId} is not a subtask. Use 'trekker task update' instead.`);
|
|
2220
2339
|
process.exit(1);
|
|
2221
2340
|
}
|
|
2222
2341
|
const updateInput = {};
|
|
2223
|
-
if (options.title !== undefined)
|
|
2342
|
+
if (options.title !== undefined) {
|
|
2224
2343
|
updateInput.title = options.title;
|
|
2225
|
-
|
|
2344
|
+
}
|
|
2345
|
+
if (options.description !== undefined) {
|
|
2226
2346
|
updateInput.description = options.description;
|
|
2227
|
-
|
|
2347
|
+
}
|
|
2348
|
+
if (options.priority !== undefined) {
|
|
2228
2349
|
updateInput.priority = parsePriority(options.priority);
|
|
2229
|
-
|
|
2350
|
+
}
|
|
2351
|
+
if (options.status !== undefined) {
|
|
2230
2352
|
updateInput.status = parseStatus(options.status, "task");
|
|
2353
|
+
}
|
|
2231
2354
|
const updated = updateTask(subtaskId, updateInput);
|
|
2232
2355
|
outputResult(updated, formatTask, `Subtask updated: ${updated.id}`);
|
|
2233
2356
|
} catch (err) {
|
|
@@ -2237,8 +2360,9 @@ subtaskCommand.command("update <subtask-id>").description("Update a subtask").op
|
|
|
2237
2360
|
subtaskCommand.command("delete <subtask-id>").description("Delete a subtask").action((subtaskId) => {
|
|
2238
2361
|
try {
|
|
2239
2362
|
const subtask = getTask(subtaskId);
|
|
2240
|
-
if (!subtask)
|
|
2363
|
+
if (!subtask) {
|
|
2241
2364
|
return handleNotFound("Subtask", subtaskId);
|
|
2365
|
+
}
|
|
2242
2366
|
if (!subtask.parentTaskId) {
|
|
2243
2367
|
error(`${subtaskId} is not a subtask. Use 'trekker task delete' instead.`);
|
|
2244
2368
|
process.exit(1);
|
|
@@ -2254,7 +2378,7 @@ subtaskCommand.command("delete <subtask-id>").description("Delete a subtask").ac
|
|
|
2254
2378
|
import { Command as Command6 } from "commander";
|
|
2255
2379
|
|
|
2256
2380
|
// src/services/comment.ts
|
|
2257
|
-
import { eq as eq4 } from "drizzle-orm";
|
|
2381
|
+
import { eq as eq4, desc as desc3, sql as sql4 } from "drizzle-orm";
|
|
2258
2382
|
function createComment(input) {
|
|
2259
2383
|
const db = getDb();
|
|
2260
2384
|
const task = db.select().from(tasks).where(eq4(tasks.id, input.taskId)).get();
|
|
@@ -2276,16 +2400,22 @@ function createComment(input) {
|
|
|
2276
2400
|
}
|
|
2277
2401
|
function getComment(id) {
|
|
2278
2402
|
const db = getDb();
|
|
2279
|
-
|
|
2280
|
-
return result;
|
|
2403
|
+
return db.select().from(comments).where(eq4(comments.id, id)).get();
|
|
2281
2404
|
}
|
|
2282
|
-
function listComments(taskId) {
|
|
2405
|
+
function listComments(taskId, options) {
|
|
2283
2406
|
const db = getDb();
|
|
2407
|
+
const limit = options?.limit ?? PAGINATION_DEFAULTS.LIST_PAGE_SIZE;
|
|
2408
|
+
const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
|
|
2409
|
+
const offset = (page - 1) * limit;
|
|
2284
2410
|
const task = db.select().from(tasks).where(eq4(tasks.id, taskId)).get();
|
|
2285
2411
|
if (!task) {
|
|
2286
2412
|
throw new Error(`Task not found: ${taskId}`);
|
|
2287
2413
|
}
|
|
2288
|
-
|
|
2414
|
+
const where = eq4(comments.taskId, taskId);
|
|
2415
|
+
const countRow = db.select({ count: sql4`count(*)` }).from(comments).where(where).get();
|
|
2416
|
+
const total = countRow?.count ?? 0;
|
|
2417
|
+
const items = db.select().from(comments).where(where).orderBy(desc3(comments.createdAt)).limit(limit).offset(offset).all();
|
|
2418
|
+
return { total, page, limit, items };
|
|
2289
2419
|
}
|
|
2290
2420
|
function updateComment(id, input) {
|
|
2291
2421
|
const db = getDb();
|
|
@@ -2297,7 +2427,11 @@ function updateComment(id, input) {
|
|
|
2297
2427
|
content: input.content,
|
|
2298
2428
|
updatedAt: new Date
|
|
2299
2429
|
}).where(eq4(comments.id, id)).run();
|
|
2300
|
-
|
|
2430
|
+
const updated = getComment(id);
|
|
2431
|
+
if (!updated) {
|
|
2432
|
+
throw new Error(`Comment not found after update: ${id}`);
|
|
2433
|
+
}
|
|
2434
|
+
return updated;
|
|
2301
2435
|
}
|
|
2302
2436
|
function deleteComment(id) {
|
|
2303
2437
|
const db = getDb();
|
|
@@ -2324,19 +2458,11 @@ commentCommand.command("add <task-id>").description("Add a comment to a task").r
|
|
|
2324
2458
|
handleCommandError(err);
|
|
2325
2459
|
}
|
|
2326
2460
|
});
|
|
2327
|
-
commentCommand.command("list <task-id>").description("List all comments on a task").action((taskId) => {
|
|
2461
|
+
commentCommand.command("list <task-id>").description("List all comments on a task").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((taskId, options) => {
|
|
2328
2462
|
try {
|
|
2329
|
-
const
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
} else {
|
|
2333
|
-
if (comments2.length === 0) {
|
|
2334
|
-
console.log(`No comments on ${taskId}`);
|
|
2335
|
-
} else {
|
|
2336
|
-
console.log(`Comments on ${taskId}:`);
|
|
2337
|
-
console.log(formatCommentList(comments2));
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2463
|
+
const { limit, page } = parsePaginationOptions(options);
|
|
2464
|
+
const result = listComments(taskId, { limit, page });
|
|
2465
|
+
outputResult(result, formatPaginatedCommentList);
|
|
2340
2466
|
} catch (err) {
|
|
2341
2467
|
handleCommandError(err);
|
|
2342
2468
|
}
|
|
@@ -2423,6 +2549,9 @@ function wouldCreateCycle(taskId, dependsOnId) {
|
|
|
2423
2549
|
const stack = [dependsOnId];
|
|
2424
2550
|
while (stack.length > 0) {
|
|
2425
2551
|
const current = stack.pop();
|
|
2552
|
+
if (current === undefined) {
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2426
2555
|
if (current === taskId) {
|
|
2427
2556
|
return true;
|
|
2428
2557
|
}
|
|
@@ -2812,14 +2941,25 @@ var SAMPLE_SUBTASKS = [
|
|
|
2812
2941
|
priority: 3
|
|
2813
2942
|
}
|
|
2814
2943
|
];
|
|
2944
|
+
var TASK_JWT = 1;
|
|
2945
|
+
var TASK_LOGIN = 2;
|
|
2946
|
+
var TASK_PASSWORD_RESET = 3;
|
|
2947
|
+
var TASK_DASHBOARD_LAYOUT = 4;
|
|
2948
|
+
var TASK_CHART_COMPONENTS = 5;
|
|
2949
|
+
var TASK_REALTIME = 6;
|
|
2950
|
+
var TASK_USER_ENDPOINTS = 8;
|
|
2951
|
+
var TASK_RATE_LIMITING = 9;
|
|
2952
|
+
var TASK_JEST_SETUP = 11;
|
|
2953
|
+
var TASK_UNIT_TESTS_AUTH = 12;
|
|
2954
|
+
var TASK_E2E_TESTING = 13;
|
|
2815
2955
|
var SAMPLE_DEPENDENCIES = [
|
|
2816
|
-
[
|
|
2817
|
-
[
|
|
2818
|
-
[
|
|
2819
|
-
[
|
|
2820
|
-
[
|
|
2821
|
-
[
|
|
2822
|
-
[
|
|
2956
|
+
[TASK_LOGIN, TASK_JWT],
|
|
2957
|
+
[TASK_PASSWORD_RESET, TASK_JWT],
|
|
2958
|
+
[TASK_REALTIME, TASK_CHART_COMPONENTS],
|
|
2959
|
+
[TASK_REALTIME, TASK_DASHBOARD_LAYOUT],
|
|
2960
|
+
[TASK_RATE_LIMITING, TASK_USER_ENDPOINTS],
|
|
2961
|
+
[TASK_UNIT_TESTS_AUTH, TASK_JEST_SETUP],
|
|
2962
|
+
[TASK_E2E_TESTING, TASK_JEST_SETUP]
|
|
2823
2963
|
];
|
|
2824
2964
|
var seedCommand = new Command9("seed").description("Seed the database with sample data (development only)").option("--force", "Skip confirmation prompt").action((options) => {
|
|
2825
2965
|
try {
|
|
@@ -2848,13 +2988,17 @@ var seedCommand = new Command9("seed").description("Seed the database with sampl
|
|
|
2848
2988
|
info(`
|
|
2849
2989
|
Creating tasks...`);
|
|
2850
2990
|
for (const taskData of SAMPLE_TASKS) {
|
|
2991
|
+
let epicId;
|
|
2992
|
+
if (taskData.epicIndex !== null) {
|
|
2993
|
+
epicId = epicIds[taskData.epicIndex];
|
|
2994
|
+
}
|
|
2851
2995
|
const task = createTask({
|
|
2852
2996
|
title: taskData.title,
|
|
2853
2997
|
description: taskData.description,
|
|
2854
2998
|
priority: taskData.priority,
|
|
2855
2999
|
status: taskData.status,
|
|
2856
3000
|
tags: taskData.tags,
|
|
2857
|
-
epicId
|
|
3001
|
+
epicId
|
|
2858
3002
|
});
|
|
2859
3003
|
taskIds.push(task.id);
|
|
2860
3004
|
info(` Created ${task.id}: ${task.title}`);
|
|
@@ -2881,8 +3025,7 @@ Creating dependencies...`);
|
|
|
2881
3025
|
success(`
|
|
2882
3026
|
Seed complete! Created ${epicIds.length} epics, ${taskIds.length} tasks, ${SAMPLE_SUBTASKS.length} subtasks, and ${SAMPLE_DEPENDENCIES.length} dependencies.`);
|
|
2883
3027
|
} catch (err) {
|
|
2884
|
-
|
|
2885
|
-
process.exit(1);
|
|
3028
|
+
handleCommandError(err);
|
|
2886
3029
|
}
|
|
2887
3030
|
});
|
|
2888
3031
|
|
|
@@ -2940,8 +3083,8 @@ function search(query, options) {
|
|
|
2940
3083
|
title: row.title,
|
|
2941
3084
|
snippet: row.snippet,
|
|
2942
3085
|
score: Math.abs(row.score),
|
|
2943
|
-
status: row.status
|
|
2944
|
-
parentId: row.parent_id
|
|
3086
|
+
status: row.status ?? null,
|
|
3087
|
+
parentId: row.parent_id ?? null
|
|
2945
3088
|
}))
|
|
2946
3089
|
};
|
|
2947
3090
|
}
|
|
@@ -2952,12 +3095,13 @@ var searchCommand = new Command10("search").description("Search across epics, ta
|
|
|
2952
3095
|
if (options.rebuildIndex) {
|
|
2953
3096
|
rebuildSearchIndex();
|
|
2954
3097
|
}
|
|
2955
|
-
const limit =
|
|
2956
|
-
const
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
3098
|
+
const { limit, page } = parsePaginationOptions(options);
|
|
3099
|
+
const rawTypes = parseCommaSeparated(options.type);
|
|
3100
|
+
let types;
|
|
3101
|
+
if (rawTypes) {
|
|
3102
|
+
validateSearchEntityTypes(rawTypes);
|
|
3103
|
+
types = rawTypes;
|
|
3104
|
+
}
|
|
2961
3105
|
const result = search(query, {
|
|
2962
3106
|
types,
|
|
2963
3107
|
status: options.status,
|
|
@@ -2980,9 +3124,15 @@ function formatSearchResults(result) {
|
|
|
2980
3124
|
`);
|
|
2981
3125
|
}
|
|
2982
3126
|
for (const r of result.results) {
|
|
2983
|
-
const typeLabel = r.type.toUpperCase().padEnd(
|
|
2984
|
-
|
|
2985
|
-
|
|
3127
|
+
const typeLabel = r.type.toUpperCase().padEnd(TYPE_PAD_WIDTH);
|
|
3128
|
+
let statusLabel = "";
|
|
3129
|
+
if (r.status) {
|
|
3130
|
+
statusLabel = ` [${r.status}]`;
|
|
3131
|
+
}
|
|
3132
|
+
let parentLabel = "";
|
|
3133
|
+
if (r.parentId) {
|
|
3134
|
+
parentLabel = ` (parent: ${r.parentId})`;
|
|
3135
|
+
}
|
|
2986
3136
|
lines.push(`${typeLabel} ${r.id}${statusLabel}${parentLabel}`);
|
|
2987
3137
|
if (r.title) {
|
|
2988
3138
|
lines.push(` Title: ${r.title}`);
|
|
@@ -3004,6 +3154,8 @@ var import_customParseFormat = __toESM(require_customParseFormat(), 1);
|
|
|
3004
3154
|
import { Command as Command11 } from "commander";
|
|
3005
3155
|
|
|
3006
3156
|
// src/services/history.ts
|
|
3157
|
+
var parseJsonRecord = JSON.parse;
|
|
3158
|
+
var parseJsonChanges = JSON.parse;
|
|
3007
3159
|
function getHistory(options) {
|
|
3008
3160
|
const sqlite = requireSqliteInstance();
|
|
3009
3161
|
const limit = options?.limit ?? PAGINATION_DEFAULTS.HISTORY_PAGE_SIZE;
|
|
@@ -3033,7 +3185,10 @@ function getHistory(options) {
|
|
|
3033
3185
|
conditions.push("created_at <= ?");
|
|
3034
3186
|
params.push(options.until.getTime());
|
|
3035
3187
|
}
|
|
3036
|
-
|
|
3188
|
+
let whereClause = "";
|
|
3189
|
+
if (conditions.length > 0) {
|
|
3190
|
+
whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
3191
|
+
}
|
|
3037
3192
|
const countQuery = `SELECT COUNT(*) as total FROM events ${whereClause}`;
|
|
3038
3193
|
const countResult = sqlite.query(countQuery).get(...params);
|
|
3039
3194
|
const total = countResult?.total ?? 0;
|
|
@@ -3049,15 +3204,25 @@ function getHistory(options) {
|
|
|
3049
3204
|
total,
|
|
3050
3205
|
page,
|
|
3051
3206
|
limit,
|
|
3052
|
-
events: results.map((row) =>
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3207
|
+
events: results.map((row) => {
|
|
3208
|
+
let snapshot = null;
|
|
3209
|
+
if (row.snapshot) {
|
|
3210
|
+
snapshot = parseJsonRecord(row.snapshot);
|
|
3211
|
+
}
|
|
3212
|
+
let changes = null;
|
|
3213
|
+
if (row.changes) {
|
|
3214
|
+
changes = parseJsonChanges(row.changes);
|
|
3215
|
+
}
|
|
3216
|
+
return {
|
|
3217
|
+
id: row.id,
|
|
3218
|
+
action: row.action,
|
|
3219
|
+
entityType: row.entity_type,
|
|
3220
|
+
entityId: row.entity_id,
|
|
3221
|
+
snapshot,
|
|
3222
|
+
changes,
|
|
3223
|
+
timestamp: new Date(row.created_at)
|
|
3224
|
+
};
|
|
3225
|
+
})
|
|
3061
3226
|
};
|
|
3062
3227
|
}
|
|
3063
3228
|
|
|
@@ -3065,31 +3230,14 @@ function getHistory(options) {
|
|
|
3065
3230
|
import_dayjs.default.extend(import_customParseFormat.default);
|
|
3066
3231
|
var historyCommand = new Command11("history").description("View history of all changes (creates, updates, deletes)").option("--entity <id>", "Filter by entity ID (e.g., TREK-1, EPIC-1)").option("--type <types>", "Filter by type: epic,task,subtask,comment,dependency (comma-separated)").option("--action <actions>", "Filter by action: create,update,delete (comma-separated)").option("--since <date>", "Events after date (YYYY-MM-DD)").option("--until <date>", "Events before date (YYYY-MM-DD)").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((options) => {
|
|
3067
3232
|
try {
|
|
3068
|
-
const types = options.type
|
|
3069
|
-
const actions = options.action
|
|
3070
|
-
const limit =
|
|
3071
|
-
const page = parseInt(options.page, 10);
|
|
3072
|
-
if (isNaN(limit) || limit < 1) {
|
|
3073
|
-
throw new Error("Invalid limit value");
|
|
3074
|
-
}
|
|
3075
|
-
if (isNaN(page) || page < 1) {
|
|
3076
|
-
throw new Error("Invalid page value");
|
|
3077
|
-
}
|
|
3078
|
-
const validTypes = ["epic", "task", "subtask", "comment", "dependency"];
|
|
3233
|
+
const types = parseCommaSeparated(options.type);
|
|
3234
|
+
const actions = parseCommaSeparated(options.action);
|
|
3235
|
+
const { limit, page } = parsePaginationOptions(options);
|
|
3079
3236
|
if (types) {
|
|
3080
|
-
|
|
3081
|
-
if (!validTypes.includes(t)) {
|
|
3082
|
-
throw new Error(`Invalid type: ${t}. Valid types: ${validTypes.join(", ")}`);
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3237
|
+
validateHistoryTypes(types);
|
|
3085
3238
|
}
|
|
3086
|
-
const validActions = ["create", "update", "delete"];
|
|
3087
3239
|
if (actions) {
|
|
3088
|
-
|
|
3089
|
-
if (!validActions.includes(a)) {
|
|
3090
|
-
throw new Error(`Invalid action: ${a}. Valid actions: ${validActions.join(", ")}`);
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3240
|
+
validateHistoryActions(actions);
|
|
3093
3241
|
}
|
|
3094
3242
|
let since;
|
|
3095
3243
|
let until;
|
|
@@ -3121,8 +3269,7 @@ var historyCommand = new Command11("history").description("View history of all c
|
|
|
3121
3269
|
console.log(formatHistoryResults(result));
|
|
3122
3270
|
}
|
|
3123
3271
|
} catch (err) {
|
|
3124
|
-
|
|
3125
|
-
process.exit(1);
|
|
3272
|
+
handleCommandError(err);
|
|
3126
3273
|
}
|
|
3127
3274
|
});
|
|
3128
3275
|
function parseDate(dateStr) {
|
|
@@ -3154,8 +3301,8 @@ function formatHistoryResults(result) {
|
|
|
3154
3301
|
}
|
|
3155
3302
|
function formatEvent(event) {
|
|
3156
3303
|
const lines = [];
|
|
3157
|
-
const timestamp = event.timestamp.toISOString().replace("T", " ").
|
|
3158
|
-
const actionLabel = event.action.toUpperCase().padEnd(
|
|
3304
|
+
const timestamp = event.timestamp.toISOString().replace("T", " ").slice(0, TIMESTAMP_SLICE_END);
|
|
3305
|
+
const actionLabel = event.action.toUpperCase().padEnd(ACTION_PAD_WIDTH);
|
|
3159
3306
|
const typeLabel = event.entityType.toUpperCase();
|
|
3160
3307
|
lines.push(`[${timestamp}] ${actionLabel} ${typeLabel} ${event.entityId}`);
|
|
3161
3308
|
if (event.action === "update" && event.changes) {
|
|
@@ -3166,13 +3313,13 @@ function formatEvent(event) {
|
|
|
3166
3313
|
}
|
|
3167
3314
|
} else if (event.snapshot) {
|
|
3168
3315
|
const snap = event.snapshot;
|
|
3169
|
-
if (snap.title) {
|
|
3316
|
+
if (typeof snap.title === "string") {
|
|
3170
3317
|
lines.push(` title: ${snap.title}`);
|
|
3171
3318
|
}
|
|
3172
|
-
if (snap.content) {
|
|
3173
|
-
lines.push(` content: ${truncate(
|
|
3319
|
+
if (typeof snap.content === "string") {
|
|
3320
|
+
lines.push(` content: ${truncate(snap.content, TRUNCATE_CONTENT)}`);
|
|
3174
3321
|
}
|
|
3175
|
-
if (snap.status) {
|
|
3322
|
+
if (typeof snap.status === "string") {
|
|
3176
3323
|
lines.push(` status: ${snap.status}`);
|
|
3177
3324
|
}
|
|
3178
3325
|
}
|
|
@@ -3184,15 +3331,21 @@ function formatValue(value) {
|
|
|
3184
3331
|
return "(none)";
|
|
3185
3332
|
}
|
|
3186
3333
|
if (typeof value === "string") {
|
|
3187
|
-
return truncate(value,
|
|
3334
|
+
return truncate(value, TRUNCATE_DEFAULT);
|
|
3335
|
+
}
|
|
3336
|
+
if (typeof value === "number") {
|
|
3337
|
+
return `${value}`;
|
|
3338
|
+
}
|
|
3339
|
+
if (typeof value === "boolean") {
|
|
3340
|
+
return String(value);
|
|
3188
3341
|
}
|
|
3189
|
-
return
|
|
3342
|
+
return JSON.stringify(value);
|
|
3190
3343
|
}
|
|
3191
3344
|
function truncate(str, maxLen) {
|
|
3192
3345
|
if (str.length <= maxLen) {
|
|
3193
3346
|
return str;
|
|
3194
3347
|
}
|
|
3195
|
-
return str.
|
|
3348
|
+
return `${str.slice(0, Math.max(0, maxLen - TRUNCATE_OFFSET))}...`;
|
|
3196
3349
|
}
|
|
3197
3350
|
|
|
3198
3351
|
// src/commands/list.ts
|
|
@@ -3201,6 +3354,7 @@ var import_customParseFormat2 = __toESM(require_customParseFormat(), 1);
|
|
|
3201
3354
|
import { Command as Command12 } from "commander";
|
|
3202
3355
|
|
|
3203
3356
|
// src/services/list.ts
|
|
3357
|
+
var VALID_SORT_FIELD_SET = new Set(VALID_SORT_FIELDS);
|
|
3204
3358
|
function listAll(options) {
|
|
3205
3359
|
const sqlite = requireSqliteInstance();
|
|
3206
3360
|
const limit = options?.limit ?? PAGINATION_DEFAULTS.LIST_PAGE_SIZE;
|
|
@@ -3225,17 +3379,25 @@ function listAll(options) {
|
|
|
3225
3379
|
}
|
|
3226
3380
|
if (options?.since) {
|
|
3227
3381
|
conditions.push("created_at >= ?");
|
|
3228
|
-
params.push(Math.floor(options.since.getTime() /
|
|
3382
|
+
params.push(Math.floor(options.since.getTime() / MS_PER_SECOND));
|
|
3229
3383
|
}
|
|
3230
3384
|
if (options?.until) {
|
|
3231
3385
|
conditions.push("created_at <= ?");
|
|
3232
|
-
params.push(Math.floor(options.until.getTime() /
|
|
3386
|
+
params.push(Math.floor(options.until.getTime() / MS_PER_SECOND));
|
|
3387
|
+
}
|
|
3388
|
+
let whereClause = "";
|
|
3389
|
+
if (conditions.length > 0) {
|
|
3390
|
+
whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
3233
3391
|
}
|
|
3234
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3235
3392
|
let orderClause = "ORDER BY created_at DESC";
|
|
3236
3393
|
if (options?.sort && options.sort.length > 0) {
|
|
3237
3394
|
const sortParts = options.sort.map((s) => {
|
|
3238
|
-
|
|
3395
|
+
let field = s.field;
|
|
3396
|
+
if (s.field === "created") {
|
|
3397
|
+
field = "created_at";
|
|
3398
|
+
} else if (s.field === "updated") {
|
|
3399
|
+
field = "updated_at";
|
|
3400
|
+
}
|
|
3239
3401
|
return `${field} ${s.direction.toUpperCase()}`;
|
|
3240
3402
|
});
|
|
3241
3403
|
orderClause = `ORDER BY ${sortParts.join(", ")}`;
|
|
@@ -3268,8 +3430,8 @@ function listAll(options) {
|
|
|
3268
3430
|
status: row.status,
|
|
3269
3431
|
priority: row.priority,
|
|
3270
3432
|
parentId: row.parent_id,
|
|
3271
|
-
createdAt: new Date(row.created_at *
|
|
3272
|
-
updatedAt: new Date(row.updated_at *
|
|
3433
|
+
createdAt: new Date(row.created_at * MS_PER_SECOND),
|
|
3434
|
+
updatedAt: new Date(row.updated_at * MS_PER_SECOND)
|
|
3273
3435
|
}))
|
|
3274
3436
|
};
|
|
3275
3437
|
}
|
|
@@ -3278,11 +3440,14 @@ function parseSort(sortStr) {
|
|
|
3278
3440
|
const result = [];
|
|
3279
3441
|
for (const part of parts) {
|
|
3280
3442
|
const [field, dir] = part.split(":").map((s) => s.trim().toLowerCase());
|
|
3281
|
-
if (!
|
|
3443
|
+
if (!VALID_SORT_FIELD_SET.has(field)) {
|
|
3282
3444
|
throw new Error(`Invalid sort field: ${field}. Valid fields: ${VALID_SORT_FIELDS.join(", ")}`);
|
|
3283
3445
|
}
|
|
3284
|
-
|
|
3285
|
-
|
|
3446
|
+
if (dir === "asc") {
|
|
3447
|
+
result.push({ field, direction: "asc" });
|
|
3448
|
+
} else {
|
|
3449
|
+
result.push({ field, direction: "desc" });
|
|
3450
|
+
}
|
|
3286
3451
|
}
|
|
3287
3452
|
return result;
|
|
3288
3453
|
}
|
|
@@ -3291,21 +3456,29 @@ function parseSort(sortStr) {
|
|
|
3291
3456
|
import_dayjs2.default.extend(import_customParseFormat2.default);
|
|
3292
3457
|
var listCommand = new Command12("list").description("List all epics, tasks, and subtasks").option("--type <types>", "Filter by type: epic,task,subtask (comma-separated)").option("--status <statuses>", "Filter by status (comma-separated)").option("--priority <levels>", "Filter by priority: 0-5 (comma-separated)").option("--since <date>", "Created after date (YYYY-MM-DD)").option("--until <date>", "Created before date (YYYY-MM-DD)").option("--sort <fields>", "Sort by fields (field:direction, comma-separated)", "created:desc").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((options) => {
|
|
3293
3458
|
try {
|
|
3294
|
-
const limit =
|
|
3295
|
-
const
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
const
|
|
3302
|
-
|
|
3459
|
+
const { limit, page } = parsePaginationOptions(options);
|
|
3460
|
+
const rawTypes = parseCommaSeparated(options.type);
|
|
3461
|
+
let types;
|
|
3462
|
+
if (rawTypes) {
|
|
3463
|
+
validateListEntityTypes(rawTypes);
|
|
3464
|
+
types = rawTypes;
|
|
3465
|
+
}
|
|
3466
|
+
const statuses = parseCommaSeparated(options.status);
|
|
3467
|
+
let priorities;
|
|
3468
|
+
if (options.priority) {
|
|
3469
|
+
priorities = options.priority.split(",").map((p) => Number.parseInt(p.trim(), RADIX_DECIMAL));
|
|
3470
|
+
}
|
|
3471
|
+
if (priorities) {
|
|
3303
3472
|
validatePriorities(priorities);
|
|
3473
|
+
}
|
|
3304
3474
|
let sort;
|
|
3305
3475
|
try {
|
|
3306
3476
|
sort = parseSort(options.sort);
|
|
3307
3477
|
} catch (err) {
|
|
3308
|
-
|
|
3478
|
+
if (err instanceof Error) {
|
|
3479
|
+
throw new Error(`Invalid sort: ${err.message}`);
|
|
3480
|
+
}
|
|
3481
|
+
throw new Error(`Invalid sort: ${String(err)}`);
|
|
3309
3482
|
}
|
|
3310
3483
|
const since = parseDate2(options.since);
|
|
3311
3484
|
if (options.since && !since) {
|
|
@@ -3331,16 +3504,24 @@ var listCommand = new Command12("list").description("List all epics, tasks, and
|
|
|
3331
3504
|
}
|
|
3332
3505
|
});
|
|
3333
3506
|
function parseDate2(dateStr) {
|
|
3334
|
-
if (!dateStr)
|
|
3507
|
+
if (!dateStr) {
|
|
3335
3508
|
return;
|
|
3509
|
+
}
|
|
3336
3510
|
const parsed = import_dayjs2.default(dateStr, "YYYY-MM-DD", true);
|
|
3337
|
-
|
|
3511
|
+
if (parsed.isValid()) {
|
|
3512
|
+
return parsed.toDate();
|
|
3513
|
+
}
|
|
3514
|
+
return;
|
|
3338
3515
|
}
|
|
3339
3516
|
function parseUntilDate(dateStr) {
|
|
3340
|
-
if (!dateStr)
|
|
3517
|
+
if (!dateStr) {
|
|
3341
3518
|
return;
|
|
3519
|
+
}
|
|
3342
3520
|
const parsed = import_dayjs2.default(dateStr, "YYYY-MM-DD", true);
|
|
3343
|
-
|
|
3521
|
+
if (parsed.isValid()) {
|
|
3522
|
+
return parsed.endOf("day").toDate();
|
|
3523
|
+
}
|
|
3524
|
+
return;
|
|
3344
3525
|
}
|
|
3345
3526
|
function formatListResults(result) {
|
|
3346
3527
|
const lines = [];
|
|
@@ -3363,16 +3544,117 @@ function formatListResults(result) {
|
|
|
3363
3544
|
`);
|
|
3364
3545
|
}
|
|
3365
3546
|
function formatItem(item) {
|
|
3366
|
-
const typeLabel = item.type.toUpperCase().padEnd(
|
|
3367
|
-
const statusLabel = item.status.padEnd(
|
|
3547
|
+
const typeLabel = item.type.toUpperCase().padEnd(TYPE_PAD_WIDTH);
|
|
3548
|
+
const statusLabel = item.status.padEnd(STATUS_PAD_WIDTH);
|
|
3368
3549
|
const priorityLabel = `P${item.priority}`;
|
|
3369
|
-
|
|
3550
|
+
let parentLabel = "";
|
|
3551
|
+
if (item.parentId) {
|
|
3552
|
+
parentLabel = ` (${item.parentId})`;
|
|
3553
|
+
}
|
|
3370
3554
|
return `${typeLabel} ${item.id} | ${statusLabel} | ${priorityLabel} | ${item.title}${parentLabel}`;
|
|
3371
3555
|
}
|
|
3556
|
+
|
|
3557
|
+
// src/commands/ready.ts
|
|
3558
|
+
import { Command as Command13 } from "commander";
|
|
3559
|
+
|
|
3560
|
+
// src/services/ready.ts
|
|
3561
|
+
function getReadyTasks(options) {
|
|
3562
|
+
const sqlite = requireSqliteInstance();
|
|
3563
|
+
const limit = options?.limit ?? PAGINATION_DEFAULTS.LIST_PAGE_SIZE;
|
|
3564
|
+
const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
|
|
3565
|
+
const offset = (page - 1) * limit;
|
|
3566
|
+
const baseWhere = `
|
|
3567
|
+
WHERE t.status = 'todo'
|
|
3568
|
+
AND t.parent_task_id IS NULL
|
|
3569
|
+
AND NOT EXISTS (
|
|
3570
|
+
SELECT 1 FROM dependencies d
|
|
3571
|
+
JOIN tasks dt ON dt.id = d.depends_on_id
|
|
3572
|
+
WHERE d.task_id = t.id
|
|
3573
|
+
AND dt.status NOT IN ('completed', 'wont_fix', 'archived')
|
|
3574
|
+
)
|
|
3575
|
+
`;
|
|
3576
|
+
const countResult = sqlite.query(`SELECT COUNT(*) as total FROM tasks t ${baseWhere}`).get();
|
|
3577
|
+
const total = countResult?.total ?? 0;
|
|
3578
|
+
const readyRows = sqlite.query(`
|
|
3579
|
+
SELECT t.id, t.title, t.description, t.priority, t.status,
|
|
3580
|
+
t.epic_id, t.tags, t.created_at, t.updated_at
|
|
3581
|
+
FROM tasks t
|
|
3582
|
+
${baseWhere}
|
|
3583
|
+
ORDER BY t.priority ASC, t.created_at ASC
|
|
3584
|
+
LIMIT ? OFFSET ?
|
|
3585
|
+
`).all(limit, offset);
|
|
3586
|
+
const dependentsQuery = sqlite.query(`
|
|
3587
|
+
SELECT t.id, t.title, t.status, t.priority
|
|
3588
|
+
FROM dependencies d
|
|
3589
|
+
JOIN tasks t ON t.id = d.task_id
|
|
3590
|
+
WHERE d.depends_on_id = ?
|
|
3591
|
+
ORDER BY t.priority ASC
|
|
3592
|
+
`);
|
|
3593
|
+
const items = readyRows.map((row) => ({
|
|
3594
|
+
id: row.id,
|
|
3595
|
+
title: row.title,
|
|
3596
|
+
description: row.description,
|
|
3597
|
+
priority: row.priority,
|
|
3598
|
+
status: row.status,
|
|
3599
|
+
epicId: row.epic_id,
|
|
3600
|
+
tags: row.tags,
|
|
3601
|
+
createdAt: new Date(row.created_at * MS_PER_SECOND),
|
|
3602
|
+
updatedAt: new Date(row.updated_at * MS_PER_SECOND),
|
|
3603
|
+
dependents: dependentsQuery.all(row.id).map((d) => ({
|
|
3604
|
+
id: d.id,
|
|
3605
|
+
title: d.title,
|
|
3606
|
+
status: d.status,
|
|
3607
|
+
priority: d.priority
|
|
3608
|
+
}))
|
|
3609
|
+
}));
|
|
3610
|
+
return { total, page, limit, items };
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
// src/commands/ready.ts
|
|
3614
|
+
var readyCommand = new Command13("ready").description("Show tasks that are ready to work on (unblocked, todo)").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((options) => {
|
|
3615
|
+
try {
|
|
3616
|
+
const { limit, page } = parsePaginationOptions(options);
|
|
3617
|
+
const result = getReadyTasks({ limit, page });
|
|
3618
|
+
outputResult(result, formatReadyTasks);
|
|
3619
|
+
} catch (err) {
|
|
3620
|
+
handleCommandError(err);
|
|
3621
|
+
}
|
|
3622
|
+
});
|
|
3623
|
+
function formatReadyTasks(result) {
|
|
3624
|
+
if (result.items.length === 0) {
|
|
3625
|
+
return "No ready tasks found.";
|
|
3626
|
+
}
|
|
3627
|
+
const lines = [];
|
|
3628
|
+
lines.push(`${result.total} ready task(s) (page ${result.page}, ${result.limit} per page)
|
|
3629
|
+
`);
|
|
3630
|
+
for (const task of result.items) {
|
|
3631
|
+
let epic = "";
|
|
3632
|
+
if (task.epicId) {
|
|
3633
|
+
epic = ` (${task.epicId})`;
|
|
3634
|
+
}
|
|
3635
|
+
let tags = "";
|
|
3636
|
+
if (task.tags) {
|
|
3637
|
+
tags = ` [${task.tags}]`;
|
|
3638
|
+
}
|
|
3639
|
+
lines.push(`${task.id} | P${task.priority} | ${task.title}${epic}${tags}`);
|
|
3640
|
+
if (task.dependents.length > 0) {
|
|
3641
|
+
for (const dep of task.dependents) {
|
|
3642
|
+
lines.push(` -> unblocks ${dep.id} | ${dep.status.padEnd(STATUS_PAD_WIDTH)} | P${dep.priority} | ${dep.title}`);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
const totalPages = Math.ceil(result.total / result.limit);
|
|
3647
|
+
if (totalPages > 1) {
|
|
3648
|
+
lines.push(`
|
|
3649
|
+
Page ${result.page} of ${totalPages}`);
|
|
3650
|
+
}
|
|
3651
|
+
return lines.join(`
|
|
3652
|
+
`);
|
|
3653
|
+
}
|
|
3372
3654
|
// package.json
|
|
3373
3655
|
var package_default = {
|
|
3374
3656
|
name: "@obsfx/trekker",
|
|
3375
|
-
version: "1.
|
|
3657
|
+
version: "1.10.0",
|
|
3376
3658
|
description: "A CLI-based issue tracker built for AI coding agents. Stores tasks, epics, and dependencies in a local SQLite database.",
|
|
3377
3659
|
type: "module",
|
|
3378
3660
|
main: "dist/index.js",
|
|
@@ -3388,6 +3670,11 @@ var package_default = {
|
|
|
3388
3670
|
dev: "bun run src/index.ts",
|
|
3389
3671
|
test: "bun test",
|
|
3390
3672
|
"test:watch": "bun test --watch",
|
|
3673
|
+
lint: "eslint . --cache",
|
|
3674
|
+
"lint:fix": "eslint . --fix --cache",
|
|
3675
|
+
format: 'prettier --write "src/**/*.ts" "tests/**/*.ts"',
|
|
3676
|
+
"format:check": 'prettier --check "src/**/*.ts" "tests/**/*.ts"',
|
|
3677
|
+
check: "bun run lint && bun run format:check",
|
|
3391
3678
|
"db:generate": "drizzle-kit generate",
|
|
3392
3679
|
"db:migrate": "drizzle-kit migrate",
|
|
3393
3680
|
"release:patch": "npm version patch && npm run build && npm publish --access public",
|
|
@@ -3429,12 +3716,18 @@ var package_default = {
|
|
|
3429
3716
|
devDependencies: {
|
|
3430
3717
|
"@types/bun": "^1.2.2",
|
|
3431
3718
|
"drizzle-kit": "^0.30.4",
|
|
3432
|
-
|
|
3719
|
+
eslint: "^10.0.2",
|
|
3720
|
+
"eslint-config-prettier": "^10.1.8",
|
|
3721
|
+
"eslint-plugin-unicorn": "^63.0.0",
|
|
3722
|
+
knip: "^5.85.0",
|
|
3723
|
+
prettier: "^3.8.1",
|
|
3724
|
+
typescript: "^5.7.3",
|
|
3725
|
+
"typescript-eslint": "^8.56.1"
|
|
3433
3726
|
}
|
|
3434
3727
|
};
|
|
3435
3728
|
|
|
3436
3729
|
// src/index.ts
|
|
3437
|
-
var program = new
|
|
3730
|
+
var program = new Command14;
|
|
3438
3731
|
program.name("trekker").description("CLI-based issue tracker for coding agents").version(package_default.version).option("--toon", "Output in TOON format").hook("preAction", (thisCommand) => {
|
|
3439
3732
|
const opts = thisCommand.opts();
|
|
3440
3733
|
if (opts.toon) {
|
|
@@ -3453,4 +3746,5 @@ program.addCommand(seedCommand);
|
|
|
3453
3746
|
program.addCommand(searchCommand);
|
|
3454
3747
|
program.addCommand(historyCommand);
|
|
3455
3748
|
program.addCommand(listCommand);
|
|
3749
|
+
program.addCommand(readyCommand);
|
|
3456
3750
|
program.parse();
|