| name | seam-finder |
| description | Identify the safest cut-points in an existing codebase for a planned change. Reads code-graph.json + a goal, ranks candidate seams by observability + funnel + read/write asymmetry, and outputs a prioritized list with evidence. |
| argument-hint | <goal-description> |
| context | fork |
Seam Finder — Where to Cut Safely
/seam-finder answers the question "given my goal, which existing module is the smallest, safest place to extend or split?" It applies the Fowler / Thoughtworks Uncovering Mainframe Seams scoring methodology to a deterministic dependency graph.
A "seam" is a junction point where program flow can be diverted without rewriting upstream or downstream code. Good seams:
- Sit at an observable boundary (HTTP route, queue, table, public class)
- Are a funnel — many modules converge on them
- Have read/write asymmetry — pure readers and pure writers are safer than mixed-use nodes
This skill is the bridge between brownfield discovery and /change, /refactor, /spec. Run it before deciding where in the existing code to land a change.
Usage
/seam-finder "add team invites to user onboarding"
/seam-finder "split the billing module into a separate service"
/seam-finder "introduce idempotency to webhook delivery"
Always pass a concrete goal. Without one, the skill ranks structural seams generically — useful for a refactor scan but less precise.
Prerequisites
specs/brownfield/code-graph.json must exist. If it does not:
- Suggest running
/code-map first.
- Stop. Do not try to score seams from grep results.
Staleness check: if .claude/state/graph-dirty.jsonl exists and is non-empty, the graph is behind recent edits — the graph-refresh Stop hook normally drains it, but if entries remain, patch first:
python3 .claude/skills/code-map/scripts/code_index/code_index.py --root . --out specs/brownfield/code-graph.json --files <dirty paths>.
If specs/brownfield/coupling-report.md exists, prefer it for hub data; otherwise compute on the fly from code-graph.json.
Outputs
| File | Purpose |
|---|
specs/brownfield/seams-<short-goal-slug>.md | Ranked seam candidates with scores, evidence, and recommended action |
Each candidate includes:
- Module / file path
observable_score (0–1) — how externally visible the boundary is
funnel_score (0–1) — fan-in + fan-out, normalized
asymmetry_score (0–1) — read vs write imbalance
total_score (0–1) — weighted combination (defaults: 0.4 / 0.4 / 0.2)
- Evidence — which files import or call it, with file:line refs
- Recommended action —
extend, wrap, split, introduce-adapter, avoid
Steps
Step 1 — Load Graph
test -f specs/brownfield/code-graph.json || { echo "Run /code-map first"; exit 1; }
Step 2 — Score Seams
node .claude/skills/seam-finder/scripts/score_seams.js \
--graph specs/brownfield/code-graph.json \
--goal "$GOAL" \
--out specs/brownfield/seams-${SLUG}.md
By default the scorer excludes test and fixture paths (tests/, __tests__/, spec/, *_test.*, *.spec.*, fixtures/, mocks/, examples/). Pass --include-tests or --include-fixtures if you genuinely want those ranked (e.g. you're refactoring the test suite itself).
The scorer:
- Computes per-node fan-in / fan-out from the graph.
- Tags nodes with an observable kind by file path heuristics:
routes/, controllers/, api/, handlers/, views/ → HTTP boundary (1.0)
queue/, events/, consumers/, producers/, webhooks/ → message boundary (0.9)
db/, repository/, models/, migrations/ → data boundary (0.8)
adapters/, gateways/, clients/, integrations/, providers/ → integration boundary (0.7)
services/, domain/, usecases/, core/, workflows/ → service layer (0.6)
- Public classes/exports in any other module → module boundary (0.5)
- Private helpers, deeply nested utils → internal (0.1)
- Scores read/write asymmetry from
imports direction and (if present) reads/writes edges.
- Filters by goal keywords: nodes whose path or symbols match goal terms get a goal-relevance bump (multiplier 1.5).
- Ranks descending by
total_score.
Step 3 — Recommend Action
For each top-N candidate, the skill labels a recommended action based on score profile:
| Score Profile | Action |
|---|
| High observable + high funnel | extend — add behaviour at the existing seam |
| High observable + low funnel | wrap — adapter at the boundary |
| Low observable + high funnel | introduce-adapter — extract a public seam first, then change |
| High asymmetry AND pure reader/writer (one side's fan is 0) | split — replicate writes via existing channel, rebuild reads |
| All scores low | avoid — not a seam; keep looking |
Step 4 — Verify Goal Fit
After the script writes the candidate list, re-read the top 3 candidates' source files and confirm they actually cover the goal's domain. The score is structural; the verification is semantic.
If none of the top 3 candidates fit the goal:
- Re-run with a refined goal phrase.
- Or, if the goal genuinely has no good seam, recommend
/spec to plan a new module rather than forcing a fit.
Step 4.5 — Phase Evaluation Gate
Spawn the evaluator agent (artifact mode) to validate seam candidates.
Agent invocation:
Spawn Agent with subagent_type="evaluator" and prompt:
- Phase: seam
- Artifact: specs/brownfield/seams-{goal-slug}.md
- Upstream: specs/brownfield/code-graph.json
- Rubric: Read .claude/templates/phase-eval-rubrics.json, key "seam"
- Iteration: 1 (increment on retry)
- Previous score: null (or previous iteration's weighted_average)
- Verify top 3 candidates reference files/functions that exist in the codebase (grep verification).
- Write result to specs/reviews/phase-seam-eval.json
Ratchet loop (max 2 iterations):
- If verdict is PASS — proceed to Step 5 (Hand Off) with eval summary.
- If verdict is FAIL — re-score or re-rank candidates based on findings. Re-run evaluator.
- Ratchet rule: weighted_average must be >= previous iteration. Revert on regression.
- After 2 iterations — present best version with findings.
Step 5 — Hand Off
Reference the chosen seam in the next step:
/change "<goal>" — extend seam: <path>
/refactor <path> (when the seam is the refactor target)
/spec (when no seam fits and a new module is the right call)
Goal-Slug Convention
The output filename uses a short, lowercase, dash-separated slug derived from the first 3–5 meaningful words of the goal.
| Goal | Slug | File |
|---|
| "add team invites to user onboarding" | team-invites | seams-team-invites.md |
| "split billing into a separate service" | split-billing | seams-split-billing.md |
Gotchas
- No graph, no seams. Do not attempt to score from filenames alone. Run
/code-map first.
- Observable score is heuristic. A
routes/ directory might be private; a lib/ directory might be the public API. Always re-read the top candidates to confirm.
- Goal relevance is keyword-based. Synonyms may miss the bump. If a candidate scores high but the goal mentions different terms, override manually.
- Hubs are not always seams. A god-module with fan-in 50 may be a refactor target, not a place to extend. The recommended action of
introduce-adapter exists for this case — extract a public seam first.
- Cycles muddy the waters. A module inside a cycle has unreliable fan-in/fan-out. The script flags cycle members so the recommendation does not silently land in tangled territory.
- Do not auto-execute the recommendation. Seam-finder produces a plan input, not a refactor. Always confirm with the user before acting on
extend / split.