@getcodesentinel/codesentinel 1.13.0 → 1.14.0

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
@@ -140,6 +140,7 @@ Then run:
140
140
 
141
141
  ```bash
142
142
  codesentinel analyze [path]
143
+ codesentinel run [path]
143
144
  codesentinel explain [path]
144
145
  codesentinel report [path]
145
146
  codesentinel check [path]
@@ -150,6 +151,8 @@ codesentinel dependency-risk <dependency[@version]>
150
151
  Examples:
151
152
 
152
153
  ```bash
154
+ codesentinel run
155
+ codesentinel run . --detail full --format text
153
156
  codesentinel analyze
154
157
  codesentinel analyze .
155
158
  codesentinel analyze ../project
@@ -243,6 +246,7 @@ pnpm dev -- analyze
243
246
  pnpm dev -- analyze .
244
247
  pnpm dev -- analyze ../project
245
248
  pnpm dev -- analyze . --author-identity strict_email
249
+ pnpm dev -- run . --format text
246
250
  pnpm dev -- explain
247
251
  pnpm dev -- explain . --top 5 --format text
248
252
  pnpm dev -- explain . --file src/app/page.tsx
@@ -271,6 +275,15 @@ Diff mode compares snapshots and reports:
271
275
  - new/resolved cycles
272
276
  - dependency exposure list changes
273
277
 
278
+ ## Run Output
279
+
280
+ `codesentinel run` is a convenience command that emits `analyze + explain + report` in one execution.
281
+
282
+ - formats: `text`, `md`, `json` (`text` default)
283
+ - detail levels: `--detail compact|standard|full` (`compact` default, `full` = full verbose sections)
284
+ - explain target selectors: `--file <path>`, `--module <name>`, `--top <n>`
285
+ - report diff/snapshot flags: `--compare <baseline.json>`, `--snapshot <path>`, `--no-trace`
286
+
274
287
  ## CI Mode
275
288
 
276
289
  `codesentinel check` evaluates enforcement gates against current analysis (and optional baseline diff).
package/dist/index.js CHANGED
@@ -1736,8 +1736,7 @@ var renderTextReport = (report) => {
1736
1736
  );
1737
1737
  lines.push(` evidence: ${factor.evidence}`);
1738
1738
  }
1739
- lines.push(` actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1740
- lines.push(` levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
1739
+ lines.push(` priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1741
1740
  }
1742
1741
  lines.push("");
1743
1742
  lines.push("Structural Observations");
@@ -1813,8 +1812,7 @@ var renderMarkdownReport = (report) => {
1813
1812
  );
1814
1813
  lines.push(` - evidence: \`${factor.evidence}\``);
1815
1814
  }
1816
- lines.push(` - Suggested actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1817
- lines.push(` - Biggest levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
1815
+ lines.push(` - Priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1818
1816
  }
1819
1817
  lines.push("");
1820
1818
  lines.push("## Structural Observations");
@@ -2461,6 +2459,7 @@ var resolveAutoBaselineRef = async (input) => {
2461
2459
 
2462
2460
  // src/index.ts
2463
2461
  import { readFileSync as readFileSync2 } from "fs";
2462
+ import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
2464
2463
  import { dirname as dirname2, resolve as resolve5 } from "path";
2465
2464
  import { fileURLToPath } from "url";
2466
2465
 
@@ -2544,6 +2543,7 @@ var formatFactorLabel = (factorId) => factorLabelById2[factorId] ?? factorId;
2544
2543
  var formatNumber = (value) => value === null || value === void 0 ? "n/a" : `${value}`;
2545
2544
  var formatDimension = (value) => value === null ? "n/a" : `${value}`;
2546
2545
  var formatFactorSummary = (factor) => `${formatFactorLabel(factor.factorId)} (+${factor.contribution}, confidence=${factor.confidence})`;
2546
+ var formatFactorContribution = (factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`;
2547
2547
  var formatFactorEvidence = (factor) => {
2548
2548
  if (factor.factorId === "repository.structural") {
2549
2549
  return `structural dimension=${formatNumber(factor.rawMetrics["structuralDimension"])}`;
@@ -2676,20 +2676,15 @@ var renderText = (payload) => {
2676
2676
  lines.push("");
2677
2677
  lines.push("explanation:");
2678
2678
  lines.push(
2679
- ` why risky: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
2679
+ ` key drivers: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
2680
2680
  );
2681
2681
  lines.push(
2682
- ` what specifically contributed: ${repositoryTopFactors.map((factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`).join(", ") || "insufficient data"}`
2682
+ ` contributions: ${repositoryTopFactors.map(formatFactorContribution).join(", ") || "insufficient data"}`
2683
2683
  );
2684
2684
  lines.push(
2685
- ` dominant factors: ${repositoryTopFactors.map((factor) => formatFactorLabel(factor.factorId)).join(", ") || "insufficient data"}`
2686
- );
2687
- lines.push(
2688
- ` intersected signals: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
2689
- );
2690
- lines.push(
2691
- ` what could reduce risk most: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`
2685
+ ` interaction effects: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
2692
2686
  );
2687
+ lines.push(` priority actions: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`);
2693
2688
  lines.push("");
2694
2689
  for (const target of payload.selectedTargets) {
2695
2690
  lines.push(renderTargetText(target));
@@ -2717,25 +2712,19 @@ var renderMarkdown = (payload) => {
2717
2712
  lines.push("");
2718
2713
  lines.push(`## Summary`);
2719
2714
  lines.push(
2720
- `- why risky: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
2721
- );
2722
- lines.push(
2723
- `- what specifically contributed: ${repositoryTopFactors.map((factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`).join(", ") || "insufficient data"}`
2724
- );
2725
- lines.push(
2726
- `- dominant factors: ${repositoryTopFactors.map((factor) => formatFactorLabel(factor.factorId)).join(", ") || "insufficient data"}`
2715
+ `- key drivers: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
2727
2716
  );
2728
2717
  lines.push(
2729
- `- intersected signals: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
2718
+ `- contributions: ${repositoryTopFactors.map(formatFactorContribution).join(", ") || "insufficient data"}`
2730
2719
  );
2731
2720
  lines.push(
2732
- `- what could reduce risk most: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`
2721
+ `- interaction effects: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
2733
2722
  );
2723
+ lines.push(`- priority actions: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`);
2734
2724
  lines.push("");
2735
2725
  for (const target of payload.selectedTargets) {
2736
2726
  lines.push(`## ${target.targetType}: \`${target.targetId}\``);
2737
2727
  lines.push(`- score: \`${target.totalScore}\` (\`${target.normalizedScore}\`)`);
2738
- lines.push(`- dominantFactors: \`${target.dominantFactors.join(", ")}\``);
2739
2728
  lines.push(`- Top factors:`);
2740
2729
  for (const factor of [...target.factors].sort(sortFactorByContribution).slice(0, 5)) {
2741
2730
  lines.push(
@@ -6029,6 +6018,118 @@ var parseRecentWindowDays = (value) => {
6029
6018
  }
6030
6019
  return parsed;
6031
6020
  };
6021
+ var stripLeadingMarkdownHeading = (value, heading) => {
6022
+ const prefix = `${heading}
6023
+ `;
6024
+ if (value.startsWith(prefix)) {
6025
+ return value.slice(prefix.length).trimStart();
6026
+ }
6027
+ return value;
6028
+ };
6029
+ var extractExplainTextSummary = (text) => {
6030
+ const splitIndex = text.search(/\n\n(?:file|module|dependency|repository): /);
6031
+ if (splitIndex < 0) {
6032
+ return text.trim();
6033
+ }
6034
+ return text.slice(0, splitIndex).trim();
6035
+ };
6036
+ var extractExplainMarkdownSummary = (markdown) => {
6037
+ const splitIndex = markdown.search(/\n\n## (?:file|module|dependency|repository): /);
6038
+ if (splitIndex < 0) {
6039
+ return markdown.trim();
6040
+ }
6041
+ return markdown.slice(0, splitIndex).trim();
6042
+ };
6043
+ var extractSummaryValue = (summary, key) => {
6044
+ const prefix = `${key}: `;
6045
+ for (const rawLine of summary.split("\n")) {
6046
+ const line = rawLine.trimStart();
6047
+ if (line.startsWith(prefix)) {
6048
+ return line.slice(prefix.length).trim();
6049
+ }
6050
+ }
6051
+ return void 0;
6052
+ };
6053
+ var renderReportHighlightsText = (report) => {
6054
+ const lines = [];
6055
+ lines.push("Repository Summary");
6056
+ lines.push(` target: ${report.repository.targetPath}`);
6057
+ lines.push(` riskScore: ${report.repository.riskScore}`);
6058
+ lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
6059
+ lines.push(` riskTier: ${report.repository.riskTier}`);
6060
+ lines.push("");
6061
+ lines.push("Top Hotspots");
6062
+ for (const hotspot of report.hotspots.slice(0, 5)) {
6063
+ lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
6064
+ lines.push(` priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
6065
+ }
6066
+ return lines.join("\n");
6067
+ };
6068
+ var renderReportHighlightsMarkdown = (report) => {
6069
+ const lines = [];
6070
+ lines.push("## Repository Summary");
6071
+ lines.push(`- target: \`${report.repository.targetPath}\``);
6072
+ lines.push(`- riskScore: \`${report.repository.riskScore}\``);
6073
+ lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
6074
+ lines.push(`- riskTier: \`${report.repository.riskTier}\``);
6075
+ lines.push("");
6076
+ lines.push("## Top Hotspots");
6077
+ for (const hotspot of report.hotspots.slice(0, 5)) {
6078
+ lines.push(`- **${hotspot.target}** (score: \`${hotspot.score}\`)`);
6079
+ lines.push(` - priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
6080
+ }
6081
+ return lines.join("\n");
6082
+ };
6083
+ var renderCompactText = (report, explainSummary) => {
6084
+ const lines = [];
6085
+ lines.push("CodeSentinel Run (compact)");
6086
+ lines.push("");
6087
+ lines.push("Repository");
6088
+ lines.push(` target: ${report.repository.targetPath}`);
6089
+ lines.push(` riskScore: ${report.repository.riskScore}`);
6090
+ lines.push(` riskTier: ${report.repository.riskTier}`);
6091
+ lines.push(
6092
+ ` dimensions: structural=${report.repository.dimensionScores.structural ?? "n/a"}, evolution=${report.repository.dimensionScores.evolution ?? "n/a"}, external=${report.repository.dimensionScores.external ?? "n/a"}, interactions=${report.repository.dimensionScores.interactions ?? "n/a"}`
6093
+ );
6094
+ lines.push("");
6095
+ lines.push(
6096
+ `Key Drivers: ${extractSummaryValue(explainSummary, "key drivers") ?? "insufficient data"}`
6097
+ );
6098
+ lines.push(
6099
+ `Priority Actions: ${extractSummaryValue(explainSummary, "priority actions") ?? "insufficient data"}`
6100
+ );
6101
+ lines.push("");
6102
+ lines.push("Top Hotspots");
6103
+ for (const hotspot of report.hotspots.slice(0, 3)) {
6104
+ lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
6105
+ }
6106
+ return lines.join("\n");
6107
+ };
6108
+ var renderCompactMarkdown = (report, explainSummary) => {
6109
+ const lines = [];
6110
+ lines.push("# CodeSentinel Run (compact)");
6111
+ lines.push("");
6112
+ lines.push("## Repository");
6113
+ lines.push(`- target: \`${report.repository.targetPath}\``);
6114
+ lines.push(`- riskScore: \`${report.repository.riskScore}\``);
6115
+ lines.push(`- riskTier: \`${report.repository.riskTier}\``);
6116
+ lines.push(
6117
+ `- dimensions: structural=\`${report.repository.dimensionScores.structural ?? "n/a"}\`, evolution=\`${report.repository.dimensionScores.evolution ?? "n/a"}\`, external=\`${report.repository.dimensionScores.external ?? "n/a"}\`, interactions=\`${report.repository.dimensionScores.interactions ?? "n/a"}\``
6118
+ );
6119
+ lines.push("");
6120
+ lines.push(
6121
+ `- key drivers: ${extractSummaryValue(explainSummary, "- key drivers") ?? "insufficient data"}`
6122
+ );
6123
+ lines.push(
6124
+ `- priority actions: ${extractSummaryValue(explainSummary, "- priority actions") ?? "insufficient data"}`
6125
+ );
6126
+ lines.push("");
6127
+ lines.push("## Top Hotspots");
6128
+ for (const hotspot of report.hotspots.slice(0, 3)) {
6129
+ lines.push(`- \`${hotspot.target}\` (score: \`${hotspot.score}\`)`);
6130
+ }
6131
+ return lines.join("\n");
6132
+ };
6032
6133
  var riskProfileOption = () => new Option(
6033
6134
  "--risk-profile <profile>",
6034
6135
  "risk profile: default (balanced) or personal (down-weights single-maintainer ownership penalties)"
@@ -6169,6 +6270,219 @@ program.command("report").argument("[path]", "path to the project to analyze").a
6169
6270
  }
6170
6271
  }
6171
6272
  );
6273
+ program.command("run").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
6274
+ new Option(
6275
+ "--author-identity <mode>",
6276
+ "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
6277
+ ).choices(["likely_merge", "strict_email"]).default("likely_merge")
6278
+ ).addOption(
6279
+ new Option(
6280
+ "--log-level <level>",
6281
+ "log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
6282
+ ).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
6283
+ ).addOption(
6284
+ new Option("--format <mode>", "combined output format: text, md, json").choices(["text", "md", "json"]).default("text")
6285
+ ).addOption(
6286
+ new Option("--detail <level>", "run detail level: compact (default), standard, full").choices(["compact", "standard", "full"]).default("compact")
6287
+ ).option("--file <path>", "explain a specific file target").option("--module <name>", "explain a specific module target").option("--top <count>", "number of top hotspots to explain when no target is selected", "5").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").option("--no-trace", "disable trace embedding in generated snapshot").addOption(
6288
+ new Option(
6289
+ "--recent-window-days <days>",
6290
+ "git recency window in days used for evolution volatility metrics"
6291
+ ).argParser(parseRecentWindowDays).default(30)
6292
+ ).action(
6293
+ async (path, options) => {
6294
+ const logger = createStderrLogger(options.logLevel);
6295
+ const top = Number.parseInt(options.top, 10);
6296
+ const explain = await runExplainCommand(
6297
+ path,
6298
+ options.authorIdentity,
6299
+ {
6300
+ ...options.file === void 0 ? {} : { file: options.file },
6301
+ ...options.module === void 0 ? {} : { module: options.module },
6302
+ top: Number.isFinite(top) ? top : 5,
6303
+ format: options.format,
6304
+ recentWindowDays: options.recentWindowDays,
6305
+ riskProfile: options.riskProfile
6306
+ },
6307
+ logger
6308
+ );
6309
+ const snapshot = createSnapshot({
6310
+ analysis: explain.summary,
6311
+ ...options.trace === true ? { trace: explain.trace } : {}
6312
+ });
6313
+ if (options.snapshot !== void 0) {
6314
+ await writeFile5(options.snapshot, JSON.stringify(snapshot, null, 2), "utf8");
6315
+ logger.info(`snapshot written: ${options.snapshot}`);
6316
+ }
6317
+ const report = options.compare === void 0 ? createReport(snapshot) : createReport(
6318
+ snapshot,
6319
+ compareSnapshots(snapshot, parseSnapshot(await readFile5(options.compare, "utf8")))
6320
+ );
6321
+ if (options.format === "json") {
6322
+ const analyzeSummaryOutput = formatAnalyzeOutput(explain.summary, "summary");
6323
+ if (options.detail === "compact") {
6324
+ process.stdout.write(
6325
+ `${JSON.stringify(
6326
+ {
6327
+ schemaVersion: "codesentinel.run.v1",
6328
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6329
+ repository: report.repository,
6330
+ keyDrivers: extractSummaryValue(
6331
+ extractExplainTextSummary(formatExplainOutput(explain, "text")),
6332
+ "key drivers"
6333
+ ),
6334
+ priorityActions: extractSummaryValue(
6335
+ extractExplainTextSummary(formatExplainOutput(explain, "text")),
6336
+ "priority actions"
6337
+ ),
6338
+ topHotspots: report.hotspots.slice(0, 3).map((hotspot) => ({
6339
+ target: hotspot.target,
6340
+ score: hotspot.score
6341
+ }))
6342
+ },
6343
+ null,
6344
+ 2
6345
+ )}
6346
+ `
6347
+ );
6348
+ return;
6349
+ }
6350
+ if (options.detail === "standard") {
6351
+ const analyzeSummaryPayload = JSON.parse(analyzeSummaryOutput);
6352
+ process.stdout.write(
6353
+ `${JSON.stringify(
6354
+ {
6355
+ schemaVersion: "codesentinel.run.v1",
6356
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6357
+ analyze: analyzeSummaryPayload,
6358
+ explain: {
6359
+ summary: extractExplainTextSummary(formatExplainOutput(explain, "text"))
6360
+ },
6361
+ report: {
6362
+ repository: report.repository,
6363
+ hotspots: report.hotspots.slice(0, 5),
6364
+ structural: report.structural,
6365
+ external: report.external
6366
+ }
6367
+ },
6368
+ null,
6369
+ 2
6370
+ )}
6371
+ `
6372
+ );
6373
+ return;
6374
+ }
6375
+ process.stdout.write(
6376
+ `${JSON.stringify(
6377
+ {
6378
+ schemaVersion: "codesentinel.run.v1",
6379
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6380
+ analyze: explain.summary,
6381
+ explain,
6382
+ report
6383
+ },
6384
+ null,
6385
+ 2
6386
+ )}
6387
+ `
6388
+ );
6389
+ return;
6390
+ }
6391
+ const analyzeRendered = formatAnalyzeOutput(explain.summary, "summary");
6392
+ const explainRendered = formatExplainOutput(explain, options.format);
6393
+ if (options.detail === "compact") {
6394
+ if (options.format === "md") {
6395
+ process.stdout.write(
6396
+ `${renderCompactMarkdown(
6397
+ report,
6398
+ extractExplainMarkdownSummary(formatExplainOutput(explain, "md"))
6399
+ )}
6400
+ `
6401
+ );
6402
+ return;
6403
+ }
6404
+ process.stdout.write(
6405
+ `${renderCompactText(report, extractExplainTextSummary(formatExplainOutput(explain, "text")))}
6406
+ `
6407
+ );
6408
+ return;
6409
+ }
6410
+ if (options.detail === "standard") {
6411
+ if (options.format === "md") {
6412
+ process.stdout.write(
6413
+ `# CodeSentinel Run
6414
+
6415
+ ## Analyze
6416
+ \`\`\`json
6417
+ ${analyzeRendered}
6418
+ \`\`\`
6419
+
6420
+ ## Explain
6421
+ ${extractExplainMarkdownSummary(
6422
+ formatExplainOutput(explain, "md")
6423
+ )}
6424
+
6425
+ ${renderReportHighlightsMarkdown(report)}
6426
+ `
6427
+ );
6428
+ return;
6429
+ }
6430
+ process.stdout.write(
6431
+ `CodeSentinel Run
6432
+
6433
+ Analyze
6434
+ ${analyzeRendered}
6435
+
6436
+ Explain
6437
+ ${extractExplainTextSummary(
6438
+ formatExplainOutput(explain, "text")
6439
+ )}
6440
+
6441
+ Report
6442
+ ${renderReportHighlightsText(report)}
6443
+ `
6444
+ );
6445
+ return;
6446
+ }
6447
+ const reportRendered = formatReport(report, options.format);
6448
+ if (options.format === "md") {
6449
+ const explainSection = stripLeadingMarkdownHeading(
6450
+ explainRendered,
6451
+ "# CodeSentinel Explanation"
6452
+ );
6453
+ const reportSection = stripLeadingMarkdownHeading(reportRendered, "# CodeSentinel Report");
6454
+ process.stdout.write(
6455
+ `# CodeSentinel Run
6456
+
6457
+ ## Analyze
6458
+ \`\`\`json
6459
+ ${analyzeRendered}
6460
+ \`\`\`
6461
+
6462
+ ## Explain
6463
+ ${explainSection}
6464
+
6465
+ ## Report
6466
+ ${reportSection}
6467
+ `
6468
+ );
6469
+ return;
6470
+ }
6471
+ process.stdout.write(
6472
+ `CodeSentinel Run
6473
+
6474
+ Analyze
6475
+ ${analyzeRendered}
6476
+
6477
+ Explain
6478
+ ${explainRendered}
6479
+
6480
+ Report
6481
+ ${reportRendered}
6482
+ `
6483
+ );
6484
+ }
6485
+ );
6172
6486
  var parseGateNumber = (value, optionName) => {
6173
6487
  if (value === void 0) {
6174
6488
  return void 0;