@mrciphersmith/keryx 0.2.164 → 0.3.0

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.
Files changed (100) hide show
  1. package/dist/cli.js +85355 -56749
  2. package/dist/core.js +28605 -18901
  3. package/package.json +2 -2
  4. package/src/gdgraph/affected-report.ts +141 -0
  5. package/src/gdgraph/build.ts +170 -23
  6. package/src/gdgraph/service.ts +6 -0
  7. package/src/gdgraph/staleness.ts +253 -45
  8. package/src/gdskills/bundled/agents/codebase-navigator.md +55 -0
  9. package/src/gdskills/bundled/agents/design-advisor.md +64 -0
  10. package/src/gdskills/bundled/agents/docs-maintainer.md +56 -0
  11. package/src/gdskills/bundled/agents/end-to-end-tester.md +56 -0
  12. package/src/gdskills/bundled/agents/error-path-auditor.md +57 -0
  13. package/src/gdskills/bundled/agents/go-build-fixer.md +52 -0
  14. package/src/gdskills/bundled/agents/go-code-auditor.md +49 -0
  15. package/src/gdskills/bundled/agents/performance-auditor.md +63 -0
  16. package/src/gdskills/bundled/agents/python-build-fixer.md +52 -0
  17. package/src/gdskills/bundled/agents/python-code-auditor.md +49 -0
  18. package/src/gdskills/bundled/agents/refactoring-steward.md +61 -0
  19. package/src/gdskills/bundled/agents/security-auditor.md +62 -0
  20. package/src/gdskills/bundled/agents/test-first-driver.md +61 -0
  21. package/src/gdskills/bundled/agents/work-planner.md +62 -0
  22. package/src/gdskills/bundled/install-manifest.json +530 -0
  23. package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +29 -1
  24. package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +2 -2
  25. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +1 -1
  26. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +74 -246
  27. package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +19 -0
  28. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +10 -0
  29. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +5 -0
  30. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-backend.md +50 -0
  31. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-frontend.md +52 -0
  32. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/review-report.md +143 -0
  33. package/src/gdskills/bundled/stacks/go/agent-refs.json +3 -0
  34. package/src/gdskills/bundled/stacks/go/governance/eval.json +1745 -0
  35. package/src/gdskills/bundled/stacks/go/governance/scout.json +31 -0
  36. package/src/gdskills/bundled/stacks/go/pack.json +41 -0
  37. package/src/gdskills/bundled/stacks/go/rules/coding-style.mdc +85 -0
  38. package/src/gdskills/bundled/stacks/go/rules/patterns.mdc +65 -0
  39. package/src/gdskills/bundled/stacks/go/rules/security.mdc +73 -0
  40. package/src/gdskills/bundled/stacks/go/rules/testing.mdc +68 -0
  41. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/SKILL.md +138 -0
  42. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/evals.json +75 -0
  43. package/src/gdskills/bundled/stacks/go/skills/go-code-review/SKILL.md +121 -0
  44. package/src/gdskills/bundled/stacks/go/skills/go-code-review/evals.json +72 -0
  45. package/src/gdskills/bundled/stacks/go/skills/go-implementation/SKILL.md +122 -0
  46. package/src/gdskills/bundled/stacks/go/skills/go-implementation/evals.json +76 -0
  47. package/src/gdskills/bundled/stacks/go/skills/go-testing/SKILL.md +126 -0
  48. package/src/gdskills/bundled/stacks/go/skills/go-testing/evals.json +73 -0
  49. package/src/gdskills/bundled/stacks/python/agent-refs.json +3 -0
  50. package/src/gdskills/bundled/stacks/python/governance/eval.json +1758 -0
  51. package/src/gdskills/bundled/stacks/python/governance/scout.json +34 -0
  52. package/src/gdskills/bundled/stacks/python/pack.json +41 -0
  53. package/src/gdskills/bundled/stacks/python/rules/coding-style.mdc +63 -0
  54. package/src/gdskills/bundled/stacks/python/rules/patterns.mdc +88 -0
  55. package/src/gdskills/bundled/stacks/python/rules/security.mdc +84 -0
  56. package/src/gdskills/bundled/stacks/python/rules/testing.mdc +77 -0
  57. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/SKILL.md +144 -0
  58. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/evals.json +74 -0
  59. package/src/gdskills/bundled/stacks/python/skills/python-code-review/SKILL.md +155 -0
  60. package/src/gdskills/bundled/stacks/python/skills/python-code-review/evals.json +72 -0
  61. package/src/gdskills/bundled/stacks/python/skills/python-implementation/SKILL.md +143 -0
  62. package/src/gdskills/bundled/stacks/python/skills/python-implementation/evals.json +78 -0
  63. package/src/gdskills/bundled/stacks/python/skills/python-testing/SKILL.md +132 -0
  64. package/src/gdskills/bundled/stacks/python/skills/python-testing/evals.json +73 -0
  65. package/src/gdskills/bundled/stacks/react/agent-refs.json +4 -0
  66. package/src/gdskills/bundled/stacks/react/governance/eval.json +2188 -0
  67. package/src/gdskills/bundled/stacks/react/governance/scout.json +40 -0
  68. package/src/gdskills/bundled/stacks/react/pack.json +42 -0
  69. package/src/gdskills/bundled/stacks/react/rules/coding-style.mdc +58 -0
  70. package/src/gdskills/bundled/stacks/react/rules/patterns.mdc +79 -0
  71. package/src/gdskills/bundled/stacks/react/rules/security.mdc +70 -0
  72. package/src/gdskills/bundled/stacks/react/rules/testing.mdc +60 -0
  73. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/SKILL.md +139 -0
  74. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/evals.json +72 -0
  75. package/src/gdskills/bundled/stacks/react/skills/react-code-review/SKILL.md +148 -0
  76. package/src/gdskills/bundled/stacks/react/skills/react-code-review/evals.json +74 -0
  77. package/src/gdskills/bundled/stacks/react/skills/react-implementation/SKILL.md +140 -0
  78. package/src/gdskills/bundled/stacks/react/skills/react-implementation/evals.json +74 -0
  79. package/src/gdskills/bundled/stacks/react/skills/react-testing/SKILL.md +142 -0
  80. package/src/gdskills/bundled/stacks/react/skills/react-testing/evals.json +83 -0
  81. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/SKILL.md +155 -0
  82. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/evals.json +74 -0
  83. package/src/gdskills/bundled/stacks/ts-js-node/agent-refs.json +4 -0
  84. package/src/gdskills/bundled/stacks/ts-js-node/governance/eval.json +2155 -0
  85. package/src/gdskills/bundled/stacks/ts-js-node/governance/scout.json +40 -0
  86. package/src/gdskills/bundled/stacks/ts-js-node/pack.json +41 -0
  87. package/src/gdskills/bundled/stacks/ts-js-node/rules/coding-style.mdc +73 -0
  88. package/src/gdskills/bundled/stacks/ts-js-node/rules/patterns.mdc +61 -0
  89. package/src/gdskills/bundled/stacks/ts-js-node/rules/security.mdc +71 -0
  90. package/src/gdskills/bundled/stacks/ts-js-node/rules/testing.mdc +63 -0
  91. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/SKILL.md +137 -0
  92. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/evals.json +73 -0
  93. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/SKILL.md +124 -0
  94. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/evals.json +74 -0
  95. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/SKILL.md +152 -0
  96. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/evals.json +71 -0
  97. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/SKILL.md +127 -0
  98. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/evals.json +72 -0
  99. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/SKILL.md +134 -0
  100. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/evals.json +70 -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
+ }