@cencori/scan 0.3.8 → 0.4.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.
package/README.md CHANGED
@@ -15,11 +15,11 @@ That's it. Run it in any project directory to instantly scan for security issues
15
15
 
16
16
  ## Features
17
17
 
18
- - 🔍 **Pattern-based scanning** - Detects 50+ types of secrets, PII, and vulnerabilities
19
- - 🤖 **AI-powered auto-fix** - Automatically fixes issues with one command
20
- - **Fast** - Scans thousands of files in seconds
21
- - 🎯 **Zero config** - Works out of the box
22
- - 📊 **Security scoring** - A through F tier grading
18
+ - **Pattern-based scanning** - Detects 50+ types of secrets, PII, and vulnerabilities
19
+ - **Cencori AI auto-fix** - Automatically fixes issues with one command
20
+ - **Fast** - Scans thousands of files in seconds
21
+ - **Zero config** - Works out of the box
22
+ - **Security scoring** - A through F tier grading
23
23
 
24
24
  ## Installation
25
25
 
@@ -80,7 +80,7 @@ Your API key is saved to `~/.cencorirc` for future scans.
80
80
 
81
81
  ## What It Detects
82
82
 
83
- ### 🔐 API Keys & Secrets
83
+ ### API Keys & Secrets
84
84
 
85
85
  | Provider | Pattern |
86
86
  |----------|---------|
@@ -94,21 +94,21 @@ Your API key is saved to `~/.cencorirc` for future scans.
94
94
  | Firebase | `firebase-adminsdk-...` |
95
95
  | And 20+ more... | |
96
96
 
97
- ### 👤 PII (Personal Identifiable Information)
97
+ ### PII (Personal Identifiable Information)
98
98
 
99
99
  - Email addresses in code
100
100
  - Phone numbers
101
101
  - Social Security Numbers
102
102
  - Credit card numbers
103
103
 
104
- ### 🛣️ Exposed Routes
104
+ ### Exposed Routes
105
105
 
106
106
  - Next.js API routes without authentication
107
107
  - Express routes without auth middleware
108
108
  - Sensitive files in `/public` folders
109
109
  - Dashboard/admin routes without protection
110
110
 
111
- ### ⚠️ Security Vulnerabilities
111
+ ### Security Vulnerabilities
112
112
 
113
113
  - SQL injection patterns
114
114
  - XSS vulnerabilities (innerHTML, dangerouslySetInnerHTML)
@@ -126,6 +126,50 @@ Your API key is saved to `~/.cencorirc` for future scans.
126
126
  | **D-Tier** | Poor | Significant issues found |
127
127
  | **F-Tier** | Critical | Secrets or major vulnerabilities exposed |
128
128
 
129
+ ## Changelog Generation
130
+
131
+ Generate AI-powered changelogs from your git commit history.
132
+
133
+ ```bash
134
+ # Generate weekly changelog
135
+ npx @cencori/scan changelog
136
+
137
+ # Custom time range
138
+ npx @cencori/scan changelog --since="2 weeks ago"
139
+
140
+ # Output to file
141
+ npx @cencori/scan changelog --output=CHANGELOG.md
142
+
143
+ # JSON format
144
+ npx @cencori/scan changelog --format=json
145
+ ```
146
+
147
+ ### Example Output
148
+
149
+ ```markdown
150
+ ## Changelog (Jan 23, 2026 - Jan 30, 2026)
151
+
152
+ ### Features
153
+
154
+ - Added AI-powered changelog generation
155
+ - New security scanning patterns for AWS secrets
156
+
157
+ ### Bug Fixes
158
+
159
+ - Fixed telemetry not sending before process exit
160
+
161
+ ### Documentation
162
+
163
+ - Updated README with new examples
164
+ ```
165
+
166
+ ### Pro Tier (with API key)
167
+
168
+ Get human-readable, summarized changelogs with AI:
169
+ - Converts developer commit messages to user-facing language
170
+ - Intelligently groups related changes
171
+ - Highlights breaking changes automatically
172
+
129
173
  ## Example Output
130
174
 
131
175
  ```
package/dist/cli.js CHANGED
@@ -973,10 +973,210 @@ function buildTelemetryData(result, version, hasApiKey) {
973
973
  };
974
974
  }
975
975
 
976
+ // src/changelog/index.ts
977
+ var import_child_process = require("child_process");
978
+ var CENCORI_API_URL2 = "https://api.cencori.com/v1";
979
+ function parseCommitType(message) {
980
+ const lower = message.toLowerCase();
981
+ if (lower.startsWith("feat") || lower.startsWith("feature")) return "feat";
982
+ if (lower.startsWith("fix") || lower.startsWith("bugfix")) return "fix";
983
+ if (lower.startsWith("chore")) return "chore";
984
+ if (lower.startsWith("docs")) return "docs";
985
+ if (lower.startsWith("style")) return "style";
986
+ if (lower.startsWith("refactor")) return "refactor";
987
+ if (lower.startsWith("test")) return "test";
988
+ return "other";
989
+ }
990
+ function getCommits(since = "1 week ago", cwd) {
991
+ try {
992
+ const output = (0, import_child_process.execSync)(
993
+ `git log --since="${since}" --pretty=format:"%H|%aI|%an|%s" --no-merges`,
994
+ {
995
+ cwd: cwd || process.cwd(),
996
+ encoding: "utf-8",
997
+ stdio: ["pipe", "pipe", "pipe"]
998
+ }
999
+ );
1000
+ if (!output.trim()) {
1001
+ return [];
1002
+ }
1003
+ return output.trim().split("\n").map((line) => {
1004
+ const [hash, date, author, ...messageParts] = line.split("|");
1005
+ const message = messageParts.join("|");
1006
+ return {
1007
+ hash: hash.substring(0, 7),
1008
+ date,
1009
+ author,
1010
+ message,
1011
+ type: parseCommitType(message)
1012
+ };
1013
+ });
1014
+ } catch (error) {
1015
+ return [];
1016
+ }
1017
+ }
1018
+ function groupCommitsByType(commits) {
1019
+ const groups = {
1020
+ feat: [],
1021
+ fix: [],
1022
+ docs: [],
1023
+ other: []
1024
+ };
1025
+ for (const commit of commits) {
1026
+ if (commit.type === "feat") {
1027
+ groups.feat.push(commit);
1028
+ } else if (commit.type === "fix") {
1029
+ groups.fix.push(commit);
1030
+ } else if (commit.type === "docs") {
1031
+ groups.docs.push(commit);
1032
+ } else {
1033
+ groups.other.push(commit);
1034
+ }
1035
+ }
1036
+ const entries = [];
1037
+ if (groups.feat.length > 0) {
1038
+ entries.push({
1039
+ type: "feature",
1040
+ title: "Features",
1041
+ commits: groups.feat.map((c) => c.message)
1042
+ });
1043
+ }
1044
+ if (groups.fix.length > 0) {
1045
+ entries.push({
1046
+ type: "fix",
1047
+ title: "Bug Fixes",
1048
+ commits: groups.fix.map((c) => c.message)
1049
+ });
1050
+ }
1051
+ if (groups.docs.length > 0) {
1052
+ entries.push({
1053
+ type: "docs",
1054
+ title: "Documentation",
1055
+ commits: groups.docs.map((c) => c.message)
1056
+ });
1057
+ }
1058
+ if (groups.other.length > 0) {
1059
+ entries.push({
1060
+ type: "other",
1061
+ title: "Other Changes",
1062
+ commits: groups.other.map((c) => c.message)
1063
+ });
1064
+ }
1065
+ return entries;
1066
+ }
1067
+ function generateMarkdownFree(entries, period) {
1068
+ const startDate = new Date(period.start).toLocaleDateString("en-US", {
1069
+ month: "short",
1070
+ day: "numeric",
1071
+ year: "numeric"
1072
+ });
1073
+ const endDate = new Date(period.end).toLocaleDateString("en-US", {
1074
+ month: "short",
1075
+ day: "numeric",
1076
+ year: "numeric"
1077
+ });
1078
+ let md = `## Changelog (${startDate} - ${endDate})
1079
+
1080
+ `;
1081
+ for (const entry of entries) {
1082
+ md += `### ${entry.title}
1083
+
1084
+ `;
1085
+ for (const commit of entry.commits) {
1086
+ const cleanMessage = commit.replace(/^(feat|fix|chore|docs|style|refactor|test)(\(.+?\))?:\s*/i, "").trim();
1087
+ md += `- ${cleanMessage}
1088
+ `;
1089
+ }
1090
+ md += "\n";
1091
+ }
1092
+ return md;
1093
+ }
1094
+ async function generateChangelogAI(commits, apiKey, period) {
1095
+ const commitMessages = commits.map((c) => c.message).join("\n");
1096
+ try {
1097
+ const response = await fetch(`${CENCORI_API_URL2}/changelog/generate`, {
1098
+ method: "POST",
1099
+ headers: {
1100
+ "Content-Type": "application/json",
1101
+ Authorization: `Bearer ${apiKey}`
1102
+ },
1103
+ body: JSON.stringify({
1104
+ commits: commitMessages,
1105
+ period
1106
+ })
1107
+ });
1108
+ if (!response.ok) {
1109
+ throw new Error("API request failed");
1110
+ }
1111
+ const data = await response.json();
1112
+ return {
1113
+ entries: data.entries || [],
1114
+ markdown: data.markdown || ""
1115
+ };
1116
+ } catch {
1117
+ const entries = groupCommitsByType(commits);
1118
+ return {
1119
+ entries,
1120
+ markdown: generateMarkdownFree(entries, period)
1121
+ };
1122
+ }
1123
+ }
1124
+ async function generateChangelog(since = "1 week ago", apiKey, cwd) {
1125
+ const commits = getCommits(since, cwd);
1126
+ const now = /* @__PURE__ */ new Date();
1127
+ const sinceDate = /* @__PURE__ */ new Date();
1128
+ const match = since.match(/(\d+)\s*(week|day|month)s?\s*ago/i);
1129
+ if (match) {
1130
+ const amount = parseInt(match[1], 10);
1131
+ const unit = match[2].toLowerCase();
1132
+ if (unit === "week") {
1133
+ sinceDate.setDate(sinceDate.getDate() - amount * 7);
1134
+ } else if (unit === "day") {
1135
+ sinceDate.setDate(sinceDate.getDate() - amount);
1136
+ } else if (unit === "month") {
1137
+ sinceDate.setMonth(sinceDate.getMonth() - amount);
1138
+ }
1139
+ } else {
1140
+ sinceDate.setDate(sinceDate.getDate() - 7);
1141
+ }
1142
+ const period = {
1143
+ start: sinceDate.toISOString(),
1144
+ end: now.toISOString()
1145
+ };
1146
+ if (commits.length === 0) {
1147
+ return {
1148
+ period,
1149
+ entries: [],
1150
+ markdown: `## Changelog
1151
+
1152
+ No commits found in the specified period.
1153
+ `,
1154
+ commitCount: 0
1155
+ };
1156
+ }
1157
+ if (apiKey) {
1158
+ const { entries, markdown } = await generateChangelogAI(commits, apiKey, period);
1159
+ return {
1160
+ period,
1161
+ entries,
1162
+ markdown,
1163
+ commitCount: commits.length
1164
+ };
1165
+ } else {
1166
+ const entries = groupCommitsByType(commits);
1167
+ return {
1168
+ period,
1169
+ entries,
1170
+ markdown: generateMarkdownFree(entries, period),
1171
+ commitCount: commits.length
1172
+ };
1173
+ }
1174
+ }
1175
+
976
1176
  // src/cli.ts
977
1177
  var fs3 = __toESM(require("fs"));
978
1178
  var path3 = __toESM(require("path"));
979
- var VERSION = "0.3.8";
1179
+ var VERSION = "0.4.1";
980
1180
  var scoreStyles = {
981
1181
  A: { color: import_chalk.default.green },
982
1182
  B: { color: import_chalk.default.blue },
@@ -1282,6 +1482,58 @@ async function main() {
1282
1482
  process.exit(1);
1283
1483
  }
1284
1484
  });
1485
+ import_commander.program.command("changelog").description("Generate AI-powered changelog from git commits").option("-s, --since <time>", "Time range for commits", "1 week ago").option("-o, --output <file>", "Output to file instead of stdout").option("-f, --format <format>", "Output format (markdown or json)", "markdown").action(async (options) => {
1486
+ printBanner();
1487
+ const spinner = (0, import_ora.default)({
1488
+ text: "Reading git history...",
1489
+ color: "cyan"
1490
+ }).start();
1491
+ try {
1492
+ const apiKey = getApiKey();
1493
+ const result = await generateChangelog(options.since, apiKey || void 0);
1494
+ if (result.commitCount === 0) {
1495
+ spinner.warn("No commits found in the specified period");
1496
+ console.log(import_chalk.default.yellow(`
1497
+ Try a larger time range with --since "2 weeks ago"
1498
+ `));
1499
+ process.exit(0);
1500
+ return;
1501
+ }
1502
+ spinner.succeed(`Found ${result.commitCount} commits`);
1503
+ if (options.format === "json") {
1504
+ const output = JSON.stringify(result, null, 2);
1505
+ if (options.output) {
1506
+ fs3.writeFileSync(options.output, output);
1507
+ console.log(import_chalk.default.green(`
1508
+ \u2714 Changelog saved to ${options.output}
1509
+ `));
1510
+ } else {
1511
+ console.log(output);
1512
+ }
1513
+ } else {
1514
+ if (options.output) {
1515
+ fs3.writeFileSync(options.output, result.markdown);
1516
+ console.log(import_chalk.default.green(`
1517
+ \u2714 Changelog saved to ${options.output}
1518
+ `));
1519
+ } else {
1520
+ console.log("\n" + result.markdown);
1521
+ }
1522
+ }
1523
+ if (!apiKey) {
1524
+ console.log(import_chalk.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1525
+ console.log(import_chalk.default.cyan("\n Want AI-enhanced changelogs?"));
1526
+ console.log(import_chalk.default.gray(" Get human-readable summaries with a Cencori API key"));
1527
+ console.log(import_chalk.default.gray(" Sign up free at https://cencori.com\n"));
1528
+ }
1529
+ process.exit(0);
1530
+ } catch (error) {
1531
+ spinner.fail("Changelog generation failed");
1532
+ console.error(import_chalk.default.red(`
1533
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1534
+ process.exit(1);
1535
+ }
1536
+ });
1285
1537
  import_commander.program.parse();
1286
1538
  }
1287
1539
  main();