@obsfx/trekker 1.10.2 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +92 -32
  2. package/dist/index.js +306 -109
  3. package/package.json +14 -14
package/README.md CHANGED
@@ -31,6 +31,51 @@ Or with npm:
31
31
  npm install -g @obsfx/trekker
32
32
  ```
33
33
 
34
+ ## Claude Code Plugin
35
+
36
+ <img src="https://omercan.io/trekker/images/claude-color.png" width="24" height="24" alt="Claude" style="vertical-align: middle;" />
37
+
38
+ Install the [trekker-claude-code](https://github.com/obsfx/trekker-claude-code) plugin for seamless integration with [Claude Code](https://claude.ai/code):
39
+
40
+ ```bash
41
+ claude plugin marketplace add obsfx/trekker-claude-code
42
+ claude plugin install trekker
43
+ ```
44
+
45
+ This gives Claude Code native access to Trekker through **26 MCP tools**, **13 slash commands**, **7 skills**, **5 lifecycle hooks**, and an **autonomous task agent**.
46
+
47
+ **Key features:**
48
+
49
+ - Persistent task memory across sessions via SQLite
50
+ - Search-first workflow to restore context
51
+ - 7 skills for guided workflows and best practices
52
+ - 5 lifecycle hooks for automatic state management
53
+ - Autonomous task agent for discovery and completion
54
+ - Blocks internal TaskCreate/TodoWrite — enforces Trekker
55
+ - Multi-instance safe with conflict handling
56
+
57
+ See the [plugin repository](https://github.com/obsfx/trekker-claude-code) for the full list of slash commands, hooks, skills, and agent details.
58
+
59
+ ## Codex Plugin
60
+
61
+ <img width="36" height="36" alt="OpenAI-black-monoblossom" src="https://github.com/user-attachments/assets/e761c75d-6012-44b0-bfbe-c6cc235b54b4" />
62
+
63
+
64
+ Support for the **Codex** plugin is still in a *very early stage* and is continuing to evolve. However, we already have a plugin for **Trekker** that you can install locally. Please follow the [plugin repository](https://github.com/obsfx/trekker-codex) and the instructions at https://developers.openai.com/codex/plugins/build#how-codex-uses-marketplaces, and you can get features similar to those available in the *Claude Code* plugin.
65
+
66
+ ## Web Interface
67
+
68
+ <img src="https://omercan.io/trekker/images/dashboard-kanban.png" width="480" alt="trekker dashboard" style="vertical-align: middle;" />
69
+
70
+ For a visual kanban board, install the separate dashboard package:
71
+
72
+ ```bash
73
+ npm install -g @obsfx/trekker-dashboard
74
+ trekker-dashboard -p 3000 # Start dashboard on port 3000
75
+ ```
76
+
77
+ You can find the detail in its [trekker-dashboard](https://github.com/obsfx/trekker-dashboard) repository.
78
+
34
79
  ## Why Trekker
35
80
 
36
81
  AI coding agents work better when they can track their own progress. A simple CLI-based task manager keeps them on the right path across sessions.
@@ -40,6 +85,7 @@ I built this after using beads for a while. Beads does the job, but its codebase
40
85
  My concerns about the future and security of that project led me here. Trekker is my simplified alternative.
41
86
 
42
87
  What you get:
88
+
43
89
  - Task and epic tracking with dependencies
44
90
  - Ready command to find unblocked tasks and see what they unblock
45
91
  - Full-text search across tasks, epics, subtasks, and comments
@@ -56,6 +102,8 @@ Initialize Trekker in your project:
56
102
 
57
103
  ```bash
58
104
  trekker init
105
+ # or set custom prefixes up front
106
+ trekker init --issue-prefix FEAT --epic-prefix PLAN --comment-prefix NOTE
59
107
  ```
60
108
 
61
109
  Create an epic for your feature:
@@ -67,16 +115,16 @@ trekker epic create -t "User Authentication" -d "JWT-based auth with login and r
67
115
  Add tasks to the epic:
68
116
 
69
117
  ```bash
70
- trekker task create -t "Create user model" -e EPIC-1
71
- trekker task create -t "Build login endpoint" -e EPIC-1
72
- trekker task create -t "Build registration endpoint" -e EPIC-1
118
+ trekker task create -t "Create user model" -e <epic-id>
119
+ trekker task create -t "Build login endpoint" -e <epic-id>
120
+ trekker task create -t "Build registration endpoint" -e <epic-id>
73
121
  ```
74
122
 
75
123
  Set dependencies between tasks:
76
124
 
77
125
  ```bash
78
- trekker dep add TREK-2 TREK-1
79
- trekker dep add TREK-3 TREK-1
126
+ trekker dep add <task-id> <depends-on-id>
127
+ trekker dep add <task-id> <depends-on-id>
80
128
  ```
81
129
 
82
130
  See what is ready to work on:
@@ -88,8 +136,8 @@ trekker ready
88
136
  Update task status as you work:
89
137
 
90
138
  ```bash
91
- trekker task update TREK-1 -s in_progress
92
- trekker task update TREK-1 -s completed
139
+ trekker task update <task-id> -s in_progress
140
+ trekker task update <task-id> -s completed
93
141
  ```
94
142
 
95
143
  ## Commands
@@ -98,10 +146,32 @@ trekker task update TREK-1 -s completed
98
146
 
99
147
  ```bash
100
148
  trekker init # Initialize in current directory
149
+ trekker init --issue-prefix FEAT --epic-prefix PLAN --comment-prefix NOTE
101
150
  trekker wipe # Delete all data
102
151
  trekker quickstart # Show full documentation
103
152
  ```
104
153
 
154
+ ### Project Config
155
+
156
+ ```bash
157
+ trekker config list
158
+ trekker config get issue_prefix
159
+ trekker config set issue_prefix FEAT
160
+ trekker config set epic_prefix PLAN
161
+ trekker config set comment_prefix NOTE
162
+ trekker config unset issue_prefix
163
+ ```
164
+
165
+ Supported keys:
166
+
167
+ - `issue_prefix` for tasks and subtasks
168
+ - `epic_prefix` for epics
169
+ - `comment_prefix` for comments
170
+
171
+ Use `issue_prefix` when you want task IDs to match your project naming scheme. For example, `trekker init --issue-prefix FEAT` starts new task and subtask IDs as `FEAT-1`, `FEAT-2`, and so on. You can also change it later with `trekker config set issue_prefix BUG`, which only affects newly created tasks and subtasks.
172
+
173
+ Prefix values are normalized to uppercase, must start with a letter, may contain numbers, and must be unique across the three families. Changing a prefix affects only newly created IDs. Existing IDs are never rewritten.
174
+
105
175
  ### Epics
106
176
 
107
177
  ```bash
@@ -158,6 +228,7 @@ trekker ready [--limit <n>] [--page <n>]
158
228
  ```
159
229
 
160
230
  Example output:
231
+
161
232
  ```
162
233
  2 ready task(s):
163
234
 
@@ -168,6 +239,7 @@ TREK-4 | P2 | Write docs
168
239
  ```
169
240
 
170
241
  Tasks are sorted by priority (critical first). A task is considered ready when:
242
+
171
243
  - Status is `todo`
172
244
  - It is a top-level task (not a subtask)
173
245
  - All its dependencies are resolved (`completed`, `wont_fix`, or `archived`)
@@ -181,6 +253,7 @@ trekker search <query> [--type <types>] [--status <status>] [--limit <n>] [--pag
181
253
  ```
182
254
 
183
255
  Examples:
256
+
184
257
  ```bash
185
258
  trekker search "authentication" # Search all entities
186
259
  trekker search "bug fix" --type task,subtask # Search only tasks and subtasks
@@ -196,9 +269,10 @@ trekker history [--entity <id>] [--type <types>] [--action <actions>] [--since <
196
269
  ```
197
270
 
198
271
  Examples:
272
+
199
273
  ```bash
200
274
  trekker history # All events
201
- trekker history --entity TREK-1 # Events for specific entity
275
+ trekker history --entity <entity-id> # Events for specific entity
202
276
  trekker history --type task --action update # Only task updates
203
277
  trekker history --since 2025-01-01 --limit 20 # Events after date
204
278
  ```
@@ -212,6 +286,7 @@ trekker list [--type <types>] [--status <statuses>] [--priority <levels>] [--sor
212
286
  ```
213
287
 
214
288
  Examples:
289
+
215
290
  ```bash
216
291
  trekker list # All items, newest first
217
292
  trekker list --type task --status in_progress # Active tasks only
@@ -219,29 +294,9 @@ trekker list --priority 0,1 --sort priority:asc # Critical/high priority first
219
294
  trekker list --sort title:asc,created:desc # Sort by title, then by date
220
295
  ```
221
296
 
222
- ### Web Interface
223
-
224
- For a visual kanban board, install the separate dashboard package:
225
-
226
- ```bash
227
- npm install -g @obsfx/trekker-dashboard
228
- trekker-dashboard -p 3000 # Start dashboard on port 3000
229
- ```
230
297
 
231
298
  The dashboard shows tasks grouped by status and reads from the same `.trekker/trekker.db` database.
232
-
233
- ## Claude Code Integration
234
-
235
- For seamless integration with [Claude Code](https://claude.ai/code), install the Trekker plugin:
236
-
237
- ```bash
238
- claude /plugin marketplace add obsfx/trekker-claude-code
239
- claude /plugin install trekker
240
- ```
241
-
242
- This gives Claude Code native access to Trekker commands through MCP. The agent can create tasks, update status, and manage dependencies without running CLI commands directly.
243
-
244
- See [trekker-claude-code](https://github.com/obsfx/trekker-claude-code) for more details.
299
+ It also lets you update issue, epic, and comment prefixes from the UI. Those changes affect only newly created IDs.
245
300
 
246
301
  ## TOON Output
247
302
 
@@ -249,7 +304,7 @@ Add the `--toon` flag to any command for structured output in [TOON format](http
249
304
 
250
305
  ```bash
251
306
  trekker --toon task list
252
- trekker --toon task show TREK-1
307
+ trekker --toon task show <task-id>
253
308
  ```
254
309
 
255
310
  ## Status Values
@@ -267,12 +322,16 @@ Epics: `todo`, `in_progress`, `completed`, `archived`
267
322
  - 4: Backlog
268
323
  - 5: Someday
269
324
 
270
- ## ID Formats
325
+ ## ID Prefixes
326
+
327
+ Default prefixes:
271
328
 
272
329
  - Epics: `EPIC-1`, `EPIC-2`
273
- - Tasks: `TREK-1`, `TREK-2`
330
+ - Tasks and subtasks: `TREK-1`, `TREK-2`
274
331
  - Comments: `CMT-1`, `CMT-2`
275
332
 
333
+ These defaults are project-scoped and configurable through `trekker init --*-prefix` or `trekker config set`. Prefix changes affect only new entities and existing IDs keep their original values.
334
+
276
335
  All list commands default to 50 items per page, sorted by newest first. Use `--limit` and `--page` to paginate through large result sets.
277
336
 
278
337
  ## Development
@@ -308,6 +367,7 @@ This is my personal workflow for getting the most out of Trekker with AI agents:
308
367
  - **Use the dashboard for visibility.** I run [trekker-dashboard](https://github.com/obsfx/trekker-dashboard) to visually track what the agent is doing. It shows tasks on a kanban board and auto-refreshes, so I can monitor progress in real-time.
309
368
 
310
369
  Example prompt snippet:
370
+
311
371
  ```
312
372
  Use trekker to track your work. Run `trekker quickstart` if you need to learn how it works.
313
373
  ```
package/dist/index.js CHANGED
@@ -455,7 +455,7 @@ var require_customParseFormat = __commonJS((exports, module) => {
455
455
  });
456
456
 
457
457
  // src/index.ts
458
- import { Command as Command14 } from "commander";
458
+ import { Command as Command15 } from "commander";
459
459
 
460
460
  // src/commands/init.ts
461
461
  import { Command } from "commander";
@@ -471,6 +471,7 @@ __export(exports_schema, {
471
471
  tasks: () => tasks,
472
472
  projectsRelations: () => projectsRelations,
473
473
  projects: () => projects,
474
+ projectConfig: () => projectConfig,
474
475
  idCounters: () => idCounters,
475
476
  events: () => events,
476
477
  epicsRelations: () => epicsRelations,
@@ -488,6 +489,10 @@ var projects = sqliteTable("projects", {
488
489
  createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
489
490
  updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
490
491
  });
492
+ var projectConfig = sqliteTable("project_config", {
493
+ key: text("key").primaryKey(),
494
+ value: text("value").notNull()
495
+ });
491
496
  var epics = sqliteTable("epics", {
492
497
  id: text("id").primaryKey(),
493
498
  projectId: text("project_id").notNull(),
@@ -590,6 +595,35 @@ var dependenciesRelations = relations(dependencies, ({ one }) => ({
590
595
  // src/db/client.ts
591
596
  import { existsSync, mkdirSync, rmSync } from "fs";
592
597
  import { join } from "path";
598
+
599
+ // src/types/index.ts
600
+ var TASK_STATUSES = ["todo", "in_progress", "completed", "wont_fix", "archived"];
601
+ var EPIC_STATUSES = ["todo", "in_progress", "completed", "archived"];
602
+ var DEFAULT_PRIORITY = 2;
603
+ var DEFAULT_TASK_STATUS = "todo";
604
+ var DEFAULT_EPIC_STATUS = "todo";
605
+ var PAGINATION_DEFAULTS = {
606
+ LIST_PAGE_SIZE: 50,
607
+ SEARCH_PAGE_SIZE: 20,
608
+ HISTORY_PAGE_SIZE: 50,
609
+ DEFAULT_PAGE: 1
610
+ };
611
+ var VALID_SORT_FIELDS = ["created", "updated", "title", "priority", "status"];
612
+ var LIST_ENTITY_TYPES = ["epic", "task", "subtask"];
613
+ var SEARCH_ENTITY_TYPES = ["epic", "task", "subtask", "comment"];
614
+ var PROJECT_CONFIG_KEYS = ["issue_prefix", "epic_prefix", "comment_prefix"];
615
+ var PROJECT_CONFIG_DEFAULTS = {
616
+ issue_prefix: "TREK",
617
+ epic_prefix: "EPIC",
618
+ comment_prefix: "CMT"
619
+ };
620
+ var ENTITY_CONFIG_KEY_MAP = {
621
+ task: "issue_prefix",
622
+ epic: "epic_prefix",
623
+ comment: "comment_prefix"
624
+ };
625
+
626
+ // src/db/client.ts
593
627
  var TREKKER_DIR = ".trekker";
594
628
  var DB_NAME = "trekker.db";
595
629
  function getTrekkerDir(cwd = process.cwd()) {
@@ -621,6 +655,7 @@ function getDb(cwd = process.cwd()) {
621
655
  dbInstance = drizzle(sqliteInstance, { schema: exports_schema });
622
656
  migrateSearchIndex(sqliteInstance);
623
657
  migrateHistoryTable(sqliteInstance);
658
+ migrateProjectConfigTable(sqliteInstance);
624
659
  return dbInstance;
625
660
  }
626
661
  function createDb(cwd = process.cwd()) {
@@ -636,6 +671,12 @@ function createDb(cwd = process.cwd()) {
636
671
  updated_at INTEGER NOT NULL
637
672
  )
638
673
  `);
674
+ sqliteInstance.run(`
675
+ CREATE TABLE IF NOT EXISTS project_config (
676
+ key TEXT PRIMARY KEY,
677
+ value TEXT NOT NULL
678
+ )
679
+ `);
639
680
  sqliteInstance.run(`
640
681
  CREATE TABLE IF NOT EXISTS epics (
641
682
  id TEXT PRIMARY KEY,
@@ -690,6 +731,7 @@ function createDb(cwd = process.cwd()) {
690
731
  sqliteInstance.run("INSERT OR IGNORE INTO id_counters (entity_type, counter) VALUES ('task', 0)");
691
732
  sqliteInstance.run("INSERT OR IGNORE INTO id_counters (entity_type, counter) VALUES ('epic', 0)");
692
733
  sqliteInstance.run("INSERT OR IGNORE INTO id_counters (entity_type, counter) VALUES ('comment', 0)");
734
+ seedProjectConfig(sqliteInstance);
693
735
  sqliteInstance.run(`
694
736
  CREATE TABLE IF NOT EXISTS events (
695
737
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -953,6 +995,20 @@ function migrateHistoryTable(sqlite) {
953
995
  createHistoryTriggers(sqlite);
954
996
  }
955
997
  }
998
+ function seedProjectConfig(sqlite) {
999
+ for (const [key, value] of Object.entries(PROJECT_CONFIG_DEFAULTS)) {
1000
+ sqlite.query("INSERT OR IGNORE INTO project_config (key, value) VALUES (?, ?)").run(key, value);
1001
+ }
1002
+ }
1003
+ function migrateProjectConfigTable(sqlite) {
1004
+ sqlite.run(`
1005
+ CREATE TABLE IF NOT EXISTS project_config (
1006
+ key TEXT PRIMARY KEY,
1007
+ value TEXT NOT NULL
1008
+ )
1009
+ `);
1010
+ seedProjectConfig(sqlite);
1011
+ }
956
1012
  function rebuildSearchIndex() {
957
1013
  getDb();
958
1014
  const sqlite = getSqliteInstance();
@@ -988,35 +1044,106 @@ function deleteDb(cwd = process.cwd()) {
988
1044
  }
989
1045
 
990
1046
  // src/utils/id-generator.ts
991
- import { eq, sql } from "drizzle-orm";
1047
+ import { eq as eq2, sql } from "drizzle-orm";
992
1048
 
993
- // src/types/index.ts
994
- var TASK_STATUSES = ["todo", "in_progress", "completed", "wont_fix", "archived"];
995
- var EPIC_STATUSES = ["todo", "in_progress", "completed", "archived"];
996
- var DEFAULT_PRIORITY = 2;
997
- var DEFAULT_TASK_STATUS = "todo";
998
- var DEFAULT_EPIC_STATUS = "todo";
999
- var PAGINATION_DEFAULTS = {
1000
- LIST_PAGE_SIZE: 50,
1001
- SEARCH_PAGE_SIZE: 20,
1002
- HISTORY_PAGE_SIZE: 50,
1003
- DEFAULT_PAGE: 1
1004
- };
1005
- var VALID_SORT_FIELDS = ["created", "updated", "title", "priority", "status"];
1006
- var LIST_ENTITY_TYPES = ["epic", "task", "subtask"];
1007
- var SEARCH_ENTITY_TYPES = ["epic", "task", "subtask", "comment"];
1008
- var PREFIX_MAP = {
1009
- task: "TREK",
1010
- epic: "EPIC",
1011
- comment: "CMT"
1012
- };
1049
+ // src/services/config.ts
1050
+ import { eq } from "drizzle-orm";
1051
+ var PROJECT_CONFIG_KEY_SET = new Set(PROJECT_CONFIG_KEYS);
1052
+ var PREFIX_PATTERN = /^[A-Z][A-Z0-9]*$/;
1053
+ function assertProjectConfigKey(key) {
1054
+ if (!PROJECT_CONFIG_KEY_SET.has(key)) {
1055
+ throw new Error(`Unsupported config key: ${key}. Valid keys: ${PROJECT_CONFIG_KEYS.join(", ")}`);
1056
+ }
1057
+ }
1058
+ function normalizePrefixValue(value) {
1059
+ const normalized = value.trim().toUpperCase();
1060
+ if (!normalized) {
1061
+ throw new Error("Prefix value is required.");
1062
+ }
1063
+ if (!PREFIX_PATTERN.test(normalized)) {
1064
+ throw new Error("Invalid prefix value. Use uppercase letters and numbers only, and start with a letter.");
1065
+ }
1066
+ return normalized;
1067
+ }
1068
+ function validateUniquePrefixes(config) {
1069
+ const seen = new Map;
1070
+ for (const key of PROJECT_CONFIG_KEYS) {
1071
+ const prefix = config[key];
1072
+ const existingKey = seen.get(prefix);
1073
+ if (existingKey) {
1074
+ throw new Error(`Prefix values must be unique. ${existingKey} and ${key} cannot both use ${prefix}.`);
1075
+ }
1076
+ seen.set(prefix, key);
1077
+ }
1078
+ }
1079
+ function resolveProjectConfig(updates, baseConfig = PROJECT_CONFIG_DEFAULTS) {
1080
+ const normalizedUpdates = {};
1081
+ if (updates) {
1082
+ for (const rawKey of PROJECT_CONFIG_KEYS) {
1083
+ const rawValue = updates[rawKey];
1084
+ if (rawValue === undefined) {
1085
+ continue;
1086
+ }
1087
+ normalizedUpdates[rawKey] = normalizePrefixValue(rawValue);
1088
+ }
1089
+ }
1090
+ const nextConfig = {
1091
+ ...baseConfig,
1092
+ ...normalizedUpdates
1093
+ };
1094
+ validateUniquePrefixes(nextConfig);
1095
+ return nextConfig;
1096
+ }
1097
+ function listProjectConfig() {
1098
+ const db = getDb();
1099
+ const rows = db.select().from(projectConfig).all();
1100
+ const config = { ...PROJECT_CONFIG_DEFAULTS };
1101
+ for (const row of rows) {
1102
+ if (!PROJECT_CONFIG_KEY_SET.has(row.key)) {
1103
+ continue;
1104
+ }
1105
+ assertProjectConfigKey(row.key);
1106
+ config[row.key] = row.value;
1107
+ }
1108
+ return config;
1109
+ }
1110
+ function listProjectConfigEntries() {
1111
+ const config = listProjectConfig();
1112
+ return PROJECT_CONFIG_KEYS.map((key) => ({ key, value: config[key] }));
1113
+ }
1114
+ function getProjectConfigValue(key) {
1115
+ return listProjectConfig()[key];
1116
+ }
1117
+ function upsertProjectConfigValue(key, value) {
1118
+ const db = getDb();
1119
+ const existing = db.select().from(projectConfig).where(eq(projectConfig.key, key)).get();
1120
+ if (existing) {
1121
+ db.update(projectConfig).set({ value }).where(eq(projectConfig.key, key)).run();
1122
+ return;
1123
+ }
1124
+ db.insert(projectConfig).values({ key, value }).run();
1125
+ }
1126
+ function setProjectConfigValues(updates) {
1127
+ const currentConfig = listProjectConfig();
1128
+ const nextConfig = resolveProjectConfig(updates, currentConfig);
1129
+ for (const key of PROJECT_CONFIG_KEYS) {
1130
+ const nextValue = nextConfig[key];
1131
+ if (nextValue !== currentConfig[key]) {
1132
+ upsertProjectConfigValue(key, nextValue);
1133
+ }
1134
+ }
1135
+ return nextConfig;
1136
+ }
1137
+ function unsetProjectConfigValue(key) {
1138
+ return setProjectConfigValues({ [key]: PROJECT_CONFIG_DEFAULTS[key] })[key];
1139
+ }
1013
1140
 
1014
1141
  // src/utils/id-generator.ts
1015
1142
  function generateId(entityType) {
1016
1143
  const db = getDb();
1017
- const prefix = PREFIX_MAP[entityType];
1018
- db.update(idCounters).set({ counter: sql`${idCounters.counter} + 1` }).where(eq(idCounters.entityType, entityType)).run();
1019
- const result = db.select({ counter: idCounters.counter }).from(idCounters).where(eq(idCounters.entityType, entityType)).get();
1144
+ const prefix = getProjectConfigValue(ENTITY_CONFIG_KEY_MAP[entityType]);
1145
+ db.update(idCounters).set({ counter: sql`${idCounters.counter} + 1` }).where(eq2(idCounters.entityType, entityType)).run();
1146
+ const result = db.select({ counter: idCounters.counter }).from(idCounters).where(eq2(idCounters.entityType, entityType)).get();
1020
1147
  if (!result) {
1021
1148
  throw new Error(`Counter not found for entity type: ${entityType}`);
1022
1149
  }
@@ -1028,10 +1155,13 @@ function generateUuid() {
1028
1155
 
1029
1156
  // src/services/project.ts
1030
1157
  import { basename } from "path";
1031
- function initProject(cwd = process.cwd()) {
1158
+ function initProject(cwd = process.cwd(), configOverrides) {
1032
1159
  if (isTrekkerInitialized(cwd)) {
1033
1160
  throw new Error("Trekker is already initialized in this directory.");
1034
1161
  }
1162
+ if (configOverrides) {
1163
+ resolveProjectConfig(configOverrides);
1164
+ }
1035
1165
  const db = createDb(cwd);
1036
1166
  const projectName = basename(cwd);
1037
1167
  const now = new Date;
@@ -1041,6 +1171,9 @@ function initProject(cwd = process.cwd()) {
1041
1171
  createdAt: now,
1042
1172
  updatedAt: now
1043
1173
  }).run();
1174
+ if (configOverrides) {
1175
+ setProjectConfigValues(configOverrides);
1176
+ }
1044
1177
  }
1045
1178
  function wipeProject(cwd = process.cwd()) {
1046
1179
  if (!isTrekkerInitialized(cwd)) {
@@ -1734,15 +1867,26 @@ function formatPaginatedCommentList(result) {
1734
1867
  return lines.join(`
1735
1868
  `);
1736
1869
  }
1870
+ function formatProjectConfigList(entries) {
1871
+ if (entries.length === 0) {
1872
+ return "No config values found.";
1873
+ }
1874
+ return entries.map((entry) => `${entry.key}=${entry.value}`).join(`
1875
+ `);
1876
+ }
1737
1877
 
1738
1878
  // src/commands/init.ts
1739
- var initCommand = new Command("init").description("Initialize Trekker in the current directory").action(() => {
1879
+ var initCommand = new Command("init").description("Initialize Trekker in the current directory").option("--issue-prefix <value>", "Prefix for tasks and subtasks (default: TREK)").option("--epic-prefix <value>", "Prefix for epics (default: EPIC)").option("--comment-prefix <value>", "Prefix for comments (default: CMT)").action((options) => {
1740
1880
  try {
1741
1881
  if (isTrekkerInitialized()) {
1742
1882
  error("Trekker is already initialized in this directory.");
1743
1883
  process.exit(1);
1744
1884
  }
1745
- initProject();
1885
+ initProject(process.cwd(), {
1886
+ issue_prefix: options.issuePrefix,
1887
+ epic_prefix: options.epicPrefix,
1888
+ comment_prefix: options.commentPrefix
1889
+ });
1746
1890
  success("Trekker initialized successfully.");
1747
1891
  } catch (err) {
1748
1892
  handleCommandError(err);
@@ -1788,7 +1932,7 @@ function confirm(prompt) {
1788
1932
  import { Command as Command3 } from "commander";
1789
1933
 
1790
1934
  // src/services/epic.ts
1791
- import { eq as eq2, and, isNull, desc, sql as sql2 } from "drizzle-orm";
1935
+ import { eq as eq3, and, isNull, desc, sql as sql2 } from "drizzle-orm";
1792
1936
  function createEpic(input) {
1793
1937
  const db = getDb();
1794
1938
  const project = db.select().from(projects).get();
@@ -1812,7 +1956,7 @@ function createEpic(input) {
1812
1956
  }
1813
1957
  function getEpic(id) {
1814
1958
  const db = getDb();
1815
- return db.select().from(epics).where(eq2(epics.id, id)).get();
1959
+ return db.select().from(epics).where(eq3(epics.id, id)).get();
1816
1960
  }
1817
1961
  function listEpics(options) {
1818
1962
  const db = getDb();
@@ -1821,7 +1965,7 @@ function listEpics(options) {
1821
1965
  const offset = (page - 1) * limit;
1822
1966
  let where;
1823
1967
  if (options?.status) {
1824
- where = eq2(epics.status, options.status);
1968
+ where = eq3(epics.status, options.status);
1825
1969
  }
1826
1970
  const countRow = db.select({ count: sql2`count(*)` }).from(epics).where(where).get();
1827
1971
  const total = countRow?.count ?? 0;
@@ -1849,7 +1993,7 @@ function updateEpic(id, input) {
1849
1993
  if (input.priority !== undefined) {
1850
1994
  updates.priority = input.priority;
1851
1995
  }
1852
- db.update(epics).set(updates).where(eq2(epics.id, id)).run();
1996
+ db.update(epics).set(updates).where(eq3(epics.id, id)).run();
1853
1997
  const updated = getEpic(id);
1854
1998
  if (!updated) {
1855
1999
  throw new Error(`Epic not found after update: ${id}`);
@@ -1862,7 +2006,7 @@ function deleteEpic(id) {
1862
2006
  if (!existing) {
1863
2007
  throw new Error(`Epic not found: ${id}`);
1864
2008
  }
1865
- db.delete(epics).where(eq2(epics.id, id)).run();
2009
+ db.delete(epics).where(eq3(epics.id, id)).run();
1866
2010
  }
1867
2011
  function completeEpic(id) {
1868
2012
  const db = getDb();
@@ -1873,20 +2017,20 @@ function completeEpic(id) {
1873
2017
  if (existing.status === "completed") {
1874
2018
  throw new Error(`Epic is already completed: ${id}`);
1875
2019
  }
1876
- const epicTasks = db.select().from(tasks).where(and(eq2(tasks.epicId, id), isNull(tasks.parentTaskId))).all();
2020
+ const epicTasks = db.select().from(tasks).where(and(eq3(tasks.epicId, id), isNull(tasks.parentTaskId))).all();
1877
2021
  const taskIds = epicTasks.map((t) => t.id);
1878
2022
  let subtaskCount = 0;
1879
2023
  if (taskIds.length > 0) {
1880
2024
  for (const taskId of taskIds) {
1881
- const subtasks = db.select().from(tasks).where(eq2(tasks.parentTaskId, taskId)).all();
2025
+ const subtasks = db.select().from(tasks).where(eq3(tasks.parentTaskId, taskId)).all();
1882
2026
  subtaskCount += subtasks.length;
1883
2027
  if (subtasks.length > 0) {
1884
- db.update(tasks).set({ status: "archived", updatedAt: new Date }).where(eq2(tasks.parentTaskId, taskId)).run();
2028
+ db.update(tasks).set({ status: "archived", updatedAt: new Date }).where(eq3(tasks.parentTaskId, taskId)).run();
1885
2029
  }
1886
2030
  }
1887
- db.update(tasks).set({ status: "archived", updatedAt: new Date }).where(and(eq2(tasks.epicId, id), isNull(tasks.parentTaskId))).run();
2031
+ db.update(tasks).set({ status: "archived", updatedAt: new Date }).where(and(eq3(tasks.epicId, id), isNull(tasks.parentTaskId))).run();
1888
2032
  }
1889
- db.update(epics).set({ status: "completed", updatedAt: new Date }).where(eq2(epics.id, id)).run();
2033
+ db.update(epics).set({ status: "completed", updatedAt: new Date }).where(eq3(epics.id, id)).run();
1890
2034
  return {
1891
2035
  epic: id,
1892
2036
  status: "completed",
@@ -2082,7 +2226,7 @@ epicCommand.command("complete <epic-id>").description("Complete an epic and arch
2082
2226
  import { Command as Command4 } from "commander";
2083
2227
 
2084
2228
  // src/services/task.ts
2085
- import { eq as eq3, and as and2, isNull as isNull2, desc as desc2, sql as sql3 } from "drizzle-orm";
2229
+ import { eq as eq4, and as and2, isNull as isNull2, desc as desc2, sql as sql3 } from "drizzle-orm";
2086
2230
  function createTask(input) {
2087
2231
  const db = getDb();
2088
2232
  const project = db.select().from(projects).get();
@@ -2090,13 +2234,13 @@ function createTask(input) {
2090
2234
  throw new Error("Project not found. Run 'trekker init' first.");
2091
2235
  }
2092
2236
  if (input.epicId) {
2093
- const epic = db.select().from(epics).where(eq3(epics.id, input.epicId)).get();
2237
+ const epic = db.select().from(epics).where(eq4(epics.id, input.epicId)).get();
2094
2238
  if (!epic) {
2095
2239
  throw new Error(`Epic not found: ${input.epicId}`);
2096
2240
  }
2097
2241
  }
2098
2242
  if (input.parentTaskId) {
2099
- const parent = db.select().from(tasks).where(eq3(tasks.id, input.parentTaskId)).get();
2243
+ const parent = db.select().from(tasks).where(eq4(tasks.id, input.parentTaskId)).get();
2100
2244
  if (!parent) {
2101
2245
  throw new Error(`Parent task not found: ${input.parentTaskId}`);
2102
2246
  }
@@ -2121,7 +2265,7 @@ function createTask(input) {
2121
2265
  }
2122
2266
  function getTask(id) {
2123
2267
  const db = getDb();
2124
- return db.select().from(tasks).where(eq3(tasks.id, id)).get();
2268
+ return db.select().from(tasks).where(eq4(tasks.id, id)).get();
2125
2269
  }
2126
2270
  function listTasks(options) {
2127
2271
  const db = getDb();
@@ -2130,15 +2274,15 @@ function listTasks(options) {
2130
2274
  const offset = (page - 1) * limit;
2131
2275
  const conditions = [];
2132
2276
  if (options?.status) {
2133
- conditions.push(eq3(tasks.status, options.status));
2277
+ conditions.push(eq4(tasks.status, options.status));
2134
2278
  }
2135
2279
  if (options?.epicId) {
2136
- conditions.push(eq3(tasks.epicId, options.epicId));
2280
+ conditions.push(eq4(tasks.epicId, options.epicId));
2137
2281
  }
2138
2282
  if (options?.parentTaskId === null) {
2139
2283
  conditions.push(isNull2(tasks.parentTaskId));
2140
2284
  } else if (options?.parentTaskId) {
2141
- conditions.push(eq3(tasks.parentTaskId, options.parentTaskId));
2285
+ conditions.push(eq4(tasks.parentTaskId, options.parentTaskId));
2142
2286
  }
2143
2287
  let where;
2144
2288
  if (conditions.length > 0) {
@@ -2154,7 +2298,7 @@ function listSubtasks(parentTaskId, options) {
2154
2298
  const limit = options?.limit ?? PAGINATION_DEFAULTS.LIST_PAGE_SIZE;
2155
2299
  const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
2156
2300
  const offset = (page - 1) * limit;
2157
- const where = eq3(tasks.parentTaskId, parentTaskId);
2301
+ const where = eq4(tasks.parentTaskId, parentTaskId);
2158
2302
  const countRow = db.select({ count: sql3`count(*)` }).from(tasks).where(where).get();
2159
2303
  const total = countRow?.count ?? 0;
2160
2304
  const items = db.select().from(tasks).where(where).orderBy(desc2(tasks.createdAt)).limit(limit).offset(offset).all();
@@ -2167,7 +2311,7 @@ function updateTask(id, input) {
2167
2311
  throw new Error(`Task not found: ${id}`);
2168
2312
  }
2169
2313
  if (input.epicId) {
2170
- const epic = db.select().from(epics).where(eq3(epics.id, input.epicId)).get();
2314
+ const epic = db.select().from(epics).where(eq4(epics.id, input.epicId)).get();
2171
2315
  if (!epic) {
2172
2316
  throw new Error(`Epic not found: ${input.epicId}`);
2173
2317
  }
@@ -2193,7 +2337,7 @@ function updateTask(id, input) {
2193
2337
  if (input.epicId !== undefined) {
2194
2338
  updates.epicId = input.epicId;
2195
2339
  }
2196
- db.update(tasks).set(updates).where(eq3(tasks.id, id)).run();
2340
+ db.update(tasks).set(updates).where(eq4(tasks.id, id)).run();
2197
2341
  const updated = getTask(id);
2198
2342
  if (!updated) {
2199
2343
  throw new Error(`Task not found after update: ${id}`);
@@ -2206,7 +2350,7 @@ function deleteTask(id) {
2206
2350
  if (!existing) {
2207
2351
  throw new Error(`Task not found: ${id}`);
2208
2352
  }
2209
- db.delete(tasks).where(eq3(tasks.id, id)).run();
2353
+ db.delete(tasks).where(eq4(tasks.id, id)).run();
2210
2354
  }
2211
2355
 
2212
2356
  // src/commands/task.ts
@@ -2378,10 +2522,10 @@ subtaskCommand.command("delete <subtask-id>").description("Delete a subtask").ac
2378
2522
  import { Command as Command6 } from "commander";
2379
2523
 
2380
2524
  // src/services/comment.ts
2381
- import { eq as eq4, desc as desc3, sql as sql4 } from "drizzle-orm";
2525
+ import { eq as eq5, desc as desc3, sql as sql4 } from "drizzle-orm";
2382
2526
  function createComment(input) {
2383
2527
  const db = getDb();
2384
- const task = db.select().from(tasks).where(eq4(tasks.id, input.taskId)).get();
2528
+ const task = db.select().from(tasks).where(eq5(tasks.id, input.taskId)).get();
2385
2529
  if (!task) {
2386
2530
  throw new Error(`Task not found: ${input.taskId}`);
2387
2531
  }
@@ -2400,18 +2544,18 @@ function createComment(input) {
2400
2544
  }
2401
2545
  function getComment(id) {
2402
2546
  const db = getDb();
2403
- return db.select().from(comments).where(eq4(comments.id, id)).get();
2547
+ return db.select().from(comments).where(eq5(comments.id, id)).get();
2404
2548
  }
2405
2549
  function listComments(taskId, options) {
2406
2550
  const db = getDb();
2407
2551
  const limit = options?.limit ?? PAGINATION_DEFAULTS.LIST_PAGE_SIZE;
2408
2552
  const page = options?.page ?? PAGINATION_DEFAULTS.DEFAULT_PAGE;
2409
2553
  const offset = (page - 1) * limit;
2410
- const task = db.select().from(tasks).where(eq4(tasks.id, taskId)).get();
2554
+ const task = db.select().from(tasks).where(eq5(tasks.id, taskId)).get();
2411
2555
  if (!task) {
2412
2556
  throw new Error(`Task not found: ${taskId}`);
2413
2557
  }
2414
- const where = eq4(comments.taskId, taskId);
2558
+ const where = eq5(comments.taskId, taskId);
2415
2559
  const countRow = db.select({ count: sql4`count(*)` }).from(comments).where(where).get();
2416
2560
  const total = countRow?.count ?? 0;
2417
2561
  const items = db.select().from(comments).where(where).orderBy(desc3(comments.createdAt)).limit(limit).offset(offset).all();
@@ -2426,7 +2570,7 @@ function updateComment(id, input) {
2426
2570
  db.update(comments).set({
2427
2571
  content: input.content,
2428
2572
  updatedAt: new Date
2429
- }).where(eq4(comments.id, id)).run();
2573
+ }).where(eq5(comments.id, id)).run();
2430
2574
  const updated = getComment(id);
2431
2575
  if (!updated) {
2432
2576
  throw new Error(`Comment not found after update: ${id}`);
@@ -2439,7 +2583,7 @@ function deleteComment(id) {
2439
2583
  if (!existing) {
2440
2584
  throw new Error(`Comment not found: ${id}`);
2441
2585
  }
2442
- db.delete(comments).where(eq4(comments.id, id)).run();
2586
+ db.delete(comments).where(eq5(comments.id, id)).run();
2443
2587
  }
2444
2588
 
2445
2589
  // src/commands/comment.ts
@@ -2491,21 +2635,21 @@ commentCommand.command("delete <comment-id>").description("Delete a comment").ac
2491
2635
  import { Command as Command7 } from "commander";
2492
2636
 
2493
2637
  // src/services/dependency.ts
2494
- import { eq as eq5 } from "drizzle-orm";
2638
+ import { eq as eq6 } from "drizzle-orm";
2495
2639
  function addDependency(taskId, dependsOnId) {
2496
2640
  const db = getDb();
2497
- const task = db.select().from(tasks).where(eq5(tasks.id, taskId)).get();
2641
+ const task = db.select().from(tasks).where(eq6(tasks.id, taskId)).get();
2498
2642
  if (!task) {
2499
2643
  throw new Error(`Task not found: ${taskId}`);
2500
2644
  }
2501
- const dependsOnTask = db.select().from(tasks).where(eq5(tasks.id, dependsOnId)).get();
2645
+ const dependsOnTask = db.select().from(tasks).where(eq6(tasks.id, dependsOnId)).get();
2502
2646
  if (!dependsOnTask) {
2503
2647
  throw new Error(`Task not found: ${dependsOnId}`);
2504
2648
  }
2505
2649
  if (taskId === dependsOnId) {
2506
2650
  throw new Error("A task cannot depend on itself.");
2507
2651
  }
2508
- const existing = db.select().from(dependencies).where(eq5(dependencies.taskId, taskId)).all().find((d) => d.dependsOnId === dependsOnId);
2652
+ const existing = db.select().from(dependencies).where(eq6(dependencies.taskId, taskId)).all().find((d) => d.dependsOnId === dependsOnId);
2509
2653
  if (existing) {
2510
2654
  throw new Error(`Dependency already exists: ${taskId} \u2192 ${dependsOnId}`);
2511
2655
  }
@@ -2525,22 +2669,22 @@ function addDependency(taskId, dependsOnId) {
2525
2669
  }
2526
2670
  function removeDependency(taskId, dependsOnId) {
2527
2671
  const db = getDb();
2528
- const existing = db.select().from(dependencies).where(eq5(dependencies.taskId, taskId)).all().find((d) => d.dependsOnId === dependsOnId);
2672
+ const existing = db.select().from(dependencies).where(eq6(dependencies.taskId, taskId)).all().find((d) => d.dependsOnId === dependsOnId);
2529
2673
  if (!existing) {
2530
2674
  throw new Error(`Dependency not found: ${taskId} \u2192 ${dependsOnId}`);
2531
2675
  }
2532
- db.delete(dependencies).where(eq5(dependencies.id, existing.id)).run();
2676
+ db.delete(dependencies).where(eq6(dependencies.id, existing.id)).run();
2533
2677
  }
2534
2678
  function getDependencies(taskId) {
2535
2679
  const db = getDb();
2536
2680
  const dependsOn = db.select({
2537
2681
  taskId: dependencies.taskId,
2538
2682
  dependsOnId: dependencies.dependsOnId
2539
- }).from(dependencies).where(eq5(dependencies.taskId, taskId)).all();
2683
+ }).from(dependencies).where(eq6(dependencies.taskId, taskId)).all();
2540
2684
  const blocks = db.select({
2541
2685
  taskId: dependencies.taskId,
2542
2686
  dependsOnId: dependencies.dependsOnId
2543
- }).from(dependencies).where(eq5(dependencies.dependsOnId, taskId)).all();
2687
+ }).from(dependencies).where(eq6(dependencies.dependsOnId, taskId)).all();
2544
2688
  return { dependsOn, blocks };
2545
2689
  }
2546
2690
  function wouldCreateCycle(taskId, dependsOnId) {
@@ -2559,7 +2703,7 @@ function wouldCreateCycle(taskId, dependsOnId) {
2559
2703
  continue;
2560
2704
  }
2561
2705
  visited.add(current);
2562
- const deps = db.select({ dependsOnId: dependencies.dependsOnId }).from(dependencies).where(eq5(dependencies.taskId, current)).all();
2706
+ const deps = db.select({ dependsOnId: dependencies.dependsOnId }).from(dependencies).where(eq6(dependencies.taskId, current)).all();
2563
2707
  for (const dep of deps) {
2564
2708
  if (!visited.has(dep.dependsOnId)) {
2565
2709
  stack.push(dep.dependsOnId);
@@ -2624,7 +2768,7 @@ trekker wipe -y # Remove all data
2624
2768
  1. Set status to \`in_progress\` when starting, \`completed\` when done
2625
2769
  2. Add summary comment before marking task complete
2626
2770
  3. Use \`--toon\` flag for token-efficient output
2627
- 4. When epic is done, use \`trekker epic complete EPIC-n\` to archive all tasks
2771
+ 4. When an epic is done, use \`trekker epic complete <epic-id>\` to archive all tasks
2628
2772
  5. Write detailed descriptions with implementation plans - future agents need this context
2629
2773
  6. Comments are your external memory - add summaries before context resets
2630
2774
 
@@ -2633,41 +2777,47 @@ trekker wipe -y # Remove all data
2633
2777
  ### Epics (features/milestones)
2634
2778
  trekker epic create -t "Title" [-d "desc"] [-p 0-5]
2635
2779
  trekker epic list [--status <status>]
2636
- trekker epic show EPIC-1
2637
- trekker epic update EPIC-1 [-t "Title"] [-d "desc"] [-p 0-5] [-s <status>]
2638
- trekker epic complete EPIC-1 # Complete and archive all tasks
2639
- trekker epic delete EPIC-1
2780
+ trekker epic show <epic-id>
2781
+ trekker epic update <epic-id> [-t "Title"] [-d "desc"] [-p 0-5] [-s <status>]
2782
+ trekker epic complete <epic-id> # Complete and archive all tasks
2783
+ trekker epic delete <epic-id>
2640
2784
 
2641
2785
  ### Tasks
2642
- trekker task create -t "Title" [-d "desc"] [-p 0-5] [-e EPIC-1] [--tags "a,b"]
2643
- trekker task list [--status <status>] [--epic EPIC-1]
2644
- trekker task show TREK-1
2645
- trekker task update TREK-1 [-t "Title"] [-d "desc"] [-p 0-5] [-s <status>] [--tags "a,b"] [-e EPIC-1] [--no-epic]
2646
- trekker task delete TREK-1
2786
+ trekker task create -t "Title" [-d "desc"] [-p 0-5] [-e <epic-id>] [--tags "a,b"]
2787
+ trekker task list [--status <status>] [--epic <epic-id>]
2788
+ trekker task show <task-id>
2789
+ trekker task update <task-id> [-t "Title"] [-d "desc"] [-p 0-5] [-s <status>] [--tags "a,b"] [-e <epic-id>] [--no-epic]
2790
+ trekker task delete <task-id>
2647
2791
 
2648
2792
  ### Subtasks
2649
- trekker subtask create TREK-1 -t "Title" [-d "desc"] [-p 0-5]
2650
- trekker subtask list TREK-1
2651
- trekker subtask update TREK-2 [-t "Title"] [-d "desc"] [-p 0-5] [-s <status>]
2652
- trekker subtask delete TREK-2
2793
+ trekker subtask create <task-id> -t "Title" [-d "desc"] [-p 0-5]
2794
+ trekker subtask list <task-id>
2795
+ trekker subtask update <subtask-id> [-t "Title"] [-d "desc"] [-p 0-5] [-s <status>]
2796
+ trekker subtask delete <subtask-id>
2653
2797
 
2654
2798
  ### Comments (external memory)
2655
- trekker comment add TREK-1 -a "agent" -c "content"
2656
- trekker comment list TREK-1
2657
- trekker comment update CMT-1 -c "new content"
2658
- trekker comment delete CMT-1
2799
+ trekker comment add <task-id> -a "agent" -c "content"
2800
+ trekker comment list <task-id>
2801
+ trekker comment update <comment-id> -c "new content"
2802
+ trekker comment delete <comment-id>
2659
2803
 
2660
2804
  ### Dependencies
2661
- trekker dep add TREK-2 TREK-1 # TREK-2 depends on TREK-1
2662
- trekker dep remove TREK-2 TREK-1
2663
- trekker dep list TREK-1
2805
+ trekker dep add <task-id> <depends-on-id>
2806
+ trekker dep remove <task-id> <depends-on-id>
2807
+ trekker dep list <task-id>
2808
+
2809
+ ### Project Config
2810
+ trekker config list
2811
+ trekker config get issue_prefix
2812
+ trekker config set issue_prefix ABC
2813
+ trekker config unset issue_prefix
2664
2814
 
2665
2815
  ### Search (full-text across all entities)
2666
2816
  trekker search "query" [--type epic,task,subtask,comment] [--status <status>]
2667
2817
  trekker search "auth bug" --type task --limit 10
2668
2818
 
2669
2819
  ### History (audit log of all changes)
2670
- trekker history [--entity TREK-1] [--type task] [--action create,update,delete]
2820
+ trekker history [--entity <entity-id>] [--type task] [--action create,update,delete]
2671
2821
  trekker history --since 2025-01-01 --limit 20
2672
2822
 
2673
2823
  ### List (unified view of all items)
@@ -2706,17 +2856,17 @@ flowchart TD
2706
2856
 
2707
2857
  ## Session Start
2708
2858
  trekker --toon task list --status in_progress
2709
- trekker --toon comment list TREK-1
2859
+ trekker --toon comment list <task-id>
2710
2860
 
2711
2861
  ## Working
2712
- trekker task update TREK-1 -s in_progress
2713
- trekker comment add TREK-1 -a "agent" -c "Analysis: ..."
2862
+ trekker task update <task-id> -s in_progress
2863
+ trekker comment add <task-id> -a "agent" -c "Analysis: ..."
2714
2864
  # ... do work ...
2715
- trekker comment add TREK-1 -a "agent" -c "Summary: implemented X in files A, B"
2716
- trekker task update TREK-1 -s completed
2865
+ trekker comment add <task-id> -a "agent" -c "Summary: implemented X in files A, B"
2866
+ trekker task update <task-id> -s completed
2717
2867
 
2718
2868
  ## Before Context Reset
2719
- trekker comment add TREK-1 -a "agent" -c "Checkpoint: done A,B. Next: C. Files: x.ts, y.ts"
2869
+ trekker comment add <task-id> -a "agent" -c "Checkpoint: done A,B. Next: C. Files: x.ts, y.ts"
2720
2870
 
2721
2871
  ## Writing Effective Descriptions
2722
2872
 
@@ -3228,7 +3378,7 @@ function getHistory(options) {
3228
3378
 
3229
3379
  // src/commands/history.ts
3230
3380
  import_dayjs.default.extend(import_customParseFormat.default);
3231
- var historyCommand = new Command11("history").description("View history of all changes (creates, updates, deletes)").option("--entity <id>", "Filter by entity ID (e.g., TREK-1, EPIC-1)").option("--type <types>", "Filter by type: epic,task,subtask,comment,dependency (comma-separated)").option("--action <actions>", "Filter by action: create,update,delete (comma-separated)").option("--since <date>", "Events after date (YYYY-MM-DD)").option("--until <date>", "Events before date (YYYY-MM-DD)").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((options) => {
3381
+ var historyCommand = new Command11("history").description("View history of all changes (creates, updates, deletes)").option("--entity <id>", "Filter by entity ID").option("--type <types>", "Filter by type: epic,task,subtask,comment,dependency (comma-separated)").option("--action <actions>", "Filter by action: create,update,delete (comma-separated)").option("--since <date>", "Events after date (YYYY-MM-DD)").option("--until <date>", "Events before date (YYYY-MM-DD)").option("--limit <n>", "Results per page (default: 50)", "50").option("--page <n>", "Page number (default: 1)", "1").action((options) => {
3232
3382
  try {
3233
3383
  const types = parseCommaSeparated(options.type);
3234
3384
  const actions = parseCommaSeparated(options.action);
@@ -3651,10 +3801,56 @@ Page ${result.page} of ${totalPages}`);
3651
3801
  return lines.join(`
3652
3802
  `);
3653
3803
  }
3804
+
3805
+ // src/commands/config.ts
3806
+ import { Command as Command14 } from "commander";
3807
+ var configCommand = new Command14("config").description("Manage project configuration");
3808
+ configCommand.command("list").description("List supported project config values").action(() => {
3809
+ try {
3810
+ if (isToonMode()) {
3811
+ output(listProjectConfig());
3812
+ return;
3813
+ }
3814
+ outputResult(listProjectConfigEntries(), formatProjectConfigList);
3815
+ } catch (err) {
3816
+ handleCommandError(err);
3817
+ }
3818
+ });
3819
+ configCommand.command("get <key>").description("Get a project config value").action((key) => {
3820
+ try {
3821
+ assertProjectConfigKey(key);
3822
+ const value = getProjectConfigValue(key);
3823
+ if (isToonMode()) {
3824
+ output({ key, value });
3825
+ return;
3826
+ }
3827
+ console.log(value);
3828
+ } catch (err) {
3829
+ handleCommandError(err);
3830
+ }
3831
+ });
3832
+ configCommand.command("set <key> <value>").description("Set a project config value").action((key, value) => {
3833
+ try {
3834
+ assertProjectConfigKey(key);
3835
+ const config = setProjectConfigValues({ [key]: value });
3836
+ success(`Config updated: ${key}=${config[key]}`, { key, value: config[key] });
3837
+ } catch (err) {
3838
+ handleCommandError(err);
3839
+ }
3840
+ });
3841
+ configCommand.command("unset <key>").description("Reset a project config value to its default").action((key) => {
3842
+ try {
3843
+ assertProjectConfigKey(key);
3844
+ const value = unsetProjectConfigValue(key);
3845
+ success(`Config reset: ${key}=${value}`, { key, value });
3846
+ } catch (err) {
3847
+ handleCommandError(err);
3848
+ }
3849
+ });
3654
3850
  // package.json
3655
3851
  var package_default = {
3656
3852
  name: "@obsfx/trekker",
3657
- version: "1.10.2",
3853
+ version: "1.11.0",
3658
3854
  description: "A CLI-based issue tracker built for AI coding agents. Stores tasks, epics, and dependencies in a local SQLite database.",
3659
3855
  type: "module",
3660
3856
  main: "dist/index.js",
@@ -3708,26 +3904,26 @@ var package_default = {
3708
3904
  bun: ">=1.0.0"
3709
3905
  },
3710
3906
  dependencies: {
3711
- "@toon-format/toon": "^2.1.0",
3712
- commander: "^13.1.0",
3713
- dayjs: "^1.11.19",
3714
- "drizzle-orm": "^0.38.4"
3907
+ "@toon-format/toon": "2.1.0",
3908
+ commander: "13.1.0",
3909
+ dayjs: "1.11.19",
3910
+ "drizzle-orm": "0.38.4"
3715
3911
  },
3716
3912
  devDependencies: {
3717
- "@types/bun": "^1.2.2",
3718
- "drizzle-kit": "^0.30.4",
3719
- eslint: "^10.0.2",
3720
- "eslint-config-prettier": "^10.1.8",
3721
- "eslint-plugin-unicorn": "^63.0.0",
3722
- knip: "^5.85.0",
3723
- prettier: "^3.8.1",
3724
- typescript: "^5.7.3",
3725
- "typescript-eslint": "^8.56.1"
3913
+ "@types/bun": "1.2.2",
3914
+ "drizzle-kit": "0.30.4",
3915
+ eslint: "10.0.2",
3916
+ "eslint-config-prettier": "10.1.8",
3917
+ "eslint-plugin-unicorn": "63.0.0",
3918
+ knip: "6.3.1",
3919
+ prettier: "3.8.1",
3920
+ typescript: "5.7.3",
3921
+ "typescript-eslint": "8.56.1"
3726
3922
  }
3727
3923
  };
3728
3924
 
3729
3925
  // src/index.ts
3730
- var program = new Command14;
3926
+ var program = new Command15;
3731
3927
  program.name("trekker").description("CLI-based issue tracker for coding agents").version(package_default.version).option("--toon", "Output in TOON format").hook("preAction", (thisCommand) => {
3732
3928
  const opts = thisCommand.opts();
3733
3929
  if (opts.toon) {
@@ -3747,4 +3943,5 @@ program.addCommand(searchCommand);
3747
3943
  program.addCommand(historyCommand);
3748
3944
  program.addCommand(listCommand);
3749
3945
  program.addCommand(readyCommand);
3946
+ program.addCommand(configCommand);
3750
3947
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obsfx/trekker",
3
- "version": "1.10.2",
3
+ "version": "1.11.0",
4
4
  "description": "A CLI-based issue tracker built for AI coding agents. Stores tasks, epics, and dependencies in a local SQLite database.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,20 +54,20 @@
54
54
  "bun": ">=1.0.0"
55
55
  },
56
56
  "dependencies": {
57
- "@toon-format/toon": "^2.1.0",
58
- "commander": "^13.1.0",
59
- "dayjs": "^1.11.19",
60
- "drizzle-orm": "^0.38.4"
57
+ "@toon-format/toon": "2.1.0",
58
+ "commander": "13.1.0",
59
+ "dayjs": "1.11.19",
60
+ "drizzle-orm": "0.38.4"
61
61
  },
62
62
  "devDependencies": {
63
- "@types/bun": "^1.2.2",
64
- "drizzle-kit": "^0.30.4",
65
- "eslint": "^10.0.2",
66
- "eslint-config-prettier": "^10.1.8",
67
- "eslint-plugin-unicorn": "^63.0.0",
68
- "knip": "^5.85.0",
69
- "prettier": "^3.8.1",
70
- "typescript": "^5.7.3",
71
- "typescript-eslint": "^8.56.1"
63
+ "@types/bun": "1.2.2",
64
+ "drizzle-kit": "0.30.4",
65
+ "eslint": "10.0.2",
66
+ "eslint-config-prettier": "10.1.8",
67
+ "eslint-plugin-unicorn": "63.0.0",
68
+ "knip": "6.3.1",
69
+ "prettier": "3.8.1",
70
+ "typescript": "5.7.3",
71
+ "typescript-eslint": "8.56.1"
72
72
  }
73
73
  }