@aisdlc/core 1.1.1 → 1.2.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/dist/index.cjs CHANGED
@@ -513,6 +513,9 @@ function createDefaultQualityPolicy() {
513
513
  max_cognitive: 15,
514
514
  min_maintainability: 50,
515
515
  max_function_lines: 40,
516
+ min_line_coverage: 85,
517
+ min_branch_coverage: 80,
518
+ min_function_coverage: 90,
516
519
  enforce_mode: "STRICT",
517
520
  target_directories: ["src", "lib", "examples", "tests"],
518
521
  supported_extensions: [...DEFAULT_EXTENSIONS],
@@ -536,6 +539,15 @@ function applyDocScope(policy, doc) {
536
539
  }
537
540
  }
538
541
  }
542
+ function applyCoverageThresholds(policy, covRaw) {
543
+ if (!covRaw || typeof covRaw !== "object") return;
544
+ const cov = covRaw;
545
+ if (cov.min_line_coverage_percent !== void 0) policy.min_line_coverage = Number(cov.min_line_coverage_percent);
546
+ if (cov.min_branch_coverage_percent !== void 0) policy.min_branch_coverage = Number(cov.min_branch_coverage_percent);
547
+ if (cov.min_function_coverage_percent !== void 0) {
548
+ policy.min_function_coverage = Number(cov.min_function_coverage_percent);
549
+ }
550
+ }
539
551
  function applyDocThresholds(policy, doc) {
540
552
  const sections = [doc.release_thresholds, doc.thresholds].filter(
541
553
  (s) => Boolean(s && typeof s === "object")
@@ -549,6 +561,7 @@ function applyDocThresholds(policy, doc) {
549
561
  if (cog !== void 0) policy.max_cognitive = Number(cog);
550
562
  if (mi !== void 0) policy.min_maintainability = Number(mi);
551
563
  if (loc !== void 0) policy.max_function_lines = Number(loc);
564
+ applyCoverageThresholds(policy, s.test_coverage);
552
565
  }
553
566
  const codingRules = doc.coding_rules;
554
567
  const rules = codingRules?.rules;
@@ -5815,6 +5828,78 @@ function detectLanguage(filePath) {
5815
5828
  return "Unknown";
5816
5829
  }
5817
5830
  }
5831
+ function parseJsonSummaryFile(filePath, relSource) {
5832
+ try {
5833
+ const raw = JSON.parse(fs6__namespace.readFileSync(filePath, "utf-8"));
5834
+ const total = raw.total;
5835
+ if (!total || typeof total !== "object") return null;
5836
+ const linesPct = Number(total.lines?.pct);
5837
+ const branchesPct = Number(total.branches?.pct);
5838
+ const functionsPct = Number(total.functions?.pct);
5839
+ if (Number.isNaN(linesPct) || Number.isNaN(branchesPct) || Number.isNaN(functionsPct)) return null;
5840
+ const statementsPct = total.statements?.pct !== void 0 ? Number(total.statements.pct) : void 0;
5841
+ return { linesPct, branchesPct, functionsPct, statementsPct, source: relSource };
5842
+ } catch {
5843
+ return null;
5844
+ }
5845
+ }
5846
+ function parseLcovFile(filePath, relSource) {
5847
+ try {
5848
+ const lines = fs6__namespace.readFileSync(filePath, "utf-8").split(/\r?\n/);
5849
+ let lf = 0, lh = 0, brf = 0, brh = 0, fnf = 0, fnh = 0;
5850
+ for (const line of lines) {
5851
+ if (line.startsWith("LF:")) lf += parseInt(line.slice(3), 10) || 0;
5852
+ else if (line.startsWith("LH:")) lh += parseInt(line.slice(3), 10) || 0;
5853
+ else if (line.startsWith("BRF:")) brf += parseInt(line.slice(4), 10) || 0;
5854
+ else if (line.startsWith("BRH:")) brh += parseInt(line.slice(4), 10) || 0;
5855
+ else if (line.startsWith("FNF:")) fnf += parseInt(line.slice(4), 10) || 0;
5856
+ else if (line.startsWith("FNH:")) fnh += parseInt(line.slice(4), 10) || 0;
5857
+ }
5858
+ if (lf === 0 && fnf === 0 && brf === 0) return null;
5859
+ const pct = (hit, found) => found > 0 ? Math.round(hit / found * 1e3) / 10 : 100;
5860
+ return {
5861
+ linesPct: pct(lh, lf),
5862
+ branchesPct: pct(brh, brf),
5863
+ functionsPct: pct(fnh, fnf),
5864
+ source: relSource
5865
+ };
5866
+ } catch {
5867
+ return null;
5868
+ }
5869
+ }
5870
+ function readCodeCoverageSummary(rootDir = process.cwd()) {
5871
+ const jsonCandidates = ["coverage/coverage-summary.json", "reports/coverage-summary.json"];
5872
+ for (const rel of jsonCandidates) {
5873
+ const full = path8__namespace.join(rootDir, rel);
5874
+ if (fs6__namespace.existsSync(full)) {
5875
+ const parsed = parseJsonSummaryFile(full, rel);
5876
+ if (parsed) return parsed;
5877
+ }
5878
+ }
5879
+ const lcovCandidates = ["coverage/lcov.info", "lcov.info", "reports/lcov.info"];
5880
+ for (const rel of lcovCandidates) {
5881
+ const full = path8__namespace.join(rootDir, rel);
5882
+ if (fs6__namespace.existsSync(full)) {
5883
+ const parsed = parseLcovFile(full, rel);
5884
+ if (parsed) return parsed;
5885
+ }
5886
+ }
5887
+ return null;
5888
+ }
5889
+ function formatCoverageRow(label, measured, minThreshold) {
5890
+ if (measured === void 0) {
5891
+ return `| **${label}** | \`N/A (Run test:coverage)\` | $\\ge ${minThreshold}\\%$ | \u2139\uFE0F NOT MEASURED |`;
5892
+ }
5893
+ const status = measured >= minThreshold ? "\u2705 COMPLIANT" : "\u26A0\uFE0F BELOW THRESHOLD";
5894
+ return `| **${label}** | \`${measured}%\` | $\\ge ${minThreshold}\\%$ | ${status} |`;
5895
+ }
5896
+ function buildCoverageRows(coverage, policy) {
5897
+ return [
5898
+ formatCoverageRow("Line Coverage", coverage?.linesPct, policy.min_line_coverage),
5899
+ formatCoverageRow("Branch Coverage", coverage?.branchesPct, policy.min_branch_coverage),
5900
+ formatCoverageRow("Function Coverage", coverage?.functionsPct, policy.min_function_coverage)
5901
+ ];
5902
+ }
5818
5903
  function generateQualityReport(options = {}) {
5819
5904
  const rootDir = options.rootDir || process.cwd();
5820
5905
  const gateResult = verifyQualityGate({
@@ -5822,6 +5907,7 @@ function generateQualityReport(options = {}) {
5822
5907
  policyPath: options.policyPath,
5823
5908
  thresholds: options.thresholds
5824
5909
  });
5910
+ const coverage = readCodeCoverageSummary(rootDir);
5825
5911
  const totalFiles = gateResult.totalFiles;
5826
5912
  const totalFunctions = gateResult.totalFunctions;
5827
5913
  let totalMi = 0;
@@ -5870,6 +5956,7 @@ function generateQualityReport(options = {}) {
5870
5956
  `| **Cyclomatic Complexity (Average)** | \`${avgCyclomatic}\` | $\\le ${gateResult.policy.max_cyclomatic}$ | ${avgCyclomatic <= gateResult.policy.max_cyclomatic ? "\u2705 COMPLIANT" : "\u274C EXCEEDED"} |`,
5871
5957
  `| **Cognitive Complexity (Average)** | \`${avgCognitive}\` | $\\le ${gateResult.policy.max_cognitive}$ | ${avgCognitive <= gateResult.policy.max_cognitive ? "\u2705 COMPLIANT" : "\u274C EXCEEDED"} |`,
5872
5958
  `| **Maintainability Index (SEI MI)** | \`${avgMaintainability} / 100\` | $\\ge ${gateResult.policy.min_maintainability}$ | ${avgMaintainability >= gateResult.policy.min_maintainability ? "\u2705 COMPLIANT" : "\u274C INSUFFICIENT"} |`,
5959
+ ...buildCoverageRows(coverage, gateResult.policy),
5873
5960
  `| **Functions in Violation** | \`${gateResult.failCount}\` | $0$ (Mode ${gateResult.policy.enforce_mode}) | ${gateResult.failCount === 0 ? "\u2705 0 VIOLATIONS" : "\u274C BLOCKED"} |`,
5874
5961
  ``,
5875
5962
  `---`,
@@ -5901,6 +5988,7 @@ function generateQualityReport(options = {}) {
5901
5988
  lines.push("## 4. Evaluation Criteria and Standards");
5902
5989
  lines.push("- **McCabe Cyclomatic Complexity (CC)**: Number of linearly independent paths.");
5903
5990
  lines.push("- **Maintainability Index (SEI MI)**: Normalized formula [0 - 100] combining Halstead Volume, CC, and LOC.");
5991
+ lines.push("- **Automated Test Coverage**: Passively ingested from `coverage/coverage-summary.json` or `lcov.info` when present.");
5904
5992
  lines.push("- **Clean Code Guardrails**: Prohibition of implicit `any` typing, function length limits ($le 40$ lines), and zero unjustified suppressions.");
5905
5993
  const markdown = lines.join("\n");
5906
5994
  const reportsDir = path8__namespace.join(rootDir, "reports");
@@ -5912,7 +6000,8 @@ function generateQualityReport(options = {}) {
5912
6000
  totalFunctions,
5913
6001
  avgMaintainability,
5914
6002
  avgCyclomatic,
5915
- verdict
6003
+ verdict,
6004
+ coverage
5916
6005
  };
5917
6006
  }
5918
6007
 
@@ -7601,10 +7690,12 @@ function collectDashboardMetrics(rootDir) {
7601
7690
  }
7602
7691
  } catch {
7603
7692
  }
7693
+ const codeCoverage = readCodeCoverageSummary(rootDir);
7604
7694
  return {
7605
7695
  gate,
7606
7696
  gov,
7607
7697
  cov,
7698
+ codeCoverage,
7608
7699
  trace,
7609
7700
  history,
7610
7701
  activePrKpi,
@@ -8146,6 +8237,11 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
8146
8237
  <span class="value">${metricsData.cov.totalRequirements > 0 ? Math.round(metricsData.cov.passedRequirements / metricsData.cov.totalRequirements * 100) : 100}%</span>
8147
8238
  <span class="subtitle">${metricsData.cov.passedRequirements} / ${metricsData.cov.totalRequirements} verified</span>
8148
8239
  </div>
8240
+ <div class="metric-card">
8241
+ <span class="title">Code Coverage (Lines)</span>
8242
+ <span class="value">${metricsData.codeCoverage ? metricsData.codeCoverage.linesPct + "%" : "N/A"}</span>
8243
+ <span class="subtitle">${metricsData.codeCoverage ? `Branches: ${metricsData.codeCoverage.branchesPct}% | Funcs: ${metricsData.codeCoverage.functionsPct}%` : `Min target: ${metricsData.gate.policy.min_line_coverage}% (Run test:coverage)`}</span>
8244
+ </div>
8149
8245
  </div>
8150
8246
 
8151
8247
  <div class="chart-container">
@@ -11558,6 +11654,7 @@ exports.parseSpecCatalogScope = parseSpecCatalogScope;
11558
11654
  exports.parseTasksDoc = parseTasksDoc;
11559
11655
  exports.parseTasksFromContent = parseTasksFromContent;
11560
11656
  exports.processPackageDir = processPackageDir;
11657
+ exports.readCodeCoverageSummary = readCodeCoverageSummary;
11561
11658
  exports.readPackageJson = readPackageJson;
11562
11659
  exports.renderAntigravityAgentFile = renderAntigravityAgentFile;
11563
11660
  exports.renderClaudeAgentFile = renderClaudeAgentFile;