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.
- package/README.md +69 -3
- package/dist/index.js +2254 -447
- package/dist/mcp/server.js +791 -98
- package/dist/ui/assets/_baseUniq-BnxjK44c.js +1 -0
- package/dist/ui/assets/arc-DZvVmMpH.js +1 -0
- package/dist/ui/assets/architectureDiagram-VXUJARFQ-BZL6VCKH.js +36 -0
- package/dist/ui/assets/blockDiagram-VD42YOAC-ClTPsT-I.js +122 -0
- package/dist/ui/assets/c4Diagram-YG6GDRKO-Bw103idR.js +10 -0
- package/dist/ui/assets/channel-D9x35499.js +1 -0
- package/dist/ui/assets/chunk-4BX2VUAB-DypK8CJ4.js +1 -0
- package/dist/ui/assets/chunk-55IACEB6-BBDdrBL9.js +1 -0
- package/dist/ui/assets/chunk-B4BG7PRW-K5LInW1J.js +165 -0
- package/dist/ui/assets/chunk-DI55MBZ5-DnPeq72k.js +220 -0
- package/dist/ui/assets/chunk-FMBD7UC4-BouMIneX.js +15 -0
- package/dist/ui/assets/chunk-QN33PNHL-Ba1GHx9G.js +1 -0
- package/dist/ui/assets/chunk-QZHKN3VN-DR78GQki.js +1 -0
- package/dist/ui/assets/chunk-TZMSLE5B-DS5ZUS0q.js +1 -0
- package/dist/ui/assets/classDiagram-2ON5EDUG-BWeu8ZEl.js +1 -0
- package/dist/ui/assets/classDiagram-v2-WZHVMYZB-BWeu8ZEl.js +1 -0
- package/dist/ui/assets/clone-CojkIg2_.js +1 -0
- package/dist/ui/assets/cose-bilkent-S5V4N54A-d4bDKFYg.js +1 -0
- package/dist/ui/assets/cytoscape.esm-5J0xJHOV.js +321 -0
- package/dist/ui/assets/dagre-6UL2VRFP-ehZmQh8m.js +4 -0
- package/dist/ui/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/ui/assets/diagram-PSM6KHXK-BHu3Ht-9.js +24 -0
- package/dist/ui/assets/diagram-QEK2KX5R-B-FuS1Ks.js +43 -0
- package/dist/ui/assets/diagram-S2PKOQOG-9mYWKwpl.js +24 -0
- package/dist/ui/assets/erDiagram-Q2GNP2WA-DyH-5pPP.js +60 -0
- package/dist/ui/assets/flowDiagram-NV44I4VS-nG1wIf-7.js +162 -0
- package/dist/ui/assets/ganttDiagram-JELNMOA3-Dme9C52c.js +267 -0
- package/dist/ui/assets/gitGraphDiagram-NY62KEGX-Ler4P2Pa.js +65 -0
- package/dist/ui/assets/graph-CVs9_WqW.js +1 -0
- package/dist/ui/assets/index-BDuGtZOD.css +1 -0
- package/dist/ui/assets/index-BfOQwuot.js +424 -0
- package/dist/ui/assets/infoDiagram-WHAUD3N6-B_M3sR5-.js +2 -0
- package/dist/ui/assets/init-Gi6I4Gst.js +1 -0
- package/dist/ui/assets/journeyDiagram-XKPGCS4Q-BSLvczLS.js +139 -0
- package/dist/ui/assets/kanban-definition-3W4ZIXB7-DeqtFXdg.js +89 -0
- package/dist/ui/assets/katex-DhXJpUyf.js +261 -0
- package/dist/ui/assets/layout-D33h6Hj3.js +1 -0
- package/dist/ui/assets/linear-BnzJ09ng.js +1 -0
- package/dist/ui/assets/min-CrKfxF3L.js +1 -0
- package/dist/ui/assets/mindmap-definition-VGOIOE7T-ClMK1T7-.js +68 -0
- package/dist/ui/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/ui/assets/pieDiagram-ADFJNKIX-t0ZVgZgx.js +30 -0
- package/dist/ui/assets/quadrantDiagram-AYHSOK5B-DpcRGPXU.js +7 -0
- package/dist/ui/assets/requirementDiagram-UZGBJVZJ-BRd7le_X.js +64 -0
- package/dist/ui/assets/sankeyDiagram-TZEHDZUN-CvzudzQ1.js +10 -0
- package/dist/ui/assets/sequenceDiagram-WL72ISMW-vMDZGgni.js +145 -0
- package/dist/ui/assets/stateDiagram-FKZM4ZOC-CsKLmSJ0.js +1 -0
- package/dist/ui/assets/stateDiagram-v2-4FDKWEC3-CBgoBfXm.js +1 -0
- package/dist/ui/assets/timeline-definition-IT6M3QCI-CtNX-O2_.js +61 -0
- package/dist/ui/assets/treemap-KMMF4GRG-DH2wEEvC.js +128 -0
- package/dist/ui/assets/xychartDiagram-PRI3JC2R-RlXBItY0.js +7 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -4
- package/dist/ui/assets/index-2CDomS1a.js +0 -317
- package/dist/ui/assets/index-B1mpVDN3.css +0 -1
- package/dist/ui/assets/inter-v12-latin-100-46Mq0mOp.woff +0 -0
- package/dist/ui/assets/inter-v12-latin-100-BQDzDElq.woff2 +0 -0
- package/dist/ui/assets/inter-v12-latin-200-BxfrU12A.woff2 +0 -0
- package/dist/ui/assets/inter-v12-latin-200-DXfqWPZg.woff +0 -0
- package/dist/ui/assets/inter-v12-latin-300-DEbyFmpd.woff2 +0 -0
- package/dist/ui/assets/inter-v12-latin-300-f7r92Nkj.woff +0 -0
- package/dist/ui/assets/inter-v12-latin-500-BQ2gQN_M.woff +0 -0
- package/dist/ui/assets/inter-v12-latin-500-DfX5FI9E.woff2 +0 -0
- package/dist/ui/assets/inter-v12-latin-600-BvOeHRLc.woff2 +0 -0
- package/dist/ui/assets/inter-v12-latin-600-D01NXWOK.woff +0 -0
- package/dist/ui/assets/inter-v12-latin-700-B5TOIllR.woff +0 -0
- package/dist/ui/assets/inter-v12-latin-700-Bj1B9WKG.woff2 +0 -0
- package/dist/ui/assets/inter-v12-latin-800-Bdy4lAMa.woff2 +0 -0
- package/dist/ui/assets/inter-v12-latin-800-DFVvDWwT.woff +0 -0
- package/dist/ui/assets/inter-v12-latin-900-CMga-52B.woff2 +0 -0
- package/dist/ui/assets/inter-v12-latin-900-ORHAl5ZU.woff +0 -0
- package/dist/ui/assets/inter-v12-latin-regular-CahmJf_6.woff +0 -0
- package/dist/ui/assets/inter-v12-latin-regular-YtgfLPRn.woff2 +0 -0
- package/dist/ui/assets/module-RjUF93sV.js +0 -716
- 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
|
|
3319
|
+
const matter16 = engine.stringify(data, options2).trim();
|
|
3320
3320
|
let buf = "";
|
|
3321
|
-
if (
|
|
3322
|
-
buf = newline(open) + newline(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
3498
|
-
|
|
3499
|
-
if (typeof file3 === "string") file3 =
|
|
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
|
-
|
|
3502
|
+
matter16.read = function(filepath, options2) {
|
|
3503
3503
|
const str2 = fs.readFileSync(filepath, "utf8");
|
|
3504
|
-
const file3 =
|
|
3504
|
+
const file3 = matter16(str2, options2);
|
|
3505
3505
|
file3.path = filepath;
|
|
3506
3506
|
return file3;
|
|
3507
3507
|
};
|
|
3508
|
-
|
|
3508
|
+
matter16.test = function(str2, options2) {
|
|
3509
3509
|
return utils.startsWith(str2, defaults2(options2).delimiters[0]);
|
|
3510
3510
|
};
|
|
3511
|
-
|
|
3511
|
+
matter16.language = function(str2, options2) {
|
|
3512
3512
|
const opts = defaults2(options2);
|
|
3513
3513
|
const open = opts.delimiters[0];
|
|
3514
|
-
if (
|
|
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
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3523
|
+
matter16.cache = {};
|
|
3524
|
+
matter16.clearCache = function() {
|
|
3525
|
+
matter16.cache = {};
|
|
3526
3526
|
};
|
|
3527
|
-
module2.exports =
|
|
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(
|
|
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
|
-
|
|
12458
|
-
if (globalThis.Handlebars ===
|
|
12457
|
+
Handlebars4.noConflict = function() {
|
|
12458
|
+
if (globalThis.Handlebars === Handlebars4) {
|
|
12459
12459
|
globalThis.Handlebars = $Handlebars;
|
|
12460
12460
|
}
|
|
12461
|
-
return
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
38625
|
+
module2.exports = Router13;
|
|
38626
38626
|
module2.exports.Route = Route;
|
|
38627
|
-
function
|
|
38628
|
-
if (!(this instanceof
|
|
38629
|
-
return new
|
|
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
|
-
|
|
38643
|
+
Router13.prototype = function() {
|
|
38644
38644
|
};
|
|
38645
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
41600
|
-
exports2.Router =
|
|
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:
|
|
41744
|
+
const { readdir: readdir19 } = await import("node:fs/promises");
|
|
41745
41745
|
try {
|
|
41746
|
-
const entries = await
|
|
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/
|
|
58807
|
-
var SKILL_default = '---\nname:
|
|
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/
|
|
58810
|
-
var SKILL_default2 = '---\nname:
|
|
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/
|
|
58813
|
-
var SKILL_default3 = '---\nname:
|
|
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/
|
|
58816
|
-
var SKILL_default4 = '---\nname:
|
|
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/
|
|
58819
|
-
var SKILL_default5 = '---\nname:
|
|
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/
|
|
58822
|
-
var SKILL_default6 = '---\nname:
|
|
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/
|
|
58825
|
-
var SKILL_default7 = '---\nname:
|
|
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/
|
|
58828
|
-
var SKILL_default8 = '---\nname:
|
|
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/
|
|
58831
|
-
var SKILL_default9 = '---\nname:
|
|
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/
|
|
58834
|
-
var SKILL_default10 = '---\nname:
|
|
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
|
|
58865
|
-
var
|
|
58866
|
-
var
|
|
58867
|
-
var
|
|
58868
|
-
var
|
|
58869
|
-
var SKILL_EXTRACT = createSkill(SKILL_default3, "
|
|
58870
|
-
var SKILL_DOC = createSkill(SKILL_default2, "
|
|
58871
|
-
var
|
|
58872
|
-
var
|
|
58873
|
-
var
|
|
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
|
-
|
|
58892
|
-
|
|
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#
|
|
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 /
|
|
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
|
|
64678
|
-
import { mkdir as mkdir14, readFile as
|
|
64679
|
-
import { join as
|
|
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
|
|
64684
|
-
import { basename as basename3, dirname as dirname7, join as
|
|
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
|
|
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
|
|
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:
|
|
65236
|
-
const content = await
|
|
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
|
|
67434
|
-
import { mkdir as mkdir13, writeFile as
|
|
67435
|
-
import { join as
|
|
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
|
|
81284
|
-
import { readFile as
|
|
81285
|
-
import { join as
|
|
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 (!
|
|
81394
|
+
if (!existsSync20(templateDir)) {
|
|
81298
81395
|
throw new TemplateParseError(`Template directory not found: ${templateDir}`);
|
|
81299
81396
|
}
|
|
81300
|
-
const configPath =
|
|
81301
|
-
if (!
|
|
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
|
|
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 ?
|
|
81429
|
+
const relativePath = base ? join24(base, entry.name) : entry.name;
|
|
81333
81430
|
if (entry.isDirectory()) {
|
|
81334
|
-
const subFiles = await findTemplateFiles(
|
|
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 (!
|
|
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 =
|
|
81351
|
-
if (!
|
|
81447
|
+
const configPath = join24(templatesDir, entry.name, CONFIG_FILENAME);
|
|
81448
|
+
if (!existsSync20(configPath)) continue;
|
|
81352
81449
|
try {
|
|
81353
|
-
const loaded = await loadTemplate(
|
|
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
|
|
81363
|
-
import { appendFile, mkdir as mkdir12, readFile as
|
|
81364
|
-
import { dirname as dirname6, join as
|
|
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 =
|
|
88248
|
+
const sourcePath = join25(template.templateDir, action.template);
|
|
88138
88249
|
const destRelative = renderPath(action.path, context);
|
|
88139
|
-
const destPath =
|
|
88140
|
-
if (
|
|
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
|
|
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 =
|
|
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 =
|
|
88163
|
-
const destRelative = renderPath(
|
|
88164
|
-
const destPath =
|
|
88165
|
-
if (
|
|
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
|
|
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 =
|
|
88185
|
-
if (!
|
|
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
|
|
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
|
|
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 =
|
|
88313
|
+
const filePath = join25(options2.projectRoot, renderPath(action.path, context));
|
|
88203
88314
|
const contentToAppend = renderString(action.template, context);
|
|
88204
|
-
if (action.unique &&
|
|
88205
|
-
const existingContent = await
|
|
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 (
|
|
88326
|
+
if (existsSync21(filePath)) {
|
|
88216
88327
|
await appendFile(filePath, fullContent, "utf-8");
|
|
88217
88328
|
} else {
|
|
88218
|
-
await
|
|
88329
|
+
await writeFile10(filePath, contentToAppend, "utf-8");
|
|
88219
88330
|
}
|
|
88220
88331
|
}
|
|
88221
|
-
if (
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
88383
|
-
if (!
|
|
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 (!
|
|
88521
|
+
if (!existsSync22(templatesDir)) {
|
|
88411
88522
|
await mkdir13(templatesDir, { recursive: true });
|
|
88412
88523
|
}
|
|
88413
|
-
const templateDir =
|
|
88414
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
88516
|
-
import { readFile as
|
|
88517
|
-
import { join as
|
|
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 =
|
|
88632
|
+
const timePath = join27(store.projectRoot, ".knowns", "time.json");
|
|
88522
88633
|
async function loadTimeData3() {
|
|
88523
|
-
if (!
|
|
88634
|
+
if (!existsSync23(timePath)) {
|
|
88524
88635
|
return { active: [] };
|
|
88525
88636
|
}
|
|
88526
|
-
const content = await
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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 =
|
|
89078
|
+
packageRoot = join29(currentDir, "..");
|
|
88850
89079
|
} else if (normalizedDir.includes("/src/server") || normalizedDir.includes("\\src\\server")) {
|
|
88851
|
-
packageRoot =
|
|
89080
|
+
packageRoot = join29(currentDir, "..", "..");
|
|
88852
89081
|
}
|
|
88853
|
-
const uiDistPath =
|
|
88854
|
-
if (!
|
|
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,
|
|
89086
|
+
const app = (0, import_express13.default)();
|
|
88858
89087
|
app.use((0, import_cors.default)());
|
|
88859
|
-
app.use(
|
|
89088
|
+
app.use(import_express13.default.json());
|
|
88860
89089
|
app.use(
|
|
88861
89090
|
"/assets",
|
|
88862
|
-
|
|
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
|
-
|
|
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 =
|
|
88879
|
-
if (
|
|
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 =
|
|
88928
|
-
const knownsDir =
|
|
88929
|
-
if (!
|
|
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 (
|
|
88935
|
-
const content = await
|
|
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
|
|
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
|
|
88972
|
-
import { mkdir as mkdir15, readFile as
|
|
88973
|
-
import { join as
|
|
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
|
|
89163
|
-
var DOCS_DIR3 =
|
|
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
|
|
89395
|
+
const entries = await readdir13(dir, { withFileTypes: true });
|
|
89167
89396
|
for (const entry of entries) {
|
|
89168
|
-
const fullPath =
|
|
89169
|
-
const relativePath = normalizePath2(basePath ?
|
|
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 (!
|
|
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 =
|
|
89201
|
-
if (!
|
|
89429
|
+
let filepath = join31(DOCS_DIR3, filename);
|
|
89430
|
+
if (!existsSync27(filepath)) {
|
|
89202
89431
|
filename = `${titleToFilename(name)}.md`;
|
|
89203
|
-
filepath =
|
|
89432
|
+
filepath = join31(DOCS_DIR3, filename);
|
|
89204
89433
|
}
|
|
89205
|
-
if (!
|
|
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 =
|
|
89444
|
+
filepath = join31(DOCS_DIR3, matchingFile);
|
|
89216
89445
|
}
|
|
89217
89446
|
}
|
|
89218
|
-
if (!
|
|
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 =
|
|
89233
|
-
relativePath =
|
|
89234
|
-
if (!
|
|
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 =
|
|
89239
|
-
if (
|
|
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 =
|
|
89256
|
-
await
|
|
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
|
|
89292
|
-
const { data, content } = (0,
|
|
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
|
|
89467
|
-
const { data, content } = (0,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
89698
|
-
if (!
|
|
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 =
|
|
89930
|
+
filepath = join31(DOCS_DIR3, filename);
|
|
89702
89931
|
}
|
|
89703
|
-
if (!
|
|
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 =
|
|
89942
|
+
filepath = join31(DOCS_DIR3, matchingFile);
|
|
89714
89943
|
}
|
|
89715
89944
|
}
|
|
89716
|
-
if (!
|
|
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
|
|
89721
|
-
const { data, content } = (0,
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 =
|
|
89768
|
-
await
|
|
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
|
|
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
|
|
89931
|
-
const { content } = (0,
|
|
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
|
|
89984
|
-
const { data, content } = (0,
|
|
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 =
|
|
90006
|
-
await
|
|
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
|
|
90028
|
-
const { data, content } = (0,
|
|
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 =
|
|
90060
|
-
await
|
|
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
|
|
90088
|
-
const { data, content } = (0,
|
|
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 =
|
|
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 =
|
|
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
|
|
90325
|
-
import { mkdir as mkdir16, readFile as
|
|
90326
|
-
import { join as
|
|
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 =
|
|
90355
|
-
if (!
|
|
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
|
|
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 =
|
|
90374
|
-
const knownsDir =
|
|
90375
|
-
if (!
|
|
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 (
|
|
90381
|
-
const content = await
|
|
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
|
|
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
|
|
90531
|
-
import { join as
|
|
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 =
|
|
90797
|
+
const skillsDir = join33(PROJECT_ROOT2, ".claude", "skills");
|
|
90542
90798
|
const mode = options2.mode ?? "mcp";
|
|
90543
|
-
if (!
|
|
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 =
|
|
90552
|
-
const skillFile =
|
|
90808
|
+
const skillFolder = join33(skillsDir, skill.folderName);
|
|
90809
|
+
const skillFile = join33(skillFolder, "SKILL.md");
|
|
90553
90810
|
const renderedContent = renderSkillContent2(skill.content, mode);
|
|
90554
|
-
if (
|
|
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 (!
|
|
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 =
|
|
90872
|
+
const targetDir = join33(PROJECT_ROOT2, config2.targetDir);
|
|
90616
90873
|
for (const file3 of config2.files) {
|
|
90617
|
-
const filePath =
|
|
90618
|
-
const fileDir =
|
|
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 (
|
|
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 (!
|
|
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
|
|
90729
|
-
import { join as
|
|
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
|
|
90733
|
-
import { readFile as
|
|
90734
|
-
import { join as
|
|
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
|
|
98052
|
+
var import_gray_matter14 = __toESM(require_gray_matter(), 1);
|
|
97795
98053
|
|
|
97796
98054
|
// src/mcp/utils.ts
|
|
97797
|
-
import { readFile as
|
|
97798
|
-
import { join as
|
|
97799
|
-
var
|
|
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
|
|
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
|
|
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
|
-
|
|
97823
|
-
|
|
97824
|
-
|
|
97825
|
-
|
|
97826
|
-
|
|
97827
|
-
|
|
97828
|
-
|
|
97829
|
-
|
|
97830
|
-
|
|
97831
|
-
|
|
97832
|
-
|
|
97833
|
-
|
|
97834
|
-
|
|
97835
|
-
|
|
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 (
|
|
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 =
|
|
98107
|
+
const entries = readdirSync4(dir);
|
|
97850
98108
|
if (entries.includes(".knowns")) {
|
|
97851
|
-
const knownsPath =
|
|
97852
|
-
if (
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 =
|
|
97965
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
98039
|
-
const fileContent = await
|
|
98040
|
-
const { data, content } = (0,
|
|
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
|
|
98415
|
-
import { readFile as
|
|
98416
|
-
import { join as
|
|
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 =
|
|
98500
|
-
if (!
|
|
98777
|
+
const timePath = join36(projectRoot, ".knowns", "time.json");
|
|
98778
|
+
if (!existsSync31(timePath)) {
|
|
98501
98779
|
return { active: [] };
|
|
98502
98780
|
}
|
|
98503
|
-
const content = await
|
|
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 =
|
|
98515
|
-
await
|
|
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
|
|
98731
|
-
import { mkdir as mkdir17, readFile as
|
|
98732
|
-
import { join as
|
|
98733
|
-
var
|
|
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
|
|
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 (!
|
|
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 (!
|
|
99177
|
+
if (!existsSync32(dir)) {
|
|
98896
99178
|
return files;
|
|
98897
99179
|
}
|
|
98898
|
-
const entries = await
|
|
99180
|
+
const entries = await readdir14(dir, { withFileTypes: true });
|
|
98899
99181
|
for (const entry of entries) {
|
|
98900
|
-
const fullPath =
|
|
98901
|
-
const relativePath = normalizePath2(basePath ?
|
|
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 =
|
|
98915
|
-
if (
|
|
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 =
|
|
98920
|
-
if (
|
|
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:
|
|
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
|
|
98954
|
-
const { data, content } = (0,
|
|
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
|
|
98987
|
-
const { data, content } = (0,
|
|
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 =
|
|
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 =
|
|
99107
|
-
relativePath =
|
|
99108
|
-
if (!
|
|
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 =
|
|
99113
|
-
if (
|
|
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 =
|
|
99130
|
-
await
|
|
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
|
|
99149
|
-
const { data, content } = (0,
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
99168
|
-
updatedContent =
|
|
99451
|
+
} else if (normalizedContent) {
|
|
99452
|
+
updatedContent = normalizedContent;
|
|
99169
99453
|
}
|
|
99170
|
-
if (
|
|
99454
|
+
if (normalizedAppend) {
|
|
99171
99455
|
updatedContent = `${updatedContent.trimEnd()}
|
|
99172
99456
|
|
|
99173
|
-
${
|
|
99457
|
+
${normalizedAppend}`;
|
|
99174
99458
|
}
|
|
99175
|
-
const newFileContent =
|
|
99176
|
-
await
|
|
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
|
|
99198
|
-
const { data, content } = (0,
|
|
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
|
|
99235
|
-
import { mkdir as mkdir18, writeFile as
|
|
99236
|
-
import { join as
|
|
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
|
|
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(
|
|
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 (!
|
|
99772
|
+
if (!existsSync33(getTemplatesDir())) {
|
|
99489
99773
|
await mkdir18(getTemplatesDir(), { recursive: true });
|
|
99490
99774
|
}
|
|
99491
|
-
const templateDir =
|
|
99492
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
99554
|
-
import { existsSync as
|
|
99555
|
-
import { readFile as
|
|
99556
|
-
import { join as
|
|
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 (!
|
|
99927
|
+
if (!existsSync34(dir)) {
|
|
99644
99928
|
return files;
|
|
99645
99929
|
}
|
|
99646
|
-
const entries = await
|
|
99930
|
+
const entries = await readdir15(dir, { withFileTypes: true });
|
|
99647
99931
|
for (const entry of entries) {
|
|
99648
|
-
const fullPath =
|
|
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 (!
|
|
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
|
|
99699
|
-
const { data, content } = (0,
|
|
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 =
|
|
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 =
|
|
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 (
|
|
99878
|
-
const { readdir:
|
|
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
|
|
100820
|
+
const entries = await readdir19(dir, { withFileTypes: true });
|
|
99882
100821
|
for (const entry of entries) {
|
|
99883
|
-
const fullPath =
|
|
99884
|
-
const relativePath = normalizePath2(basePath ?
|
|
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 =
|
|
99897
|
-
const content = await
|
|
99898
|
-
const { data } = (0,
|
|
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 =
|
|
99934
|
-
const filepath =
|
|
99935
|
-
if (!
|
|
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
|
|
99939
|
-
const { data, content: docContent } = (0,
|
|
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 =
|
|
100956
|
+
const mcpJsonPath = join42(projectRoot, ".mcp.json");
|
|
100018
100957
|
const mcpConfig = {
|
|
100019
100958
|
mcpServers: {
|
|
100020
100959
|
knowns: getMcpConfig()
|
|
100021
100960
|
}
|
|
100022
100961
|
};
|
|
100023
|
-
if (
|
|
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
|
|
100121
|
-
import { mkdir as mkdir19, writeFile as
|
|
100122
|
-
import { join as
|
|
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
|
|
101073
|
+
return join43(projectRoot, TEMPLATES_DIR3);
|
|
100135
101074
|
}
|
|
100136
101075
|
async function ensureTemplatesDir(projectRoot) {
|
|
100137
101076
|
const templatesDir = getTemplatesDir2(projectRoot);
|
|
100138
|
-
if (!
|
|
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(
|
|
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 =
|
|
100342
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
100535
|
-
import { mkdir as mkdir20, writeFile as
|
|
100536
|
-
import { join as
|
|
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
|
|
101487
|
+
return join44(projectRoot, SKILLS_DIR);
|
|
100549
101488
|
}
|
|
100550
101489
|
async function ensureSkillsDir(projectRoot) {
|
|
100551
101490
|
const skillsDir = getSkillsDir(projectRoot);
|
|
100552
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
100607
|
-
if (
|
|
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
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
100776
|
-
const exists =
|
|
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 =
|
|
100787
|
-
const exists =
|
|
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
|
|
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
|
|
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 (
|
|
101108
|
-
if (
|
|
101109
|
-
if (
|
|
101110
|
-
if (
|
|
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
|
|
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 && !
|
|
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.
|
|
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();
|