@plumpslabs/kuma 2.1.2 → 2.1.3

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.
Files changed (2) hide show
  1. package/dist/index.js +103 -11
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -2303,7 +2303,7 @@ function getGitChangedFiles() {
2303
2303
  }
2304
2304
  }
2305
2305
  async function handleCodeReviewer(params) {
2306
- const { files: inputFiles, focus = "correctness", customCriteria, format = "text" } = params;
2306
+ const { files: inputFiles, focus = "correctness", customCriteria, format = "text", convention } = params;
2307
2307
  let files = inputFiles ?? [];
2308
2308
  let isAutoDetected = false;
2309
2309
  if (files.length === 0) {
@@ -2362,6 +2362,9 @@ Pass an explicit 'files' array to review specific files.`;
2362
2362
  checkOverEngineering(filePath, content, allIssues);
2363
2363
  break;
2364
2364
  }
2365
+ if (convention === "matcha") {
2366
+ checkMatchaConventions(filePath, content, allIssues);
2367
+ }
2365
2368
  } catch (err) {
2366
2369
  allIssues.push({
2367
2370
  file: filePath,
@@ -2389,6 +2392,7 @@ Pass an explicit 'files' array to review specific files.`;
2389
2392
  filesReviewed,
2390
2393
  filesRequested: files.length,
2391
2394
  focus,
2395
+ convention,
2392
2396
  autoDetected: isAutoDetected,
2393
2397
  ...customCriteria ? { customCriteria } : {}
2394
2398
  };
@@ -2915,8 +2919,8 @@ function collectBlockLines(lines, startIdx) {
2915
2919
  let depth = 0;
2916
2920
  let started = false;
2917
2921
  const block = [];
2918
- for (let j = startIdx; j < lines.length; j++) {
2919
- const line = lines[j];
2922
+ for (let i = startIdx; i < lines.length; i++) {
2923
+ const line = lines[i];
2920
2924
  block.push(line);
2921
2925
  for (const ch of line) {
2922
2926
  if (ch === "{") {
@@ -2933,6 +2937,73 @@ function extractBodyAfter(lines, match) {
2933
2937
  if (startIdx < 0) return "";
2934
2938
  return collectBlockLines(lines, startIdx).join("\n");
2935
2939
  }
2940
+ function checkMatchaConventions(filePath, content, issues) {
2941
+ const lines = content.split("\n");
2942
+ const text = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
2943
+ for (let i = 0; i < lines.length; i++) {
2944
+ const line = stripCommentsAndStrings(lines[i]);
2945
+ const lineNum = i + 1;
2946
+ if (/\b(?:const|let|var)\s+\w+\s*=\s*(?:[2-9]|\d{2,})\b/.test(line)) {
2947
+ if (!/eslint-disable/.test(lines[i])) {
2948
+ issues.push({
2949
+ file: filePath,
2950
+ line: lineNum,
2951
+ severity: "info",
2952
+ rule: "matcha/no-hardcoded-values",
2953
+ message: "Possible hardcoded numeric value (magic number)",
2954
+ suggestion: "Extract to a named constant"
2955
+ });
2956
+ }
2957
+ }
2958
+ const envMatch = line.match(/process\.env\.([A-Z0-9_]+)/);
2959
+ if (envMatch) {
2960
+ const envName = envMatch[1];
2961
+ if (!envName.includes("_")) {
2962
+ issues.push({
2963
+ file: filePath,
2964
+ line: lineNum,
2965
+ severity: "warning",
2966
+ rule: "matcha/env-prefix",
2967
+ message: `Environment variable "${envName}" doesn't seem to have a prefix`,
2968
+ suggestion: "Use an APPNAME_ prefix for environment variables"
2969
+ });
2970
+ }
2971
+ }
2972
+ }
2973
+ const interfaceNames = [];
2974
+ for (let i = 0; i < lines.length; i++) {
2975
+ const m = lines[i].match(/^\s*(export\s+)?interface\s+(\w+)/);
2976
+ if (m) interfaceNames.push(m[2]);
2977
+ }
2978
+ for (const iface of interfaceNames) {
2979
+ const implCount = (text.match(new RegExp(`implements\\s+.*\\b${iface}\\b`, "g")) || []).length;
2980
+ if (implCount <= 1) {
2981
+ const lineNum = lines.findIndex((l) => l.includes(`interface ${iface}`)) + 1;
2982
+ issues.push({
2983
+ file: filePath,
2984
+ line: lineNum,
2985
+ severity: "info",
2986
+ rule: "matcha/no-premature-abstraction",
2987
+ message: `Interface "${iface}" has only 1 implementation \u2014 premature abstraction?`,
2988
+ suggestion: "Wait for a 2nd use case before abstracting"
2989
+ });
2990
+ }
2991
+ }
2992
+ const funcBodies = findBlockBodies(lines, /^\s*(export\s+)?(async\s+)?function\s+\w+/);
2993
+ for (const { body, nameLine, name } of funcBodies) {
2994
+ const nonEmptyLines = body.split("\n").filter((l) => l.trim()).length;
2995
+ if (nonEmptyLines > 40) {
2996
+ issues.push({
2997
+ file: filePath,
2998
+ line: nameLine,
2999
+ severity: "warning",
3000
+ rule: "matcha/single-responsibility",
3001
+ message: `Function "${name}" is ${nonEmptyLines} lines long \u2014 might be doing >1 thing`,
3002
+ suggestion: "Extract into smaller, focused functions"
3003
+ });
3004
+ }
3005
+ }
3006
+ }
2936
3007
  function formatReviewOutput(issues, filesReviewed, totalFiles, focus, customCriteria, autoDetected) {
2937
3008
  const errors = issues.filter((i) => i.severity === "error");
2938
3009
  const warnings = issues.filter((i) => i.severity === "warning");
@@ -5973,9 +6044,9 @@ function handleCodewhaleSecondary(root, results) {
5973
6044
  });
5974
6045
  }
5975
6046
  }
5976
- function runInit(types, projectRoot) {
5977
- const root = projectRoot ?? getProjectRoot();
5978
- const selected = types.length > 0 ? types : ALL_CONFIG_TYPES;
6047
+ function runInit(options) {
6048
+ const root = options.projectRoot ?? getProjectRoot();
6049
+ const selected = options.types.length > 0 ? options.types : ALL_CONFIG_TYPES;
5979
6050
  const results = [];
5980
6051
  const selectedSet = new Set(selected);
5981
6052
  const agentsMdSelected = AGENTS_MD_TYPES.filter((t) => selectedSet.has(t));
@@ -5989,12 +6060,16 @@ function runInit(types, projectRoot) {
5989
6060
  agentsMdHandled = true;
5990
6061
  const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
5991
6062
  if (fs16.existsSync(fullPath)) {
5992
- const existingContent = fs16.readFileSync(fullPath, "utf-8");
5993
- if (existingContent.includes("_Generated by Kuma MCP_")) {
6063
+ if (options.skipExisting) {
5994
6064
  results.push({ type, filePath: relativePath, action: "skipped" });
5995
6065
  } else {
5996
- fs16.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
5997
- results.push({ type, filePath: relativePath, action: "appended" });
6066
+ const existingContent = fs16.readFileSync(fullPath, "utf-8");
6067
+ if (existingContent.includes("_Generated by Kuma MCP_")) {
6068
+ results.push({ type, filePath: relativePath, action: "skipped" });
6069
+ } else {
6070
+ fs16.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
6071
+ results.push({ type, filePath: relativePath, action: "appended" });
6072
+ }
5998
6073
  }
5999
6074
  } else {
6000
6075
  const dir = path16.dirname(fullPath);
@@ -6011,6 +6086,10 @@ function runInit(types, projectRoot) {
6011
6086
  } else {
6012
6087
  const template = getTemplate();
6013
6088
  if (fs16.existsSync(fullPath)) {
6089
+ if (options.skipExisting) {
6090
+ results.push({ type, filePath: relativePath, action: "skipped" });
6091
+ continue;
6092
+ }
6014
6093
  const existingContent = fs16.readFileSync(fullPath, "utf-8");
6015
6094
  if (existingContent.includes("_Generated by Kuma MCP_")) {
6016
6095
  if (type === "antigravity") {
@@ -6110,6 +6189,8 @@ Usage:
6110
6189
  npx @plumpslabs/kuma Start MCP server (default)
6111
6190
  npx @plumpslabs/kuma init Generate AI agent config files
6112
6191
  npx @plumpslabs/kuma init --all Generate ALL config files
6192
+ npx @plumpslabs/kuma init --merge Append to existing files (default)
6193
+ npx @plumpslabs/kuma init --skip-existing Skip generation if file exists
6113
6194
  npx @plumpslabs/kuma init --claude --cursor Generate specific files
6114
6195
  npx @plumpslabs/kuma init --help Show this help
6115
6196
 
@@ -6183,9 +6264,20 @@ async function main() {
6183
6264
  }
6184
6265
  }
6185
6266
  }
6186
- const results = runInit(selectedTypes, process.cwd());
6267
+ const skipExisting = requestedFlags.includes("--skip-existing");
6268
+ const merge = requestedFlags.includes("--merge");
6269
+ const results = runInit({ types: selectedTypes, projectRoot: process.cwd(), skipExisting });
6187
6270
  const output = formatInitResults(results);
6188
6271
  console.log(output);
6272
+ const fs17 = await import("fs");
6273
+ const path17 = await import("path");
6274
+ const matchaSkills = path17.resolve(process.cwd(), "skills/matcha/SKILL.md");
6275
+ const matchaAgents = path17.resolve(process.cwd(), ".agents/skills/matcha/SKILL.md");
6276
+ const matchaCursor = path17.resolve(process.cwd(), ".cursor/rules/matcha.mdc");
6277
+ const matchaWindsurf = path17.resolve(process.cwd(), ".windsurf/rules/matcha.md");
6278
+ if (fs17.existsSync(matchaSkills) || fs17.existsSync(matchaAgents) || fs17.existsSync(matchaCursor) || fs17.existsSync(matchaWindsurf)) {
6279
+ console.error("\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!");
6280
+ }
6189
6281
  process.exit(0);
6190
6282
  }
6191
6283
  sessionMemory.init({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
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.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -56,7 +56,7 @@
56
56
  "git-diff",
57
57
  "cli"
58
58
  ],
59
- "repository": {
59
+ "repository": {
60
60
  "type": "git",
61
61
  "url": "git+https://github.com/plumpslabs/kuma.git"
62
62
  },
@@ -65,4 +65,4 @@
65
65
  },
66
66
  "homepage": "https://github.com/plumpslabs/kuma#readme",
67
67
  "license": "MIT"
68
- }
68
+ }