@plumpslabs/kuma 2.4.4 β 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/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 +158 -78
- package/hooks/kuma-agy-hooks.js +37 -0
- package/hooks.json +17 -0
- package/package.json +6 -1
- package/plugin.json +45 -3
- package/skills/kuma/SKILL.md +28 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kuma-guardian
|
|
3
|
+
description: Runtime safety guardian. Pre-flight check, architecture boundary audit, circular dependency detection, and anti-pattern prevention. Read-only.
|
|
4
|
+
mode: subagent
|
|
5
|
+
mainAgent: false
|
|
6
|
+
subagent: true
|
|
7
|
+
permission:
|
|
8
|
+
read: allow
|
|
9
|
+
grep: allow
|
|
10
|
+
glob: allow
|
|
11
|
+
list: allow
|
|
12
|
+
bash: deny
|
|
13
|
+
webfetch: deny
|
|
14
|
+
websearch: deny
|
|
15
|
+
task: deny
|
|
16
|
+
edit:
|
|
17
|
+
"*": deny
|
|
18
|
+
disallowedTools: Write, Edit, Task
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<agent_persona>
|
|
22
|
+
You are the Kuma Safety Guardian. Your directive is pre-modification safety enforcement and architectural protection.
|
|
23
|
+
You analyze proposed changes, detect anti-patterns, circular dependencies, and verify that the agent does not edit code blindly.
|
|
24
|
+
</agent_persona>
|
|
25
|
+
|
|
26
|
+
<strict_boundaries>
|
|
27
|
+
- READ-ONLY: Never modify any source code files.
|
|
28
|
+
- Inspect codebase via kuma_context, kuma_safety, grep, glob, and read tools.
|
|
29
|
+
- If high risk or circular dependency is detected, raise explicit safety blocking recommendation.
|
|
30
|
+
</strict_boundaries>
|
|
31
|
+
|
|
32
|
+
<workflow>
|
|
33
|
+
1. Run `kuma_safety({ action: "guard" })` to check session state and edit loops.
|
|
34
|
+
2. Check file boundaries and package dependencies using `kuma_context({ action: "map" })`.
|
|
35
|
+
3. If circular imports or boundary violations are found, report exact import chains.
|
|
36
|
+
4. Render verdict: PASS or BLOCK.
|
|
37
|
+
</workflow>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kuma-researcher
|
|
3
|
+
description: Codebase intelligence & blast radius researcher. Maps monorepo packages, traces dependency trees, inspects past decisions, and gathers gotchas before changes. Read-only.
|
|
4
|
+
mode: subagent
|
|
5
|
+
mainAgent: false
|
|
6
|
+
subagent: true
|
|
7
|
+
permission:
|
|
8
|
+
read: allow
|
|
9
|
+
grep: allow
|
|
10
|
+
glob: allow
|
|
11
|
+
list: allow
|
|
12
|
+
bash: deny
|
|
13
|
+
webfetch: deny
|
|
14
|
+
websearch: deny
|
|
15
|
+
task: deny
|
|
16
|
+
edit:
|
|
17
|
+
"*": deny
|
|
18
|
+
disallowedTools: Write, Edit, Task
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<agent_persona>
|
|
22
|
+
You are the Kuma Codebase Researcher. You explore unfamiliar code, compute blast radius, discover domain flows, and extract historical gotchas so the main agent has 100% clarity before modifying code.
|
|
23
|
+
</agent_persona>
|
|
24
|
+
|
|
25
|
+
<strict_boundaries>
|
|
26
|
+
- READ-ONLY: Never modify any source code files.
|
|
27
|
+
- Focus strictly on research, dependency traversal, and impact scoring.
|
|
28
|
+
</strict_boundaries>
|
|
29
|
+
|
|
30
|
+
<workflow>
|
|
31
|
+
1. Initialize context via `kuma_context({ action: "init" })`.
|
|
32
|
+
2. Inspect area via `kuma_context({ action: "research", scope: "<target>" })`.
|
|
33
|
+
3. Calculate blast radius via `kuma_context({ action: "impact", target: "<target>" })`.
|
|
34
|
+
4. Check cross-session history via `kuma_context({ action: "history", target: "<file>" })`.
|
|
35
|
+
5. Return structured report with affected files, consumers, and known gotchas.
|
|
36
|
+
</workflow>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "πΈ Kuma Checkpoint β Create an atomic named snapshot before risky work"
|
|
3
|
+
---
|
|
4
|
+
# /kuma:checkpoint
|
|
5
|
+
|
|
6
|
+
Take an atomic rollback snapshot of current files and knowledge graph.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
kuma_safety({ action: "checkpoint", label: "[pre-refactor-feature-x]" })
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Creates `<label>/` snapshot with `kuma.db` + modified files.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "π‘οΈ Kuma Guard β Pre-flight safety check against anti-patterns, drift, and circular deps"
|
|
3
|
+
---
|
|
4
|
+
# /kuma:guard
|
|
5
|
+
|
|
6
|
+
Verify safety boundaries before applying complex refactors or multi-file edits.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
kuma_safety({ action: "guard", guardGoal: "[what you are doing]" })
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**What it checks:**
|
|
13
|
+
- Missing pre-modification research
|
|
14
|
+
- Architecture boundary violations & circular imports
|
|
15
|
+
- Scope drift (editing files outside declared goal)
|
|
16
|
+
- Runaway execution loops
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "π₯ Kuma Impact β Calculate blast radius, affected consumers, and safety risk score"
|
|
3
|
+
---
|
|
4
|
+
# /kuma:impact
|
|
5
|
+
|
|
6
|
+
Evaluate blast radius before modifying high-leverage files or shared packages.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
kuma_context({ action: "impact", target: "[file or package path]" })
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**What it returns:**
|
|
13
|
+
- Direct and transitive consumers
|
|
14
|
+
- Downstream affected packages in monorepos
|
|
15
|
+
- Mapped test suites to run
|
|
16
|
+
- Safety risk level (`low`, `medium`, `high`, `critical`)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "π» Kuma Init β Initialize session context, lean brief (<500 tokens), and branch tracking"
|
|
3
|
+
---
|
|
4
|
+
# /kuma:init
|
|
5
|
+
|
|
6
|
+
Initialize Kuma session context before writing any code.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
kuma_context({ action: "init", goal: "[current task goal]" })
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**What it does:**
|
|
13
|
+
- Restores cross-session shadow memory
|
|
14
|
+
- Loads fresh gotchas for the active branch
|
|
15
|
+
- Evaluates repository rules and path constraints
|
|
16
|
+
- Informs about branch switches
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "π¬ Kuma Research β 5-step pre-edit research pipeline before editing unfamiliar code"
|
|
3
|
+
---
|
|
4
|
+
# /kuma:research
|
|
5
|
+
|
|
6
|
+
Run research pipeline before editing code you haven't touched before.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
kuma_context({ action: "research", scope: "[module or feature area]" })
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Pipeline Stages:**
|
|
13
|
+
1. Check research cache in `.kuma/kuma.db`
|
|
14
|
+
2. Query knowledge graph for related nodes and edges
|
|
15
|
+
3. Scan references and entry points
|
|
16
|
+
4. Compute blast radius and test coverage
|
|
17
|
+
5. Retrieve historical ADR decisions and gotchas
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "βͺ Kuma Rollback β Restore codebase from a labeled checkpoint snapshot"
|
|
3
|
+
---
|
|
4
|
+
# /kuma:rollback
|
|
5
|
+
|
|
6
|
+
Restore codebase to the exact state captured in a previous checkpoint.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
kuma_safety({ action: "rollback_label", label: "[label-name]" })
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If the label is not found, Kuma lists all available snapshots.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "π Kuma Studio β Web dashboard for gotchas, flows, workspace matrix, and metrics"
|
|
3
|
+
---
|
|
4
|
+
# /kuma:studio
|
|
5
|
+
|
|
6
|
+
Launch or view Kuma Studio web dashboard.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx @plumpslabs/kuma studio
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Opens `http://localhost:3322` featuring:
|
|
13
|
+
- **Gotcha Shield** (status filters & 1-click workaround copy)
|
|
14
|
+
- **Domain Sequence Flows**
|
|
15
|
+
- **Workspace & Blast Radius Simulator**
|
|
16
|
+
- **Health & Efficiency Metrics**
|
|
17
|
+
- **Calm Knowledge Graph** (physics toggle)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "π§ͺ Kuma Verify β Post-edit scoped test verification and validation"
|
|
3
|
+
---
|
|
4
|
+
# /kuma:verify
|
|
5
|
+
|
|
6
|
+
Run scoped tests and verify that changes did not introduce regressions.
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
kuma_safety({ action: "verify", scope: "[modified scope or file]" })
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**What it does:**
|
|
13
|
+
- Automatically identifies appropriate test runner (Jest, Vitest, Pytest, Go test, Cargo)
|
|
14
|
+
- Executes tests restricted to the modified blast radius
|
|
15
|
+
- Records pass/fail metrics in session memory
|
package/dist/index.js
CHANGED
|
@@ -3297,7 +3297,8 @@ __export(kumaGraph_exports, {
|
|
|
3297
3297
|
});
|
|
3298
3298
|
function nodeId(type, name) {
|
|
3299
3299
|
if (type === "file") {
|
|
3300
|
-
|
|
3300
|
+
const normalized = normalizeScope(name) || name;
|
|
3301
|
+
return `file::${normalized}`;
|
|
3301
3302
|
}
|
|
3302
3303
|
return generateNodeId(type, name);
|
|
3303
3304
|
}
|
|
@@ -3358,7 +3359,9 @@ async function pruneGraphIfNeeded() {
|
|
|
3358
3359
|
async function upsertNode(node) {
|
|
3359
3360
|
try {
|
|
3360
3361
|
const db = await getDb();
|
|
3361
|
-
const
|
|
3362
|
+
const normName = node.type === "file" ? normalizeScope(node.name) || node.name : node.name;
|
|
3363
|
+
const normFilePath = node.filePath ? normalizeScope(node.filePath) || node.filePath : node.type === "file" ? normName : void 0;
|
|
3364
|
+
const id = node.id || nodeId(node.type, normName);
|
|
3362
3365
|
const metadata = JSON.stringify(node.metadata ?? {});
|
|
3363
3366
|
const severity = node.metadata?.severity || "medium";
|
|
3364
3367
|
const confidence = node.metadata?.confidence || 0.8;
|
|
@@ -3373,19 +3376,19 @@ async function upsertNode(node) {
|
|
|
3373
3376
|
severity = excluded.severity,
|
|
3374
3377
|
confidence = excluded.confidence,
|
|
3375
3378
|
updated_at = strftime('%s','now')
|
|
3376
|
-
`, [id, node.type,
|
|
3379
|
+
`, [id, node.type, normName, normFilePath || null, metadata, severity, confidence]);
|
|
3377
3380
|
try {
|
|
3378
|
-
db.run(`INSERT INTO nodes_fts (rowid, name, metadata) VALUES (last_insert_rowid(), ?, ?)`, [
|
|
3381
|
+
db.run(`INSERT INTO nodes_fts (rowid, name, metadata) VALUES (last_insert_rowid(), ?, ?)`, [normName, metadata]);
|
|
3379
3382
|
} catch {
|
|
3380
3383
|
}
|
|
3381
|
-
if (
|
|
3382
|
-
const fileNodeId = nodeId("file",
|
|
3384
|
+
if (normFilePath && node.type !== "file") {
|
|
3385
|
+
const fileNodeId = nodeId("file", normFilePath);
|
|
3383
3386
|
try {
|
|
3384
3387
|
db.run(`
|
|
3385
3388
|
INSERT INTO nodes (id, type, name, file_path, metadata, updated_at)
|
|
3386
3389
|
VALUES (?, 'file', ?, ?, '{}', strftime('%s','now'))
|
|
3387
3390
|
ON CONFLICT(id) DO NOTHING
|
|
3388
|
-
`, [fileNodeId,
|
|
3391
|
+
`, [fileNodeId, normFilePath, normFilePath]);
|
|
3389
3392
|
db.run(`
|
|
3390
3393
|
INSERT INTO edges (source_id, target_id, type, weight, metadata, created_at)
|
|
3391
3394
|
VALUES (?, ?, 'contains', 1.0, '{}', strftime('%s','now'))
|
|
@@ -4411,6 +4414,7 @@ var init_kumaGraph = __esm({
|
|
|
4411
4414
|
init_kumaDb();
|
|
4412
4415
|
init_kumaSelfHeal();
|
|
4413
4416
|
init_languageSupport();
|
|
4417
|
+
init_pathValidator();
|
|
4414
4418
|
GRAPH_LIMITS = {
|
|
4415
4419
|
maxNodes: 5e3,
|
|
4416
4420
|
// Max nodes before pruning
|
|
@@ -7539,10 +7543,16 @@ function detectTestRunner(root = process.cwd()) {
|
|
|
7539
7543
|
if (fs24.existsSync(pkgPath)) {
|
|
7540
7544
|
try {
|
|
7541
7545
|
const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
|
|
7546
|
+
const isJest = Boolean(
|
|
7547
|
+
pkg.devDependencies?.jest || pkg.dependencies?.jest || pkg.scripts?.test && pkg.scripts.test.includes("jest") || fs24.existsSync(path25.join(root, "jest.config.js")) || fs24.existsSync(path25.join(root, "jest.config.ts")) || fs24.existsSync(path25.join(root, "jest.config.mjs")) || fs24.existsSync(path25.join(root, "jest.config.json"))
|
|
7548
|
+
);
|
|
7549
|
+
const isVitest = Boolean(
|
|
7550
|
+
pkg.devDependencies?.vitest || pkg.dependencies?.vitest || pkg.scripts?.test && pkg.scripts.test.includes("vitest") || fs24.existsSync(path25.join(root, "vitest.config.ts")) || fs24.existsSync(path25.join(root, "vitest.config.js")) || fs24.existsSync(path25.join(root, "vitest.config.mjs"))
|
|
7551
|
+
);
|
|
7542
7552
|
if (pkg.scripts && pkg.scripts.test) {
|
|
7543
|
-
if (fs24.existsSync(path25.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
7544
|
-
if (fs24.existsSync(path25.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
|
|
7545
|
-
return { runner: "npm", baseCommand: "npm test" };
|
|
7553
|
+
if (fs24.existsSync(path25.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test", isJest, isVitest };
|
|
7554
|
+
if (fs24.existsSync(path25.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test", isJest, isVitest };
|
|
7555
|
+
return { runner: "npm", baseCommand: "npm test", isJest, isVitest };
|
|
7546
7556
|
}
|
|
7547
7557
|
} catch {
|
|
7548
7558
|
}
|
|
@@ -7622,7 +7632,7 @@ async function runAutoVerification(options = {}) {
|
|
|
7622
7632
|
}
|
|
7623
7633
|
}
|
|
7624
7634
|
_localRunning = true;
|
|
7625
|
-
const { runner, baseCommand } = detectTestRunner(root);
|
|
7635
|
+
const { runner, baseCommand, isJest, isVitest } = detectTestRunner(root);
|
|
7626
7636
|
if (runner === "unknown" || !baseCommand) {
|
|
7627
7637
|
_localRunning = false;
|
|
7628
7638
|
releaseFileLock(root);
|
|
@@ -7644,49 +7654,88 @@ async function runAutoVerification(options = {}) {
|
|
|
7644
7654
|
}
|
|
7645
7655
|
let fullCommand = baseCommand;
|
|
7646
7656
|
let testFiles = [];
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
if (affected.affectedTestCommands.length > 0) {
|
|
7655
|
-
fullCommand = affected.affectedTestCommands[0];
|
|
7656
|
-
workspaceScoped = true;
|
|
7657
|
+
let modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
|
|
7658
|
+
if (modified.length === 0) {
|
|
7659
|
+
try {
|
|
7660
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
7661
|
+
const gitDiff = execSync6("git diff --name-only HEAD", { encoding: "utf-8", timeout: 3e3, cwd: root }).trim();
|
|
7662
|
+
if (gitDiff) {
|
|
7663
|
+
modified = gitDiff.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
7657
7664
|
}
|
|
7665
|
+
} catch {
|
|
7658
7666
|
}
|
|
7659
|
-
} catch {
|
|
7660
7667
|
}
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7668
|
+
const targetScope = (options.scope || options.target || "").trim();
|
|
7669
|
+
if (options.command) {
|
|
7670
|
+
const cmd = options.command.trim();
|
|
7671
|
+
const isTestCmd = /^(npm|pnpm|yarn|bun|npx|pytest|cargo|go|make|jest|vitest)\s+/i.test(cmd);
|
|
7672
|
+
if (isTestCmd) {
|
|
7673
|
+
fullCommand = cmd;
|
|
7674
|
+
}
|
|
7675
|
+
} else {
|
|
7676
|
+
let workspaceScoped = false;
|
|
7677
|
+
try {
|
|
7678
|
+
const { getWorkspaceInfo: getWorkspaceInfo2, getAffectedPackages: getAffectedPackages2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
|
|
7679
|
+
const wsInfo = await getWorkspaceInfo2(root);
|
|
7680
|
+
if (wsInfo.isWorkspace && modified.length > 0 && !targetScope) {
|
|
7681
|
+
const affected = getAffectedPackages2(modified, wsInfo);
|
|
7682
|
+
if (affected.affectedTestCommands.length > 0) {
|
|
7683
|
+
fullCommand = affected.affectedTestCommands[0];
|
|
7684
|
+
workspaceScoped = true;
|
|
7670
7685
|
}
|
|
7671
7686
|
}
|
|
7672
|
-
}
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7687
|
+
} catch {
|
|
7688
|
+
}
|
|
7689
|
+
if (!workspaceScoped) {
|
|
7690
|
+
if (targetScope && targetScope !== "session-impact") {
|
|
7691
|
+
const resolved = path25.resolve(root, targetScope);
|
|
7692
|
+
if (fs24.existsSync(resolved) && fs24.statSync(resolved).isFile()) {
|
|
7693
|
+
testFiles = [path25.relative(root, resolved)];
|
|
7694
|
+
} else {
|
|
7695
|
+
const term = targetScope.toLowerCase();
|
|
7696
|
+
testFiles = modified.filter((f) => f.toLowerCase().includes(term));
|
|
7697
|
+
if (testFiles.length === 0) {
|
|
7698
|
+
try {
|
|
7699
|
+
const { default: glob } = await import("fast-glob");
|
|
7700
|
+
const baseName = path25.basename(targetScope, path25.extname(targetScope));
|
|
7701
|
+
testFiles = await glob([
|
|
7702
|
+
`**/*${baseName}*.test.*`,
|
|
7703
|
+
`**/*${baseName}*.spec.*`,
|
|
7704
|
+
`**/*${term}*test*.*`,
|
|
7705
|
+
`**/*test*/*${term}*.*`,
|
|
7706
|
+
`**/${targetScope}`
|
|
7707
|
+
], { cwd: root, ignore: ["node_modules/**", "dist/**", ".git/**"] });
|
|
7708
|
+
} catch {
|
|
7709
|
+
}
|
|
7710
|
+
}
|
|
7711
|
+
}
|
|
7712
|
+
} else {
|
|
7713
|
+
const directTests = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
|
|
7714
|
+
if (directTests.length > 0) {
|
|
7715
|
+
testFiles = directTests;
|
|
7716
|
+
} else if (modified.length > 0) {
|
|
7717
|
+
try {
|
|
7718
|
+
const { default: glob } = await import("fast-glob");
|
|
7719
|
+
for (const f of modified.slice(0, 5)) {
|
|
7720
|
+
const base = path25.basename(f, path25.extname(f));
|
|
7721
|
+
const matches = await glob([`**/*${base}*.test.*`, `**/*${base}*.spec.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
|
|
7722
|
+
testFiles.push(...matches);
|
|
7723
|
+
}
|
|
7724
|
+
} catch {
|
|
7683
7725
|
}
|
|
7684
|
-
} catch {
|
|
7685
7726
|
}
|
|
7686
7727
|
}
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7728
|
+
if (testFiles.length > 0 && (runner === "npm" || runner === "pnpm" || runner === "yarn" || runner === "bun")) {
|
|
7729
|
+
fullCommand = `${baseCommand} -- ${Array.from(new Set(testFiles)).map((f) => `"${f}"`).join(" ")}`;
|
|
7730
|
+
} else if (testFiles.length === 0 && modified.length > 0 && isJest && (runner === "npm" || runner === "pnpm" || runner === "yarn" || runner === "bun")) {
|
|
7731
|
+
const relFiles = Array.from(new Set(modified.slice(0, 15))).map((f) => `"${f}"`).join(" ");
|
|
7732
|
+
fullCommand = `${baseCommand} -- --findRelatedTests ${relFiles}`;
|
|
7733
|
+
} else if (testFiles.length === 0 && modified.length > 0 && isVitest && (runner === "npm" || runner === "pnpm" || runner === "yarn" || runner === "bun")) {
|
|
7734
|
+
const relFiles = Array.from(new Set(modified.slice(0, 15))).map((f) => `"${f}"`).join(" ");
|
|
7735
|
+
fullCommand = `${baseCommand} -- related ${relFiles} --run`;
|
|
7736
|
+
} else if (testFiles.length > 0 && runner === "pytest") {
|
|
7737
|
+
fullCommand = `pytest ${Array.from(new Set(testFiles)).map((f) => `"${f}"`).join(" ")}`;
|
|
7738
|
+
}
|
|
7690
7739
|
}
|
|
7691
7740
|
}
|
|
7692
7741
|
return await new Promise((resolve) => {
|
|
@@ -7761,9 +7810,11 @@ async function runAutoVerification(options = {}) {
|
|
|
7761
7810
|
_currentProcess = null;
|
|
7762
7811
|
releaseFileLock(root);
|
|
7763
7812
|
clearInterval(heartbeatTimer);
|
|
7764
|
-
const
|
|
7813
|
+
const isTimedOut = Boolean(error && error.killed);
|
|
7814
|
+
const statusSymbol = passed ? "\u2705" : isTimedOut ? "\u23F1\uFE0F" : "\u{1F534}";
|
|
7815
|
+
const statusTitle = passed ? "PASSED" : isTimedOut ? "TIMED_OUT" : "FAILED";
|
|
7765
7816
|
const lines = [
|
|
7766
|
-
`${statusSymbol} **Verification ${
|
|
7817
|
+
`${statusSymbol} **Verification ${statusTitle}**`,
|
|
7767
7818
|
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
7768
7819
|
"",
|
|
7769
7820
|
`\u{1F6E0}\uFE0F **Runner**: \`${runner}\``,
|
|
@@ -7776,12 +7827,14 @@ async function runAutoVerification(options = {}) {
|
|
|
7776
7827
|
if (isNoTests) {
|
|
7777
7828
|
lines[0] = `\u26AA **Verification: NO TESTS**`;
|
|
7778
7829
|
lines.push("\u2139\uFE0F No tests ran. Install Jest, Vitest, pytest, or similar to enable verification.");
|
|
7830
|
+
} else if (isTimedOut) {
|
|
7831
|
+
lines.push(`\u23F0 **Process was killed after ${timeoutMs}ms timeout (TIMED_OUT)**`);
|
|
7832
|
+
lines.push("\u2139\uFE0F Tests did not fail; the test execution exceeded the allotted timeout threshold.");
|
|
7833
|
+
lines.push(`\u{1F4A1} *Tip*: Run a specific test file via \`scope: 'tests/my-test.test.ts'\`, specify a custom command via \`command: '...' \`, or increase timeout via \`timeoutMs: ${Math.max(timeoutMs * 2, 12e4)}\`.`);
|
|
7834
|
+
if (rawOutput) lines.push("```text", rawOutput.substring(0, 1500), "```");
|
|
7779
7835
|
} else {
|
|
7780
7836
|
lines.push("\u26A0\uFE0F **Verification failed!** Please fix test failures before shipping.");
|
|
7781
|
-
if (
|
|
7782
|
-
lines.push(`\u23F0 **Process was killed after ${timeoutMs}ms timeout**`);
|
|
7783
|
-
}
|
|
7784
|
-
lines.push("```text", rawOutput.substring(0, 1500), "```");
|
|
7837
|
+
if (rawOutput) lines.push("```text", rawOutput.substring(0, 1500), "```");
|
|
7785
7838
|
}
|
|
7786
7839
|
} else {
|
|
7787
7840
|
lines.push("\u{1F389} All scoped tests passed!");
|
|
@@ -7815,7 +7868,7 @@ var init_kumaVerifier = __esm({
|
|
|
7815
7868
|
_localRunning = false;
|
|
7816
7869
|
_currentProcess = null;
|
|
7817
7870
|
STALE_RESULT_MS = 3e5;
|
|
7818
|
-
DEFAULT_TIMEOUT_MS =
|
|
7871
|
+
DEFAULT_TIMEOUT_MS = 6e4;
|
|
7819
7872
|
RUNAWAY_WINDOW_MS = 3e5;
|
|
7820
7873
|
RUNAWAY_MAX_CALLS = 3;
|
|
7821
7874
|
_verifyCallTimestamps = [];
|
|
@@ -10255,7 +10308,8 @@ async function handleInit(_params) {
|
|
|
10255
10308
|
lines.push("**Active Gotchas (fresh & budgeted)**");
|
|
10256
10309
|
for (const g of budgetedGotchas) {
|
|
10257
10310
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
10258
|
-
|
|
10311
|
+
const desc = g.description.length <= 180 ? g.description : g.description.substring(0, 177).replace(/\s+\S*$/, "") + "...";
|
|
10312
|
+
lines.push(` ${icon} [${g.severity}] ${g.filePath} \u2014 ${desc}`);
|
|
10259
10313
|
}
|
|
10260
10314
|
if (totalActiveCount > budgetedGotchas.length) {
|
|
10261
10315
|
lines.push(` \u2139\uFE0F +${totalActiveCount - budgetedGotchas.length} more active gotchas in DB (query: kuma_memory({ action: 'gotcha' }))`);
|
|
@@ -10433,7 +10487,10 @@ async function handleHistory(params) {
|
|
|
10433
10487
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
10434
10488
|
lines.push(` ${icon} [${g.severity}] ${g.description}`);
|
|
10435
10489
|
if (g.triggerCommand) lines.push(` \u2328\uFE0F when running: \`${g.triggerCommand}\``);
|
|
10436
|
-
if (g.workaround)
|
|
10490
|
+
if (g.workaround) {
|
|
10491
|
+
const work = g.workaround.length <= 180 ? g.workaround : g.workaround.substring(0, 177).replace(/\s+\S*$/, "") + "...";
|
|
10492
|
+
lines.push(` \u{1F4A1} ${work}`);
|
|
10493
|
+
}
|
|
10437
10494
|
}
|
|
10438
10495
|
lines.push("");
|
|
10439
10496
|
}
|
|
@@ -10447,7 +10504,10 @@ async function handleHistory(params) {
|
|
|
10447
10504
|
stmt.free();
|
|
10448
10505
|
if (resolved.length > 0) {
|
|
10449
10506
|
lines.push("\u2705 **Resolved gotchas** (fixed):");
|
|
10450
|
-
for (const r of resolved)
|
|
10507
|
+
for (const r of resolved) {
|
|
10508
|
+
const rdesc = r.description.length <= 140 ? r.description : r.description.substring(0, 137).replace(/\s+\S*$/, "") + "...";
|
|
10509
|
+
lines.push(` \u2705 ${rdesc}`);
|
|
10510
|
+
}
|
|
10451
10511
|
lines.push("");
|
|
10452
10512
|
}
|
|
10453
10513
|
} catch {
|
|
@@ -10554,13 +10614,15 @@ async function handleMemory(params) {
|
|
|
10554
10614
|
}
|
|
10555
10615
|
}
|
|
10556
10616
|
async function handleDecision(params) {
|
|
10557
|
-
|
|
10558
|
-
|
|
10617
|
+
const title = params.title || params.content || (params.target ? `Decision regarding ${params.target}` : params.scope ? `Decision for ${params.scope}` : "");
|
|
10618
|
+
const rationale = params.rationale || params.description || (params.content && params.content !== title ? params.content : "");
|
|
10619
|
+
if (!title) return "\u26A0\uFE0F `title` is required (or provide `content`/`target`). Use: kuma_memory({ action: 'decision', title: '...', rationale: '...' })";
|
|
10620
|
+
if (!rationale) return "\u26A0\uFE0F `rationale` is required (or provide `content`/`description`). Why did you choose this option?";
|
|
10559
10621
|
const result = await recordDecision({
|
|
10560
|
-
title
|
|
10561
|
-
context: params.context || "",
|
|
10622
|
+
title,
|
|
10623
|
+
context: params.context || (params.target ? `Target: ${params.target}` : params.scope ? `Scope: ${params.scope}` : ""),
|
|
10562
10624
|
options: [],
|
|
10563
|
-
rationale
|
|
10625
|
+
rationale,
|
|
10564
10626
|
outcome: params.outcome || "implemented",
|
|
10565
10627
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10566
10628
|
});
|
|
@@ -11287,13 +11349,13 @@ async function handleRollbackLabel(params) {
|
|
|
11287
11349
|
const { rollbackToCheckpoint: rollbackToCheckpoint2 } = await Promise.resolve().then(() => (init_kumaCheckpoint(), kumaCheckpoint_exports));
|
|
11288
11350
|
return await rollbackToCheckpoint2(params.label);
|
|
11289
11351
|
}
|
|
11290
|
-
var _lastVerifyCall = 0;
|
|
11291
11352
|
var VERIFY_COOLDOWN_MS = 3e4;
|
|
11353
|
+
var _lastVerifyCall = 0;
|
|
11292
11354
|
async function handleVerify(params) {
|
|
11293
11355
|
const now = Date.now();
|
|
11294
|
-
if (
|
|
11356
|
+
if (now - _lastVerifyCall < VERIFY_COOLDOWN_MS && !params.force) {
|
|
11295
11357
|
const remaining = Math.ceil((VERIFY_COOLDOWN_MS - (now - _lastVerifyCall)) / 1e3);
|
|
11296
|
-
return `\u23F3
|
|
11358
|
+
return `\u23F3 Verification cooldown active. Please wait ${remaining}s before calling verify again, or use force: true.`;
|
|
11297
11359
|
}
|
|
11298
11360
|
_lastVerifyCall = now;
|
|
11299
11361
|
const recordingSummary = sessionMemory.getRecordingSummary();
|
|
@@ -11312,12 +11374,16 @@ async function handleVerify(params) {
|
|
|
11312
11374
|
|
|
11313
11375
|
\u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
|
|
11314
11376
|
}
|
|
11315
|
-
|
|
11377
|
+
const effectiveScope = params.scope || params.target;
|
|
11378
|
+
const timeoutMs = typeof params.timeoutMs === "number" && params.timeoutMs > 0 ? params.timeoutMs : typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 : process.env.KUMA_VERIFY_TIMEOUT_MS ? parseInt(process.env.KUMA_VERIFY_TIMEOUT_MS, 10) : 6e4;
|
|
11379
|
+
sessionMemory.recordToolCall("kuma_safety_verify", { scope: effectiveScope, timeoutMs });
|
|
11316
11380
|
const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
|
|
11317
11381
|
const verifyResult = await runAutoVerification2({
|
|
11318
|
-
scope:
|
|
11382
|
+
scope: effectiveScope,
|
|
11383
|
+
target: params.target,
|
|
11384
|
+
command: params.command,
|
|
11319
11385
|
force: params.force,
|
|
11320
|
-
timeoutMs
|
|
11386
|
+
timeoutMs
|
|
11321
11387
|
});
|
|
11322
11388
|
return verifyResult + recordingWarning;
|
|
11323
11389
|
}
|
|
@@ -11385,13 +11451,13 @@ var CORE_NOTE = "\n\nCORE ACTIONS (use these): ";
|
|
|
11385
11451
|
function registerAllTools(server) {
|
|
11386
11452
|
server.tool(
|
|
11387
11453
|
"kuma_context",
|
|
11388
|
-
"Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way), `flow` (read a recorded architecture flow).",
|
|
11454
|
+
"Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way), `flow` (read a recorded architecture flow), `map` (repository topology), `impact` (blast radius analysis).",
|
|
11389
11455
|
{
|
|
11390
|
-
action: z.enum(["init", "research", "history", "flow"]).describe(
|
|
11391
|
-
"init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow"
|
|
11456
|
+
action: z.enum(["init", "research", "history", "flow", "map", "impact"]).describe(
|
|
11457
|
+
"init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow, map=repo topology, impact=blast radius"
|
|
11392
11458
|
),
|
|
11393
|
-
scope: z.string().optional().describe("Research scope for
|
|
11394
|
-
target: z.string().optional().describe("Target symbol/file for history/flow"),
|
|
11459
|
+
scope: z.string().optional().describe("Research scope or target for impact"),
|
|
11460
|
+
target: z.string().optional().describe("Target symbol/file for history/flow/impact"),
|
|
11395
11461
|
goal: z.string().optional().describe("Current goal (for init)")
|
|
11396
11462
|
},
|
|
11397
11463
|
async (params) => {
|
|
@@ -11417,17 +11483,20 @@ function registerAllTools(server) {
|
|
|
11417
11483
|
"gotcha=record bug/quirk, decision=ADR, arch_flow=record architecture flow, research_save=save findings, search=quick memory+graph lookup"
|
|
11418
11484
|
),
|
|
11419
11485
|
scope: z.string().optional().describe("File path for gotcha \u2014 or scope for research_save/search"),
|
|
11486
|
+
target: z.string().optional().describe("Target file, gotcha ID, or component"),
|
|
11420
11487
|
trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha"),
|
|
11421
11488
|
query: z.string().optional().describe("Search query for search action"),
|
|
11422
11489
|
content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
|
|
11423
11490
|
record: z.string().optional().describe("JSON record string for research_save"),
|
|
11424
11491
|
confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
|
|
11425
|
-
title: z.string().optional().describe("Decision title (
|
|
11492
|
+
title: z.string().optional().describe("Decision title (optional if content/target provided)"),
|
|
11426
11493
|
context: z.string().optional().describe("Decision context"),
|
|
11427
|
-
rationale: z.string().optional().describe("Decision rationale (
|
|
11494
|
+
rationale: z.string().optional().describe("Decision rationale (optional if content provided)"),
|
|
11428
11495
|
outcome: z.string().optional().describe("Decision outcome (default: implemented)"),
|
|
11429
|
-
status: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
|
|
11430
|
-
|
|
11496
|
+
status: z.string().optional().describe("Gotcha severity (low|medium|high|critical) or status (resolved|deprecated)"),
|
|
11497
|
+
severity: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
|
|
11498
|
+
description: z.string().optional().describe("Gotcha workaround / rationale"),
|
|
11499
|
+
id: z.union([z.number(), z.string()]).optional().describe("Gotcha ID for resolution or deprecation"),
|
|
11431
11500
|
limit: z.number().min(1).max(100).optional().describe("Result limit for search")
|
|
11432
11501
|
},
|
|
11433
11502
|
async (params) => {
|
|
@@ -11436,6 +11505,7 @@ function registerAllTools(server) {
|
|
|
11436
11505
|
const text = await handleMemory({
|
|
11437
11506
|
action: params.action,
|
|
11438
11507
|
scope: params.scope,
|
|
11508
|
+
target: params.target,
|
|
11439
11509
|
query: params.query,
|
|
11440
11510
|
content: params.content,
|
|
11441
11511
|
record: params.record,
|
|
@@ -11446,8 +11516,10 @@ function registerAllTools(server) {
|
|
|
11446
11516
|
outcome: params.outcome,
|
|
11447
11517
|
limit: params.limit,
|
|
11448
11518
|
status: params.status,
|
|
11519
|
+
severity: params.severity,
|
|
11449
11520
|
description: params.description,
|
|
11450
|
-
trigger_command: params.trigger_command
|
|
11521
|
+
trigger_command: params.trigger_command,
|
|
11522
|
+
id: params.id
|
|
11451
11523
|
});
|
|
11452
11524
|
return { content: [{ type: "text", text }] };
|
|
11453
11525
|
} catch (err) {
|
|
@@ -11463,7 +11535,11 @@ function registerAllTools(server) {
|
|
|
11463
11535
|
"guard=anti-patterns/drift/loops before risky work, verify=scoped tests after edits, checkpoint=labeled snapshot before risky work, rollback_label=restore a labeled snapshot"
|
|
11464
11536
|
),
|
|
11465
11537
|
scope: z.string().optional().describe("Scope for verify (e.g. 'auth', file path)"),
|
|
11466
|
-
|
|
11538
|
+
target: z.string().optional().describe("Target test file or scope for verify"),
|
|
11539
|
+
command: z.string().optional().describe("Custom test command to run during verify"),
|
|
11540
|
+
timeoutMs: z.number().optional().describe("Custom timeout in milliseconds for verify (default: 60000)"),
|
|
11541
|
+
timeout: z.number().optional().describe("Custom timeout in seconds for verify"),
|
|
11542
|
+
force: z.boolean().optional().describe("Force re-run even if cache is fresh or rate limited (verify)"),
|
|
11467
11543
|
guardGoal: z.string().optional().describe("Goal for guard check"),
|
|
11468
11544
|
guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
|
|
11469
11545
|
label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label"),
|
|
@@ -11477,6 +11553,10 @@ function registerAllTools(server) {
|
|
|
11477
11553
|
guardGoal: params.guardGoal,
|
|
11478
11554
|
guardCheck: params.guardCheck,
|
|
11479
11555
|
scope: params.scope,
|
|
11556
|
+
target: params.target,
|
|
11557
|
+
command: params.command,
|
|
11558
|
+
timeoutMs: params.timeoutMs,
|
|
11559
|
+
timeout: params.timeout,
|
|
11480
11560
|
force: params.force,
|
|
11481
11561
|
label: params.label,
|
|
11482
11562
|
description: params.description
|
|
@@ -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/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",
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
"mcp_config.json",
|
|
17
17
|
"install.sh",
|
|
18
18
|
"INSTALL.md",
|
|
19
|
+
"hooks.json",
|
|
20
|
+
"hooks/",
|
|
21
|
+
"skills/",
|
|
22
|
+
"commands/",
|
|
23
|
+
"agents/",
|
|
19
24
|
".claude-plugin/"
|
|
20
25
|
],
|
|
21
26
|
"dependencies": {
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://antigravity.google/schemas/v1/plugin.json",
|
|
3
3
|
"name": "kuma",
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.5",
|
|
5
5
|
"description": "Safety-first context & orchestration engine for AI coding agents. Shadow memory, gotcha lifecycle, blast radius impact analysis, and test verification.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "plumpslabs",
|
|
@@ -11,8 +11,50 @@
|
|
|
11
11
|
"skills": [
|
|
12
12
|
{
|
|
13
13
|
"name": "kuma-mcp",
|
|
14
|
-
"description": "Kuma MCP
|
|
15
|
-
"file": "
|
|
14
|
+
"description": "Kuma MCP β safety toolkit for AI coding agents",
|
|
15
|
+
"file": "skills/kuma/SKILL.md"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"commands": [
|
|
19
|
+
{
|
|
20
|
+
"name": "kuma:init",
|
|
21
|
+
"description": "Initialize Kuma session context, lean brief (<500 tokens), and branch tracking",
|
|
22
|
+
"file": "commands/kuma:init.md"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "kuma:research",
|
|
26
|
+
"description": "Run 5-step research pipeline before editing unfamiliar code",
|
|
27
|
+
"file": "commands/kuma:research.md"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "kuma:impact",
|
|
31
|
+
"description": "Calculate blast radius, affected consumers, and safety risk score",
|
|
32
|
+
"file": "commands/kuma:impact.md"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "kuma:guard",
|
|
36
|
+
"description": "Pre-flight safety check against anti-patterns, drift, and circular deps",
|
|
37
|
+
"file": "commands/kuma:guard.md"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "kuma:verify",
|
|
41
|
+
"description": "Post-edit scoped test verification and validation",
|
|
42
|
+
"file": "commands/kuma:verify.md"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "kuma:checkpoint",
|
|
46
|
+
"description": "Create an atomic named snapshot before risky work",
|
|
47
|
+
"file": "commands/kuma:checkpoint.md"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "kuma:rollback",
|
|
51
|
+
"description": "Restore codebase from a labeled checkpoint snapshot",
|
|
52
|
+
"file": "commands/kuma:rollback.md"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "kuma:studio",
|
|
56
|
+
"description": "Launch or view Kuma Studio web dashboard",
|
|
57
|
+
"file": "commands/kuma:studio.md"
|
|
16
58
|
}
|
|
17
59
|
]
|
|
18
60
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kuma-mcp
|
|
3
|
+
description: Kuma MCP β safety toolkit for AI coding agents
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.
|
|
7
|
+
Record what matters, skip what doesn't.
|
|
8
|
+
|
|
9
|
+
<kuma_obedience>
|
|
10
|
+
## π Obedience (non-negotiable)
|
|
11
|
+
- MUST call `kuma_context({ action: "init" })` at session start.
|
|
12
|
+
- MUST check blast radius `kuma_context({ action: "impact", target: "<file>" })` before touching core/shared modules.
|
|
13
|
+
- MUST record gotchas IMMEDIATELY when a bug/quirk is found.
|
|
14
|
+
- MUST call `kuma_safety({ action: "verify" })` after edits.
|
|
15
|
+
- MUST NOT call actions outside the 15 core actions.
|
|
16
|
+
</kuma_obedience>
|
|
17
|
+
|
|
18
|
+
<kuma_workflow>
|
|
19
|
+
π§ **Before coding:** `kuma_context({ action: "init" })`
|
|
20
|
+
πΊοΈ **Monorepo map:** `kuma_context({ action: "map" })`
|
|
21
|
+
π¬ **Unfamiliar code / blast radius:** `kuma_context({ action: "research", scope: "<area>" })` or `kuma_context({ action: "impact", target: "<file>" })`
|
|
22
|
+
π **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)
|
|
23
|
+
π§ **Chose between options:** `kuma_memory({ action: "decision" })`
|
|
24
|
+
π **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)
|
|
25
|
+
π‘οΈ **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`
|
|
26
|
+
</kuma_workflow>
|
|
27
|
+
|
|
28
|
+
π Full rules: `.kuma/init.md`
|