Skip to content
Back to skills

Refactor Scan

ASecurity

Scan committed code, packages, or the full codebase for refactoring opportunities and create reviewable findings.

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 30, 2026
ai-agentstypescriptpythongobashnodedebuggingrefactoringgitapi

Works with

  • api
  • mcp

Security analysis

A93/100
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned September 30, 2026

npx -y skills add jskswamy/skills --skill refactor-scan --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Refactor Scan?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Refactor Scan
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jskswamy-refactor-scan/badge)](https://www.skillsdirectory.com/skills/jskswamy-refactor-scan)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: refactor-scan
description: Scan committed code, packages, or the full codebase for refactoring opportunities and create reviewable findings.
---

# Refactor Scan Skill

Scan code for refactoring opportunities. Supports three scopes:

- **diff** (default): scan committed changes against the base SHA. Single scanner + single validator (preserves prior behavior).
- **package**: scan one package directory. Single scanner shard.
- **all**: scan the entire indexed codebase. Sharded scanners (one per package), then a synthesizer that produces a human-reviewable `findings.md`, then per-section validators that create rich beads issues.

For `package` and `all` scopes, the pipeline persists state to `.refactor-scan/<ISO-timestamp>/` so a dropped session can be resumed by re-running the command.

## Argument Parsing

| Argument | Type | Default | Description |
|----------|------|---------|-------------|
| `--scope` | string | `diff` | Scan scope: `diff`, `package <path>`, or `all`. |
| `--base` | string | auto-detected | Only meaningful with `--scope=diff`. Base SHA. Auto-detects: `@{u}`, or `merge-base HEAD main`, or prompts. |
| `--limit` | integer | `8` | Maximum architectural issues for the synthesizer to produce. Singletons unaffected. Only meaningful with `--scope=package` or `--scope=all`. |
| `--fresh` | boolean | `false` | Force a new scan even if an in-flight working dir exists. |
| `--clean` | boolean | `false` | Remove completed scans (`.completed` marker present) older than 30 days from `.refactor-scan/`, then exit. |

## Execution Flow

### Step 0a: Handle --clean

If `--clean` is set:

```bash
find .refactor-scan -maxdepth 1 -type d -mtime +30 -exec test -f {}/.completed \; -print -exec rm -rf {} \;
```

Print a count of removed scans and exit. Do not run any scan.

### Step 0b: Resume Detection

If `--scope` is `package` or `all`, check for an in-flight working dir:

```bash
ls -1d .refactor-scan/*/ 2>/dev/null | sort -r | head -1
```

If a directory exists, inspect its state:

| State on disk | Detected stage | Resume action |
|---|---|---|
| no `candidates/` or empty | `scanning` | restart scan in this dir |
| `candidates/*.yaml` exist, no `findings.md` | `synthesis` | skip to Stage 2 |
| `findings.md` exists, no `.proceeded` | `review-gate` | skip to Stage 3 (re-prompt) |
| `.proceeded` exists, no `.completed` | `validating` | skip to Stage 4; cross-check `bd search` to skip already-created issues |
| `.completed` exists | `done` | print `report.md` and suggest `--clean` |

Use AskUserQuestion to prompt:

```
Found in-flight scan from <ts> (scope: <scope>)
Stage: <detected stage>

Resume this scan, or start fresh?
○ Resume — pick up at <stage>
○ Fresh — archive existing dir as .refactor-scan/<ts>.archived/ and start new
```

If `--fresh` is set, skip the prompt and archive immediately. Set `WORKING_DIR` to the resume target (or new dir) and proceed.

If no in-flight dir exists (or scope is `diff`), `WORKING_DIR` is unset (diff scope skips the working dir entirely).

### Step 0c: Ensure Codebase is Indexed

Required for all scopes (the diff scope already needed this).

1. Call `codebase-memory-mcp` `list_projects` to check availability. If the MCP server is not available:
   ```
   codebase-memory-mcp not found. Cannot run semantic scan.
   Ensure it is configured in your MCP settings.
   ```
   Exit.

2. Determine the project name from the repo:
   ```bash
   basename $(git rev-parse --show-toplevel)
   ```
   If the project is not in `list_projects`, automatically run `/codebase:index`.

3. For `--scope=package` or `--scope=all`, if `last_indexed` (from `.claude/codebase.local.md`) is missing or >24 hours old, run `/codebase:index` automatically. For `--scope=diff`, only re-index if the project is missing entirely (preserve current freshness behavior).

### Step 1: Determine Base SHA

**Skip this step unless `--scope=diff`.**

If `--base` is provided, use it directly.

If not provided, auto-detect:
```bash
git rev-parse @{u} 2>/dev/null
```
If that fails:
```bash
git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null
```
If all fail, use AskUserQuestion:
```
No upstream branch detected. What SHA should I compare against?
```

### Step 2: Build Scanner Inputs

Branch on scope:

**scope=diff:**
1. Resolve the base SHA per Step 1.
2. Run `git diff BASE..HEAD` and scan for new function definitions (existing language patterns: Go `^+.*func `, Python `^+.*def `, TypeScript `^+.*(function |const .* = |=> )`).
3. If no new function definitions found, print `No new function definitions in diff — skipping scan.` and exit cleanly.
4. Set `SHARDS = [{scope: "diff", diff: <full diff>, output: "<WORKING_DIR_OR_TMP>/candidates/diff.yaml"}]` (where `WORKING_DIR_OR_TMP` is `WORKING_DIR` if set, otherwise a temp dir).

**scope=package <path>:**
1. Validate `<path>` is a real directory under the repo. If not, exit with an error.
2. Set `SHARDS = [{scope: "package", package: "<path>", output: "<WORKING_DIR>/candidates/<path-with-slashes-as-dashes>.yaml"}]`.

**scope=all:**
1. Enumerate packages by querying `codebase-memory-mcp` `search_graph` with `label: "Folder"` and a generous `limit` (e.g. 200). The architecture API does NOT return a packages list; Folder nodes are the source of truth.
2. From the returned Folder list, select package-level folders by filtering:
   - Skip Folder names beginning with `.` (e.g. `.git`, `.beads`, `.claude-plugin`).
   - Skip empty containers like `agents`, `commands`, `skills`, `hooks`, `templates`, `themes`, `styles`, `fixtures` when they appear directly under the project root — they are organizational, not packages.
   - Prefer Folder nodes that contain at least one `File` node with a `Function` or `Method` defined (use `query_graph` or rely on the index's `DEFINES` edges to filter; if the runtime cost is too high, fall back to all non-skipped folders).
   - For Go-style repos, package-level folders are typically direct children of `cmd/`, `internal/`, `pkg/`, or the project root.
3. For each selected package, append `{scope: "all", package: "<file_path>", output: "<WORKING_DIR>/candidates/<path-with-slashes-as-dashes>.yaml"}` to `SHARDS`. Use the Folder node's `file_path` field as the package identifier.
4. If filtering produces zero packages, fall back to "all non-dot top-level Folder nodes" so the scan still runs.

Create `<WORKING_DIR>/candidates/` if it does not exist.

Write `<WORKING_DIR>/meta.yaml`:
```yaml
scope: <scope>
base_sha: <base SHA or "">
project: <project>
started_at: <ISO 8601>
limit: <limit>
```

### Step 3: Dispatch Scanner Subagents

Dispatch one `refactor-scanner` agent per shard. For `scope=all` with many shards, dispatch in parallel batches of 5 (use the dispatching-parallel-agents skill).

For each shard, the subagent prompt must include:
- The shard's `scope`, `package` (if any), `diff` (if diff scope), `task_title` (diff only), `task_description` (diff only), `base_sha`, `project`, and `output_path` (set to the shard's `output`).
- Instruction: write your YAML output to `output_path` and return a one-line `SCAN COMPLETE` summary.

Collect each shard's report. Verify each shard's `output_path` file exists. If any shard fails to write its file, capture the error in `<WORKING_DIR>/scanner-errors.log` and continue with remaining shards.

After all shards complete:
- Count total candidates across all yaml files (sum of `candidates` list lengths).
- If total candidates is `0` and scope is `diff`, print `No refactoring opportunities found.` and exit cleanly.
- If total candidates is `0` and scope is `package` or `all`, write a stub `findings.md` saying "No refactoring candidates were produced by the scanner." and skip to Stage 5 (Report).

### Step 4: Synthesizer (Stage 2)

**Skip this step if `--scope=diff`** — diff scope uses the legacy validator path directly (Step 5 below has a diff branch).

Dispatch a single `refactor-synthesizer` agent with:
- `working_dir`: `<WORKING_DIR>`
- `scope`: scope value
- `limit`: `--limit` value (default 8)
- `project`: project name

The agent reads `<WORKING_DIR>/candidates/*.yaml`, writes `<WORKING_DIR>/findings.md`, and returns a `SYNTHESIS COMPLETE` summary with counts.

If `findings.md` was not written, treat as a hard failure: print the agent's error and exit (the working dir is preserved for debugging).

### Step 4b: Human Review Gate (Stage 3)

**Skip this step if `--scope=diff`.**

Print to the user:

```
Findings written to <WORKING_DIR>/findings.md

  Architectural issues: <N>
  Singletons reported:  <M>

Review the file:
  • Delete sections you don't want filed
  • Edit titles, priorities, descriptions, target shape
  • Promote singletons by moving them into a new section above
  • Add context anywhere

Reply "proceed" to file beads issues, or "abort" to stop.
```

Use AskUserQuestion to capture the response (single-select: Proceed / Abort).

- If **Proceed**: write a `<WORKING_DIR>/.proceeded` marker file (`touch`), then continue to Step 5.
- If **Abort**: leave the working dir on disk, print "Scan aborted. Findings preserved at <WORKING_DIR>/findings.md. Re-run /refactor:scan to resume." and exit cleanly.

If this step is reached during a resume from `.proceeded` already present, skip the prompt and continue.

### Step 5: Validator (Stage 4)

Branch on scope:

**scope=diff (legacy path, preserved):**

Dispatch a single `refactor-validator` agent with the legacy contract: pass the inline candidates from the single shard's yaml file (`<WORKING_DIR_OR_TMP>/candidates/diff.yaml`) reformatted as the legacy `CANDIDATES:` text block. The agent will run its old per-candidate flow.

(This branch will be removed in a future cleanup once the validator's legacy path is retired. For now, the diff scope behavior is unchanged from the user's perspective.)

**scope=package or scope=all:**

1. Read `<WORKING_DIR>/findings.md`. Parse it into sections by splitting on `^## ` headings (skip the file's H1, skip the `## Singletons` table — it is not a section to file).

2. For each architectural section, extract:
   - `section_id` — the leading number from the heading (e.g. `## 1. Extract Class: ...` → `1`)
   - `section_title` — the rest of the heading after the number
   - `affected_packages` — comma-split from the `**Affected packages:**` line
   - `confidence` — value of the `**Confidence:**` line
   - `suggested_priority` — integer from the `**Suggested priority:**` line (`P2` → `2`)
   - `evidence_refs` — comma-split from the `<!-- evidence-refs: ... -->` HTML comment
   - `section_body` — the full markdown body from after the metadata lines through to the next `^## ` (or end of file)

   If the user added a wholly new section by hand (no `evidence-refs` comment), set `evidence_refs = []`.

3. Dispatch one `refactor-validator` subagent per section, in parallel batches of 3. Each subagent receives all of the parsed fields plus `working_dir`, `scope`, `project`, and `scan_timestamp` (from `meta.yaml`).

4. During a resume from a `validating` state: before dispatching each section's subagent, run `bd search` with the section title's key terms. If a matching open issue exists, mark the section as `skip` (already-tracked) without dispatching.

5. Collect each subagent's `SECTION VALIDATED` reply. Aggregate by `status`:
   - `created` → `(issue_id, priority, title)`
   - `skip` → `(reason, issue_id?)`
   - `failed` → `(error)`

### Step 6: Report (Stage 5)

Write `<WORKING_DIR>/report.md` (for `package`/`all`) or print directly (for `diff`) using this format:

```
Refactoring scan complete (scope: <scope>, scan: <ts>).

Architectural issues created: <N>
  <issue-id>  P<priority>  <title>
  ...

Singletons (review and file manually if needed): <M>
  <pattern> in <file:func> — <reason no correlation>
  ...

Skipped (already tracked): <count>
  <issue-id>  <title>
  ...

Failed: <count>
  <section-title> — <error>
  ...
```

For `package`/`all` scopes only, append:

```
Working directory: <WORKING_DIR>/
  - findings.md (your reviewed version, preserved)
  - candidates/*.yaml (full evidence)
  - report.md (this report)

Run /refactor:scan --clean to remove scans older than 30 days.
```

Print the report to the user. For `package`/`all` scopes, write a `<WORKING_DIR>/.completed` marker file (`touch`).

For `diff` scope, no working dir to mark — exit cleanly.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.