@mrciphersmith/keryx 0.2.164 → 0.3.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 +4 -1
- package/dist/cli.js +82540 -50300
- package/dist/core.js +28967 -18937
- package/package.json +2 -2
- package/src/gdgraph/affected-report.ts +141 -0
- package/src/gdgraph/build.ts +170 -23
- package/src/gdgraph/service.ts +6 -0
- package/src/gdgraph/staleness.ts +253 -45
- package/src/gdskills/bundled/agents/codebase-navigator.md +55 -0
- package/src/gdskills/bundled/agents/design-advisor.md +64 -0
- package/src/gdskills/bundled/agents/docs-maintainer.md +56 -0
- package/src/gdskills/bundled/agents/end-to-end-tester.md +56 -0
- package/src/gdskills/bundled/agents/error-path-auditor.md +57 -0
- package/src/gdskills/bundled/agents/go-build-fixer.md +52 -0
- package/src/gdskills/bundled/agents/go-code-auditor.md +49 -0
- package/src/gdskills/bundled/agents/performance-auditor.md +63 -0
- package/src/gdskills/bundled/agents/python-build-fixer.md +52 -0
- package/src/gdskills/bundled/agents/python-code-auditor.md +49 -0
- package/src/gdskills/bundled/agents/refactoring-steward.md +61 -0
- package/src/gdskills/bundled/agents/security-auditor.md +62 -0
- package/src/gdskills/bundled/agents/test-first-driver.md +61 -0
- package/src/gdskills/bundled/agents/work-planner.md +62 -0
- package/src/gdskills/bundled/install-manifest.json +797 -0
- package/src/gdskills/bundled/rules/core/model-selection.mdc +51 -0
- package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +29 -1
- package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +2 -2
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-jev-rules/SKILL.md +267 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +26 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +75 -247
- package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +19 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +10 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +5 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-backend.md +50 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-frontend.md +52 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/review-report.md +143 -0
- package/src/gdskills/bundled/stacks/angular/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/angular/governance/eval.json +1751 -0
- package/src/gdskills/bundled/stacks/angular/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/angular/pack.json +55 -0
- package/src/gdskills/bundled/stacks/angular/rules/coding-style.mdc +82 -0
- package/src/gdskills/bundled/stacks/angular/rules/patterns.mdc +84 -0
- package/src/gdskills/bundled/stacks/angular/rules/security.mdc +70 -0
- package/src/gdskills/bundled/stacks/angular/rules/testing.mdc +73 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/SKILL.md +98 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/SKILL.md +112 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-testing/SKILL.md +102 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-testing/evals.json +71 -0
- package/src/gdskills/bundled/stacks/go/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/go/governance/eval.json +1745 -0
- package/src/gdskills/bundled/stacks/go/governance/scout.json +31 -0
- package/src/gdskills/bundled/stacks/go/pack.json +41 -0
- package/src/gdskills/bundled/stacks/go/rules/coding-style.mdc +85 -0
- package/src/gdskills/bundled/stacks/go/rules/patterns.mdc +65 -0
- package/src/gdskills/bundled/stacks/go/rules/security.mdc +73 -0
- package/src/gdskills/bundled/stacks/go/rules/testing.mdc +68 -0
- package/src/gdskills/bundled/stacks/go/skills/go-build-fix/SKILL.md +138 -0
- package/src/gdskills/bundled/stacks/go/skills/go-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/go/skills/go-code-review/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/go/skills/go-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/go/skills/go-implementation/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/go/skills/go-implementation/evals.json +76 -0
- package/src/gdskills/bundled/stacks/go/skills/go-testing/SKILL.md +126 -0
- package/src/gdskills/bundled/stacks/go/skills/go-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/mobx/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/mobx/governance/eval.json +904 -0
- package/src/gdskills/bundled/stacks/mobx/governance/scout.json +18 -0
- package/src/gdskills/bundled/stacks/mobx/pack.json +28 -0
- package/src/gdskills/bundled/stacks/mobx/rules/coding-style.mdc +91 -0
- package/src/gdskills/bundled/stacks/mobx/rules/patterns.mdc +122 -0
- package/src/gdskills/bundled/stacks/mobx/rules/security.mdc +56 -0
- package/src/gdskills/bundled/stacks/mobx/rules/testing.mdc +63 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/nestjs/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/nestjs/governance/eval.json +1308 -0
- package/src/gdskills/bundled/stacks/nestjs/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/nestjs/pack.json +53 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/coding-style.mdc +70 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/patterns.mdc +83 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/security.mdc +73 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/testing.mdc +69 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/SKILL.md +157 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/evals.json +70 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/evals.json +71 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/evals.json +69 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/eval.json +2413 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/scout.json +42 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/pack.json +42 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/security.mdc +72 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/testing.mdc +64 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/SKILL.md +118 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/evals.json +78 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/SKILL.md +116 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/evals.json +76 -0
- package/src/gdskills/bundled/stacks/python/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/python/governance/eval.json +1758 -0
- package/src/gdskills/bundled/stacks/python/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/python/pack.json +41 -0
- package/src/gdskills/bundled/stacks/python/rules/coding-style.mdc +63 -0
- package/src/gdskills/bundled/stacks/python/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/python/rules/security.mdc +84 -0
- package/src/gdskills/bundled/stacks/python/rules/testing.mdc +77 -0
- package/src/gdskills/bundled/stacks/python/skills/python-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/python/skills/python-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/python/skills/python-code-review/SKILL.md +155 -0
- package/src/gdskills/bundled/stacks/python/skills/python-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/python/skills/python-implementation/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/python/skills/python-implementation/evals.json +78 -0
- package/src/gdskills/bundled/stacks/python/skills/python-testing/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/python/skills/python-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/react/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/react/governance/eval.json +2188 -0
- package/src/gdskills/bundled/stacks/react/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/react/pack.json +42 -0
- package/src/gdskills/bundled/stacks/react/rules/coding-style.mdc +58 -0
- package/src/gdskills/bundled/stacks/react/rules/patterns.mdc +79 -0
- package/src/gdskills/bundled/stacks/react/rules/security.mdc +70 -0
- package/src/gdskills/bundled/stacks/react/rules/testing.mdc +60 -0
- package/src/gdskills/bundled/stacks/react/skills/react-build-fix/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/react/skills/react-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/react/skills/react-code-review/SKILL.md +148 -0
- package/src/gdskills/bundled/stacks/react/skills/react-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/react/skills/react-implementation/SKILL.md +140 -0
- package/src/gdskills/bundled/stacks/react/skills/react-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/react/skills/react-testing/SKILL.md +142 -0
- package/src/gdskills/bundled/stacks/react/skills/react-testing/evals.json +83 -0
- package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/SKILL.md +155 -0
- package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ts-js-node/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ts-js-node/governance/eval.json +2155 -0
- package/src/gdskills/bundled/stacks/ts-js-node/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/ts-js-node/pack.json +41 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/coding-style.mdc +73 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/patterns.mdc +61 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/security.mdc +71 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/testing.mdc +63 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/SKILL.md +152 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/evals.json +71 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/evals.json +70 -0
- package/src/gdskills/bundled/stacks/vue/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/vue/governance/eval.json +2215 -0
- package/src/gdskills/bundled/stacks/vue/governance/scout.json +42 -0
- package/src/gdskills/bundled/stacks/vue/pack.json +42 -0
- package/src/gdskills/bundled/stacks/vue/rules/coding-style.mdc +73 -0
- package/src/gdskills/bundled/stacks/vue/rules/patterns.mdc +84 -0
- package/src/gdskills/bundled/stacks/vue/rules/security.mdc +60 -0
- package/src/gdskills/bundled/stacks/vue/rules/testing.mdc +69 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/SKILL.md +120 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/evals.json +71 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-testing/SKILL.md +115 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-testing/evals.json +72 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/evals.json +71 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"query": "Use when a Python project's pytest test suite needs writing, extending, or fixing -- covers fixture design, parametrization, mocking, and coverage gaps in existing pytest test files.",
|
|
4
|
+
"decision": "fork",
|
|
5
|
+
"topMatch": "review/review-testing-practices",
|
|
6
|
+
"recordedAt": "2026-09-24T04:48:38.183Z",
|
|
7
|
+
"skillName": "python-testing",
|
|
8
|
+
"justification": "top match review/review-testing-practices (0.37) is a review skill that checks existing tests against convention, not an authoring workflow; quality/test-gen (0.23) is a generic cross-language test generator with no pytest-specific fixture/parametrization/mocking guidance and no stack awareness. Decision is fork (below use threshold 0.55), and neither candidate is a substitute for a stack-scoped pytest authoring skill, so create."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"query": "Use when implementing or extending a feature in a modern Python (3.12/3.13) codebase -- covers project tooling discovery (pyproject.toml, uv/poetry/pip, ruff, mypy/pyright), typing (generics, Protocol, TypedDict, dataclasses), context managers, exception chaining, asyncio TaskGroup, logging, and src/-layout packaging.",
|
|
12
|
+
"decision": "create",
|
|
13
|
+
"topMatch": "python/python-build-fix",
|
|
14
|
+
"recordedAt": "2026-09-24T11:57:31.665Z",
|
|
15
|
+
"skillName": "python-implementation",
|
|
16
|
+
"justification": "top match python/python-build-fix (0.23) only shares generic Python-tooling vocabulary (mypy, ruff, pyright, pip/poetry) from its own build-fix scope, not implementation/typing/asyncio guidance; go/go-implementation (0.15) is a different language's implementation skill. Decision is create (below fork threshold 0.3), so a Python-specific implementation skill is genuinely new."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"query": "Use when reviewing Python changes for correctness and safety risks -- checks mutable default arguments, broad except clauses, resource leaks missing a with block, blocking calls inside async functions, typing holes (Any, missing Optional), N+1/ORM query misuse, and security sinks. Read-only: reports findings, does not edit code.",
|
|
20
|
+
"decision": "fork",
|
|
21
|
+
"topMatch": "ts-js-node/nodejs-code-review",
|
|
22
|
+
"recordedAt": "2026-09-24T11:58:18.113Z",
|
|
23
|
+
"skillName": "python-code-review",
|
|
24
|
+
"justification": "top match ts-js-node/nodejs-code-review (0.40) is the same review category but for a different language stack (JS/TS-specific mutable-default/resource/async checks do not transfer to Python's own except/typing/ORM idioms); go/go-code-review (0.22) is likewise a different language. Decision is fork (below use threshold 0.55, above fork threshold 0.3): the review category is shared across stacks by design, but no candidate covers Python's own mutable-default/except/async/typing/N+1/security vocabulary, so a Python-scoped fork is warranted."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"query": "Use when a Python project fails to import, build, type-check, or lint -- resolves ModuleNotFoundError/ImportError, packaging or editable-install failures, dependency resolver conflicts (pip/uv/poetry), mypy/pyright type errors, ruff failures, and pytest collection errors with the smallest root-cause fix.",
|
|
28
|
+
"decision": "fork",
|
|
29
|
+
"topMatch": "ts-js-node/nodejs-build-fix",
|
|
30
|
+
"recordedAt": "2026-09-24T11:58:25.230Z",
|
|
31
|
+
"skillName": "python-build-fix",
|
|
32
|
+
"justification": "top match ts-js-node/nodejs-build-fix (0.45) and go/go-build-fix (0.37) are the same build-fix category but for other languages' own toolchains (npm/tsc vs. pip/uv/mypy/ruff), not substitutes; python/python-implementation (0.33) is this same pack's implementation skill, sharing only generic tooling-discovery vocabulary, not a build-fix workflow. Decision is fork (below use threshold 0.55, above fork threshold 0.3): the build-fix category is shared across stacks by design, but no candidate resolves Python-specific ModuleNotFoundError/packaging/resolver/mypy/ruff/pytest-collection failures, so a Python-scoped fork is warranted."
|
|
33
|
+
}
|
|
34
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "python",
|
|
3
|
+
"family": "language",
|
|
4
|
+
"modules": ["python-rules", "python-skills"],
|
|
5
|
+
"detectionMarkers": ["python"],
|
|
6
|
+
"provenance": {
|
|
7
|
+
"origin": "authored",
|
|
8
|
+
"sourceRef": "flow 309 (W1 Lane D), completed in flow 314"
|
|
9
|
+
},
|
|
10
|
+
"stability": "stable",
|
|
11
|
+
"skills": {
|
|
12
|
+
"implement": ["python-implementation"],
|
|
13
|
+
"test": ["python-testing"],
|
|
14
|
+
"review": ["python-code-review"],
|
|
15
|
+
"build-fix": ["python-build-fix"],
|
|
16
|
+
"migrate": []
|
|
17
|
+
},
|
|
18
|
+
"agentProfile": {
|
|
19
|
+
"displayName": "Python",
|
|
20
|
+
"auditFocus": [
|
|
21
|
+
"mutable default arguments (`def f(x=[])`) instead of `None` + inside-body init",
|
|
22
|
+
"broad `except:`/`except Exception:` that swallows an unrelated failure",
|
|
23
|
+
"resource handles (files, sockets, DB connections, locks) opened without a `with` block",
|
|
24
|
+
"blocking calls (`requests`, `time.sleep`, sync file I/O) inside an `async def`",
|
|
25
|
+
"typing holes: untyped public signatures, bare `Any`, or an implicit-`None` return missing `| None`/`Optional`",
|
|
26
|
+
"security sinks: `shell=True`, `eval`/`exec`, `pickle`/`yaml.load` on untrusted input, unparameterized SQL"
|
|
27
|
+
],
|
|
28
|
+
"buildCommands": [
|
|
29
|
+
"ruff check .",
|
|
30
|
+
"ruff format --check .",
|
|
31
|
+
"mypy . || pyright",
|
|
32
|
+
"pytest -x -q"
|
|
33
|
+
],
|
|
34
|
+
"fixGuardrails": [
|
|
35
|
+
"never add `# type: ignore` or `# noqa` to silence a checker without fixing or explaining the underlying issue",
|
|
36
|
+
"never pin or downgrade a dependency to route around a real incompatibility without saying so in the report",
|
|
37
|
+
"never widen a narrowed exception catch or a real type hole just to make a check pass",
|
|
38
|
+
"fix the smallest root cause; do not refactor unrelated code while resolving a build/lint/type failure"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.py", "**/*.pyi"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Python coding style
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic style rules to Python's own
|
|
11
|
+
idiom. Applies only to `*.py`/`*.pyi` files — everything not
|
|
12
|
+
Python-specific still comes from the common rules this file `extends`.
|
|
13
|
+
|
|
14
|
+
## Formatting and typing
|
|
15
|
+
|
|
16
|
+
- Format with the project's configured formatter (`ruff format` or `black`);
|
|
17
|
+
do not hand-format around a formatter that is already configured.
|
|
18
|
+
- Type-hint every new or touched public function signature (`def f(x: int)
|
|
19
|
+
-> str:`), including `Optional`/`| None` for a parameter or return that can
|
|
20
|
+
be absent. Do not add hints to untouched code in the same change.
|
|
21
|
+
- Prefer `from __future__ import annotations` in modules that predate PEP 604
|
|
22
|
+
(`int | None`) rather than importing `Optional`/`Union` piecemeal.
|
|
23
|
+
- Use `pathlib.Path`, not `os.path` string joins, for new filesystem code.
|
|
24
|
+
- Use f-strings for interpolation in normal code (`f"{name} joined"`), not
|
|
25
|
+
`%`-formatting or `.format()` — except in logging calls, where lazy `%s`
|
|
26
|
+
interpolation is correct (see `rules/patterns.mdc`).
|
|
27
|
+
- Run `ruff check .` (or the project's configured equivalent) instead of
|
|
28
|
+
hand-checking unused imports, shadowed builtins, or import order; fix what
|
|
29
|
+
it flags rather than adding a blanket `# noqa`.
|
|
30
|
+
|
|
31
|
+
## Naming and structure
|
|
32
|
+
|
|
33
|
+
- `snake_case` for functions, variables and modules; `PascalCase` for
|
|
34
|
+
classes; `UPPER_SNAKE_CASE` for module-level constants — the PEP 8
|
|
35
|
+
baseline, not a house variant of it.
|
|
36
|
+
- One public class or cohesive function group per module; a module that
|
|
37
|
+
outgrows one screen's worth of unrelated public names should split.
|
|
38
|
+
- Private module/class internals get a single leading underscore
|
|
39
|
+
(`_helper`), not name-mangled double-underscore unless subclass
|
|
40
|
+
name-clash protection is the actual intent.
|
|
41
|
+
|
|
42
|
+
## Errors and imports
|
|
43
|
+
|
|
44
|
+
- Raise a specific exception type (`ValueError`, `KeyError`, or a project
|
|
45
|
+
exception class), never a bare `except:` or `except Exception:` that
|
|
46
|
+
swallows an unrelated failure — catch the specific type you can handle.
|
|
47
|
+
- Absolute imports (`from mypackage.module import thing`), not relative
|
|
48
|
+
dot-imports (`from ..module import thing`), for anything crossing a
|
|
49
|
+
package boundary; relative imports are fine within one package's own
|
|
50
|
+
submodules.
|
|
51
|
+
- Group imports stdlib / third-party / local, each group alphabetized, one
|
|
52
|
+
blank line between groups — the convention `isort`/`ruff --select I`
|
|
53
|
+
enforce; run it instead of hand-ordering when the project has it
|
|
54
|
+
configured.
|
|
55
|
+
|
|
56
|
+
## Docstrings
|
|
57
|
+
|
|
58
|
+
- Every public function, class and module gets a docstring stating what it
|
|
59
|
+
does and, for a function with non-obvious parameters, what each one means
|
|
60
|
+
— a one-line summary is enough when the signature is already
|
|
61
|
+
self-describing.
|
|
62
|
+
- Match the project's existing docstring convention (Google-style, NumPy-style,
|
|
63
|
+
or plain reST) rather than introducing a second style in one file.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.py", "**/*.pyi"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Python patterns
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic design guidance to idiomatic
|
|
11
|
+
Python 3.12/3.13 patterns and the anti-patterns they replace. Applies only to
|
|
12
|
+
`*.py`/`*.pyi` files.
|
|
13
|
+
|
|
14
|
+
## Data and typing
|
|
15
|
+
|
|
16
|
+
- Model a fixed set of related fields with `@dataclass` (or `attrs` if the
|
|
17
|
+
project already depends on it) instead of a `dict` with string keys — a
|
|
18
|
+
typo in a dict key fails at runtime, a typo in a dataclass field fails at
|
|
19
|
+
lint/type-check time.
|
|
20
|
+
- Use `TypedDict` for a dict shape that genuinely must stay a `dict` (e.g. a
|
|
21
|
+
JSON payload), not for data your own code constructs and passes around.
|
|
22
|
+
- Reach for `typing.Protocol` to type "anything with this method" instead of
|
|
23
|
+
requiring a concrete base class when the caller only needs structural
|
|
24
|
+
compatibility — this keeps callers free to pass any matching object.
|
|
25
|
+
- Use `Generic[T]`/type parameter syntax (`class Box[T]:` on 3.12+) for a
|
|
26
|
+
container or wrapper whose element type varies by call site, rather than
|
|
27
|
+
typing it `Any` and re-casting at every use.
|
|
28
|
+
|
|
29
|
+
## Control flow and resources
|
|
30
|
+
|
|
31
|
+
- Acquire any closable/lockable resource (file, socket, DB connection, lock,
|
|
32
|
+
temp directory) with a `with` (or `async with`) block; never rely on `del`,
|
|
33
|
+
garbage collection, or a manual `.close()` that a raised exception can skip.
|
|
34
|
+
- Write a custom context manager as a generator decorated
|
|
35
|
+
`@contextlib.contextmanager` rather than a hand-rolled
|
|
36
|
+
`__enter__`/`__exit__` class, unless the class also needs other methods.
|
|
37
|
+
- Raise a new exception `from` the one being handled
|
|
38
|
+
(`raise ValueError(...) from exc`) instead of a bare `raise ValueError(...)`
|
|
39
|
+
inside an `except` block — this preserves the causal chain instead of
|
|
40
|
+
presenting the new exception as unrelated.
|
|
41
|
+
- Prefer early return over nested `if`/`else` for guard conditions; a
|
|
42
|
+
function whose happy path is indented inside three levels of conditionals
|
|
43
|
+
should invert its guards.
|
|
44
|
+
|
|
45
|
+
## Async
|
|
46
|
+
|
|
47
|
+
- Group a set of related concurrent awaitables with `asyncio.TaskGroup`
|
|
48
|
+
(3.11+) instead of manually tracking a list of tasks and awaiting them
|
|
49
|
+
with `asyncio.gather` — a `TaskGroup` cancels its siblings automatically
|
|
50
|
+
when one fails.
|
|
51
|
+
- Treat `asyncio.CancelledError` as a signal to clean up and re-raise. Since
|
|
52
|
+
Python 3.8 it subclasses `BaseException`, not `Exception`, so a broad
|
|
53
|
+
`except Exception` does *not* catch it; only a bare `except:` or an
|
|
54
|
+
explicit `except BaseException` does, and either one must re-raise it
|
|
55
|
+
rather than swallow it.
|
|
56
|
+
- Never call a blocking function (`requests.get`, `time.sleep`, synchronous
|
|
57
|
+
file I/O, a CPU-bound loop) directly inside an `async def` — use the async
|
|
58
|
+
client, `asyncio.sleep`, or `asyncio.to_thread`/an executor instead.
|
|
59
|
+
|
|
60
|
+
## Iteration and collections
|
|
61
|
+
|
|
62
|
+
- Prefer a generator or generator expression over building an intermediate
|
|
63
|
+
list when the caller only iterates the result once.
|
|
64
|
+
- Use `itertools`/`collections` (`defaultdict`, `Counter`, `chain`,
|
|
65
|
+
`groupby`) instead of hand-rolled loops that reimplement them.
|
|
66
|
+
- Avoid a mutable default argument (`def f(items=[])`); default to `None`
|
|
67
|
+
and initialize inside the function body — a mutable default is shared and
|
|
68
|
+
mutated across every call that omits the argument.
|
|
69
|
+
|
|
70
|
+
## Logging
|
|
71
|
+
|
|
72
|
+
- Use the standard `logging` module (or the project's configured structured
|
|
73
|
+
logger), not `print`, for anything beyond a throwaway script.
|
|
74
|
+
- Pass interpolation arguments to the logging call
|
|
75
|
+
(`logger.info("got %s", value)`), not an f-string
|
|
76
|
+
(`logger.info(f"got {value}")`) — the f-string formats even when the log
|
|
77
|
+
level is disabled, and the lazy form does not.
|
|
78
|
+
|
|
79
|
+
## Anti-patterns to flag, not introduce
|
|
80
|
+
|
|
81
|
+
- Wrapping an entire function body in `try/except Exception: pass` to make a
|
|
82
|
+
flaky call "just work" — this hides real failures instead of handling the
|
|
83
|
+
specific one expected.
|
|
84
|
+
- Reassigning a loop variable's type mid-loop, or reusing one name for two
|
|
85
|
+
unrelated purposes in the same function — confuses both readers and type
|
|
86
|
+
checkers.
|
|
87
|
+
- Deep inheritance chains built to share a few methods — prefer composition
|
|
88
|
+
or a `Protocol` unless the hierarchy models a genuine is-a relationship.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.py", "**/*.pyi"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Python security
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic security guidance to Python's own
|
|
11
|
+
OWASP-style sinks and the safe API each one has a direct replacement for.
|
|
12
|
+
Applies only to `*.py`/`*.pyi` files.
|
|
13
|
+
|
|
14
|
+
## Command and code execution
|
|
15
|
+
|
|
16
|
+
- Never call `subprocess.run`/`Popen`/`call` with `shell=True` or a single
|
|
17
|
+
interpolated command string; pass an argument list
|
|
18
|
+
(`subprocess.run(["cmd", arg])`) so the shell never re-parses untrusted
|
|
19
|
+
input.
|
|
20
|
+
- Never pass untrusted input to `eval`/`exec`, `compile()` of untrusted
|
|
21
|
+
source, or `os.system` — there is no safe way to sandbox these; parse the
|
|
22
|
+
input with a real parser (`json`, `ast.literal_eval` for literals) instead.
|
|
23
|
+
- Treat `ast.literal_eval` as the only acceptable "eval-like" call on
|
|
24
|
+
untrusted text, and only for literal Python values, never arbitrary
|
|
25
|
+
expressions.
|
|
26
|
+
|
|
27
|
+
## Deserialization
|
|
28
|
+
|
|
29
|
+
- Never unpickle (`pickle.load`/`loads`, `dill`, `shelve`) data from a
|
|
30
|
+
source you do not fully trust and control — unpickling untrusted bytes is
|
|
31
|
+
equivalent to `exec`. Use `json` or a schema-validated format instead.
|
|
32
|
+
- Use `yaml.safe_load`, never `yaml.load`, for any YAML that did not
|
|
33
|
+
originate from your own trusted config in the repo.
|
|
34
|
+
- Parse XML with `defusedxml` instead of raw
|
|
35
|
+
`xml.etree.ElementTree`/`xml.dom.minidom` on untrusted input. Current
|
|
36
|
+
`xml.etree`/expat no longer fetch external entities by default, so classic
|
|
37
|
+
XXE is not the risk; the remaining risk is entity-expansion ("billion
|
|
38
|
+
laughs") and similar resource-exhaustion DoS attacks, which the stdlib
|
|
39
|
+
parsers do not guard against and `ET.parse` has no option to disable —
|
|
40
|
+
`defusedxml` is still the safe choice for untrusted input.
|
|
41
|
+
|
|
42
|
+
## Data access
|
|
43
|
+
|
|
44
|
+
- Build SQL with parameterized queries (`cursor.execute("... WHERE id = %s",
|
|
45
|
+
(id,))` or the ORM's own query builder), never by interpolating values
|
|
46
|
+
into a SQL string with f-strings/`%`/`.format()`.
|
|
47
|
+
- Validate and normalize any filesystem path built from user input before
|
|
48
|
+
using it; resolve with `Path(...).resolve()` and check it stays under the
|
|
49
|
+
intended base directory to prevent path traversal (`../../etc/passwd`).
|
|
50
|
+
|
|
51
|
+
## Secrets and randomness
|
|
52
|
+
|
|
53
|
+
- Generate tokens, session IDs, and password-reset codes with the `secrets`
|
|
54
|
+
module (`secrets.token_urlsafe`, `secrets.compare_digest`), never `random`
|
|
55
|
+
— `random` is not cryptographically secure and its output is predictable.
|
|
56
|
+
- Never hard-code a credential, API key, or secret in source; read it from
|
|
57
|
+
the project's configured secret store or environment, and never log it.
|
|
58
|
+
|
|
59
|
+
## Network calls
|
|
60
|
+
|
|
61
|
+
- Always pass an explicit `timeout` to `requests`/`httpx` calls — an
|
|
62
|
+
unbounded call to a slow or hung endpoint can hang the whole process.
|
|
63
|
+
- Never set `verify=False` (requests) or disable TLS certificate validation
|
|
64
|
+
to work around a cert error; fix the underlying trust-store/cert issue
|
|
65
|
+
instead.
|
|
66
|
+
|
|
67
|
+
## Dependencies
|
|
68
|
+
|
|
69
|
+
- Run the project's dependency audit tool (`pip-audit`, or `uv pip list
|
|
70
|
+
--outdated` plus the project's configured scanner) before adding a new
|
|
71
|
+
third-party dependency with security-sensitive functionality (auth,
|
|
72
|
+
crypto, deserialization, subprocess wrapping).
|
|
73
|
+
- Pin dependencies the project already pins (lockfile present) rather than
|
|
74
|
+
loosening a version constraint to resolve a conflict without checking the
|
|
75
|
+
changelog for the versions in between.
|
|
76
|
+
|
|
77
|
+
## Red flags
|
|
78
|
+
|
|
79
|
+
- "I'll just use `shell=True` here, the input is only ever a filename" — an
|
|
80
|
+
attacker-controlled filename can still contain shell metacharacters;
|
|
81
|
+
always pass an argument list.
|
|
82
|
+
- "`eval` is fine, this string only comes from our own config file" — if the
|
|
83
|
+
config file is ever user-editable or fetched remotely, this stops being
|
|
84
|
+
true; prefer `ast.literal_eval` or `json.loads` regardless.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.py", "**/*.pyi"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Python testing
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic testing guidance to `pytest`
|
|
11
|
+
conventions. Applies only to `*.py`/`*.pyi` files; see
|
|
12
|
+
`skills/python-testing/SKILL.md` for the full test-authoring workflow this
|
|
13
|
+
rule file backs.
|
|
14
|
+
|
|
15
|
+
## Layout and discovery
|
|
16
|
+
|
|
17
|
+
- Match the project's existing layout: a `tests/` tree mirroring `src/`, or
|
|
18
|
+
co-located `test_*.py`/`*_test.py` files — do not introduce the other
|
|
19
|
+
layout alongside it.
|
|
20
|
+
- Name test files, classes, and functions so `pytest`'s default discovery
|
|
21
|
+
finds them (`test_*.py`/`*_test.py`, `Test*` classes with no `__init__`,
|
|
22
|
+
`test_*` functions) rather than relying on manual `testpaths` overrides.
|
|
23
|
+
- Keep a fixture in the narrowest `conftest.py` that covers every test
|
|
24
|
+
needing it; do not hoist a single-file fixture to the suite root.
|
|
25
|
+
|
|
26
|
+
## Fixtures and parametrization
|
|
27
|
+
|
|
28
|
+
- Prefer the pytest default `function` scope; widen to `module`/`session`
|
|
29
|
+
only for expensive, read-only setup, and never for a fixture that mutates
|
|
30
|
+
shared state.
|
|
31
|
+
- Use `@pytest.mark.parametrize` when only inputs vary; use a fixture (or
|
|
32
|
+
`pytest.fixture(params=[...])`) when setup/teardown logic itself varies
|
|
33
|
+
between cases.
|
|
34
|
+
- Give parametrized cases explicit `ids=` when the parameter values are not
|
|
35
|
+
self-describing in pytest's default test-ID output.
|
|
36
|
+
|
|
37
|
+
## Mocking
|
|
38
|
+
|
|
39
|
+
- Patch at the point of use (`mocker.patch("mypkg.module.dependency")`), not
|
|
40
|
+
at the dependency's definition site.
|
|
41
|
+
- Mock only external dependencies (network, filesystem, other services,
|
|
42
|
+
wall-clock time); an internal collaborator mocked away stops the test
|
|
43
|
+
verifying real integration between your own modules.
|
|
44
|
+
- Prefer `unittest.mock.AsyncMock` (or `pytest-mock`'s `mocker.patch` with
|
|
45
|
+
`new=AsyncMock()`) for mocking an `async def` dependency, not a plain
|
|
46
|
+
`Mock` that returns a coroutine-shaped object nothing ever awaits.
|
|
47
|
+
|
|
48
|
+
## Async tests
|
|
49
|
+
|
|
50
|
+
- Mark an async test with `pytest.mark.asyncio` (or the project's configured
|
|
51
|
+
async plugin/mode) — a sync test function that merely calls a coroutine
|
|
52
|
+
without awaiting it silently never runs the coroutine's body.
|
|
53
|
+
- Use the project's configured event-loop fixture scope; do not create a new
|
|
54
|
+
event loop per test unless the project's async plugin requires it.
|
|
55
|
+
|
|
56
|
+
## Determinism
|
|
57
|
+
|
|
58
|
+
- Never depend on real wall-clock time, network access, or filesystem state
|
|
59
|
+
outside a fixture-managed temp directory (`tmp_path`) — freeze time
|
|
60
|
+
(`freezegun` or the project's equivalent) and mock the network.
|
|
61
|
+
- Never depend on dict/set iteration order for test correctness beyond
|
|
62
|
+
Python's own guaranteed dict insertion order; sort collections before
|
|
63
|
+
comparing when the underlying data structure does not guarantee order.
|
|
64
|
+
- A flaky test (passes/fails nondeterministically) gets fixed at its root
|
|
65
|
+
cause (a missing await, an unmocked clock, a race) — never retried into
|
|
66
|
+
passing or skipped to hide the flake.
|
|
67
|
+
|
|
68
|
+
## Coverage expectations
|
|
69
|
+
|
|
70
|
+
- Every new or touched public function/class/endpoint gets at least one
|
|
71
|
+
test covering its happy path, its documented edge cases, and any raised
|
|
72
|
+
exception type.
|
|
73
|
+
- A test asserts one behavior; a test with several unrelated assertions
|
|
74
|
+
should split so a failure names the specific behavior that broke.
|
|
75
|
+
- Run the project's configured coverage tool (`pytest --cov` when
|
|
76
|
+
configured) and treat an uncovered branch in touched code as a gap to
|
|
77
|
+
close, not to suppress with a `# pragma: no cover` on real logic.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-build-fix
|
|
3
|
+
description: "Use when a Python project fails to import, build, type-check, or lint -- resolves ModuleNotFoundError/ImportError, packaging or editable-install failures, dependency resolver conflicts (pip/uv/poetry), mypy/pyright type errors, ruff failures, and pytest collection errors with the smallest root-cause fix."
|
|
4
|
+
triggers:
|
|
5
|
+
- "fix this ModuleNotFoundError"
|
|
6
|
+
- "python import is failing"
|
|
7
|
+
- "mypy is failing"
|
|
8
|
+
- "ruff check is failing"
|
|
9
|
+
- "pip dependency conflict"
|
|
10
|
+
- "pytest collection error"
|
|
11
|
+
- "editable install is broken"
|
|
12
|
+
metadata:
|
|
13
|
+
origin: authored
|
|
14
|
+
category: build-fix
|
|
15
|
+
version: "1.0.0"
|
|
16
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
17
|
+
license: "MIT"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Python build-fix
|
|
21
|
+
|
|
22
|
+
Resolve a broken Python build, import, dependency, type-check, or lint
|
|
23
|
+
failure with the smallest change that fixes the actual cause. Scoped to
|
|
24
|
+
making the toolchain green again — for adding a feature use
|
|
25
|
+
`python-implementation`, for writing/fixing test *content* (not a collection
|
|
26
|
+
error) use `python-testing`, for reviewing without fixing use
|
|
27
|
+
`python-code-review`.
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### Step 1: Reproduce and classify the failure
|
|
32
|
+
|
|
33
|
+
Run the project's own configured commands (from `pyproject.toml`, discover
|
|
34
|
+
the run prefix — `uv run`, `poetry run`, or none):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
ruff check .
|
|
38
|
+
ruff format --check .
|
|
39
|
+
mypy . # or: pyright
|
|
40
|
+
pytest -x -q
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Read the *first* error in each tool's output — later errors are often
|
|
44
|
+
downstream of the first. Classify:
|
|
45
|
+
|
|
46
|
+
- **Import/ModuleNotFoundError** — package not installed, wrong environment
|
|
47
|
+
active, circular import, or a typo'd module path.
|
|
48
|
+
- **Packaging/editable install** — `pip install -e .` fails, or an installed
|
|
49
|
+
package's modules aren't importable (missing `__init__.py`, wrong `src/`
|
|
50
|
+
layout declared in `pyproject.toml`'s `[tool.hatch.build]`/
|
|
51
|
+
`[build-system]`).
|
|
52
|
+
- **Dependency resolver conflict** — `pip`/`uv`/`poetry` reports
|
|
53
|
+
incompatible version constraints between two dependencies.
|
|
54
|
+
- **Type errors** — `mypy`/`pyright` reports a real type mismatch.
|
|
55
|
+
- **Lint failures** — `ruff check` reports a rule violation.
|
|
56
|
+
- **Pytest collection errors** — a test file fails to import (distinct from
|
|
57
|
+
a test that runs and fails).
|
|
58
|
+
|
|
59
|
+
### Step 2: Find the root cause
|
|
60
|
+
|
|
61
|
+
- **ModuleNotFoundError**: is the package installed in the active
|
|
62
|
+
environment (`pip show <pkg>` / `uv pip show <pkg>`)? Is the import path
|
|
63
|
+
correct relative to the project's `src/`-layout or flat layout? Is this a
|
|
64
|
+
circular import (`A` imports `B` which imports `A`) that needs a
|
|
65
|
+
restructure (move the shared symbol, or import inside the function) — not
|
|
66
|
+
a `try/except ImportError` wrapper.
|
|
67
|
+
- **Packaging/editable install**: check `[build-system]` and the package
|
|
68
|
+
discovery config (`[tool.setuptools.packages.find]` or `[tool.hatch.build]`)
|
|
69
|
+
actually points at the real package directory; a missing `__init__.py` in
|
|
70
|
+
a namespace-package-by-mistake is a common cause.
|
|
71
|
+
- **Dependency conflict**: read the resolver's own explanation of which two
|
|
72
|
+
constraints collide; find the actual compatible version range (check the
|
|
73
|
+
conflicting packages' own changelogs/release notes) rather than force-
|
|
74
|
+
installing with `--no-deps` or pinning to an arbitrary older version.
|
|
75
|
+
- **Type errors**: read the exact mismatch mypy/pyright reports; fix the
|
|
76
|
+
signature or the call site — whichever is actually wrong given the
|
|
77
|
+
function's real contract, not whichever silences the error fastest.
|
|
78
|
+
- **Lint failures**: apply `ruff check --fix .` for genuinely mechanical
|
|
79
|
+
fixes (unused imports, import order); for a substantive rule (unused
|
|
80
|
+
variable that indicates a real bug, `S`-prefixed security rule) fix the
|
|
81
|
+
code, don't suppress the rule.
|
|
82
|
+
- **Pytest collection errors**: usually an import error in the test file or
|
|
83
|
+
`conftest.py` itself — apply the same import-error diagnosis above to the
|
|
84
|
+
test file's own imports.
|
|
85
|
+
|
|
86
|
+
### Step 3: Apply the smallest fix
|
|
87
|
+
|
|
88
|
+
- Fix the actual cause identified in Step 2 — the missing dependency, the
|
|
89
|
+
wrong import path, the real type mismatch, the actual lint violation.
|
|
90
|
+
- When a version conflict is genuinely unresolvable without a larger
|
|
91
|
+
upgrade, say so explicitly in the report rather than silently pinning
|
|
92
|
+
around it.
|
|
93
|
+
- Touch only what the failure requires; do not refactor unrelated code
|
|
94
|
+
while fixing a build failure.
|
|
95
|
+
|
|
96
|
+
### Step 4: Verify
|
|
97
|
+
|
|
98
|
+
Re-run every command from Step 1 in order; all must exit 0. Also run
|
|
99
|
+
`pytest -x -q` even when the original failure was only a lint/type error —
|
|
100
|
+
a fix can introduce a runtime regression the linter/type-checker won't see.
|
|
101
|
+
|
|
102
|
+
### Step 5: Report
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Fixed: ModuleNotFoundError: No module named 'mypkg.parsers'
|
|
106
|
+
Root cause: src/mypkg/parsers.py existed but pyproject.toml's package-find
|
|
107
|
+
config excluded src/mypkg/, so the editable install never linked it.
|
|
108
|
+
Fix: added "mypkg*" to [tool.setuptools.packages.find].include
|
|
109
|
+
Verified: ruff check, mypy, pytest -x -q all green
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Rules
|
|
113
|
+
|
|
114
|
+
- NEVER add `# type: ignore` or `# noqa` as a blanket suppression to make a
|
|
115
|
+
real error disappear without fixing or explicitly justifying it inline.
|
|
116
|
+
- NEVER pin, downgrade, or `--no-deps` install a dependency to route around
|
|
117
|
+
a real conflict without stating in the report that this is a workaround
|
|
118
|
+
and why a proper fix wasn't available.
|
|
119
|
+
- NEVER wrap a real ImportError in `try/except ImportError: pass` to hide a
|
|
120
|
+
missing dependency — install/declare it, or fix the import path.
|
|
121
|
+
- Fix the root cause with the smallest change; do not refactor beyond what
|
|
122
|
+
the failure requires.
|
|
123
|
+
|
|
124
|
+
## Red Flags
|
|
125
|
+
|
|
126
|
+
| Rationalization | Why it is wrong |
|
|
127
|
+
|---|---|
|
|
128
|
+
| "I'll just add `# type: ignore` here, the real fix is bigger" | Hides the type hole permanently; if the real fix is out of scope, say so in the report and leave the error visible rather than silently suppressing it |
|
|
129
|
+
| "I'll pin this package to the old version that worked" | Papers over an incompatibility that will resurface; identify the actual compatible range or report that a larger upgrade is needed |
|
|
130
|
+
| "The test file won't import, I'll just skip it with `pytest.mark.skip`" | A collection error means the test never runs at all; skipping hides that permanently instead of fixing the import |
|
|
131
|
+
| "`ruff check --fix` didn't fix everything, I'll disable the rule in `pyproject.toml`" | Disabling a rule project-wide silences it for all future code, not just this failure; fix the flagged code instead |
|
|
132
|
+
|
|
133
|
+
## Verification
|
|
134
|
+
|
|
135
|
+
Do not report the fix done until all of the following hold:
|
|
136
|
+
|
|
137
|
+
- The originally failing command now exits 0.
|
|
138
|
+
- `ruff check .`, `ruff format --check .`, `mypy .`/`pyright`, and
|
|
139
|
+
`pytest -x -q` (the project's own configured equivalents) all exit 0.
|
|
140
|
+
- No new `# type: ignore`/`# noqa` was added without an inline reason, and
|
|
141
|
+
none was added as a blanket suppression.
|
|
142
|
+
- `git status` shows only the files whose actual cause was diagnosed in
|
|
143
|
+
Step 2 — no unrelated refactor.
|
|
144
|
+
- The report names the root cause, not just the symptom that was fixed.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"Fix this ModuleNotFoundError: No module named 'mypkg.util'",
|
|
5
|
+
"Our editable pip install is broken, the package won't import",
|
|
6
|
+
"mypy is failing with a type error I don't understand, please fix it",
|
|
7
|
+
"ruff check is failing on our CI, fix the violations",
|
|
8
|
+
"pip is reporting a dependency resolver conflict between two packages",
|
|
9
|
+
"pytest collection is erroring out before any tests even run"
|
|
10
|
+
],
|
|
11
|
+
"negative": [
|
|
12
|
+
"Implement a new feature that uses asyncio.TaskGroup",
|
|
13
|
+
"Write pytest tests for the new widgets module",
|
|
14
|
+
"Review this Python diff for security issues",
|
|
15
|
+
"Fix the TypeScript build failure in our Node service",
|
|
16
|
+
"Run a full test-generation pass over this untested module",
|
|
17
|
+
"Review this code for architecture violations"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"scenarios": [
|
|
21
|
+
{
|
|
22
|
+
"id": "module-not-found-diagnosis",
|
|
23
|
+
"prompt": "Our Python project fails with `ModuleNotFoundError: No module named 'mypkg.util'` when running pytest. How do you diagnose and fix this?",
|
|
24
|
+
"strictness": "high",
|
|
25
|
+
"expected_behavior": [
|
|
26
|
+
{
|
|
27
|
+
"grader": "judge",
|
|
28
|
+
"rubric": "A correct answer diagnoses the actual cause of the ModuleNotFoundError (the package not installed in the active environment, a wrong import path relative to the project's layout, or a circular import) before proposing a fix, and the fix addresses that cause rather than hiding the failure.",
|
|
29
|
+
"pass_criteria": [
|
|
30
|
+
"identifies a concrete root cause: the package/module is not installed or the wrong environment is active, the import path is wrong for the project's src/flat layout, or the failure is a circular import",
|
|
31
|
+
"applies a fix matched to that cause and names the concrete action taken -- the actual install command (e.g. `pip install -e .`), the corrected import path, or the specific packaging-config key changed -- not just 'fix the packaging config' in the abstract",
|
|
32
|
+
"distinguishes the root cause from the symptom in how it explains or reports the fix, instead of only restating the traceback"
|
|
33
|
+
],
|
|
34
|
+
"fail_criteria": [
|
|
35
|
+
"recommends wrapping the failing import in try/except ImportError (e.g. `try/except ImportError: pass`) to hide the failure instead of fixing it. Mentioning the suppression only to warn against it is not a failure."
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"calibration": {
|
|
40
|
+
"known_right": "First check whether the environment ModuleNotFoundError is actually happening in has mypkg installed: run `pip show mypkg` (or `uv pip show mypkg`) in the same environment pytest uses. If it's missing or the wrong virtualenv is active, install it (`pip install -e .` from the project root) so the active env matches what's configured in pyproject.toml. If the package is installed, check whether mypkg/util.py actually exists at the path the import expects given the project's src/ or flat layout, and whether the package-discovery config in pyproject.toml (`[tool.setuptools.packages.find]` or `[tool.hatch.build]`) actually includes that subpackage. Also check for a circular import: does util.py import something that eventually imports back into util itself? If so, restructure by moving the shared symbol or importing inside the function, not by wrapping the import in try/except ImportError. Once fixed, rerun pytest and confirm the module imports cleanly and the report states the actual root cause found.",
|
|
41
|
+
"known_wrong": "Easiest fix: add a try/except ImportError around the import so pytest doesn't crash.\n\n```python\ntry:\n from mypkg.util import helper\nexcept ImportError:\n helper = None\n```\n\nThis way the module loads even if mypkg.util isn't available in this environment, and any code that calls helper() just gets None back instead of crashing at import time. You can leave the rest of the code as-is and run pytest again -- it should collect without the ModuleNotFoundError now. If something later needs helper for real, you can always properly install the package then, but for now this unblocks the test run without having to chase down whether it's a packaging config issue or an environment problem.",
|
|
42
|
+
"vague": "Check whether the module is actually available in the environment running pytest, and make sure the import path lines up with how the project is laid out, then re-run the tests instead of working around the error.",
|
|
43
|
+
"subtle_wrong": "This is happening because mypkg isn't installed in the environment pytest uses. Rather than chasing down the packaging config, I added `sys.path.insert(0, \"src\")` to the top of conftest.py so the import resolves locally without a real install, and pytest collects cleanly now."
|
|
44
|
+
},
|
|
45
|
+
"anti_patterns": ["try/except ImportError"]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "mypy-error-no-blanket-suppress",
|
|
49
|
+
"prompt": "mypy reports a type error on a function I touched. Fix it.",
|
|
50
|
+
"strictness": "high",
|
|
51
|
+
"expected_behavior": [
|
|
52
|
+
{
|
|
53
|
+
"grader": "judge",
|
|
54
|
+
"rubric": "A correct answer fixes the real type mismatch mypy reported -- by correcting the function's signature/annotation or the call site that violates it -- rather than making the error disappear with a blanket suppression. Because the prompt supplies neither the error text nor the code, an answer that first asks for the exact mypy message and the touched function, while explicitly ruling out a suppression as the eventual fix, is also correct: it cannot name the concrete mismatch without that information, and asking for it is the honest move.",
|
|
55
|
+
"pass_criteria": [
|
|
56
|
+
"either names the exact mismatch mypy reported (the signature, the annotation, or the call site's type) and changes that code to resolve it, or -- since neither the error text nor the code was given -- asks for the actual mypy message and the touched function before proposing a fix",
|
|
57
|
+
"commits to fixing the real mismatch once it is identified or known: the annotation, the call site, or a narrowing -- never treats a suppression as the fix itself",
|
|
58
|
+
"explicitly refuses a blanket `# type: ignore`/`# noqa` suppression, a loosened mypy config, or retyping the parameter/return as `Any` as a substitute for the real fix"
|
|
59
|
+
],
|
|
60
|
+
"fail_criteria": [
|
|
61
|
+
"adds `# type: ignore` (bare or blanket) or `# noqa`, loosens the mypy config (e.g. disables the check globally), or retypes the value as `Any` to make the error disappear, instead of fixing or asking about the actual mismatch. Mentioning the suppression only to warn against it is not a failure."
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"calibration": {
|
|
66
|
+
"known_right": "Read mypy's exact message first -- it names the mismatch, e.g. \"Argument 1 to 'process' has incompatible type 'str'; expected 'int'\". Look at the function's real contract: does `process` genuinely only make sense for `int`, or was the parameter always meant to accept both? If the function should accept `int`, fix the call site to pass an int (or convert it) instead of touching the signature. If the function's contract is actually broader than what's declared, widen the annotation to `int | str` and adjust the body to handle both cases correctly. Either way, don't add `# type: ignore` on the line just to quiet mypy -- that hides the real hole for every future caller. Once the annotation and the call sites agree with what the function actually does, rerun `mypy .` to confirm it's clean, and rerun pytest since a signature change can affect runtime behavior too.",
|
|
67
|
+
"known_wrong": "mypy is being overly strict here, the code works fine at runtime. Simplest fix is to just silence it:\n\n```python\ndef process(value) -> None: # type: ignore\n ...\n```\n\nThat clears the error mypy was reporting without needing to dig into why the annotation didn't match the call site -- the function still behaves the same, mypy just stops complaining. You can move on to the actual feature work instead of spending time chasing down whether the argument type or the annotation was the one that was wrong.",
|
|
68
|
+
"vague": "Take a look at what mypy is complaining about in that function and adjust the type so it lines up correctly, instead of just suppressing the warning.",
|
|
69
|
+
"subtle_wrong": "mypy's message is annoying but I don't have time to dig into why the annotation doesn't match the call site right now, so I added `# type: ignore[arg-type]` on that one line -- it's scoped to just that error code, not a blanket ignore, and I left a comment saying it's temporary until we get back to the real fix."
|
|
70
|
+
},
|
|
71
|
+
"anti_patterns": ["# type: ignore"]
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|