@agentsfleet/orly 0.7.1 → 0.9.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/AGENTS.md +16 -11
- package/README.md +8 -1
- package/SOUL.md +4 -1
- package/audits/agents-md.md +3 -0
- package/audits/rust-error.sh +228 -0
- package/core/operating-model.md +13 -11
- package/dispatch/lib.sh +28 -3
- package/dispatch/lifecycle.md +27 -0
- package/dispatch/verify.md +45 -11
- package/dispatch/write_go.md +8 -0
- package/dispatch/write_rust.md +166 -0
- package/dispatch/write_rust.sh +38 -0
- package/docs/EXECUTE_DOC_READS.md +1 -1
- package/docs/RULE_ENFORCEMENT.md +2 -2
- package/docs/VERIFY_TIERS.md +35 -11
- package/docs/greptile-learnings/RULES.md +1 -0
- package/package.json +1 -1
- package/packs/language/rust/rules.md +9 -0
- package/registry.json +16 -0
- package/skills/orly-babysit-prs/SKILL.md +4 -8
- package/skills/orly-spec-new/SKILL.md +4 -6
- package/skills/orly-write-integration-test/SKILL.md +4 -6
- package/skills/orly-write-unit-test/SKILL.md +4 -6
- package/src/cli.ts +11 -1
- package/src/criteria.test.ts +17 -6
- package/src/criteria.ts +34 -9
- package/src/gates.test.ts +33 -9
- package/src/gates_spec.test.ts +1 -1
- package/src/install.ts +18 -72
- package/src/install_packs.test.ts +56 -0
- package/src/loaders.test.ts +148 -0
- package/src/loaders.ts +240 -0
- package/src/render.test.ts +59 -1
- package/src/verify.test.ts +72 -2
- package/src/verify.ts +44 -3
package/dispatch/write_go.md
CHANGED
|
@@ -22,5 +22,13 @@ a working value.
|
|
|
22
22
|
Table-driven tests are the default shape. Concurrency work needs a `-race` run,
|
|
23
23
|
and a deterministic contention test rather than a sleep.
|
|
24
24
|
|
|
25
|
+
A string literal repeated in one file becomes a named constant — `audits/ufs.sh`
|
|
26
|
+
reads `*.go` and enforces it. Two Go shapes are held out, because no rename can
|
|
27
|
+
fix them: members of a `const ( … )` group, which state the binding keyword once
|
|
28
|
+
on the opening line rather than per member, and backtick struct tags, whose
|
|
29
|
+
quoted halves (`json:"id"`) are addressed by reflection and cannot hold a const.
|
|
30
|
+
Both are carve-outs for what the *language* makes unfixable, not permission —
|
|
31
|
+
a flag name or a label spelled inline twice is still the rule.
|
|
32
|
+
|
|
25
33
|
The repository owns formatting, vet, lint, build, and test commands — it
|
|
26
34
|
declares them in `.oracle/orly.json`.
|
package/dispatch/write_rust.md
CHANGED
|
@@ -33,6 +33,172 @@ Local convention wins on conflict, but the divergence is named in the review
|
|
|
33
33
|
output. Refresh the copy when the user asks or a cited rule looks stale
|
|
34
34
|
(re-run the fetch; upstream updates continuously).
|
|
35
35
|
|
|
36
|
+
## Error discipline (RULE ERR-RS)
|
|
37
|
+
|
|
38
|
+
> [DETERMINISTIC → ERR-RS]
|
|
39
|
+
|
|
40
|
+
Two of the clauses below are decided by `audits/rust-error.sh`, which
|
|
41
|
+
`dispatch/write_rust.sh` runs over staged Rust: an error type with no `Result`
|
|
42
|
+
alias in its crate, and a `map_err` that stringifies its own cause. The other
|
|
43
|
+
two — whether a `source()` implementation returns its own kind, and whether a
|
|
44
|
+
context addition is warranted — need the call site read beside the diff and stay
|
|
45
|
+
the agent's call at write time.
|
|
46
|
+
|
|
47
|
+
One error type per crate, and a `Result` alias beside it:
|
|
48
|
+
|
|
49
|
+
pub type Result<T, E = Error> = core::result::Result<T, E>;
|
|
50
|
+
|
|
51
|
+
Every fallible function in the crate returns `Result<T>`. The default type
|
|
52
|
+
parameter is what lets the few functions answering with a foreign error keep
|
|
53
|
+
the same spelling — `Result<T, VerifyError>` — instead of reaching for
|
|
54
|
+
`std::result::Result`. A reader must never have to check WHICH error a
|
|
55
|
+
signature returns to know it is this crate's.
|
|
56
|
+
|
|
57
|
+
**Compose with `From`; `?` does the lifting.** This is `M-FROM-ERROR` and is
|
|
58
|
+
read there, carve-out included; what the guideline leaves open is how the
|
|
59
|
+
variant is spelled, which locally is `#[from]`:
|
|
60
|
+
|
|
61
|
+
#[derive(Debug, thiserror::Error)]
|
|
62
|
+
pub enum BootFailure {
|
|
63
|
+
#[error(transparent)]
|
|
64
|
+
Environment(#[from] Refusal),
|
|
65
|
+
#[error("cannot boot: the database would not answer")]
|
|
66
|
+
Database(#[from] db::Error),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
**`map_err` only to ADD context the call site alone knows** — a role tag, a
|
|
70
|
+
budget, an operation name; the same carve-out `M-FROM-ERROR` names. And never
|
|
71
|
+
this, which no guideline covers:
|
|
72
|
+
|
|
73
|
+
// WRONG: a lossy conversion wearing a conversion's clothes.
|
|
74
|
+
.map_err(|error| Mine::Database(error.to_string()))?
|
|
75
|
+
|
|
76
|
+
`to_string()` on the way INTO an error type destroys the `source()` chain. It
|
|
77
|
+
compiles, it reads fine, and it silently defeats every chain walker downstream.
|
|
78
|
+
|
|
79
|
+
**`source()` returns what caused you, never yourself.** An error whose
|
|
80
|
+
`Display` already renders its kind must not also return that kind as its
|
|
81
|
+
source, or a chain walker prints the same sentence twice before reaching
|
|
82
|
+
anything new:
|
|
83
|
+
|
|
84
|
+
// WRONG — Display is "[code] {kind}", so the kind is already printed.
|
|
85
|
+
fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&self.kind) }
|
|
86
|
+
|
|
87
|
+
// RIGHT — skip ourselves, hand back what the kind wraps.
|
|
88
|
+
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
89
|
+
std::error::Error::source(&self.kind)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
**Not every error has a cause, and that is not a defect.** A variant holding
|
|
93
|
+
another error has one; a variant holding only data (`MissingUrl { knob }`) does
|
|
94
|
+
not — nothing *caused* an unset variable. A test asserting `source().is_some()`
|
|
95
|
+
for every variant is WRONG and forces authors to invent causes. Assert the real
|
|
96
|
+
invariant: where there IS a source, it is not a repeat of our own message.
|
|
97
|
+
|
|
98
|
+
**Divergence from the reference guideline, named as that section requires.**
|
|
99
|
+
`M-ERRORS-CANONICAL-STRUCTS` prescribes a situation-specific `struct` carrying
|
|
100
|
+
a `Backtrace` and lets a complex crate expose several error types;
|
|
101
|
+
`M-APP-ERROR` further lets an application crate skip its own type for
|
|
102
|
+
`anyhow`/`eyre`/`ohno::AppError`. This rule diverges on all three for one
|
|
103
|
+
reason: a `#[from]`-composed enum behind a single per-crate alias is what lets
|
|
104
|
+
`?` lift across a crate boundary with no call-site conversion, and an
|
|
105
|
+
application-level catch-all erases the variant the caller would have matched
|
|
106
|
+
on. A `Backtrace` inside a variant is welcome. A crate with two genuinely
|
|
107
|
+
unrelated fallible surfaces may carry a second type — with its own alias, under
|
|
108
|
+
these same rules. Cite this divergence in review rather than re-deriving it.
|
|
109
|
+
|
|
110
|
+
Prior art, not invention: `core_api` (multi-crate workspace, per-crate
|
|
111
|
+
`error.rs` with `pub type Result<T>` + flat `Error`, in production for years),
|
|
112
|
+
bun (`thiserror`, `#[from]`, `pub type Result<T, E = Error>`), habitat (one
|
|
113
|
+
payload-carrying `enum Error`, one alias).
|
|
114
|
+
|
|
115
|
+
## Functional design (RULE FN-RS)
|
|
116
|
+
|
|
117
|
+
Design the fallible path as a Result PIPELINE, never as a control-flow tree.
|
|
118
|
+
|
|
119
|
+
**Fallibility lives in the type, never in a sentinel.** A function that can
|
|
120
|
+
fail returns `Result<T>`; a value that can be absent returns `Option<T>`.
|
|
121
|
+
Never a bool-plus-out-parameter, never a magic value (-1, empty string, null
|
|
122
|
+
UUID), never a logged-and-swallowed error. If the caller cannot see the
|
|
123
|
+
failure in the signature, the design is wrong before the body is written.
|
|
124
|
+
|
|
125
|
+
**Compose with combinators; spend `match` only where arms genuinely differ.**
|
|
126
|
+
`?`, `map`, `and_then`, `ok_or_else`, `unwrap_or_else` say "transform on
|
|
127
|
+
success, pass failure through" in one line, where a nested match spends five
|
|
128
|
+
and gives every arm a chance to swallow the `Err`:
|
|
129
|
+
|
|
130
|
+
// WRONG: the failure path is hand-plumbed, twice.
|
|
131
|
+
let config = match load(path) {
|
|
132
|
+
Ok(raw) => match parse(&raw) {
|
|
133
|
+
Ok(c) => c,
|
|
134
|
+
Err(e) => return Err(e.into()),
|
|
135
|
+
},
|
|
136
|
+
Err(e) => return Err(e.into()),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// RIGHT: one pipeline; `?` and `From` (RULE ERR-RS) do the lifting.
|
|
140
|
+
let config = parse(&load(path)?)?;
|
|
141
|
+
|
|
142
|
+
A `match` earns its place when the arms carry different LOGIC — not when one
|
|
143
|
+
arm is a hand-written `?`.
|
|
144
|
+
|
|
145
|
+
**Make illegal states unrepresentable; parse, don't validate.** Convert raw
|
|
146
|
+
input into a type that can only hold valid states ONCE, at the boundary, and
|
|
147
|
+
pass that type inward. Two `Option` fields where exactly one is ever set is an
|
|
148
|
+
enum. A `state: String` compared against literals is an enum. A checked-then-
|
|
149
|
+
used raw value is a newtype whose constructor returns `Result` — that
|
|
150
|
+
constructor is `M-STRONG-TYPES-GUARD`, and picking the type family is
|
|
151
|
+
`M-STRONG-TYPES`. What those do not say is the consequence: interior code that
|
|
152
|
+
trusts its types needs no defensive re-checks — delete them.
|
|
153
|
+
|
|
154
|
+
**Expression-orientation: bind once.** `let x = if ... { a } else { b };` and
|
|
155
|
+
`let x = match ...` — never a mutable default that later branches overwrite.
|
|
156
|
+
A `mut` that exists only to simulate an expression is a bug surface with no
|
|
157
|
+
compensating power.
|
|
158
|
+
|
|
159
|
+
**`unwrap()` / `expect()` are for tests and for invariants already proved.**
|
|
160
|
+
`M-PANIC-ON-BUG` settles when a panic is right and `M-PANIC-MESSAGE` that it
|
|
161
|
+
must be helpful; the addition here is WHAT the message carries — the proof
|
|
162
|
+
("mutex never poisoned: no thread panics while holding it"). In production
|
|
163
|
+
code, an `unwrap()` without a written invariant is a `Result` that was never
|
|
164
|
+
designed.
|
|
165
|
+
|
|
166
|
+
Prior art, not invention: `core_api` — a multi-crate production workspace
|
|
167
|
+
where every crate with a fallible surface carries its own `error.rs` +
|
|
168
|
+
`Result` alias, and the fallible path is written as pipelines throughout
|
|
169
|
+
(573 `.map`, 463 `.and_then`, 164 `.ok_or_else` across the workspace). It has
|
|
170
|
+
run in production for years; its author attributes the absence of error-path
|
|
171
|
+
defects to exactly this design, and the maintenance record bears that out.
|
|
172
|
+
|
|
173
|
+
## Constant discipline (RULE UFS)
|
|
174
|
+
|
|
175
|
+
`audits/ufs.sh` reads `*.rs`. It did not until this pack said so, and the cost
|
|
176
|
+
was concrete: a crate spelled a wire verb inline twice — `pong == "PONG" ||
|
|
177
|
+
pong.contains("PONG")` — while `write_any.sh` printed a green UFS row over the
|
|
178
|
+
file, because the façade had always declared `*.rs` and the leaf had never read
|
|
179
|
+
it. Bind the repeated literal to a `const` or `static` and use the name.
|
|
180
|
+
|
|
181
|
+
Four Rust shapes are held out of the count, because no rename can fix them:
|
|
182
|
+
|
|
183
|
+
- **`#[cfg(test)]` and `#[test]` blocks.** Rust keeps unit tests inside the file
|
|
184
|
+
they cover, so fixture keys would otherwise read as that file's worst debt.
|
|
185
|
+
- **Feature-gated test seams** — `#[cfg(feature = "test-util")]`. A helper that
|
|
186
|
+
hands a sibling integration test one of each error kind has to compile into
|
|
187
|
+
the crate, which is the only reason it is not behind `cfg(test)`; its contents
|
|
188
|
+
are fixture data all the same. The match is narrow on purpose: `test` as a
|
|
189
|
+
whole word or a `-`/`_` segment, so `#[cfg(feature = "redis")]` still counts.
|
|
190
|
+
- **Attribute literals** — `#[serde(rename = "…")]`, `#[cfg(feature = "…")]`,
|
|
191
|
+
`#[doc = "…"]`. An attribute takes a literal token by language rule; a const
|
|
192
|
+
is not accepted in that position.
|
|
193
|
+
- **`tests/` and `benches/`** at the crate root, which are fixture trees.
|
|
194
|
+
|
|
195
|
+
Everything else counts, including `examples/` — not because it compiles, but
|
|
196
|
+
because what repeats there is coupling, not demonstration. A cache namespace
|
|
197
|
+
declared at the top of a walkthrough and matched ninety lines below it, or a
|
|
198
|
+
fixture id that is both the fake store's match arm and the key five call sites
|
|
199
|
+
pass, breaks silently when one end moves. That is the drift RULE UFS exists to
|
|
200
|
+
catch, and example code is where a reader copies it from.
|
|
201
|
+
|
|
36
202
|
## Evolution
|
|
37
203
|
|
|
38
204
|
This pack is young next to the Zig façade and grows the same way: a Rust
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dispatch/write_rust.sh — the Rust dispatch, DETERMINISTIC façade.
|
|
3
|
+
#
|
|
4
|
+
# Pairs with dispatch/write_rust.md (the LATENT façade — prose the agent reads).
|
|
5
|
+
# This .sh runs the mechanically-checkable subset of the Rust discipline and
|
|
6
|
+
# emits ONE verdict block. The latent trigger ("about to write a *.rs file")
|
|
7
|
+
# dispatches here; everything below is deterministic and re-runnable.
|
|
8
|
+
#
|
|
9
|
+
# dispatch/write_rust.sh <src/file.rs> [...] # explicit targets (EXECUTE)
|
|
10
|
+
# dispatch/write_rust.sh --staged # staged *.rs (CONFORM, pre-commit)
|
|
11
|
+
#
|
|
12
|
+
# Layering: AGENTS.md → write_rust.md (latent) → write_rust.sh (this) → audits/*.sh
|
|
13
|
+
# Signals: 🟢 pass · 🔴 fail (blocks) · 🤔 DECIDE (judgment; blocks the TURN, not the script) · 🟣 delegated
|
|
14
|
+
# Exit: 0 = mechanical gates pass · 1 = ≥1 failed · 2 = usage error.
|
|
15
|
+
#
|
|
16
|
+
# Note: Rust had no deterministic façade at all until this file, which is why
|
|
17
|
+
# the error standard the operating model calls mandatory was enforced by review
|
|
18
|
+
# alone. ERR-RS carries the two clauses a machine can decide from the text — a
|
|
19
|
+
# map_err that stringifies its own cause, and an error type with no Result alias
|
|
20
|
+
# beside it. The rest of write_rust.md stays judgment on purpose: ownership,
|
|
21
|
+
# justified `unsafe`, feature combinations, and whether a source() implementation
|
|
22
|
+
# returns its own kind all need a call site read beside the diff, and a gate that
|
|
23
|
+
# guesses at those is a gate people learn to ignore.
|
|
24
|
+
#
|
|
25
|
+
# UFS over *.rs is NOT re-run here. It is wired once in its home façade
|
|
26
|
+
# (write_any), and the universal-code carve-out makes that row satisfy the tag
|
|
27
|
+
# wherever it appears — running it twice would report one violation as two.
|
|
28
|
+
|
|
29
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
30
|
+
|
|
31
|
+
dispatch_init "RUST" '*.rs'
|
|
32
|
+
dispatch_resolve_files "$@"
|
|
33
|
+
dispatch_header
|
|
34
|
+
|
|
35
|
+
# ── deterministic gates ────────────────────────────────────────────
|
|
36
|
+
dispatch_run_helper "ERR-RS" rust-error.sh --staged
|
|
37
|
+
|
|
38
|
+
dispatch_verdict
|
|
@@ -15,7 +15,7 @@ Every triggered document requires one `📖 DOC READ: <path>` proof-line **per t
|
|
|
15
15
|
| `*.zig` | `dispatch/write_zig.md`. ZIG GATE per edit. |
|
|
16
16
|
| `*.ts`/`*.tsx`/`*.js`/`*.jsx` | `dispatch/write_ts_adhere_bun.md` — TS FILE SHAPE DECISION (§1) at PLAN, const/import/Bun-primitive discipline, anti-patterns. |
|
|
17
17
|
| `*.rs` | `dispatch/write_rust.md` — ownership, error variants, feature combinations, and contention tests. | <!-- oracle-packs:language.rust -->
|
|
18
|
-
| Rust error type, `Result` alias, or any fallible signature | `dispatch/write_rust.md` §Error discipline — one error per crate, one `Result` alias, `#[from]` composition, `source()` never returns self. | <!-- oracle-packs:language.rust -->
|
|
18
|
+
| Rust error type, `Result` alias, or any fallible signature | `dispatch/write_rust.md` §Error discipline — one error per crate, one `Result` alias, `#[from]` composition, `source()` never returns self. ERR-RS fires per edit: `audits/rust-error.sh` (via `dispatch/write_rust.sh`) blocks a `map_err` that stringifies its own cause and an error type with no alias in its crate; the remaining clauses stay judgment. | <!-- oracle-packs:language.rust -->
|
|
19
19
|
| Designing a Rust function, type, or module; writing `match` on a `Result`/`Option`; any production `unwrap()` | `dispatch/write_rust.md` §Functional design — Result pipelines over control-flow trees, illegal states unrepresentable, bind once, `unwrap` needs a proved invariant. | <!-- oracle-packs:language.rust -->
|
|
20
20
|
| `*.py` | `dispatch/write_python.md` — parsing boundaries, resource ownership, and specific exceptions. |
|
|
21
21
|
| `*.sh` | `dispatch/write_shell.md` — quoting, array arguments, cleanup, input safety, and shell compatibility. |
|
package/docs/RULE_ENFORCEMENT.md
CHANGED
|
@@ -12,9 +12,9 @@ regenerates this file and fails on any difference. To update it, run
|
|
|
12
12
|
| `docs/DOCUMENTATION_RULES.md` | — | 0 | 0 | 0/22 | latent |
|
|
13
13
|
| `docs/LIFECYCLE_PATTERNS.md` | — | 0 | 0 | 0/22 | mechanical |
|
|
14
14
|
| `docs/CHANGELOG_VOICE.md` | — | 0 | 0 | 0/3 | latent |
|
|
15
|
-
| `docs/VERIFY_TIERS.md` | — | 0 | 0 | 0/
|
|
15
|
+
| `docs/VERIFY_TIERS.md` | — | 0 | 0 | 0/7 | uncited |
|
|
16
16
|
| `docs/greptile-learnings/RULES.md` | — | 0 | 0 | 0/114 | mechanical |
|
|
17
|
-
| **corpus** | | | | **30/
|
|
17
|
+
| **corpus** | | | | **30/288** | |
|
|
18
18
|
|
|
19
19
|
## How to read this
|
|
20
20
|
|
package/docs/VERIFY_TIERS.md
CHANGED
|
@@ -8,16 +8,33 @@ page carries only what is specific to this repository.
|
|
|
8
8
|
|
|
9
9
|
| Tier | Command | When |
|
|
10
10
|
|---|---|---|
|
|
11
|
-
| conform | `make harness-verify` |
|
|
12
|
-
| lint | `make lint-all` |
|
|
13
|
-
| unit | `make test-unit-all` |
|
|
14
|
-
| version | `make check-version` |
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
| conform | `make harness-verify` | Section lane and boundary, after EXECUTE and before the rest. Any 🔴 returns to EXECUTE. |
|
|
12
|
+
| lint | `make lint-all` | Boundary. Rust rides `lint-rustd` (rustfmt + Clippy, warnings are errors); script self-tests ride `lint-scripts`. |
|
|
13
|
+
| unit | `make test-unit-all` | Boundary. The cargo workspace plus every TypeScript package coverage gate. |
|
|
14
|
+
| version | `make check-version` | Boundary. `VERSION` against `build.zig.zon`, `cli/package.json` and both `rustd/Cargo.toml` sites. |
|
|
15
|
+
| integration | `make test-integration-rustd` | Boundary. Live Postgres and Redis via docker compose, schemas reset per run. `orly gate pr` skips it on a branch carrying no code. |
|
|
16
|
+
|
|
17
|
+
These five are exactly what `.oracle/orly.json` declares and exactly what
|
|
18
|
+
`orly gate pr` runs, so the rubric and the mechanical gate grade one boundary.
|
|
19
|
+
|
|
20
|
+
**Two cadences.** A Section closing is not a milestone closing. Inside a
|
|
21
|
+
Section, run `make harness-verify` plus the lane covering the surface the
|
|
22
|
+
Section touched (`make test-unit-rustd`, `make test-unit-app`, …) — enough to
|
|
23
|
+
prove the Section, never a repository claim. The full table above runs ONCE, at
|
|
24
|
+
CHORE(close).
|
|
25
|
+
|
|
26
|
+
**Which gate runs which row.** `orly gate work` runs the `conform` row alone and
|
|
27
|
+
is what the pre-commit hook fires, so it costs seconds. `orly gate verify` runs
|
|
28
|
+
the lint, unit, and version rows and is what pre-push fires. `orly gate pr` runs
|
|
29
|
+
the integration row plus the whole-branch and spec criteria, and is what
|
|
30
|
+
CHORE(close) fires — it skips the fast rows because `git.pushed` proves HEAD is
|
|
31
|
+
the commit pre-push already graded.
|
|
32
|
+
|
|
33
|
+
One lane needs live datastores, and only one. `make/test-infra.mk` brings up
|
|
34
|
+
docker compose Postgres and Redis for `test-integration-rustd`; `make
|
|
35
|
+
test-unit-all` stays datastore-free, because every Rust test needing one is
|
|
36
|
+
`#[ignore]`d and runs only in that lane. `KEEP_TEST_STATE=1` skips the reset for
|
|
37
|
+
the inner loop; Continuous Integration (CI) never sets it.
|
|
21
38
|
|
|
22
39
|
## Test Baseline
|
|
23
40
|
|
|
@@ -61,7 +78,14 @@ Paste the deciding line, not the exit code. A gate that reports a number — a s
|
|
|
61
78
|
against a cap, a count against a budget — is owed that number in the done message.
|
|
62
79
|
|
|
63
80
|
```
|
|
64
|
-
✅ Verified: 🧪 lint-all ✓ · 🧪 test-unit-all ✓ <N>p/<M>s · 🔆 harness-verify ✓ · 🔆 check-version ✓
|
|
81
|
+
✅ Verified: 🧪 lint-all ✓ · 🧪 test-unit-all ✓ <N>p/<M>s · 🧩 test-integration-rustd ✓ · 🔆 harness-verify ✓ · 🔆 check-version ✓
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A Section lane reports what it ran and says so, never borrowing the repository's
|
|
85
|
+
verdict:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
✅ Section verified: 🔆 harness-verify ✓ · 🧪 test-unit-<surface> ✓ <N>p/<M>s
|
|
65
89
|
```
|
|
66
90
|
|
|
67
91
|
A skipped target is surfaced as 🟠 with its reason, never dressed as a pass.
|
|
@@ -51,6 +51,7 @@ every code a dispatch `.sh` emits resolves to exactly one row here.
|
|
|
51
51
|
| LOG | LOGging discipline (scoped event, error_code, severity, redaction) |
|
|
52
52
|
| MSID | Milestone-ID ban in source (M{N}_{NNN} / §x.y / T{N} / dim) |
|
|
53
53
|
| ERR | ERror Registry (UZ-XXX-NNN declared + referenced) |
|
|
54
|
+
| ERR-RS | Rust ERror discipline (one type per crate, Result alias, no stringified cause) |
|
|
54
55
|
| GRP | GREptile rule audit (diff vs greptile-learnings/RULES.md codes) |
|
|
55
56
|
| LDC | Legacy-Design Consult (A remove / B patch / C keep) |
|
|
56
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentsfleet/orly",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary \u2014 for any coding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,6 +35,15 @@ output. Refresh the copy when the user asks or a cited rule looks stale
|
|
|
35
35
|
|
|
36
36
|
## Error discipline (RULE ERR-RS)
|
|
37
37
|
|
|
38
|
+
> [DETERMINISTIC → ERR-RS]
|
|
39
|
+
|
|
40
|
+
Two of the clauses below are decided by `audits/rust-error.sh`, which
|
|
41
|
+
`dispatch/write_rust.sh` runs over staged Rust: an error type with no `Result`
|
|
42
|
+
alias in its crate, and a `map_err` that stringifies its own cause. The other
|
|
43
|
+
two — whether a `source()` implementation returns its own kind, and whether a
|
|
44
|
+
context addition is warranted — need the call site read beside the diff and stay
|
|
45
|
+
the agent's call at write time.
|
|
46
|
+
|
|
38
47
|
One error type per crate, and a `Result` alias beside it:
|
|
39
48
|
|
|
40
49
|
pub type Result<T, E = Error> = core::result::Result<T, E>;
|
package/registry.json
CHANGED
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
"source": "dispatch/write_pr_description.md",
|
|
37
37
|
"target": "dispatch/write_pr_description.md"
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
"source": "audits/doc-read.sh",
|
|
41
|
+
"target": "audits/doc-read.sh"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"source": "audits/rule-ledger-lib.sh",
|
|
45
|
+
"target": "audits/rule-ledger-lib.sh"
|
|
46
|
+
},
|
|
39
47
|
{
|
|
40
48
|
"source": "audits/ufs.sh",
|
|
41
49
|
"target": "audits/ufs.sh"
|
|
@@ -118,6 +126,14 @@
|
|
|
118
126
|
".rs"
|
|
119
127
|
],
|
|
120
128
|
"managed_files": [
|
|
129
|
+
{
|
|
130
|
+
"source": "dispatch/write_rust.sh",
|
|
131
|
+
"target": "dispatch/write_rust.sh"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"source": "audits/rust-error.sh",
|
|
135
|
+
"target": "audits/rust-error.sh"
|
|
136
|
+
},
|
|
121
137
|
{
|
|
122
138
|
"source": "packs/language/rust/rules.md",
|
|
123
139
|
"target": "dispatch/write_rust.md"
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orly-babysit-prs
|
|
3
3
|
description: |
|
|
4
|
-
Post-push follow-up for
|
|
5
|
-
(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Forge-aware (gh PR / glab MR). Use after `gh pr create` / `glab mr create`,
|
|
9
|
-
after every push to a branch with an open PR/MR, or on "babysit", "watch
|
|
10
|
-
greptile", "poll the PR/MR", "watch reviews", "follow up on review
|
|
11
|
-
feedback". Cross-agent: Claude, Codex, OpenCode, Amp.
|
|
4
|
+
Post-push follow-up for a Pull Request or Merge Request: poll Continuous
|
|
5
|
+
Integration (CI) checks, greptile threads, and the summary; fix what your diff
|
|
6
|
+
broke; stop after two empty polls with CI green. Forge-aware (gh, glab). Use
|
|
7
|
+
after creating or pushing to a PR/MR, or on "babysit", "watch reviews".
|
|
12
8
|
---
|
|
13
9
|
|
|
14
10
|
# orly-babysit-prs
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orly-spec-new
|
|
3
3
|
description: |
|
|
4
|
-
Author a milestone
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
(forbidden — every non-trivial intent becomes a spec). Cross-agent,
|
|
9
|
-
self-contained markdown.
|
|
4
|
+
Author a milestone or workstream spec that ships as a deterministic,
|
|
5
|
+
review-clean Pull Request — intent capture first, file mechanics last. Use on
|
|
6
|
+
"create a spec", "new milestone", "spec out X", "start M{N}_{NNN}", or any
|
|
7
|
+
TODO.md attempt (forbidden — every non-trivial intent becomes a spec).
|
|
10
8
|
---
|
|
11
9
|
|
|
12
10
|
# orly-spec-new
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orly-write-integration-test
|
|
3
3
|
description: >
|
|
4
|
-
Service-layer integration tests against real dependencies
|
|
5
|
-
full HTTP router
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
any real-I/O module-boundary crossing. Not for browser E2E (gstack /qa) or
|
|
9
|
-
pure logic (orly-write-unit-test).
|
|
4
|
+
Service-layer integration tests against real dependencies — Postgres, Redis,
|
|
5
|
+
full HTTP router and middleware — with failure injection, per-test isolation,
|
|
6
|
+
and leak audits. Use for handlers, repositories, services, or any real
|
|
7
|
+
input/output module boundary; not for browser end-to-end or pure logic.
|
|
10
8
|
---
|
|
11
9
|
|
|
12
10
|
# Write Integration Test
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orly-write-unit-test
|
|
3
3
|
description: >
|
|
4
|
-
Risk-weighted, failure-injecting
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
zero-leak, 100-or-more-connection concurrency, complexity/latency budgets) live
|
|
9
|
-
in the skill body. Use during implementation, not after.
|
|
4
|
+
Risk-weighted, failure-injecting unit tests across Python, TypeScript, React,
|
|
5
|
+
Zig, Rust, and shell: behaviour, failure, invariant, and regression coverage
|
|
6
|
+
behind a Definition-of-Done gate. Production-safety proofs live in the body.
|
|
7
|
+
Use during implementation, not after.
|
|
10
8
|
---
|
|
11
9
|
|
|
12
10
|
# Write Unit Test
|
package/src/cli.ts
CHANGED
|
@@ -293,6 +293,12 @@ function printHelp(): void {
|
|
|
293
293
|
Gates (read-only; no PR without every criterion green or a recorded override):
|
|
294
294
|
orly gate [--accept-dirty] run work → verify → pr; stop at first red
|
|
295
295
|
orly gate <work|verify|pr> run one gate
|
|
296
|
+
work does this commit conform? the declared conform command; no git
|
|
297
|
+
state, so a commit hook's own dirty tree never blocks it
|
|
298
|
+
verify does the work hold up? spec dimensions, docs language, and the
|
|
299
|
+
fast verify.* set
|
|
300
|
+
pr can this ship? branch, tree, pushed, every spec criterion, and
|
|
301
|
+
the slow verify.* suites
|
|
296
302
|
orly override <CRITERION> --reason <REASON>
|
|
297
303
|
empty commit with an Orly-Override trailer
|
|
298
304
|
|
|
@@ -302,7 +308,11 @@ Install (the repository is the unit — no checkout of this package required):
|
|
|
302
308
|
seeded .oracle/orly.json. A repository that
|
|
303
309
|
already has an AGENTS.md keeps it: orly's
|
|
304
310
|
rules land as AGENTS.orly.md, reached by a
|
|
305
|
-
pointer block in the file you own.
|
|
311
|
+
pointer block in the file you own. One
|
|
312
|
+
import line lands per runtime that needs
|
|
313
|
+
one (CLAUDE.md, opencode.json), so the
|
|
314
|
+
rules load rather than wait to be noticed;
|
|
315
|
+
a loader you wrote yourself is left alone.
|
|
306
316
|
orly update [--force] [--with <PACK>] [--dry-run] [--json]
|
|
307
317
|
re-materialise at the installed engine version
|
|
308
318
|
|
package/src/criteria.test.ts
CHANGED
|
@@ -24,9 +24,12 @@ describe("criteriaFor", () => {
|
|
|
24
24
|
return criteriaFor(gate, context).map((criterion) => criterion.name).sort();
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
// work is the commit-time gate, and a commit hook's tree is dirty by
|
|
28
|
+
// construction. It asks the repository's own conform command and nothing
|
|
29
|
+
// about git state.
|
|
30
|
+
test("work evaluates the profile and the conform tier", async () => {
|
|
28
31
|
const project = newSpecRepository();
|
|
29
|
-
expect(named(WORK, await contextFor(project))).toEqual(["
|
|
32
|
+
expect(named(WORK, await contextFor(project))).toEqual(["cmd.conform", REPO_CONFIG]);
|
|
30
33
|
});
|
|
31
34
|
|
|
32
35
|
test("verify pairs the spec dimensions with the fast command tier only", async () => {
|
|
@@ -34,7 +37,9 @@ describe("criteriaFor", () => {
|
|
|
34
37
|
const names = named(VERIFY, await contextFor(project));
|
|
35
38
|
|
|
36
39
|
expect(names).toContain("spec.dimensions");
|
|
37
|
-
expect(names).toContain("cmd.
|
|
40
|
+
expect(names).toContain("cmd.verify.unit");
|
|
41
|
+
// conform is the work gate's tier now, so the chain runs it once.
|
|
42
|
+
expect(names).not.toContain("cmd.conform");
|
|
38
43
|
// The slow tier belongs to the pr gate; leaking it here would make every
|
|
39
44
|
// verify run pay for integration and memory suites.
|
|
40
45
|
expect(names).not.toContain("cmd.verify.integration");
|
|
@@ -47,9 +52,15 @@ describe("criteriaFor", () => {
|
|
|
47
52
|
for (const required of ["spec.moved", "spec.baseline", "spec.ordering", "spec.deferrals", "docs.updated"]) {
|
|
48
53
|
expect(names).toContain(required);
|
|
49
54
|
}
|
|
50
|
-
// git.branch
|
|
51
|
-
//
|
|
52
|
-
|
|
55
|
+
// git.branch belongs here and only here. It was a work-gate criterion until
|
|
56
|
+
// the generated pre-commit hook was found refusing every commit made on the
|
|
57
|
+
// default branch — including the commit that installs orly, and the
|
|
58
|
+
// spec-on-main commit the operating model itself prescribes. The question
|
|
59
|
+
// "is this the default branch" is only ever decisive where a Pull Request
|
|
60
|
+
// would open from it, so it is asked there. Running `orly gate pr` on a
|
|
61
|
+
// merged branch reds, which is the honest answer: no PR opens from main.
|
|
62
|
+
expect(names).toContain("git.branch");
|
|
63
|
+
expect(names).toContain("git.tree");
|
|
53
64
|
});
|
|
54
65
|
|
|
55
66
|
test("an unknown gate name yields no criteria rather than throwing", async () => {
|
package/src/criteria.ts
CHANGED
|
@@ -36,6 +36,12 @@ const DOCS_LANGUAGE = "docs.language";
|
|
|
36
36
|
// line is a summary, not a linter dump.
|
|
37
37
|
const NAMED_FINDINGS = 3;
|
|
38
38
|
|
|
39
|
+
// CONFORM is its own lifecycle stage — EXECUTE → CONFORM → VERIFY — and it is
|
|
40
|
+
// its own tier here for the same reason. Lumped into verify, the deterministic
|
|
41
|
+
// rule audit could only run beside the lint and unit suites, so a repository
|
|
42
|
+
// paid minutes to learn something its seconds-long conform command already
|
|
43
|
+
// knew, and `orly gate` ran it twice on the way to `pr`.
|
|
44
|
+
const CONFORM_TIER = "conform";
|
|
39
45
|
const FAST_TIER = "fast";
|
|
40
46
|
const SLOW_TIER = "slow";
|
|
41
47
|
// The slow tier is a fixed name set, not a prefix rule: lint and version
|
|
@@ -45,14 +51,31 @@ const SLOW_COMMANDS = ["verify.integration", "verify.memory"];
|
|
|
45
51
|
// Every criterion is mechanical: it reads an exit code or a file, never a
|
|
46
52
|
// judgment. The anchor invariant promises the machine can PROVE the PR
|
|
47
53
|
// boundary, so anything unprovable stays prose and never lands here.
|
|
48
|
-
//
|
|
49
|
-
//
|
|
54
|
+
//
|
|
55
|
+
// One gate per cadence, and each question asked at the only moment it can be
|
|
56
|
+
// answered honestly:
|
|
57
|
+
//
|
|
58
|
+
// work — does this commit conform? Config plus the declared `conform`
|
|
59
|
+
// command. Nothing about branch or tree: inside a commit hook the
|
|
60
|
+
// tree is dirty BY CONSTRUCTION (that is what is being committed),
|
|
61
|
+
// and the operating model itself says a new spec is committed on the
|
|
62
|
+
// default branch. Judging either here made the generated pre-commit
|
|
63
|
+
// hook refuse every commit — including the one installing orly —
|
|
64
|
+
// with `--no-verify`, which Hard Safety forbids, as the only way out.
|
|
65
|
+
// verify — does the work hold up? Spec dimensions, documentation language,
|
|
66
|
+
// and the fast `verify.*` set. Runs before the work leaves the
|
|
67
|
+
// machine, not after every edit.
|
|
68
|
+
// pr — can this ship? The whole-branch facts (branch shape, clean tree,
|
|
69
|
+
// pushed) plus every spec criterion and the slow suites. A tree is
|
|
70
|
+
// only required clean where a reviewer is about to read it, and a
|
|
71
|
+
// branch is only wrong for being `main` where a PR would open from
|
|
72
|
+
// it.
|
|
50
73
|
export function criteriaFor(gate: string, context: CriterionContext): Criterion[] {
|
|
51
|
-
if (gate === "work") return [
|
|
74
|
+
if (gate === "work") return [repositoryConfig(), ...commandCriteria(context, CONFORM_TIER)];
|
|
52
75
|
if (gate === "verify") return [specDimensions(), docsLanguage(), ...commandCriteria(context, FAST_TIER)];
|
|
53
76
|
if (gate === "pr") {
|
|
54
77
|
return [
|
|
55
|
-
gitTree(), gitPushed(), specGate(), openQuestions(), productClarity(), specDimensions(),
|
|
78
|
+
gitBranch(), gitTree(), gitPushed(), specGate(), openQuestions(), productClarity(), specDimensions(),
|
|
56
79
|
specMoved(), specBaseline(), specOrdering(), specDeferrals(),
|
|
57
80
|
docsUpdated(), ...commandCriteria(context, SLOW_TIER),
|
|
58
81
|
];
|
|
@@ -107,10 +130,11 @@ function repositoryConfig(): Criterion {
|
|
|
107
130
|
}
|
|
108
131
|
|
|
109
132
|
// Model C: the repository's declared command surface. Orly owns policy and
|
|
110
|
-
// invokes these; the repository owns what they actually do.
|
|
111
|
-
// conform
|
|
112
|
-
//
|
|
113
|
-
// code, so a prose-only branch never pays for
|
|
133
|
+
// invokes these; the repository owns what they actually do. Conform tier = the
|
|
134
|
+
// `conform` command alone. Fast tier = every verify.* that is not slow. Slow
|
|
135
|
+
// tier = verify.integration and verify.memory; those auto-pass with a printed
|
|
136
|
+
// skip when the branch carries no code, so a prose-only branch never pays for
|
|
137
|
+
// the slow suites.
|
|
114
138
|
function commandCriteria(context: CriterionContext, tier: string): Criterion[] {
|
|
115
139
|
const config = resolvedConfig(context);
|
|
116
140
|
if (!config) return tier === FAST_TIER ? [criterion(REPO_CONFIG, () => ({ ok: false, detail: UNINSTALLED }))] : [];
|
|
@@ -125,8 +149,9 @@ function commandCriteria(context: CriterionContext, tier: string): Criterion[] {
|
|
|
125
149
|
}
|
|
126
150
|
|
|
127
151
|
function tierOf(key: string): string {
|
|
152
|
+
if (key === CONFORM_COMMAND) return CONFORM_TIER;
|
|
128
153
|
if (SLOW_COMMANDS.includes(key)) return SLOW_TIER;
|
|
129
|
-
if (key
|
|
154
|
+
if (key.startsWith(VERIFY_PREFIX)) return FAST_TIER;
|
|
130
155
|
return "";
|
|
131
156
|
}
|
|
132
157
|
|