gennady 0.9.0-next.2 → 0.9.0-next.3
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/ai/directives/coding/baseline-rules.xml +101 -0
- package/ai/directives/coding/go-rules.xml +94 -0
- package/ai/directives/coding/python-rules.xml +92 -0
- package/ai/directives/knowledge.xml +42 -4
- package/ai/directives/testing/baseline-testing.xml +89 -0
- package/dist/ai/directives/coding/baseline-rules.xml +101 -0
- package/dist/ai/directives/coding/go-rules.xml +94 -0
- package/dist/ai/directives/coding/python-rules.xml +92 -0
- package/dist/ai/directives/knowledge.xml +42 -4
- package/dist/ai/directives/testing/baseline-testing.xml +89 -0
- package/dist/gennady.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<BaselineCodingRules keywords="baseline, language_agnostic, contracts, error_handling, naming, yagni, ai_to_ai" type="coding-rules" ver="1.0">
|
|
2
|
+
<!--
|
|
3
|
+
Language-agnostic coding baseline. The few things that hold in ANY language. Language rule files
|
|
4
|
+
(python-rules, go-rules, typescript-rules, …) inherit this and add their own idioms; do not
|
|
5
|
+
restate these axioms there. Activated for source code files of a scope that declares no more
|
|
6
|
+
specific language rule, and as the transitive parent of the language rules that reference it.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
<BeliefState>
|
|
10
|
+
<Axiom id="AX_TELEOLOGICAL_NAMING">
|
|
11
|
+
A name states a goal, not a pattern. Forbidden semantic-noise names: `Manager`, `Handler`,
|
|
12
|
+
`Data`, `Info`, `Wrapper`, `Util`, `do…`, `process…`. `OrderFulfillmentPipeline` carries intent;
|
|
13
|
+
`OrderManager` tells the next reader nothing. Use precise verbs (`retrieve`, `verify`, `compose`)
|
|
14
|
+
over vague ones (`get`, `check`, `make`).
|
|
15
|
+
</Axiom>
|
|
16
|
+
|
|
17
|
+
<Axiom id="AX_EXPLICIT_FAILURE">
|
|
18
|
+
Failures are explicit and carry cause. Raise/return a specific, typed error with a message that
|
|
19
|
+
says what failed and with what input; when re-raising, preserve the original cause (do not
|
|
20
|
+
flatten a stack into a string). Never silently swallow an error or return a zero value that hides
|
|
21
|
+
one.
|
|
22
|
+
</Axiom>
|
|
23
|
+
|
|
24
|
+
<Axiom id="AX_CONTRACT_AT_SURFACE">
|
|
25
|
+
The public surface documents its contract: inputs, output, and failure modes, at the declaration
|
|
26
|
+
— not buried in the body. A caller must be able to use the function from its signature plus one
|
|
27
|
+
doc block, without reading the implementation.
|
|
28
|
+
</Axiom>
|
|
29
|
+
|
|
30
|
+
<Axiom id="AX_INTENT_COMMENTS">
|
|
31
|
+
Comments carry intent the code cannot: purpose, invariant, side effect, failure mode, non-goal,
|
|
32
|
+
or why-not. A comment that restates the syntax on the next line is noise and must be removed.
|
|
33
|
+
</Axiom>
|
|
34
|
+
|
|
35
|
+
<Axiom id="AX_YAGNI_NO_DEFENSE">
|
|
36
|
+
Build only what the task needs. No speculative abstraction (an interface/base class for a single
|
|
37
|
+
caller with no confirmed second one), no dead code, and no defensive branches against states the
|
|
38
|
+
types or invariants already exclude. Prefer the simplest shape that satisfies the contract.
|
|
39
|
+
</Axiom>
|
|
40
|
+
|
|
41
|
+
<Axiom id="AX_SINGLE_RESPONSIBILITY">
|
|
42
|
+
A unit does one thing at one level of abstraction. A function that mixes I/O, policy, and
|
|
43
|
+
formatting is split. Depth of nesting and length are symptoms; name the extracted piece by its
|
|
44
|
+
goal.
|
|
45
|
+
</Axiom>
|
|
46
|
+
</BeliefState>
|
|
47
|
+
|
|
48
|
+
<AntiPatterns>
|
|
49
|
+
<AntiPattern id="AP_SWALLOWED_ERROR">
|
|
50
|
+
<Bad>A caught error that is logged-and-ignored, discarded, or replaced by a generic message that
|
|
51
|
+
drops the original cause — so the failure surfaces later with no trace of its origin.</Bad>
|
|
52
|
+
<Instead>Handle it (recover with a documented fallback) or propagate it with the cause attached.
|
|
53
|
+
One or the other, never neither.</Instead>
|
|
54
|
+
</AntiPattern>
|
|
55
|
+
|
|
56
|
+
<AntiPattern id="AP_PATTERN_NAMING">
|
|
57
|
+
<Bad>`FooManager`, `DataHandler`, `RequestWrapper`, `doWork`, `processItem` — names that describe
|
|
58
|
+
a code pattern rather than a business goal.</Bad>
|
|
59
|
+
<Instead>Name the responsibility: `SubscriptionRenewal`, `parseVcsUrl`, `retryWithBackoff`.</Instead>
|
|
60
|
+
</AntiPattern>
|
|
61
|
+
|
|
62
|
+
<AntiPattern id="AP_SYNTAX_COMMENT">
|
|
63
|
+
<Bad>`// increment i` above `i++`; a doc block that repeats parameter names and types already in
|
|
64
|
+
the signature.</Bad>
|
|
65
|
+
<Instead>Comment the why, the invariant, or the non-obvious consequence — or nothing.</Instead>
|
|
66
|
+
</AntiPattern>
|
|
67
|
+
|
|
68
|
+
<AntiPattern id="AP_SPECULATIVE_ABSTRACTION">
|
|
69
|
+
<Bad>Extracting an interface, factory, or config option for one caller "in case we need it".</Bad>
|
|
70
|
+
<Instead>Inline it; abstract on the second real consumer, when the variance is known.</Instead>
|
|
71
|
+
</AntiPattern>
|
|
72
|
+
</AntiPatterns>
|
|
73
|
+
|
|
74
|
+
<VerificationHooks>
|
|
75
|
+
<Hook id="HOOK_PROJECT_GATE">
|
|
76
|
+
<Purpose>The scope's own quality gate (formatter + linter + type/compile check + tests, as the
|
|
77
|
+
project declares them) passes on the changed files.</Purpose>
|
|
78
|
+
<Command><sdd-path> verify --wip <target-files></Command>
|
|
79
|
+
<Expected>Exit 0. If the scope has not declared its gate yet, that is the finding to fix first.</Expected>
|
|
80
|
+
</Hook>
|
|
81
|
+
<Hook id="HOOK_NO_SCAFFOLD_LEFTOVER">
|
|
82
|
+
<Purpose>No placeholder/scaffold markers survive in shipped code.</Purpose>
|
|
83
|
+
<Command>rg --no-heading -n "TODO|FIXME|XXX|<[A-Z_]+>|placeholder" <target-files></Command>
|
|
84
|
+
<Expected>Empty on changed lines; a real deferred item belongs in a ticket, not a bare marker.</Expected>
|
|
85
|
+
</Hook>
|
|
86
|
+
</VerificationHooks>
|
|
87
|
+
|
|
88
|
+
<RewardCriteria>
|
|
89
|
+
✅ Names state a goal; precise verbs; no `Manager`/`Handler`/`Data`/`Util`/`do…`/`process…`.
|
|
90
|
+
✅ Every failure path raises/returns a specific error with context and preserved cause.
|
|
91
|
+
✅ Public entities document inputs, output, and failure modes at the declaration.
|
|
92
|
+
✅ Comments carry intent (purpose / invariant / side effect / failure / non-goal), never restate syntax.
|
|
93
|
+
✅ Only what the task needs: no speculative abstraction, no dead code, no defense against impossible states.
|
|
94
|
+
✅ Each unit has one responsibility at one abstraction level.
|
|
95
|
+
❌ An error caught and dropped, or re-raised with the cause lost.
|
|
96
|
+
❌ Pattern-shaped names that hide the business goal.
|
|
97
|
+
❌ Comments that restate the code, or doc blocks duplicating the signature.
|
|
98
|
+
❌ An abstraction introduced for a single caller with no confirmed second consumer.
|
|
99
|
+
❌ Placeholder/scaffold markers (`TODO`, `<NAME>`) left in shipped code.
|
|
100
|
+
</RewardCriteria>
|
|
101
|
+
</BaselineCodingRules>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<GoCodingRules keywords="go, golang, gofmt, govet, errors, wrapping, naming, testing, ai_to_ai" type="coding-rules" ver="1.0">
|
|
2
|
+
<!--
|
|
3
|
+
Thin Go baseline. Inherits the language-agnostic coding + testing baselines; adds only the Go
|
|
4
|
+
idioms the community broadly agrees on. Not an exhaustive style guide — deliberately small.
|
|
5
|
+
Grounded in: Effective Go, Go Code Review Comments, go.dev/blog/go1.13-errors, official tooling
|
|
6
|
+
(gofmt, go vet, golangci-lint) — the intersection Uber/Google style guides also share.
|
|
7
|
+
-->
|
|
8
|
+
<DependsOn>
|
|
9
|
+
- ai/directives/coding/baseline-rules.xml
|
|
10
|
+
- ai/directives/testing/baseline-testing.xml
|
|
11
|
+
</DependsOn>
|
|
12
|
+
|
|
13
|
+
<BeliefState>
|
|
14
|
+
<Axiom id="AX_GO_GOFMT">
|
|
15
|
+
Code is `gofmt`/`goimports`-clean — non-negotiable, zero config. `goimports` is preferred (it also
|
|
16
|
+
groups and prunes imports). Tabs, tool-decided layout; there are no formatting debates in Go.
|
|
17
|
+
</Axiom>
|
|
18
|
+
|
|
19
|
+
<Axiom id="AX_GO_CHECK_AND_WRAP_ERRORS">
|
|
20
|
+
`error` is the last return value and every error is checked — never discarded with `_` unless the
|
|
21
|
+
discard is deliberate and justified. Add context by wrapping with `fmt.Errorf("doing X: %w", err)`
|
|
22
|
+
(`%w`, not `%v`, so callers can unwrap). Inspect with `errors.Is` / `errors.As`, not `==` or type
|
|
23
|
+
assertions. Define a sentinel (`var ErrNotFound = errors.New(…)`) only for a condition callers
|
|
24
|
+
actually branch on. Error strings are lowercase, no trailing punctuation.
|
|
25
|
+
</Axiom>
|
|
26
|
+
|
|
27
|
+
<Axiom id="AX_GO_NO_PANIC_IN_LIB">
|
|
28
|
+
Library code returns errors; it does not `panic` on ordinary or expected failures (bad input,
|
|
29
|
+
missing file). `panic` is for programmer bugs and truly unrecoverable state only.
|
|
30
|
+
</Axiom>
|
|
31
|
+
|
|
32
|
+
<Axiom id="AX_GO_NAMING">
|
|
33
|
+
Capitalization is visibility — export only what callers need. No stutter: in package `env`, name it
|
|
34
|
+
`env.Var`, not `env.EnvVar`; `New`/`Open`, not `NewEnv` inside `env`. Receiver names are short (1–2
|
|
35
|
+
letters) and consistent across a type's methods. Interfaces are small and `-er`-named; accept
|
|
36
|
+
interfaces, return concrete structs.
|
|
37
|
+
</Axiom>
|
|
38
|
+
</BeliefState>
|
|
39
|
+
|
|
40
|
+
<AntiPatterns>
|
|
41
|
+
<AntiPattern id="AP_GO_IGNORED_ERROR">
|
|
42
|
+
<Bad>`_ = doThing()` or calling a function that returns `error` and not checking it.</Bad>
|
|
43
|
+
<Instead>Check it: handle, or `return fmt.Errorf("…: %w", err)`. Discard only with a justifying comment.</Instead>
|
|
44
|
+
</AntiPattern>
|
|
45
|
+
<AntiPattern id="AP_GO_PANIC_IN_LIB">
|
|
46
|
+
<Bad>`panic("not found")` inside a reusable package for an expected condition.</Bad>
|
|
47
|
+
<Instead>Return an `error` the caller can handle.</Instead>
|
|
48
|
+
</AntiPattern>
|
|
49
|
+
<AntiPattern id="AP_GO_PERCENT_V_WRAP">
|
|
50
|
+
<Bad>`fmt.Errorf("read config: %v", err)` — `%v` flattens the error; `errors.Is/As` can no longer unwrap it.</Bad>
|
|
51
|
+
<Instead>`fmt.Errorf("read config: %w", err)`.</Instead>
|
|
52
|
+
</AntiPattern>
|
|
53
|
+
<AntiPattern id="AP_GO_STUTTER">
|
|
54
|
+
<Bad>`env.EnvVar`, `client.ClientConfig` — the package qualifier already carries the prefix.</Bad>
|
|
55
|
+
<Instead>`env.Var`, `client.Config`.</Instead>
|
|
56
|
+
</AntiPattern>
|
|
57
|
+
</AntiPatterns>
|
|
58
|
+
|
|
59
|
+
<VerificationHooks>
|
|
60
|
+
<Hook id="HOOK_GO_FMT">
|
|
61
|
+
<Purpose>Code is gofmt-clean.</Purpose>
|
|
62
|
+
<Command>gofmt -l <target-files></Command>
|
|
63
|
+
<Expected>Empty output (no unformatted files).</Expected>
|
|
64
|
+
</Hook>
|
|
65
|
+
<Hook id="HOOK_GO_VET">
|
|
66
|
+
<Purpose>Vet catches the baseline bug classes.</Purpose>
|
|
67
|
+
<Command>go vet ./...</Command>
|
|
68
|
+
<Expected>Exit 0.</Expected>
|
|
69
|
+
</Hook>
|
|
70
|
+
<Hook id="HOOK_GO_LINT">
|
|
71
|
+
<Purpose>golangci-lint default set (govet, errcheck, staticcheck, ineffassign, unused).</Purpose>
|
|
72
|
+
<Command>golangci-lint run</Command>
|
|
73
|
+
<Expected>Exit 0 (skip if the project does not install it; go vet is the floor).</Expected>
|
|
74
|
+
</Hook>
|
|
75
|
+
<Hook id="HOOK_GO_TESTS">
|
|
76
|
+
<Purpose>The behavior's tests pass.</Purpose>
|
|
77
|
+
<Command>go test ./...</Command>
|
|
78
|
+
<Expected>Exit 0.</Expected>
|
|
79
|
+
</Hook>
|
|
80
|
+
</VerificationHooks>
|
|
81
|
+
|
|
82
|
+
<RewardCriteria>
|
|
83
|
+
✅ `gofmt`/`goimports`-clean; `go vet ./...` passes.
|
|
84
|
+
✅ Every error checked; context added with `%w`; inspected via `errors.Is`/`As`.
|
|
85
|
+
✅ Library code returns errors, never panics on expected failures.
|
|
86
|
+
✅ No stutter; minimal exported surface; short consistent receiver names; small `-er` interfaces.
|
|
87
|
+
✅ Table-driven tests with `t.Run` subtests and `t.Helper()` in helpers; stdlib `testing` only at baseline.
|
|
88
|
+
❌ An ignored/`_`-discarded error without justification.
|
|
89
|
+
❌ `panic` in library code for an expected failure.
|
|
90
|
+
❌ `%v` wrapping where `%w` is meant.
|
|
91
|
+
❌ Stutter naming (`env.EnvVar`).
|
|
92
|
+
❌ Speculatively-exported types/functions with no caller.
|
|
93
|
+
</RewardCriteria>
|
|
94
|
+
</GoCodingRules>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<PythonCodingRules keywords="python, type_hints, ruff, mypy, pytest, exceptions, pep8, ai_to_ai" type="coding-rules" ver="1.0">
|
|
2
|
+
<!--
|
|
3
|
+
Thin Python baseline. Inherits the language-agnostic coding + testing baselines; adds only the
|
|
4
|
+
Python idioms the community broadly agrees on. Not an exhaustive style guide — deliberately small.
|
|
5
|
+
Grounded in: PEP 8, Ruff/mypy docs, PyPA packaging guides (2025-2026 mainstream defaults).
|
|
6
|
+
-->
|
|
7
|
+
<DependsOn>
|
|
8
|
+
- ai/directives/coding/baseline-rules.xml
|
|
9
|
+
- ai/directives/testing/baseline-testing.xml
|
|
10
|
+
</DependsOn>
|
|
11
|
+
|
|
12
|
+
<BeliefState>
|
|
13
|
+
<Axiom id="AX_PY_TYPE_HINTS">
|
|
14
|
+
Public function and method signatures are type-annotated, and a checker (mypy or pyright) runs in
|
|
15
|
+
the project's gate. Typing is gradual, not strict, at baseline: annotate the surface, tighten over
|
|
16
|
+
time. Pick one checker per project; do not mix.
|
|
17
|
+
</Axiom>
|
|
18
|
+
|
|
19
|
+
<Axiom id="AX_PY_NARROW_EXCEPT">
|
|
20
|
+
Catch the narrowest exception you can actually handle (`FileNotFoundError`, `ValueError`), never a
|
|
21
|
+
bare `except:` or blanket `except Exception:` — those swallow `KeyboardInterrupt`/`SystemExit` and
|
|
22
|
+
hide bugs. Chain with `raise NewError(...) from err` to keep the cause; re-raise with a bare
|
|
23
|
+
`raise` after logging to keep the original traceback.
|
|
24
|
+
</Axiom>
|
|
25
|
+
|
|
26
|
+
<Axiom id="AX_PY_NO_MUTABLE_DEFAULT">
|
|
27
|
+
No mutable default arguments (`def f(x=[])` / `{}`): the default is shared across calls. Default to
|
|
28
|
+
`None` and construct inside the body.
|
|
29
|
+
</Axiom>
|
|
30
|
+
|
|
31
|
+
<Axiom id="AX_PY_FORMAT_AND_LINT">
|
|
32
|
+
Formatting and lint are tool-owned, not hand-argued: `ruff format` (Black-compatible, 88 columns)
|
|
33
|
+
plus `ruff check` with at least `F` (pyflakes), `E` (pycodestyle errors), `I` (import order), and
|
|
34
|
+
`B` (bugbear). All tool + packaging config lives in a single `pyproject.toml`.
|
|
35
|
+
</Axiom>
|
|
36
|
+
</BeliefState>
|
|
37
|
+
|
|
38
|
+
<AntiPatterns>
|
|
39
|
+
<AntiPattern id="AP_PY_MUTABLE_DEFAULT">
|
|
40
|
+
<Bad>`def append_to(item, target=[]):` — `target` persists and accumulates across calls.</Bad>
|
|
41
|
+
<Instead>`def append_to(item, target=None): target = [] if target is None else target`.</Instead>
|
|
42
|
+
</AntiPattern>
|
|
43
|
+
<AntiPattern id="AP_PY_BARE_EXCEPT">
|
|
44
|
+
<Bad>`try: … except: pass` or `except Exception:` as a catch-all that hides the failure.</Bad>
|
|
45
|
+
<Instead>Catch the specific type you handle; log-and-`raise` or `raise … from err` otherwise.</Instead>
|
|
46
|
+
</AntiPattern>
|
|
47
|
+
<AntiPattern id="AP_PY_WILDCARD_IMPORT">
|
|
48
|
+
<Bad>`from module import *` — pollutes the namespace and defeats static analysis (Ruff F403/F405).</Bad>
|
|
49
|
+
<Instead>Import the names you use explicitly.</Instead>
|
|
50
|
+
</AntiPattern>
|
|
51
|
+
<AntiPattern id="AP_PY_IDENTITY_COMPARE">
|
|
52
|
+
<Bad>`if x == None:` / `== True` — value comparison to a singleton (Ruff E711/E712).</Bad>
|
|
53
|
+
<Instead>`if x is None:` / `if flag:`.</Instead>
|
|
54
|
+
</AntiPattern>
|
|
55
|
+
</AntiPatterns>
|
|
56
|
+
|
|
57
|
+
<VerificationHooks>
|
|
58
|
+
<Hook id="HOOK_PY_FORMAT">
|
|
59
|
+
<Purpose>Code matches the canonical format.</Purpose>
|
|
60
|
+
<Command>ruff format --check <target-files></Command>
|
|
61
|
+
<Expected>Exit 0; no files would be reformatted.</Expected>
|
|
62
|
+
</Hook>
|
|
63
|
+
<Hook id="HOOK_PY_LINT">
|
|
64
|
+
<Purpose>No lint findings from the baseline rule sets.</Purpose>
|
|
65
|
+
<Command>ruff check <target-files></Command>
|
|
66
|
+
<Expected>Exit 0 (F/E/I/B clean).</Expected>
|
|
67
|
+
</Hook>
|
|
68
|
+
<Hook id="HOOK_PY_TYPES">
|
|
69
|
+
<Purpose>Annotations type-check.</Purpose>
|
|
70
|
+
<Command>mypy <target-files></Command>
|
|
71
|
+
<Expected>Exit 0 (or the project's configured pyright equivalent).</Expected>
|
|
72
|
+
</Hook>
|
|
73
|
+
<Hook id="HOOK_PY_TESTS">
|
|
74
|
+
<Purpose>The behavior's tests pass under pytest.</Purpose>
|
|
75
|
+
<Command>pytest -q</Command>
|
|
76
|
+
<Expected>Exit 0.</Expected>
|
|
77
|
+
</Hook>
|
|
78
|
+
</VerificationHooks>
|
|
79
|
+
|
|
80
|
+
<RewardCriteria>
|
|
81
|
+
✅ Public signatures are type-annotated; a checker runs in the gate.
|
|
82
|
+
✅ Exceptions caught are the narrowest handled; re-raise preserves the cause (`raise … from`) or the traceback (bare `raise`).
|
|
83
|
+
✅ No mutable default arguments; `None` + in-body construction.
|
|
84
|
+
✅ `ruff format` clean at 88 cols; `ruff check` clean on F/E/I/B; single `pyproject.toml`.
|
|
85
|
+
✅ Tests use pytest with `@pytest.mark.parametrize` for input variants (one behavior per test).
|
|
86
|
+
❌ Bare `except:` / blanket `except Exception:` that swallows the error.
|
|
87
|
+
❌ Mutable default argument.
|
|
88
|
+
❌ Wildcard import (`from x import *`).
|
|
89
|
+
❌ `== None` / `== True` singleton comparison.
|
|
90
|
+
❌ Re-raising a new exception without `from`, losing the original cause.
|
|
91
|
+
</RewardCriteria>
|
|
92
|
+
</PythonCodingRules>
|
|
@@ -64,14 +64,43 @@
|
|
|
64
64
|
<CheckPhase>lint</CheckPhase>
|
|
65
65
|
<RequiresVerification>check-command</RequiresVerification>
|
|
66
66
|
</Rule>
|
|
67
|
+
<Rule id="baseline-rules">
|
|
68
|
+
<File>ai/directives/coding/baseline-rules.xml</File>
|
|
69
|
+
<Purpose>Language-agnostic coding baseline: teleological naming, explicit failures with cause, contract at the surface, intent comments, YAGNI, single responsibility. Parent of the language rule files.</Purpose>
|
|
70
|
+
<Triggers>Target Files include source code files (not config) of any language</Triggers>
|
|
71
|
+
<SkipWhen>Config-only task; a more specific language rule already covers the file and inherits this</SkipWhen>
|
|
72
|
+
<ActivationHint>Before editing any source file. Language rules (python-rules, go-rules, typescript-rules) inherit this — read the specific one too.</ActivationHint>
|
|
73
|
+
<CheckPhase>lint</CheckPhase>
|
|
74
|
+
<RequiresVerification>check-command</RequiresVerification>
|
|
75
|
+
</Rule>
|
|
67
76
|
<Rule id="typescript-rules">
|
|
68
77
|
<File>ai/directives/coding/typescript-rules.xml</File>
|
|
69
|
-
<Purpose>Writing
|
|
70
|
-
<Triggers>Target Files include
|
|
71
|
-
<SkipWhen>Config-only task; infra-setup
|
|
72
|
-
<ActivationHint>Before editing or creating any source
|
|
78
|
+
<Purpose>Writing TypeScript: typing, DbC, patterns, anti-patterns. The TS language baseline.</Purpose>
|
|
79
|
+
<Triggers>Target Files include .ts / .tsx source files</Triggers>
|
|
80
|
+
<SkipWhen>Config-only task; non-TypeScript language (see python-rules / go-rules); infra-setup without code files</SkipWhen>
|
|
81
|
+
<ActivationHint>Before editing or creating any .ts / .tsx source file</ActivationHint>
|
|
82
|
+
<CheckPhase>typecheck</CheckPhase>
|
|
83
|
+
<RequiresVerification>check-command</RequiresVerification>
|
|
84
|
+
</Rule>
|
|
85
|
+
<Rule id="python-rules">
|
|
86
|
+
<File>ai/directives/coding/python-rules.xml</File>
|
|
87
|
+
<Purpose>Writing Python: type hints + checker, narrow exceptions with cause, no mutable defaults, ruff format/lint. Inherits baseline-rules.</Purpose>
|
|
88
|
+
<Triggers>Target Files include .py source files</Triggers>
|
|
89
|
+
<SkipWhen>Config-only task; non-Python language</SkipWhen>
|
|
90
|
+
<ActivationHint>Before editing or creating any .py file. Inherits baseline-rules — read both.</ActivationHint>
|
|
73
91
|
<CheckPhase>typecheck</CheckPhase>
|
|
74
92
|
<RequiresVerification>check-command</RequiresVerification>
|
|
93
|
+
<CrossRef id="baseline-rules">Parent directive: inherits the language-agnostic coding baseline.</CrossRef>
|
|
94
|
+
</Rule>
|
|
95
|
+
<Rule id="go-rules">
|
|
96
|
+
<File>ai/directives/coding/go-rules.xml</File>
|
|
97
|
+
<Purpose>Writing Go: gofmt/vet, check-and-wrap errors with %w, no panic in libraries, no stutter naming. Inherits baseline-rules.</Purpose>
|
|
98
|
+
<Triggers>Target Files include .go source files</Triggers>
|
|
99
|
+
<SkipWhen>Config-only task; non-Go language</SkipWhen>
|
|
100
|
+
<ActivationHint>Before editing or creating any .go file. Inherits baseline-rules — read both.</ActivationHint>
|
|
101
|
+
<CheckPhase>typecheck</CheckPhase>
|
|
102
|
+
<RequiresVerification>check-command</RequiresVerification>
|
|
103
|
+
<CrossRef id="baseline-rules">Parent directive: inherits the language-agnostic coding baseline.</CrossRef>
|
|
75
104
|
</Rule>
|
|
76
105
|
<Rule id="svelte5-runes">
|
|
77
106
|
<File>ai/directives/coding/svelte5-runes.xml</File>
|
|
@@ -97,6 +126,15 @@
|
|
|
97
126
|
</Rule>
|
|
98
127
|
</Coding>
|
|
99
128
|
<Testing>
|
|
129
|
+
<Rule id="baseline-testing">
|
|
130
|
+
<File>ai/directives/testing/baseline-testing.xml</File>
|
|
131
|
+
<Purpose>Language-agnostic testing baseline: one behavior per named test, determinism, isolation, arrange-act-assert, failure paths covered. Parent of runner-specific testing rules.</Purpose>
|
|
132
|
+
<Triggers>Target Files include test files of any language</Triggers>
|
|
133
|
+
<SkipWhen>no test files in scope; a runner-specific rule already covers them and inherits this</SkipWhen>
|
|
134
|
+
<ActivationHint>Before writing tests in any language. Runner-specific rules inherit this — read the specific one too.</ActivationHint>
|
|
135
|
+
<CheckPhase>test</CheckPhase>
|
|
136
|
+
<RequiresVerification>check-command</RequiresVerification>
|
|
137
|
+
</Rule>
|
|
100
138
|
<Rule id="testing-common">
|
|
101
139
|
<File>ai/directives/testing/common.xml</File>
|
|
102
140
|
<Purpose>Shared testing core inherited by every runner-specific directive: contract boundary, case flow, phase anchors, unified context + factory, BDD mapping, snapshot operator-confirm, file budget.</Purpose>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<BaselineTestingRules keywords="baseline, language_agnostic, testing, determinism, isolation, behavior_named, ai_to_ai" type="testing-rules" ver="1.0">
|
|
2
|
+
<!--
|
|
3
|
+
Language-agnostic testing baseline. What a good unit test is in ANY language. Language testing
|
|
4
|
+
rule files (pytest-rules, gotest-rules, vitest-rules, …) inherit this and add framework idioms;
|
|
5
|
+
do not restate these axioms there.
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
<BeliefState>
|
|
9
|
+
<Axiom id="AX_TEST_ONE_BEHAVIOR">
|
|
10
|
+
One test proves one behavior, named after that behavior — not after the function. A test named
|
|
11
|
+
`retries_until_success_then_stops` documents intent; `test_retry_2` documents nothing. The name
|
|
12
|
+
is the specification a reader trusts without opening the body.
|
|
13
|
+
</Axiom>
|
|
14
|
+
|
|
15
|
+
<Axiom id="AX_TEST_DETERMINISM">
|
|
16
|
+
A test gives the same verdict on every run. No dependence on wall-clock time, random seeds,
|
|
17
|
+
ordering between tests, or ambient environment. Inject the clock, fix the seed, control the
|
|
18
|
+
input. A flaky test is a failing test.
|
|
19
|
+
</Axiom>
|
|
20
|
+
|
|
21
|
+
<Axiom id="AX_TEST_ISOLATION">
|
|
22
|
+
A unit test touches no network, no real filesystem outside a temp dir, no shared mutable global,
|
|
23
|
+
and no other test's state. External collaborators are faked at the boundary. What needs the real
|
|
24
|
+
world is an integration test, named and gated as one.
|
|
25
|
+
</Axiom>
|
|
26
|
+
|
|
27
|
+
<Axiom id="AX_TEST_ARRANGE_ACT_ASSERT">
|
|
28
|
+
A test reads as arrange → act → assert, with the assertion on the behavior's observable outcome,
|
|
29
|
+
not on internal calls. Assert the returned value / raised error / recorded effect — not "method X
|
|
30
|
+
was invoked", unless the interaction itself is the contract.
|
|
31
|
+
</Axiom>
|
|
32
|
+
|
|
33
|
+
<Axiom id="AX_TEST_COVERS_FAILURE">
|
|
34
|
+
The failure paths are tested, not only the happy path. For each documented error mode there is a
|
|
35
|
+
test that provokes it and asserts the specific error. Untested error handling is unverified
|
|
36
|
+
error handling.
|
|
37
|
+
</Axiom>
|
|
38
|
+
</BeliefState>
|
|
39
|
+
|
|
40
|
+
<AntiPatterns>
|
|
41
|
+
<AntiPattern id="AP_TEST_NAMED_AFTER_FUNCTION">
|
|
42
|
+
<Bad>`test_parse`, `test_parse_2`, `test_parse_edge` — names that index the function, not the
|
|
43
|
+
behavior under test.</Bad>
|
|
44
|
+
<Instead>`rejects_empty_input`, `parses_nested_path`, `keeps_last_value_on_repeat`.</Instead>
|
|
45
|
+
</AntiPattern>
|
|
46
|
+
|
|
47
|
+
<AntiPattern id="AP_TEST_NONDETERMINISM">
|
|
48
|
+
<Bad>Asserting on `now()`, real `sleep`, unseeded randomness, or the order tests happen to run in.</Bad>
|
|
49
|
+
<Instead>Inject the clock/seed; assert bounds or exact controlled values.</Instead>
|
|
50
|
+
</AntiPattern>
|
|
51
|
+
|
|
52
|
+
<AntiPattern id="AP_TEST_HITS_WORLD">
|
|
53
|
+
<Bad>A unit test that opens a socket, calls a live API, or writes outside a temp directory.</Bad>
|
|
54
|
+
<Instead>Fake the boundary; move a genuine end-to-end check into a separate, gated integration test.</Instead>
|
|
55
|
+
</AntiPattern>
|
|
56
|
+
|
|
57
|
+
<AntiPattern id="AP_TEST_ASSERTS_MECHANICS">
|
|
58
|
+
<Bad>Asserting a private helper was called, or snapshotting an internal structure, so a valid
|
|
59
|
+
refactor breaks the test.</Bad>
|
|
60
|
+
<Instead>Assert the observable outcome the caller depends on.</Instead>
|
|
61
|
+
</AntiPattern>
|
|
62
|
+
</AntiPatterns>
|
|
63
|
+
|
|
64
|
+
<VerificationHooks>
|
|
65
|
+
<Hook id="HOOK_TESTS_PASS">
|
|
66
|
+
<Purpose>The scope's test suite passes via the project's declared test runner.</Purpose>
|
|
67
|
+
<Command><sdd-path> verify --wip <target-files></Command>
|
|
68
|
+
<Expected>Exit 0; the changed behavior's tests present and green.</Expected>
|
|
69
|
+
</Hook>
|
|
70
|
+
<Hook id="HOOK_NO_SKIPS">
|
|
71
|
+
<Purpose>No silently disabled tests shipped as coverage.</Purpose>
|
|
72
|
+
<Command>rg --no-heading -n "skip|xfail|t\.Skip|it\.only|fdescribe|\.only\(" <target-files></Command>
|
|
73
|
+
<Expected>Empty on changed lines, or each skip carries a comment with a ticket reference.</Expected>
|
|
74
|
+
</Hook>
|
|
75
|
+
</VerificationHooks>
|
|
76
|
+
|
|
77
|
+
<RewardCriteria>
|
|
78
|
+
✅ Each test proves one behavior and is named after that behavior.
|
|
79
|
+
✅ Deterministic: clock/seed injected, no cross-test ordering dependence.
|
|
80
|
+
✅ Isolated: no network, no real FS outside a temp dir, no shared global; boundaries faked.
|
|
81
|
+
✅ Arrange → act → assert; assertion on the observable outcome.
|
|
82
|
+
✅ Every documented failure mode has a test that provokes it and asserts the specific error.
|
|
83
|
+
❌ Tests named after the function (`test_parse_2`) rather than the behavior.
|
|
84
|
+
❌ Dependence on real time, unseeded randomness, or run order.
|
|
85
|
+
❌ A unit test that touches the network, live services, or the real filesystem.
|
|
86
|
+
❌ Assertions on internal mechanics that a valid refactor would break.
|
|
87
|
+
❌ Disabled/skipped tests shipped without a referenced reason.
|
|
88
|
+
</RewardCriteria>
|
|
89
|
+
</BaselineTestingRules>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<BaselineCodingRules keywords="baseline, language_agnostic, contracts, error_handling, naming, yagni, ai_to_ai" type="coding-rules" ver="1.0">
|
|
2
|
+
<!--
|
|
3
|
+
Language-agnostic coding baseline. The few things that hold in ANY language. Language rule files
|
|
4
|
+
(python-rules, go-rules, typescript-rules, …) inherit this and add their own idioms; do not
|
|
5
|
+
restate these axioms there. Activated for source code files of a scope that declares no more
|
|
6
|
+
specific language rule, and as the transitive parent of the language rules that reference it.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
<BeliefState>
|
|
10
|
+
<Axiom id="AX_TELEOLOGICAL_NAMING">
|
|
11
|
+
A name states a goal, not a pattern. Forbidden semantic-noise names: `Manager`, `Handler`,
|
|
12
|
+
`Data`, `Info`, `Wrapper`, `Util`, `do…`, `process…`. `OrderFulfillmentPipeline` carries intent;
|
|
13
|
+
`OrderManager` tells the next reader nothing. Use precise verbs (`retrieve`, `verify`, `compose`)
|
|
14
|
+
over vague ones (`get`, `check`, `make`).
|
|
15
|
+
</Axiom>
|
|
16
|
+
|
|
17
|
+
<Axiom id="AX_EXPLICIT_FAILURE">
|
|
18
|
+
Failures are explicit and carry cause. Raise/return a specific, typed error with a message that
|
|
19
|
+
says what failed and with what input; when re-raising, preserve the original cause (do not
|
|
20
|
+
flatten a stack into a string). Never silently swallow an error or return a zero value that hides
|
|
21
|
+
one.
|
|
22
|
+
</Axiom>
|
|
23
|
+
|
|
24
|
+
<Axiom id="AX_CONTRACT_AT_SURFACE">
|
|
25
|
+
The public surface documents its contract: inputs, output, and failure modes, at the declaration
|
|
26
|
+
— not buried in the body. A caller must be able to use the function from its signature plus one
|
|
27
|
+
doc block, without reading the implementation.
|
|
28
|
+
</Axiom>
|
|
29
|
+
|
|
30
|
+
<Axiom id="AX_INTENT_COMMENTS">
|
|
31
|
+
Comments carry intent the code cannot: purpose, invariant, side effect, failure mode, non-goal,
|
|
32
|
+
or why-not. A comment that restates the syntax on the next line is noise and must be removed.
|
|
33
|
+
</Axiom>
|
|
34
|
+
|
|
35
|
+
<Axiom id="AX_YAGNI_NO_DEFENSE">
|
|
36
|
+
Build only what the task needs. No speculative abstraction (an interface/base class for a single
|
|
37
|
+
caller with no confirmed second one), no dead code, and no defensive branches against states the
|
|
38
|
+
types or invariants already exclude. Prefer the simplest shape that satisfies the contract.
|
|
39
|
+
</Axiom>
|
|
40
|
+
|
|
41
|
+
<Axiom id="AX_SINGLE_RESPONSIBILITY">
|
|
42
|
+
A unit does one thing at one level of abstraction. A function that mixes I/O, policy, and
|
|
43
|
+
formatting is split. Depth of nesting and length are symptoms; name the extracted piece by its
|
|
44
|
+
goal.
|
|
45
|
+
</Axiom>
|
|
46
|
+
</BeliefState>
|
|
47
|
+
|
|
48
|
+
<AntiPatterns>
|
|
49
|
+
<AntiPattern id="AP_SWALLOWED_ERROR">
|
|
50
|
+
<Bad>A caught error that is logged-and-ignored, discarded, or replaced by a generic message that
|
|
51
|
+
drops the original cause — so the failure surfaces later with no trace of its origin.</Bad>
|
|
52
|
+
<Instead>Handle it (recover with a documented fallback) or propagate it with the cause attached.
|
|
53
|
+
One or the other, never neither.</Instead>
|
|
54
|
+
</AntiPattern>
|
|
55
|
+
|
|
56
|
+
<AntiPattern id="AP_PATTERN_NAMING">
|
|
57
|
+
<Bad>`FooManager`, `DataHandler`, `RequestWrapper`, `doWork`, `processItem` — names that describe
|
|
58
|
+
a code pattern rather than a business goal.</Bad>
|
|
59
|
+
<Instead>Name the responsibility: `SubscriptionRenewal`, `parseVcsUrl`, `retryWithBackoff`.</Instead>
|
|
60
|
+
</AntiPattern>
|
|
61
|
+
|
|
62
|
+
<AntiPattern id="AP_SYNTAX_COMMENT">
|
|
63
|
+
<Bad>`// increment i` above `i++`; a doc block that repeats parameter names and types already in
|
|
64
|
+
the signature.</Bad>
|
|
65
|
+
<Instead>Comment the why, the invariant, or the non-obvious consequence — or nothing.</Instead>
|
|
66
|
+
</AntiPattern>
|
|
67
|
+
|
|
68
|
+
<AntiPattern id="AP_SPECULATIVE_ABSTRACTION">
|
|
69
|
+
<Bad>Extracting an interface, factory, or config option for one caller "in case we need it".</Bad>
|
|
70
|
+
<Instead>Inline it; abstract on the second real consumer, when the variance is known.</Instead>
|
|
71
|
+
</AntiPattern>
|
|
72
|
+
</AntiPatterns>
|
|
73
|
+
|
|
74
|
+
<VerificationHooks>
|
|
75
|
+
<Hook id="HOOK_PROJECT_GATE">
|
|
76
|
+
<Purpose>The scope's own quality gate (formatter + linter + type/compile check + tests, as the
|
|
77
|
+
project declares them) passes on the changed files.</Purpose>
|
|
78
|
+
<Command><sdd-path> verify --wip <target-files></Command>
|
|
79
|
+
<Expected>Exit 0. If the scope has not declared its gate yet, that is the finding to fix first.</Expected>
|
|
80
|
+
</Hook>
|
|
81
|
+
<Hook id="HOOK_NO_SCAFFOLD_LEFTOVER">
|
|
82
|
+
<Purpose>No placeholder/scaffold markers survive in shipped code.</Purpose>
|
|
83
|
+
<Command>rg --no-heading -n "TODO|FIXME|XXX|<[A-Z_]+>|placeholder" <target-files></Command>
|
|
84
|
+
<Expected>Empty on changed lines; a real deferred item belongs in a ticket, not a bare marker.</Expected>
|
|
85
|
+
</Hook>
|
|
86
|
+
</VerificationHooks>
|
|
87
|
+
|
|
88
|
+
<RewardCriteria>
|
|
89
|
+
✅ Names state a goal; precise verbs; no `Manager`/`Handler`/`Data`/`Util`/`do…`/`process…`.
|
|
90
|
+
✅ Every failure path raises/returns a specific error with context and preserved cause.
|
|
91
|
+
✅ Public entities document inputs, output, and failure modes at the declaration.
|
|
92
|
+
✅ Comments carry intent (purpose / invariant / side effect / failure / non-goal), never restate syntax.
|
|
93
|
+
✅ Only what the task needs: no speculative abstraction, no dead code, no defense against impossible states.
|
|
94
|
+
✅ Each unit has one responsibility at one abstraction level.
|
|
95
|
+
❌ An error caught and dropped, or re-raised with the cause lost.
|
|
96
|
+
❌ Pattern-shaped names that hide the business goal.
|
|
97
|
+
❌ Comments that restate the code, or doc blocks duplicating the signature.
|
|
98
|
+
❌ An abstraction introduced for a single caller with no confirmed second consumer.
|
|
99
|
+
❌ Placeholder/scaffold markers (`TODO`, `<NAME>`) left in shipped code.
|
|
100
|
+
</RewardCriteria>
|
|
101
|
+
</BaselineCodingRules>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<GoCodingRules keywords="go, golang, gofmt, govet, errors, wrapping, naming, testing, ai_to_ai" type="coding-rules" ver="1.0">
|
|
2
|
+
<!--
|
|
3
|
+
Thin Go baseline. Inherits the language-agnostic coding + testing baselines; adds only the Go
|
|
4
|
+
idioms the community broadly agrees on. Not an exhaustive style guide — deliberately small.
|
|
5
|
+
Grounded in: Effective Go, Go Code Review Comments, go.dev/blog/go1.13-errors, official tooling
|
|
6
|
+
(gofmt, go vet, golangci-lint) — the intersection Uber/Google style guides also share.
|
|
7
|
+
-->
|
|
8
|
+
<DependsOn>
|
|
9
|
+
- ai/directives/coding/baseline-rules.xml
|
|
10
|
+
- ai/directives/testing/baseline-testing.xml
|
|
11
|
+
</DependsOn>
|
|
12
|
+
|
|
13
|
+
<BeliefState>
|
|
14
|
+
<Axiom id="AX_GO_GOFMT">
|
|
15
|
+
Code is `gofmt`/`goimports`-clean — non-negotiable, zero config. `goimports` is preferred (it also
|
|
16
|
+
groups and prunes imports). Tabs, tool-decided layout; there are no formatting debates in Go.
|
|
17
|
+
</Axiom>
|
|
18
|
+
|
|
19
|
+
<Axiom id="AX_GO_CHECK_AND_WRAP_ERRORS">
|
|
20
|
+
`error` is the last return value and every error is checked — never discarded with `_` unless the
|
|
21
|
+
discard is deliberate and justified. Add context by wrapping with `fmt.Errorf("doing X: %w", err)`
|
|
22
|
+
(`%w`, not `%v`, so callers can unwrap). Inspect with `errors.Is` / `errors.As`, not `==` or type
|
|
23
|
+
assertions. Define a sentinel (`var ErrNotFound = errors.New(…)`) only for a condition callers
|
|
24
|
+
actually branch on. Error strings are lowercase, no trailing punctuation.
|
|
25
|
+
</Axiom>
|
|
26
|
+
|
|
27
|
+
<Axiom id="AX_GO_NO_PANIC_IN_LIB">
|
|
28
|
+
Library code returns errors; it does not `panic` on ordinary or expected failures (bad input,
|
|
29
|
+
missing file). `panic` is for programmer bugs and truly unrecoverable state only.
|
|
30
|
+
</Axiom>
|
|
31
|
+
|
|
32
|
+
<Axiom id="AX_GO_NAMING">
|
|
33
|
+
Capitalization is visibility — export only what callers need. No stutter: in package `env`, name it
|
|
34
|
+
`env.Var`, not `env.EnvVar`; `New`/`Open`, not `NewEnv` inside `env`. Receiver names are short (1–2
|
|
35
|
+
letters) and consistent across a type's methods. Interfaces are small and `-er`-named; accept
|
|
36
|
+
interfaces, return concrete structs.
|
|
37
|
+
</Axiom>
|
|
38
|
+
</BeliefState>
|
|
39
|
+
|
|
40
|
+
<AntiPatterns>
|
|
41
|
+
<AntiPattern id="AP_GO_IGNORED_ERROR">
|
|
42
|
+
<Bad>`_ = doThing()` or calling a function that returns `error` and not checking it.</Bad>
|
|
43
|
+
<Instead>Check it: handle, or `return fmt.Errorf("…: %w", err)`. Discard only with a justifying comment.</Instead>
|
|
44
|
+
</AntiPattern>
|
|
45
|
+
<AntiPattern id="AP_GO_PANIC_IN_LIB">
|
|
46
|
+
<Bad>`panic("not found")` inside a reusable package for an expected condition.</Bad>
|
|
47
|
+
<Instead>Return an `error` the caller can handle.</Instead>
|
|
48
|
+
</AntiPattern>
|
|
49
|
+
<AntiPattern id="AP_GO_PERCENT_V_WRAP">
|
|
50
|
+
<Bad>`fmt.Errorf("read config: %v", err)` — `%v` flattens the error; `errors.Is/As` can no longer unwrap it.</Bad>
|
|
51
|
+
<Instead>`fmt.Errorf("read config: %w", err)`.</Instead>
|
|
52
|
+
</AntiPattern>
|
|
53
|
+
<AntiPattern id="AP_GO_STUTTER">
|
|
54
|
+
<Bad>`env.EnvVar`, `client.ClientConfig` — the package qualifier already carries the prefix.</Bad>
|
|
55
|
+
<Instead>`env.Var`, `client.Config`.</Instead>
|
|
56
|
+
</AntiPattern>
|
|
57
|
+
</AntiPatterns>
|
|
58
|
+
|
|
59
|
+
<VerificationHooks>
|
|
60
|
+
<Hook id="HOOK_GO_FMT">
|
|
61
|
+
<Purpose>Code is gofmt-clean.</Purpose>
|
|
62
|
+
<Command>gofmt -l <target-files></Command>
|
|
63
|
+
<Expected>Empty output (no unformatted files).</Expected>
|
|
64
|
+
</Hook>
|
|
65
|
+
<Hook id="HOOK_GO_VET">
|
|
66
|
+
<Purpose>Vet catches the baseline bug classes.</Purpose>
|
|
67
|
+
<Command>go vet ./...</Command>
|
|
68
|
+
<Expected>Exit 0.</Expected>
|
|
69
|
+
</Hook>
|
|
70
|
+
<Hook id="HOOK_GO_LINT">
|
|
71
|
+
<Purpose>golangci-lint default set (govet, errcheck, staticcheck, ineffassign, unused).</Purpose>
|
|
72
|
+
<Command>golangci-lint run</Command>
|
|
73
|
+
<Expected>Exit 0 (skip if the project does not install it; go vet is the floor).</Expected>
|
|
74
|
+
</Hook>
|
|
75
|
+
<Hook id="HOOK_GO_TESTS">
|
|
76
|
+
<Purpose>The behavior's tests pass.</Purpose>
|
|
77
|
+
<Command>go test ./...</Command>
|
|
78
|
+
<Expected>Exit 0.</Expected>
|
|
79
|
+
</Hook>
|
|
80
|
+
</VerificationHooks>
|
|
81
|
+
|
|
82
|
+
<RewardCriteria>
|
|
83
|
+
✅ `gofmt`/`goimports`-clean; `go vet ./...` passes.
|
|
84
|
+
✅ Every error checked; context added with `%w`; inspected via `errors.Is`/`As`.
|
|
85
|
+
✅ Library code returns errors, never panics on expected failures.
|
|
86
|
+
✅ No stutter; minimal exported surface; short consistent receiver names; small `-er` interfaces.
|
|
87
|
+
✅ Table-driven tests with `t.Run` subtests and `t.Helper()` in helpers; stdlib `testing` only at baseline.
|
|
88
|
+
❌ An ignored/`_`-discarded error without justification.
|
|
89
|
+
❌ `panic` in library code for an expected failure.
|
|
90
|
+
❌ `%v` wrapping where `%w` is meant.
|
|
91
|
+
❌ Stutter naming (`env.EnvVar`).
|
|
92
|
+
❌ Speculatively-exported types/functions with no caller.
|
|
93
|
+
</RewardCriteria>
|
|
94
|
+
</GoCodingRules>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<PythonCodingRules keywords="python, type_hints, ruff, mypy, pytest, exceptions, pep8, ai_to_ai" type="coding-rules" ver="1.0">
|
|
2
|
+
<!--
|
|
3
|
+
Thin Python baseline. Inherits the language-agnostic coding + testing baselines; adds only the
|
|
4
|
+
Python idioms the community broadly agrees on. Not an exhaustive style guide — deliberately small.
|
|
5
|
+
Grounded in: PEP 8, Ruff/mypy docs, PyPA packaging guides (2025-2026 mainstream defaults).
|
|
6
|
+
-->
|
|
7
|
+
<DependsOn>
|
|
8
|
+
- ai/directives/coding/baseline-rules.xml
|
|
9
|
+
- ai/directives/testing/baseline-testing.xml
|
|
10
|
+
</DependsOn>
|
|
11
|
+
|
|
12
|
+
<BeliefState>
|
|
13
|
+
<Axiom id="AX_PY_TYPE_HINTS">
|
|
14
|
+
Public function and method signatures are type-annotated, and a checker (mypy or pyright) runs in
|
|
15
|
+
the project's gate. Typing is gradual, not strict, at baseline: annotate the surface, tighten over
|
|
16
|
+
time. Pick one checker per project; do not mix.
|
|
17
|
+
</Axiom>
|
|
18
|
+
|
|
19
|
+
<Axiom id="AX_PY_NARROW_EXCEPT">
|
|
20
|
+
Catch the narrowest exception you can actually handle (`FileNotFoundError`, `ValueError`), never a
|
|
21
|
+
bare `except:` or blanket `except Exception:` — those swallow `KeyboardInterrupt`/`SystemExit` and
|
|
22
|
+
hide bugs. Chain with `raise NewError(...) from err` to keep the cause; re-raise with a bare
|
|
23
|
+
`raise` after logging to keep the original traceback.
|
|
24
|
+
</Axiom>
|
|
25
|
+
|
|
26
|
+
<Axiom id="AX_PY_NO_MUTABLE_DEFAULT">
|
|
27
|
+
No mutable default arguments (`def f(x=[])` / `{}`): the default is shared across calls. Default to
|
|
28
|
+
`None` and construct inside the body.
|
|
29
|
+
</Axiom>
|
|
30
|
+
|
|
31
|
+
<Axiom id="AX_PY_FORMAT_AND_LINT">
|
|
32
|
+
Formatting and lint are tool-owned, not hand-argued: `ruff format` (Black-compatible, 88 columns)
|
|
33
|
+
plus `ruff check` with at least `F` (pyflakes), `E` (pycodestyle errors), `I` (import order), and
|
|
34
|
+
`B` (bugbear). All tool + packaging config lives in a single `pyproject.toml`.
|
|
35
|
+
</Axiom>
|
|
36
|
+
</BeliefState>
|
|
37
|
+
|
|
38
|
+
<AntiPatterns>
|
|
39
|
+
<AntiPattern id="AP_PY_MUTABLE_DEFAULT">
|
|
40
|
+
<Bad>`def append_to(item, target=[]):` — `target` persists and accumulates across calls.</Bad>
|
|
41
|
+
<Instead>`def append_to(item, target=None): target = [] if target is None else target`.</Instead>
|
|
42
|
+
</AntiPattern>
|
|
43
|
+
<AntiPattern id="AP_PY_BARE_EXCEPT">
|
|
44
|
+
<Bad>`try: … except: pass` or `except Exception:` as a catch-all that hides the failure.</Bad>
|
|
45
|
+
<Instead>Catch the specific type you handle; log-and-`raise` or `raise … from err` otherwise.</Instead>
|
|
46
|
+
</AntiPattern>
|
|
47
|
+
<AntiPattern id="AP_PY_WILDCARD_IMPORT">
|
|
48
|
+
<Bad>`from module import *` — pollutes the namespace and defeats static analysis (Ruff F403/F405).</Bad>
|
|
49
|
+
<Instead>Import the names you use explicitly.</Instead>
|
|
50
|
+
</AntiPattern>
|
|
51
|
+
<AntiPattern id="AP_PY_IDENTITY_COMPARE">
|
|
52
|
+
<Bad>`if x == None:` / `== True` — value comparison to a singleton (Ruff E711/E712).</Bad>
|
|
53
|
+
<Instead>`if x is None:` / `if flag:`.</Instead>
|
|
54
|
+
</AntiPattern>
|
|
55
|
+
</AntiPatterns>
|
|
56
|
+
|
|
57
|
+
<VerificationHooks>
|
|
58
|
+
<Hook id="HOOK_PY_FORMAT">
|
|
59
|
+
<Purpose>Code matches the canonical format.</Purpose>
|
|
60
|
+
<Command>ruff format --check <target-files></Command>
|
|
61
|
+
<Expected>Exit 0; no files would be reformatted.</Expected>
|
|
62
|
+
</Hook>
|
|
63
|
+
<Hook id="HOOK_PY_LINT">
|
|
64
|
+
<Purpose>No lint findings from the baseline rule sets.</Purpose>
|
|
65
|
+
<Command>ruff check <target-files></Command>
|
|
66
|
+
<Expected>Exit 0 (F/E/I/B clean).</Expected>
|
|
67
|
+
</Hook>
|
|
68
|
+
<Hook id="HOOK_PY_TYPES">
|
|
69
|
+
<Purpose>Annotations type-check.</Purpose>
|
|
70
|
+
<Command>mypy <target-files></Command>
|
|
71
|
+
<Expected>Exit 0 (or the project's configured pyright equivalent).</Expected>
|
|
72
|
+
</Hook>
|
|
73
|
+
<Hook id="HOOK_PY_TESTS">
|
|
74
|
+
<Purpose>The behavior's tests pass under pytest.</Purpose>
|
|
75
|
+
<Command>pytest -q</Command>
|
|
76
|
+
<Expected>Exit 0.</Expected>
|
|
77
|
+
</Hook>
|
|
78
|
+
</VerificationHooks>
|
|
79
|
+
|
|
80
|
+
<RewardCriteria>
|
|
81
|
+
✅ Public signatures are type-annotated; a checker runs in the gate.
|
|
82
|
+
✅ Exceptions caught are the narrowest handled; re-raise preserves the cause (`raise … from`) or the traceback (bare `raise`).
|
|
83
|
+
✅ No mutable default arguments; `None` + in-body construction.
|
|
84
|
+
✅ `ruff format` clean at 88 cols; `ruff check` clean on F/E/I/B; single `pyproject.toml`.
|
|
85
|
+
✅ Tests use pytest with `@pytest.mark.parametrize` for input variants (one behavior per test).
|
|
86
|
+
❌ Bare `except:` / blanket `except Exception:` that swallows the error.
|
|
87
|
+
❌ Mutable default argument.
|
|
88
|
+
❌ Wildcard import (`from x import *`).
|
|
89
|
+
❌ `== None` / `== True` singleton comparison.
|
|
90
|
+
❌ Re-raising a new exception without `from`, losing the original cause.
|
|
91
|
+
</RewardCriteria>
|
|
92
|
+
</PythonCodingRules>
|
|
@@ -64,14 +64,43 @@
|
|
|
64
64
|
<CheckPhase>lint</CheckPhase>
|
|
65
65
|
<RequiresVerification>check-command</RequiresVerification>
|
|
66
66
|
</Rule>
|
|
67
|
+
<Rule id="baseline-rules">
|
|
68
|
+
<File>ai/directives/coding/baseline-rules.xml</File>
|
|
69
|
+
<Purpose>Language-agnostic coding baseline: teleological naming, explicit failures with cause, contract at the surface, intent comments, YAGNI, single responsibility. Parent of the language rule files.</Purpose>
|
|
70
|
+
<Triggers>Target Files include source code files (not config) of any language</Triggers>
|
|
71
|
+
<SkipWhen>Config-only task; a more specific language rule already covers the file and inherits this</SkipWhen>
|
|
72
|
+
<ActivationHint>Before editing any source file. Language rules (python-rules, go-rules, typescript-rules) inherit this — read the specific one too.</ActivationHint>
|
|
73
|
+
<CheckPhase>lint</CheckPhase>
|
|
74
|
+
<RequiresVerification>check-command</RequiresVerification>
|
|
75
|
+
</Rule>
|
|
67
76
|
<Rule id="typescript-rules">
|
|
68
77
|
<File>ai/directives/coding/typescript-rules.xml</File>
|
|
69
|
-
<Purpose>Writing
|
|
70
|
-
<Triggers>Target Files include
|
|
71
|
-
<SkipWhen>Config-only task; infra-setup
|
|
72
|
-
<ActivationHint>Before editing or creating any source
|
|
78
|
+
<Purpose>Writing TypeScript: typing, DbC, patterns, anti-patterns. The TS language baseline.</Purpose>
|
|
79
|
+
<Triggers>Target Files include .ts / .tsx source files</Triggers>
|
|
80
|
+
<SkipWhen>Config-only task; non-TypeScript language (see python-rules / go-rules); infra-setup without code files</SkipWhen>
|
|
81
|
+
<ActivationHint>Before editing or creating any .ts / .tsx source file</ActivationHint>
|
|
82
|
+
<CheckPhase>typecheck</CheckPhase>
|
|
83
|
+
<RequiresVerification>check-command</RequiresVerification>
|
|
84
|
+
</Rule>
|
|
85
|
+
<Rule id="python-rules">
|
|
86
|
+
<File>ai/directives/coding/python-rules.xml</File>
|
|
87
|
+
<Purpose>Writing Python: type hints + checker, narrow exceptions with cause, no mutable defaults, ruff format/lint. Inherits baseline-rules.</Purpose>
|
|
88
|
+
<Triggers>Target Files include .py source files</Triggers>
|
|
89
|
+
<SkipWhen>Config-only task; non-Python language</SkipWhen>
|
|
90
|
+
<ActivationHint>Before editing or creating any .py file. Inherits baseline-rules — read both.</ActivationHint>
|
|
73
91
|
<CheckPhase>typecheck</CheckPhase>
|
|
74
92
|
<RequiresVerification>check-command</RequiresVerification>
|
|
93
|
+
<CrossRef id="baseline-rules">Parent directive: inherits the language-agnostic coding baseline.</CrossRef>
|
|
94
|
+
</Rule>
|
|
95
|
+
<Rule id="go-rules">
|
|
96
|
+
<File>ai/directives/coding/go-rules.xml</File>
|
|
97
|
+
<Purpose>Writing Go: gofmt/vet, check-and-wrap errors with %w, no panic in libraries, no stutter naming. Inherits baseline-rules.</Purpose>
|
|
98
|
+
<Triggers>Target Files include .go source files</Triggers>
|
|
99
|
+
<SkipWhen>Config-only task; non-Go language</SkipWhen>
|
|
100
|
+
<ActivationHint>Before editing or creating any .go file. Inherits baseline-rules — read both.</ActivationHint>
|
|
101
|
+
<CheckPhase>typecheck</CheckPhase>
|
|
102
|
+
<RequiresVerification>check-command</RequiresVerification>
|
|
103
|
+
<CrossRef id="baseline-rules">Parent directive: inherits the language-agnostic coding baseline.</CrossRef>
|
|
75
104
|
</Rule>
|
|
76
105
|
<Rule id="svelte5-runes">
|
|
77
106
|
<File>ai/directives/coding/svelte5-runes.xml</File>
|
|
@@ -97,6 +126,15 @@
|
|
|
97
126
|
</Rule>
|
|
98
127
|
</Coding>
|
|
99
128
|
<Testing>
|
|
129
|
+
<Rule id="baseline-testing">
|
|
130
|
+
<File>ai/directives/testing/baseline-testing.xml</File>
|
|
131
|
+
<Purpose>Language-agnostic testing baseline: one behavior per named test, determinism, isolation, arrange-act-assert, failure paths covered. Parent of runner-specific testing rules.</Purpose>
|
|
132
|
+
<Triggers>Target Files include test files of any language</Triggers>
|
|
133
|
+
<SkipWhen>no test files in scope; a runner-specific rule already covers them and inherits this</SkipWhen>
|
|
134
|
+
<ActivationHint>Before writing tests in any language. Runner-specific rules inherit this — read the specific one too.</ActivationHint>
|
|
135
|
+
<CheckPhase>test</CheckPhase>
|
|
136
|
+
<RequiresVerification>check-command</RequiresVerification>
|
|
137
|
+
</Rule>
|
|
100
138
|
<Rule id="testing-common">
|
|
101
139
|
<File>ai/directives/testing/common.xml</File>
|
|
102
140
|
<Purpose>Shared testing core inherited by every runner-specific directive: contract boundary, case flow, phase anchors, unified context + factory, BDD mapping, snapshot operator-confirm, file budget.</Purpose>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<BaselineTestingRules keywords="baseline, language_agnostic, testing, determinism, isolation, behavior_named, ai_to_ai" type="testing-rules" ver="1.0">
|
|
2
|
+
<!--
|
|
3
|
+
Language-agnostic testing baseline. What a good unit test is in ANY language. Language testing
|
|
4
|
+
rule files (pytest-rules, gotest-rules, vitest-rules, …) inherit this and add framework idioms;
|
|
5
|
+
do not restate these axioms there.
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
<BeliefState>
|
|
9
|
+
<Axiom id="AX_TEST_ONE_BEHAVIOR">
|
|
10
|
+
One test proves one behavior, named after that behavior — not after the function. A test named
|
|
11
|
+
`retries_until_success_then_stops` documents intent; `test_retry_2` documents nothing. The name
|
|
12
|
+
is the specification a reader trusts without opening the body.
|
|
13
|
+
</Axiom>
|
|
14
|
+
|
|
15
|
+
<Axiom id="AX_TEST_DETERMINISM">
|
|
16
|
+
A test gives the same verdict on every run. No dependence on wall-clock time, random seeds,
|
|
17
|
+
ordering between tests, or ambient environment. Inject the clock, fix the seed, control the
|
|
18
|
+
input. A flaky test is a failing test.
|
|
19
|
+
</Axiom>
|
|
20
|
+
|
|
21
|
+
<Axiom id="AX_TEST_ISOLATION">
|
|
22
|
+
A unit test touches no network, no real filesystem outside a temp dir, no shared mutable global,
|
|
23
|
+
and no other test's state. External collaborators are faked at the boundary. What needs the real
|
|
24
|
+
world is an integration test, named and gated as one.
|
|
25
|
+
</Axiom>
|
|
26
|
+
|
|
27
|
+
<Axiom id="AX_TEST_ARRANGE_ACT_ASSERT">
|
|
28
|
+
A test reads as arrange → act → assert, with the assertion on the behavior's observable outcome,
|
|
29
|
+
not on internal calls. Assert the returned value / raised error / recorded effect — not "method X
|
|
30
|
+
was invoked", unless the interaction itself is the contract.
|
|
31
|
+
</Axiom>
|
|
32
|
+
|
|
33
|
+
<Axiom id="AX_TEST_COVERS_FAILURE">
|
|
34
|
+
The failure paths are tested, not only the happy path. For each documented error mode there is a
|
|
35
|
+
test that provokes it and asserts the specific error. Untested error handling is unverified
|
|
36
|
+
error handling.
|
|
37
|
+
</Axiom>
|
|
38
|
+
</BeliefState>
|
|
39
|
+
|
|
40
|
+
<AntiPatterns>
|
|
41
|
+
<AntiPattern id="AP_TEST_NAMED_AFTER_FUNCTION">
|
|
42
|
+
<Bad>`test_parse`, `test_parse_2`, `test_parse_edge` — names that index the function, not the
|
|
43
|
+
behavior under test.</Bad>
|
|
44
|
+
<Instead>`rejects_empty_input`, `parses_nested_path`, `keeps_last_value_on_repeat`.</Instead>
|
|
45
|
+
</AntiPattern>
|
|
46
|
+
|
|
47
|
+
<AntiPattern id="AP_TEST_NONDETERMINISM">
|
|
48
|
+
<Bad>Asserting on `now()`, real `sleep`, unseeded randomness, or the order tests happen to run in.</Bad>
|
|
49
|
+
<Instead>Inject the clock/seed; assert bounds or exact controlled values.</Instead>
|
|
50
|
+
</AntiPattern>
|
|
51
|
+
|
|
52
|
+
<AntiPattern id="AP_TEST_HITS_WORLD">
|
|
53
|
+
<Bad>A unit test that opens a socket, calls a live API, or writes outside a temp directory.</Bad>
|
|
54
|
+
<Instead>Fake the boundary; move a genuine end-to-end check into a separate, gated integration test.</Instead>
|
|
55
|
+
</AntiPattern>
|
|
56
|
+
|
|
57
|
+
<AntiPattern id="AP_TEST_ASSERTS_MECHANICS">
|
|
58
|
+
<Bad>Asserting a private helper was called, or snapshotting an internal structure, so a valid
|
|
59
|
+
refactor breaks the test.</Bad>
|
|
60
|
+
<Instead>Assert the observable outcome the caller depends on.</Instead>
|
|
61
|
+
</AntiPattern>
|
|
62
|
+
</AntiPatterns>
|
|
63
|
+
|
|
64
|
+
<VerificationHooks>
|
|
65
|
+
<Hook id="HOOK_TESTS_PASS">
|
|
66
|
+
<Purpose>The scope's test suite passes via the project's declared test runner.</Purpose>
|
|
67
|
+
<Command><sdd-path> verify --wip <target-files></Command>
|
|
68
|
+
<Expected>Exit 0; the changed behavior's tests present and green.</Expected>
|
|
69
|
+
</Hook>
|
|
70
|
+
<Hook id="HOOK_NO_SKIPS">
|
|
71
|
+
<Purpose>No silently disabled tests shipped as coverage.</Purpose>
|
|
72
|
+
<Command>rg --no-heading -n "skip|xfail|t\.Skip|it\.only|fdescribe|\.only\(" <target-files></Command>
|
|
73
|
+
<Expected>Empty on changed lines, or each skip carries a comment with a ticket reference.</Expected>
|
|
74
|
+
</Hook>
|
|
75
|
+
</VerificationHooks>
|
|
76
|
+
|
|
77
|
+
<RewardCriteria>
|
|
78
|
+
✅ Each test proves one behavior and is named after that behavior.
|
|
79
|
+
✅ Deterministic: clock/seed injected, no cross-test ordering dependence.
|
|
80
|
+
✅ Isolated: no network, no real FS outside a temp dir, no shared global; boundaries faked.
|
|
81
|
+
✅ Arrange → act → assert; assertion on the observable outcome.
|
|
82
|
+
✅ Every documented failure mode has a test that provokes it and asserts the specific error.
|
|
83
|
+
❌ Tests named after the function (`test_parse_2`) rather than the behavior.
|
|
84
|
+
❌ Dependence on real time, unseeded randomness, or run order.
|
|
85
|
+
❌ A unit test that touches the network, live services, or the real filesystem.
|
|
86
|
+
❌ Assertions on internal mechanics that a valid refactor would break.
|
|
87
|
+
❌ Disabled/skipped tests shipped without a referenced reason.
|
|
88
|
+
</RewardCriteria>
|
|
89
|
+
</BaselineTestingRules>
|
package/dist/gennady.js
CHANGED
|
@@ -3,7 +3,7 @@ import { c as s } from "./chunks/shared-CCgxBhk_.js";
|
|
|
3
3
|
import "node:fs";
|
|
4
4
|
import "node:path";
|
|
5
5
|
import "node:url";
|
|
6
|
-
const r = "0.9.0-next.
|
|
6
|
+
const r = "0.9.0-next.3", i = /* @__PURE__ */ new Set(["help", "--help", "-h"]), p = /* @__PURE__ */ new Set(["--version", "-v"]), t = process.argv[2];
|
|
7
7
|
p.has(t) && (console.log(r), process.exit(0));
|
|
8
8
|
(!t || i.has(t)) && (await import("./chunks/help.cmd-Bz1YqU2V.js"), process.exit(0));
|
|
9
9
|
s({ name: "gennady", version: r });
|
package/package.json
CHANGED