@agentsfleet/orly 0.6.2 → 0.6.3
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 +4 -1
- package/audits/rule-ledger-lib.sh +1 -0
- package/core/operating-model.md +5 -2
- package/docs/DOCUMENTATION_RULES.md +6 -5
- package/package.json +1 -1
- package/src/config.test.ts +27 -0
- package/src/config.ts +35 -7
package/AGENTS.md
CHANGED
|
@@ -20,7 +20,7 @@ You are this repository's coding agent: deterministic, autonomous, command-line-
|
|
|
20
20
|
|
|
21
21
|
MacBook. Languages: Python, Go, Rust, TypeScript, Zig. Tooling: `mise` first, `brew` fallback. Forges: `gh`/`glab`. Commit identity from `git config`, never here.
|
|
22
22
|
|
|
23
|
-
Dates: `MMM DD, YYYY: HH:MM AM/PM`. Filenames: `{MMM}_{DD}_{HH_MM}`.
|
|
23
|
+
Dates in session notes and log entries: `MMM DD, YYYY: HH:MM AM/PM`. Filenames: `{MMM}_{DD}_{HH_MM}`. Published pages use ISO 8601 (`DOC-23`).
|
|
24
24
|
|
|
25
25
|
**Banned vocabulary:** "contract" and "phase". Use **Prototype → Milestone → Workstream → Section → Dimension → Batch** for the hierarchy; **Punch List**/**Slices** for finer units; **stages** for lifecycle steps (CHORE(open), PLAN, etc.); **rules** / **operating model** for what AGENTS.md enforces. Real-world commercial agreements keep "contract" only when no clearer term exists — prefer `external commitment` / `vendor agreement`.
|
|
26
26
|
|
|
@@ -28,10 +28,13 @@ Dates: `MMM DD, YYYY: HH:MM AM/PM`. Filenames: `{MMM}_{DD}_{HH_MM}`.
|
|
|
28
28
|
|
|
29
29
|
**Pre-send self-checks (invariant).** Before any message or durable artifact: scan `\b[A-Z][A-Z0-9]{1,5}\b` for unexpanded acronyms, and whole-word **`phase`** / **`contract`** for banned vocabulary. Skipping = `ACRONYM CHECK: SKIPPED per user override (reason: ...)` / `BANNED-VOCAB CHECK: SKIPPED per user override (reason: ...)`; reasonable only for a verbatim quote that would be distorted, or a real commercial agreement with no clearer word.
|
|
30
30
|
|
|
31
|
+
|
|
31
32
|
**Evidence invariant (pre-send).** Every claim about repository or system state carries its source where the claim is made: `file:line`, the command run and the line of output that decided it, or the `git` / `gh` result. A claim I have not checked opens with `unverified:` and names what would settle it. Numbers name the command that produced them. "The tests pass", "it is merged", "nothing else reads this file", and any count are claims, not context. Skipping = `EVIDENCE CHECK: SKIPPED per user override (reason: ...)`.
|
|
32
33
|
|
|
33
34
|
**Why it is an invariant, not a preference.** An unchecked claim reads exactly like a checked one. Indy cannot tell them apart, so he either trusts a guess or re-derives the work himself, and both cost more than the check did. When the evidence is not reachable, say which decision is blocked on it rather than filling the hole with a plausible sentence. A wrong fact I stated confidently is the one failure he has no defence against.
|
|
34
35
|
|
|
36
|
+
**Self-repair boundary (invariant).** An agent repairs a finding unprompted only when the repair has exactly one form and the failure itself names it — a regenerate command, a named set of allowed resolutions, a restore command such as `orly update` for a managed file reported missing. Everything else is reported, not repaired. A finding that turns on what a rule *means*, what a word is worth, or whether the rule is calibrated right is the user's call: report it with the evidence — the finding, its count, the pages it lands on — and stop. Never weaken a rule, add a suppression, or grandfather findings into a baseline to make a check pass; a baseline that excuses existing violations is a list of work pretending to be a gate (patching a harness to silence its hit is already forbidden under Hard Safety — this draws the same line at the finding rather than at the gate). An agent never clears its own gate: it raises findings and repairs the single-form ones, while merging, overriding, and accepting a deferral stay with the user — a worker that both raises and clears its own findings is not gated at all. Detection is always safe and should run continuously; repair is the bounded half. `orly gate`'s `docs.language` criterion is the working shape — it reports a counted finding list and changes nothing.
|
|
37
|
+
|
|
35
38
|
## Documentation voice
|
|
36
39
|
|
|
37
40
|
Published docs and OpenAPI prose read `docs/DOCUMENTATION_RULES.md` first via `write_documentation`.
|
package/core/operating-model.md
CHANGED
|
@@ -15,7 +15,7 @@ You are this repository's coding agent: deterministic, autonomous, command-line-
|
|
|
15
15
|
|
|
16
16
|
MacBook. Languages: Python, Go, Rust, TypeScript, Zig. Tooling: `mise` first, `brew` fallback. Forges: `gh`/`glab`. Commit identity from `git config`, never here.
|
|
17
17
|
|
|
18
|
-
Dates: `MMM DD, YYYY: HH:MM AM/PM`. Filenames: `{MMM}_{DD}_{HH_MM}`.
|
|
18
|
+
Dates in session notes and log entries: `MMM DD, YYYY: HH:MM AM/PM`. Filenames: `{MMM}_{DD}_{HH_MM}`. Published pages use ISO 8601 (`DOC-23`).
|
|
19
19
|
|
|
20
20
|
**Banned vocabulary:** "contract" and "phase". Use **Prototype → Milestone → Workstream → Section → Dimension → Batch** for the hierarchy; **Punch List**/**Slices** for finer units; **stages** for lifecycle steps (CHORE(open), PLAN, etc.); **rules** / **operating model** for what AGENTS.md enforces. Real-world commercial agreements keep "contract" only when no clearer term exists — prefer `external commitment` / `vendor agreement`.
|
|
21
21
|
|
|
@@ -23,10 +23,13 @@ Dates: `MMM DD, YYYY: HH:MM AM/PM`. Filenames: `{MMM}_{DD}_{HH_MM}`.
|
|
|
23
23
|
|
|
24
24
|
**Pre-send self-checks (invariant).** Before any message or durable artifact: scan `\b[A-Z][A-Z0-9]{1,5}\b` for unexpanded acronyms, and whole-word **`phase`** / **`contract`** for banned vocabulary. Skipping = `ACRONYM CHECK: SKIPPED per user override (reason: ...)` / `BANNED-VOCAB CHECK: SKIPPED per user override (reason: ...)`; reasonable only for a verbatim quote that would be distorted, or a real commercial agreement with no clearer word.
|
|
25
25
|
|
|
26
|
+
<!-- oracle-packs:end -->
|
|
27
|
+
|
|
26
28
|
**Evidence invariant (pre-send).** Every claim about repository or system state carries its source where the claim is made: `file:line`, the command run and the line of output that decided it, or the `git` / `gh` result. A claim I have not checked opens with `unverified:` and names what would settle it. Numbers name the command that produced them. "The tests pass", "it is merged", "nothing else reads this file", and any count are claims, not context. Skipping = `EVIDENCE CHECK: SKIPPED per user override (reason: ...)`.
|
|
27
29
|
|
|
28
30
|
**Why it is an invariant, not a preference.** An unchecked claim reads exactly like a checked one. Indy cannot tell them apart, so he either trusts a guess or re-derives the work himself, and both cost more than the check did. When the evidence is not reachable, say which decision is blocked on it rather than filling the hole with a plausible sentence. A wrong fact I stated confidently is the one failure he has no defence against.
|
|
29
|
-
|
|
31
|
+
|
|
32
|
+
**Self-repair boundary (invariant).** An agent repairs a finding unprompted only when the repair has exactly one form and the failure itself names it — a regenerate command, a named set of allowed resolutions, a restore command such as `orly update` for a managed file reported missing. Everything else is reported, not repaired. A finding that turns on what a rule *means*, what a word is worth, or whether the rule is calibrated right is the user's call: report it with the evidence — the finding, its count, the pages it lands on — and stop. Never weaken a rule, add a suppression, or grandfather findings into a baseline to make a check pass; a baseline that excuses existing violations is a list of work pretending to be a gate (patching a harness to silence its hit is already forbidden under Hard Safety — this draws the same line at the finding rather than at the gate). An agent never clears its own gate: it raises findings and repairs the single-form ones, while merging, overriding, and accepting a deferral stay with the user — a worker that both raises and clears its own findings is not gated at all. Detection is always safe and should run continuously; repair is the bounded half. `orly gate`'s `docs.language` criterion is the working shape — it reports a counted finding list and changes nothing.
|
|
30
33
|
|
|
31
34
|
## Documentation voice
|
|
32
35
|
|
|
@@ -164,7 +164,7 @@ not 36 defects. They keep every other rule.
|
|
|
164
164
|
the retry count and delay, what data is lost, and what survives.
|
|
165
165
|
22. **DOC-22 — Audience boundary.** User pages state guarantees, not internals.
|
|
166
166
|
Operator pages state exact dependencies and versions.
|
|
167
|
-
23. **DOC-23 — Units and time.** Numbers carry units. Dates use ISO 8601. Times
|
|
167
|
+
23. **DOC-23 — Units and time.** Numbers carry units. Dates on a published page use ISO 8601. Times
|
|
168
168
|
use Coordinated Universal Time (UTC).
|
|
169
169
|
24. **DOC-24 — Stable errors.** Every product error has a stable Identifier (ID)
|
|
170
170
|
and an anchor that explains why it happened, how to fix it, and how to
|
|
@@ -195,10 +195,11 @@ not 36 defects. They keep every other rule.
|
|
|
195
195
|
|
|
196
196
|
## Enforcement
|
|
197
197
|
|
|
198
|
-
33. **DOC-33 — Mechanical checks.**
|
|
199
|
-
|
|
200
|
-
matter,
|
|
201
|
-
|
|
198
|
+
33. **DOC-33 — Mechanical checks.** `orly gate verify` checks banned words,
|
|
199
|
+
long sentences, long paragraphs, the em-dash budget, and heading order.
|
|
200
|
+
Front matter, section order, command blocks without output, options
|
|
201
|
+
without defaults, and procedures in tables are rules a reader enforces;
|
|
202
|
+
no check reads them yet. This list names what runs, not what is wished.
|
|
202
203
|
34. **DOC-34 — Review checks.** Review checks one-idea sentences, pronoun
|
|
203
204
|
clarity, resource lifecycle, failure behavior, implementation leaks, and
|
|
204
205
|
meaning preserved by simplification.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentsfleet/orly",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
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",
|
package/src/config.test.ts
CHANGED
|
@@ -182,6 +182,33 @@ describe("managedDrift", () => {
|
|
|
182
182
|
expect(findings[1]).toContain("zzz.md");
|
|
183
183
|
expect(findings[0]).toContain("orly update");
|
|
184
184
|
});
|
|
185
|
+
|
|
186
|
+
// The failure this pair exists for: agentsfleet ignored .claude/skills/ while
|
|
187
|
+
// .oracle/orly.json declared four files under it managed. Every local
|
|
188
|
+
// checkout passed `orly doctor` because the files sat on disk, and CI failed
|
|
189
|
+
// against a clone that never received them.
|
|
190
|
+
test("a managed file git refuses to track is drift, even though it is present", async () => {
|
|
191
|
+
const root = scratch();
|
|
192
|
+
Bun.spawnSync(["git", "init"], { cwd: root });
|
|
193
|
+
mkdirSync(join(root, ".claude/skills/orly-spec-new"), { recursive: true });
|
|
194
|
+
writeFileSync(join(root, ".claude/skills/orly-spec-new/SKILL.md"), "skill\n");
|
|
195
|
+
writeFileSync(join(root, ".gitignore"), ".claude/skills/\n");
|
|
196
|
+
const config = { ...(await seedConfig(root)), managed: [".claude/skills/orly-spec-new/SKILL.md"] };
|
|
197
|
+
const findings = managedDrift(root, config);
|
|
198
|
+
expect(findings).toHaveLength(1);
|
|
199
|
+
expect(findings[0]).toContain("ignored by git");
|
|
200
|
+
expect(findings[0]).toContain(".claude/skills/orly-spec-new/SKILL.md");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("the same file reports nothing once the ignore is scoped to spare it", async () => {
|
|
204
|
+
const root = scratch();
|
|
205
|
+
Bun.spawnSync(["git", "init"], { cwd: root });
|
|
206
|
+
mkdirSync(join(root, ".claude/skills/orly-spec-new"), { recursive: true });
|
|
207
|
+
writeFileSync(join(root, ".claude/skills/orly-spec-new/SKILL.md"), "skill\n");
|
|
208
|
+
writeFileSync(join(root, ".gitignore"), ".claude/skills/*\n!.claude/skills/orly-spec-new/\n");
|
|
209
|
+
const config = { ...(await seedConfig(root)), managed: [".claude/skills/orly-spec-new/SKILL.md"] };
|
|
210
|
+
expect(managedDrift(root, config)).toEqual([]);
|
|
211
|
+
});
|
|
185
212
|
});
|
|
186
213
|
|
|
187
214
|
describe("writeConfig", () => {
|
package/src/config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, extname, join, relative } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { assertWritableInside, isObject, isString, JsonObject, objectValue, OrlyError, readJsonObject, RulesModel, stringArray } from "./model";
|
|
5
|
+
import { UNSCOPED_ENVIRONMENT } from "./git_env";
|
|
5
6
|
import { validateCommands, validateSurfaces } from "./validation";
|
|
6
7
|
|
|
7
8
|
const ORACLE_DIRECTORY = ".oracle";
|
|
@@ -117,14 +118,41 @@ export function staleVersion(config: RepoConfig, installedVersion: string): stri
|
|
|
117
118
|
return `ruleset was installed by orly ${config.orly_version || "an unrecorded version"}, installed engine is ${installedVersion} — run \`orly update\``;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
//
|
|
121
|
+
const PIPE_OUTPUT = "pipe";
|
|
122
|
+
|
|
123
|
+
// Which of these paths git refuses to track. `check-ignore` prints one ignored
|
|
124
|
+
// path per line and exits 1 when none match, which is not an error here.
|
|
125
|
+
function ignoredPaths(targetRoot: string, paths: string[]): Set<string> {
|
|
126
|
+
if (paths.length === 0) return new Set();
|
|
127
|
+
const result = Bun.spawnSync(["git", "check-ignore", "--", ...paths], {
|
|
128
|
+
cwd: targetRoot,
|
|
129
|
+
env: UNSCOPED_ENVIRONMENT,
|
|
130
|
+
stdout: PIPE_OUTPUT,
|
|
131
|
+
stderr: PIPE_OUTPUT,
|
|
132
|
+
});
|
|
133
|
+
const printed = result.stdout.toString().trim();
|
|
134
|
+
if (printed.length === 0) return new Set();
|
|
135
|
+
return new Set(printed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// A managed file that is gone, or one git will not carry. Edits are not drift:
|
|
139
|
+
// the tree is a git repository, so `git diff` shows a hand edit and shows
|
|
140
|
+
// `orly update` replacing it, both before either is committed.
|
|
141
|
+
//
|
|
142
|
+
// An ignored file is drift even though it sits right there on disk. orly wrote
|
|
143
|
+
// it, the local checkout gates on it, and a clone never receives it — so the
|
|
144
|
+
// author passes and everybody else fails, which is the one failure the install
|
|
145
|
+
// record exists to rule out.
|
|
123
146
|
export function managedDrift(targetRoot: string, config: RepoConfig): string[] {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
147
|
+
const missing = config.managed.filter((relativePath) => !existsSync(join(targetRoot, relativePath)));
|
|
148
|
+
const present = config.managed.filter((relativePath) => !missing.includes(relativePath));
|
|
149
|
+
const ignored = ignoredPaths(targetRoot, present);
|
|
150
|
+
return [
|
|
151
|
+
...missing.map((relativePath) => `managed file is missing: ${relativePath} — run \`orly update\` to restore it`),
|
|
152
|
+
...present
|
|
153
|
+
.filter((relativePath) => ignored.has(relativePath))
|
|
154
|
+
.map((relativePath) => `managed file is ignored by git: ${relativePath} — orly wrote it and a clone will not receive it; scope the .gitignore rule that excludes it`),
|
|
155
|
+
].sort();
|
|
128
156
|
}
|
|
129
157
|
|
|
130
158
|
// Key order is fixed so a rewrite produces a reviewable diff: orly's own two
|