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/mcp/server.js
CHANGED
|
@@ -10076,10 +10076,10 @@ var require_stringify = __commonJS({
|
|
|
10076
10076
|
data = Object.assign({}, file3.data, data);
|
|
10077
10077
|
const open = opts.delimiters[0];
|
|
10078
10078
|
const close = opts.delimiters[1];
|
|
10079
|
-
const
|
|
10079
|
+
const matter8 = engine.stringify(data, options2).trim();
|
|
10080
10080
|
let buf = "";
|
|
10081
|
-
if (
|
|
10082
|
-
buf = newline(open) + newline(
|
|
10081
|
+
if (matter8 !== "{}") {
|
|
10082
|
+
buf = newline(open) + newline(matter8) + newline(close);
|
|
10083
10083
|
}
|
|
10084
10084
|
if (typeof file3.excerpt === "string" && file3.excerpt !== "") {
|
|
10085
10085
|
if (str2.indexOf(file3.excerpt.trim()) === -1) {
|
|
@@ -10185,19 +10185,19 @@ var require_gray_matter = __commonJS({
|
|
|
10185
10185
|
var toFile = require_to_file();
|
|
10186
10186
|
var parse4 = require_parse();
|
|
10187
10187
|
var utils = require_utils2();
|
|
10188
|
-
function
|
|
10188
|
+
function matter8(input, options2) {
|
|
10189
10189
|
if (input === "") {
|
|
10190
10190
|
return { data: {}, content: input, excerpt: "", orig: input };
|
|
10191
10191
|
}
|
|
10192
10192
|
let file3 = toFile(input);
|
|
10193
|
-
const cached2 =
|
|
10193
|
+
const cached2 = matter8.cache[file3.content];
|
|
10194
10194
|
if (!options2) {
|
|
10195
10195
|
if (cached2) {
|
|
10196
10196
|
file3 = Object.assign({}, cached2);
|
|
10197
10197
|
file3.orig = cached2.orig;
|
|
10198
10198
|
return file3;
|
|
10199
10199
|
}
|
|
10200
|
-
|
|
10200
|
+
matter8.cache[file3.content] = file3;
|
|
10201
10201
|
}
|
|
10202
10202
|
return parseMatter(file3, options2);
|
|
10203
10203
|
}
|
|
@@ -10219,7 +10219,7 @@ var require_gray_matter = __commonJS({
|
|
|
10219
10219
|
}
|
|
10220
10220
|
str2 = str2.slice(openLen);
|
|
10221
10221
|
const len = str2.length;
|
|
10222
|
-
const language =
|
|
10222
|
+
const language = matter8.language(str2, opts);
|
|
10223
10223
|
if (language.name) {
|
|
10224
10224
|
file3.language = language.name;
|
|
10225
10225
|
str2 = str2.slice(language.raw.length);
|
|
@@ -10254,24 +10254,24 @@ var require_gray_matter = __commonJS({
|
|
|
10254
10254
|
}
|
|
10255
10255
|
return file3;
|
|
10256
10256
|
}
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
if (typeof file3 === "string") file3 =
|
|
10257
|
+
matter8.engines = engines2;
|
|
10258
|
+
matter8.stringify = function(file3, data, options2) {
|
|
10259
|
+
if (typeof file3 === "string") file3 = matter8(file3, options2);
|
|
10260
10260
|
return stringify(file3, data, options2);
|
|
10261
10261
|
};
|
|
10262
|
-
|
|
10262
|
+
matter8.read = function(filepath, options2) {
|
|
10263
10263
|
const str2 = fs.readFileSync(filepath, "utf8");
|
|
10264
|
-
const file3 =
|
|
10264
|
+
const file3 = matter8(str2, options2);
|
|
10265
10265
|
file3.path = filepath;
|
|
10266
10266
|
return file3;
|
|
10267
10267
|
};
|
|
10268
|
-
|
|
10268
|
+
matter8.test = function(str2, options2) {
|
|
10269
10269
|
return utils.startsWith(str2, defaults2(options2).delimiters[0]);
|
|
10270
10270
|
};
|
|
10271
|
-
|
|
10271
|
+
matter8.language = function(str2, options2) {
|
|
10272
10272
|
const opts = defaults2(options2);
|
|
10273
10273
|
const open = opts.delimiters[0];
|
|
10274
|
-
if (
|
|
10274
|
+
if (matter8.test(str2)) {
|
|
10275
10275
|
str2 = str2.slice(open.length);
|
|
10276
10276
|
}
|
|
10277
10277
|
const language = str2.slice(0, str2.search(/\r?\n/));
|
|
@@ -10280,11 +10280,11 @@ var require_gray_matter = __commonJS({
|
|
|
10280
10280
|
name: language ? language.trim() : ""
|
|
10281
10281
|
};
|
|
10282
10282
|
};
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10283
|
+
matter8.cache = {};
|
|
10284
|
+
matter8.clearCache = function() {
|
|
10285
|
+
matter8.cache = {};
|
|
10286
10286
|
};
|
|
10287
|
-
module2.exports =
|
|
10287
|
+
module2.exports = matter8;
|
|
10288
10288
|
}
|
|
10289
10289
|
});
|
|
10290
10290
|
|
|
@@ -18688,7 +18688,7 @@ var require_no_conflict = __commonJS({
|
|
|
18688
18688
|
"node_modules/handlebars/dist/cjs/handlebars/no-conflict.js"(exports2, module2) {
|
|
18689
18689
|
"use strict";
|
|
18690
18690
|
exports2.__esModule = true;
|
|
18691
|
-
exports2["default"] = function(
|
|
18691
|
+
exports2["default"] = function(Handlebars3) {
|
|
18692
18692
|
(function() {
|
|
18693
18693
|
if (typeof globalThis === "object") return;
|
|
18694
18694
|
Object.prototype.__defineGetter__("__magic__", function() {
|
|
@@ -18698,11 +18698,11 @@ var require_no_conflict = __commonJS({
|
|
|
18698
18698
|
delete Object.prototype.__magic__;
|
|
18699
18699
|
})();
|
|
18700
18700
|
var $Handlebars = globalThis.Handlebars;
|
|
18701
|
-
|
|
18702
|
-
if (globalThis.Handlebars ===
|
|
18701
|
+
Handlebars3.noConflict = function() {
|
|
18702
|
+
if (globalThis.Handlebars === Handlebars3) {
|
|
18703
18703
|
globalThis.Handlebars = $Handlebars;
|
|
18704
18704
|
}
|
|
18705
|
-
return
|
|
18705
|
+
return Handlebars3;
|
|
18706
18706
|
};
|
|
18707
18707
|
};
|
|
18708
18708
|
module2.exports = exports2["default"];
|
|
@@ -20622,7 +20622,7 @@ var require_util2 = __commonJS({
|
|
|
20622
20622
|
return path2;
|
|
20623
20623
|
}
|
|
20624
20624
|
exports2.normalize = normalize2;
|
|
20625
|
-
function
|
|
20625
|
+
function join22(aRoot, aPath) {
|
|
20626
20626
|
if (aRoot === "") {
|
|
20627
20627
|
aRoot = ".";
|
|
20628
20628
|
}
|
|
@@ -20654,7 +20654,7 @@ var require_util2 = __commonJS({
|
|
|
20654
20654
|
}
|
|
20655
20655
|
return joined;
|
|
20656
20656
|
}
|
|
20657
|
-
exports2.join =
|
|
20657
|
+
exports2.join = join22;
|
|
20658
20658
|
exports2.isAbsolute = function(aPath) {
|
|
20659
20659
|
return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
|
|
20660
20660
|
};
|
|
@@ -20827,7 +20827,7 @@ var require_util2 = __commonJS({
|
|
|
20827
20827
|
parsed.path = parsed.path.substring(0, index + 1);
|
|
20828
20828
|
}
|
|
20829
20829
|
}
|
|
20830
|
-
sourceURL =
|
|
20830
|
+
sourceURL = join22(urlGenerate(parsed), sourceURL);
|
|
20831
20831
|
}
|
|
20832
20832
|
return normalize2(sourceURL);
|
|
20833
20833
|
}
|
|
@@ -28653,9 +28653,9 @@ var require_prompts3 = __commonJS({
|
|
|
28653
28653
|
});
|
|
28654
28654
|
|
|
28655
28655
|
// src/mcp/server.ts
|
|
28656
|
-
import { existsSync as
|
|
28657
|
-
import { readFile as
|
|
28658
|
-
import { join as
|
|
28656
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
28657
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
28658
|
+
import { join as join21 } from "node:path";
|
|
28659
28659
|
|
|
28660
28660
|
// node_modules/zod/v3/helpers/util.js
|
|
28661
28661
|
var util;
|
|
@@ -49715,6 +49715,7 @@ function parseTaskMarkdown(content) {
|
|
|
49715
49715
|
assignee: frontmatter.assignee,
|
|
49716
49716
|
labels: frontmatter.labels || [],
|
|
49717
49717
|
parent: frontmatter.parent,
|
|
49718
|
+
spec: frontmatter.spec,
|
|
49718
49719
|
subtasks: [],
|
|
49719
49720
|
// Will be populated by FileStore
|
|
49720
49721
|
createdAt: new Date(frontmatter.createdAt),
|
|
@@ -49741,6 +49742,7 @@ function serializeTaskMarkdown(task) {
|
|
|
49741
49742
|
};
|
|
49742
49743
|
if (task.assignee) frontmatter.assignee = task.assignee;
|
|
49743
49744
|
if (task.parent) frontmatter.parent = task.parent;
|
|
49745
|
+
if (task.spec) frontmatter.spec = task.spec;
|
|
49744
49746
|
let body = "";
|
|
49745
49747
|
body += `# ${task.title}
|
|
49746
49748
|
|
|
@@ -50433,7 +50435,7 @@ function normalizePath(filePath) {
|
|
|
50433
50435
|
}
|
|
50434
50436
|
|
|
50435
50437
|
// src/mcp/server.ts
|
|
50436
|
-
var
|
|
50438
|
+
var import_gray_matter7 = __toESM(require_gray_matter(), 1);
|
|
50437
50439
|
|
|
50438
50440
|
// src/utils/notify-server.ts
|
|
50439
50441
|
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
@@ -50818,6 +50820,10 @@ function errorResponse(error48) {
|
|
|
50818
50820
|
}
|
|
50819
50821
|
|
|
50820
50822
|
// src/mcp/handlers/task.ts
|
|
50823
|
+
function normalizeNewlines(text) {
|
|
50824
|
+
if (!text) return text;
|
|
50825
|
+
return text.replace(/\\n/g, "\n");
|
|
50826
|
+
}
|
|
50821
50827
|
var createTaskSchema = external_exports3.object({
|
|
50822
50828
|
title: external_exports3.string(),
|
|
50823
50829
|
description: external_exports3.string().optional(),
|
|
@@ -50825,7 +50831,8 @@ var createTaskSchema = external_exports3.object({
|
|
|
50825
50831
|
priority: external_exports3.enum(["low", "medium", "high"]).optional(),
|
|
50826
50832
|
assignee: external_exports3.string().optional(),
|
|
50827
50833
|
labels: external_exports3.array(external_exports3.string()).optional(),
|
|
50828
|
-
parent: external_exports3.string().optional()
|
|
50834
|
+
parent: external_exports3.string().optional(),
|
|
50835
|
+
spec: external_exports3.string().optional()
|
|
50829
50836
|
});
|
|
50830
50837
|
var getTaskSchema = external_exports3.object({
|
|
50831
50838
|
taskId: external_exports3.string()
|
|
@@ -50838,6 +50845,8 @@ var updateTaskSchema = external_exports3.object({
|
|
|
50838
50845
|
priority: external_exports3.enum(["low", "medium", "high"]).optional(),
|
|
50839
50846
|
assignee: external_exports3.string().optional(),
|
|
50840
50847
|
labels: external_exports3.array(external_exports3.string()).optional(),
|
|
50848
|
+
spec: external_exports3.string().nullable().optional(),
|
|
50849
|
+
// Spec document path (null to remove)
|
|
50841
50850
|
// AC operations
|
|
50842
50851
|
addAc: external_exports3.array(external_exports3.string()).optional(),
|
|
50843
50852
|
// Add new acceptance criteria
|
|
@@ -50859,7 +50868,8 @@ var listTasksSchema = external_exports3.object({
|
|
|
50859
50868
|
status: external_exports3.string().optional(),
|
|
50860
50869
|
priority: external_exports3.string().optional(),
|
|
50861
50870
|
assignee: external_exports3.string().optional(),
|
|
50862
|
-
label: external_exports3.string().optional()
|
|
50871
|
+
label: external_exports3.string().optional(),
|
|
50872
|
+
spec: external_exports3.string().optional()
|
|
50863
50873
|
});
|
|
50864
50874
|
var searchTasksSchema = external_exports3.object({
|
|
50865
50875
|
query: external_exports3.string()
|
|
@@ -50889,7 +50899,8 @@ var taskTools = [
|
|
|
50889
50899
|
items: { type: "string" },
|
|
50890
50900
|
description: "Task labels"
|
|
50891
50901
|
},
|
|
50892
|
-
parent: { type: "string", description: "Parent task ID for subtasks" }
|
|
50902
|
+
parent: { type: "string", description: "Parent task ID for subtasks" },
|
|
50903
|
+
spec: { type: "string", description: "Spec document path (e.g., 'specs/user-auth')" }
|
|
50893
50904
|
},
|
|
50894
50905
|
required: ["title"]
|
|
50895
50906
|
}
|
|
@@ -50930,6 +50941,7 @@ var taskTools = [
|
|
|
50930
50941
|
items: { type: "string" },
|
|
50931
50942
|
description: "New labels"
|
|
50932
50943
|
},
|
|
50944
|
+
spec: { type: "string", description: "Spec document path (set to null to remove)" },
|
|
50933
50945
|
addAc: {
|
|
50934
50946
|
type: "array",
|
|
50935
50947
|
items: { type: "string" },
|
|
@@ -50966,7 +50978,8 @@ var taskTools = [
|
|
|
50966
50978
|
status: { type: "string", description: "Filter by status" },
|
|
50967
50979
|
priority: { type: "string", description: "Filter by priority" },
|
|
50968
50980
|
assignee: { type: "string", description: "Filter by assignee" },
|
|
50969
|
-
label: { type: "string", description: "Filter by label" }
|
|
50981
|
+
label: { type: "string", description: "Filter by label" },
|
|
50982
|
+
spec: { type: "string", description: "Filter by spec document path" }
|
|
50970
50983
|
}
|
|
50971
50984
|
}
|
|
50972
50985
|
},
|
|
@@ -50986,12 +50999,13 @@ async function handleCreateTask(args, fileStore) {
|
|
|
50986
50999
|
const input = createTaskSchema.parse(args);
|
|
50987
51000
|
const task = await fileStore.createTask({
|
|
50988
51001
|
title: input.title,
|
|
50989
|
-
description: input.description,
|
|
51002
|
+
description: normalizeNewlines(input.description),
|
|
50990
51003
|
status: input.status || "todo",
|
|
50991
51004
|
priority: input.priority || "medium",
|
|
50992
51005
|
assignee: input.assignee,
|
|
50993
51006
|
labels: input.labels || [],
|
|
50994
51007
|
parent: input.parent,
|
|
51008
|
+
spec: input.spec,
|
|
50995
51009
|
subtasks: [],
|
|
50996
51010
|
acceptanceCriteria: [],
|
|
50997
51011
|
timeSpent: 0,
|
|
@@ -51024,6 +51038,7 @@ async function handleGetTask(args, fileStore) {
|
|
|
51024
51038
|
priority: task.priority,
|
|
51025
51039
|
assignee: task.assignee,
|
|
51026
51040
|
labels: task.labels,
|
|
51041
|
+
spec: task.spec,
|
|
51027
51042
|
acceptanceCriteria: task.acceptanceCriteria,
|
|
51028
51043
|
implementationPlan: task.implementationPlan,
|
|
51029
51044
|
implementationNotes: task.implementationNotes,
|
|
@@ -51042,11 +51057,14 @@ async function handleUpdateTask(args, fileStore) {
|
|
|
51042
51057
|
}
|
|
51043
51058
|
const updates = {};
|
|
51044
51059
|
if (input.title) updates.title = input.title;
|
|
51045
|
-
if (input.description) updates.description = input.description;
|
|
51060
|
+
if (input.description) updates.description = normalizeNewlines(input.description);
|
|
51046
51061
|
if (input.status) updates.status = input.status;
|
|
51047
51062
|
if (input.priority) updates.priority = input.priority;
|
|
51048
51063
|
if (input.assignee) updates.assignee = input.assignee;
|
|
51049
51064
|
if (input.labels) updates.labels = input.labels;
|
|
51065
|
+
if (input.spec !== void 0) {
|
|
51066
|
+
updates.spec = input.spec === null ? void 0 : input.spec;
|
|
51067
|
+
}
|
|
51050
51068
|
const criteria = [...currentTask.acceptanceCriteria];
|
|
51051
51069
|
let acModified = false;
|
|
51052
51070
|
if (input.addAc && input.addAc.length > 0) {
|
|
@@ -51089,15 +51107,15 @@ async function handleUpdateTask(args, fileStore) {
|
|
|
51089
51107
|
updates.acceptanceCriteria = criteria;
|
|
51090
51108
|
}
|
|
51091
51109
|
if (input.plan !== void 0) {
|
|
51092
|
-
updates.implementationPlan = input.plan;
|
|
51110
|
+
updates.implementationPlan = normalizeNewlines(input.plan);
|
|
51093
51111
|
}
|
|
51094
51112
|
if (input.notes !== void 0) {
|
|
51095
|
-
updates.implementationNotes = input.notes;
|
|
51113
|
+
updates.implementationNotes = normalizeNewlines(input.notes);
|
|
51096
51114
|
}
|
|
51097
51115
|
if (input.appendNotes) {
|
|
51098
51116
|
const existingNotes = currentTask.implementationNotes || "";
|
|
51099
51117
|
const separator = existingNotes ? "\n\n" : "";
|
|
51100
|
-
updates.implementationNotes = existingNotes + separator + input.appendNotes;
|
|
51118
|
+
updates.implementationNotes = existingNotes + separator + normalizeNewlines(input.appendNotes);
|
|
51101
51119
|
}
|
|
51102
51120
|
const task = await fileStore.updateTask(taskId, updates);
|
|
51103
51121
|
await notifyTaskUpdate(task.id);
|
|
@@ -51128,6 +51146,9 @@ async function handleListTasks(args, fileStore) {
|
|
|
51128
51146
|
if (input.label) {
|
|
51129
51147
|
tasks = tasks.filter((t) => t.labels.includes(input.label));
|
|
51130
51148
|
}
|
|
51149
|
+
if (input.spec) {
|
|
51150
|
+
tasks = tasks.filter((t) => t.spec === input.spec);
|
|
51151
|
+
}
|
|
51131
51152
|
return successResponse({
|
|
51132
51153
|
count: tasks.length,
|
|
51133
51154
|
tasks: tasks.map((t) => ({
|
|
@@ -51136,7 +51157,8 @@ async function handleListTasks(args, fileStore) {
|
|
|
51136
51157
|
status: t.status,
|
|
51137
51158
|
priority: t.priority,
|
|
51138
51159
|
assignee: t.assignee,
|
|
51139
|
-
labels: t.labels
|
|
51160
|
+
labels: t.labels,
|
|
51161
|
+
spec: t.spec
|
|
51140
51162
|
}))
|
|
51141
51163
|
});
|
|
51142
51164
|
}
|
|
@@ -51999,8 +52021,8 @@ var NpmProvider = class extends ImportProvider {
|
|
|
51999
52021
|
try {
|
|
52000
52022
|
const packageJsonPath = join12(tempDir, "package.json");
|
|
52001
52023
|
if (existsSync8(packageJsonPath)) {
|
|
52002
|
-
const { readFile:
|
|
52003
|
-
const content = await
|
|
52024
|
+
const { readFile: readFile12 } = await import("node:fs/promises");
|
|
52025
|
+
const content = await readFile12(packageJsonPath, "utf-8");
|
|
52004
52026
|
const pkg = JSON.parse(content);
|
|
52005
52027
|
if (pkg.version) {
|
|
52006
52028
|
metadata.version = pkg.version;
|
|
@@ -52409,6 +52431,10 @@ async function validateRefs(projectRoot, content, tasksDir) {
|
|
|
52409
52431
|
function getDocsDir() {
|
|
52410
52432
|
return join15(getProjectRoot(), ".knowns", "docs");
|
|
52411
52433
|
}
|
|
52434
|
+
function normalizeNewlines2(text) {
|
|
52435
|
+
if (!text) return text;
|
|
52436
|
+
return text.replace(/\\n/g, "\n");
|
|
52437
|
+
}
|
|
52412
52438
|
var listDocsSchema = external_exports3.object({
|
|
52413
52439
|
tag: external_exports3.string().optional()
|
|
52414
52440
|
});
|
|
@@ -52800,7 +52826,7 @@ async function handleCreateDoc(args) {
|
|
|
52800
52826
|
if (input.tags) {
|
|
52801
52827
|
metadata.tags = input.tags;
|
|
52802
52828
|
}
|
|
52803
|
-
const initialContent = input.content || "# Content\n\nWrite your documentation here.";
|
|
52829
|
+
const initialContent = normalizeNewlines2(input.content) || "# Content\n\nWrite your documentation here.";
|
|
52804
52830
|
const fileContent = import_gray_matter3.default.stringify(initialContent, metadata);
|
|
52805
52831
|
await writeFile4(filepath, fileContent, "utf-8");
|
|
52806
52832
|
await notifyDocUpdate(relativePath);
|
|
@@ -52829,9 +52855,11 @@ async function handleUpdateDoc(args) {
|
|
|
52829
52855
|
metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
52830
52856
|
let updatedContent = content;
|
|
52831
52857
|
let sectionUpdated;
|
|
52832
|
-
|
|
52858
|
+
const normalizedContent = normalizeNewlines2(input.content);
|
|
52859
|
+
const normalizedAppend = normalizeNewlines2(input.appendContent);
|
|
52860
|
+
if (input.section && normalizedContent) {
|
|
52833
52861
|
const sectionIndex = /^\d+$/.test(input.section) ? Number.parseInt(input.section, 10) : null;
|
|
52834
|
-
const result = sectionIndex !== null ? replaceSectionByIndex(content, sectionIndex,
|
|
52862
|
+
const result = sectionIndex !== null ? replaceSectionByIndex(content, sectionIndex, normalizedContent) : replaceSection(content, input.section, normalizedContent);
|
|
52835
52863
|
if (!result) {
|
|
52836
52864
|
return errorResponse(
|
|
52837
52865
|
`Section not found: ${input.section}. Use 'toc: true' with get_doc to see available sections.`
|
|
@@ -52839,13 +52867,13 @@ async function handleUpdateDoc(args) {
|
|
|
52839
52867
|
}
|
|
52840
52868
|
updatedContent = result;
|
|
52841
52869
|
sectionUpdated = input.section;
|
|
52842
|
-
} else if (
|
|
52843
|
-
updatedContent =
|
|
52870
|
+
} else if (normalizedContent) {
|
|
52871
|
+
updatedContent = normalizedContent;
|
|
52844
52872
|
}
|
|
52845
|
-
if (
|
|
52873
|
+
if (normalizedAppend) {
|
|
52846
52874
|
updatedContent = `${updatedContent.trimEnd()}
|
|
52847
52875
|
|
|
52848
|
-
${
|
|
52876
|
+
${normalizedAppend}`;
|
|
52849
52877
|
}
|
|
52850
52878
|
const newFileContent = import_gray_matter3.default.stringify(updatedContent, metadata);
|
|
52851
52879
|
await writeFile4(resolved.filepath, newFileContent, "utf-8");
|
|
@@ -53065,6 +53093,20 @@ async function listTemplates(templatesDir) {
|
|
|
53065
53093
|
}
|
|
53066
53094
|
return templates;
|
|
53067
53095
|
}
|
|
53096
|
+
async function getTemplateConfig(templateDir) {
|
|
53097
|
+
const configPath = join16(templateDir, CONFIG_FILENAME);
|
|
53098
|
+
if (!existsSync12(configPath)) {
|
|
53099
|
+
return null;
|
|
53100
|
+
}
|
|
53101
|
+
try {
|
|
53102
|
+
const content = await readFile6(configPath, "utf-8");
|
|
53103
|
+
const raw = (0, import_yaml.parse)(content);
|
|
53104
|
+
const result = safeValidateTemplateConfig(raw);
|
|
53105
|
+
return result.success ? result.data : null;
|
|
53106
|
+
} catch {
|
|
53107
|
+
return null;
|
|
53108
|
+
}
|
|
53109
|
+
}
|
|
53068
53110
|
|
|
53069
53111
|
// src/codegen/renderer.ts
|
|
53070
53112
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
@@ -60166,38 +60208,35 @@ async function ensureDir(dir) {
|
|
|
60166
60208
|
// src/codegen/skill-parser.ts
|
|
60167
60209
|
var import_gray_matter4 = __toESM(require_gray_matter(), 1);
|
|
60168
60210
|
|
|
60169
|
-
// src/instructions/skills/
|
|
60170
|
-
var SKILL_default = '---\nname:
|
|
60211
|
+
// src/instructions/skills/kn:commit/SKILL.md
|
|
60212
|
+
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';
|
|
60171
60213
|
|
|
60172
|
-
// src/instructions/skills/
|
|
60173
|
-
var SKILL_default2 = '---\nname:
|
|
60214
|
+
// src/instructions/skills/kn:doc/SKILL.md
|
|
60215
|
+
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';
|
|
60174
60216
|
|
|
60175
|
-
// src/instructions/skills/
|
|
60176
|
-
var SKILL_default3 = '---\nname:
|
|
60217
|
+
// src/instructions/skills/kn:extract/SKILL.md
|
|
60218
|
+
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';
|
|
60177
60219
|
|
|
60178
|
-
// src/instructions/skills/
|
|
60179
|
-
var SKILL_default4 = '---\nname:
|
|
60220
|
+
// src/instructions/skills/kn:implement/SKILL.md
|
|
60221
|
+
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';
|
|
60180
60222
|
|
|
60181
|
-
// src/instructions/skills/
|
|
60182
|
-
var SKILL_default5 = '---\nname:
|
|
60223
|
+
// src/instructions/skills/kn:init/SKILL.md
|
|
60224
|
+
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';
|
|
60183
60225
|
|
|
60184
|
-
// src/instructions/skills/
|
|
60185
|
-
var SKILL_default6 = '---\nname:
|
|
60226
|
+
// src/instructions/skills/kn:plan/SKILL.md
|
|
60227
|
+
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';
|
|
60186
60228
|
|
|
60187
|
-
// src/instructions/skills/
|
|
60188
|
-
var SKILL_default7 = '---\nname:
|
|
60229
|
+
// src/instructions/skills/kn:research/SKILL.md
|
|
60230
|
+
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';
|
|
60189
60231
|
|
|
60190
|
-
// src/instructions/skills/
|
|
60191
|
-
var SKILL_default8 = '---\nname:
|
|
60232
|
+
// src/instructions/skills/kn:spec/SKILL.md
|
|
60233
|
+
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';
|
|
60192
60234
|
|
|
60193
|
-
// src/instructions/skills/
|
|
60194
|
-
var SKILL_default9 = '---\nname:
|
|
60235
|
+
// src/instructions/skills/kn:template/SKILL.md
|
|
60236
|
+
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';
|
|
60195
60237
|
|
|
60196
|
-
// src/instructions/skills/
|
|
60197
|
-
var SKILL_default10 = '---\nname:
|
|
60198
|
-
|
|
60199
|
-
// src/instructions/skills/knowns.template/SKILL.md
|
|
60200
|
-
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';
|
|
60238
|
+
// src/instructions/skills/kn:verify/SKILL.md
|
|
60239
|
+
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';
|
|
60201
60240
|
|
|
60202
60241
|
// src/instructions/skills/index.ts
|
|
60203
60242
|
function parseSkillFrontmatter(content) {
|
|
@@ -60224,17 +60263,16 @@ function createSkill(content, folderName) {
|
|
|
60224
60263
|
content: content.trim()
|
|
60225
60264
|
};
|
|
60226
60265
|
}
|
|
60227
|
-
var
|
|
60228
|
-
var
|
|
60229
|
-
var
|
|
60230
|
-
var
|
|
60231
|
-
var
|
|
60232
|
-
var SKILL_EXTRACT = createSkill(SKILL_default3, "
|
|
60233
|
-
var SKILL_DOC = createSkill(SKILL_default2, "
|
|
60234
|
-
var
|
|
60235
|
-
var
|
|
60236
|
-
var
|
|
60237
|
-
var SKILL_TEMPLATE = createSkill(SKILL_default11, "knowns.template");
|
|
60266
|
+
var SKILL_INIT = createSkill(SKILL_default5, "kn:init");
|
|
60267
|
+
var SKILL_PLAN = createSkill(SKILL_default6, "kn:plan");
|
|
60268
|
+
var SKILL_IMPLEMENT = createSkill(SKILL_default4, "kn:implement");
|
|
60269
|
+
var SKILL_RESEARCH = createSkill(SKILL_default7, "kn:research");
|
|
60270
|
+
var SKILL_COMMIT = createSkill(SKILL_default, "kn:commit");
|
|
60271
|
+
var SKILL_EXTRACT = createSkill(SKILL_default3, "kn:extract");
|
|
60272
|
+
var SKILL_DOC = createSkill(SKILL_default2, "kn:doc");
|
|
60273
|
+
var SKILL_TEMPLATE = createSkill(SKILL_default9, "kn:template");
|
|
60274
|
+
var SKILL_SPEC = createSkill(SKILL_default8, "kn:spec");
|
|
60275
|
+
var SKILL_VERIFY = createSkill(SKILL_default10, "kn:verify");
|
|
60238
60276
|
|
|
60239
60277
|
// src/mcp/handlers/template.ts
|
|
60240
60278
|
function getTemplatesDir() {
|
|
@@ -60766,6 +60804,657 @@ async function handleSearch(args, fileStore) {
|
|
|
60766
60804
|
});
|
|
60767
60805
|
}
|
|
60768
60806
|
|
|
60807
|
+
// src/mcp/handlers/validate.ts
|
|
60808
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
60809
|
+
import { readFile as readFile10, readdir as readdir9, writeFile as writeFile7 } from "node:fs/promises";
|
|
60810
|
+
import { join as join20 } from "node:path";
|
|
60811
|
+
var import_gray_matter6 = __toESM(require_gray_matter(), 1);
|
|
60812
|
+
var import_handlebars2 = __toESM(require_lib(), 1);
|
|
60813
|
+
init_config();
|
|
60814
|
+
var validateTools = [
|
|
60815
|
+
{
|
|
60816
|
+
name: "validate",
|
|
60817
|
+
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.",
|
|
60818
|
+
inputSchema: {
|
|
60819
|
+
type: "object",
|
|
60820
|
+
properties: {
|
|
60821
|
+
scope: {
|
|
60822
|
+
type: "string",
|
|
60823
|
+
enum: ["all", "tasks", "docs", "templates", "sdd"],
|
|
60824
|
+
description: "Validation scope: 'all' (default), 'tasks', 'docs', 'templates', or 'sdd' for spec-driven checks"
|
|
60825
|
+
},
|
|
60826
|
+
strict: {
|
|
60827
|
+
type: "boolean",
|
|
60828
|
+
description: "Treat warnings as errors (default: false)"
|
|
60829
|
+
},
|
|
60830
|
+
fix: {
|
|
60831
|
+
type: "boolean",
|
|
60832
|
+
description: "Auto-fix supported issues like broken doc refs (default: false)"
|
|
60833
|
+
}
|
|
60834
|
+
}
|
|
60835
|
+
}
|
|
60836
|
+
}
|
|
60837
|
+
];
|
|
60838
|
+
function stripTrailingPunctuation(path2) {
|
|
60839
|
+
return path2.replace(/[.,;:!?`'")\]]+$/, "");
|
|
60840
|
+
}
|
|
60841
|
+
function extractRefs(content) {
|
|
60842
|
+
const docRefs = [];
|
|
60843
|
+
const taskRefs = [];
|
|
60844
|
+
const templateRefs = [];
|
|
60845
|
+
const docRefPattern = /@docs?\/([^\s,;:!?"'()\]]+)/g;
|
|
60846
|
+
for (const match2 of content.matchAll(docRefPattern)) {
|
|
60847
|
+
let docPath = stripTrailingPunctuation(match2[1] || "");
|
|
60848
|
+
docPath = docPath.replace(/\.md$/, "");
|
|
60849
|
+
if (docPath && !docRefs.includes(docPath)) {
|
|
60850
|
+
docRefs.push(docPath);
|
|
60851
|
+
}
|
|
60852
|
+
}
|
|
60853
|
+
const taskRefPattern = /@task-([a-zA-Z0-9]+)/g;
|
|
60854
|
+
for (const match2 of content.matchAll(taskRefPattern)) {
|
|
60855
|
+
const taskId = match2[1] || "";
|
|
60856
|
+
if (taskId && !taskRefs.includes(taskId)) {
|
|
60857
|
+
taskRefs.push(taskId);
|
|
60858
|
+
}
|
|
60859
|
+
}
|
|
60860
|
+
const templateRefPattern = /@template\/([^\s,;:!?"'()\]]+)/g;
|
|
60861
|
+
for (const match2 of content.matchAll(templateRefPattern)) {
|
|
60862
|
+
const templateName = stripTrailingPunctuation(match2[1] || "");
|
|
60863
|
+
if (templateName && !templateRefs.includes(templateName)) {
|
|
60864
|
+
templateRefs.push(templateName);
|
|
60865
|
+
}
|
|
60866
|
+
}
|
|
60867
|
+
return { docRefs, taskRefs, templateRefs };
|
|
60868
|
+
}
|
|
60869
|
+
async function loadValidateConfig(projectRoot) {
|
|
60870
|
+
const config2 = await readConfig(projectRoot);
|
|
60871
|
+
return config2.validate || {};
|
|
60872
|
+
}
|
|
60873
|
+
function getRuleSeverity(rule, defaultSeverity, validateConfig) {
|
|
60874
|
+
if (validateConfig.rules?.[rule]) {
|
|
60875
|
+
return validateConfig.rules[rule];
|
|
60876
|
+
}
|
|
60877
|
+
return defaultSeverity;
|
|
60878
|
+
}
|
|
60879
|
+
function shouldIgnore(entity, validateConfig) {
|
|
60880
|
+
if (!validateConfig.ignore) return false;
|
|
60881
|
+
for (const pattern of validateConfig.ignore) {
|
|
60882
|
+
const regex = new RegExp(`^${pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*")}$`);
|
|
60883
|
+
if (regex.test(entity)) return true;
|
|
60884
|
+
}
|
|
60885
|
+
return false;
|
|
60886
|
+
}
|
|
60887
|
+
async function findSimilarDocs(projectRoot, brokenRef) {
|
|
60888
|
+
const docsDir = join20(projectRoot, ".knowns", "docs");
|
|
60889
|
+
if (!existsSync16(docsDir)) return null;
|
|
60890
|
+
const allDocs = [];
|
|
60891
|
+
async function scanDir(dir, relativePath) {
|
|
60892
|
+
const entries = await readdir9(dir, { withFileTypes: true });
|
|
60893
|
+
for (const entry of entries) {
|
|
60894
|
+
if (entry.name.startsWith(".")) continue;
|
|
60895
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
60896
|
+
if (entry.isDirectory()) {
|
|
60897
|
+
await scanDir(join20(dir, entry.name), entryRelPath);
|
|
60898
|
+
} else if (entry.name.endsWith(".md")) {
|
|
60899
|
+
allDocs.push(entryRelPath.replace(/\.md$/, ""));
|
|
60900
|
+
}
|
|
60901
|
+
}
|
|
60902
|
+
}
|
|
60903
|
+
await scanDir(docsDir, "");
|
|
60904
|
+
const brokenLower = brokenRef.toLowerCase();
|
|
60905
|
+
let bestMatch = null;
|
|
60906
|
+
let bestScore = 0;
|
|
60907
|
+
for (const doc of allDocs) {
|
|
60908
|
+
const docLower = doc.toLowerCase();
|
|
60909
|
+
const brokenParts = brokenLower.split(/[-_/]/);
|
|
60910
|
+
const docParts = docLower.split(/[-_/]/);
|
|
60911
|
+
let matchScore = 0;
|
|
60912
|
+
for (const part of brokenParts) {
|
|
60913
|
+
if (docLower.includes(part) && part.length > 2) {
|
|
60914
|
+
matchScore += part.length;
|
|
60915
|
+
}
|
|
60916
|
+
}
|
|
60917
|
+
for (const part of docParts) {
|
|
60918
|
+
if (brokenLower.includes(part) && part.length > 2) {
|
|
60919
|
+
matchScore += part.length;
|
|
60920
|
+
}
|
|
60921
|
+
}
|
|
60922
|
+
if (matchScore > bestScore) {
|
|
60923
|
+
bestScore = matchScore;
|
|
60924
|
+
bestMatch = doc;
|
|
60925
|
+
}
|
|
60926
|
+
}
|
|
60927
|
+
return bestScore >= 3 ? bestMatch : null;
|
|
60928
|
+
}
|
|
60929
|
+
async function validateTasks(projectRoot, fileStore, validateConfig) {
|
|
60930
|
+
const issues = [];
|
|
60931
|
+
const tasks = await fileStore.getAllTasks();
|
|
60932
|
+
const taskIds = new Set(tasks.map((t) => t.id));
|
|
60933
|
+
for (const task of tasks) {
|
|
60934
|
+
const taskRef = `task-${task.id}`;
|
|
60935
|
+
if (shouldIgnore(taskRef, validateConfig)) continue;
|
|
60936
|
+
const content = `${task.description || ""} ${task.implementationPlan || ""} ${task.implementationNotes || ""}`;
|
|
60937
|
+
const { docRefs, taskRefs, templateRefs } = extractRefs(content);
|
|
60938
|
+
const noAcSeverity = getRuleSeverity("task-no-ac", "warning", validateConfig);
|
|
60939
|
+
if (noAcSeverity !== "off" && (!task.acceptanceCriteria || task.acceptanceCriteria.length === 0)) {
|
|
60940
|
+
issues.push({
|
|
60941
|
+
entity: taskRef,
|
|
60942
|
+
entityType: "task",
|
|
60943
|
+
rule: "task-no-ac",
|
|
60944
|
+
severity: noAcSeverity,
|
|
60945
|
+
message: "Task has no acceptance criteria"
|
|
60946
|
+
});
|
|
60947
|
+
}
|
|
60948
|
+
const noDescSeverity = getRuleSeverity("task-no-description", "warning", validateConfig);
|
|
60949
|
+
if (noDescSeverity !== "off" && (!task.description || task.description.trim() === "")) {
|
|
60950
|
+
issues.push({
|
|
60951
|
+
entity: taskRef,
|
|
60952
|
+
entityType: "task",
|
|
60953
|
+
rule: "task-no-description",
|
|
60954
|
+
severity: noDescSeverity,
|
|
60955
|
+
message: "Task has no description"
|
|
60956
|
+
});
|
|
60957
|
+
}
|
|
60958
|
+
const brokenDocSeverity = getRuleSeverity("task-broken-doc-ref", "error", validateConfig);
|
|
60959
|
+
if (brokenDocSeverity !== "off") {
|
|
60960
|
+
for (const docPath of docRefs) {
|
|
60961
|
+
const resolved = await resolveDoc(projectRoot, docPath);
|
|
60962
|
+
if (!resolved) {
|
|
60963
|
+
const suggestion = await findSimilarDocs(projectRoot, docPath);
|
|
60964
|
+
const issue2 = {
|
|
60965
|
+
entity: taskRef,
|
|
60966
|
+
entityType: "task",
|
|
60967
|
+
rule: "task-broken-doc-ref",
|
|
60968
|
+
severity: brokenDocSeverity,
|
|
60969
|
+
message: suggestion ? `Broken reference: @doc/${docPath} \u2192 did you mean @doc/${suggestion}?` : `Broken reference: @doc/${docPath}`,
|
|
60970
|
+
fixable: !!suggestion
|
|
60971
|
+
};
|
|
60972
|
+
if (suggestion) {
|
|
60973
|
+
issue2.fix = async () => {
|
|
60974
|
+
const tasksDir = join20(projectRoot, ".knowns", "tasks");
|
|
60975
|
+
const files = await readdir9(tasksDir);
|
|
60976
|
+
const taskFile = files.find((f) => f.startsWith(`task-${task.id} `));
|
|
60977
|
+
if (taskFile) {
|
|
60978
|
+
const taskFilePath = join20(tasksDir, taskFile);
|
|
60979
|
+
const taskContent = await readFile10(taskFilePath, "utf-8");
|
|
60980
|
+
const updated = taskContent.replace(
|
|
60981
|
+
new RegExp(`@docs?/${docPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"),
|
|
60982
|
+
`@doc/${suggestion}`
|
|
60983
|
+
);
|
|
60984
|
+
await writeFile7(taskFilePath, updated, "utf-8");
|
|
60985
|
+
}
|
|
60986
|
+
};
|
|
60987
|
+
}
|
|
60988
|
+
issues.push(issue2);
|
|
60989
|
+
}
|
|
60990
|
+
}
|
|
60991
|
+
}
|
|
60992
|
+
const brokenTaskSeverity = getRuleSeverity("task-broken-task-ref", "error", validateConfig);
|
|
60993
|
+
if (brokenTaskSeverity !== "off") {
|
|
60994
|
+
for (const refTaskId of taskRefs) {
|
|
60995
|
+
if (!taskIds.has(refTaskId)) {
|
|
60996
|
+
issues.push({
|
|
60997
|
+
entity: taskRef,
|
|
60998
|
+
entityType: "task",
|
|
60999
|
+
rule: "task-broken-task-ref",
|
|
61000
|
+
severity: brokenTaskSeverity,
|
|
61001
|
+
message: `Broken reference: @task-${refTaskId}`
|
|
61002
|
+
});
|
|
61003
|
+
}
|
|
61004
|
+
}
|
|
61005
|
+
}
|
|
61006
|
+
const brokenTplSeverity = getRuleSeverity("task-broken-template-ref", "error", validateConfig);
|
|
61007
|
+
if (brokenTplSeverity !== "off") {
|
|
61008
|
+
for (const templateName of templateRefs) {
|
|
61009
|
+
const resolved = await resolveTemplate(projectRoot, templateName);
|
|
61010
|
+
if (!resolved) {
|
|
61011
|
+
issues.push({
|
|
61012
|
+
entity: taskRef,
|
|
61013
|
+
entityType: "task",
|
|
61014
|
+
rule: "task-broken-template-ref",
|
|
61015
|
+
severity: brokenTplSeverity,
|
|
61016
|
+
message: `Broken reference: @template/${templateName}`
|
|
61017
|
+
});
|
|
61018
|
+
}
|
|
61019
|
+
}
|
|
61020
|
+
}
|
|
61021
|
+
const selfRefSeverity = getRuleSeverity("task-self-ref", "warning", validateConfig);
|
|
61022
|
+
if (selfRefSeverity !== "off" && taskRefs.includes(task.id)) {
|
|
61023
|
+
issues.push({
|
|
61024
|
+
entity: taskRef,
|
|
61025
|
+
entityType: "task",
|
|
61026
|
+
rule: "task-self-ref",
|
|
61027
|
+
severity: selfRefSeverity,
|
|
61028
|
+
message: "Task references itself"
|
|
61029
|
+
});
|
|
61030
|
+
}
|
|
61031
|
+
const circularSeverity = getRuleSeverity("task-circular-parent", "error", validateConfig);
|
|
61032
|
+
if (circularSeverity !== "off" && task.parent) {
|
|
61033
|
+
const visited = /* @__PURE__ */ new Set();
|
|
61034
|
+
let currentId = task.parent;
|
|
61035
|
+
while (currentId) {
|
|
61036
|
+
if (visited.has(currentId) || currentId === task.id) {
|
|
61037
|
+
issues.push({
|
|
61038
|
+
entity: taskRef,
|
|
61039
|
+
entityType: "task",
|
|
61040
|
+
rule: "task-circular-parent",
|
|
61041
|
+
severity: circularSeverity,
|
|
61042
|
+
message: currentId === task.id ? "Task is its own ancestor" : "Circular parent-child relationship detected"
|
|
61043
|
+
});
|
|
61044
|
+
break;
|
|
61045
|
+
}
|
|
61046
|
+
visited.add(currentId);
|
|
61047
|
+
const parentTask = tasks.find((t) => t.id === currentId);
|
|
61048
|
+
currentId = parentTask?.parent;
|
|
61049
|
+
}
|
|
61050
|
+
}
|
|
61051
|
+
}
|
|
61052
|
+
return issues;
|
|
61053
|
+
}
|
|
61054
|
+
async function validateDocs(projectRoot, fileStore, validateConfig) {
|
|
61055
|
+
const issues = [];
|
|
61056
|
+
const docsDir = join20(projectRoot, ".knowns", "docs");
|
|
61057
|
+
if (!existsSync16(docsDir)) return issues;
|
|
61058
|
+
const tasks = await fileStore.getAllTasks();
|
|
61059
|
+
const taskIds = new Set(tasks.map((t) => t.id));
|
|
61060
|
+
const referencedDocs = /* @__PURE__ */ new Set();
|
|
61061
|
+
for (const task of tasks) {
|
|
61062
|
+
const content = `${task.description || ""} ${task.implementationPlan || ""} ${task.implementationNotes || ""}`;
|
|
61063
|
+
const { docRefs } = extractRefs(content);
|
|
61064
|
+
for (const ref of docRefs) {
|
|
61065
|
+
referencedDocs.add(ref.toLowerCase());
|
|
61066
|
+
}
|
|
61067
|
+
}
|
|
61068
|
+
async function scanDir(dir, relativePath) {
|
|
61069
|
+
const entries = await readdir9(dir, { withFileTypes: true });
|
|
61070
|
+
for (const entry of entries) {
|
|
61071
|
+
if (entry.name.startsWith(".")) continue;
|
|
61072
|
+
const fullPath = join20(dir, entry.name);
|
|
61073
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
61074
|
+
if (entry.isDirectory()) {
|
|
61075
|
+
await scanDir(fullPath, entryRelPath);
|
|
61076
|
+
} else if (entry.name.endsWith(".md")) {
|
|
61077
|
+
const docPath = entryRelPath.replace(/\.md$/, "");
|
|
61078
|
+
const docRef = `docs/${docPath}`;
|
|
61079
|
+
if (shouldIgnore(docRef, validateConfig) || shouldIgnore(docPath, validateConfig)) continue;
|
|
61080
|
+
try {
|
|
61081
|
+
const content = await readFile10(fullPath, "utf-8");
|
|
61082
|
+
const { data, content: docContent } = (0, import_gray_matter6.default)(content);
|
|
61083
|
+
const noDescSeverity = getRuleSeverity("doc-no-description", "warning", validateConfig);
|
|
61084
|
+
if (noDescSeverity !== "off" && (!data.description || String(data.description).trim() === "")) {
|
|
61085
|
+
issues.push({
|
|
61086
|
+
entity: docRef,
|
|
61087
|
+
entityType: "doc",
|
|
61088
|
+
rule: "doc-no-description",
|
|
61089
|
+
severity: noDescSeverity,
|
|
61090
|
+
message: "Doc has no description"
|
|
61091
|
+
});
|
|
61092
|
+
}
|
|
61093
|
+
const orphanSeverity = getRuleSeverity("doc-orphan", "info", validateConfig);
|
|
61094
|
+
if (orphanSeverity !== "off" && !referencedDocs.has(docPath.toLowerCase())) {
|
|
61095
|
+
issues.push({
|
|
61096
|
+
entity: docRef,
|
|
61097
|
+
entityType: "doc",
|
|
61098
|
+
rule: "doc-orphan",
|
|
61099
|
+
severity: orphanSeverity,
|
|
61100
|
+
message: "Doc is not referenced by any task"
|
|
61101
|
+
});
|
|
61102
|
+
}
|
|
61103
|
+
const { docRefs, taskRefs } = extractRefs(docContent);
|
|
61104
|
+
const brokenDocSeverity = getRuleSeverity("doc-broken-doc-ref", "error", validateConfig);
|
|
61105
|
+
if (brokenDocSeverity !== "off") {
|
|
61106
|
+
for (const refDocPath of docRefs) {
|
|
61107
|
+
const resolved = await resolveDoc(projectRoot, refDocPath);
|
|
61108
|
+
if (!resolved) {
|
|
61109
|
+
const suggestion = await findSimilarDocs(projectRoot, refDocPath);
|
|
61110
|
+
const issue2 = {
|
|
61111
|
+
entity: docRef,
|
|
61112
|
+
entityType: "doc",
|
|
61113
|
+
rule: "doc-broken-doc-ref",
|
|
61114
|
+
severity: brokenDocSeverity,
|
|
61115
|
+
message: suggestion ? `Broken reference: @doc/${refDocPath} \u2192 did you mean @doc/${suggestion}?` : `Broken reference: @doc/${refDocPath}`,
|
|
61116
|
+
fixable: !!suggestion
|
|
61117
|
+
};
|
|
61118
|
+
if (suggestion) {
|
|
61119
|
+
issue2.fix = async () => {
|
|
61120
|
+
const docFileContent = await readFile10(fullPath, "utf-8");
|
|
61121
|
+
const updated = docFileContent.replace(
|
|
61122
|
+
new RegExp(`@docs?/${refDocPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"),
|
|
61123
|
+
`@doc/${suggestion}`
|
|
61124
|
+
);
|
|
61125
|
+
await writeFile7(fullPath, updated, "utf-8");
|
|
61126
|
+
};
|
|
61127
|
+
}
|
|
61128
|
+
issues.push(issue2);
|
|
61129
|
+
}
|
|
61130
|
+
}
|
|
61131
|
+
}
|
|
61132
|
+
const brokenTaskSeverity = getRuleSeverity("doc-broken-task-ref", "error", validateConfig);
|
|
61133
|
+
if (brokenTaskSeverity !== "off") {
|
|
61134
|
+
for (const refTaskId of taskRefs) {
|
|
61135
|
+
if (!taskIds.has(refTaskId)) {
|
|
61136
|
+
const issue2 = {
|
|
61137
|
+
entity: docRef,
|
|
61138
|
+
entityType: "doc",
|
|
61139
|
+
rule: "doc-broken-task-ref",
|
|
61140
|
+
severity: brokenTaskSeverity,
|
|
61141
|
+
message: `Broken reference: @task-${refTaskId}`,
|
|
61142
|
+
fixable: true
|
|
61143
|
+
};
|
|
61144
|
+
issue2.fix = async () => {
|
|
61145
|
+
const docFileContent = await readFile10(fullPath, "utf-8");
|
|
61146
|
+
const updated = docFileContent.replace(
|
|
61147
|
+
new RegExp(`@task-${refTaskId}\\b`, "g"),
|
|
61148
|
+
`~task-${refTaskId}`
|
|
61149
|
+
);
|
|
61150
|
+
await writeFile7(fullPath, updated, "utf-8");
|
|
61151
|
+
};
|
|
61152
|
+
issues.push(issue2);
|
|
61153
|
+
}
|
|
61154
|
+
}
|
|
61155
|
+
}
|
|
61156
|
+
} catch {
|
|
61157
|
+
}
|
|
61158
|
+
}
|
|
61159
|
+
}
|
|
61160
|
+
}
|
|
61161
|
+
await scanDir(docsDir, "");
|
|
61162
|
+
return issues;
|
|
61163
|
+
}
|
|
61164
|
+
async function validateTemplates(projectRoot, validateConfig) {
|
|
61165
|
+
const issues = [];
|
|
61166
|
+
const templates = await listAllTemplates(projectRoot);
|
|
61167
|
+
for (const template of templates) {
|
|
61168
|
+
const templateRef = `templates/${template.ref}`;
|
|
61169
|
+
if (shouldIgnore(templateRef, validateConfig)) continue;
|
|
61170
|
+
try {
|
|
61171
|
+
const config2 = await getTemplateConfig(template.path);
|
|
61172
|
+
const invalidSyntaxSeverity = getRuleSeverity("template-invalid-syntax", "error", validateConfig);
|
|
61173
|
+
if (invalidSyntaxSeverity !== "off" && !config2) {
|
|
61174
|
+
issues.push({
|
|
61175
|
+
entity: templateRef,
|
|
61176
|
+
entityType: "template",
|
|
61177
|
+
rule: "template-invalid-syntax",
|
|
61178
|
+
severity: invalidSyntaxSeverity,
|
|
61179
|
+
message: "Failed to load template config (invalid or missing _template.yaml)"
|
|
61180
|
+
});
|
|
61181
|
+
continue;
|
|
61182
|
+
}
|
|
61183
|
+
if (!config2) continue;
|
|
61184
|
+
const brokenDocSeverity = getRuleSeverity("template-broken-doc-ref", "error", validateConfig);
|
|
61185
|
+
if (brokenDocSeverity !== "off" && config2.doc) {
|
|
61186
|
+
const resolved = await resolveDoc(projectRoot, config2.doc);
|
|
61187
|
+
if (!resolved) {
|
|
61188
|
+
issues.push({
|
|
61189
|
+
entity: templateRef,
|
|
61190
|
+
entityType: "template",
|
|
61191
|
+
rule: "template-broken-doc-ref",
|
|
61192
|
+
severity: brokenDocSeverity,
|
|
61193
|
+
message: `Broken doc reference: @doc/${config2.doc}`
|
|
61194
|
+
});
|
|
61195
|
+
}
|
|
61196
|
+
}
|
|
61197
|
+
if (invalidSyntaxSeverity !== "off") {
|
|
61198
|
+
for (const action of config2.actions || []) {
|
|
61199
|
+
if (action.type === "add" && action.template) {
|
|
61200
|
+
const templateFilePath = join20(template.path, action.template);
|
|
61201
|
+
if (existsSync16(templateFilePath)) {
|
|
61202
|
+
try {
|
|
61203
|
+
const templateContent = await readFile10(templateFilePath, "utf-8");
|
|
61204
|
+
import_handlebars2.default.compile(templateContent);
|
|
61205
|
+
} catch (err) {
|
|
61206
|
+
issues.push({
|
|
61207
|
+
entity: templateRef,
|
|
61208
|
+
entityType: "template",
|
|
61209
|
+
rule: "template-invalid-syntax",
|
|
61210
|
+
severity: invalidSyntaxSeverity,
|
|
61211
|
+
message: `Invalid Handlebars syntax in ${action.template}: ${err instanceof Error ? err.message : "unknown error"}`
|
|
61212
|
+
});
|
|
61213
|
+
}
|
|
61214
|
+
}
|
|
61215
|
+
}
|
|
61216
|
+
}
|
|
61217
|
+
}
|
|
61218
|
+
const missingPartialSeverity = getRuleSeverity("template-missing-partial", "error", validateConfig);
|
|
61219
|
+
if (missingPartialSeverity !== "off" && existsSync16(template.path)) {
|
|
61220
|
+
const files = await readdir9(template.path);
|
|
61221
|
+
const hbsFiles = files.filter((f) => f.endsWith(".hbs"));
|
|
61222
|
+
for (const hbsFile of hbsFiles) {
|
|
61223
|
+
const content = await readFile10(join20(template.path, hbsFile), "utf-8");
|
|
61224
|
+
const partialPattern = /\{\{>\s*([^\s}]+)\s*\}\}/g;
|
|
61225
|
+
for (const match2 of content.matchAll(partialPattern)) {
|
|
61226
|
+
const partialName = match2[1];
|
|
61227
|
+
const partialPath = join20(template.path, `_${partialName}.hbs`);
|
|
61228
|
+
if (!existsSync16(partialPath)) {
|
|
61229
|
+
issues.push({
|
|
61230
|
+
entity: templateRef,
|
|
61231
|
+
entityType: "template",
|
|
61232
|
+
rule: "template-missing-partial",
|
|
61233
|
+
severity: missingPartialSeverity,
|
|
61234
|
+
message: `Missing partial: ${partialName} (expected at _${partialName}.hbs)`
|
|
61235
|
+
});
|
|
61236
|
+
}
|
|
61237
|
+
}
|
|
61238
|
+
}
|
|
61239
|
+
}
|
|
61240
|
+
} catch (err) {
|
|
61241
|
+
const invalidSyntaxSeverity = getRuleSeverity("template-invalid-syntax", "error", validateConfig);
|
|
61242
|
+
if (invalidSyntaxSeverity !== "off") {
|
|
61243
|
+
issues.push({
|
|
61244
|
+
entity: templateRef,
|
|
61245
|
+
entityType: "template",
|
|
61246
|
+
rule: "template-invalid-syntax",
|
|
61247
|
+
severity: invalidSyntaxSeverity,
|
|
61248
|
+
message: `Failed to load template config: ${err instanceof Error ? err.message : "unknown error"}`
|
|
61249
|
+
});
|
|
61250
|
+
}
|
|
61251
|
+
}
|
|
61252
|
+
}
|
|
61253
|
+
return issues;
|
|
61254
|
+
}
|
|
61255
|
+
async function applyFixes(issues) {
|
|
61256
|
+
const results = [];
|
|
61257
|
+
const fixableIssues = issues.filter((i) => i.fixable && i.fix);
|
|
61258
|
+
for (const issue2 of fixableIssues) {
|
|
61259
|
+
try {
|
|
61260
|
+
await issue2.fix?.();
|
|
61261
|
+
results.push({
|
|
61262
|
+
entity: issue2.entity,
|
|
61263
|
+
rule: issue2.rule,
|
|
61264
|
+
action: issue2.message,
|
|
61265
|
+
success: true
|
|
61266
|
+
});
|
|
61267
|
+
} catch (err) {
|
|
61268
|
+
results.push({
|
|
61269
|
+
entity: issue2.entity,
|
|
61270
|
+
rule: issue2.rule,
|
|
61271
|
+
action: `Failed: ${err instanceof Error ? err.message : "unknown error"}`,
|
|
61272
|
+
success: false
|
|
61273
|
+
});
|
|
61274
|
+
}
|
|
61275
|
+
}
|
|
61276
|
+
return results;
|
|
61277
|
+
}
|
|
61278
|
+
async function runSDDValidation(projectRoot, fileStore) {
|
|
61279
|
+
const tasks = await fileStore.getAllTasks();
|
|
61280
|
+
const docsDir = join20(projectRoot, ".knowns", "docs");
|
|
61281
|
+
const stats = {
|
|
61282
|
+
specs: { total: 0, approved: 0, draft: 0 },
|
|
61283
|
+
tasks: { total: tasks.length, done: 0, inProgress: 0, todo: 0, withSpec: 0, withoutSpec: 0 },
|
|
61284
|
+
coverage: { linked: 0, total: tasks.length, percent: 0 },
|
|
61285
|
+
acCompletion: {}
|
|
61286
|
+
};
|
|
61287
|
+
const warnings = [];
|
|
61288
|
+
const passed = [];
|
|
61289
|
+
for (const task of tasks) {
|
|
61290
|
+
if (task.status === "done") stats.tasks.done++;
|
|
61291
|
+
else if (task.status === "in-progress") stats.tasks.inProgress++;
|
|
61292
|
+
else stats.tasks.todo++;
|
|
61293
|
+
if (task.spec) {
|
|
61294
|
+
stats.tasks.withSpec++;
|
|
61295
|
+
} else {
|
|
61296
|
+
stats.tasks.withoutSpec++;
|
|
61297
|
+
warnings.push({
|
|
61298
|
+
type: "task-no-spec",
|
|
61299
|
+
entity: `task-${task.id}`,
|
|
61300
|
+
message: `${task.title} has no spec reference`
|
|
61301
|
+
});
|
|
61302
|
+
}
|
|
61303
|
+
}
|
|
61304
|
+
stats.coverage.linked = stats.tasks.withSpec;
|
|
61305
|
+
stats.coverage.percent = stats.tasks.total > 0 ? Math.round(stats.tasks.withSpec / stats.tasks.total * 100) : 0;
|
|
61306
|
+
const specsDir = join20(docsDir, "specs");
|
|
61307
|
+
if (existsSync16(specsDir)) {
|
|
61308
|
+
async function scanSpecs(dir, relativePath) {
|
|
61309
|
+
const entries = await readdir9(dir, { withFileTypes: true });
|
|
61310
|
+
for (const entry of entries) {
|
|
61311
|
+
if (entry.name.startsWith(".")) continue;
|
|
61312
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
61313
|
+
if (entry.isDirectory()) {
|
|
61314
|
+
await scanSpecs(join20(dir, entry.name), entryRelPath);
|
|
61315
|
+
} else if (entry.name.endsWith(".md")) {
|
|
61316
|
+
stats.specs.total++;
|
|
61317
|
+
const specPath = `specs/${entryRelPath.replace(/\.md$/, "")}`;
|
|
61318
|
+
try {
|
|
61319
|
+
const content = await readFile10(join20(dir, entry.name), "utf-8");
|
|
61320
|
+
const { data } = (0, import_gray_matter6.default)(content);
|
|
61321
|
+
if (data.status === "approved" || data.status === "implemented") {
|
|
61322
|
+
stats.specs.approved++;
|
|
61323
|
+
} else {
|
|
61324
|
+
stats.specs.draft++;
|
|
61325
|
+
}
|
|
61326
|
+
const linkedTasks = tasks.filter((t) => t.spec === specPath);
|
|
61327
|
+
if (linkedTasks.length > 0) {
|
|
61328
|
+
let totalAC = 0;
|
|
61329
|
+
let completedAC = 0;
|
|
61330
|
+
for (const task of linkedTasks) {
|
|
61331
|
+
totalAC += task.acceptanceCriteria.length;
|
|
61332
|
+
completedAC += task.acceptanceCriteria.filter((ac) => ac.completed).length;
|
|
61333
|
+
}
|
|
61334
|
+
const percent = totalAC > 0 ? Math.round(completedAC / totalAC * 100) : 100;
|
|
61335
|
+
stats.acCompletion[specPath] = { total: totalAC, completed: completedAC, percent };
|
|
61336
|
+
if (percent < 100 && totalAC > 0) {
|
|
61337
|
+
warnings.push({
|
|
61338
|
+
type: "spec-ac-incomplete",
|
|
61339
|
+
entity: specPath,
|
|
61340
|
+
message: `${completedAC}/${totalAC} ACs complete (${percent}%)`
|
|
61341
|
+
});
|
|
61342
|
+
}
|
|
61343
|
+
}
|
|
61344
|
+
} catch {
|
|
61345
|
+
}
|
|
61346
|
+
}
|
|
61347
|
+
}
|
|
61348
|
+
}
|
|
61349
|
+
await scanSpecs(specsDir, "");
|
|
61350
|
+
}
|
|
61351
|
+
for (const task of tasks) {
|
|
61352
|
+
if (task.spec) {
|
|
61353
|
+
const specDocPath = join20(docsDir, `${task.spec}.md`);
|
|
61354
|
+
if (!existsSync16(specDocPath)) {
|
|
61355
|
+
warnings.push({
|
|
61356
|
+
type: "spec-broken-link",
|
|
61357
|
+
entity: `task-${task.id}`,
|
|
61358
|
+
message: `Broken spec reference: @doc/${task.spec}`
|
|
61359
|
+
});
|
|
61360
|
+
}
|
|
61361
|
+
}
|
|
61362
|
+
}
|
|
61363
|
+
if (warnings.filter((w) => w.type === "spec-broken-link").length === 0) {
|
|
61364
|
+
passed.push("All spec references resolve");
|
|
61365
|
+
}
|
|
61366
|
+
for (const [specPath, completion] of Object.entries(stats.acCompletion)) {
|
|
61367
|
+
if (completion.percent === 100) {
|
|
61368
|
+
passed.push(`${specPath}: fully implemented`);
|
|
61369
|
+
}
|
|
61370
|
+
}
|
|
61371
|
+
return { stats, warnings, passed };
|
|
61372
|
+
}
|
|
61373
|
+
async function handleValidate(args, fileStore) {
|
|
61374
|
+
try {
|
|
61375
|
+
const projectRoot = getProjectRoot();
|
|
61376
|
+
if (args?.scope === "sdd") {
|
|
61377
|
+
const sddResult = await runSDDValidation(projectRoot, fileStore);
|
|
61378
|
+
return successResponse({
|
|
61379
|
+
mode: "sdd",
|
|
61380
|
+
stats: sddResult.stats,
|
|
61381
|
+
warnings: sddResult.warnings,
|
|
61382
|
+
passed: sddResult.passed
|
|
61383
|
+
});
|
|
61384
|
+
}
|
|
61385
|
+
const validateConfig = await loadValidateConfig(projectRoot);
|
|
61386
|
+
const allIssues = [];
|
|
61387
|
+
const stats = { tasks: 0, docs: 0, templates: 0 };
|
|
61388
|
+
if (!args?.type || args.type === "task") {
|
|
61389
|
+
const tasks = await fileStore.getAllTasks();
|
|
61390
|
+
stats.tasks = tasks.length;
|
|
61391
|
+
const taskIssues = await validateTasks(projectRoot, fileStore, validateConfig);
|
|
61392
|
+
allIssues.push(...taskIssues);
|
|
61393
|
+
}
|
|
61394
|
+
if (!args?.type || args.type === "doc") {
|
|
61395
|
+
const docsDir = join20(projectRoot, ".knowns", "docs");
|
|
61396
|
+
if (existsSync16(docsDir)) {
|
|
61397
|
+
async function countDocs(dir) {
|
|
61398
|
+
let count = 0;
|
|
61399
|
+
const entries = await readdir9(dir, { withFileTypes: true });
|
|
61400
|
+
for (const entry of entries) {
|
|
61401
|
+
if (entry.name.startsWith(".")) continue;
|
|
61402
|
+
if (entry.isDirectory()) {
|
|
61403
|
+
count += await countDocs(join20(dir, entry.name));
|
|
61404
|
+
} else if (entry.name.endsWith(".md")) {
|
|
61405
|
+
count++;
|
|
61406
|
+
}
|
|
61407
|
+
}
|
|
61408
|
+
return count;
|
|
61409
|
+
}
|
|
61410
|
+
stats.docs = await countDocs(docsDir);
|
|
61411
|
+
}
|
|
61412
|
+
const docIssues = await validateDocs(projectRoot, fileStore, validateConfig);
|
|
61413
|
+
allIssues.push(...docIssues);
|
|
61414
|
+
}
|
|
61415
|
+
if (!args?.type || args.type === "template") {
|
|
61416
|
+
const templates = await listAllTemplates(projectRoot);
|
|
61417
|
+
stats.templates = templates.length;
|
|
61418
|
+
const templateIssues = await validateTemplates(projectRoot, validateConfig);
|
|
61419
|
+
allIssues.push(...templateIssues);
|
|
61420
|
+
}
|
|
61421
|
+
if (args?.strict) {
|
|
61422
|
+
for (const issue2 of allIssues) {
|
|
61423
|
+
if (issue2.severity === "warning") {
|
|
61424
|
+
issue2.severity = "error";
|
|
61425
|
+
}
|
|
61426
|
+
}
|
|
61427
|
+
}
|
|
61428
|
+
let fixes = [];
|
|
61429
|
+
if (args?.fix) {
|
|
61430
|
+
fixes = await applyFixes(allIssues);
|
|
61431
|
+
}
|
|
61432
|
+
const errors = allIssues.filter((i) => i.severity === "error");
|
|
61433
|
+
const warnings = allIssues.filter((i) => i.severity === "warning");
|
|
61434
|
+
const infos = allIssues.filter((i) => i.severity === "info");
|
|
61435
|
+
return successResponse({
|
|
61436
|
+
valid: errors.length === 0,
|
|
61437
|
+
stats,
|
|
61438
|
+
summary: {
|
|
61439
|
+
errors: errors.length,
|
|
61440
|
+
warnings: warnings.length,
|
|
61441
|
+
info: infos.length
|
|
61442
|
+
},
|
|
61443
|
+
issues: allIssues.map((i) => ({
|
|
61444
|
+
entity: i.entity,
|
|
61445
|
+
entityType: i.entityType,
|
|
61446
|
+
rule: i.rule,
|
|
61447
|
+
severity: i.severity,
|
|
61448
|
+
message: i.message,
|
|
61449
|
+
fixable: i.fixable || false
|
|
61450
|
+
})),
|
|
61451
|
+
...args?.fix && fixes.length > 0 ? { fixes } : {}
|
|
61452
|
+
});
|
|
61453
|
+
} catch (error48) {
|
|
61454
|
+
return errorResponse(error48 instanceof Error ? error48.message : String(error48));
|
|
61455
|
+
}
|
|
61456
|
+
}
|
|
61457
|
+
|
|
60769
61458
|
// src/mcp/server.ts
|
|
60770
61459
|
var fileStoreCache = /* @__PURE__ */ new Map();
|
|
60771
61460
|
function getFileStore() {
|
|
@@ -60796,7 +61485,8 @@ var tools = [
|
|
|
60796
61485
|
...boardTools,
|
|
60797
61486
|
...docTools,
|
|
60798
61487
|
...templateTools,
|
|
60799
|
-
...searchTools
|
|
61488
|
+
...searchTools,
|
|
61489
|
+
...validateTools
|
|
60800
61490
|
];
|
|
60801
61491
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
60802
61492
|
return { tools };
|
|
@@ -60860,6 +61550,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
60860
61550
|
// Unified search handler
|
|
60861
61551
|
case "search":
|
|
60862
61552
|
return await handleSearch(args, getFileStore());
|
|
61553
|
+
// Validate handler
|
|
61554
|
+
case "validate":
|
|
61555
|
+
return await handleValidate(args, getFileStore());
|
|
60863
61556
|
default:
|
|
60864
61557
|
return errorResponse(`Unknown tool: ${name}`);
|
|
60865
61558
|
}
|
|
@@ -60869,7 +61562,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
60869
61562
|
});
|
|
60870
61563
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
60871
61564
|
const tasks = await getFileStore().getAllTasks();
|
|
60872
|
-
const docsDir =
|
|
61565
|
+
const docsDir = join21(getProjectRoot(), ".knowns", "docs");
|
|
60873
61566
|
const taskResources = tasks.map((task) => ({
|
|
60874
61567
|
uri: `knowns://task/${task.id}`,
|
|
60875
61568
|
name: task.title,
|
|
@@ -60877,14 +61570,14 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
60877
61570
|
description: `Task #${task.id}: ${task.title}`
|
|
60878
61571
|
}));
|
|
60879
61572
|
const docResources = [];
|
|
60880
|
-
if (
|
|
60881
|
-
const { readdir:
|
|
61573
|
+
if (existsSync17(docsDir)) {
|
|
61574
|
+
const { readdir: readdir10 } = await import("node:fs/promises");
|
|
60882
61575
|
async function getAllMdFiles3(dir, basePath = "") {
|
|
60883
61576
|
const files = [];
|
|
60884
|
-
const entries = await
|
|
61577
|
+
const entries = await readdir10(dir, { withFileTypes: true });
|
|
60885
61578
|
for (const entry of entries) {
|
|
60886
|
-
const fullPath =
|
|
60887
|
-
const relativePath = normalizePath(basePath ?
|
|
61579
|
+
const fullPath = join21(dir, entry.name);
|
|
61580
|
+
const relativePath = normalizePath(basePath ? join21(basePath, entry.name) : entry.name);
|
|
60888
61581
|
if (entry.isDirectory()) {
|
|
60889
61582
|
const subFiles = await getAllMdFiles3(fullPath, relativePath);
|
|
60890
61583
|
files.push(...subFiles);
|
|
@@ -60896,9 +61589,9 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
60896
61589
|
}
|
|
60897
61590
|
const mdFiles = await getAllMdFiles3(docsDir);
|
|
60898
61591
|
for (const file3 of mdFiles) {
|
|
60899
|
-
const filepath =
|
|
60900
|
-
const content = await
|
|
60901
|
-
const { data } = (0,
|
|
61592
|
+
const filepath = join21(docsDir, file3);
|
|
61593
|
+
const content = await readFile11(filepath, "utf-8");
|
|
61594
|
+
const { data } = (0, import_gray_matter7.default)(content);
|
|
60902
61595
|
docResources.push({
|
|
60903
61596
|
uri: `knowns://doc/${file3.replace(/\.md$/, "")}`,
|
|
60904
61597
|
name: data.title || file3.replace(/\.md$/, ""),
|
|
@@ -60933,13 +61626,13 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
60933
61626
|
const docMatch = uri.match(/^knowns:\/\/doc\/(.+)$/);
|
|
60934
61627
|
if (docMatch) {
|
|
60935
61628
|
const docPath = docMatch[1];
|
|
60936
|
-
const docsDir =
|
|
60937
|
-
const filepath =
|
|
60938
|
-
if (!
|
|
61629
|
+
const docsDir = join21(getProjectRoot(), ".knowns", "docs");
|
|
61630
|
+
const filepath = join21(docsDir, `${docPath}.md`);
|
|
61631
|
+
if (!existsSync17(filepath)) {
|
|
60939
61632
|
throw new Error(`Documentation ${docPath} not found`);
|
|
60940
61633
|
}
|
|
60941
|
-
const content = await
|
|
60942
|
-
const { data, content: docContent } = (0,
|
|
61634
|
+
const content = await readFile11(filepath, "utf-8");
|
|
61635
|
+
const { data, content: docContent } = (0, import_gray_matter7.default)(content);
|
|
60943
61636
|
return {
|
|
60944
61637
|
contents: [
|
|
60945
61638
|
{
|