@aacombarro89/praxis 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -19
- package/dist/codex-marketplace.d.ts +19 -0
- package/dist/codex-marketplace.js +113 -0
- package/dist/codex-marketplace.js.map +1 -0
- package/dist/codex-security.d.ts +11 -0
- package/dist/codex-security.js +144 -0
- package/dist/codex-security.js.map +1 -0
- package/dist/emit.d.ts +24 -10
- package/dist/emit.js +149 -70
- package/dist/emit.js.map +1 -1
- package/dist/init.d.ts +16 -6
- package/dist/init.js +47 -10
- package/dist/init.js.map +1 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/manifest.js +1 -1
- package/dist/manifest.js.map +1 -1
- package/dist/packages.d.ts +20 -2
- package/dist/packages.js +54 -5
- package/dist/packages.js.map +1 -1
- package/dist/permissions.d.ts +17 -2
- package/dist/permissions.js +5 -14
- package/dist/permissions.js.map +1 -1
- package/dist/plugins.d.ts +16 -2
- package/dist/plugins.js +15 -15
- package/dist/plugins.js.map +1 -1
- package/dist/program.d.ts +3 -0
- package/dist/program.js +77 -31
- package/dist/program.js.map +1 -1
- package/dist/shared-instructions.d.ts +21 -0
- package/dist/shared-instructions.js +63 -0
- package/dist/shared-instructions.js.map +1 -0
- package/dist/sync.js +103 -7
- package/dist/sync.js.map +1 -1
- package/package.json +1 -1
- package/packages/external/ponytail/plugins.yaml +4 -1
- package/packages/layer1/capture-codebase-patterns/commands/capture-codebase-patterns.md +68 -0
- package/packages/layer1/capture-codebase-patterns/package.yaml +14 -0
- package/packages/layer1/instruction-upkeep/commands/instructions.md +14 -5
- package/packages/layer1/instruction-upkeep/rules.md +7 -3
- package/packages/layer1/onboarding/commands/onboard.md +42 -24
- package/packages/layer1/onboarding/rules.md +4 -4
- package/packages/layer1/safe-permissions/permissions.yaml +2 -2
- package/packages/layer1/session-handoff/commands/handoff.md +5 -5
- package/packages/layer1/session-handoff/rules.md +2 -2
- package/packages/layer1/upkeep/commands/upkeep.md +8 -5
- package/packages/layer1/upkeep/rules.md +8 -4
- package/packages/layer1/wiki-memory/commands/wiki.md +6 -6
- package/packages/layer1/wiki-memory/rules.md +4 -4
- package/packages/layer2/node-recipes/rules.md +18 -19
- package/packages/layer2/node-testing/rules.md +21 -26
- package/packages/layer2/python-backend-recipes/rules.md +18 -22
- package/packages/layer2/python-testing/rules.md +20 -26
- package/packages/layer2/react-components/rules.md +31 -26
- package/packages/layer2/react-testing/rules.md +19 -28
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Work outlives a single session: at a natural boundary in a verified state —
|
|
4
4
|
never mid-edit — capture context before it's lost. Write handoffs by running
|
|
5
|
-
|
|
5
|
+
`{{workflow:praxis-handoff}}`, not by hand; it's the next session's only briefing.
|
|
6
6
|
|
|
7
7
|
## Delegated execution
|
|
8
8
|
|
|
@@ -11,5 +11,5 @@ neither requiring parallelism: **independent workstreams** (specifiable up
|
|
|
11
11
|
front, no mid-flight coordination) and **context/budget isolation** (a
|
|
12
12
|
context-heavy or mechanical run worth its own context and a lighter tier —
|
|
13
13
|
holds even for one sequential stream). Name the split at plan time. Produce
|
|
14
|
-
each brief with
|
|
14
|
+
each brief with `{{workflow:praxis-handoff}}`, not by hand, and verify a returned
|
|
15
15
|
workstream against its success criterion before integrating it.
|
|
@@ -4,18 +4,21 @@ argument-hint: [optional-area-to-focus]
|
|
|
4
4
|
allowed-tools: Bash(npx @aacombarro89/praxis@latest check:*), Bash(npx @aacombarro89/praxis@latest sync:*), Bash(npx @aacombarro89/praxis check:*), Bash(npx @aacombarro89/praxis sync:*), Bash(praxis check:*), Bash(praxis sync:*)
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Focus:
|
|
7
|
+
Focus: {{arguments}} — if empty, cover all three passes at a high level.
|
|
8
8
|
|
|
9
9
|
This is the single entry point for "is my methodology layer still current
|
|
10
10
|
with reality?" It sequences three existing checks rather than re-implementing
|
|
11
11
|
them — restating their logic here would duplicate content that drifts
|
|
12
12
|
independently of its canonical home.
|
|
13
13
|
|
|
14
|
-
1. **Deterministic drift.** Run `praxis check` if the CLI is on PATH
|
|
14
|
+
1. **Deterministic drift.** Run `praxis check` if the CLI is on PATH. Otherwise
|
|
15
15
|
fall back to `npx @aacombarro89/praxis@latest check` (pin `@latest` — a bare
|
|
16
16
|
`npx @aacombarro89/praxis` can silently reuse a stale cached build). If it
|
|
17
17
|
reports file drift, run `praxis sync` (or `npx @aacombarro89/praxis@latest
|
|
18
|
-
sync`) to fix it. Surface managed-block conflicts
|
|
18
|
+
sync`) to fix it. Surface managed-block conflicts, shared-project parity
|
|
19
|
+
drift, or broken anchors as-is. `sync` fixes generated artifacts but must
|
|
20
|
+
never choose a winner between project-owned `CLAUDE.md` and `AGENTS.md`
|
|
21
|
+
blocks; route that drift to the instruction pass below.
|
|
19
22
|
This step needs no LLM judgment — do it first so the semantic passes below
|
|
20
23
|
build on a known-good baseline. **If the CLI is unavailable even via npx,
|
|
21
24
|
skip only this step and proceed with steps 2 and 3 — do not skip the entire
|
|
@@ -27,10 +30,10 @@ independently of its canonical home.
|
|
|
27
30
|
invalid-manifest message), the check did not fully run — report that as a
|
|
28
31
|
failure, never as "0 checked, all fine".** A stale npx-cached CLI is the usual
|
|
29
32
|
cause; re-run pinned to `@latest`, and note the version if it still lags.
|
|
30
|
-
2. **Instruction layer.** Perform the update defined by
|
|
33
|
+
2. **Instruction layer.** Perform the update defined by `{{workflow:praxis-instructions}}`,
|
|
31
34
|
scoped to the focus above. Follow that command's own steps; do not
|
|
32
35
|
re-derive them here.
|
|
33
|
-
3. **Wiki.** Perform the wiki maintenance defined by
|
|
36
|
+
3. **Wiki.** Perform the wiki maintenance defined by `{{workflow:praxis-wiki}}`, scoped to
|
|
34
37
|
the focus above — it files newly-authored knowledge and lints existing pages,
|
|
35
38
|
and no-ops when there is nothing to do. Follow that command's own steps; do
|
|
36
39
|
not re-derive them here. (Full delegation, symmetric with the audit pass —
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
## Upkeep
|
|
2
2
|
|
|
3
|
-
The methodology layer (instructions, wiki, emitted
|
|
3
|
+
The methodology layer (instructions, wiki, emitted agent files) drifts
|
|
4
4
|
from project reality like code drifts from docs. When work reveals drift, or
|
|
5
|
-
produces new durable knowledge to file, run
|
|
6
|
-
front gate sequencing `praxis check`,
|
|
7
|
-
|
|
5
|
+
produces new durable knowledge to file, run `{{workflow:praxis-upkeep}}` — the single
|
|
6
|
+
front gate sequencing `praxis check`, `{{workflow:praxis-instructions}}`, and
|
|
7
|
+
`{{workflow:praxis-wiki}}` — rather than fixing it ad hoc or piecemeal.
|
|
8
|
+
|
|
9
|
+
When Claude Code and Codex coexist, this gate covers both native instruction
|
|
10
|
+
surfaces. Keep their project-owned shared sections synchronized while preserving
|
|
11
|
+
target-specific guidance; never make one surface import or replace the other.
|
|
8
12
|
|
|
9
13
|
Run all three passes through the gate even when only one looks like it has
|
|
10
14
|
work — skipping straight to a sub-skill is the piecemeal use this rule
|
|
@@ -10,8 +10,8 @@ You are curating a durable knowledge wiki for this project: linked markdown page
|
|
|
10
10
|
of long-lived knowledge that a future session reads instead of re-deriving. This
|
|
11
11
|
is the agent's cross-session memory **of the project** — version-controlled and
|
|
12
12
|
shared with the team. It is NOT a per-session handoff (transient — that is
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
`{{workflow:praxis-handoff}}`) and NOT the authored agent instruction layer
|
|
14
|
+
(that is `{{workflow:praxis-instructions}}`).
|
|
15
15
|
|
|
16
16
|
The wiki has two kinds of durable knowledge:
|
|
17
17
|
- **Derived summaries** compress code, config, and canonical docs. The underlying
|
|
@@ -21,7 +21,7 @@ The wiki has two kinds of durable knowledge:
|
|
|
21
21
|
diverge, surface the drift for human judgment instead of silently overwriting
|
|
22
22
|
one with the other.
|
|
23
23
|
|
|
24
|
-
Focus:
|
|
24
|
+
Focus: {{arguments}} — if empty, refresh and lint the whole wiki at a high level.
|
|
25
25
|
|
|
26
26
|
## Phase 1 — Locate or create the wiki
|
|
27
27
|
|
|
@@ -31,8 +31,8 @@ Use the first of these that already exists; otherwise create the first one
|
|
|
31
31
|
2. `wiki/`
|
|
32
32
|
3. `.praxis/wiki/`
|
|
33
33
|
|
|
34
|
-
- Existing entries:
|
|
35
|
-
- Today (for log entries, do not guess):
|
|
34
|
+
- Existing entries: {{shell:ls -R docs/wiki wiki .praxis/wiki 2>/dev/null || true}}
|
|
35
|
+
- Today (for log entries, do not guess): {{shell:date +%Y-%m-%d}}
|
|
36
36
|
|
|
37
37
|
Front the wiki with two entry-point files; create them if missing:
|
|
38
38
|
- `index.md` — a catalog of every page: a link and a one-line summary each.
|
|
@@ -81,7 +81,7 @@ Health-check the wiki and fix what you can (ask before large deletions):
|
|
|
81
81
|
- **Contradictions** — derived summaries that disagree with current code/config
|
|
82
82
|
lose to the source; fix the wiki. Canonical-intent entries are different: if
|
|
83
83
|
code and intent diverge, report the drift and route it to a decision/update
|
|
84
|
-
step (or
|
|
84
|
+
step (or `{{workflow:praxis-instructions}}` for instruction-layer changes) instead of deciding
|
|
85
85
|
automatically. Two wiki entries that disagree resolve to the more recent /
|
|
86
86
|
authoritative / better-supported claim, with the resolution noted.
|
|
87
87
|
- **Stale claims** — derived facts the current code has invalidated: fix or
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Knowledge outlives a single conversation. Keep a durable, in-repo knowledge
|
|
4
4
|
wiki — linked markdown pages a future session reads instead of re-deriving —
|
|
5
|
-
distinct from a handoff (transient) and from
|
|
6
|
-
(
|
|
5
|
+
distinct from a handoff (transient) and from authored agent instructions
|
|
6
|
+
(the instruction layer). At the start of a task, read the wiki's `index.md`
|
|
7
7
|
and follow the links that bear on the work.
|
|
8
8
|
|
|
9
9
|
When you learn something durable — an architecture fact, a gotcha, a decision,
|
|
10
10
|
a workflow — file or update it, and periodically lint the wiki for
|
|
11
|
-
contradictions and staleness. Do this by running
|
|
11
|
+
contradictions and staleness. Do this by running `{{workflow:praxis-wiki}}`, not by hand.
|
|
12
12
|
When reconciling the methodology layer after a change, enter through
|
|
13
|
-
|
|
13
|
+
`{{workflow:praxis-upkeep}}`; call `{{workflow:praxis-wiki}}` directly only for a deliberate single
|
|
14
14
|
pass (e.g. first-run bootstrap).
|
|
@@ -12,23 +12,22 @@ Layer 1 rules (think before coding, simplicity first, surgical changes,
|
|
|
12
12
|
goal-driven execution) apply unchanged — this file adds only what is
|
|
13
13
|
Node-specific.
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
|
|
17
|
-
directory, an adjacent route/command/service). Copy its structure — imports,
|
|
18
|
-
exports, error handling, naming — rather than inventing a new shape. The
|
|
19
|
-
prior art is the template; matching it is verifiable, following prose is not.
|
|
20
|
-
- **One module, one responsibility.** A new `.ts` file exports the smallest
|
|
21
|
-
coherent unit. Keep pure logic separate from I/O (filesystem, network, process)
|
|
22
|
-
so the logic is testable without mocks — mirror how the existing modules here
|
|
23
|
-
already split the two.
|
|
24
|
-
- **Use the platform and installed deps before adding one.** Node stdlib (`node:fs`,
|
|
25
|
-
`node:path`, `node:crypto`, `fetch`) and a dependency already in
|
|
26
|
-
`package.json` cover most needs. A new dependency for what a few lines do is
|
|
27
|
-
debt; justify it against what is already imported in neighbouring files.
|
|
28
|
-
- **Types are the contract.** Type the inputs and outputs at module boundaries;
|
|
29
|
-
validate external/untrusted input where it enters (a schema validator if the
|
|
30
|
-
repo already uses one, e.g. zod). Don't re-validate data already typed and
|
|
31
|
-
checked upstream.
|
|
32
|
-
- **ESM vs CJS, follow the repo.** Match the module system, import extensions
|
|
33
|
-
(`.js` specifiers under `"type": "module"`), and `tsconfig` target already in
|
|
15
|
+
- **ESM vs CJS, follow the repo.** Match the module system, `.js` import
|
|
16
|
+
specifiers under `"type": "module"`, and the `tsconfig` target already in
|
|
34
17
|
use — don't introduce a second convention.
|
|
18
|
+
- **Every promise is handled.** `await` it, `return` it, or explicitly `void`
|
|
19
|
+
it with a comment explaining why. A floating promise swallows its rejection
|
|
20
|
+
and its ordering.
|
|
21
|
+
- **Never silence the type checker.** No `any`, no `as` cast, no non-null `!`
|
|
22
|
+
to make an error disappear. Fix the type; a suppressed error is a runtime
|
|
23
|
+
bug deferred.
|
|
24
|
+
- **Prefer the platform before a dependency.** `node:`-prefixed builtins
|
|
25
|
+
(`node:fs/promises`, `node:path`, `node:crypto`) and global `fetch` cover
|
|
26
|
+
most needs; justify a new dependency against what neighbouring files
|
|
27
|
+
already import.
|
|
28
|
+
- **Throw `Error` subclasses, never strings.** Attach `cause` when
|
|
29
|
+
re-throwing, and handle `error` events on child processes and streams — an
|
|
30
|
+
unhandled one crashes the process.
|
|
31
|
+
- **Validate untrusted input where it enters.** Use the repo's validator
|
|
32
|
+
(e.g. zod) with a non-throwing parse (`safeParse`-style) at the boundary;
|
|
33
|
+
don't re-validate data already typed and checked upstream.
|
|
@@ -4,34 +4,29 @@ paths: ["**/*.ts", "**/*.tsx", "**/*.test.ts", "**/*.spec.ts"]
|
|
|
4
4
|
|
|
5
5
|
## Node/TypeScript test-layer recipe (Layer 2)
|
|
6
6
|
|
|
7
|
-
Choosing *where* a test belongs matters as much as writing it
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Choosing *where* a test belongs matters as much as writing it: pick unit,
|
|
8
|
+
integration, or conformance/end-to-end by what could actually break —
|
|
9
|
+
unit for a single module's logic, integration for real seams (filesystem, a
|
|
10
|
+
spawned process, a built artifact), conformance/e2e for rails the whole tool
|
|
11
|
+
must honour. Then mirror the nearest existing test of that same layer for its
|
|
12
|
+
runner, file location, naming, and fixtures rather than inventing a new shape.
|
|
10
13
|
|
|
11
14
|
Layer 1 rules (think before coding, simplicity first, surgical changes,
|
|
12
15
|
goal-driven execution) apply unchanged — this file adds only what is
|
|
13
16
|
Node-testing-specific.
|
|
14
17
|
|
|
15
|
-
- **
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
file location and naming convention, how it builds fixtures and asserts. Don't
|
|
31
|
-
introduce a second runner or a new fixture style.
|
|
32
|
-
- **Assert behaviour, not implementation.** Test observable inputs→outputs and
|
|
33
|
-
error cases at the boundary; avoid asserting private internals that refactors
|
|
34
|
-
will churn. One clear failing assertion beats five brittle ones.
|
|
35
|
-
- **No new framework for one test.** The repo's installed runner and plain
|
|
36
|
-
assertions cover almost everything. Reach for a new testing dependency only
|
|
37
|
-
when the existing one demonstrably cannot express the check.
|
|
18
|
+
- **One runner in `package.json`.** Use whichever of vitest or jest the repo
|
|
19
|
+
already has; don't add a second runner or a new assertion library for what
|
|
20
|
+
plain `expect` calls already cover.
|
|
21
|
+
- **Await the assertion chain.** `await` or `return` a promise-returning
|
|
22
|
+
assertion (e.g. `expect(promise).rejects...`) — a test whose promise
|
|
23
|
+
resolves after the test function returns asserts nothing and passes falsely.
|
|
24
|
+
- **Restore fake timers.** Pair `vi.useFakeTimers()`/`jest.useFakeTimers()`
|
|
25
|
+
with a restore in teardown, and never mix fake timers with a real
|
|
26
|
+
`setTimeout` await inside the same test.
|
|
27
|
+
- **Reset mocks between tests.** No shared mutable module state leaking across
|
|
28
|
+
cases; restore spies/mocks in `afterEach` so test order can't change the
|
|
29
|
+
outcome.
|
|
30
|
+
- **Temp dir for real-filesystem tests.** Use `fs.mkdtemp` (or the repo's tmp
|
|
31
|
+
helper) and clean up in teardown; never write into the repo tree or a fixed
|
|
32
|
+
path on disk.
|
|
@@ -12,26 +12,22 @@ Layer 1 rules (think before coding, simplicity first, surgical changes,
|
|
|
12
12
|
goal-driven execution) apply unchanged — this file adds only what is
|
|
13
13
|
Python-specific.
|
|
14
14
|
|
|
15
|
-
- **Mirror the nearest module.** Before adding a feature, open the closest
|
|
16
|
-
existing file that already does the same kind of thing (a sibling in the same
|
|
17
|
-
directory, an adjacent route/handler/service). Copy its structure — imports,
|
|
18
|
-
class/function layout, error handling, naming — rather than inventing a new
|
|
19
|
-
shape. The prior art is the template; matching it is verifiable, following
|
|
20
|
-
prose is not.
|
|
21
|
-
- **One module, one responsibility.** A new `.py` file exports the smallest
|
|
22
|
-
coherent unit. Keep pure logic separate from I/O (filesystem, network, database)
|
|
23
|
-
so the logic is testable without mocks — mirror how the existing modules here
|
|
24
|
-
already split the two.
|
|
25
|
-
- **Use the stdlib and installed deps before adding one.** Python stdlib
|
|
26
|
-
(`pathlib`, `dataclasses`, `typing`, `json`, `os`) and a dependency already in
|
|
27
|
-
`pyproject.toml` / `requirements*.txt` cover most needs. A new dependency for
|
|
28
|
-
what a few lines do is debt; justify it against what is already imported in
|
|
29
|
-
neighbouring files.
|
|
30
|
-
- **Type hints are the contract.** Annotate inputs and outputs at module
|
|
31
|
-
boundaries; validate external/untrusted input where it enters (a schema
|
|
32
|
-
validator if the repo already uses one, e.g. pydantic). Don't re-validate data
|
|
33
|
-
already typed and checked upstream.
|
|
34
15
|
- **Follow the repo's packaging conventions.** Match the module layout, import
|
|
35
|
-
style (absolute vs relative), and packaging already in use —
|
|
36
|
-
second convention.
|
|
37
|
-
|
|
16
|
+
style (absolute vs relative), and `src/` vs flat packaging already in use —
|
|
17
|
+
don't introduce a second convention.
|
|
18
|
+
- **No mutable default arguments.** A `[]` or `{}` default is shared across
|
|
19
|
+
every call; use a `None` sentinel and build the value inside the function
|
|
20
|
+
body.
|
|
21
|
+
- **Never swallow exceptions.** No bare `except:`; catch the narrowest
|
|
22
|
+
exception type and either handle it or re-raise with `raise ... from e` to
|
|
23
|
+
keep the chain.
|
|
24
|
+
- **Timezone-aware datetimes.** Use `datetime.now(timezone.utc)`, not naive
|
|
25
|
+
`datetime.now()` — a naive datetime is a latent bug at every boundary it
|
|
26
|
+
crosses.
|
|
27
|
+
- **No blocking calls inside `async def`.** `requests`, `time.sleep`, and sync
|
|
28
|
+
DB drivers stall the event loop; use the async equivalent or run it in an
|
|
29
|
+
executor.
|
|
30
|
+
- **Parameterized queries only.** Pass values as query parameters; never build
|
|
31
|
+
SQL with f-strings or `%` interpolation.
|
|
32
|
+
- **Context managers for resources.** Open files, connections, and locks with
|
|
33
|
+
`with` so they close on every path, including exceptions.
|
|
@@ -4,34 +4,28 @@ paths: ["**/*.py", "**/test_*.py", "**/*_test.py"]
|
|
|
4
4
|
|
|
5
5
|
## Python test-layer recipe (Layer 2)
|
|
6
6
|
|
|
7
|
-
Choosing *where* a test belongs matters as much as writing it
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Choosing *where* a test belongs matters as much as writing it: pick unit,
|
|
8
|
+
integration, or end-to-end by what could actually break — unit for a single
|
|
9
|
+
module's logic, integration for real seams (filesystem, a spawned process, a
|
|
10
|
+
database), end-to-end for an invariant the whole application must honour.
|
|
11
|
+
Then mirror the nearest existing test of that same layer for its location
|
|
12
|
+
(`tests/` or alongside), `test_*.py` naming, and `conftest.py` fixtures rather
|
|
13
|
+
than inventing a new shape.
|
|
10
14
|
|
|
11
15
|
Layer 1 rules (think before coding, simplicity first, surgical changes,
|
|
12
16
|
goal-driven execution) apply unchanged — this file adds only what is
|
|
13
17
|
Python-testing-specific.
|
|
14
18
|
|
|
15
|
-
- **
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
its setup — `pytest` is the default, its file location (`tests/` or alongside
|
|
29
|
-
source), naming convention (`test_*.py`), how it builds fixtures (`conftest.py`,
|
|
30
|
-
`@pytest.fixture`) and asserts. Don't introduce a second runner or a new fixture
|
|
31
|
-
style.
|
|
32
|
-
- **Assert behaviour, not implementation.** Test observable inputs→outputs and
|
|
33
|
-
error cases at the boundary; avoid asserting private internals that refactors
|
|
34
|
-
will churn. One clear failing assertion beats five brittle ones.
|
|
35
|
-
- **No new framework for one test.** The repo's installed runner and plain
|
|
36
|
-
`assert` statements cover almost everything. Reach for a new testing dependency
|
|
37
|
-
only when the existing one demonstrably cannot express the check.
|
|
19
|
+
- **Plain `assert` with pytest.** Let pytest's assertion rewriting report the
|
|
20
|
+
values; don't wrap checks in `unittest` assert methods or add a second
|
|
21
|
+
runner.
|
|
22
|
+
- **Prefer builtin fixtures.** Reach for `tmp_path`, `monkeypatch`, `capsys`
|
|
23
|
+
over hand-rolled setup/teardown code — they clean up after themselves.
|
|
24
|
+
- **Parametrize instead of copy-paste.** Use `@pytest.mark.parametrize` for
|
|
25
|
+
the same body run over many inputs, not duplicated test functions.
|
|
26
|
+
- **Default fixtures to function scope.** Widen to `module`/`session` scope
|
|
27
|
+
only for an expensive read-only resource; a wider scope shares mutable
|
|
28
|
+
state across tests that expect isolation.
|
|
29
|
+
- **Assert error paths with `pytest.raises`.** Use
|
|
30
|
+
`with pytest.raises(Err, match=...)` and assert on the message so the wrong
|
|
31
|
+
error can't pass silently.
|
|
@@ -13,32 +13,37 @@ Layer 1 rules (think before coding, simplicity first, surgical changes,
|
|
|
13
13
|
goal-driven execution) apply unchanged — this file adds only what is
|
|
14
14
|
React-specific.
|
|
15
15
|
|
|
16
|
-
- **Mirror the nearest component.** Before adding a component, open the closest
|
|
17
|
-
existing one that does the same kind of thing (a sibling in the same directory
|
|
18
|
-
or feature folder). Copy its structure — file layout, how it exports, how it
|
|
19
|
-
receives and passes props, where state lives — rather than inventing a new
|
|
20
|
-
shape. The prior art is the template; matching it is verifiable, following
|
|
21
|
-
prose is not.
|
|
22
16
|
- **Props typed at the boundary.** Every component declares an explicit props
|
|
23
|
-
interface or type at the top of its file. Accept the minimum props needed;
|
|
24
|
-
catch-all `[key: string]: unknown`.
|
|
25
|
-
uses them.
|
|
17
|
+
interface or type at the top of its file. Accept the minimum props needed;
|
|
18
|
+
no catch-all `[key: string]: unknown`.
|
|
26
19
|
- **Composition over configuration.** Prefer a small focused component used in
|
|
27
20
|
multiple arrangements over a large one with a boolean-flag API. When a
|
|
28
|
-
component's props grow into a switch statement, split into two components
|
|
29
|
-
share a common primitive.
|
|
30
|
-
- **Native HTML elements before a component library.** A `<button>`,
|
|
31
|
-
or `<ul>` is always the first option. Reach for a library
|
|
32
|
-
the native element demonstrably cannot satisfy the
|
|
33
|
-
whichever library the repo already imports
|
|
34
|
-
- **Accessibility is non-negotiable.** Every interactive element is reachable
|
|
35
|
-
keyboard and has a visible focus style. Form inputs have associated
|
|
36
|
-
elements
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
21
|
+
component's props grow into a switch statement, split into two components
|
|
22
|
+
that share a common primitive.
|
|
23
|
+
- **Native HTML elements before a component library.** A `<button>`,
|
|
24
|
+
`<label>`, or `<ul>` is always the first option. Reach for a library
|
|
25
|
+
component only when the native element demonstrably cannot satisfy the
|
|
26
|
+
requirement — and then use whichever library the repo already imports.
|
|
27
|
+
- **Accessibility is non-negotiable.** Every interactive element is reachable
|
|
28
|
+
by keyboard and has a visible focus style. Form inputs have associated
|
|
29
|
+
`<label>` elements. Non-decorative images have descriptive `alt` text.
|
|
30
|
+
Custom interactive elements carry the correct ARIA `role` and `aria-*`
|
|
31
|
+
attributes.
|
|
32
|
+
- **Follow the repo's hooks and state conventions.** Use
|
|
33
|
+
`useState`/`useReducer` at the level state is first needed, and lift only
|
|
34
|
+
when a sibling needs it. Don't introduce a new global-state library for a
|
|
35
|
+
single component's needs.
|
|
36
|
+
- **You might not need an effect.** Derive values during render instead of
|
|
37
|
+
mirroring props/state into state; an effect is for synchronizing with an
|
|
38
|
+
external system, not for computing from existing state.
|
|
39
|
+
- **Name the effect callback.** `useEffect(function syncScrollPosition() { … },
|
|
40
|
+
[deps])` — a named function makes the effect's intent legible and gives it a
|
|
41
|
+
real name in stack traces and React DevTools instead of an anonymous arrow.
|
|
42
|
+
- **Every subscription effect returns its cleanup.** A listener, timer, or
|
|
43
|
+
subscription set up in an effect is torn down in the function the effect
|
|
44
|
+
returns.
|
|
45
|
+
- **`key` is stable identity, not the array index.** Keys must survive
|
|
46
|
+
reordering; index keys corrupt state on insert or remove.
|
|
47
|
+
- **No `memo`/`useMemo`/`useCallback` by default.** Add memoization only
|
|
48
|
+
against a measured problem; premature memoization is complexity with no
|
|
49
|
+
payoff.
|
|
@@ -4,36 +4,27 @@ paths: ["**/*.test.tsx", "**/*.test.jsx", "**/*.spec.tsx", "**/*.spec.jsx"]
|
|
|
4
4
|
|
|
5
5
|
## React test-layer recipe (Layer 2)
|
|
6
6
|
|
|
7
|
-
Choosing *where* a test belongs matters as much as writing it
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Choosing *where* a test belongs matters as much as writing it: pick unit
|
|
8
|
+
render, integration, or end-to-end by what could actually break — a single
|
|
9
|
+
component in isolation, two or more components (or a component wired to a
|
|
10
|
+
real hook/context/store), or a full user journey in a real browser reserved
|
|
11
|
+
for critical paths. Then mirror the nearest existing test's RTL setup,
|
|
12
|
+
provider wrapping, runner, and naming rather than inventing a new shape.
|
|
10
13
|
|
|
11
14
|
Layer 1 rules (think before coding, simplicity first, surgical changes,
|
|
12
15
|
goal-driven execution) apply unchanged — this file adds only what is
|
|
13
16
|
React-testing-specific.
|
|
14
17
|
|
|
15
|
-
- **
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
critical paths that no unit or integration test can protect; reserve for the
|
|
29
|
-
rails, not for every feature.
|
|
30
|
-
- **Use React Testing Library.** The repo's RTL setup (`render`, `screen`,
|
|
31
|
-
`userEvent`, `fireEvent`) covers almost everything. Mirror the nearest
|
|
32
|
-
existing test file for setup, cleanup, and how it wraps providers. Don't
|
|
33
|
-
introduce a second render helper or a separate component-testing framework.
|
|
34
|
-
- **Mirror the existing suite.** Copy the nearest test file's structure —
|
|
35
|
-
runner, file naming, directory location, how it imports the component under
|
|
36
|
-
test, how it sets up providers or mocks. Consistency beats cleverness.
|
|
37
|
-
- **No new framework for one test.** RTL and the repo's installed runner (e.g.
|
|
38
|
-
vitest/jest) cover almost every component test need. Reach for a new dependency
|
|
39
|
-
only when the existing tooling demonstrably cannot express the check.
|
|
18
|
+
- **Query by role, label, or text.** Use `getByRole`, `getByLabelText`,
|
|
19
|
+
`getByText` — never CSS class, a test id where a role exists, or internal
|
|
20
|
+
component state. A test that breaks on a classname rename without catching
|
|
21
|
+
a regression is noise.
|
|
22
|
+
- **Interactions go through `userEvent`.** Await it
|
|
23
|
+
(`await userEvent.click(...)`); prefer it over `fireEvent`, which skips the
|
|
24
|
+
events real users trigger.
|
|
25
|
+
- **Await async UI with `findBy*`.** Use `findByRole`/`findByText` (or
|
|
26
|
+
`waitFor`) for content that appears after an effect; never put a
|
|
27
|
+
side-effecting call inside `waitFor`.
|
|
28
|
+
- **Wrap renders with the repo's provider helper.** Mirror how the nearest
|
|
29
|
+
test supplies context, store, or router rather than rendering the bare
|
|
30
|
+
component.
|