@plumpslabs/kuma 2.4.3 â 2.4.5
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/.claude-plugin/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +10 -0
- package/INSTALL.md +265 -0
- package/README.md +32 -20
- package/agents/kuma-guardian.md +37 -0
- package/agents/kuma-researcher.md +36 -0
- package/commands/kuma:checkpoint.md +12 -0
- package/commands/kuma:guard.md +16 -0
- package/commands/kuma:impact.md +16 -0
- package/commands/kuma:init.md +16 -0
- package/commands/kuma:research.md +17 -0
- package/commands/kuma:rollback.md +12 -0
- package/commands/kuma:studio.md +17 -0
- package/commands/kuma:verify.md +15 -0
- package/dist/index.js +2510 -618
- package/hooks/kuma-agy-hooks.js +37 -0
- package/hooks.json +17 -0
- package/install.sh +129 -0
- package/mcp_config.json +11 -0
- package/package.json +13 -3
- package/packages/ide/studio/dist/index.js +25 -2
- package/packages/ide/studio/public/index.html +901 -1266
- package/plugin.json +60 -0
- package/skills/kuma/SKILL.md +28 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* đģ Kuma MCP â Antigravity Lifecycle Hook
|
|
3
|
+
* Intercepts file writes to warn about known gotchas and fragile files.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
function runHook() {
|
|
9
|
+
try {
|
|
10
|
+
const input = process.env.AGY_TOOL_INPUT ? JSON.parse(process.env.AGY_TOOL_INPUT) : {};
|
|
11
|
+
const targetFile = input.TargetFile || input.file || "";
|
|
12
|
+
|
|
13
|
+
if (!targetFile) {
|
|
14
|
+
process.stdout.write(JSON.stringify({ decision: "allow" }) + "\n");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const gotchasFile = path.resolve(process.cwd(), ".kuma", "KNOWN_GOTCHAS.md");
|
|
19
|
+
if (fs.existsSync(gotchasFile)) {
|
|
20
|
+
const content = fs.readFileSync(gotchasFile, "utf-8");
|
|
21
|
+
const baseName = path.basename(targetFile);
|
|
22
|
+
if (content.includes(baseName) || content.includes(targetFile)) {
|
|
23
|
+
process.stdout.write(JSON.stringify({
|
|
24
|
+
decision: "allow",
|
|
25
|
+
systemMessage: `â ī¸ Kuma Gotcha Warning: ${baseName} has recorded gotchas in .kuma/KNOWN_GOTCHAS.md. Review before editing!`
|
|
26
|
+
}) + "\n");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
process.stdout.write(JSON.stringify({ decision: "allow" }) + "\n");
|
|
32
|
+
} catch {
|
|
33
|
+
process.stdout.write(JSON.stringify({ decision: "allow" }) + "\n");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
runHook();
|
package/hooks.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kuma-safety": {
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"PreToolUse": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "replace_file_content|write_to_file",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "node -e \"import('fs').then(fs=>{const p=['hooks/kuma-agy-hooks.js','.agents/hooks/kuma-agy-hooks.js','../hooks/kuma-agy-hooks.js'].find(x=>fs.existsSync(x));if(p)import('./'+p);else process.stdout.write(JSON.stringify({decision:'allow'})+'\\n');}).catch(()=>process.stdout.write(JSON.stringify({decision:'allow'})+'\\n'))\"",
|
|
11
|
+
"timeout": 15
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
package/install.sh
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# đģ Kuma â Universal Installation Script
|
|
5
|
+
# Usage:
|
|
6
|
+
# curl -fsSL https://raw.githubusercontent.com/plumpslabs/kuma/main/install.sh | bash
|
|
7
|
+
# curl -fsSL ... | bash -s -- --target /path
|
|
8
|
+
# ./install.sh # from cloned repo
|
|
9
|
+
|
|
10
|
+
GH_RAW="https://raw.githubusercontent.com/plumpslabs/kuma/main"
|
|
11
|
+
HERE="$(cd "$(dirname "$0")" 2>/dev/null && pwd || echo "")"
|
|
12
|
+
CLONED=false
|
|
13
|
+
[ -n "$HERE" ] && [ -f "$HERE/package.json" ] && [ -f "$HERE/dist/index.js" ] && CLONED=true
|
|
14
|
+
|
|
15
|
+
TARGET="${PWD}"
|
|
16
|
+
f=""; for a in "$@"; do [ "$f" = "--target" ] && TARGET="$a" && break; f="$a"; done
|
|
17
|
+
|
|
18
|
+
GLOBAL=false
|
|
19
|
+
for a in "$@"; do [ "$a" = "--global" ] && GLOBAL=true && break; done
|
|
20
|
+
|
|
21
|
+
ALL=false
|
|
22
|
+
for a in "$@"; do [ "$a" = "--all" ] && ALL=true && break; done
|
|
23
|
+
|
|
24
|
+
echo "đģ Kuma Safety Engine â Installer"
|
|
25
|
+
echo "Target: $TARGET"
|
|
26
|
+
$CLONED && echo "Source: local repo" || echo "Source: remote repo"
|
|
27
|
+
echo ""
|
|
28
|
+
|
|
29
|
+
# 1. Verify Node.js
|
|
30
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
31
|
+
echo "â Node.js is required but not found. Please install Node.js >= 18." >&2
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
NODE_MAJOR=$(node -v | cut -d'.' -f1 | tr -d 'v')
|
|
36
|
+
if [ "$NODE_MAJOR" -lt 18 ]; then
|
|
37
|
+
echo "â Node.js >= 18 is required. Current version: $(node -v)" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
echo "â
Node.js $(node -v) detected"
|
|
41
|
+
|
|
42
|
+
# 2. Global CLI Install (if requested or missing)
|
|
43
|
+
if $GLOBAL; then
|
|
44
|
+
echo "đĻ Installing @plumpslabs/kuma globally..."
|
|
45
|
+
if command -v npm >/dev/null 2>&1; then
|
|
46
|
+
npm install -g @plumpslabs/kuma
|
|
47
|
+
elif command -v pnpm >/dev/null 2>&1; then
|
|
48
|
+
pnpm add -g @plumpslabs/kuma
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# 3. Initialize target repository
|
|
53
|
+
echo "đ§ Configuring AI agent environments in $TARGET..."
|
|
54
|
+
|
|
55
|
+
mkdir -p "$TARGET"
|
|
56
|
+
if $CLONED; then
|
|
57
|
+
(cd "$TARGET" && node "$HERE/dist/index.js" init --merge ${ALL:+--all})
|
|
58
|
+
else
|
|
59
|
+
(cd "$TARGET" && npx -y @plumpslabs/kuma init --merge ${ALL:+--all})
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# 4. Configure MCP for detected environments (non-destructive merge)
|
|
63
|
+
echo ""
|
|
64
|
+
echo "đ Configuring MCP Server integrations..."
|
|
65
|
+
|
|
66
|
+
merge_mcp_config() {
|
|
67
|
+
local mcp_path="$1"
|
|
68
|
+
local label="$2"
|
|
69
|
+
node -e "
|
|
70
|
+
const fs = require('fs');
|
|
71
|
+
let data = {};
|
|
72
|
+
if (fs.existsSync('$mcp_path')) {
|
|
73
|
+
try { data = JSON.parse(fs.readFileSync('$mcp_path', 'utf-8')); } catch(e) {}
|
|
74
|
+
}
|
|
75
|
+
data.mcpServers = data.mcpServers || {};
|
|
76
|
+
if (!data.mcpServers.kuma) {
|
|
77
|
+
data.mcpServers.kuma = { command: 'npx', args: ['-y', '@plumpslabs/kuma'] };
|
|
78
|
+
fs.writeFileSync('$mcp_path', JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
79
|
+
console.log(' â
Added kuma to $label ($mcp_path)');
|
|
80
|
+
} else {
|
|
81
|
+
console.log(' âšī¸ kuma already registered in $label');
|
|
82
|
+
}
|
|
83
|
+
"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Claude Code / Desktop
|
|
87
|
+
if [ -d "$TARGET/.claude" ] || [ -f "$TARGET/CLAUDE.md" ]; then
|
|
88
|
+
mkdir -p "$TARGET/.claude"
|
|
89
|
+
merge_mcp_config "$TARGET/.mcp.json" "Claude Code"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Cursor
|
|
93
|
+
if [ -d "$TARGET/.cursor" ] || [ -f "$TARGET/.cursorrules" ]; then
|
|
94
|
+
mkdir -p "$TARGET/.cursor"
|
|
95
|
+
merge_mcp_config "$TARGET/.cursor/mcp.json" "Cursor"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Antigravity / Gemini CLI
|
|
99
|
+
if [ -d "$TARGET/.agents" ] || [ -f "$TARGET/GEMINI.md" ]; then
|
|
100
|
+
mkdir -p "$TARGET/.agents"
|
|
101
|
+
merge_mcp_config "$TARGET/.agents/mcp_config.json" "Antigravity"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# OpenCode
|
|
105
|
+
if [ -d "$TARGET/.opencode" ] || [ -f "$TARGET/opencode.json" ] || [ -f "$TARGET/opencode.jsonc" ]; then
|
|
106
|
+
OPENCODE_CONF="$TARGET/opencode.json"
|
|
107
|
+
node -e "
|
|
108
|
+
const fs = require('fs');
|
|
109
|
+
let data = { '\$schema': 'https://opencode.ai/config.json' };
|
|
110
|
+
const confPath = fs.existsSync('$TARGET/opencode.jsonc') ? '$TARGET/opencode.jsonc' : '$OPENCODE_CONF';
|
|
111
|
+
if (fs.existsSync(confPath)) {
|
|
112
|
+
try { data = JSON.parse(fs.readFileSync(confPath, 'utf-8')); } catch(e) {}
|
|
113
|
+
}
|
|
114
|
+
data.mcp = data.mcp || {};
|
|
115
|
+
if (!data.mcp.kuma) {
|
|
116
|
+
data.mcp.kuma = { type: 'local', command: ['npx', '-y', '@plumpslabs/kuma'], enabled: true };
|
|
117
|
+
fs.writeFileSync(confPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
118
|
+
console.log(' â
Added kuma to OpenCode (' + confPath + ')');
|
|
119
|
+
} else {
|
|
120
|
+
console.log(' âšī¸ kuma already registered in OpenCode');
|
|
121
|
+
}
|
|
122
|
+
"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
echo ""
|
|
126
|
+
echo "đ Kuma setup complete!"
|
|
127
|
+
echo "âĸ Rules & hooks: Active in target configuration files"
|
|
128
|
+
echo "âĸ MCP tools: kuma_context, kuma_memory, kuma_safety ready"
|
|
129
|
+
echo "âĸ Studio dashboard: run 'npx @plumpslabs/kuma studio' to visualize"
|
package/mcp_config.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
4
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",
|
|
@@ -11,7 +11,17 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist/",
|
|
13
13
|
"packages/ide/studio/dist/",
|
|
14
|
-
"packages/ide/studio/public/"
|
|
14
|
+
"packages/ide/studio/public/",
|
|
15
|
+
"plugin.json",
|
|
16
|
+
"mcp_config.json",
|
|
17
|
+
"install.sh",
|
|
18
|
+
"INSTALL.md",
|
|
19
|
+
"hooks.json",
|
|
20
|
+
"hooks/",
|
|
21
|
+
"skills/",
|
|
22
|
+
"commands/",
|
|
23
|
+
"agents/",
|
|
24
|
+
".claude-plugin/"
|
|
15
25
|
],
|
|
16
26
|
"dependencies": {
|
|
17
27
|
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
@@ -65,7 +75,7 @@
|
|
|
65
75
|
"scripts": {
|
|
66
76
|
"build": "tsup src/index.ts --format esm --no-splitting --clean --out-dir dist",
|
|
67
77
|
"build:studio": "cd packages/ide/studio && tsup src/index.ts --format esm --clean --out-dir dist",
|
|
68
|
-
"build:all": "
|
|
78
|
+
"build:all": "npm run build && npm run build:studio",
|
|
69
79
|
"dev": "tsup src/index.ts --format esm --watch --out-dir dist",
|
|
70
80
|
"start": "node dist/index.js",
|
|
71
81
|
"studio": "node dist/index.js studio",
|
|
@@ -163,11 +163,34 @@ async function getDashboardData() {
|
|
|
163
163
|
`SELECT json_group_array(json_object('source',source_id,'target',target_id,'relation',type,'weight',weight)) FROM edges`
|
|
164
164
|
);
|
|
165
165
|
const gotchas = jsonRows(
|
|
166
|
-
`SELECT json_group_array(json_object('id',id,'file_path',file_path,'description',REPLACE(REPLACE(description,char(10),' '),char(13),''),'severity',severity,'workaround',REPLACE(REPLACE(COALESCE(workaround,''),char(10),' '),char(13),''),'added_by',COALESCE(added_by,'agent'),'created_at',created_at)) FROM (SELECT * FROM known_gotchas ORDER BY created_at DESC)`
|
|
166
|
+
`SELECT json_group_array(json_object('id',id,'file_path',file_path,'description',REPLACE(REPLACE(description,char(10),' '),char(13),''),'severity',severity,'workaround',REPLACE(REPLACE(COALESCE(workaround,''),char(10),' '),char(13),''),'added_by',COALESCE(added_by,'agent'),'status',COALESCE(status,'active'),'scope_package',COALESCE(scope_package,''),'verified_by',COALESCE(verified_by,''),'created_at',created_at,'updated_at',COALESCE(updated_at,created_at))) FROM (SELECT * FROM known_gotchas ORDER BY created_at DESC)`
|
|
167
|
+
);
|
|
168
|
+
const flows = jsonRows(
|
|
169
|
+
`SELECT json_group_array(json_object('id',id,'name',name,'file_path',COALESCE(file_path,''),'metadata',COALESCE(metadata,'{}'),'updated_at',updated_at)) FROM (SELECT * FROM nodes WHERE type = 'arch_flow' ORDER BY updated_at DESC)`
|
|
170
|
+
);
|
|
171
|
+
const decisions = jsonRows(
|
|
172
|
+
`SELECT json_group_array(json_object('id',id,'name',name,'metadata',COALESCE(metadata,'{}'),'created_at',created_at)) FROM (SELECT * FROM nodes WHERE type = 'decision' ORDER BY created_at DESC)`
|
|
167
173
|
);
|
|
168
174
|
const features = jsonRows(
|
|
169
175
|
`SELECT json_group_array(json_object('id',id,'name',name,'metadata',COALESCE(metadata,'{}'))) FROM (SELECT * FROM nodes WHERE type = 'feature' ORDER BY updated_at DESC)`
|
|
170
176
|
);
|
|
177
|
+
let workspace = { isWorkspace: false, packages: [] };
|
|
178
|
+
try {
|
|
179
|
+
const root = projectRootFromDb(dbPath);
|
|
180
|
+
const rootPkgPath = path.join(root, "package.json");
|
|
181
|
+
if (fs.existsSync(rootPkgPath)) {
|
|
182
|
+
const pkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
|
|
183
|
+
const pnpmWs = path.join(root, "pnpm-workspace.yaml");
|
|
184
|
+
const isWs = Boolean(pkg.workspaces || fs.existsSync(pnpmWs));
|
|
185
|
+
workspace = {
|
|
186
|
+
isWorkspace: isWs,
|
|
187
|
+
name: pkg.name || path.basename(root),
|
|
188
|
+
version: pkg.version || "1.0.0",
|
|
189
|
+
type: isWs ? fs.existsSync(pnpmWs) ? "pnpm" : "npm/yarn" : "single-package"
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
171
194
|
let injectionCount = 0;
|
|
172
195
|
let injectionSavedMs = 0;
|
|
173
196
|
try {
|
|
@@ -219,7 +242,7 @@ async function getDashboardData() {
|
|
|
219
242
|
recentSessions
|
|
220
243
|
};
|
|
221
244
|
const staleness = detectStaleNodes(db, dbPath);
|
|
222
|
-
return { stats, nodes, edges, gotchas, efficiency, staleness, injections: { count: injectionCount, savedMs: injectionSavedMs, savedFormatted: Math.round(injectionSavedMs / 6e4) + " min" } };
|
|
245
|
+
return { stats, nodes, edges, gotchas, flows, decisions, workspace, efficiency, staleness, injections: { count: injectionCount, savedMs: injectionSavedMs, savedFormatted: Math.round(injectionSavedMs / 6e4) + " min" } };
|
|
223
246
|
} finally {
|
|
224
247
|
db.close();
|
|
225
248
|
}
|