knowns 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3313,10 +3313,10 @@ var require_stringify = __commonJS({
3313
3313
  data = Object.assign({}, file3.data, data);
3314
3314
  const open = opts.delimiters[0];
3315
3315
  const close = opts.delimiters[1];
3316
- const matter5 = engine.stringify(data, options2).trim();
3316
+ const matter6 = engine.stringify(data, options2).trim();
3317
3317
  let buf = "";
3318
- if (matter5 !== "{}") {
3319
- buf = newline(open) + newline(matter5) + newline(close);
3318
+ if (matter6 !== "{}") {
3319
+ buf = newline(open) + newline(matter6) + newline(close);
3320
3320
  }
3321
3321
  if (typeof file3.excerpt === "string" && file3.excerpt !== "") {
3322
3322
  if (str2.indexOf(file3.excerpt.trim()) === -1) {
@@ -3422,19 +3422,19 @@ var require_gray_matter = __commonJS({
3422
3422
  var toFile = require_to_file();
3423
3423
  var parse4 = require_parse();
3424
3424
  var utils = require_utils();
3425
- function matter5(input, options2) {
3425
+ function matter6(input, options2) {
3426
3426
  if (input === "") {
3427
3427
  return { data: {}, content: input, excerpt: "", orig: input };
3428
3428
  }
3429
3429
  let file3 = toFile(input);
3430
- const cached2 = matter5.cache[file3.content];
3430
+ const cached2 = matter6.cache[file3.content];
3431
3431
  if (!options2) {
3432
3432
  if (cached2) {
3433
3433
  file3 = Object.assign({}, cached2);
3434
3434
  file3.orig = cached2.orig;
3435
3435
  return file3;
3436
3436
  }
3437
- matter5.cache[file3.content] = file3;
3437
+ matter6.cache[file3.content] = file3;
3438
3438
  }
3439
3439
  return parseMatter(file3, options2);
3440
3440
  }
@@ -3456,7 +3456,7 @@ var require_gray_matter = __commonJS({
3456
3456
  }
3457
3457
  str2 = str2.slice(openLen);
3458
3458
  const len = str2.length;
3459
- const language = matter5.language(str2, opts);
3459
+ const language = matter6.language(str2, opts);
3460
3460
  if (language.name) {
3461
3461
  file3.language = language.name;
3462
3462
  str2 = str2.slice(language.raw.length);
@@ -3491,24 +3491,24 @@ var require_gray_matter = __commonJS({
3491
3491
  }
3492
3492
  return file3;
3493
3493
  }
3494
- matter5.engines = engines2;
3495
- matter5.stringify = function(file3, data, options2) {
3496
- if (typeof file3 === "string") file3 = matter5(file3, options2);
3494
+ matter6.engines = engines2;
3495
+ matter6.stringify = function(file3, data, options2) {
3496
+ if (typeof file3 === "string") file3 = matter6(file3, options2);
3497
3497
  return stringify(file3, data, options2);
3498
3498
  };
3499
- matter5.read = function(filepath, options2) {
3499
+ matter6.read = function(filepath, options2) {
3500
3500
  const str2 = fs.readFileSync(filepath, "utf8");
3501
- const file3 = matter5(str2, options2);
3501
+ const file3 = matter6(str2, options2);
3502
3502
  file3.path = filepath;
3503
3503
  return file3;
3504
3504
  };
3505
- matter5.test = function(str2, options2) {
3505
+ matter6.test = function(str2, options2) {
3506
3506
  return utils.startsWith(str2, defaults(options2).delimiters[0]);
3507
3507
  };
3508
- matter5.language = function(str2, options2) {
3508
+ matter6.language = function(str2, options2) {
3509
3509
  const opts = defaults(options2);
3510
3510
  const open = opts.delimiters[0];
3511
- if (matter5.test(str2)) {
3511
+ if (matter6.test(str2)) {
3512
3512
  str2 = str2.slice(open.length);
3513
3513
  }
3514
3514
  const language = str2.slice(0, str2.search(/\r?\n/));
@@ -3517,11 +3517,11 @@ var require_gray_matter = __commonJS({
3517
3517
  name: language ? language.trim() : ""
3518
3518
  };
3519
3519
  };
3520
- matter5.cache = {};
3521
- matter5.clearCache = function() {
3522
- matter5.cache = {};
3520
+ matter6.cache = {};
3521
+ matter6.clearCache = function() {
3522
+ matter6.cache = {};
3523
3523
  };
3524
- module2.exports = matter5;
3524
+ module2.exports = matter6;
3525
3525
  }
3526
3526
  });
3527
3527
 
@@ -30381,7 +30381,7 @@ var require_view = __commonJS({
30381
30381
  var dirname4 = path.dirname;
30382
30382
  var basename3 = path.basename;
30383
30383
  var extname = path.extname;
30384
- var join14 = path.join;
30384
+ var join19 = path.join;
30385
30385
  var resolve = path.resolve;
30386
30386
  module2.exports = View;
30387
30387
  function View(name, options2) {
@@ -30443,12 +30443,12 @@ var require_view = __commonJS({
30443
30443
  };
30444
30444
  View.prototype.resolve = function resolve2(dir, file3) {
30445
30445
  var ext = this.ext;
30446
- var path2 = join14(dir, file3);
30446
+ var path2 = join19(dir, file3);
30447
30447
  var stat3 = tryStat(path2);
30448
30448
  if (stat3 && stat3.isFile()) {
30449
30449
  return path2;
30450
30450
  }
30451
- path2 = join14(dir, basename3(file3, ext), "index" + ext);
30451
+ path2 = join19(dir, basename3(file3, ext), "index" + ext);
30452
30452
  stat3 = tryStat(path2);
30453
30453
  if (stat3 && stat3.isFile()) {
30454
30454
  return path2;
@@ -32186,11 +32186,11 @@ var require_router = __commonJS({
32186
32186
  var slice = Array.prototype.slice;
32187
32187
  var flatten = Array.prototype.flat;
32188
32188
  var methods = METHODS.map((method) => method.toLowerCase());
32189
- module2.exports = Router;
32189
+ module2.exports = Router9;
32190
32190
  module2.exports.Route = Route;
32191
- function Router(options2) {
32192
- if (!(this instanceof Router)) {
32193
- return new Router(options2);
32191
+ function Router9(options2) {
32192
+ if (!(this instanceof Router9)) {
32193
+ return new Router9(options2);
32194
32194
  }
32195
32195
  const opts = options2 || {};
32196
32196
  function router(req, res, next) {
@@ -32204,9 +32204,9 @@ var require_router = __commonJS({
32204
32204
  router.stack = [];
32205
32205
  return router;
32206
32206
  }
32207
- Router.prototype = function() {
32207
+ Router9.prototype = function() {
32208
32208
  };
32209
- Router.prototype.param = function param(name, fn) {
32209
+ Router9.prototype.param = function param(name, fn) {
32210
32210
  if (!name) {
32211
32211
  throw new TypeError("argument name is required");
32212
32212
  }
@@ -32226,7 +32226,7 @@ var require_router = __commonJS({
32226
32226
  params.push(fn);
32227
32227
  return this;
32228
32228
  };
32229
- Router.prototype.handle = function handle(req, res, callback) {
32229
+ Router9.prototype.handle = function handle(req, res, callback) {
32230
32230
  if (!callback) {
32231
32231
  throw new TypeError("argument callback is required");
32232
32232
  }
@@ -32353,7 +32353,7 @@ var require_router = __commonJS({
32353
32353
  }
32354
32354
  }
32355
32355
  };
32356
- Router.prototype.use = function use(handler) {
32356
+ Router9.prototype.use = function use(handler) {
32357
32357
  let offset = 0;
32358
32358
  let path = "/";
32359
32359
  if (typeof handler !== "function") {
@@ -32386,7 +32386,7 @@ var require_router = __commonJS({
32386
32386
  }
32387
32387
  return this;
32388
32388
  };
32389
- Router.prototype.route = function route(path) {
32389
+ Router9.prototype.route = function route(path) {
32390
32390
  const route2 = new Route(path);
32391
32391
  const layer = new Layer(path, {
32392
32392
  sensitive: this.caseSensitive,
@@ -32401,7 +32401,7 @@ var require_router = __commonJS({
32401
32401
  return route2;
32402
32402
  };
32403
32403
  methods.concat("all").forEach(function(method) {
32404
- Router.prototype[method] = function(path) {
32404
+ Router9.prototype[method] = function(path) {
32405
32405
  const route = this.route(path);
32406
32406
  route[method].apply(route, slice.call(arguments, 1));
32407
32407
  return this;
@@ -32584,7 +32584,7 @@ var require_application = __commonJS({
32584
32584
  var compileTrust = require_utils4().compileTrust;
32585
32585
  var resolve = __require("node:path").resolve;
32586
32586
  var once = require_once();
32587
- var Router = require_router();
32587
+ var Router9 = require_router();
32588
32588
  var slice = Array.prototype.slice;
32589
32589
  var flatten = Array.prototype.flat;
32590
32590
  var app = exports2 = module2.exports = {};
@@ -32600,7 +32600,7 @@ var require_application = __commonJS({
32600
32600
  enumerable: true,
32601
32601
  get: function getrouter() {
32602
32602
  if (router === null) {
32603
- router = new Router({
32603
+ router = new Router9({
32604
32604
  caseSensitive: this.enabled("case sensitive routing"),
32605
32605
  strict: this.enabled("strict routing")
32606
32606
  });
@@ -34093,7 +34093,7 @@ var require_send = __commonJS({
34093
34093
  var Stream = __require("stream");
34094
34094
  var util = __require("util");
34095
34095
  var extname = path.extname;
34096
- var join14 = path.join;
34096
+ var join19 = path.join;
34097
34097
  var normalize = path.normalize;
34098
34098
  var resolve = path.resolve;
34099
34099
  var sep = path.sep;
@@ -34265,7 +34265,7 @@ var require_send = __commonJS({
34265
34265
  return res;
34266
34266
  }
34267
34267
  parts = path2.split(sep);
34268
- path2 = normalize(join14(root, path2));
34268
+ path2 = normalize(join19(root, path2));
34269
34269
  } else {
34270
34270
  if (UP_PATH_REGEXP.test(path2)) {
34271
34271
  debug('malicious path "%s"', path2);
@@ -34398,7 +34398,7 @@ var require_send = __commonJS({
34398
34398
  if (err) return self.onStatError(err);
34399
34399
  return self.error(404);
34400
34400
  }
34401
- var p = join14(path2, self._index[i]);
34401
+ var p = join19(path2, self._index[i]);
34402
34402
  debug('stat "%s"', p);
34403
34403
  fs.stat(p, function(err2, stat3) {
34404
34404
  if (err2) return next(err2);
@@ -35138,7 +35138,7 @@ var require_express = __commonJS({
35138
35138
  var EventEmitter = __require("node:events").EventEmitter;
35139
35139
  var mixin = require_merge_descriptors();
35140
35140
  var proto2 = require_application();
35141
- var Router = require_router();
35141
+ var Router9 = require_router();
35142
35142
  var req = require_request();
35143
35143
  var res = require_response();
35144
35144
  exports2 = module2.exports = createApplication;
@@ -35160,8 +35160,8 @@ var require_express = __commonJS({
35160
35160
  exports2.application = proto2;
35161
35161
  exports2.request = req;
35162
35162
  exports2.response = res;
35163
- exports2.Route = Router.Route;
35164
- exports2.Router = Router;
35163
+ exports2.Route = Router9.Route;
35164
+ exports2.Router = Router9;
35165
35165
  exports2.json = bodyParser.json;
35166
35166
  exports2.raw = bodyParser.raw;
35167
35167
  exports2.static = require_serve_static();
@@ -38791,7 +38791,7 @@ import { existsSync as existsSync2 } from "node:fs";
38791
38791
  import { basename, join as join4 } from "node:path";
38792
38792
 
38793
38793
  // src/storage/file-store.ts
38794
- import { mkdir as mkdir2, readdir, unlink as unlink2 } from "node:fs/promises";
38794
+ import { mkdir as mkdir2, readdir, rename, unlink as unlink2 } from "node:fs/promises";
38795
38795
  import { join as join2 } from "node:path";
38796
38796
 
38797
38797
  // src/models/task.ts
@@ -39279,6 +39279,8 @@ var FileStore = class {
39279
39279
  // .knowns/
39280
39280
  tasksPath;
39281
39281
  // .knowns/tasks/
39282
+ archivePath;
39283
+ // .knowns/archive/
39282
39284
  projectPath;
39283
39285
  // .knowns/config.json
39284
39286
  timeEntriesPath;
@@ -39288,6 +39290,7 @@ var FileStore = class {
39288
39290
  this.projectRoot = projectRoot;
39289
39291
  this.basePath = join2(projectRoot, ".knowns");
39290
39292
  this.tasksPath = join2(this.basePath, "tasks");
39293
+ this.archivePath = join2(this.basePath, "archive");
39291
39294
  this.projectPath = join2(this.basePath, "config.json");
39292
39295
  this.timeEntriesPath = join2(this.basePath, "time-entries.json");
39293
39296
  this.versionStore = new VersionStore(projectRoot);
@@ -39462,6 +39465,7 @@ var FileStore = class {
39462
39465
  await this.addSubtask(newParent, id);
39463
39466
  }
39464
39467
  }
39468
+ const oldFileName = updates.title && updates.title !== task.title ? await this.findTaskFile(id) : null;
39465
39469
  const updatedTask = {
39466
39470
  ...task,
39467
39471
  ...updates,
@@ -39470,6 +39474,10 @@ var FileStore = class {
39470
39474
  updatedAt: /* @__PURE__ */ new Date()
39471
39475
  };
39472
39476
  await this.versionStore.recordVersion(id, task, updatedTask, author);
39477
+ if (oldFileName) {
39478
+ const oldFilePath = join2(this.tasksPath, oldFileName);
39479
+ await unlink2(oldFilePath);
39480
+ }
39473
39481
  await this.saveTask(updatedTask);
39474
39482
  if (updates.timeEntries !== void 0) {
39475
39483
  const allTimeEntries = await this.loadTimeEntries();
@@ -39489,6 +39497,66 @@ var FileStore = class {
39489
39497
  const filePath = join2(this.tasksPath, fileName);
39490
39498
  await unlink2(filePath);
39491
39499
  }
39500
+ /**
39501
+ * Archive task - move to archive folder
39502
+ */
39503
+ async archiveTask(id) {
39504
+ const task = await this.getTask(id);
39505
+ if (!task) {
39506
+ throw new Error(`Task ${id} not found`);
39507
+ }
39508
+ const fileName = await this.findTaskFile(id);
39509
+ if (!fileName) {
39510
+ throw new Error(`Task file for ${id} not found`);
39511
+ }
39512
+ await mkdir2(this.archivePath, { recursive: true });
39513
+ const oldPath = join2(this.tasksPath, fileName);
39514
+ const newPath = join2(this.archivePath, fileName);
39515
+ await rename(oldPath, newPath);
39516
+ return task;
39517
+ }
39518
+ /**
39519
+ * Unarchive task - restore from archive folder
39520
+ */
39521
+ async unarchiveTask(id) {
39522
+ try {
39523
+ const files = await readdir(this.archivePath);
39524
+ const taskFile = files.find((f) => f.startsWith(`task-${id} -`));
39525
+ if (!taskFile) {
39526
+ throw new Error(`Archived task ${id} not found`);
39527
+ }
39528
+ const archiveFilePath = join2(this.archivePath, taskFile);
39529
+ const tasksFilePath = join2(this.tasksPath, taskFile);
39530
+ await rename(archiveFilePath, tasksFilePath);
39531
+ return await this.getTask(id);
39532
+ } catch (error46) {
39533
+ console.error(`Failed to unarchive task ${id}:`, error46);
39534
+ throw error46;
39535
+ }
39536
+ }
39537
+ /**
39538
+ * Batch archive tasks - archive all done tasks older than specified duration
39539
+ * @param olderThanMs - Duration in milliseconds (tasks done before now - olderThanMs will be archived)
39540
+ * @returns Array of archived tasks
39541
+ */
39542
+ async batchArchiveTasks(olderThanMs) {
39543
+ const allTasks = await this.getAllTasks();
39544
+ const now = Date.now();
39545
+ const cutoffTime = now - olderThanMs;
39546
+ const tasksToArchive = allTasks.filter(
39547
+ (task) => task.status === "done" && new Date(task.updatedAt).getTime() < cutoffTime
39548
+ );
39549
+ const archivedTasks = [];
39550
+ for (const task of tasksToArchive) {
39551
+ try {
39552
+ await this.archiveTask(task.id);
39553
+ archivedTasks.push(task);
39554
+ } catch (error46) {
39555
+ console.error(`Failed to archive task ${task.id}:`, error46);
39556
+ }
39557
+ }
39558
+ return archivedTasks;
39559
+ }
39492
39560
  /**
39493
39561
  * Save task to file
39494
39562
  */
@@ -39507,11 +39575,18 @@ var FileStore = class {
39507
39575
  }
39508
39576
  /**
39509
39577
  * Find task file by ID
39578
+ * Warns if multiple files with same ID are found (indicates a bug)
39510
39579
  */
39511
39580
  async findTaskFile(id) {
39512
39581
  try {
39513
39582
  const files = await readdir(this.tasksPath);
39514
- return files.find((f) => f.startsWith(`task-${id} -`)) || null;
39583
+ const matchingFiles = files.filter((f) => f.startsWith(`task-${id} -`));
39584
+ if (matchingFiles.length > 1) {
39585
+ console.warn(
39586
+ `Warning: Found ${matchingFiles.length} files for task-${id}. This may cause data inconsistency. Files: ${matchingFiles.join(", ")}`
39587
+ );
39588
+ }
39589
+ return matchingFiles[0] || null;
39515
39590
  } catch (_error) {
39516
39591
  return null;
39517
39592
  }
@@ -40148,10 +40223,10 @@ When starting a new session or working on an unfamiliar project:
40148
40223
  knowns doc list --plain
40149
40224
 
40150
40225
  # 2. Read essential project docs (prioritize these)
40151
- knowns doc view "README" --plain # Project overview
40152
- knowns doc view "ARCHITECTURE" --plain # System design
40153
- knowns doc view "CONVENTIONS" --plain # Coding standards
40154
- knowns doc view "API" --plain # API specifications
40226
+ knowns doc "README" --plain # Project overview
40227
+ knowns doc "ARCHITECTURE" --plain # System design
40228
+ knowns doc "CONVENTIONS" --plain # Coding standards
40229
+ knowns doc "API" --plain # API specifications
40155
40230
 
40156
40231
  # 3. Review current task backlog
40157
40232
  knowns task list --plain
@@ -40162,17 +40237,17 @@ knowns task list --status in-progress --plain
40162
40237
 
40163
40238
  \`\`\`bash
40164
40239
  # 1. View the task details
40165
- knowns task view <id> --plain
40240
+ knowns task <id> --plain
40166
40241
 
40167
40242
  # 2. Follow ALL refs in the task (see Reference System section)
40168
- # @.knowns/tasks/task-44 - ... \u2192 knowns task view 44 --plain
40169
- # @.knowns/docs/patterns/module.md \u2192 knowns doc view "patterns/module" --plain
40243
+ # @.knowns/tasks/task-44 - ... \u2192 knowns task 44 --plain
40244
+ # @.knowns/docs/patterns/module.md \u2192 knowns doc "patterns/module" --plain
40170
40245
 
40171
40246
  # 3. Search for additional related documentation
40172
40247
  knowns search "<keywords from task>" --type doc --plain
40173
40248
 
40174
40249
  # 4. Read ALL related docs before planning
40175
- knowns doc view "<related-doc>" --plain
40250
+ knowns doc "<related-doc>" --plain
40176
40251
 
40177
40252
  # 5. Check for similar completed tasks (learn from history)
40178
40253
  knowns search "<keywords>" --type task --status done --plain
@@ -40210,10 +40285,15 @@ Tasks and docs can contain **references** to other tasks/docs. AI agents MUST un
40210
40285
 
40211
40286
  ### Reference Formats
40212
40287
 
40213
- | Type | User Input | System Output | CLI Command |
40214
- |------|------------|---------------|-------------|
40215
- | **Task ref** | \`@task-<id>\` | \`@.knowns/tasks/task-<id> - <title>.md\` | \`knowns task view <id> --plain\` |
40216
- | **Doc ref** | \`@doc/<path>\` | \`@.knowns/docs/<path>.md\` | \`knowns doc view "<path>" --plain\` |
40288
+ | Type | When Writing (Input) | When Reading (Output) | CLI Command |
40289
+ |------|---------------------|----------------------|-------------|
40290
+ | **Task ref** | \`@task-<id>\` | \`@.knowns/tasks/task-<id> - <title>.md\` | \`knowns task <id> --plain\` |
40291
+ | **Doc ref** | \`@doc/<path>\` | \`@.knowns/docs/<path>.md\` | \`knowns doc <path> --plain\` |
40292
+
40293
+ > **CRITICAL for AI Agents**:
40294
+ > - When **WRITING** refs (in descriptions, plans, notes): Use \`@task-<id>\` and \`@doc/<path>\`
40295
+ > - When **READING** output from \`--plain\`: You'll see \`@.knowns/tasks/...\` and \`@.knowns/docs/...\`
40296
+ > - **NEVER write** the output format (\`@.knowns/...\`) - always use input format (\`@task-<id>\`, \`@doc/<path>\`)
40217
40297
 
40218
40298
  ### How to Follow Refs
40219
40299
 
@@ -40225,16 +40305,16 @@ When you read a task and see refs in system output format, follow them:
40225
40305
  # @.knowns/docs/patterns/module.md
40226
40306
 
40227
40307
  # Follow task ref (extract ID from task-<id>)
40228
- knowns task view 44 --plain
40308
+ knowns task 44 --plain
40229
40309
 
40230
40310
  # Follow doc ref (extract path, remove .md)
40231
- knowns doc view "patterns/module" --plain
40311
+ knowns doc "patterns/module" --plain
40232
40312
  \`\`\`
40233
40313
 
40234
40314
  ### Parsing Rules
40235
40315
 
40236
- 1. **Task refs**: \`@.knowns/tasks/task-<id> - ...\` \u2192 extract \`<id>\` \u2192 \`knowns task view <id> --plain\`
40237
- 2. **Doc refs**: \`@.knowns/docs/<path>.md\` \u2192 extract \`<path>\` \u2192 \`knowns doc view "<path>" --plain\`
40316
+ 1. **Task refs**: \`@.knowns/tasks/task-<id> - ...\` \u2192 extract \`<id>\` \u2192 \`knowns task <id> --plain\`
40317
+ 2. **Doc refs**: \`@.knowns/docs/<path>.md\` \u2192 extract \`<path>\` \u2192 \`knowns doc "<path>" --plain\`
40238
40318
 
40239
40319
  ### Recursive Following
40240
40320
 
@@ -40260,20 +40340,20 @@ Task 42
40260
40340
 
40261
40341
  \`\`\`bash
40262
40342
  # 1. Read the task
40263
- $ knowns task view 42 --plain
40343
+ $ knowns task 42 --plain
40264
40344
 
40265
40345
  # Output contains:
40266
40346
  # @.knowns/tasks/task-44 - CLI Task Create Command.md
40267
40347
  # @.knowns/docs/README.md
40268
40348
 
40269
40349
  # 2. Follow task ref
40270
- $ knowns task view 44 --plain
40350
+ $ knowns task 44 --plain
40271
40351
 
40272
40352
  # 3. Follow doc ref
40273
- $ knowns doc view "README" --plain
40353
+ $ knowns doc "README" --plain
40274
40354
 
40275
40355
  # 4. If README contains more refs, follow them too
40276
- # @.knowns/docs/patterns/module.md \u2192 knowns doc view "patterns/module" --plain
40356
+ # @.knowns/docs/patterns/module.md \u2192 knowns doc "patterns/module" --plain
40277
40357
 
40278
40358
  # Now you have complete context
40279
40359
  \`\`\`
@@ -40292,7 +40372,12 @@ knowns init [name]
40292
40372
  knowns task create "Title" -d "Description" --ac "Criterion 1" --ac "Criterion 2"
40293
40373
 
40294
40374
  # View task (ALWAYS use --plain for AI)
40295
- knowns task view <id> --plain
40375
+ knowns task <id> --plain # Shorthand
40376
+ knowns task view <id> --plain # Full command
40377
+
40378
+ # View doc (ALWAYS use --plain for AI)
40379
+ knowns doc <path> --plain # Shorthand
40380
+ knowns doc view "<path>" --plain # Full command
40296
40381
 
40297
40382
  # List tasks
40298
40383
  knowns task list --plain
@@ -40322,9 +40407,9 @@ $ knowns doc list --plain
40322
40407
  # DOC: email-templates.md
40323
40408
 
40324
40409
  # 0b. Read essential project docs
40325
- $ knowns doc view "README" --plain
40326
- $ knowns doc view "ARCHITECTURE" --plain
40327
- $ knowns doc view "CONVENTIONS" --plain
40410
+ $ knowns doc "README" --plain
40411
+ $ knowns doc "ARCHITECTURE" --plain
40412
+ $ knowns doc "CONVENTIONS" --plain
40328
40413
 
40329
40414
  # Now the agent understands project context and conventions
40330
40415
 
@@ -40356,7 +40441,7 @@ $ knowns search "password security" --type doc --plain
40356
40441
  # DOC: email-templates.md (score: 0.78)
40357
40442
 
40358
40443
  # 4. Read the documentation
40359
- $ knowns doc view "security-patterns" --plain
40444
+ $ knowns doc "security-patterns" --plain
40360
40445
 
40361
40446
  # 5. Create implementation plan (SHARE WITH USER, WAIT FOR APPROVAL)
40362
40447
  $ knowns task edit AUTH-042 --plan $'1. Review security patterns (see @doc/security-patterns)
@@ -40436,8 +40521,8 @@ knowns time start <id>
40436
40521
  knowns search "authentication" --type doc --plain
40437
40522
 
40438
40523
  # View relevant documents
40439
- knowns doc view "API Guidelines" --plain
40440
- knowns doc view "Security Patterns" --plain
40524
+ knowns doc "API Guidelines" --plain
40525
+ knowns doc "Security Patterns" --plain
40441
40526
 
40442
40527
  # Also check for similar completed tasks
40443
40528
  knowns search "auth" --type task --status done --plain
@@ -40524,7 +40609,8 @@ knowns task edit <id> --notes "Implementation summary"
40524
40609
  knowns task edit <id> --append-notes "Progress update"
40525
40610
 
40526
40611
  # View & List
40527
- knowns task view <id> --plain # ALWAYS use --plain
40612
+ knowns task <id> --plain # Shorthand (ALWAYS use --plain)
40613
+ knowns task view <id> --plain # Full command
40528
40614
  knowns task list --plain
40529
40615
  knowns task list --status in-progress --plain
40530
40616
  knowns task list --assignee @me --plain
@@ -40555,7 +40641,8 @@ knowns time report --by-label --csv > report.csv
40555
40641
  # List & View
40556
40642
  knowns doc list --plain
40557
40643
  knowns doc list --tag architecture --plain
40558
- knowns doc view "Doc Name" --plain
40644
+ knowns doc <path> --plain # Shorthand (ALWAYS use --plain)
40645
+ knowns doc view "<path>" --plain # Full command
40559
40646
 
40560
40647
  # Create (with optional folder)
40561
40648
  knowns doc create "Title" -d "Description" -t "tags"
@@ -40662,7 +40749,7 @@ Implemented JWT auth using jsonwebtoken library.
40662
40749
  | \`Error: No active timer\` | Calling \`time stop\` without active timer | Start timer first: \`knowns time start <id>\` |
40663
40750
  | \`Error: Timer already running\` | Starting timer when one is active | Stop current: \`knowns time stop\` |
40664
40751
  | \`Error: Invalid status\` | Wrong status format | Use lowercase with hyphens: \`in-progress\`, not \`In Progress\` |
40665
- | \`Error: AC index out of range\` | Checking non-existent criterion | View task first: \`knowns task view <id> --plain\` |
40752
+ | \`Error: AC index out of range\` | Checking non-existent criterion | View task first: \`knowns task <id> --plain\` |
40666
40753
  | \`Error: Document not found\` | Wrong document name | Run \`knowns doc list --plain\` to find correct name |
40667
40754
  | \`Error: Not initialized\` | Running commands without init | Run \`knowns init\` first |
40668
40755
 
@@ -40676,7 +40763,7 @@ knowns --version
40676
40763
  knowns status
40677
40764
 
40678
40765
  # View raw task data (for debugging)
40679
- knowns task view <id> --json
40766
+ knowns task <id> --json
40680
40767
 
40681
40768
  # Check timer status
40682
40769
  knowns time status
@@ -40740,6 +40827,7 @@ Use **lowercase with hyphens**:
40740
40827
  | Plan without checking docs | Read docs before planning |
40741
40828
  | Ignore similar completed tasks | Search done tasks for patterns |
40742
40829
  | Missing doc links in description/plan | Link docs using \`@doc/<path>\` |
40830
+ | Write refs as \`@.knowns/docs/...\` or \`@.knowns/tasks/...\` | Use input format: \`@doc/<path>\`, \`@task-<id>\` |
40743
40831
  | Forget \`--plain\` flag | Always use \`--plain\` for AI |
40744
40832
  | Code before plan approval | Share plan, WAIT for approval |
40745
40833
  | Mark done without all criteria | Check ALL criteria first |
@@ -40747,8 +40835,8 @@ Use **lowercase with hyphens**:
40747
40835
  | Use \`"In Progress"\` or \`"Done"\` | Use \`in-progress\`, \`done\` |
40748
40836
  | Use \`@yourself\` | Use \`@me\` or specific username |
40749
40837
  | Ignore refs in task description | Follow ALL refs (\`@.knowns/...\`) before planning |
40750
- | See \`@.knowns/docs/...\` but don't read | Use \`knowns doc view "<path>" --plain\` |
40751
- | See \`@.knowns/tasks/task-X\` but don't check | Use \`knowns task view X --plain\` for context |
40838
+ | See \`@.knowns/docs/...\` but don't read | Use \`knowns doc "<path>" --plain\` |
40839
+ | See \`@.knowns/tasks/task-X\` but don't check | Use \`knowns task X --plain\` for context |
40752
40840
  | Follow only first-level refs | Recursively follow nested refs until complete |
40753
40841
 
40754
40842
  ---
@@ -40806,7 +40894,7 @@ EOF
40806
40894
  - [ ] ALL refs in task followed (\`@.knowns/...\`)
40807
40895
  - [ ] Nested refs recursively followed until complete context gathered
40808
40896
  - [ ] Related docs searched: \`knowns search "keyword" --type doc --plain\`
40809
- - [ ] ALL relevant docs read: \`knowns doc view "Doc Name" --plain\`
40897
+ - [ ] ALL relevant docs read: \`knowns doc "Doc Name" --plain\`
40810
40898
  - [ ] Similar done tasks reviewed for patterns
40811
40899
  - [ ] Task assigned to self: \`-a @me\`
40812
40900
  - [ ] Status set to in-progress: \`-s in-progress\`
@@ -40835,16 +40923,16 @@ EOF
40835
40923
  \`\`\`bash
40836
40924
  # === AGENT INITIALIZATION (Once per session) ===
40837
40925
  knowns doc list --plain
40838
- knowns doc view "README" --plain
40839
- knowns doc view "ARCHITECTURE" --plain
40840
- knowns doc view "CONVENTIONS" --plain
40926
+ knowns doc "README" --plain
40927
+ knowns doc "ARCHITECTURE" --plain
40928
+ knowns doc "CONVENTIONS" --plain
40841
40929
 
40842
40930
  # === FULL WORKFLOW ===
40843
40931
  knowns task create "Title" -d "Description" --ac "Criterion"
40844
40932
  knowns task edit <id> -s in-progress -a @me
40845
40933
  knowns time start <id>
40846
40934
  knowns search "keyword" --type doc --plain
40847
- knowns doc view "Doc Name" --plain
40935
+ knowns doc "Doc Name" --plain
40848
40936
  knowns search "keyword" --type task --status done --plain # Learn from history
40849
40937
  knowns task edit <id> --plan $'1. Step (see @doc/file)\\n2. Step'
40850
40938
  # ... wait for approval, then implement ...
@@ -40854,7 +40942,7 @@ knowns time stop
40854
40942
  knowns task edit <id> -s done
40855
40943
 
40856
40944
  # === VIEW & SEARCH ===
40857
- knowns task view <id> --plain
40945
+ knowns task <id> --plain # Shorthand for view
40858
40946
  knowns task list --plain
40859
40947
  knowns task list --status in-progress --assignee @me --plain
40860
40948
  knowns search "query" --plain
@@ -40868,7 +40956,7 @@ knowns time report --from "2025-12-01" --to "2025-12-31"
40868
40956
 
40869
40957
  # === DOCUMENTATION ===
40870
40958
  knowns doc list --plain
40871
- knowns doc view "path/doc-name" --plain
40959
+ knowns doc "path/doc-name" --plain # Shorthand for view
40872
40960
  knowns doc create "Title" -d "Description" -t "tags" -f "folder"
40873
40961
  knowns doc edit "doc-name" -c "New content"
40874
40962
  knowns doc edit "doc-name" -a "Appended content"
@@ -41219,6 +41307,8 @@ function findProjectRoot(startPath = process.cwd()) {
41219
41307
  // src/utils/mention-refs.ts
41220
41308
  var TASK_MENTION_REGEX = /@task-(\d+)/g;
41221
41309
  var DOC_MENTION_REGEX = /@docs?\/([^\s|)]+)/g;
41310
+ var OUTPUT_TASK_REGEX = /@\.knowns\/tasks\/task-(\d+)(?:\s*-\s*[^@\n]+?\.md|\.md)/g;
41311
+ var OUTPUT_DOC_REGEX = /@\.knowns\/docs\/([^\s@]+?)\.md(?:\s*-\s*[^@\n]+)?/g;
41222
41312
  function sanitizeTitle(title) {
41223
41313
  return title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").trim().slice(0, 50);
41224
41314
  }
@@ -41251,6 +41341,12 @@ function buildTaskMap(tasks) {
41251
41341
  }
41252
41342
  return map2;
41253
41343
  }
41344
+ function normalizeRefs(text) {
41345
+ let result = text;
41346
+ result = result.replace(new RegExp(OUTPUT_TASK_REGEX.source, "g"), (match, taskId) => `@task-${taskId}`);
41347
+ result = result.replace(new RegExp(OUTPUT_DOC_REGEX.source, "g"), (match, docPath) => `@doc/${docPath}`);
41348
+ return result;
41349
+ }
41254
41350
 
41255
41351
  // src/utils/notify-server.ts
41256
41352
  import { existsSync as existsSync5, readFileSync } from "node:fs";
@@ -41285,6 +41381,18 @@ async function notifyTaskUpdate(taskId) {
41285
41381
  } catch {
41286
41382
  }
41287
41383
  }
41384
+ async function notifyRefresh() {
41385
+ try {
41386
+ const port = getServerPort();
41387
+ await fetch(`http://localhost:${port}/api/notify`, {
41388
+ method: "POST",
41389
+ headers: { "Content-Type": "application/json" },
41390
+ body: JSON.stringify({ type: "tasks:refresh" }),
41391
+ signal: AbortSignal.timeout(1e3)
41392
+ });
41393
+ } catch {
41394
+ }
41395
+ }
41288
41396
  async function notifyDocUpdate(docPath) {
41289
41397
  try {
41290
41398
  const port = getServerPort();
@@ -41297,6 +41405,18 @@ async function notifyDocUpdate(docPath) {
41297
41405
  } catch {
41298
41406
  }
41299
41407
  }
41408
+ async function notifyTimeUpdate(active) {
41409
+ try {
41410
+ const port = getServerPort();
41411
+ await fetch(`http://localhost:${port}/api/notify`, {
41412
+ method: "POST",
41413
+ headers: { "Content-Type": "application/json" },
41414
+ body: JSON.stringify({ type: "time:updated", active }),
41415
+ signal: AbortSignal.timeout(1e3)
41416
+ });
41417
+ } catch {
41418
+ }
41419
+ }
41300
41420
 
41301
41421
  // src/commands/task.ts
41302
41422
  function getFileStore() {
@@ -41675,7 +41795,7 @@ var createCommand2 = new Command("create").description("Create a new task").argu
41675
41795
  }));
41676
41796
  const task = await fileStore.createTask({
41677
41797
  title,
41678
- description: options2.description,
41798
+ description: options2.description ? normalizeRefs(options2.description) : void 0,
41679
41799
  status: options2.status,
41680
41800
  priority: options2.priority,
41681
41801
  assignee: options2.assignee,
@@ -41780,7 +41900,7 @@ var editCommand = new Command("edit").description("Edit task properties").argume
41780
41900
  updates.title = options2.title;
41781
41901
  }
41782
41902
  if (options2.description) {
41783
- updates.description = options2.description;
41903
+ updates.description = normalizeRefs(options2.description);
41784
41904
  }
41785
41905
  if (options2.status) {
41786
41906
  if (!isValidTaskStatus(options2.status, allowedStatuses)) {
@@ -41857,15 +41977,15 @@ var editCommand = new Command("edit").description("Edit task properties").argume
41857
41977
  updates.acceptanceCriteria = criteria;
41858
41978
  }
41859
41979
  if (options2.plan) {
41860
- updates.implementationPlan = options2.plan;
41980
+ updates.implementationPlan = normalizeRefs(options2.plan);
41861
41981
  }
41862
41982
  if (options2.notes) {
41863
- updates.implementationNotes = options2.notes;
41983
+ updates.implementationNotes = normalizeRefs(options2.notes);
41864
41984
  }
41865
41985
  if (options2.appendNotes) {
41866
41986
  const existingNotes = task.implementationNotes || "";
41867
41987
  const separator = existingNotes ? "\n\n" : "";
41868
- updates.implementationNotes = existingNotes + separator + options2.appendNotes;
41988
+ updates.implementationNotes = existingNotes + separator + normalizeRefs(options2.appendNotes);
41869
41989
  }
41870
41990
  await fileStore.updateTask(id, updates);
41871
41991
  await notifyTaskUpdate(id);
@@ -41929,6 +42049,7 @@ var archiveCommand = new Command("archive").description("Archive a task").argume
41929
42049
  const content = await file(oldPath).text();
41930
42050
  await write(newPath, content);
41931
42051
  await unlink3(oldPath);
42052
+ await notifyRefresh();
41932
42053
  console.log(source_default.green(`\u2713 Archived task-${id}: ${task.title}`));
41933
42054
  } catch (error46) {
41934
42055
  console.error(source_default.red("\u2717 Failed to archive task"));
@@ -41959,6 +42080,7 @@ var unarchiveCommand = new Command("unarchive").description("Restore archived ta
41959
42080
  const content = await file(archivePath).text();
41960
42081
  await write(tasksFilePath, content);
41961
42082
  await unlink3(archivePath);
42083
+ await notifyRefresh();
41962
42084
  console.log(source_default.green(`\u2713 Restored task-${id}`));
41963
42085
  } catch (error46) {
41964
42086
  console.error(source_default.red("\u2717 Failed to restore task"));
@@ -42363,7 +42485,27 @@ var restoreCommand = new Command("restore").description("Restore task to a previ
42363
42485
  process.exit(1);
42364
42486
  }
42365
42487
  });
42366
- var taskCommand = new Command("task").description("Manage tasks");
42488
+ var taskCommand = new Command("task").description("Manage tasks").argument("[id]", "Task ID (shorthand for 'task view <id>')").option("--plain", "Plain text output for AI").action(async (id, options2) => {
42489
+ if (!id) {
42490
+ taskCommand.help();
42491
+ return;
42492
+ }
42493
+ try {
42494
+ const fileStore = getFileStore();
42495
+ const task = await fileStore.getTask(id);
42496
+ if (!task) {
42497
+ console.error(source_default.red(`\u2717 Task ${id} not found`));
42498
+ process.exit(1);
42499
+ }
42500
+ console.log(await formatTask(task, fileStore, options2.plain));
42501
+ } catch (error46) {
42502
+ console.error(source_default.red("\u2717 Failed to view task"));
42503
+ if (error46 instanceof Error) {
42504
+ console.error(source_default.red(` ${error46.message}`));
42505
+ }
42506
+ process.exit(1);
42507
+ }
42508
+ });
42367
42509
  taskCommand.addCommand(createCommand2);
42368
42510
  taskCommand.addCommand(listCommand);
42369
42511
  taskCommand.addCommand(viewCommand);
@@ -42818,6 +42960,13 @@ var startCommand = new Command("start").description("Start timer for a task").ar
42818
42960
  totalPausedMs: 0
42819
42961
  };
42820
42962
  await saveTimeData(projectRoot, data);
42963
+ await notifyTimeUpdate({
42964
+ taskId,
42965
+ taskTitle: task.title,
42966
+ startedAt: data.active.startedAt,
42967
+ pausedAt: null,
42968
+ totalPausedMs: 0
42969
+ });
42821
42970
  console.log(source_default.green(`\u23F1 Started timer for #${taskId}: ${task.title}`));
42822
42971
  } catch (error46) {
42823
42972
  console.error(source_default.red("\u2717 Failed to start timer"));
@@ -42858,6 +43007,7 @@ var stopCommand = new Command("stop").description("Stop current timer and save t
42858
43007
  }
42859
43008
  data.active = null;
42860
43009
  await saveTimeData(projectRoot, data);
43010
+ await notifyTimeUpdate(null);
42861
43011
  console.log(source_default.green(`\u23F9 Stopped timer for #${taskId}`));
42862
43012
  console.log(source_default.gray(` Duration: ${formatDuration(seconds)}`));
42863
43013
  } catch (error46) {
@@ -42871,6 +43021,7 @@ var stopCommand = new Command("stop").description("Stop current timer and save t
42871
43021
  var pauseCommand = new Command("pause").description("Pause current timer").action(async () => {
42872
43022
  try {
42873
43023
  const projectRoot = getProjectRoot();
43024
+ const fileStore = getFileStore4();
42874
43025
  const data = await loadTimeData(projectRoot);
42875
43026
  if (!data.active) {
42876
43027
  console.log(source_default.yellow("No active timer"));
@@ -42882,6 +43033,14 @@ var pauseCommand = new Command("pause").description("Pause current timer").actio
42882
43033
  }
42883
43034
  data.active.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
42884
43035
  await saveTimeData(projectRoot, data);
43036
+ const task = await fileStore.getTask(data.active.taskId);
43037
+ await notifyTimeUpdate({
43038
+ taskId: data.active.taskId,
43039
+ taskTitle: task?.title || "",
43040
+ startedAt: data.active.startedAt,
43041
+ pausedAt: data.active.pausedAt,
43042
+ totalPausedMs: data.active.totalPausedMs
43043
+ });
42885
43044
  console.log(source_default.yellow(`\u23F8 Paused timer for #${data.active.taskId}`));
42886
43045
  } catch (error46) {
42887
43046
  console.error(source_default.red("\u2717 Failed to pause timer"));
@@ -42894,6 +43053,7 @@ var pauseCommand = new Command("pause").description("Pause current timer").actio
42894
43053
  var resumeCommand = new Command("resume").description("Resume paused timer").action(async () => {
42895
43054
  try {
42896
43055
  const projectRoot = getProjectRoot();
43056
+ const fileStore = getFileStore4();
42897
43057
  const data = await loadTimeData(projectRoot);
42898
43058
  if (!data.active) {
42899
43059
  console.log(source_default.yellow("No active timer"));
@@ -42907,6 +43067,14 @@ var resumeCommand = new Command("resume").description("Resume paused timer").act
42907
43067
  data.active.totalPausedMs += pausedDuration;
42908
43068
  data.active.pausedAt = null;
42909
43069
  await saveTimeData(projectRoot, data);
43070
+ const task = await fileStore.getTask(data.active.taskId);
43071
+ await notifyTimeUpdate({
43072
+ taskId: data.active.taskId,
43073
+ taskTitle: task?.title || "",
43074
+ startedAt: data.active.startedAt,
43075
+ pausedAt: null,
43076
+ totalPausedMs: data.active.totalPausedMs
43077
+ });
42910
43078
  console.log(source_default.green(`\u25B6 Resumed timer for #${data.active.taskId}`));
42911
43079
  } catch (error46) {
42912
43080
  console.error(source_default.red("\u2717 Failed to resume timer"));
@@ -43175,20 +43343,18 @@ timeCommand.addCommand(addCommand);
43175
43343
  timeCommand.addCommand(reportCommand);
43176
43344
 
43177
43345
  // src/commands/browser.ts
43178
- import { existsSync as existsSync8 } from "node:fs";
43179
- import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
43180
- import { join as join11 } from "node:path";
43346
+ import { existsSync as existsSync12 } from "node:fs";
43347
+ import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile6 } from "node:fs/promises";
43348
+ import { join as join16 } from "node:path";
43181
43349
 
43182
43350
  // src/server/index.ts
43183
43351
  import { spawn } from "node:child_process";
43184
- import { existsSync as existsSync7 } from "node:fs";
43185
- import { mkdir as mkdir5, readFile as readFile4, readdir as readdir4, writeFile as writeFile3 } from "node:fs/promises";
43352
+ import { existsSync as existsSync11, realpathSync } from "node:fs";
43186
43353
  import { createServer } from "node:http";
43187
- import { basename as basename2, dirname as dirname3, join as join10, relative } from "node:path";
43354
+ import { basename as basename2, dirname as dirname3, join as join15 } from "node:path";
43188
43355
  import { fileURLToPath } from "node:url";
43189
43356
  var import_cors = __toESM(require_lib2(), 1);
43190
- var import_express = __toESM(require_express2(), 1);
43191
- var import_gray_matter3 = __toESM(require_gray_matter(), 1);
43357
+ var import_express9 = __toESM(require_express2(), 1);
43192
43358
 
43193
43359
  // node_modules/ws/wrapper.mjs
43194
43360
  var import_stream = __toESM(require_stream(), 1);
@@ -43197,151 +43363,161 @@ var import_sender = __toESM(require_sender(), 1);
43197
43363
  var import_websocket = __toESM(require_websocket(), 1);
43198
43364
  var import_websocket_server = __toESM(require_websocket_server(), 1);
43199
43365
 
43200
- // src/server/index.ts
43201
- var isBun2 = typeof globalThis.Bun !== "undefined";
43202
- async function startServer(options2) {
43203
- const { port, projectRoot, open } = options2;
43204
- const store = new FileStore(projectRoot);
43205
- const clients = /* @__PURE__ */ new Set();
43206
- const broadcast = (data) => {
43207
- const msg = JSON.stringify(data);
43208
- for (const client of clients) {
43209
- if (client.readyState === 1) {
43210
- client.send(msg);
43211
- }
43212
- }
43213
- };
43214
- const currentFile = fileURLToPath(import.meta.url);
43215
- const currentDir = dirname3(currentFile);
43216
- let packageRoot = currentDir;
43217
- const normalizedDir = currentDir.replace(/[/\\]+$/, "");
43218
- const dirName = basename2(normalizedDir);
43219
- if (dirName === "dist") {
43220
- packageRoot = join10(currentDir, "..");
43221
- } else if (normalizedDir.includes("/src/server") || normalizedDir.includes("\\src\\server")) {
43222
- packageRoot = join10(currentDir, "..", "..");
43223
- }
43224
- const uiDistPath = join10(packageRoot, "dist", "ui");
43225
- if (!existsSync7(join10(uiDistPath, "index.html"))) {
43226
- throw new Error(`UI build not found at ${uiDistPath}. Run 'bun run build:ui' first.`);
43227
- }
43228
- const app = (0, import_express.default)();
43229
- app.use((0, import_cors.default)());
43230
- app.use(import_express.default.json());
43231
- const httpServer = createServer(app);
43232
- const wss = new import_websocket_server.default({ server: httpServer, path: "/ws" });
43233
- wss.on("connection", (ws) => {
43234
- clients.add(ws);
43235
- ws.on("close", () => {
43236
- clients.delete(ws);
43237
- });
43238
- ws.on("error", (error46) => {
43239
- console.error("WebSocket error:", error46);
43240
- clients.delete(ws);
43241
- });
43242
- });
43243
- app.use(
43244
- "/assets",
43245
- import_express.default.static(join10(uiDistPath, "assets"), {
43246
- maxAge: "1y",
43247
- immutable: true
43248
- })
43249
- );
43250
- app.get("/api/tasks", async (_req, res) => {
43366
+ // src/server/middleware/error-handler.ts
43367
+ function errorHandler(err, _req, res, _next) {
43368
+ console.error("Server error:", err);
43369
+ res.status(500).json({ error: err.message });
43370
+ }
43371
+
43372
+ // src/server/routes/index.ts
43373
+ var import_express8 = __toESM(require_express2(), 1);
43374
+
43375
+ // src/server/routes/activities.ts
43376
+ var import_express = __toESM(require_express2(), 1);
43377
+ function createActivityRoutes(ctx) {
43378
+ const router = (0, import_express.Router)();
43379
+ const { store } = ctx;
43380
+ router.get("/", async (req, res) => {
43251
43381
  try {
43382
+ const limit = Number.parseInt(req.query.limit || "50", 10);
43383
+ const type = req.query.type;
43252
43384
  const tasks = await store.getAllTasks();
43253
- res.json(tasks);
43254
- } catch (error46) {
43255
- console.error("Error getting tasks:", error46);
43256
- res.status(500).json({ error: String(error46) });
43257
- }
43258
- });
43259
- app.get("/api/tasks/:id/history", async (req, res) => {
43260
- try {
43261
- const taskId = req.params.id;
43262
- const history = await store.getTaskVersionHistory(taskId);
43263
- res.json({ versions: history });
43385
+ const allActivities = [];
43386
+ for (const task of tasks) {
43387
+ const versions = await store.getTaskVersionHistory(task.id);
43388
+ for (const version2 of versions) {
43389
+ if (type) {
43390
+ const hasMatchingChange = version2.changes.some((c) => c.field === type);
43391
+ if (!hasMatchingChange) continue;
43392
+ }
43393
+ allActivities.push({
43394
+ taskId: task.id,
43395
+ taskTitle: task.title,
43396
+ version: version2.version,
43397
+ timestamp: version2.timestamp,
43398
+ author: version2.author,
43399
+ changes: version2.changes
43400
+ });
43401
+ }
43402
+ }
43403
+ allActivities.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
43404
+ const limited = allActivities.slice(0, limit);
43405
+ res.json({ activities: limited });
43264
43406
  } catch (error46) {
43265
- console.error("Error getting task history:", error46);
43407
+ console.error("Error getting activities:", error46);
43266
43408
  res.status(500).json({ error: String(error46) });
43267
43409
  }
43268
43410
  });
43269
- app.get("/api/tasks/:id", async (req, res) => {
43411
+ return router;
43412
+ }
43413
+
43414
+ // src/server/routes/config.ts
43415
+ var import_express2 = __toESM(require_express2(), 1);
43416
+ import { existsSync as existsSync7 } from "node:fs";
43417
+ import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
43418
+ import { join as join10 } from "node:path";
43419
+ function createConfigRoutes(ctx) {
43420
+ const router = (0, import_express2.Router)();
43421
+ const { store } = ctx;
43422
+ const configPath = join10(store.projectRoot, ".knowns", "config.json");
43423
+ router.get("/", async (_req, res) => {
43270
43424
  try {
43271
- const task = await store.getTask(req.params.id);
43272
- if (!task) {
43273
- res.status(404).json({ error: "Task not found" });
43425
+ if (!existsSync7(configPath)) {
43426
+ res.json({
43427
+ config: {
43428
+ name: "Knowns",
43429
+ defaultPriority: "medium",
43430
+ defaultLabels: [],
43431
+ timeFormat: "24h",
43432
+ visibleColumns: ["todo", "in-progress", "done"]
43433
+ }
43434
+ });
43274
43435
  return;
43275
43436
  }
43276
- res.json(task);
43277
- } catch (error46) {
43278
- console.error("Error getting task:", error46);
43279
- res.status(500).json({ error: String(error46) });
43280
- }
43281
- });
43282
- app.put("/api/tasks/:id", async (req, res) => {
43283
- try {
43284
- const updates = req.body;
43285
- const task = await store.updateTask(req.params.id, updates);
43286
- broadcast({ type: "tasks:updated", task });
43287
- res.json(task);
43288
- } catch (error46) {
43289
- console.error("Error updating task:", error46);
43290
- res.status(500).json({ error: String(error46) });
43291
- }
43292
- });
43293
- app.post("/api/tasks", async (req, res) => {
43294
- try {
43295
- const data = req.body;
43296
- const task = await store.createTask(data);
43297
- broadcast({ type: "tasks:updated", task });
43298
- res.status(201).json(task);
43437
+ const content = await readFile4(configPath, "utf-8");
43438
+ const data = JSON.parse(content);
43439
+ const settings = data.settings || {};
43440
+ const config2 = {
43441
+ name: data.name,
43442
+ id: data.id,
43443
+ createdAt: data.createdAt,
43444
+ ...settings
43445
+ };
43446
+ if (!config2.visibleColumns) {
43447
+ config2.visibleColumns = ["todo", "in-progress", "done"];
43448
+ }
43449
+ res.json({ config: config2 });
43299
43450
  } catch (error46) {
43300
- console.error("Error creating task:", error46);
43451
+ console.error("Error getting config:", error46);
43301
43452
  res.status(500).json({ error: String(error46) });
43302
43453
  }
43303
43454
  });
43304
- app.post("/api/notify", async (req, res) => {
43455
+ router.post("/", async (req, res) => {
43305
43456
  try {
43306
- const { taskId, type, docPath } = req.body;
43307
- if (taskId) {
43308
- const task = await store.getTask(taskId);
43309
- if (task) {
43310
- broadcast({ type: "tasks:updated", task });
43311
- res.json({ success: true });
43312
- return;
43313
- }
43314
- } else if (type === "tasks:refresh") {
43315
- broadcast({ type: "tasks:refresh" });
43316
- res.json({ success: true });
43317
- return;
43318
- } else if (type === "docs:updated" && docPath) {
43319
- broadcast({ type: "docs:updated", docPath });
43320
- res.json({ success: true });
43321
- return;
43322
- } else if (type === "docs:refresh") {
43323
- broadcast({ type: "docs:refresh" });
43324
- res.json({ success: true });
43325
- return;
43457
+ const config2 = req.body;
43458
+ let existingData = {};
43459
+ if (existsSync7(configPath)) {
43460
+ const content = await readFile4(configPath, "utf-8");
43461
+ existingData = JSON.parse(content);
43326
43462
  }
43327
- res.status(400).json({ success: false, error: "Invalid notify request" });
43463
+ const { name, ...settings } = config2;
43464
+ const merged = {
43465
+ ...existingData,
43466
+ name: name || existingData.name,
43467
+ settings
43468
+ };
43469
+ await writeFile3(configPath, JSON.stringify(merged, null, 2), "utf-8");
43470
+ res.json({ success: true });
43328
43471
  } catch (error46) {
43329
- console.error("[Server] Notify error:", error46);
43472
+ console.error("Error saving config:", error46);
43330
43473
  res.status(500).json({ error: String(error46) });
43331
43474
  }
43332
43475
  });
43333
- app.get("/api/docs", async (_req, res) => {
43476
+ return router;
43477
+ }
43478
+
43479
+ // src/server/routes/docs.ts
43480
+ var import_express3 = __toESM(require_express2(), 1);
43481
+ var import_gray_matter3 = __toESM(require_gray_matter(), 1);
43482
+ import { existsSync as existsSync8 } from "node:fs";
43483
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
43484
+ import { join as join12 } from "node:path";
43485
+
43486
+ // src/server/utils/markdown.ts
43487
+ import { readdir as readdir4 } from "node:fs/promises";
43488
+ import { join as join11, relative } from "node:path";
43489
+ async function findMarkdownFiles(dir, baseDir) {
43490
+ const files = [];
43491
+ const entries = await readdir4(dir, { withFileTypes: true });
43492
+ for (const entry of entries) {
43493
+ const fullPath = join11(dir, entry.name);
43494
+ if (entry.isDirectory()) {
43495
+ const subFiles = await findMarkdownFiles(fullPath, baseDir);
43496
+ files.push(...subFiles);
43497
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
43498
+ const relativePath = relative(baseDir, fullPath);
43499
+ files.push(relativePath);
43500
+ }
43501
+ }
43502
+ return files;
43503
+ }
43504
+
43505
+ // src/server/routes/docs.ts
43506
+ function createDocRoutes(ctx) {
43507
+ const router = (0, import_express3.Router)();
43508
+ const { store, broadcast } = ctx;
43509
+ const docsDir = join12(store.projectRoot, ".knowns", "docs");
43510
+ router.get("/", async (_req, res) => {
43334
43511
  try {
43335
- const docsDir = join10(store.projectRoot, ".knowns", "docs");
43336
- if (!existsSync7(docsDir)) {
43512
+ if (!existsSync8(docsDir)) {
43337
43513
  res.json({ docs: [] });
43338
43514
  return;
43339
43515
  }
43340
43516
  const mdFiles = await findMarkdownFiles(docsDir, docsDir);
43341
43517
  const docs = await Promise.all(
43342
43518
  mdFiles.map(async (relativePath) => {
43343
- const fullPath = join10(docsDir, relativePath);
43344
- const content = await readFile4(fullPath, "utf-8");
43519
+ const fullPath = join12(docsDir, relativePath);
43520
+ const content = await readFile5(fullPath, "utf-8");
43345
43521
  const { data, content: docContent } = (0, import_gray_matter3.default)(content);
43346
43522
  const pathParts = relativePath.split("/");
43347
43523
  const filename = pathParts[pathParts.length - 1];
@@ -43361,20 +43537,19 @@ async function startServer(options2) {
43361
43537
  res.status(500).json({ error: String(error46) });
43362
43538
  }
43363
43539
  });
43364
- app.get("/api/docs/{*path}", async (req, res) => {
43540
+ router.get("/{*path}", async (req, res) => {
43365
43541
  try {
43366
43542
  const docPath = Array.isArray(req.params.path) ? req.params.path.join("/") : req.params.path;
43367
- const docsDir = join10(store.projectRoot, ".knowns", "docs");
43368
- const fullPath = join10(docsDir, docPath);
43543
+ const fullPath = join12(docsDir, docPath);
43369
43544
  if (!fullPath.startsWith(docsDir)) {
43370
43545
  res.status(400).json({ error: "Invalid path" });
43371
43546
  return;
43372
43547
  }
43373
- if (!existsSync7(fullPath)) {
43548
+ if (!existsSync8(fullPath)) {
43374
43549
  res.status(404).json({ error: "Document not found" });
43375
43550
  return;
43376
43551
  }
43377
- const content = await readFile4(fullPath, "utf-8");
43552
+ const content = await readFile5(fullPath, "utf-8");
43378
43553
  const { data, content: docContent } = (0, import_gray_matter3.default)(content);
43379
43554
  const pathParts = docPath.split("/");
43380
43555
  const filename = pathParts[pathParts.length - 1];
@@ -43395,57 +43570,9 @@ async function startServer(options2) {
43395
43570
  res.status(500).json({ error: String(error46) });
43396
43571
  }
43397
43572
  });
43398
- app.put("/api/docs/{*path}", async (req, res) => {
43573
+ router.post("/", async (req, res) => {
43399
43574
  try {
43400
- const docPath = Array.isArray(req.params.path) ? req.params.path.join("/") : req.params.path;
43401
- const docsDir = join10(store.projectRoot, ".knowns", "docs");
43402
- const fullPath = join10(docsDir, docPath);
43403
- if (!fullPath.startsWith(docsDir)) {
43404
- res.status(400).json({ error: "Invalid path" });
43405
- return;
43406
- }
43407
- if (!existsSync7(fullPath)) {
43408
- res.status(404).json({ error: "Document not found" });
43409
- return;
43410
- }
43411
- const data = req.body;
43412
- const { content, title, description, tags } = data;
43413
- const existingContent = await readFile4(fullPath, "utf-8");
43414
- const { data: existingData } = (0, import_gray_matter3.default)(existingContent);
43415
- const now = (/* @__PURE__ */ new Date()).toISOString();
43416
- const updatedFrontmatter = {
43417
- ...existingData,
43418
- title: title ?? existingData.title,
43419
- description: description ?? existingData.description,
43420
- tags: tags ?? existingData.tags,
43421
- updatedAt: now
43422
- };
43423
- const newFileContent = import_gray_matter3.default.stringify(content ?? "", updatedFrontmatter);
43424
- await writeFile3(fullPath, newFileContent, "utf-8");
43425
- const pathParts = docPath.split("/");
43426
- const filename = pathParts[pathParts.length - 1];
43427
- const folder = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
43428
- const updatedDoc = {
43429
- filename,
43430
- path: docPath,
43431
- folder,
43432
- title: updatedFrontmatter.title || filename.replace(/\.md$/, ""),
43433
- description: updatedFrontmatter.description || "",
43434
- tags: updatedFrontmatter.tags || [],
43435
- metadata: updatedFrontmatter,
43436
- content: content ?? ""
43437
- };
43438
- broadcast({ type: "docs:updated", doc: updatedDoc });
43439
- res.json(updatedDoc);
43440
- } catch (error46) {
43441
- console.error("Error updating doc:", error46);
43442
- res.status(500).json({ error: String(error46) });
43443
- }
43444
- });
43445
- app.post("/api/docs", async (req, res) => {
43446
- try {
43447
- const docsDir = join10(store.projectRoot, ".knowns", "docs");
43448
- if (!existsSync7(docsDir)) {
43575
+ if (!existsSync8(docsDir)) {
43449
43576
  await mkdir5(docsDir, { recursive: true });
43450
43577
  }
43451
43578
  const data = req.body;
@@ -43459,16 +43586,16 @@ async function startServer(options2) {
43459
43586
  let targetDir;
43460
43587
  if (folder?.trim()) {
43461
43588
  const cleanFolder = folder.trim().replace(/^\/+|\/+$/g, "");
43462
- targetDir = join10(docsDir, cleanFolder);
43463
- filepath = join10(targetDir, filename);
43589
+ targetDir = join12(docsDir, cleanFolder);
43590
+ filepath = join12(targetDir, filename);
43464
43591
  } else {
43465
43592
  targetDir = docsDir;
43466
- filepath = join10(docsDir, filename);
43593
+ filepath = join12(docsDir, filename);
43467
43594
  }
43468
- if (!existsSync7(targetDir)) {
43595
+ if (!existsSync8(targetDir)) {
43469
43596
  await mkdir5(targetDir, { recursive: true });
43470
43597
  }
43471
- if (existsSync7(filepath)) {
43598
+ if (existsSync8(filepath)) {
43472
43599
  res.status(409).json({ error: "Document with this title already exists in this folder" });
43473
43600
  return;
43474
43601
  }
@@ -43481,7 +43608,7 @@ async function startServer(options2) {
43481
43608
  tags: tags || []
43482
43609
  };
43483
43610
  const markdown = import_gray_matter3.default.stringify(content || "", frontmatter);
43484
- await writeFile3(filepath, markdown, "utf-8");
43611
+ await writeFile4(filepath, markdown, "utf-8");
43485
43612
  res.status(201).json({
43486
43613
  success: true,
43487
43614
  filename,
@@ -43494,93 +43621,107 @@ async function startServer(options2) {
43494
43621
  res.status(500).json({ error: String(error46) });
43495
43622
  }
43496
43623
  });
43497
- app.get("/api/config", async (_req, res) => {
43624
+ router.put("/{*path}", async (req, res) => {
43498
43625
  try {
43499
- const configPath = join10(store.projectRoot, ".knowns", "config.json");
43500
- if (!existsSync7(configPath)) {
43501
- res.json({
43502
- config: {
43503
- name: "Knowns",
43504
- defaultPriority: "medium",
43505
- defaultLabels: [],
43506
- timeFormat: "24h",
43507
- visibleColumns: ["todo", "in-progress", "done"]
43508
- }
43509
- });
43626
+ const docPath = Array.isArray(req.params.path) ? req.params.path.join("/") : req.params.path;
43627
+ const fullPath = join12(docsDir, docPath);
43628
+ if (!fullPath.startsWith(docsDir)) {
43629
+ res.status(400).json({ error: "Invalid path" });
43510
43630
  return;
43511
43631
  }
43512
- const content = await readFile4(configPath, "utf-8");
43513
- const data = JSON.parse(content);
43514
- const settings = data.settings || {};
43515
- const config2 = {
43516
- name: data.name,
43517
- id: data.id,
43518
- createdAt: data.createdAt,
43519
- ...settings
43520
- };
43521
- if (!config2.visibleColumns) {
43522
- config2.visibleColumns = ["todo", "in-progress", "done"];
43523
- }
43524
- res.json({ config: config2 });
43525
- } catch (error46) {
43526
- console.error("Error getting config:", error46);
43527
- res.status(500).json({ error: String(error46) });
43528
- }
43529
- });
43530
- app.post("/api/config", async (req, res) => {
43531
- try {
43532
- const config2 = req.body;
43533
- const configPath = join10(store.projectRoot, ".knowns", "config.json");
43534
- let existingData = {};
43535
- if (existsSync7(configPath)) {
43536
- const content = await readFile4(configPath, "utf-8");
43537
- existingData = JSON.parse(content);
43632
+ if (!existsSync8(fullPath)) {
43633
+ res.status(404).json({ error: "Document not found" });
43634
+ return;
43538
43635
  }
43539
- const { name, ...settings } = config2;
43540
- const merged = {
43636
+ const data = req.body;
43637
+ const { content, title, description, tags } = data;
43638
+ const existingContent = await readFile5(fullPath, "utf-8");
43639
+ const { data: existingData } = (0, import_gray_matter3.default)(existingContent);
43640
+ const now = (/* @__PURE__ */ new Date()).toISOString();
43641
+ const updatedFrontmatter = {
43541
43642
  ...existingData,
43542
- name: name || existingData.name,
43543
- settings
43643
+ title: title ?? existingData.title,
43644
+ description: description ?? existingData.description,
43645
+ tags: tags ?? existingData.tags,
43646
+ updatedAt: now
43544
43647
  };
43545
- await writeFile3(configPath, JSON.stringify(merged, null, 2), "utf-8");
43546
- res.json({ success: true });
43648
+ const newFileContent = import_gray_matter3.default.stringify(content ?? "", updatedFrontmatter);
43649
+ await writeFile4(fullPath, newFileContent, "utf-8");
43650
+ const pathParts = docPath.split("/");
43651
+ const filename = pathParts[pathParts.length - 1];
43652
+ const folder = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
43653
+ const updatedDoc = {
43654
+ filename,
43655
+ path: docPath,
43656
+ folder,
43657
+ title: updatedFrontmatter.title || filename.replace(/\.md$/, ""),
43658
+ description: updatedFrontmatter.description || "",
43659
+ tags: updatedFrontmatter.tags || [],
43660
+ metadata: updatedFrontmatter,
43661
+ content: content ?? ""
43662
+ };
43663
+ broadcast({ type: "docs:updated", doc: updatedDoc });
43664
+ res.json(updatedDoc);
43547
43665
  } catch (error46) {
43548
- console.error("Error saving config:", error46);
43666
+ console.error("Error updating doc:", error46);
43549
43667
  res.status(500).json({ error: String(error46) });
43550
43668
  }
43551
43669
  });
43552
- app.get("/api/activities", async (req, res) => {
43670
+ return router;
43671
+ }
43672
+
43673
+ // src/server/routes/notify.ts
43674
+ var import_express4 = __toESM(require_express2(), 1);
43675
+ function createNotifyRoutes(ctx) {
43676
+ const router = (0, import_express4.Router)();
43677
+ const { store, broadcast } = ctx;
43678
+ router.post("/", async (req, res) => {
43553
43679
  try {
43554
- const limit = Number.parseInt(req.query.limit || "50", 10);
43555
- const type = req.query.type;
43556
- const tasks = await store.getAllTasks();
43557
- const allActivities = [];
43558
- for (const task of tasks) {
43559
- const versions = await store.getTaskVersionHistory(task.id);
43560
- for (const version2 of versions) {
43561
- if (type) {
43562
- const hasMatchingChange = version2.changes.some((c) => c.field === type);
43563
- if (!hasMatchingChange) continue;
43564
- }
43565
- allActivities.push({
43566
- taskId: task.id,
43567
- taskTitle: task.title,
43568
- version: version2.version,
43569
- timestamp: version2.timestamp,
43570
- author: version2.author,
43571
- changes: version2.changes
43572
- });
43680
+ const { taskId, type, docPath } = req.body;
43681
+ if (taskId) {
43682
+ const task = await store.getTask(taskId);
43683
+ if (task) {
43684
+ broadcast({ type: "tasks:updated", task });
43685
+ res.json({ success: true });
43686
+ return;
43573
43687
  }
43688
+ } else if (type === "tasks:refresh") {
43689
+ broadcast({ type: "tasks:refresh" });
43690
+ res.json({ success: true });
43691
+ return;
43692
+ } else if (type === "docs:updated" && docPath) {
43693
+ broadcast({ type: "docs:updated", docPath });
43694
+ res.json({ success: true });
43695
+ return;
43696
+ } else if (type === "docs:refresh") {
43697
+ broadcast({ type: "docs:refresh" });
43698
+ res.json({ success: true });
43699
+ return;
43700
+ } else if (type === "time:updated") {
43701
+ const { active } = req.body;
43702
+ broadcast({ type: "time:updated", active });
43703
+ res.json({ success: true });
43704
+ return;
43574
43705
  }
43575
- allActivities.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
43576
- const limited = allActivities.slice(0, limit);
43577
- res.json({ activities: limited });
43706
+ res.status(400).json({ success: false, error: "Invalid notify request" });
43578
43707
  } catch (error46) {
43579
- console.error("Error getting activities:", error46);
43708
+ console.error("[Server] Notify error:", error46);
43580
43709
  res.status(500).json({ error: String(error46) });
43581
43710
  }
43582
43711
  });
43583
- app.get("/api/search", async (req, res) => {
43712
+ return router;
43713
+ }
43714
+
43715
+ // src/server/routes/search.ts
43716
+ var import_express5 = __toESM(require_express2(), 1);
43717
+ var import_gray_matter4 = __toESM(require_gray_matter(), 1);
43718
+ import { existsSync as existsSync9 } from "node:fs";
43719
+ import { readFile as readFile6 } from "node:fs/promises";
43720
+ import { join as join13 } from "node:path";
43721
+ function createSearchRoutes(ctx) {
43722
+ const router = (0, import_express5.Router)();
43723
+ const { store } = ctx;
43724
+ router.get("/", async (req, res) => {
43584
43725
  try {
43585
43726
  const query = req.query.q;
43586
43727
  if (!query) {
@@ -43601,14 +43742,14 @@ async function startServer(options2) {
43601
43742
  ].join(" ").toLowerCase();
43602
43743
  return searchText.includes(q);
43603
43744
  });
43604
- const docsDir = join10(store.projectRoot, ".knowns", "docs");
43745
+ const docsDir = join13(store.projectRoot, ".knowns", "docs");
43605
43746
  const docResults = [];
43606
- if (existsSync7(docsDir)) {
43747
+ if (existsSync9(docsDir)) {
43607
43748
  const mdFiles = await findMarkdownFiles(docsDir, docsDir);
43608
43749
  for (const relativePath of mdFiles) {
43609
- const fullPath = join10(docsDir, relativePath);
43610
- const content = await readFile4(fullPath, "utf-8");
43611
- const { data, content: docContent } = (0, import_gray_matter3.default)(content);
43750
+ const fullPath = join13(docsDir, relativePath);
43751
+ const content = await readFile6(fullPath, "utf-8");
43752
+ const { data, content: docContent } = (0, import_gray_matter4.default)(content);
43612
43753
  const searchText = `${data.title || ""} ${data.description || ""} ${data.tags?.join(" ") || ""} ${docContent}`.toLowerCase();
43613
43754
  if (searchText.includes(q)) {
43614
43755
  const pathParts = relativePath.split("/");
@@ -43630,10 +43771,343 @@ async function startServer(options2) {
43630
43771
  res.status(500).json({ error: String(error46) });
43631
43772
  }
43632
43773
  });
43774
+ return router;
43775
+ }
43776
+
43777
+ // src/server/routes/tasks.ts
43778
+ var import_express6 = __toESM(require_express2(), 1);
43779
+ function createTaskRoutes(ctx) {
43780
+ const router = (0, import_express6.Router)();
43781
+ const { store, broadcast } = ctx;
43782
+ router.get("/", async (_req, res) => {
43783
+ try {
43784
+ const tasks = await store.getAllTasks();
43785
+ res.json(tasks);
43786
+ } catch (error46) {
43787
+ console.error("Error getting tasks:", error46);
43788
+ res.status(500).json({ error: String(error46) });
43789
+ }
43790
+ });
43791
+ router.get("/:id/history", async (req, res) => {
43792
+ try {
43793
+ const taskId = req.params.id;
43794
+ const history = await store.getTaskVersionHistory(taskId);
43795
+ res.json({ versions: history });
43796
+ } catch (error46) {
43797
+ console.error("Error getting task history:", error46);
43798
+ res.status(500).json({ error: String(error46) });
43799
+ }
43800
+ });
43801
+ router.get("/:id", async (req, res) => {
43802
+ try {
43803
+ const task = await store.getTask(req.params.id);
43804
+ if (!task) {
43805
+ res.status(404).json({ error: "Task not found" });
43806
+ return;
43807
+ }
43808
+ res.json(task);
43809
+ } catch (error46) {
43810
+ console.error("Error getting task:", error46);
43811
+ res.status(500).json({ error: String(error46) });
43812
+ }
43813
+ });
43814
+ router.post("/", async (req, res) => {
43815
+ try {
43816
+ const data = req.body;
43817
+ const task = await store.createTask(data);
43818
+ broadcast({ type: "tasks:updated", task });
43819
+ res.status(201).json(task);
43820
+ } catch (error46) {
43821
+ console.error("Error creating task:", error46);
43822
+ res.status(500).json({ error: String(error46) });
43823
+ }
43824
+ });
43825
+ router.post("/batch-archive", async (req, res) => {
43826
+ try {
43827
+ const { olderThanMs } = req.body;
43828
+ if (typeof olderThanMs !== "number" || olderThanMs < 0) {
43829
+ res.status(400).json({ error: "olderThanMs must be a positive number" });
43830
+ return;
43831
+ }
43832
+ const archivedTasks = await store.batchArchiveTasks(olderThanMs);
43833
+ broadcast({ type: "tasks:batch-archived", tasks: archivedTasks });
43834
+ res.json({ success: true, count: archivedTasks.length, tasks: archivedTasks });
43835
+ } catch (error46) {
43836
+ console.error("Error batch archiving tasks:", error46);
43837
+ res.status(500).json({ error: String(error46) });
43838
+ }
43839
+ });
43840
+ router.put("/:id", async (req, res) => {
43841
+ try {
43842
+ const updates = req.body;
43843
+ const task = await store.updateTask(req.params.id, updates);
43844
+ broadcast({ type: "tasks:updated", task });
43845
+ res.json(task);
43846
+ } catch (error46) {
43847
+ console.error("Error updating task:", error46);
43848
+ res.status(500).json({ error: String(error46) });
43849
+ }
43850
+ });
43851
+ router.post("/:id/archive", async (req, res) => {
43852
+ try {
43853
+ const task = await store.archiveTask(req.params.id);
43854
+ broadcast({ type: "tasks:archived", task });
43855
+ res.json({ success: true, task });
43856
+ } catch (error46) {
43857
+ console.error("Error archiving task:", error46);
43858
+ res.status(500).json({ error: String(error46) });
43859
+ }
43860
+ });
43861
+ router.post("/:id/unarchive", async (req, res) => {
43862
+ try {
43863
+ const task = await store.unarchiveTask(req.params.id);
43864
+ broadcast({ type: "tasks:unarchived", task });
43865
+ res.json({ success: true, task });
43866
+ } catch (error46) {
43867
+ console.error("Error unarchiving task:", error46);
43868
+ res.status(500).json({ error: String(error46) });
43869
+ }
43870
+ });
43871
+ return router;
43872
+ }
43873
+
43874
+ // src/server/routes/time.ts
43875
+ var import_express7 = __toESM(require_express2(), 1);
43876
+ import { existsSync as existsSync10 } from "node:fs";
43877
+ import { readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
43878
+ import { join as join14 } from "node:path";
43879
+ function createTimeRoutes(ctx) {
43880
+ const router = (0, import_express7.Router)();
43881
+ const { store, broadcast } = ctx;
43882
+ const timePath = join14(store.projectRoot, ".knowns", "time.json");
43883
+ async function loadTimeData2() {
43884
+ if (!existsSync10(timePath)) {
43885
+ return { active: null };
43886
+ }
43887
+ const content = await readFile7(timePath, "utf-8");
43888
+ return JSON.parse(content);
43889
+ }
43890
+ async function saveTimeData2(data) {
43891
+ await writeFile5(timePath, JSON.stringify(data, null, 2), "utf-8");
43892
+ }
43893
+ router.get("/status", async (_req, res) => {
43894
+ try {
43895
+ const data = await loadTimeData2();
43896
+ if (!data.active) {
43897
+ res.json({ active: null });
43898
+ return;
43899
+ }
43900
+ const task = await store.getTask(data.active.taskId);
43901
+ res.json({
43902
+ active: {
43903
+ ...data.active,
43904
+ taskTitle: task?.title || "Unknown Task"
43905
+ }
43906
+ });
43907
+ } catch (error46) {
43908
+ console.error("Error getting time status:", error46);
43909
+ res.status(500).json({ error: String(error46) });
43910
+ }
43911
+ });
43912
+ router.post("/start", async (req, res) => {
43913
+ try {
43914
+ const { taskId } = req.body;
43915
+ if (!taskId) {
43916
+ res.status(400).json({ error: "taskId is required" });
43917
+ return;
43918
+ }
43919
+ const task = await store.getTask(taskId);
43920
+ if (!task) {
43921
+ res.status(404).json({ error: `Task ${taskId} not found` });
43922
+ return;
43923
+ }
43924
+ const data = await loadTimeData2();
43925
+ if (data.active) {
43926
+ res.status(409).json({
43927
+ error: `Timer already running for task #${data.active.taskId}`,
43928
+ activeTaskId: data.active.taskId
43929
+ });
43930
+ return;
43931
+ }
43932
+ data.active = {
43933
+ taskId,
43934
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
43935
+ pausedAt: null,
43936
+ totalPausedMs: 0
43937
+ };
43938
+ await saveTimeData2(data);
43939
+ broadcast({ type: "time:updated", active: { ...data.active, taskTitle: task.title } });
43940
+ res.json({
43941
+ success: true,
43942
+ active: { ...data.active, taskTitle: task.title }
43943
+ });
43944
+ } catch (error46) {
43945
+ console.error("Error starting timer:", error46);
43946
+ res.status(500).json({ error: String(error46) });
43947
+ }
43948
+ });
43949
+ router.post("/stop", async (req, res) => {
43950
+ try {
43951
+ const data = await loadTimeData2();
43952
+ if (!data.active) {
43953
+ res.status(400).json({ error: "No active timer" });
43954
+ return;
43955
+ }
43956
+ const { taskId, startedAt, pausedAt, totalPausedMs } = data.active;
43957
+ const endTime = pausedAt ? new Date(pausedAt) : /* @__PURE__ */ new Date();
43958
+ const elapsed = endTime.getTime() - new Date(startedAt).getTime() - totalPausedMs;
43959
+ const seconds = Math.floor(elapsed / 1e3);
43960
+ const task = await store.getTask(taskId);
43961
+ if (task) {
43962
+ const entry = {
43963
+ id: `te-${Date.now()}`,
43964
+ startedAt: new Date(startedAt),
43965
+ endedAt: endTime,
43966
+ duration: seconds
43967
+ };
43968
+ task.timeEntries.push(entry);
43969
+ task.timeSpent += seconds;
43970
+ await store.updateTask(taskId, {
43971
+ timeEntries: task.timeEntries,
43972
+ timeSpent: task.timeSpent
43973
+ });
43974
+ broadcast({ type: "tasks:updated", task });
43975
+ }
43976
+ data.active = null;
43977
+ await saveTimeData2(data);
43978
+ broadcast({ type: "time:updated", active: null });
43979
+ res.json({
43980
+ success: true,
43981
+ duration: seconds,
43982
+ taskId
43983
+ });
43984
+ } catch (error46) {
43985
+ console.error("Error stopping timer:", error46);
43986
+ res.status(500).json({ error: String(error46) });
43987
+ }
43988
+ });
43989
+ router.post("/pause", async (_req, res) => {
43990
+ try {
43991
+ const data = await loadTimeData2();
43992
+ if (!data.active) {
43993
+ res.status(400).json({ error: "No active timer" });
43994
+ return;
43995
+ }
43996
+ if (data.active.pausedAt) {
43997
+ res.status(400).json({ error: "Timer is already paused" });
43998
+ return;
43999
+ }
44000
+ data.active.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
44001
+ await saveTimeData2(data);
44002
+ const task = await store.getTask(data.active.taskId);
44003
+ broadcast({ type: "time:updated", active: { ...data.active, taskTitle: task?.title } });
44004
+ res.json({
44005
+ success: true,
44006
+ active: { ...data.active, taskTitle: task?.title }
44007
+ });
44008
+ } catch (error46) {
44009
+ console.error("Error pausing timer:", error46);
44010
+ res.status(500).json({ error: String(error46) });
44011
+ }
44012
+ });
44013
+ router.post("/resume", async (_req, res) => {
44014
+ try {
44015
+ const data = await loadTimeData2();
44016
+ if (!data.active) {
44017
+ res.status(400).json({ error: "No active timer" });
44018
+ return;
44019
+ }
44020
+ if (!data.active.pausedAt) {
44021
+ res.status(400).json({ error: "Timer is not paused" });
44022
+ return;
44023
+ }
44024
+ const pausedDuration = Date.now() - new Date(data.active.pausedAt).getTime();
44025
+ data.active.totalPausedMs += pausedDuration;
44026
+ data.active.pausedAt = null;
44027
+ await saveTimeData2(data);
44028
+ const task = await store.getTask(data.active.taskId);
44029
+ broadcast({ type: "time:updated", active: { ...data.active, taskTitle: task?.title } });
44030
+ res.json({
44031
+ success: true,
44032
+ active: { ...data.active, taskTitle: task?.title }
44033
+ });
44034
+ } catch (error46) {
44035
+ console.error("Error resuming timer:", error46);
44036
+ res.status(500).json({ error: String(error46) });
44037
+ }
44038
+ });
44039
+ return router;
44040
+ }
44041
+
44042
+ // src/server/routes/index.ts
44043
+ function createRoutes(ctx) {
44044
+ const router = (0, import_express8.Router)();
44045
+ router.use("/tasks", createTaskRoutes(ctx));
44046
+ router.use("/docs", createDocRoutes(ctx));
44047
+ router.use("/config", createConfigRoutes(ctx));
44048
+ router.use("/search", createSearchRoutes(ctx));
44049
+ router.use("/activities", createActivityRoutes(ctx));
44050
+ router.use("/notify", createNotifyRoutes(ctx));
44051
+ router.use("/time", createTimeRoutes(ctx));
44052
+ return router;
44053
+ }
44054
+
44055
+ // src/server/index.ts
44056
+ var isBun2 = typeof globalThis.Bun !== "undefined";
44057
+ async function startServer(options2) {
44058
+ const { port, projectRoot, open } = options2;
44059
+ const store = new FileStore(projectRoot);
44060
+ const clients = /* @__PURE__ */ new Set();
44061
+ const broadcast = (data) => {
44062
+ const msg = JSON.stringify(data);
44063
+ for (const client of clients) {
44064
+ if (client.readyState === 1) {
44065
+ client.send(msg);
44066
+ }
44067
+ }
44068
+ };
44069
+ const currentFile = realpathSync(fileURLToPath(import.meta.url));
44070
+ const currentDir = dirname3(currentFile);
44071
+ let packageRoot = currentDir;
44072
+ const normalizedDir = currentDir.replace(/[/\\]+$/, "");
44073
+ const dirName = basename2(normalizedDir);
44074
+ if (dirName === "dist") {
44075
+ packageRoot = join15(currentDir, "..");
44076
+ } else if (normalizedDir.includes("/src/server") || normalizedDir.includes("\\src\\server")) {
44077
+ packageRoot = join15(currentDir, "..", "..");
44078
+ }
44079
+ const uiDistPath = join15(packageRoot, "dist", "ui");
44080
+ if (!existsSync11(join15(uiDistPath, "index.html"))) {
44081
+ throw new Error(`UI build not found at ${uiDistPath}. Run 'bun run build:ui' first.`);
44082
+ }
44083
+ const app = (0, import_express9.default)();
44084
+ app.use((0, import_cors.default)());
44085
+ app.use(import_express9.default.json());
44086
+ const httpServer = createServer(app);
44087
+ const wss = new import_websocket_server.default({ server: httpServer, path: "/ws" });
44088
+ wss.on("connection", (ws) => {
44089
+ clients.add(ws);
44090
+ ws.on("close", () => {
44091
+ clients.delete(ws);
44092
+ });
44093
+ ws.on("error", (error46) => {
44094
+ console.error("WebSocket error:", error46);
44095
+ clients.delete(ws);
44096
+ });
44097
+ });
44098
+ app.use(
44099
+ "/assets",
44100
+ import_express9.default.static(join15(uiDistPath, "assets"), {
44101
+ maxAge: "1y",
44102
+ immutable: true
44103
+ })
44104
+ );
44105
+ app.use("/api", createRoutes({ store, broadcast }));
44106
+ app.use(errorHandler);
43633
44107
  app.get("/{*path}", (_req, res) => {
43634
- const indexPath = join10(uiDistPath, "index.html");
43635
- if (existsSync7(indexPath)) {
43636
- res.sendFile(indexPath);
44108
+ const indexPath = join15(uiDistPath, "index.html");
44109
+ if (existsSync11(indexPath)) {
44110
+ res.sendFile("index.html", { root: uiDistPath });
43637
44111
  } else {
43638
44112
  res.status(404).send("Not Found");
43639
44113
  }
@@ -43667,41 +44141,26 @@ async function startServer(options2) {
43667
44141
  });
43668
44142
  });
43669
44143
  }
43670
- async function findMarkdownFiles(dir, baseDir) {
43671
- const files = [];
43672
- const entries = await readdir4(dir, { withFileTypes: true });
43673
- for (const entry of entries) {
43674
- const fullPath = join10(dir, entry.name);
43675
- if (entry.isDirectory()) {
43676
- const subFiles = await findMarkdownFiles(fullPath, baseDir);
43677
- files.push(...subFiles);
43678
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
43679
- const relativePath = relative(baseDir, fullPath);
43680
- files.push(relativePath);
43681
- }
43682
- }
43683
- return files;
43684
- }
43685
44144
 
43686
44145
  // src/commands/browser.ts
43687
44146
  var DEFAULT_PORT2 = 6420;
43688
44147
  var CONFIG_FILE = ".knowns/config.json";
43689
44148
  async function saveServerPort(projectRoot, port) {
43690
- const configPath = join11(projectRoot, CONFIG_FILE);
43691
- const knownsDir = join11(projectRoot, ".knowns");
43692
- if (!existsSync8(knownsDir)) {
44149
+ const configPath = join16(projectRoot, CONFIG_FILE);
44150
+ const knownsDir = join16(projectRoot, ".knowns");
44151
+ if (!existsSync12(knownsDir)) {
43693
44152
  await mkdir6(knownsDir, { recursive: true });
43694
44153
  }
43695
44154
  try {
43696
44155
  let config2 = {};
43697
- if (existsSync8(configPath)) {
43698
- const content = await readFile5(configPath, "utf-8");
44156
+ if (existsSync12(configPath)) {
44157
+ const content = await readFile8(configPath, "utf-8");
43699
44158
  config2 = JSON.parse(content);
43700
44159
  }
43701
44160
  const settings = config2.settings || {};
43702
44161
  settings.serverPort = port;
43703
44162
  config2.settings = settings;
43704
- await writeFile4(configPath, JSON.stringify(config2, null, 2), "utf-8");
44163
+ await writeFile6(configPath, JSON.stringify(config2, null, 2), "utf-8");
43705
44164
  } catch {
43706
44165
  }
43707
44166
  }
@@ -43733,17 +44192,17 @@ var browserCommand = new Command("browser").description("Open web UI for task ma
43733
44192
  });
43734
44193
 
43735
44194
  // src/commands/doc.ts
43736
- import { existsSync as existsSync9 } from "node:fs";
43737
- import { mkdir as mkdir7, readFile as readFile6, readdir as readdir5, writeFile as writeFile5 } from "node:fs/promises";
43738
- import { join as join12 } from "node:path";
43739
- var import_gray_matter4 = __toESM(require_gray_matter(), 1);
43740
- var DOCS_DIR = join12(process.cwd(), ".knowns", "docs");
44195
+ import { existsSync as existsSync13 } from "node:fs";
44196
+ import { mkdir as mkdir7, readFile as readFile9, readdir as readdir5, writeFile as writeFile7 } from "node:fs/promises";
44197
+ import { join as join17 } from "node:path";
44198
+ var import_gray_matter5 = __toESM(require_gray_matter(), 1);
44199
+ var DOCS_DIR = join17(process.cwd(), ".knowns", "docs");
43741
44200
  async function getAllMdFiles(dir, basePath = "") {
43742
44201
  const files = [];
43743
44202
  const entries = await readdir5(dir, { withFileTypes: true });
43744
44203
  for (const entry of entries) {
43745
- const fullPath = join12(dir, entry.name);
43746
- const relativePath = basePath ? join12(basePath, entry.name) : entry.name;
44204
+ const fullPath = join17(dir, entry.name);
44205
+ const relativePath = basePath ? join17(basePath, entry.name) : entry.name;
43747
44206
  if (entry.isDirectory()) {
43748
44207
  const subFiles = await getAllMdFiles(fullPath, relativePath);
43749
44208
  files.push(...subFiles);
@@ -43754,13 +44213,39 @@ async function getAllMdFiles(dir, basePath = "") {
43754
44213
  return files;
43755
44214
  }
43756
44215
  async function ensureDocsDir() {
43757
- if (!existsSync9(DOCS_DIR)) {
44216
+ if (!existsSync13(DOCS_DIR)) {
43758
44217
  await mkdir7(DOCS_DIR, { recursive: true });
43759
44218
  }
43760
44219
  }
43761
44220
  function titleToFilename(title) {
43762
44221
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
43763
44222
  }
44223
+ async function resolveDocPath(name) {
44224
+ await ensureDocsDir();
44225
+ let filename = name.endsWith(".md") ? name : `${name}.md`;
44226
+ let filepath = join17(DOCS_DIR, filename);
44227
+ if (!existsSync13(filepath)) {
44228
+ filename = `${titleToFilename(name)}.md`;
44229
+ filepath = join17(DOCS_DIR, filename);
44230
+ }
44231
+ if (!existsSync13(filepath)) {
44232
+ const allFiles = await getAllMdFiles(DOCS_DIR);
44233
+ const searchName = name.toLowerCase().replace(/\.md$/, "");
44234
+ const matchingFile = allFiles.find((file3) => {
44235
+ const fileNameOnly = file3.toLowerCase().replace(/\.md$/, "");
44236
+ const fileBaseName = file3.split("/").pop()?.toLowerCase().replace(/\.md$/, "");
44237
+ return fileNameOnly === searchName || fileBaseName === searchName || file3 === name;
44238
+ });
44239
+ if (matchingFile) {
44240
+ filename = matchingFile;
44241
+ filepath = join17(DOCS_DIR, matchingFile);
44242
+ }
44243
+ }
44244
+ if (!existsSync13(filepath)) {
44245
+ return null;
44246
+ }
44247
+ return { filepath, filename };
44248
+ }
43764
44249
  var createCommand3 = new Command("create").description("Create a new documentation file").argument("<title>", "Document title").option("-d, --description <text>", "Document description").option("-t, --tags <tags>", "Comma-separated tags").option("-f, --folder <path>", "Folder path (e.g., guides, patterns/auth)").option("--plain", "Plain text output for AI").action(async (title, options2) => {
43765
44250
  try {
43766
44251
  await ensureDocsDir();
@@ -43769,14 +44254,14 @@ var createCommand3 = new Command("create").description("Create a new documentati
43769
44254
  let relativePath = filename;
43770
44255
  if (options2.folder) {
43771
44256
  const folderPath = options2.folder.replace(/^\/|\/$/g, "");
43772
- targetDir = join12(DOCS_DIR, folderPath);
43773
- relativePath = join12(folderPath, filename);
43774
- if (!existsSync9(targetDir)) {
44257
+ targetDir = join17(DOCS_DIR, folderPath);
44258
+ relativePath = join17(folderPath, filename);
44259
+ if (!existsSync13(targetDir)) {
43775
44260
  await mkdir7(targetDir, { recursive: true });
43776
44261
  }
43777
44262
  }
43778
- const filepath = join12(targetDir, filename);
43779
- if (existsSync9(filepath)) {
44263
+ const filepath = join17(targetDir, filename);
44264
+ if (existsSync13(filepath)) {
43780
44265
  console.error(source_default.red(`\u2717 Document already exists: ${relativePath}`));
43781
44266
  process.exit(1);
43782
44267
  }
@@ -43787,13 +44272,13 @@ var createCommand3 = new Command("create").description("Create a new documentati
43787
44272
  updatedAt: now
43788
44273
  };
43789
44274
  if (options2.description) {
43790
- metadata.description = options2.description;
44275
+ metadata.description = normalizeRefs(options2.description);
43791
44276
  }
43792
44277
  if (options2.tags) {
43793
44278
  metadata.tags = options2.tags.split(",").map((t) => t.trim());
43794
44279
  }
43795
- const content = import_gray_matter4.default.stringify("# Content\n\nWrite your documentation here.\n", metadata);
43796
- await writeFile5(filepath, content, "utf-8");
44280
+ const content = import_gray_matter5.default.stringify("# Content\n\nWrite your documentation here.\n", metadata);
44281
+ await writeFile7(filepath, content, "utf-8");
43797
44282
  await notifyDocUpdate(relativePath);
43798
44283
  if (options2.plain) {
43799
44284
  console.log(`Created: ${relativePath}`);
@@ -43821,8 +44306,8 @@ var listCommand2 = new Command("list").description("List all documentation files
43821
44306
  }
43822
44307
  const docs = [];
43823
44308
  for (const file3 of mdFiles) {
43824
- const content = await readFile6(join12(DOCS_DIR, file3), "utf-8");
43825
- const { data } = (0, import_gray_matter4.default)(content);
44309
+ const content = await readFile9(join17(DOCS_DIR, file3), "utf-8");
44310
+ const { data } = (0, import_gray_matter5.default)(content);
43826
44311
  docs.push({
43827
44312
  filename: file3,
43828
44313
  metadata: data
@@ -43880,12 +44365,12 @@ var viewCommand2 = new Command("view").description("View a documentation file").
43880
44365
  try {
43881
44366
  await ensureDocsDir();
43882
44367
  let filename = name.endsWith(".md") ? name : `${name}.md`;
43883
- let filepath = join12(DOCS_DIR, filename);
43884
- if (!existsSync9(filepath)) {
44368
+ let filepath = join17(DOCS_DIR, filename);
44369
+ if (!existsSync13(filepath)) {
43885
44370
  filename = `${titleToFilename(name)}.md`;
43886
- filepath = join12(DOCS_DIR, filename);
44371
+ filepath = join17(DOCS_DIR, filename);
43887
44372
  }
43888
- if (!existsSync9(filepath)) {
44373
+ if (!existsSync13(filepath)) {
43889
44374
  const allFiles = await getAllMdFiles(DOCS_DIR);
43890
44375
  const searchName = name.toLowerCase().replace(/\.md$/, "");
43891
44376
  const matchingFile = allFiles.find((file3) => {
@@ -43895,15 +44380,15 @@ var viewCommand2 = new Command("view").description("View a documentation file").
43895
44380
  });
43896
44381
  if (matchingFile) {
43897
44382
  filename = matchingFile;
43898
- filepath = join12(DOCS_DIR, matchingFile);
44383
+ filepath = join17(DOCS_DIR, matchingFile);
43899
44384
  }
43900
44385
  }
43901
- if (!existsSync9(filepath)) {
44386
+ if (!existsSync13(filepath)) {
43902
44387
  console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
43903
44388
  process.exit(1);
43904
44389
  }
43905
- const fileContent = await readFile6(filepath, "utf-8");
43906
- const { data, content } = (0, import_gray_matter4.default)(fileContent);
44390
+ const fileContent = await readFile9(filepath, "utf-8");
44391
+ const { data, content } = (0, import_gray_matter5.default)(fileContent);
43907
44392
  const metadata = data;
43908
44393
  if (options2.plain) {
43909
44394
  const border = "-".repeat(50);
@@ -43989,13 +44474,13 @@ var editCommand2 = new Command("edit").description("Edit a documentation file (m
43989
44474
  try {
43990
44475
  await ensureDocsDir();
43991
44476
  let filename = name.endsWith(".md") ? name : `${name}.md`;
43992
- let filepath = join12(DOCS_DIR, filename);
43993
- if (!existsSync9(filepath)) {
44477
+ let filepath = join17(DOCS_DIR, filename);
44478
+ if (!existsSync13(filepath)) {
43994
44479
  const baseName = name.includes("/") ? name : titleToFilename(name);
43995
44480
  filename = baseName.endsWith(".md") ? baseName : `${baseName}.md`;
43996
- filepath = join12(DOCS_DIR, filename);
44481
+ filepath = join17(DOCS_DIR, filename);
43997
44482
  }
43998
- if (!existsSync9(filepath)) {
44483
+ if (!existsSync13(filepath)) {
43999
44484
  const allFiles = await getAllMdFiles(DOCS_DIR);
44000
44485
  const searchName = name.toLowerCase().replace(/\.md$/, "");
44001
44486
  const matchingFile = allFiles.find((file3) => {
@@ -44005,31 +44490,31 @@ var editCommand2 = new Command("edit").description("Edit a documentation file (m
44005
44490
  });
44006
44491
  if (matchingFile) {
44007
44492
  filename = matchingFile;
44008
- filepath = join12(DOCS_DIR, matchingFile);
44493
+ filepath = join17(DOCS_DIR, matchingFile);
44009
44494
  }
44010
44495
  }
44011
- if (!existsSync9(filepath)) {
44496
+ if (!existsSync13(filepath)) {
44012
44497
  console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
44013
44498
  process.exit(1);
44014
44499
  }
44015
- const fileContent = await readFile6(filepath, "utf-8");
44016
- const { data, content } = (0, import_gray_matter4.default)(fileContent);
44500
+ const fileContent = await readFile9(filepath, "utf-8");
44501
+ const { data, content } = (0, import_gray_matter5.default)(fileContent);
44017
44502
  const metadata = data;
44018
44503
  if (options2.title) metadata.title = options2.title;
44019
- if (options2.description) metadata.description = options2.description;
44504
+ if (options2.description) metadata.description = normalizeRefs(options2.description);
44020
44505
  if (options2.tags) metadata.tags = options2.tags.split(",").map((t) => t.trim());
44021
44506
  metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
44022
44507
  let updatedContent = content;
44023
44508
  if (options2.content) {
44024
- updatedContent = options2.content;
44509
+ updatedContent = normalizeRefs(options2.content);
44025
44510
  }
44026
44511
  if (options2.append) {
44027
44512
  updatedContent = `${content.trimEnd()}
44028
44513
 
44029
- ${options2.append}`;
44514
+ ${normalizeRefs(options2.append)}`;
44030
44515
  }
44031
- const newFileContent = import_gray_matter4.default.stringify(updatedContent, metadata);
44032
- await writeFile5(filepath, newFileContent, "utf-8");
44516
+ const newFileContent = import_gray_matter5.default.stringify(updatedContent, metadata);
44517
+ await writeFile7(filepath, newFileContent, "utf-8");
44033
44518
  await notifyDocUpdate(filename);
44034
44519
  if (options2.plain) {
44035
44520
  console.log(`Updated: ${filename}`);
@@ -44045,12 +44530,109 @@ ${options2.append}`;
44045
44530
  }
44046
44531
  }
44047
44532
  );
44048
- var docCommand = new Command("doc").description("Manage documentation").addCommand(createCommand3).addCommand(listCommand2).addCommand(viewCommand2).addCommand(editCommand2);
44533
+ var docCommand = new Command("doc").description("Manage documentation").argument("[name]", "Document name (shorthand for 'doc view <name>')").option("--plain", "Plain text output for AI").action(async (name, options2) => {
44534
+ if (!name) {
44535
+ docCommand.help();
44536
+ return;
44537
+ }
44538
+ try {
44539
+ const resolved = await resolveDocPath(name);
44540
+ if (!resolved) {
44541
+ console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
44542
+ process.exit(1);
44543
+ }
44544
+ const { filepath, filename } = resolved;
44545
+ const fileContent = await readFile9(filepath, "utf-8");
44546
+ const { data, content } = (0, import_gray_matter5.default)(fileContent);
44547
+ const metadata = data;
44548
+ if (options2.plain) {
44549
+ const border = "-".repeat(50);
44550
+ const titleBorder = "=".repeat(50);
44551
+ const projectRoot = process.cwd();
44552
+ console.log(`File: ${projectRoot}/.knowns/docs/${filename}`);
44553
+ console.log();
44554
+ console.log(metadata.title);
44555
+ console.log(titleBorder);
44556
+ console.log();
44557
+ console.log(`Created: ${metadata.createdAt}`);
44558
+ console.log(`Updated: ${metadata.updatedAt}`);
44559
+ if (metadata.tags && metadata.tags.length > 0) {
44560
+ console.log(`Tags: ${metadata.tags.join(", ")}`);
44561
+ }
44562
+ if (metadata.description) {
44563
+ console.log();
44564
+ console.log("Description:");
44565
+ console.log(border);
44566
+ console.log(metadata.description);
44567
+ }
44568
+ console.log();
44569
+ console.log("Content:");
44570
+ console.log(border);
44571
+ const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
44572
+ let enhancedContent = content.trimEnd();
44573
+ const linksToAdd = [];
44574
+ let match;
44575
+ while ((match = linkPattern.exec(content)) !== null) {
44576
+ const _linkText = match[1];
44577
+ const linkTarget = match[2];
44578
+ const fullMatch = match[0];
44579
+ if (linkTarget.startsWith("http://") || linkTarget.startsWith("https://")) {
44580
+ continue;
44581
+ }
44582
+ if (linkTarget.startsWith("../")) {
44583
+ continue;
44584
+ }
44585
+ let resolvedFilename = linkTarget.replace(/^\.\//, "");
44586
+ if (!resolvedFilename.endsWith(".md")) {
44587
+ resolvedFilename = `${resolvedFilename}.md`;
44588
+ }
44589
+ const resolvedPath = `@.knowns/docs/${resolvedFilename}`;
44590
+ linksToAdd.push({
44591
+ original: fullMatch,
44592
+ resolved: resolvedPath
44593
+ });
44594
+ }
44595
+ for (const { original, resolved: resolved2 } of linksToAdd) {
44596
+ enhancedContent = enhancedContent.replace(original, resolved2);
44597
+ }
44598
+ const knownProjectRoot = findProjectRoot();
44599
+ if (knownProjectRoot) {
44600
+ const fileStore = new FileStore(knownProjectRoot);
44601
+ const allTasks = await fileStore.getAllTasks();
44602
+ const taskMap = buildTaskMap(allTasks);
44603
+ enhancedContent = transformMentionsToRefs(enhancedContent, taskMap);
44604
+ }
44605
+ console.log(enhancedContent);
44606
+ } else {
44607
+ console.log(source_default.bold(`
44608
+ \u{1F4C4} ${metadata.title}
44609
+ `));
44610
+ if (metadata.description) {
44611
+ console.log(source_default.gray(metadata.description));
44612
+ console.log();
44613
+ }
44614
+ if (metadata.tags && metadata.tags.length > 0) {
44615
+ console.log(source_default.gray(`Tags: ${metadata.tags.join(", ")}`));
44616
+ }
44617
+ console.log(source_default.gray(`Updated: ${new Date(metadata.updatedAt).toLocaleString()}`));
44618
+ console.log(source_default.gray("-".repeat(60)));
44619
+ console.log(content);
44620
+ console.log();
44621
+ }
44622
+ } catch (error46) {
44623
+ console.error(source_default.red("Error viewing documentation:"), error46 instanceof Error ? error46.message : String(error46));
44624
+ process.exit(1);
44625
+ }
44626
+ });
44627
+ docCommand.addCommand(createCommand3);
44628
+ docCommand.addCommand(listCommand2);
44629
+ docCommand.addCommand(viewCommand2);
44630
+ docCommand.addCommand(editCommand2);
44049
44631
 
44050
44632
  // src/commands/config.ts
44051
- import { existsSync as existsSync10 } from "node:fs";
44052
- import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
44053
- import { join as join13 } from "node:path";
44633
+ import { existsSync as existsSync14 } from "node:fs";
44634
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
44635
+ import { join as join18 } from "node:path";
44054
44636
 
44055
44637
  // node_modules/zod/v4/classic/external.js
44056
44638
  var external_exports = {};
@@ -57607,12 +58189,12 @@ function getProjectRoot2() {
57607
58189
  return projectRoot;
57608
58190
  }
57609
58191
  async function loadConfig(projectRoot) {
57610
- const configPath = join13(projectRoot, CONFIG_FILE2);
57611
- if (!existsSync10(configPath)) {
58192
+ const configPath = join18(projectRoot, CONFIG_FILE2);
58193
+ if (!existsSync14(configPath)) {
57612
58194
  return { ...DEFAULT_CONFIG };
57613
58195
  }
57614
58196
  try {
57615
- const content = await readFile7(configPath, "utf-8");
58197
+ const content = await readFile10(configPath, "utf-8");
57616
58198
  const data = JSON.parse(content);
57617
58199
  const settings = data.settings || {};
57618
58200
  const validated = ConfigSchema.parse(settings);
@@ -57626,22 +58208,22 @@ async function loadConfig(projectRoot) {
57626
58208
  }
57627
58209
  }
57628
58210
  async function saveConfig(projectRoot, config2) {
57629
- const configPath = join13(projectRoot, CONFIG_FILE2);
57630
- const knownsDir = join13(projectRoot, ".knowns");
57631
- if (!existsSync10(knownsDir)) {
58211
+ const configPath = join18(projectRoot, CONFIG_FILE2);
58212
+ const knownsDir = join18(projectRoot, ".knowns");
58213
+ if (!existsSync14(knownsDir)) {
57632
58214
  await mkdir8(knownsDir, { recursive: true });
57633
58215
  }
57634
58216
  try {
57635
58217
  let existingData = {};
57636
- if (existsSync10(configPath)) {
57637
- const content = await readFile7(configPath, "utf-8");
58218
+ if (existsSync14(configPath)) {
58219
+ const content = await readFile10(configPath, "utf-8");
57638
58220
  existingData = JSON.parse(content);
57639
58221
  }
57640
58222
  const merged = {
57641
58223
  ...existingData,
57642
58224
  settings: config2
57643
58225
  };
57644
- await writeFile6(configPath, JSON.stringify(merged, null, 2), "utf-8");
58226
+ await writeFile8(configPath, JSON.stringify(merged, null, 2), "utf-8");
57645
58227
  } catch (error46) {
57646
58228
  console.error(source_default.red("\u2717 Failed to save config"));
57647
58229
  if (error46 instanceof Error) {
@@ -57785,7 +58367,7 @@ var configCommand = new Command("config").description("Manage configuration sett
57785
58367
  // package.json
57786
58368
  var package_default = {
57787
58369
  name: "knowns",
57788
- version: "0.2.0",
58370
+ version: "0.3.0",
57789
58371
  description: "CLI tool for dev teams to manage tasks and documentation",
57790
58372
  module: "index.ts",
57791
58373
  type: "module",
@@ -57810,13 +58392,13 @@ var package_default = {
57810
58392
  url: "https://github.com/knowns-dev/knowns/issues"
57811
58393
  },
57812
58394
  scripts: {
57813
- dev: "bun run --watch src/index.ts browser --no-open",
58395
+ dev: "npx tsx src/index.ts browser --no-open",
57814
58396
  watch: "nodemon",
57815
58397
  build: "npm run build:cli && npm run build:ui",
57816
58398
  "build:cli": "node scripts/build-cli.js",
57817
58399
  "build:ui": "vite build",
57818
- start: "bun run src/index.ts",
57819
- mcp: "bun run src/mcp/server.ts",
58400
+ start: "npx tsx src/index.ts",
58401
+ mcp: "npx tsx src/mcp/server.ts",
57820
58402
  test: "vitest run",
57821
58403
  lint: "biome check .",
57822
58404
  "lint:fix": "biome check --write .",
@@ -57824,15 +58406,25 @@ var package_default = {
57824
58406
  prepare: "husky"
57825
58407
  },
57826
58408
  dependencies: {
58409
+ "@dnd-kit/core": "^6.3.1",
58410
+ "@dnd-kit/sortable": "^10.0.0",
58411
+ "@dnd-kit/utilities": "^3.2.2",
57827
58412
  "@modelcontextprotocol/sdk": "^1.25.1",
58413
+ "@radix-ui/react-checkbox": "^1.3.3",
57828
58414
  "@radix-ui/react-collapsible": "^1.1.12",
57829
58415
  "@radix-ui/react-dialog": "^1.1.15",
58416
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
58417
+ "@radix-ui/react-label": "^2.1.8",
57830
58418
  "@radix-ui/react-navigation-menu": "^1.2.14",
57831
58419
  "@radix-ui/react-popover": "^1.1.15",
58420
+ "@radix-ui/react-progress": "^1.1.8",
57832
58421
  "@radix-ui/react-scroll-area": "^1.2.10",
58422
+ "@radix-ui/react-select": "^2.2.6",
57833
58423
  "@radix-ui/react-separator": "^1.1.8",
57834
58424
  "@radix-ui/react-slot": "^1.2.4",
57835
58425
  "@radix-ui/react-tooltip": "^1.2.8",
58426
+ "@tanstack/react-table": "^8.21.3",
58427
+ "@types/cors": "^2.8.19",
57836
58428
  "@uiw/react-md-editor": "^4.0.11",
57837
58429
  chalk: "^5.3.0",
57838
58430
  "class-variance-authority": "^0.7.1",
@@ -57841,6 +58433,7 @@ var package_default = {
57841
58433
  commander: "^12.1.0",
57842
58434
  cors: "^2.8.5",
57843
58435
  express: "^5.2.1",
58436
+ "framer-motion": "^12.23.26",
57844
58437
  "gray-matter": "^4.0.3",
57845
58438
  "lucide-react": "^0.562.0",
57846
58439
  marked: "^17.0.1",
@@ -57848,7 +58441,9 @@ var package_default = {
57848
58441
  react: "^19.2.3",
57849
58442
  "react-diff-viewer": "^3.1.1",
57850
58443
  "react-dom": "^19.2.3",
58444
+ sonner: "^2.0.7",
57851
58445
  "tailwind-merge": "^3.4.0",
58446
+ "tunnel-rat": "^0.1.2",
57852
58447
  turndown: "^7.2.2",
57853
58448
  "unist-util-visit": "^5.0.0",
57854
58449
  ws: "^8.18.3",
@@ -57864,6 +58459,7 @@ var package_default = {
57864
58459
  "@types/react-dom": "^19.2.3",
57865
58460
  "@types/ws": "^8.18.1",
57866
58461
  "@vitejs/plugin-react": "^5.1.2",
58462
+ "@vitest/coverage-v8": "^4.0.16",
57867
58463
  autoprefixer: "^10.4.23",
57868
58464
  concurrently: "^9.2.1",
57869
58465
  esbuild: "^0.27.2",