@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.
@@ -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"
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "kuma": {
4
+ "command": "npx",
5
+ "args": [
6
+ "-y",
7
+ "@plumpslabs/kuma"
8
+ ]
9
+ }
10
+ }
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.4.3",
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": "pnpm run build && pnpm run build:studio",
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
  }