@plumpslabs/kuma 2.2.7 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,8 +4,8 @@ import {
4
4
  formatInitResults,
5
5
  generateInitMdContent,
6
6
  runInit
7
- } from "./chunk-IR6HFFDU.js";
8
- import "./chunk-T55NCW63.js";
7
+ } from "./chunk-5A6AKUJZ.js";
8
+ import "./chunk-IXMWW5WA.js";
9
9
  export {
10
10
  ALL_CONFIG_TYPES,
11
11
  CONFIG_LABELS,
@@ -0,0 +1,19 @@
1
+ import {
2
+ getChanges,
3
+ getDb,
4
+ getResearchCache,
5
+ recordChange,
6
+ saveDb,
7
+ saveHealthSnapshot,
8
+ saveResearchCache
9
+ } from "./chunk-6NEMSUKP.js";
10
+ import "./chunk-IXMWW5WA.js";
11
+ export {
12
+ getChanges,
13
+ getDb,
14
+ getResearchCache,
15
+ recordChange,
16
+ saveDb,
17
+ saveHealthSnapshot,
18
+ saveResearchCache
19
+ };
@@ -0,0 +1,36 @@
1
+ import {
2
+ addEdge,
3
+ analyzeImpact,
4
+ buildFromSessionMemory,
5
+ formatFlow,
6
+ formatImpact,
7
+ getGraphStats,
8
+ queryGraph,
9
+ recordApiRoute,
10
+ recordFileDefinition,
11
+ recordFunctionCall,
12
+ recordImport,
13
+ recordTestRelation,
14
+ searchGraph,
15
+ traceFlow,
16
+ upsertNode
17
+ } from "./chunk-IMO4QAFJ.js";
18
+ import "./chunk-6NEMSUKP.js";
19
+ import "./chunk-IXMWW5WA.js";
20
+ export {
21
+ addEdge,
22
+ analyzeImpact,
23
+ buildFromSessionMemory,
24
+ formatFlow,
25
+ formatImpact,
26
+ getGraphStats,
27
+ queryGraph,
28
+ recordApiRoute,
29
+ recordFileDefinition,
30
+ recordFunctionCall,
31
+ recordImport,
32
+ recordTestRelation,
33
+ searchGraph,
34
+ traceFlow,
35
+ upsertNode
36
+ };
@@ -0,0 +1,16 @@
1
+ import {
2
+ formatDecisionTemplate,
3
+ getProactiveMemories,
4
+ recordDecision,
5
+ scoreMemoryRelevance,
6
+ shouldRecordDecision
7
+ } from "./chunk-OUPFJQKW.js";
8
+ import "./chunk-TT37TE4P.js";
9
+ import "./chunk-IXMWW5WA.js";
10
+ export {
11
+ formatDecisionTemplate,
12
+ getProactiveMemories,
13
+ recordDecision,
14
+ scoreMemoryRelevance,
15
+ shouldRecordDecision
16
+ };
@@ -0,0 +1,301 @@
1
+ import {
2
+ getGitDiffStat,
3
+ getSessionStats,
4
+ getUnresolvedCount
5
+ } from "./chunk-WP3WRPDC.js";
6
+ import {
7
+ sessionMemory
8
+ } from "./chunk-TT37TE4P.js";
9
+ import "./chunk-IXMWW5WA.js";
10
+
11
+ // src/engine/safetyScore.ts
12
+ async function computeSafetyScore(inputGoal) {
13
+ const stats = getSessionStats(inputGoal);
14
+ const checks = [];
15
+ let totalScore = 0;
16
+ const maxScore = 100;
17
+ const gitStat = getGitDiffStat();
18
+ if (gitStat) {
19
+ const lines = gitStat.split("\n").filter(Boolean).length;
20
+ if (lines === 0) {
21
+ checks.push({
22
+ label: "Git Clean",
23
+ status: "pass",
24
+ message: "Working tree is clean",
25
+ weight: 20
26
+ });
27
+ totalScore += 20;
28
+ } else if (lines <= 3) {
29
+ checks.push({
30
+ label: "Git Clean",
31
+ status: "warn",
32
+ message: `${lines} file(s) modified`,
33
+ weight: 15
34
+ });
35
+ totalScore += 15;
36
+ } else {
37
+ checks.push({
38
+ label: "Git Clean",
39
+ status: "warn",
40
+ message: `${lines} file(s) modified \u2014 consider committing or stashing`,
41
+ weight: 10
42
+ });
43
+ totalScore += 10;
44
+ }
45
+ } else {
46
+ checks.push({
47
+ label: "Git Status",
48
+ status: "pass",
49
+ message: "Not a git repository or git unavailable",
50
+ weight: 20
51
+ });
52
+ totalScore += 20;
53
+ }
54
+ try {
55
+ const { getDb } = await import("./kumaDb-KN7Z4B5V.js");
56
+ const db = await getDb();
57
+ const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] ?? 0;
58
+ const edgeCount = db.exec("SELECT COUNT(*) as c FROM edges")[0]?.values[0][0] ?? 0;
59
+ if (nodeCount > 0) {
60
+ checks.push({
61
+ label: "Graph Health",
62
+ status: "pass",
63
+ message: `${nodeCount} nodes, ${edgeCount} edges`,
64
+ weight: 10
65
+ });
66
+ totalScore += 10;
67
+ } else {
68
+ checks.push({
69
+ label: "Graph Health",
70
+ status: "warn",
71
+ message: "Empty knowledge graph \u2014 run research first",
72
+ weight: 5
73
+ });
74
+ totalScore += 5;
75
+ }
76
+ } catch {
77
+ checks.push({
78
+ label: "Graph Health",
79
+ status: "warn",
80
+ message: "Could not check graph health",
81
+ weight: 5
82
+ });
83
+ totalScore += 5;
84
+ }
85
+ try {
86
+ const { getDb } = await import("./kumaDb-KN7Z4B5V.js");
87
+ const db = await getDb();
88
+ const researchCount = db.exec("SELECT COUNT(*) as c FROM research_cache")[0]?.values[0][0] ?? 0;
89
+ checks.push({
90
+ label: "Research Cached",
91
+ status: researchCount > 0 ? "pass" : "warn",
92
+ message: researchCount > 0 ? `${researchCount} research scope(s) cached` : "No research cached \u2014 run kuma_context research first",
93
+ weight: 10
94
+ });
95
+ totalScore += researchCount > 0 ? 10 : 5;
96
+ } catch {
97
+ checks.push({
98
+ label: "Research Cached",
99
+ status: "warn",
100
+ message: "Could not check research cache",
101
+ weight: 5
102
+ });
103
+ totalScore += 5;
104
+ }
105
+ const unresolvedCount = getUnresolvedCount(stats.failedFiles);
106
+ const allFailures = stats.failedFiles.flatMap((f) => f.failures);
107
+ const testFailures = allFailures.filter((f) => f.error.toLowerCase().includes("test") || f.error.toLowerCase().includes("fail"));
108
+ const hasRunTests = stats.hasRunTests;
109
+ if (!hasRunTests) {
110
+ checks.push({
111
+ label: "Tests Status",
112
+ status: "warn",
113
+ message: "No tests run yet this session",
114
+ weight: 10
115
+ });
116
+ totalScore += 10;
117
+ } else if (testFailures.length === 0 && unresolvedCount === 0) {
118
+ checks.push({
119
+ label: "Tests Status",
120
+ status: "pass",
121
+ message: "All tests passing",
122
+ weight: 15
123
+ });
124
+ totalScore += 15;
125
+ } else if (testFailures.length <= 2) {
126
+ checks.push({
127
+ label: "Tests Status",
128
+ status: "warn",
129
+ message: `${testFailures.length} test failure(s) \u2014 needs attention`,
130
+ weight: 8
131
+ });
132
+ totalScore += 8;
133
+ } else {
134
+ checks.push({
135
+ label: "Tests Status",
136
+ status: "fail",
137
+ message: `${testFailures.length} test failure(s) \u2014 fix before proceeding`,
138
+ weight: 3
139
+ });
140
+ totalScore += 3;
141
+ }
142
+ const modifiedCount = stats.modifiedFiles.length;
143
+ if (modifiedCount === 0) {
144
+ checks.push({
145
+ label: "Modified Files",
146
+ status: "pass",
147
+ message: "No files modified yet",
148
+ weight: 15
149
+ });
150
+ totalScore += 15;
151
+ } else if (modifiedCount <= 3) {
152
+ checks.push({
153
+ label: "Modified Files",
154
+ status: "warn",
155
+ message: `${modifiedCount} file(s) modified`,
156
+ weight: 12
157
+ });
158
+ totalScore += 12;
159
+ } else if (modifiedCount <= 8) {
160
+ checks.push({
161
+ label: "Modified Files",
162
+ status: "warn",
163
+ message: `${modifiedCount} file(s) modified \u2014 consider a checkpoint`,
164
+ weight: 8
165
+ });
166
+ totalScore += 8;
167
+ } else {
168
+ checks.push({
169
+ label: "Modified Files",
170
+ status: "fail",
171
+ message: `${modifiedCount} file(s) modified \u2014 consider committing`,
172
+ weight: 4
173
+ });
174
+ totalScore += 4;
175
+ }
176
+ const loop = sessionMemory.detectLoop();
177
+ if (loop.isLooping) {
178
+ checks.push({
179
+ label: "Loop Detection",
180
+ status: "fail",
181
+ message: loop.message || "Potential tool call loop detected",
182
+ weight: 0
183
+ });
184
+ } else {
185
+ checks.push({
186
+ label: "Loop Detection",
187
+ status: "pass",
188
+ message: "No loops detected",
189
+ weight: 10
190
+ });
191
+ totalScore += 10;
192
+ }
193
+ if (unresolvedCount === 0) {
194
+ checks.push({
195
+ label: "Unresolved Failures",
196
+ status: "pass",
197
+ message: "No unresolved failures",
198
+ weight: 10
199
+ });
200
+ totalScore += 10;
201
+ } else if (unresolvedCount <= 2) {
202
+ checks.push({
203
+ label: "Unresolved Failures",
204
+ status: "warn",
205
+ message: `${unresolvedCount} unresolved failure(s)`,
206
+ weight: 6
207
+ });
208
+ totalScore += 6;
209
+ } else {
210
+ checks.push({
211
+ label: "Unresolved Failures",
212
+ status: "fail",
213
+ message: `${unresolvedCount} unresolved failure(s) \u2014 fix before continuing`,
214
+ weight: 2
215
+ });
216
+ totalScore += 2;
217
+ }
218
+ const hasConventions = !!sessionMemory.getConventions();
219
+ if (hasConventions) {
220
+ checks.push({
221
+ label: "Project Detected",
222
+ status: "pass",
223
+ message: "Framework, test runner, and conventions detected",
224
+ weight: 5
225
+ });
226
+ totalScore += 5;
227
+ } else {
228
+ checks.push({
229
+ label: "Project Detected",
230
+ status: "warn",
231
+ message: "Run project_conventions() to detect stack",
232
+ weight: 2
233
+ });
234
+ totalScore += 2;
235
+ }
236
+ const goal = inputGoal || sessionMemory.getSummary().currentGoal || "";
237
+ if (goal) {
238
+ checks.push({
239
+ label: "Goal Set",
240
+ status: "pass",
241
+ message: `Current goal: "${goal.substring(0, 60)}"`,
242
+ weight: 5
243
+ });
244
+ totalScore += 5;
245
+ } else {
246
+ checks.push({
247
+ label: "Goal Set",
248
+ status: "warn",
249
+ message: "No goal set \u2014 use goal parameter or setGoal to track intent",
250
+ weight: 2
251
+ });
252
+ totalScore += 2;
253
+ }
254
+ let risk;
255
+ if (totalScore >= 85) risk = "LOW";
256
+ else if (totalScore >= 65) risk = "MEDIUM";
257
+ else if (totalScore >= 40) risk = "HIGH";
258
+ else risk = "CRITICAL";
259
+ const passCount = checks.filter((c) => c.status === "pass").length;
260
+ const warnCount = checks.filter((c) => c.status === "warn").length;
261
+ const failCount = checks.filter((c) => c.status === "fail").length;
262
+ const summaryParts = [];
263
+ if (passCount > 0) summaryParts.push(`${passCount} check(s) passed`);
264
+ if (warnCount > 0) summaryParts.push(`${warnCount} warning(s)`);
265
+ if (failCount > 0) summaryParts.push(`${failCount} failure(s)`);
266
+ return {
267
+ score: totalScore,
268
+ maxScore,
269
+ risk,
270
+ checks,
271
+ summary: summaryParts.join(", "),
272
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
273
+ };
274
+ }
275
+ function formatSafetyScore(report) {
276
+ const barLength = 20;
277
+ const filledBars = Math.round(report.score / report.maxScore * barLength);
278
+ const emptyBars = barLength - filledBars;
279
+ const bar = "\u2588".repeat(filledBars) + "\u2591".repeat(emptyBars);
280
+ const riskEmoji = report.risk === "LOW" ? "\u{1F7E2}" : report.risk === "MEDIUM" ? "\u{1F7E1}" : report.risk === "HIGH" ? "\u{1F7E0}" : "\u{1F534}";
281
+ const lines = [
282
+ `\u{1F6E1}\uFE0F **Safety Score: ${report.score}/${report.maxScore}** ${riskEmoji}`,
283
+ ` ${bar}`,
284
+ ` Risk: **${report.risk}** \u2014 ${report.summary}`,
285
+ "",
286
+ "**Checks:**"
287
+ ];
288
+ for (const check of report.checks) {
289
+ const icon = check.status === "pass" ? "\u2705" : check.status === "warn" ? "\u26A0\uFE0F" : "\u274C";
290
+ lines.push(` ${icon} **${check.label}:** ${check.message}`);
291
+ }
292
+ lines.push(
293
+ "",
294
+ "\u{1F4A1} Run kuma_safety_score() at any time to re-evaluate project health."
295
+ );
296
+ return lines.join("\n");
297
+ }
298
+ export {
299
+ computeSafetyScore,
300
+ formatSafetyScore
301
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ sessionMemory
3
+ } from "./chunk-TT37TE4P.js";
4
+ export {
5
+ sessionMemory
6
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.2.7",
4
- "description": "Zero-setup safety toolkit for AI coding agents. MCP server with code review, git analysis, file editing, session memory, and static analysis — works with Claude Code, Cursor, Gemini CLI.",
3
+ "version": "2.3.0",
4
+ "description": "Safety-first context & orchestration engine for AI coding agents. MCP server with mandatory research pipeline, knowledge graph, impact analysis, decision memory, and safety guard — works with any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -1,14 +0,0 @@
1
- import {
2
- SessionMemory,
3
- getSessionMemory,
4
- handleWriteMemory,
5
- searchSessionMemory,
6
- sessionMemory
7
- } from "./chunk-QPVPFUCQ.js";
8
- export {
9
- SessionMemory,
10
- getSessionMemory,
11
- handleWriteMemory,
12
- searchSessionMemory,
13
- sessionMemory
14
- };