@agentsfleet/orly 0.7.0 → 0.8.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/SOUL.md +4 -1
- package/audits/agents-md.md +1 -0
- package/audits/logging.sh +90 -16
- 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_any.md +5 -4
- 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/LOGGING_STANDARD.md +14 -4
- package/docs/RULE_ENFORCEMENT.md +3 -3
- 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 +6 -0
- 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 +6 -1
- package/src/install_packs.test.ts +56 -0
- package/src/render.test.ts +59 -1
- package/src/verify.test.ts +72 -2
- package/src/verify.ts +44 -3
package/AGENTS.md
CHANGED
|
@@ -61,7 +61,7 @@ Auto-memory is **disabled** (`autoMemoryEnabled: false` + `CLAUDE_CODE_DISABLE_A
|
|
|
61
61
|
- **Skip hooks/signing.** Never `--no-verify`/`--no-gpg-sign`/`-c commit.gpgsign=false`. Hook fails → fix cause.
|
|
62
62
|
- **Plaintext secrets in entity tables.** Store vault `key_name` ref, resolve via `crypto_store.load()`.
|
|
63
63
|
- **Static strings in SQL schema.** No `DEFAULT 'value'`/`CHECK (col IN ('a','b'))`. Enforce in app via named constants.
|
|
64
|
-
- **Resolving/printing credentials.** `op read 'op://...'` at runtime — never paste/log.
|
|
64
|
+
- **Resolving/printing credentials.** `op read 'op://...'` at runtime — never paste/log. Vault names come from the environment, never from a rules file.
|
|
65
65
|
- **Force-push default branch** (`main`/`master`).
|
|
66
66
|
- **Install-process launches in core paths.** Native SDKs. Exception: personal dev tools (`op`/`gh`/`glab`/`oracle`).
|
|
67
67
|
|
|
@@ -83,14 +83,14 @@ Auto-memory is **disabled** (`autoMemoryEnabled: false` + `CLAUDE_CODE_DISABLE_A
|
|
|
83
83
|
- **Symlinked edits land in the repository that owns the file.** A file whose `readlink` resolves outside this checkout — into a dotfiles or config repository — is an edit to *that* repository, not this one. Detect via `readlink` BEFORE editing. After: `cd` to the resolved repository, `git add <files> && git commit && git push` on its default branch. Never leave uncommitted.
|
|
84
84
|
- **Docs-repo edits on own branch.** `~/Projects/docs/` is shared across milestones: check `git status` first; from `main` (checkout or worktree off `main`) commit on `chore/m{N}-{slug}-changelog`; recovery = stash, re-apply on a fresh branch.
|
|
85
85
|
- Other dotfiles (`.zshrc`/`.gitconfig`/etc.): timestamped backup; minimal edits.
|
|
86
|
+
- **Check a sibling repository for an existing pattern in the same language before inventing one.** A shape shipping next door is prior art; a new one needs a reason.
|
|
87
|
+
- **Read the reference implementation before designing.** Where the language's dispatch page names a canonical external source, that read is mandatory in review and its guideline identifiers are cited — applied or diverged from. "Broken for us" means the delta was missed: diff our call site against theirs before blaming the principle.
|
|
86
88
|
- Before commit/push: `gitleaks` must pass.
|
|
87
89
|
- No new `make` targets without a distinct caller (CI job, spec-mandated gate, or a workflow existing targets can't express) — check `make/*.mk` first; extend over near-duplicate wrappers.
|
|
88
90
|
- `*.zig` → read `dispatch/write_zig.md`; ZIG GATE fires.
|
|
89
91
|
- Auth-flow (token-minting handlers, credential-typed spec dimensions, the repository's auth directories) → read the repository's `docs/AUTH.md` first, where it exists.
|
|
90
92
|
- `conn.query()` requires `.drain()` in same fn before `deinit()`. Verify `make lint-governance`. Use `conn.exec()` for no-rows.
|
|
91
|
-
- **
|
|
92
|
-
- Check a sibling repository under `$HOME/Projects/` for an existing pattern in the same language before inventing one.
|
|
93
|
-
- **Reference canon (read before designing):** TypeScript → `oss/supabase/apps/studio` + `oss/supabase/packages/{ui,ui-patterns}` + `oss/cli`; Zig → `oss/ghostty/src/`; Rust → `oss/bun/src/` + `oss/exonum` + `oss/core_api-develop` + Microsoft's Rust guidelines (`oss/rust-guidelines/all.txt`, fetch if absent; mandatory in review). Missing checkout → ask, then clone into `~/Projects/oss/`. "Broken for us" → call-site diff first.
|
|
93
|
+
- **Indy's reference checkouts** (the machine-local half of the rule above): TypeScript → `oss/supabase/apps/studio` + `oss/supabase/packages/{ui,ui-patterns}` + `oss/cli`; Zig → `oss/ghostty/src/`; Rust → `oss/bun/src/` + `oss/exonum` + `oss/core_api-develop`. Missing checkout → ask, then clone into `~/Projects/oss/`.
|
|
94
94
|
|
|
95
95
|
**Forge detection:** `github.com` → `gh`; `gitlab.com` → `glab`. Check `git remote -v`.
|
|
96
96
|
|
|
@@ -180,7 +180,9 @@ Non-trivial (full lifecycle) if it: touches >1 file · new abstraction · data m
|
|
|
180
180
|
|
|
181
181
|
**With spec:** `CHORE(open) → PLAN → EXECUTE → CONFORM → VERIFY → REVIEW → DOCUMENT → COMMIT → CHORE(close)`. **Without spec** (bug fix/config/refactor): `PLAN → EXECUTE → CONFORM → VERIFY → REVIEW → DOCUMENT → COMMIT`. CHORE bookends iff work creates/continues a spec under `docs/v*/{active,pending}/`. Stage runbooks (checklists, recipes, formats): `dispatch/lifecycle.md`.
|
|
182
182
|
|
|
183
|
-
**Anchor invariant — `orly gate` proves the boundary.** No PR opens unless every `orly gate pr` criterion is green or carries an `Orly-Override` trailer the user recorded with a reason.
|
|
183
|
+
**Anchor invariant — `orly gate` proves the boundary.** No PR opens unless every `orly gate pr` criterion is green or carries an `Orly-Override` trailer the user recorded with a reason.
|
|
184
|
+
|
|
185
|
+
**One gate per cadence, each tier run once.** `work` = the declared `conform` command, no git state (a commit hook's tree is dirty by construction). `verify` = spec dimensions, docs language, the fast `verify.*` set. `pr` = branch shape, clean tree, pushed branch, every spec criterion, the slow suites. `orly gate pr` is the command CHORE(close) runs; `git.pushed` proves HEAD is exactly what the pre-push `orly gate verify` already graded. A bare `orly gate` chains all three, for a branch whose earlier gates never ran. Stage map: `dispatch/lifecycle.md`. Both stop at the first red group and only read; green unlocks CHORE(close) but never performs it. No spec → spec criteria skip, quality gates still run. Slow suites (`verify.integration`, `verify.memory`) run only when the branch carries code. A user-surface change without a docs change is red until docs land or an override says why not.
|
|
184
186
|
|
|
185
187
|
**LAND (after merge, or when the user confirms it merged):** pull the default branch, prune the merged worktree + branch, `make down` where defined.
|
|
186
188
|
|
|
@@ -203,13 +205,13 @@ Edit only approved scope; no opportunistic refactors. Stay in active worktree. C
|
|
|
203
205
|
|
|
204
206
|
### CONFORM
|
|
205
207
|
|
|
206
|
-
Runs after EXECUTE, before VERIFY. Invokes the `conform` commands the repository declares in `.oracle/orly.json` and aggregates every gate verdict. Any 🔴 returns to EXECUTE; the lifecycle does not advance.
|
|
208
|
+
Runs after EXECUTE, before VERIFY. Invokes the `conform` commands the repository declares in `.oracle/orly.json` and aggregates every gate verdict. Any 🔴 returns to EXECUTE; the lifecycle does not advance. This is the `work` gate's tier and the pre-commit hook runs it at every commit: declare a `conform` costing seconds, not minutes.
|
|
207
209
|
In `agentsfleet` this stage is `make harness-verify`; its output block and end-of-turn audit detail live in `docs/HARNESS_VERIFY_OUTPUT.md`. Required rows: FILE SHAPE, PUB GATE, LENGTH GATE, MILESTONE-ID GATE, ZIG GATE, UI GATE, DESIGN TOKEN GATE, UFS GATE, SCHEMA GUARD, GREPTILE GATE, Architecture consult, Coverage, and `/orly-write-unit-test`.
|
|
208
210
|
|
|
209
211
|
### VERIFY
|
|
210
212
|
|
|
211
|
-
|
|
212
|
-
The `agentsfleet` output block and
|
|
213
|
+
**Two cadences, one boundary.** Inside a Section, run CONFORM plus the declared lane covering the surface the Section touched — enough to prove a Section, reported as a Section claim. The repository's full declared `verify.*` set runs ONCE, at the milestone boundary, before the PR. A package-scoped command never replaces a listed one at either cadence, and a Section lane never satisfies a "tests pass" claim about the repository.
|
|
214
|
+
The `agentsfleet` output block and both cadences live in `docs/VERIFY_TIERS.md`. **FIRST: `/orly-write-unit-test`** audits diff coverage. **LAST: the Test Delta row** compares against the CHORE(open) baseline; zero or negative unit growth on a code-adding diff needs justification or a return to EXECUTE. Paste memory-leak evidence into PR Session Notes or cite its Continuous Integration (CI) URL. After refactors, list newly dead code before removing it.
|
|
213
215
|
|
|
214
216
|
### REVIEW
|
|
215
217
|
|
|
@@ -235,7 +237,7 @@ Required when spec involved — after last COMMIT, before PR. Also runs when par
|
|
|
235
237
|
|
|
236
238
|
| # | When | Skill |
|
|
237
239
|
|---|---|---|
|
|
238
|
-
| 1 | VERIFY | `/orly-write-unit-test
|
|
240
|
+
| 1 | VERIFY | `/orly-write-unit-test` once per Section over that Section's diff, and again at the boundary — never skipped, never deferred. Then `/orly-write-integration-test` at the boundary **when the diff crosses a module boundary with real input/output**; otherwise record `N/A — <reason>` in Session Notes. |
|
|
239
241
|
| 2 | REVIEW | gstack `/review` — every runtime, same route; address findings or record a user-acked deferral; reviewer unavailable → Session Notes: *"skipped — <reason> <ts>; rerun before merge"* |
|
|
240
242
|
| 3 | After every push | `orly-babysit-prs` — CI check runs + greptile inline threads + PR-level summary; stops on two consecutive empty polls with CI green; never `gh pr checks --watch` for greptile |
|
|
241
243
|
|
|
@@ -294,7 +296,10 @@ Optimise for one thing: he never has to ask twice.
|
|
|
294
296
|
justification clauses, importance puffery, em-dash rhythm crutches, and
|
|
295
297
|
fake-profound kickers. End on the clearest concrete sentence. The banned-word
|
|
296
298
|
list lives in `docs/DOCUMENTATION_RULES.md` §DOC-05, §DOC-07, §DOC-14b, and
|
|
297
|
-
`orly gate verify`
|
|
299
|
+
`orly gate verify` REPORTS it — `docs.language` prints its findings and stays
|
|
300
|
+
green, because the corpus predates the rule and a red on day one gets switched
|
|
301
|
+
off by the end of the week. Nobody fails a build over this, so the prose is
|
|
302
|
+
mine to get right.
|
|
298
303
|
|
|
299
304
|
## Reading Indy
|
|
300
305
|
|
|
@@ -370,5 +375,5 @@ Optimise for one thing: he never has to ask twice.
|
|
|
370
375
|
|
|
371
376
|
| Responsibility | Commands |
|
|
372
377
|
|---|---|
|
|
373
|
-
| `conform` | `make
|
|
378
|
+
| `conform` | `make conform` |
|
|
374
379
|
| `verify.unit` | `bun test src` |
|
package/SOUL.md
CHANGED
|
@@ -44,7 +44,10 @@ Optimise for one thing: he never has to ask twice.
|
|
|
44
44
|
justification clauses, importance puffery, em-dash rhythm crutches, and
|
|
45
45
|
fake-profound kickers. End on the clearest concrete sentence. The banned-word
|
|
46
46
|
list lives in `docs/DOCUMENTATION_RULES.md` §DOC-05, §DOC-07, §DOC-14b, and
|
|
47
|
-
`orly gate verify`
|
|
47
|
+
`orly gate verify` REPORTS it — `docs.language` prints its findings and stays
|
|
48
|
+
green, because the corpus predates the rule and a red on day one gets switched
|
|
49
|
+
off by the end of the week. Nobody fails a build over this, so the prose is
|
|
50
|
+
mine to get right.
|
|
48
51
|
|
|
49
52
|
## Reading Indy
|
|
50
53
|
|
package/audits/agents-md.md
CHANGED
|
@@ -95,6 +95,7 @@ The questionnaire is organised by scenario. Each scenario corresponds to a momen
|
|
|
95
95
|
| 4.23 (Documentation) | Before published Markdown JSX (MDX), reusable snippets, customer readme, or public OpenAPI prose, must the agent read `dispatch/write_documentation.md` and `docs/DOCUMENTATION_RULES.md` before any narrower guide? | YES |
|
|
96
96
|
| 4.24 (Documentation) | Are page, snippet, generated API, and changelog scopes distinct, with repository-owned pre-commit checks enforcing the mechanical rules? | YES |
|
|
97
97
|
| 4.25 (Rust) | Do `*.rs` edits route to `dispatch/write_rust.md` — ownership, error variants, feature combinations, deterministic contention tests — with Microsoft's Pragmatic Rust Guidelines (`~/Projects/oss/rust-guidelines/all.txt`, fetched when absent) read sectioned and its `M-…` IDs cited in every Rust review? | YES | <!-- oracle-packs:language.rust -->
|
|
98
|
+
| 4.25b (Rust errors) | Is RULE ERR-RS deterministic where a machine can decide it — `dispatch/write_rust.sh` running `audits/rust-error.sh` over staged `*.rs` to block a `map_err` that stringifies its own cause and an error type with no `pub type Result<…>` in its crate — while `source()` shape and whether a context addition is warranted stay the agent's call? | YES | <!-- oracle-packs:language.rust -->
|
|
98
99
|
| 4.25a (Go) | Do `*.go` edits route to `dispatch/write_go.md`, with wrapped errors, goroutine exit paths, context-first signatures, defer-at-acquire, and `-race` verification? | YES | <!-- oracle-packs:language.go -->
|
|
99
100
|
| 4.26 (Python) | Do `*.py` edits route to `dispatch/write_python.md`, with parse-boundary validation and context-managed resources? | YES |
|
|
100
101
|
| 4.27 (Shell) | Do `*.sh` edits route to `dispatch/write_shell.md`, with quoted expansions, array arguments, cleanup, and compatibility requirements? | YES |
|
package/audits/logging.sh
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# - agentsfleet scope-prefix format from LOGGING_STANDARD §7 (the
|
|
10
10
|
# `log.scoped(...)` API path under `src/logging/`)
|
|
11
11
|
# - `UZ-XXX-NNN` as the `error_code=` substring per LOGGING_STANDARD §5
|
|
12
|
-
# - `src/` + `
|
|
12
|
+
# - `src/` + `agentsfleet/src/` as the Zig and TypeScript scan roots
|
|
13
13
|
# A parameterised version that reads prefix + scope-api path + scan
|
|
14
14
|
# roots from a per-project config is the right long-term shape; not
|
|
15
15
|
# done yet. Today, this script lives in dotfiles so the gate body and
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
# BLOCK — exits 1, must fix:
|
|
20
20
|
# - `std.debug.print(` in non-test source under src/.
|
|
21
21
|
# - `println!` / `eprintln!` / `dbg!`, missing `event`, or positional
|
|
22
|
-
# tracing formatting in
|
|
22
|
+
# tracing formatting in runtime Rust source. Direct stream writes
|
|
23
|
+
# may carry `// logging: <reason>` on or immediately above the
|
|
24
|
+
# emit when stdout or stderr is the intended program interface.
|
|
23
25
|
# - `console.log/debug/info/warn/error` in agentsfleet/src outside tests.
|
|
24
26
|
# INFO — surfaced for reviewer/agent attention, doesn't block:
|
|
25
27
|
# - `std.log.scoped(...)` outside `src/logging/` (LOGGING_STANDARD §7
|
|
@@ -31,7 +33,8 @@
|
|
|
31
33
|
#
|
|
32
34
|
# Modes:
|
|
33
35
|
# --staged diff-scope: only files in `git diff --cached`
|
|
34
|
-
# --all (default) full src/ +
|
|
36
|
+
# --all (default) full src/ + every tracked/unignored *.rs +
|
|
37
|
+
# agentsfleet/src/ scan
|
|
35
38
|
# --strict promote every INFO finding to BLOCK (post-migration use)
|
|
36
39
|
#
|
|
37
40
|
# Exits 0 clean, 1 on BLOCK findings.
|
|
@@ -81,9 +84,9 @@ is_test_zig() {
|
|
|
81
84
|
return 1
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
is_non_runtime_rust_path() {
|
|
85
88
|
case "$1" in
|
|
86
|
-
|
|
89
|
+
build.rs|*/build.rs|tests/*|*/tests/*|benches/*|*/benches/*|examples/*|*/examples/*) return 0 ;;
|
|
87
90
|
esac
|
|
88
91
|
return 1
|
|
89
92
|
}
|
|
@@ -95,11 +98,13 @@ gather_paths() {
|
|
|
95
98
|
case "$MODE" in
|
|
96
99
|
--staged)
|
|
97
100
|
git diff --cached --name-only --diff-filter=ACMRT \
|
|
98
|
-
| grep -E '^
|
|
101
|
+
| grep -E '(^src/.*\.zig$|\.rs$|^agentsfleet/src/.*\.(js|jsx|ts|tsx)$)' || true
|
|
99
102
|
;;
|
|
100
103
|
--all)
|
|
101
104
|
find src -type f -name '*.zig' 2>/dev/null || true
|
|
102
|
-
|
|
105
|
+
while IFS= read -r path; do
|
|
106
|
+
[[ -f "$path" ]] && printf '%s\n' "$path"
|
|
107
|
+
done < <(git ls-files --cached --others --exclude-standard -- '*.rs')
|
|
103
108
|
find agentsfleet/src -type f \( -name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx' \) 2>/dev/null || true
|
|
104
109
|
;;
|
|
105
110
|
esac
|
|
@@ -116,12 +121,13 @@ fi
|
|
|
116
121
|
# single awk/grep passes.
|
|
117
122
|
zig_nontest=()
|
|
118
123
|
rust_nontest=()
|
|
124
|
+
rust_candidates=()
|
|
119
125
|
js_nontest=()
|
|
120
126
|
for f in "${FILES[@]}"; do
|
|
121
127
|
if [[ "$f" == *.zig ]] && ! is_test_zig "$f"; then
|
|
122
128
|
zig_nontest+=("$f")
|
|
123
129
|
fi
|
|
124
|
-
if [[ "$f" == *.rs ]] && !
|
|
130
|
+
if [[ "$f" == *.rs ]] && ! is_non_runtime_rust_path "$f"; then
|
|
125
131
|
rust_nontest+=("$f")
|
|
126
132
|
fi
|
|
127
133
|
case "$f" in
|
|
@@ -132,6 +138,10 @@ for f in "${FILES[@]}"; do
|
|
|
132
138
|
esac
|
|
133
139
|
done
|
|
134
140
|
|
|
141
|
+
if [[ ${#rust_nontest[@]} -gt 0 ]]; then
|
|
142
|
+
mapfile -t rust_candidates < <(grep -lE '(^|[^[:alnum:]_])(println|eprintln|dbg)!|tracing::(error|warn|info|debug|trace)!' "${rust_nontest[@]}" 2>/dev/null || true)
|
|
143
|
+
fi
|
|
144
|
+
|
|
135
145
|
# ---------------------------------------------------------------------------
|
|
136
146
|
# 2. BLOCKING: std.debug.print in non-test Zig source.
|
|
137
147
|
# ---------------------------------------------------------------------------
|
|
@@ -146,10 +156,40 @@ if [[ ${#zig_nontest[@]} -gt 0 ]]; then
|
|
|
146
156
|
fail "$f:$ln — \`std.debug.print\` in non-test source (LOGGING_STANDARD §10A.L1)"
|
|
147
157
|
debug_print_hits=$((debug_print_hits + 1))
|
|
148
158
|
done < <(awk '
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
function comment_text(text, output, cursor, char, escaped, quoted) {
|
|
160
|
+
escaped = 0
|
|
161
|
+
quoted = 0
|
|
162
|
+
for (cursor = 1; cursor <= length(text); cursor++) {
|
|
163
|
+
char = substr(text, cursor, 1)
|
|
164
|
+
if (quoted) {
|
|
165
|
+
if (escaped) escaped = 0
|
|
166
|
+
else if (char == "\\") escaped = 1
|
|
167
|
+
else if (char == "\"") quoted = 0
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
if (char == "\"") {
|
|
171
|
+
quoted = 1
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
if (char == "/" && substr(text, cursor + 1, 1) == "/")
|
|
175
|
+
return substr(text, cursor + 2)
|
|
176
|
+
}
|
|
177
|
+
return ""
|
|
178
|
+
}
|
|
179
|
+
function has_logging_reason(text, comment) {
|
|
180
|
+
if (text ~ /^[[:space:]]*\\\\/) return 0
|
|
181
|
+
comment = comment_text(text)
|
|
182
|
+
return comment ~ /^[[:space:]]*logging:[[:space:]]*[^[:space:]]/
|
|
183
|
+
}
|
|
184
|
+
FNR == 1 { in_test = 0; previous_annotation = 0 }
|
|
185
|
+
{
|
|
186
|
+
annotated = has_logging_reason($0) || previous_annotation
|
|
187
|
+
if ($0 ~ /^test "/) in_test = 1
|
|
188
|
+
else if ($0 ~ /^}/) in_test = 0
|
|
189
|
+
else if ($0 ~ /(^|[^A-Za-z0-9_])std\.debug\.print\(/ && !in_test && !annotated)
|
|
190
|
+
printf "%s:%d\n", FILENAME, FNR
|
|
191
|
+
previous_annotation = has_logging_reason($0)
|
|
192
|
+
}
|
|
153
193
|
' "${zig_nontest[@]}")
|
|
154
194
|
fi
|
|
155
195
|
|
|
@@ -160,7 +200,7 @@ fi
|
|
|
160
200
|
rust_direct_hits=0
|
|
161
201
|
rust_missing_event_hits=0
|
|
162
202
|
rust_positional_hits=0
|
|
163
|
-
if [[ ${#
|
|
203
|
+
if [[ ${#rust_candidates[@]} -gt 0 ]]; then
|
|
164
204
|
while IFS='|' read -r kind f ln; do
|
|
165
205
|
[[ -z "$kind" ]] && continue
|
|
166
206
|
case "$kind" in
|
|
@@ -227,6 +267,30 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
|
|
|
227
267
|
}
|
|
228
268
|
return output
|
|
229
269
|
}
|
|
270
|
+
function comment_text(text, cursor, char, escaped, quoted) {
|
|
271
|
+
escaped = 0
|
|
272
|
+
quoted = 0
|
|
273
|
+
for (cursor = 1; cursor <= length(text); cursor++) {
|
|
274
|
+
char = substr(text, cursor, 1)
|
|
275
|
+
if (quoted) {
|
|
276
|
+
if (escaped) escaped = 0
|
|
277
|
+
else if (char == "\\") escaped = 1
|
|
278
|
+
else if (char == "\"") quoted = 0
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
if (char == "\"") {
|
|
282
|
+
quoted = 1
|
|
283
|
+
continue
|
|
284
|
+
}
|
|
285
|
+
if (char == "/" && substr(text, cursor + 1, 1) == "/")
|
|
286
|
+
return substr(text, cursor + 2)
|
|
287
|
+
}
|
|
288
|
+
return ""
|
|
289
|
+
}
|
|
290
|
+
function has_logging_reason(text, comment) {
|
|
291
|
+
comment = comment_text(text)
|
|
292
|
+
return comment ~ /^[[:space:]]*logging:[[:space:]]*[^[:space:]]/
|
|
293
|
+
}
|
|
230
294
|
function check_emit() {
|
|
231
295
|
if (emit_code !~ /event[[:space:]]*=/ && emit_code !~ /[(,][[:space:]]*event[[:space:]]*[,)]/)
|
|
232
296
|
printf "event|%s|%d\n", FILENAME, emit_line
|
|
@@ -244,15 +308,21 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
|
|
|
244
308
|
emit_parens = 0
|
|
245
309
|
emit_code = ""
|
|
246
310
|
emit_source = ""
|
|
311
|
+
previous_annotation = 0
|
|
247
312
|
}
|
|
248
313
|
{
|
|
314
|
+
current_annotation = has_logging_reason($0)
|
|
249
315
|
code = code_only($0)
|
|
250
|
-
if (!in_emit && code ~ /^[[:space:]]*$/)
|
|
316
|
+
if (!in_emit && code ~ /^[[:space:]]*$/) {
|
|
317
|
+
previous_annotation = current_annotation
|
|
318
|
+
next
|
|
319
|
+
}
|
|
251
320
|
opens = count_char(code, "\\{")
|
|
252
321
|
closes = count_char(code, "\\}")
|
|
253
322
|
if (test_depth > 0) {
|
|
254
323
|
depth += opens - closes
|
|
255
324
|
if (depth < test_depth) test_depth = 0
|
|
325
|
+
previous_annotation = current_annotation
|
|
256
326
|
next
|
|
257
327
|
}
|
|
258
328
|
if (code ~ /^[[:space:]]*#\[cfg\(test\)\]/) {
|
|
@@ -261,6 +331,7 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
|
|
|
261
331
|
depth += opens - closes
|
|
262
332
|
if (depth < test_depth) test_depth = 0
|
|
263
333
|
} else cfg_test = 1
|
|
334
|
+
previous_annotation = current_annotation
|
|
264
335
|
next
|
|
265
336
|
}
|
|
266
337
|
if (cfg_test) {
|
|
@@ -270,9 +341,10 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
|
|
|
270
341
|
if (depth < test_depth) test_depth = 0
|
|
271
342
|
cfg_test = 0
|
|
272
343
|
} else if (code ~ /;/) cfg_test = 0
|
|
344
|
+
previous_annotation = current_annotation
|
|
273
345
|
next
|
|
274
346
|
}
|
|
275
|
-
if (code ~ /(^|[^[:alnum:]_])(println|eprintln|dbg)!/)
|
|
347
|
+
if (code ~ /(^|[^[:alnum:]_])(println|eprintln|dbg)!/ && !current_annotation && !previous_annotation)
|
|
276
348
|
printf "direct|%s|%d\n", FILENAME, FNR
|
|
277
349
|
if (!in_emit && match(code, /tracing::(error|warn|info|debug|trace)!/)) {
|
|
278
350
|
in_emit = 1
|
|
@@ -288,8 +360,9 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
|
|
|
288
360
|
}
|
|
289
361
|
if (in_emit && emit_parens <= 0) check_emit()
|
|
290
362
|
depth += opens - closes
|
|
363
|
+
previous_annotation = current_annotation
|
|
291
364
|
}
|
|
292
|
-
' "${
|
|
365
|
+
' "${rust_candidates[@]}")
|
|
293
366
|
fi
|
|
294
367
|
|
|
295
368
|
# ---------------------------------------------------------------------------
|
|
@@ -363,6 +436,7 @@ fi
|
|
|
363
436
|
ok "scanned ${#FILES[@]} files; std.debug.print=$debug_print_hits rust-direct=$rust_direct_hits rust-missing-event=$rust_missing_event_hits rust-positional=$rust_positional_hits console.*=$console_hits std.log.scoped=$scoped_hits missing-error_code=$missing_code_hits"
|
|
364
437
|
if [[ $FAIL -ne 0 ]]; then
|
|
365
438
|
printf "\n🔴 LOGGING GATE: blocking violations. See dispatch/write_any.md (Logging Gate).\n" >&2
|
|
439
|
+
printf "Intentional direct Zig or Rust stream output requires '// logging: <reason>' on or immediately above the emit; the reason must be non-empty.\n" >&2
|
|
366
440
|
exit 1
|
|
367
441
|
fi
|
|
368
442
|
[[ $INFO_COUNT -gt 0 ]] && note "$INFO_COUNT informational findings; not blocking. Use --strict to enforce."
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# rust-error.sh — the mechanical half of RULE ERR-RS (Rust error discipline).
|
|
3
|
+
#
|
|
4
|
+
# Dispatch façade: dispatch/write_rust.md (Error discipline). Pairs with
|
|
5
|
+
# dispatch/write_rust.sh, which is what runs it.
|
|
6
|
+
#
|
|
7
|
+
# The rule has four clauses. Two of them a machine can decide from the text
|
|
8
|
+
# alone, and only those two live here:
|
|
9
|
+
#
|
|
10
|
+
# 1. LOSSY MAP_ERR — `map_err` whose closure converts its own cause through
|
|
11
|
+
# `to_string()` or `format!`. The rule allows `map_err` to ADD context the
|
|
12
|
+
# call site alone knows; it forbids stringifying the cause on the way into
|
|
13
|
+
# an error type, because `to_string()` destroys the `source()` chain. The
|
|
14
|
+
# closure's BINDING is what makes this decidable: `.map_err(|e| Mine::Db(
|
|
15
|
+
# e.to_string()))` stringifies the cause, while `.map_err(|e| Mine::Read {
|
|
16
|
+
# source: e, path: path.to_string() })` stringifies a path and is correct.
|
|
17
|
+
# Only a `to_string()` or `format!` applied to the closure's own parameter
|
|
18
|
+
# is a finding.
|
|
19
|
+
#
|
|
20
|
+
# 2. MISSING RESULT ALIAS — a crate declaring `pub enum Error` or
|
|
21
|
+
# `pub struct Error` with no `pub type Result<` anywhere beside it. The
|
|
22
|
+
# rule is "one error type per crate, and a `Result` alias beside it": a
|
|
23
|
+
# reader must never have to check WHICH error a signature returns.
|
|
24
|
+
#
|
|
25
|
+
# The other two clauses stay JUDGMENT and are not attempted here. Whether a
|
|
26
|
+
# `source()` implementation returns its own kind needs the Display body read
|
|
27
|
+
# beside it, and whether a context addition is warranted is a call-site
|
|
28
|
+
# question. A gate that guesses at those gets switched off within a week, and a
|
|
29
|
+
# switched-off gate protects nothing — partial mechanisation stated honestly is
|
|
30
|
+
# the whole design (the same call audits/doc-read.sh makes about its record).
|
|
31
|
+
#
|
|
32
|
+
# Carve-outs match audits/logging.sh exactly, so one Rust scope answers to both
|
|
33
|
+
# leaves: build scripts, `tests/`, `benches/`, `examples/`, and `#[cfg(test)]`
|
|
34
|
+
# modules are fixture code, where a stringified error in a test assertion is
|
|
35
|
+
# not a production error path.
|
|
36
|
+
#
|
|
37
|
+
# Modes:
|
|
38
|
+
# --staged diff-scope: only *.rs in `git diff --cached`
|
|
39
|
+
# --all (default) every tracked/unignored *.rs
|
|
40
|
+
#
|
|
41
|
+
# Exits 0 clean, 1 on findings.
|
|
42
|
+
|
|
43
|
+
set -euo pipefail
|
|
44
|
+
|
|
45
|
+
MODE_STAGED="--staged"
|
|
46
|
+
MODE_ALL="--all"
|
|
47
|
+
RULE="ERR-RS"
|
|
48
|
+
FACADE="dispatch/write_rust.md"
|
|
49
|
+
# The closure body window. A `map_err` closure that has not closed its
|
|
50
|
+
# parentheses within this many lines is a block long enough that its conversion
|
|
51
|
+
# is no longer a one-liner, and the rule's subject is the one-liner.
|
|
52
|
+
MAX_CLOSURE_LINES=12
|
|
53
|
+
CARGO_MANIFEST="Cargo.toml"
|
|
54
|
+
RESULT_ALIAS_PATTERN='^[[:space:]]*pub[[:space:]]+type[[:space:]]+Result[[:space:]]*<'
|
|
55
|
+
ERROR_TYPE_PATTERN='^[[:space:]]*pub[[:space:]]+(enum|struct)[[:space:]]+Error[[:space:]]*[<{(;]'
|
|
56
|
+
|
|
57
|
+
MODE="$MODE_ALL"
|
|
58
|
+
for arg in "$@"; do
|
|
59
|
+
case "$arg" in
|
|
60
|
+
--staged|staged) MODE="$MODE_STAGED" ;;
|
|
61
|
+
--all|all) MODE="$MODE_ALL" ;;
|
|
62
|
+
-h|--help)
|
|
63
|
+
printf 'usage: %s [--staged|--all]\n' "$0"
|
|
64
|
+
exit 0
|
|
65
|
+
;;
|
|
66
|
+
*) printf 'unknown arg: %s\n' "$arg" >&2; exit 64 ;;
|
|
67
|
+
esac
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
ROOT="$(git rev-parse --show-toplevel)"
|
|
71
|
+
cd "$ROOT"
|
|
72
|
+
|
|
73
|
+
FAIL=0
|
|
74
|
+
fail() { printf 'FAIL: %s\n' "$*" >&2; FAIL=1; }
|
|
75
|
+
ok() { printf 'OK: %s\n' "$*"; }
|
|
76
|
+
|
|
77
|
+
# Non-runtime Rust paths, identical to audits/logging.sh's carve-out list.
|
|
78
|
+
is_non_runtime_rust_path() {
|
|
79
|
+
case "$1" in
|
|
80
|
+
build.rs|*/build.rs|tests/*|*/tests/*|benches/*|*/benches/*|examples/*|*/examples/*) return 0 ;;
|
|
81
|
+
esac
|
|
82
|
+
return 1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
gather_paths() {
|
|
86
|
+
case "$MODE" in
|
|
87
|
+
"$MODE_STAGED")
|
|
88
|
+
git diff --cached --name-only --diff-filter=ACMRT -- '*.rs' || true
|
|
89
|
+
;;
|
|
90
|
+
"$MODE_ALL")
|
|
91
|
+
git ls-files --cached --others --exclude-standard -- '*.rs' || true
|
|
92
|
+
;;
|
|
93
|
+
esac
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
FILES=()
|
|
97
|
+
while IFS= read -r path; do
|
|
98
|
+
[[ -n "$path" && -f "$path" ]] || continue
|
|
99
|
+
is_non_runtime_rust_path "$path" && continue
|
|
100
|
+
FILES+=("$path")
|
|
101
|
+
done < <(gather_paths)
|
|
102
|
+
|
|
103
|
+
if [[ ${#FILES[@]} -eq 0 ]]; then
|
|
104
|
+
ok "$RULE: no runtime Rust files in scope ($MODE)"
|
|
105
|
+
exit 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# 1. Scan every in-scope file once, emitting both signals.
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# One pass, one carve-out. Both halves need the same `#[cfg(test)]` skip, and
|
|
112
|
+
# implementing it twice is how the two halves would come to disagree about what
|
|
113
|
+
# a test module is — so the scanner emits `LOSSY:` and `ERRTYPE:` records from a
|
|
114
|
+
# single walk and the shell decides what to do with each.
|
|
115
|
+
#
|
|
116
|
+
# The lossy scanner accumulates a map_err closure from its opening parenthesis
|
|
117
|
+
# until the parentheses balance, so a conversion split over several lines reads
|
|
118
|
+
# the same as a one-liner.
|
|
119
|
+
signals="$(awk -v maxlines="$MAX_CLOSURE_LINES" -v errtype="$ERROR_TYPE_PATTERN" '
|
|
120
|
+
FNR == 1 { intest = 0; testdepth = 0; pending = 0; collecting = 0 }
|
|
121
|
+
|
|
122
|
+
# A #[cfg(test)] module is fixture code. The attribute precedes the item, so
|
|
123
|
+
# arm on the attribute and start counting braces at the item that follows.
|
|
124
|
+
!collecting && /#\[cfg\(test\)\]/ { pending = 1; next }
|
|
125
|
+
pending {
|
|
126
|
+
if (index($0, "{") > 0) { pending = 0; intest = 1; testdepth = 0 }
|
|
127
|
+
else next
|
|
128
|
+
}
|
|
129
|
+
intest {
|
|
130
|
+
n = gsub(/\{/, "{"); m = gsub(/\}/, "}")
|
|
131
|
+
testdepth += n - m
|
|
132
|
+
if (testdepth <= 0) intest = 0
|
|
133
|
+
next
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
line = $0
|
|
138
|
+
# Strip a trailing line comment so a commented example is not a finding.
|
|
139
|
+
sub(/\/\/.*$/, "", line)
|
|
140
|
+
|
|
141
|
+
# A public error type outside any test module. Emitted per file; the shell
|
|
142
|
+
# resolves it to a crate and asks that crate for its alias once.
|
|
143
|
+
if (!collecting && line ~ errtype) printf "ERRTYPE:%s\n", FILENAME
|
|
144
|
+
|
|
145
|
+
if (!collecting) {
|
|
146
|
+
where = match(line, /map_err[[:space:]]*\([[:space:]]*\|[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\|/)
|
|
147
|
+
if (where == 0) next
|
|
148
|
+
rest = substr(line, where)
|
|
149
|
+
# The closure binding: the identifier between the pipes.
|
|
150
|
+
bind = rest
|
|
151
|
+
sub(/^map_err[[:space:]]*\([[:space:]]*\|[[:space:]]*/, "", bind)
|
|
152
|
+
sub(/[[:space:]]*\|.*$/, "", bind)
|
|
153
|
+
if (bind == "_") next
|
|
154
|
+
collecting = 1; buffer = rest; startline = FNR; spanned = 1
|
|
155
|
+
depth = gsub(/\(/, "(", rest) - gsub(/\)/, ")", rest)
|
|
156
|
+
} else {
|
|
157
|
+
buffer = buffer " " line
|
|
158
|
+
spanned++
|
|
159
|
+
depth += gsub(/\(/, "(", line) - gsub(/\)/, ")", line)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (depth > 0 && spanned < maxlines) next
|
|
163
|
+
|
|
164
|
+
# `bind.to_string()` — the cause converted to text on the way in.
|
|
165
|
+
stringified = 0
|
|
166
|
+
if (buffer ~ ("(^|[^A-Za-z0-9_])" bind "[[:space:]]*\\.[[:space:]]*to_string[[:space:]]*\\(")) stringified = 1
|
|
167
|
+
# `format!(... bind ...)` — the same loss wearing a formatter.
|
|
168
|
+
if (buffer ~ /format!/ && buffer ~ ("(^|[^A-Za-z0-9_])" bind "([^A-Za-z0-9_]|$)")) {
|
|
169
|
+
after = buffer; sub(/^.*format!/, "", after)
|
|
170
|
+
if (after ~ ("(^|[^A-Za-z0-9_])" bind "([^A-Za-z0-9_]|$)")) stringified = 1
|
|
171
|
+
}
|
|
172
|
+
if (stringified) printf "LOSSY:%s:%d\n", FILENAME, startline
|
|
173
|
+
collecting = 0
|
|
174
|
+
}
|
|
175
|
+
' "${FILES[@]}" || true)"
|
|
176
|
+
|
|
177
|
+
lossy_count=0
|
|
178
|
+
error_type_files=()
|
|
179
|
+
while IFS= read -r signal; do
|
|
180
|
+
case "$signal" in
|
|
181
|
+
LOSSY:*)
|
|
182
|
+
lossy_count=$((lossy_count + 1))
|
|
183
|
+
fail "${signal#LOSSY:} $RULE: map_err stringifies its own cause — to_string()/format! on the closure binding destroys the source() chain. Carry the cause with #[from] or a source field; map_err only ADDS context."
|
|
184
|
+
;;
|
|
185
|
+
ERRTYPE:*)
|
|
186
|
+
error_type_files+=("${signal#ERRTYPE:}")
|
|
187
|
+
;;
|
|
188
|
+
esac
|
|
189
|
+
done <<< "$signals"
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# 2. An error type with no Result alias beside it.
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
# Crate-scoped: the alias may live in any file of the crate that declares the
|
|
195
|
+
# error, so the question is asked of the crate, once, not of the file.
|
|
196
|
+
crate_root_of() {
|
|
197
|
+
local dir; dir="$(dirname "$1")"
|
|
198
|
+
while [[ "$dir" != "." && "$dir" != "/" ]]; do
|
|
199
|
+
[[ -f "$dir/$CARGO_MANIFEST" ]] && { printf '%s' "$dir"; return 0; }
|
|
200
|
+
dir="$(dirname "$dir")"
|
|
201
|
+
done
|
|
202
|
+
[[ -f "$CARGO_MANIFEST" ]] && { printf '.'; return 0; }
|
|
203
|
+
return 1
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
alias_count=0
|
|
207
|
+
checked_crates=""
|
|
208
|
+
for path in ${error_type_files[@]+"${error_type_files[@]}"}; do
|
|
209
|
+
crate=""
|
|
210
|
+
crate_root_of "$path" > /dev/null 2>&1 && crate="$(crate_root_of "$path")"
|
|
211
|
+
# A .rs file outside any Cargo crate has no crate to carry the alias; the
|
|
212
|
+
# lossy-map_err half still judged it.
|
|
213
|
+
[[ -n "$crate" ]] || continue
|
|
214
|
+
case " $checked_crates " in *" $crate "*) continue ;; esac
|
|
215
|
+
checked_crates="$checked_crates $crate"
|
|
216
|
+
if ! git ls-files --cached --others --exclude-standard -- "$crate/*.rs" \
|
|
217
|
+
| xargs grep -lE "$RESULT_ALIAS_PATTERN" 2>/dev/null | grep -q .; then
|
|
218
|
+
alias_count=$((alias_count + 1))
|
|
219
|
+
fail "$path $RULE: crate '$crate' declares a public Error type with no \`pub type Result<T, E = Error>\` beside it — a reader must not have to check WHICH error a signature returns."
|
|
220
|
+
fi
|
|
221
|
+
done
|
|
222
|
+
|
|
223
|
+
ok "$RULE: scanned ${#FILES[@]} runtime Rust file(s); lossy-map_err=$lossy_count missing-result-alias=$alias_count"
|
|
224
|
+
if [[ $FAIL -ne 0 ]]; then
|
|
225
|
+
printf '\n🔴 %s: blocking violations. See %s (Error discipline).\n' "$RULE" "$FACADE" >&2
|
|
226
|
+
exit 1
|
|
227
|
+
fi
|
|
228
|
+
exit 0
|
package/core/operating-model.md
CHANGED
|
@@ -61,7 +61,7 @@ Auto-memory is **disabled** (`autoMemoryEnabled: false` + `CLAUDE_CODE_DISABLE_A
|
|
|
61
61
|
- **Skip hooks/signing.** Never `--no-verify`/`--no-gpg-sign`/`-c commit.gpgsign=false`. Hook fails → fix cause.
|
|
62
62
|
- **Plaintext secrets in entity tables.** Store vault `key_name` ref, resolve via `crypto_store.load()`.
|
|
63
63
|
- **Static strings in SQL schema.** No `DEFAULT 'value'`/`CHECK (col IN ('a','b'))`. Enforce in app via named constants.
|
|
64
|
-
- **Resolving/printing credentials.** `op read 'op://...'` at runtime — never paste/log.
|
|
64
|
+
- **Resolving/printing credentials.** `op read 'op://...'` at runtime — never paste/log. Vault names come from the environment, never from a rules file.
|
|
65
65
|
- **Force-push default branch** (`main`/`master`).
|
|
66
66
|
- **Install-process launches in core paths.** Native SDKs. Exception: personal dev tools (`op`/`gh`/`glab`/`oracle`).
|
|
67
67
|
|
|
@@ -80,18 +80,18 @@ Auto-memory is **disabled** (`autoMemoryEnabled: false` + `CLAUDE_CODE_DISABLE_A
|
|
|
80
80
|
### Operational defaults
|
|
81
81
|
|
|
82
82
|
- Workspace `~/Projects`. `gh`/`glab` not browsers. `trash` not `rm`. Conventional Commits. Process decisions → repo docs, not chat. "Make a note" → update `AGENTS.md`/repo docs.
|
|
83
|
-
- **Symlinked edits land in the repository that owns the file.** A file whose `readlink` resolves outside this checkout — into a dotfiles or config repository — is an edit to *that* repository, not this one. Detect via `readlink` BEFORE editing. After: `cd` to the resolved repository, `git add <files> && git commit && git push` on its default branch. Never leave uncommitted.
|
|
83
|
+
- **Symlinked edits land in the repository that owns the file.** A file whose `readlink` resolves outside this checkout — into a dotfiles or config repository — is an edit to *that* repository, not this one. Detect via `readlink` BEFORE editing. After: `cd` to the resolved repository, `git add <files> && git commit && git push` on its default branch. Never leave uncommitted.
|
|
84
84
|
- **Docs-repo edits on own branch.** `~/Projects/docs/` is shared across milestones: check `git status` first; from `main` (checkout or worktree off `main`) commit on `chore/m{N}-{slug}-changelog`; recovery = stash, re-apply on a fresh branch.
|
|
85
|
-
- Other dotfiles (`.zshrc`/`.gitconfig`/etc.): timestamped backup; minimal edits.
|
|
85
|
+
- Other dotfiles (`.zshrc`/`.gitconfig`/etc.): timestamped backup; minimal edits.
|
|
86
|
+
- **Check a sibling repository for an existing pattern in the same language before inventing one.** A shape shipping next door is prior art; a new one needs a reason.
|
|
87
|
+
- **Read the reference implementation before designing.** Where the language's dispatch page names a canonical external source, that read is mandatory in review and its guideline identifiers are cited — applied or diverged from. "Broken for us" means the delta was missed: diff our call site against theirs before blaming the principle.
|
|
86
88
|
- Before commit/push: `gitleaks` must pass.
|
|
87
89
|
- No new `make` targets without a distinct caller (CI job, spec-mandated gate, or a workflow existing targets can't express) — check `make/*.mk` first; extend over near-duplicate wrappers.
|
|
88
90
|
- `*.zig` → read `dispatch/write_zig.md`; ZIG GATE fires. <!-- oracle-packs:language.zig -->
|
|
89
91
|
- Auth-flow (token-minting handlers, credential-typed spec dimensions, the repository's auth directories) → read the repository's `docs/AUTH.md` first, where it exists. <!-- oracle-packs:domain.auth -->
|
|
90
92
|
- `conn.query()` requires `.drain()` in same fn before `deinit()`. Verify `make lint-governance`. Use `conn.exec()` for no-rows. <!-- oracle-packs:product.agentsfleet -->
|
|
91
93
|
<!-- oracle-packs:start persona.indy -->
|
|
92
|
-
- **
|
|
93
|
-
- Check a sibling repository under `$HOME/Projects/` for an existing pattern in the same language before inventing one.
|
|
94
|
-
- **Reference canon (read before designing):** TypeScript → `oss/supabase/apps/studio` + `oss/supabase/packages/{ui,ui-patterns}` + `oss/cli`; Zig → `oss/ghostty/src/`; Rust → `oss/bun/src/` + `oss/exonum` + `oss/core_api-develop` + Microsoft's Rust guidelines (`oss/rust-guidelines/all.txt`, fetch if absent; mandatory in review). Missing checkout → ask, then clone into `~/Projects/oss/`. "Broken for us" → call-site diff first.
|
|
94
|
+
- **Indy's reference checkouts** (the machine-local half of the rule above): TypeScript → `oss/supabase/apps/studio` + `oss/supabase/packages/{ui,ui-patterns}` + `oss/cli`; Zig → `oss/ghostty/src/`; Rust → `oss/bun/src/` + `oss/exonum` + `oss/core_api-develop`. Missing checkout → ask, then clone into `~/Projects/oss/`.
|
|
95
95
|
<!-- oracle-packs:end -->
|
|
96
96
|
|
|
97
97
|
**Forge detection:** `github.com` → `gh`; `gitlab.com` → `glab`. Check `git remote -v`.
|
|
@@ -186,7 +186,9 @@ Non-trivial (full lifecycle) if it: touches >1 file · new abstraction · data m
|
|
|
186
186
|
|
|
187
187
|
**With spec:** `CHORE(open) → PLAN → EXECUTE → CONFORM → VERIFY → REVIEW → DOCUMENT → COMMIT → CHORE(close)`. **Without spec** (bug fix/config/refactor): `PLAN → EXECUTE → CONFORM → VERIFY → REVIEW → DOCUMENT → COMMIT`. CHORE bookends iff work creates/continues a spec under `docs/v*/{active,pending}/`. Stage runbooks (checklists, recipes, formats): `dispatch/lifecycle.md`.
|
|
188
188
|
|
|
189
|
-
**Anchor invariant — `orly gate` proves the boundary.** No PR opens unless every `orly gate pr` criterion is green or carries an `Orly-Override` trailer the user recorded with a reason.
|
|
189
|
+
**Anchor invariant — `orly gate` proves the boundary.** No PR opens unless every `orly gate pr` criterion is green or carries an `Orly-Override` trailer the user recorded with a reason.
|
|
190
|
+
|
|
191
|
+
**One gate per cadence, each tier run once.** `work` = the declared `conform` command, no git state (a commit hook's tree is dirty by construction). `verify` = spec dimensions, docs language, the fast `verify.*` set. `pr` = branch shape, clean tree, pushed branch, every spec criterion, the slow suites. `orly gate pr` is the command CHORE(close) runs; `git.pushed` proves HEAD is exactly what the pre-push `orly gate verify` already graded. A bare `orly gate` chains all three, for a branch whose earlier gates never ran. Stage map: `dispatch/lifecycle.md`. Both stop at the first red group and only read; green unlocks CHORE(close) but never performs it. No spec → spec criteria skip, quality gates still run. Slow suites (`verify.integration`, `verify.memory`) run only when the branch carries code. A user-surface change without a docs change is red until docs land or an override says why not.
|
|
190
192
|
|
|
191
193
|
**LAND (after merge, or when the user confirms it merged):** pull the default branch, prune the merged worktree + branch, `make down` where defined.
|
|
192
194
|
|
|
@@ -213,16 +215,16 @@ Edit only approved scope; no opportunistic refactors. Stay in active worktree. C
|
|
|
213
215
|
|
|
214
216
|
### CONFORM
|
|
215
217
|
|
|
216
|
-
Runs after EXECUTE, before VERIFY. Invokes the `conform` commands the repository declares in `.oracle/orly.json` and aggregates every gate verdict. Any 🔴 returns to EXECUTE; the lifecycle does not advance.
|
|
218
|
+
Runs after EXECUTE, before VERIFY. Invokes the `conform` commands the repository declares in `.oracle/orly.json` and aggregates every gate verdict. Any 🔴 returns to EXECUTE; the lifecycle does not advance. This is the `work` gate's tier and the pre-commit hook runs it at every commit: declare a `conform` costing seconds, not minutes.
|
|
217
219
|
<!-- oracle-packs:start product.agentsfleet -->
|
|
218
220
|
In `agentsfleet` this stage is `make harness-verify`; its output block and end-of-turn audit detail live in `docs/HARNESS_VERIFY_OUTPUT.md`. Required rows: FILE SHAPE, PUB GATE, LENGTH GATE, MILESTONE-ID GATE, ZIG GATE, UI GATE, DESIGN TOKEN GATE, UFS GATE, SCHEMA GUARD, GREPTILE GATE, Architecture consult, Coverage, and `/orly-write-unit-test`.
|
|
219
221
|
<!-- oracle-packs:end -->
|
|
220
222
|
|
|
221
223
|
### VERIFY
|
|
222
224
|
|
|
223
|
-
|
|
225
|
+
**Two cadences, one boundary.** Inside a Section, run CONFORM plus the declared lane covering the surface the Section touched — enough to prove a Section, reported as a Section claim. The repository's full declared `verify.*` set runs ONCE, at the milestone boundary, before the PR. A package-scoped command never replaces a listed one at either cadence, and a Section lane never satisfies a "tests pass" claim about the repository.
|
|
224
226
|
<!-- oracle-packs:start product.agentsfleet -->
|
|
225
|
-
The `agentsfleet` output block and
|
|
227
|
+
The `agentsfleet` output block and both cadences live in `docs/VERIFY_TIERS.md`. **FIRST: `/orly-write-unit-test`** audits diff coverage. **LAST: the Test Delta row** compares against the CHORE(open) baseline; zero or negative unit growth on a code-adding diff needs justification or a return to EXECUTE. Paste memory-leak evidence into PR Session Notes or cite its Continuous Integration (CI) URL. After refactors, list newly dead code before removing it.
|
|
226
228
|
<!-- oracle-packs:end -->
|
|
227
229
|
|
|
228
230
|
### REVIEW
|
|
@@ -250,7 +252,7 @@ Required when spec involved — after last COMMIT, before PR. Also runs when par
|
|
|
250
252
|
|
|
251
253
|
| # | When | Skill |
|
|
252
254
|
|---|---|---|
|
|
253
|
-
| 1 | VERIFY | `/orly-write-unit-test
|
|
255
|
+
| 1 | VERIFY | `/orly-write-unit-test` once per Section over that Section's diff, and again at the boundary — never skipped, never deferred. Then `/orly-write-integration-test` at the boundary **when the diff crosses a module boundary with real input/output**; otherwise record `N/A — <reason>` in Session Notes. |
|
|
254
256
|
| 2 | REVIEW | gstack `/review` — every runtime, same route; address findings or record a user-acked deferral; reviewer unavailable → Session Notes: *"skipped — <reason> <ts>; rerun before merge"* |
|
|
255
257
|
| 3 | After every push | `orly-babysit-prs` — CI check runs + greptile inline threads + PR-level summary; stops on two consecutive empty polls with CI green; never `gh pr checks --watch` for greptile |
|
|
256
258
|
|