knowns 0.10.6 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +69 -3
  2. package/dist/index.js +2254 -447
  3. package/dist/mcp/server.js +791 -98
  4. package/dist/ui/assets/_baseUniq-BnxjK44c.js +1 -0
  5. package/dist/ui/assets/arc-DZvVmMpH.js +1 -0
  6. package/dist/ui/assets/architectureDiagram-VXUJARFQ-BZL6VCKH.js +36 -0
  7. package/dist/ui/assets/blockDiagram-VD42YOAC-ClTPsT-I.js +122 -0
  8. package/dist/ui/assets/c4Diagram-YG6GDRKO-Bw103idR.js +10 -0
  9. package/dist/ui/assets/channel-D9x35499.js +1 -0
  10. package/dist/ui/assets/chunk-4BX2VUAB-DypK8CJ4.js +1 -0
  11. package/dist/ui/assets/chunk-55IACEB6-BBDdrBL9.js +1 -0
  12. package/dist/ui/assets/chunk-B4BG7PRW-K5LInW1J.js +165 -0
  13. package/dist/ui/assets/chunk-DI55MBZ5-DnPeq72k.js +220 -0
  14. package/dist/ui/assets/chunk-FMBD7UC4-BouMIneX.js +15 -0
  15. package/dist/ui/assets/chunk-QN33PNHL-Ba1GHx9G.js +1 -0
  16. package/dist/ui/assets/chunk-QZHKN3VN-DR78GQki.js +1 -0
  17. package/dist/ui/assets/chunk-TZMSLE5B-DS5ZUS0q.js +1 -0
  18. package/dist/ui/assets/classDiagram-2ON5EDUG-BWeu8ZEl.js +1 -0
  19. package/dist/ui/assets/classDiagram-v2-WZHVMYZB-BWeu8ZEl.js +1 -0
  20. package/dist/ui/assets/clone-CojkIg2_.js +1 -0
  21. package/dist/ui/assets/cose-bilkent-S5V4N54A-d4bDKFYg.js +1 -0
  22. package/dist/ui/assets/cytoscape.esm-5J0xJHOV.js +321 -0
  23. package/dist/ui/assets/dagre-6UL2VRFP-ehZmQh8m.js +4 -0
  24. package/dist/ui/assets/defaultLocale-DX6XiGOO.js +1 -0
  25. package/dist/ui/assets/diagram-PSM6KHXK-BHu3Ht-9.js +24 -0
  26. package/dist/ui/assets/diagram-QEK2KX5R-B-FuS1Ks.js +43 -0
  27. package/dist/ui/assets/diagram-S2PKOQOG-9mYWKwpl.js +24 -0
  28. package/dist/ui/assets/erDiagram-Q2GNP2WA-DyH-5pPP.js +60 -0
  29. package/dist/ui/assets/flowDiagram-NV44I4VS-nG1wIf-7.js +162 -0
  30. package/dist/ui/assets/ganttDiagram-JELNMOA3-Dme9C52c.js +267 -0
  31. package/dist/ui/assets/gitGraphDiagram-NY62KEGX-Ler4P2Pa.js +65 -0
  32. package/dist/ui/assets/graph-CVs9_WqW.js +1 -0
  33. package/dist/ui/assets/index-BDuGtZOD.css +1 -0
  34. package/dist/ui/assets/index-BfOQwuot.js +424 -0
  35. package/dist/ui/assets/infoDiagram-WHAUD3N6-B_M3sR5-.js +2 -0
  36. package/dist/ui/assets/init-Gi6I4Gst.js +1 -0
  37. package/dist/ui/assets/journeyDiagram-XKPGCS4Q-BSLvczLS.js +139 -0
  38. package/dist/ui/assets/kanban-definition-3W4ZIXB7-DeqtFXdg.js +89 -0
  39. package/dist/ui/assets/katex-DhXJpUyf.js +261 -0
  40. package/dist/ui/assets/layout-D33h6Hj3.js +1 -0
  41. package/dist/ui/assets/linear-BnzJ09ng.js +1 -0
  42. package/dist/ui/assets/min-CrKfxF3L.js +1 -0
  43. package/dist/ui/assets/mindmap-definition-VGOIOE7T-ClMK1T7-.js +68 -0
  44. package/dist/ui/assets/ordinal-Cboi1Yqb.js +1 -0
  45. package/dist/ui/assets/pieDiagram-ADFJNKIX-t0ZVgZgx.js +30 -0
  46. package/dist/ui/assets/quadrantDiagram-AYHSOK5B-DpcRGPXU.js +7 -0
  47. package/dist/ui/assets/requirementDiagram-UZGBJVZJ-BRd7le_X.js +64 -0
  48. package/dist/ui/assets/sankeyDiagram-TZEHDZUN-CvzudzQ1.js +10 -0
  49. package/dist/ui/assets/sequenceDiagram-WL72ISMW-vMDZGgni.js +145 -0
  50. package/dist/ui/assets/stateDiagram-FKZM4ZOC-CsKLmSJ0.js +1 -0
  51. package/dist/ui/assets/stateDiagram-v2-4FDKWEC3-CBgoBfXm.js +1 -0
  52. package/dist/ui/assets/timeline-definition-IT6M3QCI-CtNX-O2_.js +61 -0
  53. package/dist/ui/assets/treemap-KMMF4GRG-DH2wEEvC.js +128 -0
  54. package/dist/ui/assets/xychartDiagram-PRI3JC2R-RlXBItY0.js +7 -0
  55. package/dist/ui/index.html +2 -2
  56. package/package.json +2 -4
  57. package/dist/ui/assets/index-2CDomS1a.js +0 -317
  58. package/dist/ui/assets/index-B1mpVDN3.css +0 -1
  59. package/dist/ui/assets/inter-v12-latin-100-46Mq0mOp.woff +0 -0
  60. package/dist/ui/assets/inter-v12-latin-100-BQDzDElq.woff2 +0 -0
  61. package/dist/ui/assets/inter-v12-latin-200-BxfrU12A.woff2 +0 -0
  62. package/dist/ui/assets/inter-v12-latin-200-DXfqWPZg.woff +0 -0
  63. package/dist/ui/assets/inter-v12-latin-300-DEbyFmpd.woff2 +0 -0
  64. package/dist/ui/assets/inter-v12-latin-300-f7r92Nkj.woff +0 -0
  65. package/dist/ui/assets/inter-v12-latin-500-BQ2gQN_M.woff +0 -0
  66. package/dist/ui/assets/inter-v12-latin-500-DfX5FI9E.woff2 +0 -0
  67. package/dist/ui/assets/inter-v12-latin-600-BvOeHRLc.woff2 +0 -0
  68. package/dist/ui/assets/inter-v12-latin-600-D01NXWOK.woff +0 -0
  69. package/dist/ui/assets/inter-v12-latin-700-B5TOIllR.woff +0 -0
  70. package/dist/ui/assets/inter-v12-latin-700-Bj1B9WKG.woff2 +0 -0
  71. package/dist/ui/assets/inter-v12-latin-800-Bdy4lAMa.woff2 +0 -0
  72. package/dist/ui/assets/inter-v12-latin-800-DFVvDWwT.woff +0 -0
  73. package/dist/ui/assets/inter-v12-latin-900-CMga-52B.woff2 +0 -0
  74. package/dist/ui/assets/inter-v12-latin-900-ORHAl5ZU.woff +0 -0
  75. package/dist/ui/assets/inter-v12-latin-regular-CahmJf_6.woff +0 -0
  76. package/dist/ui/assets/inter-v12-latin-regular-YtgfLPRn.woff2 +0 -0
  77. package/dist/ui/assets/module-RjUF93sV.js +0 -716
  78. package/dist/ui/assets/native-48B9X9Wg.js +0 -1
package/dist/index.js CHANGED
@@ -3316,10 +3316,10 @@ var require_stringify = __commonJS({
3316
3316
  data = Object.assign({}, file3.data, data);
3317
3317
  const open = opts.delimiters[0];
3318
3318
  const close = opts.delimiters[1];
3319
- const matter12 = engine.stringify(data, options2).trim();
3319
+ const matter16 = engine.stringify(data, options2).trim();
3320
3320
  let buf = "";
3321
- if (matter12 !== "{}") {
3322
- buf = newline(open) + newline(matter12) + newline(close);
3321
+ if (matter16 !== "{}") {
3322
+ buf = newline(open) + newline(matter16) + newline(close);
3323
3323
  }
3324
3324
  if (typeof file3.excerpt === "string" && file3.excerpt !== "") {
3325
3325
  if (str2.indexOf(file3.excerpt.trim()) === -1) {
@@ -3425,19 +3425,19 @@ var require_gray_matter = __commonJS({
3425
3425
  var toFile = require_to_file();
3426
3426
  var parse4 = require_parse();
3427
3427
  var utils = require_utils();
3428
- function matter12(input, options2) {
3428
+ function matter16(input, options2) {
3429
3429
  if (input === "") {
3430
3430
  return { data: {}, content: input, excerpt: "", orig: input };
3431
3431
  }
3432
3432
  let file3 = toFile(input);
3433
- const cached2 = matter12.cache[file3.content];
3433
+ const cached2 = matter16.cache[file3.content];
3434
3434
  if (!options2) {
3435
3435
  if (cached2) {
3436
3436
  file3 = Object.assign({}, cached2);
3437
3437
  file3.orig = cached2.orig;
3438
3438
  return file3;
3439
3439
  }
3440
- matter12.cache[file3.content] = file3;
3440
+ matter16.cache[file3.content] = file3;
3441
3441
  }
3442
3442
  return parseMatter(file3, options2);
3443
3443
  }
@@ -3459,7 +3459,7 @@ var require_gray_matter = __commonJS({
3459
3459
  }
3460
3460
  str2 = str2.slice(openLen);
3461
3461
  const len = str2.length;
3462
- const language = matter12.language(str2, opts);
3462
+ const language = matter16.language(str2, opts);
3463
3463
  if (language.name) {
3464
3464
  file3.language = language.name;
3465
3465
  str2 = str2.slice(language.raw.length);
@@ -3494,24 +3494,24 @@ var require_gray_matter = __commonJS({
3494
3494
  }
3495
3495
  return file3;
3496
3496
  }
3497
- matter12.engines = engines2;
3498
- matter12.stringify = function(file3, data, options2) {
3499
- if (typeof file3 === "string") file3 = matter12(file3, options2);
3497
+ matter16.engines = engines2;
3498
+ matter16.stringify = function(file3, data, options2) {
3499
+ if (typeof file3 === "string") file3 = matter16(file3, options2);
3500
3500
  return stringify(file3, data, options2);
3501
3501
  };
3502
- matter12.read = function(filepath, options2) {
3502
+ matter16.read = function(filepath, options2) {
3503
3503
  const str2 = fs.readFileSync(filepath, "utf8");
3504
- const file3 = matter12(str2, options2);
3504
+ const file3 = matter16(str2, options2);
3505
3505
  file3.path = filepath;
3506
3506
  return file3;
3507
3507
  };
3508
- matter12.test = function(str2, options2) {
3508
+ matter16.test = function(str2, options2) {
3509
3509
  return utils.startsWith(str2, defaults2(options2).delimiters[0]);
3510
3510
  };
3511
- matter12.language = function(str2, options2) {
3511
+ matter16.language = function(str2, options2) {
3512
3512
  const opts = defaults2(options2);
3513
3513
  const open = opts.delimiters[0];
3514
- if (matter12.test(str2)) {
3514
+ if (matter16.test(str2)) {
3515
3515
  str2 = str2.slice(open.length);
3516
3516
  }
3517
3517
  const language = str2.slice(0, str2.search(/\r?\n/));
@@ -3520,11 +3520,11 @@ var require_gray_matter = __commonJS({
3520
3520
  name: language ? language.trim() : ""
3521
3521
  };
3522
3522
  };
3523
- matter12.cache = {};
3524
- matter12.clearCache = function() {
3525
- matter12.cache = {};
3523
+ matter16.cache = {};
3524
+ matter16.clearCache = function() {
3525
+ matter16.cache = {};
3526
3526
  };
3527
- module2.exports = matter12;
3527
+ module2.exports = matter16;
3528
3528
  }
3529
3529
  });
3530
3530
 
@@ -12444,7 +12444,7 @@ var require_no_conflict = __commonJS({
12444
12444
  "node_modules/handlebars/dist/cjs/handlebars/no-conflict.js"(exports2, module2) {
12445
12445
  "use strict";
12446
12446
  exports2.__esModule = true;
12447
- exports2["default"] = function(Handlebars2) {
12447
+ exports2["default"] = function(Handlebars4) {
12448
12448
  (function() {
12449
12449
  if (typeof globalThis === "object") return;
12450
12450
  Object.prototype.__defineGetter__("__magic__", function() {
@@ -12454,11 +12454,11 @@ var require_no_conflict = __commonJS({
12454
12454
  delete Object.prototype.__magic__;
12455
12455
  })();
12456
12456
  var $Handlebars = globalThis.Handlebars;
12457
- Handlebars2.noConflict = function() {
12458
- if (globalThis.Handlebars === Handlebars2) {
12457
+ Handlebars4.noConflict = function() {
12458
+ if (globalThis.Handlebars === Handlebars4) {
12459
12459
  globalThis.Handlebars = $Handlebars;
12460
12460
  }
12461
- return Handlebars2;
12461
+ return Handlebars4;
12462
12462
  };
12463
12463
  };
12464
12464
  module2.exports = exports2["default"];
@@ -14378,7 +14378,7 @@ var require_util3 = __commonJS({
14378
14378
  return path3;
14379
14379
  }
14380
14380
  exports2.normalize = normalize3;
14381
- function join43(aRoot, aPath) {
14381
+ function join47(aRoot, aPath) {
14382
14382
  if (aRoot === "") {
14383
14383
  aRoot = ".";
14384
14384
  }
@@ -14410,7 +14410,7 @@ var require_util3 = __commonJS({
14410
14410
  }
14411
14411
  return joined;
14412
14412
  }
14413
- exports2.join = join43;
14413
+ exports2.join = join47;
14414
14414
  exports2.isAbsolute = function(aPath) {
14415
14415
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
14416
14416
  };
@@ -14583,7 +14583,7 @@ var require_util3 = __commonJS({
14583
14583
  parsed.path = parsed.path.substring(0, index + 1);
14584
14584
  }
14585
14585
  }
14586
- sourceURL = join43(urlGenerate(parsed), sourceURL);
14586
+ sourceURL = join47(urlGenerate(parsed), sourceURL);
14587
14587
  }
14588
14588
  return normalize3(sourceURL);
14589
14589
  }
@@ -36817,7 +36817,7 @@ var require_view = __commonJS({
36817
36817
  var dirname9 = path3.dirname;
36818
36818
  var basename5 = path3.basename;
36819
36819
  var extname = path3.extname;
36820
- var join43 = path3.join;
36820
+ var join47 = path3.join;
36821
36821
  var resolve3 = path3.resolve;
36822
36822
  module2.exports = View;
36823
36823
  function View(name, options2) {
@@ -36879,12 +36879,12 @@ var require_view = __commonJS({
36879
36879
  };
36880
36880
  View.prototype.resolve = function resolve4(dir, file3) {
36881
36881
  var ext2 = this.ext;
36882
- var path4 = join43(dir, file3);
36882
+ var path4 = join47(dir, file3);
36883
36883
  var stat5 = tryStat(path4);
36884
36884
  if (stat5 && stat5.isFile()) {
36885
36885
  return path4;
36886
36886
  }
36887
- path4 = join43(dir, basename5(file3, ext2), "index" + ext2);
36887
+ path4 = join47(dir, basename5(file3, ext2), "index" + ext2);
36888
36888
  stat5 = tryStat(path4);
36889
36889
  if (stat5 && stat5.isFile()) {
36890
36890
  return path4;
@@ -38622,11 +38622,11 @@ var require_router = __commonJS({
38622
38622
  var slice = Array.prototype.slice;
38623
38623
  var flatten = Array.prototype.flat;
38624
38624
  var methods = METHODS.map((method) => method.toLowerCase());
38625
- module2.exports = Router12;
38625
+ module2.exports = Router13;
38626
38626
  module2.exports.Route = Route;
38627
- function Router12(options2) {
38628
- if (!(this instanceof Router12)) {
38629
- return new Router12(options2);
38627
+ function Router13(options2) {
38628
+ if (!(this instanceof Router13)) {
38629
+ return new Router13(options2);
38630
38630
  }
38631
38631
  const opts = options2 || {};
38632
38632
  function router(req, res, next) {
@@ -38640,9 +38640,9 @@ var require_router = __commonJS({
38640
38640
  router.stack = [];
38641
38641
  return router;
38642
38642
  }
38643
- Router12.prototype = function() {
38643
+ Router13.prototype = function() {
38644
38644
  };
38645
- Router12.prototype.param = function param(name, fn) {
38645
+ Router13.prototype.param = function param(name, fn) {
38646
38646
  if (!name) {
38647
38647
  throw new TypeError("argument name is required");
38648
38648
  }
@@ -38662,7 +38662,7 @@ var require_router = __commonJS({
38662
38662
  params.push(fn);
38663
38663
  return this;
38664
38664
  };
38665
- Router12.prototype.handle = function handle(req, res, callback2) {
38665
+ Router13.prototype.handle = function handle(req, res, callback2) {
38666
38666
  if (!callback2) {
38667
38667
  throw new TypeError("argument callback is required");
38668
38668
  }
@@ -38789,7 +38789,7 @@ var require_router = __commonJS({
38789
38789
  }
38790
38790
  }
38791
38791
  };
38792
- Router12.prototype.use = function use(handler) {
38792
+ Router13.prototype.use = function use(handler) {
38793
38793
  let offset = 0;
38794
38794
  let path3 = "/";
38795
38795
  if (typeof handler !== "function") {
@@ -38822,7 +38822,7 @@ var require_router = __commonJS({
38822
38822
  }
38823
38823
  return this;
38824
38824
  };
38825
- Router12.prototype.route = function route(path3) {
38825
+ Router13.prototype.route = function route(path3) {
38826
38826
  const route2 = new Route(path3);
38827
38827
  const layer = new Layer(path3, {
38828
38828
  sensitive: this.caseSensitive,
@@ -38837,7 +38837,7 @@ var require_router = __commonJS({
38837
38837
  return route2;
38838
38838
  };
38839
38839
  methods.concat("all").forEach(function(method) {
38840
- Router12.prototype[method] = function(path3) {
38840
+ Router13.prototype[method] = function(path3) {
38841
38841
  const route = this.route(path3);
38842
38842
  route[method].apply(route, slice.call(arguments, 1));
38843
38843
  return this;
@@ -39020,7 +39020,7 @@ var require_application = __commonJS({
39020
39020
  var compileTrust = require_utils5().compileTrust;
39021
39021
  var resolve3 = __require("node:path").resolve;
39022
39022
  var once = require_once();
39023
- var Router12 = require_router();
39023
+ var Router13 = require_router();
39024
39024
  var slice = Array.prototype.slice;
39025
39025
  var flatten = Array.prototype.flat;
39026
39026
  var app = exports2 = module2.exports = {};
@@ -39036,7 +39036,7 @@ var require_application = __commonJS({
39036
39036
  enumerable: true,
39037
39037
  get: function getrouter() {
39038
39038
  if (router === null) {
39039
- router = new Router12({
39039
+ router = new Router13({
39040
39040
  caseSensitive: this.enabled("case sensitive routing"),
39041
39041
  strict: this.enabled("strict routing")
39042
39042
  });
@@ -40529,7 +40529,7 @@ var require_send = __commonJS({
40529
40529
  var Stream2 = __require("stream");
40530
40530
  var util2 = __require("util");
40531
40531
  var extname = path3.extname;
40532
- var join43 = path3.join;
40532
+ var join47 = path3.join;
40533
40533
  var normalize3 = path3.normalize;
40534
40534
  var resolve3 = path3.resolve;
40535
40535
  var sep3 = path3.sep;
@@ -40701,7 +40701,7 @@ var require_send = __commonJS({
40701
40701
  return res;
40702
40702
  }
40703
40703
  parts = path4.split(sep3);
40704
- path4 = normalize3(join43(root, path4));
40704
+ path4 = normalize3(join47(root, path4));
40705
40705
  } else {
40706
40706
  if (UP_PATH_REGEXP.test(path4)) {
40707
40707
  debug('malicious path "%s"', path4);
@@ -40834,7 +40834,7 @@ var require_send = __commonJS({
40834
40834
  if (err) return self.onStatError(err);
40835
40835
  return self.error(404);
40836
40836
  }
40837
- var p = join43(path4, self._index[i]);
40837
+ var p = join47(path4, self._index[i]);
40838
40838
  debug('stat "%s"', p);
40839
40839
  fs.stat(p, function(err2, stat5) {
40840
40840
  if (err2) return next(err2);
@@ -41574,7 +41574,7 @@ var require_express = __commonJS({
41574
41574
  var EventEmitter2 = __require("node:events").EventEmitter;
41575
41575
  var mixin = require_merge_descriptors();
41576
41576
  var proto2 = require_application();
41577
- var Router12 = require_router();
41577
+ var Router13 = require_router();
41578
41578
  var req = require_request();
41579
41579
  var res = require_response();
41580
41580
  exports2 = module2.exports = createApplication;
@@ -41596,8 +41596,8 @@ var require_express = __commonJS({
41596
41596
  exports2.application = proto2;
41597
41597
  exports2.request = req;
41598
41598
  exports2.response = res;
41599
- exports2.Route = Router12.Route;
41600
- exports2.Router = Router12;
41599
+ exports2.Route = Router13.Route;
41600
+ exports2.Router = Router13;
41601
41601
  exports2.json = bodyParser.json;
41602
41602
  exports2.raw = bodyParser.raw;
41603
41603
  exports2.static = require_serve_static();
@@ -41741,9 +41741,9 @@ async function getImportsWithMetadata(projectRoot) {
41741
41741
  }
41742
41742
  const importsDir = getImportsDir(projectRoot);
41743
41743
  if (existsSync11(importsDir)) {
41744
- const { readdir: readdir16 } = await import("node:fs/promises");
41744
+ const { readdir: readdir19 } = await import("node:fs/promises");
41745
41745
  try {
41746
- const entries = await readdir16(importsDir, { withFileTypes: true });
41746
+ const entries = await readdir19(importsDir, { withFileTypes: true });
41747
41747
  for (const entry of entries) {
41748
41748
  if (!entry.isDirectory()) continue;
41749
41749
  if (entry.name.startsWith(".")) continue;
@@ -57595,6 +57595,7 @@ function parseTaskMarkdown(content) {
57595
57595
  assignee: frontmatter.assignee,
57596
57596
  labels: frontmatter.labels || [],
57597
57597
  parent: frontmatter.parent,
57598
+ spec: frontmatter.spec,
57598
57599
  subtasks: [],
57599
57600
  // Will be populated by FileStore
57600
57601
  createdAt: new Date(frontmatter.createdAt),
@@ -57621,6 +57622,7 @@ function serializeTaskMarkdown(task) {
57621
57622
  };
57622
57623
  if (task.assignee) frontmatter.assignee = task.assignee;
57623
57624
  if (task.parent) frontmatter.parent = task.parent;
57625
+ if (task.spec) frontmatter.spec = task.spec;
57624
57626
  let body = "";
57625
57627
  body += `# ${task.title}
57626
57628
 
@@ -58799,42 +58801,39 @@ var {
58799
58801
  var import_prompts = __toESM(require_prompts3(), 1);
58800
58802
 
58801
58803
  // src/codegen/skill-sync.ts
58802
- import { existsSync as existsSync2 } from "node:fs";
58804
+ import { existsSync as existsSync2, readdirSync, rmSync } from "node:fs";
58803
58805
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
58804
58806
  import { dirname, join as join4 } from "node:path";
58805
58807
 
58806
- // src/instructions/skills/knowns.commit/SKILL.md
58807
- var SKILL_default = '---\nname: knowns.commit\ndescription: Use when committing code changes with proper conventional commit format and verification\n---\n\n# Committing Changes\n\nCreate well-formatted commits following conventional commit standards.\n\n**Announce at start:** "I\'m using the knowns.commit skill to commit changes."\n\n**Core principle:** VERIFY BEFORE COMMITTING - check staged changes, ask for confirmation.\n\n## The Process\n\n### Step 1: Review Staged Changes\n\n```bash\ngit status\ngit diff --staged\n```\n\n### Step 2: Generate Commit Message\n\n**Format:**\n```\n<type>(<scope>): <message>\n\n- Bullet point summarizing change\n- Another point if needed\n```\n\n**Types:**\n\n| Type | Description |\n|------|-------------|\n| `feat` | New feature |\n| `fix` | Bug fix |\n| `docs` | Documentation only |\n| `style` | Formatting, no code change |\n| `refactor` | Code restructure |\n| `perf` | Performance improvement |\n| `test` | Adding tests |\n| `chore` | Maintenance |\n\n**Rules:**\n- Title lowercase, no period, max 50 chars\n- Scope optional but recommended\n- Body explains *why*, not just *what*\n\n### Step 3: Ask for Confirmation\n\nPresent message to user:\n\n```\nReady to commit:\n\nfeat(auth): add JWT token refresh\n\n- Added refresh token endpoint\n- Tokens expire after 1 hour\n\nProceed? (yes/no/edit)\n```\n\n**Wait for user approval.**\n\n### Step 4: Commit\n\n```bash\ngit commit -m "feat(auth): add JWT token refresh\n\n- Added refresh token endpoint\n- Tokens expire after 1 hour"\n```\n\n## Guidelines\n\n- Only commit staged files (don\'t `git add` unless asked)\n- NO "Co-Authored-By" lines\n- NO "Generated with Claude Code" ads\n- Ask before committing, never auto-commit\n\n## Examples\n\n**Good:**\n```\nfeat(api): add user profile endpoint\nfix(auth): handle expired token gracefully\ndocs(readme): update installation steps\n```\n\n**Bad:**\n```\nupdate code (too vague)\nWIP (not ready)\nfix bug (which bug?)\n```\n\n## Remember\n\n- Review staged changes first\n- Follow conventional format\n- Ask for confirmation\n- Keep messages concise\n';
58808
+ // src/instructions/skills/kn:commit/SKILL.md
58809
+ var SKILL_default = '---\nname: kn:commit\ndescription: Use when committing code changes with proper conventional commit format and verification\n---\n\n# Committing Changes\n\n**Announce:** "Using kn:commit to commit changes."\n\n**Core principle:** VERIFY BEFORE COMMITTING - check staged changes, ask for confirmation.\n\n## Step 1: Review Staged Changes\n\n```bash\ngit status\ngit diff --staged\n```\n\n## Step 2: Generate Commit Message\n\n**Format:**\n```\n<type>(<scope>): <message>\n\n- Bullet point summarizing change\n```\n\n**Types:** feat, fix, docs, style, refactor, perf, test, chore\n\n**Rules:**\n- Title lowercase, no period, max 50 chars\n- Body explains *why*, not just *what*\n\n## Step 3: Ask for Confirmation\n\n```\nReady to commit:\n\nfeat(auth): add JWT token refresh\n\n- Added refresh token endpoint\n\nProceed? (yes/no/edit)\n```\n\n**Wait for user approval.**\n\n## Step 4: Commit\n\n```bash\ngit commit -m "feat(auth): add JWT token refresh\n\n- Added refresh token endpoint"\n```\n\n## Guidelines\n\n- Only commit staged files\n- NO "Co-Authored-By" lines\n- NO "Generated with Claude Code" ads\n- Ask before committing\n\n## Checklist\n\n- [ ] Reviewed staged changes\n- [ ] Message follows convention\n- [ ] User approved\n';
58808
58810
 
58809
- // src/instructions/skills/knowns.doc/SKILL.md
58810
- var SKILL_default2 = '---\nname: knowns.doc\ndescription: Use when working with Knowns documentation - viewing, searching, creating, or updating docs\n---\n\n# Working with Documentation\n\nNavigate, create, and update Knowns project documentation.\n\n**Announce at start:** "I\'m using the knowns.doc skill to work with documentation."\n\n**Core principle:** SEARCH BEFORE CREATING - avoid duplicates.\n\n## Quick Reference\n\n{{#if mcp}}\n```json\n// List all docs\nmcp__knowns__list_docs({})\n\n// View doc (smart mode)\nmcp__knowns__get_doc({ "path": "<path>", "smart": true })\n\n// Search docs\nmcp__knowns__search_docs({ "query": "<query>" })\n\n// Create doc\nmcp__knowns__create_doc({\n "title": "<title>",\n "description": "<description>",\n "tags": ["tag1", "tag2"],\n "folder": "folder"\n})\n\n// Update doc\nmcp__knowns__update_doc({\n "path": "<path>",\n "content": "content"\n})\n\n// Update section only\nmcp__knowns__update_doc({\n "path": "<path>",\n "section": "2",\n "content": "new section content"\n})\n```\n{{else}}\n```bash\n# List all docs\nknowns doc list --plain\n\n# View doc (auto-handles large docs)\nknowns doc "<path>" --plain\n\n# Search docs\nknowns search "<query>" --type doc --plain\n\n# Create doc\nknowns doc create "<title>" -d "<description>" -t "tags" -f "folder"\n\n# Update doc\nknowns doc edit "<path>" -c "content" # Replace\nknowns doc edit "<path>" -a "content" # Append\nknowns doc edit "<path>" --section "2" -c "content" # Section only\n```\n{{/if}}\n\n## Reading Documents\n\n{{#if mcp}}\n**Use smart mode:**\n```json\nmcp__knowns__get_doc({ "path": "<path>", "smart": true })\n```\n\n- Small doc (\u22642000 tokens) \u2192 full content\n- Large doc \u2192 stats + TOC, then request specific section\n{{else}}\n**View doc:**\n```bash\nknowns doc "<path>" --plain\n```\n\nFor large docs, use sections:\n```bash\nknowns doc "<path>" --toc --plain\nknowns doc "<path>" --section "2" --plain\n```\n{{/if}}\n\n## Creating Documents\n\n### Step 1: Search First\n\n{{#if mcp}}\n```json\nmcp__knowns__search_docs({ "query": "<topic>" })\n```\n{{else}}\n```bash\nknowns search "<topic>" --type doc --plain\n```\n{{/if}}\n\n**Don\'t duplicate.** Update existing docs when possible.\n\n### Step 2: Choose Location\n\n| Doc Type | Location | Folder |\n|----------|----------|--------|\n| Core (README, ARCH) | Root | (none) |\n| Guide | `guides/` | `guides` |\n| Pattern | `patterns/` | `patterns` |\n| API doc | `api/` | `api` |\n\n### Step 3: Create\n\n{{#if mcp}}\n```json\nmcp__knowns__create_doc({\n "title": "<title>",\n "description": "<brief description>",\n "tags": ["tag1", "tag2"],\n "folder": "folder"\n})\n```\n{{else}}\n```bash\nknowns doc create "<title>" \\\n -d "<brief description>" \\\n -t "tag1,tag2" \\\n -f "folder" # optional\n```\n{{/if}}\n\n### Step 4: Add Content\n\n{{#if mcp}}\n```json\nmcp__knowns__update_doc({\n "path": "<path>",\n "content": "# Title\\n\\n## 1. Overview\\nWhat this doc covers.\\n\\n## 2. Details\\nMain content."\n})\n```\n{{else}}\n```bash\nknowns doc edit "<path>" -c "$(cat <<\'EOF\'\n# Title\n\n## 1. Overview\nWhat this doc covers.\n\n## 2. Details\nMain content.\n\n## 3. Examples\nPractical examples.\nEOF\n)"\n```\n{{/if}}\n\n## Updating Documents\n\n### View First\n\n{{#if mcp}}\n```json\nmcp__knowns__get_doc({ "path": "<path>", "smart": true })\nmcp__knowns__get_doc({ "path": "<path>", "toc": true })\n```\n{{else}}\n```bash\nknowns doc "<path>" --plain\nknowns doc "<path>" --toc --plain # For large docs\n```\n{{/if}}\n\n### Update Methods\n\n| Method | Use When |\n|--------|----------|\n| Replace all | Rewriting entire doc |\n| Append | Adding to end |\n| Section edit | Updating one section |\n\n**Section edit is most efficient** - less context, safer.\n\n{{#if mcp}}\n```json\n// Update just section 3\nmcp__knowns__update_doc({\n "path": "<path>",\n "section": "3",\n "content": "## 3. New Content\\n\\nUpdated section content..."\n})\n```\n{{else}}\n```bash\n# Update just section 3\nknowns doc edit "<path>" --section "3" -c "## 3. New Content\n\nUpdated section content..."\n```\n{{/if}}\n\n## Document Structure\n\nUse numbered headings for section editing to work:\n\n```markdown\n# Title (H1 - only one)\n\n## 1. Overview\n...\n\n## 2. Installation\n...\n\n## 3. Configuration\n...\n```\n\n## Remember\n\n- Search before creating (avoid duplicates)\n- Use smart mode when reading\n- Use section editing for targeted updates\n- Use numbered headings\n- Reference docs with `@doc/<path>`\n';
58811
+ // src/instructions/skills/kn:doc/SKILL.md
58812
+ var SKILL_default2 = '---\nname: kn:doc\ndescription: Use when working with Knowns documentation - viewing, searching, creating, or updating docs\n---\n\n# Working with Documentation\n\n**Announce:** "Using kn:doc to work with documentation."\n\n**Core principle:** SEARCH BEFORE CREATING - avoid duplicates.\n\n## Quick Reference\n\n```json\n// List docs\nmcp__knowns__list_docs({})\n\n// View doc (smart mode)\nmcp__knowns__get_doc({ "path": "<path>", "smart": true })\n\n// Search docs\nmcp__knowns__search_docs({ "query": "<query>" })\n\n// Create doc (MUST include description)\nmcp__knowns__create_doc({\n "title": "<title>",\n "description": "<brief description of what this doc covers>",\n "tags": ["tag1", "tag2"],\n "folder": "folder"\n})\n\n// Update content\nmcp__knowns__update_doc({\n "path": "<path>",\n "content": "content"\n})\n\n// Update metadata (title, description, tags)\nmcp__knowns__update_doc({\n "path": "<path>",\n "title": "New Title",\n "description": "Updated description",\n "tags": ["new", "tags"]\n})\n\n// Update section only\nmcp__knowns__update_doc({\n "path": "<path>",\n "section": "2",\n "content": "## 2. New Content\\n\\n..."\n})\n```\n\n## Creating Documents\n\n1. Search first (avoid duplicates)\n2. Choose location:\n\n| Type | Folder |\n|------|--------|\n| Core | (root) |\n| Guide | `guides` |\n| Pattern | `patterns` |\n| API | `api` |\n\n3. Create with **title + description + tags**\n4. Add content\n5. **Validate** after creating\n\n**CRITICAL:** Always include `description` - validate will fail without it!\n\n## Updating Documents\n\n**Section edit is most efficient:**\n```json\nmcp__knowns__update_doc({\n "path": "<path>",\n "section": "3",\n "content": "## 3. New Content\\n\\n..."\n})\n```\n\n## Validate After Changes\n\n**CRITICAL:** After creating/updating docs, validate:\n\n```json\nmcp__knowns__validate({ "scope": "docs" })\n```\n\nIf errors found, fix before continuing.\n\n## Mermaid Diagrams\n\nWebUI supports mermaid rendering. Use for:\n- Architecture diagrams\n- Flowcharts\n- Sequence diagrams\n- Entity relationships\n\n````markdown\n```mermaid\ngraph TD\n A[Start] --> B{Decision}\n B -->|Yes| C[Action]\n B -->|No| D[End]\n```\n````\n\nDiagrams render automatically in WebUI preview.\n\n## Checklist\n\n- [ ] Searched for existing docs\n- [ ] Created with **description** (required!)\n- [ ] Used section editing for updates\n- [ ] Used mermaid for complex flows (optional)\n- [ ] Referenced with `@doc/<path>`\n- [ ] **Validated after changes**\n';
58811
58813
 
58812
- // src/instructions/skills/knowns.extract/SKILL.md
58813
- var SKILL_default3 = '---\nname: knowns.extract\ndescription: Use when extracting reusable patterns, solutions, or knowledge into documentation\n---\n\n# Extracting Knowledge\n\nConvert implementations, patterns, or solutions into reusable project documentation.\n\n**Announce at start:** "I\'m using the knowns.extract skill to extract knowledge."\n\n**Core principle:** ONLY EXTRACT GENERALIZABLE KNOWLEDGE.\n\n## The Process\n\n### Step 1: Identify Source\n\n**From task (if ID provided):**\n{{#if mcp}}\n```json\nmcp__knowns__get_task({ "taskId": "$ARGUMENTS" })\n```\n{{else}}\n```bash\nknowns task $ARGUMENTS --plain\n```\n{{/if}}\n\n**From current context (no arguments):**\n- Recent implementation work\n- Patterns discovered during research\n- Solutions found in conversation\n\nLook for:\n- Implementation patterns used\n- Problems solved\n- Decisions made\n- Lessons learned\n\n### Step 2: Identify Extractable Knowledge\n\n**Good candidates for extraction:**\n- Reusable code patterns\n- Error handling approaches\n- Integration patterns\n- Performance solutions\n- Security practices\n- API design decisions\n\n**NOT good for extraction:**\n- Task-specific details\n- One-time fixes\n- Context-dependent solutions\n\n### Step 3: Search for Existing Docs\n\n{{#if mcp}}\n```json\n// Check if pattern already documented\nmcp__knowns__search_docs({ "query": "<pattern/topic>" })\n\n// List related docs\nmcp__knowns__list_docs({ "tag": "pattern" })\n```\n{{else}}\n```bash\n# Check if pattern already documented\nknowns search "<pattern/topic>" --type doc --plain\n\n# List related docs\nknowns doc list --tag pattern --plain\n```\n{{/if}}\n\n**Don\'t duplicate.** Update existing docs when possible.\n\n### Step 4: Create or Update Documentation\n\n**If new pattern - create doc:**\n\n{{#if mcp}}\n```json\nmcp__knowns__create_doc({\n "title": "Pattern: <Name>",\n "description": "Reusable pattern for <purpose>",\n "tags": ["pattern", "<domain>"],\n "folder": "patterns"\n})\n```\n{{else}}\n```bash\nknowns doc create "Pattern: <Name>" \\\n -d "Reusable pattern for <purpose>" \\\n -t "pattern,<domain>" \\\n -f "patterns"\n```\n{{/if}}\n\n**Add content:**\n\n{{#if mcp}}\n```json\nmcp__knowns__update_doc({\n "path": "patterns/<name>",\n "content": "# Pattern: <Name>\\n\\n## 1. Problem\\nWhat problem this pattern solves.\\n\\n## 2. Solution\\nHow to implement the pattern.\\n\\n## 3. Example\\n```typescript\\n// Code example\\n```\\n\\n## 4. When to Use\\n- Situation 1\\n\\n## 5. Source\\nDiscovered in @task-<id>"\n})\n```\n{{else}}\n```bash\nknowns doc edit "patterns/<name>" -c "$(cat <<\'EOF\'\n# Pattern: <Name>\n\n## 1. Problem\nWhat problem this pattern solves.\n\n## 2. Solution\nHow to implement the pattern.\n\n## 3. Example\n```typescript\n// Code example\n```\n\n## 4. When to Use\n- Situation 1\n- Situation 2\n\n## 5. Source\nDiscovered in @task-<id> (or describe context)\nEOF\n)"\n```\n{{/if}}\n\n**If updating existing doc:**\n\n{{#if mcp}}\n```json\nmcp__knowns__update_doc({\n "path": "<path>",\n "appendContent": "\\n\\n## Additional: <Topic>\\n\\n<new insight or example>"\n})\n```\n{{else}}\n```bash\nknowns doc edit "<path>" -a "\n\n## Additional: <Topic>\n\n<new insight or example>\n"\n```\n{{/if}}\n\n### Step 5: Create Template (if code-generatable)\n\nIf the pattern involves repeatable code structure, create a codegen template:\n\n```bash\n# Create template skeleton\nknowns template create <pattern-name>\n```\n\n**Update template config** (`.knowns/templates/<pattern-name>/_template.yaml`):\n\n```yaml\nname: <pattern-name>\ndescription: Generate <what it creates>\ndoc: patterns/<pattern-name> # Link to the doc you just created\n\nprompts:\n - name: name\n message: Name?\n validate: required\n\nfiles:\n - template: "{{name}}.ts.hbs"\n destination: "src/{{kebabCase name}}.ts"\n```\n\n**Create template files** (`.hbs` files with Handlebars):\n\n```handlebars\n// {{name}}.ts.hbs\nexport class {{pascalCase name}} {\n // Pattern implementation\n}\n```\n\n**Link template in doc:**\n\n{{#if mcp}}\n```json\nmcp__knowns__update_doc({\n "path": "patterns/<name>",\n "appendContent": "\\n\\n## Generate\\n\\nUse @template/<pattern-name> to generate this pattern."\n})\n```\n{{else}}\n```bash\nknowns doc edit "patterns/<name>" -a "\n\n## Generate\n\nUse @template/<pattern-name> to generate this pattern.\n"\n```\n{{/if}}\n\n### Step 6: Link Back (if from task)\n\n```bash\nknowns task edit $ARGUMENTS --append-notes "\u{1F4DA} Extracted to @doc/patterns/<name>"\nknowns task edit $ARGUMENTS --append-notes "\u{1F527} Template: @template/<pattern-name>"\n```\n\n## What to Extract\n\n| Source | Extract As | Create Template? |\n|--------|------------|------------------|\n| Code pattern | Pattern doc | \u2705 Yes |\n| Component structure | Pattern doc | \u2705 Yes |\n| API endpoint pattern | Integration guide | \u2705 Yes |\n| Error solution | Troubleshooting guide | \u274C No |\n| Performance fix | Performance patterns | \u274C Usually no |\n| Security approach | Security guidelines | \u274C No |\n\n**Create template when:**\n- Pattern is repeatable (will be used multiple times)\n- Has consistent file structure\n- Can be parameterized (name, type, etc.)\n\n## Document Templates\n\n### Pattern Template\n```markdown\n# Pattern: <Name>\n\n## Problem\nWhat this solves.\n\n## Solution\nHow to implement.\n\n## Example\nWorking code.\n\n## When to Use\nWhen to apply this pattern.\n```\n\n### Guide Template\n```markdown\n# Guide: <Topic>\n\n## Overview\nWhat this covers.\n\n## Steps\n1. Step one\n2. Step two\n\n## Common Issues\n- Issue and solution\n```\n\n## Quality Checklist\n\n- [ ] Knowledge is generalizable (not task-specific)\n- [ ] Includes working example\n- [ ] Explains when to use\n- [ ] Links back to source (if applicable)\n- [ ] Tagged appropriately\n- [ ] Template created (if code-generatable)\n- [ ] Doc links to template (`@template/...`)\n- [ ] Template links to doc (`doc:` in config)\n\n## Remember\n\n- Only extract generalizable knowledge\n- Search before creating (avoid duplicates)\n- Include practical examples\n- Reference source when available\n- Tag docs for discoverability\n- **Create template for repeatable code patterns**\n- **Link doc \u2194 template bidirectionally**\n';
58814
+ // src/instructions/skills/kn:extract/SKILL.md
58815
+ var SKILL_default3 = '---\nname: kn:extract\ndescription: Use when extracting reusable patterns, solutions, or knowledge into documentation\n---\n\n# Extracting Knowledge\n\n**Announce:** "Using kn:extract to extract knowledge."\n\n**Core principle:** ONLY EXTRACT GENERALIZABLE KNOWLEDGE.\n\n## Step 1: Identify Source\n\n```json\nmcp__knowns__get_task({ "taskId": "$ARGUMENTS" })\n```\n\nLook for: patterns, problems solved, decisions made, lessons learned.\n\n## Step 2: Search for Existing Docs\n\n```json\nmcp__knowns__search_docs({ "query": "<pattern/topic>" })\n```\n\n**Don\'t duplicate.** Update existing docs when possible.\n\n## Step 3: Create Documentation\n\n```json\nmcp__knowns__create_doc({\n "title": "Pattern: <Name>",\n "description": "Reusable pattern for <purpose>",\n "tags": ["pattern", "<domain>"],\n "folder": "patterns"\n})\n\nmcp__knowns__update_doc({\n "path": "patterns/<name>",\n "content": "# Pattern: <Name>\\n\\n## Problem\\n...\\n\\n## Solution\\n...\\n\\n## Example\\n```typescript\\n// Code\\n```\\n\\n## Source\\n@task-<id>"\n})\n```\n\n## Step 4: Create Template (if code-generatable)\n\n```json\nmcp__knowns__create_template({\n "name": "<pattern-name>",\n "description": "Generate <what>",\n "doc": "patterns/<pattern-name>"\n})\n```\n\nLink template in doc:\n```json\nmcp__knowns__update_doc({\n "path": "patterns/<name>",\n "appendContent": "\\n\\n## Generate\\n\\nUse @template/<pattern-name>"\n})\n```\n\n## Step 5: Validate\n\n**CRITICAL:** After creating doc/template, validate to catch broken refs:\n\n```json\nmcp__knowns__validate({})\n```\n\nIf errors found, fix before continuing.\n\n## Step 6: Link Back to Task\n\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "appendNotes": "\u{1F4DA} Extracted to @doc/patterns/<name>"\n})\n```\n\n## What to Extract\n\n| Source | Extract As | Template? |\n|--------|------------|-----------|\n| Code pattern | Pattern doc | \u2705 Yes |\n| API pattern | Integration guide | \u2705 Yes |\n| Error solution | Troubleshooting | \u274C No |\n| Security approach | Guidelines | \u274C No |\n\n## Checklist\n\n- [ ] Knowledge is generalizable\n- [ ] Includes working example\n- [ ] Links back to source\n- [ ] Template created (if applicable)\n- [ ] **Validated (no broken refs)**\n';
58814
58816
 
58815
- // src/instructions/skills/knowns.init/SKILL.md
58816
- var SKILL_default4 = '---\nname: knowns.init\ndescription: Use at the start of a new session to read project docs, understand context, and see current state\n---\n\n# Session Initialization\n\nInitialize a session by reading project documentation and understanding current state.\n\n**Announce at start:** "I\'m using the knowns.init skill to initialize this session."\n\n**Core principle:** READ DOCS BEFORE DOING ANYTHING ELSE.\n\n## The Process\n\n### Step 1: List Available Documentation\n\n{{#if mcp}}\n```json\nmcp__knowns__list_docs({})\n```\n{{else}}\n```bash\nknowns doc list --plain\n```\n{{/if}}\n\n### Step 2: Read Core Documents\n\n**Priority order:**\n\n{{#if mcp}}\n```json\n// 1. Project overview (always read)\nmcp__knowns__get_doc({ "path": "README", "smart": true })\n\n// 2. Architecture (if exists)\nmcp__knowns__get_doc({ "path": "ARCHITECTURE", "smart": true })\n\n// 3. Conventions (if exists)\nmcp__knowns__get_doc({ "path": "CONVENTIONS", "smart": true })\n```\n{{else}}\n```bash\n# 1. Project overview (always read)\nknowns doc "README" --plain\n\n# 2. Architecture (if exists)\nknowns doc "ARCHITECTURE" --plain\n\n# 3. Conventions (if exists)\nknowns doc "CONVENTIONS" --plain\n```\n{{/if}}\n\n### Step 3: Check Current State\n\n{{#if mcp}}\n```json\n// Active timer?\nmcp__knowns__get_time_report({})\n\n// Tasks in progress\nmcp__knowns__list_tasks({ "status": "in-progress" })\n\n// Board overview\nmcp__knowns__get_board({})\n```\n{{else}}\n```bash\n# Active timer?\nknowns time status\n\n# Tasks in progress\nknowns task list --status in-progress --plain\n\n# High priority todos\nknowns task list --status todo --plain | head -20\n```\n{{/if}}\n\n### Step 4: Summarize Context\n\nProvide a brief summary:\n\n```markdown\n## Session Context\n\n### Project\n- **Name**: [from config]\n- **Purpose**: [from README]\n\n### Key Docs Available\n- README: [brief note]\n- ARCHITECTURE: [if exists]\n- CONVENTIONS: [if exists]\n\n### Current State\n- Tasks in progress: [count]\n- Active timer: [yes/no]\n\n### Ready for\n- Working on tasks\n- Creating documentation\n- Answering questions about codebase\n```\n\n## Quick Commands After Init\n\n```\n# Work on a task\n/knowns.task <id>\n\n# Search for something\n{{#if mcp}}\nmcp__knowns__search_docs({ "query": "<query>" })\n{{else}}\nknowns search "<query>" --plain\n{{/if}}\n```\n\n## When to Re-Initialize\n\n**Run init again when:**\n- Starting a new session\n- Major project changes occurred\n- Switching to different area of project\n- Context feels stale\n\n## What to Learn from Docs\n\nFrom **README**:\n- Project purpose and scope\n- Key features\n- Getting started info\n\nFrom **ARCHITECTURE**:\n- System design\n- Component structure\n- Key decisions\n\nFrom **CONVENTIONS**:\n- Coding standards\n- Naming conventions\n- File organization\n\n## Remember\n\n- Always read docs first\n- Check for active work (in-progress tasks)\n- Summarize context for reference\n- Re-init when switching areas\n';
58817
+ // src/instructions/skills/kn:implement/SKILL.md
58818
+ var SKILL_default4 = '---\nname: kn:implement\ndescription: Use when implementing a task - follow the plan, check ACs, track progress\n---\n\n# Implementing a Task\n\nExecute the implementation plan, track progress, and complete the task.\n\n**Announce:** "Using kn:implement for task [ID]."\n\n**Core principle:** CHECK AC ONLY AFTER WORK IS DONE.\n\n## Step 1: Review Task\n\n```json\nmcp__knowns__get_task({ "taskId": "$ARGUMENTS" })\n```\n\n**If task status is "done"** (reopening):\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "status": "in-progress",\n "appendNotes": "Reopened: <reason>"\n})\nmcp__knowns__start_time({ "taskId": "$ARGUMENTS" })\n```\n\nVerify: plan exists, timer running, which ACs pending.\n\n## Step 2: Check Templates\n\n```json\nmcp__knowns__list_templates({})\n```\n\nIf template exists \u2192 use it to generate boilerplate.\n\n## Step 3: Work Through Plan\n\nFor each step:\n1. Do the work\n2. Check AC (only after done!)\n3. Append note\n\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "checkAc": [1],\n "appendNotes": "Done: brief description"\n})\n```\n\n## Step 4: Handle Scope Changes\n\n**Small:** Add AC + note\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "addAc": ["New requirement"],\n "appendNotes": "Scope: added per user"\n})\n```\n\n**Large:** Stop and ask user.\n\n## Step 5: Validate & Complete\n\n1. Run tests/lint/build\n2. **Validate** to catch broken refs:\n\n```json\nmcp__knowns__validate({})\n```\n\n3. Add implementation notes (use `appendNotes`, NOT `notes`!)\n4. Stop timer + mark done\n\n```json\nmcp__knowns__stop_time({ "taskId": "$ARGUMENTS" })\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "status": "done"\n})\n```\n\n## Step 6: Extract Knowledge (optional)\n\nIf patterns discovered: `/kn:extract $ARGUMENTS`\n\n## Checklist\n\n- [ ] All ACs checked\n- [ ] Tests pass\n- [ ] **Validated (no broken refs)**\n- [ ] Notes added\n- [ ] Timer stopped\n- [ ] Status = done\n\n## Red Flags\n\n- Checking AC before work done\n- Skipping tests\n- Skipping validation\n- Using `notes` instead of `appendNotes`\n- Marking done without verification\n';
58817
58819
 
58818
- // src/instructions/skills/knowns.research/SKILL.md
58819
- var SKILL_default5 = '---\nname: knowns.research\ndescription: Use when you need to understand existing code, find patterns, or explore the codebase before implementation\n---\n\n# Researching the Codebase\n\nUnderstand existing patterns and implementation before making changes.\n\n**Announce at start:** "I\'m using the knowns.research skill to research [topic]."\n\n**Core principle:** UNDERSTAND WHAT EXISTS BEFORE ADDING NEW CODE.\n\n## The Process\n\n### Step 1: Search Documentation\n\n{{#if mcp}}\n```json\n// Search docs for topic\nmcp__knowns__search_docs({ "query": "<topic>" })\n\n// Read relevant docs\nmcp__knowns__get_doc({ "path": "<path>", "smart": true })\n```\n{{else}}\n```bash\n# Search docs for topic\nknowns search "<topic>" --type doc --plain\n\n# Read relevant docs\nknowns doc "<path>" --plain\n```\n{{/if}}\n\n### Step 2: Search Completed Tasks\n\n{{#if mcp}}\n```json\n// Find similar work that was done\nmcp__knowns__search_tasks({ "query": "<keywords>" })\n\n// View task for implementation details\nmcp__knowns__get_task({ "taskId": "<id>" })\n```\n{{else}}\n```bash\n# Find similar work that was done\nknowns search "<keywords>" --type task --status done --plain\n\n# View task for implementation details\nknowns task <id> --plain\n```\n{{/if}}\n\n**Learn from history** - completed tasks often contain valuable insights.\n\n### Step 3: Search Codebase\n\n```bash\n# Find files by name pattern\nfind . -name "*<pattern>*" -type f | grep -v node_modules | head -20\n\n# Search code content\ngrep -r "<pattern>" --include="*.ts" --include="*.tsx" -l | head -20\n```\n\n### Step 4: Analyze Patterns\n\nLook for:\n- How similar features are implemented\n- Common patterns used\n- File/folder structure conventions\n- Naming conventions\n- Error handling patterns\n\n### Step 5: Document Findings\n\n```markdown\n## Research: [Topic]\n\n### Existing Implementations\n- `src/path/file.ts`: Does X\n- `src/path/other.ts`: Handles Y\n\n### Patterns Found\n- Pattern 1: Used for...\n- Pattern 2: Applied when...\n\n### Related Docs\n- @doc/path1 - Covers X\n- @doc/path2 - Explains Y\n\n### Recommendations\nBased on research:\n1. Reuse X from Y\n2. Follow pattern Z\n3. Avoid approach W because...\n```\n\n## Research Checklist\n\n- [ ] Searched documentation\n- [ ] Reviewed similar completed tasks\n- [ ] Found existing code patterns\n- [ ] Identified reusable components\n- [ ] Noted conventions to follow\n\n## After Research\n\nUse findings in task:\n{{#if mcp}}\n```json\n// Create informed task\nmcp__knowns__create_task({\n "title": "<title>",\n "description": "Based on research: use pattern from X"\n})\n```\n{{else}}\n```bash\n# Create informed task\nknowns task create "<title>" \\\n -d "Based on research: use pattern from X" \\\n --ac "Follow pattern in src/..." \\\n --ac "Reuse component Y"\n\n# Or update existing task plan\nknowns task edit <id> --plan $\'1. Based on research...\n2. Reuse pattern from...\'\n```\n{{/if}}\n\n## What to Look For\n\n| Looking For | Where to Check |\n|-------------|----------------|\n| Conventions | @doc/CONVENTIONS, existing code |\n| Patterns | @doc/patterns/*, similar features |\n| Utilities | src/utils/*, src/lib/* |\n| Examples | Completed tasks, tests |\n| API design | Existing endpoints, @doc/api/* |\n\n## When to Research\n\n**Always research before:**\n- Implementing new features\n- Adding new patterns\n- Making architectural decisions\n\n**Skip research for:**\n- Simple bug fixes with clear cause\n- Trivial changes following obvious patterns\n\n## Remember\n\n- Check docs and tasks first\n- Look at how similar things are done\n- Note file locations for reference\n- Look at tests for expected behavior\n- Document findings for future reference\n';
58820
+ // src/instructions/skills/kn:init/SKILL.md
58821
+ var SKILL_default5 = '---\nname: kn:init\ndescription: Use at the start of a new session to read project docs, understand context, and see current state\n---\n\n# Session Initialization\n\n**Announce:** "Using kn:init to initialize session."\n\n**Core principle:** READ DOCS BEFORE DOING ANYTHING ELSE.\n\n## Step 1: List Docs\n\n```json\nmcp__knowns__list_docs({})\n```\n\n## Step 2: Read Core Docs\n\n```json\nmcp__knowns__get_doc({ "path": "README", "smart": true })\nmcp__knowns__get_doc({ "path": "ARCHITECTURE", "smart": true })\nmcp__knowns__get_doc({ "path": "CONVENTIONS", "smart": true })\n```\n\n## Step 3: Check Current State\n\n```json\nmcp__knowns__list_tasks({ "status": "in-progress" })\nmcp__knowns__get_board({})\n```\n\n## Step 4: Summarize\n\n```markdown\n## Session Context\n- **Project**: [name]\n- **Key Docs**: README, ARCHITECTURE, CONVENTIONS\n- **In-progress tasks**: [count]\n- **Ready for**: tasks, docs, questions\n```\n\n## Next Steps\n\n```\n/kn:plan <task-id> # Plan a task\n/kn:research <query> # Research codebase\n```\n';
58820
58822
 
58821
- // src/instructions/skills/knowns.task.brainstorm/SKILL.md
58822
- var SKILL_default6 = '---\nname: knowns.task.brainstorm\ndescription: Use when requirements are unclear, multiple approaches exist, or you need to explore solutions before planning\n---\n\n# Brainstorming for Tasks\n\nConvert vague requirements into concrete design through structured questioning and exploration.\n\n**Announce at start:** "I\'m using the knowns.task.brainstorm skill to explore approaches."\n\n**Core principle:** UNDERSTAND THE PROBLEM BEFORE PROPOSING SOLUTIONS.\n\n## The Process\n\n### Phase 1: Discovery\n\n**One question at a time.** Don\'t overwhelm with multiple questions.\n\nPrefer multiple-choice when possible:\n```\nWhich approach do you prefer?\nA) Quick solution with trade-offs\nB) Comprehensive solution, more effort\nC) Something else (describe)\n```\n\nQuestions to clarify:\n- What problem are we solving?\n- Who are the users/stakeholders?\n- What are the constraints?\n- What does success look like?\n\n### Phase 2: Research Existing Patterns\n\n{{#if mcp}}\n```json\n// Search docs for related patterns\nmcp__knowns__search_docs({ "query": "<topic>" })\n\n// Check how similar things were done\nmcp__knowns__search_tasks({ "query": "<keywords>" })\n```\n{{else}}\n```bash\n# Search docs for related patterns\nknowns search "<topic>" --type doc --plain\n\n# Check how similar things were done\nknowns search "<keywords>" --type task --status done --plain\n```\n{{/if}}\n\n**Learn from history** - completed tasks often contain implementation insights.\n\n### Phase 3: Explore Approaches\n\nPresent 2-3 options with trade-offs:\n\n```markdown\n## Option A: [Name]\n- **Approach**: Brief description\n- **Pros**: What\'s good\n- **Cons**: What\'s challenging\n- **Effort**: Low/Medium/High\n\n## Option B: [Name]\n- **Approach**: Brief description\n- **Pros**: What\'s good\n- **Cons**: What\'s challenging\n- **Effort**: Low/Medium/High\n```\n\n**Lead with your recommendation** and explain why.\n\n### Phase 4: Validate and Document\n\nAfter agreement:\n- Summarize the chosen approach\n- Identify potential risks\n- Define acceptance criteria\n\nIf creating a new task:\n{{#if mcp}}\n```json\nmcp__knowns__create_task({\n "title": "<title>",\n "description": "Based on brainstorm: <key decisions>",\n "acceptanceCriteria": ["Criterion 1", "Criterion 2"]\n})\n```\n{{else}}\n```bash\nknowns task create "<title>" \\\n -d "Based on brainstorm: <key decisions>" \\\n --ac "Criterion 1" \\\n --ac "Criterion 2"\n```\n{{/if}}\n\nIf updating existing task:\n{{#if mcp}}\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "description": "Updated based on brainstorm..."\n})\n```\n{{else}}\n```bash\nknowns task edit $ARGUMENTS -d "Updated based on brainstorm..."\n```\n{{/if}}\n\n## When to Use This Skill\n\n**Good candidates:**\n- Vague requirements ("make it faster", "improve UX")\n- Multiple valid approaches exist\n- Significant effort involved\n- New territory for the project\n\n**Skip for:**\n- Clear, well-defined tasks\n- Bug fixes with obvious solutions\n- Simple additions following existing patterns\n\n## Red Flags\n\n**You\'re doing it wrong if:**\n- Proposing solutions before understanding the problem\n- Asking too many questions at once\n- Not researching existing patterns first\n- Skipping trade-off analysis\n\n## Remember\n\n- One question at a time\n- Research existing patterns first\n- Present options with trade-offs\n- Lead with your recommendation\n- Document the decision\n';
58823
+ // src/instructions/skills/kn:plan/SKILL.md
58824
+ var SKILL_default6 = '---\nname: kn:plan\ndescription: Use when creating an implementation plan for a task\n---\n\n# Planning a Task\n\n**Announce:** "Using kn:plan for task [ID]."\n\n**Core principle:** GATHER CONTEXT \u2192 PLAN \u2192 VALIDATE \u2192 WAIT FOR APPROVAL.\n\n## Mode Detection\n\nCheck if `$ARGUMENTS` contains `--from`:\n- **Yes** \u2192 Go to "Generate Tasks from Spec" section\n- **No** \u2192 Continue with normal planning flow\n\n---\n\n# Normal Planning Flow\n\n## Step 1: Take Ownership\n\n```json\nmcp__knowns__get_task({ "taskId": "$ARGUMENTS" })\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "status": "in-progress",\n "assignee": "@me"\n})\nmcp__knowns__start_time({ "taskId": "$ARGUMENTS" })\n```\n\n## Step 2: Gather Context\n\nFollow refs in task:\n```json\nmcp__knowns__get_doc({ "path": "<path>", "smart": true })\nmcp__knowns__get_task({ "taskId": "<id>" })\n```\n\nSearch related:\n```json\nmcp__knowns__search_docs({ "query": "<keywords>" })\nmcp__knowns__list_templates({})\n```\n\n## Step 3: Draft Plan\n\n```markdown\n## Implementation Plan\n1. [Step] (see @doc/relevant-doc)\n2. [Step] (use @template/xxx)\n3. Add tests\n4. Update docs\n```\n\n**Tip:** Use mermaid for complex flows:\n````markdown\n```mermaid\ngraph LR\n A[Input] --> B[Process] --> C[Output]\n```\n````\n\n## Step 4: Save Plan\n\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "plan": "1. Step one\\n2. Step two\\n3. Tests"\n})\n```\n\n## Step 5: Validate\n\n**CRITICAL:** After saving plan with refs, validate to catch broken refs:\n\n```bash\nknowns validate --plain\n```\n\nIf errors found (broken `@doc/...` or `@task-...`), fix before asking approval.\n\n## Step 6: Ask Approval\n\nPresent plan and **WAIT for explicit approval**.\n\n## Next Step\n\nAfter approval: `/kn:implement $ARGUMENTS`\n\n## Checklist\n\n- [ ] Ownership taken\n- [ ] Timer started\n- [ ] Refs followed\n- [ ] Templates checked\n- [ ] **Validated (no broken refs)**\n- [ ] User approved\n\n---\n\n# Generate Tasks from Spec\n\nWhen `$ARGUMENTS` contains `--from @doc/specs/<name>`:\n\n**Announce:** "Using kn:plan to generate tasks from spec [name]."\n\n## Step 1: Read Spec Document\n\nExtract spec path from arguments (e.g., `--from @doc/specs/user-auth` \u2192 `specs/user-auth`).\n\n```json\nmcp__knowns__get_doc({ "path": "specs/<name>", "smart": true })\n```\n\n## Step 2: Parse Requirements\n\nScan spec for:\n- **Functional Requirements** (FR-1, FR-2, etc.)\n- **Acceptance Criteria** (AC-1, AC-2, etc.)\n- **Scenarios** (for edge cases)\n\nGroup related items into logical tasks.\n\n## Step 3: Generate Task Preview\n\nFor each requirement/group, create task structure:\n\n```markdown\n## Generated Tasks from specs/<name>\n\n### Task 1: [Requirement Title]\n- **Description:** [From spec]\n- **ACs:**\n - [ ] AC from spec\n - [ ] AC from spec\n- **Spec:** specs/<name>\n- **Priority:** medium\n\n### Task 2: [Requirement Title]\n- **Description:** [From spec]\n- **ACs:**\n - [ ] AC from spec\n- **Spec:** specs/<name>\n- **Priority:** medium\n\n---\nTotal: X tasks to create\n```\n\n## Step 4: Ask for Approval\n\n> I\'ve generated **X tasks** from the spec. Please review:\n> - **Approve** to create all tasks\n> - **Edit** to modify before creating\n> - **Cancel** to abort\n\n**WAIT for explicit approval.**\n\n## Step 5: Create Tasks\n\nWhen approved:\n\n```json\nmcp__knowns__create_task({\n "title": "<requirement title>",\n "description": "<from spec>",\n "spec": "specs/<name>",\n "priority": "medium",\n "labels": ["from-spec"]\n})\n```\n\nThen add ACs:\n```json\nmcp__knowns__update_task({\n "taskId": "<new-id>",\n "addAc": ["AC 1", "AC 2", "AC 3"]\n})\n```\n\nRepeat for each task.\n\n## Step 6: Summary\n\n```markdown\n## Created Tasks\n\n| ID | Title | ACs |\n|----|-------|-----|\n| task-xxx | Requirement 1 | 3 |\n| task-yyy | Requirement 2 | 2 |\n\nAll tasks linked to spec: specs/<name>\n\nNext steps:\n- Start with: `/kn:plan <first-task-id>`\n- Or view all: `knowns task list --spec specs/<name> --plain`\n```\n\n## Checklist (--from mode)\n\n- [ ] Spec document read\n- [ ] Requirements parsed\n- [ ] Tasks previewed\n- [ ] User approved\n- [ ] Tasks created with spec link\n- [ ] Summary shown\n';
58823
58825
 
58824
- // src/instructions/skills/knowns.task.implement/SKILL.md
58825
- var SKILL_default7 = '---\nname: knowns.task.implement\ndescription: Use when implementing a task - follow the plan, check ACs, track progress\n---\n\n# Implementing a Task\n\nExecute the implementation plan, track progress, and complete the task.\n\n**Announce at start:** "I\'m using the knowns.task.implement skill to implement task [ID]."\n\n**Core principle:** CHECK AC ONLY AFTER WORK IS DONE.\n\n## The Process\n\n### Step 1: Review Current State\n\n{{#if mcp}}\n```json\nmcp__knowns__get_task({ "taskId": "$ARGUMENTS" })\n```\n{{else}}\n```bash\nknowns task $ARGUMENTS --plain\n```\n{{/if}}\n\nVerify:\n- Plan exists and is approved\n- Timer is running\n- Know which ACs are pending\n\n### Step 2: Check for Applicable Templates\n\nBefore writing code, check if there\'s a template that matches:\n\n```bash\nknowns template list\n```\n\n**If template exists:**\n1. Read linked doc for context\n2. Use template to generate boilerplate\n3. Customize generated code as needed\n\n{{#if mcp}}\n```json\n// Read template\'s linked doc\nmcp__knowns__get_doc({ "path": "<template-doc>", "smart": true })\n```\n```bash\n# Generate code from template (reduces context, ensures consistency)\nknowns template run <template-name> --name "MyComponent"\n```\n{{else}}\n```bash\n# Read template\'s linked doc\nknowns doc "<template-doc>" --plain\n\n# Generate code from template (reduces context, ensures consistency)\nknowns template run <template-name> --name "MyComponent"\n```\n{{/if}}\n\n**Why use templates:**\n- Reduces context (no need to generate boilerplate)\n- Ensures consistency with project patterns\n- Faster implementation\n\n### Step 3: Work Through Plan\n\nFor each step in the plan:\n\n1. **Check for template** (use if available)\n2. **Do the work** (generate or write code)\n3. **Check related AC** (only after work is done!)\n4. **Append progress note**\n\n{{#if mcp}}\n```json\n// After completing work for AC #1:\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "checkAc": [1],\n "appendNotes": "\u2713 Done: brief description"\n})\n```\n{{else}}\n```bash\n# After completing work for AC #1:\nknowns task edit $ARGUMENTS --check-ac 1\nknowns task edit $ARGUMENTS --append-notes "\u2713 Done: brief description"\n```\n{{/if}}\n\n### Step 4: Handle Scope Changes\n\nIf new requirements emerge during implementation:\n\n**Small change:**\n{{#if mcp}}\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "addAc": ["New requirement"],\n "appendNotes": "\u26A0\uFE0F Scope: added requirement per user"\n})\n```\n{{else}}\n```bash\nknowns task edit $ARGUMENTS --ac "New requirement"\nknowns task edit $ARGUMENTS --append-notes "\u26A0\uFE0F Scope: added requirement per user"\n```\n{{/if}}\n\n**Large change:**\n- Stop and ask user\n- Consider creating follow-up task\n- Update plan if needed\n\n### Step 5: Verify & Complete\n\nWhen all ACs are checked:\n\n**1. Verify code quality:**\n```bash\nnpm test # or project\'s test command\nnpm run lint # or project\'s lint command\nnpm run build # if applicable\n```\n\n**Don\'t complete if verification fails.** Fix issues first.\n\n**2. Add implementation notes (REQUIRED for audit):**\n\nDocument all changes made for audit trail.\n\n> \u26A0\uFE0F **CRITICAL**: Use `appendNotes` (NOT `notes`). Using `notes` will DESTROY the audit trail!\n\n{{#if mcp}}\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "appendNotes": "## Implementation Complete\\n\\n### Files Changed\\n- `src/path/file.ts` - Added X\\n- `src/path/other.ts` - Modified Y\\n\\n### Key Changes\\n- Change 1: description\\n\\n### Testing\\n- Test coverage / manual testing done"\n})\n```\n{{else}}\n```bash\nknowns task edit $ARGUMENTS --append-notes $\'\n## Implementation Complete\n\n### Files Changed\n- `src/path/file.ts` - Added X\n- `src/path/other.ts` - Modified Y\n- `tests/file.test.ts` - Added tests\n\n### Key Changes\n- Change 1: description\n- Change 2: description\n\n### Testing\n- Test coverage / manual testing done\n\'\n```\n{{/if}}\n\n**IMPORTANT:** Always use `appendNotes` (not `notes`) to preserve audit trail.\n\n**3. Stop timer and mark done:**\n\n{{#if mcp}}\n```json\nmcp__knowns__stop_time({ "taskId": "$ARGUMENTS" })\n```\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "status": "done"\n})\n```\n{{else}}\n```bash\nknowns time stop\nknowns task edit $ARGUMENTS -s done\n```\n{{/if}}\n\n### Step 6: Consider Knowledge Extraction\n\nIf generalizable patterns were discovered:\n\n```\n/knowns.extract $ARGUMENTS\n```\n\n## Progress Tracking\n\nUse concise notes:\n\n{{#if mcp}}\n```json\n// Good\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "appendNotes": "\u2713 Auth middleware implemented"\n})\n\n// Bad (too verbose)\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "appendNotes": "I have successfully completed..."\n})\n```\n{{else}}\n```bash\n# Good\nknowns task edit $ARGUMENTS --append-notes "\u2713 Auth middleware implemented"\n\n# Bad (too verbose)\nknowns task edit $ARGUMENTS --append-notes "I have successfully completed..."\n```\n{{/if}}\n\n## Completion Checklist\n\n- [ ] All ACs checked\n- [ ] Tests pass\n- [ ] Lint clean\n- [ ] Implementation notes added (with file changes for audit)\n- [ ] Timer stopped\n- [ ] Status set to `done`\n- [ ] Knowledge extracted (if applicable)\n\n## Red Flags\n\n**You\'re doing it wrong if:**\n- Checking AC before work is actually complete\n- Making changes not in the approved plan (without asking)\n- Skipping tests\n- Not tracking progress with notes\n- Marking done without verification\n\n## When to Stop\n\n**STOP and ask when:**\n- Requirements unclear or contradictory\n- Approach isn\'t working after 2-3 attempts\n- Need changes outside approved scope\n- Hit unexpected blocker\n\n## If Verification Fails\n\n**Tests failing:**\n1. Keep task in-progress\n2. Fix the issue\n3. Re-run verification\n\n**Forgot to stop timer:**\n{{#if mcp}}\n```json\nmcp__knowns__add_time({\n "taskId": "$ARGUMENTS",\n "duration": "<duration>",\n "note": "Timer correction"\n})\n```\n{{else}}\n```bash\nknowns time add $ARGUMENTS <duration> -n "Timer correction"\n```\n{{/if}}\n\n## Remember\n\n- Check AC only AFTER work is done\n- Use templates when available\n- Track progress with notes\n- Ask before scope changes\n- Follow the approved plan\n- Verify before marking done\n- Always stop the timer\n- Consider knowledge extraction\n';
58826
+ // src/instructions/skills/kn:research/SKILL.md
58827
+ var SKILL_default7 = '---\nname: kn:research\ndescription: Use when you need to understand existing code, find patterns, or explore the codebase before implementation\n---\n\n# Researching the Codebase\n\n**Announce:** "Using kn:research for [topic]."\n\n**Core principle:** UNDERSTAND WHAT EXISTS BEFORE ADDING NEW CODE.\n\n## Step 1: Search Documentation\n\n```json\nmcp__knowns__search_docs({ "query": "<topic>" })\nmcp__knowns__get_doc({ "path": "<path>", "smart": true })\n```\n\n## Step 2: Search Completed Tasks\n\n```json\nmcp__knowns__search_tasks({ "query": "<keywords>" })\nmcp__knowns__get_task({ "taskId": "<id>" })\n```\n\n## Step 3: Search Codebase\n\n```bash\nfind . -name "*<pattern>*" -type f | grep -v node_modules | head -20\ngrep -r "<pattern>" --include="*.ts" -l | head -20\n```\n\n## Step 4: Document Findings\n\n```markdown\n## Research: [Topic]\n\n### Existing Implementations\n- `src/path/file.ts`: Does X\n\n### Patterns Found\n- Pattern 1: Used for...\n\n### Related Docs\n- @doc/path1 - Covers X\n\n### Recommendations\n1. Reuse X from Y\n2. Follow pattern Z\n```\n\n## Checklist\n\n- [ ] Searched documentation\n- [ ] Reviewed similar completed tasks\n- [ ] Found existing code patterns\n- [ ] Identified reusable components\n\n## Next Step\n\nAfter research: `/kn:plan <task-id>`\n';
58826
58828
 
58827
- // src/instructions/skills/knowns.task.plan/SKILL.md
58828
- var SKILL_default8 = '---\nname: knowns.task.plan\ndescription: Use when creating an implementation plan for a task\n---\n\n# Planning a Task\n\nTake ownership, gather context, create implementation plan, and get user approval.\n\n**Announce at start:** "I\'m using the knowns.task.plan skill to plan task [ID]."\n\n**Core principle:** GATHER CONTEXT \u2192 PLAN \u2192 WAIT FOR APPROVAL.\n\n## The Process\n\n### Step 1: View Task & Take Ownership\n\n{{#if mcp}}\n```json\nmcp__knowns__get_task({ "taskId": "$ARGUMENTS" })\n```\n\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "status": "in-progress",\n "assignee": "@me"\n})\n```\n\n```json\nmcp__knowns__start_time({ "taskId": "$ARGUMENTS" })\n```\n{{else}}\n```bash\nknowns task $ARGUMENTS --plain\nknowns task edit $ARGUMENTS -s in-progress -a @me\nknowns time start $ARGUMENTS\n```\n{{/if}}\n\n**Timer is mandatory.** Time data is used for estimation.\n\n### Step 2: Gather Context\n\n**Follow all refs in task:**\n\n{{#if mcp}}\n```json\n// @doc/<path> \u2192\nmcp__knowns__get_doc({ "path": "<path>", "smart": true })\n\n// @task-<id> \u2192\nmcp__knowns__get_task({ "taskId": "<id>" })\n```\n{{else}}\n```bash\n# @doc/<path> \u2192\nknowns doc "<path>" --plain\n\n# @task-<id> \u2192\nknowns task <id> --plain\n```\n{{/if}}\n\n**Search for related context:**\n\n{{#if mcp}}\n```json\nmcp__knowns__search_docs({ "query": "<keywords>" })\nmcp__knowns__search_tasks({ "query": "<keywords>" })\n```\n{{else}}\n```bash\nknowns search "<keywords>" --type doc --plain\nknowns search "<keywords>" --type task --status done --plain\n```\n{{/if}}\n\n**Check for templates:**\n\n```bash\nknowns template list\n```\n\n### Step 3: Draft Implementation Plan\n\nStructure your plan:\n\n```markdown\n## Implementation Plan\n\n1. [Step] (see @doc/relevant-doc)\n2. [Step] (use @template/xxx if available)\n3. Add tests\n4. Update documentation\n```\n\n**Plan guidelines:**\n- Reference relevant docs with `@doc/<path>`\n- Reference templates with `@template/<name>`\n- Include testing step\n- Include doc updates if needed\n- Keep steps actionable and specific\n\n### Step 4: Present to User\n\nShow the plan and **ASK for approval**:\n\n```markdown\nHere\'s my implementation plan for task [ID]:\n\n1. Step one (see @doc/xxx)\n2. Generate boilerplate with @template/xxx\n3. Customize implementation\n4. Add unit tests\n5. Update API docs\n\nShall I proceed with this plan?\n```\n\n**WAIT for explicit approval.**\n\n### Step 5: Save Plan (after approval)\n\n> \u26A0\uFE0F **Use `appendNotes` (NOT `notes`)** to preserve audit trail:\n\n{{#if mcp}}\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "plan": "1. Step one (see @doc/xxx)\\n2. Step two\\n3. Add unit tests\\n4. Update API docs",\n "appendNotes": "\u{1F4CB} Plan approved, starting implementation"\n})\n```\n{{else}}\n```bash\nknowns task edit $ARGUMENTS --plan $\'1. Step one (see @doc/xxx)\n2. Step two\n3. Add unit tests\n4. Update API docs\'\nknowns task edit $ARGUMENTS --append-notes "\u{1F4CB} Plan approved, starting implementation"\n```\n{{/if}}\n\n## Plan Quality Checklist\n\n- [ ] Task ownership taken (status: in-progress)\n- [ ] Timer started\n- [ ] All refs followed\n- [ ] Related docs/tasks searched\n- [ ] Templates identified (if any)\n- [ ] Steps are specific and actionable\n- [ ] Includes relevant doc/template references\n- [ ] Includes testing\n- [ ] User has approved\n\n## Next Step\n\nAfter plan is approved:\n\n```\n/knowns.task.implement $ARGUMENTS\n```\n\n## When Plan Isn\'t Clear\n\nIf requirements are unclear or multiple approaches exist:\n\n```\n/knowns.task.brainstorm $ARGUMENTS\n```\n\n## Remember\n\n- Take ownership and start timer first\n- Gather context before planning\n- Check for templates to use\n- Never implement without approved plan\n- Reference docs and templates in the plan\n';
58829
+ // src/instructions/skills/kn:spec/SKILL.md
58830
+ var SKILL_default8 = '---\nname: kn:spec\ndescription: Use when creating a specification document for a feature (SDD workflow)\n---\n\n# Creating a Spec Document\n\nCreate a specification document for a feature using SDD (Spec-Driven Development).\n\n**Announce:** "Using kn:spec to create spec for [name]."\n\n**Core principle:** SPEC FIRST \u2192 REVIEW \u2192 APPROVE \u2192 THEN PLAN TASKS.\n\n## Step 1: Get Feature Name\n\nIf `$ARGUMENTS` provided, use it as spec name.\n\nIf no arguments, ask user:\n> What feature are you speccing? (e.g., "user-auth", "payment-flow")\n\n## Step 2: Gather Requirements\n\nAsk user to describe the feature:\n> Please describe the feature requirements. What should it do?\n\nListen for:\n- Core functionality\n- User stories / scenarios\n- Edge cases\n- Non-functional requirements\n\n## Step 3: Create Spec Document\n\n```json\nmcp__knowns__create_doc({\n "title": "<Feature Name>",\n "description": "Specification for <feature>",\n "folder": "specs",\n "tags": ["spec", "draft"],\n "content": "<spec content>"\n})\n```\n\n**Spec Template:**\n\n```markdown\n## Overview\n\nBrief description of the feature and its purpose.\n\n## Requirements\n\n### Functional Requirements\n- FR-1: [Requirement description]\n- FR-2: [Requirement description]\n\n### Non-Functional Requirements\n- NFR-1: [Performance, security, etc.]\n\n## Acceptance Criteria\n\n- [ ] AC-1: [Testable criterion]\n- [ ] AC-2: [Testable criterion]\n- [ ] AC-3: [Testable criterion]\n\n## Scenarios\n\n### Scenario 1: [Happy Path]\n**Given** [context]\n**When** [action]\n**Then** [expected result]\n\n### Scenario 2: [Edge Case]\n**Given** [context]\n**When** [action]\n**Then** [expected result]\n\n## Technical Notes\n\nOptional implementation hints or constraints.\n\n## Open Questions\n\n- [ ] Question 1?\n- [ ] Question 2?\n```\n\n## Step 4: Ask for Review\n\nPresent the spec and ask:\n> Please review this spec:\n> - **Approve** if requirements are complete\n> - **Edit** if you want to modify something\n> - **Add more** if requirements are missing\n\n## Step 5: Handle Response\n\n**If approved:**\n```json\nmcp__knowns__update_doc({\n "path": "specs/<name>",\n "tags": ["spec", "approved"]\n})\n```\n\nThen suggest:\n> Spec approved! Ready to create tasks?\n> Run: `/kn:plan --from @doc/specs/<name>`\n\n**If edit requested:**\nUpdate the spec based on feedback and return to Step 4.\n\n**If add more:**\nGather additional requirements and update spec.\n\n## Checklist\n\n- [ ] Feature name determined\n- [ ] Requirements gathered\n- [ ] Spec created in specs/ folder\n- [ ] Includes: Overview, Requirements, ACs, Scenarios\n- [ ] User reviewed\n- [ ] Status updated (draft \u2192 approved)\n- [ ] Next step suggested (/kn:plan --from)\n\n## Red Flags\n\n- Creating spec without user input\n- Skipping review step\n- Approving without explicit user confirmation\n- Not suggesting task creation after approval\n';
58829
58831
 
58830
- // src/instructions/skills/knowns.task.reopen/SKILL.md
58831
- var SKILL_default9 = '---\nname: knowns.task.reopen\ndescription: Use when reopening a completed task to add new requirements, fix issues, or extend functionality\n---\n\n# Reopening Tasks\n\nReopen completed tasks properly with time tracking and requirement documentation.\n\n**Announce at start:** "I\'m using the knowns.task.reopen skill to reopen task [ID]."\n\n**Core principle:** DOCUMENT WHY THE TASK IS REOPENED.\n\n## The Process\n\n### Step 1: View Current Task State\n\n{{#if mcp}}\n```json\nmcp__knowns__get_task({ "taskId": "$ARGUMENTS" })\n```\n{{else}}\n```bash\nknowns task $ARGUMENTS --plain\n```\n{{/if}}\n\nVerify:\n- Task is currently `done`\n- Understand what was implemented\n- Review implementation notes\n\n### Step 2: Reopen and Start Timer\n\n{{#if mcp}}\n```json\n// Set back to in-progress\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "status": "in-progress"\n})\n\n// Start timer (REQUIRED)\nmcp__knowns__start_time({ "taskId": "$ARGUMENTS" })\n```\n{{else}}\n```bash\n# Set back to in-progress\nknowns task edit $ARGUMENTS -s in-progress\n\n# Start timer (REQUIRED)\nknowns time start $ARGUMENTS\n```\n{{/if}}\n\n### Step 3: Document Reopen Reason\n\n> \u26A0\uFE0F **Use `appendNotes` (NOT `notes`)** to preserve existing audit trail:\n\n{{#if mcp}}\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "appendNotes": "\u{1F504} Reopened: <reason>"\n})\n```\n{{else}}\n```bash\nknowns task edit $ARGUMENTS --append-notes "\u{1F504} Reopened: <reason>"\n```\n{{/if}}\n\n**Common reasons:**\n- User requested changes\n- Bug found in implementation\n- New requirements added\n- Missed acceptance criteria\n\n### Step 4: Add New Requirements\n\n{{#if mcp}}\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "addAc": ["New requirement 1", "Fix: issue description"]\n})\n```\n{{else}}\n```bash\n# Add new acceptance criteria\nknowns task edit $ARGUMENTS --ac "New requirement 1"\nknowns task edit $ARGUMENTS --ac "Fix: issue description"\n```\n{{/if}}\n\n### Step 5: Update Plan (if needed)\n\n{{#if mcp}}\n```json\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "plan": "Previous plan + new steps:\\n1. Original step (done)\\n2. Original step (done)\\n3. NEW: Address new requirement\\n4. NEW: Fix reported issue"\n})\n```\n{{else}}\n```bash\nknowns task edit $ARGUMENTS --plan $\'Previous plan + new steps:\n1. Original step (done)\n2. Original step (done)\n3. NEW: Address new requirement\n4. NEW: Fix reported issue\'\n```\n{{/if}}\n\n**Present updated plan and WAIT for approval.**\n\n### Step 6: Implement and Complete\n\nFollow normal task completion flow:\n\n{{#if mcp}}\n```json\n// Check new ACs as completed\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "checkAc": [<new-index>],\n "appendNotes": "\u2713 Done: new requirement"\n})\n\n// Stop timer\nmcp__knowns__stop_time({ "taskId": "$ARGUMENTS" })\n\n// Mark done again\nmcp__knowns__update_task({\n "taskId": "$ARGUMENTS",\n "status": "done"\n})\n```\n{{else}}\n```bash\n# Check new ACs as completed\nknowns task edit $ARGUMENTS --check-ac <new-index>\nknowns task edit $ARGUMENTS --append-notes "\u2713 Done: new requirement"\n\n# Stop timer\nknowns time stop\n\n# Mark done again\nknowns task edit $ARGUMENTS -s done\n```\n{{/if}}\n\n## When to Reopen vs Create New Task\n\n| Reopen Existing | Create New Task |\n|-----------------|-----------------|\n| Small fix/change | Major new feature |\n| Related to original work | Unrelated work |\n| Same context needed | Different context |\n| Quick addition | Significant scope |\n\n**Rule of thumb:** If it takes < 30 mins and relates to original task, reopen. Otherwise, create new task with reference.\n\n## Creating Follow-up Task Instead\n\n{{#if mcp}}\n```json\nmcp__knowns__create_task({\n "title": "Follow-up: <description>",\n "description": "Related to @task-$ARGUMENTS"\n})\n```\n{{else}}\n```bash\nknowns task create "Follow-up: <description>" \\\n -d "Related to @task-$ARGUMENTS" \\\n --ac "New requirement"\n```\n{{/if}}\n\n## Remember\n\n- Always document reopen reason\n- Start timer when reopening\n- Add new AC for traceability\n- Stop timer when done\n- Consider if new task is more appropriate\n';
58832
+ // src/instructions/skills/kn:template/SKILL.md
58833
+ var SKILL_default9 = '---\nname: kn:template\ndescription: Use when generating code from templates - list, run, or create templates\n---\n\n# Working with Templates\n\n**Announce:** "Using kn:template to work with templates."\n\n**Core principle:** USE TEMPLATES FOR CONSISTENT CODE GENERATION.\n\n## Step 1: List Templates\n\n```json\nmcp__knowns__list_templates({})\n```\n\n## Step 2: Get Template Details\n\n```json\nmcp__knowns__get_template({ "name": "<template-name>" })\n```\n\nCheck: prompts, `doc:` link, files to generate.\n\n## Step 3: Read Linked Documentation\n\n```json\nmcp__knowns__get_doc({ "path": "<doc-path>", "smart": true })\n```\n\n## Step 4: Run Template\n\n```json\n// Dry run first\nmcp__knowns__run_template({\n "name": "<template-name>",\n "variables": { "name": "MyComponent" },\n "dryRun": true\n})\n\n// Then run for real\nmcp__knowns__run_template({\n "name": "<template-name>",\n "variables": { "name": "MyComponent" },\n "dryRun": false\n})\n```\n\n## Step 5: Create New Template\n\n```json\nmcp__knowns__create_template({\n "name": "<template-name>",\n "description": "Description",\n "doc": "patterns/<related-doc>"\n})\n```\n\n## Template Config\n\n```yaml\nname: react-component\ndescription: Create a React component\ndoc: patterns/react-component\n\nprompts:\n - name: name\n message: Component name?\n validate: required\n\nfiles:\n - template: ".tsx.hbs"\n destination: "src/components//.tsx"\n```\n\n## CRITICAL: Syntax Pitfalls\n\n**NEVER write `$` + triple-brace:**\n```\n// \u274C WRONG\n$` + `{` + `{` + `{camelCase name}`\n\n// \u2705 CORRECT - add space, use ~\n${ {{~camelCase name~}}}\n```\n\n## Checklist\n\n- [ ] Listed available templates\n- [ ] Read linked documentation\n- [ ] Ran dry run first\n- [ ] Verified generated files\n';
58832
58834
 
58833
- // src/instructions/skills/knowns.task/SKILL.md
58834
- var SKILL_default10 = '---\nname: knowns.task\ndescription: Use when working on a Knowns task - view task details and decide next action\n---\n\n# Working on a Task\n\nView task details and determine the appropriate next action.\n\n**Announce at start:** "I\'m using the knowns.task skill to view task [ID]."\n\n**Core principle:** VIEW AND ROUTE - analyze state, suggest next skill.\n\n## The Process\n\n### Step 1: View Task\n\n{{#if mcp}}\n```json\nmcp__knowns__get_task({ "taskId": "$ARGUMENTS" })\n```\n{{else}}\n```bash\nknowns task $ARGUMENTS --plain\n```\n{{/if}}\n\n### Step 2: Analyze State\n\nCheck:\n- **Status**: todo, in-progress, done?\n- **Assignee**: Assigned to someone?\n- **AC**: Any checked? All checked?\n- **Plan**: Has implementation plan?\n- **Refs**: Any `@doc/` or `@task-` references?\n\n### Step 3: Suggest Next Action\n\nBased on task state, recommend the appropriate skill:\n\n| State | Next Skill |\n|-------|------------|\n| `todo`, not started | `knowns.task.plan` |\n| `in-progress`, no plan | `knowns.task.plan` |\n| `in-progress`, has plan | `knowns.task.implement` |\n| `done`, needs changes | `knowns.task.reopen` |\n| Requirements unclear | `knowns.task.brainstorm` |\n\n### Step 4: Follow Refs (if needed)\n\nIf task has references, follow them for context:\n\n{{#if mcp}}\n```json\n// Doc ref: @doc/path \u2192\nmcp__knowns__get_doc({ "path": "<path>", "smart": true })\n\n// Task ref: @task-<id> \u2192\nmcp__knowns__get_task({ "taskId": "<id>" })\n```\n{{else}}\n```bash\n# Doc ref: @doc/path \u2192\nknowns doc "<path>" --plain\n\n# Task ref: @task-<id> \u2192\nknowns task <id> --plain\n```\n{{/if}}\n\n## Quick Actions\n\n**Start planning (includes taking ownership):**\n```\n/knowns.task.plan $ARGUMENTS\n```\n\n**Continue implementing:**\n```\n/knowns.task.implement $ARGUMENTS\n```\n\n**Requirements unclear:**\n```\n/knowns.task.brainstorm $ARGUMENTS\n```\n\n**Reopen completed task:**\n```\n/knowns.task.reopen $ARGUMENTS\n```\n\n## Remember\n\n- This skill is for viewing and routing\n- Use `plan` to start a new task (takes ownership, starts timer)\n- Use `implement` to continue/complete in-progress tasks\n- Always follow refs for full context\n';
58835
-
58836
- // src/instructions/skills/knowns.template/SKILL.md
58837
- var SKILL_default11 = '---\nname: knowns.template\ndescription: Use when generating code from templates - list, run, or create templates\n---\n\n# Working with Templates\n\nGenerate code from predefined templates stored in `.knowns/templates/`.\n\n**Announce at start:** "I\'m using the knowns.template skill to work with templates."\n\n**Core principle:** USE TEMPLATES FOR CONSISTENT CODE GENERATION.\n\n## The Process\n\n### Step 1: List Available Templates\n\n{{#if mcp}}\n```json\nmcp__knowns__list_templates({})\n```\n{{else}}\n```bash\nknowns template list\n```\n{{/if}}\n\n### Step 2: Get Template Details\n\n{{#if mcp}}\n```json\nmcp__knowns__get_template({ "name": "<template-name>" })\n```\n{{else}}\n```bash\nknowns template info <template-name>\n```\n{{/if}}\n\nCheck:\n- Required variables (prompts)\n- Linked documentation (`doc:`)\n- Files that will be generated\n\n### Step 3: Read Linked Documentation\n\nIf template has a `doc:` field, read it first:\n\n{{#if mcp}}\n```json\nmcp__knowns__get_doc({ "path": "<doc-path>", "smart": true })\n```\n{{else}}\n```bash\nknowns doc "<doc-path>" --plain\n```\n{{/if}}\n\n### Step 4: Run Template\n\n{{#if mcp}}\n```json\n// Dry run first (preview)\nmcp__knowns__run_template({\n "name": "<template-name>",\n "variables": { "name": "MyComponent", "type": "page" },\n "dryRun": true\n})\n\n// Then run for real\nmcp__knowns__run_template({\n "name": "<template-name>",\n "variables": { "name": "MyComponent", "type": "page" },\n "dryRun": false\n})\n```\n{{else}}\n```bash\n# Dry run (preview)\nknowns template run <template-name> --name "MyComponent" --dry-run\n\n# Run for real\nknowns template run <template-name> --name "MyComponent"\n```\n{{/if}}\n\n### Step 5: Create New Template\n\n{{#if mcp}}\n```json\nmcp__knowns__create_template({\n "name": "<template-name>",\n "description": "Template description",\n "doc": "patterns/<related-doc>" // Optional: link to documentation\n})\n```\n{{else}}\n```bash\nknowns template create <template-name>\n```\n{{/if}}\n\nThis creates:\n```\n.knowns/templates/<template-name>/\n \u251C\u2500\u2500 _template.yaml # Config\n \u2514\u2500\u2500 example.ts.hbs # Example file\n```\n\n## Template Config (`_template.yaml`)\n\n```yaml\nname: react-component\ndescription: Create a React component with tests\ndoc: patterns/react-component # Link to documentation\n\nprompts:\n - name: name\n message: Component name?\n validate: required\n\n - name: type\n message: Component type?\n type: select\n choices:\n - page\n - component\n - layout\n\nfiles:\n - template: "{{name}}.tsx.hbs"\n destination: "src/components/{{pascalCase name}}/{{pascalCase name}}.tsx"\n\n - template: "{{name}}.test.tsx.hbs"\n destination: "src/components/{{pascalCase name}}/{{pascalCase name}}.test.tsx"\n condition: "{{includeTests}}"\n```\n\n## Template-Doc Linking\n\nTemplates can reference docs and vice versa:\n\n**In `_template.yaml`:**\n```yaml\ndoc: patterns/react-component\n```\n\n**In doc (markdown):**\n```markdown\nUse @template/react-component to generate.\n```\n\n**AI workflow:**\n1. Get template config\n2. Follow `doc:` link to understand patterns\n3. Run template with appropriate variables\n\n## Handlebars Helpers\n\nTemplates use Handlebars with built-in helpers:\n\n| Helper | Example | Output |\n|--------|---------|--------|\n| `camelCase` | `{{camelCase "my name"}}` | `myName` |\n| `pascalCase` | `{{pascalCase "my name"}}` | `MyName` |\n| `kebabCase` | `{{kebabCase "MyName"}}` | `my-name` |\n| `snakeCase` | `{{snakeCase "MyName"}}` | `my_name` |\n| `upperCase` | `{{upperCase "name"}}` | `NAME` |\n| `lowerCase` | `{{lowerCase "NAME"}}` | `name` |\n\n## CRITICAL: Template Syntax Pitfalls\n\n### JavaScript Template Literals + Handlebars\n\n**NEVER write `$` followed by triple-brace** - Handlebars interprets triple-brace as unescaped output:\n\n```\n// \u274C WRONG - Parse error!\nthis.logger.log(`Created: $` + `\\{{\\{camelCase entity}.id}`);\n\n// \u2705 CORRECT - Add space, use ~ to trim whitespace\nthis.logger.log(`Created: ${ \\{{~camelCase entity~}}.id}`);\n// Output: this.logger.log(`Created: ${product.id}`);\n```\n\n**Rules when writing .hbs templates:**\n1. Never `$` + triple-brace - always add space: `${ \\{{`\n2. Use `~` (tilde) to trim whitespace: `\\{{~helper~}}`\n3. For literal braces, escape with backslash\n\n## When to Use Templates\n\n| Scenario | Action |\n|----------|--------|\n| Creating new component | Run `react-component` template |\n| Adding API endpoint | Run `api-endpoint` template |\n| Setting up new feature | Run `feature-module` template |\n| Consistent file structure | Use template instead of copy-paste |\n\n## Integrated Workflows\n\n### During Implementation (Use Template)\n\n```\nTask \u2192 Read Context \u2192 Find Template \u2192 Generate Code \u2192 Customize\n```\n\n1. Read task and understand requirements\n2. List templates to find applicable one\n3. Get template details and read linked doc\n4. Run template (dry run first, then real)\n5. Customize generated code as needed\n6. Continue with remaining implementation\n\n**Benefits:**\n- Reduces context (no need to generate boilerplate)\n- Ensures consistency with project patterns\n- Faster implementation\n\n### During Extract (Create Template)\n\n```\nContext \u2192 Identify Pattern \u2192 Create Doc \u2192 Create Template \u2192 Link Both\n```\n\n1. Identify repeatable code pattern\n2. Create doc with `/knowns.extract`\n3. Create template with `knowns template create <name>`\n4. Link template to doc: `doc: patterns/<name>`\n5. Link doc to template: `@template/<name>`\n\n**When to create template:**\n- Pattern will be used multiple times\n- Has consistent file structure\n- Can be parameterized\n\n## Checklist\n\n- [ ] Listed available templates\n- [ ] Got template details (prompts, files)\n- [ ] Read linked documentation (if any)\n- [ ] Understood required variables\n- [ ] Ran dry run first\n- [ ] Ran template with correct inputs\n- [ ] Verified generated files\n\n## Remember\n\n- Always dry run first before writing files\n- Check `doc:` link in template for context\n- Templates ensure consistent code structure\n- Create new templates for repeated patterns\n- **NEVER write `$` + triple-brace** - use `${ \\{{~helper~}}` instead (add space, use tilde)\n';
58835
+ // src/instructions/skills/kn:verify/SKILL.md
58836
+ var SKILL_default10 = '---\nname: kn:verify\ndescription: Use when running SDD verification and coverage reporting\n---\n\n# SDD Verification\n\nRun validation with SDD-awareness to check spec coverage and task status.\n\n**Announce:** "Using kn:verify to check SDD status."\n\n**Core principle:** VERIFY SPEC COVERAGE \u2192 REPORT WARNINGS \u2192 SUGGEST FIXES.\n\n## Step 1: Run SDD Validation\n\n### Via CLI\n```bash\nknowns validate --sdd --plain\n```\n\n### Via MCP (if available)\n```json\nmcp__knowns__validate({ "scope": "sdd" })\n```\n\n## Step 2: Present SDD Status Report\n\nDisplay the results in this format:\n\n```\nSDD Status Report\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nSpecs: X total | Y approved | Z draft\nTasks: X total | Y done | Z in-progress | W todo\nCoverage: X/Y tasks linked to specs (Z%)\n\n\u26A0\uFE0F Warnings:\n - task-XX has no spec reference\n - specs/feature: X/Y ACs incomplete\n\n\u2705 Passed:\n - All spec references resolve\n - specs/auth: fully implemented\n```\n\n## Step 3: Analyze Results\n\n**Good coverage (>80%):**\n> SDD coverage is healthy. All tasks are properly linked to specs.\n\n**Medium coverage (50-80%):**\n> Some tasks are missing spec references. Consider:\n> - Link existing tasks to specs: `knowns task edit <id> --spec specs/<name>`\n> - Create specs for unlinked work: `/kn:spec <feature-name>`\n\n**Low coverage (<50%):**\n> Many tasks lack spec references. For better traceability:\n> 1. Create specs for major features: `/kn:spec <feature>`\n> 2. Link tasks to specs: `knowns task edit <id> --spec specs/<name>`\n> 3. Use `/kn:plan --from @doc/specs/<name>` for new tasks\n\n## Step 4: Suggest Actions\n\nBased on warnings, suggest specific fixes:\n\n**For tasks without spec:**\n> Link task to spec:\n> ```bash\n> knowns task edit <id> --spec specs/<name>\n> ```\n\n**For incomplete ACs:**\n> Check task progress:\n> ```bash\n> knowns task <id> --plain\n> ```\n\n**For approved specs without tasks:**\n> Create tasks from spec:\n> ```\n> /kn:plan --from @doc/specs/<name>\n> ```\n\n## Checklist\n\n- [ ] Ran validate --sdd\n- [ ] Presented status report\n- [ ] Analyzed coverage level\n- [ ] Suggested specific fixes for warnings\n\n## Red Flags\n\n- Ignoring warnings\n- Not suggesting actionable fixes\n- Skipping coverage analysis\n';
58838
58837
 
58839
58838
  // src/instructions/skills/index.ts
58840
58839
  function parseSkillFrontmatter(content) {
@@ -58861,35 +58860,27 @@ function createSkill(content, folderName) {
58861
58860
  content: content.trim()
58862
58861
  };
58863
58862
  }
58864
- var SKILL_TASK = createSkill(SKILL_default10, "knowns.task");
58865
- var SKILL_TASK_PLAN = createSkill(SKILL_default8, "knowns.task.plan");
58866
- var SKILL_TASK_IMPLEMENT = createSkill(SKILL_default7, "knowns.task.implement");
58867
- var SKILL_TASK_BRAINSTORM = createSkill(SKILL_default6, "knowns.task.brainstorm");
58868
- var SKILL_TASK_REOPEN = createSkill(SKILL_default9, "knowns.task.reopen");
58869
- var SKILL_EXTRACT = createSkill(SKILL_default3, "knowns.extract");
58870
- var SKILL_DOC = createSkill(SKILL_default2, "knowns.doc");
58871
- var SKILL_COMMIT = createSkill(SKILL_default, "knowns.commit");
58872
- var SKILL_INIT = createSkill(SKILL_default4, "knowns.init");
58873
- var SKILL_RESEARCH = createSkill(SKILL_default5, "knowns.research");
58874
- var SKILL_TEMPLATE = createSkill(SKILL_default11, "knowns.template");
58863
+ var SKILL_INIT = createSkill(SKILL_default5, "kn:init");
58864
+ var SKILL_PLAN = createSkill(SKILL_default6, "kn:plan");
58865
+ var SKILL_IMPLEMENT = createSkill(SKILL_default4, "kn:implement");
58866
+ var SKILL_RESEARCH = createSkill(SKILL_default7, "kn:research");
58867
+ var SKILL_COMMIT = createSkill(SKILL_default, "kn:commit");
58868
+ var SKILL_EXTRACT = createSkill(SKILL_default3, "kn:extract");
58869
+ var SKILL_DOC = createSkill(SKILL_default2, "kn:doc");
58870
+ var SKILL_TEMPLATE = createSkill(SKILL_default9, "kn:template");
58871
+ var SKILL_SPEC = createSkill(SKILL_default8, "kn:spec");
58872
+ var SKILL_VERIFY = createSkill(SKILL_default10, "kn:verify");
58875
58873
  var SKILLS = [
58876
- // Task skills
58877
- SKILL_TASK,
58878
- SKILL_TASK_PLAN,
58879
- SKILL_TASK_IMPLEMENT,
58880
- SKILL_TASK_BRAINSTORM,
58881
- SKILL_TASK_REOPEN,
58882
- // Extract skill
58883
- SKILL_EXTRACT,
58884
- // Doc skills
58885
- SKILL_DOC,
58886
- // Git skills
58887
- SKILL_COMMIT,
58888
- // Session skills
58889
58874
  SKILL_INIT,
58875
+ SKILL_PLAN,
58876
+ SKILL_IMPLEMENT,
58890
58877
  SKILL_RESEARCH,
58891
- // Template skill
58892
- SKILL_TEMPLATE
58878
+ SKILL_COMMIT,
58879
+ SKILL_EXTRACT,
58880
+ SKILL_DOC,
58881
+ SKILL_TEMPLATE,
58882
+ SKILL_SPEC,
58883
+ SKILL_VERIFY
58893
58884
  ];
58894
58885
 
58895
58886
  // src/codegen/renderer.ts
@@ -59271,11 +59262,28 @@ async function syncToPlatform(skills, platform, options2) {
59271
59262
  }
59272
59263
  return result;
59273
59264
  }
59265
+ function cleanupDeprecatedSkills(targetDir, dryRun) {
59266
+ let removed = 0;
59267
+ if (existsSync2(targetDir)) {
59268
+ const entries = readdirSync(targetDir, { withFileTypes: true });
59269
+ for (const entry of entries) {
59270
+ if (entry.isDirectory() && entry.name.startsWith("knowns.")) {
59271
+ if (!dryRun) {
59272
+ const deprecatedPath = join4(targetDir, entry.name);
59273
+ rmSync(deprecatedPath, { recursive: true, force: true });
59274
+ }
59275
+ removed++;
59276
+ }
59277
+ }
59278
+ }
59279
+ return removed;
59280
+ }
59274
59281
  async function syncToFolderPattern(skills, platform, options2, result) {
59275
59282
  const targetDir = join4(options2.projectRoot, platform.targetDir);
59276
59283
  if (!options2.dryRun && !existsSync2(targetDir)) {
59277
59284
  await mkdir3(targetDir, { recursive: true });
59278
59285
  }
59286
+ cleanupDeprecatedSkills(targetDir, options2.dryRun);
59279
59287
  for (const skill of skills) {
59280
59288
  const skillFolder = join4(targetDir, skill.folderName);
59281
59289
  const skillFile = join4(skillFolder, platform.extension);
@@ -59410,16 +59418,16 @@ function detectPlatforms(projectRoot) {
59410
59418
  var commands_reference_default = '{{#if mcp}}\n# MCP Tools Reference\n\n## Project Tools (Session Init)\n\n**CRITICAL: Call these at session start to initialize the project.**\n\n### mcp__knowns__detect_projects\n\nScan for all Knowns projects on the system:\n\n```json\n{}\n```\n\nReturns: `{ projects: [{ path, name }], currentProject, note }`\n\n### mcp__knowns__set_project\n\nSet the active project for all operations:\n\n```json\n{ "projectRoot": "/absolute/path/to/project" }\n```\n\n### mcp__knowns__get_current_project\n\nCheck current project status:\n\n```json\n{}\n```\n\nReturns: `{ projectRoot, isExplicitlySet, isValid, source }`\n\n---\n\n## Task Tools\n\n### mcp__knowns__create_task\n\n```json\n{\n "title": "Task title",\n "description": "Task description",\n "status": "todo",\n "priority": "medium",\n "labels": ["label1"],\n "assignee": "@me",\n "parent": "parent-id"\n}\n```\n\n### mcp__knowns__update_task\n\n```json\n{\n "taskId": "<id>",\n "status": "in-progress",\n "assignee": "@me",\n "addAc": ["Criterion 1", "Criterion 2"],\n "checkAc": [1, 2],\n "uncheckAc": [3],\n "removeAc": [4],\n "plan": "1. Step one\\n2. Step two",\n "notes": "Implementation notes",\n "appendNotes": "Additional notes"\n}\n```\n\n| Field | Purpose |\n|-------|---------|\n| `addAc` | Add new acceptance criteria |\n| `checkAc` | Mark AC done (1-based index) |\n| `uncheckAc` | Unmark AC (1-based index) |\n| `removeAc` | Remove AC (1-based index) |\n| `plan` | Set implementation plan |\n| `notes` | Replace implementation notes |\n| `appendNotes` | Append to notes |\n\n### mcp__knowns__get_task\n\n```json\n{ "taskId": "<id>" }\n```\n\n### mcp__knowns__list_tasks\n\n```json\n{ "status": "in-progress", "assignee": "@me" }\n```\n\n### mcp__knowns__search_tasks\n\n```json\n{ "query": "keyword" }\n```\n\n---\n\n## Doc Tools\n\n### mcp__knowns__get_doc\n\n**ALWAYS use `smart: true`** - auto-handles small/large docs:\n\n```json\n{ "path": "readme", "smart": true }\n```\n\nIf large, returns TOC. Then read section:\n```json\n{ "path": "readme", "section": "3" }\n```\n\n### mcp__knowns__list_docs\n\n```json\n{ "tag": "api" }\n```\n\n### mcp__knowns__create_doc\n\n```json\n{\n "title": "Doc Title",\n "description": "Description",\n "tags": ["tag1"],\n "folder": "guides",\n "content": "Initial content"\n}\n```\n\n### mcp__knowns__update_doc\n\n```json\n{\n "path": "readme",\n "content": "Replace content",\n "section": "2"\n}\n```\n\n### mcp__knowns__search_docs\n\n```json\n{ "query": "keyword", "tag": "api" }\n```\n\n### mcp__knowns__search (Unified)\n\n```json\n{\n "query": "keyword",\n "type": "all",\n "status": "in-progress",\n "priority": "high",\n "assignee": "@me",\n "label": "feature",\n "tag": "api",\n "limit": 20\n}\n```\n\n| Field | Purpose |\n|-------|---------|\n| `type` | "all", "task", or "doc" |\n| `status/priority/assignee/label` | Task filters |\n| `tag` | Doc filter |\n| `limit` | Max results (default: 20) |\n\n---\n\n## Time Tools\n\n### mcp__knowns__start_time\n\n```json\n{ "taskId": "<id>" }\n```\n\n### mcp__knowns__stop_time\n\n```json\n{ "taskId": "<id>" }\n```\n\n### mcp__knowns__add_time\n\n```json\n{\n "taskId": "<id>",\n "duration": "2h30m",\n "note": "Note",\n "date": "2025-01-15"\n}\n```\n\n### mcp__knowns__get_time_report\n\n```json\n{ "from": "2025-01-01", "to": "2025-01-31", "groupBy": "task" }\n```\n\n---\n\n## Template Tools\n\n### mcp__knowns__list_templates\n\n```json\n{}\n```\n\n### mcp__knowns__get_template\n\n```json\n{ "name": "template-name" }\n```\n\n### mcp__knowns__run_template\n\n```json\n{\n "name": "template-name",\n "variables": { "name": "MyComponent" },\n "dryRun": true\n}\n```\n\n### mcp__knowns__create_template\n\n```json\n{\n "name": "my-template",\n "description": "Description",\n "doc": "patterns/my-pattern"\n}\n```\n\n---\n\n## Other\n\n### mcp__knowns__get_board\n\n```json\n{}\n```\n\n{{/if}}\n{{#if cli}}\n# CLI Commands Reference\n\n## task create\n\n```bash\nknowns task create <title> [options]\n```\n\n| Flag | Short | Purpose |\n|------|-------|---------|\n| `--description` | `-d` | Task description |\n| `--ac` | | Acceptance criterion (repeatable) |\n| `--labels` | `-l` | Comma-separated labels |\n| `--assignee` | `-a` | Assign to user |\n| `--priority` | | low/medium/high |\n| `--parent` | | Parent task ID (raw ID only!) |\n\n**`-a` = assignee, NOT acceptance criteria! Use `--ac` for AC.**\n\n---\n\n## task edit\n\n```bash\nknowns task edit <id> [options]\n```\n\n| Flag | Short | Purpose |\n|------|-------|---------|\n| `--status` | `-s` | Change status |\n| `--assignee` | `-a` | Assign user |\n| `--ac` | | Add acceptance criterion |\n| `--check-ac` | | Mark AC done (1-indexed) |\n| `--uncheck-ac` | | Unmark AC |\n| `--plan` | | Set implementation plan |\n| `--notes` | | Replace notes |\n| `--append-notes` | | Add to notes |\n\n---\n\n## task view/list\n\n```bash\nknowns task <id> --plain\nknowns task list --plain\nknowns task list --status in-progress --plain\nknowns task list --tree --plain\n```\n\n---\n\n## doc create\n\n```bash\nknowns doc create <title> [options]\n```\n\n| Flag | Short | Purpose |\n|------|-------|---------|\n| `--description` | `-d` | Description |\n| `--tags` | `-t` | Comma-separated tags |\n| `--folder` | `-f` | Folder path |\n\n---\n\n## doc edit\n\n```bash\nknowns doc edit <name> [options]\n```\n\n| Flag | Short | Purpose |\n|------|-------|---------|\n| `--content` | `-c` | Replace content |\n| `--append` | `-a` | Append content |\n| `--section` | | Target section (use with -c) |\n\n**In doc edit, `-a` = append content, NOT assignee!**\n\n---\n\n## doc view/list\n\n**ALWAYS use `--smart`** - auto-handles small/large docs:\n\n```bash\nknowns doc <path> --plain --smart\n```\n\nIf large, returns TOC. Then read section:\n```bash\nknowns doc <path> --plain --section 3\n```\n\n```bash\nknowns doc list --plain\nknowns doc list --tag api --plain\n```\n\n---\n\n## time\n\n```bash\nknowns time start <id> # REQUIRED when taking task\nknowns time stop # REQUIRED when completing\nknowns time status\nknowns time add <id> <duration> -n "Note"\n```\n\n---\n\n## search\n\n```bash\nknowns search "query" --plain\nknowns search "auth" --type task --plain\nknowns search "api" --type doc --plain\n```\n\n---\n\n## template\n\n```bash\nknowns template list\nknowns template info <name>\nknowns template run <name> --name "X" --dry-run\nknowns template create <name>\n```\n\n---\n\n## Multi-line Input\n\n```bash\nknowns task edit <id> --plan $\'1. Step\\n2. Step\\n3. Step\'\n```\n{{/if}}\n';
59411
59419
 
59412
59420
  // src/instructions/guidelines/unified/common-mistakes.md
59413
- var common_mistakes_default = '# Common Mistakes\n\n{{#if cli}}\n## CRITICAL: The -a Flag\n\n| Command | `-a` Means | NOT This! |\n|---------|------------|-----------|\n| `task create/edit` | `--assignee` | ~~acceptance criteria~~ |\n| `doc edit` | `--append` | ~~assignee~~ |\n\n```bash\n# WRONG (sets assignee to garbage!)\nknowns task edit 35 -a "Criterion text"\n\n# CORRECT (use --ac)\nknowns task edit 35 --ac "Criterion text"\n```\n\n---\n{{/if}}\n\n## CRITICAL: Notes vs Append Notes\n\n**NEVER use `notes`/`--notes` for progress updates - it REPLACES all existing notes!**\n\n{{#if cli}}\n```bash\n# \u274C WRONG - Destroys audit trail!\nknowns task edit <id> --notes "Done: feature X"\n\n# \u2705 CORRECT - Preserves history\nknowns task edit <id> --append-notes "Done: feature X"\n```\n{{/if}}\n{{#if mcp}}\n```json\n// \u274C WRONG - Destroys audit trail!\nmcp__knowns__update_task({\n "taskId": "<id>",\n "notes": "Done: feature X"\n})\n\n// \u2705 CORRECT - Preserves history\nmcp__knowns__update_task({\n "taskId": "<id>",\n "appendNotes": "Done: feature X"\n})\n```\n{{/if}}\n\n| Field | Behavior |\n|-------|----------|\n{{#if cli}}\n| `--notes` | **REPLACES** all notes (use only for initial setup) |\n| `--append-notes` | **APPENDS** to existing notes (use for progress) |\n{{/if}}\n{{#if mcp}}\n| `notes` | **REPLACES** all notes (use only for initial setup) |\n| `appendNotes` | **APPENDS** to existing notes (use for progress) |\n{{/if}}\n\n---\n\n## Quick Reference\n\n| DON\'T | DO |\n|-------|-----|\n{{#if cli}}\n| Edit .md files directly | Use CLI commands |\n| `-a "criterion"` | `--ac "criterion"` |\n| `--parent task-48` | `--parent 48` (raw ID) |\n| `--plain` with create/edit | `--plain` only for view/list |\n| `--notes` for progress | `--append-notes` for progress |\n{{/if}}\n{{#if mcp}}\n| Edit .md files directly | Use MCP tools |\n| `notes` for progress | `appendNotes` for progress |\n{{/if}}\n| Check AC before work done | Check AC AFTER work done |\n| Code before plan approval | Wait for user approval |\n| Code before reading docs | Read docs FIRST |\n| Skip time tracking | Always start/stop timer |\n| Ignore refs | Follow ALL `@task-xxx`, `@doc/xxx`, `@template/xxx` refs |\n\n{{#if mcp}}\n---\n\n## MCP Task Operations\n\nAll task operations are available via MCP:\n\n| Operation | MCP Field |\n|-----------|-----------|\n| Add acceptance criteria | `addAc: ["criterion"]` |\n| Check AC | `checkAc: [1, 2]` (1-based) |\n| Uncheck AC | `uncheckAc: [1]` (1-based) |\n| Remove AC | `removeAc: [1]` (1-based) |\n| Set plan | `plan: "..."` |\n| Set notes | `notes: "..."` |\n| Append notes | `appendNotes: "..."` |\n| Change status | `status: "in-progress"` |\n| Assign | `assignee: "@me"` |\n{{/if}}\n\n---\n\n## Template Syntax Pitfalls\n\nWhen writing `.hbs` templates, **NEVER** create `$` followed by triple-brace - Handlebars interprets triple-brace as unescaped output:\n\n```\n// \u274C WRONG - Parse error!\nthis.logger.log(`Created: $` + `{` + `{` + `{camelCase entity}.id}`);\n\n// \u2705 CORRECT - Add space between ${ and double-brace, use ~ to trim whitespace\nthis.logger.log(`Created: ${ \\{{~camelCase entity~}}.id}`);\n```\n\n| DON\'T | DO |\n|-------|-----|\n| `$` + triple-brace | `${ \\{{~helper~}}}` (space + escaped) |\n\n**Rules:**\n- Add space between `${` and double-brace\n- Use `~` (tilde) to trim whitespace in output\n- Escape literal braces with backslash\n\n---\n\n## Error Recovery\n\n| Problem | Solution |\n|---------|----------|\n{{#if cli}}\n| Set assignee to AC text | `knowns task edit <id> -a @me` |\n| Forgot to stop timer | `knowns time add <id> <duration>` |\n| Checked AC too early | `knowns task edit <id> --uncheck-ac N` |\n| Task not found | `knowns task list --plain` |\n| Replaced notes by mistake | Cannot recover - notes are lost. Use `--append-notes` next time |\n{{/if}}\n{{#if mcp}}\n| Forgot to stop timer | `mcp__knowns__add_time` with duration |\n| Wrong status | `mcp__knowns__update_task` to fix |\n| Task not found | `mcp__knowns__list_tasks` to find ID |\n| Need to uncheck AC | `mcp__knowns__update_task` with `uncheckAc: [N]` |\n| Checked AC too early | `mcp__knowns__update_task` with `uncheckAc: [N]` |\n| Replaced notes by mistake | Cannot recover - notes are lost. Use `appendNotes` next time |\n{{/if}}\n';
59421
+ var common_mistakes_default = '# Common Mistakes\n\n{{#if cli}}\n## CRITICAL: The -a Flag\n\n| Command | `-a` Means | NOT This! |\n|---------|------------|-----------|\n| `task create/edit` | `--assignee` | ~~acceptance criteria~~ |\n| `doc edit` | `--append` | ~~assignee~~ |\n\n```bash\n# WRONG (sets assignee to garbage!)\nknowns task edit 35 -a "Criterion text"\n\n# CORRECT (use --ac)\nknowns task edit 35 --ac "Criterion text"\n```\n\n---\n{{/if}}\n\n## CRITICAL: Notes vs Append Notes\n\n**NEVER use `notes`/`--notes` for progress updates - it REPLACES all existing notes!**\n\n{{#if cli}}\n```bash\n# \u274C WRONG - Destroys audit trail!\nknowns task edit <id> --notes "Done: feature X"\n\n# \u2705 CORRECT - Preserves history\nknowns task edit <id> --append-notes "Done: feature X"\n```\n{{/if}}\n{{#if mcp}}\n```json\n// \u274C WRONG - Destroys audit trail!\nmcp__knowns__update_task({\n "taskId": "<id>",\n "notes": "Done: feature X"\n})\n\n// \u2705 CORRECT - Preserves history\nmcp__knowns__update_task({\n "taskId": "<id>",\n "appendNotes": "Done: feature X"\n})\n```\n{{/if}}\n\n| Field | Behavior |\n|-------|----------|\n{{#if cli}}\n| `--notes` | **REPLACES** all notes (use only for initial setup) |\n| `--append-notes` | **APPENDS** to existing notes (use for progress) |\n{{/if}}\n{{#if mcp}}\n| `notes` | **REPLACES** all notes (use only for initial setup) |\n| `appendNotes` | **APPENDS** to existing notes (use for progress) |\n{{/if}}\n\n---\n\n## Quick Reference\n\n| DON\'T | DO |\n|-------|-----|\n{{#if cli}}\n| Edit .md files directly | Use CLI commands |\n| `-a "criterion"` | `--ac "criterion"` |\n| `--parent task-48` | `--parent 48` (raw ID) |\n| `--plain` with create/edit | `--plain` only for view/list |\n| `--notes` for progress | `--append-notes` for progress |\n{{/if}}\n{{#if mcp}}\n| Edit .md files directly | Use MCP tools |\n| `notes` for progress | `appendNotes` for progress |\n{{/if}}\n| Check AC before work done | Check AC AFTER work done |\n| Code before plan approval | Wait for user approval |\n| Code before reading docs | Read docs FIRST |\n| Skip time tracking | Always start/stop timer |\n| Skip validation | Run validate before completing |\n| Ignore refs | Follow ALL `@task-xxx`, `@doc/xxx`, `@template/xxx` refs |\n\n{{#if mcp}}\n---\n\n## MCP Task Operations\n\nAll task operations are available via MCP:\n\n| Operation | MCP Field |\n|-----------|-----------|\n| Add acceptance criteria | `addAc: ["criterion"]` |\n| Check AC | `checkAc: [1, 2]` (1-based) |\n| Uncheck AC | `uncheckAc: [1]` (1-based) |\n| Remove AC | `removeAc: [1]` (1-based) |\n| Set plan | `plan: "..."` |\n| Set notes | `notes: "..."` |\n| Append notes | `appendNotes: "..."` |\n| Change status | `status: "in-progress"` |\n| Assign | `assignee: "@me"` |\n{{/if}}\n\n---\n\n## Template Syntax Pitfalls\n\nWhen writing `.hbs` templates, **NEVER** create `$` followed by triple-brace - Handlebars interprets triple-brace as unescaped output:\n\n```\n// \u274C WRONG - Parse error!\nthis.logger.log(`Created: $` + `{` + `{` + `{camelCase entity}.id}`);\n\n// \u2705 CORRECT - Add space between ${ and double-brace, use ~ to trim whitespace\nthis.logger.log(`Created: ${ \\{{~camelCase entity~}}.id}`);\n```\n\n| DON\'T | DO |\n|-------|-----|\n| `$` + triple-brace | `${ \\{{~helper~}}}` (space + escaped) |\n\n**Rules:**\n- Add space between `${` and double-brace\n- Use `~` (tilde) to trim whitespace in output\n- Escape literal braces with backslash\n\n---\n\n## Error Recovery\n\n| Problem | Solution |\n|---------|----------|\n{{#if cli}}\n| Set assignee to AC text | `knowns task edit <id> -a @me` |\n| Forgot to stop timer | `knowns time add <id> <duration>` |\n| Checked AC too early | `knowns task edit <id> --uncheck-ac N` |\n| Task not found | `knowns task list --plain` |\n| Replaced notes by mistake | Cannot recover - notes are lost. Use `--append-notes` next time |\n| Broken refs in task/doc | Run `knowns validate`, fix refs, validate again |\n{{/if}}\n{{#if mcp}}\n| Forgot to stop timer | `mcp__knowns__add_time` with duration |\n| Wrong status | `mcp__knowns__update_task` to fix |\n| Task not found | `mcp__knowns__list_tasks` to find ID |\n| Need to uncheck AC | `mcp__knowns__update_task` with `uncheckAc: [N]` |\n| Checked AC too early | `mcp__knowns__update_task` with `uncheckAc: [N]` |\n| Replaced notes by mistake | Cannot recover - notes are lost. Use `appendNotes` next time |\n| Broken refs in task/doc | Run `mcp__knowns__validate`, fix refs, validate again |\n{{/if}}\n';
59414
59422
 
59415
59423
  // src/instructions/guidelines/unified/context-optimization.md
59416
59424
  var context_optimization_default = '# Context Optimization\n\nOptimize your context usage to work more efficiently within token limits.\n\n---\n\n{{#if cli}}\n## Output Format\n\n```bash\n# Verbose output\nknowns task 42 --json\n\n# Compact output (always use --plain)\nknowns task 42 --plain\n```\n\n---\n{{/if}}\n\n## Search Before Read\n\n{{#if cli}}\n### CLI\n```bash\n# DON\'T: Read all docs hoping to find info\nknowns doc "doc1" --plain\nknowns doc "doc2" --plain\n\n# DO: Search first, then read only relevant docs\nknowns search "authentication" --type doc --plain\nknowns doc "security-patterns" --plain\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\n// DON\'T: Read all docs hoping to find info\nmcp__knowns__get_doc({ "path": "doc1" })\nmcp__knowns__get_doc({ "path": "doc2" })\n\n// DO: Search first, then read only relevant docs\nmcp__knowns__search_docs({ "query": "authentication" })\nmcp__knowns__get_doc({ "path": "security-patterns" })\n```\n{{/if}}\n\n---\n\n{{#if mcp}}\n## Use Filters\n\n```json\n// DON\'T: List all then filter manually\nmcp__knowns__list_tasks({})\n\n// DO: Use filters in the query\nmcp__knowns__list_tasks({\n "status": "in-progress",\n "assignee": "@me"\n})\n```\n\n---\n{{/if}}\n\n## Reading Documents\n\n{{#if cli}}\n### CLI\n**ALWAYS use `--smart`** - auto-handles both small and large docs:\n\n```bash\n# DON\'T: Read without --smart\nknowns doc readme --plain\n\n# DO: Always use --smart\nknowns doc readme --plain --smart\n# Small doc \u2192 full content\n# Large doc \u2192 stats + TOC\n\n# If large, read specific section:\nknowns doc readme --plain --section 3\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n**ALWAYS use `smart: true`** - auto-handles both small and large docs:\n\n```json\n// DON\'T: Read without smart\nmcp__knowns__get_doc({ "path": "readme" })\n\n// DO: Always use smart\nmcp__knowns__get_doc({ "path": "readme", "smart": true })\n// Small doc \u2192 full content\n// Large doc \u2192 stats + TOC\n\n// If large, read specific section:\nmcp__knowns__get_doc({ "path": "readme", "section": "3" })\n```\n{{/if}}\n\n**Behavior:**\n- **\u22642000 tokens**: Returns full content automatically\n- **>2000 tokens**: Returns stats + TOC, then use section parameter\n\n---\n\n## Compact Notes\n\n```bash\n# DON\'T: Verbose notes\nknowns task edit 42 --append-notes "I have successfully completed the implementation..."\n\n# DO: Compact notes\nknowns task edit 42 --append-notes "Done: Auth middleware + JWT validation"\n```\n\n---\n\n## Avoid Redundant Operations\n\n| Don\'t | Do Instead |\n|-------|------------|\n| Re-read files already in context | Reference from memory |\n| List tasks/docs multiple times | List once, remember results |\n| Quote entire file contents | Summarize key points |\n\n---\n\n## Efficient Workflow\n\n| Phase | Context-Efficient Approach |\n|-------|---------------------------|\n| **Research** | Search \u2192 Read only matches |\n| **Planning** | Brief plan, not detailed prose |\n| **Coding** | Read only files being modified |\n| **Notes** | Bullet points, not paragraphs |\n| **Completion** | Summary, not full log |\n\n---\n\n## Quick Rules\n\n{{#if cli}}\n1. **Always `--plain`** - Never use `--json` unless needed\n2. **Always `--smart`** - Auto-handles doc size\n{{/if}}\n{{#if mcp}}\n1. **Always `smart: true`** - Auto-handles doc size\n{{/if}}\n3. **Search first** - Don\'t read all docs hoping to find info\n4. **Read selectively** - Only fetch what you need\n5. **Write concise** - Compact notes, not essays\n6. **Don\'t repeat** - Reference context already loaded\n';
59417
59425
 
59418
59426
  // src/instructions/guidelines/unified/core-rules.md
59419
- var core_rules_default = '# Core Rules\n\n> These rules are NON-NEGOTIABLE. Violating them leads to data corruption and lost work.\n\n---\n\n## The Golden Rule\n\n{{#if mcp}}\n{{#if cli}}\n**If you want to change ANYTHING in a task or doc, use MCP tools (preferred) or CLI commands (fallback). NEVER edit .md files directly.**\n{{else}}\n**If you want to change ANYTHING in a task or doc, use MCP tools. NEVER edit .md files directly.**\n{{/if}}\n{{else}}\n{{#if cli}}\n**If you want to change ANYTHING in a task or doc, use CLI commands. NEVER edit .md files directly.**\n{{/if}}\n{{/if}}\n\n{{#if mcp}}\n---\n\n## Session Initialization (MCP)\n\n**CRITICAL: At the START of every session, run these tools to initialize the project:**\n\n```json\n// Step 1: Detect available projects\nmcp__knowns__detect_projects({})\n\n// Step 2: Set the project you want to work with\nmcp__knowns__set_project({ "projectRoot": "/path/to/project" })\n\n// Step 3: Verify project is set correctly\nmcp__knowns__get_current_project({})\n```\n\n**Why?** The MCP server may not know which project you\'re working in. These tools:\n- `detect_projects` - Scans common workspace directories for Knowns projects\n- `set_project` - Sets the active project for all subsequent operations\n- `get_current_project` - Verifies the current project path\n\n**If you skip this step**, other tools like `list_tasks`, `get_doc`, etc. may fail or work on the wrong project.\n{{/if}}\n\n{{#if cli}}\n---\n\n## CRITICAL: The -a Flag Confusion\n\nThe `-a` flag means DIFFERENT things in different commands:\n\n| Command | `-a` Means | NOT This! |\n|---------|------------|-----------|\n| `task create` | `--assignee` (assign user) | ~~acceptance criteria~~ |\n| `task edit` | `--assignee` (assign user) | ~~acceptance criteria~~ |\n| `doc edit` | `--append` (append content) | ~~assignee~~ |\n\n### Acceptance Criteria: Use --ac\n\n```bash\n# WRONG: -a is assignee, NOT acceptance criteria!\nknowns task edit 35 -a "- [ ] Criterion" # Sets assignee to garbage!\n\n# CORRECT: Use --ac for acceptance criteria\nknowns task edit 35 --ac "Criterion one"\nknowns task create "Title" --ac "Criterion one" --ac "Criterion two"\n```\n{{/if}}\n\n---\n\n## Quick Reference\n\n| Rule | Description |\n|------|-------------|\n{{#if mcp}}\n{{#if cli}}\n| **MCP Tools (preferred)** | Use MCP tools for ALL operations. Fallback to CLI if needed. NEVER edit .md files directly |\n{{else}}\n| **MCP Tools Only** | Use MCP tools for ALL operations. NEVER edit .md files directly |\n{{/if}}\n{{else}}\n{{#if cli}}\n| **CLI Only** | Use commands for ALL operations. NEVER edit .md files directly |\n{{/if}}\n{{/if}}\n| **Docs First** | Read project docs BEFORE planning or coding |\n| **Time Tracking** | Start timer when taking task, stop when done |\n| **Plan Approval** | Share plan with user, WAIT for approval before coding |\n| **Check AC After** | Only mark criteria done AFTER completing work |\n\n{{#if cli}}\n---\n\n## The --plain Flag\n\n**ONLY for view/list/search commands (NOT create/edit):**\n\n```bash\n# CORRECT\nknowns task <id> --plain\nknowns task list --plain\nknowns doc "path" --plain\nknowns search "query" --plain\n\n# WRONG (create/edit don\'t support --plain)\nknowns task create "Title" --plain # ERROR!\nknowns task edit <id> -s done --plain # ERROR!\n```\n{{/if}}\n\n---\n\n## Reference System\n\nTasks, docs, and templates can reference each other:\n\n| Type | Writing (Input) | Reading (Output) |\n|------|-----------------|------------------|\n| Task | `@task-<id>` | `@.knowns/tasks/task-<id>` |\n| Doc | `@doc/<path>` | `@.knowns/docs/<path>.md` |\n| Template | `@template/<name>` | `@.knowns/templates/<name>` |\n\n**Always follow refs recursively** to gather complete context before planning.\n\n---\n\n## Subtasks\n\n{{#if cli}}\n### CLI\n```bash\nknowns task create "Subtask title" --parent 48\n```\n\n**CRITICAL:** Use raw ID for `--parent`:\n```bash\n# CORRECT\nknowns task create "Title" --parent 48\n\n# WRONG\nknowns task create "Title" --parent task-48\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\nmcp__knowns__create_task({\n "title": "Subtask title",\n "parent": "parent-task-id"\n})\n```\n\n**CRITICAL:** Use raw ID (string) for all MCP tool calls.\n{{/if}}\n';
59427
+ var core_rules_default = '# Core Rules\n\n> These rules are NON-NEGOTIABLE. Violating them leads to data corruption and lost work.\n\n---\n\n## The Golden Rule\n\n{{#if mcp}}\n{{#if cli}}\n**If you want to change ANYTHING in a task or doc, use MCP tools (preferred) or CLI commands (fallback). NEVER edit .md files directly.**\n{{else}}\n**If you want to change ANYTHING in a task or doc, use MCP tools. NEVER edit .md files directly.**\n{{/if}}\n{{else}}\n{{#if cli}}\n**If you want to change ANYTHING in a task or doc, use CLI commands. NEVER edit .md files directly.**\n{{/if}}\n{{/if}}\n\n{{#if mcp}}\n---\n\n## Session Initialization (MCP)\n\n**CRITICAL: At the START of every session, run these tools to initialize the project:**\n\n```json\n// Step 1: Detect available projects\nmcp__knowns__detect_projects({})\n\n// Step 2: Set the project you want to work with\nmcp__knowns__set_project({ "projectRoot": "/path/to/project" })\n\n// Step 3: Verify project is set correctly\nmcp__knowns__get_current_project({})\n```\n\n**Why?** The MCP server may not know which project you\'re working in. These tools:\n- `detect_projects` - Scans common workspace directories for Knowns projects\n- `set_project` - Sets the active project for all subsequent operations\n- `get_current_project` - Verifies the current project path\n\n**If you skip this step**, other tools like `list_tasks`, `get_doc`, etc. may fail or work on the wrong project.\n{{/if}}\n\n{{#if cli}}\n---\n\n## CRITICAL: The -a Flag Confusion\n\nThe `-a` flag means DIFFERENT things in different commands:\n\n| Command | `-a` Means | NOT This! |\n|---------|------------|-----------|\n| `task create` | `--assignee` (assign user) | ~~acceptance criteria~~ |\n| `task edit` | `--assignee` (assign user) | ~~acceptance criteria~~ |\n| `doc edit` | `--append` (append content) | ~~assignee~~ |\n\n### Acceptance Criteria: Use --ac\n\n```bash\n# WRONG: -a is assignee, NOT acceptance criteria!\nknowns task edit 35 -a "- [ ] Criterion" # Sets assignee to garbage!\n\n# CORRECT: Use --ac for acceptance criteria\nknowns task edit 35 --ac "Criterion one"\nknowns task create "Title" --ac "Criterion one" --ac "Criterion two"\n```\n{{/if}}\n\n---\n\n## Quick Reference\n\n| Rule | Description |\n|------|-------------|\n{{#if mcp}}\n{{#if cli}}\n| **MCP Tools (preferred)** | Use MCP tools for ALL operations. Fallback to CLI if needed. NEVER edit .md files directly |\n{{else}}\n| **MCP Tools Only** | Use MCP tools for ALL operations. NEVER edit .md files directly |\n{{/if}}\n{{else}}\n{{#if cli}}\n| **CLI Only** | Use commands for ALL operations. NEVER edit .md files directly |\n{{/if}}\n{{/if}}\n| **Docs First** | Read project docs BEFORE planning or coding |\n| **Time Tracking** | Start timer when taking task, stop when done |\n| **Plan Approval** | Share plan with user, WAIT for approval before coding |\n| **Check AC After** | Only mark criteria done AFTER completing work |\n| **Validate** | Run validate before completing task |\n\n{{#if cli}}\n---\n\n## The --plain Flag\n\n**ONLY for view/list/search commands (NOT create/edit):**\n\n```bash\n# CORRECT\nknowns task <id> --plain\nknowns task list --plain\nknowns doc "path" --plain\nknowns search "query" --plain\n\n# WRONG (create/edit don\'t support --plain)\nknowns task create "Title" --plain # ERROR!\nknowns task edit <id> -s done --plain # ERROR!\n```\n{{/if}}\n\n---\n\n## Reference System\n\nTasks, docs, and templates can reference each other:\n\n| Type | Writing (Input) | Reading (Output) |\n|------|-----------------|------------------|\n| Task | `@task-<id>` | `@.knowns/tasks/task-<id>` |\n| Doc | `@doc/<path>` | `@.knowns/docs/<path>.md` |\n| Template | `@template/<name>` | `@.knowns/templates/<name>` |\n\n**Always follow refs recursively** to gather complete context before planning.\n\n---\n\n## Subtasks\n\n{{#if cli}}\n### CLI\n```bash\nknowns task create "Subtask title" --parent 48\n```\n\n**CRITICAL:** Use raw ID for `--parent`:\n```bash\n# CORRECT\nknowns task create "Title" --parent 48\n\n# WRONG\nknowns task create "Title" --parent task-48\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\nmcp__knowns__create_task({\n "title": "Subtask title",\n "parent": "parent-task-id"\n})\n```\n\n**CRITICAL:** Use raw ID (string) for all MCP tool calls.\n{{/if}}\n';
59420
59428
 
59421
59429
  // src/instructions/guidelines/unified/workflow-completion.md
59422
- var workflow_completion_default = '# Task Completion\n\n## Definition of Done\n\nA task is **Done** when ALL of these are complete:\n\n{{#if cli}}\n### CLI\n| Requirement | Command |\n|-------------|---------|\n| All AC checked | `knowns task edit <id> --check-ac N` |\n| Notes added | `knowns task edit <id> --notes "Summary"` |\n| Timer stopped | `knowns time stop` |\n| Status = done | `knowns task edit <id> -s done` |\n| Tests pass | Run test suite |\n{{/if}}\n{{#if mcp}}\n### MCP\n| Requirement | How |\n|-------------|-----|\n| All AC checked | `mcp__knowns__update_task` with `checkAc` |\n| Notes added | `mcp__knowns__update_task` with `notes` |\n| Timer stopped | `mcp__knowns__stop_time` |\n| Status = done | `mcp__knowns__update_task` with `status: "done"` |\n| Tests pass | Run test suite |\n{{/if}}\n\n---\n\n## Completion Steps\n\n{{#if cli}}\n### CLI\n```bash\n# 1. Verify all AC are checked\nknowns task <id> --plain\n\n# 2. Add implementation notes\nknowns task edit <id> --notes $\'## Summary\nWhat was done and key decisions.\'\n\n# 3. Stop timer (REQUIRED!)\nknowns time stop\n\n# 4. Mark done\nknowns task edit <id> -s done\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\n// 1. Verify all AC are checked\nmcp__knowns__get_task({ "taskId": "<id>" })\n\n// 2. Add implementation notes\nmcp__knowns__update_task({\n "taskId": "<id>",\n "notes": "## Summary\\nWhat was done and key decisions."\n})\n\n// 3. Stop timer (REQUIRED!)\nmcp__knowns__stop_time({ "taskId": "<id>" })\n\n// 4. Mark done\nmcp__knowns__update_task({\n "taskId": "<id>",\n "status": "done"\n})\n```\n{{/if}}\n\n---\n\n## Post-Completion Changes\n\nIf user requests changes after task is done:\n\n{{#if cli}}\n### CLI\n```bash\nknowns task edit <id> -s in-progress # Reopen\nknowns time start <id> # Restart timer\nknowns task edit <id> --ac "Fix: description"\nknowns task edit <id> --append-notes "Reopened: reason"\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\n// 1. Reopen task\nmcp__knowns__update_task({\n "taskId": "<id>",\n "status": "in-progress"\n})\n\n// 2. Restart timer\nmcp__knowns__start_time({ "taskId": "<id>" })\n\n// 3. Add AC for the fix\nmcp__knowns__update_task({\n "taskId": "<id>",\n "addAc": ["Fix: description"],\n "appendNotes": "Reopened: reason"\n})\n```\n{{/if}}\n\nThen follow completion steps again.\n\n---\n\n## Checklist\n\n{{#if cli}}\n### CLI\n- [ ] All AC checked (`--check-ac`)\n- [ ] Notes added (`--notes`)\n- [ ] Timer stopped (`time stop`)\n- [ ] Tests pass\n- [ ] Status = done (`-s done`)\n{{/if}}\n{{#if mcp}}\n### MCP\n- [ ] All AC checked (`checkAc`)\n- [ ] Notes added (`notes`)\n- [ ] Timer stopped (`mcp__knowns__stop_time`)\n- [ ] Tests pass\n- [ ] Status = done (`mcp__knowns__update_task`)\n{{/if}}\n';
59430
+ var workflow_completion_default = '# Task Completion\n\n## Definition of Done\n\nA task is **Done** when ALL of these are complete:\n\n{{#if cli}}\n### CLI\n| Requirement | Command |\n|-------------|---------|\n| All AC checked | `knowns task edit <id> --check-ac N` |\n| Notes added | `knowns task edit <id> --notes "Summary"` |\n| Refs validated | `knowns validate` |\n| Timer stopped | `knowns time stop` |\n| Status = done | `knowns task edit <id> -s done` |\n| Tests pass | Run test suite |\n{{/if}}\n{{#if mcp}}\n### MCP\n| Requirement | How |\n|-------------|-----|\n| All AC checked | `mcp__knowns__update_task` with `checkAc` |\n| Notes added | `mcp__knowns__update_task` with `notes` |\n| Refs validated | `mcp__knowns__validate` |\n| Timer stopped | `mcp__knowns__stop_time` |\n| Status = done | `mcp__knowns__update_task` with `status: "done"` |\n| Tests pass | Run test suite |\n{{/if}}\n\n---\n\n## Completion Steps\n\n{{#if cli}}\n### CLI\n```bash\n# 1. Verify all AC are checked\nknowns task <id> --plain\n\n# 2. Add implementation notes\nknowns task edit <id> --notes $\'## Summary\nWhat was done and key decisions.\'\n\n# 3. Validate refs (catch broken @doc/ @task- refs)\nknowns validate\n\n# 4. Stop timer (REQUIRED!)\nknowns time stop\n\n# 5. Mark done\nknowns task edit <id> -s done\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\n// 1. Verify all AC are checked\nmcp__knowns__get_task({ "taskId": "<id>" })\n\n// 2. Add implementation notes\nmcp__knowns__update_task({\n "taskId": "<id>",\n "notes": "## Summary\\nWhat was done and key decisions."\n})\n\n// 3. Validate refs (catch broken @doc/ @task- refs)\nmcp__knowns__validate({})\n\n// 4. Stop timer (REQUIRED!)\nmcp__knowns__stop_time({ "taskId": "<id>" })\n\n// 5. Mark done\nmcp__knowns__update_task({\n "taskId": "<id>",\n "status": "done"\n})\n```\n{{/if}}\n\n---\n\n## Post-Completion Changes\n\nIf user requests changes after task is done:\n\n{{#if cli}}\n### CLI\n```bash\nknowns task edit <id> -s in-progress # Reopen\nknowns time start <id> # Restart timer\nknowns task edit <id> --ac "Fix: description"\nknowns task edit <id> --append-notes "Reopened: reason"\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\n// 1. Reopen task\nmcp__knowns__update_task({\n "taskId": "<id>",\n "status": "in-progress"\n})\n\n// 2. Restart timer\nmcp__knowns__start_time({ "taskId": "<id>" })\n\n// 3. Add AC for the fix\nmcp__knowns__update_task({\n "taskId": "<id>",\n "addAc": ["Fix: description"],\n "appendNotes": "Reopened: reason"\n})\n```\n{{/if}}\n\nThen follow completion steps again.\n\n---\n\n## Checklist\n\n{{#if cli}}\n### CLI\n- [ ] All AC checked (`--check-ac`)\n- [ ] Notes added (`--notes`)\n- [ ] Refs validated (`knowns validate`)\n- [ ] Timer stopped (`time stop`)\n- [ ] Tests pass\n- [ ] Status = done (`-s done`)\n{{/if}}\n{{#if mcp}}\n### MCP\n- [ ] All AC checked (`checkAc`)\n- [ ] Notes added (`notes`)\n- [ ] Refs validated (`mcp__knowns__validate`)\n- [ ] Timer stopped (`mcp__knowns__stop_time`)\n- [ ] Tests pass\n- [ ] Status = done (`mcp__knowns__update_task`)\n{{/if}}\n';
59423
59431
 
59424
59432
  // src/instructions/guidelines/unified/workflow-creation.md
59425
59433
  var workflow_creation_default = '# Task Creation\n\n## Before Creating\n\n{{#if cli}}\n### CLI\n```bash\n# Search for existing tasks first\nknowns search "keyword" --type task --plain\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\n// Search for existing tasks first\nmcp__knowns__search_tasks({ "query": "keyword" })\n```\n{{/if}}\n\n---\n\n## Create Task\n\n{{#if cli}}\n### CLI\n```bash\nknowns task create "Clear title (WHAT)" \\\n -d "Description (WHY)" \\\n --ac "Outcome 1" \\\n --ac "Outcome 2" \\\n --priority medium \\\n -l "labels"\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\nmcp__knowns__create_task({\n "title": "Clear title (WHAT)",\n "description": "Description (WHY). Related: @doc/security-patterns",\n "priority": "medium",\n "labels": ["feature", "auth"]\n})\n```\n\n**Note:** Add acceptance criteria after creation:\n```bash\nknowns task edit <id> --ac "Outcome 1" --ac "Outcome 2"\n```\n{{/if}}\n\n---\n\n## Quality Guidelines\n\n### Title\n| Bad | Good |\n|-----|------|\n| Do auth stuff | Add JWT authentication |\n| Fix bug | Fix login timeout |\n\n### Description\nExplain WHY. Include doc refs: `@doc/security-patterns`\n\n### Acceptance Criteria\n**Outcome-focused, NOT implementation steps:**\n\n| Bad | Good |\n|-----|------|\n| Add handleLogin() function | User can login |\n| Use bcrypt | Passwords are hashed |\n| Add try-catch | Errors return proper HTTP codes |\n\n---\n\n## Subtasks\n\n{{#if cli}}\n### CLI\n```bash\nknowns task create "Parent task"\nknowns task create "Subtask" --parent 48 # Raw ID only!\n```\n{{/if}}\n{{#if mcp}}\n### MCP\n```json\n// Create parent first\nmcp__knowns__create_task({ "title": "Parent task" })\n\n// Then create subtask with parent ID\nmcp__knowns__create_task({\n "title": "Subtask",\n "parent": "parent-task-id"\n})\n```\n{{/if}}\n\n---\n\n## Anti-Patterns\n\n- Too many AC in one task -> Split into multiple tasks\n- Implementation steps as AC -> Write outcomes instead\n- Skip search -> Always check existing tasks first\n';
@@ -60015,7 +60023,7 @@ var initCommand = new Command("init").description("Initialize .knowns/ folder in
60015
60023
  console.log(source_default.cyan("Get started:"));
60016
60024
  console.log(source_default.gray(' knowns task create "My first task"'));
60017
60025
  if (hasSkillsPlatform) {
60018
- console.log(source_default.gray(" Use /knowns.init to start a session"));
60026
+ console.log(source_default.gray(" Use /kn:init to start a session"));
60019
60027
  }
60020
60028
  if (hasNonSkillsPlatform) {
60021
60029
  console.log(source_default.gray(" See AGENTS.md for full AI guidelines"));
@@ -60681,6 +60689,9 @@ async function formatTask(task, fileStore, plain = false) {
60681
60689
  if (task.subtasks.length > 0) {
60682
60690
  output2.push(`Subtasks: ${task.subtasks.join(", ")}`);
60683
60691
  }
60692
+ if (task.spec) {
60693
+ output2.push(`Spec: @doc/${task.spec}`);
60694
+ }
60684
60695
  if (task.timeSpent > 0) {
60685
60696
  const hours = Math.floor(task.timeSpent / 3600);
60686
60697
  const minutes = Math.floor(task.timeSpent % 3600 / 60);
@@ -60746,6 +60757,9 @@ async function formatTask(task, fileStore, plain = false) {
60746
60757
  if (task.parent) {
60747
60758
  output.push(`${source_default.gray("Parent:")} ${source_default.yellow(task.parent)}`);
60748
60759
  }
60760
+ if (task.spec) {
60761
+ output.push(`${source_default.gray("Spec:")} ${source_default.magenta(`@doc/${task.spec}`)}`);
60762
+ }
60749
60763
  output.push("");
60750
60764
  if (task.description) {
60751
60765
  output.push(source_default.bold("Description:"));
@@ -61004,7 +61018,7 @@ function formatTaskList(tasks, plain = false) {
61004
61018
  }
61005
61019
  return output.join("\n");
61006
61020
  }
61007
- var createCommand2 = new Command("create").description("Create a new task").argument("<title>", "Task title").option("-d, --description <text>", "Task description").option("--ac <criterion>", "Acceptance criterion (can be used multiple times)", collect, []).option("-l, --labels <labels>", "Comma-separated labels").option("-a, --assignee <name>", "Assignee (@username)").option("--priority <level>", "Priority: low, medium, high", "medium").option("-s, --status <status>", "Status", "todo").option("--parent <id>", "Parent task ID for subtasks").action(
61021
+ var createCommand2 = new Command("create").description("Create a new task").argument("<title>", "Task title").option("-d, --description <text>", "Task description").option("--ac <criterion>", "Acceptance criterion (can be used multiple times)", collect, []).option("-l, --labels <labels>", "Comma-separated labels").option("-a, --assignee <name>", "Assignee (@username)").option("--priority <level>", "Priority: low, medium, high", "medium").option("-s, --status <status>", "Status", "todo").option("--parent <id>", "Parent task ID for subtasks").option("--spec <path>", "Link to spec document (e.g., specs/user-auth)").action(
61008
61022
  async (title, options2) => {
61009
61023
  try {
61010
61024
  const fileStore = getFileStore();
@@ -61033,6 +61047,7 @@ var createCommand2 = new Command("create").description("Create a new task").argu
61033
61047
  assignee: options2.assignee,
61034
61048
  labels,
61035
61049
  parent: options2.parent,
61050
+ spec: options2.spec,
61036
61051
  acceptanceCriteria,
61037
61052
  subtasks: [],
61038
61053
  timeSpent: 0,
@@ -61052,7 +61067,7 @@ var createCommand2 = new Command("create").description("Create a new task").argu
61052
61067
  }
61053
61068
  }
61054
61069
  );
61055
- var listCommand = new Command("list").description("List all tasks").option("--status <status>", "Filter by status").option("--assignee <name>", "Filter by assignee").option("-l, --labels <labels>", "Filter by labels (comma-separated)").option("--priority <level>", "Filter by priority").option("--tree", "Display tasks as tree hierarchy").option("--plain", "Plain text output for AI").action(
61070
+ var listCommand = new Command("list").description("List all tasks").option("--status <status>", "Filter by status").option("--assignee <name>", "Filter by assignee").option("-l, --labels <labels>", "Filter by labels (comma-separated)").option("--priority <level>", "Filter by priority").option("--spec <path>", "Filter by spec document path").option("--tree", "Display tasks as tree hierarchy").option("--plain", "Plain text output for AI").action(
61056
61071
  async (options2) => {
61057
61072
  try {
61058
61073
  const fileStore = getFileStore();
@@ -61070,6 +61085,9 @@ var listCommand = new Command("list").description("List all tasks").option("--st
61070
61085
  const filterLabels = options2.labels.split(",").map((l) => l.trim());
61071
61086
  tasks = tasks.filter((t) => filterLabels.some((label) => t.labels.includes(label)));
61072
61087
  }
61088
+ if (options2.spec) {
61089
+ tasks = tasks.filter((t) => t.spec === options2.spec);
61090
+ }
61073
61091
  if (options2.tree) {
61074
61092
  console.log(await formatTaskTree(tasks, fileStore, options2.plain));
61075
61093
  } else {
@@ -61121,7 +61139,7 @@ var editCommand = new Command("edit").description("Edit task properties").argume
61121
61139
  "Remove acceptance criterion by index (1-based, can be used multiple times)",
61122
61140
  collectNumbers,
61123
61141
  []
61124
- ).option("--plan <text>", "Implementation plan").option("--notes <text>", "Implementation notes (replaces existing)").option("--append-notes <text>", "Append to implementation notes").action(
61142
+ ).option("--plan <text>", "Implementation plan").option("--notes <text>", "Implementation notes (replaces existing)").option("--append-notes <text>", "Append to implementation notes").option("--spec <path>", "Link to spec document (use 'none' to remove)").action(
61125
61143
  async (rawId, options2) => {
61126
61144
  try {
61127
61145
  const id = normalizeTaskId(rawId);
@@ -61179,6 +61197,13 @@ var editCommand = new Command("edit").description("Edit task properties").argume
61179
61197
  updates.parent = options2.parent;
61180
61198
  }
61181
61199
  }
61200
+ if (options2.spec !== void 0) {
61201
+ if (options2.spec.toLowerCase() === "none") {
61202
+ updates.spec = void 0;
61203
+ } else {
61204
+ updates.spec = options2.spec;
61205
+ }
61206
+ }
61182
61207
  if (options2.ac.length > 0 || options2.checkAc.length > 0 || options2.uncheckAc.length > 0 || options2.removeAc.length > 0) {
61183
61208
  const criteria = [...task.acceptanceCriteria];
61184
61209
  for (const text of options2.ac) {
@@ -61247,6 +61272,13 @@ var editCommand = new Command("edit").description("Edit task properties").argume
61247
61272
  if (options2.plan) changes.push("updated implementation plan");
61248
61273
  if (options2.notes) changes.push("updated implementation notes");
61249
61274
  if (options2.appendNotes) changes.push("appended to implementation notes");
61275
+ if (options2.spec !== void 0) {
61276
+ if (options2.spec.toLowerCase() === "none") {
61277
+ changes.push("spec removed");
61278
+ } else {
61279
+ changes.push(`spec \u2192 ${options2.spec}`);
61280
+ }
61281
+ }
61250
61282
  if (changes.length > 0) {
61251
61283
  console.log(source_default.gray(` ${changes.join(", ")}`));
61252
61284
  }
@@ -64674,17 +64706,17 @@ timeCommand.addCommand(addCommand);
64674
64706
  timeCommand.addCommand(reportCommand);
64675
64707
 
64676
64708
  // src/commands/browser.ts
64677
- import { existsSync as existsSync24 } from "node:fs";
64678
- import { mkdir as mkdir14, readFile as readFile17, writeFile as writeFile12 } from "node:fs/promises";
64679
- import { join as join28 } from "node:path";
64709
+ import { existsSync as existsSync26 } from "node:fs";
64710
+ import { mkdir as mkdir14, readFile as readFile19, writeFile as writeFile13 } from "node:fs/promises";
64711
+ import { join as join30 } from "node:path";
64680
64712
 
64681
64713
  // src/server/index.ts
64682
64714
  import { spawn } from "node:child_process";
64683
- import { existsSync as existsSync23, realpathSync as realpathSync2 } from "node:fs";
64684
- import { basename as basename3, dirname as dirname7, join as join27 } from "node:path";
64715
+ import { existsSync as existsSync25, realpathSync as realpathSync2 } from "node:fs";
64716
+ import { basename as basename3, dirname as dirname7, join as join29 } from "node:path";
64685
64717
  import { fileURLToPath as fileURLToPath4 } from "node:url";
64686
64718
  var import_cors = __toESM(require_lib5(), 1);
64687
- var import_express12 = __toESM(require_express2(), 1);
64719
+ var import_express13 = __toESM(require_express2(), 1);
64688
64720
 
64689
64721
  // src/server/middleware/error-handler.ts
64690
64722
  function errorHandler(err, _req, res, _next) {
@@ -64693,7 +64725,7 @@ function errorHandler(err, _req, res, _next) {
64693
64725
  }
64694
64726
 
64695
64727
  // src/server/routes/index.ts
64696
- var import_express10 = __toESM(require_express2(), 1);
64728
+ var import_express11 = __toESM(require_express2(), 1);
64697
64729
 
64698
64730
  // src/server/routes/activities.ts
64699
64731
  var import_express = __toESM(require_express2(), 1);
@@ -65232,8 +65264,8 @@ var NpmProvider = class extends ImportProvider {
65232
65264
  try {
65233
65265
  const packageJsonPath = join16(tempDir, "package.json");
65234
65266
  if (existsSync13(packageJsonPath)) {
65235
- const { readFile: readFile25 } = await import("node:fs/promises");
65236
- const content = await readFile25(packageJsonPath, "utf-8");
65267
+ const { readFile: readFile29 } = await import("node:fs/promises");
65268
+ const content = await readFile29(packageJsonPath, "utf-8");
65237
65269
  const pkg = JSON.parse(content);
65238
65270
  if (pkg.version) {
65239
65271
  metadata.version = pkg.version;
@@ -67333,6 +67365,53 @@ function createSearchRoutes(ctx) {
67333
67365
 
67334
67366
  // src/server/routes/tasks.ts
67335
67367
  var import_express7 = __toESM(require_express2(), 1);
67368
+ var import_gray_matter7 = __toESM(require_gray_matter(), 1);
67369
+ import { existsSync as existsSync19 } from "node:fs";
67370
+ import { readFile as readFile14, writeFile as writeFile9 } from "node:fs/promises";
67371
+ import { join as join23 } from "node:path";
67372
+ function normalizeText(text) {
67373
+ return text.toLowerCase().replace(/^(ac-?\d+:?\s*|#\d+\s*)/i, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
67374
+ }
67375
+ function textSimilarity(a, b) {
67376
+ const wordsA = new Set(normalizeText(a).split(" ").filter(Boolean));
67377
+ const wordsB = new Set(normalizeText(b).split(" ").filter(Boolean));
67378
+ if (wordsA.size === 0 || wordsB.size === 0) return 0;
67379
+ const intersection2 = [...wordsA].filter((w) => wordsB.has(w)).length;
67380
+ const union2 = (/* @__PURE__ */ new Set([...wordsA, ...wordsB])).size;
67381
+ return intersection2 / union2;
67382
+ }
67383
+ async function syncSpecACs(task, projectRoot, broadcast2) {
67384
+ if (!projectRoot) return;
67385
+ if (!task.spec) return;
67386
+ const completedACs = task.acceptanceCriteria.filter((ac) => ac.completed);
67387
+ if (completedACs.length === 0) return;
67388
+ const specPath = join23(projectRoot, ".knowns", "docs", `${task.spec}.md`);
67389
+ if (!existsSync19(specPath)) return;
67390
+ try {
67391
+ const content = await readFile14(specPath, "utf-8");
67392
+ const { data: frontmatter, content: docContent } = (0, import_gray_matter7.default)(content);
67393
+ let updatedContent = docContent;
67394
+ let hasChanges = false;
67395
+ const acPattern = /^([ \t]*)-\s*\[\s*\]\s*(.+)$/gm;
67396
+ updatedContent = docContent.replace(acPattern, (match2, indent, acText) => {
67397
+ for (const taskAC of completedACs) {
67398
+ const similarity = textSimilarity(taskAC.text, acText);
67399
+ if (similarity >= 0.5) {
67400
+ hasChanges = true;
67401
+ return `${indent}- [x] ${acText}`;
67402
+ }
67403
+ }
67404
+ return match2;
67405
+ });
67406
+ if (hasChanges) {
67407
+ const newFileContent = import_gray_matter7.default.stringify(updatedContent, frontmatter);
67408
+ await writeFile9(specPath, newFileContent, "utf-8");
67409
+ broadcast2({ type: "docs:updated", path: task.spec });
67410
+ }
67411
+ } catch (err) {
67412
+ console.error("Error syncing spec ACs:", err);
67413
+ }
67414
+ }
67336
67415
  function createTaskRoutes(ctx) {
67337
67416
  const router = (0, import_express7.Router)();
67338
67417
  const { store, broadcast: broadcast2 } = ctx;
@@ -67399,12 +67478,30 @@ function createTaskRoutes(ctx) {
67399
67478
  const updates = req.body;
67400
67479
  const task = await store.updateTask(req.params.id, updates);
67401
67480
  broadcast2({ type: "tasks:updated", task });
67481
+ if (updates.acceptanceCriteria || updates.status === "done") {
67482
+ await syncSpecACs(task, store.projectRoot, broadcast2);
67483
+ }
67402
67484
  res.json(task);
67403
67485
  } catch (error48) {
67404
67486
  console.error("Error updating task:", error48);
67405
67487
  res.status(500).json({ error: String(error48) });
67406
67488
  }
67407
67489
  });
67490
+ router.post("/sync-spec-acs", async (_req, res) => {
67491
+ try {
67492
+ const tasks = await store.getAllTasks();
67493
+ const doneTasks = tasks.filter((t) => t.status === "done" && t.spec);
67494
+ let syncedCount = 0;
67495
+ for (const task of doneTasks) {
67496
+ await syncSpecACs(task, store.projectRoot, broadcast2);
67497
+ syncedCount++;
67498
+ }
67499
+ res.json({ success: true, synced: syncedCount });
67500
+ } catch (error48) {
67501
+ console.error("Error syncing spec ACs:", error48);
67502
+ res.status(500).json({ error: String(error48) });
67503
+ }
67504
+ });
67408
67505
  router.post("/:id/archive", async (req, res) => {
67409
67506
  try {
67410
67507
  const task = await store.archiveTask(req.params.id);
@@ -67430,9 +67527,9 @@ function createTaskRoutes(ctx) {
67430
67527
 
67431
67528
  // src/server/routes/templates.ts
67432
67529
  var import_express8 = __toESM(require_express2(), 1);
67433
- import { existsSync as existsSync21 } from "node:fs";
67434
- import { mkdir as mkdir13, writeFile as writeFile10 } from "node:fs/promises";
67435
- import { join as join25 } from "node:path";
67530
+ import { existsSync as existsSync22 } from "node:fs";
67531
+ import { mkdir as mkdir13, writeFile as writeFile11 } from "node:fs/promises";
67532
+ import { join as join26 } from "node:path";
67436
67533
 
67437
67534
  // node_modules/zod/v4/classic/external.js
67438
67535
  var external_exports = {};
@@ -81280,9 +81377,9 @@ function safeValidateTemplateConfig(data) {
81280
81377
 
81281
81378
  // src/codegen/parser.ts
81282
81379
  var import_yaml = __toESM(require_dist3(), 1);
81283
- import { existsSync as existsSync19 } from "node:fs";
81284
- import { readFile as readFile14, readdir as readdir10 } from "node:fs/promises";
81285
- import { join as join23 } from "node:path";
81380
+ import { existsSync as existsSync20 } from "node:fs";
81381
+ import { readFile as readFile15, readdir as readdir10 } from "node:fs/promises";
81382
+ import { join as join24 } from "node:path";
81286
81383
  var CONFIG_FILENAME = "_template.yaml";
81287
81384
  var TEMPLATE_EXTENSION = ".hbs";
81288
81385
  var TemplateParseError = class extends Error {
@@ -81294,16 +81391,16 @@ var TemplateParseError = class extends Error {
81294
81391
  }
81295
81392
  };
81296
81393
  async function loadTemplate(templateDir) {
81297
- if (!existsSync19(templateDir)) {
81394
+ if (!existsSync20(templateDir)) {
81298
81395
  throw new TemplateParseError(`Template directory not found: ${templateDir}`);
81299
81396
  }
81300
- const configPath = join23(templateDir, CONFIG_FILENAME);
81301
- if (!existsSync19(configPath)) {
81397
+ const configPath = join24(templateDir, CONFIG_FILENAME);
81398
+ if (!existsSync20(configPath)) {
81302
81399
  throw new TemplateParseError(`Template config not found: ${CONFIG_FILENAME}`, void 0, [
81303
81400
  `Expected file at: ${configPath}`
81304
81401
  ]);
81305
81402
  }
81306
- const configContent = await readFile14(configPath, "utf-8");
81403
+ const configContent = await readFile15(configPath, "utf-8");
81307
81404
  let rawConfig;
81308
81405
  try {
81309
81406
  rawConfig = (0, import_yaml.parse)(configContent);
@@ -81329,9 +81426,9 @@ async function findTemplateFiles(dir, base = "") {
81329
81426
  const files = [];
81330
81427
  const entries = await readdir10(dir, { withFileTypes: true });
81331
81428
  for (const entry of entries) {
81332
- const relativePath = base ? join23(base, entry.name) : entry.name;
81429
+ const relativePath = base ? join24(base, entry.name) : entry.name;
81333
81430
  if (entry.isDirectory()) {
81334
- const subFiles = await findTemplateFiles(join23(dir, entry.name), relativePath);
81431
+ const subFiles = await findTemplateFiles(join24(dir, entry.name), relativePath);
81335
81432
  files.push(...subFiles);
81336
81433
  } else if (entry.name.endsWith(TEMPLATE_EXTENSION)) {
81337
81434
  files.push(relativePath);
@@ -81340,28 +81437,42 @@ async function findTemplateFiles(dir, base = "") {
81340
81437
  return files;
81341
81438
  }
81342
81439
  async function listTemplates(templatesDir) {
81343
- if (!existsSync19(templatesDir)) {
81440
+ if (!existsSync20(templatesDir)) {
81344
81441
  return [];
81345
81442
  }
81346
81443
  const entries = await readdir10(templatesDir, { withFileTypes: true });
81347
81444
  const templates = [];
81348
81445
  for (const entry of entries) {
81349
81446
  if (!entry.isDirectory()) continue;
81350
- const configPath = join23(templatesDir, entry.name, CONFIG_FILENAME);
81351
- if (!existsSync19(configPath)) continue;
81447
+ const configPath = join24(templatesDir, entry.name, CONFIG_FILENAME);
81448
+ if (!existsSync20(configPath)) continue;
81352
81449
  try {
81353
- const loaded = await loadTemplate(join23(templatesDir, entry.name));
81450
+ const loaded = await loadTemplate(join24(templatesDir, entry.name));
81354
81451
  templates.push(loaded.config);
81355
81452
  } catch {
81356
81453
  }
81357
81454
  }
81358
81455
  return templates;
81359
81456
  }
81457
+ async function getTemplateConfig(templateDir) {
81458
+ const configPath = join24(templateDir, CONFIG_FILENAME);
81459
+ if (!existsSync20(configPath)) {
81460
+ return null;
81461
+ }
81462
+ try {
81463
+ const content = await readFile15(configPath, "utf-8");
81464
+ const raw = (0, import_yaml.parse)(content);
81465
+ const result = safeValidateTemplateConfig(raw);
81466
+ return result.success ? result.data : null;
81467
+ } catch {
81468
+ return null;
81469
+ }
81470
+ }
81360
81471
 
81361
81472
  // src/codegen/runner.ts
81362
- import { existsSync as existsSync20 } from "node:fs";
81363
- import { appendFile, mkdir as mkdir12, readFile as readFile15, writeFile as writeFile9 } from "node:fs/promises";
81364
- import { dirname as dirname6, join as join24 } from "node:path";
81473
+ import { existsSync as existsSync21 } from "node:fs";
81474
+ import { appendFile, mkdir as mkdir12, readFile as readFile16, writeFile as writeFile10 } from "node:fs/promises";
81475
+ import { dirname as dirname6, join as join25 } from "node:path";
81365
81476
 
81366
81477
  // node_modules/@isaacs/balanced-match/dist/esm/index.js
81367
81478
  var balanced = (a, b, str2) => {
@@ -84343,7 +84454,7 @@ var LRUCache = class _LRUCache {
84343
84454
  // node_modules/path-scurry/dist/esm/index.js
84344
84455
  import { posix as posix2, win32 } from "node:path";
84345
84456
  import { fileURLToPath as fileURLToPath2 } from "node:url";
84346
- import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps } from "fs";
84457
+ import { lstatSync, readdir as readdirCB, readdirSync as readdirSync2, readlinkSync, realpathSync as rps } from "fs";
84347
84458
  import * as actualFS from "node:fs";
84348
84459
  import { lstat, readdir as readdir11, readlink as readlink2, realpath } from "node:fs/promises";
84349
84460
 
@@ -85230,7 +85341,7 @@ var realpathSync = rps.native;
85230
85341
  var defaultFS = {
85231
85342
  lstatSync,
85232
85343
  readdir: readdirCB,
85233
- readdirSync,
85344
+ readdirSync: readdirSync2,
85234
85345
  readlinkSync,
85235
85346
  realpathSync,
85236
85347
  promises: {
@@ -88134,10 +88245,10 @@ async function executeAction(action, template, context, options2, result) {
88134
88245
  }
88135
88246
  }
88136
88247
  async function executeAddAction(action, template, context, options2, result) {
88137
- const sourcePath = join24(template.templateDir, action.template);
88248
+ const sourcePath = join25(template.templateDir, action.template);
88138
88249
  const destRelative = renderPath(action.path, context);
88139
- const destPath = join24(options2.projectRoot, template.config.destination || "", destRelative);
88140
- if (existsSync20(destPath)) {
88250
+ const destPath = join25(options2.projectRoot, template.config.destination || "", destRelative);
88251
+ if (existsSync21(destPath)) {
88141
88252
  if (action.skipIfExists && !options2.force) {
88142
88253
  result.skipped.push(destRelative);
88143
88254
  return;
@@ -88150,19 +88261,19 @@ async function executeAddAction(action, template, context, options2, result) {
88150
88261
  const content = await renderFile(sourcePath, context);
88151
88262
  if (!options2.dryRun) {
88152
88263
  await ensureDir(dirname6(destPath));
88153
- await writeFile9(destPath, content, "utf-8");
88264
+ await writeFile10(destPath, content, "utf-8");
88154
88265
  }
88155
88266
  result.created.push(destRelative);
88156
88267
  }
88157
88268
  async function executeAddManyAction(action, template, context, options2, result) {
88158
- const sourceDir = join24(template.templateDir, action.source);
88269
+ const sourceDir = join25(template.templateDir, action.source);
88159
88270
  const pattern = action.globPattern || "**/*.hbs";
88160
88271
  const files = await glob2(pattern, { cwd: sourceDir, nodir: true });
88161
88272
  for (const file3 of files) {
88162
- const sourcePath = join24(sourceDir, file3);
88163
- const destRelative = renderPath(join24(action.destination, file3), context);
88164
- const destPath = join24(options2.projectRoot, template.config.destination || "", destRelative);
88165
- if (existsSync20(destPath)) {
88273
+ const sourcePath = join25(sourceDir, file3);
88274
+ const destRelative = renderPath(join25(action.destination, file3), context);
88275
+ const destPath = join25(options2.projectRoot, template.config.destination || "", destRelative);
88276
+ if (existsSync21(destPath)) {
88166
88277
  if (action.skipIfExists && !options2.force) {
88167
88278
  result.skipped.push(destRelative);
88168
88279
  continue;
@@ -88175,17 +88286,17 @@ async function executeAddManyAction(action, template, context, options2, result)
88175
88286
  const content = await renderFile(sourcePath, context);
88176
88287
  if (!options2.dryRun) {
88177
88288
  await ensureDir(dirname6(destPath));
88178
- await writeFile9(destPath, content, "utf-8");
88289
+ await writeFile10(destPath, content, "utf-8");
88179
88290
  }
88180
88291
  result.created.push(destRelative);
88181
88292
  }
88182
88293
  }
88183
88294
  async function executeModifyAction(action, context, options2, result) {
88184
- const filePath = join24(options2.projectRoot, renderPath(action.path, context));
88185
- if (!existsSync20(filePath)) {
88295
+ const filePath = join25(options2.projectRoot, renderPath(action.path, context));
88296
+ if (!existsSync21(filePath)) {
88186
88297
  throw new Error(`Cannot modify: file not found: ${action.path}`);
88187
88298
  }
88188
- const content = await readFile15(filePath, "utf-8");
88299
+ const content = await readFile16(filePath, "utf-8");
88189
88300
  const replacement = renderString(action.template, context);
88190
88301
  const pattern = new RegExp(action.pattern, "g");
88191
88302
  const newContent = content.replace(pattern, replacement);
@@ -88194,15 +88305,15 @@ async function executeModifyAction(action, context, options2, result) {
88194
88305
  return;
88195
88306
  }
88196
88307
  if (!options2.dryRun) {
88197
- await writeFile9(filePath, newContent, "utf-8");
88308
+ await writeFile10(filePath, newContent, "utf-8");
88198
88309
  }
88199
88310
  result.modified.push(action.path);
88200
88311
  }
88201
88312
  async function executeAppendAction(action, context, options2, result) {
88202
- const filePath = join24(options2.projectRoot, renderPath(action.path, context));
88313
+ const filePath = join25(options2.projectRoot, renderPath(action.path, context));
88203
88314
  const contentToAppend = renderString(action.template, context);
88204
- if (action.unique && existsSync20(filePath)) {
88205
- const existingContent = await readFile15(filePath, "utf-8");
88315
+ if (action.unique && existsSync21(filePath)) {
88316
+ const existingContent = await readFile16(filePath, "utf-8");
88206
88317
  if (existingContent.includes(contentToAppend.trim())) {
88207
88318
  result.skipped.push(action.path);
88208
88319
  return;
@@ -88212,20 +88323,20 @@ async function executeAppendAction(action, context, options2, result) {
88212
88323
  const fullContent = separator + contentToAppend;
88213
88324
  if (!options2.dryRun) {
88214
88325
  await ensureDir(dirname6(filePath));
88215
- if (existsSync20(filePath)) {
88326
+ if (existsSync21(filePath)) {
88216
88327
  await appendFile(filePath, fullContent, "utf-8");
88217
88328
  } else {
88218
- await writeFile9(filePath, contentToAppend, "utf-8");
88329
+ await writeFile10(filePath, contentToAppend, "utf-8");
88219
88330
  }
88220
88331
  }
88221
- if (existsSync20(filePath)) {
88332
+ if (existsSync21(filePath)) {
88222
88333
  result.modified.push(action.path);
88223
88334
  } else {
88224
88335
  result.created.push(action.path);
88225
88336
  }
88226
88337
  }
88227
88338
  async function ensureDir(dir) {
88228
- if (!existsSync20(dir)) {
88339
+ if (!existsSync21(dir)) {
88229
88340
  await mkdir12(dir, { recursive: true });
88230
88341
  }
88231
88342
  }
@@ -88258,7 +88369,7 @@ function countFileActions(actions) {
88258
88369
  function createTemplateRoutes(ctx) {
88259
88370
  const router = (0, import_express8.Router)();
88260
88371
  const { store, broadcast: broadcast2 } = ctx;
88261
- const templatesDir = join25(store.projectRoot, ".knowns", "templates");
88372
+ const templatesDir = join26(store.projectRoot, ".knowns", "templates");
88262
88373
  router.get("/", async (_req, res) => {
88263
88374
  try {
88264
88375
  const allTemplates = await listAllTemplates(store.projectRoot);
@@ -88379,8 +88490,8 @@ function createTemplateRoutes(ctx) {
88379
88490
  context[prompt.name] = prompt.initial;
88380
88491
  }
88381
88492
  }
88382
- const templatePath = join25(template.templateDir, templateFile);
88383
- if (!existsSync21(templatePath)) {
88493
+ const templatePath = join26(template.templateDir, templateFile);
88494
+ if (!existsSync22(templatePath)) {
88384
88495
  res.status(404).json({ error: `Template file not found: ${templateFile}` });
88385
88496
  return;
88386
88497
  }
@@ -88407,11 +88518,11 @@ function createTemplateRoutes(ctx) {
88407
88518
  res.status(400).json({ error: "Template name is required" });
88408
88519
  return;
88409
88520
  }
88410
- if (!existsSync21(templatesDir)) {
88521
+ if (!existsSync22(templatesDir)) {
88411
88522
  await mkdir13(templatesDir, { recursive: true });
88412
88523
  }
88413
- const templateDir = join25(templatesDir, name);
88414
- if (existsSync21(templateDir)) {
88524
+ const templateDir = join26(templatesDir, name);
88525
+ if (existsSync22(templateDir)) {
88415
88526
  res.status(409).json({ error: `Template "${name}" already exists` });
88416
88527
  return;
88417
88528
  }
@@ -88441,7 +88552,7 @@ messages:
88441
88552
  success: |
88442
88553
  \u2713 Created {{name}}!
88443
88554
  `;
88444
- await writeFile10(join25(templateDir, "_template.yaml"), configContent, "utf-8");
88555
+ await writeFile11(join26(templateDir, "_template.yaml"), configContent, "utf-8");
88445
88556
  const exampleTemplate = `/**
88446
88557
  * {{pascalCase name}}
88447
88558
  * Generated from ${name} template
@@ -88451,7 +88562,7 @@ export function {{camelCase name}}() {
88451
88562
  console.log("Hello from {{name}}!");
88452
88563
  }
88453
88564
  `;
88454
- await writeFile10(join25(templateDir, "example.ts.hbs"), exampleTemplate, "utf-8");
88565
+ await writeFile11(join26(templateDir, "example.ts.hbs"), exampleTemplate, "utf-8");
88455
88566
  broadcast2({ type: "templates:created", template: name });
88456
88567
  res.status(201).json({
88457
88568
  success: true,
@@ -88512,18 +88623,18 @@ export function {{camelCase name}}() {
88512
88623
 
88513
88624
  // src/server/routes/time.ts
88514
88625
  var import_express9 = __toESM(require_express2(), 1);
88515
- import { existsSync as existsSync22 } from "node:fs";
88516
- import { readFile as readFile16, writeFile as writeFile11 } from "node:fs/promises";
88517
- import { join as join26 } from "node:path";
88626
+ import { existsSync as existsSync23 } from "node:fs";
88627
+ import { readFile as readFile17, writeFile as writeFile12 } from "node:fs/promises";
88628
+ import { join as join27 } from "node:path";
88518
88629
  function createTimeRoutes(ctx) {
88519
88630
  const router = (0, import_express9.Router)();
88520
88631
  const { store, broadcast: broadcast2 } = ctx;
88521
- const timePath = join26(store.projectRoot, ".knowns", "time.json");
88632
+ const timePath = join27(store.projectRoot, ".knowns", "time.json");
88522
88633
  async function loadTimeData3() {
88523
- if (!existsSync22(timePath)) {
88634
+ if (!existsSync23(timePath)) {
88524
88635
  return { active: [] };
88525
88636
  }
88526
- const content = await readFile16(timePath, "utf-8");
88637
+ const content = await readFile17(timePath, "utf-8");
88527
88638
  const data = JSON.parse(content);
88528
88639
  if (data.active && !Array.isArray(data.active)) {
88529
88640
  return { active: [data.active] };
@@ -88534,7 +88645,7 @@ function createTimeRoutes(ctx) {
88534
88645
  return data;
88535
88646
  }
88536
88647
  async function saveTimeData3(data) {
88537
- await writeFile11(timePath, JSON.stringify(data, null, 2), "utf-8");
88648
+ await writeFile12(timePath, JSON.stringify(data, null, 2), "utf-8");
88538
88649
  }
88539
88650
  async function enrichTimers(timers) {
88540
88651
  return Promise.all(
@@ -88766,9 +88877,126 @@ function createTimeRoutes(ctx) {
88766
88877
  return router;
88767
88878
  }
88768
88879
 
88880
+ // src/server/routes/validate.ts
88881
+ var import_express10 = __toESM(require_express2(), 1);
88882
+ var import_gray_matter8 = __toESM(require_gray_matter(), 1);
88883
+ import { existsSync as existsSync24 } from "node:fs";
88884
+ import { readFile as readFile18, readdir as readdir12 } from "node:fs/promises";
88885
+ import { join as join28 } from "node:path";
88886
+ function createValidateRoutes(ctx) {
88887
+ const router = (0, import_express10.Router)();
88888
+ const { store } = ctx;
88889
+ router.get("/sdd", async (_req, res) => {
88890
+ try {
88891
+ const tasks = await store.getAllTasks();
88892
+ const docsDir = join28(store.projectRoot, ".knowns", "docs");
88893
+ const stats = {
88894
+ specs: { total: 0, approved: 0, draft: 0, implemented: 0 },
88895
+ tasks: { total: tasks.length, done: 0, inProgress: 0, todo: 0, withSpec: 0, withoutSpec: 0 },
88896
+ coverage: { linked: 0, total: tasks.length, percent: 0 },
88897
+ acCompletion: {}
88898
+ };
88899
+ const warnings = [];
88900
+ const passed = [];
88901
+ for (const task of tasks) {
88902
+ if (task.status === "done") stats.tasks.done++;
88903
+ else if (task.status === "in-progress") stats.tasks.inProgress++;
88904
+ else stats.tasks.todo++;
88905
+ if (task.spec) {
88906
+ stats.tasks.withSpec++;
88907
+ } else {
88908
+ stats.tasks.withoutSpec++;
88909
+ if (task.status !== "done") {
88910
+ warnings.push({
88911
+ type: "task-no-spec",
88912
+ entity: `task-${task.id}`,
88913
+ message: `${task.title} has no spec reference`
88914
+ });
88915
+ }
88916
+ }
88917
+ }
88918
+ stats.coverage.linked = stats.tasks.withSpec;
88919
+ stats.coverage.percent = stats.tasks.total > 0 ? Math.round(stats.tasks.withSpec / stats.tasks.total * 100) : 0;
88920
+ const specsDir = join28(docsDir, "specs");
88921
+ if (existsSync24(specsDir)) {
88922
+ async function scanSpecs(dir, relativePath) {
88923
+ const entries = await readdir12(dir, { withFileTypes: true });
88924
+ for (const entry of entries) {
88925
+ if (entry.name.startsWith(".")) continue;
88926
+ const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
88927
+ if (entry.isDirectory()) {
88928
+ await scanSpecs(join28(dir, entry.name), entryRelPath);
88929
+ } else if (entry.name.endsWith(".md")) {
88930
+ stats.specs.total++;
88931
+ const specPath = `specs/${entryRelPath.replace(/\.md$/, "")}`;
88932
+ try {
88933
+ const content = await readFile18(join28(dir, entry.name), "utf-8");
88934
+ const { data } = (0, import_gray_matter8.default)(content);
88935
+ if (data.status === "implemented") {
88936
+ stats.specs.implemented++;
88937
+ } else if (data.status === "approved") {
88938
+ stats.specs.approved++;
88939
+ } else {
88940
+ stats.specs.draft++;
88941
+ }
88942
+ const linkedTasks = tasks.filter((t) => t.spec === specPath);
88943
+ if (linkedTasks.length > 0) {
88944
+ let totalAC = 0;
88945
+ let completedAC = 0;
88946
+ for (const task of linkedTasks) {
88947
+ totalAC += task.acceptanceCriteria.length;
88948
+ completedAC += task.acceptanceCriteria.filter((ac) => ac.completed).length;
88949
+ }
88950
+ const percent = totalAC > 0 ? Math.round(completedAC / totalAC * 100) : 100;
88951
+ stats.acCompletion[specPath] = { total: totalAC, completed: completedAC, percent };
88952
+ if (percent < 100 && totalAC > 0) {
88953
+ warnings.push({
88954
+ type: "spec-ac-incomplete",
88955
+ entity: specPath,
88956
+ message: `${completedAC}/${totalAC} ACs complete (${percent}%)`
88957
+ });
88958
+ }
88959
+ }
88960
+ } catch {
88961
+ }
88962
+ }
88963
+ }
88964
+ }
88965
+ await scanSpecs(specsDir, "");
88966
+ }
88967
+ for (const task of tasks) {
88968
+ if (task.spec) {
88969
+ const specDocPath = join28(docsDir, `${task.spec}.md`);
88970
+ if (!existsSync24(specDocPath)) {
88971
+ warnings.push({
88972
+ type: "spec-broken-link",
88973
+ entity: `task-${task.id}`,
88974
+ message: `Broken spec reference: @doc/${task.spec}`
88975
+ });
88976
+ }
88977
+ }
88978
+ }
88979
+ if (warnings.filter((w) => w.type === "spec-broken-link").length === 0) {
88980
+ passed.push("All spec references resolve");
88981
+ }
88982
+ for (const [specPath, completion] of Object.entries(stats.acCompletion)) {
88983
+ if (completion.percent === 100) {
88984
+ passed.push(`${specPath}: fully implemented`);
88985
+ }
88986
+ }
88987
+ const result = { stats, warnings, passed };
88988
+ res.json(result);
88989
+ } catch (error48) {
88990
+ console.error("Error getting SDD stats:", error48);
88991
+ res.status(500).json({ error: String(error48) });
88992
+ }
88993
+ });
88994
+ return router;
88995
+ }
88996
+
88769
88997
  // src/server/routes/index.ts
88770
88998
  function createRoutes(ctx) {
88771
- const router = (0, import_express10.Router)();
88999
+ const router = (0, import_express11.Router)();
88772
89000
  router.use("/tasks", createTaskRoutes(ctx));
88773
89001
  router.use("/docs", createDocRoutes(ctx));
88774
89002
  router.use("/templates", createTemplateRoutes(ctx));
@@ -88778,11 +89006,12 @@ function createRoutes(ctx) {
88778
89006
  router.use("/notify", createNotifyRoutes(ctx));
88779
89007
  router.use("/time", createTimeRoutes(ctx));
88780
89008
  router.use("/imports", createImportRoutes(ctx));
89009
+ router.use("/validate", createValidateRoutes(ctx));
88781
89010
  return router;
88782
89011
  }
88783
89012
 
88784
89013
  // src/server/routes/events.ts
88785
- var import_express11 = __toESM(require_express2(), 1);
89014
+ var import_express12 = __toESM(require_express2(), 1);
88786
89015
  var clients = /* @__PURE__ */ new Set();
88787
89016
  function broadcast(event, data) {
88788
89017
  const message = `event: ${event}
@@ -88798,7 +89027,7 @@ data: ${JSON.stringify(data)}
88798
89027
  }
88799
89028
  }
88800
89029
  function createEventsRoute() {
88801
- const router = (0, import_express11.Router)();
89030
+ const router = (0, import_express12.Router)();
88802
89031
  router.get("/", (req, res) => {
88803
89032
  res.setHeader("Content-Type", "text/event-stream");
88804
89033
  res.setHeader("Cache-Control", "no-cache");
@@ -88846,26 +89075,26 @@ async function startServer(options2) {
88846
89075
  const normalizedDir = currentDir.replace(/[/\\]+$/, "");
88847
89076
  const dirName = basename3(normalizedDir);
88848
89077
  if (dirName === "dist") {
88849
- packageRoot = join27(currentDir, "..");
89078
+ packageRoot = join29(currentDir, "..");
88850
89079
  } else if (normalizedDir.includes("/src/server") || normalizedDir.includes("\\src\\server")) {
88851
- packageRoot = join27(currentDir, "..", "..");
89080
+ packageRoot = join29(currentDir, "..", "..");
88852
89081
  }
88853
- const uiDistPath = join27(packageRoot, "dist", "ui");
88854
- if (!existsSync23(join27(uiDistPath, "index.html"))) {
89082
+ const uiDistPath = join29(packageRoot, "dist", "ui");
89083
+ if (!existsSync25(join29(uiDistPath, "index.html"))) {
88855
89084
  throw new Error(`UI build not found at ${uiDistPath}. Run 'bun run build:ui' first.`);
88856
89085
  }
88857
- const app = (0, import_express12.default)();
89086
+ const app = (0, import_express13.default)();
88858
89087
  app.use((0, import_cors.default)());
88859
- app.use(import_express12.default.json());
89088
+ app.use(import_express13.default.json());
88860
89089
  app.use(
88861
89090
  "/assets",
88862
- import_express12.default.static(join27(uiDistPath, "assets"), {
89091
+ import_express13.default.static(join29(uiDistPath, "assets"), {
88863
89092
  maxAge: "1y",
88864
89093
  immutable: true
88865
89094
  })
88866
89095
  );
88867
89096
  app.use(
88868
- import_express12.default.static(uiDistPath, {
89097
+ import_express13.default.static(uiDistPath, {
88869
89098
  maxAge: "1d",
88870
89099
  index: false
88871
89100
  // Don't serve index.html automatically
@@ -88875,8 +89104,8 @@ async function startServer(options2) {
88875
89104
  app.use("/api", createRoutes({ store, broadcast: broadcastEvent }));
88876
89105
  app.use(errorHandler);
88877
89106
  app.get("/{*path}", (_req, res) => {
88878
- const indexPath = join27(uiDistPath, "index.html");
88879
- if (existsSync23(indexPath)) {
89107
+ const indexPath = join29(uiDistPath, "index.html");
89108
+ if (existsSync25(indexPath)) {
88880
89109
  res.sendFile("index.html", { root: uiDistPath });
88881
89110
  } else {
88882
89111
  res.status(404).send("Not Found");
@@ -88924,21 +89153,21 @@ async function startServer(options2) {
88924
89153
  var DEFAULT_PORT2 = 6420;
88925
89154
  var CONFIG_FILE2 = ".knowns/config.json";
88926
89155
  async function saveServerPort(projectRoot, port) {
88927
- const configPath = join28(projectRoot, CONFIG_FILE2);
88928
- const knownsDir = join28(projectRoot, ".knowns");
88929
- if (!existsSync24(knownsDir)) {
89156
+ const configPath = join30(projectRoot, CONFIG_FILE2);
89157
+ const knownsDir = join30(projectRoot, ".knowns");
89158
+ if (!existsSync26(knownsDir)) {
88930
89159
  await mkdir14(knownsDir, { recursive: true });
88931
89160
  }
88932
89161
  try {
88933
89162
  let config2 = {};
88934
- if (existsSync24(configPath)) {
88935
- const content = await readFile17(configPath, "utf-8");
89163
+ if (existsSync26(configPath)) {
89164
+ const content = await readFile19(configPath, "utf-8");
88936
89165
  config2 = JSON.parse(content);
88937
89166
  }
88938
89167
  const settings = config2.settings || {};
88939
89168
  settings.serverPort = port;
88940
89169
  config2.settings = settings;
88941
- await writeFile12(configPath, JSON.stringify(config2, null, 2), "utf-8");
89170
+ await writeFile13(configPath, JSON.stringify(config2, null, 2), "utf-8");
88942
89171
  } catch {
88943
89172
  }
88944
89173
  }
@@ -88968,9 +89197,9 @@ var browserCommand = new Command("browser").description("Open web UI for task ma
88968
89197
  });
88969
89198
 
88970
89199
  // src/commands/doc.ts
88971
- import { existsSync as existsSync25 } from "node:fs";
88972
- import { mkdir as mkdir15, readFile as readFile18, readdir as readdir12, writeFile as writeFile13 } from "node:fs/promises";
88973
- import { join as join29 } from "node:path";
89200
+ import { existsSync as existsSync27 } from "node:fs";
89201
+ import { mkdir as mkdir15, readFile as readFile20, readdir as readdir13, writeFile as writeFile14 } from "node:fs/promises";
89202
+ import { join as join31 } from "node:path";
88974
89203
 
88975
89204
  // src/utils/markdown-toc.ts
88976
89205
  function extractToc(markdown) {
@@ -89159,14 +89388,14 @@ function matchesPartial(title, search) {
89159
89388
  }
89160
89389
 
89161
89390
  // src/commands/doc.ts
89162
- var import_gray_matter7 = __toESM(require_gray_matter(), 1);
89163
- var DOCS_DIR3 = join29(process.cwd(), ".knowns", "docs");
89391
+ var import_gray_matter9 = __toESM(require_gray_matter(), 1);
89392
+ var DOCS_DIR3 = join31(process.cwd(), ".knowns", "docs");
89164
89393
  async function getAllMdFiles(dir, basePath = "") {
89165
89394
  const files = [];
89166
- const entries = await readdir12(dir, { withFileTypes: true });
89395
+ const entries = await readdir13(dir, { withFileTypes: true });
89167
89396
  for (const entry of entries) {
89168
- const fullPath = join29(dir, entry.name);
89169
- const relativePath = normalizePath2(basePath ? join29(basePath, entry.name) : entry.name);
89397
+ const fullPath = join31(dir, entry.name);
89398
+ const relativePath = normalizePath2(basePath ? join31(basePath, entry.name) : entry.name);
89170
89399
  if (entry.isDirectory()) {
89171
89400
  const subFiles = await getAllMdFiles(fullPath, relativePath);
89172
89401
  files.push(...subFiles);
@@ -89177,7 +89406,7 @@ async function getAllMdFiles(dir, basePath = "") {
89177
89406
  return files;
89178
89407
  }
89179
89408
  async function ensureDocsDir() {
89180
- if (!existsSync25(DOCS_DIR3)) {
89409
+ if (!existsSync27(DOCS_DIR3)) {
89181
89410
  await mkdir15(DOCS_DIR3, { recursive: true });
89182
89411
  }
89183
89412
  }
@@ -89197,12 +89426,12 @@ async function resolveDocPath(name, context) {
89197
89426
  };
89198
89427
  }
89199
89428
  let filename = name.endsWith(".md") ? name : `${name}.md`;
89200
- let filepath = join29(DOCS_DIR3, filename);
89201
- if (!existsSync25(filepath)) {
89429
+ let filepath = join31(DOCS_DIR3, filename);
89430
+ if (!existsSync27(filepath)) {
89202
89431
  filename = `${titleToFilename(name)}.md`;
89203
- filepath = join29(DOCS_DIR3, filename);
89432
+ filepath = join31(DOCS_DIR3, filename);
89204
89433
  }
89205
- if (!existsSync25(filepath)) {
89434
+ if (!existsSync27(filepath)) {
89206
89435
  const allFiles = await getAllMdFiles(DOCS_DIR3);
89207
89436
  const searchName = name.toLowerCase().replace(/\.md$/, "");
89208
89437
  const matchingFile = allFiles.find((file3) => {
@@ -89212,10 +89441,10 @@ async function resolveDocPath(name, context) {
89212
89441
  });
89213
89442
  if (matchingFile) {
89214
89443
  filename = matchingFile;
89215
- filepath = join29(DOCS_DIR3, matchingFile);
89444
+ filepath = join31(DOCS_DIR3, matchingFile);
89216
89445
  }
89217
89446
  }
89218
- if (!existsSync25(filepath)) {
89447
+ if (!existsSync27(filepath)) {
89219
89448
  return null;
89220
89449
  }
89221
89450
  return { filepath, filename };
@@ -89229,14 +89458,14 @@ var createCommand3 = new Command("create").description("Create a new documentati
89229
89458
  let relativePath = filename;
89230
89459
  if (options2.folder) {
89231
89460
  const folderPath = options2.folder.replace(/^\/|\/$/g, "");
89232
- targetDir = join29(DOCS_DIR3, folderPath);
89233
- relativePath = join29(folderPath, filename);
89234
- if (!existsSync25(targetDir)) {
89461
+ targetDir = join31(DOCS_DIR3, folderPath);
89462
+ relativePath = join31(folderPath, filename);
89463
+ if (!existsSync27(targetDir)) {
89235
89464
  await mkdir15(targetDir, { recursive: true });
89236
89465
  }
89237
89466
  }
89238
- const filepath = join29(targetDir, filename);
89239
- if (existsSync25(filepath)) {
89467
+ const filepath = join31(targetDir, filename);
89468
+ if (existsSync27(filepath)) {
89240
89469
  console.error(source_default.red(`\u2717 Document already exists: ${relativePath}`));
89241
89470
  process.exit(1);
89242
89471
  }
@@ -89252,8 +89481,8 @@ var createCommand3 = new Command("create").description("Create a new documentati
89252
89481
  if (options2.tags) {
89253
89482
  metadata.tags = options2.tags.split(",").map((t) => t.trim());
89254
89483
  }
89255
- const content = import_gray_matter7.default.stringify("# Content\n\nWrite your documentation here.\n", metadata);
89256
- await writeFile13(filepath, content, "utf-8");
89484
+ const content = import_gray_matter9.default.stringify("# Content\n\nWrite your documentation here.\n", metadata);
89485
+ await writeFile14(filepath, content, "utf-8");
89257
89486
  await notifyDocUpdate(relativePath);
89258
89487
  if (options2.plain) {
89259
89488
  console.log(`Created: ${relativePath}`);
@@ -89288,8 +89517,8 @@ var listCommand2 = new Command("list").description("List all documentation files
89288
89517
  const docs = [];
89289
89518
  for (const doc of allDocs) {
89290
89519
  try {
89291
- const fileContent = await readFile18(doc.fullPath, "utf-8");
89292
- const { data, content } = (0, import_gray_matter7.default)(fileContent);
89520
+ const fileContent = await readFile20(doc.fullPath, "utf-8");
89521
+ const { data, content } = (0, import_gray_matter9.default)(fileContent);
89293
89522
  const stats = calculateDocStats(content);
89294
89523
  docs.push({
89295
89524
  ref: doc.ref,
@@ -89463,8 +89692,8 @@ var viewCommand2 = new Command("view").description("View a documentation file").
89463
89692
  process.exit(1);
89464
89693
  }
89465
89694
  const { filepath, filename, isImported, source } = resolved;
89466
- const fileContent = await readFile18(filepath, "utf-8");
89467
- const { data, content } = (0, import_gray_matter7.default)(fileContent);
89695
+ const fileContent = await readFile20(filepath, "utf-8");
89696
+ const { data, content } = (0, import_gray_matter9.default)(fileContent);
89468
89697
  const metadata = data;
89469
89698
  if (options2.smart) {
89470
89699
  const stats = calculateDocStats(content);
@@ -89639,7 +89868,7 @@ var viewCommand2 = new Command("view").description("View a documentation file").
89639
89868
  }
89640
89869
  console.log(enhancedContent);
89641
89870
  const projectRoot = findProjectRoot() || process.cwd();
89642
- const tasksDir = join29(projectRoot, ".knowns", "tasks");
89871
+ const tasksDir = join31(projectRoot, ".knowns", "tasks");
89643
89872
  const refs = await validateRefs(projectRoot, enhancedContent, tasksDir);
89644
89873
  const brokenRefs = refs.filter((r) => !r.exists);
89645
89874
  if (brokenRefs.length > 0) {
@@ -89669,7 +89898,7 @@ var viewCommand2 = new Command("view").description("View a documentation file").
89669
89898
  }
89670
89899
  console.log(displayContent);
89671
89900
  const projectRoot = findProjectRoot() || process.cwd();
89672
- const tasksDir = join29(projectRoot, ".knowns", "tasks");
89901
+ const tasksDir = join31(projectRoot, ".knowns", "tasks");
89673
89902
  const refs = await validateRefs(projectRoot, displayContent, tasksDir);
89674
89903
  const brokenRefs = refs.filter((r) => !r.exists);
89675
89904
  if (brokenRefs.length > 0) {
@@ -89694,13 +89923,13 @@ var editCommand2 = new Command("edit").description("Edit a documentation file (m
89694
89923
  try {
89695
89924
  await ensureDocsDir();
89696
89925
  let filename = name.endsWith(".md") ? name : `${name}.md`;
89697
- let filepath = join29(DOCS_DIR3, filename);
89698
- if (!existsSync25(filepath)) {
89926
+ let filepath = join31(DOCS_DIR3, filename);
89927
+ if (!existsSync27(filepath)) {
89699
89928
  const baseName = name.includes("/") ? name : titleToFilename(name);
89700
89929
  filename = baseName.endsWith(".md") ? baseName : `${baseName}.md`;
89701
- filepath = join29(DOCS_DIR3, filename);
89930
+ filepath = join31(DOCS_DIR3, filename);
89702
89931
  }
89703
- if (!existsSync25(filepath)) {
89932
+ if (!existsSync27(filepath)) {
89704
89933
  const allFiles = await getAllMdFiles(DOCS_DIR3);
89705
89934
  const searchName = name.toLowerCase().replace(/\.md$/, "");
89706
89935
  const matchingFile = allFiles.find((file3) => {
@@ -89710,15 +89939,15 @@ var editCommand2 = new Command("edit").description("Edit a documentation file (m
89710
89939
  });
89711
89940
  if (matchingFile) {
89712
89941
  filename = matchingFile;
89713
- filepath = join29(DOCS_DIR3, matchingFile);
89942
+ filepath = join31(DOCS_DIR3, matchingFile);
89714
89943
  }
89715
89944
  }
89716
- if (!existsSync25(filepath)) {
89945
+ if (!existsSync27(filepath)) {
89717
89946
  console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
89718
89947
  process.exit(1);
89719
89948
  }
89720
- const fileContent = await readFile18(filepath, "utf-8");
89721
- const { data, content } = (0, import_gray_matter7.default)(fileContent);
89949
+ const fileContent = await readFile20(filepath, "utf-8");
89950
+ const { data, content } = (0, import_gray_matter9.default)(fileContent);
89722
89951
  const metadata = data;
89723
89952
  if (options2.title) metadata.title = options2.title;
89724
89953
  if (options2.description) metadata.description = normalizeRefs(options2.description);
@@ -89727,19 +89956,19 @@ var editCommand2 = new Command("edit").description("Edit a documentation file (m
89727
89956
  let updatedContent = content;
89728
89957
  let sourceFile;
89729
89958
  if (options2.contentFile) {
89730
- if (!existsSync25(options2.contentFile)) {
89959
+ if (!existsSync27(options2.contentFile)) {
89731
89960
  console.error(source_default.red(`\u2717 File not found: ${options2.contentFile}`));
89732
89961
  process.exit(1);
89733
89962
  }
89734
- const fileData = await readFile18(options2.contentFile, "utf-8");
89963
+ const fileData = await readFile20(options2.contentFile, "utf-8");
89735
89964
  updatedContent = normalizeRefs(fileData);
89736
89965
  sourceFile = options2.contentFile;
89737
89966
  } else if (options2.appendFile) {
89738
- if (!existsSync25(options2.appendFile)) {
89967
+ if (!existsSync27(options2.appendFile)) {
89739
89968
  console.error(source_default.red(`\u2717 File not found: ${options2.appendFile}`));
89740
89969
  process.exit(1);
89741
89970
  }
89742
- const fileData = await readFile18(options2.appendFile, "utf-8");
89971
+ const fileData = await readFile20(options2.appendFile, "utf-8");
89743
89972
  updatedContent = `${content.trimEnd()}
89744
89973
 
89745
89974
  ${normalizeRefs(fileData)}`;
@@ -89764,8 +89993,8 @@ ${normalizeRefs(fileData)}`;
89764
89993
 
89765
89994
  ${normalizeRefs(options2.append)}`;
89766
89995
  }
89767
- const newFileContent = import_gray_matter7.default.stringify(updatedContent, metadata);
89768
- await writeFile13(filepath, newFileContent, "utf-8");
89996
+ const newFileContent = import_gray_matter9.default.stringify(updatedContent, metadata);
89997
+ await writeFile14(filepath, newFileContent, "utf-8");
89769
89998
  await notifyDocUpdate(filename);
89770
89999
  if (options2.plain) {
89771
90000
  console.log(`Updated: ${filename}`);
@@ -89800,7 +90029,7 @@ var validateCommand2 = new Command("validate").description("Validate a documenta
89800
90029
  console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
89801
90030
  process.exit(1);
89802
90031
  }
89803
- const content = await readFile18(resolved.filepath, "utf-8");
90032
+ const content = await readFile20(resolved.filepath, "utf-8");
89804
90033
  const result = validateDoc(content);
89805
90034
  if (options2.plain) {
89806
90035
  console.log(`Validating: ${resolved.filename}`);
@@ -89927,8 +90156,8 @@ var searchInCommand = new Command("search-in").description("Search text within a
89927
90156
  console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
89928
90157
  process.exit(1);
89929
90158
  }
89930
- const fileContent = await readFile18(resolved.filepath, "utf-8");
89931
- const { content } = (0, import_gray_matter7.default)(fileContent);
90159
+ const fileContent = await readFile20(resolved.filepath, "utf-8");
90160
+ const { content } = (0, import_gray_matter9.default)(fileContent);
89932
90161
  const lines = content.split("\n");
89933
90162
  const matches = [];
89934
90163
  const searchQuery = options2.ignoreCase ? query.toLowerCase() : query;
@@ -89980,8 +90209,8 @@ var replaceCommand = new Command("replace").description("Replace text in a docum
89980
90209
  console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
89981
90210
  process.exit(1);
89982
90211
  }
89983
- const fileContent = await readFile18(resolved.filepath, "utf-8");
89984
- const { data, content } = (0, import_gray_matter7.default)(fileContent);
90212
+ const fileContent = await readFile20(resolved.filepath, "utf-8");
90213
+ const { data, content } = (0, import_gray_matter9.default)(fileContent);
89985
90214
  if (!content.includes(oldText)) {
89986
90215
  if (options2.plain) {
89987
90216
  console.log(`Text not found: "${oldText}"`);
@@ -90002,8 +90231,8 @@ var replaceCommand = new Command("replace").description("Replace text in a docum
90002
90231
  }
90003
90232
  const metadata = data;
90004
90233
  metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
90005
- const updatedFileContent = import_gray_matter7.default.stringify(newContent, data);
90006
- await writeFile13(resolved.filepath, updatedFileContent, "utf-8");
90234
+ const updatedFileContent = import_gray_matter9.default.stringify(newContent, data);
90235
+ await writeFile14(resolved.filepath, updatedFileContent, "utf-8");
90007
90236
  await notifyDocUpdate(resolved.filename);
90008
90237
  if (options2.plain) {
90009
90238
  console.log(`Updated: ${resolved.filename}`);
@@ -90024,8 +90253,8 @@ var replaceSectionCommand = new Command("replace-section").description("Replace
90024
90253
  console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
90025
90254
  process.exit(1);
90026
90255
  }
90027
- const fileContent = await readFile18(resolved.filepath, "utf-8");
90028
- const { data, content } = (0, import_gray_matter7.default)(fileContent);
90256
+ const fileContent = await readFile20(resolved.filepath, "utf-8");
90257
+ const { data, content } = (0, import_gray_matter9.default)(fileContent);
90029
90258
  const headerLevel = (header.match(/^#+/) || ["##"])[0];
90030
90259
  const headerPattern = new RegExp(`^${header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
90031
90260
  const headerMatch = content.match(headerPattern);
@@ -90056,8 +90285,8 @@ ${newSectionContent}
90056
90285
  ${afterSection.trimStart()}`;
90057
90286
  const metadata = data;
90058
90287
  metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
90059
- const updatedFileContent = import_gray_matter7.default.stringify(newContent.trim(), data);
90060
- await writeFile13(resolved.filepath, updatedFileContent, "utf-8");
90288
+ const updatedFileContent = import_gray_matter9.default.stringify(newContent.trim(), data);
90289
+ await writeFile14(resolved.filepath, updatedFileContent, "utf-8");
90061
90290
  await notifyDocUpdate(resolved.filename);
90062
90291
  if (options2.plain) {
90063
90292
  console.log(`Updated: ${resolved.filename}`);
@@ -90084,8 +90313,8 @@ var docCommand = new Command("doc").description("Manage documentation").argument
90084
90313
  process.exit(1);
90085
90314
  }
90086
90315
  const { filepath, filename, isImported, source } = resolved;
90087
- const fileContent = await readFile18(filepath, "utf-8");
90088
- const { data, content } = (0, import_gray_matter7.default)(fileContent);
90316
+ const fileContent = await readFile20(filepath, "utf-8");
90317
+ const { data, content } = (0, import_gray_matter9.default)(fileContent);
90089
90318
  const metadata = data;
90090
90319
  if (options2.smart) {
90091
90320
  const stats = calculateDocStats(content);
@@ -90260,7 +90489,7 @@ var docCommand = new Command("doc").description("Manage documentation").argument
90260
90489
  }
90261
90490
  console.log(enhancedContent);
90262
90491
  const projectRoot = findProjectRoot() || process.cwd();
90263
- const tasksDir = join29(projectRoot, ".knowns", "tasks");
90492
+ const tasksDir = join31(projectRoot, ".knowns", "tasks");
90264
90493
  const refs = await validateRefs(projectRoot, enhancedContent, tasksDir);
90265
90494
  const brokenRefs = refs.filter((r) => !r.exists);
90266
90495
  if (brokenRefs.length > 0) {
@@ -90290,7 +90519,7 @@ var docCommand = new Command("doc").description("Manage documentation").argument
90290
90519
  }
90291
90520
  console.log(displayContent);
90292
90521
  const projectRoot = findProjectRoot() || process.cwd();
90293
- const tasksDir = join29(projectRoot, ".knowns", "tasks");
90522
+ const tasksDir = join31(projectRoot, ".knowns", "tasks");
90294
90523
  const refs = await validateRefs(projectRoot, displayContent, tasksDir);
90295
90524
  const brokenRefs = refs.filter((r) => !r.exists);
90296
90525
  if (brokenRefs.length > 0) {
@@ -90321,9 +90550,9 @@ docCommand.addCommand(replaceCommand);
90321
90550
  docCommand.addCommand(replaceSectionCommand);
90322
90551
 
90323
90552
  // src/commands/config.ts
90324
- import { existsSync as existsSync26 } from "node:fs";
90325
- import { mkdir as mkdir16, readFile as readFile19, writeFile as writeFile14 } from "node:fs/promises";
90326
- import { join as join30 } from "node:path";
90553
+ import { existsSync as existsSync28 } from "node:fs";
90554
+ import { mkdir as mkdir16, readFile as readFile21, writeFile as writeFile15 } from "node:fs/promises";
90555
+ import { join as join32 } from "node:path";
90327
90556
  var CONFIG_FILE3 = ".knowns/config.json";
90328
90557
  var ConfigSchema = external_exports.object({
90329
90558
  defaultAssignee: external_exports.string().optional(),
@@ -90351,12 +90580,12 @@ function getProjectRoot2() {
90351
90580
  return projectRoot;
90352
90581
  }
90353
90582
  async function loadConfig(projectRoot) {
90354
- const configPath = join30(projectRoot, CONFIG_FILE3);
90355
- if (!existsSync26(configPath)) {
90583
+ const configPath = join32(projectRoot, CONFIG_FILE3);
90584
+ if (!existsSync28(configPath)) {
90356
90585
  return { ...DEFAULT_CONFIG };
90357
90586
  }
90358
90587
  try {
90359
- const content = await readFile19(configPath, "utf-8");
90588
+ const content = await readFile21(configPath, "utf-8");
90360
90589
  const data = JSON.parse(content);
90361
90590
  const settings = data.settings || {};
90362
90591
  const validated = ConfigSchema.parse(settings);
@@ -90370,22 +90599,22 @@ async function loadConfig(projectRoot) {
90370
90599
  }
90371
90600
  }
90372
90601
  async function saveConfig(projectRoot, config2) {
90373
- const configPath = join30(projectRoot, CONFIG_FILE3);
90374
- const knownsDir = join30(projectRoot, ".knowns");
90375
- if (!existsSync26(knownsDir)) {
90602
+ const configPath = join32(projectRoot, CONFIG_FILE3);
90603
+ const knownsDir = join32(projectRoot, ".knowns");
90604
+ if (!existsSync28(knownsDir)) {
90376
90605
  await mkdir16(knownsDir, { recursive: true });
90377
90606
  }
90378
90607
  try {
90379
90608
  let existingData = {};
90380
- if (existsSync26(configPath)) {
90381
- const content = await readFile19(configPath, "utf-8");
90609
+ if (existsSync28(configPath)) {
90610
+ const content = await readFile21(configPath, "utf-8");
90382
90611
  existingData = JSON.parse(content);
90383
90612
  }
90384
90613
  const merged = {
90385
90614
  ...existingData,
90386
90615
  settings: config2
90387
90616
  };
90388
- await writeFile14(configPath, JSON.stringify(merged, null, 2), "utf-8");
90617
+ await writeFile15(configPath, JSON.stringify(merged, null, 2), "utf-8");
90389
90618
  } catch (error48) {
90390
90619
  console.error(source_default.red("\u2717 Failed to save config"));
90391
90620
  if (error48 instanceof Error) {
@@ -90527,8 +90756,8 @@ var resetCommand = new Command("reset").description("Reset configuration to defa
90527
90756
  var configCommand = new Command("config").description("Manage configuration settings").addCommand(listCommand3).addCommand(getCommand).addCommand(setCommand).addCommand(resetCommand);
90528
90757
 
90529
90758
  // src/commands/sync.ts
90530
- import { existsSync as existsSync27, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
90531
- import { join as join31 } from "node:path";
90759
+ import { existsSync as existsSync29, mkdirSync, readFileSync as readFileSync2, readdirSync as readdirSync3, rmSync as rmSync2, writeFileSync } from "node:fs";
90760
+ import { join as join33 } from "node:path";
90532
90761
  var PROJECT_ROOT2 = process.cwd();
90533
90762
  function renderSkillContent2(content, mode) {
90534
90763
  try {
@@ -90537,21 +90766,49 @@ function renderSkillContent2(content, mode) {
90537
90766
  return content;
90538
90767
  }
90539
90768
  }
90769
+ function cleanupDeprecatedSkills2(skillsDir) {
90770
+ let removed = 0;
90771
+ if (existsSync29(skillsDir)) {
90772
+ const entries = readdirSync3(skillsDir, { withFileTypes: true });
90773
+ for (const entry of entries) {
90774
+ if (entry.isDirectory() && entry.name.startsWith("knowns.")) {
90775
+ const deprecatedPath = join33(skillsDir, entry.name);
90776
+ rmSync2(deprecatedPath, { recursive: true, force: true });
90777
+ console.log(source_default.yellow(`\u2713 Removed deprecated: ${entry.name}`));
90778
+ removed++;
90779
+ }
90780
+ }
90781
+ }
90782
+ const agentSkillsDir = join33(PROJECT_ROOT2, ".agent", "skills");
90783
+ if (existsSync29(agentSkillsDir)) {
90784
+ const entries = readdirSync3(agentSkillsDir, { withFileTypes: true });
90785
+ for (const entry of entries) {
90786
+ if (entry.isDirectory() && entry.name.startsWith("knowns.")) {
90787
+ const deprecatedPath = join33(agentSkillsDir, entry.name);
90788
+ rmSync2(deprecatedPath, { recursive: true, force: true });
90789
+ console.log(source_default.yellow(`\u2713 Removed deprecated: .agent/skills/${entry.name}`));
90790
+ removed++;
90791
+ }
90792
+ }
90793
+ }
90794
+ return removed;
90795
+ }
90540
90796
  async function syncSkills2(options2) {
90541
- const skillsDir = join31(PROJECT_ROOT2, ".claude", "skills");
90797
+ const skillsDir = join33(PROJECT_ROOT2, ".claude", "skills");
90542
90798
  const mode = options2.mode ?? "mcp";
90543
- if (!existsSync27(skillsDir)) {
90799
+ if (!existsSync29(skillsDir)) {
90544
90800
  mkdirSync(skillsDir, { recursive: true });
90545
90801
  console.log(source_default.green("\u2713 Created .claude/skills/"));
90546
90802
  }
90803
+ const removed = cleanupDeprecatedSkills2(skillsDir);
90547
90804
  let created = 0;
90548
90805
  let updated = 0;
90549
90806
  let skipped = 0;
90550
90807
  for (const skill of SKILLS) {
90551
- const skillFolder = join31(skillsDir, skill.folderName);
90552
- const skillFile = join31(skillFolder, "SKILL.md");
90808
+ const skillFolder = join33(skillsDir, skill.folderName);
90809
+ const skillFile = join33(skillFolder, "SKILL.md");
90553
90810
  const renderedContent = renderSkillContent2(skill.content, mode);
90554
- if (existsSync27(skillFile)) {
90811
+ if (existsSync29(skillFile)) {
90555
90812
  if (options2.force) {
90556
90813
  const existing = readFileSync2(skillFile, "utf-8");
90557
90814
  if (existing.trim() !== renderedContent.trim()) {
@@ -90567,7 +90824,7 @@ async function syncSkills2(options2) {
90567
90824
  skipped++;
90568
90825
  }
90569
90826
  } else {
90570
- if (!existsSync27(skillFolder)) {
90827
+ if (!existsSync29(skillFolder)) {
90571
90828
  mkdirSync(skillFolder, { recursive: true });
90572
90829
  }
90573
90830
  writeFileSync(skillFile, renderedContent, "utf-8");
@@ -90575,7 +90832,7 @@ async function syncSkills2(options2) {
90575
90832
  created++;
90576
90833
  }
90577
90834
  }
90578
- return { created, updated, skipped };
90835
+ return { created, updated, skipped, removed };
90579
90836
  }
90580
90837
  async function syncAgents(options2) {
90581
90838
  const type = options2.type === "mcp" ? "mcp" : "cli";
@@ -90612,13 +90869,13 @@ async function syncIDE(options2) {
90612
90869
  let skipped = 0;
90613
90870
  const configs = options2.ide ? [getIDEConfig(options2.ide)].filter(Boolean) : IDE_CONFIGS;
90614
90871
  for (const config2 of configs) {
90615
- const targetDir = join31(PROJECT_ROOT2, config2.targetDir);
90872
+ const targetDir = join33(PROJECT_ROOT2, config2.targetDir);
90616
90873
  for (const file3 of config2.files) {
90617
- const filePath = join31(targetDir, file3.filename);
90618
- const fileDir = join31(targetDir, ...file3.filename.split("/").slice(0, -1));
90874
+ const filePath = join33(targetDir, file3.filename);
90875
+ const fileDir = join33(targetDir, ...file3.filename.split("/").slice(0, -1));
90619
90876
  const content = file3.isJson ? `${JSON.stringify(file3.content, null, 2)}
90620
90877
  ` : String(file3.content);
90621
- if (existsSync27(filePath)) {
90878
+ if (existsSync29(filePath)) {
90622
90879
  if (options2.force) {
90623
90880
  const existing = readFileSync2(filePath, "utf-8");
90624
90881
  if (existing.trim() !== content.trim()) {
@@ -90634,7 +90891,7 @@ async function syncIDE(options2) {
90634
90891
  skipped++;
90635
90892
  }
90636
90893
  } else {
90637
- if (!existsSync27(fileDir)) {
90894
+ if (!existsSync29(fileDir)) {
90638
90895
  mkdirSync(fileDir, { recursive: true });
90639
90896
  }
90640
90897
  writeFileSync(filePath, content, "utf-8");
@@ -90648,6 +90905,7 @@ async function syncIDE(options2) {
90648
90905
  function printSummary(label, stats) {
90649
90906
  console.log(source_default.bold(`
90650
90907
  ${label}:`));
90908
+ if (stats.removed && stats.removed > 0) console.log(source_default.yellow(` Removed: ${stats.removed}`));
90651
90909
  if (stats.created > 0) console.log(source_default.green(` Created: ${stats.created}`));
90652
90910
  if (stats.updated > 0) console.log(source_default.green(` Updated: ${stats.updated}`));
90653
90911
  if (stats.skipped > 0) console.log(source_default.gray(` Skipped: ${stats.skipped}`));
@@ -90725,13 +90983,13 @@ syncCommand.addCommand(agentSubcommand);
90725
90983
  syncCommand.addCommand(ideSubcommand);
90726
90984
 
90727
90985
  // src/commands/mcp.ts
90728
- import { existsSync as existsSync34, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
90729
- import { join as join39 } from "node:path";
90986
+ import { existsSync as existsSync37, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
90987
+ import { join as join42 } from "node:path";
90730
90988
 
90731
90989
  // src/mcp/server.ts
90732
- import { existsSync as existsSync33 } from "node:fs";
90733
- import { readFile as readFile24 } from "node:fs/promises";
90734
- import { join as join38 } from "node:path";
90990
+ import { existsSync as existsSync36 } from "node:fs";
90991
+ import { readFile as readFile27 } from "node:fs/promises";
90992
+ import { join as join41 } from "node:path";
90735
90993
 
90736
90994
  // node_modules/zod/v3/helpers/util.js
90737
90995
  var util;
@@ -97791,17 +98049,17 @@ var StdioServerTransport = class {
97791
98049
  };
97792
98050
 
97793
98051
  // src/mcp/server.ts
97794
- var import_gray_matter11 = __toESM(require_gray_matter(), 1);
98052
+ var import_gray_matter14 = __toESM(require_gray_matter(), 1);
97795
98053
 
97796
98054
  // src/mcp/utils.ts
97797
- import { readFile as readFile20 } from "node:fs/promises";
97798
- import { join as join33 } from "node:path";
97799
- var import_gray_matter8 = __toESM(require_gray_matter(), 1);
98055
+ import { readFile as readFile22 } from "node:fs/promises";
98056
+ import { join as join35 } from "node:path";
98057
+ var import_gray_matter10 = __toESM(require_gray_matter(), 1);
97800
98058
 
97801
98059
  // src/mcp/handlers/project.ts
97802
- import { existsSync as existsSync28, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
98060
+ import { existsSync as existsSync30, readdirSync as readdirSync4, statSync as statSync2 } from "node:fs";
97803
98061
  import { homedir as homedir2 } from "node:os";
97804
- import { basename as basename4, join as join32 } from "node:path";
98062
+ import { basename as basename4, join as join34 } from "node:path";
97805
98063
  var currentProjectRoot = null;
97806
98064
  function getProjectRoot3() {
97807
98065
  if (currentProjectRoot) {
@@ -97819,23 +98077,23 @@ function getWorkspaceDirectories() {
97819
98077
  const home = homedir2();
97820
98078
  const dirs = [];
97821
98079
  const commonPaths = [
97822
- join32(home, "Workspaces"),
97823
- join32(home, "workspace"),
97824
- join32(home, "projects"),
97825
- join32(home, "Projects"),
97826
- join32(home, "dev"),
97827
- join32(home, "Development"),
97828
- join32(home, "Code"),
97829
- join32(home, "code"),
97830
- join32(home, "repos"),
97831
- join32(home, "Repos"),
97832
- join32(home, "src"),
97833
- join32(home, "Documents", "Projects"),
97834
- join32(home, "Documents", "GitHub"),
97835
- join32(home, "GitHub")
98080
+ join34(home, "Workspaces"),
98081
+ join34(home, "workspace"),
98082
+ join34(home, "projects"),
98083
+ join34(home, "Projects"),
98084
+ join34(home, "dev"),
98085
+ join34(home, "Development"),
98086
+ join34(home, "Code"),
98087
+ join34(home, "code"),
98088
+ join34(home, "repos"),
98089
+ join34(home, "Repos"),
98090
+ join34(home, "src"),
98091
+ join34(home, "Documents", "Projects"),
98092
+ join34(home, "Documents", "GitHub"),
98093
+ join34(home, "GitHub")
97836
98094
  ];
97837
98095
  for (const path3 of commonPaths) {
97838
- if (existsSync28(path3)) {
98096
+ if (existsSync30(path3)) {
97839
98097
  dirs.push(path3);
97840
98098
  }
97841
98099
  }
@@ -97846,10 +98104,10 @@ function scanForKnownsProjects(baseDir, maxDepth = 2) {
97846
98104
  function scan(dir, depth) {
97847
98105
  if (depth > maxDepth) return;
97848
98106
  try {
97849
- const entries = readdirSync2(dir);
98107
+ const entries = readdirSync4(dir);
97850
98108
  if (entries.includes(".knowns")) {
97851
- const knownsPath = join32(dir, ".knowns");
97852
- if (existsSync28(knownsPath) && statSync2(knownsPath).isDirectory()) {
98109
+ const knownsPath = join34(dir, ".knowns");
98110
+ if (existsSync30(knownsPath) && statSync2(knownsPath).isDirectory()) {
97853
98111
  projects.push(dir);
97854
98112
  return;
97855
98113
  }
@@ -97858,7 +98116,7 @@ function scanForKnownsProjects(baseDir, maxDepth = 2) {
97858
98116
  if (entry.startsWith(".") || entry === "node_modules" || entry === "vendor") {
97859
98117
  continue;
97860
98118
  }
97861
- const entryPath = join32(dir, entry);
98119
+ const entryPath = join34(dir, entry);
97862
98120
  try {
97863
98121
  if (statSync2(entryPath).isDirectory()) {
97864
98122
  scan(entryPath, depth + 1);
@@ -97876,7 +98134,7 @@ function detectKnownsProjects() {
97876
98134
  const workspaceDirs = getWorkspaceDirectories();
97877
98135
  const allProjects = [];
97878
98136
  const cwd = process.cwd();
97879
- if (existsSync28(join32(cwd, ".knowns"))) {
98137
+ if (existsSync30(join34(cwd, ".knowns"))) {
97880
98138
  allProjects.push(cwd);
97881
98139
  }
97882
98140
  for (const dir of workspaceDirs) {
@@ -97934,7 +98192,7 @@ function handleDetectProjects(input) {
97934
98192
  let projects = detectKnownsProjects();
97935
98193
  if (input.additionalPaths) {
97936
98194
  for (const path3 of input.additionalPaths) {
97937
- if (existsSync28(path3)) {
98195
+ if (existsSync30(path3)) {
97938
98196
  const additional = scanForKnownsProjects(path3);
97939
98197
  for (const project of additional) {
97940
98198
  if (!projects.find((p) => p.path === project)) {
@@ -97954,15 +98212,15 @@ function handleDetectProjects(input) {
97954
98212
  }
97955
98213
  function handleSetProject(input) {
97956
98214
  const { projectRoot } = input;
97957
- if (!existsSync28(projectRoot)) {
98215
+ if (!existsSync30(projectRoot)) {
97958
98216
  return {
97959
98217
  success: false,
97960
98218
  projectRoot,
97961
98219
  message: `Path does not exist: ${projectRoot}`
97962
98220
  };
97963
98221
  }
97964
- const knownsPath = join32(projectRoot, ".knowns");
97965
- if (!existsSync28(knownsPath)) {
98222
+ const knownsPath = join34(projectRoot, ".knowns");
98223
+ if (!existsSync30(knownsPath)) {
97966
98224
  return {
97967
98225
  success: false,
97968
98226
  projectRoot,
@@ -97986,7 +98244,7 @@ function handleGetCurrentProject() {
97986
98244
  source = "env";
97987
98245
  projectRoot = process.env.KNOWNS_PROJECT_ROOT;
97988
98246
  }
97989
- const isValid2 = existsSync28(join32(projectRoot, ".knowns"));
98247
+ const isValid2 = existsSync30(join34(projectRoot, ".knowns"));
97990
98248
  return {
97991
98249
  projectRoot,
97992
98250
  isExplicitlySet: source === "explicit",
@@ -98027,7 +98285,7 @@ function formatDuration2(seconds) {
98027
98285
  }
98028
98286
  async function fetchLinkedDocs(task) {
98029
98287
  const projectRoot = getProjectRoot3();
98030
- const docsDir = join33(projectRoot, ".knowns", "docs");
98288
+ const docsDir = join35(projectRoot, ".knowns", "docs");
98031
98289
  const allContent = [task.description || "", task.implementationPlan || "", task.implementationNotes || ""].join("\n");
98032
98290
  const docRefs = resolveDocReferences(allContent, projectRoot);
98033
98291
  const linkedDocs = [];
@@ -98035,9 +98293,9 @@ async function fetchLinkedDocs(task) {
98035
98293
  if (!ref.exists) continue;
98036
98294
  try {
98037
98295
  const filename = ref.resolvedPath.replace("@.knowns/docs/", "");
98038
- const filepath = join33(docsDir, filename);
98039
- const fileContent = await readFile20(filepath, "utf-8");
98040
- const { data, content } = (0, import_gray_matter8.default)(fileContent);
98296
+ const filepath = join35(docsDir, filename);
98297
+ const fileContent = await readFile22(filepath, "utf-8");
98298
+ const { data, content } = (0, import_gray_matter10.default)(fileContent);
98041
98299
  linkedDocs.push({
98042
98300
  path: ref.resolvedPath,
98043
98301
  title: data.title || ref.text,
@@ -98070,6 +98328,10 @@ function errorResponse(error48) {
98070
98328
  }
98071
98329
 
98072
98330
  // src/mcp/handlers/task.ts
98331
+ function normalizeNewlines(text) {
98332
+ if (!text) return text;
98333
+ return text.replace(/\\n/g, "\n");
98334
+ }
98073
98335
  var createTaskSchema = external_exports.object({
98074
98336
  title: external_exports.string(),
98075
98337
  description: external_exports.string().optional(),
@@ -98077,7 +98339,8 @@ var createTaskSchema = external_exports.object({
98077
98339
  priority: external_exports.enum(["low", "medium", "high"]).optional(),
98078
98340
  assignee: external_exports.string().optional(),
98079
98341
  labels: external_exports.array(external_exports.string()).optional(),
98080
- parent: external_exports.string().optional()
98342
+ parent: external_exports.string().optional(),
98343
+ spec: external_exports.string().optional()
98081
98344
  });
98082
98345
  var getTaskSchema = external_exports.object({
98083
98346
  taskId: external_exports.string()
@@ -98090,6 +98353,8 @@ var updateTaskSchema = external_exports.object({
98090
98353
  priority: external_exports.enum(["low", "medium", "high"]).optional(),
98091
98354
  assignee: external_exports.string().optional(),
98092
98355
  labels: external_exports.array(external_exports.string()).optional(),
98356
+ spec: external_exports.string().nullable().optional(),
98357
+ // Spec document path (null to remove)
98093
98358
  // AC operations
98094
98359
  addAc: external_exports.array(external_exports.string()).optional(),
98095
98360
  // Add new acceptance criteria
@@ -98111,7 +98376,8 @@ var listTasksSchema = external_exports.object({
98111
98376
  status: external_exports.string().optional(),
98112
98377
  priority: external_exports.string().optional(),
98113
98378
  assignee: external_exports.string().optional(),
98114
- label: external_exports.string().optional()
98379
+ label: external_exports.string().optional(),
98380
+ spec: external_exports.string().optional()
98115
98381
  });
98116
98382
  var searchTasksSchema = external_exports.object({
98117
98383
  query: external_exports.string()
@@ -98141,7 +98407,8 @@ var taskTools = [
98141
98407
  items: { type: "string" },
98142
98408
  description: "Task labels"
98143
98409
  },
98144
- parent: { type: "string", description: "Parent task ID for subtasks" }
98410
+ parent: { type: "string", description: "Parent task ID for subtasks" },
98411
+ spec: { type: "string", description: "Spec document path (e.g., 'specs/user-auth')" }
98145
98412
  },
98146
98413
  required: ["title"]
98147
98414
  }
@@ -98182,6 +98449,7 @@ var taskTools = [
98182
98449
  items: { type: "string" },
98183
98450
  description: "New labels"
98184
98451
  },
98452
+ spec: { type: "string", description: "Spec document path (set to null to remove)" },
98185
98453
  addAc: {
98186
98454
  type: "array",
98187
98455
  items: { type: "string" },
@@ -98218,7 +98486,8 @@ var taskTools = [
98218
98486
  status: { type: "string", description: "Filter by status" },
98219
98487
  priority: { type: "string", description: "Filter by priority" },
98220
98488
  assignee: { type: "string", description: "Filter by assignee" },
98221
- label: { type: "string", description: "Filter by label" }
98489
+ label: { type: "string", description: "Filter by label" },
98490
+ spec: { type: "string", description: "Filter by spec document path" }
98222
98491
  }
98223
98492
  }
98224
98493
  },
@@ -98238,12 +98507,13 @@ async function handleCreateTask(args2, fileStore) {
98238
98507
  const input = createTaskSchema.parse(args2);
98239
98508
  const task = await fileStore.createTask({
98240
98509
  title: input.title,
98241
- description: input.description,
98510
+ description: normalizeNewlines(input.description),
98242
98511
  status: input.status || "todo",
98243
98512
  priority: input.priority || "medium",
98244
98513
  assignee: input.assignee,
98245
98514
  labels: input.labels || [],
98246
98515
  parent: input.parent,
98516
+ spec: input.spec,
98247
98517
  subtasks: [],
98248
98518
  acceptanceCriteria: [],
98249
98519
  timeSpent: 0,
@@ -98276,6 +98546,7 @@ async function handleGetTask(args2, fileStore) {
98276
98546
  priority: task.priority,
98277
98547
  assignee: task.assignee,
98278
98548
  labels: task.labels,
98549
+ spec: task.spec,
98279
98550
  acceptanceCriteria: task.acceptanceCriteria,
98280
98551
  implementationPlan: task.implementationPlan,
98281
98552
  implementationNotes: task.implementationNotes,
@@ -98294,11 +98565,14 @@ async function handleUpdateTask(args2, fileStore) {
98294
98565
  }
98295
98566
  const updates = {};
98296
98567
  if (input.title) updates.title = input.title;
98297
- if (input.description) updates.description = input.description;
98568
+ if (input.description) updates.description = normalizeNewlines(input.description);
98298
98569
  if (input.status) updates.status = input.status;
98299
98570
  if (input.priority) updates.priority = input.priority;
98300
98571
  if (input.assignee) updates.assignee = input.assignee;
98301
98572
  if (input.labels) updates.labels = input.labels;
98573
+ if (input.spec !== void 0) {
98574
+ updates.spec = input.spec === null ? void 0 : input.spec;
98575
+ }
98302
98576
  const criteria = [...currentTask.acceptanceCriteria];
98303
98577
  let acModified = false;
98304
98578
  if (input.addAc && input.addAc.length > 0) {
@@ -98341,15 +98615,15 @@ async function handleUpdateTask(args2, fileStore) {
98341
98615
  updates.acceptanceCriteria = criteria;
98342
98616
  }
98343
98617
  if (input.plan !== void 0) {
98344
- updates.implementationPlan = input.plan;
98618
+ updates.implementationPlan = normalizeNewlines(input.plan);
98345
98619
  }
98346
98620
  if (input.notes !== void 0) {
98347
- updates.implementationNotes = input.notes;
98621
+ updates.implementationNotes = normalizeNewlines(input.notes);
98348
98622
  }
98349
98623
  if (input.appendNotes) {
98350
98624
  const existingNotes = currentTask.implementationNotes || "";
98351
98625
  const separator = existingNotes ? "\n\n" : "";
98352
- updates.implementationNotes = existingNotes + separator + input.appendNotes;
98626
+ updates.implementationNotes = existingNotes + separator + normalizeNewlines(input.appendNotes);
98353
98627
  }
98354
98628
  const task = await fileStore.updateTask(taskId, updates);
98355
98629
  await notifyTaskUpdate(task.id);
@@ -98380,6 +98654,9 @@ async function handleListTasks(args2, fileStore) {
98380
98654
  if (input.label) {
98381
98655
  tasks = tasks.filter((t) => t.labels.includes(input.label));
98382
98656
  }
98657
+ if (input.spec) {
98658
+ tasks = tasks.filter((t) => t.spec === input.spec);
98659
+ }
98383
98660
  return successResponse({
98384
98661
  count: tasks.length,
98385
98662
  tasks: tasks.map((t) => ({
@@ -98388,7 +98665,8 @@ async function handleListTasks(args2, fileStore) {
98388
98665
  status: t.status,
98389
98666
  priority: t.priority,
98390
98667
  assignee: t.assignee,
98391
- labels: t.labels
98668
+ labels: t.labels,
98669
+ spec: t.spec
98392
98670
  }))
98393
98671
  });
98394
98672
  }
@@ -98411,9 +98689,9 @@ async function handleSearchTasks(args2, fileStore) {
98411
98689
  }
98412
98690
 
98413
98691
  // src/mcp/handlers/time.ts
98414
- import { existsSync as existsSync29 } from "node:fs";
98415
- import { readFile as readFile21, writeFile as writeFile15 } from "node:fs/promises";
98416
- import { join as join34 } from "node:path";
98692
+ import { existsSync as existsSync31 } from "node:fs";
98693
+ import { readFile as readFile23, writeFile as writeFile16 } from "node:fs/promises";
98694
+ import { join as join36 } from "node:path";
98417
98695
  var startTimeSchema = external_exports.object({
98418
98696
  taskId: external_exports.string()
98419
98697
  });
@@ -98496,11 +98774,11 @@ var timeTools = [
98496
98774
  }
98497
98775
  ];
98498
98776
  async function loadTimeData2(projectRoot) {
98499
- const timePath = join34(projectRoot, ".knowns", "time.json");
98500
- if (!existsSync29(timePath)) {
98777
+ const timePath = join36(projectRoot, ".knowns", "time.json");
98778
+ if (!existsSync31(timePath)) {
98501
98779
  return { active: [] };
98502
98780
  }
98503
- const content = await readFile21(timePath, "utf-8");
98781
+ const content = await readFile23(timePath, "utf-8");
98504
98782
  const data = JSON.parse(content);
98505
98783
  if (data.active && !Array.isArray(data.active)) {
98506
98784
  return { active: [data.active] };
@@ -98511,8 +98789,8 @@ async function loadTimeData2(projectRoot) {
98511
98789
  return data;
98512
98790
  }
98513
98791
  async function saveTimeData2(projectRoot, data) {
98514
- const timePath = join34(projectRoot, ".knowns", "time.json");
98515
- await writeFile15(timePath, JSON.stringify(data, null, 2), "utf-8");
98792
+ const timePath = join36(projectRoot, ".knowns", "time.json");
98793
+ await writeFile16(timePath, JSON.stringify(data, null, 2), "utf-8");
98516
98794
  }
98517
98795
  async function handleStartTime(args2, fileStore) {
98518
98796
  const input = startTimeSchema.parse(args2);
@@ -98727,12 +99005,16 @@ async function handleGetBoard(fileStore) {
98727
99005
  }
98728
99006
 
98729
99007
  // src/mcp/handlers/doc.ts
98730
- import { existsSync as existsSync30 } from "node:fs";
98731
- import { mkdir as mkdir17, readFile as readFile22, readdir as readdir13, writeFile as writeFile16 } from "node:fs/promises";
98732
- import { join as join35 } from "node:path";
98733
- var import_gray_matter9 = __toESM(require_gray_matter(), 1);
99008
+ import { existsSync as existsSync32 } from "node:fs";
99009
+ import { mkdir as mkdir17, readFile as readFile24, readdir as readdir14, writeFile as writeFile17 } from "node:fs/promises";
99010
+ import { join as join37 } from "node:path";
99011
+ var import_gray_matter11 = __toESM(require_gray_matter(), 1);
98734
99012
  function getDocsDir() {
98735
- return join35(getProjectRoot3(), ".knowns", "docs");
99013
+ return join37(getProjectRoot3(), ".knowns", "docs");
99014
+ }
99015
+ function normalizeNewlines2(text) {
99016
+ if (!text) return text;
99017
+ return text.replace(/\\n/g, "\n");
98736
99018
  }
98737
99019
  var listDocsSchema = external_exports.object({
98738
99020
  tag: external_exports.string().optional()
@@ -98883,7 +99165,7 @@ var docTools = [
98883
99165
  }
98884
99166
  ];
98885
99167
  async function ensureDocsDir2() {
98886
- if (!existsSync30(getDocsDir())) {
99168
+ if (!existsSync32(getDocsDir())) {
98887
99169
  await mkdir17(getDocsDir(), { recursive: true });
98888
99170
  }
98889
99171
  }
@@ -98892,13 +99174,13 @@ function titleToFilename2(title) {
98892
99174
  }
98893
99175
  async function getAllMdFiles2(dir, basePath = "") {
98894
99176
  const files = [];
98895
- if (!existsSync30(dir)) {
99177
+ if (!existsSync32(dir)) {
98896
99178
  return files;
98897
99179
  }
98898
- const entries = await readdir13(dir, { withFileTypes: true });
99180
+ const entries = await readdir14(dir, { withFileTypes: true });
98899
99181
  for (const entry of entries) {
98900
- const fullPath = join35(dir, entry.name);
98901
- const relativePath = normalizePath2(basePath ? join35(basePath, entry.name) : entry.name);
99182
+ const fullPath = join37(dir, entry.name);
99183
+ const relativePath = normalizePath2(basePath ? join37(basePath, entry.name) : entry.name);
98902
99184
  if (entry.isDirectory()) {
98903
99185
  const subFiles = await getAllMdFiles2(fullPath, relativePath);
98904
99186
  files.push(...subFiles);
@@ -98911,13 +99193,13 @@ async function getAllMdFiles2(dir, basePath = "") {
98911
99193
  async function resolveDocPath2(name) {
98912
99194
  await ensureDocsDir2();
98913
99195
  let filename = name.endsWith(".md") ? name : `${name}.md`;
98914
- let filepath = join35(getDocsDir(), filename);
98915
- if (existsSync30(filepath)) {
99196
+ let filepath = join37(getDocsDir(), filename);
99197
+ if (existsSync32(filepath)) {
98916
99198
  return { filepath, filename };
98917
99199
  }
98918
99200
  filename = `${titleToFilename2(name)}.md`;
98919
- filepath = join35(getDocsDir(), filename);
98920
- if (existsSync30(filepath)) {
99201
+ filepath = join37(getDocsDir(), filename);
99202
+ if (existsSync32(filepath)) {
98921
99203
  return { filepath, filename };
98922
99204
  }
98923
99205
  const allFiles = await getAllMdFiles2(getDocsDir());
@@ -98929,7 +99211,7 @@ async function resolveDocPath2(name) {
98929
99211
  });
98930
99212
  if (matchingFile) {
98931
99213
  return {
98932
- filepath: join35(getDocsDir(), matchingFile),
99214
+ filepath: join37(getDocsDir(), matchingFile),
98933
99215
  filename: matchingFile
98934
99216
  };
98935
99217
  }
@@ -98950,8 +99232,8 @@ async function handleListDocs(args2) {
98950
99232
  const docs = [];
98951
99233
  for (const doc of allDocs) {
98952
99234
  try {
98953
- const fileContent = await readFile22(doc.fullPath, "utf-8");
98954
- const { data, content } = (0, import_gray_matter9.default)(fileContent);
99235
+ const fileContent = await readFile24(doc.fullPath, "utf-8");
99236
+ const { data, content } = (0, import_gray_matter11.default)(fileContent);
98955
99237
  const metadata = data;
98956
99238
  const stats = calculateDocStats(content);
98957
99239
  if (input.tag && !metadata.tags?.includes(input.tag)) {
@@ -98983,8 +99265,8 @@ async function handleGetDoc(args2) {
98983
99265
  if (!resolved) {
98984
99266
  return errorResponse(`Documentation not found: ${input.path}`);
98985
99267
  }
98986
- const fileContent = await readFile22(resolved.filepath, "utf-8");
98987
- const { data, content } = (0, import_gray_matter9.default)(fileContent);
99268
+ const fileContent = await readFile24(resolved.filepath, "utf-8");
99269
+ const { data, content } = (0, import_gray_matter11.default)(fileContent);
98988
99270
  const metadata = data;
98989
99271
  if (input.smart) {
98990
99272
  const stats = calculateDocStats(content);
@@ -99079,7 +99361,7 @@ async function handleGetDoc(args2) {
99079
99361
  });
99080
99362
  }
99081
99363
  const projectRoot = getProjectRoot3();
99082
- const tasksDir = join35(projectRoot, ".knowns", "tasks");
99364
+ const tasksDir = join37(projectRoot, ".knowns", "tasks");
99083
99365
  const refs = await validateRefs(projectRoot, content, tasksDir);
99084
99366
  const brokenRefs = refs.filter((r) => !r.exists).map((r) => r.ref);
99085
99367
  return successResponse({
@@ -99103,14 +99385,14 @@ async function handleCreateDoc(args2) {
99103
99385
  let relativePath = filename;
99104
99386
  if (input.folder) {
99105
99387
  const folderPath = input.folder.replace(/^\/|\/$/g, "");
99106
- targetDir = join35(getDocsDir(), folderPath);
99107
- relativePath = join35(folderPath, filename);
99108
- if (!existsSync30(targetDir)) {
99388
+ targetDir = join37(getDocsDir(), folderPath);
99389
+ relativePath = join37(folderPath, filename);
99390
+ if (!existsSync32(targetDir)) {
99109
99391
  await mkdir17(targetDir, { recursive: true });
99110
99392
  }
99111
99393
  }
99112
- const filepath = join35(targetDir, filename);
99113
- if (existsSync30(filepath)) {
99394
+ const filepath = join37(targetDir, filename);
99395
+ if (existsSync32(filepath)) {
99114
99396
  return errorResponse(`Document already exists: ${relativePath}`);
99115
99397
  }
99116
99398
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -99125,9 +99407,9 @@ async function handleCreateDoc(args2) {
99125
99407
  if (input.tags) {
99126
99408
  metadata.tags = input.tags;
99127
99409
  }
99128
- const initialContent = input.content || "# Content\n\nWrite your documentation here.";
99129
- const fileContent = import_gray_matter9.default.stringify(initialContent, metadata);
99130
- await writeFile16(filepath, fileContent, "utf-8");
99410
+ const initialContent = normalizeNewlines2(input.content) || "# Content\n\nWrite your documentation here.";
99411
+ const fileContent = import_gray_matter11.default.stringify(initialContent, metadata);
99412
+ await writeFile17(filepath, fileContent, "utf-8");
99131
99413
  await notifyDocUpdate(relativePath);
99132
99414
  return successResponse({
99133
99415
  message: `Created documentation: ${relativePath}`,
@@ -99145,8 +99427,8 @@ async function handleUpdateDoc(args2) {
99145
99427
  if (!resolved) {
99146
99428
  return errorResponse(`Documentation not found: ${input.path}`);
99147
99429
  }
99148
- const fileContent = await readFile22(resolved.filepath, "utf-8");
99149
- const { data, content } = (0, import_gray_matter9.default)(fileContent);
99430
+ const fileContent = await readFile24(resolved.filepath, "utf-8");
99431
+ const { data, content } = (0, import_gray_matter11.default)(fileContent);
99150
99432
  const metadata = data;
99151
99433
  if (input.title) metadata.title = input.title;
99152
99434
  if (input.description) metadata.description = input.description;
@@ -99154,9 +99436,11 @@ async function handleUpdateDoc(args2) {
99154
99436
  metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
99155
99437
  let updatedContent = content;
99156
99438
  let sectionUpdated;
99157
- if (input.section && input.content) {
99439
+ const normalizedContent = normalizeNewlines2(input.content);
99440
+ const normalizedAppend = normalizeNewlines2(input.appendContent);
99441
+ if (input.section && normalizedContent) {
99158
99442
  const sectionIndex = /^\d+$/.test(input.section) ? Number.parseInt(input.section, 10) : null;
99159
- const result = sectionIndex !== null ? replaceSectionByIndex(content, sectionIndex, input.content) : replaceSection(content, input.section, input.content);
99443
+ const result = sectionIndex !== null ? replaceSectionByIndex(content, sectionIndex, normalizedContent) : replaceSection(content, input.section, normalizedContent);
99160
99444
  if (!result) {
99161
99445
  return errorResponse(
99162
99446
  `Section not found: ${input.section}. Use 'toc: true' with get_doc to see available sections.`
@@ -99164,16 +99448,16 @@ async function handleUpdateDoc(args2) {
99164
99448
  }
99165
99449
  updatedContent = result;
99166
99450
  sectionUpdated = input.section;
99167
- } else if (input.content) {
99168
- updatedContent = input.content;
99451
+ } else if (normalizedContent) {
99452
+ updatedContent = normalizedContent;
99169
99453
  }
99170
- if (input.appendContent) {
99454
+ if (normalizedAppend) {
99171
99455
  updatedContent = `${updatedContent.trimEnd()}
99172
99456
 
99173
- ${input.appendContent}`;
99457
+ ${normalizedAppend}`;
99174
99458
  }
99175
- const newFileContent = import_gray_matter9.default.stringify(updatedContent, metadata);
99176
- await writeFile16(resolved.filepath, newFileContent, "utf-8");
99459
+ const newFileContent = import_gray_matter11.default.stringify(updatedContent, metadata);
99460
+ await writeFile17(resolved.filepath, newFileContent, "utf-8");
99177
99461
  await notifyDocUpdate(resolved.filename);
99178
99462
  return successResponse({
99179
99463
  message: sectionUpdated ? `Updated section "${sectionUpdated}" in ${resolved.filename}` : `Updated documentation: ${resolved.filename}`,
@@ -99194,8 +99478,8 @@ async function handleSearchDocs(args2) {
99194
99478
  const query = input.query.toLowerCase();
99195
99479
  const results = [];
99196
99480
  for (const file3 of mdFiles) {
99197
- const fileContent = await readFile22(join35(getDocsDir(), file3), "utf-8");
99198
- const { data, content } = (0, import_gray_matter9.default)(fileContent);
99481
+ const fileContent = await readFile24(join37(getDocsDir(), file3), "utf-8");
99482
+ const { data, content } = (0, import_gray_matter11.default)(fileContent);
99199
99483
  const metadata = data;
99200
99484
  if (input.tag && !metadata.tags?.includes(input.tag)) {
99201
99485
  continue;
@@ -99231,11 +99515,11 @@ async function handleSearchDocs(args2) {
99231
99515
  }
99232
99516
 
99233
99517
  // src/mcp/handlers/template.ts
99234
- import { existsSync as existsSync31 } from "node:fs";
99235
- import { mkdir as mkdir18, writeFile as writeFile17 } from "node:fs/promises";
99236
- import { join as join36 } from "node:path";
99518
+ import { existsSync as existsSync33 } from "node:fs";
99519
+ import { mkdir as mkdir18, writeFile as writeFile18 } from "node:fs/promises";
99520
+ import { join as join38 } from "node:path";
99237
99521
  function getTemplatesDir() {
99238
- return join36(getProjectRoot3(), ".knowns", "templates");
99522
+ return join38(getProjectRoot3(), ".knowns", "templates");
99239
99523
  }
99240
99524
  var listTemplatesSchema = external_exports.object({});
99241
99525
  var getTemplateSchema = external_exports.object({
@@ -99342,7 +99626,7 @@ async function handleListTemplates(_args) {
99342
99626
  const templateList = [];
99343
99627
  for (const t of allTemplates) {
99344
99628
  try {
99345
- const loaded = await listTemplates(join36(t.path, ".."));
99629
+ const loaded = await listTemplates(join38(t.path, ".."));
99346
99630
  const match2 = loaded.find((l) => l.name === t.name);
99347
99631
  templateList.push({
99348
99632
  name: t.name,
@@ -99485,11 +99769,11 @@ async function handleRunTemplate(args2) {
99485
99769
  async function handleCreateTemplate(args2) {
99486
99770
  const input = createTemplateSchema.parse(args2);
99487
99771
  try {
99488
- if (!existsSync31(getTemplatesDir())) {
99772
+ if (!existsSync33(getTemplatesDir())) {
99489
99773
  await mkdir18(getTemplatesDir(), { recursive: true });
99490
99774
  }
99491
- const templateDir = join36(getTemplatesDir(), input.name);
99492
- if (existsSync31(templateDir)) {
99775
+ const templateDir = join38(getTemplatesDir(), input.name);
99776
+ if (existsSync33(templateDir)) {
99493
99777
  return errorResponse(`Template "${input.name}" already exists`);
99494
99778
  }
99495
99779
  await mkdir18(templateDir, { recursive: true });
@@ -99518,7 +99802,7 @@ messages:
99518
99802
  success: |
99519
99803
  \u2713 Created {{name}}!
99520
99804
  `;
99521
- await writeFile17(join36(templateDir, "_template.yaml"), configContent, "utf-8");
99805
+ await writeFile18(join38(templateDir, "_template.yaml"), configContent, "utf-8");
99522
99806
  const exampleTemplate = `/**
99523
99807
  * {{pascalCase name}}
99524
99808
  * Generated from ${input.name} template
@@ -99528,7 +99812,7 @@ export function {{camelCase name}}() {
99528
99812
  console.log("Hello from {{name}}!");
99529
99813
  }
99530
99814
  `;
99531
- await writeFile17(join36(templateDir, "example.ts.hbs"), exampleTemplate, "utf-8");
99815
+ await writeFile18(join38(templateDir, "example.ts.hbs"), exampleTemplate, "utf-8");
99532
99816
  return successResponse({
99533
99817
  message: `Created template: ${input.name}`,
99534
99818
  template: {
@@ -99550,10 +99834,10 @@ export function {{camelCase name}}() {
99550
99834
  }
99551
99835
 
99552
99836
  // src/mcp/handlers/search.ts
99553
- var import_gray_matter10 = __toESM(require_gray_matter(), 1);
99554
- import { existsSync as existsSync32 } from "node:fs";
99555
- import { readFile as readFile23, readdir as readdir14 } from "node:fs/promises";
99556
- import { join as join37 } from "node:path";
99837
+ var import_gray_matter12 = __toESM(require_gray_matter(), 1);
99838
+ import { existsSync as existsSync34 } from "node:fs";
99839
+ import { readFile as readFile25, readdir as readdir15 } from "node:fs/promises";
99840
+ import { join as join39 } from "node:path";
99557
99841
  var searchSchema = external_exports.object({
99558
99842
  query: external_exports.string(),
99559
99843
  type: external_exports.enum(["all", "task", "doc"]).optional(),
@@ -99640,12 +99924,12 @@ function calculateDocScore2(title, description, content, tags, query) {
99640
99924
  }
99641
99925
  async function getAllMdFiles3(dir, basePath = "") {
99642
99926
  const files = [];
99643
- if (!existsSync32(dir)) {
99927
+ if (!existsSync34(dir)) {
99644
99928
  return files;
99645
99929
  }
99646
- const entries = await readdir14(dir, { withFileTypes: true });
99930
+ const entries = await readdir15(dir, { withFileTypes: true });
99647
99931
  for (const entry of entries) {
99648
- const fullPath = join37(dir, entry.name);
99932
+ const fullPath = join39(dir, entry.name);
99649
99933
  const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
99650
99934
  if (entry.isDirectory()) {
99651
99935
  const subFiles = await getAllMdFiles3(fullPath, relativePath);
@@ -99688,15 +99972,15 @@ async function searchTasks(fileStore, query, filters) {
99688
99972
  })).sort((a, b) => b.score - a.score);
99689
99973
  }
99690
99974
  async function searchDocs2(docsDir, query, tagFilter) {
99691
- if (!existsSync32(docsDir)) {
99975
+ if (!existsSync34(docsDir)) {
99692
99976
  return [];
99693
99977
  }
99694
99978
  const mdFiles = await getAllMdFiles3(docsDir);
99695
99979
  const q = query.toLowerCase();
99696
99980
  const results = [];
99697
99981
  for (const file3 of mdFiles) {
99698
- const fileContent = await readFile23(join37(docsDir, file3), "utf-8");
99699
- const { data, content } = (0, import_gray_matter10.default)(fileContent);
99982
+ const fileContent = await readFile25(join39(docsDir, file3), "utf-8");
99983
+ const { data, content } = (0, import_gray_matter12.default)(fileContent);
99700
99984
  const metadata = data;
99701
99985
  if (tagFilter && !metadata.tags?.includes(tagFilter)) {
99702
99986
  continue;
@@ -99732,7 +100016,7 @@ async function handleSearch(args2, fileStore) {
99732
100016
  const input = searchSchema.parse(args2);
99733
100017
  const searchType = input.type || "all";
99734
100018
  const limit = input.limit || 20;
99735
- const docsDir = join37(getProjectRoot3(), ".knowns", "docs");
100019
+ const docsDir = join39(getProjectRoot3(), ".knowns", "docs");
99736
100020
  let taskResults = [];
99737
100021
  let docResults = [];
99738
100022
  if (searchType === "all" || searchType === "task") {
@@ -99763,6 +100047,657 @@ async function handleSearch(args2, fileStore) {
99763
100047
  });
99764
100048
  }
99765
100049
 
100050
+ // src/mcp/handlers/validate.ts
100051
+ import { existsSync as existsSync35 } from "node:fs";
100052
+ import { readFile as readFile26, readdir as readdir16, writeFile as writeFile19 } from "node:fs/promises";
100053
+ import { join as join40 } from "node:path";
100054
+ var import_gray_matter13 = __toESM(require_gray_matter(), 1);
100055
+ var import_handlebars2 = __toESM(require_lib2(), 1);
100056
+ init_config();
100057
+ var validateTools = [
100058
+ {
100059
+ name: "validate",
100060
+ description: "Validate tasks, docs, and templates for reference integrity and quality. Returns errors, warnings, and info about broken refs, missing AC, orphan docs, etc. Use scope='sdd' for SDD (Spec-Driven Development) validation.",
100061
+ inputSchema: {
100062
+ type: "object",
100063
+ properties: {
100064
+ scope: {
100065
+ type: "string",
100066
+ enum: ["all", "tasks", "docs", "templates", "sdd"],
100067
+ description: "Validation scope: 'all' (default), 'tasks', 'docs', 'templates', or 'sdd' for spec-driven checks"
100068
+ },
100069
+ strict: {
100070
+ type: "boolean",
100071
+ description: "Treat warnings as errors (default: false)"
100072
+ },
100073
+ fix: {
100074
+ type: "boolean",
100075
+ description: "Auto-fix supported issues like broken doc refs (default: false)"
100076
+ }
100077
+ }
100078
+ }
100079
+ }
100080
+ ];
100081
+ function stripTrailingPunctuation(path3) {
100082
+ return path3.replace(/[.,;:!?`'")\]]+$/, "");
100083
+ }
100084
+ function extractRefs(content) {
100085
+ const docRefs = [];
100086
+ const taskRefs = [];
100087
+ const templateRefs = [];
100088
+ const docRefPattern = /@docs?\/([^\s,;:!?"'()\]]+)/g;
100089
+ for (const match2 of content.matchAll(docRefPattern)) {
100090
+ let docPath = stripTrailingPunctuation(match2[1] || "");
100091
+ docPath = docPath.replace(/\.md$/, "");
100092
+ if (docPath && !docRefs.includes(docPath)) {
100093
+ docRefs.push(docPath);
100094
+ }
100095
+ }
100096
+ const taskRefPattern = /@task-([a-zA-Z0-9]+)/g;
100097
+ for (const match2 of content.matchAll(taskRefPattern)) {
100098
+ const taskId = match2[1] || "";
100099
+ if (taskId && !taskRefs.includes(taskId)) {
100100
+ taskRefs.push(taskId);
100101
+ }
100102
+ }
100103
+ const templateRefPattern = /@template\/([^\s,;:!?"'()\]]+)/g;
100104
+ for (const match2 of content.matchAll(templateRefPattern)) {
100105
+ const templateName = stripTrailingPunctuation(match2[1] || "");
100106
+ if (templateName && !templateRefs.includes(templateName)) {
100107
+ templateRefs.push(templateName);
100108
+ }
100109
+ }
100110
+ return { docRefs, taskRefs, templateRefs };
100111
+ }
100112
+ async function loadValidateConfig(projectRoot) {
100113
+ const config2 = await readConfig(projectRoot);
100114
+ return config2.validate || {};
100115
+ }
100116
+ function getRuleSeverity(rule, defaultSeverity, validateConfig) {
100117
+ if (validateConfig.rules?.[rule]) {
100118
+ return validateConfig.rules[rule];
100119
+ }
100120
+ return defaultSeverity;
100121
+ }
100122
+ function shouldIgnore(entity, validateConfig) {
100123
+ if (!validateConfig.ignore) return false;
100124
+ for (const pattern of validateConfig.ignore) {
100125
+ const regex2 = new RegExp(`^${pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*")}$`);
100126
+ if (regex2.test(entity)) return true;
100127
+ }
100128
+ return false;
100129
+ }
100130
+ async function findSimilarDocs(projectRoot, brokenRef) {
100131
+ const docsDir = join40(projectRoot, ".knowns", "docs");
100132
+ if (!existsSync35(docsDir)) return null;
100133
+ const allDocs = [];
100134
+ async function scanDir(dir, relativePath) {
100135
+ const entries = await readdir16(dir, { withFileTypes: true });
100136
+ for (const entry of entries) {
100137
+ if (entry.name.startsWith(".")) continue;
100138
+ const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
100139
+ if (entry.isDirectory()) {
100140
+ await scanDir(join40(dir, entry.name), entryRelPath);
100141
+ } else if (entry.name.endsWith(".md")) {
100142
+ allDocs.push(entryRelPath.replace(/\.md$/, ""));
100143
+ }
100144
+ }
100145
+ }
100146
+ await scanDir(docsDir, "");
100147
+ const brokenLower = brokenRef.toLowerCase();
100148
+ let bestMatch = null;
100149
+ let bestScore = 0;
100150
+ for (const doc of allDocs) {
100151
+ const docLower = doc.toLowerCase();
100152
+ const brokenParts = brokenLower.split(/[-_/]/);
100153
+ const docParts = docLower.split(/[-_/]/);
100154
+ let matchScore = 0;
100155
+ for (const part of brokenParts) {
100156
+ if (docLower.includes(part) && part.length > 2) {
100157
+ matchScore += part.length;
100158
+ }
100159
+ }
100160
+ for (const part of docParts) {
100161
+ if (brokenLower.includes(part) && part.length > 2) {
100162
+ matchScore += part.length;
100163
+ }
100164
+ }
100165
+ if (matchScore > bestScore) {
100166
+ bestScore = matchScore;
100167
+ bestMatch = doc;
100168
+ }
100169
+ }
100170
+ return bestScore >= 3 ? bestMatch : null;
100171
+ }
100172
+ async function validateTasks(projectRoot, fileStore, validateConfig) {
100173
+ const issues = [];
100174
+ const tasks = await fileStore.getAllTasks();
100175
+ const taskIds = new Set(tasks.map((t) => t.id));
100176
+ for (const task of tasks) {
100177
+ const taskRef = `task-${task.id}`;
100178
+ if (shouldIgnore(taskRef, validateConfig)) continue;
100179
+ const content = `${task.description || ""} ${task.implementationPlan || ""} ${task.implementationNotes || ""}`;
100180
+ const { docRefs, taskRefs, templateRefs } = extractRefs(content);
100181
+ const noAcSeverity = getRuleSeverity("task-no-ac", "warning", validateConfig);
100182
+ if (noAcSeverity !== "off" && (!task.acceptanceCriteria || task.acceptanceCriteria.length === 0)) {
100183
+ issues.push({
100184
+ entity: taskRef,
100185
+ entityType: "task",
100186
+ rule: "task-no-ac",
100187
+ severity: noAcSeverity,
100188
+ message: "Task has no acceptance criteria"
100189
+ });
100190
+ }
100191
+ const noDescSeverity = getRuleSeverity("task-no-description", "warning", validateConfig);
100192
+ if (noDescSeverity !== "off" && (!task.description || task.description.trim() === "")) {
100193
+ issues.push({
100194
+ entity: taskRef,
100195
+ entityType: "task",
100196
+ rule: "task-no-description",
100197
+ severity: noDescSeverity,
100198
+ message: "Task has no description"
100199
+ });
100200
+ }
100201
+ const brokenDocSeverity = getRuleSeverity("task-broken-doc-ref", "error", validateConfig);
100202
+ if (brokenDocSeverity !== "off") {
100203
+ for (const docPath of docRefs) {
100204
+ const resolved = await resolveDoc(projectRoot, docPath);
100205
+ if (!resolved) {
100206
+ const suggestion = await findSimilarDocs(projectRoot, docPath);
100207
+ const issue2 = {
100208
+ entity: taskRef,
100209
+ entityType: "task",
100210
+ rule: "task-broken-doc-ref",
100211
+ severity: brokenDocSeverity,
100212
+ message: suggestion ? `Broken reference: @doc/${docPath} \u2192 did you mean @doc/${suggestion}?` : `Broken reference: @doc/${docPath}`,
100213
+ fixable: !!suggestion
100214
+ };
100215
+ if (suggestion) {
100216
+ issue2.fix = async () => {
100217
+ const tasksDir = join40(projectRoot, ".knowns", "tasks");
100218
+ const files = await readdir16(tasksDir);
100219
+ const taskFile = files.find((f) => f.startsWith(`task-${task.id} `));
100220
+ if (taskFile) {
100221
+ const taskFilePath = join40(tasksDir, taskFile);
100222
+ const taskContent = await readFile26(taskFilePath, "utf-8");
100223
+ const updated = taskContent.replace(
100224
+ new RegExp(`@docs?/${docPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"),
100225
+ `@doc/${suggestion}`
100226
+ );
100227
+ await writeFile19(taskFilePath, updated, "utf-8");
100228
+ }
100229
+ };
100230
+ }
100231
+ issues.push(issue2);
100232
+ }
100233
+ }
100234
+ }
100235
+ const brokenTaskSeverity = getRuleSeverity("task-broken-task-ref", "error", validateConfig);
100236
+ if (brokenTaskSeverity !== "off") {
100237
+ for (const refTaskId of taskRefs) {
100238
+ if (!taskIds.has(refTaskId)) {
100239
+ issues.push({
100240
+ entity: taskRef,
100241
+ entityType: "task",
100242
+ rule: "task-broken-task-ref",
100243
+ severity: brokenTaskSeverity,
100244
+ message: `Broken reference: @task-${refTaskId}`
100245
+ });
100246
+ }
100247
+ }
100248
+ }
100249
+ const brokenTplSeverity = getRuleSeverity("task-broken-template-ref", "error", validateConfig);
100250
+ if (brokenTplSeverity !== "off") {
100251
+ for (const templateName of templateRefs) {
100252
+ const resolved = await resolveTemplate(projectRoot, templateName);
100253
+ if (!resolved) {
100254
+ issues.push({
100255
+ entity: taskRef,
100256
+ entityType: "task",
100257
+ rule: "task-broken-template-ref",
100258
+ severity: brokenTplSeverity,
100259
+ message: `Broken reference: @template/${templateName}`
100260
+ });
100261
+ }
100262
+ }
100263
+ }
100264
+ const selfRefSeverity = getRuleSeverity("task-self-ref", "warning", validateConfig);
100265
+ if (selfRefSeverity !== "off" && taskRefs.includes(task.id)) {
100266
+ issues.push({
100267
+ entity: taskRef,
100268
+ entityType: "task",
100269
+ rule: "task-self-ref",
100270
+ severity: selfRefSeverity,
100271
+ message: "Task references itself"
100272
+ });
100273
+ }
100274
+ const circularSeverity = getRuleSeverity("task-circular-parent", "error", validateConfig);
100275
+ if (circularSeverity !== "off" && task.parent) {
100276
+ const visited = /* @__PURE__ */ new Set();
100277
+ let currentId = task.parent;
100278
+ while (currentId) {
100279
+ if (visited.has(currentId) || currentId === task.id) {
100280
+ issues.push({
100281
+ entity: taskRef,
100282
+ entityType: "task",
100283
+ rule: "task-circular-parent",
100284
+ severity: circularSeverity,
100285
+ message: currentId === task.id ? "Task is its own ancestor" : "Circular parent-child relationship detected"
100286
+ });
100287
+ break;
100288
+ }
100289
+ visited.add(currentId);
100290
+ const parentTask = tasks.find((t) => t.id === currentId);
100291
+ currentId = parentTask?.parent;
100292
+ }
100293
+ }
100294
+ }
100295
+ return issues;
100296
+ }
100297
+ async function validateDocs(projectRoot, fileStore, validateConfig) {
100298
+ const issues = [];
100299
+ const docsDir = join40(projectRoot, ".knowns", "docs");
100300
+ if (!existsSync35(docsDir)) return issues;
100301
+ const tasks = await fileStore.getAllTasks();
100302
+ const taskIds = new Set(tasks.map((t) => t.id));
100303
+ const referencedDocs = /* @__PURE__ */ new Set();
100304
+ for (const task of tasks) {
100305
+ const content = `${task.description || ""} ${task.implementationPlan || ""} ${task.implementationNotes || ""}`;
100306
+ const { docRefs } = extractRefs(content);
100307
+ for (const ref of docRefs) {
100308
+ referencedDocs.add(ref.toLowerCase());
100309
+ }
100310
+ }
100311
+ async function scanDir(dir, relativePath) {
100312
+ const entries = await readdir16(dir, { withFileTypes: true });
100313
+ for (const entry of entries) {
100314
+ if (entry.name.startsWith(".")) continue;
100315
+ const fullPath = join40(dir, entry.name);
100316
+ const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
100317
+ if (entry.isDirectory()) {
100318
+ await scanDir(fullPath, entryRelPath);
100319
+ } else if (entry.name.endsWith(".md")) {
100320
+ const docPath = entryRelPath.replace(/\.md$/, "");
100321
+ const docRef = `docs/${docPath}`;
100322
+ if (shouldIgnore(docRef, validateConfig) || shouldIgnore(docPath, validateConfig)) continue;
100323
+ try {
100324
+ const content = await readFile26(fullPath, "utf-8");
100325
+ const { data, content: docContent } = (0, import_gray_matter13.default)(content);
100326
+ const noDescSeverity = getRuleSeverity("doc-no-description", "warning", validateConfig);
100327
+ if (noDescSeverity !== "off" && (!data.description || String(data.description).trim() === "")) {
100328
+ issues.push({
100329
+ entity: docRef,
100330
+ entityType: "doc",
100331
+ rule: "doc-no-description",
100332
+ severity: noDescSeverity,
100333
+ message: "Doc has no description"
100334
+ });
100335
+ }
100336
+ const orphanSeverity = getRuleSeverity("doc-orphan", "info", validateConfig);
100337
+ if (orphanSeverity !== "off" && !referencedDocs.has(docPath.toLowerCase())) {
100338
+ issues.push({
100339
+ entity: docRef,
100340
+ entityType: "doc",
100341
+ rule: "doc-orphan",
100342
+ severity: orphanSeverity,
100343
+ message: "Doc is not referenced by any task"
100344
+ });
100345
+ }
100346
+ const { docRefs, taskRefs } = extractRefs(docContent);
100347
+ const brokenDocSeverity = getRuleSeverity("doc-broken-doc-ref", "error", validateConfig);
100348
+ if (brokenDocSeverity !== "off") {
100349
+ for (const refDocPath of docRefs) {
100350
+ const resolved = await resolveDoc(projectRoot, refDocPath);
100351
+ if (!resolved) {
100352
+ const suggestion = await findSimilarDocs(projectRoot, refDocPath);
100353
+ const issue2 = {
100354
+ entity: docRef,
100355
+ entityType: "doc",
100356
+ rule: "doc-broken-doc-ref",
100357
+ severity: brokenDocSeverity,
100358
+ message: suggestion ? `Broken reference: @doc/${refDocPath} \u2192 did you mean @doc/${suggestion}?` : `Broken reference: @doc/${refDocPath}`,
100359
+ fixable: !!suggestion
100360
+ };
100361
+ if (suggestion) {
100362
+ issue2.fix = async () => {
100363
+ const docFileContent = await readFile26(fullPath, "utf-8");
100364
+ const updated = docFileContent.replace(
100365
+ new RegExp(`@docs?/${refDocPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"),
100366
+ `@doc/${suggestion}`
100367
+ );
100368
+ await writeFile19(fullPath, updated, "utf-8");
100369
+ };
100370
+ }
100371
+ issues.push(issue2);
100372
+ }
100373
+ }
100374
+ }
100375
+ const brokenTaskSeverity = getRuleSeverity("doc-broken-task-ref", "error", validateConfig);
100376
+ if (brokenTaskSeverity !== "off") {
100377
+ for (const refTaskId of taskRefs) {
100378
+ if (!taskIds.has(refTaskId)) {
100379
+ const issue2 = {
100380
+ entity: docRef,
100381
+ entityType: "doc",
100382
+ rule: "doc-broken-task-ref",
100383
+ severity: brokenTaskSeverity,
100384
+ message: `Broken reference: @task-${refTaskId}`,
100385
+ fixable: true
100386
+ };
100387
+ issue2.fix = async () => {
100388
+ const docFileContent = await readFile26(fullPath, "utf-8");
100389
+ const updated = docFileContent.replace(
100390
+ new RegExp(`@task-${refTaskId}\\b`, "g"),
100391
+ `~task-${refTaskId}`
100392
+ );
100393
+ await writeFile19(fullPath, updated, "utf-8");
100394
+ };
100395
+ issues.push(issue2);
100396
+ }
100397
+ }
100398
+ }
100399
+ } catch {
100400
+ }
100401
+ }
100402
+ }
100403
+ }
100404
+ await scanDir(docsDir, "");
100405
+ return issues;
100406
+ }
100407
+ async function validateTemplates(projectRoot, validateConfig) {
100408
+ const issues = [];
100409
+ const templates = await listAllTemplates(projectRoot);
100410
+ for (const template of templates) {
100411
+ const templateRef = `templates/${template.ref}`;
100412
+ if (shouldIgnore(templateRef, validateConfig)) continue;
100413
+ try {
100414
+ const config2 = await getTemplateConfig(template.path);
100415
+ const invalidSyntaxSeverity = getRuleSeverity("template-invalid-syntax", "error", validateConfig);
100416
+ if (invalidSyntaxSeverity !== "off" && !config2) {
100417
+ issues.push({
100418
+ entity: templateRef,
100419
+ entityType: "template",
100420
+ rule: "template-invalid-syntax",
100421
+ severity: invalidSyntaxSeverity,
100422
+ message: "Failed to load template config (invalid or missing _template.yaml)"
100423
+ });
100424
+ continue;
100425
+ }
100426
+ if (!config2) continue;
100427
+ const brokenDocSeverity = getRuleSeverity("template-broken-doc-ref", "error", validateConfig);
100428
+ if (brokenDocSeverity !== "off" && config2.doc) {
100429
+ const resolved = await resolveDoc(projectRoot, config2.doc);
100430
+ if (!resolved) {
100431
+ issues.push({
100432
+ entity: templateRef,
100433
+ entityType: "template",
100434
+ rule: "template-broken-doc-ref",
100435
+ severity: brokenDocSeverity,
100436
+ message: `Broken doc reference: @doc/${config2.doc}`
100437
+ });
100438
+ }
100439
+ }
100440
+ if (invalidSyntaxSeverity !== "off") {
100441
+ for (const action of config2.actions || []) {
100442
+ if (action.type === "add" && action.template) {
100443
+ const templateFilePath = join40(template.path, action.template);
100444
+ if (existsSync35(templateFilePath)) {
100445
+ try {
100446
+ const templateContent = await readFile26(templateFilePath, "utf-8");
100447
+ import_handlebars2.default.compile(templateContent);
100448
+ } catch (err) {
100449
+ issues.push({
100450
+ entity: templateRef,
100451
+ entityType: "template",
100452
+ rule: "template-invalid-syntax",
100453
+ severity: invalidSyntaxSeverity,
100454
+ message: `Invalid Handlebars syntax in ${action.template}: ${err instanceof Error ? err.message : "unknown error"}`
100455
+ });
100456
+ }
100457
+ }
100458
+ }
100459
+ }
100460
+ }
100461
+ const missingPartialSeverity = getRuleSeverity("template-missing-partial", "error", validateConfig);
100462
+ if (missingPartialSeverity !== "off" && existsSync35(template.path)) {
100463
+ const files = await readdir16(template.path);
100464
+ const hbsFiles = files.filter((f) => f.endsWith(".hbs"));
100465
+ for (const hbsFile of hbsFiles) {
100466
+ const content = await readFile26(join40(template.path, hbsFile), "utf-8");
100467
+ const partialPattern = /\{\{>\s*([^\s}]+)\s*\}\}/g;
100468
+ for (const match2 of content.matchAll(partialPattern)) {
100469
+ const partialName = match2[1];
100470
+ const partialPath = join40(template.path, `_${partialName}.hbs`);
100471
+ if (!existsSync35(partialPath)) {
100472
+ issues.push({
100473
+ entity: templateRef,
100474
+ entityType: "template",
100475
+ rule: "template-missing-partial",
100476
+ severity: missingPartialSeverity,
100477
+ message: `Missing partial: ${partialName} (expected at _${partialName}.hbs)`
100478
+ });
100479
+ }
100480
+ }
100481
+ }
100482
+ }
100483
+ } catch (err) {
100484
+ const invalidSyntaxSeverity = getRuleSeverity("template-invalid-syntax", "error", validateConfig);
100485
+ if (invalidSyntaxSeverity !== "off") {
100486
+ issues.push({
100487
+ entity: templateRef,
100488
+ entityType: "template",
100489
+ rule: "template-invalid-syntax",
100490
+ severity: invalidSyntaxSeverity,
100491
+ message: `Failed to load template config: ${err instanceof Error ? err.message : "unknown error"}`
100492
+ });
100493
+ }
100494
+ }
100495
+ }
100496
+ return issues;
100497
+ }
100498
+ async function applyFixes(issues) {
100499
+ const results = [];
100500
+ const fixableIssues = issues.filter((i) => i.fixable && i.fix);
100501
+ for (const issue2 of fixableIssues) {
100502
+ try {
100503
+ await issue2.fix?.();
100504
+ results.push({
100505
+ entity: issue2.entity,
100506
+ rule: issue2.rule,
100507
+ action: issue2.message,
100508
+ success: true
100509
+ });
100510
+ } catch (err) {
100511
+ results.push({
100512
+ entity: issue2.entity,
100513
+ rule: issue2.rule,
100514
+ action: `Failed: ${err instanceof Error ? err.message : "unknown error"}`,
100515
+ success: false
100516
+ });
100517
+ }
100518
+ }
100519
+ return results;
100520
+ }
100521
+ async function runSDDValidation(projectRoot, fileStore) {
100522
+ const tasks = await fileStore.getAllTasks();
100523
+ const docsDir = join40(projectRoot, ".knowns", "docs");
100524
+ const stats = {
100525
+ specs: { total: 0, approved: 0, draft: 0 },
100526
+ tasks: { total: tasks.length, done: 0, inProgress: 0, todo: 0, withSpec: 0, withoutSpec: 0 },
100527
+ coverage: { linked: 0, total: tasks.length, percent: 0 },
100528
+ acCompletion: {}
100529
+ };
100530
+ const warnings = [];
100531
+ const passed = [];
100532
+ for (const task of tasks) {
100533
+ if (task.status === "done") stats.tasks.done++;
100534
+ else if (task.status === "in-progress") stats.tasks.inProgress++;
100535
+ else stats.tasks.todo++;
100536
+ if (task.spec) {
100537
+ stats.tasks.withSpec++;
100538
+ } else {
100539
+ stats.tasks.withoutSpec++;
100540
+ warnings.push({
100541
+ type: "task-no-spec",
100542
+ entity: `task-${task.id}`,
100543
+ message: `${task.title} has no spec reference`
100544
+ });
100545
+ }
100546
+ }
100547
+ stats.coverage.linked = stats.tasks.withSpec;
100548
+ stats.coverage.percent = stats.tasks.total > 0 ? Math.round(stats.tasks.withSpec / stats.tasks.total * 100) : 0;
100549
+ const specsDir = join40(docsDir, "specs");
100550
+ if (existsSync35(specsDir)) {
100551
+ async function scanSpecs(dir, relativePath) {
100552
+ const entries = await readdir16(dir, { withFileTypes: true });
100553
+ for (const entry of entries) {
100554
+ if (entry.name.startsWith(".")) continue;
100555
+ const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
100556
+ if (entry.isDirectory()) {
100557
+ await scanSpecs(join40(dir, entry.name), entryRelPath);
100558
+ } else if (entry.name.endsWith(".md")) {
100559
+ stats.specs.total++;
100560
+ const specPath = `specs/${entryRelPath.replace(/\.md$/, "")}`;
100561
+ try {
100562
+ const content = await readFile26(join40(dir, entry.name), "utf-8");
100563
+ const { data } = (0, import_gray_matter13.default)(content);
100564
+ if (data.status === "approved" || data.status === "implemented") {
100565
+ stats.specs.approved++;
100566
+ } else {
100567
+ stats.specs.draft++;
100568
+ }
100569
+ const linkedTasks = tasks.filter((t) => t.spec === specPath);
100570
+ if (linkedTasks.length > 0) {
100571
+ let totalAC = 0;
100572
+ let completedAC = 0;
100573
+ for (const task of linkedTasks) {
100574
+ totalAC += task.acceptanceCriteria.length;
100575
+ completedAC += task.acceptanceCriteria.filter((ac) => ac.completed).length;
100576
+ }
100577
+ const percent = totalAC > 0 ? Math.round(completedAC / totalAC * 100) : 100;
100578
+ stats.acCompletion[specPath] = { total: totalAC, completed: completedAC, percent };
100579
+ if (percent < 100 && totalAC > 0) {
100580
+ warnings.push({
100581
+ type: "spec-ac-incomplete",
100582
+ entity: specPath,
100583
+ message: `${completedAC}/${totalAC} ACs complete (${percent}%)`
100584
+ });
100585
+ }
100586
+ }
100587
+ } catch {
100588
+ }
100589
+ }
100590
+ }
100591
+ }
100592
+ await scanSpecs(specsDir, "");
100593
+ }
100594
+ for (const task of tasks) {
100595
+ if (task.spec) {
100596
+ const specDocPath = join40(docsDir, `${task.spec}.md`);
100597
+ if (!existsSync35(specDocPath)) {
100598
+ warnings.push({
100599
+ type: "spec-broken-link",
100600
+ entity: `task-${task.id}`,
100601
+ message: `Broken spec reference: @doc/${task.spec}`
100602
+ });
100603
+ }
100604
+ }
100605
+ }
100606
+ if (warnings.filter((w) => w.type === "spec-broken-link").length === 0) {
100607
+ passed.push("All spec references resolve");
100608
+ }
100609
+ for (const [specPath, completion] of Object.entries(stats.acCompletion)) {
100610
+ if (completion.percent === 100) {
100611
+ passed.push(`${specPath}: fully implemented`);
100612
+ }
100613
+ }
100614
+ return { stats, warnings, passed };
100615
+ }
100616
+ async function handleValidate(args2, fileStore) {
100617
+ try {
100618
+ const projectRoot = getProjectRoot3();
100619
+ if (args2?.scope === "sdd") {
100620
+ const sddResult = await runSDDValidation(projectRoot, fileStore);
100621
+ return successResponse({
100622
+ mode: "sdd",
100623
+ stats: sddResult.stats,
100624
+ warnings: sddResult.warnings,
100625
+ passed: sddResult.passed
100626
+ });
100627
+ }
100628
+ const validateConfig = await loadValidateConfig(projectRoot);
100629
+ const allIssues = [];
100630
+ const stats = { tasks: 0, docs: 0, templates: 0 };
100631
+ if (!args2?.type || args2.type === "task") {
100632
+ const tasks = await fileStore.getAllTasks();
100633
+ stats.tasks = tasks.length;
100634
+ const taskIssues = await validateTasks(projectRoot, fileStore, validateConfig);
100635
+ allIssues.push(...taskIssues);
100636
+ }
100637
+ if (!args2?.type || args2.type === "doc") {
100638
+ const docsDir = join40(projectRoot, ".knowns", "docs");
100639
+ if (existsSync35(docsDir)) {
100640
+ async function countDocs(dir) {
100641
+ let count = 0;
100642
+ const entries = await readdir16(dir, { withFileTypes: true });
100643
+ for (const entry of entries) {
100644
+ if (entry.name.startsWith(".")) continue;
100645
+ if (entry.isDirectory()) {
100646
+ count += await countDocs(join40(dir, entry.name));
100647
+ } else if (entry.name.endsWith(".md")) {
100648
+ count++;
100649
+ }
100650
+ }
100651
+ return count;
100652
+ }
100653
+ stats.docs = await countDocs(docsDir);
100654
+ }
100655
+ const docIssues = await validateDocs(projectRoot, fileStore, validateConfig);
100656
+ allIssues.push(...docIssues);
100657
+ }
100658
+ if (!args2?.type || args2.type === "template") {
100659
+ const templates = await listAllTemplates(projectRoot);
100660
+ stats.templates = templates.length;
100661
+ const templateIssues = await validateTemplates(projectRoot, validateConfig);
100662
+ allIssues.push(...templateIssues);
100663
+ }
100664
+ if (args2?.strict) {
100665
+ for (const issue2 of allIssues) {
100666
+ if (issue2.severity === "warning") {
100667
+ issue2.severity = "error";
100668
+ }
100669
+ }
100670
+ }
100671
+ let fixes = [];
100672
+ if (args2?.fix) {
100673
+ fixes = await applyFixes(allIssues);
100674
+ }
100675
+ const errors = allIssues.filter((i) => i.severity === "error");
100676
+ const warnings = allIssues.filter((i) => i.severity === "warning");
100677
+ const infos = allIssues.filter((i) => i.severity === "info");
100678
+ return successResponse({
100679
+ valid: errors.length === 0,
100680
+ stats,
100681
+ summary: {
100682
+ errors: errors.length,
100683
+ warnings: warnings.length,
100684
+ info: infos.length
100685
+ },
100686
+ issues: allIssues.map((i) => ({
100687
+ entity: i.entity,
100688
+ entityType: i.entityType,
100689
+ rule: i.rule,
100690
+ severity: i.severity,
100691
+ message: i.message,
100692
+ fixable: i.fixable || false
100693
+ })),
100694
+ ...args2?.fix && fixes.length > 0 ? { fixes } : {}
100695
+ });
100696
+ } catch (error48) {
100697
+ return errorResponse(error48 instanceof Error ? error48.message : String(error48));
100698
+ }
100699
+ }
100700
+
99766
100701
  // src/mcp/server.ts
99767
100702
  var fileStoreCache = /* @__PURE__ */ new Map();
99768
100703
  function getFileStore5() {
@@ -99793,7 +100728,8 @@ var tools = [
99793
100728
  ...boardTools,
99794
100729
  ...docTools,
99795
100730
  ...templateTools,
99796
- ...searchTools
100731
+ ...searchTools,
100732
+ ...validateTools
99797
100733
  ];
99798
100734
  server.setRequestHandler(ListToolsRequestSchema, async () => {
99799
100735
  return { tools };
@@ -99857,6 +100793,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
99857
100793
  // Unified search handler
99858
100794
  case "search":
99859
100795
  return await handleSearch(args2, getFileStore5());
100796
+ // Validate handler
100797
+ case "validate":
100798
+ return await handleValidate(args2, getFileStore5());
99860
100799
  default:
99861
100800
  return errorResponse(`Unknown tool: ${name}`);
99862
100801
  }
@@ -99866,7 +100805,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
99866
100805
  });
99867
100806
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
99868
100807
  const tasks = await getFileStore5().getAllTasks();
99869
- const docsDir = join38(getProjectRoot3(), ".knowns", "docs");
100808
+ const docsDir = join41(getProjectRoot3(), ".knowns", "docs");
99870
100809
  const taskResources = tasks.map((task) => ({
99871
100810
  uri: `knowns://task/${task.id}`,
99872
100811
  name: task.title,
@@ -99874,14 +100813,14 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
99874
100813
  description: `Task #${task.id}: ${task.title}`
99875
100814
  }));
99876
100815
  const docResources = [];
99877
- if (existsSync33(docsDir)) {
99878
- const { readdir: readdir16 } = await import("node:fs/promises");
100816
+ if (existsSync36(docsDir)) {
100817
+ const { readdir: readdir19 } = await import("node:fs/promises");
99879
100818
  async function getAllMdFiles4(dir, basePath = "") {
99880
100819
  const files = [];
99881
- const entries = await readdir16(dir, { withFileTypes: true });
100820
+ const entries = await readdir19(dir, { withFileTypes: true });
99882
100821
  for (const entry of entries) {
99883
- const fullPath = join38(dir, entry.name);
99884
- const relativePath = normalizePath2(basePath ? join38(basePath, entry.name) : entry.name);
100822
+ const fullPath = join41(dir, entry.name);
100823
+ const relativePath = normalizePath2(basePath ? join41(basePath, entry.name) : entry.name);
99885
100824
  if (entry.isDirectory()) {
99886
100825
  const subFiles = await getAllMdFiles4(fullPath, relativePath);
99887
100826
  files.push(...subFiles);
@@ -99893,9 +100832,9 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
99893
100832
  }
99894
100833
  const mdFiles = await getAllMdFiles4(docsDir);
99895
100834
  for (const file3 of mdFiles) {
99896
- const filepath = join38(docsDir, file3);
99897
- const content = await readFile24(filepath, "utf-8");
99898
- const { data } = (0, import_gray_matter11.default)(content);
100835
+ const filepath = join41(docsDir, file3);
100836
+ const content = await readFile27(filepath, "utf-8");
100837
+ const { data } = (0, import_gray_matter14.default)(content);
99899
100838
  docResources.push({
99900
100839
  uri: `knowns://doc/${file3.replace(/\.md$/, "")}`,
99901
100840
  name: data.title || file3.replace(/\.md$/, ""),
@@ -99930,13 +100869,13 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
99930
100869
  const docMatch = uri.match(/^knowns:\/\/doc\/(.+)$/);
99931
100870
  if (docMatch) {
99932
100871
  const docPath = docMatch[1];
99933
- const docsDir = join38(getProjectRoot3(), ".knowns", "docs");
99934
- const filepath = join38(docsDir, `${docPath}.md`);
99935
- if (!existsSync33(filepath)) {
100872
+ const docsDir = join41(getProjectRoot3(), ".knowns", "docs");
100873
+ const filepath = join41(docsDir, `${docPath}.md`);
100874
+ if (!existsSync36(filepath)) {
99936
100875
  throw new Error(`Documentation ${docPath} not found`);
99937
100876
  }
99938
- const content = await readFile24(filepath, "utf-8");
99939
- const { data, content: docContent } = (0, import_gray_matter11.default)(content);
100877
+ const content = await readFile27(filepath, "utf-8");
100878
+ const { data, content: docContent } = (0, import_gray_matter14.default)(content);
99940
100879
  return {
99941
100880
  contents: [
99942
100881
  {
@@ -100014,13 +100953,13 @@ async function setupClaudeCode() {
100014
100953
  return false;
100015
100954
  }
100016
100955
  function createProjectMcpJson(projectRoot) {
100017
- const mcpJsonPath = join39(projectRoot, ".mcp.json");
100956
+ const mcpJsonPath = join42(projectRoot, ".mcp.json");
100018
100957
  const mcpConfig = {
100019
100958
  mcpServers: {
100020
100959
  knowns: getMcpConfig()
100021
100960
  }
100022
100961
  };
100023
- if (existsSync34(mcpJsonPath)) {
100962
+ if (existsSync37(mcpJsonPath)) {
100024
100963
  try {
100025
100964
  const existing = JSON.parse(readFileSync3(mcpJsonPath, "utf-8"));
100026
100965
  if (existing?.mcpServers?.knowns) {
@@ -100117,9 +101056,9 @@ function showConfigInfo() {
100117
101056
  }
100118
101057
 
100119
101058
  // src/commands/template.ts
100120
- import { existsSync as existsSync35 } from "node:fs";
100121
- import { mkdir as mkdir19, writeFile as writeFile18 } from "node:fs/promises";
100122
- import { join as join40 } from "node:path";
101059
+ import { existsSync as existsSync38 } from "node:fs";
101060
+ import { mkdir as mkdir19, writeFile as writeFile20 } from "node:fs/promises";
101061
+ import { join as join43 } from "node:path";
100123
101062
  var TEMPLATES_DIR3 = ".knowns/templates";
100124
101063
  function getProjectRoot4() {
100125
101064
  const projectRoot = findProjectRoot();
@@ -100131,11 +101070,11 @@ function getProjectRoot4() {
100131
101070
  return projectRoot;
100132
101071
  }
100133
101072
  function getTemplatesDir2(projectRoot) {
100134
- return join40(projectRoot, TEMPLATES_DIR3);
101073
+ return join43(projectRoot, TEMPLATES_DIR3);
100135
101074
  }
100136
101075
  async function ensureTemplatesDir(projectRoot) {
100137
101076
  const templatesDir = getTemplatesDir2(projectRoot);
100138
- if (!existsSync35(templatesDir)) {
101077
+ if (!existsSync38(templatesDir)) {
100139
101078
  await mkdir19(templatesDir, { recursive: true });
100140
101079
  }
100141
101080
  return templatesDir;
@@ -100163,7 +101102,7 @@ var listCommand4 = new Command("list").description("List available templates (lo
100163
101102
  const templatesWithDesc = [];
100164
101103
  for (const t of templates) {
100165
101104
  try {
100166
- const loaded = await listTemplates(join40(t.path, ".."));
101105
+ const loaded = await listTemplates(join43(t.path, ".."));
100167
101106
  const match2 = loaded.find((l) => l.name === t.name);
100168
101107
  templatesWithDesc.push({
100169
101108
  ref: t.ref,
@@ -100338,8 +101277,8 @@ var createCommand4 = new Command("create").description("Create a new template sc
100338
101277
  try {
100339
101278
  const projectRoot = getProjectRoot4();
100340
101279
  const templatesDir = await ensureTemplatesDir(projectRoot);
100341
- const templateDir = join40(templatesDir, name);
100342
- if (existsSync35(templateDir)) {
101280
+ const templateDir = join43(templatesDir, name);
101281
+ if (existsSync38(templateDir)) {
100343
101282
  console.error(source_default.red(`\u2717 Template "${name}" already exists`));
100344
101283
  process.exit(1);
100345
101284
  }
@@ -100370,7 +101309,7 @@ messages:
100370
101309
  success: |
100371
101310
  \u2713 Created {{name}}!
100372
101311
  `;
100373
- await writeFile18(join40(templateDir, "_template.yaml"), configContent, "utf-8");
101312
+ await writeFile20(join43(templateDir, "_template.yaml"), configContent, "utf-8");
100374
101313
  const exampleTemplate = `/**
100375
101314
  * {{pascalCase name}}
100376
101315
  * Generated from ${name} template
@@ -100380,7 +101319,7 @@ export function {{camelCase name}}() {
100380
101319
  console.log("Hello from {{name}}!");
100381
101320
  }
100382
101321
  `;
100383
- await writeFile18(join40(templateDir, "example.ts.hbs"), exampleTemplate, "utf-8");
101322
+ await writeFile20(join43(templateDir, "example.ts.hbs"), exampleTemplate, "utf-8");
100384
101323
  console.log();
100385
101324
  console.log(source_default.green(`\u2713 Created template: ${name}`));
100386
101325
  console.log(source_default.gray(` Location: ${TEMPLATES_DIR3}/${name}/`));
@@ -100531,9 +101470,9 @@ templateCommand.argument("[name]", "Template name (shorthand for view)").option(
100531
101470
  });
100532
101471
 
100533
101472
  // src/commands/skill.ts
100534
- import { existsSync as existsSync36 } from "node:fs";
100535
- import { mkdir as mkdir20, writeFile as writeFile19 } from "node:fs/promises";
100536
- import { join as join41 } from "node:path";
101473
+ import { existsSync as existsSync39 } from "node:fs";
101474
+ import { mkdir as mkdir20, writeFile as writeFile21 } from "node:fs/promises";
101475
+ import { join as join44 } from "node:path";
100537
101476
  var SKILLS_DIR = ".knowns/skills";
100538
101477
  function getProjectRoot5() {
100539
101478
  const projectRoot = findProjectRoot();
@@ -100545,11 +101484,11 @@ function getProjectRoot5() {
100545
101484
  return projectRoot;
100546
101485
  }
100547
101486
  function getSkillsDir(projectRoot) {
100548
- return join41(projectRoot, SKILLS_DIR);
101487
+ return join44(projectRoot, SKILLS_DIR);
100549
101488
  }
100550
101489
  async function ensureSkillsDir(projectRoot) {
100551
101490
  const skillsDir = getSkillsDir(projectRoot);
100552
- if (!existsSync36(skillsDir)) {
101491
+ if (!existsSync39(skillsDir)) {
100553
101492
  await mkdir20(skillsDir, { recursive: true });
100554
101493
  }
100555
101494
  return skillsDir;
@@ -100559,7 +101498,7 @@ var listCommand5 = new Command("list").description("List available skills").opti
100559
101498
  try {
100560
101499
  const projectRoot = getProjectRoot5();
100561
101500
  const skillsDir = getSkillsDir(projectRoot);
100562
- if (!existsSync36(skillsDir)) {
101501
+ if (!existsSync39(skillsDir)) {
100563
101502
  if (options2.plain) {
100564
101503
  console.log("No skills found");
100565
101504
  } else {
@@ -100603,8 +101542,8 @@ var createCommand5 = new Command("create").description("Create a new skill").arg
100603
101542
  try {
100604
101543
  const projectRoot = getProjectRoot5();
100605
101544
  const skillsDir = await ensureSkillsDir(projectRoot);
100606
- const skillDir = join41(skillsDir, name);
100607
- if (existsSync36(skillDir)) {
101545
+ const skillDir = join44(skillsDir, name);
101546
+ if (existsSync39(skillDir)) {
100608
101547
  console.error(source_default.red(`\u2717 Skill "${name}" already exists`));
100609
101548
  process.exit(1);
100610
101549
  }
@@ -100635,7 +101574,7 @@ Describe when this skill should be used.
100635
101574
  knowns task list
100636
101575
  \`\`\`
100637
101576
  `;
100638
- await writeFile19(join41(skillDir, "SKILL.md"), skillContent, "utf-8");
101577
+ await writeFile21(join44(skillDir, "SKILL.md"), skillContent, "utf-8");
100639
101578
  console.log();
100640
101579
  console.log(source_default.green(`\u2713 Created skill: ${name}`));
100641
101580
  console.log(source_default.gray(` Location: ${SKILLS_DIR}/${name}/SKILL.md`));
@@ -100687,7 +101626,7 @@ var syncCommand2 = new Command("sync").description("Sync skills to AI platforms"
100687
101626
  try {
100688
101627
  const projectRoot = getProjectRoot5();
100689
101628
  const skillsDir = getSkillsDir(projectRoot);
100690
- if (!existsSync36(skillsDir)) {
101629
+ if (!existsSync39(skillsDir)) {
100691
101630
  console.error(source_default.red("\u2717 No skills directory found"));
100692
101631
  console.error(source_default.gray(` Create skills in ${SKILLS_DIR}/`));
100693
101632
  process.exit(1);
@@ -100768,12 +101707,12 @@ var statusCommand2 = new Command("status").description("Check skill sync status"
100768
101707
  try {
100769
101708
  const projectRoot = getProjectRoot5();
100770
101709
  const skillsDir = getSkillsDir(projectRoot);
100771
- const skills = existsSync36(skillsDir) ? await listSkills(skillsDir) : [];
101710
+ const skills = existsSync39(skillsDir) ? await listSkills(skillsDir) : [];
100772
101711
  if (options2.plain) {
100773
101712
  console.log(`Skills: ${skills.length}`);
100774
101713
  for (const platform of PLATFORMS) {
100775
- const targetPath = join41(projectRoot, platform.targetDir);
100776
- const exists = existsSync36(targetPath);
101714
+ const targetPath = join44(projectRoot, platform.targetDir);
101715
+ const exists = existsSync39(targetPath);
100777
101716
  console.log(`${platform.name}: ${exists ? "present" : "not found"}`);
100778
101717
  }
100779
101718
  } else {
@@ -100783,8 +101722,8 @@ var statusCommand2 = new Command("status").description("Check skill sync status"
100783
101722
  console.log(source_default.bold("Platforms:"));
100784
101723
  console.log(source_default.gray("\u2500".repeat(50)));
100785
101724
  for (const platform of PLATFORMS) {
100786
- const targetPath = join41(projectRoot, platform.targetDir);
100787
- const exists = existsSync36(targetPath);
101725
+ const targetPath = join44(projectRoot, platform.targetDir);
101726
+ const exists = existsSync39(targetPath);
100788
101727
  const status = exists ? source_default.green("\u2713 Present") : source_default.gray("\u25CB Not synced");
100789
101728
  console.log(` ${platform.name.padEnd(15)} ${platform.targetDir.padEnd(20)} ${status}`);
100790
101729
  }
@@ -101086,11 +102025,880 @@ importCommand.argument("[source]", "Source to import (shorthand for 'import add'
101086
102025
  }
101087
102026
  });
101088
102027
 
102028
+ // src/commands/validate.ts
102029
+ import { existsSync as existsSync40 } from "node:fs";
102030
+ import { readFile as readFile28, readdir as readdir18, writeFile as writeFile22 } from "node:fs/promises";
102031
+ import { join as join45 } from "node:path";
102032
+ var import_gray_matter15 = __toESM(require_gray_matter(), 1);
102033
+ var import_handlebars3 = __toESM(require_lib2(), 1);
102034
+ init_config();
102035
+ function getFileStore6() {
102036
+ const projectRoot = findProjectRoot();
102037
+ if (!projectRoot) {
102038
+ console.error(source_default.red("\u2717 Not a knowns project"));
102039
+ console.error(source_default.gray(' Run "knowns init" to initialize'));
102040
+ process.exit(1);
102041
+ }
102042
+ return new FileStore(projectRoot);
102043
+ }
102044
+ function getProjectRoot7() {
102045
+ const projectRoot = findProjectRoot();
102046
+ if (!projectRoot) {
102047
+ console.error(source_default.red("\u2717 Not a knowns project"));
102048
+ console.error(source_default.gray(' Run "knowns init" to initialize'));
102049
+ process.exit(1);
102050
+ }
102051
+ return projectRoot;
102052
+ }
102053
+ async function loadValidateConfig2(projectRoot) {
102054
+ const config2 = await readConfig(projectRoot);
102055
+ return config2.validate || {};
102056
+ }
102057
+ function getRuleSeverity2(rule, defaultSeverity, validateConfig) {
102058
+ if (validateConfig.rules?.[rule]) {
102059
+ return validateConfig.rules[rule];
102060
+ }
102061
+ return defaultSeverity;
102062
+ }
102063
+ function shouldIgnore2(entity, validateConfig) {
102064
+ if (!validateConfig.ignore) return false;
102065
+ for (const pattern of validateConfig.ignore) {
102066
+ const regex2 = new RegExp(`^${pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*")}$`);
102067
+ if (regex2.test(entity)) return true;
102068
+ }
102069
+ return false;
102070
+ }
102071
+ async function findSimilarDocs2(projectRoot, brokenRef) {
102072
+ const docsDir = join45(projectRoot, ".knowns", "docs");
102073
+ if (!existsSync40(docsDir)) return null;
102074
+ const allDocs = [];
102075
+ async function scanDir(dir, relativePath) {
102076
+ const entries = await readdir18(dir, { withFileTypes: true });
102077
+ for (const entry of entries) {
102078
+ if (entry.name.startsWith(".")) continue;
102079
+ const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
102080
+ if (entry.isDirectory()) {
102081
+ await scanDir(join45(dir, entry.name), entryRelPath);
102082
+ } else if (entry.name.endsWith(".md")) {
102083
+ allDocs.push(entryRelPath.replace(/\.md$/, ""));
102084
+ }
102085
+ }
102086
+ }
102087
+ await scanDir(docsDir, "");
102088
+ const brokenLower = brokenRef.toLowerCase();
102089
+ let bestMatch = null;
102090
+ let bestScore = 0;
102091
+ for (const doc of allDocs) {
102092
+ const docLower = doc.toLowerCase();
102093
+ const brokenParts = brokenLower.split(/[-_/]/);
102094
+ const docParts = docLower.split(/[-_/]/);
102095
+ let matchScore = 0;
102096
+ for (const part of brokenParts) {
102097
+ if (docLower.includes(part) && part.length > 2) {
102098
+ matchScore += part.length;
102099
+ }
102100
+ }
102101
+ for (const part of docParts) {
102102
+ if (brokenLower.includes(part) && part.length > 2) {
102103
+ matchScore += part.length;
102104
+ }
102105
+ }
102106
+ if (matchScore > bestScore) {
102107
+ bestScore = matchScore;
102108
+ bestMatch = doc;
102109
+ }
102110
+ }
102111
+ return bestScore >= 3 ? bestMatch : null;
102112
+ }
102113
+ function stripTrailingPunctuation2(path3) {
102114
+ return path3.replace(/[.,;:!?`'")\]]+$/, "");
102115
+ }
102116
+ function extractRefs2(content) {
102117
+ const docRefs = [];
102118
+ const taskRefs = [];
102119
+ const templateRefs = [];
102120
+ const docRefPattern = /@docs?\/([^\s,;:!?"'()\]]+)/g;
102121
+ for (const match2 of content.matchAll(docRefPattern)) {
102122
+ let docPath = stripTrailingPunctuation2(match2[1] || "");
102123
+ docPath = docPath.replace(/\.md$/, "");
102124
+ if (docPath && !docRefs.includes(docPath)) {
102125
+ docRefs.push(docPath);
102126
+ }
102127
+ }
102128
+ const taskRefPattern = /@task-([a-zA-Z0-9]+)/g;
102129
+ for (const match2 of content.matchAll(taskRefPattern)) {
102130
+ const taskId = match2[1] || "";
102131
+ if (taskId && !taskRefs.includes(taskId)) {
102132
+ taskRefs.push(taskId);
102133
+ }
102134
+ }
102135
+ const templateRefPattern = /@template\/([^\s,;:!?"'()\]]+)/g;
102136
+ for (const match2 of content.matchAll(templateRefPattern)) {
102137
+ const templateName = stripTrailingPunctuation2(match2[1] || "");
102138
+ if (templateName && !templateRefs.includes(templateName)) {
102139
+ templateRefs.push(templateName);
102140
+ }
102141
+ }
102142
+ return { docRefs, taskRefs, templateRefs };
102143
+ }
102144
+ async function validateTasks2(projectRoot, fileStore, validateConfig) {
102145
+ const issues = [];
102146
+ const tasks = await fileStore.getAllTasks();
102147
+ const taskIds = new Set(tasks.map((t) => t.id));
102148
+ for (const task of tasks) {
102149
+ const taskRef = `task-${task.id}`;
102150
+ if (shouldIgnore2(taskRef, validateConfig)) continue;
102151
+ const content = `${task.description || ""} ${task.implementationPlan || ""} ${task.implementationNotes || ""}`;
102152
+ const { docRefs, taskRefs, templateRefs } = extractRefs2(content);
102153
+ const noAcSeverity = getRuleSeverity2("task-no-ac", "warning", validateConfig);
102154
+ if (noAcSeverity !== "off" && (!task.acceptanceCriteria || task.acceptanceCriteria.length === 0)) {
102155
+ issues.push({
102156
+ entity: taskRef,
102157
+ entityType: "task",
102158
+ rule: "task-no-ac",
102159
+ severity: noAcSeverity,
102160
+ message: "Task has no acceptance criteria"
102161
+ });
102162
+ }
102163
+ const noDescSeverity = getRuleSeverity2("task-no-description", "warning", validateConfig);
102164
+ if (noDescSeverity !== "off" && (!task.description || task.description.trim() === "")) {
102165
+ issues.push({
102166
+ entity: taskRef,
102167
+ entityType: "task",
102168
+ rule: "task-no-description",
102169
+ severity: noDescSeverity,
102170
+ message: "Task has no description"
102171
+ });
102172
+ }
102173
+ const brokenDocSeverity = getRuleSeverity2("task-broken-doc-ref", "error", validateConfig);
102174
+ if (brokenDocSeverity !== "off") {
102175
+ for (const docPath of docRefs) {
102176
+ const resolved = await resolveDoc(projectRoot, docPath);
102177
+ if (!resolved) {
102178
+ const suggestion = await findSimilarDocs2(projectRoot, docPath);
102179
+ const issue2 = {
102180
+ entity: taskRef,
102181
+ entityType: "task",
102182
+ rule: "task-broken-doc-ref",
102183
+ severity: brokenDocSeverity,
102184
+ message: suggestion ? `Broken reference: @doc/${docPath} \u2192 did you mean @doc/${suggestion}?` : `Broken reference: @doc/${docPath}`,
102185
+ fixable: !!suggestion
102186
+ };
102187
+ if (suggestion) {
102188
+ issue2.fix = async () => {
102189
+ const tasksDir = join45(projectRoot, ".knowns", "tasks");
102190
+ const files = await readdir18(tasksDir);
102191
+ const taskFile = files.find((f) => f.startsWith(`task-${task.id} `));
102192
+ if (taskFile) {
102193
+ const taskFilePath = join45(tasksDir, taskFile);
102194
+ const taskContent = await readFile28(taskFilePath, "utf-8");
102195
+ const updated = taskContent.replace(
102196
+ new RegExp(`@docs?/${docPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"),
102197
+ `@doc/${suggestion}`
102198
+ );
102199
+ await writeFile22(taskFilePath, updated, "utf-8");
102200
+ }
102201
+ };
102202
+ }
102203
+ issues.push(issue2);
102204
+ }
102205
+ }
102206
+ }
102207
+ const brokenTaskSeverity = getRuleSeverity2("task-broken-task-ref", "error", validateConfig);
102208
+ if (brokenTaskSeverity !== "off") {
102209
+ for (const refTaskId of taskRefs) {
102210
+ if (!taskIds.has(refTaskId)) {
102211
+ issues.push({
102212
+ entity: taskRef,
102213
+ entityType: "task",
102214
+ rule: "task-broken-task-ref",
102215
+ severity: brokenTaskSeverity,
102216
+ message: `Broken reference: @task-${refTaskId}`
102217
+ });
102218
+ }
102219
+ }
102220
+ }
102221
+ const brokenTplSeverity = getRuleSeverity2("task-broken-template-ref", "error", validateConfig);
102222
+ if (brokenTplSeverity !== "off") {
102223
+ for (const templateName of templateRefs) {
102224
+ const resolved = await resolveTemplate(projectRoot, templateName);
102225
+ if (!resolved) {
102226
+ issues.push({
102227
+ entity: taskRef,
102228
+ entityType: "task",
102229
+ rule: "task-broken-template-ref",
102230
+ severity: brokenTplSeverity,
102231
+ message: `Broken reference: @template/${templateName}`
102232
+ });
102233
+ }
102234
+ }
102235
+ }
102236
+ const selfRefSeverity = getRuleSeverity2("task-self-ref", "warning", validateConfig);
102237
+ if (selfRefSeverity !== "off" && taskRefs.includes(task.id)) {
102238
+ issues.push({
102239
+ entity: taskRef,
102240
+ entityType: "task",
102241
+ rule: "task-self-ref",
102242
+ severity: selfRefSeverity,
102243
+ message: "Task references itself"
102244
+ });
102245
+ }
102246
+ const circularSeverity = getRuleSeverity2("task-circular-parent", "error", validateConfig);
102247
+ if (circularSeverity !== "off" && task.parent) {
102248
+ const visited = /* @__PURE__ */ new Set();
102249
+ let currentId = task.parent;
102250
+ while (currentId) {
102251
+ if (visited.has(currentId)) {
102252
+ issues.push({
102253
+ entity: taskRef,
102254
+ entityType: "task",
102255
+ rule: "task-circular-parent",
102256
+ severity: circularSeverity,
102257
+ message: "Circular parent-child relationship detected"
102258
+ });
102259
+ break;
102260
+ }
102261
+ if (currentId === task.id) {
102262
+ issues.push({
102263
+ entity: taskRef,
102264
+ entityType: "task",
102265
+ rule: "task-circular-parent",
102266
+ severity: circularSeverity,
102267
+ message: "Task is its own ancestor"
102268
+ });
102269
+ break;
102270
+ }
102271
+ visited.add(currentId);
102272
+ const parentTask = tasks.find((t) => t.id === currentId);
102273
+ currentId = parentTask?.parent;
102274
+ }
102275
+ }
102276
+ }
102277
+ return issues;
102278
+ }
102279
+ async function validateDocs2(projectRoot, fileStore, validateConfig) {
102280
+ const issues = [];
102281
+ const docsDir = join45(projectRoot, ".knowns", "docs");
102282
+ if (!existsSync40(docsDir)) {
102283
+ return issues;
102284
+ }
102285
+ const tasks = await fileStore.getAllTasks();
102286
+ const taskIds = new Set(tasks.map((t) => t.id));
102287
+ const referencedDocs = /* @__PURE__ */ new Set();
102288
+ for (const task of tasks) {
102289
+ const content = `${task.description || ""} ${task.implementationPlan || ""} ${task.implementationNotes || ""}`;
102290
+ const { docRefs } = extractRefs2(content);
102291
+ for (const ref of docRefs) {
102292
+ referencedDocs.add(ref.toLowerCase());
102293
+ }
102294
+ }
102295
+ async function scanDir(dir, relativePath) {
102296
+ const entries = await readdir18(dir, { withFileTypes: true });
102297
+ for (const entry of entries) {
102298
+ if (entry.name.startsWith(".")) continue;
102299
+ const fullPath = join45(dir, entry.name);
102300
+ const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
102301
+ if (entry.isDirectory()) {
102302
+ await scanDir(fullPath, entryRelPath);
102303
+ } else if (entry.name.endsWith(".md")) {
102304
+ const docPath = entryRelPath.replace(/\.md$/, "");
102305
+ const docRef = `docs/${docPath}`;
102306
+ if (shouldIgnore2(docRef, validateConfig)) continue;
102307
+ if (shouldIgnore2(docPath, validateConfig)) continue;
102308
+ try {
102309
+ const content = await readFile28(fullPath, "utf-8");
102310
+ const { data, content: docContent } = (0, import_gray_matter15.default)(content);
102311
+ const metadata = data;
102312
+ const noDescSeverity = getRuleSeverity2("doc-no-description", "warning", validateConfig);
102313
+ if (noDescSeverity !== "off" && (!metadata.description || metadata.description.trim() === "")) {
102314
+ issues.push({
102315
+ entity: docRef,
102316
+ entityType: "doc",
102317
+ rule: "doc-no-description",
102318
+ severity: noDescSeverity,
102319
+ message: "Doc has no description"
102320
+ });
102321
+ }
102322
+ const orphanSeverity = getRuleSeverity2("doc-orphan", "info", validateConfig);
102323
+ if (orphanSeverity !== "off" && !referencedDocs.has(docPath.toLowerCase())) {
102324
+ issues.push({
102325
+ entity: docRef,
102326
+ entityType: "doc",
102327
+ rule: "doc-orphan",
102328
+ severity: orphanSeverity,
102329
+ message: "Doc is not referenced by any task"
102330
+ });
102331
+ }
102332
+ const { docRefs, taskRefs } = extractRefs2(docContent);
102333
+ const brokenDocSeverity = getRuleSeverity2("doc-broken-doc-ref", "error", validateConfig);
102334
+ if (brokenDocSeverity !== "off") {
102335
+ for (const refDocPath of docRefs) {
102336
+ const resolved = await resolveDoc(projectRoot, refDocPath);
102337
+ if (!resolved) {
102338
+ const suggestion = await findSimilarDocs2(projectRoot, refDocPath);
102339
+ const issue2 = {
102340
+ entity: docRef,
102341
+ entityType: "doc",
102342
+ rule: "doc-broken-doc-ref",
102343
+ severity: brokenDocSeverity,
102344
+ message: suggestion ? `Broken reference: @doc/${refDocPath} \u2192 did you mean @doc/${suggestion}?` : `Broken reference: @doc/${refDocPath}`,
102345
+ fixable: !!suggestion
102346
+ };
102347
+ if (suggestion) {
102348
+ issue2.fix = async () => {
102349
+ const docFileContent = await readFile28(fullPath, "utf-8");
102350
+ const updated = docFileContent.replace(
102351
+ new RegExp(`@docs?/${refDocPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"),
102352
+ `@doc/${suggestion}`
102353
+ );
102354
+ await writeFile22(fullPath, updated, "utf-8");
102355
+ };
102356
+ }
102357
+ issues.push(issue2);
102358
+ }
102359
+ }
102360
+ }
102361
+ const brokenTaskSeverity = getRuleSeverity2("doc-broken-task-ref", "error", validateConfig);
102362
+ if (brokenTaskSeverity !== "off") {
102363
+ for (const refTaskId of taskRefs) {
102364
+ if (!taskIds.has(refTaskId)) {
102365
+ const issue2 = {
102366
+ entity: docRef,
102367
+ entityType: "doc",
102368
+ rule: "doc-broken-task-ref",
102369
+ severity: brokenTaskSeverity,
102370
+ message: `Broken reference: @task-${refTaskId}`,
102371
+ fixable: true
102372
+ };
102373
+ issue2.fix = async () => {
102374
+ const docFileContent = await readFile28(fullPath, "utf-8");
102375
+ const updated = docFileContent.replace(
102376
+ new RegExp(`@task-${refTaskId}\\b`, "g"),
102377
+ `~task-${refTaskId}`
102378
+ );
102379
+ await writeFile22(fullPath, updated, "utf-8");
102380
+ };
102381
+ issues.push(issue2);
102382
+ }
102383
+ }
102384
+ }
102385
+ } catch {
102386
+ }
102387
+ }
102388
+ }
102389
+ }
102390
+ await scanDir(docsDir, "");
102391
+ return issues;
102392
+ }
102393
+ async function validateTemplates2(projectRoot, validateConfig) {
102394
+ const issues = [];
102395
+ const templates = await listAllTemplates(projectRoot);
102396
+ for (const template of templates) {
102397
+ const templateRef = `templates/${template.ref}`;
102398
+ if (shouldIgnore2(templateRef, validateConfig)) continue;
102399
+ try {
102400
+ const config2 = await getTemplateConfig(template.path);
102401
+ const invalidSyntaxSeverity = getRuleSeverity2("template-invalid-syntax", "error", validateConfig);
102402
+ if (invalidSyntaxSeverity !== "off" && !config2) {
102403
+ issues.push({
102404
+ entity: templateRef,
102405
+ entityType: "template",
102406
+ rule: "template-invalid-syntax",
102407
+ severity: invalidSyntaxSeverity,
102408
+ message: "Failed to load template config (invalid or missing _template.yaml)"
102409
+ });
102410
+ continue;
102411
+ }
102412
+ if (!config2) continue;
102413
+ const brokenDocSeverity = getRuleSeverity2("template-broken-doc-ref", "error", validateConfig);
102414
+ if (brokenDocSeverity !== "off" && config2.doc) {
102415
+ const resolved = await resolveDoc(projectRoot, config2.doc);
102416
+ if (!resolved) {
102417
+ issues.push({
102418
+ entity: templateRef,
102419
+ entityType: "template",
102420
+ rule: "template-broken-doc-ref",
102421
+ severity: brokenDocSeverity,
102422
+ message: `Broken doc reference: @doc/${config2.doc}`
102423
+ });
102424
+ }
102425
+ }
102426
+ if (invalidSyntaxSeverity !== "off") {
102427
+ for (const action of config2.actions || []) {
102428
+ if (action.type === "add" && action.template) {
102429
+ const templateFilePath = join45(template.path, action.template);
102430
+ if (existsSync40(templateFilePath)) {
102431
+ try {
102432
+ const templateContent = await readFile28(templateFilePath, "utf-8");
102433
+ import_handlebars3.default.compile(templateContent);
102434
+ } catch (err) {
102435
+ issues.push({
102436
+ entity: templateRef,
102437
+ entityType: "template",
102438
+ rule: "template-invalid-syntax",
102439
+ severity: invalidSyntaxSeverity,
102440
+ message: `Invalid Handlebars syntax in ${action.template}: ${err instanceof Error ? err.message : "unknown error"}`
102441
+ });
102442
+ }
102443
+ }
102444
+ }
102445
+ }
102446
+ }
102447
+ const missingPartialSeverity = getRuleSeverity2("template-missing-partial", "error", validateConfig);
102448
+ if (missingPartialSeverity !== "off") {
102449
+ const templatesDir = template.path;
102450
+ if (existsSync40(templatesDir)) {
102451
+ const files = await readdir18(templatesDir);
102452
+ const hbsFiles = files.filter((f) => f.endsWith(".hbs"));
102453
+ for (const hbsFile of hbsFiles) {
102454
+ const content = await readFile28(join45(templatesDir, hbsFile), "utf-8");
102455
+ const partialPattern = /\{\{>\s*([^\s}]+)\s*\}\}/g;
102456
+ for (const match2 of content.matchAll(partialPattern)) {
102457
+ const partialName = match2[1];
102458
+ const partialPath = join45(templatesDir, `_${partialName}.hbs`);
102459
+ if (!existsSync40(partialPath)) {
102460
+ issues.push({
102461
+ entity: templateRef,
102462
+ entityType: "template",
102463
+ rule: "template-missing-partial",
102464
+ severity: missingPartialSeverity,
102465
+ message: `Missing partial: ${partialName} (expected at _${partialName}.hbs)`
102466
+ });
102467
+ }
102468
+ }
102469
+ }
102470
+ }
102471
+ }
102472
+ } catch (err) {
102473
+ const invalidSyntaxSeverity = getRuleSeverity2("template-invalid-syntax", "error", validateConfig);
102474
+ if (invalidSyntaxSeverity !== "off") {
102475
+ issues.push({
102476
+ entity: templateRef,
102477
+ entityType: "template",
102478
+ rule: "template-invalid-syntax",
102479
+ severity: invalidSyntaxSeverity,
102480
+ message: `Failed to load template config: ${err instanceof Error ? err.message : "unknown error"}`
102481
+ });
102482
+ }
102483
+ }
102484
+ }
102485
+ return issues;
102486
+ }
102487
+ async function runSDDValidation2(projectRoot, fileStore) {
102488
+ const tasks = await fileStore.getAllTasks();
102489
+ const docsDir = join45(projectRoot, ".knowns", "docs");
102490
+ const stats = {
102491
+ specs: { total: 0, approved: 0, draft: 0 },
102492
+ tasks: { total: tasks.length, done: 0, inProgress: 0, todo: 0, withSpec: 0, withoutSpec: 0 },
102493
+ coverage: { linked: 0, total: tasks.length, percent: 0 },
102494
+ acCompletion: /* @__PURE__ */ new Map()
102495
+ };
102496
+ const warnings = [];
102497
+ const passed = [];
102498
+ for (const task of tasks) {
102499
+ if (task.status === "done") stats.tasks.done++;
102500
+ else if (task.status === "in-progress") stats.tasks.inProgress++;
102501
+ else stats.tasks.todo++;
102502
+ if (task.spec) {
102503
+ stats.tasks.withSpec++;
102504
+ } else {
102505
+ stats.tasks.withoutSpec++;
102506
+ warnings.push({
102507
+ type: "task-no-spec",
102508
+ entity: `task-${task.id}`,
102509
+ message: `${task.title} has no spec reference`
102510
+ });
102511
+ }
102512
+ }
102513
+ stats.coverage.linked = stats.tasks.withSpec;
102514
+ stats.coverage.percent = stats.tasks.total > 0 ? Math.round(stats.tasks.withSpec / stats.tasks.total * 100) : 0;
102515
+ const specsDir = join45(docsDir, "specs");
102516
+ if (existsSync40(specsDir)) {
102517
+ async function scanSpecs(dir, relativePath) {
102518
+ const entries = await readdir18(dir, { withFileTypes: true });
102519
+ for (const entry of entries) {
102520
+ if (entry.name.startsWith(".")) continue;
102521
+ const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
102522
+ if (entry.isDirectory()) {
102523
+ await scanSpecs(join45(dir, entry.name), entryRelPath);
102524
+ } else if (entry.name.endsWith(".md")) {
102525
+ stats.specs.total++;
102526
+ const specPath = `specs/${entryRelPath.replace(/\.md$/, "")}`;
102527
+ try {
102528
+ const content = await readFile28(join45(dir, entry.name), "utf-8");
102529
+ const { data } = (0, import_gray_matter15.default)(content);
102530
+ if (data.status === "approved" || data.status === "implemented") {
102531
+ stats.specs.approved++;
102532
+ } else {
102533
+ stats.specs.draft++;
102534
+ }
102535
+ const linkedTasks = tasks.filter((t) => t.spec === specPath);
102536
+ if (linkedTasks.length > 0) {
102537
+ let totalAC = 0;
102538
+ let completedAC = 0;
102539
+ for (const task of linkedTasks) {
102540
+ totalAC += task.acceptanceCriteria.length;
102541
+ completedAC += task.acceptanceCriteria.filter((ac) => ac.completed).length;
102542
+ }
102543
+ const percent = totalAC > 0 ? Math.round(completedAC / totalAC * 100) : 100;
102544
+ stats.acCompletion.set(specPath, { total: totalAC, completed: completedAC, percent });
102545
+ if (percent < 100 && totalAC > 0) {
102546
+ warnings.push({
102547
+ type: "spec-ac-incomplete",
102548
+ entity: specPath,
102549
+ message: `${completedAC}/${totalAC} ACs complete (${percent}%)`
102550
+ });
102551
+ }
102552
+ }
102553
+ } catch {
102554
+ }
102555
+ }
102556
+ }
102557
+ }
102558
+ await scanSpecs(specsDir, "");
102559
+ }
102560
+ for (const task of tasks) {
102561
+ if (task.spec) {
102562
+ const specDocPath = join45(docsDir, `${task.spec}.md`);
102563
+ if (!existsSync40(specDocPath)) {
102564
+ warnings.push({
102565
+ type: "spec-broken-link",
102566
+ entity: `task-${task.id}`,
102567
+ message: `Broken spec reference: @doc/${task.spec}`
102568
+ });
102569
+ }
102570
+ }
102571
+ }
102572
+ if (warnings.filter((w) => w.type === "spec-broken-link").length === 0) {
102573
+ passed.push("All spec references resolve");
102574
+ }
102575
+ for (const [specPath, completion] of stats.acCompletion) {
102576
+ if (completion.percent === 100) {
102577
+ passed.push(`${specPath}: fully implemented`);
102578
+ }
102579
+ }
102580
+ return { stats, warnings, passed };
102581
+ }
102582
+ function formatSDDReport(result, plain) {
102583
+ const { stats, warnings, passed } = result;
102584
+ if (plain) {
102585
+ console.log("\nSDD Status Report");
102586
+ console.log("=".repeat(50));
102587
+ console.log(`Specs: ${stats.specs.total} total | ${stats.specs.approved} approved | ${stats.specs.draft} draft`);
102588
+ console.log(
102589
+ `Tasks: ${stats.tasks.total} total | ${stats.tasks.done} done | ${stats.tasks.inProgress} in-progress | ${stats.tasks.todo} todo`
102590
+ );
102591
+ console.log(
102592
+ `Coverage: ${stats.coverage.linked}/${stats.coverage.total} tasks linked to specs (${stats.coverage.percent}%)`
102593
+ );
102594
+ if (warnings.length > 0) {
102595
+ console.log("\nWarnings:");
102596
+ for (const w of warnings) {
102597
+ console.log(` - ${w.entity}: ${w.message}`);
102598
+ }
102599
+ }
102600
+ if (passed.length > 0) {
102601
+ console.log("\nPassed:");
102602
+ for (const p of passed) {
102603
+ console.log(` - ${p}`);
102604
+ }
102605
+ }
102606
+ } else {
102607
+ console.log(source_default.bold("\n\u{1F4CB} SDD Status Report"));
102608
+ console.log(source_default.gray("\u2550".repeat(50)));
102609
+ console.log(
102610
+ `${source_default.gray("Specs:")} ${stats.specs.total} total | ${source_default.green(`${stats.specs.approved} approved`)} | ${source_default.yellow(`${stats.specs.draft} draft`)}`
102611
+ );
102612
+ console.log(
102613
+ `${source_default.gray("Tasks:")} ${stats.tasks.total} total | ${source_default.green(`${stats.tasks.done} done`)} | ${source_default.yellow(`${stats.tasks.inProgress} in-progress`)} | ${source_default.gray(`${stats.tasks.todo} todo`)}`
102614
+ );
102615
+ console.log(
102616
+ `${source_default.gray("Coverage:")} ${stats.coverage.linked}/${stats.coverage.total} tasks linked to specs (${stats.coverage.percent >= 75 ? source_default.green(`${stats.coverage.percent}%`) : source_default.yellow(`${stats.coverage.percent}%`)})`
102617
+ );
102618
+ if (warnings.length > 0) {
102619
+ console.log(source_default.bold.yellow("\n\u26A0\uFE0F Warnings:"));
102620
+ for (const w of warnings) {
102621
+ console.log(` ${source_default.yellow("\u2022")} ${source_default.bold(w.entity)}: ${w.message}`);
102622
+ }
102623
+ }
102624
+ if (passed.length > 0) {
102625
+ console.log(source_default.bold.green("\n\u2705 Passed:"));
102626
+ for (const p of passed) {
102627
+ console.log(` ${source_default.green("\u2022")} ${p}`);
102628
+ }
102629
+ }
102630
+ }
102631
+ }
102632
+ async function runValidation(options2) {
102633
+ const projectRoot = getProjectRoot7();
102634
+ const fileStore = getFileStore6();
102635
+ const validateConfig = await loadValidateConfig2(projectRoot);
102636
+ const allIssues = [];
102637
+ const stats = { tasks: 0, docs: 0, templates: 0 };
102638
+ if (!options2.type || options2.type === "task") {
102639
+ const tasks = await fileStore.getAllTasks();
102640
+ stats.tasks = tasks.length;
102641
+ const taskIssues = await validateTasks2(projectRoot, fileStore, validateConfig);
102642
+ allIssues.push(...taskIssues);
102643
+ }
102644
+ if (!options2.type || options2.type === "doc") {
102645
+ const docsDir = join45(projectRoot, ".knowns", "docs");
102646
+ if (existsSync40(docsDir)) {
102647
+ async function countDocs(dir) {
102648
+ let count = 0;
102649
+ const entries = await readdir18(dir, { withFileTypes: true });
102650
+ for (const entry of entries) {
102651
+ if (entry.name.startsWith(".")) continue;
102652
+ if (entry.isDirectory()) {
102653
+ count += await countDocs(join45(dir, entry.name));
102654
+ } else if (entry.name.endsWith(".md")) {
102655
+ count++;
102656
+ }
102657
+ }
102658
+ return count;
102659
+ }
102660
+ stats.docs = await countDocs(docsDir);
102661
+ }
102662
+ const docIssues = await validateDocs2(projectRoot, fileStore, validateConfig);
102663
+ allIssues.push(...docIssues);
102664
+ }
102665
+ if (!options2.type || options2.type === "template") {
102666
+ const templates = await listAllTemplates(projectRoot);
102667
+ stats.templates = templates.length;
102668
+ const templateIssues = await validateTemplates2(projectRoot, validateConfig);
102669
+ allIssues.push(...templateIssues);
102670
+ }
102671
+ if (options2.strict) {
102672
+ for (const issue2 of allIssues) {
102673
+ if (issue2.severity === "warning") {
102674
+ issue2.severity = "error";
102675
+ }
102676
+ }
102677
+ }
102678
+ return { issues: allIssues, stats };
102679
+ }
102680
+ async function applyFixes2(issues) {
102681
+ const results = [];
102682
+ const fixableIssues = issues.filter((i) => i.fixable && i.fix);
102683
+ for (const issue2 of fixableIssues) {
102684
+ try {
102685
+ await issue2.fix?.();
102686
+ results.push({
102687
+ entity: issue2.entity,
102688
+ rule: issue2.rule,
102689
+ action: issue2.message,
102690
+ success: true
102691
+ });
102692
+ } catch (err) {
102693
+ results.push({
102694
+ entity: issue2.entity,
102695
+ rule: issue2.rule,
102696
+ action: `Failed: ${err instanceof Error ? err.message : "unknown error"}`,
102697
+ success: false
102698
+ });
102699
+ }
102700
+ }
102701
+ return results;
102702
+ }
102703
+ function formatIssues(issues, plain) {
102704
+ const errors = issues.filter((i) => i.severity === "error");
102705
+ const warnings = issues.filter((i) => i.severity === "warning");
102706
+ const infos = issues.filter((i) => i.severity === "info");
102707
+ if (plain) {
102708
+ if (errors.length > 0) {
102709
+ console.log("\nErrors:");
102710
+ for (const issue2 of errors) {
102711
+ console.log(` ${issue2.entity}: [${issue2.rule}] ${issue2.message}`);
102712
+ }
102713
+ }
102714
+ if (warnings.length > 0) {
102715
+ console.log("\nWarnings:");
102716
+ for (const issue2 of warnings) {
102717
+ console.log(` ${issue2.entity}: [${issue2.rule}] ${issue2.message}`);
102718
+ }
102719
+ }
102720
+ if (infos.length > 0) {
102721
+ console.log("\nInfo:");
102722
+ for (const issue2 of infos) {
102723
+ console.log(` ${issue2.entity}: [${issue2.rule}] ${issue2.message}`);
102724
+ }
102725
+ }
102726
+ } else {
102727
+ if (errors.length > 0) {
102728
+ console.log(source_default.bold.red("\n\u2717 Errors:"));
102729
+ for (const issue2 of errors) {
102730
+ console.log(
102731
+ ` ${source_default.red("\u2022")} ${source_default.bold(issue2.entity)}: ${source_default.gray(`[${issue2.rule}]`)} ${issue2.message}`
102732
+ );
102733
+ }
102734
+ }
102735
+ if (warnings.length > 0) {
102736
+ console.log(source_default.bold.yellow("\n\u26A0 Warnings:"));
102737
+ for (const issue2 of warnings) {
102738
+ console.log(
102739
+ ` ${source_default.yellow("\u2022")} ${source_default.bold(issue2.entity)}: ${source_default.gray(`[${issue2.rule}]`)} ${issue2.message}`
102740
+ );
102741
+ }
102742
+ }
102743
+ if (infos.length > 0) {
102744
+ console.log(source_default.bold.blue("\n\u2139 Info:"));
102745
+ for (const issue2 of infos) {
102746
+ console.log(
102747
+ ` ${source_default.blue("\u2022")} ${source_default.bold(issue2.entity)}: ${source_default.gray(`[${issue2.rule}]`)} ${issue2.message}`
102748
+ );
102749
+ }
102750
+ }
102751
+ }
102752
+ }
102753
+ var validateCommand3 = new Command("validate").description("Validate tasks, docs, and templates for quality and reference integrity").option("--type <type>", "Entity type to validate: task, doc, template").option("--strict", "Treat warnings as errors").option("--json", "Output results as JSON").option("--fix", "Auto-fix supported issues (doc renames, stale refs)").option("--sdd", "Run SDD (Spec-Driven Development) validation checks").option("--plain", "Plain text output for AI").action(
102754
+ async (options2) => {
102755
+ try {
102756
+ if (options2.type && !["task", "doc", "template"].includes(options2.type)) {
102757
+ console.error(source_default.red(`\u2717 Invalid type: ${options2.type}`));
102758
+ console.error(source_default.gray(" Valid types: task, doc, template"));
102759
+ process.exit(1);
102760
+ }
102761
+ const projectRoot = getProjectRoot7();
102762
+ const fileStore = getFileStore6();
102763
+ const startTime = Date.now();
102764
+ if (options2.sdd) {
102765
+ const sddResult = await runSDDValidation2(projectRoot, fileStore);
102766
+ const elapsed2 = Date.now() - startTime;
102767
+ if (options2.json) {
102768
+ const jsonOutput = {
102769
+ mode: "sdd",
102770
+ stats: {
102771
+ specs: sddResult.stats.specs,
102772
+ tasks: sddResult.stats.tasks,
102773
+ coverage: sddResult.stats.coverage,
102774
+ acCompletion: Object.fromEntries(sddResult.stats.acCompletion)
102775
+ },
102776
+ warnings: sddResult.warnings,
102777
+ passed: sddResult.passed,
102778
+ elapsed: elapsed2
102779
+ };
102780
+ console.log(JSON.stringify(jsonOutput, null, 2));
102781
+ } else {
102782
+ formatSDDReport(sddResult, !!options2.plain);
102783
+ console.log(options2.plain ? `
102784
+ Time: ${elapsed2}ms` : source_default.gray(`
102785
+ Time: ${elapsed2}ms`));
102786
+ }
102787
+ process.exit(0);
102788
+ }
102789
+ const result = await runValidation({
102790
+ type: options2.type,
102791
+ strict: options2.strict
102792
+ });
102793
+ const elapsed = Date.now() - startTime;
102794
+ let fixResults = [];
102795
+ if (options2.fix) {
102796
+ fixResults = await applyFixes2(result.issues);
102797
+ }
102798
+ const errors = result.issues.filter((i) => i.severity === "error");
102799
+ const warnings = result.issues.filter((i) => i.severity === "warning");
102800
+ const fixableCount = result.issues.filter((i) => i.fixable).length;
102801
+ if (options2.json) {
102802
+ const jsonOutput = {
102803
+ valid: errors.length === 0,
102804
+ stats: result.stats,
102805
+ summary: {
102806
+ errors: errors.length,
102807
+ warnings: warnings.length,
102808
+ info: result.issues.filter((i) => i.severity === "info").length
102809
+ },
102810
+ issues: result.issues.map((i) => ({
102811
+ entity: i.entity,
102812
+ entityType: i.entityType,
102813
+ rule: i.rule,
102814
+ severity: i.severity,
102815
+ message: i.message,
102816
+ fixable: i.fixable || false
102817
+ })),
102818
+ elapsed,
102819
+ ...options2.fix && fixResults.length > 0 ? { fixes: fixResults } : {}
102820
+ };
102821
+ console.log(JSON.stringify(jsonOutput, null, 2));
102822
+ process.exit(errors.length > 0 ? 1 : 0);
102823
+ }
102824
+ const isPlain = options2.plain;
102825
+ if (isPlain) {
102826
+ console.log("Validating...");
102827
+ console.log(` Tasks: ${result.stats.tasks} checked`);
102828
+ console.log(` Docs: ${result.stats.docs} checked`);
102829
+ console.log(` Templates: ${result.stats.templates} checked`);
102830
+ console.log(` Time: ${elapsed}ms`);
102831
+ } else {
102832
+ console.log(source_default.bold("\n\u{1F50D} Validating..."));
102833
+ console.log(` ${source_default.gray("Tasks:")} ${result.stats.tasks} checked`);
102834
+ console.log(` ${source_default.gray("Docs:")} ${result.stats.docs} checked`);
102835
+ console.log(` ${source_default.gray("Templates:")} ${result.stats.templates} checked`);
102836
+ console.log(` ${source_default.gray("Time:")} ${elapsed}ms`);
102837
+ }
102838
+ if (options2.fix && fixResults.length > 0) {
102839
+ if (isPlain) {
102840
+ console.log(`
102841
+ Fixed ${fixResults.filter((r) => r.success).length} issue(s):`);
102842
+ for (const fix of fixResults) {
102843
+ console.log(` ${fix.success ? "\u2713" : "\u2717"} ${fix.entity}: ${fix.action}`);
102844
+ }
102845
+ } else {
102846
+ console.log(source_default.bold.green(`
102847
+ \u2713 Fixed ${fixResults.filter((r) => r.success).length} issue(s):`));
102848
+ for (const fix of fixResults) {
102849
+ if (fix.success) {
102850
+ console.log(` ${source_default.green("\u2713")} ${source_default.bold(fix.entity)}: ${fix.action}`);
102851
+ } else {
102852
+ console.log(` ${source_default.red("\u2717")} ${source_default.bold(fix.entity)}: ${fix.action}`);
102853
+ }
102854
+ }
102855
+ }
102856
+ }
102857
+ if (result.issues.length === 0) {
102858
+ if (isPlain) {
102859
+ console.log("\n\u2713 All validations passed");
102860
+ } else {
102861
+ console.log(source_default.green("\n\u2713 All validations passed"));
102862
+ }
102863
+ process.exit(0);
102864
+ }
102865
+ if (isPlain) {
102866
+ console.log(`
102867
+ ${errors.length} error(s), ${warnings.length} warning(s)`);
102868
+ if (fixableCount > 0 && !options2.fix) {
102869
+ console.log(` ${fixableCount} issue(s) can be auto-fixed with --fix`);
102870
+ }
102871
+ } else {
102872
+ const summaryParts = [];
102873
+ if (errors.length > 0) {
102874
+ summaryParts.push(source_default.red(`${errors.length} error(s)`));
102875
+ }
102876
+ if (warnings.length > 0) {
102877
+ summaryParts.push(source_default.yellow(`${warnings.length} warning(s)`));
102878
+ }
102879
+ console.log(`
102880
+ ${summaryParts.join(", ")}`);
102881
+ if (fixableCount > 0 && !options2.fix) {
102882
+ console.log(source_default.gray(` ${fixableCount} issue(s) can be auto-fixed with --fix`));
102883
+ }
102884
+ }
102885
+ formatIssues(result.issues, !!isPlain);
102886
+ process.exit(errors.length > 0 ? 1 : 0);
102887
+ } catch (error48) {
102888
+ console.error(source_default.red("\u2717 Validation failed"));
102889
+ if (error48 instanceof Error) {
102890
+ console.error(source_default.red(` ${error48.message}`));
102891
+ }
102892
+ process.exit(1);
102893
+ }
102894
+ }
102895
+ );
102896
+
101089
102897
  // src/utils/update-notifier.ts
101090
- import { existsSync as existsSync37, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
102898
+ import { existsSync as existsSync41, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
101091
102899
  import { mkdir as mkdir21 } from "node:fs/promises";
101092
102900
  import { homedir as homedir3 } from "node:os";
101093
- import { dirname as dirname8, join as join42 } from "node:path";
102901
+ import { dirname as dirname8, join as join46 } from "node:path";
101094
102902
  var DEFAULT_TTL_MS = 60 * 60 * 1e3;
101095
102903
  var DEFAULT_TIMEOUT_MS = 2e3;
101096
102904
  var hasNotifiedThisProcess = false;
@@ -101104,10 +102912,10 @@ function detectPackageManager(cwd) {
101104
102912
  if (ua.startsWith("yarn/")) return "yarn";
101105
102913
  if (ua.startsWith("bun/")) return "bun";
101106
102914
  if (ua.startsWith("npm/")) return "npm";
101107
- if (existsSync37(join42(cwd, "pnpm-lock.yaml"))) return "pnpm";
101108
- if (existsSync37(join42(cwd, "yarn.lock"))) return "yarn";
101109
- if (existsSync37(join42(cwd, "bun.lock"))) return "bun";
101110
- if (existsSync37(join42(cwd, "package-lock.json"))) return "npm";
102915
+ if (existsSync41(join46(cwd, "pnpm-lock.yaml"))) return "pnpm";
102916
+ if (existsSync41(join46(cwd, "yarn.lock"))) return "yarn";
102917
+ if (existsSync41(join46(cwd, "bun.lock"))) return "bun";
102918
+ if (existsSync41(join46(cwd, "package-lock.json"))) return "npm";
101111
102919
  return "npm";
101112
102920
  }
101113
102921
  function compareVersions(a, b) {
@@ -101131,7 +102939,7 @@ function shouldSkip(args2, force) {
101131
102939
  }
101132
102940
  function getGlobalCachePath(explicit) {
101133
102941
  if (explicit) return explicit;
101134
- return join42(homedir3(), ".knowns", "cli-cache.json");
102942
+ return join46(homedir3(), ".knowns", "cli-cache.json");
101135
102943
  }
101136
102944
  function readCache(cachePath) {
101137
102945
  try {
@@ -101147,7 +102955,7 @@ function readCache(cachePath) {
101147
102955
  }
101148
102956
  async function writeCache(cachePath, data) {
101149
102957
  const dir = dirname8(cachePath);
101150
- if (dir && !existsSync37(dir)) {
102958
+ if (dir && !existsSync41(dir)) {
101151
102959
  await mkdir21(dir, { recursive: true });
101152
102960
  }
101153
102961
  writeFileSync3(cachePath, JSON.stringify(data, null, 2), "utf-8");
@@ -101216,7 +103024,7 @@ async function notifyCliUpdate(options2) {
101216
103024
  // package.json
101217
103025
  var package_default = {
101218
103026
  name: "knowns",
101219
- version: "0.10.6",
103027
+ version: "0.11.0",
101220
103028
  description: "AI-native task and documentation management for dev teams",
101221
103029
  module: "index.ts",
101222
103030
  type: "module",
@@ -101266,9 +103074,6 @@ var package_default = {
101266
103074
  prepare: "husky"
101267
103075
  },
101268
103076
  dependencies: {
101269
- "@blocknote/core": "^0.45.0",
101270
- "@blocknote/react": "^0.45.0",
101271
- "@blocknote/shadcn": "^0.45.0",
101272
103077
  "@dnd-kit/core": "^6.3.1",
101273
103078
  "@dnd-kit/sortable": "^10.0.0",
101274
103079
  "@dnd-kit/utilities": "^3.2.2",
@@ -101307,6 +103112,7 @@ var package_default = {
101307
103112
  handlebars: "^4.7.8",
101308
103113
  "lucide-react": "^0.562.0",
101309
103114
  marked: "^17.0.1",
103115
+ mermaid: "^11.12.2",
101310
103116
  pluralize: "^8.0.0",
101311
103117
  prompts: "^2.4.2",
101312
103118
  react: "^19.2.3",
@@ -101386,6 +103192,7 @@ program2.addCommand(mcpCommand);
101386
103192
  program2.addCommand(templateCommand);
101387
103193
  program2.addCommand(skillCommand);
101388
103194
  program2.addCommand(importCommand);
103195
+ program2.addCommand(validateCommand3);
101389
103196
  var args = process.argv.slice(2);
101390
103197
  if (args.length === 0) {
101391
103198
  showBanner();