@agentsfleet/orly 0.5.3 → 0.6.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 +45 -21
- package/README.md +174 -105
- package/SOUL.md +38 -18
- package/core/operating-model.md +7 -3
- package/docs/DOCUMENTATION_RULES.md +38 -7
- package/docs/RULE_ENFORCEMENT.md +2 -2
- package/fixtures/doc-slop.md +29 -0
- package/llms.txt +223 -0
- package/package.json +2 -1
- package/src/criteria.ts +34 -1
- package/src/doc_rules.test.ts +101 -0
- package/src/doc_rules.ts +390 -0
- package/src/gates.test.ts +1 -1
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
> any narrower guide such as `CHANGELOG_VOICE.md` or before editing documentation
|
|
5
5
|
> about the dispatch model.
|
|
6
6
|
|
|
7
|
-
These rules govern
|
|
7
|
+
These rules govern any documentation orly writes or renders. They keep pages
|
|
8
8
|
short, accurate, and usable by a first-day reader. Compliance is binary. A
|
|
9
9
|
rejection cites the Documentation (DOC) rule identifier that failed.
|
|
10
10
|
|
|
11
11
|
## Scope and precedence
|
|
12
12
|
|
|
13
|
-
- **DOC-S1 —
|
|
14
|
-
|
|
13
|
+
- **DOC-S1 — Any page orly writes.** Every Markdown or Markdown JSX (MDX) file
|
|
14
|
+
orly writes, renders, or materialises follows every applicable rule in this
|
|
15
|
+
document. A published page listed in `docs.json`, a rule page installed into
|
|
16
|
+
a repository, a skill page, and a `README.md` are all in scope.
|
|
15
17
|
- **DOC-S2 — Generated API pages.** OpenAPI source follows the reference-page
|
|
16
18
|
intent through summaries, descriptions, parameters, examples, responses, and
|
|
17
19
|
stable errors. Generated pages do not need MDX front matter.
|
|
@@ -26,6 +28,9 @@ rejection cites the Documentation (DOC) rule identifier that failed.
|
|
|
26
28
|
- **DOC-S6 — Source wins.** Runtime code is the source of truth. OpenAPI and the
|
|
27
29
|
command-line interface follow runtime behavior. Published pages follow all
|
|
28
30
|
three.
|
|
31
|
+
- **DOC-S7 — Records stay as written.** A historical record keeps its original
|
|
32
|
+
wording. Spec records under `docs/v1/` and closed changelog entries are read,
|
|
33
|
+
never rewritten to a later rule.
|
|
29
34
|
|
|
30
35
|
## Required front matter
|
|
31
36
|
|
|
@@ -110,10 +115,36 @@ Cut these on sight. They read as machine prose and they hide the point.
|
|
|
110
115
|
| Fake-profound kicker | a closing metaphor or mic-drop | End on the last concrete sentence. |
|
|
111
116
|
| Summary recap | "In conclusion", "Overall" | Stop. The reader was just there. |
|
|
112
117
|
|
|
113
|
-
Also banned, on top of DOC-05 and DOC-07: delve, foster, elevate,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
Also banned, on top of DOC-05 and DOC-07: delve, foster, elevate, supercharge,
|
|
119
|
+
embark, ever-evolving, tapestry, realm, paradigm shift, game changer, deep
|
|
120
|
+
dive, blazing fast, streamline, empower, journey, landscape, testament,
|
|
121
|
+
pivotal, notably, moreover, furthermore, myriad, plethora, aforementioned,
|
|
122
|
+
meticulous, boasts, cornerstone, best-in-class, industry-leading,
|
|
123
|
+
state-of-the-art, unparalleled, effortless, turnkey, battle-tested,
|
|
124
|
+
rock-solid, and innovative.
|
|
125
|
+
|
|
126
|
+
DOC-05 covers "utilize" as well as "utilise". The checker always enforced
|
|
127
|
+
both; this page named only one. A rule readers cannot read is a rule that
|
|
128
|
+
drifts.
|
|
129
|
+
|
|
130
|
+
"contract" and "phase" are banned too. That ban is not new here: the operating
|
|
131
|
+
model states it and names the replacements, so read it there rather than
|
|
132
|
+
twice. Use Prototype, Milestone, Workstream, Section, Dimension, and Batch for
|
|
133
|
+
the hierarchy, "stages" for lifecycle steps, and "rules" for what `AGENTS.md`
|
|
134
|
+
enforces. A real commercial agreement keeps "contract" only where no clearer
|
|
135
|
+
term exists.
|
|
136
|
+
|
|
137
|
+
"harness" is not banned. It is this product's own word for what orly installs,
|
|
138
|
+
and it reaches every consumer through the rendered `AGENTS.md`. A word the
|
|
139
|
+
product uses for itself is vocabulary, not slop.
|
|
140
|
+
|
|
141
|
+
Em dashes are not a rhythm crutch: none in short copy, at most two per long
|
|
142
|
+
page, and only where they beat a comma or a period. The rule corpus is exempt
|
|
143
|
+
from that budget — `dispatch/`, `core/`, `audits/`, `skills/`, `AGENTS.md`,
|
|
144
|
+
`AGENTS.orly.md`, `SOUL.md`, and any `RULES.md`. Those pages are dense
|
|
145
|
+
reference text where the dash separates a claim from its consequence. Measured
|
|
146
|
+
across the corpus the budget flagged 36 of 39 pages, which is a rule mis-fit,
|
|
147
|
+
not 36 defects. They keep every other rule.
|
|
117
148
|
|
|
118
149
|
## Content
|
|
119
150
|
|
package/docs/RULE_ENFORCEMENT.md
CHANGED
|
@@ -9,12 +9,12 @@ regenerates this file and fails on any difference. To update it, run
|
|
|
9
9
|
| `docs/LOGGING_STANDARD.md` | `LOG` → `logging.sh` | 10 | 13 | 26/26 | mechanical |
|
|
10
10
|
| `docs/REST_API_DESIGN_GUIDELINES.md` | — | 0 | 0 | 0/74 | mechanical |
|
|
11
11
|
| `docs/SCHEMA_CONVENTIONS.md` | — | 0 | 0 | 0/16 | mechanical |
|
|
12
|
-
| `docs/DOCUMENTATION_RULES.md` | — | 0 | 0 | 0/
|
|
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
15
|
| `docs/VERIFY_TIERS.md` | — | 0 | 0 | 0/4 | uncited |
|
|
16
16
|
| `docs/greptile-learnings/RULES.md` | — | 0 | 0 | 0/113 | mechanical |
|
|
17
|
-
| **corpus** | | | | **26/
|
|
17
|
+
| **corpus** | | | | **26/280** | |
|
|
18
18
|
|
|
19
19
|
## How to read this
|
|
20
20
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Slop fixture
|
|
2
|
+
|
|
3
|
+
This page is the fail fixture for the documentation-language check. Every
|
|
4
|
+
violation below is deliberate, and the test asserts the check finds each one.
|
|
5
|
+
|
|
6
|
+
We leverage a robust and seamless pipeline to execute the request, and the
|
|
7
|
+
result is a powerful experience that lets you delve into a realm of insight
|
|
8
|
+
without ever once thinking about the mechanics of the thing you are using.
|
|
9
|
+
This sentence exists to be long. This one too. And a fourth, to break DOC-03.
|
|
10
|
+
|
|
11
|
+
An em dash — in short copy — is over budget by itself.
|
|
12
|
+
|
|
13
|
+
### Skipped level
|
|
14
|
+
|
|
15
|
+
The heading above jumps from H1 to H3.
|
|
16
|
+
|
|
17
|
+
# Second level-one heading
|
|
18
|
+
|
|
19
|
+
`make harness-verify` and `provision-env-1password` are command names, not
|
|
20
|
+
banned words. The check must leave both alone.
|
|
21
|
+
|
|
22
|
+
| leverage | robust | seamless |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| a table cell is exempt | so is this | and this |
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# a fenced block is exempt: execute the robust and seamless pipeline
|
|
28
|
+
orly gate verify
|
|
29
|
+
```
|
package/llms.txt
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# orly — agent instructions
|
|
2
|
+
|
|
3
|
+
> You are an autonomous coding agent. This file is the authoritative setup and
|
|
4
|
+
> operating guide for orly. Read it in full before running any command.
|
|
5
|
+
> Do not infer behaviour from README.md; that page is written for humans.
|
|
6
|
+
|
|
7
|
+
orly installs two things into a repository: rules you must read before editing,
|
|
8
|
+
and gate scripts wired into git hooks that fail the commit when a rule was
|
|
9
|
+
ignored. Hosts supported: Claude Code, Codex, OpenCode, Amp.
|
|
10
|
+
|
|
11
|
+
Package: `@agentsfleet/orly` (npm)
|
|
12
|
+
Source: https://github.com/agentsfleet/orly
|
|
13
|
+
License: MIT
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Preconditions
|
|
18
|
+
|
|
19
|
+
Verify all of these before acting. If a check fails, stop and report it to the
|
|
20
|
+
user rather than working around it.
|
|
21
|
+
|
|
22
|
+
| Check | Command | Required |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| bun present | `bun --version` | ≥ 1.4.0 |
|
|
25
|
+
| git present | `git --version` | any |
|
|
26
|
+
| inside a repository | `git rev-parse --show-toplevel` | must succeed |
|
|
27
|
+
| working tree clean | `git status --porcelain` | should be empty |
|
|
28
|
+
|
|
29
|
+
Do not install bun, git, or any package manager yourself unless the user asks.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Install sequence
|
|
34
|
+
|
|
35
|
+
Run from the repository root. Execute these in order and stop at the first
|
|
36
|
+
non-zero exit.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 1. preview — writes nothing
|
|
40
|
+
bunx @agentsfleet/orly init --dry-run
|
|
41
|
+
|
|
42
|
+
# 2. install
|
|
43
|
+
bunx @agentsfleet/orly init
|
|
44
|
+
|
|
45
|
+
# 3. confirm installed state matches what orly would write today
|
|
46
|
+
bunx @agentsfleet/orly doctor
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then commit the result. Teammates receive the rules on clone.
|
|
50
|
+
|
|
51
|
+
IMPORTANT: git does not clone hooks. Each checkout must run `orly init` once.
|
|
52
|
+
If a human reports that gates are not firing, this is the first thing to check.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 3. What the install writes
|
|
57
|
+
|
|
58
|
+
| Path | Contents | Owner |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `AGENTS.md` | your host auto-loads this | the user — never rewrite it |
|
|
61
|
+
| `AGENTS.orly.md` | generated rules: safety, dispatch router, lifecycle | orly — rewritten on update |
|
|
62
|
+
| `dispatch/*.md` | one rule page per kind of work | orly |
|
|
63
|
+
| `audits/*.sh` | the deterministic gates | orly |
|
|
64
|
+
| `docs/*.md` | the standards those rules cite | orly |
|
|
65
|
+
| `.claude/skills/`, `.agents/skills/`, `.opencode/skills/` | skills materialised per host | orly |
|
|
66
|
+
| `.githooks/` | pre-commit and pre-push, wired to `orly gate` | orly |
|
|
67
|
+
| `.oracle/orly.json` | packs, gate commands, install record | shared — edit the commands |
|
|
68
|
+
|
|
69
|
+
`orly init` seeds `.oracle/orly.json` with gate commands found in `Makefile` or
|
|
70
|
+
`package.json`. If commands are missing, ask the user which ones to add. Do not
|
|
71
|
+
invent check commands.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 4. Read order before you edit anything
|
|
76
|
+
|
|
77
|
+
1. `AGENTS.md` — the user's own rules. **These win on any conflict.**
|
|
78
|
+
2. `AGENTS.orly.md` — the generated rules and the dispatch router.
|
|
79
|
+
3. The `dispatch/*.md` page that matches the kind of file you are about to edit.
|
|
80
|
+
4. Any `docs/*.md` standard those pages cite.
|
|
81
|
+
|
|
82
|
+
Re-read step 3 for each new file kind you touch in the same task.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 5. The task lifecycle
|
|
87
|
+
|
|
88
|
+
The spec is a file on disk. Its directory is the status. Only the named stages
|
|
89
|
+
move it. `orly gate pr` reads where it ended up.
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
docs/v1/pending/ spec written, committed on main
|
|
93
|
+
│
|
|
94
|
+
│ CHORE(open) branch cut, baseline recorded, NO CODE until it commits
|
|
95
|
+
▼
|
|
96
|
+
docs/v1/active/ PLAN → EXECUTE → CONFORM → VERIFY → REVIEW → DOCUMENT → COMMIT
|
|
97
|
+
│
|
|
98
|
+
│ CHORE(close) session notes, orphan sweep
|
|
99
|
+
▼
|
|
100
|
+
docs/v1/done/ orly gate pr green → Pull Request opens
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Rules for you:
|
|
104
|
+
|
|
105
|
+
- Write the spec yourself before any code exists.
|
|
106
|
+
- Do not write code before `CHORE(open)` has committed.
|
|
107
|
+
- Do not skip a stage. The hooks run gates on every commit and push.
|
|
108
|
+
- A red gate returns you to EXECUTE. Fix the cause; do not retry unchanged.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 6. Failure decision table
|
|
113
|
+
|
|
114
|
+
| Situation | Do this |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `orly gate` fails a check | return to EXECUTE, fix the cause, re-run `orly gate` |
|
|
117
|
+
| same gate fails twice on the same cause | stop, report the gate name and output to the user |
|
|
118
|
+
| `orly init` refuses a file | orly did not write that file. Report it. Do **not** pass `--force` on your own initiative |
|
|
119
|
+
| hooks not firing | run `git config core.hooksPath` and confirm `orly init` was run in this checkout |
|
|
120
|
+
| gate command missing from `.oracle/orly.json` | ask the user for the command; do not invent one |
|
|
121
|
+
| `orly gate pr` red and the failure is genuinely inapplicable | ask the user; only they authorise `orly override` |
|
|
122
|
+
| gstack `/review` unavailable | continue; orly records the skipped review in the PR notes |
|
|
123
|
+
|
|
124
|
+
Overrides are recorded as an empty commit that rides into the Pull Request:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
bunx @agentsfleet/orly override <criterion> --reason "<why>"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Never record an override without an explicit instruction from the user, and
|
|
131
|
+
never with a placeholder reason.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 7. Hard rules
|
|
136
|
+
|
|
137
|
+
- Never rewrite `AGENTS.md`. orly adds one delimited pointer block; leave the
|
|
138
|
+
rest alone.
|
|
139
|
+
- Never edit or delete files under `.githooks/` or `audits/` to make a gate pass.
|
|
140
|
+
- Never pass `--force` or `--no-hooks` unless the user asks for it by name.
|
|
141
|
+
- Never disable a check in `.oracle/orly.json` to get green.
|
|
142
|
+
- When the user's `AGENTS.md` and orly's rules disagree, the user's file wins.
|
|
143
|
+
- A refused run changes nothing. Report the refusal; do not retry with flags.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 8. Command reference
|
|
148
|
+
|
|
149
|
+
| Command | Effect |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `orly init` | write rules, gates, skills, and hooks |
|
|
152
|
+
| `orly init --dry-run` | print what would be written; change nothing |
|
|
153
|
+
| `orly update` | re-materialise at a newer engine version |
|
|
154
|
+
| `orly update --with <pack>` | add an opt-in pack, recorded for every clone |
|
|
155
|
+
| `orly gate` | run declared checks in order, stopping at the first failure |
|
|
156
|
+
| `orly gate pr` | the Pull Request criteria check |
|
|
157
|
+
| `orly override <criterion> --reason <why>` | record a gate exception (user-authorised only) |
|
|
158
|
+
| `orly doctor` | diff installed state against what orly would write today |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 9. Packs
|
|
163
|
+
|
|
164
|
+
Nine language packs are selected from file extensions in the user's source.
|
|
165
|
+
orly scans four directories deep and skips `node_modules`, `target`, `.venv`,
|
|
166
|
+
and other dependency trees.
|
|
167
|
+
|
|
168
|
+
`.zig` → `language.zig` · `.ts`/`.tsx` → `language.typescript` ·
|
|
169
|
+
`.js`/`.jsx` → `language.javascript` · `.rs` → `language.rust` ·
|
|
170
|
+
`.go` → `language.go` · `.py` → `language.python` · `.sh` → `language.shell` ·
|
|
171
|
+
`.mdx` → `language.mdx` · `.sql` → `domain.sql`
|
|
172
|
+
|
|
173
|
+
Seven install in every repository: `universal.authoring`, `domain.http`,
|
|
174
|
+
`domain.auth`, `domain.documentation`, `domain.changelog`,
|
|
175
|
+
`workflow.specifications`, `workflow.skills`.
|
|
176
|
+
|
|
177
|
+
Three install only when `.oracle/orly.json` names them: `workflow.governance`
|
|
178
|
+
(editing orly's own rules), `product.agentsfleet` (agentsfleet-specific audits
|
|
179
|
+
and docs), `persona.indy` (tone and address handles only, no files).
|
|
180
|
+
|
|
181
|
+
Skills provided by `workflow.skills`, per repository: `orly-spec-new`,
|
|
182
|
+
`orly-babysit-prs`, `orly-write-unit-test`, `orly-write-integration-test`.
|
|
183
|
+
General-purpose skills come from gstack, per agent host — orly neither installs
|
|
184
|
+
nor manages gstack.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 10. Working on orly itself
|
|
189
|
+
|
|
190
|
+
Only relevant if the task is a change to this repository.
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
git clone git@github.com:agentsfleet/orly.git && cd orly
|
|
194
|
+
git config core.hooksPath .githooks
|
|
195
|
+
bun install --frozen-lockfile
|
|
196
|
+
make audit
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- `make audit` is the bar: typecheck and unit tests, render determinism, and
|
|
200
|
+
gate fixtures (each gate proved against one passing and one failing case).
|
|
201
|
+
Runs in parallel, under half a minute.
|
|
202
|
+
- Coverage floor is 90% lines. Below it, the workflow fails.
|
|
203
|
+
- `make install-evals` runs real installs into throwaway repositories. Run on
|
|
204
|
+
demand; CI runs them on every pull request and release.
|
|
205
|
+
- Render orly's own rules with `bin/orly update --no-hooks`. The `--no-hooks`
|
|
206
|
+
flag is required here because this checkout hand-wrote its `.githooks/`.
|
|
207
|
+
- Release: merge to `main` with a new `package.json` version. That publishes,
|
|
208
|
+
tags, and cuts the GitHub release. No second command.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 11. Self-check prompt
|
|
213
|
+
|
|
214
|
+
After install, verify your own understanding by answering this from the
|
|
215
|
+
repository, without guessing:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Read AGENTS.orly.md. Name which of the last ten commits would have tripped a
|
|
219
|
+
gate, and the rule that caught each one.
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
If you cannot answer it from the installed files, re-read section 4 before
|
|
223
|
+
continuing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentsfleet/orly",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary — for any coding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"src/",
|
|
52
52
|
"registry.json",
|
|
53
53
|
"AGENTS.md",
|
|
54
|
+
"llms.txt",
|
|
54
55
|
"SOUL.md"
|
|
55
56
|
],
|
|
56
57
|
"publishConfig": {
|
package/src/criteria.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
openQuestions, productClarity, specBaseline, specDeferrals, specDimensions,
|
|
9
9
|
specGate, specMoved, specOrdering,
|
|
10
10
|
} from "./criteria_spec";
|
|
11
|
+
import { documentationSurfaces, scanSurfaces } from "./doc_rules";
|
|
11
12
|
import { isObject, JsonObject, objectValue, OrlyError, RulesModel } from "./model";
|
|
12
13
|
import { readConfigSync, RepoConfig } from "./config";
|
|
13
14
|
import { classifyBranch, SurfaceReport } from "./surfaces";
|
|
@@ -30,6 +31,10 @@ const GIT_TREE = "git.tree";
|
|
|
30
31
|
const GIT_PUSHED = "git.pushed";
|
|
31
32
|
const REPO_CONFIG = "repo.config";
|
|
32
33
|
const DOCS_UPDATED = "docs.updated";
|
|
34
|
+
const DOCS_LANGUAGE = "docs.language";
|
|
35
|
+
// How many findings a report names before it stops listing them. A criterion
|
|
36
|
+
// line is a summary, not a linter dump.
|
|
37
|
+
const NAMED_FINDINGS = 3;
|
|
33
38
|
|
|
34
39
|
const FAST_TIER = "fast";
|
|
35
40
|
const SLOW_TIER = "slow";
|
|
@@ -44,7 +49,7 @@ const SLOW_COMMANDS = ["verify.integration", "verify.memory"];
|
|
|
44
49
|
// pr = can this ship (whole-branch checks + the slow suites).
|
|
45
50
|
export function criteriaFor(gate: string, context: CriterionContext): Criterion[] {
|
|
46
51
|
if (gate === "work") return [gitBranch(), gitTree(), repositoryConfig()];
|
|
47
|
-
if (gate === "verify") return [specDimensions(), ...commandCriteria(context, FAST_TIER)];
|
|
52
|
+
if (gate === "verify") return [specDimensions(), docsLanguage(), ...commandCriteria(context, FAST_TIER)];
|
|
48
53
|
if (gate === "pr") {
|
|
49
54
|
return [
|
|
50
55
|
gitTree(), gitPushed(), specGate(), openQuestions(), productClarity(), specDimensions(),
|
|
@@ -139,6 +144,34 @@ function docsUpdated(): Criterion {
|
|
|
139
144
|
});
|
|
140
145
|
}
|
|
141
146
|
|
|
147
|
+
// docs/DOCUMENTATION_RULES.md, read back against the pages orly renders here.
|
|
148
|
+
// The scope rule is the document's own: any documentation orly writes or
|
|
149
|
+
// renders, README.md included.
|
|
150
|
+
//
|
|
151
|
+
// REPORTING ONLY, and deliberately so. The rule document landed against a
|
|
152
|
+
// corpus that predates it, and the corpus does not obey it yet: this
|
|
153
|
+
// repository alone carries hundreds of pre-existing findings. A red gate on
|
|
154
|
+
// day one would be switched off by the end of the week, and grandfathering
|
|
155
|
+
// them into a baseline file would be a list of chores wearing a gate's badge.
|
|
156
|
+
// So the check reports what it found and leaves the verdict green until a
|
|
157
|
+
// human has decided how the debt gets paid. Partial mechanization stated
|
|
158
|
+
// honestly beats a red that means nothing (the same call audits/doc-read.sh
|
|
159
|
+
// makes when its record is missing). Flipping this to a gate is a one-line
|
|
160
|
+
// change: return ok on `findings.length === 0`.
|
|
161
|
+
function docsLanguage(): Criterion {
|
|
162
|
+
return criterion(DOCS_LANGUAGE, (context) => {
|
|
163
|
+
const surfaces = documentationSurfaces(context.root);
|
|
164
|
+
if (surfaces.length === 0) return { ok: true, detail: "no documentation surface here" };
|
|
165
|
+
const findings = scanSurfaces(context.root, surfaces);
|
|
166
|
+
if (findings.length === 0) return { ok: true, detail: `${surfaces.length} page(s) clean` };
|
|
167
|
+
const named = findings.slice(0, NAMED_FINDINGS).map((finding) => `${finding.file}:${finding.line} ${finding.code}`);
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
detail: `${findings.length} finding(s) across ${surfaces.length} page(s), reporting only: ${named.join(", ")}`,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
142
175
|
// The branch is classified once per inspection and cached on the context.
|
|
143
176
|
function report(context: CriterionContext, surfaces: JsonObject | undefined): SurfaceReport {
|
|
144
177
|
context.surfaces ??= classifyBranch(context.root, surfaces);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { DOC_DASH, DOC_HEADING, DOC_PARAGRAPH, DOC_SENTENCE, DOC_WORD, documentationSurfaces, scanDocument } from "./doc_rules";
|
|
6
|
+
|
|
7
|
+
const ROOT = resolve(import.meta.dir, "..");
|
|
8
|
+
const SLOP_FIXTURE = "fixtures/doc-slop.md";
|
|
9
|
+
const PAGE = "page.md";
|
|
10
|
+
|
|
11
|
+
// The pass fixture: one clean page written to the rules it is checked against.
|
|
12
|
+
// Every line here is prose the check must leave alone, so a false positive
|
|
13
|
+
// anywhere in the reader turns this test red.
|
|
14
|
+
const CLEAN = `# A clean page
|
|
15
|
+
|
|
16
|
+
orly writes this page. The check reads it and finds nothing.
|
|
17
|
+
|
|
18
|
+
## What it leaves alone
|
|
19
|
+
|
|
20
|
+
Command names keep their banned word: run \`make harness-verify\` first.
|
|
21
|
+
A link keeps its text and drops its [target](https://example.com/execute).
|
|
22
|
+
|
|
23
|
+
| leverage | robust |
|
|
24
|
+
|---|---|
|
|
25
|
+
| a table cell is exempt | so is this |
|
|
26
|
+
|
|
27
|
+
\`\`\`bash
|
|
28
|
+
# fenced code is exempt: execute the robust pipeline
|
|
29
|
+
orly gate verify
|
|
30
|
+
\`\`\`
|
|
31
|
+
|
|
32
|
+
The EXECUTE stage is a name, not a verb. A rule may quote “leverage” to ban it.
|
|
33
|
+
|
|
34
|
+
<!-- lint-ignore: DOCWORD — this paragraph names the banned words to ban them -->
|
|
35
|
+
Also banned: delve, foster, elevate, harness, tapestry, realm.
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
function codes(text: string): string[] {
|
|
39
|
+
return scanDocument(PAGE, text).map((finding) => finding.code);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("scanDocument", () => {
|
|
43
|
+
test("passes a clean page", () => {
|
|
44
|
+
expect(scanDocument(PAGE, CLEAN)).toEqual([]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("fails the slop fixture on every mechanical rule", () => {
|
|
48
|
+
const found = codes(readFileSync(join(ROOT, SLOP_FIXTURE), "utf8"));
|
|
49
|
+
|
|
50
|
+
expect(found).toContain(DOC_WORD);
|
|
51
|
+
expect(found).toContain(DOC_DASH);
|
|
52
|
+
expect(found).toContain(DOC_SENTENCE);
|
|
53
|
+
expect(found).toContain(DOC_PARAGRAPH);
|
|
54
|
+
expect(found).toContain(DOC_HEADING);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("names the banned word it found", () => {
|
|
58
|
+
const findings = scanDocument(PAGE, "# Page\n\nWe leverage it.\n");
|
|
59
|
+
|
|
60
|
+
expect(findings).toHaveLength(1);
|
|
61
|
+
expect(findings[0]?.message).toContain("leverage");
|
|
62
|
+
expect(findings[0]?.line).toBe(3);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("counts an over-budget page by its overage", () => {
|
|
66
|
+
const findings = scanDocument(PAGE, "# Page\n\nOne — two — three.\n").filter((finding) => finding.code === DOC_DASH);
|
|
67
|
+
|
|
68
|
+
expect(findings[0]?.weight).toBe(2);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("reads a lowercase product name as a sentence start", () => {
|
|
72
|
+
const paragraph = "# Page\n\nOne here. orly two. bun three. git four.\n";
|
|
73
|
+
|
|
74
|
+
expect(codes(paragraph)).toEqual([DOC_PARAGRAPH]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("leaves a hyphenated command name alone", () => {
|
|
78
|
+
expect(codes("# Page\n\nRun provision-env-1password before the hook.\n")).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("counts a code span as one word, not as a hole", () => {
|
|
82
|
+
const findings = scanDocument(PAGE, "# Page\n\nThe hooks run on every commit and push. `orly gate pr` then refuses it.\n");
|
|
83
|
+
|
|
84
|
+
expect(findings).toEqual([]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("requires exactly one level-one heading", () => {
|
|
88
|
+
expect(codes("## Only a level two\n\nProse.\n")).toContain(DOC_HEADING);
|
|
89
|
+
expect(codes("# One\n\nProse.\n\n# Two\n\nMore prose.\n")).toContain(DOC_HEADING);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("documentationSurfaces", () => {
|
|
94
|
+
test("covers the readme and the pages orly renders", () => {
|
|
95
|
+
const surfaces = documentationSurfaces(ROOT);
|
|
96
|
+
|
|
97
|
+
expect(surfaces).toContain("README.md");
|
|
98
|
+
expect(surfaces).toContain("docs/DOCUMENTATION_RULES.md");
|
|
99
|
+
expect(surfaces.every((relative) => relative.endsWith(".md"))).toBeTrue();
|
|
100
|
+
});
|
|
101
|
+
});
|