@agentsfleet/orly 0.10.5 → 0.10.7
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 +6 -6
- package/README.md +26 -8
- package/audits/agents-md.md +7 -3
- package/audits/doc-read.sh +97 -16
- package/audits/spec-template.sh +30 -113
- package/audits/spec-template.ts +193 -0
- package/core/operating-model.md +6 -6
- package/dispatch/lifecycle.md +98 -42
- package/dispatch/verify.md +30 -48
- package/dispatch/write_spec.md +56 -82
- package/docs/ORLY_ARCHITECTURE.md +13 -3
- package/docs/RULE_ENFORCEMENT.md +2 -2
- package/docs/TEMPLATE.md +82 -77
- package/docs/VERIFY_TIERS.md +20 -26
- package/llms.txt +17 -12
- package/package.json +1 -1
- package/registry.json +4 -0
- package/skills/orly-spec-new/SKILL.md +3 -1
- package/skills/orly-write-integration-test/SKILL.md +1 -1
- package/skills/orly-write-unit-test/SKILL.md +1 -1
- package/src/cli.ts +14 -16
- package/src/criteria.test.ts +47 -6
- package/src/criteria.ts +30 -49
- package/src/criteria_spec.ts +53 -6
- package/src/criteria_support.ts +5 -4
- package/src/gates.test.ts +2 -2
- package/src/gates_spec.test.ts +40 -1
- package/src/gates_test_support.ts +18 -4
- package/src/install.ts +16 -81
- package/src/render.test.ts +5 -7
- package/src/setup.test.ts +116 -0
- package/src/spec_baseline.test.ts +96 -0
- package/src/spec_template.test.ts +109 -0
- package/src/spec_template_io.test.ts +97 -0
- package/src/validation.ts +9 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, normalize, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
type MarkdownElement = { type?: unknown; props: { children?: MarkdownNode[] } };
|
|
5
|
+
type MarkdownNode = string | MarkdownElement;
|
|
6
|
+
type Section = { title: string; nodes: MarkdownNode[] };
|
|
7
|
+
type Commands = Record<string, string[][]>;
|
|
8
|
+
|
|
9
|
+
const CONFIG_PATH = ".oracle/orly.json";
|
|
10
|
+
const SPEC_LINE_LIMIT = 320;
|
|
11
|
+
const BACKTICK = "`";
|
|
12
|
+
const EMPTY = "";
|
|
13
|
+
const PIPE_OUTPUT = "pipe";
|
|
14
|
+
const TABLE_ROW = "tr";
|
|
15
|
+
const TEST_HEADING = /^Test Specification/;
|
|
16
|
+
const RUBRIC_HEADING = /^Acceptance (Rubric|Criteria)/;
|
|
17
|
+
const REQUIRED = [
|
|
18
|
+
/^Overview/, /^PR Intent/, /^Implementing agent/, /^Files Changed/, /^Applicable Rules/,
|
|
19
|
+
/^Applicable Gates/, /^(Prior-Art|Reference Implementation)/, /^Sections/,
|
|
20
|
+
/^Interfaces/, /^Failure Modes/, /^Invariants/, /^Metrics.*Observability/,
|
|
21
|
+
TEST_HEADING, RUBRIC_HEADING, /^Dead Code Sweep/, /^Out of Scope/,
|
|
22
|
+
/^Product Clarity/, /^(Decomposition|Alternatives)/, /^Discovery/,
|
|
23
|
+
];
|
|
24
|
+
const RESIDUE = /<!--\s*tpl:|\{\{fill:|\{unresolved_[^}]+\}|path\/to\/file\.ext|path\/to\/spec_or_doc\.md|test_<short_name>|\{one-line reason\}|\{Slice title\}|\{smallest verifiable behaviour\}|\{one-line behavioural claim\}/;
|
|
25
|
+
const STAGED_MODES = new Set(["--staged", "staged"]);
|
|
26
|
+
const EXPECTED_COLUMN = "Expected";
|
|
27
|
+
|
|
28
|
+
function sectionsOf(text: string): Section[] {
|
|
29
|
+
const sections: Section[] = [];
|
|
30
|
+
const document = Bun.markdown.react(text) as MarkdownElement;
|
|
31
|
+
for (const node of children(document)) {
|
|
32
|
+
if (!isString(node) && node.type === "h2") sections.push({ title: textOf(node), nodes: [] });
|
|
33
|
+
else if (sections.length) sections[sections.length - 1]!.nodes.push(node);
|
|
34
|
+
}
|
|
35
|
+
return sections;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function section(sections: Section[], heading: RegExp): MarkdownNode[] {
|
|
39
|
+
return sections.find((entry) => heading.test(entry.title))?.nodes ?? [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function children(node: MarkdownNode): MarkdownNode[] {
|
|
43
|
+
return isString(node) ? [] : node.props.children ?? [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function elements(nodes: MarkdownNode[], type: string): MarkdownElement[] {
|
|
47
|
+
return nodes.filter((node): node is MarkdownElement => !isString(node) && node.type === type);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function textOf(node: MarkdownNode): string {
|
|
51
|
+
if (isString(node)) return node;
|
|
52
|
+
const text = children(node).map(textOf).join(EMPTY);
|
|
53
|
+
return node.type === "code" ? `${BACKTICK}${text}${BACKTICK}` : text;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function table(nodes: MarkdownNode[]): { columns: string[]; rows: string[][] } {
|
|
57
|
+
const groups = elements(nodes, "table").flatMap(children);
|
|
58
|
+
const headers = elements(groups, "thead").flatMap(children);
|
|
59
|
+
const bodies = elements(groups, "tbody").flatMap(children);
|
|
60
|
+
return {
|
|
61
|
+
columns: elements(headers, TABLE_ROW).flatMap(children).map((node) => textOf(node).trim()),
|
|
62
|
+
rows: elements(bodies, TABLE_ROW).map((row) => children(row).map((node) => textOf(node).trim())),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function testMappings(sections: Section[]): string[] {
|
|
67
|
+
const errors: string[] = [];
|
|
68
|
+
const items = elements(section(sections, /^Sections/), "ul").flatMap(children).map(textOf);
|
|
69
|
+
const declared = items.filter((item) => item.startsWith("Dimension "));
|
|
70
|
+
const dimensions = declared.flatMap((item) => [...item.matchAll(/^Dimension (\d+\.\d+)\b[^\n]*?→ Test `([^`]+)`/g)]);
|
|
71
|
+
if (!dimensions.length || dimensions.length !== declared.length) errors.push("every Dimension must name its proof with → Test `name`");
|
|
72
|
+
const ids = dimensions.map((match) => match[1]);
|
|
73
|
+
if (new Set(ids).size !== ids.length) errors.push("Dimension identifiers must be unique");
|
|
74
|
+
const tests = table(section(sections, TEST_HEADING));
|
|
75
|
+
const tier = tests.columns.indexOf("Tier");
|
|
76
|
+
const name = tests.columns.indexOf("Test");
|
|
77
|
+
const assertion = tests.columns.findIndex((column) => column.startsWith("Asserts"));
|
|
78
|
+
const id = tests.columns.indexOf("Dimension");
|
|
79
|
+
if ([tier, name, assertion, id].some((index) => index < 0)) return [...errors, "Test Specification needs Dimension, Tier, Test, and Asserts columns"];
|
|
80
|
+
for (const dimension of dimensions) {
|
|
81
|
+
if (!tests.rows.some((row) => row[id] === dimension[1] && row[name]?.replaceAll(BACKTICK, EMPTY) === dimension[2] && row[tier] && row[assertion])) {
|
|
82
|
+
errors.push(`Dimension ${dimension[1]} has no matching tiered test and assertion`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const row of tests.rows) {
|
|
86
|
+
if (row[id] && !ids.includes(row[id])) errors.push(`test row references unknown Dimension ${row[id]}`);
|
|
87
|
+
}
|
|
88
|
+
return errors;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readFirst(sections: Section[], exists: (path: string) => boolean): string[] {
|
|
92
|
+
const items = elements(section(sections, /^Implementing agent/), "ol").flatMap(children).map(textOf);
|
|
93
|
+
const references = items.flatMap((item) => [...item.matchAll(/^(?:`([^`]+)`|(https?:\/\/\S+))/g)]);
|
|
94
|
+
if (!references.length) return ["read-first section needs existing repository paths or URLs"];
|
|
95
|
+
const errors: string[] = [];
|
|
96
|
+
for (const reference of references) {
|
|
97
|
+
const path = reference[1] ?? reference[2] ?? EMPTY;
|
|
98
|
+
if (/^https?:\/\//.test(path)) continue;
|
|
99
|
+
const normalized = normalize(path.split("#")[0] ?? EMPTY);
|
|
100
|
+
if (isAbsolute(normalized) || normalized.startsWith("..") || normalized.includes("{")) {
|
|
101
|
+
errors.push(`read-first pointer must name a repository file: ${path}`);
|
|
102
|
+
} else if (!exists(normalized)) {
|
|
103
|
+
errors.push(`read-first pointer does not resolve: ${path}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return errors;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function git(argv: string[], root: string): { ok: boolean; text: string } {
|
|
110
|
+
const result = Bun.spawnSync(["git", ...argv], { cwd: root, stdout: PIPE_OUTPUT, stderr: PIPE_OUTPUT });
|
|
111
|
+
return { ok: result.exitCode === 0, text: result.stdout.toString() };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function commands(staged: boolean, root: string): Promise<Commands> {
|
|
115
|
+
const text = staged ? git(["show", `:${CONFIG_PATH}`], root) : { ok: true, text: await Bun.file(join(root, CONFIG_PATH)).text() };
|
|
116
|
+
if (!text.ok) throw new Error(`cannot read ${CONFIG_PATH} from the index`);
|
|
117
|
+
const config: unknown = JSON.parse(text.text);
|
|
118
|
+
if (!object(config) || !object(config.commands)) throw new Error(`${CONFIG_PATH} needs declared commands`);
|
|
119
|
+
const result: Commands = {};
|
|
120
|
+
for (const [name, invocations] of Object.entries(config.commands)) {
|
|
121
|
+
if (!Array.isArray(invocations) || !invocations.length) throw new Error(`empty command group: ${name}`);
|
|
122
|
+
result[name] = invocations.map((argv: unknown) => {
|
|
123
|
+
if (!Array.isArray(argv) || !argv.length || !argv.every(isString)) throw new Error(`invalid arguments for ${name}`);
|
|
124
|
+
return argv;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (!Object.keys(result).length) throw new Error(`${CONFIG_PATH} needs declared commands`);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function object(value: unknown): value is Record<string, unknown> {
|
|
132
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isString(value: unknown): value is string {
|
|
136
|
+
return typeof value === "string";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function rubricCommands(sections: Section[], declared: Commands): string[] {
|
|
140
|
+
const rubric = table(section(sections, RUBRIC_HEADING));
|
|
141
|
+
const verify = rubric.columns.findIndex((column) => column.startsWith("Verify"));
|
|
142
|
+
const expected = rubric.columns.indexOf(EXPECTED_COLUMN);
|
|
143
|
+
if (verify < 0 || expected < 0) return ["Acceptance Rubric needs Verify and Expected columns"];
|
|
144
|
+
const errors: string[] = [];
|
|
145
|
+
for (const [name, invocations] of Object.entries(declared)) {
|
|
146
|
+
if (name !== "conform" && !name.startsWith("verify.")) continue;
|
|
147
|
+
for (const argv of invocations) {
|
|
148
|
+
const command = argv.join(" ");
|
|
149
|
+
if (!rubric.rows.some((row) => row[verify] === `${BACKTICK}${command}${BACKTICK}` && row[expected])) {
|
|
150
|
+
errors.push(`rubric misses declared command verbatim: ${command}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return errors;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function validateSpec(text: string, declared: Commands, exists: (path: string) => boolean): string[] {
|
|
158
|
+
const sections = sectionsOf(text);
|
|
159
|
+
const errors: string[] = [];
|
|
160
|
+
const count = text.trimEnd().split(/\r?\n/).length;
|
|
161
|
+
if (count > SPEC_LINE_LIMIT) errors.push(`spec has ${count} lines; cap ${SPEC_LINE_LIMIT}`);
|
|
162
|
+
for (const heading of REQUIRED) {
|
|
163
|
+
if (!section(sections, heading).map(textOf).join(EMPTY).trim()) errors.push(`missing or empty required section: ${heading.source}`);
|
|
164
|
+
}
|
|
165
|
+
if (RESIDUE.test(text)) errors.push("unfilled template placeholder or authoring guidance");
|
|
166
|
+
errors.push(...readFirst(sections, exists), ...testMappings(sections), ...rubricCommands(sections, declared));
|
|
167
|
+
return errors;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function auditSpec(args: string[], root: string): Promise<string[]> {
|
|
171
|
+
const [spec, path, mode] = args;
|
|
172
|
+
if (!spec || !path || !mode) throw new Error("expected spec path, content path, and audit mode");
|
|
173
|
+
const staged = STAGED_MODES.has(mode);
|
|
174
|
+
const exists = (reference: string) => staged ? git(["cat-file", "-e", `:${reference}`], root).ok : existsSync(join(root, reference));
|
|
175
|
+
return validateSpec(await Bun.file(resolve(root, path)).text(), await commands(staged, root), exists);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (import.meta.main) {
|
|
179
|
+
try {
|
|
180
|
+
const errors = await auditSpec(Bun.argv.slice(2), process.cwd());
|
|
181
|
+
for (const error of errors) {
|
|
182
|
+
// logging: audit findings are the command-line interface.
|
|
183
|
+
console.error(`FAIL: ${Bun.argv[2]} — ${error}`);
|
|
184
|
+
}
|
|
185
|
+
if (errors.length) process.exitCode = 1;
|
|
186
|
+
// logging: report structural checks without claiming semantic readiness.
|
|
187
|
+
else console.log(`OK: ${Bun.argv[2]} — sections, references, test mappings, and declared commands checked`);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
// logging: unavailable audit inputs must fail, never silently skip checks.
|
|
190
|
+
console.error(`FAIL: spec readiness check — ${error instanceof Error ? error.message : String(error)}`);
|
|
191
|
+
process.exitCode = 1;
|
|
192
|
+
}
|
|
193
|
+
}
|
package/core/operating-model.md
CHANGED
|
@@ -173,7 +173,7 @@ Guards fire pre-hoc regardless of lifecycle stage. Override: `<GATE>: SKIPPED pe
|
|
|
173
173
|
| Begin implementation OR branch carries spec changes in `pending/` | CHORE(open): `pending/`→`active/`, `Status: IN_PROGRESS` + `Branch:`, create worktree, commit on feature branch. **No code until 4 steps committed.** |
|
|
174
174
|
| Every commit during implementation | Mark completed Dimensions/Sections `DONE` same commit as the code. |
|
|
175
175
|
| All work complete, before PR | CHORE(close). |
|
|
176
|
-
|
|
|
176
|
+
| Final implementation commit, or explicit request to park a spec | CHORE(close) is mandatory next action; Section commits continue the stream. |
|
|
177
177
|
<!-- oracle-packs:end -->
|
|
178
178
|
|
|
179
179
|
---
|
|
@@ -188,14 +188,14 @@ Non-trivial (full lifecycle) if it: touches >1 file · new abstraction · data m
|
|
|
188
188
|
|
|
189
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
190
|
|
|
191
|
-
**
|
|
191
|
+
**Gate ownership.** `work` runs the declared `conform` command at commit. `verify` runs documentation checks and non-test `verify.*` commands at push; it permits unfinished Sections. `pr` checks branch shape, exact upstream revision, spec completeness, and every declared `verify.*` command itself. `orly gate pr` is the command CHORE(close) runs; a push is never proof that tests ran. Unit and integration baselines are measured before the Pull Request against the recorded comparison revision. Full unit and integration suites run at the PR boundary, not at opening or every push. Integration and memory suites skip only on branches with no code. The authoritative sequence is `dispatch/lifecycle.md`; no spec means only spec criteria skip. A user-surface change without a docs change is red until docs land or an override says why not.
|
|
192
192
|
|
|
193
193
|
**LAND (after merge, or when the user confirms it merged):** pull the default branch, prune the merged worktree + branch, `make down` where defined.
|
|
194
194
|
|
|
195
195
|
<!-- oracle-packs:start workflow.specifications -->
|
|
196
196
|
### CHORE (open)
|
|
197
197
|
|
|
198
|
-
Spec `pending/`→`active/`; `Status: IN_PROGRESS`; `Branch:` set; **`Test Baseline:`
|
|
198
|
+
Spec `pending/`→`active/`; `Status: IN_PROGRESS`; `Branch:` set; **`Test Baseline:` declared pending; `Baseline revision:` records the full comparison commit** (unit and integration counts plus evidence are due before the Pull Request); committed. Worktree created, CWD inside (verify `pwd` + `git worktree list`), carrying the base checkout's uncommitted work in with it. No code until the 4 steps are committed.
|
|
199
199
|
<!-- oracle-packs:end -->
|
|
200
200
|
|
|
201
201
|
### PLAN
|
|
@@ -206,7 +206,7 @@ Required: one-paragraph goal · explicit assumptions · file/task impact list ·
|
|
|
206
206
|
|
|
207
207
|
Read what the selected dispatch names — **sectioned, not whole-file**: scan headers (`grep -nE "^##+ "`), read the sections the diff shape touches, cite them.
|
|
208
208
|
<!-- oracle-packs:start product.agentsfleet -->
|
|
209
|
-
The full `agentsfleet` trigger map lives in `docs/EXECUTE_DOC_READS.md`. At EXECUTE start read the **Rule-code gloss legend** of `docs/greptile-learnings/RULES.md` plus the sections the spec's "Applicable Rules" names — never the full file. On sub-task shape change, section-scan (`grep -n "^## "`) and re-read only the sections the new shape touches. DOC READ GATE emits one `📖 DOC READ: <path>` proof-line **per triggered document per turn** before the turn's first triggering edit, citing the applied section or a cited skip; auto-mode and prior-turn reads never excuse it. **Record it, don't just claim it:** `bash audits/doc-read.sh log <path
|
|
209
|
+
The full `agentsfleet` trigger map lives in `docs/EXECUTE_DOC_READS.md`. At EXECUTE start read the **Rule-code gloss legend** of `docs/greptile-learnings/RULES.md` plus the sections the spec's "Applicable Rules" names — never the full file. On sub-task shape change, section-scan (`grep -n "^## "`) and re-read only the sections the new shape touches. DOC READ GATE emits one `📖 DOC READ: <path>` proof-line **per triggered document per turn** before the turn's first triggering edit, citing the applied section or a cited skip; auto-mode and prior-turn reads never excuse it. **Record it, don't just claim it:** `bash audits/doc-read.sh log <path> "<section applied>"` — any runtime; Claude Code's Read hook does it for you; pre-commit compares the record to the staged diff. **Log each page as you read it, and cite the section**: a loop writes the same rows, and once did — nine façades in one second, `check` green, violations from a section nobody opened. `check` counts citations and names any bulk second.
|
|
210
210
|
<!-- oracle-packs:end -->
|
|
211
211
|
|
|
212
212
|
Edit only approved scope; no opportunistic refactors. Stay in active worktree. Cross-repo writes to `~/Projects/docs/` need explicit per-session ask.
|
|
@@ -222,9 +222,9 @@ In `agentsfleet` this stage is `make harness-verify`; its output block and end-o
|
|
|
222
222
|
|
|
223
223
|
### VERIFY
|
|
224
224
|
|
|
225
|
-
**
|
|
225
|
+
**Section proof and final verification.** Follow `dispatch/lifecycle.md` for command timing. Section checks prove the changed Dimensions; the final gate runs the full declared verification set. A package-scoped command never replaces a declared boundary command, and a Section result never satisfies a repository-wide "tests pass" claim.
|
|
226
226
|
<!-- oracle-packs:start product.agentsfleet -->
|
|
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
|
|
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 recorded comparison revision, measured before the Pull Request; 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.
|
|
228
228
|
<!-- oracle-packs:end -->
|
|
229
229
|
|
|
230
230
|
### REVIEW
|
package/dispatch/lifecycle.md
CHANGED
|
@@ -12,55 +12,108 @@ are entering, not the whole file.
|
|
|
12
12
|
|
|
13
13
|
## What runs at each stage
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`.oracle/orly.json` commands and never invents one.
|
|
15
|
+
This is the authoritative sequence. Commands come from the repository's
|
|
16
|
+
`.oracle/orly.json`; the engine contains no product-specific build recipes.
|
|
18
17
|
|
|
19
|
-
| Stage | Runs |
|
|
18
|
+
| Stage | Runs | Owner |
|
|
20
19
|
|---|---|---|
|
|
21
|
-
| CHORE(open) | the
|
|
22
|
-
| PLAN |
|
|
23
|
-
| EXECUTE |
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
| CHORE(open) | Record the comparison commit and pending baseline; create the tree, carry owned changes, commit metadata | Agent; commit hook still runs |
|
|
21
|
+
| PLAN | Resolve references, scope, decisions, and proof mapping; no application build | Agent |
|
|
22
|
+
| EXECUTE | Read triggered rules; implement one Section and its tests | Agent |
|
|
23
|
+
| First Section verification | Hydrate only the touched surfaces; format, lint, and execute Section proofs | Agent |
|
|
24
|
+
| Later Section verification | Reuse the tree; repeat affected formatting, lint, and proof commands | Agent |
|
|
25
|
+
| COMMIT | Declared `conform` over staged changes | `orly gate work` in pre-commit |
|
|
26
|
+
| Push | Documentation check and declared non-test verification commands | `orly gate verify` in generated pre-push |
|
|
27
|
+
| Before PR | Measure the comparison revision's unit and integration lanes; review results and fix final-branch failures | Agent |
|
|
28
|
+
| CHORE(close) | Branch and spec criteria, exact upstream revision, every declared `verify.*` command | `orly gate pr` |
|
|
29
|
+
| LAND | Pull and clean up the merged stream | Agent after merge |
|
|
30
|
+
|
|
31
|
+
`verify.unit`, `verify.integration`, and `verify.memory` belong to the PR
|
|
32
|
+
boundary. Other `verify.*` commands also run at push and are checked again at
|
|
33
|
+
that independent boundary. Full suites run once per PR-gate invocation;
|
|
34
|
+
failures or later changes require a new invocation. No verification cache is
|
|
35
|
+
kept, and a custom hook cannot silently remove final verification.
|
|
36
|
+
`conform` remains the commit hook's responsibility because it may read the
|
|
37
|
+
index. A bare `orly gate` runs all groups and stops at the first red group.
|
|
38
|
+
|
|
39
|
+
Formatting and lint do not prove behavior. A Section is complete only when its
|
|
40
|
+
Dimensions have passing proofs. Use existing repository test lanes; if a lane
|
|
41
|
+
runs a whole workspace, describe it honestly and add a supported filter to that
|
|
42
|
+
recipe when narrower Section checks are needed. Boundary commands remain
|
|
43
|
+
unfiltered. Rust projects run `cargo fmt` in their Rust workspace, followed by
|
|
44
|
+
the declared lint wrapper covering Clippy and the shipping build configuration.
|
|
45
|
+
Other languages use their own declared formatter and lint commands.
|
|
39
46
|
|
|
40
47
|
## CHORE (open) — runbook
|
|
41
48
|
|
|
42
49
|
1. Spec `docs/v*/pending/` → `active/`; `Status: IN_PROGRESS`; `Branch:` set.
|
|
43
|
-
2. **Test Baseline** —
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
2. **Test Baseline** — record `**Baseline revision:** <full commit>` and
|
|
51
|
+
`**Test Baseline:** pending — measured before the Pull Request`. Run no
|
|
52
|
+
application suite at opening. Before the PR, obtain both declared baseline
|
|
53
|
+
lanes (`verify.unit` and `verify.integration`) for that exact revision from
|
|
54
|
+
matching successful run evidence or an isolated checkout of that revision.
|
|
55
|
+
Never assume the default checkout stayed at the comparison commit. Keep
|
|
56
|
+
baseline and final integration environments separate; neither may reset a
|
|
57
|
+
shared deployment. Record passed, failed, and skipped counts per lane in a
|
|
58
|
+
report, with commands, revision, and environment; put totals in the header
|
|
59
|
+
and the report path or run URL in `**Baseline evidence:**`.
|
|
60
|
+
A missing or failed baseline run is recorded honestly; fix failures in the
|
|
61
|
+
implementation branch and keep the historical result unchanged. Baseline
|
|
62
|
+
failures never permit final failures. Compare like-for-like lanes and report
|
|
63
|
+
added, removed, or changed test selections rather than inventing a delta.
|
|
64
|
+
`n/a — <reason>` is valid only when there is no code or no baseline lane.
|
|
65
|
+
The gate validates counts, revision ancestry, and the evidence reference;
|
|
66
|
+
the agent reviews the report's contents. Performance baselines required by
|
|
67
|
+
a spec remain due before the changes they measure.
|
|
68
|
+
3. Create the worktree and carry the base checkout's uncommitted work into it;
|
|
69
|
+
verify CWD is inside it (`pwd` + `git worktree list`).
|
|
49
70
|
4. Commit the four steps on the feature branch. No code until the commit lands.
|
|
50
71
|
|
|
72
|
+
**The base checkout's uncommitted work moves with the stream.** A worktree cut
|
|
73
|
+
from a dirty base strands that work on the default branch, where it is neither
|
|
74
|
+
committed, nor reviewed, nor in the tree the stream is about to gate — and the
|
|
75
|
+
next `git status` there reads as drift nobody owns. So step 3 moves it, before
|
|
76
|
+
any spec edit:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git -C <base> status --porcelain -uall # nothing? the step is a no-op
|
|
80
|
+
git -C <base> stash push --include-untracked -m "chore(open): <branch>"
|
|
81
|
+
git -C <worktree> stash pop
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`git stash` is per-repository, not per-worktree, so the entry the base pushes is
|
|
85
|
+
the entry the worktree pops. **Move, never copy** — work left in both places is
|
|
86
|
+
committed twice and conflicts on the merge. Name what came across in the
|
|
87
|
+
CHORE(open) report; a carry-over nobody announced is indistinguishable from
|
|
88
|
+
scope that wandered in. A pop that conflicts KEEPS the stash: stop there and
|
|
89
|
+
resolve before step 4, because a half-applied carry-over is worse than a dirty
|
|
90
|
+
base. Work that does not belong to this stream is the one exception — leave it,
|
|
91
|
+
and say so.
|
|
92
|
+
|
|
51
93
|
<!-- oracle-packs:start product.agentsfleet -->
|
|
52
94
|
## Worktree recipe (agentsfleet)
|
|
53
95
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
96
|
+
```bash
|
|
97
|
+
cd ~/Projects/agentsfleet && git checkout main
|
|
98
|
+
git status --porcelain -uall # dirty? carry it over — the two stash lines below
|
|
99
|
+
git stash push --include-untracked -m "chore(open): feat/mNN-name"
|
|
100
|
+
git branch feat/mNN-name
|
|
101
|
+
git worktree add ../agentsfleet-mNN-name feat/mNN-name
|
|
102
|
+
cd ../agentsfleet-mNN-name && git stash pop
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Omit stash operations when the base is clean. Identify the saved stash entry
|
|
106
|
+
explicitly when other streams exist, restore its staged state, and remove only
|
|
107
|
+
that entry after successful restoration. Leave changes owned by another stream
|
|
108
|
+
in place and report them. No application installation or build runs here.
|
|
109
|
+
`git worktree add` may run the repository's cheap environment-link hook.
|
|
110
|
+
|
|
111
|
+
At first Section verification, install dependencies for the surfaces being
|
|
112
|
+
verified: root `bun install` for the JavaScript workspace; `cli/` has its own
|
|
113
|
+
install and build. Rust checks run from `rustd/` using `make lint-rustd` after
|
|
114
|
+
`cargo fmt`. The first compilation resolves Rust dependencies; later Sections
|
|
115
|
+
reuse the build artifacts. Missing environment links follow the repository's
|
|
116
|
+
provisioning instructions before a check that needs them.
|
|
64
117
|
|
|
65
118
|
**agentsfleet CHORE(close) paths:** the `<Update>` lands in
|
|
66
119
|
`~/Projects/docs/changelog.mdx` (template + version-bump matrix:
|
|
@@ -99,8 +152,9 @@ into a separate tree.
|
|
|
99
152
|
guesses `surfaces`.
|
|
100
153
|
First session in a repository whose config is still that seed:
|
|
101
154
|
1. Read the build files properly. Fill every `verify.*` the repository really
|
|
102
|
-
has — the
|
|
103
|
-
|
|
155
|
+
has — the gate ownership is fixed: `conform` at commit, non-test checks at
|
|
156
|
+
push, all `verify.*` at the PR boundary; only integration and memory
|
|
157
|
+
skip on branches with no code.
|
|
104
158
|
2. Set `surfaces.user` and `surfaces.docs` to real path prefixes, or the docs
|
|
105
159
|
gate can never fire and a user-visible change ships undocumented.
|
|
106
160
|
3. Add any opt-in pack the repository's own sources cannot imply
|
|
@@ -108,8 +162,10 @@ into a separate tree.
|
|
|
108
162
|
`orly update` so they materialise.
|
|
109
163
|
4. Commit it. Every teammate and every later session reads this file, and
|
|
110
164
|
`orly` never rewrites it — so an edit here is permanent.
|
|
111
|
-
|
|
112
|
-
|
|
165
|
+
Setup requires `conform` and at least one named `verify.*` command.
|
|
166
|
+
A documentation repository can declare `verify.docs` for its site and link
|
|
167
|
+
checks without application unit or integration suites. `init` reports
|
|
168
|
+
missing commands; `doctor` and the gates reject incomplete setup.
|
|
113
169
|
- **Priming:** (1) Human runs `playbooks/founding/01_bootstrap/001_playbook.md`.
|
|
114
170
|
(2) Agent runs `./playbooks/founding/02_preflight/00_gate.sh` (green before
|
|
115
171
|
next). (3) Agent runs `playbooks/founding/03_priming_infra/001_playbook.md`.
|
package/dispatch/verify.md
CHANGED
|
@@ -25,58 +25,40 @@ Only when a target is genuinely unrunnable (e.g. Docker missing for integration
|
|
|
25
25
|
tests). Surface the limitation in the done message — never dress a skip as "tests
|
|
26
26
|
pass".
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## Repository commands are canonical
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
The commands in `.oracle/orly.json` define full verification. Use repository
|
|
31
|
+
Make targets where declared; a repository may also declare its native tools
|
|
32
|
+
directly. A narrower package run never substitutes for a declared full check.
|
|
33
|
+
Documentation repositories may use `verify.docs` for site and link checks;
|
|
34
|
+
application test suites are required only when the repository declares them.
|
|
33
35
|
|
|
34
36
|
## Two cadences, one boundary
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
the pre-push `orly gate verify` already graded. A bare `orly gate` chains all
|
|
61
|
-
three gates — use it on a branch whose earlier gates never ran.
|
|
62
|
-
|
|
63
|
-
| Target | When |
|
|
64
|
-
|---|---|
|
|
65
|
-
| `make harness-verify` | Section lane and boundary. The declared `conform` — the deterministic gate audit. |
|
|
66
|
-
| `make lint-all` | Boundary. The declared `verify.lint`. |
|
|
67
|
-
| `make test-unit-all` | Boundary. The declared `verify.unit` — the cargo workspace plus every package coverage gate. |
|
|
68
|
-
| `make check-version` | Boundary. The declared `verify.version`. |
|
|
69
|
-
| `make test-integration-rustd` | Boundary. The declared `verify.integration` — live Postgres and Redis; `orly gate pr` skips it on a branch carrying no code. |
|
|
70
|
-
| `make wire-fixtures` | The `/v1/runners` wire types changed. Regenerate, then review the diff — a changed fixture IS the wire change. |
|
|
71
|
-
| `make bench` (local) | Diff touches request-path code, allocator wiring, or startup/shutdown sequencing. |
|
|
72
|
-
| `API_BENCH_URL=https://api-dev.agentsfleet.net/healthz make bench` | After branch deploys to dev. |
|
|
73
|
-
| `/orly-write-integration-test` (skill) | With `/orly-write-unit-test` at VERIFY when the diff crosses module boundaries with real I/O; otherwise record `N/A — <reason>`. |
|
|
74
|
-
| Acceptance e2e (live tier) | Diff touches a surface the live/acceptance tier covers — relevant suites green, or their opt-in skip matrix recorded. |
|
|
75
|
-
|
|
76
|
-
The declared set in `.oracle/orly.json` is the source of truth for what exists,
|
|
77
|
-
and a lane listed there is a lane that runs at the boundary — including the slow
|
|
78
|
-
tier. A package-scoped runner (`cargo test -p afd_wire`, `bun run test` inside a
|
|
79
|
-
package) proves that package and never the repository, at either cadence.
|
|
38
|
+
The command ownership and chronological sequence live in
|
|
39
|
+
`dispatch/lifecycle.md` §What runs at each stage. Read that table before
|
|
40
|
+
verification; this page defines what counts as evidence.
|
|
41
|
+
|
|
42
|
+
- **Section proof:** format, lint, and run the tests covering the changed
|
|
43
|
+
Dimensions. Hydrate the required application dependencies at the first
|
|
44
|
+
Section verification, then reuse that worktree. Passing lint alone never
|
|
45
|
+
marks a behavior DONE.
|
|
46
|
+
- **Baseline:** before the Pull Request, measure the recorded comparison
|
|
47
|
+
revision's declared unit and integration lanes. Keep commands, counts, and
|
|
48
|
+
output in an evidence report. Compare equivalent selections; explain removed
|
|
49
|
+
tests. A historical failure never exempts the final branch.
|
|
50
|
+
- **Final verification:** `orly gate pr` runs every declared `verify.*` command
|
|
51
|
+
itself. It does not infer success from a pushed branch or an installed hook.
|
|
52
|
+
Capture full command output and the decisive counts, not only exit codes.
|
|
53
|
+
Custom hooks may run additional checks but cannot replace the final gate.
|
|
54
|
+
- **Repeat only for cause:** a failure or a subsequent change invalidating the
|
|
55
|
+
result requires a rerun. Run the final gate once per candidate; do not also
|
|
56
|
+
launch its full suites manually as an unrecorded duplicate.
|
|
57
|
+
|
|
58
|
+
Use repository-owned Make targets where declared. A narrower package run is
|
|
59
|
+
Section evidence only, never a substitute for the full declared boundary.
|
|
60
|
+
When a diff crosses a module boundary with real input/output, apply
|
|
61
|
+
`orly-write-integration-test`; otherwise record why it does not apply.
|
|
80
62
|
|
|
81
63
|
## Wire-fixture evidence rule
|
|
82
64
|
|