@agentsfleet/orly 0.5.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.
- package/.githooks/pre-commit +42 -0
- package/.githooks/pre-push +67 -0
- package/AGENTS.md +348 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/SOUL.md +90 -0
- package/audits/agents-md.md +497 -0
- package/audits/agents-md.sh +353 -0
- package/audits/cross-tier-rates.sh +105 -0
- package/audits/data.sh +185 -0
- package/audits/deinit-pairs.sh +224 -0
- package/audits/design-tokens.sh +143 -0
- package/audits/doc-read.sh +183 -0
- package/audits/error-codes.sh +221 -0
- package/audits/fixtures/clean.diff +15 -0
- package/audits/fixtures/dirty.diff +16 -0
- package/audits/lifecycle-anchors.sh +22 -0
- package/audits/logging.sh +217 -0
- package/audits/msid-ui.sh +144 -0
- package/audits/parity-dispatch.sh +51 -0
- package/audits/rule-ledger-lib.sh +264 -0
- package/audits/rule-ledger.sh +225 -0
- package/audits/rule-paths.sh +108 -0
- package/audits/spec-template.sh +258 -0
- package/audits/sql-mod.sh +102 -0
- package/audits/ufs.sh +303 -0
- package/bin/orly +26 -0
- package/core/operating-model.md +256 -0
- package/dispatch/edit_rules.md +69 -0
- package/dispatch/lib.sh +213 -0
- package/dispatch/lifecycle.md +187 -0
- package/dispatch/name_architecture.md +93 -0
- package/dispatch/verify.md +111 -0
- package/dispatch/write_any.md +740 -0
- package/dispatch/write_any.sh +49 -0
- package/dispatch/write_auth.md +36 -0
- package/dispatch/write_changelog.md +64 -0
- package/dispatch/write_documentation.md +45 -0
- package/dispatch/write_go.md +24 -0
- package/dispatch/write_http.md +45 -0
- package/dispatch/write_mdx.md +7 -0
- package/dispatch/write_python.md +8 -0
- package/dispatch/write_rust.md +38 -0
- package/dispatch/write_shell.md +8 -0
- package/dispatch/write_spec.md +105 -0
- package/dispatch/write_sql.md +112 -0
- package/dispatch/write_sql.sh +36 -0
- package/dispatch/write_ts_adhere_bun.md +493 -0
- package/dispatch/write_ts_adhere_bun.sh +37 -0
- package/dispatch/write_zig.md +852 -0
- package/dispatch/write_zig.sh +40 -0
- package/docs/CHANGELOG_VOICE.md +18 -0
- package/docs/DISPATCH_ARCHITECTURE.md +664 -0
- package/docs/DISPATCH_REVIEW_DISPOSITION.md +65 -0
- package/docs/DOCUMENTATION_RULES.md +209 -0
- package/docs/EXECUTE_DOC_READS.md +29 -0
- package/docs/HARNESS_VERIFY_OUTPUT.md +40 -0
- package/docs/LIFECYCLE_PATTERNS.md +316 -0
- package/docs/LOGGING_STANDARD.md +397 -0
- package/docs/ORLY_ARCHITECTURE.md +129 -0
- package/docs/REST_API_DESIGN_GUIDELINES.md +783 -0
- package/docs/RULE_ENFORCEMENT.md +41 -0
- package/docs/SCHEMA_CONVENTIONS.md +93 -0
- package/docs/TEMPLATE.md +411 -0
- package/docs/VERIFY_TIERS.md +57 -0
- package/docs/greptile-learnings/README.md +51 -0
- package/docs/greptile-learnings/RULES.md +1045 -0
- package/fixtures/registry-invalid.json +1 -0
- package/fixtures/registry-valid.json +18 -0
- package/fixtures/unclosed-pack-block.md +8 -0
- package/package.json +63 -0
- package/packs/language/go/rules.md +24 -0
- package/packs/language/mdx/rules.md +7 -0
- package/packs/language/python/rules.md +8 -0
- package/packs/language/rust/rules.md +38 -0
- package/packs/language/shell/rules.md +8 -0
- package/registry.json +405 -0
- package/schemas/evidence.schema.json +43 -0
- package/schemas/profile.schema.json +12 -0
- package/schemas/registry.schema.json +12 -0
- package/skills/orly-babysit-prs/SKILL.md +326 -0
- package/skills/orly-spec-new/SKILL.md +157 -0
- package/skills/orly-write-integration-test/SKILL.md +470 -0
- package/skills/orly-write-unit-test/SKILL.md +520 -0
- package/src/cli.test.ts +70 -0
- package/src/cli.ts +287 -0
- package/src/config.test.ts +226 -0
- package/src/config.ts +288 -0
- package/src/criteria.test.ts +137 -0
- package/src/criteria.ts +166 -0
- package/src/criteria_spec.ts +127 -0
- package/src/criteria_support.ts +53 -0
- package/src/gates.test.ts +183 -0
- package/src/gates.ts +169 -0
- package/src/gates_spec.test.ts +98 -0
- package/src/gates_test_support.ts +194 -0
- package/src/git_env.ts +20 -0
- package/src/install.test.ts +514 -0
- package/src/install.ts +447 -0
- package/src/install_nested.test.ts +32 -0
- package/src/model.test.ts +31 -0
- package/src/model.ts +245 -0
- package/src/references.test.ts +47 -0
- package/src/references.ts +147 -0
- package/src/render.test.ts +60 -0
- package/src/render.ts +94 -0
- package/src/surfaces.test.ts +43 -0
- package/src/surfaces.ts +89 -0
- package/src/validation.test.ts +11 -0
- package/src/validation.ts +87 -0
- package/src/verify.test.ts +13 -0
- package/src/verify.ts +59 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orly-write-unit-test
|
|
3
|
+
description: >
|
|
4
|
+
Risk-weighted, failure-injecting test generation across 8 stacks (Python,
|
|
5
|
+
Python SDK, OpenAPI, JS/TS CLI, React/TS, Zig, Rust, Shell) enforcing behaviour +
|
|
6
|
+
failure + invariant + integration + regression coverage with a
|
|
7
|
+
Definition-of-Done gate; deterministic production-safety proofs (Zig
|
|
8
|
+
zero-leak, 100-or-more-connection concurrency, complexity/latency budgets) live
|
|
9
|
+
in the skill body. Use during implementation, not after.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Write Unit Test
|
|
13
|
+
|
|
14
|
+
Generate tests that catch bugs before they ship. Tests run *during* implementation, not after.
|
|
15
|
+
|
|
16
|
+
> **What this guarantees / what it doesn't.** No test suite proves "zero bugs in
|
|
17
|
+
> production" — nothing does. This skill guarantees something *checkable*: **no
|
|
18
|
+
> changed behaviour, branch, or named failure-mode ships untested, and the tests
|
|
19
|
+
> provably *assert* rather than merely *execute*.** Three production-safety proofs
|
|
20
|
+
> (Zig zero-leak · concurrency-at-scale · performance/complexity budget) are gated
|
|
21
|
+
> on **deterministic** evidence — counters, exhaustive injection, pinned baselines —
|
|
22
|
+
> not wall-clock vibes. Coverage proves a line *ran*; these proofs prove a test
|
|
23
|
+
> would *scream* when the behaviour breaks.
|
|
24
|
+
|
|
25
|
+
## Test quality bar
|
|
26
|
+
|
|
27
|
+
Every test answers three questions. If it answers none, delete it.
|
|
28
|
+
|
|
29
|
+
1. **What bug would this catch?**
|
|
30
|
+
2. **What behaviour does this prove?**
|
|
31
|
+
3. **What invariant does this enforce?**
|
|
32
|
+
|
|
33
|
+
A green test that answers none of these is worse than no test — it's false confidence.
|
|
34
|
+
|
|
35
|
+
## Truth hierarchy
|
|
36
|
+
|
|
37
|
+
When sources disagree, trust in this order:
|
|
38
|
+
|
|
39
|
+
1. **Runtime behaviour** — prod logs, incidents, observed traces
|
|
40
|
+
2. **Spec** — `docs/v*/active/M*.md`, PR description, milestone doc
|
|
41
|
+
3. **Code** — current implementation
|
|
42
|
+
|
|
43
|
+
Spec contradicts runtime → surface the conflict before testing. Code contradicts spec → spec wins; test the spec, flag the code as a defect.
|
|
44
|
+
|
|
45
|
+
## First steps
|
|
46
|
+
|
|
47
|
+
1. **Read the spec, then the code** — spec defines what to test; code is what gets tested. Don't let code shape expectations.
|
|
48
|
+
2. **Read `docs/greptile-learnings/RULES.md`** — every rule is a regression test.
|
|
49
|
+
3. **Detect the stack** from project files (`pyproject.toml`, `Cargo.toml`, `build.zig`, `package.json` + `react`/`bin`, `*.sh`, `openapi.yaml`).
|
|
50
|
+
4. **Run the existing suite first** — establish the green baseline before adding tests.
|
|
51
|
+
5. **Follow existing naming and fixture conventions.**
|
|
52
|
+
|
|
53
|
+
## Step 0 — the diff is the spec: build the required-test ledger first
|
|
54
|
+
|
|
55
|
+
Before writing a single test, compute what *must* be tested **from the diff** — not from
|
|
56
|
+
re-reading whole files. This is the optimised review: bounded by the change, not the
|
|
57
|
+
codebase, and it's the artifact that makes "is the skill applied?" auditable.
|
|
58
|
+
|
|
59
|
+
1. `git diff origin/main...HEAD` (or the PR base). Enumerate, per changed file:
|
|
60
|
+
- every changed/added **public function or method**
|
|
61
|
+
- every new or modified **branch** (`if`/`switch`/`match`/`?`/ternary)
|
|
62
|
+
- every new **error construction** (`catch`/`orelse`/`try`/`Err`/`raise`/`throw`)
|
|
63
|
+
- every new **public symbol** (export, `pub`, `__all__`, header signature)
|
|
64
|
+
- every ordered boundary interaction when one operation touches multiple systems or acquires the same resource more than once; repeated acquisitions are separate rows
|
|
65
|
+
2. Each row gets a required test type and a status:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
| Changed unit | Test type | Exists? | Note |
|
|
69
|
+
|---------------------------|---------------|---------|----------------------------|
|
|
70
|
+
| parseLease() new branch | Failure | ❌ | empty-input path |
|
|
71
|
+
| revokeKey() orelse | Failure (inj) | ❌ | DB-down → 503 |
|
|
72
|
+
| computeTotals() n² scan | Performance | ❌ | counter ladder + refactor? |
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
3. The ledger is **done only when every row is ✅ or carries an explicit
|
|
76
|
+
`won't-test: <reason>` / `needs-infra`**. A bare gap is a defect, not a deferral.
|
|
77
|
+
4. **Paste the resolved ledger into PR Session Notes** so `/review`, `orly-babysit-prs`,
|
|
78
|
+
and a human can audit it — the check becomes reviewable, not merely claimed.
|
|
79
|
+
|
|
80
|
+
## Three execution modes
|
|
81
|
+
|
|
82
|
+
Pick one. Don't run all categories on every file.
|
|
83
|
+
|
|
84
|
+
| Mode | When | Required categories |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| **Change-set** | Standard PR, isolated fix | Behaviour, Failure, Integration, Regression |
|
|
87
|
+
| **Hardening** | Critical paths: auth, money, data integrity, migrations, distributed ops | + Invariant, Concurrency, Fuzz |
|
|
88
|
+
| **Deep audit** | Infra/core, dependency upgrade, full release QA, security review | All categories + Performance + Contract |
|
|
89
|
+
|
|
90
|
+
Auto-detect from diff: auth/payment/migration/concurrency/streaming files → Hardening. Otherwise → Change-set.
|
|
91
|
+
|
|
92
|
+
## Risk-weighted priority
|
|
93
|
+
|
|
94
|
+
Within each mode, write tests in this order. Stop when you've covered the surface.
|
|
95
|
+
|
|
96
|
+
1. **State transitions** — PENDING→RUNNING, idempotent vs not, monotonic
|
|
97
|
+
2. **External boundaries** — I/O, network, DB, FS, IPC; test release-then-reacquire windows first
|
|
98
|
+
3. **Concurrency paths** — races, ordering, double-fire
|
|
99
|
+
4. **Error handling branches** — every `catch`, `orelse`, `?`, `Result::Err`, `try/except`
|
|
100
|
+
5. **Pure functions** — last; usually low bug density
|
|
101
|
+
|
|
102
|
+
A string helper does not get the same treatment as a distributed retry loop. Allocate effort by blast radius.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Five test categories
|
|
107
|
+
|
|
108
|
+
### 1. Behaviour
|
|
109
|
+
Input → exact output shape AND side effects. Assert *both*. Asserting only the return value misses DB writes, event emits, log lines, state changes.
|
|
110
|
+
|
|
111
|
+
### 2. Failure (≥50% of all tests)
|
|
112
|
+
- **Specific error contracts** — type + message + structured fields (status code, error code, hint). Bare `expect error` / `assert.throws` is a smell.
|
|
113
|
+
- **Boundaries** — empty, null/None/`Option::None`, max (`i64::MAX`, `Number.MAX_SAFE_INTEGER`), overflow, NaN, malformed, truncated, unicode (CJK, emoji, RTL, ZWJ), CRLF/LF, leading/trailing whitespace, embedded newlines.
|
|
114
|
+
- **Expected vs fatal distinction** — a function returning null for both timeout (expected) and connection-reset (fatal) causes busy-loops. Test that they take different paths.
|
|
115
|
+
- **Auth failures** — no token, expired, wrong role, revoked, rate limited.
|
|
116
|
+
- **Downstream errors** — 4xx, 5xx, timeout, DNS, TLS, partial UTF-8, broken pipe.
|
|
117
|
+
- **Partial completion** — when one operation touches multiple systems or acquires the same resource more than once, enumerate every ordered connection, transaction, and external call; select each workflow ordinal and boundary-call ordinal for failure, record failure timing / remote outcome, then assert the residual durable state, external state, user-visible state, and retry outcome.
|
|
118
|
+
|
|
119
|
+
Write the matrix before the tests:
|
|
120
|
+
|
|
121
|
+
| Workflow N | Boundary call N | Dependency / resource | Interaction | Prior completed actions | Injection seam | Operation / request scope | Response | Failure timing / remote outcome | Durable residual state | External residual state | User-visible state | Retry outcome | Design finding |
|
|
122
|
+
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
123
|
+
| 1..N | 1..M | named boundary | exact call | side effects already committed | ordinal-selecting mechanism | test plus operation ID | exact surface | before effect / remote committed, acknowledgement lost / after durable write | rows and transactions | remote effects | next user read | heals or stays broken | ordering defect or none |
|
|
124
|
+
|
|
125
|
+
The workflow ordinal locates the interaction in the whole operation. The
|
|
126
|
+
boundary-call ordinal selects a repeated call at one dependency. Scope the
|
|
127
|
+
failpoint to the test and operation or request identifier, reset it per test,
|
|
128
|
+
make its counter concurrency-safe, and assert it fired exactly once.
|
|
129
|
+
|
|
130
|
+
Every external mutation gets rows for failure before the effect, remote commit with
|
|
131
|
+
acknowledgement-loss, and failure after the following durable write. For
|
|
132
|
+
acknowledgement-loss, inspect remote state or a provider status query, assert the
|
|
133
|
+
idempotency or reconciliation record, and prove retry reaches exactly one effect.
|
|
134
|
+
Durable intent or an outbox makes repair observable; it does not by itself prove a
|
|
135
|
+
retry cannot reissue the remote mutation.
|
|
136
|
+
|
|
137
|
+
### 3. Invariant
|
|
138
|
+
Properties that must hold across all valid inputs. Use property-based testing (`hypothesis`/`proptest`/`fast-check`) when input domain is large.
|
|
139
|
+
|
|
140
|
+
- **Idempotency** — N calls = 1 effect. Required for webhooks, retries, distributed ops, PUT semantics.
|
|
141
|
+
- **Monotonic state** — RUNNING never goes back to PENDING.
|
|
142
|
+
- **No duplicate side effects** — same event ID never processes twice.
|
|
143
|
+
- **Eventual consistency** — final state converges regardless of order.
|
|
144
|
+
- **Conservation** — sum of debits = sum of credits; total events in = total out + dropped.
|
|
145
|
+
- **Schema lock** — no silent field add/remove on stored or wire format.
|
|
146
|
+
|
|
147
|
+
### 4. Integration
|
|
148
|
+
End-to-end through real stack. **Mock only at system boundaries** (network, disk, external APIs, clock, randomness). Never mock internal modules — that validates fake behaviour and integration bugs slip through.
|
|
149
|
+
|
|
150
|
+
- Full middleware chain executes (auth, rate-limit, logging, CORS).
|
|
151
|
+
- Real DB, real serializer, real router. Use containers, not in-memory fakes when behaviour differs.
|
|
152
|
+
- For spec claims involving "real-time" / "streaming" / "incremental": assert bytes arrive incrementally — not just that the parser handles pre-buffered data.
|
|
153
|
+
- Failure modes named in code → integration test with deterministic injection.
|
|
154
|
+
|
|
155
|
+
### 5. Regression
|
|
156
|
+
Pin behaviour that must not silently change.
|
|
157
|
+
|
|
158
|
+
- Golden output files — diff on change. Agent must explain *why* a snapshot changed; never silently re-bless.
|
|
159
|
+
- Public API surface (`__all__`, exports, header signatures, `@sizeOf`, ABI stability).
|
|
160
|
+
- Breaking-change detectors (`oasdiff breaking`, `buf breaking`, `cargo public-api`).
|
|
161
|
+
- Old test suite passes against new code on dependency upgrades.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Hard rules
|
|
166
|
+
|
|
167
|
+
- **Mock only at system boundaries.** No mocking internal modules. No asserting on mocks (`expect(mockFn).toHaveBeenCalled()`) without also asserting outcome state.
|
|
168
|
+
- **State isolation per test.** Reset globals, DB rows, fixtures, env vars, caches. Tests must pass under randomised execution order. Run `pytest -p randomly` / `cargo test -- --shuffle` / equivalent at least once per branch.
|
|
169
|
+
- **Specific error contracts.** Assert error type AND message AND structured fields.
|
|
170
|
+
- **≥50% negative-path tests** on changed surface. If success tests outnumber failure tests, you're not done.
|
|
171
|
+
- **Test names read as documentation.** `should_reject_empty_user_id`, `should_retry_on_502_from_gateway`, `should_not_duplicate_event_on_retry`. No `test_1`, `test_basic`, `it works`.
|
|
172
|
+
- **No testing private functions directly.** Test through the public surface. If a private function needs direct testing, it's actually public — promote it.
|
|
173
|
+
- **No duplicating implementation logic in tests.** A test that re-implements the function under test proves nothing.
|
|
174
|
+
- **Tests favour clarity over DRY.** Independence and explicitness beat reuse. Extract a helper only when ≥3 tests share non-trivial setup AND the helper has no logic worth hiding.
|
|
175
|
+
- **Untestable claims are flagged**, not silently skipped. Mark `needs infra` in the claim-tracing table.
|
|
176
|
+
- **Inject at the selected ordinals, not only the first interaction.** A boundary fake must select both the workflow ordinal and boundary-call ordinal. Draining a whole resource pool proves only the first acquisition and cannot cover a later one.
|
|
177
|
+
- **Residual state is the failure assertion.** For each injected interaction, assert what survived, what external state changed, what the user sees next, and whether retry heals it. The response alone is incomplete.
|
|
178
|
+
- **Unsafe external mutation ordering is a design defect.** Read-only prerequisites are valid. If a mutation can complete before any durable intent or repair record, report it; prefer durable intent first, or require idempotency, compensation, and reconciliation when sequencing cannot change.
|
|
179
|
+
- **Acknowledgement loss needs a remote-state proof.** For every external mutation, make the remote effect succeed while its acknowledgement is lost; inspect remote state, assert the idempotency or reconciliation record, and prove retry reaches exactly one effect. A durable record alone is not an exactly-once proof.
|
|
180
|
+
- **Record the partial-completion matrix.** Paste the completed rows into PR Session Notes. A checked box without the rows is indistinguishable from a skipped proof.
|
|
181
|
+
|
|
182
|
+
## Anti-patterns (reject in review)
|
|
183
|
+
|
|
184
|
+
- ❌ Snapshot-only tests with no behavioural assertion
|
|
185
|
+
- ❌ Asserting on mocks instead of outcomes
|
|
186
|
+
- ❌ Testing private functions directly
|
|
187
|
+
- ❌ Duplicating implementation logic in tests
|
|
188
|
+
- ❌ Bare `expect error` / `assert.throws` with no type/message contract
|
|
189
|
+
- ❌ Tests that pass only when run in a specific order
|
|
190
|
+
- ❌ "Minimum N tests per file" filler that asserts the same invariant N times
|
|
191
|
+
- ❌ Mocking internal modules
|
|
192
|
+
- ❌ Coverage padding — line coverage with no behavioural claim
|
|
193
|
+
- ❌ Hand-wavy performance assertions ("not too slow")
|
|
194
|
+
- ❌ Silent golden-file re-blessing on snapshot drift
|
|
195
|
+
- ❌ Draining an entire pool and calling it coverage of every acquisition
|
|
196
|
+
- ❌ Asserting a failure response without asserting the operation's residual state and retry outcome
|
|
197
|
+
- ❌ Treating a durable intent row as proof that acknowledgement-loss cannot reissue a remote effect
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Failure injection toolkit
|
|
202
|
+
|
|
203
|
+
Vague claim: "handles connection reset". Real test: deterministic injection. If you can't reproduce a failure deterministically, the test is fiction.
|
|
204
|
+
|
|
205
|
+
| Failure | Tool / technique |
|
|
206
|
+
|---|---|
|
|
207
|
+
| TCP reset / half-open | `toxiproxy`, `tc netem`, raw socket close from peer |
|
|
208
|
+
| Slow / delayed response | `toxiproxy` latency, `respx` delay, fake clock |
|
|
209
|
+
| Partial read | `io.LimitedReader`, custom `Read` returning short |
|
|
210
|
+
| DB connection drop | `pg_terminate_backend()` mid-transaction |
|
|
211
|
+
| Timeout | shrink `SO_RCVTIMEO`, `tokio::time::pause()` |
|
|
212
|
+
| Clock skew | freeze/advance fake clock; `chrono` mock; `mock-time` |
|
|
213
|
+
| Disk full / EIO | `tmpfs` size limit; mock filesystem |
|
|
214
|
+
| OOM | `FixedBufferAllocator` (Zig), small heap caps |
|
|
215
|
+
| Concurrency races | `loom` (Rust), `std.Thread` + barrier (Zig) |
|
|
216
|
+
| Network partition | `iptables`/`pfctl` block, `toxiproxy` down |
|
|
217
|
+
| Selected workflow and boundary-call ordinals | Counter-controlled boundary fake fails the selected interaction |
|
|
218
|
+
|
|
219
|
+
Every failure mode named in the spec or code (`catch`, `orelse`, retry loop) needs at least one injection-driven test.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Coverage that matters
|
|
224
|
+
|
|
225
|
+
Line coverage is noise. Track and report:
|
|
226
|
+
|
|
227
|
+
- **Branch coverage** — every `if`/`match`/`switch` arm hit
|
|
228
|
+
- **Error-path coverage** — every `catch`/`orelse`/`Err` constructed in code is exercised by a test
|
|
229
|
+
- **Input-class coverage** — empty / single / many / unicode / malformed each appear at least once per public input
|
|
230
|
+
|
|
231
|
+
Targets on touched code: branch ≥80%, error-path 100%, negative-path ratio ≥50%.
|
|
232
|
+
|
|
233
|
+
## Mutation testing — coverage proves *ran*, mutation proves *caught*
|
|
234
|
+
|
|
235
|
+
Branch coverage says a line executed; it can't tell a real assertion from
|
|
236
|
+
`expect(true).toBe(true)`. Mutation testing flips operators and deletes statements in the
|
|
237
|
+
**changed** code and checks your tests **fail** (kill the mutant). A *surviving* mutant on
|
|
238
|
+
a changed line = a test that asserts nothing meaningful. Required in **Hardening** mode;
|
|
239
|
+
recommended on any changed logic with branching.
|
|
240
|
+
|
|
241
|
+
- **Scope to the diff** — mutate only files changed vs the PR base; full-repo mutation is
|
|
242
|
+
too slow to gate on.
|
|
243
|
+
- **Determinism:** prefer **rule-based** mutators (same mutants every run) over LLM-based
|
|
244
|
+
ones (nondeterministic — fights the invariance spine).
|
|
245
|
+
- **Gate:** **0 surviving mutants on changed lines** (or each survivor justified as an
|
|
246
|
+
equivalent mutant). Paste the kill ratio.
|
|
247
|
+
|
|
248
|
+
| Stack | Tool | Diff-scoping |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| TypeScript / Next.js / React | **Stryker** (`@stryker-mutator/core` + `vitest-runner`/`jest-runner` + `typescript-checker`) | `--since=<base>` / `--incremental`; scope to `lib/`, server actions, reducers, validators — **not** render-heavy `.tsx` |
|
|
251
|
+
| Python | `mutmut` or `cosmic-ray` | mutate changed modules only |
|
|
252
|
+
| Rust | `cargo-mutants` | `--in-diff <patch>` (native git-diff scoping) |
|
|
253
|
+
| Zig | no dedicated tool — **`universalmutator`** (regex/Comby, language-agnostic; offloads non-compiling mutants to the strict compiler), or a homegrown operator-swap harness (`==`↔`!=`, `<`↔`<=`, `+`↔`-`, `and`↔`or`, drop `orelse`/`errdefer`) driving `zig build test` | restrict the mutate target to changed files |
|
|
254
|
+
|
|
255
|
+
## Red-green proof (every bug-fix / regression PR)
|
|
256
|
+
|
|
257
|
+
A regression test that passes on the *buggy* code proves nothing. Prove the red:
|
|
258
|
+
|
|
259
|
+
1. Check out the parent commit (`git worktree add ../pre <parent>`) or stash the fix.
|
|
260
|
+
2. Run the new test there — it **must FAIL** (the bug reproduces).
|
|
261
|
+
3. Restore HEAD — the test **must PASS**.
|
|
262
|
+
4. Paste both results. A test green on the pre-fix code is a false test — fix it until it
|
|
263
|
+
reproduces the bug, or it doesn't ship.
|
|
264
|
+
|
|
265
|
+
This is the only proof the test actually pins the bug. Mandatory whenever the PR references
|
|
266
|
+
a bug, incident, or "fix"; recommended for any behaviour-correcting change.
|
|
267
|
+
|
|
268
|
+
## Spec integration
|
|
269
|
+
|
|
270
|
+
When a spec exists with these tables, treat them as authoritative test sources:
|
|
271
|
+
|
|
272
|
+
| Spec table | Generates |
|
|
273
|
+
|---|---|
|
|
274
|
+
| Test Specification | One test per row, name = `dim X.Y: <name>` |
|
|
275
|
+
| Error Contracts | One negative test per row asserting exact error |
|
|
276
|
+
| Failure Modes | One integration test per row with deterministic injection |
|
|
277
|
+
| Partial Completion | One row per ordered failure point with workflow-ordinal and boundary-call-ordinal injection plus residual-state assertions |
|
|
278
|
+
| Implementation Constraints | Verification command run + result reported |
|
|
279
|
+
|
|
280
|
+
If the spec has none of these, build a claim-tracing table from spec prose / PR description:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
| Claim | Test type | Injection mechanism | Test exists? |
|
|
284
|
+
|---|---|---|---|
|
|
285
|
+
| "gate results print in real time" | integration (streaming) | partial-read reader | ❌ |
|
|
286
|
+
| "heartbeat fires within 30s" | integration (timer) | fake clock | ❌ |
|
|
287
|
+
| "no busy-loop on Redis reset" | failure (fatal vs timeout) | `pg_terminate_backend` | ❌ |
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Every claim → ≥1 test. Untestable claims → flag `needs infra`.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Definition of Done
|
|
295
|
+
|
|
296
|
+
Reject the change if any are missing on touched code:
|
|
297
|
+
|
|
298
|
+
- [ ] ≥50% negative-path tests for changed surface
|
|
299
|
+
- [ ] Boundary tests: empty, null/None, max, malformed
|
|
300
|
+
- [ ] Error contract tests: type + message + structured fields
|
|
301
|
+
- [ ] Idempotency check where retries / webhooks / distributed ops apply
|
|
302
|
+
- [ ] State isolation: tests pass under random execution order
|
|
303
|
+
- [ ] Integration test through real stack for any spec claim involving transport/streaming/real-time
|
|
304
|
+
- [ ] Failure-injection test for every failure mode named in code
|
|
305
|
+
- [ ] **Partial completion:** every operation touching multiple systems or acquiring the same resource more than once has one matrix row per ordered failure point; each row selects workflow and boundary-call ordinals, failure timing / remote outcome, scopes a concurrency-safe failpoint to one operation, and asserts durable, external, user-visible, and retry residual state; every external mutation proves acknowledgement-loss remote state plus exactly one effect on retry; the completed matrix is pasted into PR Session Notes
|
|
306
|
+
- [ ] Test names read as documentation
|
|
307
|
+
- [ ] No mocking of internal modules
|
|
308
|
+
- [ ] Golden-file drift, if any, has a written explanation
|
|
309
|
+
- [ ] Branch coverage ≥80%, error-path 100% on touched code
|
|
310
|
+
- [ ] **Performance:** counter-based complexity proof on the n-ladder (O-bound asserted) + median-of-K latency vs pinned baseline; N+1 round-trips ruled out
|
|
311
|
+
- [ ] **Zig zero-leak:** `make memleak` 0 leaks; every alloc-with-error-path proven by `std.testing.checkAllAllocationFailures`; no cross-request high-water growth over N iters
|
|
312
|
+
- [ ] **Concurrency:** ≥100-connection barrier-started test proves exactly-once + parallelism (peak-concurrency/lock-wait counter, or wall-time < R×); passes K runs + race detector clean
|
|
313
|
+
- [ ] **Refactor escalation:** any super-linear or serialization finding on touched code carries a refactor proposal (root cause + target design + patch alternative + recommendation) surfaced for decision — not silently band-aided
|
|
314
|
+
- [ ] **Diff ledger** resolved 100% — every changed unit has a test or an explicit `won't-test`/`needs-infra`; ledger pasted into PR Session Notes
|
|
315
|
+
- [ ] **Mutation** (Hardening / changed logic): 0 surviving mutants on changed lines (diff-scoped), or each survivor justified; kill ratio pasted
|
|
316
|
+
- [ ] **Red-green** (fix PRs): the new test fails on the parent commit and passes on HEAD — both pasted
|
|
317
|
+
|
|
318
|
+
100% required before declaring done.
|
|
319
|
+
|
|
320
|
+
## CI execution strategy
|
|
321
|
+
|
|
322
|
+
| Stage | What runs |
|
|
323
|
+
|---|---|
|
|
324
|
+
| **PR** | Behaviour + Failure + Regression unit; fast Integration; lint; type-check; randomised order |
|
|
325
|
+
| **Pre-merge** | Full Integration; Contract (`oasdiff` / `buf breaking`); coverage delta |
|
|
326
|
+
| **Nightly** | Concurrency stress; Fuzz (`schemathesis` / `proptest` / `cargo-fuzz`); Performance benchmarks |
|
|
327
|
+
| **Pre-release** | Golden full-suite; cross-platform; memory-leak (`std.testing.allocator`); ABI stability |
|
|
328
|
+
|
|
329
|
+
A test in the wrong stage either delays PRs or never runs. Place deliberately.
|
|
330
|
+
|
|
331
|
+
## Production-safety proofs (deterministic & invariant)
|
|
332
|
+
|
|
333
|
+
These three catch bugs that reach production silently and cost the most to remove.
|
|
334
|
+
Each is written to be **deterministic** (same verdict every run) and **invariant**
|
|
335
|
+
(independent of machine, load noise, and execution order).
|
|
336
|
+
|
|
337
|
+
**The rule that governs all three: prefer counters over clocks.** An allocation
|
|
338
|
+
count, a query/round-trip count, a lock-wait tally, a peak-concurrency count is
|
|
339
|
+
noise-free and reproducible; wall-clock time is not. Where wall-clock is unavoidable
|
|
340
|
+
(latency budgets), use **median-of-K runs + a margin** against a **pinned baseline
|
|
341
|
+
file**, never a single sample. Where an input space must be covered, prefer
|
|
342
|
+
**exhaustive injection** (every allocation site, every interleaving the tool can
|
|
343
|
+
enumerate) over sampling.
|
|
344
|
+
|
|
345
|
+
### Proof 1 — Zig: zero leaks in production, error paths included
|
|
346
|
+
|
|
347
|
+
"0 leaks" means *every* path frees, including the ones that only run on failure — not
|
|
348
|
+
"the happy path didn't leak."
|
|
349
|
+
|
|
350
|
+
- **Every test allocates via `std.testing.allocator`.** It fails the test on any leak
|
|
351
|
+
or double-free, deterministically. Gate: no test uses `std.heap.page_allocator` or a
|
|
352
|
+
long-lived General Purpose Allocator (GPA); `grep -rn "page_allocator" '**/test*.zig'`
|
|
353
|
+
in test code is empty (or justified per line).
|
|
354
|
+
- **Exhaustive error-path proof — `std.testing.checkAllAllocationFailures`.** For every
|
|
355
|
+
function that allocates *and* can return an error, run it under
|
|
356
|
+
`checkAllAllocationFailures` (or a `FailingAllocator` loop): it fails each allocation
|
|
357
|
+
site in turn and asserts the function leaks nothing on the resulting error return.
|
|
358
|
+
This is the *only* acceptable proof that every `errdefer` is correct — "looks right"
|
|
359
|
+
is not. Exhaustive over allocation sites ⇒ deterministic.
|
|
360
|
+
- **No cross-request growth.** Drive the full lifecycle (init → handle → deinit, or
|
|
361
|
+
arena acquire → use → reset) for N iterations and assert outstanding-bytes /
|
|
362
|
+
high-water does **not** grow monotonically. A slow leak the single-shot test misses
|
|
363
|
+
shows up here as linear growth.
|
|
364
|
+
- **Gate:** `make memleak` → **0 leaks**, output pasted into PR Session Notes; cross-compile
|
|
365
|
+
both targets first (`x86_64-linux`, `aarch64-linux`) — stdlib alloc paths differ from macOS.
|
|
366
|
+
|
|
367
|
+
### Proof 2 — Concurrency proven at ≥100 connections (no hidden global lock)
|
|
368
|
+
|
|
369
|
+
The code must be correct **and actually parallel** under real contention. A single
|
|
370
|
+
global `Thread.Mutex` makes correctness tests pass while quietly serialising every
|
|
371
|
+
request — exactly the regression this proof exists to catch.
|
|
372
|
+
|
|
373
|
+
- **Real contention, deterministic start.** ≥100 concurrent clients/connections,
|
|
374
|
+
released together off a **barrier** so contention is real and reproducible (not
|
|
375
|
+
staggered by spawn latency).
|
|
376
|
+
- **Correctness invariant (the hard gate, fully deterministic):** same row / same
|
|
377
|
+
idempotency key / same lease under the 100-way race → **exactly one effect**, no lost
|
|
378
|
+
update, no corruption. Assert the invariant, not "no crash."
|
|
379
|
+
- **Parallelism assertion (catches the global-lock regression):** prove work runs in
|
|
380
|
+
parallel. Cheapest deterministic form is a **counter** — assert peak simultaneous
|
|
381
|
+
in-flight ≥ a threshold, or lock-wait ≈ 0. Where only wall-clock exists, assert 100
|
|
382
|
+
concurrent ops finish in **< R×** a single op (R with margin: a global lock blows past
|
|
383
|
+
~100×, so R≈10 catches it without flaking), median-of-K.
|
|
384
|
+
- **Flush ordering bugs:** run K× under randomised order. `loom` (Rust) enumerates
|
|
385
|
+
interleavings exhaustively — use it where the stack supports it; for Zig, barrier +
|
|
386
|
+
repeat + invariant assertions.
|
|
387
|
+
- **Gate:** the ≥100-connection test passes K consecutive runs; race detector clean where
|
|
388
|
+
available; counters/timing pasted.
|
|
389
|
+
- **A serialization bottleneck is a *structural* finding, not a patch target.** If the
|
|
390
|
+
parallelism counter shows one global lock serialising requests, propose the concurrent
|
|
391
|
+
redesign (shard the lock · per-key/per-shard locks · lock-free CAS · read-copy-update),
|
|
392
|
+
not a hack around it — and let the ≥100-connection parallelism counter be the acceptance
|
|
393
|
+
proof of the redesign (Proof 3 optimisation loop, step 3).
|
|
394
|
+
|
|
395
|
+
### Proof 3 — Performance & complexity budget (and how I optimise to meet it)
|
|
396
|
+
|
|
397
|
+
A changed hot path declares a latency budget **and** a proven complexity bound — and
|
|
398
|
+
when it misses, I optimise deterministically rather than hand-wave "no O(n²)".
|
|
399
|
+
|
|
400
|
+
- **Complexity by counters (deterministic).** Measure a **work counter** (allocations,
|
|
401
|
+
DB/Redis round-trips, comparisons, bytes scanned) at n, 2n, 4n, 8n and assert the
|
|
402
|
+
growth matches the claim: O(n) → ~doubles per doubling; O(1) → flat; O(n²) → ~4×.
|
|
403
|
+
Counters are noise-free, so the verdict is invariant across machines.
|
|
404
|
+
- **N+1 detector (the most common super-linear surprise):** assert round-trip count is
|
|
405
|
+
constant or linear in n, never multiplicative — count at n and 10n.
|
|
406
|
+
- **Latency budget (when wall-clock is required):** fixed input + warmup + **median-of-K**
|
|
407
|
+
+ p95/p99 threshold + regression threshold vs a **pinned baseline file** (fail at 1.5×
|
|
408
|
+
baseline). Single samples banned — they flake.
|
|
409
|
+
- **Conservation:** allocation count bounded; connection / file-descriptor (FD) open-count
|
|
410
|
+
before == after.
|
|
411
|
+
|
|
412
|
+
**The optimisation loop I run when a budget is missed** (deterministic, repeatable).
|
|
413
|
+
Default bias for the perf/concurrency class: **propose the structural refactor, not the
|
|
414
|
+
band-aid** — a patched O(n²) or a hacked-around global lock still ships a fragile design.
|
|
415
|
+
|
|
416
|
+
1. **Measure with counters first**, not a profiler guess — find the term that grows
|
|
417
|
+
super-linearly (the N+1, the O(n²) nested scan, the per-iteration allocation) or the
|
|
418
|
+
**serialization point** (one lock every request waits on).
|
|
419
|
+
2. **Classify the fix — local vs structural.** *Local* = a genuine one-liner: add an
|
|
420
|
+
index, hoist one allocation, collapse one N+1 into a join. *Structural* = the data
|
|
421
|
+
structure, locking model, or call graph is wrong: an O(n²) algorithm, a global
|
|
422
|
+
`Thread.Mutex` serialising all requests, per-request work that belongs in a shared
|
|
423
|
+
cache, a layer doing I/O in a loop.
|
|
424
|
+
3. **Structural ⇒ analyse and propose the large refactor — surface it, don't silently
|
|
425
|
+
band-aid.** Write: the **root cause**, the **target design** that is deterministic +
|
|
426
|
+
performant + concurrent (e.g. shard the lock / per-key or per-shard locks / lock-free
|
|
427
|
+
CAS instead of one mutex; one batched query instead of N+1 scattered across layers;
|
|
428
|
+
an index or precompute instead of repeated scans; streaming instead of full-buffer),
|
|
429
|
+
the **throwaway-patch alternative and why it's worse**, and the **blast radius**.
|
|
430
|
+
This follows the patch-vs-refactor discipline (`AGENTS.md` Decomposition) — the call
|
|
431
|
+
is surfaced to Indy, not made unilaterally. The test then pins the **target** bound,
|
|
432
|
+
so the refactor is provably met and can't regress.
|
|
433
|
+
4. **Local ⇒ apply**, then re-measure the same counter on the same n-ladder and assert
|
|
434
|
+
the growth ratio dropped to target. The before/after counter delta *is* the proof —
|
|
435
|
+
pin it in the baseline file so a future regression fails the gate.
|
|
436
|
+
5. If the budget still can't be met after the agreed change, that's a **design finding** —
|
|
437
|
+
surface it; never loosen the threshold silently.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Stack appendix
|
|
442
|
+
|
|
443
|
+
Tooling per stack:
|
|
444
|
+
|
|
445
|
+
| Stack | Runner | Branch coverage | Property | Fuzz | Concurrency |
|
|
446
|
+
|---|---|---|---|---|---|
|
|
447
|
+
| Python | `pytest` | `pytest-cov --branch` | `hypothesis` | `atheris` | `ThreadPoolExecutor`, `asyncio` |
|
|
448
|
+
| Python SDK | `pytest` + `responses`/`respx` | same | `hypothesis` | `schemathesis` | `asyncio.gather` |
|
|
449
|
+
| OpenAPI | `schemathesis` | n/a | n/a | `schemathesis run --checks all` | n/a |
|
|
450
|
+
| JS/TS CLI | `vitest` + `execa` | `c8` branch | `fast-check` | `fast-check` | `Promise.all` |
|
|
451
|
+
| React | `vitest` + `testing-library` | `c8` branch | `fast-check` | n/a | `userEvent` rapid |
|
|
452
|
+
| Zig | `zig build test` | manual | manual | manual mutation | `std.Thread.spawn` |
|
|
453
|
+
| Rust | `cargo test` | `cargo-llvm-cov` | `proptest` | `cargo-fuzz` | `loom`, `tokio::spawn` |
|
|
454
|
+
| Shell | `bats` | `bashcov` | n/a | n/a | `&` + `wait`, `flock` |
|
|
455
|
+
|
|
456
|
+
Naming convention by stack:
|
|
457
|
+
|
|
458
|
+
- Python: `test_should_<behaviour>_when_<condition>`
|
|
459
|
+
- Rust: `fn should_<behaviour>_when_<condition>()` in `mod tests`
|
|
460
|
+
- Zig: `test "should <behaviour> when <condition>"`
|
|
461
|
+
- React/TS: `it("should <behaviour> when <condition>")`
|
|
462
|
+
- Shell/bats: `@test "should <behaviour> when <condition>"`
|
|
463
|
+
|
|
464
|
+
Stack-specific must-knows:
|
|
465
|
+
|
|
466
|
+
- **Zig** — `std.testing.allocator` auto-detects leaks; verify cross-compile (`x86_64-linux`, `aarch64-linux`) since stdlib API may differ from macOS; `conn.query()` requires `.drain()`.
|
|
467
|
+
- **Python SDK** — assert API key not in debug output; HTTPS enforced; pagination doesn't load all pages in memory; rate-limit retry honours `Retry-After`.
|
|
468
|
+
- **Rust gRPC** — protobuf round-trip; streaming doesn't buffer full response; `tonic::Status` codes assert specific variant.
|
|
469
|
+
- **Rust HTTP** — `axum::extract::rejection` paths; payload too large (413); missing `Content-Type`.
|
|
470
|
+
- **React** — `axe` accessibility audit; no `dangerouslySetInnerHTML` with unsanitized input; controlled vs uncontrolled input switching.
|
|
471
|
+
- **Shell** — all variables quoted (`"$var"`); `set -euo pipefail`; no `eval` with user input; `mktemp` for temp files.
|
|
472
|
+
- **OpenAPI** — `schemathesis run --checks all`; `oasdiff breaking` in CI; `spectral lint` for style.
|
|
473
|
+
|
|
474
|
+
## OWASP for agent-bound data
|
|
475
|
+
|
|
476
|
+
Code paths sending data to LLMs / tool-calling agents (directly or via queue/DB/API):
|
|
477
|
+
|
|
478
|
+
- [ ] User-controlled input never concatenated raw into prompts; structured roles only
|
|
479
|
+
- [ ] Length-bounded, validated, sanitised against instruction-injection markers (`ignore previous`, role-confusion XML/JSON)
|
|
480
|
+
- [ ] Tool permissions scoped per-invocation, not globally granted
|
|
481
|
+
- [ ] Secrets/credentials/PII stripped before reaching the agent; opaque references only
|
|
482
|
+
- [ ] Agent output validated against allowlist/schema before any side effect; never `eval()` raw output
|
|
483
|
+
- [ ] Authorization re-checked at every hop; agent inherits caller's permissions, not service-level
|
|
484
|
+
- [ ] Audit log: who, input, tools called, output, side effects — tamper-resistant
|
|
485
|
+
- [ ] Fail-closed on any trust check failure — never silently fall back
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## Output format
|
|
490
|
+
|
|
491
|
+
For each test write:
|
|
492
|
+
|
|
493
|
+
1. Name (per stack convention)
|
|
494
|
+
2. Category (Behaviour / Failure / Invariant / Integration / Regression)
|
|
495
|
+
3. The bug it catches in one line
|
|
496
|
+
4. The test code
|
|
497
|
+
5. Failure-injection mechanism if applicable
|
|
498
|
+
|
|
499
|
+
After the suite produce:
|
|
500
|
+
|
|
501
|
+
```
|
|
502
|
+
## Coverage report: <module>
|
|
503
|
+
| Metric | Before | After | Target |
|
|
504
|
+
| Branch | 41% | 87% | ≥80% |
|
|
505
|
+
| Error-path | 60% | 100% | 100% |
|
|
506
|
+
| Negative-path ratio | 20% | 55% | ≥50% |
|
|
507
|
+
Categories: Behaviour ✅ · Failure ✅ · Invariant ✅ · Integration ✅ · Regression ✅
|
|
508
|
+
Production-safety: Zig-leak ✅ 0 (incl. checkAllAllocationFailures) · Concurrency ✅ 100-conn exactly-once + parallel · Perf ✅ O(n) counter + p95 vs baseline
|
|
509
|
+
Refactor proposals surfaced: <n> (or "none — no structural finding")
|
|
510
|
+
Partial completion: <rows complete>/<ordered failure points> · residual state + retry asserted per row
|
|
511
|
+
Diff ledger: 7/7 rows resolved (5 tested · 2 won't-test w/ reason)
|
|
512
|
+
Mutation (changed lines): 18/19 killed · 1 justified equivalent
|
|
513
|
+
Red-green (fix PRs): ✅ fails on parent, passes on HEAD
|
|
514
|
+
DoD checklist: 19/19
|
|
515
|
+
Mode: Change-set | Hardening | Deep audit
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## Validation gate
|
|
519
|
+
|
|
520
|
+
Single command runs all tests in the chosen mode. Report exact command, pass/fail/skip counts, coverage delta, and DoD checklist status. Do not declare done until DoD is 100%.
|
package/src/cli.test.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
const ROOT = resolve(import.meta.dir, "..");
|
|
7
|
+
const COMMAND = resolve(ROOT, "bin/orly");
|
|
8
|
+
|
|
9
|
+
describe("orly command", () => {
|
|
10
|
+
test("uses the concise public command surface", () => {
|
|
11
|
+
const result = Bun.spawnSync([COMMAND, "--help"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
|
|
12
|
+
const output = result.stdout.toString();
|
|
13
|
+
|
|
14
|
+
expect(result.exitCode).toBe(0);
|
|
15
|
+
expect(output).toContain("orly gate");
|
|
16
|
+
expect(output).toContain("orly override <CRITERION> --reason <REASON>");
|
|
17
|
+
expect(output).not.toContain("orly adopt");
|
|
18
|
+
expect(output).not.toContain("oracle-rules");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Adversarial review found recordOptIn running before the --dry-run branch,
|
|
22
|
+
// so a preview created .oracle/orly.json — and on an installed repository
|
|
23
|
+
// permanently added the pack. A dry run changes nothing, opt-in included.
|
|
24
|
+
test("--dry-run records no pack", () => {
|
|
25
|
+
const repo = mkdtempSync(join(tmpdir(), "orly-dry-"));
|
|
26
|
+
try {
|
|
27
|
+
Bun.spawnSync(["git", "init", "-q", repo], { stdout: "pipe", stderr: "pipe" });
|
|
28
|
+
writeFileSync(join(repo, "m.go"), "package main\n");
|
|
29
|
+
|
|
30
|
+
const result = Bun.spawnSync([COMMAND, "init", "--with", "persona.indy", "--dry-run"], { cwd: repo, stdout: "pipe", stderr: "pipe" });
|
|
31
|
+
|
|
32
|
+
expect(result.exitCode).toBe(0);
|
|
33
|
+
expect(result.stdout.toString()).toContain("Generated by");
|
|
34
|
+
expect(existsSync(join(repo, ".oracle/orly.json"))).toBe(false);
|
|
35
|
+
expect(existsSync(join(repo, "AGENTS.orly.md"))).toBe(false);
|
|
36
|
+
} finally {
|
|
37
|
+
rmSync(repo, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("reports the version carried by the package manifest", async () => {
|
|
42
|
+
const result = Bun.spawnSync([COMMAND, "--version"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
|
|
43
|
+
const manifest = (await Bun.file(resolve(ROOT, "package.json")).json()) as { version: string };
|
|
44
|
+
|
|
45
|
+
expect(result.exitCode).toBe(0);
|
|
46
|
+
expect(result.stdout.toString().trim()).toBe(manifest.version);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// `validate` is gone: verifyRenders calls model.validate() before its own
|
|
50
|
+
// checks, so the verb was a strict subset of `verify` with a second name.
|
|
51
|
+
test("verify validates the registry on its way to the render checks", () => {
|
|
52
|
+
const result = Bun.spawnSync([COMMAND, "verify"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
|
|
53
|
+
|
|
54
|
+
expect(result.exitCode).toBe(0);
|
|
55
|
+
expect(result.stdout.toString()).toContain("render.local.idempotent");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("validate is not a verb", () => {
|
|
59
|
+
const result = Bun.spawnSync([COMMAND, "validate"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
|
|
60
|
+
|
|
61
|
+
expect(result.exitCode).toBe(1);
|
|
62
|
+
expect(result.stderr.toString()).toContain("unknown command: validate");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("verify needs no --all, because there was never a not-all", () => {
|
|
66
|
+
const result = Bun.spawnSync([COMMAND, "verify"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
|
|
67
|
+
|
|
68
|
+
expect(result.exitCode).toBe(0);
|
|
69
|
+
});
|
|
70
|
+
});
|