@obsfx/trekker 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/trekker.js +15 -0
- package/dist/index.js +2278 -0
- package/package.json +57 -0
- package/webapp-dist/.next/BUILD_ID +1 -0
- package/webapp-dist/.next/app-path-routes-manifest.json +14 -0
- package/webapp-dist/.next/build-manifest.json +19 -0
- package/webapp-dist/.next/package.json +1 -0
- package/webapp-dist/.next/prerender-manifest.json +85 -0
- package/webapp-dist/.next/required-server-files.json +323 -0
- package/webapp-dist/.next/routes-manifest.json +125 -0
- package/webapp-dist/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/_global-error/page/build-manifest.json +16 -0
- package/webapp-dist/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
- package/webapp-dist/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
- package/webapp-dist/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/_global-error/page.js +10 -0
- package/webapp-dist/.next/server/app/_global-error/page.js.map +5 -0
- package/webapp-dist/.next/server/app/_global-error/page.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/_global-error/page_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/_global-error.html +2 -0
- package/webapp-dist/.next/server/app/_global-error.meta +15 -0
- package/webapp-dist/.next/server/app/_global-error.rsc +12 -0
- package/webapp-dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +5 -0
- package/webapp-dist/.next/server/app/_global-error.segments/_full.segment.rsc +12 -0
- package/webapp-dist/.next/server/app/_global-error.segments/_head.segment.rsc +5 -0
- package/webapp-dist/.next/server/app/_global-error.segments/_index.segment.rsc +4 -0
- package/webapp-dist/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
- package/webapp-dist/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/_not-found/page/build-manifest.json +16 -0
- package/webapp-dist/.next/server/app/_not-found/page/next-font-manifest.json +6 -0
- package/webapp-dist/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
- package/webapp-dist/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/_not-found/page.js +13 -0
- package/webapp-dist/.next/server/app/_not-found/page.js.map +5 -0
- package/webapp-dist/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/_not-found/page_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/_not-found.html +17 -0
- package/webapp-dist/.next/server/app/_not-found.meta +16 -0
- package/webapp-dist/.next/server/app/_not-found.rsc +14 -0
- package/webapp-dist/.next/server/app/_not-found.segments/_full.segment.rsc +14 -0
- package/webapp-dist/.next/server/app/_not-found.segments/_head.segment.rsc +5 -0
- package/webapp-dist/.next/server/app/_not-found.segments/_index.segment.rsc +6 -0
- package/webapp-dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
- package/webapp-dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +4 -0
- package/webapp-dist/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
- package/webapp-dist/.next/server/app/api/comments/[id]/route/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/api/comments/[id]/route/build-manifest.json +11 -0
- package/webapp-dist/.next/server/app/api/comments/[id]/route/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/api/comments/[id]/route.js +7 -0
- package/webapp-dist/.next/server/app/api/comments/[id]/route.js.map +5 -0
- package/webapp-dist/.next/server/app/api/comments/[id]/route.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/api/comments/[id]/route_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/api/dependencies/route/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/api/dependencies/route/build-manifest.json +11 -0
- package/webapp-dist/.next/server/app/api/dependencies/route/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/api/dependencies/route.js +7 -0
- package/webapp-dist/.next/server/app/api/dependencies/route.js.map +5 -0
- package/webapp-dist/.next/server/app/api/dependencies/route.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/api/dependencies/route_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/api/epics/[id]/route/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/api/epics/[id]/route/build-manifest.json +11 -0
- package/webapp-dist/.next/server/app/api/epics/[id]/route/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/api/epics/[id]/route.js +7 -0
- package/webapp-dist/.next/server/app/api/epics/[id]/route.js.map +5 -0
- package/webapp-dist/.next/server/app/api/epics/[id]/route.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/api/epics/[id]/route_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/api/epics/route/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/api/epics/route/build-manifest.json +11 -0
- package/webapp-dist/.next/server/app/api/epics/route/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/api/epics/route.js +7 -0
- package/webapp-dist/.next/server/app/api/epics/route.js.map +5 -0
- package/webapp-dist/.next/server/app/api/epics/route.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/api/epics/route_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/api/events/route/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/api/events/route/build-manifest.json +11 -0
- package/webapp-dist/.next/server/app/api/events/route/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/api/events/route.js +6 -0
- package/webapp-dist/.next/server/app/api/events/route.js.map +5 -0
- package/webapp-dist/.next/server/app/api/events/route.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/api/events/route_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/api/project/route/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/api/project/route/build-manifest.json +11 -0
- package/webapp-dist/.next/server/app/api/project/route/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/api/project/route.js +7 -0
- package/webapp-dist/.next/server/app/api/project/route.js.map +5 -0
- package/webapp-dist/.next/server/app/api/project/route.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/api/project/route_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/comments/route/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/comments/route/build-manifest.json +11 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/comments/route/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/comments/route.js +7 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/comments/route.js.map +5 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/comments/route.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/comments/route_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/route/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/route/build-manifest.json +11 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/route/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/route.js +7 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/route.js.map +5 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/route.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/api/tasks/[id]/route_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/api/tasks/route/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/api/tasks/route/build-manifest.json +11 -0
- package/webapp-dist/.next/server/app/api/tasks/route/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/api/tasks/route.js +7 -0
- package/webapp-dist/.next/server/app/api/tasks/route.js.map +5 -0
- package/webapp-dist/.next/server/app/api/tasks/route.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/api/tasks/route_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app/index.html +17 -0
- package/webapp-dist/.next/server/app/index.meta +14 -0
- package/webapp-dist/.next/server/app/index.rsc +18 -0
- package/webapp-dist/.next/server/app/index.segments/__PAGE__.segment.rsc +9 -0
- package/webapp-dist/.next/server/app/index.segments/_full.segment.rsc +18 -0
- package/webapp-dist/.next/server/app/index.segments/_head.segment.rsc +5 -0
- package/webapp-dist/.next/server/app/index.segments/_index.segment.rsc +6 -0
- package/webapp-dist/.next/server/app/index.segments/_tree.segment.rsc +2 -0
- package/webapp-dist/.next/server/app/page/app-paths-manifest.json +3 -0
- package/webapp-dist/.next/server/app/page/build-manifest.json +16 -0
- package/webapp-dist/.next/server/app/page/next-font-manifest.json +6 -0
- package/webapp-dist/.next/server/app/page/react-loadable-manifest.json +1 -0
- package/webapp-dist/.next/server/app/page/server-reference-manifest.json +4 -0
- package/webapp-dist/.next/server/app/page.js +15 -0
- package/webapp-dist/.next/server/app/page.js.map +5 -0
- package/webapp-dist/.next/server/app/page.js.nft.json +1 -0
- package/webapp-dist/.next/server/app/page_client-reference-manifest.js +2 -0
- package/webapp-dist/.next/server/app-paths-manifest.json +14 -0
- package/webapp-dist/.next/server/chunks/0a3b6_webapp__next-internal_server_app_api_comments_[id]_route_actions_af232ab0.js +3 -0
- package/webapp-dist/.next/server/chunks/0a3b6_webapp__next-internal_server_app_api_dependencies_route_actions_e60a141e.js +3 -0
- package/webapp-dist/.next/server/chunks/0a3b6_webapp__next-internal_server_app_api_tasks_[id]_comments_route_actions_7664dee6.js +3 -0
- package/webapp-dist/.next/server/chunks/457e5_packages_webapp__next-internal_server_app_api_epics_[id]_route_actions_4214a255.js +3 -0
- package/webapp-dist/.next/server/chunks/457e5_packages_webapp__next-internal_server_app_api_epics_route_actions_21a25d78.js +3 -0
- package/webapp-dist/.next/server/chunks/457e5_packages_webapp__next-internal_server_app_api_events_route_actions_7eb71690.js +3 -0
- package/webapp-dist/.next/server/chunks/457e5_packages_webapp__next-internal_server_app_api_project_route_actions_b9749fa9.js +3 -0
- package/webapp-dist/.next/server/chunks/457e5_packages_webapp__next-internal_server_app_api_tasks_[id]_route_actions_0a245d38.js +3 -0
- package/webapp-dist/.next/server/chunks/457e5_packages_webapp__next-internal_server_app_api_tasks_route_actions_36c9bc86.js +3 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__167728e4._.js +3 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__288a9fb4._.js +3 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__b483bdb2._.js +3 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__b9c44791._.js +7 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__d195c75a._.js +3 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__d5a6bced._.js +3 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__dccbb1d9._.js +19 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__eb3302c2._.js +3 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__f0999659._.js +3 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__f91dba61._.js +19 -0
- package/webapp-dist/.next/server/chunks/[root-of-the-server]__fa733c09._.js +3 -0
- package/webapp-dist/.next/server/chunks/[turbopack]_runtime.js +795 -0
- package/webapp-dist/.next/server/chunks/b001a_next_39efd30d._.js +17 -0
- package/webapp-dist/.next/server/chunks/ssr/457e5_packages_webapp__next-internal_server_app__global-error_page_actions_316934be.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/457e5_packages_webapp__next-internal_server_app__not-found_page_actions_51c8ae48.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/953a0_trekker_packages_webapp__next-internal_server_app_page_actions_c6362592.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/[root-of-the-server]__282182c1._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/[root-of-the-server]__3c9630e5._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/[root-of-the-server]__560e4049._.js +4 -0
- package/webapp-dist/.next/server/chunks/ssr/[root-of-the-server]__660feac5._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/[root-of-the-server]__7a85eec3._.js +10 -0
- package/webapp-dist/.next/server/chunks/ssr/[root-of-the-server]__8a9deef1._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/[root-of-the-server]__958d14fd._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/[root-of-the-server]__bbfb83b6._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/[turbopack]_runtime.js +795 -0
- package/webapp-dist/.next/server/chunks/ssr/b001a_next_dist_4febb8e2._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/b001a_next_dist_70accf5e._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/b001a_next_dist_722d79f4._.js +6 -0
- package/webapp-dist/.next/server/chunks/ssr/b001a_next_dist_99636b3e._.js +4 -0
- package/webapp-dist/.next/server/chunks/ssr/b001a_next_dist_client_components_10f1741b._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/b001a_next_dist_client_components_builtin_forbidden_e274141c.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/b001a_next_dist_client_components_builtin_global-error_a5dade91.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/b001a_next_dist_client_components_builtin_unauthorized_348d5f3a.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/b001a_next_dist_esm_build_templates_app-page_441c6a47.js +4 -0
- package/webapp-dist/.next/server/chunks/ssr/workspace_trekker_7282b7cb._.js +4 -0
- package/webapp-dist/.next/server/chunks/ssr/workspace_trekker_a32cc225._.js +48 -0
- package/webapp-dist/.next/server/chunks/ssr/workspace_trekker_d88c7e00._.js +4 -0
- package/webapp-dist/.next/server/chunks/ssr/workspace_trekker_packages_webapp_src_68df25c3._.js +3 -0
- package/webapp-dist/.next/server/chunks/ssr/workspace_trekker_packages_webapp_src_components_providers_tsx_35c69be9._.js +3 -0
- package/webapp-dist/.next/server/functions-config-manifest.json +4 -0
- package/webapp-dist/.next/server/middleware-build-manifest.js +20 -0
- package/webapp-dist/.next/server/middleware-manifest.json +6 -0
- package/webapp-dist/.next/server/next-font-manifest.js +1 -0
- package/webapp-dist/.next/server/next-font-manifest.json +6 -0
- package/webapp-dist/.next/server/pages/404.html +17 -0
- package/webapp-dist/.next/server/pages/500.html +2 -0
- package/webapp-dist/.next/server/pages-manifest.json +4 -0
- package/webapp-dist/.next/server/server-reference-manifest.js +1 -0
- package/webapp-dist/.next/server/server-reference-manifest.json +5 -0
- package/webapp-dist/.next/static/chunks/1ac2cd06236076d4.js +46 -0
- package/webapp-dist/.next/static/chunks/3036933cb9bf38e3.js +1 -0
- package/webapp-dist/.next/static/chunks/5e0be427f68f6080.js +1 -0
- package/webapp-dist/.next/static/chunks/6aa9c115948055d1.js +1 -0
- package/webapp-dist/.next/static/chunks/a3a48138431e69f2.css +1 -0
- package/webapp-dist/.next/static/chunks/a6dad97d9634a72d.js +1 -0
- package/webapp-dist/.next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- package/webapp-dist/.next/static/chunks/c1589cab6fb15b0d.js +5 -0
- package/webapp-dist/.next/static/chunks/de03c42fd52463c2.js +2 -0
- package/webapp-dist/.next/static/chunks/f87dc668b9e005e0.js +1 -0
- package/webapp-dist/.next/static/chunks/turbopack-d39d36eb8f918d5a.js +4 -0
- package/webapp-dist/.next/static/sziMhYzomTi_mBUq2dVcq/_buildManifest.js +11 -0
- package/webapp-dist/.next/static/sziMhYzomTi_mBUq2dVcq/_clientMiddlewareManifest.json +1 -0
- package/webapp-dist/.next/static/sziMhYzomTi_mBUq2dVcq/_ssgManifest.js +1 -0
- package/webapp-dist/server.js +38 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2278 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __export = (target, all) => {
|
|
5
|
+
for (var name in all)
|
|
6
|
+
__defProp(target, name, {
|
|
7
|
+
get: all[name],
|
|
8
|
+
enumerable: true,
|
|
9
|
+
configurable: true,
|
|
10
|
+
set: (newValue) => all[name] = () => newValue
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/index.ts
|
|
15
|
+
import { Command as Command11 } from "commander";
|
|
16
|
+
|
|
17
|
+
// src/commands/init.ts
|
|
18
|
+
import { Command } from "commander";
|
|
19
|
+
|
|
20
|
+
// src/db/client.ts
|
|
21
|
+
import { Database } from "bun:sqlite";
|
|
22
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
23
|
+
|
|
24
|
+
// src/db/schema.ts
|
|
25
|
+
var exports_schema = {};
|
|
26
|
+
__export(exports_schema, {
|
|
27
|
+
tasksRelations: () => tasksRelations,
|
|
28
|
+
tasks: () => tasks,
|
|
29
|
+
projectsRelations: () => projectsRelations,
|
|
30
|
+
projects: () => projects,
|
|
31
|
+
idCounters: () => idCounters,
|
|
32
|
+
epicsRelations: () => epicsRelations,
|
|
33
|
+
epics: () => epics,
|
|
34
|
+
dependenciesRelations: () => dependenciesRelations,
|
|
35
|
+
dependencies: () => dependencies,
|
|
36
|
+
commentsRelations: () => commentsRelations,
|
|
37
|
+
comments: () => comments
|
|
38
|
+
});
|
|
39
|
+
import { relations } from "drizzle-orm";
|
|
40
|
+
|
|
41
|
+
// ../shared/schema.ts
|
|
42
|
+
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
43
|
+
var projects = sqliteTable("projects", {
|
|
44
|
+
id: text("id").primaryKey(),
|
|
45
|
+
name: text("name").notNull().unique(),
|
|
46
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
47
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
48
|
+
});
|
|
49
|
+
var epics = sqliteTable("epics", {
|
|
50
|
+
id: text("id").primaryKey(),
|
|
51
|
+
projectId: text("project_id").notNull(),
|
|
52
|
+
title: text("title").notNull(),
|
|
53
|
+
description: text("description"),
|
|
54
|
+
status: text("status").notNull().default("todo"),
|
|
55
|
+
priority: integer("priority").notNull().default(2),
|
|
56
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
57
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
58
|
+
});
|
|
59
|
+
var tasks = sqliteTable("tasks", {
|
|
60
|
+
id: text("id").primaryKey(),
|
|
61
|
+
projectId: text("project_id").notNull(),
|
|
62
|
+
epicId: text("epic_id"),
|
|
63
|
+
parentTaskId: text("parent_task_id"),
|
|
64
|
+
title: text("title").notNull(),
|
|
65
|
+
description: text("description"),
|
|
66
|
+
priority: integer("priority").notNull().default(2),
|
|
67
|
+
status: text("status").notNull().default("todo"),
|
|
68
|
+
tags: text("tags"),
|
|
69
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
70
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
71
|
+
});
|
|
72
|
+
var comments = sqliteTable("comments", {
|
|
73
|
+
id: text("id").primaryKey(),
|
|
74
|
+
taskId: text("task_id").notNull(),
|
|
75
|
+
author: text("author").notNull(),
|
|
76
|
+
content: text("content").notNull(),
|
|
77
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
78
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
79
|
+
});
|
|
80
|
+
var dependencies = sqliteTable("dependencies", {
|
|
81
|
+
id: text("id").primaryKey(),
|
|
82
|
+
taskId: text("task_id").notNull(),
|
|
83
|
+
dependsOnId: text("depends_on_id").notNull(),
|
|
84
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
85
|
+
});
|
|
86
|
+
var idCounters = sqliteTable("id_counters", {
|
|
87
|
+
entityType: text("entity_type").primaryKey(),
|
|
88
|
+
counter: integer("counter").notNull().default(0)
|
|
89
|
+
});
|
|
90
|
+
// ../shared/types.ts
|
|
91
|
+
var TASK_STATUSES = [
|
|
92
|
+
"todo",
|
|
93
|
+
"in_progress",
|
|
94
|
+
"completed",
|
|
95
|
+
"wont_fix",
|
|
96
|
+
"archived"
|
|
97
|
+
];
|
|
98
|
+
var EPIC_STATUSES = [
|
|
99
|
+
"todo",
|
|
100
|
+
"in_progress",
|
|
101
|
+
"completed",
|
|
102
|
+
"archived"
|
|
103
|
+
];
|
|
104
|
+
var PREFIX_MAP = {
|
|
105
|
+
task: "TREK",
|
|
106
|
+
epic: "EPIC",
|
|
107
|
+
comment: "CMT"
|
|
108
|
+
};
|
|
109
|
+
// src/db/schema.ts
|
|
110
|
+
var projectsRelations = relations(projects, ({ many }) => ({
|
|
111
|
+
epics: many(epics),
|
|
112
|
+
tasks: many(tasks)
|
|
113
|
+
}));
|
|
114
|
+
var epicsRelations = relations(epics, ({ one, many }) => ({
|
|
115
|
+
project: one(projects, {
|
|
116
|
+
fields: [epics.projectId],
|
|
117
|
+
references: [projects.id]
|
|
118
|
+
}),
|
|
119
|
+
tasks: many(tasks)
|
|
120
|
+
}));
|
|
121
|
+
var tasksRelations = relations(tasks, ({ one, many }) => ({
|
|
122
|
+
project: one(projects, {
|
|
123
|
+
fields: [tasks.projectId],
|
|
124
|
+
references: [projects.id]
|
|
125
|
+
}),
|
|
126
|
+
epic: one(epics, {
|
|
127
|
+
fields: [tasks.epicId],
|
|
128
|
+
references: [epics.id]
|
|
129
|
+
}),
|
|
130
|
+
parentTask: one(tasks, {
|
|
131
|
+
fields: [tasks.parentTaskId],
|
|
132
|
+
references: [tasks.id],
|
|
133
|
+
relationName: "subtasks"
|
|
134
|
+
}),
|
|
135
|
+
subtasks: many(tasks, { relationName: "subtasks" }),
|
|
136
|
+
comments: many(comments),
|
|
137
|
+
dependsOn: many(dependencies, { relationName: "dependsOn" }),
|
|
138
|
+
blockedBy: many(dependencies, { relationName: "blockedBy" })
|
|
139
|
+
}));
|
|
140
|
+
var commentsRelations = relations(comments, ({ one }) => ({
|
|
141
|
+
task: one(tasks, {
|
|
142
|
+
fields: [comments.taskId],
|
|
143
|
+
references: [tasks.id]
|
|
144
|
+
})
|
|
145
|
+
}));
|
|
146
|
+
var dependenciesRelations = relations(dependencies, ({ one }) => ({
|
|
147
|
+
task: one(tasks, {
|
|
148
|
+
fields: [dependencies.taskId],
|
|
149
|
+
references: [tasks.id],
|
|
150
|
+
relationName: "dependsOn"
|
|
151
|
+
}),
|
|
152
|
+
dependsOn: one(tasks, {
|
|
153
|
+
fields: [dependencies.dependsOnId],
|
|
154
|
+
references: [tasks.id],
|
|
155
|
+
relationName: "blockedBy"
|
|
156
|
+
})
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
// src/db/client.ts
|
|
160
|
+
import { existsSync, mkdirSync, rmSync } from "fs";
|
|
161
|
+
import { join } from "path";
|
|
162
|
+
var TREKKER_DIR = ".trekker";
|
|
163
|
+
var DB_NAME = "trekker.db";
|
|
164
|
+
function getTrekkerDir(cwd = process.cwd()) {
|
|
165
|
+
return join(cwd, TREKKER_DIR);
|
|
166
|
+
}
|
|
167
|
+
function getDbPath(cwd = process.cwd()) {
|
|
168
|
+
return join(getTrekkerDir(cwd), DB_NAME);
|
|
169
|
+
}
|
|
170
|
+
function isTrekkerInitialized(cwd = process.cwd()) {
|
|
171
|
+
return existsSync(getDbPath(cwd));
|
|
172
|
+
}
|
|
173
|
+
function ensureTrekkerDir(cwd = process.cwd()) {
|
|
174
|
+
const trekkerDir = getTrekkerDir(cwd);
|
|
175
|
+
if (!existsSync(trekkerDir)) {
|
|
176
|
+
mkdirSync(trekkerDir, { recursive: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
var dbInstance = null;
|
|
180
|
+
var sqliteInstance = null;
|
|
181
|
+
function getDb(cwd = process.cwd()) {
|
|
182
|
+
if (dbInstance) {
|
|
183
|
+
return dbInstance;
|
|
184
|
+
}
|
|
185
|
+
const dbPath = getDbPath(cwd);
|
|
186
|
+
if (!existsSync(dbPath)) {
|
|
187
|
+
throw new Error("Trekker not initialized. Run 'trekker init' first.");
|
|
188
|
+
}
|
|
189
|
+
sqliteInstance = new Database(dbPath);
|
|
190
|
+
dbInstance = drizzle(sqliteInstance, { schema: exports_schema });
|
|
191
|
+
return dbInstance;
|
|
192
|
+
}
|
|
193
|
+
function createDb(cwd = process.cwd()) {
|
|
194
|
+
ensureTrekkerDir(cwd);
|
|
195
|
+
const dbPath = getDbPath(cwd);
|
|
196
|
+
sqliteInstance = new Database(dbPath);
|
|
197
|
+
dbInstance = drizzle(sqliteInstance, { schema: exports_schema });
|
|
198
|
+
sqliteInstance.exec(`
|
|
199
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
200
|
+
id TEXT PRIMARY KEY,
|
|
201
|
+
name TEXT NOT NULL UNIQUE,
|
|
202
|
+
created_at INTEGER NOT NULL,
|
|
203
|
+
updated_at INTEGER NOT NULL
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
CREATE TABLE IF NOT EXISTS epics (
|
|
207
|
+
id TEXT PRIMARY KEY,
|
|
208
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
209
|
+
title TEXT NOT NULL,
|
|
210
|
+
description TEXT,
|
|
211
|
+
status TEXT NOT NULL DEFAULT 'todo',
|
|
212
|
+
priority INTEGER NOT NULL DEFAULT 2,
|
|
213
|
+
created_at INTEGER NOT NULL,
|
|
214
|
+
updated_at INTEGER NOT NULL
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
218
|
+
id TEXT PRIMARY KEY,
|
|
219
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
220
|
+
epic_id TEXT REFERENCES epics(id) ON DELETE SET NULL,
|
|
221
|
+
parent_task_id TEXT,
|
|
222
|
+
title TEXT NOT NULL,
|
|
223
|
+
description TEXT,
|
|
224
|
+
priority INTEGER NOT NULL DEFAULT 2,
|
|
225
|
+
status TEXT NOT NULL DEFAULT 'todo',
|
|
226
|
+
tags TEXT,
|
|
227
|
+
created_at INTEGER NOT NULL,
|
|
228
|
+
updated_at INTEGER NOT NULL
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
CREATE TABLE IF NOT EXISTS comments (
|
|
232
|
+
id TEXT PRIMARY KEY,
|
|
233
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
234
|
+
author TEXT NOT NULL,
|
|
235
|
+
content TEXT NOT NULL,
|
|
236
|
+
created_at INTEGER NOT NULL,
|
|
237
|
+
updated_at INTEGER NOT NULL
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
CREATE TABLE IF NOT EXISTS dependencies (
|
|
241
|
+
id TEXT PRIMARY KEY,
|
|
242
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
243
|
+
depends_on_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
244
|
+
created_at INTEGER NOT NULL
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
CREATE TABLE IF NOT EXISTS id_counters (
|
|
248
|
+
entity_type TEXT PRIMARY KEY,
|
|
249
|
+
counter INTEGER NOT NULL DEFAULT 0
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
-- Initialize counters
|
|
253
|
+
INSERT OR IGNORE INTO id_counters (entity_type, counter) VALUES ('task', 0);
|
|
254
|
+
INSERT OR IGNORE INTO id_counters (entity_type, counter) VALUES ('epic', 0);
|
|
255
|
+
INSERT OR IGNORE INTO id_counters (entity_type, counter) VALUES ('comment', 0);
|
|
256
|
+
`);
|
|
257
|
+
return dbInstance;
|
|
258
|
+
}
|
|
259
|
+
function closeDb() {
|
|
260
|
+
if (sqliteInstance) {
|
|
261
|
+
sqliteInstance.close();
|
|
262
|
+
sqliteInstance = null;
|
|
263
|
+
dbInstance = null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function deleteDb(cwd = process.cwd()) {
|
|
267
|
+
closeDb();
|
|
268
|
+
const trekkerDir = getTrekkerDir(cwd);
|
|
269
|
+
if (existsSync(trekkerDir)) {
|
|
270
|
+
rmSync(trekkerDir, { recursive: true, force: true });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/utils/id-generator.ts
|
|
275
|
+
import { eq, sql } from "drizzle-orm";
|
|
276
|
+
function generateId(entityType) {
|
|
277
|
+
const db = getDb();
|
|
278
|
+
const prefix = PREFIX_MAP[entityType];
|
|
279
|
+
db.update(idCounters).set({ counter: sql`${idCounters.counter} + 1` }).where(eq(idCounters.entityType, entityType)).run();
|
|
280
|
+
const result = db.select({ counter: idCounters.counter }).from(idCounters).where(eq(idCounters.entityType, entityType)).get();
|
|
281
|
+
if (!result) {
|
|
282
|
+
throw new Error(`Counter not found for entity type: ${entityType}`);
|
|
283
|
+
}
|
|
284
|
+
return `${prefix}-${result.counter}`;
|
|
285
|
+
}
|
|
286
|
+
function generateUuid() {
|
|
287
|
+
return crypto.randomUUID();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/services/project.ts
|
|
291
|
+
import { basename } from "path";
|
|
292
|
+
function initProject(cwd = process.cwd()) {
|
|
293
|
+
if (isTrekkerInitialized(cwd)) {
|
|
294
|
+
throw new Error("Trekker is already initialized in this directory.");
|
|
295
|
+
}
|
|
296
|
+
const db = createDb(cwd);
|
|
297
|
+
const projectName = basename(cwd);
|
|
298
|
+
const now = new Date;
|
|
299
|
+
db.insert(projects).values({
|
|
300
|
+
id: generateUuid(),
|
|
301
|
+
name: projectName,
|
|
302
|
+
createdAt: now,
|
|
303
|
+
updatedAt: now
|
|
304
|
+
}).run();
|
|
305
|
+
}
|
|
306
|
+
function wipeProject(cwd = process.cwd()) {
|
|
307
|
+
if (!isTrekkerInitialized(cwd)) {
|
|
308
|
+
throw new Error("Trekker is not initialized in this directory.");
|
|
309
|
+
}
|
|
310
|
+
deleteDb(cwd);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/utils/output.ts
|
|
314
|
+
var jsonMode = false;
|
|
315
|
+
function setJsonMode(enabled) {
|
|
316
|
+
jsonMode = enabled;
|
|
317
|
+
}
|
|
318
|
+
function isJsonMode() {
|
|
319
|
+
return jsonMode;
|
|
320
|
+
}
|
|
321
|
+
function output(data) {
|
|
322
|
+
if (jsonMode) {
|
|
323
|
+
console.log(JSON.stringify(data, null, 2));
|
|
324
|
+
} else if (typeof data === "string") {
|
|
325
|
+
console.log(data);
|
|
326
|
+
} else {
|
|
327
|
+
console.log(JSON.stringify(data, null, 2));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function success(message, data) {
|
|
331
|
+
if (jsonMode) {
|
|
332
|
+
console.log(JSON.stringify({ success: true, message, data }, null, 2));
|
|
333
|
+
} else {
|
|
334
|
+
console.log(`\u2713 ${message}`);
|
|
335
|
+
if (data) {
|
|
336
|
+
output(data);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function error(message, details) {
|
|
341
|
+
if (jsonMode) {
|
|
342
|
+
console.error(JSON.stringify({ success: false, error: message, details }, null, 2));
|
|
343
|
+
} else {
|
|
344
|
+
console.error(`\u2717 Error: ${message}`);
|
|
345
|
+
if (details) {
|
|
346
|
+
console.error(details);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function info(message) {
|
|
351
|
+
if (!jsonMode) {
|
|
352
|
+
console.log(message);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function formatTask(task) {
|
|
356
|
+
const lines = [
|
|
357
|
+
`ID: ${task.id}`,
|
|
358
|
+
`Title: ${task.title}`,
|
|
359
|
+
`Status: ${task.status}`,
|
|
360
|
+
`Priority: ${task.priority}`
|
|
361
|
+
];
|
|
362
|
+
if (task.description) {
|
|
363
|
+
lines.push(`Description: ${task.description}`);
|
|
364
|
+
}
|
|
365
|
+
if (task.epicId) {
|
|
366
|
+
lines.push(`Epic: ${task.epicId}`);
|
|
367
|
+
}
|
|
368
|
+
if (task.parentTaskId) {
|
|
369
|
+
lines.push(`Parent: ${task.parentTaskId}`);
|
|
370
|
+
}
|
|
371
|
+
if (task.tags) {
|
|
372
|
+
lines.push(`Tags: ${task.tags}`);
|
|
373
|
+
}
|
|
374
|
+
lines.push(`Created: ${task.createdAt.toISOString()}`);
|
|
375
|
+
lines.push(`Updated: ${task.updatedAt.toISOString()}`);
|
|
376
|
+
return lines.join(`
|
|
377
|
+
`);
|
|
378
|
+
}
|
|
379
|
+
function formatEpic(epic) {
|
|
380
|
+
const lines = [
|
|
381
|
+
`ID: ${epic.id}`,
|
|
382
|
+
`Title: ${epic.title}`,
|
|
383
|
+
`Status: ${epic.status}`,
|
|
384
|
+
`Priority: ${epic.priority}`
|
|
385
|
+
];
|
|
386
|
+
if (epic.description) {
|
|
387
|
+
lines.push(`Description: ${epic.description}`);
|
|
388
|
+
}
|
|
389
|
+
lines.push(`Created: ${epic.createdAt.toISOString()}`);
|
|
390
|
+
lines.push(`Updated: ${epic.updatedAt.toISOString()}`);
|
|
391
|
+
return lines.join(`
|
|
392
|
+
`);
|
|
393
|
+
}
|
|
394
|
+
function formatComment(comment) {
|
|
395
|
+
const lines = [
|
|
396
|
+
`ID: ${comment.id}`,
|
|
397
|
+
`Author: ${comment.author}`,
|
|
398
|
+
`Content: ${comment.content}`,
|
|
399
|
+
`Created: ${comment.createdAt.toISOString()}`
|
|
400
|
+
];
|
|
401
|
+
return lines.join(`
|
|
402
|
+
`);
|
|
403
|
+
}
|
|
404
|
+
function formatTaskList(tasks2) {
|
|
405
|
+
if (tasks2.length === 0) {
|
|
406
|
+
return "No tasks found.";
|
|
407
|
+
}
|
|
408
|
+
const lines = tasks2.map((task) => {
|
|
409
|
+
const tags = task.tags ? ` [${task.tags}]` : "";
|
|
410
|
+
const parent = task.parentTaskId ? ` (subtask of ${task.parentTaskId})` : "";
|
|
411
|
+
return `${task.id} | ${task.status.padEnd(11)} | P${task.priority} | ${task.title}${tags}${parent}`;
|
|
412
|
+
});
|
|
413
|
+
return lines.join(`
|
|
414
|
+
`);
|
|
415
|
+
}
|
|
416
|
+
function formatEpicList(epics2) {
|
|
417
|
+
if (epics2.length === 0) {
|
|
418
|
+
return "No epics found.";
|
|
419
|
+
}
|
|
420
|
+
const lines = epics2.map((epic) => {
|
|
421
|
+
return `${epic.id} | ${epic.status.padEnd(11)} | P${epic.priority} | ${epic.title}`;
|
|
422
|
+
});
|
|
423
|
+
return lines.join(`
|
|
424
|
+
`);
|
|
425
|
+
}
|
|
426
|
+
function formatCommentList(comments2) {
|
|
427
|
+
if (comments2.length === 0) {
|
|
428
|
+
return "No comments found.";
|
|
429
|
+
}
|
|
430
|
+
return comments2.map((c) => `[${c.id}] ${c.author}: ${c.content}`).join(`
|
|
431
|
+
`);
|
|
432
|
+
}
|
|
433
|
+
function formatDependencyList(dependencies2, direction) {
|
|
434
|
+
if (dependencies2.length === 0) {
|
|
435
|
+
return direction === "depends_on" ? "No dependencies." : "Does not block any tasks.";
|
|
436
|
+
}
|
|
437
|
+
if (direction === "depends_on") {
|
|
438
|
+
return dependencies2.map((d) => ` \u2192 depends on ${d.dependsOnId}`).join(`
|
|
439
|
+
`);
|
|
440
|
+
} else {
|
|
441
|
+
return dependencies2.map((d) => ` \u2192 blocks ${d.taskId}`).join(`
|
|
442
|
+
`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/commands/init.ts
|
|
447
|
+
var initCommand = new Command("init").description("Initialize Trekker in the current directory").action(() => {
|
|
448
|
+
try {
|
|
449
|
+
if (isTrekkerInitialized()) {
|
|
450
|
+
error("Trekker is already initialized in this directory.");
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
initProject();
|
|
454
|
+
success("Trekker initialized successfully.");
|
|
455
|
+
} catch (err) {
|
|
456
|
+
error(err instanceof Error ? err.message : String(err));
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// src/commands/wipe.ts
|
|
462
|
+
import { Command as Command2 } from "commander";
|
|
463
|
+
import * as readline from "readline";
|
|
464
|
+
var wipeCommand = new Command2("wipe").description("Delete all Trekker data in the current directory").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
|
|
465
|
+
try {
|
|
466
|
+
if (!isTrekkerInitialized()) {
|
|
467
|
+
error("Trekker is not initialized in this directory.");
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
if (!options.yes) {
|
|
471
|
+
const confirmed = await confirm("Are you sure you want to delete all Trekker data? This cannot be undone. (y/N): ");
|
|
472
|
+
if (!confirmed) {
|
|
473
|
+
console.log("Aborted.");
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
wipeProject();
|
|
478
|
+
success("Trekker data deleted successfully.");
|
|
479
|
+
} catch (err) {
|
|
480
|
+
error(err instanceof Error ? err.message : String(err));
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
function confirm(prompt) {
|
|
485
|
+
return new Promise((resolve) => {
|
|
486
|
+
const rl = readline.createInterface({
|
|
487
|
+
input: process.stdin,
|
|
488
|
+
output: process.stdout
|
|
489
|
+
});
|
|
490
|
+
rl.question(prompt, (answer) => {
|
|
491
|
+
rl.close();
|
|
492
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/commands/epic.ts
|
|
498
|
+
import { Command as Command3 } from "commander";
|
|
499
|
+
|
|
500
|
+
// src/services/epic.ts
|
|
501
|
+
import { eq as eq2 } from "drizzle-orm";
|
|
502
|
+
function createEpic(input) {
|
|
503
|
+
const db = getDb();
|
|
504
|
+
const project = db.select().from(projects).get();
|
|
505
|
+
if (!project) {
|
|
506
|
+
throw new Error("Project not found. Run 'trekker init' first.");
|
|
507
|
+
}
|
|
508
|
+
const id = generateId("epic");
|
|
509
|
+
const now = new Date;
|
|
510
|
+
const epic = {
|
|
511
|
+
id,
|
|
512
|
+
projectId: project.id,
|
|
513
|
+
title: input.title,
|
|
514
|
+
description: input.description ?? null,
|
|
515
|
+
status: input.status ?? "todo",
|
|
516
|
+
priority: input.priority ?? 2,
|
|
517
|
+
createdAt: now,
|
|
518
|
+
updatedAt: now
|
|
519
|
+
};
|
|
520
|
+
db.insert(epics).values(epic).run();
|
|
521
|
+
return epic;
|
|
522
|
+
}
|
|
523
|
+
function getEpic(id) {
|
|
524
|
+
const db = getDb();
|
|
525
|
+
const result = db.select().from(epics).where(eq2(epics.id, id)).get();
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
528
|
+
function listEpics(status) {
|
|
529
|
+
const db = getDb();
|
|
530
|
+
if (status) {
|
|
531
|
+
return db.select().from(epics).where(eq2(epics.status, status)).all();
|
|
532
|
+
}
|
|
533
|
+
return db.select().from(epics).all();
|
|
534
|
+
}
|
|
535
|
+
function updateEpic(id, input) {
|
|
536
|
+
const db = getDb();
|
|
537
|
+
const existing = getEpic(id);
|
|
538
|
+
if (!existing) {
|
|
539
|
+
throw new Error(`Epic not found: ${id}`);
|
|
540
|
+
}
|
|
541
|
+
const updates = {
|
|
542
|
+
updatedAt: new Date
|
|
543
|
+
};
|
|
544
|
+
if (input.title !== undefined)
|
|
545
|
+
updates.title = input.title;
|
|
546
|
+
if (input.description !== undefined)
|
|
547
|
+
updates.description = input.description;
|
|
548
|
+
if (input.status !== undefined)
|
|
549
|
+
updates.status = input.status;
|
|
550
|
+
if (input.priority !== undefined)
|
|
551
|
+
updates.priority = input.priority;
|
|
552
|
+
db.update(epics).set(updates).where(eq2(epics.id, id)).run();
|
|
553
|
+
return getEpic(id);
|
|
554
|
+
}
|
|
555
|
+
function deleteEpic(id) {
|
|
556
|
+
const db = getDb();
|
|
557
|
+
const existing = getEpic(id);
|
|
558
|
+
if (!existing) {
|
|
559
|
+
throw new Error(`Epic not found: ${id}`);
|
|
560
|
+
}
|
|
561
|
+
db.delete(epics).where(eq2(epics.id, id)).run();
|
|
562
|
+
}
|
|
563
|
+
// src/utils/validator.ts
|
|
564
|
+
function isValidTaskStatus(status) {
|
|
565
|
+
return TASK_STATUSES.includes(status);
|
|
566
|
+
}
|
|
567
|
+
function isValidEpicStatus(status) {
|
|
568
|
+
return EPIC_STATUSES.includes(status);
|
|
569
|
+
}
|
|
570
|
+
function isValidPriority(priority) {
|
|
571
|
+
return Number.isInteger(priority) && priority >= 0 && priority <= 5;
|
|
572
|
+
}
|
|
573
|
+
function parseStatus(status, type) {
|
|
574
|
+
if (!status)
|
|
575
|
+
return;
|
|
576
|
+
const normalizedStatus = status.toLowerCase().replace(/-/g, "_");
|
|
577
|
+
if (type === "task") {
|
|
578
|
+
if (!isValidTaskStatus(normalizedStatus)) {
|
|
579
|
+
throw new Error(`Invalid task status: ${status}. Valid values: ${TASK_STATUSES.join(", ")}`);
|
|
580
|
+
}
|
|
581
|
+
return normalizedStatus;
|
|
582
|
+
} else {
|
|
583
|
+
if (!isValidEpicStatus(normalizedStatus)) {
|
|
584
|
+
throw new Error(`Invalid epic status: ${status}. Valid values: ${EPIC_STATUSES.join(", ")}`);
|
|
585
|
+
}
|
|
586
|
+
return normalizedStatus;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function parsePriority(priority) {
|
|
590
|
+
if (priority === undefined)
|
|
591
|
+
return;
|
|
592
|
+
const num = parseInt(priority, 10);
|
|
593
|
+
if (isNaN(num) || !isValidPriority(num)) {
|
|
594
|
+
throw new Error(`Invalid priority: ${priority}. Must be a number between 0 and 5.`);
|
|
595
|
+
}
|
|
596
|
+
return num;
|
|
597
|
+
}
|
|
598
|
+
function validateRequired(value, fieldName) {
|
|
599
|
+
if (value === undefined || value === null || value === "") {
|
|
600
|
+
throw new Error(`${fieldName} is required`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/commands/epic.ts
|
|
605
|
+
var epicCommand = new Command3("epic").description("Manage epics");
|
|
606
|
+
epicCommand.command("create").description("Create a new epic").requiredOption("-t, --title <title>", "Epic title").option("-d, --description <description>", "Epic description").option("-p, --priority <priority>", "Priority (0-5, default: 2)").option("-s, --status <status>", "Status (todo, in_progress, completed, archived)").action((options) => {
|
|
607
|
+
try {
|
|
608
|
+
validateRequired(options.title, "Title");
|
|
609
|
+
const epic = createEpic({
|
|
610
|
+
title: options.title,
|
|
611
|
+
description: options.description,
|
|
612
|
+
priority: parsePriority(options.priority),
|
|
613
|
+
status: parseStatus(options.status, "epic")
|
|
614
|
+
});
|
|
615
|
+
if (isJsonMode()) {
|
|
616
|
+
output(epic);
|
|
617
|
+
} else {
|
|
618
|
+
success(`Epic created: ${epic.id}`);
|
|
619
|
+
console.log(formatEpic(epic));
|
|
620
|
+
}
|
|
621
|
+
} catch (err) {
|
|
622
|
+
error(err instanceof Error ? err.message : String(err));
|
|
623
|
+
process.exit(1);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
epicCommand.command("list").description("List all epics").option("-s, --status <status>", "Filter by status").action((options) => {
|
|
627
|
+
try {
|
|
628
|
+
const status = parseStatus(options.status, "epic");
|
|
629
|
+
const epics2 = listEpics(status);
|
|
630
|
+
if (isJsonMode()) {
|
|
631
|
+
output(epics2);
|
|
632
|
+
} else {
|
|
633
|
+
console.log(formatEpicList(epics2));
|
|
634
|
+
}
|
|
635
|
+
} catch (err) {
|
|
636
|
+
error(err instanceof Error ? err.message : String(err));
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
epicCommand.command("show <epic-id>").description("Show epic details").action((epicId) => {
|
|
641
|
+
try {
|
|
642
|
+
const epic = getEpic(epicId);
|
|
643
|
+
if (!epic) {
|
|
644
|
+
error(`Epic not found: ${epicId}`);
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
if (isJsonMode()) {
|
|
648
|
+
output(epic);
|
|
649
|
+
} else {
|
|
650
|
+
console.log(formatEpic(epic));
|
|
651
|
+
}
|
|
652
|
+
} catch (err) {
|
|
653
|
+
error(err instanceof Error ? err.message : String(err));
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
epicCommand.command("update <epic-id>").description("Update an epic").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((epicId, options) => {
|
|
658
|
+
try {
|
|
659
|
+
const epic = updateEpic(epicId, {
|
|
660
|
+
title: options.title,
|
|
661
|
+
description: options.description,
|
|
662
|
+
priority: parsePriority(options.priority),
|
|
663
|
+
status: parseStatus(options.status, "epic")
|
|
664
|
+
});
|
|
665
|
+
if (isJsonMode()) {
|
|
666
|
+
output(epic);
|
|
667
|
+
} else {
|
|
668
|
+
success(`Epic updated: ${epic.id}`);
|
|
669
|
+
console.log(formatEpic(epic));
|
|
670
|
+
}
|
|
671
|
+
} catch (err) {
|
|
672
|
+
error(err instanceof Error ? err.message : String(err));
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
epicCommand.command("delete <epic-id>").description("Delete an epic").action((epicId) => {
|
|
677
|
+
try {
|
|
678
|
+
deleteEpic(epicId);
|
|
679
|
+
success(`Epic deleted: ${epicId}`);
|
|
680
|
+
} catch (err) {
|
|
681
|
+
error(err instanceof Error ? err.message : String(err));
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// src/commands/task.ts
|
|
687
|
+
import { Command as Command4 } from "commander";
|
|
688
|
+
|
|
689
|
+
// src/services/task.ts
|
|
690
|
+
import { eq as eq3, and, isNull } from "drizzle-orm";
|
|
691
|
+
function createTask(input) {
|
|
692
|
+
const db = getDb();
|
|
693
|
+
const project = db.select().from(projects).get();
|
|
694
|
+
if (!project) {
|
|
695
|
+
throw new Error("Project not found. Run 'trekker init' first.");
|
|
696
|
+
}
|
|
697
|
+
if (input.epicId) {
|
|
698
|
+
const epic = db.select().from(epics).where(eq3(epics.id, input.epicId)).get();
|
|
699
|
+
if (!epic) {
|
|
700
|
+
throw new Error(`Epic not found: ${input.epicId}`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (input.parentTaskId) {
|
|
704
|
+
const parent = db.select().from(tasks).where(eq3(tasks.id, input.parentTaskId)).get();
|
|
705
|
+
if (!parent) {
|
|
706
|
+
throw new Error(`Parent task not found: ${input.parentTaskId}`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const id = generateId("task");
|
|
710
|
+
const now = new Date;
|
|
711
|
+
const task = {
|
|
712
|
+
id,
|
|
713
|
+
projectId: project.id,
|
|
714
|
+
epicId: input.epicId ?? null,
|
|
715
|
+
parentTaskId: input.parentTaskId ?? null,
|
|
716
|
+
title: input.title,
|
|
717
|
+
description: input.description ?? null,
|
|
718
|
+
priority: input.priority ?? 2,
|
|
719
|
+
status: input.status ?? "todo",
|
|
720
|
+
tags: input.tags ?? null,
|
|
721
|
+
createdAt: now,
|
|
722
|
+
updatedAt: now
|
|
723
|
+
};
|
|
724
|
+
db.insert(tasks).values(task).run();
|
|
725
|
+
return task;
|
|
726
|
+
}
|
|
727
|
+
function getTask(id) {
|
|
728
|
+
const db = getDb();
|
|
729
|
+
const result = db.select().from(tasks).where(eq3(tasks.id, id)).get();
|
|
730
|
+
return result;
|
|
731
|
+
}
|
|
732
|
+
function listTasks(options) {
|
|
733
|
+
const db = getDb();
|
|
734
|
+
const conditions = [];
|
|
735
|
+
if (options?.status) {
|
|
736
|
+
conditions.push(eq3(tasks.status, options.status));
|
|
737
|
+
}
|
|
738
|
+
if (options?.epicId) {
|
|
739
|
+
conditions.push(eq3(tasks.epicId, options.epicId));
|
|
740
|
+
}
|
|
741
|
+
if (options?.parentTaskId === null) {
|
|
742
|
+
conditions.push(isNull(tasks.parentTaskId));
|
|
743
|
+
} else if (options?.parentTaskId) {
|
|
744
|
+
conditions.push(eq3(tasks.parentTaskId, options.parentTaskId));
|
|
745
|
+
}
|
|
746
|
+
if (conditions.length > 0) {
|
|
747
|
+
return db.select().from(tasks).where(and(...conditions)).all();
|
|
748
|
+
}
|
|
749
|
+
return db.select().from(tasks).all();
|
|
750
|
+
}
|
|
751
|
+
function listSubtasks(parentTaskId) {
|
|
752
|
+
const db = getDb();
|
|
753
|
+
return db.select().from(tasks).where(eq3(tasks.parentTaskId, parentTaskId)).all();
|
|
754
|
+
}
|
|
755
|
+
function updateTask(id, input) {
|
|
756
|
+
const db = getDb();
|
|
757
|
+
const existing = getTask(id);
|
|
758
|
+
if (!existing) {
|
|
759
|
+
throw new Error(`Task not found: ${id}`);
|
|
760
|
+
}
|
|
761
|
+
if (input.epicId) {
|
|
762
|
+
const epic = db.select().from(epics).where(eq3(epics.id, input.epicId)).get();
|
|
763
|
+
if (!epic) {
|
|
764
|
+
throw new Error(`Epic not found: ${input.epicId}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
const updates = {
|
|
768
|
+
updatedAt: new Date
|
|
769
|
+
};
|
|
770
|
+
if (input.title !== undefined)
|
|
771
|
+
updates.title = input.title;
|
|
772
|
+
if (input.description !== undefined)
|
|
773
|
+
updates.description = input.description;
|
|
774
|
+
if (input.priority !== undefined)
|
|
775
|
+
updates.priority = input.priority;
|
|
776
|
+
if (input.status !== undefined)
|
|
777
|
+
updates.status = input.status;
|
|
778
|
+
if (input.tags !== undefined)
|
|
779
|
+
updates.tags = input.tags;
|
|
780
|
+
if (input.epicId !== undefined)
|
|
781
|
+
updates.epicId = input.epicId;
|
|
782
|
+
db.update(tasks).set(updates).where(eq3(tasks.id, id)).run();
|
|
783
|
+
return getTask(id);
|
|
784
|
+
}
|
|
785
|
+
function deleteTask(id) {
|
|
786
|
+
const db = getDb();
|
|
787
|
+
const existing = getTask(id);
|
|
788
|
+
if (!existing) {
|
|
789
|
+
throw new Error(`Task not found: ${id}`);
|
|
790
|
+
}
|
|
791
|
+
db.delete(tasks).where(eq3(tasks.id, id)).run();
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/commands/task.ts
|
|
795
|
+
var taskCommand = new Command4("task").description("Manage tasks");
|
|
796
|
+
taskCommand.command("create").description("Create a new task").requiredOption("-t, --title <title>", "Task title").option("-d, --description <description>", "Task description").option("-p, --priority <priority>", "Priority (0-5, default: 2)").option("-s, --status <status>", "Status (todo, in_progress, completed, wont_fix, archived)").option("--tags <tags>", "Comma-separated tags").option("-e, --epic <epic-id>", "Epic ID to assign task to").action((options) => {
|
|
797
|
+
try {
|
|
798
|
+
validateRequired(options.title, "Title");
|
|
799
|
+
const task = createTask({
|
|
800
|
+
title: options.title,
|
|
801
|
+
description: options.description,
|
|
802
|
+
priority: parsePriority(options.priority),
|
|
803
|
+
status: parseStatus(options.status, "task"),
|
|
804
|
+
tags: options.tags,
|
|
805
|
+
epicId: options.epic
|
|
806
|
+
});
|
|
807
|
+
if (isJsonMode()) {
|
|
808
|
+
output(task);
|
|
809
|
+
} else {
|
|
810
|
+
success(`Task created: ${task.id}`);
|
|
811
|
+
console.log(formatTask(task));
|
|
812
|
+
}
|
|
813
|
+
} catch (err) {
|
|
814
|
+
error(err instanceof Error ? err.message : String(err));
|
|
815
|
+
process.exit(1);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
taskCommand.command("list").description("List all tasks").option("-s, --status <status>", "Filter by status").option("-e, --epic <epic-id>", "Filter by epic").action((options) => {
|
|
819
|
+
try {
|
|
820
|
+
const status = parseStatus(options.status, "task");
|
|
821
|
+
const tasks2 = listTasks({
|
|
822
|
+
status,
|
|
823
|
+
epicId: options.epic,
|
|
824
|
+
parentTaskId: null
|
|
825
|
+
});
|
|
826
|
+
if (isJsonMode()) {
|
|
827
|
+
output(tasks2);
|
|
828
|
+
} else {
|
|
829
|
+
console.log(formatTaskList(tasks2));
|
|
830
|
+
}
|
|
831
|
+
} catch (err) {
|
|
832
|
+
error(err instanceof Error ? err.message : String(err));
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
taskCommand.command("show <task-id>").description("Show task details").action((taskId) => {
|
|
837
|
+
try {
|
|
838
|
+
const task = getTask(taskId);
|
|
839
|
+
if (!task) {
|
|
840
|
+
error(`Task not found: ${taskId}`);
|
|
841
|
+
process.exit(1);
|
|
842
|
+
}
|
|
843
|
+
if (isJsonMode()) {
|
|
844
|
+
output(task);
|
|
845
|
+
} else {
|
|
846
|
+
console.log(formatTask(task));
|
|
847
|
+
}
|
|
848
|
+
} catch (err) {
|
|
849
|
+
error(err instanceof Error ? err.message : String(err));
|
|
850
|
+
process.exit(1);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
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) => {
|
|
854
|
+
try {
|
|
855
|
+
const updateInput = {};
|
|
856
|
+
if (options.title !== undefined)
|
|
857
|
+
updateInput.title = options.title;
|
|
858
|
+
if (options.description !== undefined)
|
|
859
|
+
updateInput.description = options.description;
|
|
860
|
+
if (options.priority !== undefined)
|
|
861
|
+
updateInput.priority = parsePriority(options.priority);
|
|
862
|
+
if (options.status !== undefined) {
|
|
863
|
+
updateInput.status = parseStatus(options.status, "task");
|
|
864
|
+
}
|
|
865
|
+
if (options.tags !== undefined)
|
|
866
|
+
updateInput.tags = options.tags;
|
|
867
|
+
if (options.epic === false) {
|
|
868
|
+
updateInput.epicId = null;
|
|
869
|
+
} else if (options.epic !== undefined) {
|
|
870
|
+
updateInput.epicId = options.epic;
|
|
871
|
+
}
|
|
872
|
+
const task = updateTask(taskId, updateInput);
|
|
873
|
+
if (isJsonMode()) {
|
|
874
|
+
output(task);
|
|
875
|
+
} else {
|
|
876
|
+
success(`Task updated: ${task.id}`);
|
|
877
|
+
console.log(formatTask(task));
|
|
878
|
+
}
|
|
879
|
+
} catch (err) {
|
|
880
|
+
error(err instanceof Error ? err.message : String(err));
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
taskCommand.command("delete <task-id>").description("Delete a task").action((taskId) => {
|
|
885
|
+
try {
|
|
886
|
+
deleteTask(taskId);
|
|
887
|
+
success(`Task deleted: ${taskId}`);
|
|
888
|
+
} catch (err) {
|
|
889
|
+
error(err instanceof Error ? err.message : String(err));
|
|
890
|
+
process.exit(1);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
// src/commands/subtask.ts
|
|
895
|
+
import { Command as Command5 } from "commander";
|
|
896
|
+
var subtaskCommand = new Command5("subtask").description("Manage subtasks");
|
|
897
|
+
subtaskCommand.command("create <parent-task-id>").description("Create a new subtask").requiredOption("-t, --title <title>", "Subtask title").option("-d, --description <description>", "Subtask description").option("-p, --priority <priority>", "Priority (0-5, default: 2)").option("-s, --status <status>", "Status (todo, in_progress, completed, wont_fix, archived)").action((parentTaskId, options) => {
|
|
898
|
+
try {
|
|
899
|
+
validateRequired(options.title, "Title");
|
|
900
|
+
const parent = getTask(parentTaskId);
|
|
901
|
+
if (!parent) {
|
|
902
|
+
error(`Parent task not found: ${parentTaskId}`);
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
const subtask = createTask({
|
|
906
|
+
title: options.title,
|
|
907
|
+
description: options.description,
|
|
908
|
+
priority: parsePriority(options.priority),
|
|
909
|
+
status: parseStatus(options.status, "task"),
|
|
910
|
+
parentTaskId,
|
|
911
|
+
epicId: parent.epicId ?? undefined
|
|
912
|
+
});
|
|
913
|
+
if (isJsonMode()) {
|
|
914
|
+
output(subtask);
|
|
915
|
+
} else {
|
|
916
|
+
success(`Subtask created: ${subtask.id}`);
|
|
917
|
+
console.log(formatTask(subtask));
|
|
918
|
+
}
|
|
919
|
+
} catch (err) {
|
|
920
|
+
error(err instanceof Error ? err.message : String(err));
|
|
921
|
+
process.exit(1);
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
subtaskCommand.command("list <parent-task-id>").description("List all subtasks of a task").action((parentTaskId) => {
|
|
925
|
+
try {
|
|
926
|
+
const parent = getTask(parentTaskId);
|
|
927
|
+
if (!parent) {
|
|
928
|
+
error(`Parent task not found: ${parentTaskId}`);
|
|
929
|
+
process.exit(1);
|
|
930
|
+
}
|
|
931
|
+
const subtasks = listSubtasks(parentTaskId);
|
|
932
|
+
if (isJsonMode()) {
|
|
933
|
+
output(subtasks);
|
|
934
|
+
} else {
|
|
935
|
+
if (subtasks.length === 0) {
|
|
936
|
+
console.log(`No subtasks for ${parentTaskId}`);
|
|
937
|
+
} else {
|
|
938
|
+
console.log(`Subtasks of ${parentTaskId}:`);
|
|
939
|
+
console.log(formatTaskList(subtasks));
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
} catch (err) {
|
|
943
|
+
error(err instanceof Error ? err.message : String(err));
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
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) => {
|
|
948
|
+
try {
|
|
949
|
+
const subtask = getTask(subtaskId);
|
|
950
|
+
if (!subtask) {
|
|
951
|
+
error(`Subtask not found: ${subtaskId}`);
|
|
952
|
+
process.exit(1);
|
|
953
|
+
}
|
|
954
|
+
if (!subtask.parentTaskId) {
|
|
955
|
+
error(`${subtaskId} is not a subtask. Use 'trekker task update' instead.`);
|
|
956
|
+
process.exit(1);
|
|
957
|
+
}
|
|
958
|
+
const updateInput = {};
|
|
959
|
+
if (options.title !== undefined)
|
|
960
|
+
updateInput.title = options.title;
|
|
961
|
+
if (options.description !== undefined)
|
|
962
|
+
updateInput.description = options.description;
|
|
963
|
+
if (options.priority !== undefined)
|
|
964
|
+
updateInput.priority = parsePriority(options.priority);
|
|
965
|
+
if (options.status !== undefined) {
|
|
966
|
+
updateInput.status = parseStatus(options.status, "task");
|
|
967
|
+
}
|
|
968
|
+
const updated = updateTask(subtaskId, updateInput);
|
|
969
|
+
if (isJsonMode()) {
|
|
970
|
+
output(updated);
|
|
971
|
+
} else {
|
|
972
|
+
success(`Subtask updated: ${updated.id}`);
|
|
973
|
+
console.log(formatTask(updated));
|
|
974
|
+
}
|
|
975
|
+
} catch (err) {
|
|
976
|
+
error(err instanceof Error ? err.message : String(err));
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
subtaskCommand.command("delete <subtask-id>").description("Delete a subtask").action((subtaskId) => {
|
|
981
|
+
try {
|
|
982
|
+
const subtask = getTask(subtaskId);
|
|
983
|
+
if (!subtask) {
|
|
984
|
+
error(`Subtask not found: ${subtaskId}`);
|
|
985
|
+
process.exit(1);
|
|
986
|
+
}
|
|
987
|
+
if (!subtask.parentTaskId) {
|
|
988
|
+
error(`${subtaskId} is not a subtask. Use 'trekker task delete' instead.`);
|
|
989
|
+
process.exit(1);
|
|
990
|
+
}
|
|
991
|
+
deleteTask(subtaskId);
|
|
992
|
+
success(`Subtask deleted: ${subtaskId}`);
|
|
993
|
+
} catch (err) {
|
|
994
|
+
error(err instanceof Error ? err.message : String(err));
|
|
995
|
+
process.exit(1);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
// src/commands/comment.ts
|
|
1000
|
+
import { Command as Command6 } from "commander";
|
|
1001
|
+
|
|
1002
|
+
// src/services/comment.ts
|
|
1003
|
+
import { eq as eq4 } from "drizzle-orm";
|
|
1004
|
+
function createComment(input) {
|
|
1005
|
+
const db = getDb();
|
|
1006
|
+
const task = db.select().from(tasks).where(eq4(tasks.id, input.taskId)).get();
|
|
1007
|
+
if (!task) {
|
|
1008
|
+
throw new Error(`Task not found: ${input.taskId}`);
|
|
1009
|
+
}
|
|
1010
|
+
const id = generateId("comment");
|
|
1011
|
+
const now = new Date;
|
|
1012
|
+
const comment = {
|
|
1013
|
+
id,
|
|
1014
|
+
taskId: input.taskId,
|
|
1015
|
+
author: input.author,
|
|
1016
|
+
content: input.content,
|
|
1017
|
+
createdAt: now,
|
|
1018
|
+
updatedAt: now
|
|
1019
|
+
};
|
|
1020
|
+
db.insert(comments).values(comment).run();
|
|
1021
|
+
return comment;
|
|
1022
|
+
}
|
|
1023
|
+
function getComment(id) {
|
|
1024
|
+
const db = getDb();
|
|
1025
|
+
const result = db.select().from(comments).where(eq4(comments.id, id)).get();
|
|
1026
|
+
return result;
|
|
1027
|
+
}
|
|
1028
|
+
function listComments(taskId) {
|
|
1029
|
+
const db = getDb();
|
|
1030
|
+
const task = db.select().from(tasks).where(eq4(tasks.id, taskId)).get();
|
|
1031
|
+
if (!task) {
|
|
1032
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1033
|
+
}
|
|
1034
|
+
return db.select().from(comments).where(eq4(comments.taskId, taskId)).all();
|
|
1035
|
+
}
|
|
1036
|
+
function updateComment(id, input) {
|
|
1037
|
+
const db = getDb();
|
|
1038
|
+
const existing = getComment(id);
|
|
1039
|
+
if (!existing) {
|
|
1040
|
+
throw new Error(`Comment not found: ${id}`);
|
|
1041
|
+
}
|
|
1042
|
+
db.update(comments).set({
|
|
1043
|
+
content: input.content,
|
|
1044
|
+
updatedAt: new Date
|
|
1045
|
+
}).where(eq4(comments.id, id)).run();
|
|
1046
|
+
return getComment(id);
|
|
1047
|
+
}
|
|
1048
|
+
function deleteComment(id) {
|
|
1049
|
+
const db = getDb();
|
|
1050
|
+
const existing = getComment(id);
|
|
1051
|
+
if (!existing) {
|
|
1052
|
+
throw new Error(`Comment not found: ${id}`);
|
|
1053
|
+
}
|
|
1054
|
+
db.delete(comments).where(eq4(comments.id, id)).run();
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// src/commands/comment.ts
|
|
1058
|
+
var commentCommand = new Command6("comment").description("Manage comments");
|
|
1059
|
+
commentCommand.command("add <task-id>").description("Add a comment to a task").requiredOption("-a, --author <author>", "Comment author").requiredOption("-c, --content <content>", "Comment content").action((taskId, options) => {
|
|
1060
|
+
try {
|
|
1061
|
+
validateRequired(options.author, "Author");
|
|
1062
|
+
validateRequired(options.content, "Content");
|
|
1063
|
+
const comment = createComment({
|
|
1064
|
+
taskId,
|
|
1065
|
+
author: options.author,
|
|
1066
|
+
content: options.content
|
|
1067
|
+
});
|
|
1068
|
+
if (isJsonMode()) {
|
|
1069
|
+
output(comment);
|
|
1070
|
+
} else {
|
|
1071
|
+
success(`Comment added: ${comment.id}`);
|
|
1072
|
+
console.log(formatComment(comment));
|
|
1073
|
+
}
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1076
|
+
process.exit(1);
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
commentCommand.command("list <task-id>").description("List all comments on a task").action((taskId) => {
|
|
1080
|
+
try {
|
|
1081
|
+
const comments2 = listComments(taskId);
|
|
1082
|
+
if (isJsonMode()) {
|
|
1083
|
+
output(comments2);
|
|
1084
|
+
} else {
|
|
1085
|
+
if (comments2.length === 0) {
|
|
1086
|
+
console.log(`No comments on ${taskId}`);
|
|
1087
|
+
} else {
|
|
1088
|
+
console.log(`Comments on ${taskId}:`);
|
|
1089
|
+
console.log(formatCommentList(comments2));
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1094
|
+
process.exit(1);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
commentCommand.command("update <comment-id>").description("Update a comment").requiredOption("-c, --content <content>", "New comment content").action((commentId, options) => {
|
|
1098
|
+
try {
|
|
1099
|
+
validateRequired(options.content, "Content");
|
|
1100
|
+
const comment = updateComment(commentId, {
|
|
1101
|
+
content: options.content
|
|
1102
|
+
});
|
|
1103
|
+
if (isJsonMode()) {
|
|
1104
|
+
output(comment);
|
|
1105
|
+
} else {
|
|
1106
|
+
success(`Comment updated: ${comment.id}`);
|
|
1107
|
+
console.log(formatComment(comment));
|
|
1108
|
+
}
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1111
|
+
process.exit(1);
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
commentCommand.command("delete <comment-id>").description("Delete a comment").action((commentId) => {
|
|
1115
|
+
try {
|
|
1116
|
+
deleteComment(commentId);
|
|
1117
|
+
success(`Comment deleted: ${commentId}`);
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1120
|
+
process.exit(1);
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
// src/commands/dep.ts
|
|
1125
|
+
import { Command as Command7 } from "commander";
|
|
1126
|
+
|
|
1127
|
+
// src/services/dependency.ts
|
|
1128
|
+
import { eq as eq5 } from "drizzle-orm";
|
|
1129
|
+
function addDependency(taskId, dependsOnId) {
|
|
1130
|
+
const db = getDb();
|
|
1131
|
+
const task = db.select().from(tasks).where(eq5(tasks.id, taskId)).get();
|
|
1132
|
+
if (!task) {
|
|
1133
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1134
|
+
}
|
|
1135
|
+
const dependsOnTask = db.select().from(tasks).where(eq5(tasks.id, dependsOnId)).get();
|
|
1136
|
+
if (!dependsOnTask) {
|
|
1137
|
+
throw new Error(`Task not found: ${dependsOnId}`);
|
|
1138
|
+
}
|
|
1139
|
+
if (taskId === dependsOnId) {
|
|
1140
|
+
throw new Error("A task cannot depend on itself.");
|
|
1141
|
+
}
|
|
1142
|
+
const existing = db.select().from(dependencies).where(eq5(dependencies.taskId, taskId)).all().find((d) => d.dependsOnId === dependsOnId);
|
|
1143
|
+
if (existing) {
|
|
1144
|
+
throw new Error(`Dependency already exists: ${taskId} \u2192 ${dependsOnId}`);
|
|
1145
|
+
}
|
|
1146
|
+
if (wouldCreateCycle(taskId, dependsOnId)) {
|
|
1147
|
+
throw new Error(`Adding this dependency would create a cycle. ${dependsOnId} already depends on ${taskId} (directly or transitively).`);
|
|
1148
|
+
}
|
|
1149
|
+
const id = generateUuid();
|
|
1150
|
+
const now = new Date;
|
|
1151
|
+
const dependency = {
|
|
1152
|
+
id,
|
|
1153
|
+
taskId,
|
|
1154
|
+
dependsOnId,
|
|
1155
|
+
createdAt: now
|
|
1156
|
+
};
|
|
1157
|
+
db.insert(dependencies).values(dependency).run();
|
|
1158
|
+
return dependency;
|
|
1159
|
+
}
|
|
1160
|
+
function removeDependency(taskId, dependsOnId) {
|
|
1161
|
+
const db = getDb();
|
|
1162
|
+
const existing = db.select().from(dependencies).where(eq5(dependencies.taskId, taskId)).all().find((d) => d.dependsOnId === dependsOnId);
|
|
1163
|
+
if (!existing) {
|
|
1164
|
+
throw new Error(`Dependency not found: ${taskId} \u2192 ${dependsOnId}`);
|
|
1165
|
+
}
|
|
1166
|
+
db.delete(dependencies).where(eq5(dependencies.id, existing.id)).run();
|
|
1167
|
+
}
|
|
1168
|
+
function getDependencies(taskId) {
|
|
1169
|
+
const db = getDb();
|
|
1170
|
+
const dependsOn = db.select({
|
|
1171
|
+
taskId: dependencies.taskId,
|
|
1172
|
+
dependsOnId: dependencies.dependsOnId
|
|
1173
|
+
}).from(dependencies).where(eq5(dependencies.taskId, taskId)).all();
|
|
1174
|
+
const blocks = db.select({
|
|
1175
|
+
taskId: dependencies.taskId,
|
|
1176
|
+
dependsOnId: dependencies.dependsOnId
|
|
1177
|
+
}).from(dependencies).where(eq5(dependencies.dependsOnId, taskId)).all();
|
|
1178
|
+
return { dependsOn, blocks };
|
|
1179
|
+
}
|
|
1180
|
+
function wouldCreateCycle(taskId, dependsOnId) {
|
|
1181
|
+
const db = getDb();
|
|
1182
|
+
const visited = new Set;
|
|
1183
|
+
const stack = [dependsOnId];
|
|
1184
|
+
while (stack.length > 0) {
|
|
1185
|
+
const current = stack.pop();
|
|
1186
|
+
if (current === taskId) {
|
|
1187
|
+
return true;
|
|
1188
|
+
}
|
|
1189
|
+
if (visited.has(current)) {
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
visited.add(current);
|
|
1193
|
+
const deps = db.select({ dependsOnId: dependencies.dependsOnId }).from(dependencies).where(eq5(dependencies.taskId, current)).all();
|
|
1194
|
+
for (const dep of deps) {
|
|
1195
|
+
if (!visited.has(dep.dependsOnId)) {
|
|
1196
|
+
stack.push(dep.dependsOnId);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/commands/dep.ts
|
|
1204
|
+
var depCommand = new Command7("dep").description("Manage task dependencies");
|
|
1205
|
+
depCommand.command("add <task-id> <depends-on-id>").description("Add a dependency (task-id depends on depends-on-id)").action((taskId, dependsOnId) => {
|
|
1206
|
+
try {
|
|
1207
|
+
const dependency = addDependency(taskId, dependsOnId);
|
|
1208
|
+
if (isJsonMode()) {
|
|
1209
|
+
output(dependency);
|
|
1210
|
+
} else {
|
|
1211
|
+
success(`Dependency added: ${taskId} \u2192 depends on ${dependsOnId}`);
|
|
1212
|
+
}
|
|
1213
|
+
} catch (err) {
|
|
1214
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1215
|
+
process.exit(1);
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
depCommand.command("remove <task-id> <depends-on-id>").description("Remove a dependency").action((taskId, dependsOnId) => {
|
|
1219
|
+
try {
|
|
1220
|
+
removeDependency(taskId, dependsOnId);
|
|
1221
|
+
success(`Dependency removed: ${taskId} \u2192 ${dependsOnId}`);
|
|
1222
|
+
} catch (err) {
|
|
1223
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1224
|
+
process.exit(1);
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
depCommand.command("list <task-id>").description("List dependencies for a task").action((taskId) => {
|
|
1228
|
+
try {
|
|
1229
|
+
const { dependsOn, blocks } = getDependencies(taskId);
|
|
1230
|
+
if (isJsonMode()) {
|
|
1231
|
+
output({ taskId, dependsOn, blocks });
|
|
1232
|
+
} else {
|
|
1233
|
+
console.log(`Dependencies for ${taskId}:`);
|
|
1234
|
+
console.log(`
|
|
1235
|
+
Depends on:`);
|
|
1236
|
+
console.log(formatDependencyList(dependsOn, "depends_on"));
|
|
1237
|
+
console.log(`
|
|
1238
|
+
Blocks:`);
|
|
1239
|
+
console.log(formatDependencyList(blocks, "blocks"));
|
|
1240
|
+
}
|
|
1241
|
+
} catch (err) {
|
|
1242
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1243
|
+
process.exit(1);
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
// src/commands/quickstart.ts
|
|
1248
|
+
import { Command as Command8 } from "commander";
|
|
1249
|
+
var QUICKSTART_TEXT = `# Trekker - AI Agent Quickstart Guide
|
|
1250
|
+
|
|
1251
|
+
Trekker is a CLI-based issue tracker designed for coding agents. It stores all data in a local SQLite database at \`.trekker/trekker.db\` in your project root.
|
|
1252
|
+
|
|
1253
|
+
## Why Use Trekker?
|
|
1254
|
+
|
|
1255
|
+
As an AI agent, you should use Trekker to:
|
|
1256
|
+
- **Track your work** across multiple sessions and context windows
|
|
1257
|
+
- **Break down complex tasks** into manageable, atomic pieces
|
|
1258
|
+
- **Document decisions** and progress for future reference
|
|
1259
|
+
- **Manage dependencies** between tasks to ensure correct execution order
|
|
1260
|
+
- **Offload context** - store your reasoning, findings, and state externally so you can recover after context resets
|
|
1261
|
+
- **Persist memory** - comments survive context window limits and session boundaries
|
|
1262
|
+
|
|
1263
|
+
## Best Practices for AI Agents
|
|
1264
|
+
|
|
1265
|
+
### Creating Epics
|
|
1266
|
+
- Create one epic per **feature or major goal**
|
|
1267
|
+
- Use clear, descriptive titles that explain the outcome (e.g., "User Authentication System" not "Auth")
|
|
1268
|
+
- Write descriptions that capture the **why** and **scope** of the work
|
|
1269
|
+
- Example: \`trekker epic create -t "REST API for User Management" -d "Build CRUD endpoints for users with JWT auth, input validation, and rate limiting"\`
|
|
1270
|
+
|
|
1271
|
+
### Creating Atomic Tasks
|
|
1272
|
+
- Each task should be **completable in one focused session**
|
|
1273
|
+
- Tasks should have a **single, clear objective**
|
|
1274
|
+
- If a task feels too big, break it into subtasks
|
|
1275
|
+
- Use action verbs: "Implement", "Add", "Fix", "Refactor", "Update"
|
|
1276
|
+
- Bad: \`"Work on authentication"\`
|
|
1277
|
+
- Good: \`"Implement JWT token generation endpoint"\`
|
|
1278
|
+
|
|
1279
|
+
### Writing Good Descriptions
|
|
1280
|
+
- Explain **what** needs to be done and **how** to verify it's complete
|
|
1281
|
+
- Include relevant technical details (endpoints, file paths, function names)
|
|
1282
|
+
- Mention acceptance criteria when applicable
|
|
1283
|
+
- Example: \`-d "Create POST /api/auth/login that accepts {email, password}, validates credentials against DB, returns JWT token. Should return 401 for invalid credentials."\`
|
|
1284
|
+
|
|
1285
|
+
### Using Tags Effectively
|
|
1286
|
+
- Use tags to categorize work: \`--tags "api,auth,security"\`
|
|
1287
|
+
- Common tag categories: component (\`api\`, \`ui\`, \`db\`), type (\`bug\`, \`feature\`, \`refactor\`), area (\`auth\`, \`payments\`)
|
|
1288
|
+
|
|
1289
|
+
### Managing Dependencies
|
|
1290
|
+
- Always define dependencies when task order matters
|
|
1291
|
+
- A task should not start until its dependencies are complete
|
|
1292
|
+
- Use \`trekker dep list <task-id>\` to check what's blocking a task
|
|
1293
|
+
|
|
1294
|
+
### Adding Comments (Critical for Context Management)
|
|
1295
|
+
|
|
1296
|
+
Comments are your **external memory**. Use them extensively to:
|
|
1297
|
+
|
|
1298
|
+
**Save Your Thought Process:**
|
|
1299
|
+
- Document your reasoning and analysis as you work
|
|
1300
|
+
- Record hypotheses before investigating them
|
|
1301
|
+
- Note alternatives you considered and why you chose/rejected them
|
|
1302
|
+
|
|
1303
|
+
**Offload Context:**
|
|
1304
|
+
- When your context window is filling up, dump your current state into comments
|
|
1305
|
+
- Record what you've tried, what worked, what didn't
|
|
1306
|
+
- Save partial progress so you can resume after a context reset
|
|
1307
|
+
|
|
1308
|
+
**Preserve Investigation Results:**
|
|
1309
|
+
- Store findings from code exploration
|
|
1310
|
+
- Document file locations and relevant code snippets
|
|
1311
|
+
- Record error messages and stack traces you're debugging
|
|
1312
|
+
|
|
1313
|
+
**Track Decision History:**
|
|
1314
|
+
- Log architectural decisions with rationale
|
|
1315
|
+
- Document trade-offs you evaluated
|
|
1316
|
+
- Record blockers and how you resolved them
|
|
1317
|
+
|
|
1318
|
+
**Examples:**
|
|
1319
|
+
\`\`\`bash
|
|
1320
|
+
# Starting a task - record your initial analysis
|
|
1321
|
+
trekker comment add TREK-1 -a "agent" -c "Initial analysis: Need to modify auth.ts (line 45-80) and add new endpoint in routes/api.ts. Dependencies: bcrypt, jsonwebtoken already installed."
|
|
1322
|
+
|
|
1323
|
+
# During investigation - save what you found
|
|
1324
|
+
trekker comment add TREK-1 -a "agent" -c "Found existing validation in utils/validate.ts:23. Can reuse validateEmail() and validatePassword(). Token generation should follow pattern in auth/jwt.ts."
|
|
1325
|
+
|
|
1326
|
+
# Recording a decision
|
|
1327
|
+
trekker comment add TREK-1 -a "agent" -c "Decision: Using bcrypt over argon2 for password hashing. Rationale: better library support, existing team familiarity, sufficient security for this use case."
|
|
1328
|
+
|
|
1329
|
+
# Saving progress before context reset
|
|
1330
|
+
trekker comment add TREK-1 -a "agent" -c "Progress checkpoint: Implemented login endpoint (auth.ts:45-120). TODO: Add rate limiting, write tests. Blocked by: Need to clarify password reset flow with user."
|
|
1331
|
+
|
|
1332
|
+
# After hitting an issue
|
|
1333
|
+
trekker comment add TREK-1 -a "agent" -c "Issue: JWT verification failing. Tried: 1) Checked secret key - correct, 2) Verified token format - valid, 3) Found issue - clock skew on server. Solution: Added 30s leeway to verification."
|
|
1334
|
+
\`\`\`
|
|
1335
|
+
|
|
1336
|
+
---
|
|
1337
|
+
|
|
1338
|
+
## Initialization
|
|
1339
|
+
|
|
1340
|
+
Before using Trekker, initialize it in your project directory:
|
|
1341
|
+
|
|
1342
|
+
\`\`\`bash
|
|
1343
|
+
trekker init # Creates .trekker/ directory with database
|
|
1344
|
+
\`\`\`
|
|
1345
|
+
|
|
1346
|
+
To remove all Trekker data:
|
|
1347
|
+
|
|
1348
|
+
\`\`\`bash
|
|
1349
|
+
trekker wipe # Prompts for confirmation
|
|
1350
|
+
trekker wipe -y # Skip confirmation
|
|
1351
|
+
\`\`\`
|
|
1352
|
+
|
|
1353
|
+
## Global Options
|
|
1354
|
+
|
|
1355
|
+
| Option | Description |
|
|
1356
|
+
|-----------|--------------------------------------------|
|
|
1357
|
+
| \`--json\` | Output in JSON format (recommended for agents) |
|
|
1358
|
+
| \`--help\` | Show help for any command |
|
|
1359
|
+
|
|
1360
|
+
**Example:**
|
|
1361
|
+
\`\`\`bash
|
|
1362
|
+
trekker --json task list # Returns tasks as JSON array
|
|
1363
|
+
\`\`\`
|
|
1364
|
+
|
|
1365
|
+
---
|
|
1366
|
+
|
|
1367
|
+
## Epics
|
|
1368
|
+
|
|
1369
|
+
> High-level features or milestones
|
|
1370
|
+
|
|
1371
|
+
### Create Epic
|
|
1372
|
+
|
|
1373
|
+
\`\`\`bash
|
|
1374
|
+
trekker epic create -t <title> [-d <description>] [-p <0-5>] [-s <status>]
|
|
1375
|
+
\`\`\`
|
|
1376
|
+
|
|
1377
|
+
| Option | Required | Description |
|
|
1378
|
+
|--------|----------|-------------|
|
|
1379
|
+
| \`-t, --title\` | Yes | Epic title |
|
|
1380
|
+
| \`-d, --description\` | No | Detailed description |
|
|
1381
|
+
| \`-p, --priority\` | No | 0=critical, 1=high, 2=medium (default), 3=low, 4=backlog, 5=someday |
|
|
1382
|
+
| \`-s, --status\` | No | todo (default), in_progress, completed, archived |
|
|
1383
|
+
|
|
1384
|
+
**Example:**
|
|
1385
|
+
\`\`\`bash
|
|
1386
|
+
trekker epic create -t "User Authentication" -d "Implement OAuth2 login" -p 1
|
|
1387
|
+
\`\`\`
|
|
1388
|
+
|
|
1389
|
+
### List Epics
|
|
1390
|
+
|
|
1391
|
+
\`\`\`bash
|
|
1392
|
+
trekker epic list [--status <status>]
|
|
1393
|
+
\`\`\`
|
|
1394
|
+
|
|
1395
|
+
### Show Epic
|
|
1396
|
+
|
|
1397
|
+
\`\`\`bash
|
|
1398
|
+
trekker epic show <epic-id>
|
|
1399
|
+
\`\`\`
|
|
1400
|
+
|
|
1401
|
+
### Update Epic
|
|
1402
|
+
|
|
1403
|
+
\`\`\`bash
|
|
1404
|
+
trekker epic update <epic-id> [-t <title>] [-d <desc>] [-p <priority>] [-s <status>]
|
|
1405
|
+
\`\`\`
|
|
1406
|
+
|
|
1407
|
+
### Delete Epic
|
|
1408
|
+
|
|
1409
|
+
\`\`\`bash
|
|
1410
|
+
trekker epic delete <epic-id>
|
|
1411
|
+
\`\`\`
|
|
1412
|
+
|
|
1413
|
+
---
|
|
1414
|
+
|
|
1415
|
+
## Tasks
|
|
1416
|
+
|
|
1417
|
+
> Work items that can be assigned to epics
|
|
1418
|
+
|
|
1419
|
+
### Create Task
|
|
1420
|
+
|
|
1421
|
+
\`\`\`bash
|
|
1422
|
+
trekker task create -t <title> [-d <description>] [-p <0-5>] [-s <status>] [--tags <tags>] [-e <epic-id>]
|
|
1423
|
+
\`\`\`
|
|
1424
|
+
|
|
1425
|
+
| Option | Required | Description |
|
|
1426
|
+
|--------|----------|-------------|
|
|
1427
|
+
| \`-t, --title\` | Yes | Task title |
|
|
1428
|
+
| \`-d, --description\` | No | Detailed description |
|
|
1429
|
+
| \`-p, --priority\` | No | 0-5, default: 2 |
|
|
1430
|
+
| \`-s, --status\` | No | todo (default), in_progress, completed, wont_fix, archived |
|
|
1431
|
+
| \`--tags\` | No | Comma-separated tags, e.g., "api,auth,backend" |
|
|
1432
|
+
| \`-e, --epic\` | No | Epic ID to assign task to |
|
|
1433
|
+
|
|
1434
|
+
**Example:**
|
|
1435
|
+
\`\`\`bash
|
|
1436
|
+
trekker task create -t "Implement login API" -d "POST /api/login endpoint" -e EPIC-1 --tags "api,auth"
|
|
1437
|
+
\`\`\`
|
|
1438
|
+
|
|
1439
|
+
### List Tasks
|
|
1440
|
+
|
|
1441
|
+
\`\`\`bash
|
|
1442
|
+
trekker task list [--status <status>] [--epic <epic-id>]
|
|
1443
|
+
\`\`\`
|
|
1444
|
+
|
|
1445
|
+
**Examples:**
|
|
1446
|
+
\`\`\`bash
|
|
1447
|
+
trekker task list # All top-level tasks
|
|
1448
|
+
trekker task list --status todo # Filter by status
|
|
1449
|
+
trekker task list --epic EPIC-1 # Filter by epic
|
|
1450
|
+
\`\`\`
|
|
1451
|
+
|
|
1452
|
+
### Show Task
|
|
1453
|
+
|
|
1454
|
+
\`\`\`bash
|
|
1455
|
+
trekker task show <task-id>
|
|
1456
|
+
trekker --json task show TREK-1 # Get full task details as JSON
|
|
1457
|
+
\`\`\`
|
|
1458
|
+
|
|
1459
|
+
### Update Task
|
|
1460
|
+
|
|
1461
|
+
\`\`\`bash
|
|
1462
|
+
trekker task update <task-id> [-t <title>] [-d <desc>] [-p <priority>] [-s <status>] [--tags <tags>] [-e <epic-id>] [--no-epic]
|
|
1463
|
+
\`\`\`
|
|
1464
|
+
|
|
1465
|
+
**Examples:**
|
|
1466
|
+
\`\`\`bash
|
|
1467
|
+
trekker task update TREK-1 -s in_progress
|
|
1468
|
+
trekker task update TREK-1 --no-epic # Remove from epic
|
|
1469
|
+
\`\`\`
|
|
1470
|
+
|
|
1471
|
+
### Delete Task
|
|
1472
|
+
|
|
1473
|
+
\`\`\`bash
|
|
1474
|
+
trekker task delete <task-id>
|
|
1475
|
+
\`\`\`
|
|
1476
|
+
|
|
1477
|
+
---
|
|
1478
|
+
|
|
1479
|
+
## Subtasks
|
|
1480
|
+
|
|
1481
|
+
> Child tasks that belong to a parent task. They inherit the epic from their parent.
|
|
1482
|
+
|
|
1483
|
+
### Create Subtask
|
|
1484
|
+
|
|
1485
|
+
\`\`\`bash
|
|
1486
|
+
trekker subtask create <parent-task-id> -t <title> [-d <description>] [-p <0-5>] [-s <status>]
|
|
1487
|
+
\`\`\`
|
|
1488
|
+
|
|
1489
|
+
**Example:**
|
|
1490
|
+
\`\`\`bash
|
|
1491
|
+
trekker subtask create TREK-1 -t "Add input validation"
|
|
1492
|
+
\`\`\`
|
|
1493
|
+
|
|
1494
|
+
### List Subtasks
|
|
1495
|
+
|
|
1496
|
+
\`\`\`bash
|
|
1497
|
+
trekker subtask list <parent-task-id>
|
|
1498
|
+
\`\`\`
|
|
1499
|
+
|
|
1500
|
+
### Update Subtask
|
|
1501
|
+
|
|
1502
|
+
\`\`\`bash
|
|
1503
|
+
trekker subtask update <subtask-id> [-t <title>] [-d <desc>] [-p <priority>] [-s <status>]
|
|
1504
|
+
\`\`\`
|
|
1505
|
+
|
|
1506
|
+
### Delete Subtask
|
|
1507
|
+
|
|
1508
|
+
\`\`\`bash
|
|
1509
|
+
trekker subtask delete <subtask-id>
|
|
1510
|
+
\`\`\`
|
|
1511
|
+
|
|
1512
|
+
---
|
|
1513
|
+
|
|
1514
|
+
## Comments
|
|
1515
|
+
|
|
1516
|
+
> Your external memory - use extensively to preserve context and reasoning
|
|
1517
|
+
|
|
1518
|
+
Comments are **critical for AI agents**. They persist beyond your context window and session boundaries. Use them to:
|
|
1519
|
+
- Store your analysis and reasoning
|
|
1520
|
+
- Save investigation results
|
|
1521
|
+
- Record decisions with rationale
|
|
1522
|
+
- Checkpoint progress before context resets
|
|
1523
|
+
- Document blockers and solutions
|
|
1524
|
+
|
|
1525
|
+
### Add Comment
|
|
1526
|
+
|
|
1527
|
+
\`\`\`bash
|
|
1528
|
+
trekker comment add <task-id> -a <author> -c <content>
|
|
1529
|
+
\`\`\`
|
|
1530
|
+
|
|
1531
|
+
| Option | Required | Description |
|
|
1532
|
+
|--------|----------|-------------|
|
|
1533
|
+
| \`-a, --author\` | Yes | Comment author name (use "agent" for AI) |
|
|
1534
|
+
| \`-c, --content\` | Yes | Comment text (can be multi-line) |
|
|
1535
|
+
|
|
1536
|
+
**Examples:**
|
|
1537
|
+
\`\`\`bash
|
|
1538
|
+
# Record analysis
|
|
1539
|
+
trekker comment add TREK-1 -a "agent" -c "Analyzed codebase: auth logic in src/auth/, uses JWT stored in httpOnly cookies"
|
|
1540
|
+
|
|
1541
|
+
# Save progress checkpoint
|
|
1542
|
+
trekker comment add TREK-1 -a "agent" -c "Checkpoint: Completed steps 1-3. Next: implement validation. Files modified: auth.ts, routes.ts"
|
|
1543
|
+
|
|
1544
|
+
# Document a blocker
|
|
1545
|
+
trekker comment add TREK-1 -a "agent" -c "BLOCKED: Need clarification on password requirements. Asked user, waiting for response."
|
|
1546
|
+
\`\`\`
|
|
1547
|
+
|
|
1548
|
+
### List Comments
|
|
1549
|
+
|
|
1550
|
+
\`\`\`bash
|
|
1551
|
+
trekker comment list <task-id>
|
|
1552
|
+
trekker --json comment list <task-id> # Get as JSON for parsing
|
|
1553
|
+
\`\`\`
|
|
1554
|
+
|
|
1555
|
+
**Pro tip:** Always read comments when resuming work on a task - they contain your previous context!
|
|
1556
|
+
|
|
1557
|
+
### Update Comment
|
|
1558
|
+
|
|
1559
|
+
\`\`\`bash
|
|
1560
|
+
trekker comment update <comment-id> -c <new-content>
|
|
1561
|
+
\`\`\`
|
|
1562
|
+
|
|
1563
|
+
### Delete Comment
|
|
1564
|
+
|
|
1565
|
+
\`\`\`bash
|
|
1566
|
+
trekker comment delete <comment-id>
|
|
1567
|
+
\`\`\`
|
|
1568
|
+
|
|
1569
|
+
---
|
|
1570
|
+
|
|
1571
|
+
## Dependencies
|
|
1572
|
+
|
|
1573
|
+
> Task relationships - which tasks must be completed before others can start.
|
|
1574
|
+
> Trekker automatically detects and prevents circular dependencies.
|
|
1575
|
+
|
|
1576
|
+
### Add Dependency
|
|
1577
|
+
|
|
1578
|
+
\`\`\`bash
|
|
1579
|
+
trekker dep add <task-id> <depends-on-id>
|
|
1580
|
+
\`\`\`
|
|
1581
|
+
|
|
1582
|
+
This means:
|
|
1583
|
+
- \`<task-id>\` depends on \`<depends-on-id>\`
|
|
1584
|
+
- \`<task-id>\` cannot start until \`<depends-on-id>\` is done
|
|
1585
|
+
|
|
1586
|
+
**Example:**
|
|
1587
|
+
\`\`\`bash
|
|
1588
|
+
trekker dep add TREK-2 TREK-1 # TREK-2 depends on TREK-1
|
|
1589
|
+
\`\`\`
|
|
1590
|
+
|
|
1591
|
+
### Remove Dependency
|
|
1592
|
+
|
|
1593
|
+
\`\`\`bash
|
|
1594
|
+
trekker dep remove <task-id> <depends-on-id>
|
|
1595
|
+
\`\`\`
|
|
1596
|
+
|
|
1597
|
+
### List Dependencies
|
|
1598
|
+
|
|
1599
|
+
\`\`\`bash
|
|
1600
|
+
trekker dep list <task-id>
|
|
1601
|
+
\`\`\`
|
|
1602
|
+
|
|
1603
|
+
Shows both:
|
|
1604
|
+
- What this task depends on (blockers)
|
|
1605
|
+
- What tasks this task blocks
|
|
1606
|
+
|
|
1607
|
+
---
|
|
1608
|
+
|
|
1609
|
+
## Web Interface
|
|
1610
|
+
|
|
1611
|
+
Trekker includes a web interface for visual task management.
|
|
1612
|
+
|
|
1613
|
+
### Start the Web Interface
|
|
1614
|
+
|
|
1615
|
+
\`\`\`bash
|
|
1616
|
+
trekker serve # Start on port 3000
|
|
1617
|
+
trekker serve -p 8080 # Start on custom port
|
|
1618
|
+
\`\`\`
|
|
1619
|
+
|
|
1620
|
+
The web interface provides:
|
|
1621
|
+
- Kanban board with tasks grouped by status (TODO, In Progress, Completed)
|
|
1622
|
+
- Epic filter to focus on specific features
|
|
1623
|
+
- Task details including dependencies, subtasks, and tags
|
|
1624
|
+
- Auto-refresh every 5 seconds to reflect CLI changes
|
|
1625
|
+
|
|
1626
|
+
---
|
|
1627
|
+
|
|
1628
|
+
## Reference
|
|
1629
|
+
|
|
1630
|
+
### ID Formats
|
|
1631
|
+
|
|
1632
|
+
| Type | Format | Example |
|
|
1633
|
+
|------|--------|---------|
|
|
1634
|
+
| Epic | \`EPIC-n\` | EPIC-1, EPIC-2 |
|
|
1635
|
+
| Task/Subtask | \`TREK-n\` | TREK-1, TREK-42 |
|
|
1636
|
+
| Comment | \`CMT-n\` | CMT-1, CMT-5 |
|
|
1637
|
+
|
|
1638
|
+
IDs are auto-generated and sequential within each type.
|
|
1639
|
+
|
|
1640
|
+
### Task Status Values
|
|
1641
|
+
|
|
1642
|
+
| Status | Description |
|
|
1643
|
+
|--------|-------------|
|
|
1644
|
+
| \`todo\` | Not started (default) |
|
|
1645
|
+
| \`in_progress\` | Currently being worked on |
|
|
1646
|
+
| \`completed\` | Finished successfully |
|
|
1647
|
+
| \`wont_fix\` | Decided not to implement |
|
|
1648
|
+
| \`archived\` | No longer relevant |
|
|
1649
|
+
|
|
1650
|
+
### Epic Status Values
|
|
1651
|
+
|
|
1652
|
+
| Status | Description |
|
|
1653
|
+
|--------|-------------|
|
|
1654
|
+
| \`todo\` | Not started (default) |
|
|
1655
|
+
| \`in_progress\` | Work has begun |
|
|
1656
|
+
| \`completed\` | All tasks done |
|
|
1657
|
+
| \`archived\` | No longer relevant |
|
|
1658
|
+
|
|
1659
|
+
### Priority Scale
|
|
1660
|
+
|
|
1661
|
+
| Value | Meaning |
|
|
1662
|
+
|-------|---------|
|
|
1663
|
+
| 0 | Critical (drop everything) |
|
|
1664
|
+
| 1 | High (do soon) |
|
|
1665
|
+
| 2 | Medium (default) |
|
|
1666
|
+
| 3 | Low (when time permits) |
|
|
1667
|
+
| 4 | Backlog (future consideration) |
|
|
1668
|
+
| 5 | Someday (nice to have) |
|
|
1669
|
+
|
|
1670
|
+
---
|
|
1671
|
+
|
|
1672
|
+
## Recommended Workflow for AI Agents
|
|
1673
|
+
|
|
1674
|
+
1. **Initialize** (once per project):
|
|
1675
|
+
\`\`\`bash
|
|
1676
|
+
trekker init
|
|
1677
|
+
\`\`\`
|
|
1678
|
+
|
|
1679
|
+
2. **Check existing state** (every session start):
|
|
1680
|
+
\`\`\`bash
|
|
1681
|
+
trekker --json task list --status in_progress # See what's active
|
|
1682
|
+
trekker --json comment list TREK-1 # Read your previous context
|
|
1683
|
+
\`\`\`
|
|
1684
|
+
|
|
1685
|
+
3. **Create epic** for the feature you're working on:
|
|
1686
|
+
\`\`\`bash
|
|
1687
|
+
trekker epic create -t "Feature Name" -d "Description"
|
|
1688
|
+
\`\`\`
|
|
1689
|
+
|
|
1690
|
+
4. **Create tasks** for each piece of work:
|
|
1691
|
+
\`\`\`bash
|
|
1692
|
+
trekker task create -t "Task name" -e EPIC-1
|
|
1693
|
+
\`\`\`
|
|
1694
|
+
|
|
1695
|
+
5. **Add dependencies** if tasks have ordering requirements:
|
|
1696
|
+
\`\`\`bash
|
|
1697
|
+
trekker dep add TREK-2 TREK-1
|
|
1698
|
+
\`\`\`
|
|
1699
|
+
|
|
1700
|
+
6. **Document your initial analysis** before starting work:
|
|
1701
|
+
\`\`\`bash
|
|
1702
|
+
trekker comment add TREK-1 -a "agent" -c "Analysis: Need to modify X, Y, Z. Approach: ..."
|
|
1703
|
+
\`\`\`
|
|
1704
|
+
|
|
1705
|
+
7. **Update status** as you work:
|
|
1706
|
+
\`\`\`bash
|
|
1707
|
+
trekker task update TREK-1 -s in_progress
|
|
1708
|
+
\`\`\`
|
|
1709
|
+
|
|
1710
|
+
8. **Add comments frequently** - this is your external memory:
|
|
1711
|
+
\`\`\`bash
|
|
1712
|
+
# After investigating
|
|
1713
|
+
trekker comment add TREK-1 -a "agent" -c "Found: auth logic in src/auth.ts:45-80"
|
|
1714
|
+
|
|
1715
|
+
# After making a decision
|
|
1716
|
+
trekker comment add TREK-1 -a "agent" -c "Decision: Using approach X because Y"
|
|
1717
|
+
|
|
1718
|
+
# Before context might reset
|
|
1719
|
+
trekker comment add TREK-1 -a "agent" -c "Checkpoint: Completed A, B. Next: C, D"
|
|
1720
|
+
\`\`\`
|
|
1721
|
+
|
|
1722
|
+
9. **Mark complete** when done:
|
|
1723
|
+
\`\`\`bash
|
|
1724
|
+
trekker task update TREK-1 -s completed
|
|
1725
|
+
trekker comment add TREK-1 -a "agent" -c "Completed. Summary: Implemented X in files A, B, C."
|
|
1726
|
+
\`\`\`
|
|
1727
|
+
|
|
1728
|
+
10. **Use JSON output** for parsing:
|
|
1729
|
+
\`\`\`bash
|
|
1730
|
+
trekker --json task list
|
|
1731
|
+
trekker --json task show TREK-1
|
|
1732
|
+
\`\`\`
|
|
1733
|
+
|
|
1734
|
+
---
|
|
1735
|
+
|
|
1736
|
+
## Context Management Strategies
|
|
1737
|
+
|
|
1738
|
+
AI agents have limited context windows. Use Trekker to extend your effective memory:
|
|
1739
|
+
|
|
1740
|
+
### Starting a New Session
|
|
1741
|
+
|
|
1742
|
+
Always begin by reading your previous state:
|
|
1743
|
+
\`\`\`bash
|
|
1744
|
+
# What was I working on?
|
|
1745
|
+
trekker --json task list --status in_progress
|
|
1746
|
+
|
|
1747
|
+
# What did I learn/decide?
|
|
1748
|
+
trekker --json comment list TREK-1
|
|
1749
|
+
\`\`\`
|
|
1750
|
+
|
|
1751
|
+
### During Long Tasks
|
|
1752
|
+
|
|
1753
|
+
Periodically checkpoint your progress:
|
|
1754
|
+
\`\`\`bash
|
|
1755
|
+
trekker comment add TREK-1 -a "agent" -c "Progress: Steps 1-3 done. Current state: X. Next: Y. Blockers: Z."
|
|
1756
|
+
\`\`\`
|
|
1757
|
+
|
|
1758
|
+
### Before Context Window Fills Up
|
|
1759
|
+
|
|
1760
|
+
When you notice context getting large, dump your current mental state:
|
|
1761
|
+
\`\`\`bash
|
|
1762
|
+
trekker comment add TREK-1 -a "agent" -c "Context dump:
|
|
1763
|
+
- Working on: implementing login validation
|
|
1764
|
+
- Files involved: auth.ts (modified lines 45-80), routes.ts (new endpoint at line 120)
|
|
1765
|
+
- Current approach: using existing validateEmail() from utils
|
|
1766
|
+
- Remaining work: add rate limiting, write tests
|
|
1767
|
+
- Open questions: unclear if we need refresh tokens"
|
|
1768
|
+
\`\`\`
|
|
1769
|
+
|
|
1770
|
+
### After Solving a Problem
|
|
1771
|
+
|
|
1772
|
+
Document the solution so future-you doesn't repeat the investigation:
|
|
1773
|
+
\`\`\`bash
|
|
1774
|
+
trekker comment add TREK-1 -a "agent" -c "Solved: JWT verification failing. Root cause: clock skew. Fix: added 30s leeway in verify(). See auth.ts:67."
|
|
1775
|
+
\`\`\`
|
|
1776
|
+
|
|
1777
|
+
### When Blocked
|
|
1778
|
+
|
|
1779
|
+
Record what you're waiting for:
|
|
1780
|
+
\`\`\`bash
|
|
1781
|
+
trekker comment add TREK-1 -a "agent" -c "BLOCKED: Need user input on password complexity requirements. Asked in conversation. Current assumption: min 8 chars, 1 number."
|
|
1782
|
+
\`\`\`
|
|
1783
|
+
|
|
1784
|
+
---
|
|
1785
|
+
|
|
1786
|
+
## Common Scenarios
|
|
1787
|
+
|
|
1788
|
+
### Start a new feature
|
|
1789
|
+
|
|
1790
|
+
\`\`\`bash
|
|
1791
|
+
trekker epic create -t "API Rate Limiting" -d "Implement rate limiting for all endpoints" -p 1
|
|
1792
|
+
trekker task create -t "Design rate limit algorithm" -e EPIC-1
|
|
1793
|
+
trekker task create -t "Implement Redis counter" -e EPIC-1
|
|
1794
|
+
trekker task create -t "Add middleware" -e EPIC-1
|
|
1795
|
+
trekker dep add TREK-3 TREK-2 # Middleware depends on Redis counter
|
|
1796
|
+
trekker dep add TREK-2 TREK-1 # Redis counter depends on design
|
|
1797
|
+
\`\`\`
|
|
1798
|
+
|
|
1799
|
+
### Check what's ready to work on
|
|
1800
|
+
|
|
1801
|
+
\`\`\`bash
|
|
1802
|
+
trekker --json task list --status todo
|
|
1803
|
+
\`\`\`
|
|
1804
|
+
|
|
1805
|
+
### Get all details about a task
|
|
1806
|
+
|
|
1807
|
+
\`\`\`bash
|
|
1808
|
+
trekker --json task show TREK-1
|
|
1809
|
+
\`\`\`
|
|
1810
|
+
|
|
1811
|
+
### Log progress
|
|
1812
|
+
|
|
1813
|
+
\`\`\`bash
|
|
1814
|
+
trekker task update TREK-1 -s in_progress
|
|
1815
|
+
trekker comment add TREK-1 -a "agent" -c "Chose token bucket algorithm"
|
|
1816
|
+
trekker task update TREK-1 -s completed
|
|
1817
|
+
\`\`\`
|
|
1818
|
+
|
|
1819
|
+
### Break down a complex task
|
|
1820
|
+
|
|
1821
|
+
\`\`\`bash
|
|
1822
|
+
trekker subtask create TREK-2 -t "Set up Redis connection"
|
|
1823
|
+
trekker subtask create TREK-2 -t "Implement increment logic"
|
|
1824
|
+
trekker subtask create TREK-2 -t "Add TTL handling"
|
|
1825
|
+
\`\`\`
|
|
1826
|
+
|
|
1827
|
+
### Resume work after context reset
|
|
1828
|
+
|
|
1829
|
+
\`\`\`bash
|
|
1830
|
+
# 1. Find what you were working on
|
|
1831
|
+
trekker --json task list --status in_progress
|
|
1832
|
+
|
|
1833
|
+
# 2. Read your previous comments to restore context
|
|
1834
|
+
trekker --json comment list TREK-1
|
|
1835
|
+
|
|
1836
|
+
# 3. Read task details for full picture
|
|
1837
|
+
trekker --json task show TREK-1
|
|
1838
|
+
|
|
1839
|
+
# 4. Continue work with restored context
|
|
1840
|
+
\`\`\`
|
|
1841
|
+
|
|
1842
|
+
### Save context before stopping
|
|
1843
|
+
|
|
1844
|
+
\`\`\`bash
|
|
1845
|
+
# Dump everything you know before session ends
|
|
1846
|
+
trekker comment add TREK-1 -a "agent" -c "Session end checkpoint:
|
|
1847
|
+
- Completed: login endpoint, validation
|
|
1848
|
+
- In progress: rate limiting (50% done, see middleware.ts:30)
|
|
1849
|
+
- Files modified: auth.ts, routes.ts, middleware.ts
|
|
1850
|
+
- Next steps: finish rate limiter, add tests
|
|
1851
|
+
- Notes: user prefers 100 req/min limit"
|
|
1852
|
+
\`\`\`
|
|
1853
|
+
`;
|
|
1854
|
+
var quickstartCommand = new Command8("quickstart").description("Show comprehensive guide for AI agents").action(() => {
|
|
1855
|
+
console.log(QUICKSTART_TEXT);
|
|
1856
|
+
});
|
|
1857
|
+
|
|
1858
|
+
// src/commands/serve.ts
|
|
1859
|
+
import { Command as Command9 } from "commander";
|
|
1860
|
+
import { spawn, spawnSync } from "child_process";
|
|
1861
|
+
import { resolve, dirname } from "path";
|
|
1862
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1863
|
+
function findWebappDir() {
|
|
1864
|
+
const scriptPath = import.meta.url.replace("file://", "");
|
|
1865
|
+
const scriptDir = dirname(scriptPath);
|
|
1866
|
+
const cliRoot = scriptDir.includes("/src/") ? dirname(dirname(dirname(scriptPath))) : dirname(dirname(scriptPath));
|
|
1867
|
+
const bundledWebapp = resolve(cliRoot, "webapp-dist");
|
|
1868
|
+
if (existsSync2(bundledWebapp)) {
|
|
1869
|
+
return { path: bundledWebapp, isBundled: true };
|
|
1870
|
+
}
|
|
1871
|
+
const packagesDir = dirname(cliRoot);
|
|
1872
|
+
const webappDir = resolve(packagesDir, "webapp");
|
|
1873
|
+
if (existsSync2(webappDir)) {
|
|
1874
|
+
return { path: webappDir, isBundled: false };
|
|
1875
|
+
}
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
var serveCommand = new Command9("serve").description("Start the Trekker web interface").option("-p, --port <port>", "Port to run on", "3000").option("--dev", "Run in development mode").action(async (options) => {
|
|
1879
|
+
try {
|
|
1880
|
+
if (!isTrekkerInitialized()) {
|
|
1881
|
+
error("Trekker is not initialized. Run 'trekker init' first.");
|
|
1882
|
+
process.exit(1);
|
|
1883
|
+
}
|
|
1884
|
+
const dbPath = getDbPath();
|
|
1885
|
+
const port = options.port;
|
|
1886
|
+
const webapp = findWebappDir();
|
|
1887
|
+
if (!webapp) {
|
|
1888
|
+
error("Webapp not found. Please reinstall Trekker.");
|
|
1889
|
+
process.exit(1);
|
|
1890
|
+
}
|
|
1891
|
+
const { path: webappDir, isBundled } = webapp;
|
|
1892
|
+
const env = {
|
|
1893
|
+
...process.env,
|
|
1894
|
+
TREKKER_DB_PATH: dbPath,
|
|
1895
|
+
PORT: port
|
|
1896
|
+
};
|
|
1897
|
+
if (isBundled) {
|
|
1898
|
+
success(`Starting Trekker web interface on http://localhost:${port}`);
|
|
1899
|
+
console.log(`Press Ctrl+C to stop
|
|
1900
|
+
`);
|
|
1901
|
+
const server2 = spawn("bun", ["run", "server.js"], {
|
|
1902
|
+
cwd: webappDir,
|
|
1903
|
+
stdio: "inherit",
|
|
1904
|
+
env
|
|
1905
|
+
});
|
|
1906
|
+
setupSignalHandlers(server2);
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
const nodeModulesPath = resolve(webappDir, "node_modules");
|
|
1910
|
+
if (!existsSync2(nodeModulesPath)) {
|
|
1911
|
+
console.log("Installing webapp dependencies...");
|
|
1912
|
+
const result = spawnSync("bun", ["install"], {
|
|
1913
|
+
cwd: webappDir,
|
|
1914
|
+
stdio: "inherit"
|
|
1915
|
+
});
|
|
1916
|
+
if (result.status !== 0) {
|
|
1917
|
+
error("Failed to install dependencies");
|
|
1918
|
+
process.exit(1);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
if (options.dev) {
|
|
1922
|
+
success(`Starting Trekker web interface (dev) on http://localhost:${port}`);
|
|
1923
|
+
console.log(`Press Ctrl+C to stop
|
|
1924
|
+
`);
|
|
1925
|
+
const dev = spawn("bun", ["run", "dev", "--", "-p", port], {
|
|
1926
|
+
cwd: webappDir,
|
|
1927
|
+
stdio: "inherit",
|
|
1928
|
+
env
|
|
1929
|
+
});
|
|
1930
|
+
setupSignalHandlers(dev);
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
const standalonePath = resolve(webappDir, ".next", "standalone");
|
|
1934
|
+
const buildMarker = resolve(webappDir, ".next", "BUILD_ID");
|
|
1935
|
+
if (!existsSync2(standalonePath) || !existsSync2(buildMarker)) {
|
|
1936
|
+
console.log("Building webapp...");
|
|
1937
|
+
const result = spawnSync("bun", ["run", "build"], {
|
|
1938
|
+
cwd: webappDir,
|
|
1939
|
+
stdio: "inherit",
|
|
1940
|
+
env
|
|
1941
|
+
});
|
|
1942
|
+
if (result.status !== 0) {
|
|
1943
|
+
error("Failed to build webapp");
|
|
1944
|
+
process.exit(1);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
const staticSrc = resolve(webappDir, ".next", "static");
|
|
1948
|
+
const staticDest = resolve(standalonePath, ".next", "static");
|
|
1949
|
+
if (existsSync2(staticSrc) && !existsSync2(staticDest)) {
|
|
1950
|
+
spawnSync("cp", ["-r", staticSrc, resolve(standalonePath, ".next")], {
|
|
1951
|
+
stdio: "inherit"
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
const publicSrc = resolve(webappDir, "public");
|
|
1955
|
+
const publicDest = resolve(standalonePath, "public");
|
|
1956
|
+
if (existsSync2(publicSrc) && !existsSync2(publicDest)) {
|
|
1957
|
+
spawnSync("cp", ["-r", publicSrc, standalonePath], {
|
|
1958
|
+
stdio: "inherit"
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
success(`Starting Trekker web interface on http://localhost:${port}`);
|
|
1962
|
+
console.log(`Press Ctrl+C to stop
|
|
1963
|
+
`);
|
|
1964
|
+
const server = spawn("bun", ["run", "server.js"], {
|
|
1965
|
+
cwd: standalonePath,
|
|
1966
|
+
stdio: "inherit",
|
|
1967
|
+
env
|
|
1968
|
+
});
|
|
1969
|
+
setupSignalHandlers(server);
|
|
1970
|
+
} catch (err) {
|
|
1971
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1972
|
+
process.exit(1);
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
function setupSignalHandlers(child) {
|
|
1976
|
+
child.on("error", (err) => {
|
|
1977
|
+
error(`Failed to start webapp: ${err.message}`);
|
|
1978
|
+
process.exit(1);
|
|
1979
|
+
});
|
|
1980
|
+
process.on("SIGINT", () => {
|
|
1981
|
+
child.kill("SIGINT");
|
|
1982
|
+
process.exit(0);
|
|
1983
|
+
});
|
|
1984
|
+
process.on("SIGTERM", () => {
|
|
1985
|
+
child.kill("SIGTERM");
|
|
1986
|
+
process.exit(0);
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// src/commands/seed.ts
|
|
1991
|
+
import { Command as Command10 } from "commander";
|
|
1992
|
+
var SAMPLE_EPICS = [
|
|
1993
|
+
{
|
|
1994
|
+
title: "User Authentication",
|
|
1995
|
+
description: "Implement user authentication and authorization system",
|
|
1996
|
+
priority: 0,
|
|
1997
|
+
status: "in_progress"
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
title: "Dashboard",
|
|
2001
|
+
description: "Build the main dashboard with analytics and metrics",
|
|
2002
|
+
priority: 1,
|
|
2003
|
+
status: "todo"
|
|
2004
|
+
},
|
|
2005
|
+
{
|
|
2006
|
+
title: "API Development",
|
|
2007
|
+
description: "Design and implement RESTful API endpoints",
|
|
2008
|
+
priority: 1,
|
|
2009
|
+
status: "in_progress"
|
|
2010
|
+
},
|
|
2011
|
+
{
|
|
2012
|
+
title: "Testing & QA",
|
|
2013
|
+
description: "Set up testing infrastructure and write tests",
|
|
2014
|
+
priority: 2,
|
|
2015
|
+
status: "todo"
|
|
2016
|
+
}
|
|
2017
|
+
];
|
|
2018
|
+
var SAMPLE_TASKS = [
|
|
2019
|
+
{
|
|
2020
|
+
epicIndex: 0,
|
|
2021
|
+
title: "Set up OAuth 2.0 provider",
|
|
2022
|
+
description: "Configure OAuth 2.0 with Google and GitHub providers",
|
|
2023
|
+
priority: 0,
|
|
2024
|
+
status: "completed",
|
|
2025
|
+
tags: "backend,security"
|
|
2026
|
+
},
|
|
2027
|
+
{
|
|
2028
|
+
epicIndex: 0,
|
|
2029
|
+
title: "Implement JWT token handling",
|
|
2030
|
+
description: "Create JWT generation, validation, and refresh logic",
|
|
2031
|
+
priority: 1,
|
|
2032
|
+
status: "in_progress",
|
|
2033
|
+
tags: "backend,security"
|
|
2034
|
+
},
|
|
2035
|
+
{
|
|
2036
|
+
epicIndex: 0,
|
|
2037
|
+
title: "Build login page",
|
|
2038
|
+
description: "Create responsive login page with social login buttons",
|
|
2039
|
+
priority: 1,
|
|
2040
|
+
status: "todo",
|
|
2041
|
+
tags: "frontend,ui"
|
|
2042
|
+
},
|
|
2043
|
+
{
|
|
2044
|
+
epicIndex: 0,
|
|
2045
|
+
title: "Add password reset flow",
|
|
2046
|
+
description: "Implement forgot password and reset password functionality",
|
|
2047
|
+
priority: 2,
|
|
2048
|
+
status: "todo",
|
|
2049
|
+
tags: "backend,frontend"
|
|
2050
|
+
},
|
|
2051
|
+
{
|
|
2052
|
+
epicIndex: 1,
|
|
2053
|
+
title: "Design dashboard layout",
|
|
2054
|
+
description: "Create wireframes and mockups for the main dashboard",
|
|
2055
|
+
priority: 1,
|
|
2056
|
+
status: "completed",
|
|
2057
|
+
tags: "design,ui"
|
|
2058
|
+
},
|
|
2059
|
+
{
|
|
2060
|
+
epicIndex: 1,
|
|
2061
|
+
title: "Implement chart components",
|
|
2062
|
+
description: "Build reusable chart components using Chart.js",
|
|
2063
|
+
priority: 2,
|
|
2064
|
+
status: "in_progress",
|
|
2065
|
+
tags: "frontend,ui"
|
|
2066
|
+
},
|
|
2067
|
+
{
|
|
2068
|
+
epicIndex: 1,
|
|
2069
|
+
title: "Add real-time data updates",
|
|
2070
|
+
description: "Implement WebSocket connection for live dashboard updates",
|
|
2071
|
+
priority: 2,
|
|
2072
|
+
status: "todo",
|
|
2073
|
+
tags: "frontend,backend"
|
|
2074
|
+
},
|
|
2075
|
+
{
|
|
2076
|
+
epicIndex: 2,
|
|
2077
|
+
title: "Define API schema",
|
|
2078
|
+
description: "Document API endpoints using OpenAPI specification",
|
|
2079
|
+
priority: 1,
|
|
2080
|
+
status: "completed",
|
|
2081
|
+
tags: "backend,docs"
|
|
2082
|
+
},
|
|
2083
|
+
{
|
|
2084
|
+
epicIndex: 2,
|
|
2085
|
+
title: "Implement user endpoints",
|
|
2086
|
+
description: "Create CRUD endpoints for user management",
|
|
2087
|
+
priority: 1,
|
|
2088
|
+
status: "completed",
|
|
2089
|
+
tags: "backend"
|
|
2090
|
+
},
|
|
2091
|
+
{
|
|
2092
|
+
epicIndex: 2,
|
|
2093
|
+
title: "Add rate limiting",
|
|
2094
|
+
description: "Implement rate limiting middleware for API protection",
|
|
2095
|
+
priority: 2,
|
|
2096
|
+
status: "in_progress",
|
|
2097
|
+
tags: "backend,security"
|
|
2098
|
+
},
|
|
2099
|
+
{
|
|
2100
|
+
epicIndex: 2,
|
|
2101
|
+
title: "Set up API versioning",
|
|
2102
|
+
description: "Implement v1/v2 API versioning strategy",
|
|
2103
|
+
priority: 3,
|
|
2104
|
+
status: "todo",
|
|
2105
|
+
tags: "backend"
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
epicIndex: 3,
|
|
2109
|
+
title: "Set up Jest testing framework",
|
|
2110
|
+
description: "Configure Jest with TypeScript support",
|
|
2111
|
+
priority: 1,
|
|
2112
|
+
status: "completed",
|
|
2113
|
+
tags: "testing,devops"
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
epicIndex: 3,
|
|
2117
|
+
title: "Write unit tests for auth module",
|
|
2118
|
+
description: "Create comprehensive unit tests for authentication logic",
|
|
2119
|
+
priority: 2,
|
|
2120
|
+
status: "todo",
|
|
2121
|
+
tags: "testing"
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
epicIndex: 3,
|
|
2125
|
+
title: "Set up E2E testing with Playwright",
|
|
2126
|
+
description: "Configure Playwright for end-to-end testing",
|
|
2127
|
+
priority: 3,
|
|
2128
|
+
status: "todo",
|
|
2129
|
+
tags: "testing,devops"
|
|
2130
|
+
},
|
|
2131
|
+
{
|
|
2132
|
+
epicIndex: null,
|
|
2133
|
+
title: "Update README documentation",
|
|
2134
|
+
description: "Add installation instructions and usage examples",
|
|
2135
|
+
priority: 3,
|
|
2136
|
+
status: "todo",
|
|
2137
|
+
tags: "docs"
|
|
2138
|
+
},
|
|
2139
|
+
{
|
|
2140
|
+
epicIndex: null,
|
|
2141
|
+
title: "Configure CI/CD pipeline",
|
|
2142
|
+
description: "Set up GitHub Actions for automated testing and deployment",
|
|
2143
|
+
priority: 2,
|
|
2144
|
+
status: "in_progress",
|
|
2145
|
+
tags: "devops"
|
|
2146
|
+
}
|
|
2147
|
+
];
|
|
2148
|
+
var SAMPLE_SUBTASKS = [
|
|
2149
|
+
{
|
|
2150
|
+
parentIndex: 1,
|
|
2151
|
+
title: "Implement access token generation",
|
|
2152
|
+
status: "completed",
|
|
2153
|
+
priority: 1
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
parentIndex: 1,
|
|
2157
|
+
title: "Implement refresh token logic",
|
|
2158
|
+
status: "in_progress",
|
|
2159
|
+
priority: 1
|
|
2160
|
+
},
|
|
2161
|
+
{
|
|
2162
|
+
parentIndex: 1,
|
|
2163
|
+
title: "Add token blacklisting",
|
|
2164
|
+
status: "todo",
|
|
2165
|
+
priority: 2
|
|
2166
|
+
},
|
|
2167
|
+
{
|
|
2168
|
+
parentIndex: 5,
|
|
2169
|
+
title: "Create bar chart component",
|
|
2170
|
+
status: "completed",
|
|
2171
|
+
priority: 2
|
|
2172
|
+
},
|
|
2173
|
+
{
|
|
2174
|
+
parentIndex: 5,
|
|
2175
|
+
title: "Create line chart component",
|
|
2176
|
+
status: "in_progress",
|
|
2177
|
+
priority: 2
|
|
2178
|
+
},
|
|
2179
|
+
{
|
|
2180
|
+
parentIndex: 5,
|
|
2181
|
+
title: "Create pie chart component",
|
|
2182
|
+
status: "todo",
|
|
2183
|
+
priority: 3
|
|
2184
|
+
}
|
|
2185
|
+
];
|
|
2186
|
+
var SAMPLE_DEPENDENCIES = [
|
|
2187
|
+
[2, 1],
|
|
2188
|
+
[3, 1],
|
|
2189
|
+
[6, 5],
|
|
2190
|
+
[6, 4],
|
|
2191
|
+
[9, 8],
|
|
2192
|
+
[12, 11],
|
|
2193
|
+
[13, 11]
|
|
2194
|
+
];
|
|
2195
|
+
var seedCommand = new Command10("seed").description("Seed the database with sample data (development only)").option("--force", "Skip confirmation prompt").action((options) => {
|
|
2196
|
+
try {
|
|
2197
|
+
if (!isTrekkerInitialized()) {
|
|
2198
|
+
error("Trekker is not initialized. Run 'trekker init' first.");
|
|
2199
|
+
process.exit(1);
|
|
2200
|
+
}
|
|
2201
|
+
if (!options.force) {
|
|
2202
|
+
info("This will create sample epics, tasks, and dependencies.");
|
|
2203
|
+
info(`Use --force to skip this confirmation.
|
|
2204
|
+
`);
|
|
2205
|
+
}
|
|
2206
|
+
const epicIds = [];
|
|
2207
|
+
const taskIds = [];
|
|
2208
|
+
info("Creating epics...");
|
|
2209
|
+
for (const epicData of SAMPLE_EPICS) {
|
|
2210
|
+
const epic = createEpic({
|
|
2211
|
+
title: epicData.title,
|
|
2212
|
+
description: epicData.description,
|
|
2213
|
+
priority: epicData.priority,
|
|
2214
|
+
status: epicData.status
|
|
2215
|
+
});
|
|
2216
|
+
epicIds.push(epic.id);
|
|
2217
|
+
info(` Created ${epic.id}: ${epic.title}`);
|
|
2218
|
+
}
|
|
2219
|
+
info(`
|
|
2220
|
+
Creating tasks...`);
|
|
2221
|
+
for (const taskData of SAMPLE_TASKS) {
|
|
2222
|
+
const task = createTask({
|
|
2223
|
+
title: taskData.title,
|
|
2224
|
+
description: taskData.description,
|
|
2225
|
+
priority: taskData.priority,
|
|
2226
|
+
status: taskData.status,
|
|
2227
|
+
tags: taskData.tags,
|
|
2228
|
+
epicId: taskData.epicIndex !== null ? epicIds[taskData.epicIndex] : undefined
|
|
2229
|
+
});
|
|
2230
|
+
taskIds.push(task.id);
|
|
2231
|
+
info(` Created ${task.id}: ${task.title}`);
|
|
2232
|
+
}
|
|
2233
|
+
info(`
|
|
2234
|
+
Creating subtasks...`);
|
|
2235
|
+
for (const subtaskData of SAMPLE_SUBTASKS) {
|
|
2236
|
+
const subtask = createTask({
|
|
2237
|
+
title: subtaskData.title,
|
|
2238
|
+
priority: subtaskData.priority,
|
|
2239
|
+
status: subtaskData.status,
|
|
2240
|
+
parentTaskId: taskIds[subtaskData.parentIndex]
|
|
2241
|
+
});
|
|
2242
|
+
info(` Created ${subtask.id}: ${subtask.title} (subtask of ${taskIds[subtaskData.parentIndex]})`);
|
|
2243
|
+
}
|
|
2244
|
+
info(`
|
|
2245
|
+
Creating dependencies...`);
|
|
2246
|
+
for (const [taskIndex, dependsOnIndex] of SAMPLE_DEPENDENCIES) {
|
|
2247
|
+
const taskId = taskIds[taskIndex];
|
|
2248
|
+
const dependsOnId = taskIds[dependsOnIndex];
|
|
2249
|
+
addDependency(taskId, dependsOnId);
|
|
2250
|
+
info(` ${taskId} depends on ${dependsOnId}`);
|
|
2251
|
+
}
|
|
2252
|
+
success(`
|
|
2253
|
+
Seed complete! Created ${epicIds.length} epics, ${taskIds.length} tasks, ${SAMPLE_SUBTASKS.length} subtasks, and ${SAMPLE_DEPENDENCIES.length} dependencies.`);
|
|
2254
|
+
} catch (err) {
|
|
2255
|
+
error(err instanceof Error ? err.message : String(err));
|
|
2256
|
+
process.exit(1);
|
|
2257
|
+
}
|
|
2258
|
+
});
|
|
2259
|
+
|
|
2260
|
+
// src/index.ts
|
|
2261
|
+
var program = new Command11;
|
|
2262
|
+
program.name("trekker").description("CLI-based issue tracker for coding agents").version("0.1.0").option("--json", "Output in JSON format").hook("preAction", (thisCommand) => {
|
|
2263
|
+
const opts = thisCommand.opts();
|
|
2264
|
+
if (opts.json) {
|
|
2265
|
+
setJsonMode(true);
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2268
|
+
program.addCommand(initCommand);
|
|
2269
|
+
program.addCommand(wipeCommand);
|
|
2270
|
+
program.addCommand(epicCommand);
|
|
2271
|
+
program.addCommand(taskCommand);
|
|
2272
|
+
program.addCommand(subtaskCommand);
|
|
2273
|
+
program.addCommand(commentCommand);
|
|
2274
|
+
program.addCommand(depCommand);
|
|
2275
|
+
program.addCommand(quickstartCommand);
|
|
2276
|
+
program.addCommand(serveCommand);
|
|
2277
|
+
program.addCommand(seedCommand);
|
|
2278
|
+
program.parse();
|