feat: wire dead-code to /v1/analysis/dead-code endpoint#23
Conversation
Replace naive graph-edge counting with the API's dedicated dead code analysis endpoint which provides multi-phase reachability, transitive propagation, confidence levels, line numbers, and reasons. New flags: --min-confidence (high/medium/low), --limit Removed: --include-exports (handled by API) Closes supermodeltools#22
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
WalkthroughThe dead-code CLI was reworked to zip the repository, POST to /v1/analysis/dead-code (with min_confidence/limit), poll until completion, apply optional ignore globs, and render rich results (file, line, function, confidence, reason) or JSON. Changes
Sequence DiagramsequenceDiagram
participant CLI as CLI
participant Handler as deadcode.Handler
participant Zip as deadcode.zip
participant Client as api.Client
participant API as Remote API
CLI->>Handler: run dead-code (opts)
Handler->>Zip: createZip(targetDir)
alt git repo
Zip->>Zip: git archive -> temp.zip
else filesystem fallback
Zip->>Zip: walk files -> temp.zip
end
Zip-->>Handler: zipPath
Handler->>Client: DeadCode(ctx, zipPath, idempotencyKey, minConfidence, limit)
Client->>API: POST /v1/analysis/dead-code (upload)
API-->>Client: {jobId, status: "pending"}
loop poll until done
Client->>API: POST /v1/analysis/dead-code (repost jobId)
API-->>Client: {status: "processing"|"completed", result?}
end
Client-->>Handler: DeadCodeResult
Handler->>Handler: filterIgnored(result, patterns)
Handler->>CLI: printResults(table or JSON)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (7)
internal/deadcode/handler_test.go (2)
29-34: Minor: Simplify string checkingYou've got
outas astringalready, so you can usestrings.Contains(out, "unusedHelper")instead of converting to[]bytetwice. It's just a tiny bit cleaner:// Instead of: bytes.Contains([]byte(out), []byte("unusedHelper")) // You could do: strings.Contains(out, "unusedHelper")Same thing on line 32. Not a big deal though!
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/deadcode/handler_test.go` around lines 29 - 34, Replace the redundant bytes.Contains([]byte(out), []byte("...")) checks in the test (the variable out is a string) with strings.Contains(out, "..."); update the two occurrences that check for "unusedHelper" and "2 dead code candidate(s)" and ensure the test imports the "strings" package (and remove the "bytes" import if it becomes unused).
37-51: Consider adding a JSON output testYou've got
TestPrintResults_HumanandTestPrintResults_Empty, but no test for the JSON format path (ui.FormatJSON). Since the handler supports both output modes, it'd be nice to have a quick test that verifies JSON output works too. Something like:func TestPrintResults_JSON(t *testing.T) { result := &api.DeadCodeResult{ DeadCodeCandidates: []api.DeadCodeCandidate{ {File: "src/utils.ts", Line: 8, Name: "unusedHelper"}, }, } var buf bytes.Buffer err := printResults(&buf, result, ui.FormatJSON) if err != nil { t.Fatal(err) } if !strings.Contains(buf.String(), `"unusedHelper"`) { t.Errorf("expected JSON output with unusedHelper") } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/deadcode/handler_test.go` around lines 37 - 51, Add a new unit test that exercises the JSON output path of printResults: create a DeadCodeResult with at least one api.DeadCodeCandidate (e.g., File "src/utils.ts", Line 8, Name "unusedHelper"), write to a bytes.Buffer, call printResults(&buf, result, ui.FormatJSON), check err is nil, and assert the buffer string contains the candidate name (e.g., "unusedHelper"); place the test alongside TestPrintResults_Human/TestPrintResults_Empty and name it TestPrintResults_JSON so it verifies the JSON formatting branch.internal/api/client.go (2)
91-99: Consider usingurl.Valuesfor query string buildingBuilding query strings manually works, but it can get tricky with special characters. Using Go's
net/urlpackage is safer and more idiomatic:import "net/url" // ... endpoint := deadCodeEndpoint params := url.Values{} if minConfidence != "" { params.Set("min_confidence", minConfidence) } if limit > 0 { params.Set("limit", fmt.Sprintf("%d", limit)) } if len(params) > 0 { endpoint += "?" + params.Encode() }This handles URL encoding automatically. For your current use case (where values are just
high/medium/lowand integers), the manual approach works fine—buturl.Valuesis a good habit.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/api/client.go` around lines 91 - 99, The current manual query string construction around variables endpoint, deadCodeEndpoint, minConfidence and limit can produce incorrect encoding/escaping for special characters; replace the manual concat logic with net/url's url.Values: create params := url.Values{}, call params.Set("min_confidence", minConfidence) and params.Set("limit", fmt.Sprintf("%d", limit)) when applicable, then append "?" + params.Encode() to endpoint only if params is non-empty so query encoding is handled safely and cleanly.
106-121: Polling loop is duplicated fromAnalyzeThe polling logic here (lines 106-121) is nearly identical to lines 48-63 in
Analyze. That's not terrible for just 2 usages, but if you add more async endpoints later, you might want to extract a helper like:func (c *Client) pollUntilDone(ctx context.Context, poll func() (*JobResponse, error)) (*JobResponse, error)Just something to keep in mind for the future—not a blocker for this PR!
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/api/client.go` around lines 106 - 121, The polling logic duplicated between Analyze and the postZipTo loop should be extracted into a reusable helper on Client; create a method like pollUntilDone(ctx context.Context, poll func() (*JobResponse, error)) (*JobResponse, error) that encapsulates the wait/retry/select loop and error handling, then replace the polling blocks in Analyze and the postZipTo caller with calls to c.pollUntilDone passing a closure that invokes postZipTo (or the original Analyze poll function) so both paths reuse the same logic and return the final *JobResponse and error.cmd/deadcode.go (1)
41-42: Consider validating--min-confidencevaluesRight now
--min-confidenceaccepts any string, but the help text says it should behigh,medium, orlow. If someone typos it like--min-confidence=hgih, the API might return an error or (worse) silently ignore it.You could add a quick validation in the
RunEfunction:if opts.MinConfidence != "" { switch opts.MinConfidence { case "high", "medium", "low": // ok default: return fmt.Errorf("invalid --min-confidence value %q: must be high, medium, or low", opts.MinConfidence) } }Same idea for
--limitif negative values don't make sense (though the API probably handles that gracefully).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/deadcode.go` around lines 41 - 42, Validate the CLI flag values before executing the command: in the command's RunE (where opts is used) check opts.MinConfidence when non-empty and reject anything other than "high", "medium", or "low" with a clear error (e.g., return fmt.Errorf("invalid --min-confidence value %q: must be high, medium, or low", opts.MinConfidence)); also validate opts.Limit to ensure it's non-negative (return an error when negative) so the flags registered with c.Flags().StringVar(&opts.MinConfidence, ...) and c.Flags().IntVar(&opts.Limit, ...) are validated early and reliably.internal/deadcode/handler.go (1)
33-36: Hash is computed but only used for idempotency keyYou're calling
cache.HashFile(zipPath)here, but the hash is only used to build the idempotency key ("deadcode-"+hash[:16]). That's fine! But it means thecacheimport is a bit misleading—readers might expect caching behavior.If caching isn't planned, consider inlining a simpler hash computation or adding a comment explaining that the hash is just for idempotency, not caching.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/deadcode/handler.go` around lines 33 - 36, The code calls cache.HashFile(zipPath) only to build the idempotency key ("deadcode-"+hash[:16]), which makes the cache import misleading; either replace cache.HashFile with an inline/simple hash helper (e.g., compute SHA256 of zipPath or file contents in a small helper) and remove the cache import, or keep using cache.HashFile but add a clear comment next to the HashFile call (and above the idempotency key construction) stating the hash is used solely for idempotency and not for caching; locate the call to cache.HashFile(zipPath) and the idempotency key construction ("deadcode-"+hash[:16]) to apply the change.internal/deadcode/zip.go (1)
44-48: Swallowed error might hide useful infoWhen
gitArchivefails, the error is silently discarded and you fall back towalkZip. That's a reasonable strategy! But it might be helpful to log the error at debug level so folks can understand why git archive didn't work (e.g., dirty working tree, detached HEAD, git not installed).Something like:
if err := gitArchive(dir, dest); err == nil { return dest, nil } // Could add: log.Debug("git archive failed, falling back to walk: %v", err)Not a blocker—just something that could save debugging time later.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/deadcode/zip.go` around lines 44 - 48, The gitArchive error is currently discarded when isGitRepo(dir) is true, making failures opaque; update the block that calls gitArchive(dir, dest) to log the returned error at debug (or trace) level before falling back to walkZip so callers can see why git archive failed. Specifically, in the function using isGitRepo, gitArchive and walkZip, capture the error from gitArchive and call the package logger (or a passed logger) with a debug message including the error string, then continue to the existing fallback to walkZip.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/api/client.go`:
- Line 90: The function signature for Client.DeadCode has consecutive string
parameters `idempotencyKey string, minConfidence string` which the linter flags;
change the signature to combine them as `idempotencyKey, minConfidence string`
in the DeadCode method on type Client, and update any callers, interfaces, and
tests that reference `DeadCode` to use the new signature form so the parameter
types remain identical but declared in the combined style; ensure any generated
docs or usages (e.g., method declarations elsewhere) are updated accordingly.
In `@internal/deadcode/handler.go`:
- Line 17: The Options struct's Force bool is unused in Run; either implement
caching using the same pattern as internal/analyze/handler.go or remove the
flag/field and CLI wiring in cmd/deadcode.go. To implement caching: in the Run
function (deadcode handler) check if !opts.Force then call cache.Get(hash) and
return the cached result if present, and after obtaining results from the API
call call cache.Put(hash, result); ensure you reference Options.Force, the Run
function, and use cache.Get/cache.Put consistent with
internal/analyze/handler.go. If you choose removal: delete Force from Options
and remove the --force flag handling in cmd/deadcode.go and any references in
Run.
In `@internal/deadcode/zip.go`:
- Around line 82-110: The current filepath.Walk callback does not skip symlinks
which can traverse outside the repo or cause loops; update the traversal to
detect and skip symlinks—either switch to filepath.WalkDir and use
fs.DirEntry.Type()/entry.Type() to check for os.ModeSymlink, or keep
filepath.Walk and test info.Mode()&os.ModeSymlink != 0 at the start of the
callback and return nil (or filepath.SkipDir for symlinked dirs) to skip them;
ensure this check is performed before opening files (before os.Open(path) and
zw.Create calls) so symlinked files and directories are not added to the zip.
---
Nitpick comments:
In `@cmd/deadcode.go`:
- Around line 41-42: Validate the CLI flag values before executing the command:
in the command's RunE (where opts is used) check opts.MinConfidence when
non-empty and reject anything other than "high", "medium", or "low" with a clear
error (e.g., return fmt.Errorf("invalid --min-confidence value %q: must be high,
medium, or low", opts.MinConfidence)); also validate opts.Limit to ensure it's
non-negative (return an error when negative) so the flags registered with
c.Flags().StringVar(&opts.MinConfidence, ...) and c.Flags().IntVar(&opts.Limit,
...) are validated early and reliably.
In `@internal/api/client.go`:
- Around line 91-99: The current manual query string construction around
variables endpoint, deadCodeEndpoint, minConfidence and limit can produce
incorrect encoding/escaping for special characters; replace the manual concat
logic with net/url's url.Values: create params := url.Values{}, call
params.Set("min_confidence", minConfidence) and params.Set("limit",
fmt.Sprintf("%d", limit)) when applicable, then append "?" + params.Encode() to
endpoint only if params is non-empty so query encoding is handled safely and
cleanly.
- Around line 106-121: The polling logic duplicated between Analyze and the
postZipTo loop should be extracted into a reusable helper on Client; create a
method like pollUntilDone(ctx context.Context, poll func() (*JobResponse,
error)) (*JobResponse, error) that encapsulates the wait/retry/select loop and
error handling, then replace the polling blocks in Analyze and the postZipTo
caller with calls to c.pollUntilDone passing a closure that invokes postZipTo
(or the original Analyze poll function) so both paths reuse the same logic and
return the final *JobResponse and error.
In `@internal/deadcode/handler_test.go`:
- Around line 29-34: Replace the redundant bytes.Contains([]byte(out),
[]byte("...")) checks in the test (the variable out is a string) with
strings.Contains(out, "..."); update the two occurrences that check for
"unusedHelper" and "2 dead code candidate(s)" and ensure the test imports the
"strings" package (and remove the "bytes" import if it becomes unused).
- Around line 37-51: Add a new unit test that exercises the JSON output path of
printResults: create a DeadCodeResult with at least one api.DeadCodeCandidate
(e.g., File "src/utils.ts", Line 8, Name "unusedHelper"), write to a
bytes.Buffer, call printResults(&buf, result, ui.FormatJSON), check err is nil,
and assert the buffer string contains the candidate name (e.g., "unusedHelper");
place the test alongside TestPrintResults_Human/TestPrintResults_Empty and name
it TestPrintResults_JSON so it verifies the JSON formatting branch.
In `@internal/deadcode/handler.go`:
- Around line 33-36: The code calls cache.HashFile(zipPath) only to build the
idempotency key ("deadcode-"+hash[:16]), which makes the cache import
misleading; either replace cache.HashFile with an inline/simple hash helper
(e.g., compute SHA256 of zipPath or file contents in a small helper) and remove
the cache import, or keep using cache.HashFile but add a clear comment next to
the HashFile call (and above the idempotency key construction) stating the hash
is used solely for idempotency and not for caching; locate the call to
cache.HashFile(zipPath) and the idempotency key construction
("deadcode-"+hash[:16]) to apply the change.
In `@internal/deadcode/zip.go`:
- Around line 44-48: The gitArchive error is currently discarded when
isGitRepo(dir) is true, making failures opaque; update the block that calls
gitArchive(dir, dest) to log the returned error at debug (or trace) level before
falling back to walkZip so callers can see why git archive failed. Specifically,
in the function using isGitRepo, gitArchive and walkZip, capture the error from
gitArchive and call the package logger (or a passed logger) with a debug message
including the error string, then continue to the existing fallback to walkZip.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0eda1d17-7361-4fed-a17f-fe8dffdb65f2
📒 Files selected for processing (6)
cmd/deadcode.gointernal/api/client.gointernal/api/types.gointernal/deadcode/handler.gointernal/deadcode/handler_test.gointernal/deadcode/zip.go
|
|
||
| // DeadCode uploads a repository ZIP and runs dead code analysis, | ||
| // polling until the async job completes and returning the result. | ||
| func (c *Client) DeadCode(ctx context.Context, zipPath, idempotencyKey string, minConfidence string, limit int) (*DeadCodeResult, error) { |
There was a problem hiding this comment.
Fix: Combine consecutive string parameters (pipeline failure)
The linter is unhappy because you have idempotencyKey string, minConfidence string when they could be combined. This is causing the CI to fail.
🔧 Proposed fix
-func (c *Client) DeadCode(ctx context.Context, zipPath, idempotencyKey string, minConfidence string, limit int) (*DeadCodeResult, error) {
+func (c *Client) DeadCode(ctx context.Context, zipPath, idempotencyKey, minConfidence string, limit int) (*DeadCodeResult, error) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (c *Client) DeadCode(ctx context.Context, zipPath, idempotencyKey string, minConfidence string, limit int) (*DeadCodeResult, error) { | |
| func (c *Client) DeadCode(ctx context.Context, zipPath, idempotencyKey, minConfidence string, limit int) (*DeadCodeResult, error) { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/api/client.go` at line 90, The function signature for
Client.DeadCode has consecutive string parameters `idempotencyKey string,
minConfidence string` which the linter flags; change the signature to combine
them as `idempotencyKey, minConfidence string` in the DeadCode method on type
Client, and update any callers, interfaces, and tests that reference `DeadCode`
to use the new signature form so the parameter types remain identical but
declared in the combined style; ensure any generated docs or usages (e.g.,
method declarations elsewhere) are updated accordingly.
internal/deadcode/handler.go
Outdated
| Force bool // bypass cache | ||
| Output string // "human" | "json" | ||
| IncludeExports bool // include exported (public) functions in results | ||
| Force bool // bypass cache |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if --force flag is still wired up in cmd/deadcode.go
rg -n "Force" cmd/deadcode.go internal/deadcode/handler.goRepository: supermodeltools/cli
Length of output: 244
🏁 Script executed:
cat -n internal/deadcode/handler.go | head -60Repository: supermodeltools/cli
Length of output: 2024
🏁 Script executed:
cat -n internal/analyze/handler.go | head -80Repository: supermodeltools/cli
Length of output: 2820
🏁 Script executed:
grep -rn "cache\|Force" cmd/deadcode.go internal/deadcode/ --include="*.go" -B 2 -A 2Repository: supermodeltools/cli
Length of output: 2029
The Force flag doesn't actually do anything
So here's the thing: you've got Force bool in Options (line 17) and the CLI flag is wired up in cmd/deadcode.go saying "re-analyze even if a cached result exists". But if you look at the Run function, you never actually check this flag. Plus, there's no cache.Get() or cache.Put() calls anywhere—you just hash the file and use that hash for the idempotency key.
Compare that to internal/analyze/handler.go, which does the full caching dance:
if !force {
if g, _ := cache.Get(hash); g != nil {
return g, hash, nil // use cached result
}
}
// ... call API ...
cache.Put(hash, g) // save the resultSo either:
- Caching should be implemented: Copy the pattern from the analyze handler and actually store/retrieve dead-code results
- Or the flag should be removed: If dead-code analysis shouldn't be cached (maybe results are too volatile?), then ditch the
Forcefield and the--forceCLI flag so you're not promising something that doesn't exist
What was the intention here?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/deadcode/handler.go` at line 17, The Options struct's Force bool is
unused in Run; either implement caching using the same pattern as
internal/analyze/handler.go or remove the flag/field and CLI wiring in
cmd/deadcode.go. To implement caching: in the Run function (deadcode handler)
check if !opts.Force then call cache.Get(hash) and return the cached result if
present, and after obtaining results from the API call call cache.Put(hash,
result); ensure you reference Options.Force, the Run function, and use
cache.Get/cache.Put consistent with internal/analyze/handler.go. If you choose
removal: delete Force from Options and remove the --force flag handling in
cmd/deadcode.go and any references in Run.
| return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { | ||
| if err != nil { | ||
| return err | ||
| } | ||
| rel, err := filepath.Rel(dir, path) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if info.IsDir() { | ||
| if skipDirs[info.Name()] { | ||
| return filepath.SkipDir | ||
| } | ||
| return nil | ||
| } | ||
| if strings.HasPrefix(info.Name(), ".") || info.Size() > 10<<20 { | ||
| return nil | ||
| } | ||
| w, err := zw.Create(filepath.ToSlash(rel)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| f, err := os.Open(path) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer f.Close() | ||
| _, err = io.Copy(w, f) | ||
| return err | ||
| }) |
There was a problem hiding this comment.
Heads up: symlinks could cause surprises
filepath.Walk follows symlinks by default. If someone has a symlink pointing outside the repo (or worse, a symlink loop), this could either include unexpected files or hang.
A couple options:
- Use
filepath.WalkDir(Go 1.16+) withfs.DirEntry.Type()to detect symlinks and skip them - Check
info.Mode()&os.ModeSymlink != 0and skip
Here's a quick fix if you want to skip symlinks:
🛡️ Proposed fix to skip symlinks
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
+ // Skip symlinks to avoid following links outside the repo or loops
+ if info.Mode()&os.ModeSymlink != 0 {
+ return nil
+ }
rel, err := filepath.Rel(dir, path)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { | |
| if err != nil { | |
| return err | |
| } | |
| rel, err := filepath.Rel(dir, path) | |
| if err != nil { | |
| return err | |
| } | |
| if info.IsDir() { | |
| if skipDirs[info.Name()] { | |
| return filepath.SkipDir | |
| } | |
| return nil | |
| } | |
| if strings.HasPrefix(info.Name(), ".") || info.Size() > 10<<20 { | |
| return nil | |
| } | |
| w, err := zw.Create(filepath.ToSlash(rel)) | |
| if err != nil { | |
| return err | |
| } | |
| f, err := os.Open(path) | |
| if err != nil { | |
| return err | |
| } | |
| defer f.Close() | |
| _, err = io.Copy(w, f) | |
| return err | |
| }) | |
| return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { | |
| if err != nil { | |
| return err | |
| } | |
| // Skip symlinks to avoid following links outside the repo or loops | |
| if info.Mode()&os.ModeSymlink != 0 { | |
| return nil | |
| } | |
| rel, err := filepath.Rel(dir, path) | |
| if err != nil { | |
| return err | |
| } | |
| if info.IsDir() { | |
| if skipDirs[info.Name()] { | |
| return filepath.SkipDir | |
| } | |
| return nil | |
| } | |
| if strings.HasPrefix(info.Name(), ".") || info.Size() > 10<<20 { | |
| return nil | |
| } | |
| w, err := zw.Create(filepath.ToSlash(rel)) | |
| if err != nil { | |
| return err | |
| } | |
| f, err := os.Open(path) | |
| if err != nil { | |
| return err | |
| } | |
| defer f.Close() | |
| _, err = io.Copy(w, f) | |
| return err | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/deadcode/zip.go` around lines 82 - 110, The current filepath.Walk
callback does not skip symlinks which can traverse outside the repo or cause
loops; update the traversal to detect and skip symlinks—either switch to
filepath.WalkDir and use fs.DirEntry.Type()/entry.Type() to check for
os.ModeSymlink, or keep filepath.Walk and test info.Mode()&os.ModeSymlink != 0
at the start of the callback and return nil (or filepath.SkipDir for symlinked
dirs) to skip them; ensure this check is performed before opening files (before
os.Open(path) and zw.Create calls) so symlinked files and directories are not
added to the zip.
- dead-code: --ignore <glob> filters candidates client-side (repeatable, supports ** across segments). Implemented without new dependencies. - config: SUPERMODEL_API_KEY and SUPERMODEL_API_BASE env vars override config file values, enabling CI/CD usage without a config file. - dead-code summary line now reflects post-filter candidate count. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes #22
Summary
Replaces naive graph-edge counting with the API's dedicated dead code analysis endpoint (
POST /v1/analysis/dead-code), which provides multi-phase reachability, transitive propagation, confidence levels, line numbers, and reasons.Before (main)
No confidence, no line numbers, no reasons, no file paths on most results. Just "no incoming calls edge" — misses transitive dead code, framework entry points, etc.
After (this PR)
Changes
internal/api/client.go— addDeadCode()method with async polling, extractpostZipTo()helperinternal/api/types.go— addDeadCodeResult,DeadCodeCandidate,AliveCode,EntryPointtypesinternal/deadcode/handler.go— rewritten to call API endpoint, display rich resultsinternal/deadcode/zip.go— zip helper (VSA, copied from analyze slice)cmd/deadcode.go— new flags:--min-confidence,--limit; removed--include-exportsinternal/deadcode/handler_test.go— updated to test new output formatTest plan
go test ./...— all passsupermodel dead-code --min-confidence high --limit 10returns rich resultssupermodel dead-code -o jsonreturns structured dataSummary by CodeRabbit
New Features
Changes
Tests