kirograph 0.27.1 → 0.28.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 +35 -2
- package/dist/bin/commands/annotations.js +56 -0
- package/dist/bin/commands/annotations.js.map +7 -0
- package/dist/bin/commands/ast-rewrite.js +55 -0
- package/dist/bin/commands/ast-rewrite.js.map +7 -0
- package/dist/bin/commands/bench.js +103 -0
- package/dist/bin/commands/bench.js.map +7 -0
- package/dist/bin/commands/branch.js +133 -0
- package/dist/bin/commands/branch.js.map +7 -0
- package/dist/bin/commands/callees.js +76 -0
- package/dist/bin/commands/callees.js.map +7 -0
- package/dist/bin/commands/callers.js +76 -0
- package/dist/bin/commands/callers.js.map +7 -0
- package/dist/bin/commands/changelog.js +55 -0
- package/dist/bin/commands/changelog.js.map +7 -0
- package/dist/bin/commands/circular-deps.js +68 -0
- package/dist/bin/commands/circular-deps.js.map +7 -0
- package/dist/bin/commands/commit-context.js +55 -0
- package/dist/bin/commands/commit-context.js.map +7 -0
- package/dist/bin/commands/complexity.js +60 -0
- package/dist/bin/commands/complexity.js.map +7 -0
- package/dist/bin/commands/cost.js +146 -0
- package/dist/bin/commands/cost.js.map +7 -0
- package/dist/bin/commands/dependency-depth.js +55 -0
- package/dist/bin/commands/dependency-depth.js.map +7 -0
- package/dist/bin/commands/diff-context.js +55 -0
- package/dist/bin/commands/diff-context.js.map +7 -0
- package/dist/bin/commands/distribution.js +56 -0
- package/dist/bin/commands/distribution.js.map +7 -0
- package/dist/bin/commands/doc-coverage.js +56 -0
- package/dist/bin/commands/doc-coverage.js.map +7 -0
- package/dist/bin/commands/doctor.js +202 -0
- package/dist/bin/commands/doctor.js.map +7 -0
- package/dist/bin/commands/dsm.js +56 -0
- package/dist/bin/commands/dsm.js.map +7 -0
- package/dist/bin/commands/gini.js +55 -0
- package/dist/bin/commands/gini.js.map +7 -0
- package/dist/bin/commands/god-class.js +56 -0
- package/dist/bin/commands/god-class.js.map +7 -0
- package/dist/bin/commands/health.js +55 -0
- package/dist/bin/commands/health.js.map +7 -0
- package/dist/bin/commands/help.js +224 -45
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/commands/impact.js +76 -0
- package/dist/bin/commands/impact.js.map +7 -0
- package/dist/bin/commands/inheritance-depth.js +56 -0
- package/dist/bin/commands/inheritance-depth.js.map +7 -0
- package/dist/bin/commands/insert-at.js +55 -0
- package/dist/bin/commands/insert-at.js.map +7 -0
- package/dist/bin/commands/largest.js +56 -0
- package/dist/bin/commands/largest.js.map +7 -0
- package/dist/bin/commands/manifest.js +158 -0
- package/dist/bin/commands/manifest.js.map +7 -0
- package/dist/bin/commands/module-api.js +57 -0
- package/dist/bin/commands/module-api.js.map +7 -0
- package/dist/bin/commands/monitor.js +91 -0
- package/dist/bin/commands/monitor.js.map +7 -0
- package/dist/bin/commands/multi-replace.js +66 -0
- package/dist/bin/commands/multi-replace.js.map +7 -0
- package/dist/bin/commands/pr-context.js +55 -0
- package/dist/bin/commands/pr-context.js.map +7 -0
- package/dist/bin/commands/query.js +19 -1
- package/dist/bin/commands/query.js.map +3 -3
- package/dist/bin/commands/rank.js +56 -0
- package/dist/bin/commands/rank.js.map +7 -0
- package/dist/bin/commands/read.js +1 -1
- package/dist/bin/commands/read.js.map +2 -2
- package/dist/bin/commands/recursion.js +56 -0
- package/dist/bin/commands/recursion.js.map +7 -0
- package/dist/bin/commands/rename-preview.js +56 -0
- package/dist/bin/commands/rename-preview.js.map +7 -0
- package/dist/bin/commands/session.js +60 -0
- package/dist/bin/commands/session.js.map +7 -0
- package/dist/bin/commands/simplify-scan.js +56 -0
- package/dist/bin/commands/simplify-scan.js.map +7 -0
- package/dist/bin/commands/str-replace.js +55 -0
- package/dist/bin/commands/str-replace.js.map +7 -0
- package/dist/bin/commands/test-coverage.js +59 -0
- package/dist/bin/commands/test-coverage.js.map +7 -0
- package/dist/bin/commands/test-map.js +56 -0
- package/dist/bin/commands/test-map.js.map +7 -0
- package/dist/bin/commands/test-risk.js +59 -0
- package/dist/bin/commands/test-risk.js.map +7 -0
- package/dist/bin/commands/type-hierarchy.js +76 -0
- package/dist/bin/commands/type-hierarchy.js.map +7 -0
- package/dist/bin/commands/unused-imports.js +55 -0
- package/dist/bin/commands/unused-imports.js.map +7 -0
- package/dist/bin/commands/upgrade.js +78 -0
- package/dist/bin/commands/upgrade.js.map +7 -0
- package/dist/bin/installer/common.js +19 -9
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/config-prompt.js +147 -12
- package/dist/bin/installer/config-prompt.js.map +3 -3
- package/dist/bin/installer/index.js +62 -6
- package/dist/bin/installer/index.js.map +2 -2
- package/dist/bin/installer/instructions.js +14 -25
- package/dist/bin/installer/instructions.js.map +2 -2
- package/dist/bin/installer/mcp.js.map +2 -2
- package/dist/bin/installer/steering.js +569 -100
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/aider.js +3 -3
- package/dist/bin/installer/targets/aider.js.map +2 -2
- package/dist/bin/installer/targets/amp.js +3 -3
- package/dist/bin/installer/targets/amp.js.map +2 -2
- package/dist/bin/installer/targets/antigravity.js +4 -4
- package/dist/bin/installer/targets/antigravity.js.map +2 -2
- package/dist/bin/installer/targets/augment.js +3 -3
- package/dist/bin/installer/targets/augment.js.map +2 -2
- package/dist/bin/installer/targets/claude.js +3 -3
- package/dist/bin/installer/targets/claude.js.map +2 -2
- package/dist/bin/installer/targets/cline.js +4 -4
- package/dist/bin/installer/targets/cline.js.map +2 -2
- package/dist/bin/installer/targets/codex.js +4 -4
- package/dist/bin/installer/targets/codex.js.map +2 -2
- package/dist/bin/installer/targets/continue.js +4 -4
- package/dist/bin/installer/targets/continue.js.map +2 -2
- package/dist/bin/installer/targets/copilot-cli.js +4 -4
- package/dist/bin/installer/targets/copilot-cli.js.map +2 -2
- package/dist/bin/installer/targets/copilot.js +4 -4
- package/dist/bin/installer/targets/copilot.js.map +2 -2
- package/dist/bin/installer/targets/cursor.js +5 -5
- package/dist/bin/installer/targets/cursor.js.map +2 -2
- package/dist/bin/installer/targets/devin.js +4 -4
- package/dist/bin/installer/targets/devin.js.map +2 -2
- package/dist/bin/installer/targets/gemini-cli.js +4 -4
- package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
- package/dist/bin/installer/targets/generic.js +2 -2
- package/dist/bin/installer/targets/generic.js.map +2 -2
- package/dist/bin/installer/targets/goose.js +3 -4
- package/dist/bin/installer/targets/goose.js.map +3 -3
- package/dist/bin/installer/targets/index.js.map +1 -1
- package/dist/bin/installer/targets/junie.js +4 -4
- package/dist/bin/installer/targets/junie.js.map +2 -2
- package/dist/bin/installer/targets/kilo.js +4 -4
- package/dist/bin/installer/targets/kilo.js.map +2 -2
- package/dist/bin/installer/targets/kiro.js +27 -3
- package/dist/bin/installer/targets/kiro.js.map +2 -2
- package/dist/bin/installer/targets/opencode.js +3 -3
- package/dist/bin/installer/targets/opencode.js.map +2 -2
- package/dist/bin/installer/targets/openhands.js +3 -3
- package/dist/bin/installer/targets/openhands.js.map +2 -2
- package/dist/bin/installer/targets/qoder.js +2 -2
- package/dist/bin/installer/targets/qoder.js.map +2 -2
- package/dist/bin/installer/targets/qwen.js +2 -2
- package/dist/bin/installer/targets/qwen.js.map +2 -2
- package/dist/bin/installer/targets/replit.js +3 -3
- package/dist/bin/installer/targets/replit.js.map +2 -2
- package/dist/bin/installer/targets/roo.js +4 -4
- package/dist/bin/installer/targets/roo.js.map +2 -2
- package/dist/bin/installer/targets/tabnine.js +3 -3
- package/dist/bin/installer/targets/tabnine.js.map +2 -2
- package/dist/bin/installer/targets/trae.js +3 -3
- package/dist/bin/installer/targets/trae.js.map +2 -2
- package/dist/bin/installer/targets/warp.js +4 -4
- package/dist/bin/installer/targets/warp.js.map +2 -2
- package/dist/bin/installer/targets/windsurf.js +4 -4
- package/dist/bin/installer/targets/windsurf.js.map +2 -2
- package/dist/bin/kirograph.js +83 -1
- package/dist/bin/kirograph.js.map +3 -3
- package/dist/config.js +30 -8
- package/dist/config.js.map +2 -2
- package/dist/core/branch-manager.js +111 -0
- package/dist/core/branch-manager.js.map +7 -0
- package/dist/core/pipeline.js +3 -3
- package/dist/core/pipeline.js.map +2 -2
- package/dist/data/pixelrag-bridge.js +93 -0
- package/dist/data/pixelrag-bridge.js.map +7 -0
- package/dist/data/pixelrag-manager.js +416 -0
- package/dist/data/pixelrag-manager.js.map +7 -0
- package/dist/db/database.js +34 -7
- package/dist/db/database.js.map +2 -2
- package/dist/db/schema.sql +7 -1
- package/dist/extraction/complexity.js +116 -0
- package/dist/extraction/complexity.js.map +7 -0
- package/dist/extraction/extractor.js +52 -1
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/graph/git-context.js +229 -0
- package/dist/graph/git-context.js.map +7 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +2 -2
- package/dist/mcp/cache.js +1 -1
- package/dist/mcp/cache.js.map +2 -2
- package/dist/mcp/handler.js +446 -0
- package/dist/mcp/handler.js.map +7 -0
- package/dist/mcp/handlers/architecture.js +297 -0
- package/dist/mcp/handlers/architecture.js.map +7 -0
- package/dist/mcp/handlers/branch.js +175 -0
- package/dist/mcp/handlers/branch.js.map +7 -0
- package/dist/mcp/handlers/code-health.js +563 -0
- package/dist/mcp/handlers/code-health.js.map +7 -0
- package/dist/mcp/handlers/complexity.js +267 -0
- package/dist/mcp/handlers/complexity.js.map +7 -0
- package/dist/mcp/handlers/core.js +611 -0
- package/dist/mcp/handlers/core.js.map +7 -0
- package/dist/mcp/handlers/data.js +337 -0
- package/dist/mcp/handlers/data.js.map +7 -0
- package/dist/mcp/handlers/docs.js +159 -0
- package/dist/mcp/handlers/docs.js.map +7 -0
- package/dist/mcp/handlers/edit-primitives.js +200 -0
- package/dist/mcp/handlers/edit-primitives.js.map +7 -0
- package/dist/mcp/handlers/git-context.js +286 -0
- package/dist/mcp/handlers/git-context.js.map +7 -0
- package/dist/mcp/handlers/memory.js +363 -0
- package/dist/mcp/handlers/memory.js.map +7 -0
- package/dist/mcp/handlers/patterns.js +310 -0
- package/dist/mcp/handlers/patterns.js.map +7 -0
- package/dist/mcp/handlers/security.js +699 -0
- package/dist/mcp/handlers/security.js.map +7 -0
- package/dist/mcp/handlers/utils.js +97 -0
- package/dist/mcp/handlers/utils.js.map +7 -0
- package/dist/mcp/handlers/watchmen.js +104 -0
- package/dist/mcp/handlers/watchmen.js.map +7 -0
- package/dist/mcp/handlers/wiki.js +242 -0
- package/dist/mcp/handlers/wiki.js.map +7 -0
- package/dist/mcp/server.js +72 -3
- package/dist/mcp/server.js.map +2 -2
- package/dist/mcp/tool-names.js +136 -10
- package/dist/mcp/tool-names.js.map +2 -2
- package/dist/mcp/tools.js +846 -3019
- package/dist/mcp/tools.js.map +3 -3
- package/dist/types.js.map +2 -2
- package/package.json +36 -10
package/dist/mcp/tools.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,76 +15,17 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
var tools_exports = {};
|
|
30
20
|
__export(tools_exports, {
|
|
31
21
|
KIROGRAPH_TOOL_NAMES: () => import_tool_names.KIROGRAPH_TOOL_NAMES,
|
|
32
22
|
LIVE_SEARCH_TOOL_DEFINITION: () => LIVE_SEARCH_TOOL_DEFINITION,
|
|
33
|
-
ToolHandler: () => ToolHandler,
|
|
23
|
+
ToolHandler: () => import_handler.ToolHandler,
|
|
34
24
|
tools: () => tools
|
|
35
25
|
});
|
|
36
26
|
module.exports = __toCommonJS(tools_exports);
|
|
37
|
-
var fs = __toESM(require("fs"));
|
|
38
|
-
var path = __toESM(require("path"));
|
|
39
|
-
var crypto = __toESM(require("crypto"));
|
|
40
|
-
var import_child_process = require("child_process");
|
|
41
|
-
var import__ = __toESM(require("../index"));
|
|
42
|
-
var import_errors = require("../errors");
|
|
43
|
-
var import_compression = require("../compression/index");
|
|
44
|
-
var import_tracker = require("../compression/tracker");
|
|
45
27
|
var import_tool_names = require("./tool-names");
|
|
46
|
-
|
|
47
|
-
function truncate(s) {
|
|
48
|
-
return s.length > MAX_OUTPUT ? s.slice(0, MAX_OUTPUT) + "\n\u2026[truncated]" : s;
|
|
49
|
-
}
|
|
50
|
-
function estimateFileTokens(projectRoot, filePaths) {
|
|
51
|
-
let total = 0;
|
|
52
|
-
for (const fp of filePaths) {
|
|
53
|
-
try {
|
|
54
|
-
const fullPath = path.isAbsolute(fp) ? fp : path.join(projectRoot, fp);
|
|
55
|
-
const stat = fs.statSync(fullPath);
|
|
56
|
-
total += Math.round(stat.size / 4);
|
|
57
|
-
} catch {
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return total;
|
|
61
|
-
}
|
|
62
|
-
function clampLimit(value, defaultValue) {
|
|
63
|
-
const n = typeof value === "number" ? value : defaultValue;
|
|
64
|
-
return Math.max(1, Math.min(100, Math.round(n)));
|
|
65
|
-
}
|
|
66
|
-
function mapKind(kind) {
|
|
67
|
-
if (kind === "type_alias") return "type";
|
|
68
|
-
return kind;
|
|
69
|
-
}
|
|
70
|
-
function writeSessionMarker(projectRoot) {
|
|
71
|
-
try {
|
|
72
|
-
const hash = crypto.createHash("sha256").update(projectRoot).digest("hex").slice(0, 16);
|
|
73
|
-
fs.writeFileSync(`/tmp/kirograph-consulted-${hash}`, String(Date.now()));
|
|
74
|
-
} catch {
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function formatAge(timestamp) {
|
|
78
|
-
const diff = Date.now() - timestamp;
|
|
79
|
-
const minutes = Math.floor(diff / 6e4);
|
|
80
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
81
|
-
const hours = Math.floor(minutes / 60);
|
|
82
|
-
if (hours < 24) return `${hours}h ago`;
|
|
83
|
-
const days = Math.floor(hours / 24);
|
|
84
|
-
if (days < 7) return `${days}d ago`;
|
|
85
|
-
const weeks = Math.floor(days / 7);
|
|
86
|
-
if (weeks < 5) return `${weeks}w ago`;
|
|
87
|
-
const months = Math.floor(days / 30);
|
|
88
|
-
return `${months}mo ago`;
|
|
89
|
-
}
|
|
28
|
+
var import_handler = require("./handler");
|
|
90
29
|
const tools = [
|
|
91
30
|
{
|
|
92
31
|
name: "kirograph_search",
|
|
@@ -95,27 +34,39 @@ const tools = [
|
|
|
95
34
|
type: "object",
|
|
96
35
|
properties: {
|
|
97
36
|
query: { type: "string", description: 'Symbol name or partial name (e.g., "auth", "signIn", "UserService")' },
|
|
37
|
+
mode: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: 'Search mode: "name" (default, FTS prefix match) or "similar" (fuzzy substring match, broader results)',
|
|
40
|
+
enum: ["name", "similar"],
|
|
41
|
+
default: "name"
|
|
42
|
+
},
|
|
98
43
|
kind: {
|
|
99
44
|
type: "string",
|
|
100
45
|
description: "Filter by node kind",
|
|
101
46
|
enum: ["function", "method", "class", "interface", "type_alias", "variable", "route", "component"]
|
|
102
47
|
},
|
|
103
48
|
limit: { type: "number", description: "Max results 1-100 (default: 10)", default: 10 },
|
|
104
|
-
projectPath: { type: "string"
|
|
49
|
+
projectPath: { type: "string" }
|
|
105
50
|
},
|
|
106
51
|
required: ["query"]
|
|
107
52
|
}
|
|
108
53
|
},
|
|
109
54
|
{
|
|
110
55
|
name: "kirograph_context",
|
|
111
|
-
description: "
|
|
56
|
+
description: "Build comprehensive context for a task or feature request.",
|
|
112
57
|
inputSchema: {
|
|
113
58
|
type: "object",
|
|
114
59
|
properties: {
|
|
115
|
-
task: { type: "string", description: "
|
|
116
|
-
maxNodes: { type: "number", description: "Max symbols to include
|
|
117
|
-
includeCode: { type: "boolean", description: "Include code snippets
|
|
118
|
-
|
|
60
|
+
task: { type: "string", description: "Task description or feature name" },
|
|
61
|
+
maxNodes: { type: "number", description: "Max symbols to include", default: 20 },
|
|
62
|
+
includeCode: { type: "boolean", description: "Include code snippets. Deprecated \u2014 prefer detail.", default: true },
|
|
63
|
+
detail: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "Code verbosity: full, signatures, or summary",
|
|
66
|
+
enum: ["full", "signatures", "summary"],
|
|
67
|
+
default: "full"
|
|
68
|
+
},
|
|
69
|
+
projectPath: { type: "string" }
|
|
119
70
|
},
|
|
120
71
|
required: ["task"]
|
|
121
72
|
}
|
|
@@ -128,7 +79,7 @@ const tools = [
|
|
|
128
79
|
properties: {
|
|
129
80
|
symbol: { type: "string", description: "Symbol name to find callers for" },
|
|
130
81
|
limit: { type: "number", description: "Max results 1-100 (default: 20)", default: 20 },
|
|
131
|
-
projectPath: { type: "string"
|
|
82
|
+
projectPath: { type: "string" }
|
|
132
83
|
},
|
|
133
84
|
required: ["symbol"]
|
|
134
85
|
}
|
|
@@ -141,33 +92,40 @@ const tools = [
|
|
|
141
92
|
properties: {
|
|
142
93
|
symbol: { type: "string", description: "Symbol name to find callees for" },
|
|
143
94
|
limit: { type: "number", description: "Max results 1-100 (default: 20)", default: 20 },
|
|
144
|
-
projectPath: { type: "string"
|
|
95
|
+
projectPath: { type: "string" }
|
|
145
96
|
},
|
|
146
97
|
required: ["symbol"]
|
|
147
98
|
}
|
|
148
99
|
},
|
|
149
100
|
{
|
|
150
101
|
name: "kirograph_impact",
|
|
151
|
-
description: "Analyze what code would be affected by changing a symbol.
|
|
102
|
+
description: "Analyze what code would be affected by changing a symbol.",
|
|
152
103
|
inputSchema: {
|
|
153
104
|
type: "object",
|
|
154
105
|
properties: {
|
|
155
106
|
symbol: { type: "string", description: "Symbol name to analyze impact for" },
|
|
156
107
|
depth: { type: "number", description: "Traversal depth (default: 2)", default: 2 },
|
|
157
|
-
projectPath: { type: "string"
|
|
108
|
+
projectPath: { type: "string" }
|
|
158
109
|
},
|
|
159
110
|
required: ["symbol"]
|
|
160
111
|
}
|
|
161
112
|
},
|
|
162
113
|
{
|
|
163
114
|
name: "kirograph_node",
|
|
164
|
-
description: "Get details about a specific symbol
|
|
115
|
+
description: "Get details about a specific symbol.",
|
|
165
116
|
inputSchema: {
|
|
166
117
|
type: "object",
|
|
167
118
|
properties: {
|
|
168
|
-
symbol: { type: "string", description: "Symbol name to look up" },
|
|
169
|
-
|
|
170
|
-
|
|
119
|
+
symbol: { type: "string", description: "Symbol name or fully-qualified name to look up" },
|
|
120
|
+
qualified: { type: "boolean", description: "When true, treat symbol as a fully-qualified name (exact match, bypasses FTS \u2014 use when you have the exact qualifiedName from a previous result)", default: false },
|
|
121
|
+
includeCode: { type: "boolean", description: "Include full source code. Deprecated \u2014 prefer detail.", default: false },
|
|
122
|
+
detail: {
|
|
123
|
+
type: "string",
|
|
124
|
+
description: "Output level: summary, signatures, or full",
|
|
125
|
+
enum: ["summary", "signatures", "full"],
|
|
126
|
+
default: "summary"
|
|
127
|
+
},
|
|
128
|
+
projectPath: { type: "string" }
|
|
171
129
|
},
|
|
172
130
|
required: ["symbol"]
|
|
173
131
|
}
|
|
@@ -178,27 +136,27 @@ const tools = [
|
|
|
178
136
|
inputSchema: {
|
|
179
137
|
type: "object",
|
|
180
138
|
properties: {
|
|
181
|
-
projectPath: { type: "string"
|
|
139
|
+
projectPath: { type: "string" }
|
|
182
140
|
}
|
|
183
141
|
}
|
|
184
142
|
},
|
|
185
143
|
{
|
|
186
144
|
name: "kirograph_files",
|
|
187
|
-
description: "List
|
|
145
|
+
description: "List indexed file structure with optional filtering.",
|
|
188
146
|
inputSchema: {
|
|
189
147
|
type: "object",
|
|
190
148
|
properties: {
|
|
191
149
|
filterPath: { type: "string", description: 'Filter by directory path prefix (e.g., "src/")' },
|
|
192
150
|
pattern: { type: "string", description: 'Filter by glob pattern (e.g., "**/*.ts")' },
|
|
193
|
-
maxDepth: { type: "number", description: "
|
|
151
|
+
maxDepth: { type: "number", description: "Max directory depth to traverse" },
|
|
194
152
|
format: {
|
|
195
153
|
type: "string",
|
|
196
154
|
description: 'Output format: "tree" (default, visual tree), "flat" (one path per line), "grouped" (grouped by directory), "compact" (rtk-style summary with counts)',
|
|
197
155
|
enum: ["tree", "flat", "grouped", "compact"],
|
|
198
156
|
default: "tree"
|
|
199
157
|
},
|
|
200
|
-
includeMetadata: { type: "boolean", description: "Include language and symbol count
|
|
201
|
-
projectPath: { type: "string"
|
|
158
|
+
includeMetadata: { type: "boolean", description: "Include language and symbol count", default: true },
|
|
159
|
+
projectPath: { type: "string" }
|
|
202
160
|
}
|
|
203
161
|
}
|
|
204
162
|
},
|
|
@@ -209,7 +167,7 @@ const tools = [
|
|
|
209
167
|
type: "object",
|
|
210
168
|
properties: {
|
|
211
169
|
limit: { type: "number", description: "Max results 1-100 (default: 50)", default: 50 },
|
|
212
|
-
projectPath: { type: "string"
|
|
170
|
+
projectPath: { type: "string" }
|
|
213
171
|
}
|
|
214
172
|
}
|
|
215
173
|
},
|
|
@@ -219,7 +177,7 @@ const tools = [
|
|
|
219
177
|
inputSchema: {
|
|
220
178
|
type: "object",
|
|
221
179
|
properties: {
|
|
222
|
-
projectPath: { type: "string"
|
|
180
|
+
projectPath: { type: "string" }
|
|
223
181
|
}
|
|
224
182
|
}
|
|
225
183
|
},
|
|
@@ -231,58 +189,71 @@ const tools = [
|
|
|
231
189
|
properties: {
|
|
232
190
|
from: { type: "string", description: "Source symbol name" },
|
|
233
191
|
to: { type: "string", description: "Target symbol name" },
|
|
234
|
-
projectPath: { type: "string"
|
|
192
|
+
projectPath: { type: "string" }
|
|
235
193
|
},
|
|
236
194
|
required: ["from", "to"]
|
|
237
195
|
}
|
|
238
196
|
},
|
|
239
197
|
{
|
|
240
198
|
name: "kirograph_architecture",
|
|
241
|
-
description: "
|
|
199
|
+
description: "Show high-level architecture: packages, layers, dependencies.",
|
|
242
200
|
inputSchema: {
|
|
243
201
|
type: "object",
|
|
244
202
|
properties: {
|
|
245
203
|
level: {
|
|
246
204
|
type: "string",
|
|
247
|
-
description:
|
|
205
|
+
description: "packages, layers, or both",
|
|
248
206
|
enum: ["packages", "layers", "both"],
|
|
249
207
|
default: "both"
|
|
250
208
|
},
|
|
251
|
-
includeFiles: { type: "boolean", description: "Include per-file package/layer assignments
|
|
252
|
-
projectPath: { type: "string"
|
|
209
|
+
includeFiles: { type: "boolean", description: "Include per-file package/layer assignments", default: false },
|
|
210
|
+
projectPath: { type: "string" }
|
|
253
211
|
}
|
|
254
212
|
}
|
|
255
213
|
},
|
|
256
214
|
{
|
|
257
215
|
name: "kirograph_coupling",
|
|
258
|
-
description: "
|
|
216
|
+
description: "Package coupling metrics: Ca, Ce, and instability (Ce/(Ca+Ce)).",
|
|
259
217
|
inputSchema: {
|
|
260
218
|
type: "object",
|
|
261
219
|
properties: {
|
|
262
220
|
sortBy: {
|
|
263
221
|
type: "string",
|
|
264
|
-
description:
|
|
222
|
+
description: "Sort by instability, ca, ce, or name",
|
|
265
223
|
enum: ["instability", "afferent", "efferent"],
|
|
266
224
|
default: "instability"
|
|
267
225
|
},
|
|
268
226
|
limit: { type: "number", description: "Max results (default: 20)", default: 20 },
|
|
269
|
-
projectPath: { type: "string"
|
|
227
|
+
projectPath: { type: "string" }
|
|
270
228
|
}
|
|
271
229
|
}
|
|
272
230
|
},
|
|
273
231
|
{
|
|
274
232
|
name: "kirograph_package",
|
|
275
|
-
description: "Drill into
|
|
233
|
+
description: "Drill into a package: files, exports, and dependencies.",
|
|
276
234
|
inputSchema: {
|
|
277
235
|
type: "object",
|
|
278
236
|
properties: {
|
|
279
237
|
package: { type: "string", description: "Package name or path (partial match accepted)" },
|
|
280
238
|
includeFiles: { type: "boolean", description: "List files in the package (default: true)", default: true },
|
|
281
|
-
projectPath: { type: "string"
|
|
239
|
+
projectPath: { type: "string" }
|
|
282
240
|
},
|
|
283
241
|
required: ["package"]
|
|
284
242
|
}
|
|
285
243
|
},
|
|
244
|
+
{
|
|
245
|
+
name: "kirograph_manifest",
|
|
246
|
+
description: "Workspace manifest summary: list all packages across ecosystems with versions, external dep counts, and license. Pass package= to drill into one package. Pass ecosystem= to filter (npm, cargo, go, python, maven, gradle, csproj). Pass showDrift=true to surface packages that appear in multiple manifest files with differing versions.",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
package: { type: "string", description: "Package name or path for a focused view (partial match accepted)" },
|
|
251
|
+
ecosystem: { type: "string", description: 'Filter by ecosystem / language (e.g. "npm", "cargo", "go", "python")' },
|
|
252
|
+
showDrift: { type: "boolean", description: "Highlight packages declared in multiple manifests with different versions (default: false)", default: false },
|
|
253
|
+
projectPath: { type: "string" }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
286
257
|
{
|
|
287
258
|
name: "kirograph_hotspots",
|
|
288
259
|
description: "Find the most-connected symbols in the codebase by total edge degree (incoming + outgoing). Useful for identifying load-bearing code, core abstractions, or blast-radius hot zones.",
|
|
@@ -290,7 +261,7 @@ const tools = [
|
|
|
290
261
|
type: "object",
|
|
291
262
|
properties: {
|
|
292
263
|
limit: { type: "number", description: "Max results 1-100 (default: 20)", default: 20 },
|
|
293
|
-
projectPath: { type: "string"
|
|
264
|
+
projectPath: { type: "string" }
|
|
294
265
|
}
|
|
295
266
|
}
|
|
296
267
|
},
|
|
@@ -301,78 +272,272 @@ const tools = [
|
|
|
301
272
|
type: "object",
|
|
302
273
|
properties: {
|
|
303
274
|
limit: { type: "number", description: "Max results 1-100 (default: 20)", default: 20 },
|
|
304
|
-
projectPath: { type: "string"
|
|
275
|
+
projectPath: { type: "string" }
|
|
305
276
|
}
|
|
306
277
|
}
|
|
307
278
|
},
|
|
308
279
|
{
|
|
309
280
|
name: "kirograph_diff",
|
|
310
|
-
description: "Compare
|
|
281
|
+
description: "Compare current graph against a saved snapshot.",
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: "object",
|
|
284
|
+
properties: {
|
|
285
|
+
snapshot: { type: "string", description: "Snapshot label; omit for latest" },
|
|
286
|
+
projectPath: { type: "string" }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "kirograph_snapshot_save",
|
|
292
|
+
description: "Save a snapshot of the current graph state. Snapshots can be compared with kirograph_diff to track structural changes over time.",
|
|
293
|
+
inputSchema: {
|
|
294
|
+
type: "object",
|
|
295
|
+
properties: {
|
|
296
|
+
label: { type: "string", description: "Label for the snapshot (default: timestamp-based label)" },
|
|
297
|
+
projectPath: { type: "string" }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: "kirograph_snapshot_list",
|
|
303
|
+
description: "List all saved graph snapshots with their labels, timestamps, and symbol/edge counts.",
|
|
311
304
|
inputSchema: {
|
|
312
305
|
type: "object",
|
|
313
306
|
properties: {
|
|
314
|
-
|
|
315
|
-
projectPath: { type: "string", description: "Project root path (optional)" }
|
|
307
|
+
projectPath: { type: "string" }
|
|
316
308
|
}
|
|
317
309
|
}
|
|
318
310
|
},
|
|
319
311
|
{
|
|
320
312
|
name: "kirograph_type_hierarchy",
|
|
321
|
-
description: "Traverse
|
|
313
|
+
description: "Traverse base and derived types of a class or interface.",
|
|
322
314
|
inputSchema: {
|
|
323
315
|
type: "object",
|
|
324
316
|
properties: {
|
|
325
317
|
symbol: { type: "string", description: "Class or interface name" },
|
|
326
318
|
direction: {
|
|
327
319
|
type: "string",
|
|
328
|
-
description:
|
|
320
|
+
description: "up=base, down=derived, both=all",
|
|
329
321
|
enum: ["up", "down", "both"],
|
|
330
322
|
default: "both"
|
|
331
323
|
},
|
|
332
|
-
projectPath: { type: "string"
|
|
324
|
+
projectPath: { type: "string" }
|
|
333
325
|
},
|
|
334
326
|
required: ["symbol"]
|
|
335
327
|
}
|
|
336
328
|
},
|
|
329
|
+
{
|
|
330
|
+
name: "kirograph_module_api",
|
|
331
|
+
description: "List all exported symbols in a file or directory \u2014 the public API surface.",
|
|
332
|
+
inputSchema: {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: {
|
|
335
|
+
path: { type: "string", description: 'File path (e.g. "src/auth/service.ts") or directory prefix (e.g. "src/auth/"). Omit for the whole project.' },
|
|
336
|
+
limit: { type: "number", description: "Max results (default: 100)", default: 100 },
|
|
337
|
+
projectPath: { type: "string" }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "kirograph_rename_preview",
|
|
343
|
+
description: "Show every reference site for a symbol \u2014 blast-radius preview before a rename.",
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: "object",
|
|
346
|
+
properties: {
|
|
347
|
+
symbol: { type: "string", description: "Symbol name to find all references for" },
|
|
348
|
+
projectPath: { type: "string" }
|
|
349
|
+
},
|
|
350
|
+
required: ["symbol"]
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: "kirograph_doc_coverage",
|
|
355
|
+
description: "Find exported symbols that are missing docstrings.",
|
|
356
|
+
inputSchema: {
|
|
357
|
+
type: "object",
|
|
358
|
+
properties: {
|
|
359
|
+
limit: { type: "number", description: "Max results (default: 50)", default: 50 },
|
|
360
|
+
projectPath: { type: "string" }
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: "kirograph_god_class",
|
|
366
|
+
description: "List classes ranked by member count to identify god classes.",
|
|
367
|
+
inputSchema: {
|
|
368
|
+
type: "object",
|
|
369
|
+
properties: {
|
|
370
|
+
threshold: { type: "number", description: "Member count threshold for flagging (default: 10)", default: 10 },
|
|
371
|
+
limit: { type: "number", description: "Max results (default: 20)", default: 20 },
|
|
372
|
+
projectPath: { type: "string" }
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "kirograph_inheritance_depth",
|
|
378
|
+
description: "Show the deepest inheritance chains (BFS on extends/implements edges).",
|
|
379
|
+
inputSchema: {
|
|
380
|
+
type: "object",
|
|
381
|
+
properties: {
|
|
382
|
+
limit: { type: "number", description: "Max results (default: 20)", default: 20 },
|
|
383
|
+
projectPath: { type: "string" }
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "kirograph_recursion",
|
|
389
|
+
description: "Find recursive and mutually-recursive functions (SCC on call edges).",
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: "object",
|
|
392
|
+
properties: {
|
|
393
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
394
|
+
projectPath: { type: "string" }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: "kirograph_largest",
|
|
400
|
+
description: "List symbols ranked by lines of code (end_line \u2212 start_line).",
|
|
401
|
+
inputSchema: {
|
|
402
|
+
type: "object",
|
|
403
|
+
properties: {
|
|
404
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
405
|
+
projectPath: { type: "string" }
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
name: "kirograph_rank",
|
|
411
|
+
description: "Rank symbols by fan-in (callers) or fan-out (callees) edge count.",
|
|
412
|
+
inputSchema: {
|
|
413
|
+
type: "object",
|
|
414
|
+
properties: {
|
|
415
|
+
by: { type: "string", description: 'Ranking dimension: "fan-in" (default) or "fan-out"', enum: ["fan-in", "fan-out"], default: "fan-in" },
|
|
416
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
417
|
+
projectPath: { type: "string" }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: "kirograph_distribution",
|
|
423
|
+
description: "Symbol-kind breakdown (function, class, method\u2026) for a file, directory, or the whole project.",
|
|
424
|
+
inputSchema: {
|
|
425
|
+
type: "object",
|
|
426
|
+
properties: {
|
|
427
|
+
path: { type: "string", description: "Directory or file path prefix to scope. Omit for whole project." },
|
|
428
|
+
projectPath: { type: "string" }
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: "kirograph_annotations",
|
|
434
|
+
description: "Decorator/annotation histogram for the whole project, or list of symbols using a specific decorator.",
|
|
435
|
+
inputSchema: {
|
|
436
|
+
type: "object",
|
|
437
|
+
properties: {
|
|
438
|
+
decorator: { type: "string", description: 'Decorator name to filter (e.g. "Injectable"). Omit for full histogram.' },
|
|
439
|
+
limit: { type: "number", description: "Max results (default: 50)", default: 50 },
|
|
440
|
+
projectPath: { type: "string" }
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: "kirograph_session_start",
|
|
446
|
+
description: "Save the current graph metrics as a session baseline.",
|
|
447
|
+
inputSchema: {
|
|
448
|
+
type: "object",
|
|
449
|
+
properties: {
|
|
450
|
+
projectPath: { type: "string" }
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: "kirograph_session_end",
|
|
456
|
+
description: "Diff current graph metrics against the session baseline and report per-dimension deltas.",
|
|
457
|
+
inputSchema: {
|
|
458
|
+
type: "object",
|
|
459
|
+
properties: {
|
|
460
|
+
projectPath: { type: "string" }
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: "kirograph_unused_imports",
|
|
466
|
+
description: "Find import nodes with zero resolved downstream edges (i.e. nothing in the codebase references the imported symbol).",
|
|
467
|
+
inputSchema: {
|
|
468
|
+
type: "object",
|
|
469
|
+
properties: {
|
|
470
|
+
limit: { type: "number", description: "Max results (default: 50)" },
|
|
471
|
+
projectPath: { type: "string" }
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: "kirograph_gini",
|
|
477
|
+
description: "Compute the Gini inequality coefficient for a size metric (loc, fan-in, or fan-out) across all function/method nodes. Returns the coefficient plus top/bottom 5.",
|
|
478
|
+
inputSchema: {
|
|
479
|
+
type: "object",
|
|
480
|
+
properties: {
|
|
481
|
+
metric: {
|
|
482
|
+
type: "string",
|
|
483
|
+
description: 'Metric to measure: "loc" (lines of code), "fan-in" (incoming calls), "fan-out" (outgoing calls)',
|
|
484
|
+
enum: ["loc", "fan-in", "fan-out"],
|
|
485
|
+
default: "loc"
|
|
486
|
+
},
|
|
487
|
+
projectPath: { type: "string" }
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "kirograph_dependency_depth",
|
|
493
|
+
description: "Run a Kahn topological sort on the file import graph and report each file's longest path from a root (depth). Useful for spotting deeply layered dependency chains.",
|
|
494
|
+
inputSchema: {
|
|
495
|
+
type: "object",
|
|
496
|
+
properties: {
|
|
497
|
+
limit: { type: "number", description: "Max files to show sorted by depth DESC (default: 20)" },
|
|
498
|
+
projectPath: { type: "string" }
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
},
|
|
337
502
|
{
|
|
338
503
|
name: "kirograph_exec",
|
|
339
|
-
description: "Run a shell command
|
|
504
|
+
description: "Run a shell command with token-optimized, noise-filtered output.",
|
|
340
505
|
inputSchema: {
|
|
341
506
|
type: "object",
|
|
342
507
|
properties: {
|
|
343
|
-
command: { type: "string", description:
|
|
344
|
-
cwd: { type: "string", description: "Working directory
|
|
508
|
+
command: { type: "string", description: "Shell command to execute" },
|
|
509
|
+
cwd: { type: "string", description: "Working directory" },
|
|
345
510
|
level: {
|
|
346
511
|
type: "string",
|
|
347
512
|
description: 'Compression level: "normal" (balanced), "aggressive" (more compact), "ultra" (maximum compression)',
|
|
348
513
|
enum: ["normal", "aggressive", "ultra"],
|
|
349
514
|
default: "normal"
|
|
350
515
|
},
|
|
351
|
-
timeout: { type: "number", description: "Timeout in seconds
|
|
352
|
-
projectPath: { type: "string"
|
|
516
|
+
timeout: { type: "number", description: "Timeout in seconds", default: 60 },
|
|
517
|
+
projectPath: { type: "string" }
|
|
353
518
|
},
|
|
354
519
|
required: ["command"]
|
|
355
520
|
}
|
|
356
521
|
},
|
|
357
522
|
{
|
|
358
523
|
name: "kirograph_gain",
|
|
359
|
-
description: "Show token savings
|
|
524
|
+
description: "Show token savings from graph tools and shell compression.",
|
|
360
525
|
inputSchema: {
|
|
361
526
|
type: "object",
|
|
362
527
|
properties: {
|
|
363
528
|
period: {
|
|
364
529
|
type: "string",
|
|
365
|
-
description:
|
|
530
|
+
description: "Time window: session, hour, day, week, all",
|
|
366
531
|
enum: ["session", "today", "week", "all"],
|
|
367
532
|
default: "session"
|
|
368
533
|
},
|
|
369
|
-
projectPath: { type: "string"
|
|
534
|
+
projectPath: { type: "string" }
|
|
370
535
|
}
|
|
371
536
|
}
|
|
372
537
|
},
|
|
373
538
|
{
|
|
374
539
|
name: "kirograph_read",
|
|
375
|
-
description:
|
|
540
|
+
description: "Read a file with caching; unchanged files return cache marker.",
|
|
376
541
|
inputSchema: {
|
|
377
542
|
type: "object",
|
|
378
543
|
properties: {
|
|
@@ -385,66 +550,251 @@ const tools = [
|
|
|
385
550
|
},
|
|
386
551
|
start: { type: "number", description: "Start line (for lines mode)" },
|
|
387
552
|
end: { type: "number", description: "End line (for lines mode)" },
|
|
388
|
-
noCache: { type: "boolean", description: "Force fresh read, bypass cache
|
|
389
|
-
projectPath: { type: "string"
|
|
553
|
+
noCache: { type: "boolean", description: "Force fresh read, bypass cache", default: false },
|
|
554
|
+
projectPath: { type: "string" }
|
|
555
|
+
},
|
|
556
|
+
required: ["path"]
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
name: "kirograph_retrieve",
|
|
561
|
+
description: 'Retrieve cached file content by path (CCR \u2014 Cached Content Retrieval). Returns the full content stored in the session cache, or reads and caches the file if not yet seen. Use after kirograph_read returns a "[cached: file unchanged]" marker to get the actual content without a redundant filesystem read.',
|
|
562
|
+
inputSchema: {
|
|
563
|
+
type: "object",
|
|
564
|
+
properties: {
|
|
565
|
+
path: { type: "string", description: "File path to retrieve (absolute or relative to project root)" },
|
|
566
|
+
projectPath: { type: "string" }
|
|
390
567
|
},
|
|
391
568
|
required: ["path"]
|
|
392
569
|
}
|
|
393
570
|
},
|
|
571
|
+
{
|
|
572
|
+
name: "kirograph_compress",
|
|
573
|
+
description: "Compress text via rtk shell filters or caveman grammar engine.",
|
|
574
|
+
inputSchema: {
|
|
575
|
+
type: "object",
|
|
576
|
+
properties: {
|
|
577
|
+
text: { type: "string", description: "Text to compress" },
|
|
578
|
+
command: { type: "string", description: "Shell command text came from (selects rtk engine)" },
|
|
579
|
+
level: {
|
|
580
|
+
type: "string",
|
|
581
|
+
description: 'Compression intensity: "lite" / "normal" (light), "full" / "aggressive" (default), "ultra" (maximum)',
|
|
582
|
+
enum: ["lite", "normal", "full", "aggressive", "ultra"],
|
|
583
|
+
default: "full"
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
required: ["text"]
|
|
587
|
+
}
|
|
588
|
+
},
|
|
394
589
|
{
|
|
395
590
|
name: "kirograph_budget",
|
|
396
|
-
description: "Show
|
|
591
|
+
description: "Show session context budget: tokens used, remaining, utilization.",
|
|
397
592
|
inputSchema: {
|
|
398
593
|
type: "object",
|
|
399
594
|
properties: {
|
|
400
|
-
reset: { type: "boolean", description: "Reset session budget counters
|
|
401
|
-
projectPath: { type: "string"
|
|
595
|
+
reset: { type: "boolean", description: "Reset session budget counters", default: false },
|
|
596
|
+
projectPath: { type: "string" }
|
|
402
597
|
}
|
|
403
598
|
}
|
|
404
599
|
},
|
|
405
600
|
{
|
|
406
601
|
name: "kirograph_flows",
|
|
407
|
-
description: "Trace execution flows from entry points
|
|
602
|
+
description: "Trace execution flows from entry points through the call graph.",
|
|
408
603
|
inputSchema: {
|
|
409
604
|
type: "object",
|
|
410
605
|
properties: {
|
|
411
|
-
entryPoint: { type: "string", description: "Symbol
|
|
606
|
+
entryPoint: { type: "string", description: "Symbol to trace; omit to auto-detect" },
|
|
412
607
|
maxFlows: { type: "number", description: "Max number of flows to return (default 10)" },
|
|
413
608
|
maxDepth: { type: "number", description: "Max call chain depth (default 10)" },
|
|
414
|
-
projectPath: { type: "string"
|
|
609
|
+
projectPath: { type: "string" }
|
|
415
610
|
}
|
|
416
611
|
}
|
|
417
612
|
},
|
|
418
613
|
{
|
|
419
614
|
name: "kirograph_communities",
|
|
420
|
-
description: "Detect
|
|
615
|
+
description: "Detect clusters of related symbols via graph community detection.",
|
|
421
616
|
inputSchema: {
|
|
422
617
|
type: "object",
|
|
423
618
|
properties: {
|
|
424
|
-
resolution: { type: "number", description: "
|
|
619
|
+
resolution: { type: "number", description: "Granularity 0.1\u20132.0; higher=more smaller clusters" },
|
|
425
620
|
limit: { type: "number", description: "Max communities to return (default 15)" },
|
|
426
|
-
projectPath: { type: "string"
|
|
621
|
+
projectPath: { type: "string" }
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
// ── Complexity tools (require enableComplexity=true) ──────────────────────────
|
|
626
|
+
{
|
|
627
|
+
name: "kirograph_complexity",
|
|
628
|
+
description: "Rank symbols by cyclomatic complexity, cognitive complexity, or maintainability index. Requires enableComplexity: true and a re-index.",
|
|
629
|
+
inputSchema: {
|
|
630
|
+
type: "object",
|
|
631
|
+
properties: {
|
|
632
|
+
metric: { type: "string", description: 'Ranking metric: "cyclomatic" (default), "cognitive", or "maintainability"', enum: ["cyclomatic", "cognitive", "maintainability"], default: "cyclomatic" },
|
|
633
|
+
threshold: { type: "number", description: "Flag symbols above (CC/cog) or below (MI) this threshold" },
|
|
634
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
635
|
+
projectPath: { type: "string" }
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: "kirograph_simplify_scan",
|
|
641
|
+
description: "Find functions/methods that exceed complexity, size, or maintainability thresholds \u2014 simplification candidates.",
|
|
642
|
+
inputSchema: {
|
|
643
|
+
type: "object",
|
|
644
|
+
properties: {
|
|
645
|
+
thresholdCC: { type: "number", description: "Cyclomatic complexity threshold (default: 10)", default: 10 },
|
|
646
|
+
thresholdMI: { type: "number", description: "Maintainability index lower bound \u2014 flag below this (default: 50)", default: 50 },
|
|
647
|
+
thresholdLOC: { type: "number", description: "Lines of code threshold (default: 50)", default: 50 },
|
|
648
|
+
limit: { type: "number", description: "Max results (default: 30)", default: 30 },
|
|
649
|
+
projectPath: { type: "string" }
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
// ── Git context tools (require enableGitContext=true) ─────────────────────────
|
|
654
|
+
{
|
|
655
|
+
name: "kirograph_diff_context",
|
|
656
|
+
description: "Identify symbols changed in the working tree (or staged), with their callers and callees for immediate blast-radius context.",
|
|
657
|
+
inputSchema: {
|
|
658
|
+
type: "object",
|
|
659
|
+
properties: {
|
|
660
|
+
staged: { type: "boolean", description: "If true, show staged changes (git diff --cached) instead of unstaged", default: false },
|
|
661
|
+
projectPath: { type: "string" }
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: "kirograph_commit_context",
|
|
667
|
+
description: "Structured summary of staged changes \u2014 affected symbols, diff stat, staged files. Use to craft precise commit messages.",
|
|
668
|
+
inputSchema: {
|
|
669
|
+
type: "object",
|
|
670
|
+
properties: {
|
|
671
|
+
projectPath: { type: "string" }
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
name: "kirograph_pr_context",
|
|
677
|
+
description: "Semantic diff between two git refs \u2014 changed symbols with callers/callees. Use to write PR descriptions.",
|
|
678
|
+
inputSchema: {
|
|
679
|
+
type: "object",
|
|
680
|
+
properties: {
|
|
681
|
+
base: { type: "string", description: "Base ref (branch name, tag, or commit hash)" },
|
|
682
|
+
head: { type: "string", description: "Head ref (default: HEAD)", default: "HEAD" },
|
|
683
|
+
projectPath: { type: "string" }
|
|
684
|
+
},
|
|
685
|
+
required: ["base"]
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
name: "kirograph_changelog",
|
|
690
|
+
description: "Human-readable semantic changelog between two git refs: commit log + affected symbols.",
|
|
691
|
+
inputSchema: {
|
|
692
|
+
type: "object",
|
|
693
|
+
properties: {
|
|
694
|
+
ref1: { type: "string", description: "Start ref (older)" },
|
|
695
|
+
ref2: { type: "string", description: "End ref (default: HEAD)", default: "HEAD" },
|
|
696
|
+
projectPath: { type: "string" }
|
|
697
|
+
},
|
|
698
|
+
required: ["ref1"]
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
name: "kirograph_test_map",
|
|
703
|
+
description: "Find test files covering a symbol, or list exported symbols with no test coverage.",
|
|
704
|
+
inputSchema: {
|
|
705
|
+
type: "object",
|
|
706
|
+
properties: {
|
|
707
|
+
symbol: { type: "string", description: "Symbol name to find tests for. Omit to list all uncovered exported symbols." },
|
|
708
|
+
projectPath: { type: "string" }
|
|
427
709
|
}
|
|
428
710
|
}
|
|
429
711
|
},
|
|
430
712
|
{
|
|
431
713
|
name: "kirograph_refactor",
|
|
432
|
-
description:
|
|
714
|
+
description: "Rename preview (all references) or function summarization.",
|
|
433
715
|
inputSchema: {
|
|
434
716
|
type: "object",
|
|
435
717
|
properties: {
|
|
436
|
-
mode: { type: "string", description:
|
|
718
|
+
mode: { type: "string", description: "rename=preview references, summarize=summary", enum: ["rename", "suggest"] },
|
|
437
719
|
symbol: { type: "string", description: "Symbol name (required for rename mode)" },
|
|
438
720
|
limit: { type: "number", description: "Max results (default 10)" },
|
|
439
|
-
projectPath: { type: "string"
|
|
721
|
+
projectPath: { type: "string" }
|
|
440
722
|
},
|
|
441
723
|
required: ["mode"]
|
|
442
724
|
}
|
|
443
725
|
},
|
|
726
|
+
// ── Edit primitives (require enableEditPrimitives=true) ───────────────────────
|
|
727
|
+
{
|
|
728
|
+
name: "kirograph_str_replace",
|
|
729
|
+
description: "Replace a unique string anchor in a file; fails if the anchor appears 0 or >1 times.",
|
|
730
|
+
inputSchema: {
|
|
731
|
+
type: "object",
|
|
732
|
+
properties: {
|
|
733
|
+
file: { type: "string", description: "File path relative to project root" },
|
|
734
|
+
old_str: { type: "string", description: "Exact string to find (must be unique in the file)" },
|
|
735
|
+
new_str: { type: "string", description: "Replacement string" },
|
|
736
|
+
projectPath: { type: "string" }
|
|
737
|
+
},
|
|
738
|
+
required: ["file", "old_str", "new_str"]
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
name: "kirograph_multi_str_replace",
|
|
743
|
+
description: "Apply N replacements as an all-or-nothing transaction; aborts if any anchor is ambiguous or missing.",
|
|
744
|
+
inputSchema: {
|
|
745
|
+
type: "object",
|
|
746
|
+
properties: {
|
|
747
|
+
file: { type: "string", description: "File path relative to project root" },
|
|
748
|
+
pairs: {
|
|
749
|
+
type: "array",
|
|
750
|
+
description: "Array of {old_str, new_str} replacement pairs applied in order",
|
|
751
|
+
items: {
|
|
752
|
+
type: "object",
|
|
753
|
+
properties: {
|
|
754
|
+
old_str: { type: "string", description: "Exact string to find (must be unique)" },
|
|
755
|
+
new_str: { type: "string", description: "Replacement string" }
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
projectPath: { type: "string" }
|
|
760
|
+
},
|
|
761
|
+
required: ["file", "pairs"]
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
name: "kirograph_insert_at",
|
|
766
|
+
description: "Insert content before or after a unique anchor string or line number.",
|
|
767
|
+
inputSchema: {
|
|
768
|
+
type: "object",
|
|
769
|
+
properties: {
|
|
770
|
+
file: { type: "string", description: "File path relative to project root" },
|
|
771
|
+
anchor: { type: "string", description: "Unique string anchor to insert relative to (mutually exclusive with line)" },
|
|
772
|
+
line: { type: "number", description: "1-based line number to insert relative to (mutually exclusive with anchor)" },
|
|
773
|
+
content: { type: "string", description: "Content to insert (include newline if needed)" },
|
|
774
|
+
position: { type: "string", description: '"before" or "after" the anchor/line (default: "after")', enum: ["before", "after"], default: "after" },
|
|
775
|
+
projectPath: { type: "string" }
|
|
776
|
+
},
|
|
777
|
+
required: ["file", "content"]
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: "kirograph_ast_grep_rewrite",
|
|
782
|
+
description: "Structural AST rewrite via ast-grep. Skipped if ast-grep is not installed.",
|
|
783
|
+
inputSchema: {
|
|
784
|
+
type: "object",
|
|
785
|
+
properties: {
|
|
786
|
+
file: { type: "string", description: "File path relative to project root" },
|
|
787
|
+
pattern: { type: "string", description: "ast-grep pattern to match" },
|
|
788
|
+
rewrite: { type: "string", description: "ast-grep rewrite template" },
|
|
789
|
+
projectPath: { type: "string" }
|
|
790
|
+
},
|
|
791
|
+
required: ["file", "pattern", "rewrite"]
|
|
792
|
+
}
|
|
793
|
+
},
|
|
444
794
|
// ── Memory tools (require enableMemory=true) ────────────────────────────────
|
|
445
795
|
{
|
|
446
796
|
name: "kirograph_mem_search",
|
|
447
|
-
description: "Search project memory for
|
|
797
|
+
description: "Search project memory for decisions, errors, patterns, context.",
|
|
448
798
|
inputSchema: {
|
|
449
799
|
type: "object",
|
|
450
800
|
properties: {
|
|
@@ -457,14 +807,14 @@ const tools = [
|
|
|
457
807
|
limit: { type: "number", description: "Max results (default: 10)", default: 10 },
|
|
458
808
|
sessionId: { type: "string", description: "Filter to specific session" },
|
|
459
809
|
asOf: { type: "number", description: "Query facts valid at this timestamp (epoch ms). Filters out expired/superseded observations." },
|
|
460
|
-
projectPath: { type: "string"
|
|
810
|
+
projectPath: { type: "string" }
|
|
461
811
|
},
|
|
462
812
|
required: ["query"]
|
|
463
813
|
}
|
|
464
814
|
},
|
|
465
815
|
{
|
|
466
816
|
name: "kirograph_mem_store",
|
|
467
|
-
description: "Store an observation in project memory.
|
|
817
|
+
description: "Store an observation in project memory.",
|
|
468
818
|
inputSchema: {
|
|
469
819
|
type: "object",
|
|
470
820
|
properties: {
|
|
@@ -475,9 +825,9 @@ const tools = [
|
|
|
475
825
|
enum: ["decision", "error", "pattern", "architecture", "summary", "note"],
|
|
476
826
|
default: "note"
|
|
477
827
|
},
|
|
478
|
-
projectPath: { type: "string"
|
|
828
|
+
projectPath: { type: "string" },
|
|
479
829
|
topicKey: { type: "string", description: "Stable semantic key for this observation (e.g. 'architecture/auth-model'). Enables addressing by concept." },
|
|
480
|
-
reviewAfter: { type: "number", description: "
|
|
830
|
+
reviewAfter: { type: "number", description: "ISO date to flag for review" }
|
|
481
831
|
},
|
|
482
832
|
required: ["content"]
|
|
483
833
|
}
|
|
@@ -488,42 +838,42 @@ const tools = [
|
|
|
488
838
|
inputSchema: {
|
|
489
839
|
type: "object",
|
|
490
840
|
properties: {
|
|
491
|
-
limit: { type: "number", description: "Number of sessions to show
|
|
841
|
+
limit: { type: "number", description: "Number of sessions to show", default: 5 },
|
|
492
842
|
sessionId: { type: "string", description: "Show observations for a specific session" },
|
|
493
|
-
projectPath: { type: "string"
|
|
843
|
+
projectPath: { type: "string" }
|
|
494
844
|
}
|
|
495
845
|
}
|
|
496
846
|
},
|
|
497
847
|
{
|
|
498
848
|
name: "kirograph_mem_status",
|
|
499
|
-
description: "Memory subsystem health:
|
|
849
|
+
description: "Memory subsystem health: sessions, observations, embeddings, size.",
|
|
500
850
|
inputSchema: {
|
|
501
851
|
type: "object",
|
|
502
852
|
properties: {
|
|
503
|
-
projectPath: { type: "string"
|
|
853
|
+
projectPath: { type: "string" }
|
|
504
854
|
}
|
|
505
855
|
}
|
|
506
856
|
},
|
|
507
857
|
// ── Engram-parity mem tools ──────────────────────────────────────────────────
|
|
508
858
|
{
|
|
509
859
|
name: "kirograph_mem_review",
|
|
510
|
-
description: "List observations past their review_after date
|
|
860
|
+
description: "List observations past their review_after date.",
|
|
511
861
|
inputSchema: {
|
|
512
862
|
type: "object",
|
|
513
863
|
properties: {
|
|
514
864
|
limit: { type: "number", description: "Max results (default: 20)", default: 20 },
|
|
515
|
-
projectPath: { type: "string"
|
|
865
|
+
projectPath: { type: "string" }
|
|
516
866
|
}
|
|
517
867
|
}
|
|
518
868
|
},
|
|
519
869
|
{
|
|
520
870
|
name: "kirograph_mem_mark_reviewed",
|
|
521
|
-
description: "
|
|
871
|
+
description: "Clear review_after date on an observation.",
|
|
522
872
|
inputSchema: {
|
|
523
873
|
type: "object",
|
|
524
874
|
properties: {
|
|
525
875
|
id: { type: "string", description: "Observation ID" },
|
|
526
|
-
projectPath: { type: "string"
|
|
876
|
+
projectPath: { type: "string" }
|
|
527
877
|
},
|
|
528
878
|
required: ["id"]
|
|
529
879
|
}
|
|
@@ -538,9 +888,9 @@ const tools = [
|
|
|
538
888
|
observationB: { type: "string", description: "Second observation ID or topic_key" },
|
|
539
889
|
relation: { type: "string", enum: ["supersedes", "conflicts_with", "compatible", "scoped", "related", "not_conflict"], description: "Relation type" },
|
|
540
890
|
confidence: { type: "number", description: "Confidence 0.0\u20131.0 (default: 1.0)" },
|
|
541
|
-
reason: { type: "string", description: "Explanation
|
|
891
|
+
reason: { type: "string", description: "Explanation of the relation" },
|
|
542
892
|
evidence: { type: "string", description: "Supporting evidence text" },
|
|
543
|
-
projectPath: { type: "string"
|
|
893
|
+
projectPath: { type: "string" }
|
|
544
894
|
},
|
|
545
895
|
required: ["observationA", "observationB", "relation"]
|
|
546
896
|
}
|
|
@@ -554,218 +904,303 @@ const tools = [
|
|
|
554
904
|
relationId: { type: "string", description: "Relation ID (from kirograph_mem_compare or kirograph_mem_search)" },
|
|
555
905
|
relation: { type: "string", enum: ["supersedes", "conflicts_with", "compatible", "scoped", "related", "not_conflict"], description: "Final relation type" },
|
|
556
906
|
confidence: { type: "number", description: "Final confidence 0.0\u20131.0" },
|
|
557
|
-
reason: { type: "string", description: "Reasoning
|
|
907
|
+
reason: { type: "string", description: "Reasoning" },
|
|
558
908
|
evidence: { type: "string", description: "Supporting evidence" },
|
|
559
|
-
projectPath: { type: "string"
|
|
909
|
+
projectPath: { type: "string" }
|
|
560
910
|
},
|
|
561
911
|
required: ["relationId", "relation", "confidence"]
|
|
562
912
|
}
|
|
563
913
|
},
|
|
564
914
|
{
|
|
565
915
|
name: "kirograph_mem_capture",
|
|
566
|
-
description: "Extract and store structured learnings from
|
|
916
|
+
description: "Extract and store structured learnings from freeform text.",
|
|
567
917
|
inputSchema: {
|
|
568
918
|
type: "object",
|
|
569
919
|
properties: {
|
|
570
920
|
content: { type: "string", description: "Text block with structured sections" },
|
|
571
|
-
projectPath: { type: "string"
|
|
921
|
+
projectPath: { type: "string" }
|
|
572
922
|
},
|
|
573
923
|
required: ["content"]
|
|
574
924
|
}
|
|
575
925
|
},
|
|
576
926
|
{
|
|
577
927
|
name: "kirograph_mem_save_prompt",
|
|
578
|
-
description: "Save the current user prompt to session memory
|
|
928
|
+
description: "Save the current user prompt to session memory.",
|
|
579
929
|
inputSchema: {
|
|
580
930
|
type: "object",
|
|
581
931
|
properties: {
|
|
582
|
-
content: { type: "string", description: "
|
|
583
|
-
projectPath: { type: "string"
|
|
932
|
+
content: { type: "string", description: "The user prompt text to save" },
|
|
933
|
+
projectPath: { type: "string" }
|
|
584
934
|
},
|
|
585
935
|
required: ["content"]
|
|
586
936
|
}
|
|
587
937
|
},
|
|
588
938
|
{
|
|
589
939
|
name: "kirograph_mem_suggest_topic_key",
|
|
590
|
-
description:
|
|
940
|
+
description: "Suggest a stable topic_key slug for an observation.",
|
|
591
941
|
inputSchema: {
|
|
592
942
|
type: "object",
|
|
593
943
|
properties: {
|
|
594
944
|
kind: { type: "string", enum: ["decision", "error", "pattern", "architecture", "summary", "note"], description: "Observation kind" },
|
|
595
|
-
title: { type: "string", description: "Short
|
|
596
|
-
projectPath: { type: "string"
|
|
945
|
+
title: { type: "string", description: "Short description of the observation" },
|
|
946
|
+
projectPath: { type: "string" }
|
|
597
947
|
},
|
|
598
948
|
required: ["kind", "title"]
|
|
599
949
|
}
|
|
600
950
|
},
|
|
601
951
|
{
|
|
602
952
|
name: "kirograph_mem_conflicts_scan",
|
|
603
|
-
description: "Scan recent observations for potential conflicts using FTS
|
|
953
|
+
description: "Scan recent observations for potential conflicts using FTS.",
|
|
604
954
|
inputSchema: {
|
|
605
955
|
type: "object",
|
|
606
956
|
properties: {
|
|
607
957
|
limit: { type: "number", description: "Max observations to scan (default: 50)", default: 50 },
|
|
608
|
-
projectPath: { type: "string"
|
|
958
|
+
projectPath: { type: "string" }
|
|
609
959
|
}
|
|
610
960
|
}
|
|
611
961
|
},
|
|
612
|
-
// ── Wiki tools (require enableWiki=true) ────────────────────────────────────
|
|
613
962
|
{
|
|
614
|
-
name: "
|
|
615
|
-
description: "
|
|
963
|
+
name: "kirograph_mem_prune",
|
|
964
|
+
description: "Remove memory observations older than a given duration.",
|
|
616
965
|
inputSchema: {
|
|
617
966
|
type: "object",
|
|
618
967
|
properties: {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
},
|
|
623
|
-
required: ["source"]
|
|
968
|
+
olderThan: { type: "string", description: 'Duration threshold e.g. "90d" or "6m"', default: "90d" },
|
|
969
|
+
projectPath: { type: "string" }
|
|
970
|
+
}
|
|
624
971
|
}
|
|
625
972
|
},
|
|
626
973
|
{
|
|
627
|
-
name: "
|
|
628
|
-
description: "
|
|
974
|
+
name: "kirograph_mem_lint",
|
|
975
|
+
description: "Health check: stale links, model mismatch, orphaned sessions.",
|
|
629
976
|
inputSchema: {
|
|
630
977
|
type: "object",
|
|
631
978
|
properties: {
|
|
632
|
-
|
|
633
|
-
projectPath: { type: "string"
|
|
634
|
-
}
|
|
635
|
-
required: ["diff"]
|
|
979
|
+
fix: { type: "boolean", description: "Auto-remove stale links", default: false },
|
|
980
|
+
projectPath: { type: "string" }
|
|
981
|
+
}
|
|
636
982
|
}
|
|
637
983
|
},
|
|
638
984
|
{
|
|
639
|
-
name: "
|
|
640
|
-
description: "
|
|
985
|
+
name: "kirograph_mem_conflicts_list",
|
|
986
|
+
description: "List pending conflict relations needing resolution.",
|
|
641
987
|
inputSchema: {
|
|
642
988
|
type: "object",
|
|
643
989
|
properties: {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
990
|
+
limit: { type: "number", description: "Max results (default: 20)", default: 20 },
|
|
991
|
+
projectPath: { type: "string" }
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
name: "kirograph_mem_conflicts_ignore",
|
|
997
|
+
description: "Dismiss a pending conflict relation as not relevant.",
|
|
998
|
+
inputSchema: {
|
|
999
|
+
type: "object",
|
|
1000
|
+
properties: {
|
|
1001
|
+
relationId: { type: "string", description: "Relation ID to ignore (from kirograph_mem_conflicts_list)" },
|
|
1002
|
+
projectPath: { type: "string" }
|
|
1003
|
+
},
|
|
1004
|
+
required: ["relationId"]
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
// ── Wiki tools (require enableWiki=true) ────────────────────────────────────
|
|
1008
|
+
{
|
|
1009
|
+
name: "kirograph_wiki_ingest",
|
|
1010
|
+
description: "Build ingest prompt for LLM to produce a WIKI_DIFF.",
|
|
1011
|
+
inputSchema: {
|
|
1012
|
+
type: "object",
|
|
1013
|
+
properties: {
|
|
1014
|
+
source: { type: "string", description: "Source content to ingest (markdown, notes, ADR text, etc.)" },
|
|
1015
|
+
sourceName: { type: "string", description: "Name or path of the source", default: "source" },
|
|
1016
|
+
projectPath: { type: "string" }
|
|
1017
|
+
},
|
|
1018
|
+
required: ["source"]
|
|
1019
|
+
}
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
name: "kirograph_wiki_apply_diff",
|
|
1023
|
+
description: "Apply a WIKI_DIFF to the wiki filesystem and SQLite index.",
|
|
1024
|
+
inputSchema: {
|
|
1025
|
+
type: "object",
|
|
1026
|
+
properties: {
|
|
1027
|
+
diff: { type: "string", description: "WIKI_DIFF string with WIKI_DIFF_START/END blocks" },
|
|
1028
|
+
projectPath: { type: "string" }
|
|
1029
|
+
},
|
|
1030
|
+
required: ["diff"]
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
name: "kirograph_wiki_search",
|
|
1035
|
+
description: "FTS over wiki pages; returns slugs, titles, previews.",
|
|
1036
|
+
inputSchema: {
|
|
1037
|
+
type: "object",
|
|
1038
|
+
properties: {
|
|
1039
|
+
query: { type: "string", description: "Search query" },
|
|
1040
|
+
limit: { type: "number", description: "Max results (default: 5)", default: 5 },
|
|
1041
|
+
projectPath: { type: "string" }
|
|
1042
|
+
},
|
|
648
1043
|
required: ["query"]
|
|
649
1044
|
}
|
|
650
1045
|
},
|
|
651
1046
|
{
|
|
652
1047
|
name: "kirograph_wiki_page",
|
|
653
|
-
description: "Retrieve
|
|
1048
|
+
description: "Retrieve full markdown of a wiki page by slug.",
|
|
654
1049
|
inputSchema: {
|
|
655
1050
|
type: "object",
|
|
656
1051
|
properties: {
|
|
657
|
-
slug: { type: "string", description: 'Page slug (e.g. "
|
|
658
|
-
projectPath: { type: "string"
|
|
1052
|
+
slug: { type: "string", description: 'Page slug (e.g. "arch/auth-model")' },
|
|
1053
|
+
projectPath: { type: "string" }
|
|
659
1054
|
},
|
|
660
1055
|
required: ["slug"]
|
|
661
1056
|
}
|
|
662
1057
|
},
|
|
663
1058
|
{
|
|
664
1059
|
name: "kirograph_wiki_lint",
|
|
665
|
-
description: "
|
|
1060
|
+
description: "Check wiki for broken links, orphans, stale sources, contradictions.",
|
|
666
1061
|
inputSchema: {
|
|
667
1062
|
type: "object",
|
|
668
1063
|
properties: {
|
|
669
|
-
projectPath: { type: "string"
|
|
1064
|
+
projectPath: { type: "string" }
|
|
670
1065
|
}
|
|
671
1066
|
}
|
|
672
1067
|
},
|
|
673
1068
|
{
|
|
674
1069
|
name: "kirograph_wiki_list",
|
|
675
|
-
description: "List all wiki pages with slug, title, source count,
|
|
1070
|
+
description: "List all wiki pages with slug, title, source count, date.",
|
|
1071
|
+
inputSchema: {
|
|
1072
|
+
type: "object",
|
|
1073
|
+
properties: {
|
|
1074
|
+
projectPath: { type: "string" }
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
name: "kirograph_wiki_synthesize",
|
|
1080
|
+
description: 'Run local-model wiki synthesis: process the pending source queue (requires wikiSynthesisMode: "local").',
|
|
1081
|
+
inputSchema: {
|
|
1082
|
+
type: "object",
|
|
1083
|
+
properties: {
|
|
1084
|
+
projectPath: { type: "string" }
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
name: "kirograph_wiki_init",
|
|
1090
|
+
description: "Initialize the wiki: create SCHEMA.md and MANIFEST.md in .kirograph/wiki/.",
|
|
676
1091
|
inputSchema: {
|
|
677
1092
|
type: "object",
|
|
678
1093
|
properties: {
|
|
679
|
-
projectPath: { type: "string"
|
|
1094
|
+
projectPath: { type: "string" }
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
name: "kirograph_wiki_reindex",
|
|
1100
|
+
description: "Rebuild the SQLite index from .kirograph/wiki/*.md files on disk.",
|
|
1101
|
+
inputSchema: {
|
|
1102
|
+
type: "object",
|
|
1103
|
+
properties: {
|
|
1104
|
+
projectPath: { type: "string" }
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
{
|
|
1109
|
+
name: "kirograph_wiki_status",
|
|
1110
|
+
description: "Show wiki subsystem stats: page count, total sources, oldest/newest page dates.",
|
|
1111
|
+
inputSchema: {
|
|
1112
|
+
type: "object",
|
|
1113
|
+
properties: {
|
|
1114
|
+
projectPath: { type: "string" }
|
|
680
1115
|
}
|
|
681
1116
|
}
|
|
682
1117
|
},
|
|
683
1118
|
// ── Docs tools (require enableDocs=true) ────────────────────────────────────
|
|
684
1119
|
{
|
|
685
1120
|
name: "kirograph_docs_toc",
|
|
686
|
-
description: "Get
|
|
1121
|
+
description: "Get TOC for a doc file or whole project (IDs, titles, levels, summaries).",
|
|
687
1122
|
inputSchema: {
|
|
688
1123
|
type: "object",
|
|
689
1124
|
properties: {
|
|
690
1125
|
file: { type: "string", description: "Filter to a specific doc file (relative path). Omit for project-wide TOC." },
|
|
691
|
-
tree: { type: "boolean", description: "Return nested tree structure
|
|
692
|
-
projectPath: { type: "string"
|
|
1126
|
+
tree: { type: "boolean", description: "Return nested tree structure.", default: false },
|
|
1127
|
+
projectPath: { type: "string" }
|
|
693
1128
|
}
|
|
694
1129
|
}
|
|
695
1130
|
},
|
|
696
1131
|
{
|
|
697
1132
|
name: "kirograph_docs_search",
|
|
698
|
-
description: "Search documentation sections by query
|
|
1133
|
+
description: "Search documentation sections by query, ranked by relevance.",
|
|
699
1134
|
inputSchema: {
|
|
700
1135
|
type: "object",
|
|
701
1136
|
properties: {
|
|
702
1137
|
query: { type: "string", description: "Search query (natural language or keywords)" },
|
|
703
|
-
file: { type: "string", description: "
|
|
1138
|
+
file: { type: "string", description: "Limit search to a specific doc file." },
|
|
704
1139
|
limit: { type: "number", description: "Max results (default: 10)", default: 10 },
|
|
705
|
-
projectPath: { type: "string"
|
|
1140
|
+
projectPath: { type: "string" }
|
|
706
1141
|
},
|
|
707
1142
|
required: ["query"]
|
|
708
1143
|
}
|
|
709
1144
|
},
|
|
710
1145
|
{
|
|
711
1146
|
name: "kirograph_docs_section",
|
|
712
|
-
description: "Retrieve full content of a
|
|
1147
|
+
description: "Retrieve full content of a doc section by its stable ID.",
|
|
713
1148
|
inputSchema: {
|
|
714
1149
|
type: "object",
|
|
715
1150
|
properties: {
|
|
716
|
-
id: { type: "string", description: "Section ID
|
|
717
|
-
context: { type: "boolean", description: "Include ancestor
|
|
718
|
-
projectPath: { type: "string"
|
|
1151
|
+
id: { type: "string", description: "Section ID." },
|
|
1152
|
+
context: { type: "boolean", description: "Include ancestor headings and child summaries.", default: false },
|
|
1153
|
+
projectPath: { type: "string" }
|
|
719
1154
|
},
|
|
720
1155
|
required: ["id"]
|
|
721
1156
|
}
|
|
722
1157
|
},
|
|
723
1158
|
{
|
|
724
1159
|
name: "kirograph_docs_outline",
|
|
725
|
-
description: "Get
|
|
1160
|
+
description: "Get heading hierarchy for a single documentation file.",
|
|
726
1161
|
inputSchema: {
|
|
727
1162
|
type: "object",
|
|
728
1163
|
properties: {
|
|
729
|
-
file: { type: "string", description: "
|
|
730
|
-
projectPath: { type: "string"
|
|
1164
|
+
file: { type: "string", description: "Documentation file path (relative to project root)." },
|
|
1165
|
+
projectPath: { type: "string" }
|
|
731
1166
|
},
|
|
732
1167
|
required: ["file"]
|
|
733
1168
|
}
|
|
734
1169
|
},
|
|
735
1170
|
{
|
|
736
1171
|
name: "kirograph_docs_refs",
|
|
737
|
-
description: "
|
|
1172
|
+
description: "Bidirectional lookup: doc section\u2194code symbol references.",
|
|
738
1173
|
inputSchema: {
|
|
739
1174
|
type: "object",
|
|
740
1175
|
properties: {
|
|
741
|
-
sectionId: { type: "string", description: "Doc section ID
|
|
742
|
-
nodeId: { type: "string", description: "Code symbol
|
|
743
|
-
projectPath: { type: "string"
|
|
1176
|
+
sectionId: { type: "string", description: "Doc section ID to find referenced symbols." },
|
|
1177
|
+
nodeId: { type: "string", description: "Code symbol ID to find referencing doc sections." },
|
|
1178
|
+
projectPath: { type: "string" }
|
|
744
1179
|
}
|
|
745
1180
|
}
|
|
746
1181
|
},
|
|
747
1182
|
// ── Data tools (require enableData=true) ────────────────────────────────────
|
|
748
1183
|
{
|
|
749
1184
|
name: "kirograph_data_list",
|
|
750
|
-
description: "List all indexed datasets with row
|
|
751
|
-
inputSchema: { type: "object", properties: { projectPath: { type: "string"
|
|
1185
|
+
description: "List all indexed datasets with row/column counts and file sizes.",
|
|
1186
|
+
inputSchema: { type: "object", properties: { projectPath: { type: "string" } } }
|
|
752
1187
|
},
|
|
753
1188
|
{
|
|
754
1189
|
name: "kirograph_data_describe",
|
|
755
|
-
description: "Full schema profile
|
|
1190
|
+
description: "Full schema profile: types, cardinality, null%, sample values.",
|
|
756
1191
|
inputSchema: {
|
|
757
1192
|
type: "object",
|
|
758
1193
|
properties: {
|
|
759
1194
|
dataset: { type: "string", description: "Dataset ID (from kirograph_data_list)" },
|
|
760
|
-
column: { type: "string", description: "
|
|
761
|
-
projectPath: { type: "string"
|
|
1195
|
+
column: { type: "string", description: "Deep-dive on a single column." },
|
|
1196
|
+
projectPath: { type: "string" }
|
|
762
1197
|
},
|
|
763
1198
|
required: ["dataset"]
|
|
764
1199
|
}
|
|
765
1200
|
},
|
|
766
1201
|
{
|
|
767
1202
|
name: "kirograph_data_query",
|
|
768
|
-
description: "Filtered row retrieval with structured operators
|
|
1203
|
+
description: "Filtered row retrieval with structured operators (max 500 rows).",
|
|
769
1204
|
inputSchema: {
|
|
770
1205
|
type: "object",
|
|
771
1206
|
properties: {
|
|
@@ -774,14 +1209,14 @@ const tools = [
|
|
|
774
1209
|
columns: { type: "array", description: "Column projection (only return these columns)" },
|
|
775
1210
|
limit: { type: "number", description: "Max rows (default: 100, hard cap: 500)", default: 100 },
|
|
776
1211
|
offset: { type: "number", description: "Pagination offset", default: 0 },
|
|
777
|
-
projectPath: { type: "string"
|
|
1212
|
+
projectPath: { type: "string" }
|
|
778
1213
|
},
|
|
779
1214
|
required: ["dataset"]
|
|
780
1215
|
}
|
|
781
1216
|
},
|
|
782
1217
|
{
|
|
783
1218
|
name: "kirograph_data_aggregate",
|
|
784
|
-
description: "Server-side GROUP BY aggregation
|
|
1219
|
+
description: "Server-side GROUP BY aggregation: count, sum, avg, min, max.",
|
|
785
1220
|
inputSchema: {
|
|
786
1221
|
type: "object",
|
|
787
1222
|
properties: {
|
|
@@ -789,27 +1224,27 @@ const tools = [
|
|
|
789
1224
|
groupBy: { type: "array", description: "Columns to group by" },
|
|
790
1225
|
metrics: { type: "array", description: "Array of {column, op} metrics. Ops: count, sum, avg, min, max, count_distinct" },
|
|
791
1226
|
filters: { type: "array", description: "Optional pre-filters (same format as kirograph_data_query)" },
|
|
792
|
-
projectPath: { type: "string"
|
|
1227
|
+
projectPath: { type: "string" }
|
|
793
1228
|
},
|
|
794
1229
|
required: ["dataset", "groupBy", "metrics"]
|
|
795
1230
|
}
|
|
796
1231
|
},
|
|
797
1232
|
{
|
|
798
1233
|
name: "kirograph_data_search",
|
|
799
|
-
description: "Search column names and sample values by keyword.
|
|
1234
|
+
description: "Search column names and sample values by keyword.",
|
|
800
1235
|
inputSchema: {
|
|
801
1236
|
type: "object",
|
|
802
1237
|
properties: {
|
|
803
1238
|
dataset: { type: "string", description: "Dataset ID" },
|
|
804
1239
|
query: { type: "string", description: "Search keyword" },
|
|
805
|
-
projectPath: { type: "string"
|
|
1240
|
+
projectPath: { type: "string" }
|
|
806
1241
|
},
|
|
807
1242
|
required: ["dataset", "query"]
|
|
808
1243
|
}
|
|
809
1244
|
},
|
|
810
1245
|
{
|
|
811
1246
|
name: "kirograph_data_join",
|
|
812
|
-
description: "SQL JOIN across two indexed datasets.
|
|
1247
|
+
description: "SQL JOIN across two indexed datasets.",
|
|
813
1248
|
inputSchema: {
|
|
814
1249
|
type: "object",
|
|
815
1250
|
properties: {
|
|
@@ -820,50 +1255,126 @@ const tools = [
|
|
|
820
1255
|
type: { type: "string", description: "Join type: inner (default), left, right", enum: ["inner", "left", "right"], default: "inner" },
|
|
821
1256
|
columns: { type: "array", description: "Column projection (prefix with dataset ID)" },
|
|
822
1257
|
limit: { type: "number", description: "Max rows (default: 100, hard cap: 500)", default: 100 },
|
|
823
|
-
projectPath: { type: "string"
|
|
1258
|
+
projectPath: { type: "string" }
|
|
824
1259
|
},
|
|
825
1260
|
required: ["left", "right", "leftColumn", "rightColumn"]
|
|
826
1261
|
}
|
|
827
1262
|
},
|
|
828
1263
|
{
|
|
829
1264
|
name: "kirograph_data_correlations",
|
|
830
|
-
description: "Pairwise Pearson correlations between numeric columns.
|
|
1265
|
+
description: "Pairwise Pearson correlations between numeric columns.",
|
|
831
1266
|
inputSchema: {
|
|
832
1267
|
type: "object",
|
|
833
1268
|
properties: {
|
|
834
1269
|
dataset: { type: "string", description: "Dataset ID" },
|
|
835
1270
|
threshold: { type: "number", description: "Min absolute correlation to include (default: 0.3)", default: 0.3 },
|
|
836
|
-
projectPath: { type: "string"
|
|
1271
|
+
projectPath: { type: "string" }
|
|
837
1272
|
},
|
|
838
1273
|
required: ["dataset"]
|
|
839
1274
|
}
|
|
840
1275
|
},
|
|
841
1276
|
{
|
|
842
1277
|
name: "kirograph_data_quality",
|
|
843
|
-
description: "
|
|
1278
|
+
description: "Rank columns by data quality risk: nulls, cardinality, type issues.",
|
|
844
1279
|
inputSchema: {
|
|
845
1280
|
type: "object",
|
|
846
1281
|
properties: {
|
|
847
1282
|
dataset: { type: "string", description: "Dataset ID" },
|
|
848
|
-
projectPath: { type: "string"
|
|
1283
|
+
projectPath: { type: "string" }
|
|
1284
|
+
},
|
|
1285
|
+
required: ["dataset"]
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
name: "kirograph_data_drift",
|
|
1290
|
+
description: "Schema drift between last two index runs: columns, types, row counts.",
|
|
1291
|
+
inputSchema: {
|
|
1292
|
+
type: "object",
|
|
1293
|
+
properties: {
|
|
1294
|
+
dataset: { type: "string", description: "Dataset ID (from kirograph_data_list)" },
|
|
1295
|
+
projectPath: { type: "string" }
|
|
1296
|
+
},
|
|
1297
|
+
required: ["dataset"]
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
name: "kirograph_data_history",
|
|
1302
|
+
description: "History of schema snapshots: timestamps, row/column counts, hashes.",
|
|
1303
|
+
inputSchema: {
|
|
1304
|
+
type: "object",
|
|
1305
|
+
properties: {
|
|
1306
|
+
dataset: { type: "string", description: "Dataset ID (from kirograph_data_list)" },
|
|
1307
|
+
limit: { type: "number", description: "Max snapshots to return (default: 10)", default: 10 },
|
|
1308
|
+
projectPath: { type: "string" }
|
|
849
1309
|
},
|
|
850
1310
|
required: ["dataset"]
|
|
851
1311
|
}
|
|
852
1312
|
},
|
|
1313
|
+
// ── Watchmen tools (require enableMemory=true + enableWatchmen=true) ──────────
|
|
1314
|
+
{
|
|
1315
|
+
name: "kirograph_watchmen_status",
|
|
1316
|
+
description: "Show watchmen status: pending observation count, synthesis threshold, and which brief files would be written on next synthesis.",
|
|
1317
|
+
inputSchema: {
|
|
1318
|
+
type: "object",
|
|
1319
|
+
properties: {
|
|
1320
|
+
projectPath: { type: "string" }
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
name: "kirograph_watchmen_synthesize",
|
|
1326
|
+
description: 'Run watchmen synthesis immediately using the local model (requires watchmenSynthesisMode: "local"). Processes pending observations into a workspace brief.',
|
|
1327
|
+
inputSchema: {
|
|
1328
|
+
type: "object",
|
|
1329
|
+
properties: {
|
|
1330
|
+
force: { type: "boolean", description: "Run even if below threshold (default: false)", default: false },
|
|
1331
|
+
projectPath: { type: "string" }
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
name: "kirograph_watchmen_reset",
|
|
1337
|
+
description: "Reset the watchmen synthesis counter by storing a summary observation, without running full synthesis.",
|
|
1338
|
+
inputSchema: {
|
|
1339
|
+
type: "object",
|
|
1340
|
+
properties: {
|
|
1341
|
+
projectPath: { type: "string" }
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
// ── Affected tests ────────────────────────────────────────────────────────────
|
|
1346
|
+
{
|
|
1347
|
+
name: "kirograph_affected",
|
|
1348
|
+
description: "Find test files affected by a set of changed source files, by traversing the dependency graph.",
|
|
1349
|
+
inputSchema: {
|
|
1350
|
+
type: "object",
|
|
1351
|
+
properties: {
|
|
1352
|
+
files: {
|
|
1353
|
+
type: "array",
|
|
1354
|
+
items: { type: "string" },
|
|
1355
|
+
description: "List of changed source file paths (relative to project root)"
|
|
1356
|
+
},
|
|
1357
|
+
depth: { type: "number", description: "Max dependency traversal depth (default: 5)", default: 5 },
|
|
1358
|
+
testPattern: { type: "string", description: "Custom glob to identify test files (optional)" },
|
|
1359
|
+
projectPath: { type: "string" }
|
|
1360
|
+
},
|
|
1361
|
+
required: ["files"]
|
|
1362
|
+
}
|
|
1363
|
+
},
|
|
853
1364
|
// ── Security tools (require enableSecurity=true) ────────────────────────────
|
|
854
1365
|
{
|
|
855
1366
|
name: "kirograph_security",
|
|
856
|
-
description: "Security overview:
|
|
1367
|
+
description: "Security overview: vuln counts, verdicts, stale data warnings.",
|
|
857
1368
|
inputSchema: {
|
|
858
1369
|
type: "object",
|
|
859
1370
|
properties: {
|
|
860
|
-
projectPath: { type: "string"
|
|
1371
|
+
projectPath: { type: "string" }
|
|
861
1372
|
}
|
|
862
1373
|
}
|
|
863
1374
|
},
|
|
864
1375
|
{
|
|
865
1376
|
name: "kirograph_vulns",
|
|
866
|
-
description: "List
|
|
1377
|
+
description: "List vulns with reachability verdicts, severity, affected components.",
|
|
867
1378
|
inputSchema: {
|
|
868
1379
|
type: "object",
|
|
869
1380
|
properties: {
|
|
@@ -871,13 +1382,13 @@ const tools = [
|
|
|
871
1382
|
verdict: { type: "string", description: "Filter by reachability verdict", enum: ["affected", "not_affected", "under_investigation"] },
|
|
872
1383
|
limit: { type: "number", description: "Max results (default: 20)", default: 20 },
|
|
873
1384
|
refresh: { type: "boolean", description: "Trigger fresh vulnerability enrichment before listing (default: false)", default: false },
|
|
874
|
-
projectPath: { type: "string"
|
|
1385
|
+
projectPath: { type: "string" }
|
|
875
1386
|
}
|
|
876
1387
|
}
|
|
877
1388
|
},
|
|
878
1389
|
{
|
|
879
1390
|
name: "kirograph_vuln_add",
|
|
880
|
-
description: "Manually register a CVE against a dependency
|
|
1391
|
+
description: "Manually register a CVE against a dependency.",
|
|
881
1392
|
inputSchema: {
|
|
882
1393
|
type: "object",
|
|
883
1394
|
properties: {
|
|
@@ -886,178 +1397,263 @@ const tools = [
|
|
|
886
1397
|
severity: { type: "number", description: "CVSS v3.1 base score (optional)" },
|
|
887
1398
|
summary: { type: "string", description: "Vulnerability summary (optional)" },
|
|
888
1399
|
fixedVersion: { type: "string", description: "Version that fixes the vulnerability (optional)" },
|
|
889
|
-
projectPath: { type: "string"
|
|
1400
|
+
projectPath: { type: "string" }
|
|
890
1401
|
},
|
|
891
1402
|
required: ["cveId", "package"]
|
|
892
1403
|
}
|
|
893
1404
|
},
|
|
894
1405
|
{
|
|
895
1406
|
name: "kirograph_vuln_suppress",
|
|
896
|
-
description: "Suppress a CVE
|
|
1407
|
+
description: "Suppress a CVE from vulnerability reports.",
|
|
897
1408
|
inputSchema: {
|
|
898
1409
|
type: "object",
|
|
899
1410
|
properties: {
|
|
900
1411
|
cveId: { type: "string", description: 'CVE identifier to suppress (e.g., "CVE-2024-1234")' },
|
|
901
1412
|
reason: { type: "string", description: "Reason for suppression (optional)" },
|
|
902
1413
|
expires: { type: "string", description: 'Expiry date in ISO format after which the suppression is removed (e.g. "2026-12-31", optional)' },
|
|
903
|
-
projectPath: { type: "string"
|
|
1414
|
+
projectPath: { type: "string" }
|
|
904
1415
|
},
|
|
905
1416
|
required: ["cveId"]
|
|
906
1417
|
}
|
|
907
1418
|
},
|
|
908
1419
|
{
|
|
909
1420
|
name: "kirograph_sbom",
|
|
910
|
-
description: "Generate
|
|
1421
|
+
description: "Generate CycloneDX 1.5 SBOM JSON for the project.",
|
|
911
1422
|
inputSchema: {
|
|
912
1423
|
type: "object",
|
|
913
1424
|
properties: {
|
|
914
|
-
projectPath: { type: "string"
|
|
1425
|
+
projectPath: { type: "string" }
|
|
915
1426
|
}
|
|
916
1427
|
}
|
|
917
1428
|
},
|
|
918
1429
|
{
|
|
919
1430
|
name: "kirograph_vex",
|
|
920
|
-
description: "Generate
|
|
1431
|
+
description: "Generate CycloneDX 1.5 VEX JSON with reachability verdicts.",
|
|
921
1432
|
inputSchema: {
|
|
922
1433
|
type: "object",
|
|
923
1434
|
properties: {
|
|
924
|
-
projectPath: { type: "string"
|
|
1435
|
+
projectPath: { type: "string" }
|
|
925
1436
|
}
|
|
926
1437
|
}
|
|
927
1438
|
},
|
|
928
1439
|
{
|
|
929
1440
|
name: "kirograph_reachability",
|
|
930
|
-
description: "Check reachability for a
|
|
1441
|
+
description: "Check reachability for a dependency or CVE.",
|
|
931
1442
|
inputSchema: {
|
|
932
1443
|
type: "object",
|
|
933
1444
|
properties: {
|
|
934
1445
|
target: { type: "string", description: "Dependency name or CVE ID to check reachability for" },
|
|
935
|
-
projectPath: { type: "string"
|
|
1446
|
+
projectPath: { type: "string" }
|
|
936
1447
|
},
|
|
937
1448
|
required: ["target"]
|
|
938
1449
|
}
|
|
939
1450
|
},
|
|
940
1451
|
{
|
|
941
1452
|
name: "kirograph_staleness",
|
|
942
|
-
description: "Check dependency freshness
|
|
1453
|
+
description: "Check dependency freshness against latest published versions.",
|
|
943
1454
|
inputSchema: {
|
|
944
1455
|
type: "object",
|
|
945
1456
|
properties: {
|
|
946
1457
|
threshold: { type: "number", description: "Only return packages with staleness_score >= threshold (default: 0.3)", default: 0.3 },
|
|
947
1458
|
refresh: { type: "boolean", description: "Fetch latest version info from registries before listing (default: false)", default: false },
|
|
948
|
-
projectPath: { type: "string"
|
|
1459
|
+
projectPath: { type: "string" }
|
|
949
1460
|
}
|
|
950
1461
|
}
|
|
951
1462
|
},
|
|
952
1463
|
{
|
|
953
1464
|
name: "kirograph_licenses",
|
|
954
|
-
description: "Show
|
|
1465
|
+
description: "Show licenses and check against configured deny/warn policy.",
|
|
955
1466
|
inputSchema: {
|
|
956
1467
|
type: "object",
|
|
957
1468
|
properties: {
|
|
958
1469
|
policy: { type: "boolean", description: "Return only policy violations (default: false)", default: false },
|
|
959
|
-
projectPath: { type: "string"
|
|
1470
|
+
projectPath: { type: "string" }
|
|
960
1471
|
}
|
|
961
1472
|
}
|
|
962
1473
|
},
|
|
963
1474
|
{
|
|
964
1475
|
name: "kirograph_attack_surface",
|
|
965
|
-
description: "Map
|
|
1476
|
+
description: "Map HTTP routes to vulnerable deps with hop count and auth status.",
|
|
966
1477
|
inputSchema: {
|
|
967
1478
|
type: "object",
|
|
968
1479
|
properties: {
|
|
969
1480
|
limit: { type: "number", description: "Max routes to return (default: 20)", default: 20 },
|
|
970
1481
|
publicOnly: { type: "boolean", description: "Only return public/unauthenticated routes", default: false },
|
|
971
|
-
projectPath: { type: "string"
|
|
1482
|
+
projectPath: { type: "string" }
|
|
972
1483
|
}
|
|
973
1484
|
}
|
|
974
1485
|
},
|
|
975
1486
|
{
|
|
976
1487
|
name: "kirograph_secrets",
|
|
977
|
-
description: "Scan for hardcoded secrets
|
|
1488
|
+
description: "Scan for hardcoded secrets with call-graph blast radius.",
|
|
978
1489
|
inputSchema: {
|
|
979
1490
|
type: "object",
|
|
980
1491
|
properties: {
|
|
981
1492
|
includeTests: { type: "boolean", description: "Include test files in scan", default: false },
|
|
982
1493
|
severity: { type: "string", description: "Filter by severity: critical, high, medium, low", enum: ["critical", "high", "medium", "low"] },
|
|
983
|
-
projectPath: { type: "string"
|
|
1494
|
+
projectPath: { type: "string" }
|
|
984
1495
|
}
|
|
985
1496
|
}
|
|
986
1497
|
},
|
|
987
1498
|
{
|
|
988
1499
|
name: "kirograph_security_flows",
|
|
989
|
-
description: "SAST-lite: detect
|
|
1500
|
+
description: "SAST-lite: detect SQL injection, eval, deserialize, path, crypto flows.",
|
|
990
1501
|
inputSchema: {
|
|
991
1502
|
type: "object",
|
|
992
1503
|
properties: {
|
|
993
1504
|
type: { type: "string", description: "Filter by type: sql, eval, deserialize, path, crypto, all", default: "all" },
|
|
994
|
-
projectPath: { type: "string"
|
|
1505
|
+
projectPath: { type: "string" }
|
|
995
1506
|
}
|
|
996
1507
|
}
|
|
997
1508
|
},
|
|
998
1509
|
{
|
|
999
1510
|
name: "kirograph_supply_chain",
|
|
1000
|
-
description: "
|
|
1511
|
+
description: "OpenSSF Scorecard scores, maintainer count, abandoned package detection.",
|
|
1001
1512
|
inputSchema: {
|
|
1002
1513
|
type: "object",
|
|
1003
1514
|
properties: {
|
|
1004
1515
|
threshold: { type: "string", description: "Minimum risk level: critical, high, medium", enum: ["critical", "high", "medium"] },
|
|
1005
1516
|
refresh: { type: "boolean", description: "Re-fetch from APIs", default: false },
|
|
1006
|
-
projectPath: { type: "string"
|
|
1517
|
+
projectPath: { type: "string" }
|
|
1007
1518
|
}
|
|
1008
1519
|
}
|
|
1009
1520
|
},
|
|
1010
1521
|
{
|
|
1011
1522
|
name: "kirograph_dep_confusion",
|
|
1012
|
-
description: "Detect dependency confusion
|
|
1523
|
+
description: "Detect dependency confusion: internal names in public registries.",
|
|
1013
1524
|
inputSchema: {
|
|
1014
1525
|
type: "object",
|
|
1015
1526
|
properties: {
|
|
1016
|
-
projectPath: { type: "string"
|
|
1527
|
+
projectPath: { type: "string" }
|
|
1017
1528
|
}
|
|
1018
1529
|
}
|
|
1019
1530
|
},
|
|
1020
1531
|
{
|
|
1021
1532
|
name: "kirograph_remediation",
|
|
1022
|
-
description: "Remediation SLA tracking:
|
|
1533
|
+
description: "Remediation SLA tracking: overdue vulns by severity thresholds.",
|
|
1023
1534
|
inputSchema: {
|
|
1024
1535
|
type: "object",
|
|
1025
1536
|
properties: {
|
|
1026
1537
|
overdueOnly: { type: "boolean", description: "Show only overdue items", default: false },
|
|
1027
|
-
projectPath: { type: "string"
|
|
1538
|
+
projectPath: { type: "string" }
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
// ── Phase 3 complexity tools ──────────────────────────────────────────────────
|
|
1543
|
+
{
|
|
1544
|
+
name: "kirograph_health",
|
|
1545
|
+
description: "Composite graph health score (0\u201310000) across complexity, dead code, coupling, and circular dependencies. Higher is better.",
|
|
1546
|
+
inputSchema: {
|
|
1547
|
+
type: "object",
|
|
1548
|
+
properties: {
|
|
1549
|
+
projectPath: { type: "string" }
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
name: "kirograph_dsm",
|
|
1555
|
+
description: "Design Structure Matrix \u2014 dependency counts between top-level modules. Reveals architectural coupling and layering violations.",
|
|
1556
|
+
inputSchema: {
|
|
1557
|
+
type: "object",
|
|
1558
|
+
properties: {
|
|
1559
|
+
limit: { type: "number", description: "Max module groups to include (default 15)", default: 15 },
|
|
1560
|
+
projectPath: { type: "string" }
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
name: "kirograph_test_risk",
|
|
1566
|
+
description: "Risk-ranked list of functions: complexity \xD7 fan-in. Highest-risk functions are most likely to break when changed.",
|
|
1567
|
+
inputSchema: {
|
|
1568
|
+
type: "object",
|
|
1569
|
+
properties: {
|
|
1570
|
+
limit: { type: "number", description: "Max results (default 20)", default: 20 },
|
|
1571
|
+
threshold: { type: "number", description: "Min risk score to include (default 0)", default: 0 },
|
|
1572
|
+
projectPath: { type: "string" }
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
},
|
|
1576
|
+
// ── Phase 3 git context tools ─────────────────────────────────────────────────
|
|
1577
|
+
{
|
|
1578
|
+
name: "kirograph_test_coverage",
|
|
1579
|
+
description: "Parse lcov/Istanbul/Cobertura coverage files and show per-file coverage percentage. Sorted worst-first by default.",
|
|
1580
|
+
inputSchema: {
|
|
1581
|
+
type: "object",
|
|
1582
|
+
properties: {
|
|
1583
|
+
sortBy: { type: "string", description: "Sort order: asc (worst first) or desc (best first)", enum: ["asc", "desc"], default: "asc" },
|
|
1584
|
+
limit: { type: "number", description: "Max results (default 30)", default: 30 },
|
|
1585
|
+
projectPath: { type: "string" }
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
},
|
|
1589
|
+
// ── Phase 4 branch tools ──────────────────────────────────────────────────────
|
|
1590
|
+
{
|
|
1591
|
+
name: "kirograph_branch_list",
|
|
1592
|
+
description: "List tracked branches with their DB sizes and last sync times.",
|
|
1593
|
+
inputSchema: {
|
|
1594
|
+
type: "object",
|
|
1595
|
+
properties: {
|
|
1596
|
+
projectPath: { type: "string" }
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
name: "kirograph_branch_diff",
|
|
1602
|
+
description: "Symbols added, removed, or changed between two tracked branches.",
|
|
1603
|
+
inputSchema: {
|
|
1604
|
+
type: "object",
|
|
1605
|
+
required: ["branchA"],
|
|
1606
|
+
properties: {
|
|
1607
|
+
branchA: { type: "string", description: "First branch name (required)" },
|
|
1608
|
+
branchB: { type: "string", description: "Second branch name (default: main)", default: "main" },
|
|
1609
|
+
projectPath: { type: "string" }
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
name: "kirograph_branch_search",
|
|
1615
|
+
description: "Search for symbols in a specific tracked branch graph.",
|
|
1616
|
+
inputSchema: {
|
|
1617
|
+
type: "object",
|
|
1618
|
+
required: ["query", "branch"],
|
|
1619
|
+
properties: {
|
|
1620
|
+
query: { type: "string", description: "Search query (symbol name, partial match)" },
|
|
1621
|
+
branch: { type: "string", description: "Branch name to search in" },
|
|
1622
|
+
limit: { type: "number", description: "Max results (default 20)", default: 20 },
|
|
1623
|
+
projectPath: { type: "string" }
|
|
1028
1624
|
}
|
|
1029
1625
|
}
|
|
1030
1626
|
},
|
|
1031
1627
|
// ── Pattern tools (require enablePatterns=true) ─────────────────────────────
|
|
1032
1628
|
{
|
|
1033
1629
|
name: "kirograph_pattern_coverage",
|
|
1034
|
-
description: "OWASP Top 10 coverage report:
|
|
1630
|
+
description: "OWASP Top 10 coverage report: categories covered and match counts.",
|
|
1035
1631
|
inputSchema: {
|
|
1036
1632
|
type: "object",
|
|
1037
1633
|
properties: {
|
|
1038
|
-
projectPath: { type: "string"
|
|
1634
|
+
projectPath: { type: "string" }
|
|
1039
1635
|
}
|
|
1040
1636
|
}
|
|
1041
1637
|
},
|
|
1042
1638
|
{
|
|
1043
1639
|
name: "kirograph_pattern_save_baseline",
|
|
1044
|
-
description: "Save current pattern match counts as a baseline
|
|
1640
|
+
description: "Save current pattern match counts as a baseline.",
|
|
1045
1641
|
inputSchema: {
|
|
1046
1642
|
type: "object",
|
|
1047
1643
|
properties: {
|
|
1048
1644
|
label: { type: "string", description: 'Baseline label (default: "default")', default: "default" },
|
|
1049
|
-
projectPath: { type: "string"
|
|
1645
|
+
projectPath: { type: "string" }
|
|
1050
1646
|
}
|
|
1051
1647
|
}
|
|
1052
1648
|
},
|
|
1053
1649
|
{
|
|
1054
1650
|
name: "kirograph_pattern_diff",
|
|
1055
|
-
description: "Diff
|
|
1651
|
+
description: "Diff pattern matches against a saved baseline.",
|
|
1056
1652
|
inputSchema: {
|
|
1057
1653
|
type: "object",
|
|
1058
1654
|
properties: {
|
|
1059
1655
|
label: { type: "string", description: 'Baseline label (default: "default")', default: "default" },
|
|
1060
|
-
projectPath: { type: "string"
|
|
1656
|
+
projectPath: { type: "string" }
|
|
1061
1657
|
}
|
|
1062
1658
|
}
|
|
1063
1659
|
}
|
|
@@ -1072,2779 +1668,10 @@ const LIVE_SEARCH_TOOL_DEFINITION = {
|
|
|
1072
1668
|
pattern: { type: "string", description: 'ast-grep inline pattern (e.g. "eval($X)")' },
|
|
1073
1669
|
language: { type: "string", description: "Language to search: javascript, typescript, python, go, rust, java, etc." },
|
|
1074
1670
|
limit: { type: "number", description: "Max results (default 20, max 100)", default: 20 },
|
|
1075
|
-
projectPath: { type: "string"
|
|
1671
|
+
projectPath: { type: "string" }
|
|
1076
1672
|
}
|
|
1077
1673
|
}
|
|
1078
1674
|
};
|
|
1079
|
-
class ToolHandler {
|
|
1080
|
-
constructor(cg) {
|
|
1081
|
-
this.connections = /* @__PURE__ */ new Map();
|
|
1082
|
-
/** Anti-loop: track recent data_query calls per dataset for pagination detection. */
|
|
1083
|
-
this.queryTracker = /* @__PURE__ */ new Map();
|
|
1084
|
-
this.defaultCg = cg;
|
|
1085
|
-
}
|
|
1086
|
-
setDefaultKiroGraph(cg) {
|
|
1087
|
-
this.defaultCg = cg;
|
|
1088
|
-
}
|
|
1089
|
-
/** Close all cached cross-project connections. */
|
|
1090
|
-
closeAll() {
|
|
1091
|
-
for (const cg of this.connections.values()) {
|
|
1092
|
-
try {
|
|
1093
|
-
cg.close();
|
|
1094
|
-
} catch {
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
this.connections.clear();
|
|
1098
|
-
}
|
|
1099
|
-
/** Anti-loop: detect pagination patterns and warn the agent. */
|
|
1100
|
-
checkPaginationLoop(dataset, offset, response) {
|
|
1101
|
-
const now = Date.now();
|
|
1102
|
-
const key = dataset;
|
|
1103
|
-
const currentOffset = offset ?? 0;
|
|
1104
|
-
for (const [k, v] of this.queryTracker) {
|
|
1105
|
-
if (now - v.lastCall > 6e4) this.queryTracker.delete(k);
|
|
1106
|
-
}
|
|
1107
|
-
const entry = this.queryTracker.get(key) ?? { offsets: [], lastCall: 0 };
|
|
1108
|
-
entry.offsets.push(currentOffset);
|
|
1109
|
-
entry.lastCall = now;
|
|
1110
|
-
if (entry.offsets.length > 10) entry.offsets = entry.offsets.slice(-10);
|
|
1111
|
-
this.queryTracker.set(key, entry);
|
|
1112
|
-
if (entry.offsets.length > 5) {
|
|
1113
|
-
const recent = entry.offsets.slice(-6);
|
|
1114
|
-
let isIncrementing = true;
|
|
1115
|
-
for (let i = 1; i < recent.length; i++) {
|
|
1116
|
-
if (recent[i] <= recent[i - 1]) {
|
|
1117
|
-
isIncrementing = false;
|
|
1118
|
-
break;
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
if (isIncrementing) {
|
|
1122
|
-
return response + "\n\n\u26A0 Pagination detected. Consider using kirograph_data_aggregate for summary statistics instead of paginating through all rows.";
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
return response;
|
|
1126
|
-
}
|
|
1127
|
-
async getConnection(projectPath) {
|
|
1128
|
-
if (!projectPath) return this.defaultCg;
|
|
1129
|
-
const resolved = path.resolve(projectPath);
|
|
1130
|
-
if (!path.isAbsolute(resolved)) return null;
|
|
1131
|
-
if (this.connections.has(resolved)) return this.connections.get(resolved);
|
|
1132
|
-
try {
|
|
1133
|
-
const cg = await import__.default.open(resolved);
|
|
1134
|
-
this.connections.set(resolved, cg);
|
|
1135
|
-
return cg;
|
|
1136
|
-
} catch {
|
|
1137
|
-
return null;
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
async handle(toolName, args) {
|
|
1141
|
-
try {
|
|
1142
|
-
const text = await this.dispatch(toolName, args);
|
|
1143
|
-
const truncated = truncate(text);
|
|
1144
|
-
if (toolName !== "kirograph_exec" && toolName !== "kirograph_gain") {
|
|
1145
|
-
try {
|
|
1146
|
-
const projectRoot = args.projectPath || this.defaultCg?.getProjectRoot() || process.cwd();
|
|
1147
|
-
const { estimateNaiveCost } = await Promise.resolve().then(() => require("../compression/naive-cost.js"));
|
|
1148
|
-
const { estimateTokens: estimateTokens2 } = await Promise.resolve().then(() => require("../compression/index.js"));
|
|
1149
|
-
const outputTokens = estimateTokens2(truncated);
|
|
1150
|
-
const naiveCost = estimateNaiveCost(toolName, outputTokens, args);
|
|
1151
|
-
if (naiveCost !== null && naiveCost > outputTokens) {
|
|
1152
|
-
const tracker = new import_tracker.TokenTracker(projectRoot);
|
|
1153
|
-
if (toolName.startsWith("kirograph_mem_")) {
|
|
1154
|
-
tracker.recordMemorySaving(toolName, outputTokens, naiveCost);
|
|
1155
|
-
} else if (toolName.startsWith("kirograph_docs_")) {
|
|
1156
|
-
tracker.recordDocsSaving(toolName, outputTokens, naiveCost);
|
|
1157
|
-
} else if (toolName.startsWith("kirograph_data_")) {
|
|
1158
|
-
tracker.recordDataSaving(toolName, outputTokens, naiveCost);
|
|
1159
|
-
} else {
|
|
1160
|
-
tracker.recordGraphSaving(toolName, outputTokens, naiveCost);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
} catch {
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
return { content: [{ type: "text", text: truncated }] };
|
|
1167
|
-
} catch (err) {
|
|
1168
|
-
(0, import_errors.logError)("MCP tool error", { tool: toolName, error: err });
|
|
1169
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1170
|
-
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
async dispatch(toolName, args) {
|
|
1174
|
-
if (toolName === "kirograph_exec") {
|
|
1175
|
-
const cmd = args.command;
|
|
1176
|
-
if (!cmd) return "Error: command is required.";
|
|
1177
|
-
const projectRoot = args.projectPath || args.cwd || process.cwd();
|
|
1178
|
-
const execCwd = args.cwd || projectRoot;
|
|
1179
|
-
let defaultLevel = "normal";
|
|
1180
|
-
try {
|
|
1181
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1182
|
-
const config = await loadConfig(projectRoot);
|
|
1183
|
-
if (config.shellCompressionLevel && config.shellCompressionLevel !== "off") {
|
|
1184
|
-
defaultLevel = config.shellCompressionLevel;
|
|
1185
|
-
}
|
|
1186
|
-
} catch {
|
|
1187
|
-
}
|
|
1188
|
-
const level = args.level ?? defaultLevel;
|
|
1189
|
-
const timeout = (args.timeout ?? 60) * 1e3;
|
|
1190
|
-
let rawOutput;
|
|
1191
|
-
let exitCode = 0;
|
|
1192
|
-
try {
|
|
1193
|
-
rawOutput = (0, import_child_process.execSync)(cmd, {
|
|
1194
|
-
cwd: execCwd,
|
|
1195
|
-
timeout,
|
|
1196
|
-
encoding: "utf8",
|
|
1197
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1198
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1199
|
-
});
|
|
1200
|
-
} catch (err) {
|
|
1201
|
-
rawOutput = (err.stdout || "") + (err.stderr || "");
|
|
1202
|
-
exitCode = err.status ?? 1;
|
|
1203
|
-
}
|
|
1204
|
-
const result = (0, import_compression.compress)(cmd, rawOutput, { level, preserveErrors: exitCode !== 0 });
|
|
1205
|
-
const tracker = new import_tracker.TokenTracker(projectRoot);
|
|
1206
|
-
tracker.record(cmd, result.originalTokens, result.compressedTokens, result.strategy);
|
|
1207
|
-
const header = exitCode !== 0 ? `[exit ${exitCode}] ` : "";
|
|
1208
|
-
const footer = result.savings > 5 ? `
|
|
1209
|
-
|
|
1210
|
-
[${result.savings}% tokens saved | ${result.originalTokens}\u2192${result.compressedTokens} | ${result.strategy}]` : "";
|
|
1211
|
-
return `${header}${result.output}${footer}`;
|
|
1212
|
-
}
|
|
1213
|
-
if (toolName === "kirograph_gain") {
|
|
1214
|
-
const projectRoot = args.projectPath || process.cwd();
|
|
1215
|
-
const period = args.period ?? "session";
|
|
1216
|
-
const tracker = new import_tracker.TokenTracker(projectRoot);
|
|
1217
|
-
const stats = tracker.getStats(period);
|
|
1218
|
-
if (stats.totalCommands === 0) {
|
|
1219
|
-
return "No savings recorded yet. Use kirograph tools and kirograph_exec \u2014 savings are tracked automatically.";
|
|
1220
|
-
}
|
|
1221
|
-
const lines = [
|
|
1222
|
-
`Token Savings (${period}):`,
|
|
1223
|
-
` Total calls: ${stats.totalCommands}`,
|
|
1224
|
-
` Tokens without KiroGraph: ~${stats.totalOriginal.toLocaleString()}`,
|
|
1225
|
-
` Tokens with KiroGraph: ~${stats.totalCompressed.toLocaleString()}`,
|
|
1226
|
-
` Saved: ${stats.totalSaved.toLocaleString()} tokens (${stats.savingsPercent}%)`
|
|
1227
|
-
];
|
|
1228
|
-
if (stats.bySource.exec.count > 0 || stats.bySource.graph.count > 0 || stats.bySource.memory.count > 0 || stats.bySource.docs.count > 0) {
|
|
1229
|
-
lines.push("", "By source:");
|
|
1230
|
-
if (stats.bySource.graph.count > 0) {
|
|
1231
|
-
lines.push(` Graph tools: ${stats.bySource.graph.count} calls, ~${stats.bySource.graph.saved.toLocaleString()} tokens saved (vs file reads/grep)`);
|
|
1232
|
-
}
|
|
1233
|
-
if (stats.bySource.docs.count > 0) {
|
|
1234
|
-
lines.push(` Docs tools: ${stats.bySource.docs.count} calls, ~${stats.bySource.docs.saved.toLocaleString()} tokens saved (vs reading full doc files)`);
|
|
1235
|
-
}
|
|
1236
|
-
if (stats.bySource.data.count > 0) {
|
|
1237
|
-
lines.push(` Data tools: ${stats.bySource.data.count} calls, ~${stats.bySource.data.saved.toLocaleString()} tokens saved (vs loading raw data files)`);
|
|
1238
|
-
}
|
|
1239
|
-
if (stats.bySource.exec.count > 0) {
|
|
1240
|
-
lines.push(` Compression: ${stats.bySource.exec.count} calls, ~${stats.bySource.exec.saved.toLocaleString()} tokens saved (vs raw output)`);
|
|
1241
|
-
}
|
|
1242
|
-
if (stats.bySource.memory.count > 0) {
|
|
1243
|
-
lines.push(` Memory: ${stats.bySource.memory.count} calls, ~${stats.bySource.memory.saved.toLocaleString()} tokens saved (vs re-discovering context)`);
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
if (Object.keys(stats.byFamily).length > 0) {
|
|
1247
|
-
lines.push("", "Top families:");
|
|
1248
|
-
for (const [family, data] of Object.entries(stats.byFamily).slice(0, 7)) {
|
|
1249
|
-
lines.push(` ${family}: ${data.count} calls, ${data.savings}% avg savings`);
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
if (stats.recentCommands.length > 0) {
|
|
1253
|
-
lines.push("", "Recent:");
|
|
1254
|
-
for (const cmd of stats.recentCommands.slice(0, 5)) {
|
|
1255
|
-
const tag = cmd.source === "graph" ? "\u{1F4CA}" : "\u26A1";
|
|
1256
|
-
lines.push(` ${tag} ${cmd.command.slice(0, 40)} \u2192 ${cmd.savings}% saved`);
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
return lines.join("\n");
|
|
1260
|
-
}
|
|
1261
|
-
if (toolName === "kirograph_read") {
|
|
1262
|
-
const filePath = args.path;
|
|
1263
|
-
if (!filePath) return "Error: path is required.";
|
|
1264
|
-
const projectRoot = args.projectPath || process.cwd();
|
|
1265
|
-
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(projectRoot, filePath);
|
|
1266
|
-
const mode = args.mode ?? "full";
|
|
1267
|
-
const noCache = args.noCache ?? false;
|
|
1268
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
1269
|
-
return `Error: File not found: ${resolvedPath}`;
|
|
1270
|
-
}
|
|
1271
|
-
const { getFileReadCache } = await Promise.resolve().then(() => require("./cache.js"));
|
|
1272
|
-
const { executeReadMode } = await Promise.resolve().then(() => require("./read-modes.js"));
|
|
1273
|
-
const cache = getFileReadCache();
|
|
1274
|
-
if (mode !== "full") {
|
|
1275
|
-
let cg2 = null;
|
|
1276
|
-
try {
|
|
1277
|
-
cg2 = await this.getConnection(args.projectPath);
|
|
1278
|
-
} catch {
|
|
1279
|
-
}
|
|
1280
|
-
const result2 = executeReadMode({
|
|
1281
|
-
mode,
|
|
1282
|
-
filePath: resolvedPath,
|
|
1283
|
-
start: args.start,
|
|
1284
|
-
end: args.end,
|
|
1285
|
-
cg: cg2
|
|
1286
|
-
});
|
|
1287
|
-
cache.read(resolvedPath, true);
|
|
1288
|
-
return result2.content;
|
|
1289
|
-
}
|
|
1290
|
-
const result = cache.read(resolvedPath, noCache);
|
|
1291
|
-
if (result.cached) {
|
|
1292
|
-
return result.content;
|
|
1293
|
-
}
|
|
1294
|
-
if (result.changed) {
|
|
1295
|
-
return `[file changed since last read]
|
|
1296
|
-
|
|
1297
|
-
${result.content}`;
|
|
1298
|
-
}
|
|
1299
|
-
return result.content;
|
|
1300
|
-
}
|
|
1301
|
-
if (toolName === "kirograph_budget") {
|
|
1302
|
-
const projectRoot = args.projectPath || process.cwd();
|
|
1303
|
-
const reset = args.reset ?? false;
|
|
1304
|
-
const { BudgetTracker } = await Promise.resolve().then(() => require("../compression/tracker.js"));
|
|
1305
|
-
const budget = BudgetTracker.getInstance(projectRoot);
|
|
1306
|
-
if (reset) {
|
|
1307
|
-
budget.reset();
|
|
1308
|
-
return "Context budget counters reset.";
|
|
1309
|
-
}
|
|
1310
|
-
const status = budget.getStatus();
|
|
1311
|
-
const lines = [
|
|
1312
|
-
"Context Budget:",
|
|
1313
|
-
` Tokens consumed: ${status.consumed.toLocaleString()}`,
|
|
1314
|
-
` Budget limit: ${status.limit > 0 ? status.limit.toLocaleString() : "unlimited"}`,
|
|
1315
|
-
` Remaining: ${status.limit > 0 ? status.remaining.toLocaleString() : "\u221E"}`,
|
|
1316
|
-
` Utilization: ${status.utilization}%`
|
|
1317
|
-
];
|
|
1318
|
-
if (status.warning) {
|
|
1319
|
-
lines.push(`
|
|
1320
|
-
\u26A0 ${status.warning}`);
|
|
1321
|
-
}
|
|
1322
|
-
return lines.join("\n");
|
|
1323
|
-
}
|
|
1324
|
-
const cg = await this.getConnection(args.projectPath);
|
|
1325
|
-
if (!cg) return "KiroGraph not initialized. Run `kirograph init` in your project first.";
|
|
1326
|
-
writeSessionMarker(cg.getProjectRoot());
|
|
1327
|
-
switch (toolName) {
|
|
1328
|
-
case "kirograph_search": {
|
|
1329
|
-
const limit = clampLimit(args.limit, 10);
|
|
1330
|
-
const results = cg.searchNodes(
|
|
1331
|
-
args.query,
|
|
1332
|
-
args.kind,
|
|
1333
|
-
limit
|
|
1334
|
-
);
|
|
1335
|
-
if (results.length === 0) return `No symbols found matching "${args.query}".`;
|
|
1336
|
-
return results.map(
|
|
1337
|
-
(r) => `${mapKind(r.node.kind)} ${r.node.name}
|
|
1338
|
-
File: ${r.node.filePath}:${r.node.startLine}
|
|
1339
|
-
Qualified: ${r.node.qualifiedName}`
|
|
1340
|
-
).join("\n\n");
|
|
1341
|
-
}
|
|
1342
|
-
case "kirograph_context": {
|
|
1343
|
-
const ctx = await cg.buildContext(args.task, {
|
|
1344
|
-
maxNodes: args.maxNodes ?? 20,
|
|
1345
|
-
includeCode: args.includeCode ?? true
|
|
1346
|
-
});
|
|
1347
|
-
const lines = [ctx.summary, ""];
|
|
1348
|
-
if (ctx.entryPoints.length === 0) {
|
|
1349
|
-
lines.push("No matching symbols found. If this is a new feature, consider using kirograph_files to explore the codebase structure.");
|
|
1350
|
-
} else {
|
|
1351
|
-
lines.push("## Entry Points");
|
|
1352
|
-
for (const n of ctx.entryPoints) {
|
|
1353
|
-
lines.push(`- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`);
|
|
1354
|
-
if (ctx.codeSnippets.has(n.id)) {
|
|
1355
|
-
lines.push("```", ctx.codeSnippets.get(n.id), "```");
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
if (ctx.relatedNodes.length > 0) {
|
|
1359
|
-
lines.push("", "## Related Symbols");
|
|
1360
|
-
for (const n of ctx.relatedNodes.slice(0, 10)) {
|
|
1361
|
-
lines.push(`- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`);
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
try {
|
|
1366
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1367
|
-
const projectRoot = cg.getProjectRoot();
|
|
1368
|
-
const config = await loadConfig(projectRoot);
|
|
1369
|
-
if (config.enableMemory) {
|
|
1370
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
1371
|
-
const db = cg.getDatabase();
|
|
1372
|
-
db.applyMemorySchema();
|
|
1373
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
1374
|
-
mem.initialize();
|
|
1375
|
-
const qualifiedNames = [
|
|
1376
|
-
...ctx.entryPoints.map((n) => n.qualifiedName),
|
|
1377
|
-
...ctx.relatedNodes.slice(0, 5).map((n) => n.qualifiedName)
|
|
1378
|
-
].filter(Boolean);
|
|
1379
|
-
const contextLimit = config.memoryContextLimit ?? 3;
|
|
1380
|
-
const contextThreshold = config.memoryContextThreshold ?? 0.3;
|
|
1381
|
-
let memResults = mem.getLinkedObservations(qualifiedNames, contextLimit, contextThreshold);
|
|
1382
|
-
if (memResults.length === 0) {
|
|
1383
|
-
memResults = (await mem.search(args.task, { limit: contextLimit })).filter((r) => r.score >= contextThreshold);
|
|
1384
|
-
}
|
|
1385
|
-
if (memResults.length > 0) {
|
|
1386
|
-
lines.push("", "## Related Memory");
|
|
1387
|
-
for (const r of memResults.slice(0, contextLimit)) {
|
|
1388
|
-
const age = formatAge(r.observation.createdAt);
|
|
1389
|
-
lines.push(`- [${r.observation.kind}] ${r.observation.content} (${age})`);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
} catch {
|
|
1394
|
-
}
|
|
1395
|
-
try {
|
|
1396
|
-
const projectRoot2 = cg.getProjectRoot();
|
|
1397
|
-
const config2 = await (await Promise.resolve().then(() => require("../config.js"))).loadConfig(projectRoot2);
|
|
1398
|
-
if (config2.enableDocs && config2.docsContextLimit > 0) {
|
|
1399
|
-
const db2 = cg.getDatabase();
|
|
1400
|
-
db2.applyDocsSchema();
|
|
1401
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
1402
|
-
const docsQueries = new DocsQueries(db2.getRawDb(), projectRoot2);
|
|
1403
|
-
const qNames = ctx.entryPoints.map((n) => n.qualifiedName).filter(Boolean);
|
|
1404
|
-
if (qNames.length > 0) {
|
|
1405
|
-
const docRefs = docsQueries.getRefs({ qualifiedName: qNames[0] });
|
|
1406
|
-
const additionalRefs = qNames.slice(1, 5).flatMap((qn) => docsQueries.getRefs({ qualifiedName: qn }));
|
|
1407
|
-
const allDocRefs = [...docRefs, ...additionalRefs];
|
|
1408
|
-
const seenSections = /* @__PURE__ */ new Set();
|
|
1409
|
-
const uniqueRefs = allDocRefs.filter((r) => {
|
|
1410
|
-
if (seenSections.has(r.sectionId)) return false;
|
|
1411
|
-
seenSections.add(r.sectionId);
|
|
1412
|
-
return r.confidence >= config2.docsContextThreshold;
|
|
1413
|
-
}).slice(0, config2.docsContextLimit);
|
|
1414
|
-
if (uniqueRefs.length > 0) {
|
|
1415
|
-
lines.push("", "## Related Documentation");
|
|
1416
|
-
for (const ref of uniqueRefs) {
|
|
1417
|
-
const section = docsQueries.getSection(ref.sectionId);
|
|
1418
|
-
if (section) {
|
|
1419
|
-
const summary = section.section.summary ?? section.section.title;
|
|
1420
|
-
lines.push(`- [${ref.refType}] ${summary} \u2014 ${section.section.filePath} (ID: ${ref.sectionId})`);
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
} catch {
|
|
1427
|
-
}
|
|
1428
|
-
try {
|
|
1429
|
-
const projectRoot3 = cg.getProjectRoot();
|
|
1430
|
-
const config3 = await (await Promise.resolve().then(() => require("../config.js"))).loadConfig(projectRoot3);
|
|
1431
|
-
if (config3.enableData && config3.dataContextLimit > 0) {
|
|
1432
|
-
const db3 = cg.getDatabase();
|
|
1433
|
-
db3.applyDataSchema();
|
|
1434
|
-
const entryFiles = ctx.entryPoints.map((n) => n.filePath).filter(Boolean);
|
|
1435
|
-
if (entryFiles.length > 0) {
|
|
1436
|
-
const placeholders = entryFiles.map(() => "?").join(", ");
|
|
1437
|
-
const dataRefs = db3.getRawDb().all(
|
|
1438
|
-
`SELECT DISTINCT d.id, d.file_path, d.row_count, d.column_count
|
|
1439
|
-
FROM data_code_refs r JOIN data_datasets d ON r.dataset_id = d.id
|
|
1440
|
-
WHERE r.qualified_name IN (${placeholders})`,
|
|
1441
|
-
entryFiles
|
|
1442
|
-
);
|
|
1443
|
-
if (dataRefs.length > 0) {
|
|
1444
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
1445
|
-
const dq = new DataQueries(db3.getRawDb());
|
|
1446
|
-
const limit = config3.dataContextLimit;
|
|
1447
|
-
lines.push("", "## Related Data");
|
|
1448
|
-
for (const ref of dataRefs.slice(0, limit)) {
|
|
1449
|
-
const info = dq.describeDataset(ref.id);
|
|
1450
|
-
if (info) {
|
|
1451
|
-
const colSummary = info.columns.map((c) => `${c.name}:${c.inferredType}`).join(", ");
|
|
1452
|
-
lines.push(`- **${ref.id}** (${ref.file_path}) \u2014 ${ref.row_count} rows, ${ref.column_count} cols`);
|
|
1453
|
-
lines.push(` Schema: ${colSummary}`);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
} catch {
|
|
1460
|
-
}
|
|
1461
|
-
try {
|
|
1462
|
-
const projectRootSec = cg.getProjectRoot();
|
|
1463
|
-
const configSec = await (await Promise.resolve().then(() => require("../config.js"))).loadConfig(projectRootSec);
|
|
1464
|
-
if (configSec.enableSecurity) {
|
|
1465
|
-
const dbSec = cg.getDatabase();
|
|
1466
|
-
dbSec.applySecuritySchema();
|
|
1467
|
-
const rawDbSec = dbSec.getRawDb();
|
|
1468
|
-
const contextNodeIds = [
|
|
1469
|
-
...ctx.entryPoints.map((n) => n.id),
|
|
1470
|
-
...ctx.relatedNodes.map((n) => n.id)
|
|
1471
|
-
].filter(Boolean);
|
|
1472
|
-
if (contextNodeIds.length > 0) {
|
|
1473
|
-
const { getSecurityWarningsForNodes, formatSecurityWarnings } = await Promise.resolve().then(() => require("../security/context-warnings.js"));
|
|
1474
|
-
const warnings = getSecurityWarningsForNodes(rawDbSec, contextNodeIds);
|
|
1475
|
-
if (warnings.length > 0) {
|
|
1476
|
-
const nodeNames = /* @__PURE__ */ new Map();
|
|
1477
|
-
for (const n of ctx.entryPoints) {
|
|
1478
|
-
nodeNames.set(n.id, n.name);
|
|
1479
|
-
}
|
|
1480
|
-
for (const n of ctx.relatedNodes) {
|
|
1481
|
-
nodeNames.set(n.id, n.name);
|
|
1482
|
-
}
|
|
1483
|
-
const secSection = formatSecurityWarnings(warnings, nodeNames);
|
|
1484
|
-
if (secSection) {
|
|
1485
|
-
lines.push(secSection);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
} catch {
|
|
1491
|
-
}
|
|
1492
|
-
try {
|
|
1493
|
-
const projectRootPat = cg.getProjectRoot();
|
|
1494
|
-
const configPat = await (await Promise.resolve().then(() => require("../config.js"))).loadConfig(projectRootPat);
|
|
1495
|
-
if (configPat.enablePatterns) {
|
|
1496
|
-
const dbPat = cg.getDatabase();
|
|
1497
|
-
dbPat.applyPatternsSchema();
|
|
1498
|
-
const rawDbPat = dbPat.getRawDb();
|
|
1499
|
-
const tableExists = rawDbPat.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
1500
|
-
if (tableExists) {
|
|
1501
|
-
const contextFiles = [
|
|
1502
|
-
...ctx.entryPoints.map((n) => n.filePath),
|
|
1503
|
-
...ctx.relatedNodes.map((n) => n.filePath)
|
|
1504
|
-
].filter(Boolean);
|
|
1505
|
-
const uniqueContextFiles = [...new Set(contextFiles)];
|
|
1506
|
-
if (uniqueContextFiles.length > 0) {
|
|
1507
|
-
const placeholders = uniqueContextFiles.map(() => "?").join(",");
|
|
1508
|
-
const patternRows = rawDbPat.all(
|
|
1509
|
-
`SELECT file_path, pattern_id, severity, owasp_category, line FROM pattern_matches WHERE file_path IN (${placeholders}) ORDER BY severity DESC LIMIT 5`,
|
|
1510
|
-
uniqueContextFiles
|
|
1511
|
-
);
|
|
1512
|
-
if (patternRows.length > 0) {
|
|
1513
|
-
const patternWarnings = patternRows.map(
|
|
1514
|
-
(p) => `- **${p.pattern_id}** [${p.severity.toUpperCase()}/${p.owasp_category}]: ${p.file_path}:${p.line}`
|
|
1515
|
-
).join("\n");
|
|
1516
|
-
lines.push("", "## \u26A0 Pattern Findings", "", patternWarnings);
|
|
1517
|
-
if (patternRows.length === 5) lines.push("", "More findings \u2014 run `kirograph pattern --list` to see all rules.");
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
} catch {
|
|
1523
|
-
}
|
|
1524
|
-
const graphTokens = lines.join("\n").length / 4;
|
|
1525
|
-
const uniqueFiles = /* @__PURE__ */ new Set([
|
|
1526
|
-
...ctx.entryPoints.map((n) => n.filePath),
|
|
1527
|
-
...ctx.relatedNodes.map((n) => n.filePath)
|
|
1528
|
-
]);
|
|
1529
|
-
if (uniqueFiles.size > 0) {
|
|
1530
|
-
const naiveTokens = estimateFileTokens(cg.getProjectRoot(), [...uniqueFiles]);
|
|
1531
|
-
if (naiveTokens > 0) {
|
|
1532
|
-
const savingsPct = Math.round((1 - graphTokens / naiveTokens) * 100);
|
|
1533
|
-
if (savingsPct > 0) {
|
|
1534
|
-
lines.push("", `---`, `Context savings: ~${Math.round(graphTokens)} tokens (graph) vs ~${naiveTokens} tokens (full files) \u2014 ${savingsPct}% reduction`);
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
return lines.join("\n");
|
|
1539
|
-
}
|
|
1540
|
-
case "kirograph_callers": {
|
|
1541
|
-
const limit = clampLimit(args.limit, 20);
|
|
1542
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
1543
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
1544
|
-
const node = results[0].node;
|
|
1545
|
-
const callers = await cg.getCallers(node.id, limit);
|
|
1546
|
-
if (callers.length === 0) return `No callers found for \`${node.name}\`.`;
|
|
1547
|
-
return `Callers of \`${node.name}\`:
|
|
1548
|
-
` + callers.map(
|
|
1549
|
-
(n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`
|
|
1550
|
-
).join("\n");
|
|
1551
|
-
}
|
|
1552
|
-
case "kirograph_callees": {
|
|
1553
|
-
const limit = clampLimit(args.limit, 20);
|
|
1554
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
1555
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
1556
|
-
const node = results[0].node;
|
|
1557
|
-
const callees = await cg.getCallees(node.id, limit);
|
|
1558
|
-
if (callees.length === 0) return `\`${node.name}\` doesn't call any indexed symbols.`;
|
|
1559
|
-
return `\`${node.name}\` calls:
|
|
1560
|
-
` + callees.map(
|
|
1561
|
-
(n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`
|
|
1562
|
-
).join("\n");
|
|
1563
|
-
}
|
|
1564
|
-
case "kirograph_impact": {
|
|
1565
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
1566
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
1567
|
-
const node = results[0].node;
|
|
1568
|
-
const affected = await cg.getImpactRadius(node.id, args.depth ?? 2);
|
|
1569
|
-
if (affected.length === 0) return `No dependents found for \`${node.name}\`.`;
|
|
1570
|
-
let output = `Changing \`${node.name}\` may affect ${affected.length} symbol(s):
|
|
1571
|
-
` + affected.map((n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`).join("\n");
|
|
1572
|
-
try {
|
|
1573
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
1574
|
-
const projectRoot = cg.getProjectRoot();
|
|
1575
|
-
const config = await loadConfig(projectRoot);
|
|
1576
|
-
if (config.enableMemory) {
|
|
1577
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
1578
|
-
const db = cg.getDatabase();
|
|
1579
|
-
db.applyMemorySchema();
|
|
1580
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
1581
|
-
mem.initialize();
|
|
1582
|
-
const contextLimit = config.memoryContextLimit ?? 3;
|
|
1583
|
-
const contextThreshold = config.memoryContextThreshold ?? 0.3;
|
|
1584
|
-
const memResults = mem.getLinkedObservations(
|
|
1585
|
-
[node.qualifiedName],
|
|
1586
|
-
contextLimit,
|
|
1587
|
-
contextThreshold
|
|
1588
|
-
);
|
|
1589
|
-
if (memResults.length > 0) {
|
|
1590
|
-
output += "\n\nRelated Memory:";
|
|
1591
|
-
for (const r of memResults) {
|
|
1592
|
-
const age = formatAge(r.observation.createdAt);
|
|
1593
|
-
output += `
|
|
1594
|
-
- [${r.observation.kind}] ${r.observation.content} (${age})`;
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
} catch {
|
|
1599
|
-
}
|
|
1600
|
-
try {
|
|
1601
|
-
const rawDbImp = cg.getDatabase().getRawDb();
|
|
1602
|
-
const tableExistsImp = rawDbImp.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
1603
|
-
if (tableExistsImp) {
|
|
1604
|
-
const symNode = rawDbImp.get("SELECT id FROM nodes WHERE name = ? LIMIT 1", [args.symbol]);
|
|
1605
|
-
if (symNode) {
|
|
1606
|
-
const patternMatches = rawDbImp.all(
|
|
1607
|
-
"SELECT pattern_id, severity, line FROM pattern_matches WHERE symbol_node_id = ?",
|
|
1608
|
-
[symNode.id]
|
|
1609
|
-
);
|
|
1610
|
-
if (patternMatches.length > 0) {
|
|
1611
|
-
output += "\n\n\u26A0 This symbol has pattern matches:";
|
|
1612
|
-
for (const pm of patternMatches) {
|
|
1613
|
-
output += `
|
|
1614
|
-
[${pm.severity.toUpperCase()}] ${pm.pattern_id} at line ${pm.line}`;
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
} catch {
|
|
1620
|
-
}
|
|
1621
|
-
return output;
|
|
1622
|
-
}
|
|
1623
|
-
case "kirograph_node": {
|
|
1624
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
1625
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
1626
|
-
const node = results[0].node;
|
|
1627
|
-
const lines = [
|
|
1628
|
-
`${mapKind(node.kind)} \`${node.name}\``,
|
|
1629
|
-
`File: ${node.filePath}:${node.startLine}-${node.endLine}`,
|
|
1630
|
-
`Qualified: ${node.qualifiedName}`,
|
|
1631
|
-
node.signature ? `Signature: ${node.signature}` : "",
|
|
1632
|
-
node.docstring ? `Docs: ${node.docstring}` : ""
|
|
1633
|
-
].filter(Boolean);
|
|
1634
|
-
if (args.includeCode) {
|
|
1635
|
-
const src = cg.getNodeSource(node);
|
|
1636
|
-
if (src) lines.push("", "```", src, "```");
|
|
1637
|
-
}
|
|
1638
|
-
return lines.join("\n");
|
|
1639
|
-
}
|
|
1640
|
-
case "kirograph_status": {
|
|
1641
|
-
const stats = await cg.getStats();
|
|
1642
|
-
const langLine = Object.entries(stats.filesByLanguage).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
1643
|
-
const dbMb = (stats.dbSizeBytes / 1024 / 1024).toFixed(2);
|
|
1644
|
-
const semanticLines = stats.embeddingsEnabled ? [
|
|
1645
|
-
` Semantic search: enabled`,
|
|
1646
|
-
` Semantic model: ${stats.embeddingModel}`,
|
|
1647
|
-
` Semantic engine: ${stats.semanticEngine === "turboquant" ? `turboquant (${stats.vecIndexCount} entries \xB7 ANN)` : stats.semanticEngine === "turbovec" ? `turbovec (${stats.vecIndexCount} entries \xB7 ${stats.turbovecBits ?? 4} bits \xB7 Rust/SIMD)` : stats.semanticEngine === "sqlite-vec" ? `sqlite-vec (${stats.vecIndexCount} entries in ANN index)` : stats.semanticEngine === "orama" ? `orama hybrid (${stats.vecIndexCount} docs in index)` : stats.semanticEngine === "pglite" ? `pglite+pgvector (${stats.vecIndexCount} rows in DB)` : stats.semanticEngine === "lancedb" ? `lancedb (${stats.vecIndexCount} entries in ANN index)` : stats.semanticEngine === "qdrant" ? `qdrant (${stats.vecIndexCount} points in collection)` : stats.semanticEngine === "typesense" ? `typesense (${stats.vecIndexCount} documents in collection)` : "in-process cosine"}`,
|
|
1648
|
-
` Embeddings: ${stats.embeddingCount} / ${stats.embeddableNodeCount || stats.nodes} embeddable symbols`,
|
|
1649
|
-
...stats.engineFallback ? [` \u26A0 Engine fallback: ${stats.engineFallback}`] : []
|
|
1650
|
-
] : [` Semantic search: disabled`];
|
|
1651
|
-
const frameworkLine = stats.frameworks.length > 0 ? ` Frameworks: ${stats.frameworks.join(", ")}` : ` Frameworks: none detected`;
|
|
1652
|
-
const archLine = stats.architectureEnabled ? stats.architectureStats ? ` Architecture: enabled \u2014 ${stats.architectureStats.packages} packages, ${stats.architectureStats.layers} layers, ${stats.architectureStats.packageDeps} deps` : ` Architecture: enabled (not yet analyzed \u2014 run kirograph index)` : ` Architecture: disabled`;
|
|
1653
|
-
let docsLine = " Documentation: disabled";
|
|
1654
|
-
try {
|
|
1655
|
-
const { loadConfig: loadCfg } = await Promise.resolve().then(() => require("../config.js"));
|
|
1656
|
-
const cfg = await loadCfg(cg.getProjectRoot());
|
|
1657
|
-
if (cfg.enableDocs) {
|
|
1658
|
-
const db = cg.getDatabase();
|
|
1659
|
-
db.applyDocsSchema();
|
|
1660
|
-
const rawDb = db.getRawDb();
|
|
1661
|
-
const docFiles = rawDb.get("SELECT COUNT(DISTINCT file_path) as cnt FROM doc_sections")?.cnt ?? 0;
|
|
1662
|
-
const docSections = rawDb.get("SELECT COUNT(*) as cnt FROM doc_sections")?.cnt ?? 0;
|
|
1663
|
-
const docRefs = rawDb.get("SELECT COUNT(*) as cnt FROM doc_code_refs")?.cnt ?? 0;
|
|
1664
|
-
docsLine = ` Documentation: enabled \u2014 ${docFiles} files, ${docSections} sections, ${docRefs} code refs`;
|
|
1665
|
-
}
|
|
1666
|
-
} catch {
|
|
1667
|
-
}
|
|
1668
|
-
let dataLine = " Data: disabled";
|
|
1669
|
-
try {
|
|
1670
|
-
const { loadConfig: loadCfg2 } = await Promise.resolve().then(() => require("../config.js"));
|
|
1671
|
-
const cfg2 = await loadCfg2(cg.getProjectRoot());
|
|
1672
|
-
if (cfg2.enableData) {
|
|
1673
|
-
const rawDb2 = cg.getDatabase().getRawDb();
|
|
1674
|
-
cg.getDatabase().applyDataSchema();
|
|
1675
|
-
const datasetCount = rawDb2.get("SELECT COUNT(*) as cnt FROM data_datasets")?.cnt ?? 0;
|
|
1676
|
-
if (datasetCount > 0) {
|
|
1677
|
-
const totalRows = rawDb2.get("SELECT SUM(row_count) as total FROM data_datasets")?.total ?? 0;
|
|
1678
|
-
const totalCols = rawDb2.get("SELECT SUM(column_count) as total FROM data_datasets")?.total ?? 0;
|
|
1679
|
-
const totalSize = rawDb2.get("SELECT SUM(file_size) as total FROM data_datasets")?.total ?? 0;
|
|
1680
|
-
const sizeMb = (totalSize / 1024 / 1024).toFixed(2);
|
|
1681
|
-
dataLine = ` Data: enabled \u2014 ${datasetCount} datasets, ${totalRows.toLocaleString()} rows, ${totalCols} columns (${sizeMb} MB source)`;
|
|
1682
|
-
} else {
|
|
1683
|
-
dataLine = ` Data: enabled (no datasets indexed yet \u2014 run kirograph index)`;
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
} catch {
|
|
1687
|
-
}
|
|
1688
|
-
let securityLine = " Security: disabled";
|
|
1689
|
-
try {
|
|
1690
|
-
const { loadConfig: loadCfgSec } = await Promise.resolve().then(() => require("../config.js"));
|
|
1691
|
-
const cfgSec = await loadCfgSec(cg.getProjectRoot());
|
|
1692
|
-
if (cfgSec.enableSecurity) {
|
|
1693
|
-
const db = cg.getDatabase();
|
|
1694
|
-
db.applySecuritySchema();
|
|
1695
|
-
const rawDb = db.getRawDb();
|
|
1696
|
-
const depCount = rawDb.get("SELECT COUNT(*) as cnt FROM sec_dependencies")?.cnt ?? 0;
|
|
1697
|
-
const vulnCount = rawDb.get("SELECT COUNT(*) as cnt FROM sec_vulnerabilities")?.cnt ?? 0;
|
|
1698
|
-
const affectedCount = rawDb.get("SELECT COUNT(*) as cnt FROM sec_reachability WHERE verdict = 'affected'")?.cnt ?? 0;
|
|
1699
|
-
const staleCount = rawDb.get("SELECT COUNT(*) as cnt FROM sec_dependencies WHERE vuln_data_stale = 1")?.cnt ?? 0;
|
|
1700
|
-
const staleNote = staleCount > 0 ? ` \u26A0 ${staleCount} stale` : "";
|
|
1701
|
-
securityLine = ` Security: enabled \u2014 ${depCount} deps, ${vulnCount} vulns (${affectedCount} affected)${staleNote}`;
|
|
1702
|
-
}
|
|
1703
|
-
} catch {
|
|
1704
|
-
}
|
|
1705
|
-
let patternsLine = " Patterns: disabled";
|
|
1706
|
-
try {
|
|
1707
|
-
const { loadConfig: loadCfgPat } = await Promise.resolve().then(() => require("../config.js"));
|
|
1708
|
-
const cfgP = await loadCfgPat(cg.getProjectRoot());
|
|
1709
|
-
if (cfgP.enablePatterns) {
|
|
1710
|
-
const rawDbP = cg.getDatabase().getRawDb();
|
|
1711
|
-
const tableExistsP = rawDbP.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
1712
|
-
if (tableExistsP) {
|
|
1713
|
-
const matchCount = rawDbP.get("SELECT COUNT(*) as cnt FROM pattern_matches")?.cnt ?? 0;
|
|
1714
|
-
const fileCount = rawDbP.get("SELECT COUNT(DISTINCT file_path) as cnt FROM pattern_matches")?.cnt ?? 0;
|
|
1715
|
-
const ruleCount = rawDbP.get("SELECT COUNT(DISTINCT pattern_id) as cnt FROM pattern_matches")?.cnt ?? 0;
|
|
1716
|
-
patternsLine = ` Patterns: enabled \u2014 ${matchCount} matches, ${ruleCount} rules triggered, ${fileCount} files`;
|
|
1717
|
-
} else {
|
|
1718
|
-
patternsLine = ` Patterns: enabled (no data yet \u2014 run kirograph index)`;
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
} catch {
|
|
1722
|
-
}
|
|
1723
|
-
const threshold = stats.syncWarningThreshold ?? 10;
|
|
1724
|
-
const pendingFiles = stats.pendingFiles ?? 0;
|
|
1725
|
-
const syncRunning = stats.syncRunning ?? false;
|
|
1726
|
-
const syncLines = [];
|
|
1727
|
-
if (syncRunning) {
|
|
1728
|
-
syncLines.push(` \u26A0 Sync is currently running in the background.`);
|
|
1729
|
-
}
|
|
1730
|
-
if (threshold > 0 && pendingFiles >= threshold) {
|
|
1731
|
-
syncLines.push(
|
|
1732
|
-
` \u26A0 Index may be incomplete \u2014 ${pendingFiles} file${pendingFiles !== 1 ? "s" : ""} pending sync.` + (syncRunning ? " Sync is running in background." : " Run `kirograph sync` to update.") + ` Would you like to wait before proceeding?`
|
|
1733
|
-
);
|
|
1734
|
-
}
|
|
1735
|
-
const tracker = new import_tracker.TokenTracker(cg.getProjectRoot());
|
|
1736
|
-
const gainStats = tracker.getStats("session");
|
|
1737
|
-
const gainLines = [];
|
|
1738
|
-
if (gainStats.totalCommands > 0) {
|
|
1739
|
-
gainLines.push(` Compression: ${gainStats.totalCommands} commands, ${gainStats.savingsPercent}% avg savings (${gainStats.totalSaved.toLocaleString()} tokens saved this session)`);
|
|
1740
|
-
}
|
|
1741
|
-
return [
|
|
1742
|
-
`KiroGraph Status`,
|
|
1743
|
-
` Project: ${cg.getProjectRoot()}`,
|
|
1744
|
-
` Files indexed: ${stats.files}`,
|
|
1745
|
-
` Symbols: ${stats.nodes}`,
|
|
1746
|
-
` Relationships: ${stats.edges}`,
|
|
1747
|
-
` By kind: ${Object.entries(stats.nodesByKind).map(([k, v]) => `${k}=${v}`).join(", ")}`,
|
|
1748
|
-
langLine ? ` By language: ${langLine}` : "",
|
|
1749
|
-
frameworkLine,
|
|
1750
|
-
archLine,
|
|
1751
|
-
docsLine,
|
|
1752
|
-
dataLine,
|
|
1753
|
-
securityLine,
|
|
1754
|
-
patternsLine,
|
|
1755
|
-
` DB size: ${dbMb} MB`,
|
|
1756
|
-
...semanticLines,
|
|
1757
|
-
...syncLines,
|
|
1758
|
-
...gainLines
|
|
1759
|
-
].filter(Boolean).join("\n");
|
|
1760
|
-
}
|
|
1761
|
-
case "kirograph_files": {
|
|
1762
|
-
let renderTree2 = function(nodes, prefix) {
|
|
1763
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
1764
|
-
const node = nodes[i];
|
|
1765
|
-
const isLast = i === nodes.length - 1;
|
|
1766
|
-
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
1767
|
-
const childPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
1768
|
-
const meta = includeMetadata && node.type === "file" && node.language ? ` [${node.language}${node.symbolCount ? ` \xB7 ${node.symbolCount} symbols` : ""}]` : "";
|
|
1769
|
-
lines.push(`${prefix}${connector}${node.name}${meta}`);
|
|
1770
|
-
if (node.children?.length) renderTree2(node.children, childPrefix);
|
|
1771
|
-
}
|
|
1772
|
-
};
|
|
1773
|
-
var renderTree = renderTree2;
|
|
1774
|
-
const format = args.format ?? "tree";
|
|
1775
|
-
const includeMetadata = args.includeMetadata !== false;
|
|
1776
|
-
const tree = cg.getFiles({
|
|
1777
|
-
filterPath: args.filterPath,
|
|
1778
|
-
pattern: args.pattern,
|
|
1779
|
-
maxDepth: args.maxDepth
|
|
1780
|
-
});
|
|
1781
|
-
if (format === "flat") {
|
|
1782
|
-
let flattenTree2 = function(nodes) {
|
|
1783
|
-
for (const node of nodes) {
|
|
1784
|
-
if (node.type === "file") {
|
|
1785
|
-
const meta = includeMetadata && node.language ? ` [${node.language}${node.symbolCount ? ` \xB7 ${node.symbolCount}` : ""}]` : "";
|
|
1786
|
-
flat.push(`${node.path}${meta}`);
|
|
1787
|
-
}
|
|
1788
|
-
if (node.children?.length) flattenTree2(node.children);
|
|
1789
|
-
}
|
|
1790
|
-
};
|
|
1791
|
-
var flattenTree = flattenTree2;
|
|
1792
|
-
const flat = [];
|
|
1793
|
-
flattenTree2(tree);
|
|
1794
|
-
return flat.length > 0 ? flat.join("\n") : "No indexed files found.";
|
|
1795
|
-
}
|
|
1796
|
-
if (format === "grouped") {
|
|
1797
|
-
let groupTree2 = function(nodes) {
|
|
1798
|
-
for (const node of nodes) {
|
|
1799
|
-
if (node.type === "file") {
|
|
1800
|
-
const dir = node.path.includes("/") ? node.path.slice(0, node.path.lastIndexOf("/")) : ".";
|
|
1801
|
-
if (!groups.has(dir)) groups.set(dir, []);
|
|
1802
|
-
groups.get(dir).push(node);
|
|
1803
|
-
}
|
|
1804
|
-
if (node.children?.length) groupTree2(node.children);
|
|
1805
|
-
}
|
|
1806
|
-
};
|
|
1807
|
-
var groupTree = groupTree2;
|
|
1808
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1809
|
-
groupTree2(tree);
|
|
1810
|
-
const lines2 = [];
|
|
1811
|
-
for (const [dir, files] of [...groups.entries()].sort()) {
|
|
1812
|
-
lines2.push(`${dir}/`);
|
|
1813
|
-
for (const f of files) {
|
|
1814
|
-
const meta = includeMetadata && f.language ? ` [${f.language}${f.symbolCount ? ` \xB7 ${f.symbolCount}` : ""}]` : "";
|
|
1815
|
-
lines2.push(` ${f.name}${meta}`);
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
return lines2.length > 0 ? lines2.join("\n") : "No indexed files found.";
|
|
1819
|
-
}
|
|
1820
|
-
if (format === "compact") {
|
|
1821
|
-
let compactTree2 = function(nodes) {
|
|
1822
|
-
for (const node of nodes) {
|
|
1823
|
-
if (node.type === "file") {
|
|
1824
|
-
const dir = node.path.includes("/") ? node.path.slice(0, node.path.lastIndexOf("/")) : ".";
|
|
1825
|
-
const stat = dirStats.get(dir) || { files: 0, symbols: 0, langs: /* @__PURE__ */ new Map() };
|
|
1826
|
-
stat.files++;
|
|
1827
|
-
stat.symbols += node.symbolCount || 0;
|
|
1828
|
-
if (node.language) stat.langs.set(node.language, (stat.langs.get(node.language) || 0) + 1);
|
|
1829
|
-
dirStats.set(dir, stat);
|
|
1830
|
-
}
|
|
1831
|
-
if (node.children?.length) compactTree2(node.children);
|
|
1832
|
-
}
|
|
1833
|
-
};
|
|
1834
|
-
var compactTree = compactTree2;
|
|
1835
|
-
const dirStats = /* @__PURE__ */ new Map();
|
|
1836
|
-
compactTree2(tree);
|
|
1837
|
-
const totalFiles = [...dirStats.values()].reduce((s, d) => s + d.files, 0);
|
|
1838
|
-
const totalSymbols = [...dirStats.values()].reduce((s, d) => s + d.symbols, 0);
|
|
1839
|
-
const lines2 = [`${totalFiles} files, ${totalSymbols} symbols in ${dirStats.size} directories:
|
|
1840
|
-
`];
|
|
1841
|
-
for (const [dir, stat] of [...dirStats.entries()].sort()) {
|
|
1842
|
-
const langSummary = [...stat.langs.entries()].map(([l, c]) => `${l}:${c}`).join(" ");
|
|
1843
|
-
lines2.push(`${dir}/ (${stat.files} files, ${stat.symbols} symbols) ${langSummary}`);
|
|
1844
|
-
}
|
|
1845
|
-
return lines2.join("\n");
|
|
1846
|
-
}
|
|
1847
|
-
const lines = [];
|
|
1848
|
-
renderTree2(tree, "");
|
|
1849
|
-
return lines.length > 0 ? lines.join("\n") : "No indexed files found.";
|
|
1850
|
-
}
|
|
1851
|
-
case "kirograph_dead_code": {
|
|
1852
|
-
const limit = clampLimit(args.limit, 50);
|
|
1853
|
-
const dead = cg.findDeadCode(limit);
|
|
1854
|
-
if (dead.length === 0) return "No dead code detected.";
|
|
1855
|
-
return `Potential dead code (${dead.length} unexported symbols with no incoming references):
|
|
1856
|
-
` + dead.map((n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`).join("\n");
|
|
1857
|
-
}
|
|
1858
|
-
case "kirograph_circular_deps": {
|
|
1859
|
-
const cycles = cg.findCircularDependencies();
|
|
1860
|
-
if (cycles.length === 0) return "No circular dependencies found.";
|
|
1861
|
-
return `Found ${cycles.length} circular dependency cycle(s):
|
|
1862
|
-
` + cycles.map((cycle, i) => `Cycle ${i + 1}: ${cycle.join(" \u2192 ")}`).join("\n");
|
|
1863
|
-
}
|
|
1864
|
-
case "kirograph_path": {
|
|
1865
|
-
const fromResults = cg.searchNodes(args.from, void 0, 3);
|
|
1866
|
-
const toResults = cg.searchNodes(args.to, void 0, 3);
|
|
1867
|
-
if (fromResults.length === 0) return `Symbol "${args.from}" not found in index.`;
|
|
1868
|
-
if (toResults.length === 0) return `Symbol "${args.to}" not found in index.`;
|
|
1869
|
-
const fromNode = fromResults[0].node;
|
|
1870
|
-
const toNode = toResults[0].node;
|
|
1871
|
-
const pathNodes = await cg.findPath(fromNode.id, toNode.id);
|
|
1872
|
-
if (pathNodes.length === 0) return `No path found between \`${fromNode.name}\` and \`${toNode.name}\`.`;
|
|
1873
|
-
return `Path from \`${fromNode.name}\` to \`${toNode.name}\` (${pathNodes.length} nodes):
|
|
1874
|
-
` + pathNodes.map((n, i) => `${i + 1}. ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`).join("\n");
|
|
1875
|
-
}
|
|
1876
|
-
case "kirograph_type_hierarchy": {
|
|
1877
|
-
const results = cg.searchNodes(args.symbol, void 0, 5);
|
|
1878
|
-
if (results.length === 0) return `Symbol "${args.symbol}" not found in index.`;
|
|
1879
|
-
const node = results[0].node;
|
|
1880
|
-
const direction = args.direction ?? "both";
|
|
1881
|
-
const hierarchy = cg.getTypeHierarchy(node.id, direction);
|
|
1882
|
-
if (hierarchy.length === 0) return `No type hierarchy found for \`${node.name}\`.`;
|
|
1883
|
-
return `Type hierarchy for \`${node.name}\` (direction: ${direction}):
|
|
1884
|
-
` + hierarchy.map((n) => `- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}:${n.startLine}`).join("\n");
|
|
1885
|
-
}
|
|
1886
|
-
case "kirograph_architecture": {
|
|
1887
|
-
if (!cg.isArchitectureEnabled()) {
|
|
1888
|
-
return "Architecture analysis is disabled. Set enableArchitecture=true in .kirograph/config.json and re-index.";
|
|
1889
|
-
}
|
|
1890
|
-
const level = args.level ?? "both";
|
|
1891
|
-
const includeFiles = args.includeFiles === true;
|
|
1892
|
-
const arch = cg.getArchitecture();
|
|
1893
|
-
const lines = ["# Architecture"];
|
|
1894
|
-
if ((level === "packages" || level === "both") && arch.packages.length > 0) {
|
|
1895
|
-
lines.push("\n## Packages");
|
|
1896
|
-
for (const pkg of arch.packages) {
|
|
1897
|
-
const meta = [pkg.language, pkg.version].filter(Boolean).join(", ");
|
|
1898
|
-
lines.push(`- **${pkg.name}** (${pkg.path}) [${pkg.source}${meta ? " \xB7 " + meta : ""}]`);
|
|
1899
|
-
}
|
|
1900
|
-
if (arch.packageDeps.length > 0) {
|
|
1901
|
-
lines.push("\n## Package Dependencies");
|
|
1902
|
-
for (const dep of arch.packageDeps) {
|
|
1903
|
-
const src = arch.packages.find((p) => p.id === dep.sourcePkg)?.name ?? dep.sourcePkg;
|
|
1904
|
-
const tgt = arch.packages.find((p) => p.id === dep.targetPkg)?.name ?? dep.targetPkg;
|
|
1905
|
-
lines.push(`- ${src} \u2192 ${tgt} (${dep.depCount} import${dep.depCount !== 1 ? "s" : ""})`);
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
if ((level === "layers" || level === "both") && arch.layers.length > 0) {
|
|
1910
|
-
lines.push("\n## Layers");
|
|
1911
|
-
for (const layer of arch.layers) {
|
|
1912
|
-
const fileCount = Object.values(arch.fileLayers).filter((fl) => fl.some((l) => l.layerId === layer.id)).length;
|
|
1913
|
-
lines.push(`- **${layer.name}** [${layer.source}] \u2014 ${fileCount} file${fileCount !== 1 ? "s" : ""}`);
|
|
1914
|
-
}
|
|
1915
|
-
if (arch.layerDeps.length > 0) {
|
|
1916
|
-
lines.push("\n## Layer Dependencies");
|
|
1917
|
-
for (const dep of arch.layerDeps) {
|
|
1918
|
-
const src = dep.sourceLayer.replace("layer:", "");
|
|
1919
|
-
const tgt = dep.targetLayer.replace("layer:", "");
|
|
1920
|
-
lines.push(`- ${src} \u2192 ${tgt} (${dep.depCount})`);
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
if (includeFiles && (level === "packages" || level === "both")) {
|
|
1925
|
-
lines.push("\n## File \u2192 Package");
|
|
1926
|
-
for (const [file, pkgIds] of Object.entries(arch.filePackages).slice(0, 50)) {
|
|
1927
|
-
const names = pkgIds.map((id) => arch.packages.find((p) => p.id === id)?.name ?? id).join(", ");
|
|
1928
|
-
lines.push(`- ${file}: ${names}`);
|
|
1929
|
-
}
|
|
1930
|
-
if (Object.keys(arch.filePackages).length > 50) lines.push(" \u2026(truncated)");
|
|
1931
|
-
}
|
|
1932
|
-
if (arch.packages.length === 0 && arch.layers.length === 0) {
|
|
1933
|
-
return "No architecture data found. Run `kirograph index` with enableArchitecture=true.";
|
|
1934
|
-
}
|
|
1935
|
-
return lines.join("\n");
|
|
1936
|
-
}
|
|
1937
|
-
case "kirograph_coupling": {
|
|
1938
|
-
if (!cg.isArchitectureEnabled()) {
|
|
1939
|
-
return "Architecture analysis is disabled. Set enableArchitecture=true in .kirograph/config.json and re-index.";
|
|
1940
|
-
}
|
|
1941
|
-
const sortBy = args.sortBy ?? "instability";
|
|
1942
|
-
const limit = clampLimit(args.limit, 20);
|
|
1943
|
-
const arch = cg.getArchitecture();
|
|
1944
|
-
if (arch.coupling.length === 0) {
|
|
1945
|
-
return "No coupling data. Run `kirograph index` with enableArchitecture=true.";
|
|
1946
|
-
}
|
|
1947
|
-
const sorted = [...arch.coupling].sort((a, b) => {
|
|
1948
|
-
if (sortBy === "afferent") return b.afferent - a.afferent;
|
|
1949
|
-
if (sortBy === "efferent") return b.efferent - a.efferent;
|
|
1950
|
-
return b.instability - a.instability;
|
|
1951
|
-
}).slice(0, limit);
|
|
1952
|
-
const lines = [
|
|
1953
|
-
`Coupling Metrics (sorted by ${sortBy}):`,
|
|
1954
|
-
"",
|
|
1955
|
-
"Package Ca Ce I",
|
|
1956
|
-
"\u2500".repeat(52)
|
|
1957
|
-
];
|
|
1958
|
-
for (const c of sorted) {
|
|
1959
|
-
const pkg = arch.packages.find((p) => p.id === c.packageId);
|
|
1960
|
-
const name = (pkg?.name ?? c.packageId).slice(0, 32).padEnd(32);
|
|
1961
|
-
const ca = String(c.afferent).padStart(4);
|
|
1962
|
-
const ce = String(c.efferent).padStart(4);
|
|
1963
|
-
const inst = c.instability.toFixed(2).padStart(5);
|
|
1964
|
-
lines.push(`${name} ${ca} ${ce} ${inst}`);
|
|
1965
|
-
}
|
|
1966
|
-
lines.push("", "Ca=afferent (depended on by), Ce=efferent (depends on), I=instability (Ce/(Ca+Ce))");
|
|
1967
|
-
return lines.join("\n");
|
|
1968
|
-
}
|
|
1969
|
-
case "kirograph_package": {
|
|
1970
|
-
if (!cg.isArchitectureEnabled()) {
|
|
1971
|
-
return "Architecture analysis is disabled. Set enableArchitecture=true in .kirograph/config.json and re-index.";
|
|
1972
|
-
}
|
|
1973
|
-
const query = args.package.toLowerCase();
|
|
1974
|
-
const includeFiles = args.includeFiles !== false;
|
|
1975
|
-
const arch = cg.getArchitecture();
|
|
1976
|
-
const pkg = arch.packages.find(
|
|
1977
|
-
(p) => p.name.toLowerCase().includes(query) || p.path.toLowerCase().includes(query) || p.id.toLowerCase().includes(query)
|
|
1978
|
-
);
|
|
1979
|
-
if (!pkg) return `Package "${args.package}" not found. Use kirograph_architecture to list all packages.`;
|
|
1980
|
-
const lines = [
|
|
1981
|
-
`## Package: ${pkg.name}`,
|
|
1982
|
-
`Path: ${pkg.path}`,
|
|
1983
|
-
`Source: ${pkg.source}${pkg.manifestPath ? ` (${pkg.manifestPath})` : ""}`,
|
|
1984
|
-
...pkg.version ? [`Version: ${pkg.version}`] : [],
|
|
1985
|
-
...pkg.language ? [`Language: ${pkg.language}`] : []
|
|
1986
|
-
];
|
|
1987
|
-
const deps = arch.packageDeps.filter((d) => d.sourcePkg === pkg.id);
|
|
1988
|
-
const dependents = arch.packageDeps.filter((d) => d.targetPkg === pkg.id);
|
|
1989
|
-
const coupling = arch.coupling.find((c) => c.packageId === pkg.id);
|
|
1990
|
-
if (coupling) {
|
|
1991
|
-
lines.push("", `Coupling: Ca=${coupling.afferent} Ce=${coupling.efferent} I=${coupling.instability.toFixed(2)}`);
|
|
1992
|
-
}
|
|
1993
|
-
if (deps.length > 0) {
|
|
1994
|
-
lines.push("", `Depends on (${deps.length}):`);
|
|
1995
|
-
for (const dep of deps) {
|
|
1996
|
-
const name = arch.packages.find((p) => p.id === dep.targetPkg)?.name ?? dep.targetPkg;
|
|
1997
|
-
lines.push(` \u2192 ${name} (${dep.depCount} import${dep.depCount !== 1 ? "s" : ""})`);
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
if (dependents.length > 0) {
|
|
2001
|
-
lines.push("", `Depended on by (${dependents.length}):`);
|
|
2002
|
-
for (const dep of dependents) {
|
|
2003
|
-
const name = arch.packages.find((p) => p.id === dep.sourcePkg)?.name ?? dep.sourcePkg;
|
|
2004
|
-
lines.push(` \u2190 ${name} (${dep.depCount} import${dep.depCount !== 1 ? "s" : ""})`);
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
if (pkg.externalDeps && pkg.externalDeps.length > 0) {
|
|
2008
|
-
lines.push("", `External deps (${pkg.externalDeps.length}): ${pkg.externalDeps.slice(0, 10).join(", ")}${pkg.externalDeps.length > 10 ? "\u2026" : ""}`);
|
|
2009
|
-
}
|
|
2010
|
-
if (includeFiles) {
|
|
2011
|
-
const files = Object.entries(arch.filePackages).filter(([, ids]) => ids.includes(pkg.id)).map(([f]) => f).sort();
|
|
2012
|
-
if (files.length > 0) {
|
|
2013
|
-
lines.push("", `Files (${files.length}):`);
|
|
2014
|
-
for (const f of files.slice(0, 30)) lines.push(` ${f}`);
|
|
2015
|
-
if (files.length > 30) lines.push(` \u2026and ${files.length - 30} more`);
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
return lines.join("\n");
|
|
2019
|
-
}
|
|
2020
|
-
case "kirograph_hotspots": {
|
|
2021
|
-
const limit = clampLimit(args.limit, 20);
|
|
2022
|
-
const hotspots = cg.findHotspots(limit);
|
|
2023
|
-
if (hotspots.length === 0) return "No symbols found in index.";
|
|
2024
|
-
const lines = [`Top ${hotspots.length} most-connected symbols (by edge degree):
|
|
2025
|
-
`];
|
|
2026
|
-
for (const n of hotspots) {
|
|
2027
|
-
lines.push(`${mapKind(n.kind)} \`${n.name}\` \u2014 degree ${n.degree} (in: ${n.inDegree}, out: ${n.outDegree})`);
|
|
2028
|
-
lines.push(` File: ${n.filePath}:${n.startLine}`);
|
|
2029
|
-
}
|
|
2030
|
-
return lines.join("\n");
|
|
2031
|
-
}
|
|
2032
|
-
case "kirograph_surprising": {
|
|
2033
|
-
const limit = clampLimit(args.limit, 20);
|
|
2034
|
-
const connections = cg.findSurprisingConnections(limit);
|
|
2035
|
-
if (connections.length === 0) return "No surprising cross-file connections found.";
|
|
2036
|
-
const lines = [`Top ${connections.length} surprising cross-file connections:
|
|
2037
|
-
`];
|
|
2038
|
-
for (const c of connections) {
|
|
2039
|
-
lines.push(`${mapKind(c.source.kind)} \`${c.source.name}\` ${c.kind}\u2192 ${mapKind(c.target.kind)} \`${c.target.name}\` (score: ${c.score.toFixed(2)})`);
|
|
2040
|
-
lines.push(` ${c.source.filePath} \u2192 ${c.target.filePath}`);
|
|
2041
|
-
}
|
|
2042
|
-
return lines.join("\n");
|
|
2043
|
-
}
|
|
2044
|
-
case "kirograph_diff": {
|
|
2045
|
-
const sm = cg.createSnapshotManager();
|
|
2046
|
-
const snapshot = args.snapshot ? sm.load(args.snapshot) : sm.loadLatest();
|
|
2047
|
-
if (!snapshot) {
|
|
2048
|
-
return args.snapshot ? `Snapshot "${args.snapshot}" not found. Use \`kirograph snapshot list\` to see available snapshots.` : "No snapshots found. Run `kirograph snapshot` to save one first.";
|
|
2049
|
-
}
|
|
2050
|
-
const diff = sm.diff(snapshot, sm.currentSnapshot());
|
|
2051
|
-
const fromDate = new Date(diff.from.timestamp).toISOString().slice(0, 19).replace("T", " ");
|
|
2052
|
-
const lines = [
|
|
2053
|
-
`Graph diff: "${diff.from.label}" (${fromDate}) \u2192 current`,
|
|
2054
|
-
``,
|
|
2055
|
-
`Symbols: +${diff.addedNodes.length} added, -${diff.removedNodes.length} removed`,
|
|
2056
|
-
`Edges: +${diff.addedEdges.length} added, -${diff.removedEdges.length} removed`
|
|
2057
|
-
];
|
|
2058
|
-
if (diff.addedNodes.length > 0) {
|
|
2059
|
-
lines.push(`
|
|
2060
|
-
## Added symbols (${diff.addedNodes.length})`);
|
|
2061
|
-
for (const n of diff.addedNodes.slice(0, 30)) {
|
|
2062
|
-
lines.push(`+ ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}`);
|
|
2063
|
-
}
|
|
2064
|
-
if (diff.addedNodes.length > 30) lines.push(` \u2026and ${diff.addedNodes.length - 30} more`);
|
|
2065
|
-
}
|
|
2066
|
-
if (diff.removedNodes.length > 0) {
|
|
2067
|
-
lines.push(`
|
|
2068
|
-
## Removed symbols (${diff.removedNodes.length})`);
|
|
2069
|
-
for (const n of diff.removedNodes.slice(0, 30)) {
|
|
2070
|
-
lines.push(`- ${mapKind(n.kind)} \`${n.name}\` \u2014 ${n.filePath}`);
|
|
2071
|
-
}
|
|
2072
|
-
if (diff.removedNodes.length > 30) lines.push(` \u2026and ${diff.removedNodes.length - 30} more`);
|
|
2073
|
-
}
|
|
2074
|
-
return lines.join("\n");
|
|
2075
|
-
}
|
|
2076
|
-
case "kirograph_flows": {
|
|
2077
|
-
const { getExecutionFlows, traceFlow, detectEntryPoints } = await Promise.resolve().then(() => require("../graph/flows.js"));
|
|
2078
|
-
const db = cg.getDatabase();
|
|
2079
|
-
if (args.entryPoint) {
|
|
2080
|
-
const results = cg.searchNodes(args.entryPoint, void 0, 5);
|
|
2081
|
-
if (results.length === 0) return `Symbol "${args.entryPoint}" not found in index.`;
|
|
2082
|
-
const hops = traceFlow(db, results[0].node.id, args.maxDepth ?? 10);
|
|
2083
|
-
if (hops.length < 2) return `No outgoing call chain found from "${args.entryPoint}".`;
|
|
2084
|
-
const lines2 = [`## Execution flow from \`${args.entryPoint}\``, ""];
|
|
2085
|
-
for (let i = 0; i < hops.length; i++) {
|
|
2086
|
-
const hop = hops[i];
|
|
2087
|
-
const indent = " ".repeat(i);
|
|
2088
|
-
const arrow = i === 0 ? "\u2192" : "\u21B3";
|
|
2089
|
-
const conf = hop.confidence && hop.confidence !== "extracted" ? ` [${hop.confidence}]` : "";
|
|
2090
|
-
lines2.push(`${indent}${arrow} ${hop.kind} \`${hop.symbol}\` \u2014 ${hop.filePath}:${hop.line}${conf}`);
|
|
2091
|
-
}
|
|
2092
|
-
return lines2.join("\n");
|
|
2093
|
-
}
|
|
2094
|
-
const flows = getExecutionFlows(db, {
|
|
2095
|
-
maxFlows: args.maxFlows ?? 10,
|
|
2096
|
-
maxDepth: args.maxDepth ?? 10
|
|
2097
|
-
});
|
|
2098
|
-
if (flows.length === 0) return "No execution flows detected. The graph may be too small or have no call edges.";
|
|
2099
|
-
const lines = [`## Execution Flows (${flows.length} detected)`, ""];
|
|
2100
|
-
for (const flow of flows) {
|
|
2101
|
-
lines.push(`### \`${flow.entryPoint}\` (${flow.entryPointKind}) \u2014 criticality: ${flow.criticality.toFixed(2)}`);
|
|
2102
|
-
lines.push(`File: ${flow.entryPointFile}`, "");
|
|
2103
|
-
for (let i = 0; i < flow.hops.length; i++) {
|
|
2104
|
-
const hop = flow.hops[i];
|
|
2105
|
-
const indent = " ".repeat(Math.min(i, 5));
|
|
2106
|
-
const arrow = i === 0 ? "\u2192" : "\u21B3";
|
|
2107
|
-
const conf = hop.confidence && hop.confidence !== "extracted" ? ` [${hop.confidence}]` : "";
|
|
2108
|
-
lines.push(`${indent}${arrow} \`${hop.symbol}\` (${hop.kind}) \u2014 ${hop.filePath}:${hop.line}${conf}`);
|
|
2109
|
-
}
|
|
2110
|
-
lines.push("");
|
|
2111
|
-
}
|
|
2112
|
-
return lines.join("\n");
|
|
2113
|
-
}
|
|
2114
|
-
case "kirograph_communities": {
|
|
2115
|
-
const { detectCommunities } = await Promise.resolve().then(() => require("../graph/communities.js"));
|
|
2116
|
-
const db = cg.getDatabase();
|
|
2117
|
-
const result = detectCommunities(db, {
|
|
2118
|
-
resolution: args.resolution ?? 1
|
|
2119
|
-
});
|
|
2120
|
-
if (result.communities.length === 0) return "No communities detected. The graph may be too small or have no edges.";
|
|
2121
|
-
const limit = Math.min(args.limit ?? 15, result.communities.length);
|
|
2122
|
-
const lines = [
|
|
2123
|
-
`## Communities (${result.communities.length} detected, modularity: ${result.modularity.toFixed(3)})`,
|
|
2124
|
-
`Graph: ${result.totalNodes} nodes, ${result.totalEdges} edges`,
|
|
2125
|
-
""
|
|
2126
|
-
];
|
|
2127
|
-
for (const c of result.communities.slice(0, limit)) {
|
|
2128
|
-
lines.push(`### ${c.label} (${c.memberCount} symbols)`);
|
|
2129
|
-
lines.push(`- Directory: \`${c.dominantDirectory}\``);
|
|
2130
|
-
lines.push(`- Language: ${c.dominantLanguage}`);
|
|
2131
|
-
lines.push(`- Inter-community edges: ${c.interCommunityEdges}`);
|
|
2132
|
-
lines.push(`- Top members:`);
|
|
2133
|
-
for (const m of c.members.slice(0, 8)) {
|
|
2134
|
-
lines.push(` - ${m.kind} \`${m.name}\` \u2014 ${m.filePath}`);
|
|
2135
|
-
}
|
|
2136
|
-
if (c.memberCount > 8) lines.push(` - \u2026and ${c.memberCount - 8} more`);
|
|
2137
|
-
lines.push("");
|
|
2138
|
-
}
|
|
2139
|
-
return lines.join("\n");
|
|
2140
|
-
}
|
|
2141
|
-
case "kirograph_refactor": {
|
|
2142
|
-
const { renamePreview, suggestRefactorings } = await Promise.resolve().then(() => require("../graph/refactor.js"));
|
|
2143
|
-
const db = cg.getDatabase();
|
|
2144
|
-
const mode = args.mode;
|
|
2145
|
-
if (mode === "rename") {
|
|
2146
|
-
if (!args.symbol) return 'Error: "symbol" parameter is required for rename mode.';
|
|
2147
|
-
const preview = renamePreview(db, args.symbol);
|
|
2148
|
-
if (!preview) return `Symbol "${args.symbol}" not found in index.`;
|
|
2149
|
-
const lines = [
|
|
2150
|
-
`## Rename Preview: \`${preview.symbol}\``,
|
|
2151
|
-
`Kind: ${preview.kind}`,
|
|
2152
|
-
`Defined at: ${preview.filePath}:${preview.line}`,
|
|
2153
|
-
`Total references: ${preview.totalReferences}`,
|
|
2154
|
-
""
|
|
2155
|
-
];
|
|
2156
|
-
if (preview.references.length === 0) {
|
|
2157
|
-
lines.push("No references found \u2014 this symbol can be safely renamed without affecting other code.");
|
|
2158
|
-
} else {
|
|
2159
|
-
const byFile = /* @__PURE__ */ new Map();
|
|
2160
|
-
for (const ref of preview.references) {
|
|
2161
|
-
if (!byFile.has(ref.filePath)) byFile.set(ref.filePath, []);
|
|
2162
|
-
byFile.get(ref.filePath).push(ref);
|
|
2163
|
-
}
|
|
2164
|
-
for (const [file, refs] of byFile) {
|
|
2165
|
-
lines.push(`### ${file} (${refs.length} references)`);
|
|
2166
|
-
for (const ref of refs.slice(0, 10)) {
|
|
2167
|
-
lines.push(`- Line ${ref.line}: \`${ref.context}\` (${ref.edgeKind})`);
|
|
2168
|
-
}
|
|
2169
|
-
if (refs.length > 10) lines.push(` \u2026and ${refs.length - 10} more`);
|
|
2170
|
-
lines.push("");
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2173
|
-
return lines.join("\n");
|
|
2174
|
-
}
|
|
2175
|
-
if (mode === "suggest") {
|
|
2176
|
-
const suggestions = suggestRefactorings(db, args.limit ?? 10);
|
|
2177
|
-
if (suggestions.length === 0) return "No refactoring suggestions \u2014 the codebase structure looks clean.";
|
|
2178
|
-
const lines = [`## Refactoring Suggestions (${suggestions.length})`, ""];
|
|
2179
|
-
for (const s of suggestions) {
|
|
2180
|
-
const icon = s.priority === "high" ? "\u{1F534}" : s.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
2181
|
-
lines.push(`${icon} **${s.type}** [${s.priority}]: ${s.description}`);
|
|
2182
|
-
lines.push(` Rationale: ${s.rationale}`);
|
|
2183
|
-
if (s.symbols.length > 0) {
|
|
2184
|
-
lines.push(` Symbols: ${s.symbols.slice(0, 3).join(", ")}`);
|
|
2185
|
-
}
|
|
2186
|
-
lines.push("");
|
|
2187
|
-
}
|
|
2188
|
-
return lines.join("\n");
|
|
2189
|
-
}
|
|
2190
|
-
return 'Unknown mode. Use "rename" or "suggest".';
|
|
2191
|
-
}
|
|
2192
|
-
// ── Memory tools ────────────────────────────────────────────────────────
|
|
2193
|
-
case "kirograph_mem_search": {
|
|
2194
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2195
|
-
const projectRoot = cg.getProjectRoot();
|
|
2196
|
-
const config = await loadConfig(projectRoot);
|
|
2197
|
-
if (!config.enableMemory) return "Memory is not enabled. Set enableMemory: true in .kirograph/config.json";
|
|
2198
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2199
|
-
const db = cg.getDatabase();
|
|
2200
|
-
db.applyMemorySchema();
|
|
2201
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2202
|
-
mem.initialize();
|
|
2203
|
-
const results = await mem.search(args.query, {
|
|
2204
|
-
limit: args.limit ?? 10,
|
|
2205
|
-
kind: args.kind,
|
|
2206
|
-
sessionId: args.sessionId,
|
|
2207
|
-
asOf: args.asOf
|
|
2208
|
-
});
|
|
2209
|
-
if (results.length === 0) return `No memory observations found for "${args.query}".`;
|
|
2210
|
-
return results.map((r, i) => {
|
|
2211
|
-
const age = formatAge(r.observation.createdAt);
|
|
2212
|
-
const lines = [`${i + 1}. [${r.observation.kind}] ${r.observation.content} (${age})`];
|
|
2213
|
-
if (r.relations && r.relations.length > 0) {
|
|
2214
|
-
for (const rel of r.relations) {
|
|
2215
|
-
const other = rel.observationA === r.observation.id ? rel.observationB : rel.observationA;
|
|
2216
|
-
const icon = rel.relation === "conflicts_with" ? "\u26A1" : rel.relation === "supersedes" ? "\u21A9" : "~";
|
|
2217
|
-
lines.push(` ${icon} ${rel.relation} ${other} (confidence: ${rel.confidence})${rel.judgmentStatus === "judged" ? " (judged)" : ""}`);
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
return lines.join("\n");
|
|
2221
|
-
}).join("\n");
|
|
2222
|
-
}
|
|
2223
|
-
case "kirograph_mem_store": {
|
|
2224
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2225
|
-
const projectRoot = cg.getProjectRoot();
|
|
2226
|
-
const config = await loadConfig(projectRoot);
|
|
2227
|
-
if (!config.enableMemory) return "Memory is not enabled. Set enableMemory: true in .kirograph/config.json";
|
|
2228
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2229
|
-
const db = cg.getDatabase();
|
|
2230
|
-
db.applyMemorySchema();
|
|
2231
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2232
|
-
mem.initialize();
|
|
2233
|
-
const id = await mem.store({
|
|
2234
|
-
content: args.content,
|
|
2235
|
-
kind: args.kind ?? "note",
|
|
2236
|
-
source: "agent",
|
|
2237
|
-
topicKey: args.topicKey,
|
|
2238
|
-
reviewAfter: args.reviewAfter
|
|
2239
|
-
});
|
|
2240
|
-
if (!id) return "Observation already exists (duplicate content).";
|
|
2241
|
-
return `Stored observation ${id} [${args.kind ?? "note"}]`;
|
|
2242
|
-
}
|
|
2243
|
-
case "kirograph_mem_timeline": {
|
|
2244
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2245
|
-
const projectRoot = cg.getProjectRoot();
|
|
2246
|
-
const config = await loadConfig(projectRoot);
|
|
2247
|
-
if (!config.enableMemory) return "Memory is not enabled. Set enableMemory: true in .kirograph/config.json";
|
|
2248
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2249
|
-
const db = cg.getDatabase();
|
|
2250
|
-
db.applyMemorySchema();
|
|
2251
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2252
|
-
mem.initialize();
|
|
2253
|
-
const { sessions, observations } = mem.timeline({
|
|
2254
|
-
limit: args.limit ?? 5,
|
|
2255
|
-
sessionId: args.sessionId
|
|
2256
|
-
});
|
|
2257
|
-
if (sessions.length === 0) return "No memory sessions found.";
|
|
2258
|
-
const lines = [];
|
|
2259
|
-
for (const session of sessions) {
|
|
2260
|
-
const start = new Date(session.startedAt).toISOString().slice(0, 16).replace("T", " ");
|
|
2261
|
-
const status = session.endedAt ? "ended" : "active";
|
|
2262
|
-
const obs = observations.get(session.id) ?? [];
|
|
2263
|
-
lines.push(`## ${start} [${session.ide ?? "unknown"}] (${status}, ${obs.length} observations)`);
|
|
2264
|
-
for (const o of obs.slice(0, 5)) {
|
|
2265
|
-
lines.push(` - [${o.kind}] ${o.content.slice(0, 120)}`);
|
|
2266
|
-
}
|
|
2267
|
-
if (obs.length > 5) lines.push(` \u2026and ${obs.length - 5} more`);
|
|
2268
|
-
}
|
|
2269
|
-
return lines.join("\n");
|
|
2270
|
-
}
|
|
2271
|
-
case "kirograph_mem_status": {
|
|
2272
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2273
|
-
const projectRoot = cg.getProjectRoot();
|
|
2274
|
-
const config = await loadConfig(projectRoot);
|
|
2275
|
-
if (!config.enableMemory) return "Memory is not enabled. Set enableMemory: true in .kirograph/config.json";
|
|
2276
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2277
|
-
const db = cg.getDatabase();
|
|
2278
|
-
db.applyMemorySchema();
|
|
2279
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2280
|
-
mem.initialize();
|
|
2281
|
-
const stats = mem.getStats();
|
|
2282
|
-
const lines = [
|
|
2283
|
-
"KiroGraph Memory Status",
|
|
2284
|
-
` Sessions: ${stats.sessions} (${stats.activeSessions} active)`,
|
|
2285
|
-
` Observations: ${stats.observations}`,
|
|
2286
|
-
` Symbol links: ${stats.links}`,
|
|
2287
|
-
` Embeddings: ${stats.vectors} / ${stats.embeddableCount}`,
|
|
2288
|
-
` Model mismatch: ${stats.modelMismatch ? "\u26A0 yes \u2014 run kirograph mem reembed" : "no"}`,
|
|
2289
|
-
` Relations: ${stats.relations} (${stats.pendingConflicts} pending)`,
|
|
2290
|
-
` Caveman compression: ${config.cavemanMode !== "off" ? config.cavemanMode : "off (storing raw)"}`
|
|
2291
|
-
];
|
|
2292
|
-
return lines.join("\n");
|
|
2293
|
-
}
|
|
2294
|
-
case "kirograph_mem_review": {
|
|
2295
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2296
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2297
|
-
const config = await loadConfig(projectRoot);
|
|
2298
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2299
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2300
|
-
const db = cg.getDatabase();
|
|
2301
|
-
db.applyMemorySchema();
|
|
2302
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2303
|
-
mem.initialize();
|
|
2304
|
-
const obs = mem.getObservationsForReview(args.limit ?? 20);
|
|
2305
|
-
if (obs.length === 0) return "No overdue observations \u2014 all review_after dates are in the future.";
|
|
2306
|
-
const now = Date.now();
|
|
2307
|
-
return obs.map((o, i) => {
|
|
2308
|
-
const daysOverdue = Math.floor((now - (o.reviewAfter ?? now)) / 864e5);
|
|
2309
|
-
return `${i + 1}. [${o.kind}]${o.topicKey ? ` (${o.topicKey})` : ""} ${o.content.slice(0, 120)} \u2014 overdue by ${daysOverdue}d`;
|
|
2310
|
-
}).join("\n");
|
|
2311
|
-
}
|
|
2312
|
-
case "kirograph_mem_mark_reviewed": {
|
|
2313
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2314
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2315
|
-
const config = await loadConfig(projectRoot);
|
|
2316
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2317
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2318
|
-
const db = cg.getDatabase();
|
|
2319
|
-
db.applyMemorySchema();
|
|
2320
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2321
|
-
mem.initialize();
|
|
2322
|
-
mem.markReviewed(args.id);
|
|
2323
|
-
return `Observation ${args.id} marked as reviewed.`;
|
|
2324
|
-
}
|
|
2325
|
-
case "kirograph_mem_compare": {
|
|
2326
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2327
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2328
|
-
const config = await loadConfig(projectRoot);
|
|
2329
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2330
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2331
|
-
const db = cg.getDatabase();
|
|
2332
|
-
db.applyMemorySchema();
|
|
2333
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2334
|
-
mem.initialize();
|
|
2335
|
-
const relationId = mem.compareObservations({
|
|
2336
|
-
observationA: args.observationA,
|
|
2337
|
-
observationB: args.observationB,
|
|
2338
|
-
relation: args.relation,
|
|
2339
|
-
confidence: args.confidence ?? 1,
|
|
2340
|
-
reason: args.reason,
|
|
2341
|
-
evidence: args.evidence
|
|
2342
|
-
});
|
|
2343
|
-
return `Relation ${relationId} created (${args.relation}, confidence: ${args.confidence ?? 1}). Use kirograph_mem_judge to finalize.`;
|
|
2344
|
-
}
|
|
2345
|
-
case "kirograph_mem_judge": {
|
|
2346
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2347
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2348
|
-
const config = await loadConfig(projectRoot);
|
|
2349
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2350
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2351
|
-
const db = cg.getDatabase();
|
|
2352
|
-
db.applyMemorySchema();
|
|
2353
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2354
|
-
mem.initialize();
|
|
2355
|
-
mem.judgeRelation(
|
|
2356
|
-
args.relationId,
|
|
2357
|
-
args.relation,
|
|
2358
|
-
args.confidence,
|
|
2359
|
-
args.reason,
|
|
2360
|
-
args.evidence
|
|
2361
|
-
);
|
|
2362
|
-
return `Relation ${args.relationId} judged as [${args.relation}] (confidence: ${args.confidence}).`;
|
|
2363
|
-
}
|
|
2364
|
-
case "kirograph_mem_capture": {
|
|
2365
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2366
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2367
|
-
const config = await loadConfig(projectRoot);
|
|
2368
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2369
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2370
|
-
const db = cg.getDatabase();
|
|
2371
|
-
db.applyMemorySchema();
|
|
2372
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2373
|
-
mem.initialize();
|
|
2374
|
-
const extracted = mem.capturePassive(args.content);
|
|
2375
|
-
if (extracted.length === 0) return "No structured sections found. Use ## Key Learnings, ## Observations, ## Decisions, or ## Key Changes headings.";
|
|
2376
|
-
return `Captured ${extracted.length} observation(s):
|
|
2377
|
-
` + extracted.map((e, i) => `${i + 1}. [${e.kind}] ${e.content.slice(0, 100)}${e.id ? "" : " (duplicate)"}`).join("\n");
|
|
2378
|
-
}
|
|
2379
|
-
case "kirograph_mem_save_prompt": {
|
|
2380
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2381
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2382
|
-
const config = await loadConfig(projectRoot);
|
|
2383
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2384
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2385
|
-
const db = cg.getDatabase();
|
|
2386
|
-
db.applyMemorySchema();
|
|
2387
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2388
|
-
mem.initialize();
|
|
2389
|
-
const id = await mem.savePrompt(args.content);
|
|
2390
|
-
return `Prompt saved as ${id}.`;
|
|
2391
|
-
}
|
|
2392
|
-
case "kirograph_mem_suggest_topic_key": {
|
|
2393
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2394
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2395
|
-
const config = await loadConfig(projectRoot);
|
|
2396
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2397
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2398
|
-
const db = cg.getDatabase();
|
|
2399
|
-
db.applyMemorySchema();
|
|
2400
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2401
|
-
mem.initialize();
|
|
2402
|
-
const key = mem.suggestTopicKey(args.kind, args.title);
|
|
2403
|
-
return `Suggested topic_key: ${key}`;
|
|
2404
|
-
}
|
|
2405
|
-
case "kirograph_mem_conflicts_scan": {
|
|
2406
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2407
|
-
const projectRoot = args.projectPath ?? cg.getProjectRoot();
|
|
2408
|
-
const config = await loadConfig(projectRoot);
|
|
2409
|
-
if (!config.enableMemory) return "Memory is not enabled.";
|
|
2410
|
-
const { MemoryManager } = await Promise.resolve().then(() => require("../memory/index.js"));
|
|
2411
|
-
const db = cg.getDatabase();
|
|
2412
|
-
db.applyMemorySchema();
|
|
2413
|
-
const mem = new MemoryManager(config, db.getRawDb());
|
|
2414
|
-
mem.initialize();
|
|
2415
|
-
const candidates = mem.scanConflicts(args.limit ?? 50);
|
|
2416
|
-
if (candidates.length === 0) return "No potential conflicts found among recent observations.";
|
|
2417
|
-
return `Found ${candidates.length} potential conflict(s):
|
|
2418
|
-
` + candidates.map(
|
|
2419
|
-
(c, i) => `${i + 1}. [${c.observationA.kind}] "${c.observationA.content.slice(0, 80)}" \u2194 [${c.observationB.kind}] "${c.observationB.content.slice(0, 80)}" (similarity: ${c.similarity.toFixed(2)})`
|
|
2420
|
-
).join("\n") + "\n\nUse kirograph_mem_compare to establish relations.";
|
|
2421
|
-
}
|
|
2422
|
-
// ── Docs tools ────────────────────────────────────────────────────────────
|
|
2423
|
-
case "kirograph_docs_toc": {
|
|
2424
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2425
|
-
const projectRoot = cg.getProjectRoot();
|
|
2426
|
-
const config = await loadConfig(projectRoot);
|
|
2427
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2428
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2429
|
-
const db = cg.getDatabase();
|
|
2430
|
-
db.applyDocsSchema();
|
|
2431
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
2432
|
-
const toc = docs.getToc({ file: args.file, tree: args.tree });
|
|
2433
|
-
if (toc.length === 0) return args.file ? `No sections found in "${args.file}".` : "No documentation indexed. Run kirograph index.";
|
|
2434
|
-
const lines = [];
|
|
2435
|
-
const renderEntry = (entry, indent) => {
|
|
2436
|
-
const prefix = "#".repeat(entry.level || 1);
|
|
2437
|
-
const summary = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
2438
|
-
lines.push(`${indent}${prefix} ${entry.title}${summary}`);
|
|
2439
|
-
lines.push(`${indent} ID: ${entry.id}`);
|
|
2440
|
-
if (entry.children?.length) {
|
|
2441
|
-
for (const child of entry.children) renderEntry(child, indent + " ");
|
|
2442
|
-
}
|
|
2443
|
-
};
|
|
2444
|
-
if (args.tree) {
|
|
2445
|
-
for (const entry of toc) renderEntry(entry, "");
|
|
2446
|
-
} else {
|
|
2447
|
-
for (const entry of toc) {
|
|
2448
|
-
const prefix = "#".repeat(entry.level || 1);
|
|
2449
|
-
const summary = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
2450
|
-
lines.push(`${prefix} ${entry.title} [${entry.filePath}]${summary}`);
|
|
2451
|
-
lines.push(` ID: ${entry.id}`);
|
|
2452
|
-
}
|
|
2453
|
-
}
|
|
2454
|
-
return lines.join("\n");
|
|
2455
|
-
}
|
|
2456
|
-
case "kirograph_docs_search": {
|
|
2457
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2458
|
-
const projectRoot = cg.getProjectRoot();
|
|
2459
|
-
const config = await loadConfig(projectRoot);
|
|
2460
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2461
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2462
|
-
const db = cg.getDatabase();
|
|
2463
|
-
db.applyDocsSchema();
|
|
2464
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot, config);
|
|
2465
|
-
const results = await docs.searchSections(args.query, {
|
|
2466
|
-
file: args.file,
|
|
2467
|
-
limit: args.limit ?? 10
|
|
2468
|
-
});
|
|
2469
|
-
if (results.length === 0) return `No documentation sections found matching "${args.query}".`;
|
|
2470
|
-
return results.map((r, i) => {
|
|
2471
|
-
const summary = r.section.summary ? `
|
|
2472
|
-
${r.section.summary}` : "";
|
|
2473
|
-
return `${i + 1}. ${r.section.title} [${r.section.filePath}]${summary}
|
|
2474
|
-
ID: ${r.section.id}`;
|
|
2475
|
-
}).join("\n\n");
|
|
2476
|
-
}
|
|
2477
|
-
case "kirograph_docs_section": {
|
|
2478
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2479
|
-
const projectRoot = cg.getProjectRoot();
|
|
2480
|
-
const config = await loadConfig(projectRoot);
|
|
2481
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2482
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2483
|
-
const db = cg.getDatabase();
|
|
2484
|
-
db.applyDocsSchema();
|
|
2485
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
2486
|
-
const result = docs.getSection(args.id, { context: args.context });
|
|
2487
|
-
if (!result) return `Section "${args.id}" not found.`;
|
|
2488
|
-
const lines = [];
|
|
2489
|
-
if (result.ancestors?.length) {
|
|
2490
|
-
lines.push("Breadcrumb: " + result.ancestors.map((a) => a.title).join(" > ") + " > " + result.section.title);
|
|
2491
|
-
lines.push("");
|
|
2492
|
-
}
|
|
2493
|
-
lines.push(result.content);
|
|
2494
|
-
if (result.children?.length) {
|
|
2495
|
-
lines.push("", "## Child sections:");
|
|
2496
|
-
for (const child of result.children) {
|
|
2497
|
-
const summary = child.summary ? ` \u2014 ${child.summary}` : "";
|
|
2498
|
-
lines.push(` - ${child.title}${summary} (ID: ${child.id})`);
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
return lines.join("\n");
|
|
2502
|
-
}
|
|
2503
|
-
case "kirograph_docs_outline": {
|
|
2504
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2505
|
-
const projectRoot = cg.getProjectRoot();
|
|
2506
|
-
const config = await loadConfig(projectRoot);
|
|
2507
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2508
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2509
|
-
const db = cg.getDatabase();
|
|
2510
|
-
db.applyDocsSchema();
|
|
2511
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
2512
|
-
const outline = docs.getOutline(args.file);
|
|
2513
|
-
if (outline.length === 0) return `No sections found in "${args.file}". Is the file indexed?`;
|
|
2514
|
-
const lines = [`Outline: ${args.file}`, ""];
|
|
2515
|
-
const renderOutline = (entries, indent) => {
|
|
2516
|
-
for (const entry of entries) {
|
|
2517
|
-
const summary = entry.summary ? ` \u2014 ${entry.summary}` : "";
|
|
2518
|
-
lines.push(`${indent}${"#".repeat(entry.level || 1)} ${entry.title}${summary}`);
|
|
2519
|
-
if (entry.children?.length) renderOutline(entry.children, indent + " ");
|
|
2520
|
-
}
|
|
2521
|
-
};
|
|
2522
|
-
renderOutline(outline, "");
|
|
2523
|
-
return lines.join("\n");
|
|
2524
|
-
}
|
|
2525
|
-
case "kirograph_docs_refs": {
|
|
2526
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2527
|
-
const projectRoot = cg.getProjectRoot();
|
|
2528
|
-
const config = await loadConfig(projectRoot);
|
|
2529
|
-
if (!config.enableDocs) return "Documentation indexing is not enabled. Set enableDocs: true in .kirograph/config.json and run kirograph index.";
|
|
2530
|
-
const { DocsQueries } = await Promise.resolve().then(() => require("../docs/queries.js"));
|
|
2531
|
-
const db = cg.getDatabase();
|
|
2532
|
-
db.applyDocsSchema();
|
|
2533
|
-
const docs = new DocsQueries(db.getRawDb(), projectRoot);
|
|
2534
|
-
const refs = docs.getRefs({
|
|
2535
|
-
sectionId: args.sectionId,
|
|
2536
|
-
qualifiedName: args.nodeId
|
|
2537
|
-
});
|
|
2538
|
-
if (refs.length === 0) {
|
|
2539
|
-
if (args.sectionId) return `No code references found in section "${args.sectionId}".`;
|
|
2540
|
-
if (args.nodeId) return `No documentation sections reference "${args.nodeId}".`;
|
|
2541
|
-
return "Provide either sectionId or nodeId to look up cross-references.";
|
|
2542
|
-
}
|
|
2543
|
-
return refs.map((r) => {
|
|
2544
|
-
const direction = args.sectionId ? `\u2192 ${r.qualifiedName}` : `\u2190 ${r.sectionTitle ?? r.sectionId}`;
|
|
2545
|
-
return `[${r.refType}] ${direction} (confidence: ${r.confidence.toFixed(2)})`;
|
|
2546
|
-
}).join("\n");
|
|
2547
|
-
}
|
|
2548
|
-
// ── Data tools ────────────────────────────────────────────────────────────
|
|
2549
|
-
case "kirograph_data_list": {
|
|
2550
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2551
|
-
const projectRoot = cg.getProjectRoot();
|
|
2552
|
-
const config = await loadConfig(projectRoot);
|
|
2553
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2554
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2555
|
-
const db = cg.getDatabase();
|
|
2556
|
-
db.applyDataSchema();
|
|
2557
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2558
|
-
const datasets = dq.listDatasets();
|
|
2559
|
-
if (datasets.length === 0) return "No datasets indexed. Run kirograph index or kirograph data reindex.";
|
|
2560
|
-
return datasets.map((ds) => {
|
|
2561
|
-
const sizeMb = (ds.fileSize / 1024 / 1024).toFixed(2);
|
|
2562
|
-
return `${ds.id} (${ds.format})
|
|
2563
|
-
File: ${ds.filePath}
|
|
2564
|
-
Rows: ${ds.rowCount.toLocaleString()} | Columns: ${ds.columnCount} | Size: ${sizeMb} MB`;
|
|
2565
|
-
}).join("\n\n");
|
|
2566
|
-
}
|
|
2567
|
-
case "kirograph_data_describe": {
|
|
2568
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2569
|
-
const projectRoot = cg.getProjectRoot();
|
|
2570
|
-
const config = await loadConfig(projectRoot);
|
|
2571
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2572
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2573
|
-
const db = cg.getDatabase();
|
|
2574
|
-
db.applyDataSchema();
|
|
2575
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2576
|
-
if (args.column) {
|
|
2577
|
-
const col = dq.describeColumn(args.dataset, args.column);
|
|
2578
|
-
if (!col) return `Column "${args.column}" not found in dataset "${args.dataset}".`;
|
|
2579
|
-
return [
|
|
2580
|
-
`Column: ${col.name}`,
|
|
2581
|
-
`Type: ${col.inferredType}`,
|
|
2582
|
-
`Nullable: ${col.nullable} (${(col.nullPct * 100).toFixed(1)}% null)`,
|
|
2583
|
-
`Cardinality: ${col.cardinality}`,
|
|
2584
|
-
col.minValue ? `Min: ${col.minValue}` : "",
|
|
2585
|
-
col.maxValue ? `Max: ${col.maxValue}` : "",
|
|
2586
|
-
col.meanValue !== null ? `Mean: ${col.meanValue.toFixed(2)}` : "",
|
|
2587
|
-
`Samples: ${col.sampleValues.join(", ")}`
|
|
2588
|
-
].filter(Boolean).join("\n");
|
|
2589
|
-
}
|
|
2590
|
-
const result = dq.describeDataset(args.dataset);
|
|
2591
|
-
if (!result) return `Dataset "${args.dataset}" not found. Use kirograph_data_list to see available datasets.`;
|
|
2592
|
-
const lines = [
|
|
2593
|
-
`Dataset: ${result.dataset.id} (${result.dataset.format})`,
|
|
2594
|
-
`File: ${result.dataset.filePath}`,
|
|
2595
|
-
`Rows: ${result.dataset.rowCount.toLocaleString()} | Columns: ${result.dataset.columnCount}`,
|
|
2596
|
-
"",
|
|
2597
|
-
"Columns:"
|
|
2598
|
-
];
|
|
2599
|
-
for (const col of result.columns) {
|
|
2600
|
-
const nullInfo = col.nullable ? ` (${(col.nullPct * 100).toFixed(0)}% null)` : "";
|
|
2601
|
-
const samples = col.sampleValues.length > 0 ? ` \u2014 samples: ${col.sampleValues.slice(0, 3).join(", ")}` : "";
|
|
2602
|
-
const summary = col.summary ? ` [${col.summary}]` : "";
|
|
2603
|
-
lines.push(` ${col.name}: ${col.inferredType}${nullInfo} [${col.cardinality} distinct]${samples}${summary}`);
|
|
2604
|
-
}
|
|
2605
|
-
const rules = dq.validationRules(args.dataset);
|
|
2606
|
-
if (rules && rules.length > 0) {
|
|
2607
|
-
lines.push("", "Validation rules:");
|
|
2608
|
-
for (const r of rules.slice(0, 10)) {
|
|
2609
|
-
lines.push(` ${r.column}: ${r.rules.join("; ")}`);
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
const hints = dq.sampleHints(args.dataset);
|
|
2613
|
-
if (hints && hints.length > 0) {
|
|
2614
|
-
lines.push("", "Sample data hints:");
|
|
2615
|
-
for (const h of hints.slice(0, 10)) {
|
|
2616
|
-
lines.push(` ${h.column}: ${h.hint}`);
|
|
2617
|
-
}
|
|
2618
|
-
}
|
|
2619
|
-
return lines.join("\n");
|
|
2620
|
-
}
|
|
2621
|
-
case "kirograph_data_query": {
|
|
2622
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2623
|
-
const projectRoot = cg.getProjectRoot();
|
|
2624
|
-
const config = await loadConfig(projectRoot);
|
|
2625
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2626
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2627
|
-
const db = cg.getDatabase();
|
|
2628
|
-
db.applyDataSchema();
|
|
2629
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2630
|
-
const result = dq.queryRows(args.dataset, {
|
|
2631
|
-
filters: args.filters,
|
|
2632
|
-
columns: args.columns,
|
|
2633
|
-
limit: args.limit ?? 100,
|
|
2634
|
-
offset: args.offset ?? 0
|
|
2635
|
-
});
|
|
2636
|
-
if (!result) return `Dataset "${args.dataset}" not found.`;
|
|
2637
|
-
if (result.rows.length === 0) return `No rows match the given filters (${result.totalMatching} total in dataset).`;
|
|
2638
|
-
const header = `${result.rows.length} rows returned (${result.totalMatching} total matching):
|
|
2639
|
-
`;
|
|
2640
|
-
const rowStrs = result.rows.slice(0, 50).map((row, i) => {
|
|
2641
|
-
const vals = Object.entries(row).map(([k, v]) => `${k}=${v ?? "null"}`).join(", ");
|
|
2642
|
-
return ` ${i + 1}. ${vals}`;
|
|
2643
|
-
});
|
|
2644
|
-
if (result.rows.length > 50) rowStrs.push(` \u2026and ${result.rows.length - 50} more rows`);
|
|
2645
|
-
let response = header + rowStrs.join("\n");
|
|
2646
|
-
const maxChars = config.dataMaxResponseTokens * 4;
|
|
2647
|
-
if (response.length > maxChars) {
|
|
2648
|
-
response = response.slice(0, maxChars) + "\n\n[truncated: response exceeded token budget]";
|
|
2649
|
-
}
|
|
2650
|
-
response = this.checkPaginationLoop(args.dataset, args.offset, response);
|
|
2651
|
-
return response;
|
|
2652
|
-
}
|
|
2653
|
-
case "kirograph_data_aggregate": {
|
|
2654
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2655
|
-
const projectRoot = cg.getProjectRoot();
|
|
2656
|
-
const config = await loadConfig(projectRoot);
|
|
2657
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2658
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2659
|
-
const db = cg.getDatabase();
|
|
2660
|
-
db.applyDataSchema();
|
|
2661
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2662
|
-
const result = dq.aggregate(args.dataset, {
|
|
2663
|
-
groupBy: args.groupBy ?? [],
|
|
2664
|
-
metrics: args.metrics ?? [],
|
|
2665
|
-
filters: args.filters
|
|
2666
|
-
});
|
|
2667
|
-
if (!result) return `Dataset "${args.dataset}" not found.`;
|
|
2668
|
-
if (result.rows.length === 0) return "No results (empty dataset or all rows filtered out).";
|
|
2669
|
-
const keys = Object.keys(result.rows[0]);
|
|
2670
|
-
const header = keys.join(" | ");
|
|
2671
|
-
const separator = keys.map(() => "---").join(" | ");
|
|
2672
|
-
const rows = result.rows.slice(0, 100).map((row) => keys.map((k) => row[k] ?? "null").join(" | "));
|
|
2673
|
-
let response = `${header}
|
|
2674
|
-
${separator}
|
|
2675
|
-
${rows.join("\n")}${result.rows.length > 100 ? `
|
|
2676
|
-
\u2026and ${result.rows.length - 100} more groups` : ""}`;
|
|
2677
|
-
const maxChars = config.dataMaxResponseTokens * 4;
|
|
2678
|
-
if (response.length > maxChars) {
|
|
2679
|
-
response = response.slice(0, maxChars) + "\n\n[truncated: response exceeded token budget]";
|
|
2680
|
-
}
|
|
2681
|
-
return response;
|
|
2682
|
-
}
|
|
2683
|
-
case "kirograph_data_search": {
|
|
2684
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2685
|
-
const projectRoot = cg.getProjectRoot();
|
|
2686
|
-
const config = await loadConfig(projectRoot);
|
|
2687
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2688
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2689
|
-
const db = cg.getDatabase();
|
|
2690
|
-
db.applyDataSchema();
|
|
2691
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2692
|
-
const cols = dq.searchColumns(args.dataset, args.query);
|
|
2693
|
-
if (cols.length === 0) return `No columns matching "${args.query}" in dataset "${args.dataset}".`;
|
|
2694
|
-
return cols.map((c) => {
|
|
2695
|
-
const samples = c.sampleValues.length > 0 ? ` \u2014 samples: ${c.sampleValues.slice(0, 3).join(", ")}` : "";
|
|
2696
|
-
return `${c.name}: ${c.inferredType} [${c.cardinality} distinct]${samples}`;
|
|
2697
|
-
}).join("\n");
|
|
2698
|
-
}
|
|
2699
|
-
case "kirograph_data_join": {
|
|
2700
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2701
|
-
const projectRoot = cg.getProjectRoot();
|
|
2702
|
-
const config = await loadConfig(projectRoot);
|
|
2703
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2704
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2705
|
-
const db = cg.getDatabase();
|
|
2706
|
-
db.applyDataSchema();
|
|
2707
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2708
|
-
try {
|
|
2709
|
-
const result = dq.join({
|
|
2710
|
-
left: args.left,
|
|
2711
|
-
right: args.right,
|
|
2712
|
-
leftColumn: args.leftColumn,
|
|
2713
|
-
rightColumn: args.rightColumn,
|
|
2714
|
-
type: args.type ?? "inner",
|
|
2715
|
-
columns: args.columns,
|
|
2716
|
-
limit: args.limit
|
|
2717
|
-
});
|
|
2718
|
-
if (!result) return `Dataset not found. Verify both dataset IDs with kirograph_data_list.`;
|
|
2719
|
-
const joinTypeStr = String(args.type ?? "inner").toUpperCase();
|
|
2720
|
-
const header = `Join: ${args.left}.${args.leftColumn} ${joinTypeStr} JOIN ${args.right}.${args.rightColumn}
|
|
2721
|
-
Matching rows: ${result.totalMatching} (showing ${result.rows.length})`;
|
|
2722
|
-
if (result.rows.length === 0) return `${header}
|
|
2723
|
-
|
|
2724
|
-
No matching rows.`;
|
|
2725
|
-
const lines = result.rows.map((r) => JSON.stringify(r));
|
|
2726
|
-
let response = `${header}
|
|
2727
|
-
|
|
2728
|
-
${lines.join("\n")}`;
|
|
2729
|
-
const maxChars = config.dataMaxResponseTokens * 4;
|
|
2730
|
-
if (response.length > maxChars) {
|
|
2731
|
-
response = response.slice(0, maxChars) + "\n\n[truncated: response exceeded token budget]";
|
|
2732
|
-
}
|
|
2733
|
-
return response;
|
|
2734
|
-
} catch (err) {
|
|
2735
|
-
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
2736
|
-
}
|
|
2737
|
-
}
|
|
2738
|
-
case "kirograph_data_correlations": {
|
|
2739
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2740
|
-
const projectRoot = cg.getProjectRoot();
|
|
2741
|
-
const config = await loadConfig(projectRoot);
|
|
2742
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2743
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2744
|
-
const db = cg.getDatabase();
|
|
2745
|
-
db.applyDataSchema();
|
|
2746
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2747
|
-
const pairs = dq.correlations(args.dataset, args.threshold);
|
|
2748
|
-
if (pairs === null) return `Dataset "${args.dataset}" not found.`;
|
|
2749
|
-
if (pairs.length === 0) return `No correlations above threshold ${args.threshold ?? 0.3} found. The dataset may have fewer than 2 numeric columns or no significant correlations.`;
|
|
2750
|
-
const lines = pairs.map(
|
|
2751
|
-
(p) => `${p.column1} \u2194 ${p.column2}: ${p.correlation > 0 ? "+" : ""}${p.correlation.toFixed(4)} (${p.strength})`
|
|
2752
|
-
);
|
|
2753
|
-
return `Correlations for "${args.dataset}" (threshold: ${args.threshold ?? 0.3}):
|
|
2754
|
-
|
|
2755
|
-
${lines.join("\n")}`;
|
|
2756
|
-
}
|
|
2757
|
-
case "kirograph_data_quality": {
|
|
2758
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2759
|
-
const projectRoot = cg.getProjectRoot();
|
|
2760
|
-
const config = await loadConfig(projectRoot);
|
|
2761
|
-
if (!config.enableData) return "Data indexing is not enabled. Set enableData: true in .kirograph/config.json and run kirograph index.";
|
|
2762
|
-
const { DataQueries } = await Promise.resolve().then(() => require("../data/queries.js"));
|
|
2763
|
-
const db = cg.getDatabase();
|
|
2764
|
-
db.applyDataSchema();
|
|
2765
|
-
const dq = new DataQueries(db.getRawDb());
|
|
2766
|
-
const quality = dq.quality(args.dataset);
|
|
2767
|
-
if (quality === null) return `Dataset "${args.dataset}" not found.`;
|
|
2768
|
-
if (quality.length === 0) return `No quality issues detected in "${args.dataset}". All columns look healthy.`;
|
|
2769
|
-
const lines = quality.map(
|
|
2770
|
-
(q) => `${q.column} (risk: ${(q.riskScore * 100).toFixed(0)}%): ${q.issues.join("; ")}`
|
|
2771
|
-
);
|
|
2772
|
-
return `Quality report for "${args.dataset}" (${quality.length} columns with issues):
|
|
2773
|
-
|
|
2774
|
-
${lines.join("\n")}`;
|
|
2775
|
-
}
|
|
2776
|
-
// ── Security tools (require enableSecurity=true) ──────────────────────────
|
|
2777
|
-
case "kirograph_security": {
|
|
2778
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2779
|
-
const projectRoot = cg.getProjectRoot();
|
|
2780
|
-
const config = await loadConfig(projectRoot);
|
|
2781
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
2782
|
-
if (!config.enableArchitecture) return "Security requires architecture analysis. Set enableArchitecture: true in .kirograph/config.json and run kirograph index.";
|
|
2783
|
-
const db = cg.getDatabase();
|
|
2784
|
-
db.applySecuritySchema();
|
|
2785
|
-
const rawDb = db.getRawDb();
|
|
2786
|
-
const depCount = rawDb.get(
|
|
2787
|
-
`SELECT COUNT(*) as count FROM sec_dependencies`
|
|
2788
|
-
) ?? { count: 0 };
|
|
2789
|
-
const vulnCount = rawDb.get(
|
|
2790
|
-
`SELECT COUNT(*) as count FROM sec_vulnerabilities`
|
|
2791
|
-
) ?? { count: 0 };
|
|
2792
|
-
const verdictRows = rawDb.all(
|
|
2793
|
-
`SELECT verdict, COUNT(*) as count FROM sec_reachability GROUP BY verdict`
|
|
2794
|
-
);
|
|
2795
|
-
const verdicts = {};
|
|
2796
|
-
for (const row of verdictRows) {
|
|
2797
|
-
verdicts[row.verdict] = row.count;
|
|
2798
|
-
}
|
|
2799
|
-
const staleCount = rawDb.get(
|
|
2800
|
-
`SELECT COUNT(*) as count FROM sec_dependencies WHERE vuln_data_stale = 1`
|
|
2801
|
-
) ?? { count: 0 };
|
|
2802
|
-
const { SuppressionManager: SecSuppressionManager } = await Promise.resolve().then(() => require("../security/suppressions.js"));
|
|
2803
|
-
const secSuppressions = new SecSuppressionManager(projectRoot);
|
|
2804
|
-
const allCveIds = rawDb.all(`SELECT cve_id FROM sec_vulnerabilities`);
|
|
2805
|
-
const suppressedCveCount = allCveIds.filter((r) => secSuppressions.isSuppressed(r.cve_id)).length;
|
|
2806
|
-
const visibleVulnCount = vulnCount.count - suppressedCveCount;
|
|
2807
|
-
const lines = [
|
|
2808
|
-
"# Security Overview",
|
|
2809
|
-
"",
|
|
2810
|
-
`Dependencies: ${depCount.count}`,
|
|
2811
|
-
`Vulnerabilities: ${visibleVulnCount}${suppressedCveCount > 0 ? ` (${suppressedCveCount} suppressed)` : ""}`
|
|
2812
|
-
];
|
|
2813
|
-
if (visibleVulnCount > 0) {
|
|
2814
|
-
const affected = verdicts["affected"] ?? 0;
|
|
2815
|
-
const notAffected = verdicts["not_affected"] ?? 0;
|
|
2816
|
-
const underInvestigation = verdicts["under_investigation"] ?? 0;
|
|
2817
|
-
const pending = visibleVulnCount - affected - notAffected - underInvestigation;
|
|
2818
|
-
lines.push("", "## Reachability Verdicts", "");
|
|
2819
|
-
if (affected > 0) lines.push(`\u25CF Affected: ${affected}`);
|
|
2820
|
-
if (notAffected > 0) lines.push(`\u25CF Not affected: ${notAffected}`);
|
|
2821
|
-
if (underInvestigation > 0) lines.push(`\u25CF Under investigation: ${underInvestigation}`);
|
|
2822
|
-
if (pending > 0) lines.push(`\u25CF Pending analysis: ${pending}`);
|
|
2823
|
-
}
|
|
2824
|
-
if (staleCount.count > 0) {
|
|
2825
|
-
lines.push("", `\u26A0 ${staleCount.count} dependenc${staleCount.count === 1 ? "y has" : "ies have"} stale vulnerability data. Use kirograph_vulns with refresh=true to update.`);
|
|
2826
|
-
}
|
|
2827
|
-
const lastVulnCheck = rawDb.get(
|
|
2828
|
-
`SELECT MIN(last_vuln_check) as oldest FROM sec_dependencies WHERE last_vuln_check IS NOT NULL`
|
|
2829
|
-
)?.oldest;
|
|
2830
|
-
if (lastVulnCheck != null) {
|
|
2831
|
-
const ageMs = Date.now() - lastVulnCheck;
|
|
2832
|
-
const ageDays = ageMs / (1e3 * 60 * 60 * 24);
|
|
2833
|
-
const maxAge = config.securityEnrichMaxAgeDays ?? 7;
|
|
2834
|
-
if (ageDays > maxAge) {
|
|
2835
|
-
lines.push("", `\u26A0 Vulnerability data is ${Math.floor(ageDays)} days old (max: ${maxAge}). Run kirograph vulns --refresh to update.`);
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
try {
|
|
2839
|
-
const rawDbSec = db.getRawDb();
|
|
2840
|
-
const tableExistsSec = rawDbSec.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
2841
|
-
if (tableExistsSec) {
|
|
2842
|
-
const patternCount = rawDbSec.get("SELECT COUNT(*) as cnt FROM pattern_matches")?.cnt ?? 0;
|
|
2843
|
-
if (patternCount > 0) {
|
|
2844
|
-
const critCount = rawDbSec.get("SELECT COUNT(*) as cnt FROM pattern_matches WHERE severity = 'critical'")?.cnt ?? 0;
|
|
2845
|
-
lines.push(`SAST findings: ${patternCount} pattern matches (${critCount} critical)`);
|
|
2846
|
-
}
|
|
2847
|
-
}
|
|
2848
|
-
} catch {
|
|
2849
|
-
}
|
|
2850
|
-
return lines.join("\n");
|
|
2851
|
-
}
|
|
2852
|
-
case "kirograph_vulns": {
|
|
2853
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2854
|
-
const projectRoot = cg.getProjectRoot();
|
|
2855
|
-
const config = await loadConfig(projectRoot);
|
|
2856
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
2857
|
-
const db = cg.getDatabase();
|
|
2858
|
-
db.applySecuritySchema();
|
|
2859
|
-
const rawDb = db.getRawDb();
|
|
2860
|
-
if (args.refresh === true) {
|
|
2861
|
-
const { OsvAdapter } = await Promise.resolve().then(() => require("../security/vuln/osv-adapter.js"));
|
|
2862
|
-
const { VulnerabilityDatabaseClient } = await Promise.resolve().then(() => require("../security/vuln/client.js"));
|
|
2863
|
-
const adapters = config.securityDatabases.map((dbName) => {
|
|
2864
|
-
if (dbName.toUpperCase() === "OSV") return new OsvAdapter();
|
|
2865
|
-
return null;
|
|
2866
|
-
}).filter(Boolean);
|
|
2867
|
-
const client = new VulnerabilityDatabaseClient(adapters, db);
|
|
2868
|
-
await client.enrichAll();
|
|
2869
|
-
}
|
|
2870
|
-
let query = `
|
|
2871
|
-
SELECT
|
|
2872
|
-
v.node_id, v.cve_id, v.severity_score, v.fixed_version, v.summary,
|
|
2873
|
-
v.epss_score, v.epss_percentile, v.risk_score,
|
|
2874
|
-
d.package_name, d.ecosystem, d.resolved_version, d.declared_constraint,
|
|
2875
|
-
r.verdict
|
|
2876
|
-
FROM sec_vulnerabilities v
|
|
2877
|
-
LEFT JOIN edges e ON e.target = v.node_id AND e.kind = 'has_vulnerability'
|
|
2878
|
-
LEFT JOIN sec_dependencies d ON d.node_id = e.source
|
|
2879
|
-
LEFT JOIN sec_reachability r ON r.vulnerability_node_id = v.node_id
|
|
2880
|
-
WHERE 1=1
|
|
2881
|
-
`;
|
|
2882
|
-
const params = [];
|
|
2883
|
-
if (args.severity) {
|
|
2884
|
-
const severityRanges = {
|
|
2885
|
-
critical: [9, 10],
|
|
2886
|
-
high: [7, 8.9],
|
|
2887
|
-
medium: [4, 6.9],
|
|
2888
|
-
low: [0.1, 3.9]
|
|
2889
|
-
};
|
|
2890
|
-
const range = severityRanges[args.severity.toLowerCase()];
|
|
2891
|
-
if (!range) return `Invalid severity: ${args.severity}. Use: critical, high, medium, low`;
|
|
2892
|
-
query += ` AND v.severity_score >= ? AND v.severity_score <= ?`;
|
|
2893
|
-
params.push(range[0], range[1]);
|
|
2894
|
-
}
|
|
2895
|
-
if (args.verdict) {
|
|
2896
|
-
const validVerdicts = ["affected", "not_affected", "under_investigation"];
|
|
2897
|
-
if (!validVerdicts.includes(args.verdict)) {
|
|
2898
|
-
return `Invalid verdict: ${args.verdict}. Use: affected, not_affected, under_investigation`;
|
|
2899
|
-
}
|
|
2900
|
-
query += ` AND r.verdict = ?`;
|
|
2901
|
-
params.push(args.verdict);
|
|
2902
|
-
}
|
|
2903
|
-
const limit = clampLimit(args.limit, 20);
|
|
2904
|
-
query += ` ORDER BY v.risk_score DESC NULLS LAST LIMIT ?`;
|
|
2905
|
-
params.push(limit);
|
|
2906
|
-
const rows = rawDb.all(query, params);
|
|
2907
|
-
const verdictRankMcp = (v) => v === "affected" ? 3 : v === "under_investigation" ? 2 : v === "not_affected" ? 1 : 0;
|
|
2908
|
-
const dedupMapMcp = /* @__PURE__ */ new Map();
|
|
2909
|
-
for (const row of rows) {
|
|
2910
|
-
const key = `${row.cve_id}::${row.package_name ?? ""}::${row.ecosystem ?? ""}`;
|
|
2911
|
-
const ex = dedupMapMcp.get(key);
|
|
2912
|
-
if (!ex || verdictRankMcp(row.verdict) > verdictRankMcp(ex.verdict)) dedupMapMcp.set(key, row);
|
|
2913
|
-
}
|
|
2914
|
-
const dedupedRowsMcp = [...dedupMapMcp.values()];
|
|
2915
|
-
const { SuppressionManager } = await Promise.resolve().then(() => require("../security/suppressions.js"));
|
|
2916
|
-
const suppressionMgr = new SuppressionManager(projectRoot);
|
|
2917
|
-
const suppressedCount = dedupedRowsMcp.filter((row) => suppressionMgr.isSuppressed(row.cve_id)).length;
|
|
2918
|
-
const filteredRows = dedupedRowsMcp.filter((row) => !suppressionMgr.isSuppressed(row.cve_id));
|
|
2919
|
-
if (filteredRows.length === 0) {
|
|
2920
|
-
const noMatch = "No vulnerabilities found" + (args.severity || args.verdict ? " matching filters." : ".");
|
|
2921
|
-
return suppressedCount > 0 ? `${noMatch} (${suppressedCount} suppressed)` : noMatch;
|
|
2922
|
-
}
|
|
2923
|
-
const { formatFixSuggestion } = await Promise.resolve().then(() => require("../security/export/fix-suggestions.js"));
|
|
2924
|
-
const lines = [`Vulnerabilities (${filteredRows.length}${suppressedCount > 0 ? `, ${suppressedCount} suppressed` : ""}):
|
|
2925
|
-
`];
|
|
2926
|
-
for (const row of filteredRows) {
|
|
2927
|
-
const score = row.severity_score;
|
|
2928
|
-
let severityLabel;
|
|
2929
|
-
if (score == null) severityLabel = "UNKNOWN";
|
|
2930
|
-
else if (score >= 9) severityLabel = "CRITICAL";
|
|
2931
|
-
else if (score >= 7) severityLabel = "HIGH";
|
|
2932
|
-
else if (score >= 4) severityLabel = "MEDIUM";
|
|
2933
|
-
else severityLabel = "LOW";
|
|
2934
|
-
let verdictLabel;
|
|
2935
|
-
if (!row.verdict) verdictLabel = "pending";
|
|
2936
|
-
else if (row.verdict === "affected") verdictLabel = "affected";
|
|
2937
|
-
else if (row.verdict === "not_affected") verdictLabel = "not affected";
|
|
2938
|
-
else verdictLabel = "investigating";
|
|
2939
|
-
const pkg = row.package_name ? `${row.package_name}@${row.resolved_version || row.declared_constraint || "?"}` : "unknown package";
|
|
2940
|
-
const epssNote = row.epss_score != null ? ` [EPSS: ${row.epss_score.toFixed(2)}]` : "";
|
|
2941
|
-
const riskNote = row.risk_score != null ? ` [Risk: ${row.risk_score.toFixed(1)}]` : "";
|
|
2942
|
-
lines.push(`${severityLabel} ${row.cve_id} ${pkg} [${verdictLabel}]${epssNote}${riskNote}`);
|
|
2943
|
-
if (row.summary && row.summary !== "Manually registered") {
|
|
2944
|
-
const truncSummary = row.summary.length > 120 ? row.summary.slice(0, 120) + "\u2026" : row.summary;
|
|
2945
|
-
lines.push(` ${truncSummary}`);
|
|
2946
|
-
}
|
|
2947
|
-
if (row.fixed_version && row.ecosystem && row.package_name) {
|
|
2948
|
-
const fix = formatFixSuggestion(row.ecosystem, row.package_name, row.fixed_version);
|
|
2949
|
-
if (fix) lines.push(` ${fix}`);
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
return truncate(lines.join("\n"));
|
|
2953
|
-
}
|
|
2954
|
-
case "kirograph_vuln_add": {
|
|
2955
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
2956
|
-
const projectRoot = cg.getProjectRoot();
|
|
2957
|
-
const config = await loadConfig(projectRoot);
|
|
2958
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
2959
|
-
const cveId = args.cveId;
|
|
2960
|
-
const pkgName = args.package;
|
|
2961
|
-
if (!cveId) return "Error: cveId is required.";
|
|
2962
|
-
if (!pkgName) return "Error: package is required.";
|
|
2963
|
-
const db = cg.getDatabase();
|
|
2964
|
-
db.applySecuritySchema();
|
|
2965
|
-
const rawDb = db.getRawDb();
|
|
2966
|
-
const depRow = rawDb.get(
|
|
2967
|
-
`SELECT node_id, ecosystem FROM sec_dependencies WHERE package_name = ?`,
|
|
2968
|
-
[pkgName]
|
|
2969
|
-
);
|
|
2970
|
-
if (!depRow) {
|
|
2971
|
-
return `No dependency found matching "${pkgName}". Run kirograph index first to discover dependencies.`;
|
|
2972
|
-
}
|
|
2973
|
-
const vulnNodeId = `vuln:${cveId}`;
|
|
2974
|
-
const now = Date.now();
|
|
2975
|
-
const severity = args.severity;
|
|
2976
|
-
const summary = args.summary ?? "Manually registered";
|
|
2977
|
-
const fixedVersion = args.fixedVersion ?? null;
|
|
2978
|
-
rawDb.run(
|
|
2979
|
-
`INSERT OR REPLACE INTO nodes
|
|
2980
|
-
(id, kind, name, qualified_name, file_path, language,
|
|
2981
|
-
start_line, end_line, start_column, end_column,
|
|
2982
|
-
is_exported, is_async, is_static, is_abstract, updated_at)
|
|
2983
|
-
VALUES (?, 'vulnerability', ?, ?, '', 'unknown', 0, 0, 0, 0, 0, 0, 0, 0, ?)`,
|
|
2984
|
-
[vulnNodeId, cveId, cveId, now]
|
|
2985
|
-
);
|
|
2986
|
-
rawDb.run(
|
|
2987
|
-
`INSERT OR REPLACE INTO sec_vulnerabilities
|
|
2988
|
-
(node_id, cve_id, severity_score, affected_ranges, fixed_version, summary, source_database)
|
|
2989
|
-
VALUES (?, ?, ?, '[]', ?, ?, 'manual')`,
|
|
2990
|
-
[vulnNodeId, cveId, severity ?? null, fixedVersion, summary]
|
|
2991
|
-
);
|
|
2992
|
-
rawDb.run(
|
|
2993
|
-
`INSERT OR IGNORE INTO edges (source, target, kind, confidence, confidence_score)
|
|
2994
|
-
VALUES (?, ?, 'has_vulnerability', 'extracted', 1.0)`,
|
|
2995
|
-
[depRow.node_id, vulnNodeId]
|
|
2996
|
-
);
|
|
2997
|
-
return `Registered ${cveId} against ${pkgName}.`;
|
|
2998
|
-
}
|
|
2999
|
-
case "kirograph_vuln_suppress": {
|
|
3000
|
-
const projectRoot = cg.getProjectRoot();
|
|
3001
|
-
const cveId = args.cveId;
|
|
3002
|
-
if (!cveId) return "Error: cveId is required.";
|
|
3003
|
-
const { SuppressionManager } = await Promise.resolve().then(() => require("../security/suppressions.js"));
|
|
3004
|
-
const manager = new SuppressionManager(projectRoot);
|
|
3005
|
-
manager.add(cveId, args.reason, args.expires);
|
|
3006
|
-
const reasonNote = args.reason ? ` Reason: ${args.reason}.` : "";
|
|
3007
|
-
const expiresNote = args.expires ? ` Expires: ${args.expires}.` : "";
|
|
3008
|
-
return `${cveId} suppressed.${reasonNote}${expiresNote}`;
|
|
3009
|
-
}
|
|
3010
|
-
case "kirograph_sbom": {
|
|
3011
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3012
|
-
const projectRoot = cg.getProjectRoot();
|
|
3013
|
-
const config = await loadConfig(projectRoot);
|
|
3014
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3015
|
-
const db = cg.getDatabase();
|
|
3016
|
-
db.applySecuritySchema();
|
|
3017
|
-
const { SBOMExporter } = await Promise.resolve().then(() => require("../security/export/sbom.js"));
|
|
3018
|
-
const exporter = new SBOMExporter(db, projectRoot);
|
|
3019
|
-
const json = exporter.exportJSON();
|
|
3020
|
-
return truncate(json);
|
|
3021
|
-
}
|
|
3022
|
-
case "kirograph_vex": {
|
|
3023
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3024
|
-
const projectRoot = cg.getProjectRoot();
|
|
3025
|
-
const config = await loadConfig(projectRoot);
|
|
3026
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3027
|
-
const db = cg.getDatabase();
|
|
3028
|
-
db.applySecuritySchema();
|
|
3029
|
-
const { VEXExporter } = await Promise.resolve().then(() => require("../security/export/vex.js"));
|
|
3030
|
-
const exporter = new VEXExporter(db, projectRoot);
|
|
3031
|
-
const json = exporter.exportJSON();
|
|
3032
|
-
return truncate(json);
|
|
3033
|
-
}
|
|
3034
|
-
case "kirograph_reachability": {
|
|
3035
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3036
|
-
const projectRoot = cg.getProjectRoot();
|
|
3037
|
-
const config = await loadConfig(projectRoot);
|
|
3038
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3039
|
-
const target = args.target;
|
|
3040
|
-
if (!target) return "Error: target is required (dependency name or CVE ID).";
|
|
3041
|
-
const db = cg.getDatabase();
|
|
3042
|
-
db.applySecuritySchema();
|
|
3043
|
-
const rawDb = db.getRawDb();
|
|
3044
|
-
let vulnerabilityNodeId = null;
|
|
3045
|
-
let targetLabel = target;
|
|
3046
|
-
const vulnRow = rawDb.get(
|
|
3047
|
-
`SELECT node_id FROM sec_vulnerabilities WHERE cve_id = ?`,
|
|
3048
|
-
[target]
|
|
3049
|
-
);
|
|
3050
|
-
if (vulnRow) {
|
|
3051
|
-
vulnerabilityNodeId = vulnRow.node_id;
|
|
3052
|
-
} else {
|
|
3053
|
-
const depRow = rawDb.get(
|
|
3054
|
-
`SELECT node_id, package_name FROM sec_dependencies WHERE package_name = ?`,
|
|
3055
|
-
[target]
|
|
3056
|
-
);
|
|
3057
|
-
if (depRow) {
|
|
3058
|
-
targetLabel = depRow.package_name;
|
|
3059
|
-
const vulnEdge = rawDb.get(
|
|
3060
|
-
`SELECT target FROM edges WHERE source = ? AND kind = 'has_vulnerability' LIMIT 1`,
|
|
3061
|
-
[depRow.node_id]
|
|
3062
|
-
);
|
|
3063
|
-
if (vulnEdge) {
|
|
3064
|
-
vulnerabilityNodeId = vulnEdge.target;
|
|
3065
|
-
} else {
|
|
3066
|
-
return `No vulnerabilities found for dependency "${target}". The dependency exists but has no known vulnerabilities.`;
|
|
3067
|
-
}
|
|
3068
|
-
} else {
|
|
3069
|
-
return `Target "${target}" not found. Provide a valid CVE ID or dependency package name.`;
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
const { ReachabilityAnalyzer } = await Promise.resolve().then(() => require("../security/reachability.js"));
|
|
3073
|
-
const analyzer = new ReachabilityAnalyzer(db, config);
|
|
3074
|
-
const result = await analyzer.analyze(vulnerabilityNodeId);
|
|
3075
|
-
const lines = [
|
|
3076
|
-
`# Reachability: ${targetLabel}`,
|
|
3077
|
-
"",
|
|
3078
|
-
`Verdict: ${result.verdict}`,
|
|
3079
|
-
`Reaching entry points: ${result.reachingEntryPointCount}`
|
|
3080
|
-
];
|
|
3081
|
-
if (result.paths.length > 0) {
|
|
3082
|
-
lines.push("", "## Paths");
|
|
3083
|
-
for (const p of result.paths.slice(0, 5)) {
|
|
3084
|
-
lines.push(`- From ${p.entryPoint}: ${p.path.join(" \u2192 ")}`);
|
|
3085
|
-
}
|
|
3086
|
-
if (result.paths.length > 5) {
|
|
3087
|
-
lines.push(` \u2026and ${result.paths.length - 5} more paths`);
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
if (result.unresolvedSymbols.length > 0) {
|
|
3091
|
-
lines.push("", "## Unresolved Symbols");
|
|
3092
|
-
for (const sym of result.unresolvedSymbols.slice(0, 10)) {
|
|
3093
|
-
lines.push(`- ${sym}`);
|
|
3094
|
-
}
|
|
3095
|
-
if (result.unresolvedSymbols.length > 10) {
|
|
3096
|
-
lines.push(` \u2026and ${result.unresolvedSymbols.length - 10} more`);
|
|
3097
|
-
}
|
|
3098
|
-
}
|
|
3099
|
-
if (result.verdict === "affected") {
|
|
3100
|
-
const impact = await analyzer.getImpactSummary(vulnerabilityNodeId);
|
|
3101
|
-
if (impact) {
|
|
3102
|
-
lines.push("", "## Impact Summary");
|
|
3103
|
-
if (impact.affectedLayers.length > 0) {
|
|
3104
|
-
lines.push(`Affected layers: ${impact.affectedLayers.join(", ")}`);
|
|
3105
|
-
}
|
|
3106
|
-
lines.push(`Affected entry points: ${impact.affectedEntryPoints.length}`);
|
|
3107
|
-
lines.push(`Distinct paths: ${impact.distinctPathCount}`);
|
|
3108
|
-
}
|
|
3109
|
-
}
|
|
3110
|
-
return truncate(lines.join("\n"));
|
|
3111
|
-
}
|
|
3112
|
-
case "kirograph_staleness": {
|
|
3113
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3114
|
-
const projectRoot = cg.getProjectRoot();
|
|
3115
|
-
const config = await loadConfig(projectRoot);
|
|
3116
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3117
|
-
const db = cg.getDatabase();
|
|
3118
|
-
db.applySecuritySchema();
|
|
3119
|
-
const rawDb = db.getRawDb();
|
|
3120
|
-
const threshold = typeof args.threshold === "number" ? args.threshold : 0.3;
|
|
3121
|
-
if (args.refresh === true) {
|
|
3122
|
-
const { StalenessChecker } = await Promise.resolve().then(() => require("../security/staleness.js"));
|
|
3123
|
-
const checker = new StalenessChecker(db);
|
|
3124
|
-
await checker.checkAll();
|
|
3125
|
-
}
|
|
3126
|
-
const rows = rawDb.all(
|
|
3127
|
-
`SELECT package_name, ecosystem, resolved_version, declared_constraint,
|
|
3128
|
-
latest_version, latest_published, staleness_score
|
|
3129
|
-
FROM sec_dependencies
|
|
3130
|
-
WHERE staleness_score >= ?
|
|
3131
|
-
ORDER BY staleness_score DESC`,
|
|
3132
|
-
[threshold]
|
|
3133
|
-
);
|
|
3134
|
-
if (rows.length === 0) {
|
|
3135
|
-
return `No dependencies found with staleness_score >= ${threshold}.` + (args.refresh ? "" : " Use refresh=true to fetch latest version data from registries.");
|
|
3136
|
-
}
|
|
3137
|
-
const lines = [`Stale Dependencies (threshold: ${threshold}):
|
|
3138
|
-
`];
|
|
3139
|
-
for (const row of rows) {
|
|
3140
|
-
const resolved = row.resolved_version ?? row.declared_constraint ?? "?";
|
|
3141
|
-
const latest = row.latest_version ?? "?";
|
|
3142
|
-
const score = row.staleness_score ?? 0;
|
|
3143
|
-
const months = row.latest_published ? Math.round((Date.now() - row.latest_published) / (1e3 * 60 * 60 * 24 * 30)) : null;
|
|
3144
|
-
const bar = "\u2588".repeat(Math.round(score * 10)) + "\u2591".repeat(10 - Math.round(score * 10));
|
|
3145
|
-
const monthsStr = months !== null ? `, ${months}mo since latest` : "";
|
|
3146
|
-
lines.push(`${row.package_name} (${row.ecosystem}): ${resolved} \u2192 ${latest}${monthsStr}`);
|
|
3147
|
-
lines.push(` ${bar} ${score.toFixed(2)}`);
|
|
3148
|
-
}
|
|
3149
|
-
const totalCount = rawDb.get(`SELECT COUNT(*) as count FROM sec_dependencies`) ?? { count: 0 };
|
|
3150
|
-
lines.push("", `${rows.length} of ${totalCount.count} dependencies are stale (score >= ${threshold})`);
|
|
3151
|
-
return truncate(lines.join("\n"));
|
|
3152
|
-
}
|
|
3153
|
-
case "kirograph_licenses": {
|
|
3154
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3155
|
-
const projectRoot = cg.getProjectRoot();
|
|
3156
|
-
const config = await loadConfig(projectRoot);
|
|
3157
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3158
|
-
const db = cg.getDatabase();
|
|
3159
|
-
db.applySecuritySchema();
|
|
3160
|
-
const rawDb = db.getRawDb();
|
|
3161
|
-
const deps = rawDb.all(
|
|
3162
|
-
`SELECT package_name, ecosystem, license FROM sec_dependencies ORDER BY ecosystem, package_name`
|
|
3163
|
-
);
|
|
3164
|
-
const { checkLicensePolicy } = await Promise.resolve().then(() => require("../security/license.js"));
|
|
3165
|
-
const policy = config.securityLicensePolicy;
|
|
3166
|
-
const violations = checkLicensePolicy(deps, policy);
|
|
3167
|
-
if (args.policy === true) {
|
|
3168
|
-
if (violations.length === 0) {
|
|
3169
|
-
return "No license policy violations found.";
|
|
3170
|
-
}
|
|
3171
|
-
const lines2 = ["# License Policy Violations\n"];
|
|
3172
|
-
for (const v of violations) {
|
|
3173
|
-
lines2.push(`${v.severity.toUpperCase()} ${v.packageName} [${v.ecosystem}] ${v.license}`);
|
|
3174
|
-
}
|
|
3175
|
-
const denyCount2 = violations.filter((v) => v.severity === "deny").length;
|
|
3176
|
-
const warnCount2 = violations.filter((v) => v.severity === "warn").length;
|
|
3177
|
-
lines2.push("");
|
|
3178
|
-
if (denyCount2 > 0) lines2.push(`${denyCount2} denied license${denyCount2 !== 1 ? "s" : ""}`);
|
|
3179
|
-
if (warnCount2 > 0) lines2.push(`${warnCount2} license warning${warnCount2 !== 1 ? "s" : ""}`);
|
|
3180
|
-
return truncate(lines2.join("\n"));
|
|
3181
|
-
}
|
|
3182
|
-
if (deps.length === 0) {
|
|
3183
|
-
return "No dependencies found. Run kirograph index first.";
|
|
3184
|
-
}
|
|
3185
|
-
const violationMap = /* @__PURE__ */ new Map();
|
|
3186
|
-
for (const v of violations) {
|
|
3187
|
-
violationMap.set(`${v.ecosystem}:${v.packageName}`, v.severity);
|
|
3188
|
-
}
|
|
3189
|
-
const lines = [`# License Report (${deps.length} dependencies)
|
|
3190
|
-
`];
|
|
3191
|
-
if (violations.length > 0) {
|
|
3192
|
-
lines.push("## Policy Violations\n");
|
|
3193
|
-
for (const v of violations) {
|
|
3194
|
-
lines.push(`${v.severity.toUpperCase()} ${v.packageName} [${v.ecosystem}] ${v.license}`);
|
|
3195
|
-
}
|
|
3196
|
-
lines.push("");
|
|
3197
|
-
}
|
|
3198
|
-
lines.push("## All Dependencies\n");
|
|
3199
|
-
lines.push("package | ecosystem | license | status");
|
|
3200
|
-
lines.push("------- | --------- | ------- | ------");
|
|
3201
|
-
for (const dep of deps) {
|
|
3202
|
-
const key = `${dep.ecosystem}:${dep.package_name}`;
|
|
3203
|
-
const violation = violationMap.get(key);
|
|
3204
|
-
const license = dep.license ?? "(unknown)";
|
|
3205
|
-
const status = violation ?? (dep.license ? "ok" : "unknown");
|
|
3206
|
-
lines.push(`${dep.package_name} | ${dep.ecosystem} | ${license} | ${status}`);
|
|
3207
|
-
}
|
|
3208
|
-
const denyCount = violations.filter((v) => v.severity === "deny").length;
|
|
3209
|
-
const warnCount = violations.filter((v) => v.severity === "warn").length;
|
|
3210
|
-
const unknownCount = deps.filter((d) => !d.license).length;
|
|
3211
|
-
lines.push("");
|
|
3212
|
-
if (denyCount > 0) lines.push(`${denyCount} denied license${denyCount !== 1 ? "s" : ""}`);
|
|
3213
|
-
if (warnCount > 0) lines.push(`${warnCount} license warning${warnCount !== 1 ? "s" : ""}`);
|
|
3214
|
-
if (unknownCount > 0) lines.push(`${unknownCount} unknown license${unknownCount !== 1 ? "s" : ""}`);
|
|
3215
|
-
if (denyCount === 0 && warnCount === 0) lines.push("No policy violations");
|
|
3216
|
-
return truncate(lines.join("\n"));
|
|
3217
|
-
}
|
|
3218
|
-
case "kirograph_attack_surface": {
|
|
3219
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3220
|
-
const projectRoot = cg.getProjectRoot();
|
|
3221
|
-
const config = await loadConfig(projectRoot);
|
|
3222
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3223
|
-
const db = cg.getDatabase();
|
|
3224
|
-
db.applySecuritySchema();
|
|
3225
|
-
const { AttackSurfaceAnalyzer } = await Promise.resolve().then(() => require("../security/attack-surface.js"));
|
|
3226
|
-
const analyzer = new AttackSurfaceAnalyzer(db);
|
|
3227
|
-
const result = await analyzer.analyze();
|
|
3228
|
-
if (result.totalRoutes === 0) {
|
|
3229
|
-
return "No route nodes found in the graph. Ensure the project has been indexed with architecture analysis enabled.";
|
|
3230
|
-
}
|
|
3231
|
-
const limit = clampLimit(args.limit, 20);
|
|
3232
|
-
const routes = args.publicOnly === true ? result.criticalPaths.filter((r) => r.exposureLevel === "public") : result.criticalPaths;
|
|
3233
|
-
const lines = [
|
|
3234
|
-
"# Attack Surface",
|
|
3235
|
-
"",
|
|
3236
|
-
`Total routes: ${result.totalRoutes} Public: ${result.publicRoutes} Authenticated: ${result.authenticatedRoutes} Routes with vulns: ${result.routesWithVulns}`,
|
|
3237
|
-
""
|
|
3238
|
-
];
|
|
3239
|
-
const displayed = routes.slice(0, limit);
|
|
3240
|
-
if (displayed.length === 0) {
|
|
3241
|
-
lines.push(args.publicOnly === true ? "No public routes with vulnerable dependencies found." : "No routes with vulnerable dependencies found.");
|
|
3242
|
-
} else {
|
|
3243
|
-
for (const entry of displayed) {
|
|
3244
|
-
const authTag = entry.isAuthenticated ? "[auth]" : "[public]";
|
|
3245
|
-
const riskTag = entry.riskScore > 0 ? ` risk=${entry.riskScore.toFixed(1)}` : "";
|
|
3246
|
-
lines.push(`${authTag} ${entry.route} (${entry.exposureLevel})${riskTag} ${entry.filePath}`);
|
|
3247
|
-
for (const dep of entry.vulnerableDeps.slice(0, 3)) {
|
|
3248
|
-
lines.push(` \u2514 ${dep.cveId} via ${dep.packageName} (${dep.hopCount} hop${dep.hopCount !== 1 ? "s" : ""}${dep.verdict ? `, ${dep.verdict}` : ""})`);
|
|
3249
|
-
}
|
|
3250
|
-
if (entry.vulnerableDeps.length > 3) {
|
|
3251
|
-
lines.push(` \u2514 \u2026and ${entry.vulnerableDeps.length - 3} more vulns`);
|
|
3252
|
-
}
|
|
3253
|
-
}
|
|
3254
|
-
if (routes.length > limit) {
|
|
3255
|
-
lines.push("", `\u2026and ${routes.length - limit} more routes (increase limit to see all)`);
|
|
3256
|
-
}
|
|
3257
|
-
}
|
|
3258
|
-
return truncate(lines.join("\n"));
|
|
3259
|
-
}
|
|
3260
|
-
case "kirograph_secrets": {
|
|
3261
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3262
|
-
const projectRoot = cg.getProjectRoot();
|
|
3263
|
-
const config = await loadConfig(projectRoot);
|
|
3264
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3265
|
-
const db = cg.getDatabase();
|
|
3266
|
-
db.applySecuritySchema();
|
|
3267
|
-
const { SecretsScanner } = await Promise.resolve().then(() => require("../security/secrets.js"));
|
|
3268
|
-
const scanner = new SecretsScanner(db, projectRoot);
|
|
3269
|
-
const result = await scanner.scan({ includeTests: args.includeTests === true });
|
|
3270
|
-
if (result.totalFindings === 0) {
|
|
3271
|
-
return `No secrets found. Scanned ${result.filesScanned} file${result.filesScanned !== 1 ? "s" : ""}.`;
|
|
3272
|
-
}
|
|
3273
|
-
let findings = result.findings;
|
|
3274
|
-
if (args.severity) {
|
|
3275
|
-
findings = findings.filter((f) => f.severity === args.severity);
|
|
3276
|
-
}
|
|
3277
|
-
if (findings.length === 0) {
|
|
3278
|
-
return `No secrets found matching severity "${args.severity}". Total findings (all severities): ${result.totalFindings}.`;
|
|
3279
|
-
}
|
|
3280
|
-
const lines = [
|
|
3281
|
-
`# Secrets Scan (${result.filesScanned} files scanned)`,
|
|
3282
|
-
"",
|
|
3283
|
-
`Found: ${result.totalFindings} Critical: ${result.criticalCount} High: ${result.highCount}`,
|
|
3284
|
-
""
|
|
3285
|
-
];
|
|
3286
|
-
for (const f of findings) {
|
|
3287
|
-
lines.push(`${f.severity.toUpperCase()} ${f.type}`);
|
|
3288
|
-
lines.push(` ${f.filePath}:${f.line}:${f.column} snippet: ${f.snippet}`);
|
|
3289
|
-
if (f.nodeName) {
|
|
3290
|
-
lines.push(` in function: ${f.nodeName}`);
|
|
3291
|
-
}
|
|
3292
|
-
if (f.entryPointCount > 0) {
|
|
3293
|
-
lines.push(` reachable from ${f.entryPointCount} entry point${f.entryPointCount !== 1 ? "s" : ""}`);
|
|
3294
|
-
}
|
|
3295
|
-
}
|
|
3296
|
-
return truncate(lines.join("\n"));
|
|
3297
|
-
}
|
|
3298
|
-
case "kirograph_security_flows": {
|
|
3299
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3300
|
-
const projectRoot = cg.getProjectRoot();
|
|
3301
|
-
const config = await loadConfig(projectRoot);
|
|
3302
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3303
|
-
const db = cg.getDatabase();
|
|
3304
|
-
db.applySecuritySchema();
|
|
3305
|
-
const { DataFlowAnalyzer } = await Promise.resolve().then(() => require("../security/data-flows.js"));
|
|
3306
|
-
const analyzer = new DataFlowAnalyzer(db);
|
|
3307
|
-
let findings = await analyzer.analyze();
|
|
3308
|
-
const typeFilter = args.type ?? "all";
|
|
3309
|
-
const TYPE_MAP = {
|
|
3310
|
-
sql: "sql-injection",
|
|
3311
|
-
eval: "dangerous-eval",
|
|
3312
|
-
deserialize: "unsafe-deserialize",
|
|
3313
|
-
path: "path-traversal",
|
|
3314
|
-
crypto: "hardcoded-crypto"
|
|
3315
|
-
};
|
|
3316
|
-
if (typeFilter !== "all") {
|
|
3317
|
-
const mapped = TYPE_MAP[typeFilter];
|
|
3318
|
-
if (!mapped) return `Invalid type filter "${typeFilter}". Use: sql, eval, deserialize, path, crypto, all`;
|
|
3319
|
-
findings = findings.filter((f) => f.type === mapped);
|
|
3320
|
-
}
|
|
3321
|
-
if (findings.length === 0) {
|
|
3322
|
-
return typeFilter === "all" ? "No dangerous data flows detected." : `No "${typeFilter}" findings detected.`;
|
|
3323
|
-
}
|
|
3324
|
-
const lines = [
|
|
3325
|
-
`# Security Flows (${findings.length} finding${findings.length !== 1 ? "s" : ""})`,
|
|
3326
|
-
""
|
|
3327
|
-
];
|
|
3328
|
-
for (const f of findings) {
|
|
3329
|
-
lines.push(`${f.severity.toUpperCase()} [${f.owaspCategory}] ${f.type}`);
|
|
3330
|
-
lines.push(` ${f.filePath}:${f.line} symbol: ${f.symbol}`);
|
|
3331
|
-
lines.push(` ${f.description}`);
|
|
3332
|
-
lines.push(` Fix: ${f.recommendation}`);
|
|
3333
|
-
lines.push("");
|
|
3334
|
-
}
|
|
3335
|
-
return truncate(lines.join("\n"));
|
|
3336
|
-
}
|
|
3337
|
-
case "kirograph_supply_chain": {
|
|
3338
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3339
|
-
const projectRoot = cg.getProjectRoot();
|
|
3340
|
-
const config = await loadConfig(projectRoot);
|
|
3341
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3342
|
-
const db = cg.getDatabase();
|
|
3343
|
-
db.applySecuritySchema();
|
|
3344
|
-
const { SupplyChainChecker } = await Promise.resolve().then(() => require("../security/supply-chain.js"));
|
|
3345
|
-
const checker = new SupplyChainChecker(db);
|
|
3346
|
-
const { results, errors } = await checker.checkAll();
|
|
3347
|
-
const RISK_ORDER = { critical: 0, high: 1, medium: 2, low: 3, unknown: 4 };
|
|
3348
|
-
const thresholdArg = args.threshold;
|
|
3349
|
-
const thresholdOrder = thresholdArg ? RISK_ORDER[thresholdArg] ?? 4 : 2;
|
|
3350
|
-
const filtered = results.filter((r) => (RISK_ORDER[r.riskLevel] ?? 4) <= thresholdOrder).sort((a, b) => (RISK_ORDER[a.riskLevel] ?? 4) - (RISK_ORDER[b.riskLevel] ?? 4));
|
|
3351
|
-
if (filtered.length === 0) {
|
|
3352
|
-
return `No supply chain risks found at threshold "${thresholdArg ?? "medium"}" or above. Checked ${results.length} dependencies.`;
|
|
3353
|
-
}
|
|
3354
|
-
const lines = [
|
|
3355
|
-
`# Supply Chain Health (${filtered.length} risks, threshold: ${thresholdArg ?? "medium"})`,
|
|
3356
|
-
""
|
|
3357
|
-
];
|
|
3358
|
-
for (const r of filtered) {
|
|
3359
|
-
const scoreStr = r.scorecardScore !== null ? ` scorecard=${r.scorecardScore.toFixed(1)}/10` : "";
|
|
3360
|
-
const maintainerStr = r.maintainerCount !== null ? ` maintainers=${r.maintainerCount}` : "";
|
|
3361
|
-
lines.push(`${r.riskLevel.toUpperCase()} ${r.packageName} (${r.ecosystem})${scoreStr}${maintainerStr}`);
|
|
3362
|
-
for (const reason of r.riskReasons) {
|
|
3363
|
-
lines.push(` \u2022 ${reason}`);
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
if (errors.length > 0) {
|
|
3367
|
-
lines.push("", `\u26A0 ${errors.length} package${errors.length !== 1 ? "s" : ""} could not be checked (network errors)`);
|
|
3368
|
-
}
|
|
3369
|
-
return truncate(lines.join("\n"));
|
|
3370
|
-
}
|
|
3371
|
-
case "kirograph_dep_confusion": {
|
|
3372
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3373
|
-
const projectRoot = cg.getProjectRoot();
|
|
3374
|
-
const config = await loadConfig(projectRoot);
|
|
3375
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3376
|
-
const db = cg.getDatabase();
|
|
3377
|
-
db.applySecuritySchema();
|
|
3378
|
-
const { DepConfusionChecker } = await Promise.resolve().then(() => require("../security/dep-confusion.js"));
|
|
3379
|
-
const checker = new DepConfusionChecker(db);
|
|
3380
|
-
const findings = await checker.check();
|
|
3381
|
-
if (findings.length === 0) {
|
|
3382
|
-
return "No dependency confusion vulnerabilities detected.";
|
|
3383
|
-
}
|
|
3384
|
-
const RISK_ORDER = { critical: 0, high: 1, medium: 2 };
|
|
3385
|
-
const sorted = [...findings].sort((a, b) => (RISK_ORDER[a.riskLevel] ?? 3) - (RISK_ORDER[b.riskLevel] ?? 3));
|
|
3386
|
-
const lines = [
|
|
3387
|
-
`# Dependency Confusion (${findings.length} finding${findings.length !== 1 ? "s" : ""})`,
|
|
3388
|
-
""
|
|
3389
|
-
];
|
|
3390
|
-
for (const f of sorted) {
|
|
3391
|
-
lines.push(`${f.riskLevel.toUpperCase()} ${f.packageName} (${f.ecosystem})`);
|
|
3392
|
-
lines.push(` ${f.explanation}`);
|
|
3393
|
-
if (f.publicExists && f.publicVersion) {
|
|
3394
|
-
lines.push(` Public version: ${f.publicVersion}${f.publicPublishedAt ? ` published: ${f.publicPublishedAt}` : ""}`);
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3397
|
-
return truncate(lines.join("\n"));
|
|
3398
|
-
}
|
|
3399
|
-
case "kirograph_remediation": {
|
|
3400
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3401
|
-
const projectRoot = cg.getProjectRoot();
|
|
3402
|
-
const config = await loadConfig(projectRoot);
|
|
3403
|
-
if (!config.enableSecurity) return "Security analysis is not enabled. Set enableSecurity: true in .kirograph/config.json and run kirograph index.";
|
|
3404
|
-
const db = cg.getDatabase();
|
|
3405
|
-
db.applySecuritySchema();
|
|
3406
|
-
const { RemediationTracker } = await Promise.resolve().then(() => require("../security/remediation.js"));
|
|
3407
|
-
const tracker = new RemediationTracker(db);
|
|
3408
|
-
let statuses = tracker.getStatus();
|
|
3409
|
-
if (args.overdueOnly === true) {
|
|
3410
|
-
statuses = statuses.filter((s) => s.isOverdue);
|
|
3411
|
-
}
|
|
3412
|
-
if (statuses.length === 0) {
|
|
3413
|
-
return args.overdueOnly === true ? "No overdue vulnerabilities found." : "No open vulnerabilities with SLA tracking data found.";
|
|
3414
|
-
}
|
|
3415
|
-
const SLA_ORDER = { overdue: 0, warning: 1, no_fix: 2, ok: 3 };
|
|
3416
|
-
statuses.sort((a, b) => {
|
|
3417
|
-
const slaA = SLA_ORDER[a.slaStatus] ?? 3;
|
|
3418
|
-
const slaB = SLA_ORDER[b.slaStatus] ?? 3;
|
|
3419
|
-
if (slaA !== slaB) return slaA - slaB;
|
|
3420
|
-
return (b.severity ?? 0) - (a.severity ?? 0);
|
|
3421
|
-
});
|
|
3422
|
-
const overdueCount = statuses.filter((s) => s.isOverdue).length;
|
|
3423
|
-
const warningCount = statuses.filter((s) => s.slaStatus === "warning").length;
|
|
3424
|
-
const lines = [
|
|
3425
|
-
`# Remediation SLA (${statuses.length} open${overdueCount > 0 ? `, ${overdueCount} overdue` : ""}${warningCount > 0 ? `, ${warningCount} warning` : ""})`,
|
|
3426
|
-
""
|
|
3427
|
-
];
|
|
3428
|
-
for (const s of statuses) {
|
|
3429
|
-
const severityLabel = s.severity == null ? "UNKNOWN" : s.severity >= 9 ? "CRITICAL" : s.severity >= 7 ? "HIGH" : s.severity >= 4 ? "MEDIUM" : "LOW";
|
|
3430
|
-
const slaTag = s.slaStatus === "overdue" ? "[OVERDUE]" : s.slaStatus === "warning" ? "[WARNING]" : s.slaStatus === "no_fix" ? "[NO_FIX]" : "[OK]";
|
|
3431
|
-
lines.push(`${slaTag} ${severityLabel} ${s.cveId} ${s.packageName}`);
|
|
3432
|
-
if (s.daysOpen !== null) lines.push(` Open for ${s.daysOpen} day${s.daysOpen !== 1 ? "s" : ""}`);
|
|
3433
|
-
if (s.daysWithFixAvailable !== null) lines.push(` Fix available for ${s.daysWithFixAvailable} day${s.daysWithFixAvailable !== 1 ? "s" : ""}`);
|
|
3434
|
-
if (s.slaDeadline !== null) {
|
|
3435
|
-
const deadlineDate = new Date(s.slaDeadline).toISOString().slice(0, 10);
|
|
3436
|
-
lines.push(` SLA deadline: ${deadlineDate}`);
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
return truncate(lines.join("\n"));
|
|
3440
|
-
}
|
|
3441
|
-
case "kirograph_live_search": {
|
|
3442
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3443
|
-
const projectRoot = cg.getProjectRoot();
|
|
3444
|
-
const config = await loadConfig(projectRoot);
|
|
3445
|
-
if (!config.enablePatterns) {
|
|
3446
|
-
return "kirograph_live_search requires enablePatterns: true in .kirograph/config.json";
|
|
3447
|
-
}
|
|
3448
|
-
const { PatternRunner } = await Promise.resolve().then(() => require("../patterns/runner.js"));
|
|
3449
|
-
const runner = new PatternRunner();
|
|
3450
|
-
if (!runner.isAvailable()) {
|
|
3451
|
-
return "kirograph_live_search requires @ast-grep/napi. Run: npm install @ast-grep/napi";
|
|
3452
|
-
}
|
|
3453
|
-
const pattern = args.pattern;
|
|
3454
|
-
const language = args.language;
|
|
3455
|
-
const rawLimit = typeof args.limit === "number" ? args.limit : 20;
|
|
3456
|
-
const limit = Math.max(1, Math.min(100, Math.round(rawLimit)));
|
|
3457
|
-
const db = cg.getDatabase();
|
|
3458
|
-
const rawDb = db.getRawDb();
|
|
3459
|
-
const files = rawDb.all(
|
|
3460
|
-
"SELECT path FROM files WHERE language = ? LIMIT 5000",
|
|
3461
|
-
[language]
|
|
3462
|
-
);
|
|
3463
|
-
const results = [];
|
|
3464
|
-
let truncated = false;
|
|
3465
|
-
for (const file of files) {
|
|
3466
|
-
if (results.length >= limit) {
|
|
3467
|
-
truncated = true;
|
|
3468
|
-
break;
|
|
3469
|
-
}
|
|
3470
|
-
let content;
|
|
3471
|
-
try {
|
|
3472
|
-
const fullPath = path.isAbsolute(file.path) ? file.path : path.join(projectRoot, file.path);
|
|
3473
|
-
content = fs.readFileSync(fullPath, "utf8");
|
|
3474
|
-
} catch {
|
|
3475
|
-
continue;
|
|
3476
|
-
}
|
|
3477
|
-
const matches = await runner.runInline(pattern, language, content);
|
|
3478
|
-
for (const m of matches) {
|
|
3479
|
-
if (results.length >= limit) {
|
|
3480
|
-
truncated = true;
|
|
3481
|
-
break;
|
|
3482
|
-
}
|
|
3483
|
-
results.push({ filePath: file.path, line: m.line, matchText: m.matchText });
|
|
3484
|
-
}
|
|
3485
|
-
}
|
|
3486
|
-
if (results.length === 0) {
|
|
3487
|
-
return `0 matches for '${pattern}' in ${language} files`;
|
|
3488
|
-
}
|
|
3489
|
-
const lines = [
|
|
3490
|
-
`${results.length} match${results.length !== 1 ? "es" : ""} for '${pattern}' in ${language} files`,
|
|
3491
|
-
""
|
|
3492
|
-
];
|
|
3493
|
-
for (const r of results) {
|
|
3494
|
-
lines.push(` ${r.filePath}:${r.line}`);
|
|
3495
|
-
lines.push(` ${r.matchText}`);
|
|
3496
|
-
lines.push("");
|
|
3497
|
-
}
|
|
3498
|
-
if (truncated) {
|
|
3499
|
-
lines.push(`[truncated \u2014 showing first ${limit} results; refine your pattern or use --lang to narrow scope]`);
|
|
3500
|
-
}
|
|
3501
|
-
return truncate(lines.join("\n"));
|
|
3502
|
-
}
|
|
3503
|
-
// ── Pattern tools ──────────────────────────────────────────────────────────
|
|
3504
|
-
case "kirograph_pattern_coverage": {
|
|
3505
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3506
|
-
const projectRoot = cg.getProjectRoot();
|
|
3507
|
-
const config = await loadConfig(projectRoot);
|
|
3508
|
-
if (!config.enablePatterns) {
|
|
3509
|
-
return "Pattern analysis is not enabled. Set enablePatterns: true in .kirograph/config.json and run kirograph index.";
|
|
3510
|
-
}
|
|
3511
|
-
const { PatternLibraryLoader } = await Promise.resolve().then(() => require("../patterns/loader.js"));
|
|
3512
|
-
const loader = new PatternLibraryLoader();
|
|
3513
|
-
const builtinPath = path.join(__dirname, "../patterns/library");
|
|
3514
|
-
const customPath = config.patternLibraryPath;
|
|
3515
|
-
let rules = [];
|
|
3516
|
-
try {
|
|
3517
|
-
rules = loader.load(builtinPath, customPath);
|
|
3518
|
-
} catch {
|
|
3519
|
-
}
|
|
3520
|
-
const rulesByCategory = /* @__PURE__ */ new Map();
|
|
3521
|
-
for (const rule of rules) {
|
|
3522
|
-
const prefix = rule.owaspCategory.match(/^(A\d{2})/)?.[1] ?? rule.owaspCategory;
|
|
3523
|
-
if (!rulesByCategory.has(prefix)) rulesByCategory.set(prefix, []);
|
|
3524
|
-
rulesByCategory.get(prefix).push(rule);
|
|
3525
|
-
}
|
|
3526
|
-
const db = cg.getDatabase();
|
|
3527
|
-
db.applyPatternsSchema();
|
|
3528
|
-
const rawDb = db.getRawDb();
|
|
3529
|
-
const matchCountsByCategory = /* @__PURE__ */ new Map();
|
|
3530
|
-
try {
|
|
3531
|
-
const tableExists = rawDb.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
3532
|
-
if (tableExists) {
|
|
3533
|
-
const rows = rawDb.all(
|
|
3534
|
-
"SELECT owasp_category, COUNT(*) as cnt FROM pattern_matches GROUP BY owasp_category"
|
|
3535
|
-
);
|
|
3536
|
-
for (const row of rows) {
|
|
3537
|
-
const prefix = row.owasp_category.match(/^(A\d{2})/)?.[1] ?? row.owasp_category;
|
|
3538
|
-
matchCountsByCategory.set(prefix, (matchCountsByCategory.get(prefix) ?? 0) + row.cnt);
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
} catch {
|
|
3542
|
-
}
|
|
3543
|
-
const owaspTop10 = [
|
|
3544
|
-
"A01",
|
|
3545
|
-
"A02",
|
|
3546
|
-
"A03",
|
|
3547
|
-
"A04",
|
|
3548
|
-
"A05",
|
|
3549
|
-
"A06",
|
|
3550
|
-
"A07",
|
|
3551
|
-
"A08",
|
|
3552
|
-
"A09",
|
|
3553
|
-
"A10"
|
|
3554
|
-
];
|
|
3555
|
-
const owaspNames = {
|
|
3556
|
-
A01: "Broken Access Control",
|
|
3557
|
-
A02: "Cryptographic Failures",
|
|
3558
|
-
A03: "Injection",
|
|
3559
|
-
A04: "Insecure Design",
|
|
3560
|
-
A05: "Security Misconfiguration",
|
|
3561
|
-
A06: "Vulnerable and Outdated Components",
|
|
3562
|
-
A07: "Identification and Authentication Failures",
|
|
3563
|
-
A08: "Software and Data Integrity Failures",
|
|
3564
|
-
A09: "Security Logging and Monitoring Failures",
|
|
3565
|
-
A10: "Server-Side Request Forgery"
|
|
3566
|
-
};
|
|
3567
|
-
const covered = [];
|
|
3568
|
-
const uncovered = [];
|
|
3569
|
-
const lines = [
|
|
3570
|
-
"# OWASP Top 10 Pattern Coverage",
|
|
3571
|
-
"",
|
|
3572
|
-
`Total rules loaded: ${rules.length}`,
|
|
3573
|
-
"",
|
|
3574
|
-
"Category | Rules | Matches",
|
|
3575
|
-
"-------- | ----- | -------"
|
|
3576
|
-
];
|
|
3577
|
-
for (const cat of owaspTop10) {
|
|
3578
|
-
const catRules = rulesByCategory.get(cat) ?? [];
|
|
3579
|
-
const matchCount = matchCountsByCategory.get(cat) ?? 0;
|
|
3580
|
-
const label = `${cat}: ${owaspNames[cat] ?? cat}`;
|
|
3581
|
-
if (catRules.length > 0) {
|
|
3582
|
-
covered.push(label);
|
|
3583
|
-
lines.push(`${label} | ${catRules.length} | ${matchCount}`);
|
|
3584
|
-
} else {
|
|
3585
|
-
uncovered.push(label);
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3588
|
-
for (const [cat, catRules] of rulesByCategory.entries()) {
|
|
3589
|
-
if (!owaspTop10.includes(cat)) {
|
|
3590
|
-
const matchCount = matchCountsByCategory.get(cat) ?? 0;
|
|
3591
|
-
lines.push(`${cat} | ${catRules.length} | ${matchCount}`);
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
if (uncovered.length > 0) {
|
|
3595
|
-
lines.push("", `Uncovered categories (${uncovered.length}):`, ...uncovered.map((c) => ` - ${c}`));
|
|
3596
|
-
}
|
|
3597
|
-
lines.push("", `Coverage: ${covered.length} / ${owaspTop10.length} OWASP Top 10 categories`);
|
|
3598
|
-
return lines.join("\n");
|
|
3599
|
-
}
|
|
3600
|
-
case "kirograph_pattern_save_baseline": {
|
|
3601
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3602
|
-
const projectRoot = cg.getProjectRoot();
|
|
3603
|
-
const config = await loadConfig(projectRoot);
|
|
3604
|
-
if (!config.enablePatterns) {
|
|
3605
|
-
return "Pattern analysis is not enabled. Set enablePatterns: true in .kirograph/config.json and run kirograph index.";
|
|
3606
|
-
}
|
|
3607
|
-
const label = args.label ?? "default";
|
|
3608
|
-
const db = cg.getDatabase();
|
|
3609
|
-
db.applyPatternsSchema();
|
|
3610
|
-
const rawDb = db.getRawDb();
|
|
3611
|
-
const tableExists = rawDb.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
3612
|
-
if (!tableExists) {
|
|
3613
|
-
return "No pattern_matches table found. Run kirograph index first to populate pattern data.";
|
|
3614
|
-
}
|
|
3615
|
-
const rows = rawDb.all(
|
|
3616
|
-
"SELECT pattern_id, COUNT(*) as cnt FROM pattern_matches GROUP BY pattern_id"
|
|
3617
|
-
);
|
|
3618
|
-
const baseline = {};
|
|
3619
|
-
for (const row of rows) {
|
|
3620
|
-
baseline[row.pattern_id] = row.cnt;
|
|
3621
|
-
}
|
|
3622
|
-
const kirographDir = path.join(projectRoot, ".kirograph");
|
|
3623
|
-
if (!fs.existsSync(kirographDir)) fs.mkdirSync(kirographDir, { recursive: true });
|
|
3624
|
-
const baselinePath = path.join(kirographDir, `pattern-baseline-${label}.json`);
|
|
3625
|
-
fs.writeFileSync(baselinePath, JSON.stringify({ label, savedAt: Date.now(), counts: baseline }, null, 2));
|
|
3626
|
-
const totalMatches = Object.values(baseline).reduce((s, c) => s + c, 0);
|
|
3627
|
-
return `Baseline "${label}" saved: ${Object.keys(baseline).length} patterns, ${totalMatches} total matches \u2192 ${baselinePath}`;
|
|
3628
|
-
}
|
|
3629
|
-
case "kirograph_pattern_diff": {
|
|
3630
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3631
|
-
const projectRoot = cg.getProjectRoot();
|
|
3632
|
-
const config = await loadConfig(projectRoot);
|
|
3633
|
-
if (!config.enablePatterns) {
|
|
3634
|
-
return "Pattern analysis is not enabled. Set enablePatterns: true in .kirograph/config.json and run kirograph index.";
|
|
3635
|
-
}
|
|
3636
|
-
const label = args.label ?? "default";
|
|
3637
|
-
const baselinePath = path.join(projectRoot, ".kirograph", `pattern-baseline-${label}.json`);
|
|
3638
|
-
if (!fs.existsSync(baselinePath)) {
|
|
3639
|
-
return `Baseline "${label}" not found. Run kirograph_pattern_save_baseline first.`;
|
|
3640
|
-
}
|
|
3641
|
-
let baseline;
|
|
3642
|
-
try {
|
|
3643
|
-
baseline = JSON.parse(fs.readFileSync(baselinePath, "utf8"));
|
|
3644
|
-
} catch {
|
|
3645
|
-
return `Failed to read baseline file: ${baselinePath}`;
|
|
3646
|
-
}
|
|
3647
|
-
const db = cg.getDatabase();
|
|
3648
|
-
db.applyPatternsSchema();
|
|
3649
|
-
const rawDb = db.getRawDb();
|
|
3650
|
-
const tableExists = rawDb.get("SELECT name FROM sqlite_master WHERE type='table' AND name='pattern_matches'");
|
|
3651
|
-
const currentCounts = {};
|
|
3652
|
-
if (tableExists) {
|
|
3653
|
-
const rows = rawDb.all(
|
|
3654
|
-
"SELECT pattern_id, COUNT(*) as cnt FROM pattern_matches GROUP BY pattern_id"
|
|
3655
|
-
);
|
|
3656
|
-
for (const row of rows) {
|
|
3657
|
-
currentCounts[row.pattern_id] = row.cnt;
|
|
3658
|
-
}
|
|
3659
|
-
}
|
|
3660
|
-
const allPatternIds = /* @__PURE__ */ new Set([...Object.keys(baseline.counts), ...Object.keys(currentCounts)]);
|
|
3661
|
-
const newFindings = [];
|
|
3662
|
-
const resolvedFindings = [];
|
|
3663
|
-
const unchanged = [];
|
|
3664
|
-
const changed = [];
|
|
3665
|
-
for (const id of allPatternIds) {
|
|
3666
|
-
const before = baseline.counts[id] ?? 0;
|
|
3667
|
-
const after = currentCounts[id] ?? 0;
|
|
3668
|
-
if (before === 0 && after > 0) {
|
|
3669
|
-
newFindings.push({ id, count: after });
|
|
3670
|
-
} else if (before > 0 && after === 0) {
|
|
3671
|
-
resolvedFindings.push({ id, count: before });
|
|
3672
|
-
} else if (before === after) {
|
|
3673
|
-
unchanged.push({ id, count: after });
|
|
3674
|
-
} else {
|
|
3675
|
-
changed.push({ id, before, after, delta: after - before });
|
|
3676
|
-
}
|
|
3677
|
-
}
|
|
3678
|
-
const savedDate = new Date(baseline.savedAt).toISOString().slice(0, 16).replace("T", " ");
|
|
3679
|
-
const lines = [
|
|
3680
|
-
`# Pattern Diff: "${label}" (saved ${savedDate}) \u2192 current`,
|
|
3681
|
-
"",
|
|
3682
|
-
`NEW: ${newFindings.length} RESOLVED: ${resolvedFindings.length} CHANGED: ${changed.length} UNCHANGED: ${unchanged.length}`
|
|
3683
|
-
];
|
|
3684
|
-
if (newFindings.length > 0) {
|
|
3685
|
-
lines.push("", "## New Findings (not in baseline)");
|
|
3686
|
-
for (const f of newFindings.sort((a, b) => b.count - a.count)) {
|
|
3687
|
-
lines.push(` + ${f.id}: ${f.count} matches`);
|
|
3688
|
-
}
|
|
3689
|
-
}
|
|
3690
|
-
if (resolvedFindings.length > 0) {
|
|
3691
|
-
lines.push("", "## Resolved (in baseline, not in current)");
|
|
3692
|
-
for (const f of resolvedFindings.sort((a, b) => b.count - a.count)) {
|
|
3693
|
-
lines.push(` - ${f.id}: was ${f.count} matches`);
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
if (changed.length > 0) {
|
|
3697
|
-
lines.push("", "## Changed");
|
|
3698
|
-
for (const f of changed.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta))) {
|
|
3699
|
-
const sign = f.delta > 0 ? "+" : "";
|
|
3700
|
-
lines.push(` ~ ${f.id}: ${f.before} \u2192 ${f.after} (${sign}${f.delta})`);
|
|
3701
|
-
}
|
|
3702
|
-
}
|
|
3703
|
-
return lines.join("\n");
|
|
3704
|
-
}
|
|
3705
|
-
// ── Wiki tools ────────────────────────────────────────────────────────────
|
|
3706
|
-
case "kirograph_wiki_ingest": {
|
|
3707
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3708
|
-
const projectRoot = cg.getProjectRoot();
|
|
3709
|
-
const config = await loadConfig(projectRoot);
|
|
3710
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
3711
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
3712
|
-
const db = cg.getDatabase();
|
|
3713
|
-
db.applyWikiSchema();
|
|
3714
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph", {
|
|
3715
|
-
autoResolveConflicts: config.wikiAutoResolveConflicts ?? false
|
|
3716
|
-
});
|
|
3717
|
-
wiki.initialize();
|
|
3718
|
-
const source = args.source ?? "";
|
|
3719
|
-
const sourceName = args.sourceName ?? "source";
|
|
3720
|
-
if (!source) return "source is required";
|
|
3721
|
-
if (config.wikiSynthesisMode === "local") {
|
|
3722
|
-
wiki.queueSource(source, sourceName);
|
|
3723
|
-
const count = wiki.getQueueCount();
|
|
3724
|
-
return `Source "${sourceName}" queued for local wiki synthesis (${count} in queue). The agentStop hook will run "kirograph wiki synthesize" automatically.`;
|
|
3725
|
-
}
|
|
3726
|
-
const prompt = wiki.getIngestPrompt(source, sourceName);
|
|
3727
|
-
return `WIKI_INGEST_PROMPT
|
|
3728
|
-
${prompt}
|
|
3729
|
-
|
|
3730
|
-
Produce WIKI_DIFF blocks following SCHEMA.md, then call kirograph_wiki_apply_diff with the diff string.`;
|
|
3731
|
-
}
|
|
3732
|
-
case "kirograph_wiki_apply_diff": {
|
|
3733
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3734
|
-
const projectRoot = cg.getProjectRoot();
|
|
3735
|
-
const config = await loadConfig(projectRoot);
|
|
3736
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
3737
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
3738
|
-
const db = cg.getDatabase();
|
|
3739
|
-
db.applyWikiSchema();
|
|
3740
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph", {
|
|
3741
|
-
autoResolveConflicts: config.wikiAutoResolveConflicts ?? false
|
|
3742
|
-
});
|
|
3743
|
-
wiki.initialize();
|
|
3744
|
-
const diff = args.diff ?? "";
|
|
3745
|
-
if (!diff) return "diff is required";
|
|
3746
|
-
const result = wiki.applyDiff(diff);
|
|
3747
|
-
const lines = ["\u2713 Wiki diff applied"];
|
|
3748
|
-
if (result.created.length) lines.push(` Created: ${result.created.join(", ")}`);
|
|
3749
|
-
if (result.updated.length) lines.push(` Updated: ${result.updated.join(", ")}`);
|
|
3750
|
-
if (result.conflictsResolved.length) lines.push(` Conflicts auto-resolved: ${result.conflictsResolved.join(", ")}`);
|
|
3751
|
-
if (result.conflictsPending.length) {
|
|
3752
|
-
lines.push(` \u26A1 Conflicts pending (${result.conflictsPending.length}):`);
|
|
3753
|
-
for (const c of result.conflictsPending) {
|
|
3754
|
-
lines.push(` ${c.page} \xA7${c.section}: "${c.existing}" vs "${c.incoming}" (source: ${c.source})`);
|
|
3755
|
-
}
|
|
3756
|
-
}
|
|
3757
|
-
return lines.join("\n");
|
|
3758
|
-
}
|
|
3759
|
-
case "kirograph_wiki_search": {
|
|
3760
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3761
|
-
const projectRoot = cg.getProjectRoot();
|
|
3762
|
-
const config = await loadConfig(projectRoot);
|
|
3763
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
3764
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
3765
|
-
const db = cg.getDatabase();
|
|
3766
|
-
db.applyWikiSchema();
|
|
3767
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
3768
|
-
wiki.initialize();
|
|
3769
|
-
const query = args.query ?? "";
|
|
3770
|
-
if (!query) return "query is required";
|
|
3771
|
-
const limit = Math.min(Number(args.limit ?? 5), 20);
|
|
3772
|
-
const results = wiki.search(query, limit);
|
|
3773
|
-
if (results.length === 0) return `No wiki pages found for "${query}".`;
|
|
3774
|
-
const lines = [`Wiki Search: ${query}`, ""];
|
|
3775
|
-
for (const { page, score } of results) {
|
|
3776
|
-
lines.push(`[${page.slug}] ${page.title} (score: ${score.toFixed(3)})`);
|
|
3777
|
-
const preview = page.content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
|
|
3778
|
-
if (preview) lines.push(` ${preview.trim().slice(0, 120)}`);
|
|
3779
|
-
lines.push("");
|
|
3780
|
-
}
|
|
3781
|
-
return lines.join("\n").trim();
|
|
3782
|
-
}
|
|
3783
|
-
case "kirograph_wiki_page": {
|
|
3784
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3785
|
-
const projectRoot = cg.getProjectRoot();
|
|
3786
|
-
const config = await loadConfig(projectRoot);
|
|
3787
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
3788
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
3789
|
-
const db = cg.getDatabase();
|
|
3790
|
-
db.applyWikiSchema();
|
|
3791
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
3792
|
-
wiki.initialize();
|
|
3793
|
-
const slug = args.slug ?? "";
|
|
3794
|
-
if (!slug) return "slug is required";
|
|
3795
|
-
const page = wiki.getPage(slug);
|
|
3796
|
-
if (!page) return `Wiki page "${slug}" not found. Use kirograph_wiki_list to see available pages.`;
|
|
3797
|
-
return page.content;
|
|
3798
|
-
}
|
|
3799
|
-
case "kirograph_wiki_lint": {
|
|
3800
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3801
|
-
const projectRoot = cg.getProjectRoot();
|
|
3802
|
-
const config = await loadConfig(projectRoot);
|
|
3803
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
3804
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
3805
|
-
const db = cg.getDatabase();
|
|
3806
|
-
db.applyWikiSchema();
|
|
3807
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
3808
|
-
wiki.initialize();
|
|
3809
|
-
const issues = wiki.lint();
|
|
3810
|
-
if (issues.length === 0) return "\u2713 Wiki lint passed \u2014 no issues found.";
|
|
3811
|
-
const lines = [`Wiki Lint \u2014 ${issues.length} issue(s)`, ""];
|
|
3812
|
-
for (const issue of issues) {
|
|
3813
|
-
const icon = issue.kind === "contradiction" ? "\u26A1" : issue.kind === "orphan" ? "\u25CB" : issue.kind === "broken_link" ? "\u{1F517}" : "\u26A0";
|
|
3814
|
-
lines.push(`${icon} [${issue.kind}] ${issue.slug}`);
|
|
3815
|
-
lines.push(` ${issue.detail}`);
|
|
3816
|
-
lines.push("");
|
|
3817
|
-
}
|
|
3818
|
-
return lines.join("\n").trim();
|
|
3819
|
-
}
|
|
3820
|
-
case "kirograph_wiki_list": {
|
|
3821
|
-
const { loadConfig } = await Promise.resolve().then(() => require("../config.js"));
|
|
3822
|
-
const projectRoot = cg.getProjectRoot();
|
|
3823
|
-
const config = await loadConfig(projectRoot);
|
|
3824
|
-
if (!config.enableWiki) return "Wiki is not enabled. Set enableWiki: true in .kirograph/config.json";
|
|
3825
|
-
const { KiroGraphWiki } = await Promise.resolve().then(() => require("../wiki/index.js"));
|
|
3826
|
-
const db = cg.getDatabase();
|
|
3827
|
-
db.applyWikiSchema();
|
|
3828
|
-
const wiki = new KiroGraphWiki(db.getRawDb(), projectRoot + "/.kirograph");
|
|
3829
|
-
wiki.initialize();
|
|
3830
|
-
const pages = wiki.listPages();
|
|
3831
|
-
if (pages.length === 0) return "Wiki is empty. Use kirograph_wiki_ingest to add pages.";
|
|
3832
|
-
const stats = wiki.getStats();
|
|
3833
|
-
const lines = [
|
|
3834
|
-
`Wiki \u2014 ${stats.pageCount} page(s), ${stats.totalSources} total sources`,
|
|
3835
|
-
""
|
|
3836
|
-
];
|
|
3837
|
-
for (const page of pages) {
|
|
3838
|
-
const date = new Date(page.updatedAt).toISOString().slice(0, 10);
|
|
3839
|
-
lines.push(` ${page.slug.padEnd(32)} ${page.title} (${page.sourceCount} src, ${date})`);
|
|
3840
|
-
}
|
|
3841
|
-
return lines.join("\n");
|
|
3842
|
-
}
|
|
3843
|
-
default:
|
|
3844
|
-
return `Unknown tool: ${toolName}`;
|
|
3845
|
-
}
|
|
3846
|
-
}
|
|
3847
|
-
}
|
|
3848
1675
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3849
1676
|
0 && (module.exports = {
|
|
3850
1677
|
KIROGRAPH_TOOL_NAMES,
|