@jeeva1398/eventa 0.1.0 → 0.1.1

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +115 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -27,7 +27,7 @@ Eventa parses the stack trace or `tsc` output, reads the source lines it points
27
27
  Reviews `git diff --staged`, falling back to unstaged changes. Use `--base main` to review everything since `main`. Each file gets static checks first (missing `await`, SQL/command injection, `eval`, sync fs, empty `catch`, disabled TLS, hard-coded secrets), then the model confirms or dismisses them and reports `- [severity] line N: problem → fix`.
28
28
 
29
29
  ### `eventa deps`
30
- Runs `npm audit` and `npm outdated` and scans your imports. It prints the vulnerabilities with their **exact fix commands**, outdated packages (flagging major bumps), unused dependencies and packages that are imported but not declared. Then it adds AI advice on upgrade risk. Use `--no-ai` for the report only.
30
+ Runs `npm audit` and `npm outdated` and scans your imports. It prints the vulnerabilities with their **exact fix commands**, outdated packages (flagging major bumps), unused dependencies and packages that are imported but not declared. Then it prints an **action plan** built only from facts: the fix commands, verified breaking-change notes for about 25 popular packages (others say "read the changelog" instead of guessing), and cleanup commands. It is instant and works without the model. Add `--ai` for optional extra commentary from the model.
31
31
 
32
32
  ### More
33
33
  - `eventa ask <question>`: any Node.js question
package/dist/index.js CHANGED
@@ -559,6 +559,11 @@ function fixPlan(vulns) {
559
559
  }
560
560
  return [...groups].map(([command, fixes]) => ({ command, fixes }));
561
561
  }
562
+ function breaking(current, latest) {
563
+ const [cMajor, cMinor] = current.split(".");
564
+ const [lMajor, lMinor] = latest.split(".");
565
+ return cMajor !== lMajor || cMajor === "0" && cMinor !== lMinor;
566
+ }
562
567
  function parseOutdated(text) {
563
568
  const json = parseJson(text) ?? {};
564
569
  return Object.entries(json).map(([name, v]) => ({
@@ -566,7 +571,7 @@ function parseOutdated(text) {
566
571
  current: v.current,
567
572
  wanted: v.wanted,
568
573
  latest: v.latest,
569
- major: Boolean(v.current && v.latest && v.current.split(".")[0] !== v.latest.split(".")[0])
574
+ major: Boolean(v.current && v.latest && breaking(v.current, v.latest))
570
575
  }));
571
576
  }
572
577
  function packageName(specifier) {
@@ -644,21 +649,99 @@ async function collectDeps(cwd) {
644
649
  };
645
650
  }
646
651
 
652
+ // src/prompts/upgrades.ts
653
+ var UPGRADE_NOTES = {
654
+ "express@5": "Express 5 uses path-to-regexp v8: wildcard routes must be named (`/*splat`) and optional params use braces; `req.param()` and `app.del()` were removed; rejected promises in async handlers now go to error middleware. Run your route tests.",
655
+ "body-parser@2": "body-parser 2 (used by Express 5) drops deprecated options and changes `urlencoded` `extended` default to false; check form parsing.",
656
+ "chalk@5": "chalk 5 is ESM-only; `require('chalk')` fails in CommonJS. Stay on 4 for CJS or migrate the project to ESM.",
657
+ "node-fetch@3": "node-fetch 3 is ESM-only. On Node 18+ prefer the built-in global `fetch` and remove the dependency.",
658
+ uuid: "Deep imports such as `require('uuid/v4')` were removed in v8; use `const { v4 } = require('uuid')`.",
659
+ "mongoose@7": "Mongoose 7 removed callback support from model methods (use async/await) and changed the `strictQuery` default.",
660
+ "mongoose@8": "Mongoose 7 removed callback support from model methods (use async/await) and changed the `strictQuery` default; Mongoose 8 needs MongoDB 4.4+.",
661
+ "jest@28": "Jest 28 no longer bundles jsdom (install `jest-environment-jsdom`) and renamed `testURL` to `testEnvironmentOptions.url`.",
662
+ "jest@29": "Jest 28 no longer bundles jsdom (install `jest-environment-jsdom`) and renamed `testURL` to `testEnvironmentOptions.url`; Jest 29 changed the default snapshot format.",
663
+ "eslint@9": "ESLint 9 uses flat config (`eslint.config.js`) by default and ignores `.eslintrc`; many plugins need updated versions.",
664
+ "typescript@5": "TypeScript 5 deprecates flags such as `importsNotUsedAsValues` and implements standard decorators unless `experimentalDecorators` is set; expect some new type errors.",
665
+ "axios@1": "axios 1.x adds an `exports` map, so deep imports like `axios/lib/...` break, and some TypeScript types were renamed.",
666
+ helmet: "Newer helmet enables a strict Content-Security-Policy and cross-origin headers by default, which can block inline scripts and third-party assets; test your pages.",
667
+ inquirer: "inquirer 9+ is ESM-only and v10+ moved to the new `@inquirer/prompts` API.",
668
+ got: "got 12+ is ESM-only and changed several option names; consider native `fetch` for simple calls.",
669
+ "webpack@5": "webpack 5 removed automatic Node.js polyfills (`Buffer`, `process`, `crypto`) for browser bundles; add `resolve.fallback` where needed. It also fixes ERR_OSSL_EVP_UNSUPPORTED on Node 17+.",
670
+ dotenv: "Low risk: dotenv 16 keeps the same `config()` API and adds multiline values.",
671
+ "@nestjs/core@11": "Nest 11 requires Node 20+ and uses Express 5 by default, so wildcard routes change syntax (`*` must be named, e.g. `*splat`). Upgrade all `@nestjs/*` packages together.",
672
+ "@nestjs/common@11": "Nest 11 requires Node 20+ and uses Express 5 by default, so wildcard routes change syntax (`*` must be named, e.g. `*splat`). Upgrade all `@nestjs/*` packages together.",
673
+ "@prisma/client": "Prisma 5 removed `rejectOnNotFound` (use `findUniqueOrThrow`); Prisma 6 needs Node 18.18+ and TypeScript 5.1+, and `Bytes` fields become `Uint8Array`. Upgrade `prisma` and `@prisma/client` to the same version and re-run `prisma generate`.",
674
+ prisma: "Upgrade `prisma` and `@prisma/client` to the same version and re-run `prisma generate`; Prisma 5 removed `rejectOnNotFound`, Prisma 6 needs Node 18.18+ and TypeScript 5.1+.",
675
+ "class-validator@0": "Low risk for most apps: 0.14 fixes a security issue where unknown values skipped validation; `forbidUnknownValues` now defaults to true, so plain objects without decorators are rejected.",
676
+ "redis@4": "node-redis 4 is promise-based: call `await client.connect()` before use, and commands are camelCase (`hSet`, not `hset`) with no callbacks.",
677
+ glob: "glob 9+ has a promise API only (no callbacks) and a named export: `import { glob } from 'glob'`.",
678
+ rimraf: "rimraf 4+ has a promise API only and a named export: `import { rimraf } from 'rimraf'`."
679
+ };
680
+ var major = (version) => version?.split(".")[0];
681
+ function upgradeNote(name, latest) {
682
+ return UPGRADE_NOTES[`${name}@${major(latest)}`] ?? UPGRADE_NOTES[name];
683
+ }
684
+
685
+ // src/context/depsPlan.ts
686
+ var SEVERITY_RANK = ["critical", "high", "moderate", "low", "info"];
687
+ function buildPlan(r) {
688
+ const sections = [];
689
+ const byName = new Map(r.vulnerabilities.map((v) => [v.name, v]));
690
+ if (r.vulnerabilities.length) {
691
+ const items = fixPlan(r.vulnerabilities).map(({ command, fixes }) => {
692
+ const vulns = fixes.map((n) => byName.get(n));
693
+ const worst = vulns.map((v) => v.severity).sort((a, b) => SEVERITY_RANK.indexOf(a) - SEVERITY_RANK.indexOf(b))[0];
694
+ const titles = vulns.map((v) => `${v.name}: ${v.title ?? "advisory"}`).join("; ");
695
+ if (command === "no fix available") return `- **No fix available** (${worst}): ${titles}. Replace the package or check whether the vulnerable code is reachable.`;
696
+ const major2 = command.endsWith("(major)");
697
+ return `- \`${command.replace(" (major)", "")}\`${major2 ? " **(major version, see notes below)**" : ""} (${worst}): ${titles}.`;
698
+ });
699
+ sections.push(`**1. Security**
700
+ ${items.join("\n")}`);
701
+ }
702
+ const majors = r.outdated.filter((o) => o.major);
703
+ if (majors.length) {
704
+ const items = majors.map((o) => {
705
+ const note = upgradeNote(o.name, o.latest);
706
+ return `- **${o.name} ${o.current ?? "?"} \u2192 ${o.latest}**: ${note ?? "no verified notes. Read its changelog / release notes before upgrading."}`;
707
+ });
708
+ sections.push(`**2. Major upgrades**
709
+ ${items.join("\n")}`);
710
+ }
711
+ const cleanup = [];
712
+ if (r.unused.length) cleanup.push(`- \`npm uninstall ${r.unused.join(" ")}\`: not imported anywhere (double-check config files, scripts and dynamic requires first).`);
713
+ if (r.missing.length) cleanup.push(`- \`npm install ${r.missing.join(" ")}\`: imported in code but missing from package.json.`);
714
+ const minors = r.outdated.filter((o) => !o.major);
715
+ if (minors.length) cleanup.push(`- \`npm update\`: safe minor/patch updates for ${minors.map((m) => m.name).join(", ")}.`);
716
+ if (cleanup.length) sections.push(`**3. Cleanup**
717
+ ${cleanup.join("\n")}`);
718
+ return sections.join("\n\n");
719
+ }
720
+
647
721
  // src/prompts/deps.ts
648
722
  function depsPrompt(r, nodeVersion) {
649
- const lines = [`Node ${nodeVersion}`];
723
+ const lines2 = [`Node ${nodeVersion}`];
650
724
  if (r.vulnerabilities.length) {
651
- lines.push("Vulnerabilities:", ...r.vulnerabilities.slice(0, 20).map((v) => `- ${v.name} [${v.severity}] ${v.title ?? ""}`));
652
- lines.push("Fix commands (already computed and correct):", ...fixPlan(r.vulnerabilities).map((f) => `- ${f.command} \u2192 fixes ${f.fixes.join(", ")}`));
725
+ const title = new Map(r.vulnerabilities.map((v) => [v.name, v.title]));
726
+ lines2.push("Vulnerabilities:", ...r.vulnerabilities.slice(0, 20).map((v) => `- ${v.name} [${v.severity}] ${v.title ?? ""}`));
727
+ lines2.push(
728
+ "Fix commands (already computed and correct):",
729
+ ...fixPlan(r.vulnerabilities).map((f) => `- ${f.command} \u2192 fixes ${f.fixes.map((n) => title.get(n) ? `${n} (${title.get(n)})` : n).join(", ")}`)
730
+ );
653
731
  }
654
732
  if (r.outdated.length) {
655
- lines.push("Outdated:", ...r.outdated.slice(0, 25).map((o) => `- ${o.name} ${o.current ?? "not installed"} \u2192 latest ${o.latest}${o.major ? " (MAJOR)" : ""}`));
733
+ lines2.push("Outdated:", ...r.outdated.slice(0, 25).map((o) => `- ${o.name} ${o.current ?? "not installed"} \u2192 latest ${o.latest}${o.major ? " (MAJOR)" : ""}`));
734
+ const notes = r.outdated.filter((o) => o.major).map((o) => [o, upgradeNote(o.name, o.latest)]);
735
+ const known = notes.filter(([, n]) => n);
736
+ if (known.length) lines2.push("Verified upgrade notes:", ...known.map(([o, n]) => `- ${o.name} \u2192 ${o.latest}: ${n}`));
737
+ const unknown = notes.filter(([, n]) => !n).map(([o]) => o.name);
738
+ if (unknown.length) lines2.push(`No verified notes for: ${unknown.join(", ")}`);
656
739
  }
657
- if (r.unused.length) lines.push(`Possibly unused dependencies: ${r.unused.join(", ")}`);
658
- if (r.missing.length) lines.push(`Imported but not declared in package.json: ${r.missing.join(", ")}`);
740
+ if (r.unused.length) lines2.push(`Possibly unused dependencies: ${r.unused.join(", ")}`);
741
+ if (r.missing.length) lines2.push(`Imported but not declared in package.json: ${r.missing.join(", ")}`);
659
742
  return {
660
- instruction: "You are given facts about a Node.js project's dependencies. Write a short, prioritized action plan:\n1. Security: repeat each fix command exactly as given and say in one line why it matters. Never change versions.\n2. Major upgrades (marked MAJOR only): for each, the main breaking-change risk you know of and what to check.\n3. Cleanup: unused packages to remove and missing packages to add.\nDo not repeat the raw lists. Do not invent versions or advisories. Skip any section with nothing to do.",
661
- input: lines.join("\n")
743
+ instruction: "You are given facts about a Node.js project's dependencies. Write a short, prioritized action plan:\n1. Security: repeat each fix command exactly as given and say in one line why it matters, based only on the advisory titles. Never change versions.\n2. Major upgrades (marked MAJOR only): restate the verified upgrade note for each. For packages with no verified notes, say to read their changelog before upgrading; do not guess API changes.\n3. Cleanup: unused packages to remove and missing packages to add.\nDo not repeat the raw lists. Do not invent versions, advisories or APIs. Skip any section with nothing to do.",
744
+ input: lines2.join("\n")
662
745
  };
663
746
  }
664
747
 
@@ -702,9 +785,10 @@ async function depsCommand(config2, opts) {
702
785
  const spinner = !opts.json && process.stderr.isTTY ? ora2({ text: "Running npm audit and npm outdated\u2026", stream: process.stderr }).start() : null;
703
786
  const report = await collectDeps(cwd).finally(() => spinner?.stop());
704
787
  const wantAi = opts.ai && !isHealthy(report);
788
+ const plan = isHealthy(report) ? "" : buildPlan(report);
705
789
  if (opts.json) {
706
790
  const analysis = wantAi ? await generate(config2, depsPrompt(report, process.versions.node), { json: true }) : void 0;
707
- printJson({ ...report, analysis });
791
+ printJson({ ...report, plan, analysis });
708
792
  return;
709
793
  }
710
794
  printReport(report);
@@ -712,11 +796,18 @@ async function depsCommand(config2, opts) {
712
796
  console.log(chalk3.green("\u2714 Dependencies look healthy."));
713
797
  return;
714
798
  }
799
+ console.log(chalk3.bold.underline("Action plan"));
800
+ await renderStream(lines(plan), { raw: opts.raw });
715
801
  if (wantAi) {
716
- console.log(chalk3.bold.underline("Advice"));
802
+ console.log(chalk3.bold.underline("\nAI notes"));
717
803
  await generate(config2, depsPrompt(report, process.versions.node), opts);
804
+ } else {
805
+ console.log(chalk3.dim("\nRun with --ai for extra AI commentary."));
718
806
  }
719
807
  }
808
+ async function* lines(text) {
809
+ yield text;
810
+ }
720
811
 
721
812
  // src/commands/doctor.ts
722
813
  import chalk4 from "chalk";
@@ -735,8 +826,8 @@ async function probe(name, check) {
735
826
  }
736
827
  }
737
828
  async function doctorCommand(config2) {
738
- const major = Number(process.versions.node.split(".")[0]);
739
- if (major >= 20) ok(`Node.js ${process.versions.node}`);
829
+ const major2 = Number(process.versions.node.split(".")[0]);
830
+ if (major2 >= 20) ok(`Node.js ${process.versions.node}`);
740
831
  else fail(`Node.js ${process.versions.node}`, "Eventa needs Node.js 20 or newer");
741
832
  const ollamaOk = await probe(`Ollama (${config2.model})`, () => new OllamaProvider(config2.model, config2.ollamaHost).check());
742
833
  const localOk = await probe(`Built-in runtime (${config2.localModel})`, () => new LocalProvider(config2.localModel).check());
@@ -744,7 +835,7 @@ async function doctorCommand(config2) {
744
835
  console.log(chalk4.dim(`
745
836
  Provider setting: ${config2.provider} \u2192 using ${active.name} (${active.model})`));
746
837
  if (!ollamaOk && !localOk) console.log(chalk4.dim("Neither is ready yet. Eventa installs the built-in runtime and model on first use, or run `eventa model pull` now."));
747
- return major >= 20 && (ollamaOk || localOk);
838
+ return major2 >= 20 && (ollamaOk || localOk);
748
839
  }
749
840
 
750
841
  // src/commands/model.ts
@@ -860,12 +951,12 @@ function userFrames(parsed, cwd, limit = 3) {
860
951
  }
861
952
  function readSnippet(file, marked, radius = 15, cwd = process.cwd()) {
862
953
  const targets = (Array.isArray(marked) ? marked : [marked]).slice().sort((a, b) => a - b);
863
- const lines = readFileSync3(file, "utf8").split(/\r?\n/);
954
+ const lines2 = readFileSync3(file, "utf8").split(/\r?\n/);
864
955
  const start = Math.max(1, targets[0] - radius);
865
- const end = Math.min(lines.length, targets[targets.length - 1] + radius);
956
+ const end = Math.min(lines2.length, targets[targets.length - 1] + radius);
866
957
  const body = [];
867
958
  for (let n = start; n <= end; n++) {
868
- body.push(targets.includes(n) ? `${lines[n - 1]} // <-- line ${n}, in the stack trace` : lines[n - 1]);
959
+ body.push(targets.includes(n) ? `${lines2[n - 1]} // <-- line ${n}, in the stack trace` : lines2[n - 1]);
869
960
  }
870
961
  return `// ${relative(cwd, file) || file} (lines ${start}-${end})
871
962
  ${body.join("\n")}`;
@@ -873,12 +964,12 @@ ${body.join("\n")}`;
873
964
  function snippetsFor(frames, radius, cwd) {
874
965
  const byFile = /* @__PURE__ */ new Map();
875
966
  for (const f of frames) {
876
- const lines = byFile.get(f.file) ?? [];
877
- const last = lines[lines.length - 1];
878
- if (last === void 0 || Math.abs(f.line - last) <= radius * 2) lines.push(f.line);
879
- byFile.set(f.file, lines);
967
+ const lines2 = byFile.get(f.file) ?? [];
968
+ const last = lines2[lines2.length - 1];
969
+ if (last === void 0 || Math.abs(f.line - last) <= radius * 2) lines2.push(f.line);
970
+ byFile.set(f.file, lines2);
880
971
  }
881
- return [...byFile].map(([file, lines]) => readSnippet(file, lines, radius, cwd));
972
+ return [...byFile].map(([file, lines2]) => readSnippet(file, lines2, radius, cwd));
882
973
  }
883
974
 
884
975
  // src/context/tokens.ts
@@ -1197,7 +1288,7 @@ async function reviewCommand(config2, opts) {
1197
1288
 
1198
1289
  // src/index.ts
1199
1290
  var program = new Command();
1200
- program.name("eventa").description("Offline AI assistant for Node.js developers").version("0.1.0").option("-m, --model <name>", "model to use (overrides config)").option("--host <url>", "Ollama host URL").option("-p, --provider <name>", "auto | ollama | local").option("--raw", "print plain output without terminal formatting");
1291
+ program.name("eventa").description("Offline AI assistant for Node.js developers").version("0.1.1").option("-m, --model <name>", "model to use (overrides config)").option("--host <url>", "Ollama host URL").option("-p, --provider <name>", "auto | ollama | local").option("--raw", "print plain output without terminal formatting");
1201
1292
  var configFrom = (cmd) => {
1202
1293
  const g = cmd.optsWithGlobals();
1203
1294
  if (g.provider && !["auto", "ollama", "local"].includes(g.provider)) throw new Error("--provider must be auto, ollama or local");
@@ -1215,7 +1306,7 @@ program.command("review").description("review your staged git diff (falls back t
1215
1306
  const { config: config2, raw } = configFrom(cmd);
1216
1307
  await reviewCommand(config2, { ...opts, raw });
1217
1308
  });
1218
- program.command("deps").description("audit dependencies: vulnerabilities, outdated, unused and missing packages").option("--no-ai", "only print the report, skip AI advice").option("--json", "print JSON output").action(async (opts, cmd) => {
1309
+ program.command("deps").description("audit dependencies: vulnerabilities, outdated, unused and missing packages").option("--ai", "also ask the model for extra commentary (the action plan itself is always deterministic)").option("--json", "print JSON output").action(async (opts, cmd) => {
1219
1310
  const { config: config2, raw } = configFrom(cmd);
1220
1311
  await depsCommand(config2, { ...opts, raw });
1221
1312
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeeva1398/eventa",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Offline AI assistant for Node.js developers: explain errors, review diffs, audit dependencies.",
5
5
  "type": "module",
6
6
  "bin": {