diff --git a/README.md b/README.md
index 7b1a534..8a8b77d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Dead Code Hunter
-A GitHub Action that finds unreachable functions in your codebase using [Supermodel](https://supermodeltools.com).
+A GitHub Action that finds unused code in your codebase using [Supermodel](https://supermodeltools.com) static analysis.
## Installation
@@ -45,6 +45,7 @@ That's it! The action will now analyze your code on every PR and comment with an
| Input | Description | Required | Default |
|-------|-------------|----------|---------|
| `supermodel-api-key` | Your Supermodel API key | Yes | - |
+| `github-token` | GitHub token for posting PR comments | No | `github.token` |
| `comment-on-pr` | Post findings as PR comment | No | `true` |
| `fail-on-dead-code` | Fail the action if dead code found | No | `false` |
| `ignore-patterns` | JSON array of glob patterns to ignore | No | `[]` |
@@ -61,36 +62,45 @@ That's it! The action will now analyze your code on every PR and comment with an
## What it does
-1. Creates a zip of your repository
-2. Sends it to Supermodel for analysis
-3. Identifies functions with no callers
-4. Filters out false positives (entry points, exports, tests)
-5. Posts findings as a PR comment
+1. Creates a zip of your repository via `git archive`
+2. Sends it to the Supermodel dead code analysis API
+3. The API performs symbol-level import analysis to identify unused exports
+4. Results are returned with confidence levels and reasons
+5. Posts findings as a PR comment with a sortable table
+
+## What it detects
+
+- **Functions** and **methods** with no callers
+- **Classes** and **interfaces** that are never referenced
+- **Types**, **variables**, and **constants** that are exported but never imported
+- **Orphaned files** whose exports have no importers anywhere in the codebase
+- **Transitively dead code** — code only called by other dead code
+
+Each finding includes a **confidence level** (high, medium, low) and a **reason** explaining why it was flagged.
## Example output
> ## Dead Code Hunter
>
-> Found **3** potentially unused functions:
+> Found **3** potentially unused code elements:
+>
+> | Name | Type | File | Line | Confidence |
+> |------|------|------|------|------------|
+> | `unusedHelper` | function | src/utils.ts#L42 | L42 | :red_circle: high |
+> | `OldValidator` | class | src/validation.ts#L15 | L15 | :red_circle: high |
+> | `LegacyConfig` | interface | src/legacy.ts#L8 | L8 | :orange_circle: medium |
+>
+> Analysis summary
>
-> | Function | File | Line |
-> |----------|------|------|
-> | `unusedHelper` | src/utils.ts#L42 | L42 |
-> | `oldValidator` | src/validation.ts#L15 | L15 |
-> | `deprecatedFn` | src/legacy.ts#L8 | L8 |
+> - **Total declarations analyzed**: 150
+> - **Dead code candidates**: 3
+> - **Alive code**: 147
+> - **Analysis method**: symbol_level_import_analysis
+>
+>
>
> ---
-> _Powered by [Supermodel](https://supermodeltools.com) graph analysis_
-
-## False positive filtering
-
-The action automatically skips:
-
-- **Entry point files**: `index.ts`, `main.ts`, `app.ts`
-- **Entry point functions**: `main`, `run`, `start`, `init`, `handler`
-- **Exported functions**: May be called from outside the repo
-- **Test files**: `*.test.ts`, `*.spec.ts`, `__tests__/**`
-- **Build output**: `node_modules`, `dist`, `build`, `target`
+> _Powered by [Supermodel](https://supermodeltools.com) dead code analysis_
## Supported languages
diff --git a/action.yml b/action.yml
index 7c37c98..e0fc913 100644
--- a/action.yml
+++ b/action.yml
@@ -1,5 +1,5 @@
name: 'Dead Code Hunter'
-description: 'Find unreachable functions in your codebase using Supermodel call graphs'
+description: 'Find unused code in your codebase using Supermodel static analysis'
author: 'Supermodel Tools'
branding:
@@ -29,9 +29,9 @@ inputs:
outputs:
dead-code-count:
- description: 'Number of dead code functions found'
+ description: 'Number of unused code elements found'
dead-code-json:
- description: 'JSON array of dead code findings'
+ description: 'JSON array of dead code findings with type, confidence, and reason'
runs:
using: 'node20'
diff --git a/dist/dead-code.d.ts b/dist/dead-code.d.ts
index 3e5b129..2e2a7ca 100644
--- a/dist/dead-code.d.ts
+++ b/dist/dead-code.d.ts
@@ -1,50 +1,12 @@
-import { CodeGraphNode, CodeGraphRelationship } from '@supermodeltools/sdk';
+import type { DeadCodeCandidate, DeadCodeAnalysisResponse, DeadCodeAnalysisMetadata } from '@supermodeltools/sdk';
+export type { DeadCodeCandidate, DeadCodeAnalysisResponse, DeadCodeAnalysisMetadata };
/**
- * Represents a potentially unused function found in the codebase.
+ * Filters dead code candidates by user-provided ignore patterns.
+ * The API handles all analysis server-side; this is purely for
+ * client-side post-filtering on file paths.
*/
-export interface DeadCodeResult {
- id: string;
- name: string;
- filePath: string;
- startLine?: number;
- endLine?: number;
-}
-/** Default glob patterns for files to exclude from dead code analysis. */
-export declare const DEFAULT_EXCLUDE_PATTERNS: string[];
-/** Glob patterns for files that are considered entry points. */
-export declare const ENTRY_POINT_PATTERNS: string[];
-/** Function names that are considered entry points. */
-export declare const ENTRY_POINT_FUNCTION_NAMES: string[];
+export declare function filterByIgnorePatterns(candidates: DeadCodeCandidate[], ignorePatterns: string[]): DeadCodeCandidate[];
/**
- * Checks if a file path matches any entry point pattern.
- * @param filePath - The file path to check
- * @returns True if the file is an entry point
+ * Formats dead code analysis results as a GitHub PR comment.
*/
-export declare function isEntryPointFile(filePath: string): boolean;
-/**
- * Checks if a function name is a common entry point name.
- * @param name - The function name to check
- * @returns True if the function name is an entry point
- */
-export declare function isEntryPointFunction(name: string): boolean;
-/**
- * Checks if a file should be ignored based on exclude patterns.
- * @param filePath - The file path to check
- * @param ignorePatterns - Additional patterns to ignore
- * @returns True if the file should be ignored
- */
-export declare function shouldIgnoreFile(filePath: string, ignorePatterns?: string[]): boolean;
-/**
- * Analyzes a code graph to find functions that are never called.
- * @param nodes - All nodes from the code graph
- * @param relationships - All relationships from the code graph
- * @param ignorePatterns - Additional glob patterns to ignore
- * @returns Array of potentially unused functions
- */
-export declare function findDeadCode(nodes: CodeGraphNode[], relationships: CodeGraphRelationship[], ignorePatterns?: string[]): DeadCodeResult[];
-/**
- * Formats dead code results as a GitHub PR comment.
- * @param deadCode - Array of dead code results
- * @returns Markdown-formatted comment string
- */
-export declare function formatPrComment(deadCode: DeadCodeResult[]): string;
+export declare function formatPrComment(candidates: DeadCodeCandidate[], metadata?: DeadCodeAnalysisMetadata): string;
diff --git a/dist/dead-code.d.ts.map b/dist/dead-code.d.ts.map
index 987c001..18a350a 100644
--- a/dist/dead-code.d.ts.map
+++ b/dist/dead-code.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jag/dead-code-hunter/src/dead-code.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE5E;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,0EAA0E;AAC1E,eAAO,MAAM,wBAAwB,UAiBpC,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,UAUhC,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,0BAA0B,UAUtC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG1D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,GAAE,MAAM,EAAO,GAAG,OAAO,CAGzF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,aAAa,EAAE,EACtB,aAAa,EAAE,qBAAqB,EAAE,EACtC,cAAc,GAAE,MAAM,EAAO,GAC5B,cAAc,EAAE,CA6ClB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAiClE"}
\ No newline at end of file
+{"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jag/repos/dead-code-hunter-issue-8/src/dead-code.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAGlH,YAAY,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,CAAC;AAEtF;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,iBAAiB,EAAE,EAC/B,cAAc,EAAE,MAAM,EAAE,GACvB,iBAAiB,EAAE,CAGrB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,iBAAiB,EAAE,EAC/B,QAAQ,CAAC,EAAE,wBAAwB,GAClC,MAAM,CAgDR"}
\ No newline at end of file
diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map
index 650454b..93efa40 100644
--- a/dist/index.d.ts.map
+++ b/dist/index.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jag/dead-code-hunter/src/index.ts"],"names":[],"mappings":""}
\ No newline at end of file
+{"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jag/repos/dead-code-hunter-issue-8/src/index.ts"],"names":[],"mappings":""}
\ No newline at end of file
diff --git a/dist/index.js b/dist/index.js
index aba9b31..73d82ed 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -7161,7 +7161,7 @@ var request = withDefaults(import_endpoint.endpoint, {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -7230,7 +7230,7 @@ class DefaultApi extends runtime.BaseAPI {
query: queryParameters,
body: formParams,
}, initOverrides);
- return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.CodeGraphEnvelopeFromJSON)(jsonValue));
+ return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.CodeGraphEnvelopeAsyncFromJSON)(jsonValue));
});
}
/**
@@ -7243,6 +7243,64 @@ class DefaultApi extends runtime.BaseAPI {
return yield response.value();
});
}
+ /**
+ * Upload a zipped repository snapshot to identify dead (unreachable) code candidates by combining parse graph declarations with call graph relationships.
+ * Dead code analysis
+ */
+ generateDeadCodeAnalysisRaw(requestParameters, initOverrides) {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (requestParameters['idempotencyKey'] == null) {
+ throw new runtime.RequiredError('idempotencyKey', 'Required parameter "idempotencyKey" was null or undefined when calling generateDeadCodeAnalysis().');
+ }
+ if (requestParameters['file'] == null) {
+ throw new runtime.RequiredError('file', 'Required parameter "file" was null or undefined when calling generateDeadCodeAnalysis().');
+ }
+ const queryParameters = {};
+ const headerParameters = {};
+ if (requestParameters['idempotencyKey'] != null) {
+ headerParameters['Idempotency-Key'] = String(requestParameters['idempotencyKey']);
+ }
+ if (this.configuration && this.configuration.apiKey) {
+ headerParameters["X-Api-Key"] = yield this.configuration.apiKey("X-Api-Key"); // ApiKeyAuth authentication
+ }
+ const consumes = [
+ { contentType: 'multipart/form-data' },
+ ];
+ // @ts-ignore: canConsumeForm may be unused
+ const canConsumeForm = runtime.canConsumeForm(consumes);
+ let formParams;
+ let useForm = false;
+ // use FormData to transmit files using content-type "multipart/form-data"
+ useForm = canConsumeForm;
+ if (useForm) {
+ formParams = new FormData();
+ }
+ else {
+ formParams = new URLSearchParams();
+ }
+ if (requestParameters['file'] != null) {
+ formParams.append('file', requestParameters['file']);
+ }
+ const response = yield this.request({
+ path: `/v1/analysis/dead-code`,
+ method: 'POST',
+ headers: headerParameters,
+ query: queryParameters,
+ body: formParams,
+ }, initOverrides);
+ return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.DeadCodeAnalysisResponseAsyncFromJSON)(jsonValue));
+ });
+ }
+ /**
+ * Upload a zipped repository snapshot to identify dead (unreachable) code candidates by combining parse graph declarations with call graph relationships.
+ * Dead code analysis
+ */
+ generateDeadCodeAnalysis(requestParameters, initOverrides) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const response = yield this.generateDeadCodeAnalysisRaw(requestParameters, initOverrides);
+ return yield response.value();
+ });
+ }
/**
* Upload a zipped repository snapshot to generate the dependency graph.
* Dependency graph
@@ -7288,7 +7346,7 @@ class DefaultApi extends runtime.BaseAPI {
query: queryParameters,
body: formParams,
}, initOverrides);
- return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.CodeGraphEnvelopeFromJSON)(jsonValue));
+ return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.CodeGraphEnvelopeAsyncFromJSON)(jsonValue));
});
}
/**
@@ -7346,7 +7404,7 @@ class DefaultApi extends runtime.BaseAPI {
query: queryParameters,
body: formParams,
}, initOverrides);
- return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.DomainClassificationResponseFromJSON)(jsonValue));
+ return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.DomainClassificationResponseAsyncFromJSON)(jsonValue));
});
}
/**
@@ -7404,7 +7462,7 @@ class DefaultApi extends runtime.BaseAPI {
query: queryParameters,
body: formParams,
}, initOverrides);
- return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.CodeGraphEnvelopeFromJSON)(jsonValue));
+ return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.CodeGraphEnvelopeAsyncFromJSON)(jsonValue));
});
}
/**
@@ -7462,7 +7520,7 @@ class DefaultApi extends runtime.BaseAPI {
query: queryParameters,
body: formParams,
}, initOverrides);
- return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.SupermodelIRFromJSON)(jsonValue));
+ return new runtime.JSONApiResponse(response, (jsonValue) => (0, index_1.SupermodelIRAsyncFromJSON)(jsonValue));
});
}
/**
@@ -7506,6 +7564,249 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__nccwpck_require__(1464), exports);
+/***/ }),
+
+/***/ 1277:
+/***/ (function(__unused_webpack_module, exports) {
+
+"use strict";
+
+/**
+ * Async Client Wrapper for Supermodel API
+ *
+ * Provides automatic polling for async job endpoints, so you can use them
+ * like synchronous APIs without manually implementing polling loops.
+ *
+ * @example
+ * ```typescript
+ * import { DefaultApi, Configuration, SupermodelClient } from '@supermodeltools/sdk';
+ *
+ * const api = new DefaultApi(new Configuration({
+ * basePath: 'https://api.supermodel.tools',
+ * apiKey: () => 'your-api-key'
+ * }));
+ *
+ * const client = new SupermodelClient(api);
+ *
+ * // Returns the unwrapped result - polling is automatic!
+ * const graph = await client.generateDependencyGraph(zipFile);
+ * console.log(graph.graph.nodes.length);
+ * ```
+ */
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.SupermodelClient = exports.PollingTimeoutError = exports.JobFailedError = void 0;
+/**
+ * Error thrown when a job fails.
+ */
+class JobFailedError extends Error {
+ constructor(jobId, errorMessage) {
+ super(`Job ${jobId} failed: ${errorMessage}`);
+ this.jobId = jobId;
+ this.errorMessage = errorMessage;
+ this.name = 'JobFailedError';
+ }
+}
+exports.JobFailedError = JobFailedError;
+/**
+ * Error thrown when polling times out.
+ */
+class PollingTimeoutError extends Error {
+ constructor(jobId, timeoutMs, attempts) {
+ super(`Polling timed out for job ${jobId} after ${timeoutMs}ms (${attempts} attempts)`);
+ this.jobId = jobId;
+ this.timeoutMs = timeoutMs;
+ this.attempts = attempts;
+ this.name = 'PollingTimeoutError';
+ }
+}
+exports.PollingTimeoutError = PollingTimeoutError;
+/**
+ * Default idempotency key generator.
+ */
+function defaultGenerateIdempotencyKey() {
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
+ return crypto.randomUUID();
+ }
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
+ const r = (Math.random() * 16) | 0;
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
+ return v.toString(16);
+ });
+}
+function sleep(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+function sleepWithAbort(ms, signal) {
+ return new Promise((resolve, reject) => {
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
+ const error = new Error('Polling aborted');
+ error.name = 'AbortError';
+ reject(error);
+ return;
+ }
+ const timeout = setTimeout(() => {
+ if (signal && onAbort) {
+ signal.removeEventListener('abort', onAbort);
+ }
+ resolve();
+ }, ms);
+ let onAbort;
+ if (signal) {
+ onAbort = () => {
+ clearTimeout(timeout);
+ signal.removeEventListener('abort', onAbort);
+ const error = new Error('Polling aborted');
+ error.name = 'AbortError';
+ reject(error);
+ };
+ signal.addEventListener('abort', onAbort);
+ }
+ });
+}
+/**
+ * Poll an async endpoint until completion.
+ */
+function pollUntilComplete(apiCall, options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const { timeoutMs = 900000, defaultRetryIntervalMs = 10000, maxPollingAttempts = 90, onPollingProgress, signal, } = options;
+ const startTime = Date.now();
+ let attempt = 0;
+ let jobId = '';
+ while (attempt < maxPollingAttempts) {
+ // Check for abort before each attempt
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
+ const error = new Error('Polling aborted');
+ error.name = 'AbortError';
+ throw error;
+ }
+ attempt++;
+ const elapsedMs = Date.now() - startTime;
+ if (elapsedMs >= timeoutMs) {
+ throw new PollingTimeoutError(jobId || 'unknown', timeoutMs, attempt);
+ }
+ const response = yield apiCall();
+ jobId = response.jobId;
+ const status = response.status;
+ if (onPollingProgress) {
+ const nextRetryMs = status === 'completed' || status === 'failed'
+ ? undefined
+ : (response.retryAfter || defaultRetryIntervalMs / 1000) * 1000;
+ onPollingProgress({
+ jobId,
+ status,
+ attempt,
+ maxAttempts: maxPollingAttempts,
+ elapsedMs,
+ nextRetryMs,
+ });
+ }
+ if (status === 'completed') {
+ if (response.result !== undefined) {
+ return response.result;
+ }
+ throw new Error(`Job ${jobId} completed but result is undefined`);
+ }
+ if (status === 'failed') {
+ throw new JobFailedError(jobId, response.error || 'Unknown error');
+ }
+ const retryAfterMs = (response.retryAfter || defaultRetryIntervalMs / 1000) * 1000;
+ // Use abortable sleep
+ yield sleepWithAbort(retryAfterMs, signal);
+ }
+ throw new PollingTimeoutError(jobId || 'unknown', timeoutMs, attempt);
+ });
+}
+/**
+ * Async client wrapper that handles polling automatically.
+ *
+ * Wraps the generated DefaultApi and provides simplified methods
+ * for graph generation that handle async job polling internally.
+ */
+class SupermodelClient {
+ constructor(api, options = {}) {
+ this.api = api;
+ this.options = options;
+ this.generateIdempotencyKey = options.generateIdempotencyKey || defaultGenerateIdempotencyKey;
+ }
+ /**
+ * Generate a dependency graph from a zip file.
+ * Automatically handles polling until the job completes.
+ *
+ * @param file - Zip file containing the repository
+ * @param options - Optional request options
+ * @returns The dependency graph result
+ */
+ generateDependencyGraph(file, options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const key = (options === null || options === void 0 ? void 0 : options.idempotencyKey) || this.generateIdempotencyKey();
+ const pollOptions = (options === null || options === void 0 ? void 0 : options.signal) ? Object.assign(Object.assign({}, this.options), { signal: options.signal }) : this.options;
+ return pollUntilComplete(() => this.api.generateDependencyGraph({ idempotencyKey: key, file }, options === null || options === void 0 ? void 0 : options.initOverrides), pollOptions);
+ });
+ }
+ /**
+ * Generate a call graph from a zip file.
+ * Automatically handles polling until the job completes.
+ */
+ generateCallGraph(file, options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const key = (options === null || options === void 0 ? void 0 : options.idempotencyKey) || this.generateIdempotencyKey();
+ const pollOptions = (options === null || options === void 0 ? void 0 : options.signal) ? Object.assign(Object.assign({}, this.options), { signal: options.signal }) : this.options;
+ return pollUntilComplete(() => this.api.generateCallGraph({ idempotencyKey: key, file }, options === null || options === void 0 ? void 0 : options.initOverrides), pollOptions);
+ });
+ }
+ /**
+ * Generate a domain graph from a zip file.
+ * Automatically handles polling until the job completes.
+ */
+ generateDomainGraph(file, options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const key = (options === null || options === void 0 ? void 0 : options.idempotencyKey) || this.generateIdempotencyKey();
+ const pollOptions = (options === null || options === void 0 ? void 0 : options.signal) ? Object.assign(Object.assign({}, this.options), { signal: options.signal }) : this.options;
+ return pollUntilComplete(() => this.api.generateDomainGraph({ idempotencyKey: key, file }, options === null || options === void 0 ? void 0 : options.initOverrides), pollOptions);
+ });
+ }
+ /**
+ * Generate a parse graph from a zip file.
+ * Automatically handles polling until the job completes.
+ */
+ generateParseGraph(file, options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const key = (options === null || options === void 0 ? void 0 : options.idempotencyKey) || this.generateIdempotencyKey();
+ const pollOptions = (options === null || options === void 0 ? void 0 : options.signal) ? Object.assign(Object.assign({}, this.options), { signal: options.signal }) : this.options;
+ return pollUntilComplete(() => this.api.generateParseGraph({ idempotencyKey: key, file }, options === null || options === void 0 ? void 0 : options.initOverrides), pollOptions);
+ });
+ }
+ /**
+ * Generate a Supermodel IR from a zip file.
+ * Automatically handles polling until the job completes.
+ */
+ generateSupermodelGraph(file, options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const key = (options === null || options === void 0 ? void 0 : options.idempotencyKey) || this.generateIdempotencyKey();
+ const pollOptions = (options === null || options === void 0 ? void 0 : options.signal) ? Object.assign(Object.assign({}, this.options), { signal: options.signal }) : this.options;
+ return pollUntilComplete(() => this.api.generateSupermodelGraph({ idempotencyKey: key, file }, options === null || options === void 0 ? void 0 : options.initOverrides), pollOptions);
+ });
+ }
+ /**
+ * Access the underlying raw API for methods that don't need polling
+ * or when you want direct control over the async envelope responses.
+ */
+ get rawApi() {
+ return this.api;
+ }
+}
+exports.SupermodelClient = SupermodelClient;
+
+
/***/ }),
/***/ 6381:
@@ -7533,6 +7834,90 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__nccwpck_require__(6361), exports);
__exportStar(__nccwpck_require__(8415), exports);
__exportStar(__nccwpck_require__(3056), exports);
+__exportStar(__nccwpck_require__(1277), exports);
+
+
+/***/ }),
+
+/***/ 5331:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.AliveCodeItemTypeEnum = void 0;
+exports.instanceOfAliveCodeItem = instanceOfAliveCodeItem;
+exports.AliveCodeItemFromJSON = AliveCodeItemFromJSON;
+exports.AliveCodeItemFromJSONTyped = AliveCodeItemFromJSONTyped;
+exports.AliveCodeItemToJSON = AliveCodeItemToJSON;
+/**
+ * @export
+ */
+exports.AliveCodeItemTypeEnum = {
+ Function: 'function',
+ Class: 'class',
+ Method: 'method',
+ Interface: 'interface',
+ Type: 'type',
+ Variable: 'variable',
+ Constant: 'constant'
+};
+/**
+ * Check if a given object implements the AliveCodeItem interface.
+ */
+function instanceOfAliveCodeItem(value) {
+ if (!('file' in value) || value['file'] === undefined)
+ return false;
+ if (!('name' in value) || value['name'] === undefined)
+ return false;
+ if (!('line' in value) || value['line'] === undefined)
+ return false;
+ if (!('type' in value) || value['type'] === undefined)
+ return false;
+ if (!('callerCount' in value) || value['callerCount'] === undefined)
+ return false;
+ return true;
+}
+function AliveCodeItemFromJSON(json) {
+ return AliveCodeItemFromJSONTyped(json, false);
+}
+function AliveCodeItemFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'file': json['file'],
+ 'name': json['name'],
+ 'line': json['line'],
+ 'type': json['type'],
+ 'callerCount': json['callerCount'],
+ };
+}
+function AliveCodeItemToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'file': value['file'],
+ 'name': value['name'],
+ 'line': value['line'],
+ 'type': value['type'],
+ 'callerCount': value['callerCount'],
+ };
+}
/***/ }),
@@ -7548,7 +7933,7 @@ __exportStar(__nccwpck_require__(3056), exports);
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -7564,10 +7949,24 @@ exports.ClassificationStatsToJSON = ClassificationStatsToJSON;
* Check if a given object implements the ClassificationStats interface.
*/
function instanceOfClassificationStats(value) {
- if (!('domainCount' in value) || value['domainCount'] === undefined)
+ if (!('nodeCount' in value) || value['nodeCount'] === undefined)
return false;
if (!('relationshipCount' in value) || value['relationshipCount'] === undefined)
return false;
+ if (!('nodeTypes' in value) || value['nodeTypes'] === undefined)
+ return false;
+ if (!('relationshipTypes' in value) || value['relationshipTypes'] === undefined)
+ return false;
+ if (!('domainCount' in value) || value['domainCount'] === undefined)
+ return false;
+ if (!('subdomainCount' in value) || value['subdomainCount'] === undefined)
+ return false;
+ if (!('assignedFileCount' in value) || value['assignedFileCount'] === undefined)
+ return false;
+ if (!('assignedFunctionCount' in value) || value['assignedFunctionCount'] === undefined)
+ return false;
+ if (!('assignedClassCount' in value) || value['assignedClassCount'] === undefined)
+ return false;
if (!('fileAssignments' in value) || value['fileAssignments'] === undefined)
return false;
if (!('functionAssignments' in value) || value['functionAssignments'] === undefined)
@@ -7586,8 +7985,15 @@ function ClassificationStatsFromJSONTyped(json, ignoreDiscriminator) {
return json;
}
return {
- 'domainCount': json['domainCount'],
+ 'nodeCount': json['nodeCount'],
'relationshipCount': json['relationshipCount'],
+ 'nodeTypes': json['nodeTypes'],
+ 'relationshipTypes': json['relationshipTypes'],
+ 'domainCount': json['domainCount'],
+ 'subdomainCount': json['subdomainCount'],
+ 'assignedFileCount': json['assignedFileCount'],
+ 'assignedFunctionCount': json['assignedFunctionCount'],
+ 'assignedClassCount': json['assignedClassCount'],
'fileAssignments': json['fileAssignments'],
'functionAssignments': json['functionAssignments'],
'unassignedFunctions': json['unassignedFunctions'],
@@ -7599,8 +8005,15 @@ function ClassificationStatsToJSON(value) {
return value;
}
return {
- 'domainCount': value['domainCount'],
+ 'nodeCount': value['nodeCount'],
'relationshipCount': value['relationshipCount'],
+ 'nodeTypes': value['nodeTypes'],
+ 'relationshipTypes': value['relationshipTypes'],
+ 'domainCount': value['domainCount'],
+ 'subdomainCount': value['subdomainCount'],
+ 'assignedFileCount': value['assignedFileCount'],
+ 'assignedFunctionCount': value['assignedFunctionCount'],
+ 'assignedClassCount': value['assignedClassCount'],
'fileAssignments': value['fileAssignments'],
'functionAssignments': value['functionAssignments'],
'unassignedFunctions': value['unassignedFunctions'],
@@ -7622,7 +8035,7 @@ function ClassificationStatsToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -7636,6 +8049,7 @@ exports.CodeGraphEnvelopeFromJSONTyped = CodeGraphEnvelopeFromJSONTyped;
exports.CodeGraphEnvelopeToJSON = CodeGraphEnvelopeToJSON;
const CodeGraphStats_1 = __nccwpck_require__(2208);
const CodeGraphEnvelopeGraph_1 = __nccwpck_require__(727);
+const CodeGraphEnvelopeMetadata_1 = __nccwpck_require__(7122);
/**
* Check if a given object implements the CodeGraphEnvelope interface.
*/
@@ -7655,6 +8069,7 @@ function CodeGraphEnvelopeFromJSONTyped(json, ignoreDiscriminator) {
'generatedAt': json['generatedAt'] == null ? undefined : (new Date(json['generatedAt'])),
'message': json['message'] == null ? undefined : json['message'],
'stats': json['stats'] == null ? undefined : (0, CodeGraphStats_1.CodeGraphStatsFromJSON)(json['stats']),
+ 'metadata': json['metadata'] == null ? undefined : (0, CodeGraphEnvelopeMetadata_1.CodeGraphEnvelopeMetadataFromJSON)(json['metadata']),
'graph': (0, CodeGraphEnvelopeGraph_1.CodeGraphEnvelopeGraphFromJSON)(json['graph']),
};
}
@@ -7666,11 +8081,87 @@ function CodeGraphEnvelopeToJSON(value) {
'generatedAt': value['generatedAt'] == null ? undefined : ((value['generatedAt']).toISOString()),
'message': value['message'],
'stats': (0, CodeGraphStats_1.CodeGraphStatsToJSON)(value['stats']),
+ 'metadata': (0, CodeGraphEnvelopeMetadata_1.CodeGraphEnvelopeMetadataToJSON)(value['metadata']),
'graph': (0, CodeGraphEnvelopeGraph_1.CodeGraphEnvelopeGraphToJSON)(value['graph']),
};
}
+/***/ }),
+
+/***/ 8711:
+/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.CodeGraphEnvelopeAsyncStatusEnum = void 0;
+exports.instanceOfCodeGraphEnvelopeAsync = instanceOfCodeGraphEnvelopeAsync;
+exports.CodeGraphEnvelopeAsyncFromJSON = CodeGraphEnvelopeAsyncFromJSON;
+exports.CodeGraphEnvelopeAsyncFromJSONTyped = CodeGraphEnvelopeAsyncFromJSONTyped;
+exports.CodeGraphEnvelopeAsyncToJSON = CodeGraphEnvelopeAsyncToJSON;
+const CodeGraphEnvelope_1 = __nccwpck_require__(9995);
+/**
+ * @export
+ */
+exports.CodeGraphEnvelopeAsyncStatusEnum = {
+ Pending: 'pending',
+ Processing: 'processing',
+ Completed: 'completed',
+ Failed: 'failed'
+};
+/**
+ * Check if a given object implements the CodeGraphEnvelopeAsync interface.
+ */
+function instanceOfCodeGraphEnvelopeAsync(value) {
+ if (!('status' in value) || value['status'] === undefined)
+ return false;
+ if (!('jobId' in value) || value['jobId'] === undefined)
+ return false;
+ return true;
+}
+function CodeGraphEnvelopeAsyncFromJSON(json) {
+ return CodeGraphEnvelopeAsyncFromJSONTyped(json, false);
+}
+function CodeGraphEnvelopeAsyncFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'status': json['status'],
+ 'jobId': json['jobId'],
+ 'retryAfter': json['retryAfter'] == null ? undefined : json['retryAfter'],
+ 'error': json['error'] == null ? undefined : json['error'],
+ 'result': json['result'] == null ? undefined : (0, CodeGraphEnvelope_1.CodeGraphEnvelopeFromJSON)(json['result']),
+ };
+}
+function CodeGraphEnvelopeAsyncToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'status': value['status'],
+ 'jobId': value['jobId'],
+ 'retryAfter': value['retryAfter'],
+ 'error': value['error'],
+ 'result': (0, CodeGraphEnvelope_1.CodeGraphEnvelopeToJSON)(value['result']),
+ };
+}
+
+
/***/ }),
/***/ 727:
@@ -7684,7 +8175,7 @@ function CodeGraphEnvelopeToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -7733,7 +8224,7 @@ function CodeGraphEnvelopeGraphToJSON(value) {
/***/ }),
-/***/ 1811:
+/***/ 7122:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
@@ -7744,7 +8235,7 @@ function CodeGraphEnvelopeGraphToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -7752,9 +8243,67 @@ function CodeGraphEnvelopeGraphToJSON(value) {
* Do not edit the class manually.
*/
Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.instanceOfCodeGraphNode = instanceOfCodeGraphNode;
-exports.CodeGraphNodeFromJSON = CodeGraphNodeFromJSON;
-exports.CodeGraphNodeFromJSONTyped = CodeGraphNodeFromJSONTyped;
+exports.instanceOfCodeGraphEnvelopeMetadata = instanceOfCodeGraphEnvelopeMetadata;
+exports.CodeGraphEnvelopeMetadataFromJSON = CodeGraphEnvelopeMetadataFromJSON;
+exports.CodeGraphEnvelopeMetadataFromJSONTyped = CodeGraphEnvelopeMetadataFromJSONTyped;
+exports.CodeGraphEnvelopeMetadataToJSON = CodeGraphEnvelopeMetadataToJSON;
+/**
+ * Check if a given object implements the CodeGraphEnvelopeMetadata interface.
+ */
+function instanceOfCodeGraphEnvelopeMetadata(value) {
+ return true;
+}
+function CodeGraphEnvelopeMetadataFromJSON(json) {
+ return CodeGraphEnvelopeMetadataFromJSONTyped(json, false);
+}
+function CodeGraphEnvelopeMetadataFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'analysisStartTime': json['analysisStartTime'] == null ? undefined : (new Date(json['analysisStartTime'])),
+ 'analysisEndTime': json['analysisEndTime'] == null ? undefined : (new Date(json['analysisEndTime'])),
+ 'fileCount': json['fileCount'] == null ? undefined : json['fileCount'],
+ 'languages': json['languages'] == null ? undefined : json['languages'],
+ };
+}
+function CodeGraphEnvelopeMetadataToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'analysisStartTime': value['analysisStartTime'] == null ? undefined : ((value['analysisStartTime']).toISOString()),
+ 'analysisEndTime': value['analysisEndTime'] == null ? undefined : ((value['analysisEndTime']).toISOString()),
+ 'fileCount': value['fileCount'],
+ 'languages': value['languages'],
+ };
+}
+
+
+/***/ }),
+
+/***/ 1811:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.instanceOfCodeGraphNode = instanceOfCodeGraphNode;
+exports.CodeGraphNodeFromJSON = CodeGraphNodeFromJSON;
+exports.CodeGraphNodeFromJSONTyped = CodeGraphNodeFromJSONTyped;
exports.CodeGraphNodeToJSON = CodeGraphNodeToJSON;
/**
* Check if a given object implements the CodeGraphNode interface.
@@ -7802,7 +8351,7 @@ function CodeGraphNodeToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -7870,7 +8419,7 @@ function CodeGraphRelationshipToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -7896,6 +8445,10 @@ function CodeGraphStatsFromJSONTyped(json, ignoreDiscriminator) {
return json;
}
return {
+ 'nodeCount': json['nodeCount'] == null ? undefined : json['nodeCount'],
+ 'relationshipCount': json['relationshipCount'] == null ? undefined : json['relationshipCount'],
+ 'nodeTypes': json['nodeTypes'] == null ? undefined : json['nodeTypes'],
+ 'relationshipTypes': json['relationshipTypes'] == null ? undefined : json['relationshipTypes'],
'filesProcessed': json['filesProcessed'] == null ? undefined : json['filesProcessed'],
'classes': json['classes'] == null ? undefined : json['classes'],
'functions': json['functions'] == null ? undefined : json['functions'],
@@ -7908,6 +8461,10 @@ function CodeGraphStatsToJSON(value) {
return value;
}
return {
+ 'nodeCount': value['nodeCount'],
+ 'relationshipCount': value['relationshipCount'],
+ 'nodeTypes': value['nodeTypes'],
+ 'relationshipTypes': value['relationshipTypes'],
'filesProcessed': value['filesProcessed'],
'classes': value['classes'],
'functions': value['functions'],
@@ -7917,6 +8474,322 @@ function CodeGraphStatsToJSON(value) {
}
+/***/ }),
+
+/***/ 8386:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.instanceOfDeadCodeAnalysisMetadata = instanceOfDeadCodeAnalysisMetadata;
+exports.DeadCodeAnalysisMetadataFromJSON = DeadCodeAnalysisMetadataFromJSON;
+exports.DeadCodeAnalysisMetadataFromJSONTyped = DeadCodeAnalysisMetadataFromJSONTyped;
+exports.DeadCodeAnalysisMetadataToJSON = DeadCodeAnalysisMetadataToJSON;
+/**
+ * Check if a given object implements the DeadCodeAnalysisMetadata interface.
+ */
+function instanceOfDeadCodeAnalysisMetadata(value) {
+ if (!('totalDeclarations' in value) || value['totalDeclarations'] === undefined)
+ return false;
+ if (!('deadCodeCandidates' in value) || value['deadCodeCandidates'] === undefined)
+ return false;
+ if (!('aliveCode' in value) || value['aliveCode'] === undefined)
+ return false;
+ if (!('analysisMethod' in value) || value['analysisMethod'] === undefined)
+ return false;
+ return true;
+}
+function DeadCodeAnalysisMetadataFromJSON(json) {
+ return DeadCodeAnalysisMetadataFromJSONTyped(json, false);
+}
+function DeadCodeAnalysisMetadataFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'totalDeclarations': json['totalDeclarations'],
+ 'deadCodeCandidates': json['deadCodeCandidates'],
+ 'aliveCode': json['aliveCode'],
+ 'rootFilesCount': json['rootFilesCount'] == null ? undefined : json['rootFilesCount'],
+ 'transitiveDeadCount': json['transitiveDeadCount'] == null ? undefined : json['transitiveDeadCount'],
+ 'symbolLevelDeadCount': json['symbolLevelDeadCount'] == null ? undefined : json['symbolLevelDeadCount'],
+ 'analysisMethod': json['analysisMethod'],
+ 'analysisStartTime': json['analysisStartTime'] == null ? undefined : (new Date(json['analysisStartTime'])),
+ 'analysisEndTime': json['analysisEndTime'] == null ? undefined : (new Date(json['analysisEndTime'])),
+ };
+}
+function DeadCodeAnalysisMetadataToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'totalDeclarations': value['totalDeclarations'],
+ 'deadCodeCandidates': value['deadCodeCandidates'],
+ 'aliveCode': value['aliveCode'],
+ 'rootFilesCount': value['rootFilesCount'],
+ 'transitiveDeadCount': value['transitiveDeadCount'],
+ 'symbolLevelDeadCount': value['symbolLevelDeadCount'],
+ 'analysisMethod': value['analysisMethod'],
+ 'analysisStartTime': value['analysisStartTime'] == null ? undefined : ((value['analysisStartTime']).toISOString()),
+ 'analysisEndTime': value['analysisEndTime'] == null ? undefined : ((value['analysisEndTime']).toISOString()),
+ };
+}
+
+
+/***/ }),
+
+/***/ 536:
+/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.instanceOfDeadCodeAnalysisResponse = instanceOfDeadCodeAnalysisResponse;
+exports.DeadCodeAnalysisResponseFromJSON = DeadCodeAnalysisResponseFromJSON;
+exports.DeadCodeAnalysisResponseFromJSONTyped = DeadCodeAnalysisResponseFromJSONTyped;
+exports.DeadCodeAnalysisResponseToJSON = DeadCodeAnalysisResponseToJSON;
+const DeadCodeAnalysisMetadata_1 = __nccwpck_require__(8386);
+const DeadCodeCandidate_1 = __nccwpck_require__(4196);
+const EntryPoint_1 = __nccwpck_require__(8512);
+const AliveCodeItem_1 = __nccwpck_require__(5331);
+/**
+ * Check if a given object implements the DeadCodeAnalysisResponse interface.
+ */
+function instanceOfDeadCodeAnalysisResponse(value) {
+ if (!('metadata' in value) || value['metadata'] === undefined)
+ return false;
+ if (!('deadCodeCandidates' in value) || value['deadCodeCandidates'] === undefined)
+ return false;
+ if (!('aliveCode' in value) || value['aliveCode'] === undefined)
+ return false;
+ if (!('entryPoints' in value) || value['entryPoints'] === undefined)
+ return false;
+ return true;
+}
+function DeadCodeAnalysisResponseFromJSON(json) {
+ return DeadCodeAnalysisResponseFromJSONTyped(json, false);
+}
+function DeadCodeAnalysisResponseFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'metadata': (0, DeadCodeAnalysisMetadata_1.DeadCodeAnalysisMetadataFromJSON)(json['metadata']),
+ 'deadCodeCandidates': (json['deadCodeCandidates'].map(DeadCodeCandidate_1.DeadCodeCandidateFromJSON)),
+ 'aliveCode': (json['aliveCode'].map(AliveCodeItem_1.AliveCodeItemFromJSON)),
+ 'entryPoints': (json['entryPoints'].map(EntryPoint_1.EntryPointFromJSON)),
+ };
+}
+function DeadCodeAnalysisResponseToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'metadata': (0, DeadCodeAnalysisMetadata_1.DeadCodeAnalysisMetadataToJSON)(value['metadata']),
+ 'deadCodeCandidates': (value['deadCodeCandidates'].map(DeadCodeCandidate_1.DeadCodeCandidateToJSON)),
+ 'aliveCode': (value['aliveCode'].map(AliveCodeItem_1.AliveCodeItemToJSON)),
+ 'entryPoints': (value['entryPoints'].map(EntryPoint_1.EntryPointToJSON)),
+ };
+}
+
+
+/***/ }),
+
+/***/ 3750:
+/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.DeadCodeAnalysisResponseAsyncStatusEnum = void 0;
+exports.instanceOfDeadCodeAnalysisResponseAsync = instanceOfDeadCodeAnalysisResponseAsync;
+exports.DeadCodeAnalysisResponseAsyncFromJSON = DeadCodeAnalysisResponseAsyncFromJSON;
+exports.DeadCodeAnalysisResponseAsyncFromJSONTyped = DeadCodeAnalysisResponseAsyncFromJSONTyped;
+exports.DeadCodeAnalysisResponseAsyncToJSON = DeadCodeAnalysisResponseAsyncToJSON;
+const DeadCodeAnalysisResponse_1 = __nccwpck_require__(536);
+/**
+ * @export
+ */
+exports.DeadCodeAnalysisResponseAsyncStatusEnum = {
+ Pending: 'pending',
+ Processing: 'processing',
+ Completed: 'completed',
+ Failed: 'failed'
+};
+/**
+ * Check if a given object implements the DeadCodeAnalysisResponseAsync interface.
+ */
+function instanceOfDeadCodeAnalysisResponseAsync(value) {
+ if (!('status' in value) || value['status'] === undefined)
+ return false;
+ if (!('jobId' in value) || value['jobId'] === undefined)
+ return false;
+ return true;
+}
+function DeadCodeAnalysisResponseAsyncFromJSON(json) {
+ return DeadCodeAnalysisResponseAsyncFromJSONTyped(json, false);
+}
+function DeadCodeAnalysisResponseAsyncFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'status': json['status'],
+ 'jobId': json['jobId'],
+ 'retryAfter': json['retryAfter'] == null ? undefined : json['retryAfter'],
+ 'error': json['error'] == null ? undefined : json['error'],
+ 'result': json['result'] == null ? undefined : (0, DeadCodeAnalysisResponse_1.DeadCodeAnalysisResponseFromJSON)(json['result']),
+ };
+}
+function DeadCodeAnalysisResponseAsyncToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'status': value['status'],
+ 'jobId': value['jobId'],
+ 'retryAfter': value['retryAfter'],
+ 'error': value['error'],
+ 'result': (0, DeadCodeAnalysisResponse_1.DeadCodeAnalysisResponseToJSON)(value['result']),
+ };
+}
+
+
+/***/ }),
+
+/***/ 4196:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.DeadCodeCandidateConfidenceEnum = exports.DeadCodeCandidateTypeEnum = void 0;
+exports.instanceOfDeadCodeCandidate = instanceOfDeadCodeCandidate;
+exports.DeadCodeCandidateFromJSON = DeadCodeCandidateFromJSON;
+exports.DeadCodeCandidateFromJSONTyped = DeadCodeCandidateFromJSONTyped;
+exports.DeadCodeCandidateToJSON = DeadCodeCandidateToJSON;
+/**
+ * @export
+ */
+exports.DeadCodeCandidateTypeEnum = {
+ Function: 'function',
+ Class: 'class',
+ Method: 'method',
+ Interface: 'interface',
+ Type: 'type',
+ Variable: 'variable',
+ Constant: 'constant'
+};
+/**
+ * @export
+ */
+exports.DeadCodeCandidateConfidenceEnum = {
+ High: 'high',
+ Medium: 'medium',
+ Low: 'low'
+};
+/**
+ * Check if a given object implements the DeadCodeCandidate interface.
+ */
+function instanceOfDeadCodeCandidate(value) {
+ if (!('file' in value) || value['file'] === undefined)
+ return false;
+ if (!('name' in value) || value['name'] === undefined)
+ return false;
+ if (!('line' in value) || value['line'] === undefined)
+ return false;
+ if (!('type' in value) || value['type'] === undefined)
+ return false;
+ if (!('confidence' in value) || value['confidence'] === undefined)
+ return false;
+ if (!('reason' in value) || value['reason'] === undefined)
+ return false;
+ return true;
+}
+function DeadCodeCandidateFromJSON(json) {
+ return DeadCodeCandidateFromJSONTyped(json, false);
+}
+function DeadCodeCandidateFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'file': json['file'],
+ 'name': json['name'],
+ 'line': json['line'],
+ 'type': json['type'],
+ 'confidence': json['confidence'],
+ 'reason': json['reason'],
+ };
+}
+function DeadCodeCandidateToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'file': value['file'],
+ 'name': value['name'],
+ 'line': value['line'],
+ 'type': value['type'],
+ 'confidence': value['confidence'],
+ 'reason': value['reason'],
+ };
+}
+
+
/***/ }),
/***/ 4181:
@@ -7930,7 +8803,7 @@ function CodeGraphStatsToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -7988,7 +8861,7 @@ function DomainClassAssignmentToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8008,12 +8881,18 @@ const FunctionDescription_1 = __nccwpck_require__(8942);
const DomainRelationship_1 = __nccwpck_require__(1988);
const DomainSummary_1 = __nccwpck_require__(7228);
const ClassificationStats_1 = __nccwpck_require__(6367);
+const DomainClassificationResponseGraph_1 = __nccwpck_require__(9625);
+const CodeGraphEnvelopeMetadata_1 = __nccwpck_require__(7122);
/**
* Check if a given object implements the DomainClassificationResponse interface.
*/
function instanceOfDomainClassificationResponse(value) {
if (!('runId' in value) || value['runId'] === undefined)
return false;
+ if (!('graph' in value) || value['graph'] === undefined)
+ return false;
+ if (!('metadata' in value) || value['metadata'] === undefined)
+ return false;
if (!('domains' in value) || value['domains'] === undefined)
return false;
if (!('relationships' in value) || value['relationships'] === undefined)
@@ -8039,6 +8918,8 @@ function DomainClassificationResponseFromJSONTyped(json, ignoreDiscriminator) {
}
return {
'runId': json['runId'],
+ 'graph': (0, DomainClassificationResponseGraph_1.DomainClassificationResponseGraphFromJSON)(json['graph']),
+ 'metadata': (0, CodeGraphEnvelopeMetadata_1.CodeGraphEnvelopeMetadataFromJSON)(json['metadata']),
'domains': (json['domains'].map(DomainSummary_1.DomainSummaryFromJSON)),
'relationships': (json['relationships'].map(DomainRelationship_1.DomainRelationshipFromJSON)),
'fileAssignments': (json['fileAssignments'].map(DomainFileAssignment_1.DomainFileAssignmentFromJSON)),
@@ -8055,6 +8936,8 @@ function DomainClassificationResponseToJSON(value) {
}
return {
'runId': value['runId'],
+ 'graph': (0, DomainClassificationResponseGraph_1.DomainClassificationResponseGraphToJSON)(value['graph']),
+ 'metadata': (0, CodeGraphEnvelopeMetadata_1.CodeGraphEnvelopeMetadataToJSON)(value['metadata']),
'domains': (value['domains'].map(DomainSummary_1.DomainSummaryToJSON)),
'relationships': (value['relationships'].map(DomainRelationship_1.DomainRelationshipToJSON)),
'fileAssignments': (value['fileAssignments'].map(DomainFileAssignment_1.DomainFileAssignmentToJSON)),
@@ -8067,6 +8950,141 @@ function DomainClassificationResponseToJSON(value) {
}
+/***/ }),
+
+/***/ 5501:
+/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.DomainClassificationResponseAsyncStatusEnum = void 0;
+exports.instanceOfDomainClassificationResponseAsync = instanceOfDomainClassificationResponseAsync;
+exports.DomainClassificationResponseAsyncFromJSON = DomainClassificationResponseAsyncFromJSON;
+exports.DomainClassificationResponseAsyncFromJSONTyped = DomainClassificationResponseAsyncFromJSONTyped;
+exports.DomainClassificationResponseAsyncToJSON = DomainClassificationResponseAsyncToJSON;
+const DomainClassificationResponse_1 = __nccwpck_require__(9137);
+/**
+ * @export
+ */
+exports.DomainClassificationResponseAsyncStatusEnum = {
+ Pending: 'pending',
+ Processing: 'processing',
+ Completed: 'completed',
+ Failed: 'failed'
+};
+/**
+ * Check if a given object implements the DomainClassificationResponseAsync interface.
+ */
+function instanceOfDomainClassificationResponseAsync(value) {
+ if (!('status' in value) || value['status'] === undefined)
+ return false;
+ if (!('jobId' in value) || value['jobId'] === undefined)
+ return false;
+ return true;
+}
+function DomainClassificationResponseAsyncFromJSON(json) {
+ return DomainClassificationResponseAsyncFromJSONTyped(json, false);
+}
+function DomainClassificationResponseAsyncFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'status': json['status'],
+ 'jobId': json['jobId'],
+ 'retryAfter': json['retryAfter'] == null ? undefined : json['retryAfter'],
+ 'error': json['error'] == null ? undefined : json['error'],
+ 'result': json['result'] == null ? undefined : (0, DomainClassificationResponse_1.DomainClassificationResponseFromJSON)(json['result']),
+ };
+}
+function DomainClassificationResponseAsyncToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'status': value['status'],
+ 'jobId': value['jobId'],
+ 'retryAfter': value['retryAfter'],
+ 'error': value['error'],
+ 'result': (0, DomainClassificationResponse_1.DomainClassificationResponseToJSON)(value['result']),
+ };
+}
+
+
+/***/ }),
+
+/***/ 9625:
+/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.instanceOfDomainClassificationResponseGraph = instanceOfDomainClassificationResponseGraph;
+exports.DomainClassificationResponseGraphFromJSON = DomainClassificationResponseGraphFromJSON;
+exports.DomainClassificationResponseGraphFromJSONTyped = DomainClassificationResponseGraphFromJSONTyped;
+exports.DomainClassificationResponseGraphToJSON = DomainClassificationResponseGraphToJSON;
+const CodeGraphNode_1 = __nccwpck_require__(1811);
+const CodeGraphRelationship_1 = __nccwpck_require__(5473);
+/**
+ * Check if a given object implements the DomainClassificationResponseGraph interface.
+ */
+function instanceOfDomainClassificationResponseGraph(value) {
+ if (!('nodes' in value) || value['nodes'] === undefined)
+ return false;
+ if (!('relationships' in value) || value['relationships'] === undefined)
+ return false;
+ return true;
+}
+function DomainClassificationResponseGraphFromJSON(json) {
+ return DomainClassificationResponseGraphFromJSONTyped(json, false);
+}
+function DomainClassificationResponseGraphFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'nodes': (json['nodes'].map(CodeGraphNode_1.CodeGraphNodeFromJSON)),
+ 'relationships': (json['relationships'].map(CodeGraphRelationship_1.CodeGraphRelationshipFromJSON)),
+ };
+}
+function DomainClassificationResponseGraphToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'nodes': (value['nodes'].map(CodeGraphNode_1.CodeGraphNodeToJSON)),
+ 'relationships': (value['relationships'].map(CodeGraphRelationship_1.CodeGraphRelationshipToJSON)),
+ };
+}
+
+
/***/ }),
/***/ 385:
@@ -8080,7 +9098,7 @@ function DomainClassificationResponseToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8138,7 +9156,7 @@ function DomainFileAssignmentToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8198,7 +9216,7 @@ function DomainFunctionAssignmentToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8266,7 +9284,7 @@ function DomainRelationshipToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8324,6 +9342,89 @@ function DomainSummaryToJSON(value) {
}
+/***/ }),
+
+/***/ 8512:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.EntryPointTypeEnum = void 0;
+exports.instanceOfEntryPoint = instanceOfEntryPoint;
+exports.EntryPointFromJSON = EntryPointFromJSON;
+exports.EntryPointFromJSONTyped = EntryPointFromJSONTyped;
+exports.EntryPointToJSON = EntryPointToJSON;
+/**
+ * @export
+ */
+exports.EntryPointTypeEnum = {
+ Function: 'function',
+ Class: 'class',
+ Method: 'method',
+ Interface: 'interface',
+ Type: 'type',
+ Variable: 'variable',
+ Constant: 'constant'
+};
+/**
+ * Check if a given object implements the EntryPoint interface.
+ */
+function instanceOfEntryPoint(value) {
+ if (!('file' in value) || value['file'] === undefined)
+ return false;
+ if (!('name' in value) || value['name'] === undefined)
+ return false;
+ if (!('line' in value) || value['line'] === undefined)
+ return false;
+ if (!('type' in value) || value['type'] === undefined)
+ return false;
+ if (!('reason' in value) || value['reason'] === undefined)
+ return false;
+ return true;
+}
+function EntryPointFromJSON(json) {
+ return EntryPointFromJSONTyped(json, false);
+}
+function EntryPointFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'file': json['file'],
+ 'name': json['name'],
+ 'line': json['line'],
+ 'type': json['type'],
+ 'reason': json['reason'],
+ };
+}
+function EntryPointToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'file': value['file'],
+ 'name': value['name'],
+ 'line': value['line'],
+ 'type': value['type'],
+ 'reason': value['reason'],
+ };
+}
+
+
/***/ }),
/***/ 8560:
@@ -8337,7 +9438,7 @@ function DomainSummaryToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8397,7 +9498,7 @@ function ErrorDetailsInnerToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8444,6 +9545,78 @@ function FunctionDescriptionToJSON(value) {
}
+/***/ }),
+
+/***/ 2185:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.JobStatusStatusEnum = void 0;
+exports.instanceOfJobStatus = instanceOfJobStatus;
+exports.JobStatusFromJSON = JobStatusFromJSON;
+exports.JobStatusFromJSONTyped = JobStatusFromJSONTyped;
+exports.JobStatusToJSON = JobStatusToJSON;
+/**
+ * @export
+ */
+exports.JobStatusStatusEnum = {
+ Pending: 'pending',
+ Processing: 'processing',
+ Completed: 'completed',
+ Failed: 'failed'
+};
+/**
+ * Check if a given object implements the JobStatus interface.
+ */
+function instanceOfJobStatus(value) {
+ if (!('status' in value) || value['status'] === undefined)
+ return false;
+ if (!('jobId' in value) || value['jobId'] === undefined)
+ return false;
+ return true;
+}
+function JobStatusFromJSON(json) {
+ return JobStatusFromJSONTyped(json, false);
+}
+function JobStatusFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'status': json['status'],
+ 'jobId': json['jobId'],
+ 'retryAfter': json['retryAfter'] == null ? undefined : json['retryAfter'],
+ 'error': json['error'] == null ? undefined : json['error'],
+ };
+}
+function JobStatusToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'status': value['status'],
+ 'jobId': value['jobId'],
+ 'retryAfter': value['retryAfter'],
+ 'error': value['error'],
+ };
+}
+
+
/***/ }),
/***/ 4203:
@@ -8457,7 +9630,7 @@ function FunctionDescriptionToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8528,7 +9701,7 @@ function ModelErrorToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8548,6 +9721,12 @@ function instanceOfSubdomainSummary(value) {
return false;
if (!('descriptionSummary' in value) || value['descriptionSummary'] === undefined)
return false;
+ if (!('files' in value) || value['files'] === undefined)
+ return false;
+ if (!('functions' in value) || value['functions'] === undefined)
+ return false;
+ if (!('classes' in value) || value['classes'] === undefined)
+ return false;
return true;
}
function SubdomainSummaryFromJSON(json) {
@@ -8560,6 +9739,9 @@ function SubdomainSummaryFromJSONTyped(json, ignoreDiscriminator) {
return {
'name': json['name'],
'descriptionSummary': json['descriptionSummary'],
+ 'files': json['files'],
+ 'functions': json['functions'],
+ 'classes': json['classes'],
};
}
function SubdomainSummaryToJSON(value) {
@@ -8569,6 +9751,9 @@ function SubdomainSummaryToJSON(value) {
return {
'name': value['name'],
'descriptionSummary': value['descriptionSummary'],
+ 'files': value['files'],
+ 'functions': value['functions'],
+ 'classes': value['classes'],
};
}
@@ -8586,7 +9771,7 @@ function SubdomainSummaryToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8650,7 +9835,7 @@ function SupermodelArtifactToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8663,6 +9848,9 @@ exports.SupermodelIRFromJSON = SupermodelIRFromJSON;
exports.SupermodelIRFromJSONTyped = SupermodelIRFromJSONTyped;
exports.SupermodelIRToJSON = SupermodelIRToJSON;
const SupermodelIRGraph_1 = __nccwpck_require__(1881);
+const SupermodelIRStats_1 = __nccwpck_require__(7654);
+const DomainSummary_1 = __nccwpck_require__(7228);
+const CodeGraphEnvelopeMetadata_1 = __nccwpck_require__(7122);
const SupermodelArtifact_1 = __nccwpck_require__(4020);
/**
* Check if a given object implements the SupermodelIR interface.
@@ -8676,6 +9864,12 @@ function instanceOfSupermodelIR(value) {
return false;
if (!('generatedAt' in value) || value['generatedAt'] === undefined)
return false;
+ if (!('stats' in value) || value['stats'] === undefined)
+ return false;
+ if (!('metadata' in value) || value['metadata'] === undefined)
+ return false;
+ if (!('domains' in value) || value['domains'] === undefined)
+ return false;
if (!('graph' in value) || value['graph'] === undefined)
return false;
return true;
@@ -8693,6 +9887,9 @@ function SupermodelIRFromJSONTyped(json, ignoreDiscriminator) {
'schemaVersion': json['schemaVersion'],
'generatedAt': (new Date(json['generatedAt'])),
'summary': json['summary'] == null ? undefined : json['summary'],
+ 'stats': (0, SupermodelIRStats_1.SupermodelIRStatsFromJSON)(json['stats']),
+ 'metadata': (0, CodeGraphEnvelopeMetadata_1.CodeGraphEnvelopeMetadataFromJSON)(json['metadata']),
+ 'domains': (json['domains'].map(DomainSummary_1.DomainSummaryFromJSON)),
'graph': (0, SupermodelIRGraph_1.SupermodelIRGraphFromJSON)(json['graph']),
'artifacts': json['artifacts'] == null ? undefined : (json['artifacts'].map(SupermodelArtifact_1.SupermodelArtifactFromJSON)),
};
@@ -8707,12 +9904,90 @@ function SupermodelIRToJSON(value) {
'schemaVersion': value['schemaVersion'],
'generatedAt': ((value['generatedAt']).toISOString()),
'summary': value['summary'],
+ 'stats': (0, SupermodelIRStats_1.SupermodelIRStatsToJSON)(value['stats']),
+ 'metadata': (0, CodeGraphEnvelopeMetadata_1.CodeGraphEnvelopeMetadataToJSON)(value['metadata']),
+ 'domains': (value['domains'].map(DomainSummary_1.DomainSummaryToJSON)),
'graph': (0, SupermodelIRGraph_1.SupermodelIRGraphToJSON)(value['graph']),
'artifacts': value['artifacts'] == null ? undefined : (value['artifacts'].map(SupermodelArtifact_1.SupermodelArtifactToJSON)),
};
}
+/***/ }),
+
+/***/ 8397:
+/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.SupermodelIRAsyncStatusEnum = void 0;
+exports.instanceOfSupermodelIRAsync = instanceOfSupermodelIRAsync;
+exports.SupermodelIRAsyncFromJSON = SupermodelIRAsyncFromJSON;
+exports.SupermodelIRAsyncFromJSONTyped = SupermodelIRAsyncFromJSONTyped;
+exports.SupermodelIRAsyncToJSON = SupermodelIRAsyncToJSON;
+const SupermodelIR_1 = __nccwpck_require__(3569);
+/**
+ * @export
+ */
+exports.SupermodelIRAsyncStatusEnum = {
+ Pending: 'pending',
+ Processing: 'processing',
+ Completed: 'completed',
+ Failed: 'failed'
+};
+/**
+ * Check if a given object implements the SupermodelIRAsync interface.
+ */
+function instanceOfSupermodelIRAsync(value) {
+ if (!('status' in value) || value['status'] === undefined)
+ return false;
+ if (!('jobId' in value) || value['jobId'] === undefined)
+ return false;
+ return true;
+}
+function SupermodelIRAsyncFromJSON(json) {
+ return SupermodelIRAsyncFromJSONTyped(json, false);
+}
+function SupermodelIRAsyncFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'status': json['status'],
+ 'jobId': json['jobId'],
+ 'retryAfter': json['retryAfter'] == null ? undefined : json['retryAfter'],
+ 'error': json['error'] == null ? undefined : json['error'],
+ 'result': json['result'] == null ? undefined : (0, SupermodelIR_1.SupermodelIRFromJSON)(json['result']),
+ };
+}
+function SupermodelIRAsyncToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'status': value['status'],
+ 'jobId': value['jobId'],
+ 'retryAfter': value['retryAfter'],
+ 'error': value['error'],
+ 'result': (0, SupermodelIR_1.SupermodelIRToJSON)(value['result']),
+ };
+}
+
+
/***/ }),
/***/ 1881:
@@ -8726,7 +10001,7 @@ function SupermodelIRToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8773,6 +10048,64 @@ function SupermodelIRGraphToJSON(value) {
}
+/***/ }),
+
+/***/ 7654:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Supermodel
+ * Code Graphing & Analysis API
+ *
+ * The version of the OpenAPI document: 0.9.3
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.instanceOfSupermodelIRStats = instanceOfSupermodelIRStats;
+exports.SupermodelIRStatsFromJSON = SupermodelIRStatsFromJSON;
+exports.SupermodelIRStatsFromJSONTyped = SupermodelIRStatsFromJSONTyped;
+exports.SupermodelIRStatsToJSON = SupermodelIRStatsToJSON;
+/**
+ * Check if a given object implements the SupermodelIRStats interface.
+ */
+function instanceOfSupermodelIRStats(value) {
+ return true;
+}
+function SupermodelIRStatsFromJSON(json) {
+ return SupermodelIRStatsFromJSONTyped(json, false);
+}
+function SupermodelIRStatsFromJSONTyped(json, ignoreDiscriminator) {
+ if (json == null) {
+ return json;
+ }
+ return {
+ 'nodeCount': json['nodeCount'] == null ? undefined : json['nodeCount'],
+ 'relationshipCount': json['relationshipCount'] == null ? undefined : json['relationshipCount'],
+ 'nodeTypes': json['nodeTypes'] == null ? undefined : json['nodeTypes'],
+ 'relationshipTypes': json['relationshipTypes'] == null ? undefined : json['relationshipTypes'],
+ };
+}
+function SupermodelIRStatsToJSON(value) {
+ if (value == null) {
+ return value;
+ }
+ return {
+ 'nodeCount': value['nodeCount'],
+ 'relationshipCount': value['relationshipCount'],
+ 'nodeTypes': value['nodeTypes'],
+ 'relationshipTypes': value['relationshipTypes'],
+ };
+}
+
+
/***/ }),
/***/ 129:
@@ -8786,7 +10119,7 @@ function SupermodelIRGraphToJSON(value) {
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -8855,25 +10188,38 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
Object.defineProperty(exports, "__esModule", ({ value: true }));
/* tslint:disable */
/* eslint-disable */
+__exportStar(__nccwpck_require__(5331), exports);
__exportStar(__nccwpck_require__(6367), exports);
__exportStar(__nccwpck_require__(9995), exports);
+__exportStar(__nccwpck_require__(8711), exports);
__exportStar(__nccwpck_require__(727), exports);
+__exportStar(__nccwpck_require__(7122), exports);
__exportStar(__nccwpck_require__(1811), exports);
__exportStar(__nccwpck_require__(5473), exports);
__exportStar(__nccwpck_require__(2208), exports);
+__exportStar(__nccwpck_require__(8386), exports);
+__exportStar(__nccwpck_require__(536), exports);
+__exportStar(__nccwpck_require__(3750), exports);
+__exportStar(__nccwpck_require__(4196), exports);
__exportStar(__nccwpck_require__(4181), exports);
__exportStar(__nccwpck_require__(9137), exports);
+__exportStar(__nccwpck_require__(5501), exports);
+__exportStar(__nccwpck_require__(9625), exports);
__exportStar(__nccwpck_require__(385), exports);
__exportStar(__nccwpck_require__(5903), exports);
__exportStar(__nccwpck_require__(1988), exports);
__exportStar(__nccwpck_require__(7228), exports);
+__exportStar(__nccwpck_require__(8512), exports);
__exportStar(__nccwpck_require__(8560), exports);
__exportStar(__nccwpck_require__(8942), exports);
+__exportStar(__nccwpck_require__(2185), exports);
__exportStar(__nccwpck_require__(4203), exports);
__exportStar(__nccwpck_require__(8268), exports);
__exportStar(__nccwpck_require__(4020), exports);
__exportStar(__nccwpck_require__(3569), exports);
+__exportStar(__nccwpck_require__(8397), exports);
__exportStar(__nccwpck_require__(1881), exports);
+__exportStar(__nccwpck_require__(7654), exports);
__exportStar(__nccwpck_require__(129), exports);
@@ -8890,7 +10236,7 @@ __exportStar(__nccwpck_require__(129), exports);
* Supermodel
* Code Graphing & Analysis API
*
- * The version of the OpenAPI document: 0.4.1
+ * The version of the OpenAPI document: 0.9.3
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -32286,156 +33632,64 @@ function wrappy (fn, cb) {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.ENTRY_POINT_FUNCTION_NAMES = exports.ENTRY_POINT_PATTERNS = exports.DEFAULT_EXCLUDE_PATTERNS = void 0;
-exports.isEntryPointFile = isEntryPointFile;
-exports.isEntryPointFunction = isEntryPointFunction;
-exports.shouldIgnoreFile = shouldIgnoreFile;
-exports.findDeadCode = findDeadCode;
+exports.filterByIgnorePatterns = filterByIgnorePatterns;
exports.formatPrComment = formatPrComment;
const minimatch_1 = __nccwpck_require__(6507);
-/** Default glob patterns for files to exclude from dead code analysis. */
-exports.DEFAULT_EXCLUDE_PATTERNS = [
- '**/node_modules/**',
- '**/dist/**',
- '**/build/**',
- '**/.git/**',
- '**/vendor/**',
- '**/target/**',
- '**/*.test.ts',
- '**/*.test.tsx',
- '**/*.test.js',
- '**/*.test.jsx',
- '**/*.spec.ts',
- '**/*.spec.tsx',
- '**/*.spec.js',
- '**/*.spec.jsx',
- '**/__tests__/**',
- '**/__mocks__/**',
-];
-/** Glob patterns for files that are considered entry points. */
-exports.ENTRY_POINT_PATTERNS = [
- '**/index.ts',
- '**/index.js',
- '**/main.ts',
- '**/main.js',
- '**/app.ts',
- '**/app.js',
- '**/*.test.*',
- '**/*.spec.*',
- '**/__tests__/**',
-];
-/** Function names that are considered entry points. */
-exports.ENTRY_POINT_FUNCTION_NAMES = [
- 'main',
- 'run',
- 'start',
- 'init',
- 'setup',
- 'bootstrap',
- 'default',
- 'handler',
- 'GET', 'POST', 'PUT', 'DELETE', 'PATCH',
-];
-/**
- * Checks if a file path matches any entry point pattern.
- * @param filePath - The file path to check
- * @returns True if the file is an entry point
- */
-function isEntryPointFile(filePath) {
- return exports.ENTRY_POINT_PATTERNS.some(pattern => (0, minimatch_1.minimatch)(filePath, pattern));
-}
+const markdown_1 = __nccwpck_require__(3758);
/**
- * Checks if a function name is a common entry point name.
- * @param name - The function name to check
- * @returns True if the function name is an entry point
+ * Filters dead code candidates by user-provided ignore patterns.
+ * The API handles all analysis server-side; this is purely for
+ * client-side post-filtering on file paths.
*/
-function isEntryPointFunction(name) {
- const lowerName = name.toLowerCase();
- return exports.ENTRY_POINT_FUNCTION_NAMES.some(ep => lowerName === ep.toLowerCase());
-}
-/**
- * Checks if a file should be ignored based on exclude patterns.
- * @param filePath - The file path to check
- * @param ignorePatterns - Additional patterns to ignore
- * @returns True if the file should be ignored
- */
-function shouldIgnoreFile(filePath, ignorePatterns = []) {
- const allPatterns = [...exports.DEFAULT_EXCLUDE_PATTERNS, ...ignorePatterns];
- return allPatterns.some(pattern => (0, minimatch_1.minimatch)(filePath, pattern));
-}
-/**
- * Analyzes a code graph to find functions that are never called.
- * @param nodes - All nodes from the code graph
- * @param relationships - All relationships from the code graph
- * @param ignorePatterns - Additional glob patterns to ignore
- * @returns Array of potentially unused functions
- */
-function findDeadCode(nodes, relationships, ignorePatterns = []) {
- const functionNodes = nodes.filter(node => node.labels?.includes('Function'));
- const callRelationships = relationships.filter(rel => rel.type === 'calls');
- const calledFunctionIds = new Set(callRelationships.map(rel => rel.endNode));
- const deadCode = [];
- for (const node of functionNodes) {
- const props = node.properties || {};
- const filePath = props.filePath || props.file || '';
- const name = props.name || 'anonymous';
- if (calledFunctionIds.has(node.id)) {
- continue;
- }
- if (shouldIgnoreFile(filePath, ignorePatterns)) {
- continue;
- }
- if (isEntryPointFile(filePath)) {
- continue;
- }
- if (isEntryPointFunction(name)) {
- continue;
- }
- if (props.exported === true || props.isExported === true) {
- continue;
- }
- deadCode.push({
- id: node.id,
- name,
- filePath,
- startLine: props.startLine,
- endLine: props.endLine,
- });
- }
- return deadCode;
+function filterByIgnorePatterns(candidates, ignorePatterns) {
+ if (ignorePatterns.length === 0)
+ return candidates;
+ return candidates.filter(c => !ignorePatterns.some(p => (0, minimatch_1.minimatch)(c.file, p)));
}
/**
- * Formats dead code results as a GitHub PR comment.
- * @param deadCode - Array of dead code results
- * @returns Markdown-formatted comment string
+ * Formats dead code analysis results as a GitHub PR comment.
*/
-function formatPrComment(deadCode) {
- if (deadCode.length === 0) {
+function formatPrComment(candidates, metadata) {
+ if (candidates.length === 0) {
return `## Dead Code Hunter
No dead code found! Your codebase is clean.`;
}
- const rows = deadCode
+ const rows = candidates
.slice(0, 50)
.map(dc => {
- const lineInfo = dc.startLine ? `L${dc.startLine}` : '';
- const fileLink = dc.startLine
- ? `${dc.filePath}#L${dc.startLine}`
- : dc.filePath;
- return `| \`${dc.name}\` | ${fileLink} | ${lineInfo} |`;
+ const lineInfo = dc.line ? `L${dc.line}` : '';
+ const fileLink = dc.line ? `${dc.file}#L${dc.line}` : dc.file;
+ const badge = dc.confidence === 'high' ? ':red_circle:' :
+ dc.confidence === 'medium' ? ':orange_circle:' : ':yellow_circle:';
+ return `| \`${(0, markdown_1.escapeTableCell)(dc.name)}\` | ${dc.type} | ${fileLink} | ${lineInfo} | ${badge} ${dc.confidence} |`;
})
.join('\n');
let comment = `## Dead Code Hunter
-Found **${deadCode.length}** potentially unused function${deadCode.length === 1 ? '' : 's'}:
+Found **${candidates.length}** potentially unused code element${candidates.length === 1 ? '' : 's'}:
-| Function | File | Line |
-|----------|------|------|
+| Name | Type | File | Line | Confidence |
+|------|------|------|------|------------|
${rows}`;
- if (deadCode.length > 50) {
- comment += `\n\n_...and ${deadCode.length - 50} more. See action output for full list._`;
+ if (candidates.length > 50) {
+ comment += `\n\n_...and ${candidates.length - 50} more. See action output for full list._`;
+ }
+ if (metadata) {
+ comment += `\n\nAnalysis summary
\n\n`;
+ comment += `- **Total declarations analyzed**: ${metadata.totalDeclarations}\n`;
+ comment += `- **Dead code candidates**: ${metadata.deadCodeCandidates}\n`;
+ comment += `- **Alive code**: ${metadata.aliveCode}\n`;
+ comment += `- **Analysis method**: ${metadata.analysisMethod}\n`;
+ if (metadata.transitiveDeadCount != null) {
+ comment += `- **Transitive dead**: ${metadata.transitiveDeadCount}\n`;
+ }
+ if (metadata.symbolLevelDeadCount != null) {
+ comment += `- **Symbol-level dead**: ${metadata.symbolLevelDeadCount}\n`;
+ }
+ comment += `\n `;
}
- comment += `\n\n---\n_Powered by [Supermodel](https://supermodeltools.com) graph analysis_`;
+ comment += `\n\n---\n_Powered by [Supermodel](https://supermodeltools.com) dead code analysis_`;
return comment;
}
@@ -32504,6 +33758,9 @@ const SENSITIVE_KEYS = new Set([
'x-api-key',
]);
const MAX_VALUE_LENGTH = 1000;
+const MAX_POLL_ATTEMPTS = 90;
+const DEFAULT_RETRY_INTERVAL_MS = 10_000;
+const POLL_TIMEOUT_MS = 15 * 60 * 1000;
/**
* Safely serialize a value for logging, handling circular refs, BigInt, and large values.
* Redacts sensitive fields.
@@ -32512,15 +33769,12 @@ function safeSerialize(value, maxLength = MAX_VALUE_LENGTH) {
try {
const seen = new WeakSet();
const serialized = JSON.stringify(value, (key, val) => {
- // Redact sensitive keys
if (key && SENSITIVE_KEYS.has(key.toLowerCase())) {
return '[REDACTED]';
}
- // Handle BigInt
if (typeof val === 'bigint') {
return val.toString();
}
- // Handle circular references
if (typeof val === 'object' && val !== null) {
if (seen.has(val)) {
return '[Circular]';
@@ -32529,7 +33783,6 @@ function safeSerialize(value, maxLength = MAX_VALUE_LENGTH) {
}
return val;
}, 2);
- // Truncate if too long
if (serialized && serialized.length > maxLength) {
return serialized.slice(0, maxLength) + '... [truncated]';
}
@@ -32580,8 +33833,38 @@ async function generateIdempotencyKey(workspacePath) {
});
const commitHash = output.trim();
const repoName = path.basename(workspacePath);
- // Use UUID to ensure unique key per run (avoids 409 conflicts, scales to many concurrent users)
- return `${repoName}:deadcode:${commitHash}:${(0, crypto_1.randomUUID)()}`;
+ return `${repoName}:analysis:deadcode:${commitHash}:${(0, crypto_1.randomUUID)()}`;
+}
+function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+/**
+ * Polls the dead code analysis endpoint until the job completes or fails.
+ * The API returns 202 while processing; re-submitting the same request
+ * with the same idempotency key acts as a poll.
+ */
+async function pollForResult(api, idempotencyKey, zipBlob) {
+ const startTime = Date.now();
+ for (let attempt = 1; attempt <= MAX_POLL_ATTEMPTS; attempt++) {
+ const response = await api.generateDeadCodeAnalysis({
+ idempotencyKey,
+ file: zipBlob,
+ });
+ if (response.status === 'completed' && response.result) {
+ return response.result;
+ }
+ if (response.status === 'failed') {
+ throw new Error(`Analysis job failed: ${response.error || 'unknown error'}`);
+ }
+ const elapsed = Date.now() - startTime;
+ if (elapsed >= POLL_TIMEOUT_MS) {
+ throw new Error(`Analysis timed out after ${Math.round(elapsed / 1000)}s (job: ${response.jobId})`);
+ }
+ const retryMs = (response.retryAfter ?? DEFAULT_RETRY_INTERVAL_MS / 1000) * 1000;
+ core.info(`Job ${response.jobId} status: ${response.status} (attempt ${attempt}/${MAX_POLL_ATTEMPTS}, retry in ${retryMs / 1000}s)`);
+ await sleep(retryMs);
+ }
+ throw new Error(`Analysis did not complete within ${MAX_POLL_ATTEMPTS} polling attempts`);
}
async function run() {
try {
@@ -32598,7 +33881,7 @@ async function run() {
const zipPath = await createZipArchive(workspacePath);
// Step 2: Generate idempotency key
const idempotencyKey = await generateIdempotencyKey(workspacePath);
- // Step 3: Call Supermodel API
+ // Step 3: Call Supermodel dead code analysis API
core.info('Analyzing codebase with Supermodel...');
const config = new sdk_1.Configuration({
basePath: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com',
@@ -32607,24 +33890,24 @@ async function run() {
const api = new sdk_1.DefaultApi(config);
const zipBuffer = await fs.readFile(zipPath);
const zipBlob = new Blob([zipBuffer], { type: 'application/zip' });
- const response = await api.generateSupermodelGraph({
- idempotencyKey,
- file: zipBlob,
- });
- // Step 4: Analyze for dead code
- const nodes = response.graph?.nodes || [];
- const relationships = response.graph?.relationships || [];
- const deadCode = (0, dead_code_1.findDeadCode)(nodes, relationships, ignorePatterns);
- core.info(`Found ${deadCode.length} potentially unused functions`);
+ const result = await pollForResult(api, idempotencyKey, zipBlob);
+ // Step 4: Apply client-side ignore patterns
+ const candidates = (0, dead_code_1.filterByIgnorePatterns)(result.deadCodeCandidates, ignorePatterns);
+ core.info(`Found ${candidates.length} potentially unused code elements (${result.metadata.totalDeclarations} declarations analyzed)`);
+ core.info(`Analysis method: ${result.metadata.analysisMethod}`);
+ core.info(`Alive: ${result.metadata.aliveCode}, Entry points: ${result.entryPoints.length}, Root files: ${result.metadata.rootFilesCount ?? 'n/a'}`);
+ for (const dc of candidates) {
+ core.info(` [${dc.confidence}] ${dc.type} ${dc.name} @ ${dc.file}:${dc.line} — ${dc.reason}`);
+ }
// Step 5: Set outputs
- core.setOutput('dead-code-count', deadCode.length);
- core.setOutput('dead-code-json', JSON.stringify(deadCode));
+ core.setOutput('dead-code-count', candidates.length);
+ core.setOutput('dead-code-json', JSON.stringify(candidates));
// Step 6: Post PR comment if enabled
if (commentOnPr && github.context.payload.pull_request) {
const token = core.getInput('github-token') || process.env.GITHUB_TOKEN;
if (token) {
const octokit = github.getOctokit(token);
- const comment = (0, dead_code_1.formatPrComment)(deadCode);
+ const comment = (0, dead_code_1.formatPrComment)(candidates, result.metadata);
await octokit.rest.issues.createComment({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
@@ -32640,24 +33923,20 @@ async function run() {
// Step 7: Clean up
await fs.unlink(zipPath);
// Step 8: Fail if configured and dead code found
- if (deadCode.length > 0 && failOnDeadCode) {
- core.setFailed(`Found ${deadCode.length} potentially unused functions`);
+ if (candidates.length > 0 && failOnDeadCode) {
+ core.setFailed(`Found ${candidates.length} potentially unused code elements`);
}
}
catch (error) {
- // Log error details for debugging (using debug level for potentially sensitive data)
core.info('--- Error Debug Info ---');
core.info(`Error type: ${error?.constructor?.name ?? 'unknown'}`);
core.info(`Error message: ${error?.message ?? 'no message'}`);
core.info(`Error name: ${error?.name ?? 'no name'}`);
- // Check various error structures used by different HTTP clients
- // Use core.debug for detailed/sensitive info, core.info for safe summaries
try {
if (error?.response) {
core.info(`Response status: ${error.response.status ?? 'unknown'}`);
core.info(`Response statusText: ${error.response.statusText ?? 'unknown'}`);
core.info(`Response data: ${safeSerialize(error.response.data)}`);
- // Headers may contain sensitive values - use debug level
core.debug(`Response headers: ${safeSerialize(redactSensitive(error.response.headers))}`);
}
if (error?.body) {
@@ -32679,10 +33958,8 @@ async function run() {
core.info('--- End Debug Info ---');
let errorMessage = 'An unknown error occurred';
let helpText = '';
- // Try multiple error structures
const status = error?.response?.status || error?.status || error?.statusCode;
let apiMessage = '';
- // Try to extract message from various locations
try {
apiMessage =
error?.response?.data?.message ||
@@ -32705,7 +33982,6 @@ async function run() {
}
else if (status === 500) {
errorMessage = apiMessage || 'Internal server error';
- // Check for common issues and provide guidance
if (apiMessage.includes('Nested archives')) {
helpText = 'Your repository contains nested archive files (.zip, .tar, etc.). ' +
'Add them to .gitattributes with "export-ignore" to exclude from analysis. ' +
@@ -32740,6 +34016,23 @@ async function run() {
run();
+/***/ }),
+
+/***/ 3758:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.escapeTableCell = escapeTableCell;
+/**
+ * Escapes pipe characters for safe rendering inside markdown tables.
+ */
+function escapeTableCell(text) {
+ return text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
+}
+
+
/***/ }),
/***/ 2613:
diff --git a/dist/markdown.d.ts b/dist/markdown.d.ts
new file mode 100644
index 0000000..f5d883b
--- /dev/null
+++ b/dist/markdown.d.ts
@@ -0,0 +1,4 @@
+/**
+ * Escapes pipe characters for safe rendering inside markdown tables.
+ */
+export declare function escapeTableCell(text: string): string;
diff --git a/dist/markdown.d.ts.map b/dist/markdown.d.ts.map
new file mode 100644
index 0000000..a0760d3
--- /dev/null
+++ b/dist/markdown.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jag/repos/dead-code-hunter-issue-8/src/markdown.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index ee9ba94..acc57df 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
"@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1",
"@actions/github": "^6.0.0",
- "@supermodeltools/sdk": "^0.4.1",
+ "@supermodeltools/sdk": "^0.9.3",
"minimatch": "^9.0.0"
},
"devDependencies": {
@@ -1047,9 +1047,9 @@
"license": "MIT"
},
"node_modules/@supermodeltools/sdk": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@supermodeltools/sdk/-/sdk-0.4.1.tgz",
- "integrity": "sha512-/hVzGvceyrv9S+HxaYhbp1DaX2B94t1td2kG2HiM5Ey4p59F5FojK7vOAurBcmCQ5Hhphp+BhgUWrgwd/sOSOQ==",
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/@supermodeltools/sdk/-/sdk-0.9.3.tgz",
+ "integrity": "sha512-IDvNoke9ymCAnzHxJjMEj+USyuN53Dyulex/gpQ/sUBrvIM3PKaiVYxpr3oV5z+rhjnix1GqJHtXOw9yRYSw0w==",
"license": "UNLICENSED"
},
"node_modules/@types/chai": {
diff --git a/package.json b/package.json
index f283d56..ca21975 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "dead-code-hunter",
- "version": "0.1.0",
- "description": "GitHub Action to find unreachable functions using Supermodel call graphs",
+ "version": "0.2.0",
+ "description": "GitHub Action to find dead code using the Supermodel dead code analysis API",
"main": "dist/index.js",
"scripts": {
"build": "ncc build src/index.ts -o dist",
@@ -25,7 +25,7 @@
"@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1",
"@actions/github": "^6.0.0",
- "@supermodeltools/sdk": "^0.4.1",
+ "@supermodeltools/sdk": "^0.9.3",
"minimatch": "^9.0.0"
},
"devDependencies": {
diff --git a/src/__tests__/dead-code.test.ts b/src/__tests__/dead-code.test.ts
index 5a8c6d3..c966278 100644
--- a/src/__tests__/dead-code.test.ts
+++ b/src/__tests__/dead-code.test.ts
@@ -1,261 +1,203 @@
import { describe, it, expect } from 'vitest';
-import {
- findDeadCode,
- isEntryPointFile,
- isEntryPointFunction,
- shouldIgnoreFile,
- formatPrComment,
- DeadCodeResult,
-} from '../dead-code';
-import { CodeGraphNode, CodeGraphRelationship } from '@supermodeltools/sdk';
+import { filterByIgnorePatterns, formatPrComment } from '../dead-code';
+import { escapeTableCell } from '../markdown';
+import type { DeadCodeCandidate, DeadCodeAnalysisMetadata } from '@supermodeltools/sdk';
-describe('isEntryPointFile', () => {
- it('should identify index files as entry points', () => {
- expect(isEntryPointFile('src/index.ts')).toBe(true);
- expect(isEntryPointFile('lib/index.js')).toBe(true);
- });
-
- it('should identify main files as entry points', () => {
- expect(isEntryPointFile('src/main.ts')).toBe(true);
- expect(isEntryPointFile('main.js')).toBe(true);
- });
-
- it('should identify app files as entry points', () => {
- expect(isEntryPointFile('src/app.ts')).toBe(true);
- });
+function makeCandidate(overrides: Partial = {}): DeadCodeCandidate {
+ return {
+ file: 'src/utils.ts',
+ name: 'unusedFn',
+ line: 10,
+ type: 'function' as const,
+ confidence: 'high' as const,
+ reason: 'No callers found in codebase',
+ ...overrides,
+ };
+}
- it('should identify test files as entry points', () => {
- expect(isEntryPointFile('src/utils.test.ts')).toBe(true);
- expect(isEntryPointFile('src/utils.spec.js')).toBe(true);
- expect(isEntryPointFile('src/__tests__/utils.ts')).toBe(true);
- });
-
- it('should not identify regular files as entry points', () => {
- expect(isEntryPointFile('src/utils.ts')).toBe(false);
- expect(isEntryPointFile('src/helpers/format.js')).toBe(false);
- });
-});
+function makeMetadata(overrides: Partial = {}): DeadCodeAnalysisMetadata {
+ return {
+ totalDeclarations: 100,
+ deadCodeCandidates: 5,
+ aliveCode: 95,
+ analysisMethod: 'parse_graph + call_graph',
+ ...overrides,
+ };
+}
-describe('isEntryPointFunction', () => {
- it('should identify common entry point function names', () => {
- expect(isEntryPointFunction('main')).toBe(true);
- expect(isEntryPointFunction('run')).toBe(true);
- expect(isEntryPointFunction('start')).toBe(true);
- expect(isEntryPointFunction('init')).toBe(true);
- expect(isEntryPointFunction('handler')).toBe(true);
+describe('escapeTableCell', () => {
+ it('should escape pipe characters', () => {
+ expect(escapeTableCell('a|b|c')).toBe('a\\|b\\|c');
});
- it('should be case-insensitive', () => {
- expect(isEntryPointFunction('Main')).toBe(true);
- expect(isEntryPointFunction('MAIN')).toBe(true);
- expect(isEntryPointFunction('Handler')).toBe(true);
+ it('should replace newlines with spaces', () => {
+ expect(escapeTableCell('line1\nline2')).toBe('line1 line2');
});
- it('should identify HTTP method handlers', () => {
- expect(isEntryPointFunction('GET')).toBe(true);
- expect(isEntryPointFunction('POST')).toBe(true);
- expect(isEntryPointFunction('PUT')).toBe(true);
- expect(isEntryPointFunction('DELETE')).toBe(true);
+ it('should handle both pipes and newlines', () => {
+ expect(escapeTableCell('a|b\nc|d')).toBe('a\\|b c\\|d');
});
- it('should not identify regular function names', () => {
- expect(isEntryPointFunction('processData')).toBe(false);
- expect(isEntryPointFunction('calculateTotal')).toBe(false);
+ it('should return unchanged string when no special characters', () => {
+ expect(escapeTableCell('normalText')).toBe('normalText');
});
});
-describe('shouldIgnoreFile', () => {
- it('should ignore node_modules', () => {
- expect(shouldIgnoreFile('node_modules/lodash/index.js')).toBe(true);
+describe('filterByIgnorePatterns', () => {
+ it('should return all candidates when no patterns provided', () => {
+ const candidates = [makeCandidate(), makeCandidate({ file: 'src/helpers.ts' })];
+ const result = filterByIgnorePatterns(candidates, []);
+ expect(result).toHaveLength(2);
});
- it('should ignore dist folder', () => {
- expect(shouldIgnoreFile('dist/index.js')).toBe(true);
- });
-
- it('should ignore build folder', () => {
- expect(shouldIgnoreFile('build/main.js')).toBe(true);
+ it('should filter candidates matching ignore patterns', () => {
+ const candidates = [
+ makeCandidate({ file: 'src/generated/api.ts' }),
+ makeCandidate({ file: 'src/utils.ts' }),
+ ];
+ const result = filterByIgnorePatterns(candidates, ['**/generated/**']);
+ expect(result).toHaveLength(1);
+ expect(result[0].file).toBe('src/utils.ts');
});
- it('should ignore test files', () => {
- expect(shouldIgnoreFile('src/utils.test.ts')).toBe(true);
- expect(shouldIgnoreFile('src/utils.spec.js')).toBe(true);
+ it('should support multiple ignore patterns', () => {
+ const candidates = [
+ makeCandidate({ file: 'src/generated/api.ts' }),
+ makeCandidate({ file: 'src/migrations/001.ts' }),
+ makeCandidate({ file: 'src/utils.ts' }),
+ ];
+ const result = filterByIgnorePatterns(candidates, ['**/generated/**', '**/migrations/**']);
+ expect(result).toHaveLength(1);
+ expect(result[0].file).toBe('src/utils.ts');
});
- it('should not ignore regular source files', () => {
- expect(shouldIgnoreFile('src/utils.ts')).toBe(false);
- expect(shouldIgnoreFile('lib/helpers.js')).toBe(false);
+ it('should not filter when patterns do not match', () => {
+ const candidates = [makeCandidate({ file: 'src/utils.ts' })];
+ const result = filterByIgnorePatterns(candidates, ['**/generated/**']);
+ expect(result).toHaveLength(1);
});
- it('should respect custom ignore patterns', () => {
- expect(shouldIgnoreFile('src/generated/api.ts', ['**/generated/**'])).toBe(true);
- expect(shouldIgnoreFile('src/utils.ts', ['**/generated/**'])).toBe(false);
+ it('should filter across different code types', () => {
+ const candidates = [
+ makeCandidate({ file: 'src/generated/types.ts', type: 'interface' }),
+ makeCandidate({ file: 'src/generated/client.ts', type: 'class' }),
+ makeCandidate({ file: 'src/service.ts', type: 'function' }),
+ ];
+ const result = filterByIgnorePatterns(candidates, ['**/generated/**']);
+ expect(result).toHaveLength(1);
+ expect(result[0].file).toBe('src/service.ts');
});
});
-describe('findDeadCode', () => {
- it('should find functions with no callers', () => {
- const nodes: CodeGraphNode[] = [
- { id: 'fn1', labels: ['Function'], properties: { name: 'usedFunction', filePath: 'src/utils.ts' } },
- { id: 'fn2', labels: ['Function'], properties: { name: 'unusedFunction', filePath: 'src/helpers.ts' } },
- ];
-
- const relationships: CodeGraphRelationship[] = [
- { id: 'rel1', type: 'calls', startNode: 'fn3', endNode: 'fn1' },
- ];
-
- const deadCode = findDeadCode(nodes, relationships);
-
- expect(deadCode).toHaveLength(1);
- expect(deadCode[0].name).toBe('unusedFunction');
+describe('formatPrComment', () => {
+ it('should format empty results', () => {
+ const comment = formatPrComment([]);
+ expect(comment).toContain('No dead code found');
+ expect(comment).toContain('codebase is clean');
});
- it('should not report functions that are called', () => {
- const nodes: CodeGraphNode[] = [
- { id: 'fn1', labels: ['Function'], properties: { name: 'calledFunction', filePath: 'src/utils.ts' } },
- ];
-
- const relationships: CodeGraphRelationship[] = [
- { id: 'rel1', type: 'calls', startNode: 'fn2', endNode: 'fn1' },
- ];
-
- const deadCode = findDeadCode(nodes, relationships);
+ it('should format single result with type and confidence', () => {
+ const candidates = [makeCandidate()];
+ const comment = formatPrComment(candidates);
- expect(deadCode).toHaveLength(0);
+ expect(comment).toContain('1** potentially unused code element:');
+ expect(comment).toContain('`unusedFn`');
+ expect(comment).toContain('function');
+ expect(comment).toContain('src/utils.ts#L10');
+ expect(comment).toContain('high');
});
- it('should skip exported functions', () => {
- const nodes: CodeGraphNode[] = [
- { id: 'fn1', labels: ['Function'], properties: { name: 'exportedFn', filePath: 'src/utils.ts', exported: true } },
- { id: 'fn2', labels: ['Function'], properties: { name: 'notExported', filePath: 'src/helpers.ts' } },
+ it('should format multiple results', () => {
+ const candidates = [
+ makeCandidate({ name: 'fn1', file: 'src/a.ts', line: 1 }),
+ makeCandidate({ name: 'fn2', file: 'src/b.ts', line: 2, type: 'class' as const }),
];
+ const comment = formatPrComment(candidates);
- const relationships: CodeGraphRelationship[] = [];
-
- const deadCode = findDeadCode(nodes, relationships);
-
- expect(deadCode).toHaveLength(1);
- expect(deadCode[0].name).toBe('notExported');
+ expect(comment).toContain('2** potentially unused code elements');
+ expect(comment).toContain('`fn1`');
+ expect(comment).toContain('`fn2`');
+ expect(comment).toContain('class');
});
- it('should skip entry point functions', () => {
- const nodes: CodeGraphNode[] = [
- { id: 'fn1', labels: ['Function'], properties: { name: 'main', filePath: 'src/cli.ts' } },
- { id: 'fn2', labels: ['Function'], properties: { name: 'unusedHelper', filePath: 'src/helpers.ts' } },
- ];
-
- const relationships: CodeGraphRelationship[] = [];
-
- const deadCode = findDeadCode(nodes, relationships);
+ it('should render all supported code types', () => {
+ const types = ['function', 'class', 'method', 'interface', 'type', 'variable', 'constant'] as const;
+ const candidates = types.map((type, i) =>
+ makeCandidate({ name: `item${i}`, file: `src/${type}.ts`, line: i + 1, type })
+ );
+ const comment = formatPrComment(candidates);
- expect(deadCode).toHaveLength(1);
- expect(deadCode[0].name).toBe('unusedHelper');
+ for (const type of types) {
+ expect(comment).toContain(`| ${type} |`);
+ }
});
- it('should skip functions in entry point files', () => {
- const nodes: CodeGraphNode[] = [
- { id: 'fn1', labels: ['Function'], properties: { name: 'someFunc', filePath: 'src/index.ts' } },
- { id: 'fn2', labels: ['Function'], properties: { name: 'unusedHelper', filePath: 'src/helpers.ts' } },
- ];
-
- const relationships: CodeGraphRelationship[] = [];
-
- const deadCode = findDeadCode(nodes, relationships);
+ it('should escape pipe characters in candidate names', () => {
+ const candidates = [makeCandidate({ name: 'fn|with|pipes' })];
+ const comment = formatPrComment(candidates);
- expect(deadCode).toHaveLength(1);
- expect(deadCode[0].name).toBe('unusedHelper');
+ expect(comment).toContain('fn\\|with\\|pipes');
+ expect(comment).not.toContain('`fn|with|pipes`');
});
- it('should skip functions in ignored paths', () => {
- const nodes: CodeGraphNode[] = [
- { id: 'fn1', labels: ['Function'], properties: { name: 'testHelper', filePath: 'src/__tests__/helpers.ts' } },
- { id: 'fn2', labels: ['Function'], properties: { name: 'unusedHelper', filePath: 'src/helpers.ts' } },
- ];
-
- const relationships: CodeGraphRelationship[] = [];
-
- const deadCode = findDeadCode(nodes, relationships);
+ it('should truncate at 50 results', () => {
+ const candidates = Array.from({ length: 60 }, (_, i) =>
+ makeCandidate({ name: `fn${i}`, file: `src/file${i}.ts`, line: i + 1 })
+ );
+ const comment = formatPrComment(candidates);
- expect(deadCode).toHaveLength(1);
- expect(deadCode[0].name).toBe('unusedHelper');
+ expect(comment).toContain('60** potentially unused code elements');
+ expect(comment).toContain('and 10 more');
});
- it('should only consider Function nodes', () => {
- const nodes: CodeGraphNode[] = [
- { id: 'file1', labels: ['File'], properties: { name: 'utils.ts', filePath: 'src/utils.ts' } },
- { id: 'fn1', labels: ['Function'], properties: { name: 'unusedFn', filePath: 'src/helpers.ts' } },
- ];
-
- const relationships: CodeGraphRelationship[] = [];
-
- const deadCode = findDeadCode(nodes, relationships);
+ it('should include metadata details section when provided', () => {
+ const candidates = [makeCandidate()];
+ const metadata = makeMetadata({ transitiveDeadCount: 3, symbolLevelDeadCount: 7 });
+ const comment = formatPrComment(candidates, metadata);
- expect(deadCode).toHaveLength(1);
- expect(deadCode[0].name).toBe('unusedFn');
+ expect(comment).toContain('Analysis summary');
+ expect(comment).toContain('Total declarations analyzed');
+ expect(comment).toContain('100');
+ expect(comment).toContain('parse_graph + call_graph');
+ expect(comment).toContain('Transitive dead');
+ expect(comment).toContain('3');
+ expect(comment).toContain('Symbol-level dead');
+ expect(comment).toContain('7');
});
- it('should include line numbers when available', () => {
- const nodes: CodeGraphNode[] = [
- { id: 'fn1', labels: ['Function'], properties: { name: 'unusedFn', filePath: 'src/helpers.ts', startLine: 10, endLine: 20 } },
- ];
+ it('should omit optional metadata fields when not present', () => {
+ const candidates = [makeCandidate()];
+ const metadata = makeMetadata();
+ const comment = formatPrComment(candidates, metadata);
- const deadCode = findDeadCode(nodes, []);
-
- expect(deadCode[0].startLine).toBe(10);
- expect(deadCode[0].endLine).toBe(20);
- });
-});
-
-describe('formatPrComment', () => {
- it('should format empty results', () => {
- const comment = formatPrComment([]);
- expect(comment).toContain('No dead code found');
- expect(comment).toContain('codebase is clean');
+ expect(comment).toContain('Analysis summary');
+ expect(comment).not.toContain('Transitive dead');
+ expect(comment).not.toContain('Symbol-level dead');
});
- it('should format single result', () => {
- const deadCode: DeadCodeResult[] = [
- { id: 'fn1', name: 'unusedFn', filePath: 'src/utils.ts', startLine: 10 },
+ it('should show confidence badges', () => {
+ const candidates = [
+ makeCandidate({ confidence: 'high' as const }),
+ makeCandidate({ name: 'fn2', file: 'src/b.ts', confidence: 'medium' as const }),
+ makeCandidate({ name: 'fn3', file: 'src/c.ts', confidence: 'low' as const }),
];
+ const comment = formatPrComment(candidates);
- const comment = formatPrComment(deadCode);
-
- expect(comment).toContain('1** potentially unused function');
- expect(comment).toContain('`unusedFn`');
- expect(comment).toContain('src/utils.ts#L10');
+ expect(comment).toContain(':red_circle: high');
+ expect(comment).toContain(':orange_circle: medium');
+ expect(comment).toContain(':yellow_circle: low');
});
- it('should format multiple results', () => {
- const deadCode: DeadCodeResult[] = [
- { id: 'fn1', name: 'unusedFn1', filePath: 'src/utils.ts', startLine: 10 },
- { id: 'fn2', name: 'unusedFn2', filePath: 'src/helpers.ts', startLine: 20 },
- ];
-
- const comment = formatPrComment(deadCode);
-
- expect(comment).toContain('2** potentially unused functions');
- expect(comment).toContain('`unusedFn1`');
- expect(comment).toContain('`unusedFn2`');
+ it('should include Supermodel attribution', () => {
+ const candidates = [makeCandidate()];
+ const comment = formatPrComment(candidates);
+ expect(comment).toContain('Powered by [Supermodel]');
});
- it('should truncate at 50 results', () => {
- const deadCode: DeadCodeResult[] = Array.from({ length: 60 }, (_, i) => ({
- id: `fn${i}`,
- name: `unusedFn${i}`,
- filePath: `src/file${i}.ts`,
- }));
+ it('should render table header with all columns', () => {
+ const candidates = [makeCandidate()];
+ const comment = formatPrComment(candidates);
- const comment = formatPrComment(deadCode);
-
- expect(comment).toContain('60** potentially unused functions');
- expect(comment).toContain('and 10 more');
- });
-
- it('should include Supermodel attribution when dead code found', () => {
- const deadCode: DeadCodeResult[] = [
- { id: 'fn1', name: 'unusedFn', filePath: 'src/utils.ts' },
- ];
- const comment = formatPrComment(deadCode);
- expect(comment).toContain('Powered by [Supermodel]');
+ expect(comment).toContain('| Name | Type | File | Line | Confidence |');
});
});
diff --git a/src/__tests__/integration.test.ts b/src/__tests__/integration.test.ts
index 11dc794..67951f0 100644
--- a/src/__tests__/integration.test.ts
+++ b/src/__tests__/integration.test.ts
@@ -3,15 +3,50 @@ import { execSync } from 'child_process';
import * as fs from 'fs/promises';
import * as path from 'path';
import { Configuration, DefaultApi } from '@supermodeltools/sdk';
-import { findDeadCode } from '../dead-code';
+import type { DeadCodeAnalysisResponseAsync, DeadCodeAnalysisResponse } from '@supermodeltools/sdk';
+import { filterByIgnorePatterns } from '../dead-code';
const API_KEY = process.env.SUPERMODEL_API_KEY;
const SKIP_INTEGRATION = !API_KEY;
+async function pollForResult(
+ api: DefaultApi,
+ idempotencyKey: string,
+ zipBlob: Blob,
+ timeoutMs = 120_000
+): Promise {
+ const startTime = Date.now();
+
+ for (let attempt = 1; attempt <= 30; attempt++) {
+ const response: DeadCodeAnalysisResponseAsync = await api.generateDeadCodeAnalysis({
+ idempotencyKey,
+ file: zipBlob,
+ });
+
+ if (response.status === 'completed' && response.result) {
+ return response.result;
+ }
+
+ if (response.status === 'failed') {
+ throw new Error(`Analysis job failed: ${response.error || 'unknown error'}`);
+ }
+
+ if (Date.now() - startTime >= timeoutMs) {
+ throw new Error(`Polling timed out after ${timeoutMs}ms`);
+ }
+
+ const retryMs = (response.retryAfter ?? 10) * 1000;
+ await new Promise(resolve => setTimeout(resolve, retryMs));
+ }
+
+ throw new Error('Max polling attempts exceeded');
+}
+
describe.skipIf(SKIP_INTEGRATION)('Integration Tests', () => {
let api: DefaultApi;
let zipPath: string;
let idempotencyKey: string;
+ let result: DeadCodeAnalysisResponse;
beforeAll(async () => {
const config = new Configuration({
@@ -20,7 +55,6 @@ describe.skipIf(SKIP_INTEGRATION)('Integration Tests', () => {
});
api = new DefaultApi(config);
- // Create zip of this repo (dead-code-hunter testing itself!)
const repoRoot = path.resolve(__dirname, '../..');
zipPath = '/tmp/dead-code-hunter-test.zip';
@@ -29,68 +63,68 @@ describe.skipIf(SKIP_INTEGRATION)('Integration Tests', () => {
const commitHash = execSync('git rev-parse --short HEAD', { cwd: repoRoot })
.toString()
.trim();
- idempotencyKey = `dead-code-hunter:call:${commitHash}`;
- });
+ idempotencyKey = `dead-code-hunter:integration:${commitHash}`;
- it('should call the Supermodel API and get a call graph', async () => {
const zipBuffer = await fs.readFile(zipPath);
const zipBlob = new Blob([zipBuffer], { type: 'application/zip' });
+ result = await pollForResult(api, idempotencyKey, zipBlob);
+ }, 120_000);
+
+ it('should return a valid response shape', () => {
+ expect(result).toBeDefined();
+ expect(result.metadata).toBeDefined();
+ expect(result.deadCodeCandidates).toBeDefined();
+ expect(result.aliveCode).toBeDefined();
+ expect(result.entryPoints).toBeDefined();
+ expect(result.metadata.totalDeclarations).toBeGreaterThan(0);
+ expect(typeof result.metadata.analysisMethod).toBe('string');
+ });
- const response = await api.generateCallGraph({
- idempotencyKey,
- file: zipBlob,
- });
-
- expect(response).toBeDefined();
- expect(response.graph).toBeDefined();
- expect(response.graph?.nodes).toBeDefined();
- expect(response.graph?.relationships).toBeDefined();
- expect(response.stats).toBeDefined();
-
- console.log('API Stats:', response.stats);
- console.log('Nodes:', response.graph?.nodes?.length);
- console.log('Relationships:', response.graph?.relationships?.length);
- }, 60000); // 60 second timeout for API call
-
- it('should find dead code in the dead-code-hunter repo itself', async () => {
- const zipBuffer = await fs.readFile(zipPath);
- const zipBlob = new Blob([zipBuffer], { type: 'application/zip' });
-
- const response = await api.generateCallGraph({
- idempotencyKey,
- file: zipBlob,
- });
-
- const nodes = response.graph?.nodes || [];
- const relationships = response.graph?.relationships || [];
+ it('should analyze the codebase without errors', () => {
+ console.log('\n=== Dead Code Analysis Results ===');
+ console.log(`Total declarations: ${result.metadata.totalDeclarations}`);
+ console.log(`Dead code candidates: ${result.deadCodeCandidates.length}`);
+ console.log(`Alive code: ${result.metadata.aliveCode}`);
+ console.log(`Analysis method: ${result.metadata.analysisMethod}`);
- const deadCode = findDeadCode(nodes, relationships);
+ for (const dc of result.deadCodeCandidates) {
+ console.log(` [${dc.confidence}] ${dc.type} ${dc.name} @ ${dc.file}:${dc.line} — ${dc.reason}`);
+ }
- console.log('\n=== Dead Code Hunter Self-Analysis ===');
- console.log(`Total functions: ${nodes.filter(n => n.labels?.includes('Function')).length}`);
- console.log(`Total call relationships: ${relationships.filter(r => r.type === 'calls').length}`);
- console.log(`Dead code found: ${deadCode.length}`);
+ expect(result.metadata.totalDeclarations).toBeGreaterThan(0);
+ });
- if (deadCode.length > 0) {
- console.log('\nPotentially dead functions:');
- for (const dc of deadCode.slice(0, 10)) {
- console.log(` - ${dc.name} (${dc.filePath}:${dc.startLine || '?'})`);
- }
+ it('should include valid fields on every candidate', () => {
+ for (const dc of result.deadCodeCandidates) {
+ expect(dc.file).toBeTruthy();
+ expect(dc.name).toBeTruthy();
+ expect(dc.line).toBeGreaterThan(0);
+ expect(dc.type).toBeTruthy();
+ expect(['high', 'medium', 'low']).toContain(dc.confidence);
+ expect(dc.reason).toBeTruthy();
}
+ });
- // The test passes regardless of dead code count - we just want to verify the flow works
- expect(Array.isArray(deadCode)).toBe(true);
- }, 60000);
+ it('should support client-side ignore-patterns filtering', () => {
+ const all = result.deadCodeCandidates;
+ // Even if no dead code exists, verify the filter runs without error
+ const filtered = filterByIgnorePatterns(all, ['**/nonexistent/**']);
+ expect(filtered).toHaveLength(all.length);
+
+ // Filtering with a broad pattern should return fewer or equal results
+ const aggressive = filterByIgnorePatterns(all, ['**/*.ts']);
+ expect(aggressive.length).toBeLessThanOrEqual(all.length);
+ });
});
describe('Integration Test Prerequisites', () => {
it('should have SUPERMODEL_API_KEY to run integration tests', () => {
if (SKIP_INTEGRATION) {
- console.log('⚠️ SUPERMODEL_API_KEY not set - skipping integration tests');
+ console.log('SUPERMODEL_API_KEY not set - skipping integration tests');
console.log(' Set the environment variable to run integration tests');
} else {
- console.log('✓ SUPERMODEL_API_KEY is set');
+ console.log('SUPERMODEL_API_KEY is set');
}
- expect(true).toBe(true); // Always passes
+ expect(true).toBe(true);
});
});
diff --git a/src/dead-code.ts b/src/dead-code.ts
index a1ec4f6..d93c964 100644
--- a/src/dead-code.ts
+++ b/src/dead-code.ts
@@ -1,187 +1,74 @@
import { minimatch } from 'minimatch';
-import { CodeGraphNode, CodeGraphRelationship } from '@supermodeltools/sdk';
+import type { DeadCodeCandidate, DeadCodeAnalysisResponse, DeadCodeAnalysisMetadata } from '@supermodeltools/sdk';
+import { escapeTableCell } from './markdown';
-/**
- * Represents a potentially unused function found in the codebase.
- */
-export interface DeadCodeResult {
- id: string;
- name: string;
- filePath: string;
- startLine?: number;
- endLine?: number;
-}
-
-/** Default glob patterns for files to exclude from dead code analysis. */
-export const DEFAULT_EXCLUDE_PATTERNS = [
- '**/node_modules/**',
- '**/dist/**',
- '**/build/**',
- '**/.git/**',
- '**/vendor/**',
- '**/target/**',
- '**/*.test.ts',
- '**/*.test.tsx',
- '**/*.test.js',
- '**/*.test.jsx',
- '**/*.spec.ts',
- '**/*.spec.tsx',
- '**/*.spec.js',
- '**/*.spec.jsx',
- '**/__tests__/**',
- '**/__mocks__/**',
-];
-
-/** Glob patterns for files that are considered entry points. */
-export const ENTRY_POINT_PATTERNS = [
- '**/index.ts',
- '**/index.js',
- '**/main.ts',
- '**/main.js',
- '**/app.ts',
- '**/app.js',
- '**/*.test.*',
- '**/*.spec.*',
- '**/__tests__/**',
-];
-
-/** Function names that are considered entry points. */
-export const ENTRY_POINT_FUNCTION_NAMES = [
- 'main',
- 'run',
- 'start',
- 'init',
- 'setup',
- 'bootstrap',
- 'default',
- 'handler',
- 'GET', 'POST', 'PUT', 'DELETE', 'PATCH',
-];
-
-/**
- * Checks if a file path matches any entry point pattern.
- * @param filePath - The file path to check
- * @returns True if the file is an entry point
- */
-export function isEntryPointFile(filePath: string): boolean {
- return ENTRY_POINT_PATTERNS.some(pattern => minimatch(filePath, pattern));
-}
+export type { DeadCodeCandidate, DeadCodeAnalysisResponse, DeadCodeAnalysisMetadata };
/**
- * Checks if a function name is a common entry point name.
- * @param name - The function name to check
- * @returns True if the function name is an entry point
+ * Filters dead code candidates by user-provided ignore patterns.
+ * The API handles all analysis server-side; this is purely for
+ * client-side post-filtering on file paths.
*/
-export function isEntryPointFunction(name: string): boolean {
- const lowerName = name.toLowerCase();
- return ENTRY_POINT_FUNCTION_NAMES.some(ep => lowerName === ep.toLowerCase());
-}
-
-/**
- * Checks if a file should be ignored based on exclude patterns.
- * @param filePath - The file path to check
- * @param ignorePatterns - Additional patterns to ignore
- * @returns True if the file should be ignored
- */
-export function shouldIgnoreFile(filePath: string, ignorePatterns: string[] = []): boolean {
- const allPatterns = [...DEFAULT_EXCLUDE_PATTERNS, ...ignorePatterns];
- return allPatterns.some(pattern => minimatch(filePath, pattern));
-}
-
-/**
- * Analyzes a code graph to find functions that are never called.
- * @param nodes - All nodes from the code graph
- * @param relationships - All relationships from the code graph
- * @param ignorePatterns - Additional glob patterns to ignore
- * @returns Array of potentially unused functions
- */
-export function findDeadCode(
- nodes: CodeGraphNode[],
- relationships: CodeGraphRelationship[],
- ignorePatterns: string[] = []
-): DeadCodeResult[] {
- const functionNodes = nodes.filter(node =>
- node.labels?.includes('Function')
- );
-
- const callRelationships = relationships.filter(rel => rel.type === 'calls');
- const calledFunctionIds = new Set(callRelationships.map(rel => rel.endNode));
-
- const deadCode: DeadCodeResult[] = [];
-
- for (const node of functionNodes) {
- const props = node.properties || {};
- const filePath = props.filePath || props.file || '';
- const name = props.name || 'anonymous';
-
- if (calledFunctionIds.has(node.id)) {
- continue;
- }
-
- if (shouldIgnoreFile(filePath, ignorePatterns)) {
- continue;
- }
-
- if (isEntryPointFile(filePath)) {
- continue;
- }
-
- if (isEntryPointFunction(name)) {
- continue;
- }
-
- if (props.exported === true || props.isExported === true) {
- continue;
- }
-
- deadCode.push({
- id: node.id,
- name,
- filePath,
- startLine: props.startLine,
- endLine: props.endLine,
- });
- }
-
- return deadCode;
+export function filterByIgnorePatterns(
+ candidates: DeadCodeCandidate[],
+ ignorePatterns: string[]
+): DeadCodeCandidate[] {
+ if (ignorePatterns.length === 0) return candidates;
+ return candidates.filter(c => !ignorePatterns.some(p => minimatch(c.file, p)));
}
/**
- * Formats dead code results as a GitHub PR comment.
- * @param deadCode - Array of dead code results
- * @returns Markdown-formatted comment string
+ * Formats dead code analysis results as a GitHub PR comment.
*/
-export function formatPrComment(deadCode: DeadCodeResult[]): string {
- if (deadCode.length === 0) {
+export function formatPrComment(
+ candidates: DeadCodeCandidate[],
+ metadata?: DeadCodeAnalysisMetadata
+): string {
+ if (candidates.length === 0) {
return `## Dead Code Hunter
No dead code found! Your codebase is clean.`;
}
- const rows = deadCode
+ const rows = candidates
.slice(0, 50)
.map(dc => {
- const lineInfo = dc.startLine ? `L${dc.startLine}` : '';
- const fileLink = dc.startLine
- ? `${dc.filePath}#L${dc.startLine}`
- : dc.filePath;
- return `| \`${dc.name}\` | ${fileLink} | ${lineInfo} |`;
+ const lineInfo = dc.line ? `L${dc.line}` : '';
+ const fileLink = dc.line ? `${dc.file}#L${dc.line}` : dc.file;
+ const badge = dc.confidence === 'high' ? ':red_circle:' :
+ dc.confidence === 'medium' ? ':orange_circle:' : ':yellow_circle:';
+ return `| \`${escapeTableCell(dc.name)}\` | ${dc.type} | ${fileLink} | ${lineInfo} | ${badge} ${dc.confidence} |`;
})
.join('\n');
let comment = `## Dead Code Hunter
-Found **${deadCode.length}** potentially unused function${deadCode.length === 1 ? '' : 's'}:
+Found **${candidates.length}** potentially unused code element${candidates.length === 1 ? '' : 's'}:
-| Function | File | Line |
-|----------|------|------|
+| Name | Type | File | Line | Confidence |
+|------|------|------|------|------------|
${rows}`;
- if (deadCode.length > 50) {
- comment += `\n\n_...and ${deadCode.length - 50} more. See action output for full list._`;
+ if (candidates.length > 50) {
+ comment += `\n\n_...and ${candidates.length - 50} more. See action output for full list._`;
+ }
+
+ if (metadata) {
+ comment += `\n\nAnalysis summary
\n\n`;
+ comment += `- **Total declarations analyzed**: ${metadata.totalDeclarations}\n`;
+ comment += `- **Dead code candidates**: ${metadata.deadCodeCandidates}\n`;
+ comment += `- **Alive code**: ${metadata.aliveCode}\n`;
+ comment += `- **Analysis method**: ${metadata.analysisMethod}\n`;
+ if (metadata.transitiveDeadCount != null) {
+ comment += `- **Transitive dead**: ${metadata.transitiveDeadCount}\n`;
+ }
+ if (metadata.symbolLevelDeadCount != null) {
+ comment += `- **Symbol-level dead**: ${metadata.symbolLevelDeadCount}\n`;
+ }
+ comment += `\n `;
}
- comment += `\n\n---\n_Powered by [Supermodel](https://supermodeltools.com) graph analysis_`;
+ comment += `\n\n---\n_Powered by [Supermodel](https://supermodeltools.com) dead code analysis_`;
return comment;
}
diff --git a/src/index.ts b/src/index.ts
index f9d35f3..59ab1e1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -5,7 +5,8 @@ import * as fs from 'fs/promises';
import * as path from 'path';
import { randomUUID } from 'crypto';
import { Configuration, DefaultApi } from '@supermodeltools/sdk';
-import { findDeadCode, formatPrComment } from './dead-code';
+import type { DeadCodeAnalysisResponseAsync, DeadCodeAnalysisResponse } from '@supermodeltools/sdk';
+import { filterByIgnorePatterns, formatPrComment } from './dead-code';
/** Fields that should be redacted from logs */
const SENSITIVE_KEYS = new Set([
@@ -23,6 +24,9 @@ const SENSITIVE_KEYS = new Set([
]);
const MAX_VALUE_LENGTH = 1000;
+const MAX_POLL_ATTEMPTS = 90;
+const DEFAULT_RETRY_INTERVAL_MS = 10_000;
+const POLL_TIMEOUT_MS = 15 * 60 * 1000;
/**
* Safely serialize a value for logging, handling circular refs, BigInt, and large values.
@@ -33,28 +37,21 @@ function safeSerialize(value: unknown, maxLength = MAX_VALUE_LENGTH): string {
const seen = new WeakSet();
const serialized = JSON.stringify(value, (key, val) => {
- // Redact sensitive keys
if (key && SENSITIVE_KEYS.has(key.toLowerCase())) {
return '[REDACTED]';
}
-
- // Handle BigInt
if (typeof val === 'bigint') {
return val.toString();
}
-
- // Handle circular references
if (typeof val === 'object' && val !== null) {
if (seen.has(val)) {
return '[Circular]';
}
seen.add(val);
}
-
return val;
}, 2);
- // Truncate if too long
if (serialized && serialized.length > maxLength) {
return serialized.slice(0, maxLength) + '... [truncated]';
}
@@ -114,8 +111,50 @@ async function generateIdempotencyKey(workspacePath: string): Promise {
const commitHash = output.trim();
const repoName = path.basename(workspacePath);
- // Use UUID to ensure unique key per run (avoids 409 conflicts, scales to many concurrent users)
- return `${repoName}:deadcode:${commitHash}:${randomUUID()}`;
+ return `${repoName}:analysis:deadcode:${commitHash}:${randomUUID()}`;
+}
+
+function sleep(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+/**
+ * Polls the dead code analysis endpoint until the job completes or fails.
+ * The API returns 202 while processing; re-submitting the same request
+ * with the same idempotency key acts as a poll.
+ */
+async function pollForResult(
+ api: DefaultApi,
+ idempotencyKey: string,
+ zipBlob: Blob
+): Promise {
+ const startTime = Date.now();
+
+ for (let attempt = 1; attempt <= MAX_POLL_ATTEMPTS; attempt++) {
+ const response: DeadCodeAnalysisResponseAsync = await api.generateDeadCodeAnalysis({
+ idempotencyKey,
+ file: zipBlob,
+ });
+
+ if (response.status === 'completed' && response.result) {
+ return response.result;
+ }
+
+ if (response.status === 'failed') {
+ throw new Error(`Analysis job failed: ${response.error || 'unknown error'}`);
+ }
+
+ const elapsed = Date.now() - startTime;
+ if (elapsed >= POLL_TIMEOUT_MS) {
+ throw new Error(`Analysis timed out after ${Math.round(elapsed / 1000)}s (job: ${response.jobId})`);
+ }
+
+ const retryMs = (response.retryAfter ?? DEFAULT_RETRY_INTERVAL_MS / 1000) * 1000;
+ core.info(`Job ${response.jobId} status: ${response.status} (attempt ${attempt}/${MAX_POLL_ATTEMPTS}, retry in ${retryMs / 1000}s)`);
+ await sleep(retryMs);
+ }
+
+ throw new Error(`Analysis did not complete within ${MAX_POLL_ATTEMPTS} polling attempts`);
}
async function run(): Promise {
@@ -128,7 +167,7 @@ async function run(): Promise {
const commentOnPr = core.getBooleanInput('comment-on-pr');
const failOnDeadCode = core.getBooleanInput('fail-on-dead-code');
- const ignorePatterns = JSON.parse(core.getInput('ignore-patterns') || '[]');
+ const ignorePatterns: string[] = JSON.parse(core.getInput('ignore-patterns') || '[]');
const workspacePath = process.env.GITHUB_WORKSPACE || process.cwd();
@@ -140,7 +179,7 @@ async function run(): Promise {
// Step 2: Generate idempotency key
const idempotencyKey = await generateIdempotencyKey(workspacePath);
- // Step 3: Call Supermodel API
+ // Step 3: Call Supermodel dead code analysis API
core.info('Analyzing codebase with Supermodel...');
const config = new Configuration({
@@ -153,29 +192,28 @@ async function run(): Promise {
const zipBuffer = await fs.readFile(zipPath);
const zipBlob = new Blob([zipBuffer], { type: 'application/zip' });
- const response = await api.generateSupermodelGraph({
- idempotencyKey,
- file: zipBlob,
- });
-
- // Step 4: Analyze for dead code
- const nodes = response.graph?.nodes || [];
- const relationships = response.graph?.relationships || [];
+ const result = await pollForResult(api, idempotencyKey, zipBlob);
- const deadCode = findDeadCode(nodes, relationships, ignorePatterns);
+ // Step 4: Apply client-side ignore patterns
+ const candidates = filterByIgnorePatterns(result.deadCodeCandidates, ignorePatterns);
- core.info(`Found ${deadCode.length} potentially unused functions`);
+ core.info(`Found ${candidates.length} potentially unused code elements (${result.metadata.totalDeclarations} declarations analyzed)`);
+ core.info(`Analysis method: ${result.metadata.analysisMethod}`);
+ core.info(`Alive: ${result.metadata.aliveCode}, Entry points: ${result.entryPoints.length}, Root files: ${result.metadata.rootFilesCount ?? 'n/a'}`);
+ for (const dc of candidates) {
+ core.info(` [${dc.confidence}] ${dc.type} ${dc.name} @ ${dc.file}:${dc.line} — ${dc.reason}`);
+ }
// Step 5: Set outputs
- core.setOutput('dead-code-count', deadCode.length);
- core.setOutput('dead-code-json', JSON.stringify(deadCode));
+ core.setOutput('dead-code-count', candidates.length);
+ core.setOutput('dead-code-json', JSON.stringify(candidates));
// Step 6: Post PR comment if enabled
if (commentOnPr && github.context.payload.pull_request) {
const token = core.getInput('github-token') || process.env.GITHUB_TOKEN;
if (token) {
const octokit = github.getOctokit(token);
- const comment = formatPrComment(deadCode);
+ const comment = formatPrComment(candidates, result.metadata);
await octokit.rest.issues.createComment({
owner: github.context.repo.owner,
@@ -194,25 +232,21 @@ async function run(): Promise {
await fs.unlink(zipPath);
// Step 8: Fail if configured and dead code found
- if (deadCode.length > 0 && failOnDeadCode) {
- core.setFailed(`Found ${deadCode.length} potentially unused functions`);
+ if (candidates.length > 0 && failOnDeadCode) {
+ core.setFailed(`Found ${candidates.length} potentially unused code elements`);
}
} catch (error: any) {
- // Log error details for debugging (using debug level for potentially sensitive data)
core.info('--- Error Debug Info ---');
core.info(`Error type: ${error?.constructor?.name ?? 'unknown'}`);
core.info(`Error message: ${error?.message ?? 'no message'}`);
core.info(`Error name: ${error?.name ?? 'no name'}`);
- // Check various error structures used by different HTTP clients
- // Use core.debug for detailed/sensitive info, core.info for safe summaries
try {
if (error?.response) {
core.info(`Response status: ${error.response.status ?? 'unknown'}`);
core.info(`Response statusText: ${error.response.statusText ?? 'unknown'}`);
core.info(`Response data: ${safeSerialize(error.response.data)}`);
- // Headers may contain sensitive values - use debug level
core.debug(`Response headers: ${safeSerialize(redactSensitive(error.response.headers))}`);
}
if (error?.body) {
@@ -235,11 +269,9 @@ async function run(): Promise {
let errorMessage = 'An unknown error occurred';
let helpText = '';
- // Try multiple error structures
const status = error?.response?.status || error?.status || error?.statusCode;
let apiMessage = '';
- // Try to extract message from various locations
try {
apiMessage =
error?.response?.data?.message ||
@@ -262,7 +294,6 @@ async function run(): Promise {
} else if (status === 500) {
errorMessage = apiMessage || 'Internal server error';
- // Check for common issues and provide guidance
if (apiMessage.includes('Nested archives')) {
helpText = 'Your repository contains nested archive files (.zip, .tar, etc.). ' +
'Add them to .gitattributes with "export-ignore" to exclude from analysis. ' +
diff --git a/src/markdown.ts b/src/markdown.ts
new file mode 100644
index 0000000..7c6e871
--- /dev/null
+++ b/src/markdown.ts
@@ -0,0 +1,6 @@
+/**
+ * Escapes pipe characters for safe rendering inside markdown tables.
+ */
+export function escapeTableCell(text: string): string {
+ return text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
+}