@uniqbit/mate-core 0.15.5 → 0.16.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-plugin/.claude-plugin/plugin.json +2 -2
- package/claude-plugin/hooks/hooks.json +3 -6
- package/claude-plugin/hooks/session-guidance.mjs +8 -0
- package/claude-plugin/hooks/ts-loader.mjs +19 -0
- package/package.json +7 -5
- package/src/cli/commands/artifact/artifact.ts +19 -3
- package/src/cli/commands/artifact/finish/command.ts +183 -57
- package/src/cli/commands/artifact/finish/engine.ts +80 -91
- package/src/cli/commands/artifact/finish/finisher.ts +26 -33
- package/src/cli/commands/artifact/finish/git.ts +34 -23
- package/src/cli/commands/artifact/finish/index.ts +9 -3
- package/src/cli/commands/artifact/finish/openspec.ts +225 -97
- package/src/cli/commands/artifact/pending/command.ts +175 -0
- package/src/cli/commands/artifact/pending/discovery.ts +249 -0
- package/src/cli/commands/artifact/pending/index.ts +17 -0
- package/src/cli/commands/cap/index-cmd.ts +9 -1
- package/src/cli/commands/cap/index.ts +2 -6
- package/src/cli/commands/cap/tokensave.ts +4 -4
- package/src/cli/commands/companion/companion.ts +5 -1
- package/src/cli/commands/companion/link.ts +2 -2
- package/src/cli/commands/companion/sync.ts +92 -0
- package/src/cli/commands/doctor.ts +0 -3
- package/src/cli/commands/launch/shared.ts +23 -5
- package/src/cli/commands/report/collector.ts +72 -78
- package/src/cli/commands/report/contract.ts +40 -1
- package/src/cli/commands/report/highlight.ts +27 -0
- package/src/cli/commands/report/index.ts +11 -18
- package/src/cli/commands/report/renderer.ts +199 -2
- package/src/cli/commands/report/types.ts +26 -1
- package/src/cli/commands/shared/companion-selection.ts +107 -10
- package/src/cli/commands/studio/areas.ts +68 -0
- package/src/cli/commands/studio/index.ts +69 -0
- package/src/cli/commands/studio/inventory.ts +55 -0
- package/src/cli/commands/studio/mate-inventory.ts +43 -0
- package/src/cli/commands/studio/openspec-cli.ts +198 -0
- package/src/cli/commands/studio/payload.ts +184 -0
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +61 -0
- package/src/cli/commands/studio/server.ts +201 -0
- package/src/cli/commands/studio/snapshot.ts +63 -0
- package/src/cli/commands/studio/topology.ts +199 -0
- package/src/cli/commands/studio/views/client.ts +197 -0
- package/src/cli/commands/studio/views/companion-picker.tsx +91 -0
- package/src/cli/commands/studio/views/companion-selector.tsx +90 -0
- package/src/cli/commands/studio/views/dashboard/changes.tsx +107 -0
- package/src/cli/commands/studio/views/dashboard/index.tsx +19 -0
- package/src/cli/commands/studio/views/document.tsx +256 -0
- package/src/cli/commands/studio/views/error.tsx +20 -0
- package/src/cli/commands/studio/views/model.ts +34 -0
- package/src/cli/commands/studio/views/pairings.tsx +38 -0
- package/src/cli/commands/studio/views/skills/index.tsx +87 -0
- package/src/cli/commands/studio/views/specs/index.tsx +101 -0
- package/src/cli/commands/studio/views/styles.ts +389 -0
- package/src/cli/commands/studio/views/warnings.tsx +21 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +21 -0
- package/src/cli/commands/studio/views/workflow/steps.ts +329 -0
- package/src/cli/commands/studio/views/workflow/transcript.tsx +190 -0
- package/src/cli/commands/unwrap.ts +70 -0
- package/src/cli/commands/wrap.ts +164 -0
- package/src/cli/main.ts +68 -19
- package/src/cli/parse-flags.ts +36 -11
- package/src/cli/usage.ts +12 -3
- package/src/framework.ts +1 -7
- package/src/hooks/session-banner.ts +64 -11
- package/src/hooks/session-guidance.ts +40 -0
- package/src/hooks/validate-artifact-path.ts +108 -35
- package/src/lib/fs-utils.ts +9 -0
- package/src/lib/install.ts +33 -0
- package/src/lib/orchestrator/adapters/base.ts +14 -125
- package/src/lib/orchestrator/adapters/claude.ts +0 -11
- package/src/lib/orchestrator/adapters/opencode.ts +2 -32
- package/src/lib/orchestrator/companion-git-sync.ts +94 -84
- package/src/lib/orchestrator/config-store.ts +2 -21
- package/src/lib/orchestrator/editor.ts +12 -22
- package/src/lib/orchestrator/framework-context.ts +17 -6
- package/src/lib/orchestrator/global-config-store.ts +1 -1
- package/src/lib/orchestrator/launcher.ts +97 -7
- package/src/lib/orchestrator/opencode-guidance.ts +4 -56
- package/src/lib/orchestrator/projection-claude-entry.ts +198 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +120 -0
- package/src/lib/orchestrator/projection-companion-link.ts +62 -0
- package/src/lib/orchestrator/projection-entries.ts +377 -0
- package/src/lib/orchestrator/projection-record.ts +56 -0
- package/src/lib/orchestrator/projection-runtime-documents.ts +424 -0
- package/src/lib/orchestrator/projection-types.ts +169 -0
- package/src/lib/orchestrator/repo-local-registry.ts +37 -133
- package/src/lib/orchestrator/repo-local-store.ts +96 -0
- package/src/lib/orchestrator/setup-compatibilities.ts +1 -9
- package/src/lib/orchestrator/types.ts +1 -0
- package/src/lib/orchestrator/working-repo-projection.ts +366 -0
- package/src/lib/orchestrator/workspace-inventory.ts +1 -1
- package/src/lib/package-paths.ts +11 -1
- package/src/lib/public-npm.ts +2 -1
- package/src/lib/update-checker.ts +15 -9
- package/src/opencode/companion-hooks.ts +89 -245
- package/src/opencode/companion-policy.ts +35 -10
- package/src/opencode/index.ts +1 -0
- package/src/opencode/projected-guidance.ts +56 -0
- package/src/opencode/tui.tsx +13 -4
- package/src/playbooks/companion-guidance.ts +32 -116
- package/src/plugins.ts +0 -1
- package/src/runtime/companion-git-state.ts +156 -0
- package/src/runtime/companion-git.ts +203 -0
- package/src/runtime/companion-guidance.ts +222 -0
- package/src/runtime/companion-sync.ts +298 -0
- package/src/runtime/env-names.ts +30 -0
- package/src/runtime/env.ts +67 -35
- package/src/runtime/framework.ts +10 -0
- package/src/runtime/freshness.ts +58 -0
- package/src/runtime/index.ts +104 -0
- package/src/runtime/install.ts +30 -0
- package/src/runtime/policy.ts +66 -0
- package/src/runtime/projected-guidance.ts +45 -0
- package/src/runtime/projection.ts +224 -0
- package/src/runtime/repo-local.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +56 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +48 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/tasks.md +22 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +31 -90
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/design.md +3 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/explore-brief.md +6 -6
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/tasks.md +3 -0
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +30 -0
- package/src/templates/capabilities/react-doctor/claude/hooks/react-doctor.sh +2 -2
- package/src/templates/mate-skills/agents/mate-artifact-publish/SKILL.md +185 -0
- package/src/templates/mate-skills/agents/mate-artifact-publish/references/openspec.md +227 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/SKILL.md +68 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +14 -0
- package/src/templates/mate-skills/agents/mate-grill-with-docs/SKILL.md +29 -0
- package/src/templates/mate-skills/agents/mate-grilling/SKILL.md +49 -0
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +156 -0
- package/src/templates/{capabilities/openspec-cap/mate-skills → mate-skills}/agents/mate-openspec-backfill/SKILL.md +4 -2
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +139 -0
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +503 -0
- package/src/templates/mate-skills/claude/mate-artifact-publish/SKILL.md +185 -0
- package/src/templates/mate-skills/claude/mate-artifact-publish/references/openspec.md +227 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/SKILL.md +68 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +14 -0
- package/src/templates/mate-skills/claude/mate-grill-with-docs/SKILL.md +29 -0
- package/src/templates/mate-skills/claude/mate-grilling/SKILL.md +49 -0
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +156 -0
- package/src/templates/mate-skills/claude/mate-openspec-backfill/SKILL.md +67 -0
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +139 -0
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +503 -0
- package/src/templates/report-assets/README.md +32 -0
- package/src/templates/report-assets/mermaid.LICENSE +21 -0
- package/src/templates/report-assets/mermaid.min.js +4376 -0
- package/src/templates/root/TEMPLATE_CLAUDE.md +1 -9
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +1476 -197
- package/src/tools/setup/capabilities/graphify.ts +16 -7
- package/src/tools/setup/capabilities/openspec.ts +63 -56
- package/src/tools/setup/capabilities/tokensave.ts +116 -2
- package/src/tools/setup/engine.ts +34 -6
- package/src/tools/setup/mate.ts +42 -13
- package/src/tools/setup/plugin.ts +9 -0
- package/src/tools/setup/plugins/guidance.ts +11 -1
- package/src/tools/setup/providers/claude-format.ts +49 -4
- package/src/tools/setup/providers/claude-plugin-hooks.ts +117 -0
- package/src/tools/setup/providers/claude.ts +55 -220
- package/src/tools/setup/providers/opencode.ts +41 -14
- package/src/tools/setup/runtime-documents.ts +174 -0
- package/src/tools/setup/surface-target.ts +50 -0
- package/src/tools/setup/working-repo-cleanup.ts +33 -26
- package/src/tools/setup/working-repo-local-state.ts +21 -1
- package/src/tools/setup.ts +25 -3
- package/wrappers/bin/graphify +57 -8
- package/wrappers/bin/openspec +50 -3
- package/claude-plugin/hooks/artifact-finish-nudge.mjs +0 -8
- package/src/cli/commands/cap/headroom.ts +0 -52
- package/src/cli/commands/workspace/list.ts +0 -25
- package/src/cli/commands/workspace/materialize.ts +0 -46
- package/src/cli/commands/workspace/workspace.ts +0 -22
- package/src/hooks/artifact-finish-nudge.ts +0 -244
- package/src/lib/orchestrator/headroom/proxy.ts +0 -116
- package/src/lib/orchestrator/workspace-materialize.ts +0 -80
- package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/SKILL.md +0 -51
- package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/references/openspec.md +0 -134
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/SKILL.md +0 -58
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/references/openspec.md +0 -139
- package/src/tools/setup/capabilities/headroom.ts +0 -57
- /package/src/cli/commands/{workspace → companion}/open.ts +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mate-show-me
|
|
3
|
+
description: Explain the current topic or the applied change in a browser-rendered Mate report. Use only when the user explicitly invokes the skill to see how something works, what a change did, or wants a diagram, call tree, or rendered diff of the current work.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
allowed-tools: Bash(git:*), Bash(mate:*)
|
|
6
|
+
license: MIT
|
|
7
|
+
compatibility: Requires the mate CLI and the openspec capability enabled.
|
|
8
|
+
metadata:
|
|
9
|
+
author: mate
|
|
10
|
+
version: "1.0"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Mate Show Me
|
|
14
|
+
|
|
15
|
+
> Inspired by [humanlayer's show-me skill](https://github.com/humanlayer/skills/tree/main/plugins/show-me/skills/show-me) and adapted here as a Mate process-driven skill.
|
|
16
|
+
|
|
17
|
+
Explain the current topic visually in a browser-rendered Mate report. Skip the preamble, keep prose brief, and pick the smallest report view that makes the key point clear.
|
|
18
|
+
|
|
19
|
+
## Mate Workflow
|
|
20
|
+
|
|
21
|
+
- Explanation only. Never write or edit source code, tests, documentation, OpenSpec artifacts, context files, or ADRs. The only file this skill produces is the report document handed to `mate report --input`, which the CLI writes into the operating system temporary directory.
|
|
22
|
+
- Draw from the repositories, not from memory. Every diagram and every diff line comes from the inspected repository state, never from recollection of the conversation. Use the available code-graph tools before broad source scans.
|
|
23
|
+
- Code lives in the Working Repository; the Companion Repository holds only artifacts. Run `git diff` in the Working Repository.
|
|
24
|
+
- Never put an absolute local path, home directory, username, machine name, temporary report path, Companion Repository path, or raw path-valued environment variable in the report. Use repository-relative paths or neutral labels such as `Working Repository` and `Companion Repository`.
|
|
25
|
+
- Never commit, push, or create a pull request.
|
|
26
|
+
|
|
27
|
+
## Browser Report Required
|
|
28
|
+
|
|
29
|
+
Always assemble and open a Mate report, even when a small inline sketch would be sufficient. Do not answer with an inline-only visual or paste the complete diagram or diff into the conversation. The final response should briefly state what the browser report shows without repeating its temporary file path.
|
|
30
|
+
|
|
31
|
+
## Report Contents
|
|
32
|
+
|
|
33
|
+
- Show logic or an algorithm as a `diagram` section. Use a `mermaid` payload when branches, stages, or relationships benefit from a rendered visual; reserve `text` for compact pseudocode:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
on(save)
|
|
37
|
+
if content is unchanged
|
|
38
|
+
return cached result
|
|
39
|
+
write new content
|
|
40
|
+
return fresh result
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- Show runtime control flow as a `diagram` section with a `mermaid` payload. Use `flowchart TD` for a call tree or control flow and `sequenceDiagram` when ordering between components matters:
|
|
44
|
+
|
|
45
|
+
```mermaid
|
|
46
|
+
flowchart TD
|
|
47
|
+
renderHTML[renderHTML] --> validate[validateReportDocument]
|
|
48
|
+
renderHTML --> section[renderHTMLSection]
|
|
49
|
+
section --> diagram[renderHTMLDiagram]
|
|
50
|
+
section --> diff[renderHTMLDiff]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- Show UI structure as a `diagram` section with a `mermaid` payload when showing relationships between components or modules. Use a `text` payload only when exact component syntax is the point, including the state boundaries that matter:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<WorkflowView> (studio/views/workflow/index.tsx)
|
|
57
|
+
workflowPlan(availableSkills)
|
|
58
|
+
<StepList>
|
|
59
|
+
<StepRow optional>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- Show file responsibility or a broad refactor as a `diagram` section with a `text` payload containing a shallow file tree:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
src/
|
|
66
|
+
|-- commands/ # parses user actions
|
|
67
|
+
|-- report/ # owns the report contract and renderer
|
|
68
|
+
`-- templates/ # ships assets and skills
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- Show what changes as a `diff` section when the surrounding shape already exists. Match the diff shape to the topic - component tree, file layout, call tree, or control flow:
|
|
72
|
+
|
|
73
|
+
```diff
|
|
74
|
+
on(save)
|
|
75
|
+
- write content
|
|
76
|
+
+ if content is unchanged
|
|
77
|
+
+ return cached result
|
|
78
|
+
+ write new content
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- Show a whole block in the report when most of it is new, when omitted context would hide ownership or order, or when the user needs a copyable target shape.
|
|
82
|
+
|
|
83
|
+
## Browser Delivery
|
|
84
|
+
|
|
85
|
+
The report is mandatory. Prefer `mate report --input <temporary-json-file>` without `--json` so the CLI reads the complete document reliably, writes self-contained HTML, and opens it in the default browser. The CLI also supports `mate report --input -` when JSON is piped to stdin; if stdin delivery reports empty or truncated JSON, switch to a temporary JSON file rather than retrying the same transport. If validation fails, fix the report document and retry; do not fall back to an inline explanation or JSON-only delivery.
|
|
86
|
+
|
|
87
|
+
Never hand-write an HTML file, never start a server, and never open a browser by any other means. `mate report` is the only browser surface.
|
|
88
|
+
|
|
89
|
+
## Assembling The Report
|
|
90
|
+
|
|
91
|
+
Build a JSON document that conforms to the `mate-create-report` contract, then hand it over:
|
|
92
|
+
|
|
93
|
+
1. Serialize the complete document to an OS temporary JSON file outside both repositories. Do not place the report input in the Working Repository or Companion Repository.
|
|
94
|
+
2. Run `mate report --input <temporary-json-file>`.
|
|
95
|
+
|
|
96
|
+
A report assembled by this skill carries:
|
|
97
|
+
|
|
98
|
+
- `metadata` naming the subject and a neutral repository label, never a local path
|
|
99
|
+
- one `diagram` section for the structure or flow at issue, even when the visual is small
|
|
100
|
+
- use a `mermaid` payload for runtime control flow, call trees, data flow, or component relationships; use `text` only when a sketch or pseudocode is clearer
|
|
101
|
+
- one `diff` section carrying the unified diff, when there is something to diff
|
|
102
|
+
- a short `text` section next to each visual, saying what the reader should notice
|
|
103
|
+
|
|
104
|
+
A `diagram` section carries exactly one payload: `mermaid` for diagram source the report draws as a picture, or `text` for a monospace ASCII sketch or pseudocode block. A `diff` section carries a unified `patch` string.
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"id": "structure",
|
|
109
|
+
"title": "Report rendering",
|
|
110
|
+
"type": "diagram",
|
|
111
|
+
"mermaid": "classDiagram\n Renderer --> Highlight : uses"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"id": "changes",
|
|
118
|
+
"title": "What changed",
|
|
119
|
+
"type": "diff",
|
|
120
|
+
"patch": "diff --git a/src/acme.ts b/src/acme.ts\n..."
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If the document fails contract validation, report the diagnostic and correct the document. Do not fall back to hand-written HTML.
|
|
125
|
+
|
|
126
|
+
## Mermaid Authoring Rules
|
|
127
|
+
|
|
128
|
+
- Use Mermaid whenever the user asks for a diagram or the subject has meaningful control flow, sequencing, lifecycle, or relationships. A `text` payload is a deliberate fallback, not the default for runtime flows.
|
|
129
|
+
- Pick the diagram type that matches the question: `classDiagram` for structure, `sequenceDiagram` for interaction between components, `stateDiagram-v2` for lifecycles, `flowchart TD` for control flow and call trees.
|
|
130
|
+
- Keep labels short enough to read at report width. A label that needs a sentence belongs in the neighbouring `text` section.
|
|
131
|
+
- Do not use ELK-only layouts or math labels. The report inlines the self-contained mermaid bundle, which may not carry those chunks.
|
|
132
|
+
- Pair each Mermaid visual with a short neighbouring `text` section that calls out the ownership boundary or invariant the reader should notice.
|
|
133
|
+
- Prefer the `text` payload for pseudocode, shallow file trees, or cases where a picture would genuinely obscure the point.
|
|
134
|
+
|
|
135
|
+
## Reviewing An Applied Change
|
|
136
|
+
|
|
137
|
+
- Read the change scope before drawing it, then diagram the structure or flow the change establishes - not the structure it replaced.
|
|
138
|
+
- Take the diff from `git diff` in the Working Repository. Keep only repository-relative paths in the patch, default to the working tree, and ask the user when their intent is a committed range instead.
|
|
139
|
+
- When the inspected scope has no changes, say so and omit the `diff` section. Never invent a patch.
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mate-simplify-code
|
|
3
|
+
description: Simplifies code for clarity. Use when refactoring code for clarity without changing behavior. Use when code works but is harder to read, maintain, or extend than it should be. Use when reviewing code that has accumulated unnecessary complexity.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Mate Simplify Code
|
|
8
|
+
|
|
9
|
+
> Inspired by [Addy Osmani's code-simplification skill](https://github.com/addyosmani/agent-skills/tree/main/skills/code-simplification) and adapted here as a Mate process-driven skill.
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Simplify code by reducing complexity while preserving exact behavior. The goal is not fewer lines — it's code that is easier to read, understand, modify, and debug. Every simplification must pass a simple test: "Would a new team member understand this faster than the original?"
|
|
14
|
+
|
|
15
|
+
## Mate Workflow
|
|
16
|
+
|
|
17
|
+
- Inspect the requested scope and its callers, callees, and tests before editing. Use the available code-graph tools before broad source scans.
|
|
18
|
+
- Keep the refactor limited to the requested scope; do not make unrelated cleanup changes.
|
|
19
|
+
- Run the relevant tests after each simplification. Do not modify tests merely to make a refactor pass.
|
|
20
|
+
- Format touched files with the project's own formatter — detect it first (see Principle 2); never run a formatter the project has not adopted.
|
|
21
|
+
- Run `mate cap index --tokensave` after code changes.
|
|
22
|
+
- Never commit, push, or create a pull request unless the user explicitly asks.
|
|
23
|
+
|
|
24
|
+
## When to Use
|
|
25
|
+
|
|
26
|
+
- After a feature is working and tests pass, but the implementation feels heavier than it needs to be
|
|
27
|
+
- During code review when readability or complexity issues are flagged
|
|
28
|
+
- When you encounter deeply nested logic, long functions, or unclear names
|
|
29
|
+
- When refactoring code written under time pressure
|
|
30
|
+
- When consolidating related logic scattered across files
|
|
31
|
+
- After merging changes that introduced duplication or inconsistency
|
|
32
|
+
|
|
33
|
+
**When NOT to use:**
|
|
34
|
+
|
|
35
|
+
- Code is already clean and readable — don't simplify for the sake of it
|
|
36
|
+
- You don't understand what the code does yet — comprehend before you simplify
|
|
37
|
+
- The code is performance-critical and the "simpler" version would be measurably slower
|
|
38
|
+
- You're about to rewrite the module entirely — simplifying throwaway code wastes effort
|
|
39
|
+
|
|
40
|
+
## The Five Principles
|
|
41
|
+
|
|
42
|
+
### 1. Preserve Behavior Exactly
|
|
43
|
+
|
|
44
|
+
Don't change what the code does — only how it expresses it. All inputs, outputs, side effects, error behavior, and edge cases must remain identical. If you're not sure a simplification preserves behavior, don't make it.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
ASK BEFORE EVERY CHANGE:
|
|
48
|
+
→ Does this produce the same output for every input?
|
|
49
|
+
→ Does this maintain the same error behavior?
|
|
50
|
+
→ Does this preserve the same side effects and ordering?
|
|
51
|
+
→ Do all existing tests still pass without modification?
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Follow Project Conventions
|
|
55
|
+
|
|
56
|
+
Simplification means making code more consistent with the codebase, not imposing external preferences. Before simplifying:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
1. Read CLAUDE.md / project conventions
|
|
60
|
+
2. Study how neighboring code handles similar patterns
|
|
61
|
+
3. Match the project's style for:
|
|
62
|
+
- Import ordering and module system
|
|
63
|
+
- Function declaration style
|
|
64
|
+
- Naming conventions
|
|
65
|
+
- Error handling patterns
|
|
66
|
+
- Type annotation depth
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Simplification that breaks project consistency is not simplification — it's churn.
|
|
70
|
+
|
|
71
|
+
**Formatting is a project convention, not yours.** Never reach for a formatter by habit. Reformatting with a tool the project has not adopted rewrites lines you never touched and buries the refactor in noise.
|
|
72
|
+
|
|
73
|
+
Detect the formatter before formatting anything — first match wins:
|
|
74
|
+
|
|
75
|
+
| Signal in the repo | Formatter to run |
|
|
76
|
+
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
77
|
+
| `format` / `fmt` script in `package.json`, `Makefile`, `justfile`, `Taskfile.yml` | Run that script — it encodes the project's intent and needs no further detection |
|
|
78
|
+
| `biome.json` / `biome.jsonc` | `biome format --write` |
|
|
79
|
+
| `.oxfmtrc.json`, or `oxfmt` in devDependencies | `oxfmt` |
|
|
80
|
+
| `dprint.json` / `.dprint.jsonc` | `dprint fmt` |
|
|
81
|
+
| `.prettierrc*`, `prettier.config.*`, or a `prettier` key in `package.json` | `prettier --write` |
|
|
82
|
+
| `rustfmt.toml`, or any Cargo crate | `cargo fmt` |
|
|
83
|
+
| `[tool.ruff]` in `pyproject.toml` | `ruff format` |
|
|
84
|
+
| `[tool.black]` in `pyproject.toml` | `black` |
|
|
85
|
+
| Go module | `gofmt -w` (or `goimports -w` if already used) |
|
|
86
|
+
| Only `.editorconfig`, or no signal at all | Do not format — match the surrounding style by hand |
|
|
87
|
+
|
|
88
|
+
Rules:
|
|
89
|
+
|
|
90
|
+
- Invoke the project's pinned local binary through its package manager or runner, inferred from the lockfile (`bun`, `pnpm`, `yarn`, `npm`) — never a global install and never an `npx`-downloaded version, which may differ from the one that formatted the committed code.
|
|
91
|
+
- If several formatters are configured, the `format` script decides. If there is no script and the signals conflict, ask which is canonical instead of picking one.
|
|
92
|
+
- Format only the files you changed. A repo-wide format pass is a separate change with its own diff.
|
|
93
|
+
- If the formatter rewrites far more than the lines you touched, its config disagrees with the committed code. Stop, revert the formatting, and report it — do not fold that drift into a simplification change.
|
|
94
|
+
|
|
95
|
+
### 3. Prefer Clarity Over Cleverness
|
|
96
|
+
|
|
97
|
+
Explicit code is better than compact code when the compact version requires a mental pause to parse.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// UNCLEAR: Dense ternary chain
|
|
101
|
+
const label = isNew ? "New" : isUpdated ? "Updated" : isArchived ? "Archived" : "Active";
|
|
102
|
+
|
|
103
|
+
// CLEAR: Readable mapping
|
|
104
|
+
function getStatusLabel(item: Item): string {
|
|
105
|
+
if (item.isNew) return "New";
|
|
106
|
+
if (item.isUpdated) return "Updated";
|
|
107
|
+
if (item.isArchived) return "Archived";
|
|
108
|
+
return "Active";
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// UNCLEAR: Chained reduces with inline logic
|
|
114
|
+
const result = items.reduce(
|
|
115
|
+
(acc, item) => ({
|
|
116
|
+
...acc,
|
|
117
|
+
[item.id]: { ...acc[item.id], count: (acc[item.id]?.count ?? 0) + 1 },
|
|
118
|
+
}),
|
|
119
|
+
{},
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// CLEAR: Named intermediate step
|
|
123
|
+
const countById = new Map<string, number>();
|
|
124
|
+
for (const item of items) {
|
|
125
|
+
countById.set(item.id, (countById.get(item.id) ?? 0) + 1);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 4. Maintain Balance
|
|
130
|
+
|
|
131
|
+
Simplification has a failure mode: over-simplification. Watch for these traps:
|
|
132
|
+
|
|
133
|
+
- **Inlining too aggressively** — removing a helper that gave a concept a name makes the call site harder to read
|
|
134
|
+
- **Combining unrelated logic** — two simple functions merged into one complex function is not simpler
|
|
135
|
+
- **Removing "unnecessary" abstraction** — some abstractions exist for extensibility or testability, not complexity
|
|
136
|
+
- **Optimizing for line count** — fewer lines is not the goal; easier comprehension is
|
|
137
|
+
|
|
138
|
+
### 5. Scope to What Changed
|
|
139
|
+
|
|
140
|
+
Default to simplifying recently modified code. Avoid drive-by refactors of unrelated code unless explicitly asked to broaden scope. Unscoped simplification creates noise in diffs and risks unintended regressions.
|
|
141
|
+
|
|
142
|
+
## The Simplification Process
|
|
143
|
+
|
|
144
|
+
### Step 1: Understand Before Touching (Chesterton's Fence)
|
|
145
|
+
|
|
146
|
+
Before changing or removing anything, understand why it exists. This is Chesterton's Fence: if you see a fence across a road and don't understand why it's there, don't tear it down. First understand the reason, then decide if the reason still applies.
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
BEFORE SIMPLIFYING, ANSWER:
|
|
150
|
+
- What is this code's responsibility?
|
|
151
|
+
- What calls it? What does it call?
|
|
152
|
+
- What are the edge cases and error paths?
|
|
153
|
+
- Are there tests that define the expected behavior?
|
|
154
|
+
- Why might it have been written this way? (Performance? Platform constraint? Historical reason?)
|
|
155
|
+
- Check git blame: what was the original context for this code?
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
If you can't answer these, you're not ready to simplify. Read more context first.
|
|
159
|
+
|
|
160
|
+
### Step 2: Identify Simplification Opportunities
|
|
161
|
+
|
|
162
|
+
#### Step 2a: Mechanical pre-scan (required)
|
|
163
|
+
|
|
164
|
+
Run the scan before reading source. Its output **is** the candidate list — a pattern
|
|
165
|
+
nobody looked for is a pattern nobody finds, and prose tables alone are read with a
|
|
166
|
+
bias toward "this file looks fine".
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
PRE-SCAN THE REQUESTED SCOPE:
|
|
170
|
+
1. Code graph, if available — tokensave_module_api (export surface vs. real
|
|
171
|
+
consumers), tokensave_dead_code, tokensave_similar (near-duplicate bodies),
|
|
172
|
+
tokensave_complexity / tokensave_largest (nesting, long functions)
|
|
173
|
+
2. Unused-export detector, if the project already has one configured
|
|
174
|
+
(knip, ts-prune, eslint import-x/no-unused-modules, Python vulture)
|
|
175
|
+
3. Grep for consumers when neither is available — including test files,
|
|
176
|
+
and including the bare symbol name, not just import statements
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Do not skip the pre-scan because the file "looks clean". Judgment applies to the
|
|
180
|
+
candidates it produces, not to whether to produce them. Report candidates you
|
|
181
|
+
deliberately leave alone, with the reason.
|
|
182
|
+
|
|
183
|
+
**A pre-scan hit is a question, not a verdict.** These tools find symbols nothing
|
|
184
|
+
_imports_; they cannot see symbols resolved by name — framework exports, reflective
|
|
185
|
+
lookups, config-referenced files. Every "unused export" candidate must clear the
|
|
186
|
+
**Behavior-preservation rules for the module surface** below before you touch it.
|
|
187
|
+
Read that section before acting on this list, not after.
|
|
188
|
+
|
|
189
|
+
Then scan for these patterns — each one is a concrete signal, not a vague smell:
|
|
190
|
+
|
|
191
|
+
**Structural complexity:**
|
|
192
|
+
|
|
193
|
+
| Pattern | Signal | Simplification |
|
|
194
|
+
| -------------------------- | ---------------------------------- | --------------------------------------------------------- |
|
|
195
|
+
| Deep nesting (3+ levels) | Hard to follow control flow | Extract conditions into guard clauses or helper functions |
|
|
196
|
+
| Long functions (50+ lines) | Multiple responsibilities | Split into focused functions with descriptive names |
|
|
197
|
+
| Nested ternaries | Requires mental stack to parse | Replace with if/else chains, switch, or lookup objects |
|
|
198
|
+
| Boolean parameter flags | `doThing(true, false, true)` | Replace with options objects or separate functions |
|
|
199
|
+
| Repeated conditionals | Same `if` check in multiple places | Extract to a well-named predicate function |
|
|
200
|
+
|
|
201
|
+
**Naming and readability:**
|
|
202
|
+
|
|
203
|
+
| Pattern | Signal | Simplification |
|
|
204
|
+
| -------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------ |
|
|
205
|
+
| Generic names | `data`, `result`, `temp`, `val`, `item` | Rename to describe the content: `userProfile`, `validationErrors` |
|
|
206
|
+
| Abbreviated names | `usr`, `cfg`, `btn`, `evt` | Use full words unless the abbreviation is universal (`id`, `url`, `api`) |
|
|
207
|
+
| Misleading names | Function named `get` that also mutates state | Rename to reflect actual behavior |
|
|
208
|
+
| Comments explaining "what" | `// increment counter` above `count++` | Delete the comment — the code is clear enough |
|
|
209
|
+
| Comments explaining "why" | `// Retry because the API is flaky under load` | Keep these — they carry intent the code can't express |
|
|
210
|
+
|
|
211
|
+
**Redundancy:**
|
|
212
|
+
|
|
213
|
+
| Pattern | Signal | Simplification |
|
|
214
|
+
| ------------------------- | ------------------------------------------------------------ | --------------------------------------------------------- |
|
|
215
|
+
| Duplicated logic | Same 5+ lines in multiple places | Extract to a shared function |
|
|
216
|
+
| Dead code | Unreachable branches, unused variables, commented-out blocks | Remove (after confirming it's truly dead) |
|
|
217
|
+
| Unnecessary abstractions | Wrapper that adds no value | Inline the wrapper, call the underlying function directly |
|
|
218
|
+
| Over-engineered patterns | Factory-for-a-factory, strategy-with-one-strategy | Replace with the simple direct approach |
|
|
219
|
+
| Redundant type assertions | Casting to a type that's already inferred | Remove the assertion |
|
|
220
|
+
|
|
221
|
+
**Module surface:**
|
|
222
|
+
|
|
223
|
+
A module's public API should match what is actually consumed. Surface bloat is
|
|
224
|
+
invisible inside a single file — it only shows up when you compare exports against
|
|
225
|
+
callers, which is what the Step 2a pre-scan does.
|
|
226
|
+
|
|
227
|
+
| Pattern | Signal | Simplification |
|
|
228
|
+
| ----------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
229
|
+
| Over-exported module | Export has no consumer outside its own file | Drop `export` — keep the symbol, narrow its visibility |
|
|
230
|
+
| Leaked intermediate types | Type exported only to annotate a private helper or internal step | Unexport — but only if it appears in no exported signature |
|
|
231
|
+
| Speculative public API | Export added "for later" with no caller | Unexport first; delete only per the dead-code rule above |
|
|
232
|
+
| Single-consumer bag-of-things | Many exports, exactly one importer | **Propose only** — API reshape, not a visibility change (see below) |
|
|
233
|
+
| Re-export passthrough | Barrel file that only forwards a single symbol | **Propose only** — changes module resolution repo-wide (see below) |
|
|
234
|
+
|
|
235
|
+
Narrowing visibility is not inlining — the named helper survives, so this does not
|
|
236
|
+
conflict with the over-simplification traps in Principle 4. Only inline a helper when
|
|
237
|
+
the name itself carries no meaning at the call site.
|
|
238
|
+
|
|
239
|
+
#### Behavior-preservation rules for the module surface
|
|
240
|
+
|
|
241
|
+
Principle 1 governs this table without exception. Visibility is not behavior — until
|
|
242
|
+
something resolves the symbol by name rather than by import. Then it is.
|
|
243
|
+
|
|
244
|
+
**Unexport, don't delete.** In a type-checked project, removing `export` is verified by
|
|
245
|
+
the compiler: any importer becomes a compile error, so a mistake fails loudly and
|
|
246
|
+
immediately. Deleting the symbol has no such net. Narrow visibility as its own step;
|
|
247
|
+
treat deletion as a separate decision under the dead-code rule, not as part of the
|
|
248
|
+
same edit.
|
|
249
|
+
|
|
250
|
+
**That safety net requires a type checker that actually runs over the file.** Plain
|
|
251
|
+
JavaScript, TypeScript with `checkJs` off, or a project with no `tsc --noEmit` gate
|
|
252
|
+
gets no compile error — a broken import fails at runtime instead, possibly only on one
|
|
253
|
+
code path. In an unchecked project, do not unexport on pre-scan evidence alone: confirm
|
|
254
|
+
each consumer by grep first, or leave the export and report it.
|
|
255
|
+
|
|
256
|
+
**A test is a consumer.** If the only importer is a test file, the export is load-
|
|
257
|
+
bearing — unexporting it breaks the test, and Principle 1 forbids editing tests to make
|
|
258
|
+
a refactor pass. Leave it exported. "No production consumer" is not "no consumer";
|
|
259
|
+
report it as a possible test-only seam instead of acting on it.
|
|
260
|
+
|
|
261
|
+
**A type used in an exported signature stays exported.** If an exported function takes
|
|
262
|
+
or returns the type, consumers need to name it — unexporting breaks call sites and
|
|
263
|
+
declaration emit even though nothing imports the type directly today. Only unexport a
|
|
264
|
+
type that appears exclusively in module-private positions.
|
|
265
|
+
|
|
266
|
+
**Never touch an export the framework resolves by name.** These have no importer
|
|
267
|
+
anywhere by design, so "no consumer found" is meaningless for them — the pre-scan and
|
|
268
|
+
every unused-export detector will report them as dead, and they are not:
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
FRAMEWORK-RESOLVED — OUT OF SCOPE, DO NOT UNEXPORT OR RENAME:
|
|
272
|
+
- Next.js app router: default, metadata, generateMetadata, generateStaticParams,
|
|
273
|
+
revalidate, dynamic, runtime, viewport, route handlers (GET/POST/...),
|
|
274
|
+
middleware, error/loading/not-found boundaries
|
|
275
|
+
- Next.js pages router: default, getServerSideProps, getStaticProps, getStaticPaths
|
|
276
|
+
- Test and story files: Storybook CSF (default + named story exports), fixtures,
|
|
277
|
+
setup files referenced by config rather than imported
|
|
278
|
+
- Package entry points: anything reachable from package.json exports/main/types,
|
|
279
|
+
or from a documented public API
|
|
280
|
+
- Config-referenced modules: paths named in tsconfig, bundler, or tool config
|
|
281
|
+
- Reflective resolution: dynamic import() with a computed specifier, glob imports
|
|
282
|
+
(import.meta.glob, require.context), DI containers, decorators, plugin registries
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Propose-only rows are not yours to apply.** Collapsing a multi-export module to one
|
|
286
|
+
entry point, or deleting a barrel, is an API reshape: it rewrites call sites in files
|
|
287
|
+
outside the requested scope, changes module resolution for deep importers, and is a
|
|
288
|
+
design decision rather than a behavior-preserving edit. Both collide with Principle 5.
|
|
289
|
+
Describe the change and the affected files, then stop and let the user decide — the
|
|
290
|
+
same treatment the prop-drilling case gets under React guidance.
|
|
291
|
+
|
|
292
|
+
**When the pre-scan flags a symbol you cannot prove is unreferenced, leave it and say
|
|
293
|
+
so.** An export you were unsure about and kept costs a line of explanation. An export
|
|
294
|
+
you removed on a guess costs a production incident. Unverifiable candidates are
|
|
295
|
+
reported, not acted on.
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
BEFORE REMOVING ANY export, ALL MUST HOLD:
|
|
299
|
+
[ ] Not framework-resolved (checked against the list above)
|
|
300
|
+
[ ] Not reachable from a package entry point or documented API
|
|
301
|
+
[ ] No importer anywhere — including tests, stories, and config
|
|
302
|
+
[ ] If a type: appears in no exported signature
|
|
303
|
+
[ ] A type checker covers this file and will run before the change is accepted
|
|
304
|
+
[ ] The symbol name greps clean outside its own file
|
|
305
|
+
Any box unchecked → report the candidate, do not touch it.
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Step 3: Apply Changes Incrementally
|
|
309
|
+
|
|
310
|
+
Make one simplification at a time. Run tests after each change. **Submit refactoring changes separately from feature or bug fix changes.** A PR that refactors and adds a feature is two PRs — split them.
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
FOR EACH SIMPLIFICATION:
|
|
314
|
+
1. Make the change
|
|
315
|
+
2. Run the type checker / compiler, then the test suite
|
|
316
|
+
(visibility changes surface as compile errors, not test failures — a green
|
|
317
|
+
test run alone does not prove an unexport was safe)
|
|
318
|
+
3. If both pass → continue to the next simplification; commit only if the user explicitly asks
|
|
319
|
+
4. If either fails → revert and reconsider
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Avoid batching multiple simplifications into a single untested change. If something breaks, you need to know which simplification caused it.
|
|
323
|
+
|
|
324
|
+
**The Rule of 500:** If a refactoring would touch more than 500 lines, invest in automation (codemods or AST transforms) rather than making the changes by hand. Manual edits at that scale are error-prone and exhausting to review.
|
|
325
|
+
|
|
326
|
+
### Step 4: Verify the Result
|
|
327
|
+
|
|
328
|
+
Run the project's formatter (detected in Principle 2) over the files you touched, then re-run the type checker and test suite — formatting must never be the last unverified step.
|
|
329
|
+
|
|
330
|
+
Then step back and evaluate the whole:
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
COMPARE BEFORE AND AFTER:
|
|
334
|
+
- Is the simplified version genuinely easier to understand?
|
|
335
|
+
- Did you introduce any new patterns inconsistent with the codebase?
|
|
336
|
+
- Is the diff clean and reviewable?
|
|
337
|
+
- Would a teammate approve this change?
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
If the "simplified" version is harder to understand or review, revert. Not every simplification attempt succeeds.
|
|
341
|
+
|
|
342
|
+
## Language-Specific Guidance
|
|
343
|
+
|
|
344
|
+
### TypeScript / JavaScript
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
// SIMPLIFY: Unnecessary async wrapper
|
|
348
|
+
// Before
|
|
349
|
+
async function getUser(id: string): Promise<User> {
|
|
350
|
+
return await userService.findById(id);
|
|
351
|
+
}
|
|
352
|
+
// After
|
|
353
|
+
function getUser(id: string): Promise<User> {
|
|
354
|
+
return userService.findById(id);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// SIMPLIFY: Verbose conditional assignment
|
|
358
|
+
// Before
|
|
359
|
+
let displayName: string;
|
|
360
|
+
if (user.nickname) {
|
|
361
|
+
displayName = user.nickname;
|
|
362
|
+
} else {
|
|
363
|
+
displayName = user.fullName;
|
|
364
|
+
}
|
|
365
|
+
// After
|
|
366
|
+
const displayName = user.nickname || user.fullName;
|
|
367
|
+
|
|
368
|
+
// SIMPLIFY: Manual array building
|
|
369
|
+
// Before
|
|
370
|
+
const activeUsers: User[] = [];
|
|
371
|
+
for (const user of users) {
|
|
372
|
+
if (user.isActive) {
|
|
373
|
+
activeUsers.push(user);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// After
|
|
377
|
+
const activeUsers = users.filter((user) => user.isActive);
|
|
378
|
+
|
|
379
|
+
// SIMPLIFY: Redundant boolean return
|
|
380
|
+
// Before
|
|
381
|
+
function isValid(input: string): boolean {
|
|
382
|
+
if (input.length > 0 && input.length < 100) {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
// After
|
|
388
|
+
function isValid(input: string): boolean {
|
|
389
|
+
return input.length > 0 && input.length < 100;
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Python
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
# SIMPLIFY: Verbose dictionary building
|
|
397
|
+
# Before
|
|
398
|
+
result = {}
|
|
399
|
+
for item in items:
|
|
400
|
+
result[item.id] = item.name
|
|
401
|
+
# After
|
|
402
|
+
result = {item.id: item.name for item in items}
|
|
403
|
+
|
|
404
|
+
# SIMPLIFY: Nested conditionals with early return
|
|
405
|
+
# Before
|
|
406
|
+
def process(data):
|
|
407
|
+
if data is not None:
|
|
408
|
+
if data.is_valid():
|
|
409
|
+
if data.has_permission():
|
|
410
|
+
return do_work(data)
|
|
411
|
+
else:
|
|
412
|
+
raise PermissionError("No permission")
|
|
413
|
+
else:
|
|
414
|
+
raise ValueError("Invalid data")
|
|
415
|
+
else:
|
|
416
|
+
raise TypeError("Data is None")
|
|
417
|
+
# After
|
|
418
|
+
def process(data):
|
|
419
|
+
if data is None:
|
|
420
|
+
raise TypeError("Data is None")
|
|
421
|
+
if not data.is_valid():
|
|
422
|
+
raise ValueError("Invalid data")
|
|
423
|
+
if not data.has_permission():
|
|
424
|
+
raise PermissionError("No permission")
|
|
425
|
+
return do_work(data)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### React / JSX
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
// SIMPLIFY: Verbose conditional rendering
|
|
432
|
+
// Before
|
|
433
|
+
function UserBadge({ user }: Props) {
|
|
434
|
+
if (user.isAdmin) {
|
|
435
|
+
return <Badge variant="admin">Admin</Badge>;
|
|
436
|
+
} else {
|
|
437
|
+
return <Badge variant="default">User</Badge>;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// After
|
|
441
|
+
function UserBadge({ user }: Props) {
|
|
442
|
+
const variant = user.isAdmin ? "admin" : "default";
|
|
443
|
+
const label = user.isAdmin ? "Admin" : "User";
|
|
444
|
+
return <Badge variant={variant}>{label}</Badge>;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// SIMPLIFY: Prop drilling through intermediate components
|
|
448
|
+
// Before — consider whether context or composition solves this better.
|
|
449
|
+
// This is a judgment call — flag it, don't auto-refactor.
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Common Rationalizations
|
|
453
|
+
|
|
454
|
+
| Rationalization | Reality |
|
|
455
|
+
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
456
|
+
| "It's working, no need to touch it" | Working code that's hard to read will be hard to fix when it breaks. Simplifying now saves time on every future change. |
|
|
457
|
+
| "Fewer lines is always simpler" | A 1-line nested ternary is not simpler than a 5-line if/else. Simplicity is about comprehension speed, not line count. |
|
|
458
|
+
| "I'll just quickly simplify this unrelated code too" | Unscoped simplification creates noisy diffs and risks regressions in code you didn't intend to change. Stay focused. |
|
|
459
|
+
| "The types make it self-documenting" | Types document structure, not intent. A well-named function explains _why_ better than a type signature explains _what_. |
|
|
460
|
+
| "This abstraction might be useful later" | Don't preserve speculative abstractions. If it's not used now, it's complexity without value. Remove it and re-add when needed. |
|
|
461
|
+
| "The original author must have had a reason" | Maybe. Check git blame — apply Chesterton's Fence. But accumulated complexity often has no reason; it's just the residue of iteration under pressure. |
|
|
462
|
+
| "I'll refactor while adding this feature" | Separate refactoring from feature work. Mixed changes are harder to review, revert, and understand in history. |
|
|
463
|
+
|
|
464
|
+
## Red Flags
|
|
465
|
+
|
|
466
|
+
- Simplification that requires modifying tests to pass (you likely changed behavior)
|
|
467
|
+
- "Simplified" code that is longer and harder to follow than the original
|
|
468
|
+
- Renaming things to match your preferences rather than project conventions
|
|
469
|
+
- Removing error handling because "it makes the code cleaner"
|
|
470
|
+
- Simplifying code you don't fully understand
|
|
471
|
+
- Batching many simplifications into one large, hard-to-review commit
|
|
472
|
+
- Refactoring code outside the scope of the current task without being asked
|
|
473
|
+
- Deleting an export because a tool reported it unused, without checking whether the
|
|
474
|
+
framework resolves it by name (`page.tsx`, `route.ts`, stories, config-referenced files)
|
|
475
|
+
- Acting on a pre-scan candidate you could not verify — report it instead
|
|
476
|
+
- Treating a green test run as proof a visibility change was safe without a type check
|
|
477
|
+
- Unexporting a symbol whose only importer is a test, then editing the test to match
|
|
478
|
+
- Applying a propose-only row (module collapse, barrel deletion) without user sign-off
|
|
479
|
+
- Trusting "the compiler would catch it" in a project the type checker does not cover
|
|
480
|
+
|
|
481
|
+
## Verification
|
|
482
|
+
|
|
483
|
+
After completing a simplification pass:
|
|
484
|
+
|
|
485
|
+
- [ ] All existing tests pass without modification
|
|
486
|
+
- [ ] Build succeeds with no new warnings
|
|
487
|
+
- [ ] The project's own formatter was detected and run on the touched files — no formatter the project has not adopted was used
|
|
488
|
+
- [ ] Formatting touched only the changed files (no repo-wide reformat mixed in)
|
|
489
|
+
- [ ] Linter passes (no style regressions)
|
|
490
|
+
- [ ] Each simplification is a reviewable, incremental change
|
|
491
|
+
- [ ] The diff is clean — no unrelated changes mixed in
|
|
492
|
+
- [ ] Simplified code follows project conventions (checked against CLAUDE.md or equivalent)
|
|
493
|
+
- [ ] No error handling was removed or weakened
|
|
494
|
+
- [ ] No dead code was left behind (unused imports, unreachable branches)
|
|
495
|
+
- [ ] The Step 2a pre-scan was run, and every candidate is either fixed or explained
|
|
496
|
+
- [ ] Export surface matches actual consumers — nothing exported without a caller outside its file
|
|
497
|
+
- [ ] Type checker passes — no unexport broke an importer
|
|
498
|
+
- [ ] No framework-resolved export was unexported, renamed, or deleted
|
|
499
|
+
- [ ] Every removed `export` cleared all boxes of the pre-removal checklist
|
|
500
|
+
- [ ] No test was edited to accommodate a visibility change
|
|
501
|
+
- [ ] Propose-only findings were reported, not applied
|
|
502
|
+
- [ ] Behavior is bit-for-bit identical: same inputs, outputs, side effects, error paths
|
|
503
|
+
- [ ] A teammate or review agent would approve the change as a net improvement
|