clean-code-tools 1.0.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/README.md +66 -0
- package/configs/eslint.clean-code.recommended.mjs +211 -0
- package/configs/python.clean-code.pyproject.toml +143 -0
- package/data/clean-code-patterns.jsonl +264 -0
- package/data/vector-record.schema.json +77 -0
- package/docs/README.md +29 -0
- package/docs/eslint-custom-rules.md +74 -0
- package/docs/eslint-recommended-config.md +87 -0
- package/docs/fastmcp-local-server.md +104 -0
- package/docs/publishing.md +125 -0
- package/docs/python-lint-recommended-config.md +57 -0
- package/docs/python-pylint-custom-rules.md +77 -0
- package/docs/semantic-weaviate.md +80 -0
- package/docs/static-trigger-semantic-review.md +97 -0
- package/evals/clean-code-retrieval.jsonl +13 -0
- package/ops/dev/weaviate/README.md +34 -0
- package/ops/dev/weaviate/compose.yaml +34 -0
- package/ops/dev/weaviate/smoke.sh +28 -0
- package/package.json +96 -0
- package/pyproject.toml +303 -0
- package/sample-apps/README.md +40 -0
- package/sample-apps/python-app/pyproject.toml +113 -0
- package/sample-apps/python-app/src/clean_pricing.py +10 -0
- package/sample-apps/python-app/src/smelly_pricing.py +8 -0
- package/sample-apps/ts-backend/eslint.config.mjs +3 -0
- package/sample-apps/ts-backend/package.json +18 -0
- package/sample-apps/ts-backend/src/clean-handler.ts +19 -0
- package/sample-apps/ts-backend/src/smelly-handler.ts +29 -0
- package/sample-apps/ts-backend/tsconfig.json +9 -0
- package/sample-apps/ts-frontend/eslint.config.mjs +3 -0
- package/sample-apps/ts-frontend/package.json +18 -0
- package/sample-apps/ts-frontend/src/CleanWidget.tsx +18 -0
- package/sample-apps/ts-frontend/src/SmellyWidget.tsx +27 -0
- package/sample-apps/ts-frontend/tsconfig.json +10 -0
- package/scripts/_mcp_app.py +21 -0
- package/scripts/check_clean_code_review_candidates.py +302 -0
- package/scripts/check_fastmcp_server.py +106 -0
- package/scripts/check_packages.py +137 -0
- package/scripts/check_python_config.py +130 -0
- package/scripts/check_repo_python_lint.py +46 -0
- package/scripts/check_retrieval_evals.py +132 -0
- package/scripts/check_sample_apps.py +169 -0
- package/scripts/check_semantic_search_tooling.py +102 -0
- package/scripts/clean_code_eslint_triggers.py +272 -0
- package/scripts/clean_code_mcp_server.py +7 -0
- package/scripts/clean_code_python_triggers.py +318 -0
- package/scripts/clean_code_review_candidates.py +291 -0
- package/scripts/clean_code_review_io.py +36 -0
- package/scripts/clean_code_review_models.py +43 -0
- package/scripts/clean_code_semantic.py +27 -0
- package/scripts/set_package_versions.py +82 -0
- package/scripts/weaviate_ingest_clean_code.py +44 -0
- package/scripts/weaviate_search_clean_code.py +51 -0
- package/skills/clean-code-mcp-reviewer/SKILL.md +209 -0
- package/skills/clean-code-mcp-reviewer/evals/evals.json +30 -0
- package/src/js/eslint-plugin-clean-code.mjs +758 -0
- package/src/python/clean_code_tools_pylint/__init__.py +14 -0
- package/src/python/clean_code_tools_pylint/ast_checker.py +122 -0
- package/src/python/clean_code_tools_pylint/comments.py +83 -0
- package/src/python/clean_code_tools_pylint/helpers.py +196 -0
- package/src/python/mcp_server/__init__.py +1 -0
- package/src/python/mcp_server/corpus.py +160 -0
- package/src/python/mcp_server/markdown.py +126 -0
- package/src/python/mcp_server/models.py +73 -0
- package/src/python/mcp_server/ranking.py +125 -0
- package/src/python/mcp_server/ranking_scoring.py +232 -0
- package/src/python/mcp_server/semantic.py +192 -0
- package/src/python/mcp_server/server.py +235 -0
- package/src/python/mcp_server/server_payloads.py +83 -0
- package/src/python/mcp_server/text.py +104 -0
- package/src/python/mcp_server/utils/__init__.py +1 -0
- package/src/python/mcp_server/utils/httpx_loader.py +14 -0
- package/src/python/mcp_server/utils/increment.py +7 -0
- package/src/python/mcp_server/utils/sha256_text.py +8 -0
- package/src/python/mcp_server/utils/unique_strings.py +15 -0
- package/src/python/mcp_server/weaviate.py +182 -0
- package/uv.lock +2012 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from _mcp_app import load_semantic_module
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
parser = argparse.ArgumentParser(description="Inspect clean-code semantic chunks.")
|
|
12
|
+
parser.add_argument("--json", action="store_true", help="Print chunks as JSONL.")
|
|
13
|
+
args = parser.parse_args()
|
|
14
|
+
semantic = load_semantic_module()
|
|
15
|
+
chunks = semantic.build_chunks()
|
|
16
|
+
if args.json:
|
|
17
|
+
for chunk in chunks:
|
|
18
|
+
print(json.dumps(chunk.properties, sort_keys=True))
|
|
19
|
+
return
|
|
20
|
+
by_kind: dict[str, int] = {}
|
|
21
|
+
for chunk in chunks:
|
|
22
|
+
by_kind[chunk.chunk_kind] = by_kind.get(chunk.chunk_kind, 0) + 1
|
|
23
|
+
print(json.dumps({"chunks": len(chunks), "by_kind": by_kind}, sort_keys=True))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
main()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
10
|
+
PACKAGE_JSON = ROOT / "package.json"
|
|
11
|
+
PYPROJECT = ROOT / "pyproject.toml"
|
|
12
|
+
RELEASE_VERSION_RE = re.compile(r"^\d+\.\d+\.\d+(?:[a-zA-Z0-9.-]+)?$")
|
|
13
|
+
CORE_VERSION_RE = re.compile(r"^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$")
|
|
14
|
+
PYPROJECT_VERSION_RE = re.compile(r'(?m)^version = "([^"]+)"$')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def package_version() -> str:
|
|
18
|
+
return str(json.loads(PACKAGE_JSON.read_text())["version"])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def set_json_version(path: Path, version: str) -> None:
|
|
22
|
+
payload = json.loads(path.read_text())
|
|
23
|
+
payload["version"] = version
|
|
24
|
+
path.write_text(json.dumps(payload, indent=2) + "\n")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def set_pyproject_version(version: str) -> None:
|
|
28
|
+
text = PYPROJECT.read_text()
|
|
29
|
+
updated, count = PYPROJECT_VERSION_RE.subn(f'version = "{version}"', text, count=1)
|
|
30
|
+
if count != 1:
|
|
31
|
+
raise SystemExit("Expected exactly one [project] version in pyproject.toml")
|
|
32
|
+
PYPROJECT.write_text(updated)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def set_versions(*, npm_version: str, python_version: str) -> None:
|
|
36
|
+
set_json_version(PACKAGE_JSON, npm_version)
|
|
37
|
+
set_pyproject_version(python_version)
|
|
38
|
+
print(f"npm_version={npm_version}")
|
|
39
|
+
print(f"python_version={python_version}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def bumped_version(base: str, part: str) -> str:
|
|
43
|
+
match = CORE_VERSION_RE.match(base)
|
|
44
|
+
if match is None:
|
|
45
|
+
raise SystemExit(f"Version bumps require a plain major.minor.patch base, got: {base}")
|
|
46
|
+
major = int(match.group("major"))
|
|
47
|
+
minor = int(match.group("minor"))
|
|
48
|
+
patch = int(match.group("patch"))
|
|
49
|
+
if part == "major":
|
|
50
|
+
return f"{major + 1}.0.0"
|
|
51
|
+
if part == "minor":
|
|
52
|
+
return f"{major}.{minor + 1}.0"
|
|
53
|
+
return f"{major}.{minor}.{patch + 1}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main() -> None:
|
|
57
|
+
parser = argparse.ArgumentParser(description="Set coordinated npm and Python package versions.")
|
|
58
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
59
|
+
group.add_argument("--release", help="Release version to write to both manifests, for example 1.2.3.")
|
|
60
|
+
group.add_argument("--bump", choices=("patch", "minor", "major"), help="Bump the base release version.")
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
"--base",
|
|
63
|
+
default=None,
|
|
64
|
+
help="Base SemVer version for --bump. Defaults to package.json.",
|
|
65
|
+
)
|
|
66
|
+
args = parser.parse_args()
|
|
67
|
+
|
|
68
|
+
if args.release:
|
|
69
|
+
if not RELEASE_VERSION_RE.match(args.release):
|
|
70
|
+
raise SystemExit(f"Invalid release version: {args.release}")
|
|
71
|
+
set_versions(npm_version=args.release, python_version=args.release)
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
base = args.base or package_version()
|
|
75
|
+
if not RELEASE_VERSION_RE.match(base):
|
|
76
|
+
raise SystemExit(f"Invalid base version: {base}")
|
|
77
|
+
version = bumped_version(base, args.bump)
|
|
78
|
+
set_versions(npm_version=version, python_version=version)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
main()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
|
|
6
|
+
from _mcp_app import load_semantic_module
|
|
7
|
+
|
|
8
|
+
semantic = load_semantic_module()
|
|
9
|
+
DEFAULT_BATCH_SIZE = semantic.DEFAULT_BATCH_SIZE
|
|
10
|
+
build_chunks = semantic.build_chunks
|
|
11
|
+
ingest_chunks = semantic.ingest_chunks
|
|
12
|
+
reset_collection = semantic.reset_collection
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def add_common_args(parser: argparse.ArgumentParser) -> None:
|
|
16
|
+
parser.add_argument("--url", default=semantic.DEFAULT_WEAVIATE_URL)
|
|
17
|
+
parser.add_argument("--collection", default=semantic.COLLECTION_NAME)
|
|
18
|
+
parser.add_argument("--model", default=semantic.DEFAULT_EMBEDDING_MODEL)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> None:
|
|
22
|
+
parser = argparse.ArgumentParser(description="Ingest clean-code semantic chunks into Weaviate.")
|
|
23
|
+
add_common_args(parser)
|
|
24
|
+
parser.add_argument("--reset", action="store_true", help="Drop and recreate the collection first.")
|
|
25
|
+
parser.add_argument("--batch-size", type=int, default=DEFAULT_BATCH_SIZE)
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
|
|
28
|
+
if args.batch_size < 1:
|
|
29
|
+
raise SystemExit("--batch-size must be at least 1")
|
|
30
|
+
if args.reset:
|
|
31
|
+
reset_collection(url=args.url, collection_name=args.collection)
|
|
32
|
+
chunks = build_chunks()
|
|
33
|
+
inserted = ingest_chunks(
|
|
34
|
+
chunks=chunks,
|
|
35
|
+
url=args.url,
|
|
36
|
+
collection_name=args.collection,
|
|
37
|
+
model_name=args.model,
|
|
38
|
+
batch_size=args.batch_size,
|
|
39
|
+
)
|
|
40
|
+
print(f"ingested={inserted} collection={args.collection}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
main()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
|
|
6
|
+
from _mcp_app import load_semantic_module
|
|
7
|
+
|
|
8
|
+
semantic = load_semantic_module()
|
|
9
|
+
search_chunks = semantic.search_chunks
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def add_common_args(parser: argparse.ArgumentParser) -> None:
|
|
13
|
+
parser.add_argument("--url", default=semantic.DEFAULT_WEAVIATE_URL)
|
|
14
|
+
parser.add_argument("--collection", default=semantic.COLLECTION_NAME)
|
|
15
|
+
parser.add_argument("--model", default=semantic.DEFAULT_EMBEDDING_MODEL)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def print_search_results(results: list[dict[str, object]]) -> None:
|
|
19
|
+
for index, row in enumerate(results, start=1):
|
|
20
|
+
additional = row.get("_additional") or {}
|
|
21
|
+
distance = additional.get("distance", "?") if isinstance(additional, dict) else "?"
|
|
22
|
+
print(
|
|
23
|
+
f"{index}. {row.get('recordId') or row.get('chunkId')} "
|
|
24
|
+
f"{row.get('title')} distance={distance}"
|
|
25
|
+
)
|
|
26
|
+
print(f" source={row.get('sourceFile')} kind={row.get('chunkKind', row.get('sourceKind'))}")
|
|
27
|
+
text = " ".join(str(row.get("contentText", "")).split())
|
|
28
|
+
print(f" {text[:280]}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main() -> None:
|
|
32
|
+
parser = argparse.ArgumentParser(description="Search clean-code semantic chunks in Weaviate.")
|
|
33
|
+
add_common_args(parser)
|
|
34
|
+
parser.add_argument("query")
|
|
35
|
+
parser.add_argument("--limit", type=int, default=8)
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
|
|
38
|
+
if args.limit < 1:
|
|
39
|
+
raise SystemExit("--limit must be at least 1")
|
|
40
|
+
results = search_chunks(
|
|
41
|
+
query=args.query,
|
|
42
|
+
url=args.url,
|
|
43
|
+
collection_name=args.collection,
|
|
44
|
+
model_name=args.model,
|
|
45
|
+
limit=args.limit,
|
|
46
|
+
)
|
|
47
|
+
print_search_results(results)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main()
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clean-code-mcp-reviewer
|
|
3
|
+
description: Use this skill whenever reviewing, refactoring, or designing lint checks for TypeScript, JavaScript, Python, or React code where clean-code patterns may help. This skill teaches agents how to use the clean-code MCP interactively: read code first, form concrete smell hypotheses, query the MCP narrowly, suppress weak matches, and apply only guidance anchored to local code evidence. Use it for maintainability reviews, readability concerns, refactor planning, and clean-code lint-rule design, even when the user does not explicitly mention MCP.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Clean-Code MCP Reviewer
|
|
7
|
+
|
|
8
|
+
Use the clean-code MCP as decision support, not as a generic style rulebook. The
|
|
9
|
+
tool is valuable when a concrete code shape creates a maintainability decision:
|
|
10
|
+
function boundaries, arguments, naming, side effects, comments, duplication,
|
|
11
|
+
tests, error handling, literals, or object navigation.
|
|
12
|
+
|
|
13
|
+
## Operating Principle
|
|
14
|
+
|
|
15
|
+
Read the code before querying. A good MCP query starts from observed local
|
|
16
|
+
evidence, not from the task title or a generic desire to "make it cleaner." The
|
|
17
|
+
agent remains responsible for judging whether retrieved guidance fits the local
|
|
18
|
+
framework, public API, tests, performance constraints, and project conventions.
|
|
19
|
+
|
|
20
|
+
This skill is self-contained. Do not assume separate language-specific
|
|
21
|
+
clean-code skills are installed. Use the language heuristics below when judging
|
|
22
|
+
Python, JavaScript, TypeScript, or React code.
|
|
23
|
+
|
|
24
|
+
## Refactor Discipline
|
|
25
|
+
|
|
26
|
+
- Read formatter, linter, type-checker, framework, tests, and nearby files
|
|
27
|
+
before applying generic advice.
|
|
28
|
+
- Name the concrete smell before proposing a cleanup.
|
|
29
|
+
- Keep the refactor local unless the user asks for a broader redesign.
|
|
30
|
+
- Preserve public APIs, return shapes, exception behavior, async boundaries,
|
|
31
|
+
mutability expectations, and framework contracts.
|
|
32
|
+
- Prefer the smallest useful change: rename, flatten control flow, extract one
|
|
33
|
+
cohesive helper, introduce a stable data shape, or clarify an error boundary.
|
|
34
|
+
- Avoid class hierarchies, speculative abstractions, trivial wrappers, and
|
|
35
|
+
extraction that hides the main logic.
|
|
36
|
+
- Optimize for the call site: the best boundary makes the caller obviously
|
|
37
|
+
correct.
|
|
38
|
+
- Verify that tests, lint, types, and the relevant runtime contract still hold.
|
|
39
|
+
|
|
40
|
+
## When To Use The MCP
|
|
41
|
+
|
|
42
|
+
Use the MCP when you have a specific clean-code concern:
|
|
43
|
+
|
|
44
|
+
- following up deterministic lint triggers emitted as clean-code review
|
|
45
|
+
candidates
|
|
46
|
+
- reviewing a possible maintainability finding
|
|
47
|
+
- planning a behavior-preserving refactor
|
|
48
|
+
- deciding whether a pattern is lintable
|
|
49
|
+
- comparing alternative extraction, naming, or argument-shape choices
|
|
50
|
+
- checking whether a repeated smell should become an ESLint, Ruff, Pylint, or
|
|
51
|
+
Semgrep rule
|
|
52
|
+
|
|
53
|
+
Do not query for:
|
|
54
|
+
|
|
55
|
+
- formatting-only edits
|
|
56
|
+
- dependency bumps
|
|
57
|
+
- obvious build/type errors
|
|
58
|
+
- purely mechanical renames
|
|
59
|
+
- generated files or migrations unless the user explicitly asks
|
|
60
|
+
- code where local conventions or framework idioms already settle the decision
|
|
61
|
+
|
|
62
|
+
## Query Workflow
|
|
63
|
+
|
|
64
|
+
1. Inspect the changed code, nearby tests, and local conventions.
|
|
65
|
+
2. Identify one concrete concern at a time.
|
|
66
|
+
3. Summarize the concern as a smell hypothesis.
|
|
67
|
+
4. Query `search_clean_code_patterns` with language and relevant filters.
|
|
68
|
+
5. If a result looks useful, call `get_clean_code_pattern` for full detail.
|
|
69
|
+
6. Use the pattern only when it matches a concrete code anchor.
|
|
70
|
+
7. Say there is no strong clean-code match when results are generic or weak.
|
|
71
|
+
|
|
72
|
+
When a repo provides `clean-code-review-candidates/v1` input, treat each
|
|
73
|
+
candidate as a deterministic tripwire, not as a finding. Read the file plus the
|
|
74
|
+
symbol or anchor named by the candidate, check whether the listed semantic
|
|
75
|
+
questions are actually supported by the code, then run only the relevant MCP
|
|
76
|
+
queries. A candidate may produce `no strong clean-code match`, an advisory note,
|
|
77
|
+
or a targeted refactor plan.
|
|
78
|
+
|
|
79
|
+
Prefer concise queries over whole-file or whole-diff input.
|
|
80
|
+
|
|
81
|
+
If the pattern-first tools are not available yet, use the lower-level
|
|
82
|
+
`search_clean_code` tool as a fallback and be more conservative: treat mixed
|
|
83
|
+
markdown/chunk results as supporting context only, and do not claim full pattern
|
|
84
|
+
applicability without a canonical pattern record.
|
|
85
|
+
|
|
86
|
+
Good query examples:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
typescript function boolean parameter controls behavior in calculatePrice
|
|
90
|
+
python function mutates output argument and also returns status
|
|
91
|
+
react component mixes data normalization conditional rendering and side effects
|
|
92
|
+
typescript review lint candidate TODO comment without tracked issue id
|
|
93
|
+
python long parameter list configuration values passed positionally
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Poor query examples:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
make this cleaner
|
|
100
|
+
review this entire diff
|
|
101
|
+
clean code suggestions for app.tsx
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Result Handling
|
|
105
|
+
|
|
106
|
+
Treat MCP results as candidates. Before using a result, check:
|
|
107
|
+
|
|
108
|
+
- Does the result match the language or framework?
|
|
109
|
+
- Does the result describe the observed code shape?
|
|
110
|
+
- Does `avoid_when` apply?
|
|
111
|
+
- Is the pattern lintable, review-only, or context-dependent?
|
|
112
|
+
- Would applying it preserve the public API and behavior?
|
|
113
|
+
- Is the match specific enough to mention in a review or plan?
|
|
114
|
+
|
|
115
|
+
Use at most 1-3 selected matches in visible output. Do not decorate every
|
|
116
|
+
review finding with pattern IDs. Cite a pattern ID only when it materially
|
|
117
|
+
changed the recommendation.
|
|
118
|
+
|
|
119
|
+
## Review Output
|
|
120
|
+
|
|
121
|
+
When writing code-review findings, lead with local evidence. Use MCP guidance as
|
|
122
|
+
supporting context.
|
|
123
|
+
|
|
124
|
+
Preferred shape:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
Finding: `calculatePrice(user, includeDiscounts)` uses a boolean selector that
|
|
128
|
+
changes behavior, so callers must understand two execution modes from one
|
|
129
|
+
signature.
|
|
130
|
+
|
|
131
|
+
Clean-code support: CC-043 applies because the boolean argument selects behavior
|
|
132
|
+
rather than representing plain data. A safer remediation is to introduce
|
|
133
|
+
intention-revealing functions while keeping a compatibility wrapper if the API
|
|
134
|
+
is public.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Avoid findings that say only "Clean code says..." or "Pattern CC-043 says...".
|
|
138
|
+
The issue must stand on the code.
|
|
139
|
+
|
|
140
|
+
## Language Heuristics
|
|
141
|
+
|
|
142
|
+
For Python:
|
|
143
|
+
|
|
144
|
+
- Prefer Pythonic clarity over abstract purity.
|
|
145
|
+
- Use plain functions when data flow is simple.
|
|
146
|
+
- Use `TypedDict` for stable mapping-shaped data, `dataclass` for value-like
|
|
147
|
+
data, and richer models only when validation, serialization, or invariants
|
|
148
|
+
justify them.
|
|
149
|
+
- Use keyword-only parameters when they improve call-site clarity.
|
|
150
|
+
- Preserve the local failure style: exceptions, `None`, result objects, or
|
|
151
|
+
framework responses.
|
|
152
|
+
- Separate parsing, validation, transformation, and side effects when they are
|
|
153
|
+
tangled.
|
|
154
|
+
- Do not add docstrings to every private helper; comments should explain why,
|
|
155
|
+
constraints, or surprising behavior.
|
|
156
|
+
|
|
157
|
+
For JavaScript and TypeScript:
|
|
158
|
+
|
|
159
|
+
- Prefer domain names over implementation names; drop vague suffixes like
|
|
160
|
+
`Data`, `Info`, `Manager`, or `Helper` unless they distinguish real concepts.
|
|
161
|
+
- Use boolean names that read like questions: `isReady`, `hasAccess`,
|
|
162
|
+
`shouldRetry`.
|
|
163
|
+
- Prefer stronger TypeScript types over explanatory comments.
|
|
164
|
+
- Narrow external data early and keep internal code on trusted shapes.
|
|
165
|
+
- Use discriminated unions when the code already branches on variants.
|
|
166
|
+
- Prefer object parameters when several values travel together, but do not
|
|
167
|
+
introduce options objects only to satisfy an arbitrary parameter count.
|
|
168
|
+
- Follow the existing error boundary style: throw, result object, or
|
|
169
|
+
framework-specific response.
|
|
170
|
+
|
|
171
|
+
## Refactor Output
|
|
172
|
+
|
|
173
|
+
When planning a refactor, translate selected patterns into constraints:
|
|
174
|
+
|
|
175
|
+
- what behavior must stay unchanged
|
|
176
|
+
- what code shape should change
|
|
177
|
+
- what compatibility wrapper is needed, if any
|
|
178
|
+
- what tests or checks should verify the change
|
|
179
|
+
|
|
180
|
+
Keep the refactor small unless the user asks for a broader rewrite.
|
|
181
|
+
|
|
182
|
+
## Lint-Rule Design
|
|
183
|
+
|
|
184
|
+
For lint-rule work, filter toward high and medium lintability. If the MCP accepts
|
|
185
|
+
a list, pass `["high", "medium"]`; if it accepts only one value, run separate
|
|
186
|
+
queries or use the lint-rule recommendation tool. Keep
|
|
187
|
+
`review_only` patterns out of automated lint checks unless there is a narrow,
|
|
188
|
+
low-false-positive signal.
|
|
189
|
+
|
|
190
|
+
A lint recommendation should include:
|
|
191
|
+
|
|
192
|
+
- target tool: ESLint, Ruff, Pylint, Semgrep, or review-only
|
|
193
|
+
- static signal
|
|
194
|
+
- likely false positives
|
|
195
|
+
- safe contexts to ignore
|
|
196
|
+
- suppression strategy
|
|
197
|
+
- autofix feasibility
|
|
198
|
+
|
|
199
|
+
## Weak-Match Policy
|
|
200
|
+
|
|
201
|
+
Suppress weak or generic MCP results. Say `no strong clean-code match` when:
|
|
202
|
+
|
|
203
|
+
- the top results are broad clean-code advice without a local code anchor
|
|
204
|
+
- the result depends on context the agent has not verified
|
|
205
|
+
- the code is idiomatic for the framework
|
|
206
|
+
- the evidence comes from generated, fixture, migration, or test-helper code
|
|
207
|
+
- applying the pattern would conflict with stable public API constraints
|
|
208
|
+
|
|
209
|
+
Missing a weak suggestion is better than producing a noisy style finding.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "clean-code-mcp-reviewer",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "Review this TypeScript helper. Use the clean-code MCP only if it helps.\n\n```ts\nexport function calculatePrice(customer: Customer, includeDiscounts: boolean): Money {\n let total = customer.cart.subtotal;\n if (includeDiscounts) {\n total = total.minus(customer.discountAmount);\n }\n return total;\n}\n\nconst invoicePrice = calculatePrice(customer, false);\nconst checkoutPrice = calculatePrice(customer, true);\n```",
|
|
7
|
+
"expected_output": "The agent reads the code first, queries for a boolean selector or flag argument pattern, uses a high-fit match, and anchors the finding to the local function/callers."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": 2,
|
|
11
|
+
"prompt": "Review this React component and decide whether clean-code MCP guidance should block it.\n\n```tsx\nexport function SaveDialog({ isSaving }: { isSaving: boolean }) {\n return (\n <Modal open>\n <Button disabled={isSaving}>Save</Button>\n </Modal>\n );\n}\n```",
|
|
12
|
+
"expected_output": "The agent treats declarative React boolean props as a near-miss and avoids a flag-argument finding unless there is behavior-selection evidence."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": 3,
|
|
16
|
+
"prompt": "Plan a Python refactor for this function. Use the clean-code MCP only after identifying the concern.\n\n```python\ndef normalize_order(order: Order, errors: list[str]) -> str:\n if order.total <= 0:\n errors.append(\"total must be positive\")\n return \"invalid\"\n order.customer_email = order.customer_email.strip().lower()\n return \"valid\"\n```",
|
|
17
|
+
"expected_output": "The agent queries for output argument mutation or side-effect concerns, fetches details only for a relevant match, and proposes a behavior-preserving refactor with verification."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": 4,
|
|
21
|
+
"prompt": "Design a clean-code lint check for repeated TODO comments in TypeScript and Python.\n\nExamples that should pass:\n```ts\n// TODO(PROJ-123): remove legacy tax fallback after migration.\n```\n\nExamples that should fail:\n```python\n# TODO fix this later\nresult = legacy_process(data)\n```",
|
|
22
|
+
"expected_output": "The agent uses lintability filters, distinguishes valid tracked TODOs from weak comments, and recommends appropriate target tooling and false-positive controls."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": 5,
|
|
26
|
+
"prompt": "Review this generated API client fixture change and decide whether clean-code patterns should block it.\n\n```ts\n// generated from billing-openapi.yaml\nexport const fixture = {\n account: { owner: { address: { city: \"London\" } } },\n status: \"active\"\n};\n\nexport const city = fixture.account.owner.address.city;\n```\n\nThe file path is `src/generated/billingClient.fixture.ts`.",
|
|
27
|
+
"expected_output": "The agent recognizes generated/fixture code as a weak-match context and reports no strong clean-code finding unless local project rules say otherwise."
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|