@agentsfleet/orly 0.5.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.
Files changed (112) hide show
  1. package/.githooks/pre-commit +42 -0
  2. package/.githooks/pre-push +67 -0
  3. package/AGENTS.md +348 -0
  4. package/LICENSE +21 -0
  5. package/README.md +249 -0
  6. package/SOUL.md +90 -0
  7. package/audits/agents-md.md +497 -0
  8. package/audits/agents-md.sh +353 -0
  9. package/audits/cross-tier-rates.sh +105 -0
  10. package/audits/data.sh +185 -0
  11. package/audits/deinit-pairs.sh +224 -0
  12. package/audits/design-tokens.sh +143 -0
  13. package/audits/doc-read.sh +183 -0
  14. package/audits/error-codes.sh +221 -0
  15. package/audits/fixtures/clean.diff +15 -0
  16. package/audits/fixtures/dirty.diff +16 -0
  17. package/audits/lifecycle-anchors.sh +22 -0
  18. package/audits/logging.sh +217 -0
  19. package/audits/msid-ui.sh +144 -0
  20. package/audits/parity-dispatch.sh +51 -0
  21. package/audits/rule-ledger-lib.sh +264 -0
  22. package/audits/rule-ledger.sh +225 -0
  23. package/audits/rule-paths.sh +108 -0
  24. package/audits/spec-template.sh +258 -0
  25. package/audits/sql-mod.sh +102 -0
  26. package/audits/ufs.sh +303 -0
  27. package/bin/orly +26 -0
  28. package/core/operating-model.md +256 -0
  29. package/dispatch/edit_rules.md +69 -0
  30. package/dispatch/lib.sh +213 -0
  31. package/dispatch/lifecycle.md +187 -0
  32. package/dispatch/name_architecture.md +93 -0
  33. package/dispatch/verify.md +111 -0
  34. package/dispatch/write_any.md +740 -0
  35. package/dispatch/write_any.sh +49 -0
  36. package/dispatch/write_auth.md +36 -0
  37. package/dispatch/write_changelog.md +64 -0
  38. package/dispatch/write_documentation.md +45 -0
  39. package/dispatch/write_go.md +24 -0
  40. package/dispatch/write_http.md +45 -0
  41. package/dispatch/write_mdx.md +7 -0
  42. package/dispatch/write_python.md +8 -0
  43. package/dispatch/write_rust.md +38 -0
  44. package/dispatch/write_shell.md +8 -0
  45. package/dispatch/write_spec.md +105 -0
  46. package/dispatch/write_sql.md +112 -0
  47. package/dispatch/write_sql.sh +36 -0
  48. package/dispatch/write_ts_adhere_bun.md +493 -0
  49. package/dispatch/write_ts_adhere_bun.sh +37 -0
  50. package/dispatch/write_zig.md +852 -0
  51. package/dispatch/write_zig.sh +40 -0
  52. package/docs/CHANGELOG_VOICE.md +18 -0
  53. package/docs/DISPATCH_ARCHITECTURE.md +664 -0
  54. package/docs/DISPATCH_REVIEW_DISPOSITION.md +65 -0
  55. package/docs/DOCUMENTATION_RULES.md +209 -0
  56. package/docs/EXECUTE_DOC_READS.md +29 -0
  57. package/docs/HARNESS_VERIFY_OUTPUT.md +40 -0
  58. package/docs/LIFECYCLE_PATTERNS.md +316 -0
  59. package/docs/LOGGING_STANDARD.md +397 -0
  60. package/docs/ORLY_ARCHITECTURE.md +129 -0
  61. package/docs/REST_API_DESIGN_GUIDELINES.md +783 -0
  62. package/docs/RULE_ENFORCEMENT.md +41 -0
  63. package/docs/SCHEMA_CONVENTIONS.md +93 -0
  64. package/docs/TEMPLATE.md +411 -0
  65. package/docs/VERIFY_TIERS.md +57 -0
  66. package/docs/greptile-learnings/README.md +51 -0
  67. package/docs/greptile-learnings/RULES.md +1045 -0
  68. package/fixtures/registry-invalid.json +1 -0
  69. package/fixtures/registry-valid.json +18 -0
  70. package/fixtures/unclosed-pack-block.md +8 -0
  71. package/package.json +63 -0
  72. package/packs/language/go/rules.md +24 -0
  73. package/packs/language/mdx/rules.md +7 -0
  74. package/packs/language/python/rules.md +8 -0
  75. package/packs/language/rust/rules.md +38 -0
  76. package/packs/language/shell/rules.md +8 -0
  77. package/registry.json +405 -0
  78. package/schemas/evidence.schema.json +43 -0
  79. package/schemas/profile.schema.json +12 -0
  80. package/schemas/registry.schema.json +12 -0
  81. package/skills/orly-babysit-prs/SKILL.md +326 -0
  82. package/skills/orly-spec-new/SKILL.md +157 -0
  83. package/skills/orly-write-integration-test/SKILL.md +470 -0
  84. package/skills/orly-write-unit-test/SKILL.md +520 -0
  85. package/src/cli.test.ts +70 -0
  86. package/src/cli.ts +287 -0
  87. package/src/config.test.ts +226 -0
  88. package/src/config.ts +288 -0
  89. package/src/criteria.test.ts +137 -0
  90. package/src/criteria.ts +166 -0
  91. package/src/criteria_spec.ts +127 -0
  92. package/src/criteria_support.ts +53 -0
  93. package/src/gates.test.ts +183 -0
  94. package/src/gates.ts +169 -0
  95. package/src/gates_spec.test.ts +98 -0
  96. package/src/gates_test_support.ts +194 -0
  97. package/src/git_env.ts +20 -0
  98. package/src/install.test.ts +514 -0
  99. package/src/install.ts +447 -0
  100. package/src/install_nested.test.ts +32 -0
  101. package/src/model.test.ts +31 -0
  102. package/src/model.ts +245 -0
  103. package/src/references.test.ts +47 -0
  104. package/src/references.ts +147 -0
  105. package/src/render.test.ts +60 -0
  106. package/src/render.ts +94 -0
  107. package/src/surfaces.test.ts +43 -0
  108. package/src/surfaces.ts +89 -0
  109. package/src/validation.test.ts +11 -0
  110. package/src/validation.ts +87 -0
  111. package/src/verify.test.ts +13 -0
  112. package/src/verify.ts +59 -0
@@ -0,0 +1,194 @@
1
+ // Test harness for the gate suites (gates.test.ts, gates_spec.test.ts).
2
+ // Extracted per dispatch/write_any.md §Splitting conventions: inline test
3
+ // support is the first cut on an over-long file, because coverage instruments
4
+ // count harness code written inline as product.
5
+ import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join, resolve } from "node:path";
8
+
9
+ import { CONFIG_PATH } from "./config";
10
+ import { RulesModel } from "./model";
11
+
12
+ const GIT = "git";
13
+ const QUIET = "-q";
14
+ const COMMIT = "commit";
15
+ const CONFIG = "config";
16
+ const ADD = "add";
17
+ const MESSAGE = "-m";
18
+ const PIPE_OUTPUT = "pipe";
19
+ const TRUE_COMMAND = "true";
20
+ const ACCEPTANCE_RUBRIC_HEADING = "Acceptance Rubric";
21
+ const FIXTURE = "fixture";
22
+ const AUDITS_DIR = "audits";
23
+ const SPEC_GATE_SCRIPT = `${AUDITS_DIR}/spec-template.sh`;
24
+
25
+ export const ROOT = resolve(import.meta.dir, "..");
26
+ export const SPEC_RELATIVE = "docs/v1/active/M99_001_P2_CLI_FIXTURE.md";
27
+ export const FIXTURE_CONFIG = { schema_version: 1, packs: [], commands: { conform: [[TRUE_COMMAND]], "verify.unit": [[TRUE_COMMAND]] } };
28
+
29
+ const temporaryDirectories: string[] = [];
30
+ const GIT_SCOPE_VARIABLES = ["GIT_DIR", "GIT_INDEX_FILE", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_PREFIX", "GIT_OBJECT_DIRECTORY", "GIT_ALTERNATE_OBJECT_DIRECTORIES"];
31
+
32
+ // git exports GIT_DIR and GIT_INDEX_FILE to its hooks, and .githooks/pre-commit
33
+ // runs this suite through `make audit`. Inherited, they scope every git call at
34
+ // the dotfiles checkout: relative values resolve fixture-locally by luck in a
35
+ // plain repository and fatally in a linked worktree, whose .git is a file, so
36
+ // .git/index reads as "Not a directory". Bun does not propagate a deletion from
37
+ // process.env to a spawned child, so the scrubbed copy is passed explicitly.
38
+ const HERMETIC_ENVIRONMENT = hermeticEnvironment();
39
+
40
+ // Each suite registers this in its own afterEach — the array is module state
41
+ // shared by every importer, so a suite that skips it leaks its fixtures.
42
+ export function cleanupTemporaryDirectories(): void {
43
+ for (const path of temporaryDirectories.splice(0)) rmSync(path, { recursive: true, force: true });
44
+ }
45
+
46
+ export function names(report: { results: Array<{ name: string }> }): string[] {
47
+ return report.results.map((result) => result.name).sort();
48
+ }
49
+
50
+ export function orly(project: string, registry: string, ...args: string[]): { code: number; output: string } {
51
+ const result = Bun.spawnSync(["bun", join(ROOT, "src/cli.ts"), "--root", registry, ...args], { cwd: project, env: HERMETIC_ENVIRONMENT, stdout: PIPE_OUTPUT, stderr: PIPE_OUTPUT });
52
+ return { code: result.exitCode, output: `${result.stdout.toString()}${result.stderr.toString()}` };
53
+ }
54
+
55
+ // A minimal registry root: the gate verbs read the repository's own config and
56
+ // never render, so the full pack inventory is not needed.
57
+ export function fixtureRegistry(project: string): string {
58
+ const root = temporaryDirectory();
59
+ Bun.write(join(root, "registry.json"), JSON.stringify({ schema_version: 1, core_documents: [], packs: {}, rules: [] }));
60
+ writeFixtureConfig(project);
61
+ return root;
62
+ }
63
+
64
+ // The gate resolves commands from `.oracle/orly.json` in the repository under
65
+ // test, so every fixture repository carries one.
66
+ export function writeFixtureConfig(project: string, surfaces?: { user: string[]; docs: string[] }, extraCommands?: Record<string, string[][]>): void {
67
+ const config: Record<string, unknown> = structuredClone(FIXTURE_CONFIG);
68
+ if (surfaces) config.surfaces = surfaces;
69
+ if (extraCommands) config.commands = { ...(config.commands as Record<string, unknown>), ...extraCommands };
70
+ mkdirSync(join(project, ".oracle"), { recursive: true });
71
+ writeFileSync(join(project, CONFIG_PATH), JSON.stringify(config, undefined, 2));
72
+ // Committed, exactly as a real repository carries it: a linked worktree gets
73
+ // its rules config from the checkout, and an uncommitted one would also make
74
+ // every git.tree criterion read dirty.
75
+ git(project, ADD, CONFIG_PATH);
76
+ git(project, COMMIT, QUIET, MESSAGE, "chore: orly config");
77
+ }
78
+
79
+ export async function modelFor(
80
+ project: string,
81
+ surfaces?: { user: string[]; docs: string[] },
82
+ extraCommands?: Record<string, string[][]>,
83
+ ): Promise<RulesModel> {
84
+ writeFixtureConfig(project, surfaces, extraCommands);
85
+ return RulesModel.load(ROOT);
86
+ }
87
+
88
+ export function newRepository(): string {
89
+ const project = temporaryDirectory();
90
+ git(project, "init", QUIET);
91
+ git(project, CONFIG, "user.email", "orly-tests@example.invalid");
92
+ git(project, CONFIG, "user.name", "Orly Tests");
93
+ Bun.write(join(project, "README.md"), "fixture\n");
94
+ git(project, ADD, ".");
95
+ git(project, COMMIT, QUIET, MESSAGE, "test: init");
96
+ return project;
97
+ }
98
+
99
+ // A stream that already ran CHORE(close): the spec sits under done/ with
100
+ // Status: DONE and a Branch: header naming the branch, committed as the
101
+ // branch's first commit so ordering holds.
102
+ export function closedSpecRepository(branch: string, extraLines: string[] = []): string {
103
+ const project = newRepository();
104
+ git(project, "checkout", QUIET, "-b", branch);
105
+ mkdirSync(join(project, "docs/v1/done"), { recursive: true });
106
+ mkdirSync(join(project, AUDITS_DIR), { recursive: true });
107
+ copyFileSync(join(ROOT, SPEC_GATE_SCRIPT), join(project, SPEC_GATE_SCRIPT));
108
+ Bun.write(join(project, "docs/v1/done/M99_001_P2_CLI_FIXTURE.md"), specFixture("DONE", branch, extraLines));
109
+ git(project, ADD, ".");
110
+ git(project, COMMIT, QUIET, MESSAGE, "chore: close the fixture spec");
111
+ return project;
112
+ }
113
+
114
+ export function newSpecRepository(): string {
115
+ const project = newRepository();
116
+ mkdirSync(join(project, "docs/v1/active"), { recursive: true });
117
+ mkdirSync(join(project, AUDITS_DIR), { recursive: true });
118
+ copyFileSync(join(ROOT, SPEC_GATE_SCRIPT), join(project, SPEC_GATE_SCRIPT));
119
+ Bun.write(join(project, SPEC_RELATIVE), specFixture());
120
+ git(project, ADD, ".");
121
+ git(project, COMMIT, QUIET, MESSAGE, "test: add fixture spec");
122
+ return project;
123
+ }
124
+
125
+ // A spec carrying every section audits/spec-template.sh requires, with all
126
+ // Dimensions already DONE so the gates' own criteria are what is under test.
127
+ export function specFixture(status = "IN_PROGRESS", branch?: string, extraLines: string[] = []): string {
128
+ const sections = [
129
+ "PR Intent & comprehension handshake",
130
+ "Overview",
131
+ "Implementing agent — read these first",
132
+ "Files Changed (blast radius)",
133
+ "Applicable Rules",
134
+ "Applicable Gates",
135
+ "Prior-Art / Reference Implementations",
136
+ "Interfaces",
137
+ "Failure Modes",
138
+ "Invariants",
139
+ "Metrics & Observability",
140
+ "Test Specification (tiered)",
141
+ ACCEPTANCE_RUBRIC_HEADING,
142
+ "Out of Scope",
143
+ "Product Clarity (authoring record)",
144
+ "Decomposition & alternatives",
145
+ "Discovery (consult log)",
146
+ ].map((heading) =>
147
+ // The Acceptance Rubric must quote the declared conform + verify.unit
148
+ // commands verbatim (declared-command parity, audits/spec-template.sh
149
+ // Family 2b) — the fixture models a compliant spec, so its rubric carries
150
+ // them the way a real spec's S-rows do.
151
+ heading === ACCEPTANCE_RUBRIC_HEADING
152
+ ? `## ${heading}\n\n| S1 | Conform gates green | \`${TRUE_COMMAND}\` | exit 0 | P0 | |\n| S2 | Unit tests pass | \`${TRUE_COMMAND}\` | exit 0 | P0 | |\n`
153
+ : `## ${heading}\n\nFixture content for ${heading}.\n`,
154
+ );
155
+ return [
156
+ "# M99_001: Fixture milestone",
157
+ "",
158
+ `**Status:** ${status}`,
159
+ "**Priority:** P2 — fixture",
160
+ ...(branch ? [`**Branch:** \`${branch}\``] : []),
161
+ "**Test Baseline:** unit=0 integration=0",
162
+ ...extraLines,
163
+ "",
164
+ "## Sections (implementation slices)",
165
+ "",
166
+ "### §1 — Fixture slice",
167
+ "",
168
+ "- **Dimension 1.1** — DONE — fixture behaviour → Test `fixture_test`",
169
+ "",
170
+ ...sections,
171
+ ].join("\n");
172
+ }
173
+
174
+ function hermeticEnvironment(): Record<string, string | undefined> {
175
+ const environment: Record<string, string | undefined> = { ...process.env };
176
+ for (const name of GIT_SCOPE_VARIABLES) delete environment[name];
177
+ return environment;
178
+ }
179
+
180
+ export function git(project: string, ...args: string[]): void {
181
+ const result = Bun.spawnSync([GIT, ...args], { cwd: project, env: HERMETIC_ENVIRONMENT, stdout: "ignore", stderr: PIPE_OUTPUT });
182
+ if (result.exitCode !== 0) throw new Error(result.stderr.toString());
183
+ }
184
+
185
+ export function gitOutput(project: string, ...args: string[]): string {
186
+ const result = Bun.spawnSync([GIT, ...args], { cwd: project, env: HERMETIC_ENVIRONMENT, stdout: PIPE_OUTPUT, stderr: PIPE_OUTPUT });
187
+ return result.exitCode === 0 ? result.stdout.toString().trim() : "";
188
+ }
189
+
190
+ export function temporaryDirectory(): string {
191
+ const path = mkdtempSync(join(tmpdir(), "orly-gates-test-"));
192
+ temporaryDirectories.push(path);
193
+ return path;
194
+ }
package/src/git_env.ts ADDED
@@ -0,0 +1,20 @@
1
+ // git exports these to every hook it runs. Inherited by a spawned git, they
2
+ // pin it to the HOOK's repository instead of the directory we asked about: in
3
+ // the main checkout a relative GIT_DIR resolves locally by luck, but a linked
4
+ // worktree's .git is a FILE, so `.git/index` reads as "Not a directory" and
5
+ // every criterion silently evaluates against the wrong tree. Gates must judge
6
+ // the path they were handed, so every spawn in orly passes this scrubbed copy.
7
+ // Bun does not propagate a delete from process.env to a child, so the copy is
8
+ // explicit rather than a mutation.
9
+ const GIT_SCOPE_VARIABLES = [
10
+ "GIT_DIR", "GIT_INDEX_FILE", "GIT_WORK_TREE", "GIT_COMMON_DIR",
11
+ "GIT_PREFIX", "GIT_OBJECT_DIRECTORY", "GIT_ALTERNATE_OBJECT_DIRECTORIES",
12
+ ];
13
+
14
+ function unscopedEnvironment(): Record<string, string | undefined> {
15
+ const environment: Record<string, string | undefined> = { ...process.env };
16
+ for (const name of GIT_SCOPE_VARIABLES) delete environment[name];
17
+ return environment;
18
+ }
19
+
20
+ export const UNSCOPED_ENVIRONMENT = unscopedEnvironment();