diff --git a/.github/workflows/ai-code-quality-sonarcloud-manual.yml b/.github/workflows/ai-code-quality-sonarcloud-manual.yml index a550eff..78ef841 100644 --- a/.github/workflows/ai-code-quality-sonarcloud-manual.yml +++ b/.github/workflows/ai-code-quality-sonarcloud-manual.yml @@ -2,6 +2,9 @@ name: AI Code Quality - SonarCloud (Manual) on: workflow_dispatch: + push: + branches: + - '**' jobs: quality: @@ -27,30 +30,81 @@ jobs: - name: Install project & analysis dependencies run: | npm ci - npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin \ - typhonjs-escomplex typescript - # removed semgrep from npm installs because Semgrep is installed/run via the official action (or via pipx/docker) - name: TypeScript compile (build project -> dist) - run: npx tsc -p tsconfig.json + continue-on-error: true + run: | + set +e + npx tsc -p tsconfig.json + EXIT_CODE=$? + + if [ "$EXIT_CODE" -eq 0 ]; then + COMPILED=1 + else + COMPILED=0 + fi + + node - < node-test-output.txt 2>&1 || true - - name: Run Semgrep (SAST) — official action - # Official Semgrep action will install and run Semgrep (no need to install with npm) - uses: returntocorp/semgrep-action@v1 - continue-on-error: true - with: - config: 'p/ci' - output: 'semgrep.json' - format: 'json' + # Parse test counts from the Node test runner output and update tools/AgentScoreCard.json + node - <<'NODE' + const fs = require('fs'); + + const OUTPUT_PATH = 'node-test-output.txt'; + const SCORECARD_PATH = 'tools/AgentScoreCard.json'; + + let totalTests = 0; + let passedTests = 0; + + try { + const text = fs.readFileSync(OUTPUT_PATH, 'utf8'); - - name: Run escomplex (cyclomatic complexity) - run: npx typhonjs-escomplex -f json -o escomplex.json "src/**/*.ts" || true + // Summary lines typically contain "tests N" and "pass M" + // e.g. "ℹ tests 3" / "ℹ pass 3" or similar. + const testsMatch = text.match(/tests\s+(\d+)/); + const passMatch = text.match(/pass\s+(\d+)/); + + if (testsMatch) totalTests = Number(testsMatch[1]) || 0; + if (passMatch) passedTests = Number(passMatch[1]) || 0; + } catch (e) { + console.warn('Failed to read or parse node-test-output.txt', e); + } + + const passRate = totalTests > 0 ? passedTests / totalTests : 0; + + try { + const raw = fs.readFileSync(SCORECARD_PATH, 'utf8'); + const data = JSON.parse(raw); + data.testsPassRate = passRate; + fs.writeFileSync(SCORECARD_PATH, JSON.stringify(data, null, 2)); + console.log( + 'Updated AgentScoreCard.json testsPassRate to', + passRate, + `(${passedTests}/${totalTests})` + ); + } catch (e) { + console.warn('Failed to update AgentScoreCard.json testsPassRate', e); + } + NODE - name: Collect files/lines info run: | @@ -80,6 +134,7 @@ jobs: -Dsonar.organization=${{ secrets.SONAR_ORG }} -Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }} -Dsonar.sources=src + -Dsonar.inclusions=src/app.ts -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} @@ -88,7 +143,9 @@ jobs: env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | - METRICS="coverage,security_rating,vulnerabilities,code_smells,duplicated_lines_density,complexity,alert_status,reliability_rating,maintainability_rating" + # Metrics include: coverage, security_rating, vulnerabilities, code_smells, duplicated_lines_density, + # complexity (cyclomatic complexity), alert_status, reliability_rating, maintainability_rating + METRICS="coverage,security_rating,vulnerabilities,code_smells,duplicated_lines_density,complexity,alert_status,reliability_rating,sqale_index" curl -s -u "${SONAR_TOKEN}:" "https://sonarcloud.io/api/measures/component?component=${{ secrets.SONAR_PROJECT_KEY }}&metricKeys=${METRICS}" -o sonar_metrics.json || true echo "Saved sonar_metrics.json" @@ -115,13 +172,23 @@ jobs: path: | composite_score.txt score_breakdown.json + score_report.json + sonar_metrics.json - name: Print composite + detailed breakdown run: | - if [ -f composite_score.txt ]; then - echo "Composite Score: $(cat composite_score.txt)" + if [ -f norms.json ]; then + echo "" + echo "Score Card:" + cat norms.json fi - if [ -f score_breakdown.json ]; then - echo "Detailed Category Breakdown:" - cat score_breakdown.json + if [ -f sonar_metrics.json ]; then + echo "" + echo "sonar metrics:" + cat sonar_metrics.json fi + if [ -f result.json ]; then + echo "" + echo "Results:" + cat result.json + fi \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 504ec6b..97c671b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@eslint/eslintrc": "^3.3.1", "@paypal/paypal-server-sdk": "^2.0.0", + "dotenv": "^16.4.5", "fs": "^0.0.1-security" }, "devDependencies": { @@ -528,6 +529,7 @@ "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", @@ -755,6 +757,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1025,6 +1028,18 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "license": "MIT" }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1144,6 +1159,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -1884,6 +1900,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2054,6 +2071,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 6c875e6..67af491 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "dependencies": { "@eslint/eslintrc": "^3.3.1", "@paypal/paypal-server-sdk": "^2.0.0", - "fs": "^0.0.1-security" + "fs": "^0.0.1-security", + "dotenv": "^16.4.5" }, "name": "ai_code_quality_sonarcloud", "version": "1.0.0", diff --git a/src/app.ts b/src/app.ts index 8a3f89d..3d5b9e0 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,77 +1,185 @@ +import "dotenv/config"; + import { ApiError, CheckoutPaymentIntent, Client, + CustomError, Environment, LogLevel, - OrdersController, -} from '@paypal/paypal-server-sdk'; - -const client = new Client({ - clientCredentialsAuthCredentials: { - oAuthClientId: 'OAuthClientId', - oAuthClientSecret: 'OAuthClientSecret' - }, - timeout: 0, - environment: Environment.Sandbox, - logging: { - logLevel: LogLevel.Info, - logRequest: { - logBody: true + OrdersController +} from "@paypal/paypal-server-sdk"; + +let cachedOrdersController: OrdersController | null = null; + +type CreateOrderOptions = { + amount?: string; + currencyCode?: string; + intent?: CheckoutPaymentIntent; + referenceId?: string; + preferMinimalResponse?: boolean; +}; + +function resolveEnvironment(rawValue: string | undefined | null): Environment { + const normalized = (rawValue ?? "sandbox").trim().toLowerCase(); + + if (normalized === "production" || normalized === "live") { + return Environment.Production; + } + + return Environment.Sandbox; +} + +function parseTimeout(rawTimeout: string | undefined): number { + const parsed = Number.parseInt(rawTimeout ?? "0", 10); + return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0; +} + +function resolveConfig() { + const clientId = process.env.PAYPAL_CLIENT_ID; + const clientSecret = process.env.PAYPAL_CLIENT_SECRET; + + if (!clientId || !clientSecret) { + throw new Error( + "Missing PayPal credentials. Set PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET environment variables before calling PayPal APIs." + ); + } + + return { + clientId, + clientSecret, + environment: resolveEnvironment(process.env.PAYPAL_ENV), + timeout: parseTimeout(process.env.PAYPAL_TIMEOUT_MS) + }; +} + +function getOrdersController(): OrdersController { + if (cachedOrdersController) { + return cachedOrdersController; + } + + const { clientId, clientSecret, environment, timeout } = resolveConfig(); + + const client = new Client({ + clientCredentialsAuthCredentials: { + oAuthClientId: clientId, + oAuthClientSecret: clientSecret }, - logResponse: { - logHeaders: true + environment, + timeout, + logging: { + logLevel: LogLevel.Info, + logRequest: { logBody: true }, + logResponse: { logHeaders: true } } - }, -}); - -const ordersController = new OrdersController(client); - -async function createAndCaptureOrder() { - const createOrderRequest = { - body: { - intent: CheckoutPaymentIntent.Capture, - purchaseUnits: [ - { - amount: { - currencyCode: 'EUR', - value: '100.00', - }, - } - ], - paymentSource: { - mybank: { - name: 'John Doe', - countryCode: 'IT' - } + }); + + cachedOrdersController = new OrdersController(client); + return cachedOrdersController; +} + +function handlePayPalError(context: string, error: unknown): never { + if (error instanceof ApiError) { + console.error(`${context} (status: ${error.statusCode})`); + + if (error.body) { + console.error("Response body:", JSON.stringify(error.body, null, 2)); + } + + if (error instanceof CustomError && error.result) { + console.error("Error name:", error.result.name); + console.error("Error message:", error.result.message); + + if (error.result.details) { + console.error( + "Error details:", + JSON.stringify(error.result.details, null, 2) + ); } - }, - prefer: 'return=minimal' - }; + } + } else { + console.error(`${context}:`, error); + } - try { - const createOrderResponse = await ordersController.createOrder(createOrderRequest); - const orderId = createOrderResponse.result.id; + throw error instanceof Error ? error : new Error(String(error)); +} - console.log('Order created with ID:', orderId); +export async function createOrder( + options: CreateOrderOptions = {} +): Promise { + const { + amount = "100.00", + currencyCode = "USD", + intent = CheckoutPaymentIntent.Capture, + referenceId = "default", + preferMinimalResponse = false + } = options; - const captureOrderRequest = { - id: orderId as string, - prefer: 'return=minimal' - }; + try { + const controller = getOrdersController(); + + const createResponse = await controller.createOrder({ + body: { + intent, + purchaseUnits: [ + { + referenceId, + amount: { + currencyCode, + value: amount + } + } + ] + }, + prefer: preferMinimalResponse + ? "return=minimal" + : "return=representation" + }); - const captureOrderResponse = await ordersController.captureOrder(captureOrderRequest); + return createResponse.result; + } catch (error) { + return handlePayPalError("Failed to create order", error); + } +} - console.log('Order captured with ID:', captureOrderResponse.result.id); - console.log('Capture status:', captureOrderResponse.result.status); +export async function getOrder(orderId: string): Promise { + if (!orderId) { + throw new Error("orderId is required to retrieve an order."); + } + try { + const controller = getOrdersController(); + const orderResponse = await controller.getOrder({ id: orderId }); + return orderResponse.result; } catch (error) { - if (error instanceof ApiError) { - console.error('API Error:', error.statusCode, error.body); - } else { - console.error('Unexpected Error:', error); - } + return handlePayPalError("Failed to retrieve order", error); } } -createAndCaptureOrder(); \ No newline at end of file +async function runSample(): Promise { + if ((process.env.RUN_PAYPAL_ORDER_SAMPLE ?? "").toLowerCase() !== "true") { + return; + } + + const createdOrder = await createOrder(); + + const createdOrderId = + createdOrder && typeof createdOrder === "object" + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (createdOrder as any).id + : undefined; + + if (!createdOrderId) { + console.warn("Created order is missing an ID. Skipping retrieval step."); + return; + } + + const orderDetails = await getOrder(createdOrderId); + + console.log("Created order:", createdOrder); + console.log("Retrieved order:", orderDetails); +} + +void runSample(); + + diff --git a/tests/app.test.ts b/tests/app.test.ts new file mode 100644 index 0000000..463dd9a --- /dev/null +++ b/tests/app.test.ts @@ -0,0 +1,92 @@ +import "dotenv/config"; +import { strict as assert } from "node:assert"; +import test from "node:test"; + +// IMPORTANT: +// This file is compiled by TypeScript into dist/tests/app.test.js. +// The relative import below is resolved at runtime from dist/tests to dist/src. +import { createOrder, getOrder } from "../src/app.js"; + +const hasPayPalCredentials = + Boolean(process.env.PAYPAL_CLIENT_ID) && + Boolean(process.env.PAYPAL_CLIENT_SECRET); + +test("getOrder throws when orderId is empty", async () => { + await assert.rejects( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async () => getOrder("" as any), + (err: unknown) => { + assert.ok(err instanceof Error); + assert.equal( + (err as Error).message, + "orderId is required to retrieve an order." + ); + return true; + } + ); +}); + +test("createOrder fails with missing PayPal credentials", async () => { + const originalClientId = process.env.PAYPAL_CLIENT_ID; + const originalClientSecret = process.env.PAYPAL_CLIENT_SECRET; + + delete process.env.PAYPAL_CLIENT_ID; + delete process.env.PAYPAL_CLIENT_SECRET; + + try { + await assert.rejects( + async () => { + await createOrder(); + }, + (err: unknown) => { + assert.ok(err instanceof Error); + assert.equal( + (err as Error).message, + "Missing PayPal credentials. Set PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET environment variables before calling PayPal APIs." + ); + return true; + } + ); + } finally { + if (originalClientId !== undefined) { + process.env.PAYPAL_CLIENT_ID = originalClientId; + } + if (originalClientSecret !== undefined) { + process.env.PAYPAL_CLIENT_SECRET = originalClientSecret; + } + } +}); + +if (!hasPayPalCredentials) { + test.skip( + "createOrder then getOrder using returned id (skipped: missing PAYPAL credentials)", + () => {} + ); +} else { + test("createOrder then getOrder using returned id", async () => { + const createdOrder = await createOrder(); + + const createdOrderId = + createdOrder && typeof createdOrder === "object" + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (createdOrder as any).id + : undefined; + + assert.ok(createdOrderId, "Expected created order to provide an id"); + + const fetchedOrder = await getOrder(createdOrderId as string); + + const fetchedOrderId = + fetchedOrder && typeof fetchedOrder === "object" + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (fetchedOrder as any).id + : undefined; + + assert.equal( + fetchedOrderId, + createdOrderId, + "Fetched order should have the same id as the created order" + ); + }); +} + diff --git a/tools/AgentScoreCard.json b/tools/AgentScoreCard.json new file mode 100644 index 0000000..d5df46b --- /dev/null +++ b/tools/AgentScoreCard.json @@ -0,0 +1,17 @@ +{ + "issuesFound": 1, + "issuesFixed": 1, + "fixAttempts": 1, + "compilable": 0, + "testsPassRate": 0, + "examples": { + "fixAttempts": 3, + "issuesFound": 2, + "issuesFixed": 1 + }, + "example_explanations": { + "issuesFound": "Count of issues found in the codebase (integer). Example: 2 issues found during your implementation", + "issuesFixed": "Count of issues fixed in the codebase (integer). Example: 1 issue fixed during your implementation", + "fixAttempts": "Count of distinct implementation attempts before success (integer). Example: 2 retries = 3 attempts during your implementation" + } +} \ No newline at end of file diff --git a/tools/score.ts b/tools/score.ts index c96b4e8..da7949f 100644 --- a/tools/score.ts +++ b/tools/score.ts @@ -1,8 +1,16 @@ // tools/score.ts -import fs from "fs"; +import * as fs from "fs"; type Norms = Record; type Weights = Record; +type AgentScoreCard = { + fixAttempts?: number; + issuesFound?: number; + issuesFixed?: number; + compilable?: number; + testsPassRate?: number; + [key: string]: any; +}; function safeRead(p: string) { try { @@ -14,59 +22,28 @@ function safeRead(p: string) { } } -/* Normalizers */ - -// coverage from coverage-summary.json (jest json-summary) -function normCoverage(cov: any): number { - try { - const pct = cov?.total?.lines?.pct ?? cov?.total?.lines?.percentage ?? 0; - return Math.round(Math.max(0, Math.min(100, pct))); - } catch { - return 0; - } -} +function applyAgentScoreCard(norms: Norms) { + const scoreCard = safeRead("tools/AgentScoreCard.json") as AgentScoreCard | null; + if (!scoreCard) return; -// eslint.json => errors per KLOC -> norm -function normESLint(eslintJson: any, totalLines: number): number { - if (!eslintJson) return 100; - const reports = Array.isArray(eslintJson) ? eslintJson : []; - const totalMessages = reports.reduce((acc: number, r: any) => acc + (r.messages?.length ?? 0), 0); - const kloc = Math.max(0.001, totalLines / 1000); - const errorsPerKloc = totalMessages / kloc; - return Math.round(Math.max(0, Math.min(100, 100 - errorsPerKloc * 8))); -} + const fieldMap: Record = { + unitTestPassRate: "testsPassRate", + compilation: "compilable", + issuesFound: "issuesFound", + issuesFixed: "issuesFixed", + fixAttempts: "fixAttempts" + }; -// semgrep.json => severity-based penalty -function normSemgrep(semgrepJson: any): number { - if (!semgrepJson) return 100; - const results = semgrepJson.results ?? semgrepJson; - let score = 100; - for (const r of results) { - const sev = (r.extra?.severity || r.severity || "INFO").toString().toUpperCase(); - if (["CRITICAL", "HIGH"].includes(sev)) score -= 30; - else if (["MEDIUM"].includes(sev)) score -= 10; - else score -= 2; + for (const [normKey, cardKey] of Object.entries(fieldMap)) { + const raw = scoreCard[cardKey]; + const val = typeof raw === "number" ? raw : Number(raw); + if (!Number.isNaN(val)) { + norms[normKey] = val; + } } - return Math.max(0, score); } -// escomplex.json -> average cyclomatic -> norm -function normComplexity(escomplexJson: any): number { - if (!escomplexJson) return 100; - let vals: number[] = []; - const collect = (obj: any) => { - if (!obj || typeof obj !== "object") return; - for (const k of Object.keys(obj)) { - const v = obj[k]; - if (k.toLowerCase().includes("cyclomatic") && typeof v === "number") vals.push(v); - else if (Array.isArray(v)) v.forEach(collect); - else if (typeof v === "object") collect(v); - } - }; - collect(escomplexJson); - const avg = vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 1; - return Math.round(Math.max(0, Math.min(100, 100 - 5 * (avg - 1)))); -} +/* Normalizers */ // Sonar metrics: sonar_metrics.json contains measures array function readSonarMetrics(sonarJson: any) { @@ -84,19 +61,39 @@ function readSonarMetrics(sonarJson: any) { function normFromSonarMeasure(measures: Record) { const norms: Partial> = {}; - norms.correctness = measures.coverage ? Math.round(Number(measures.coverage)) : 0; - const vulns = Number(measures.vulnerabilities ?? 0); - norms.security = Math.max(0, 100 - vulns * 25); + console.log("[Sonar] code_smells:", measures.code_smells); + console.log("[Sonar] sqale_index:", measures.sqale_index); + console.log("[Sonar] complexity:", measures.complexity); + console.log("[Sonar] duplicated_lines_density:", measures.duplicated_lines_density); + console.log("[Sonar] reliability_rating:", measures.reliability_rating); + console.log("[Sonar] security_rating:", measures.security_rating); const smells = Number(measures.code_smells ?? 0); - norms.maintainability = Math.max(0, 100 - smells * 0.2); + norms.maintainability = Math.max(0, 100 - smells - (measures.sqale_index * 0.2)); + // code_smells: # + // sqale_index: mins + + norms.performance = Number(measures.complexity ?? 0); + // complexity: branches const dup = Number(measures.duplicated_lines_density ?? 0); norms.duplication = Math.max(0, Math.round(100 - dup)); - - const complexity = Number(measures.complexity ?? 0); - norms.maintainability = Math.round(Math.max(0, Math.min(100, (norms.maintainability ?? 100) - complexity * 0.05))); + // duplicated_lines_density: % + + const reliabilityRating = Number(measures.reliability_rating ?? 0); + norms.reliability = Math.max( + 0, + Math.min(100, ((5 - reliabilityRating) / 4) * 100) + ); + // reliability_rating: 1 (best) - 5 (worst), mapped so 1 -> 100, 5 -> 0 + + const securityRating = Number(measures.security_rating ?? 0); + norms.security = Math.max( + 0, + Math.min(100, ((5 - securityRating) / 4) * 100) + ); + // security_rating: 1 (best) - 5 (worst), mapped so 1 -> 100, 5 -> 0 return norms as Record; } @@ -112,27 +109,33 @@ function computeComposite(norms: Norms, weights: Weights): number { return Math.round(s * 100) / 100; } -function main() { - const coverage = safeRead("coverage/coverage-summary.json"); - const eslintJson = safeRead("eslint.json"); - const semgrepJson = safeRead("semgrep.json"); - const escomplexJson = safeRead("escomplex.json"); +function main(): Norms { const filesInfo = safeRead("files_info.json") || { total_lines: 0 }; const sonarMetricsRaw = safeRead("sonar_metrics.json"); const totalLines = filesInfo?.total_lines ?? 0; // norms from individual tools + // (initialize defaults; individual normalizers can override) const norms: Norms = { - correctness: normCoverage(coverage), - security: normSemgrep(semgrepJson), - maintainability: normComplexity(escomplexJson), - readability: normESLint(eslintJson, totalLines), - robustness: 90, // placeholder - duplication: 95, - performance: 85, - consistency: 90, + // Correctness + unitTestPassRate: -1, + compilation: -1, + issuesFound: -1, + issuesFixed: -1, + autoFixRate:0, + // Quality + security: -1, + reliability: -1, + maintainability: -1, + duplication: -1, + performance: -1, + // Efficiency + fixAttempts: -1 }; + + + applyAgentScoreCard(norms); // incorporate Sonar measures if (sonarMetricsRaw) { @@ -140,39 +143,40 @@ function main() { const sonarNorms = normFromSonarMeasure(sonarMap); for (const k of Object.keys(sonarNorms)) { const val = sonarNorms[k]; - if (typeof val === "number") norms[k] = Math.round(((norms[k] ?? 0) + val) / 2); + if (typeof val === "number") { + // Prefer Sonar-derived norm; overwrite any existing value. + norms[k] = val; + } } } - const weights: Weights = { - correctness: 25, - security: 20, - maintainability: 15, - readability: 10, - robustness: 10, - duplication: 6, - performance: 6, - consistency: 8 - }; - - const score = computeComposite(norms, weights); - - // Write outputs - fs.writeFileSync("composite_score.txt", String(score), "utf8"); - - // Detailed per-category breakdown for workflow artifact - const breakdown: Record = {}; - for (const k of Object.keys(weights)) { - breakdown[k] = norms[k] ?? 0; - } - fs.writeFileSync("score_breakdown.json", JSON.stringify(breakdown, null, 2), "utf8"); - - // Full report for debugging/history - const fullReport = { score, norms, weights, timestamp: new Date().toISOString() }; - fs.writeFileSync("score_report.json", JSON.stringify(fullReport, null, 2), "utf8"); - - console.log("Composite Score:", score); - console.log("Per-category breakdown:", breakdown); + // Primary output is just norms; logs are kept for visibility. + fs.writeFileSync("norms.json", JSON.stringify(norms, null, 2), "utf8"); + + // Write a compact, comma-separated summary line to result.json + // Order: compilation, issuesFound, issuesFixed, autoFixRate, security, + // reliability, maintainability, duplication, performance, fixAttempts + const resultValues = [ + norms.unitTestPassRate, + norms.compilation, + norms.issuesFound, + norms.issuesFixed, + norms.autoFixRate, + norms.security, + norms.reliability, + norms.maintainability, + norms.duplication, + norms.performance, + norms.fixAttempts + ]; + + const csvLine = resultValues.join(","); + fs.writeFileSync("result.json", csvLine, "utf8"); + + console.log("Norms summary written to result.json (CSV):", csvLine); + + return norms; } -main(); +const norms = main(); +export default norms; diff --git a/tsconfig.json b/tsconfig.json index e395720..bb29bc9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "module": "nodenext", "target": "esnext", - "types": [], + "types": ["node"], "sourceMap": true, "declaration": true, @@ -22,5 +22,5 @@ "moduleDetection": "force", "skipLibCheck": true }, - "include": ["src/**/*", "tools/**/*"] + "include": ["src/**/*", "tools/**/*", "tests/**/*"] }