@agentsfleet/orly 0.10.13 → 0.11.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 (155) hide show
  1. package/.githooks/pre-push +2 -1
  2. package/AGENTS.md +21 -31
  3. package/README.md +65 -29
  4. package/SOUL.md +13 -23
  5. package/audits/agents-md.md +67 -57
  6. package/audits/deinit-pairs.sh +13 -18
  7. package/audits/design-tokens.sh +9 -23
  8. package/audits/doc-read.sh +10 -33
  9. package/audits/doc-reads.ts +41 -0
  10. package/audits/error-codes.sh +18 -25
  11. package/audits/logging-rust.awk +145 -0
  12. package/audits/logging.sh +10 -166
  13. package/audits/msid-ui.sh +45 -18
  14. package/audits/rule-ledger-lib.sh +9 -6
  15. package/audits/rust-error.sh +20 -15
  16. package/audits/scope.sh +88 -0
  17. package/audits/spec-template.sh +2 -2
  18. package/audits/spec-template.ts +1 -1
  19. package/audits/sql-mod.sh +11 -14
  20. package/audits/ufs-strings.awk +164 -0
  21. package/audits/ufs.sh +10 -188
  22. package/core/operating-model.md +8 -8
  23. package/dispatch/edit_rules.md +8 -6
  24. package/dispatch/lib.sh +41 -26
  25. package/dispatch/lifecycle.md +18 -6
  26. package/dispatch/name_architecture.md +8 -3
  27. package/dispatch/verify.md +1 -1
  28. package/dispatch/write_any.md +10 -3
  29. package/dispatch/write_changelog.md +7 -4
  30. package/dispatch/write_go.md +1 -1
  31. package/dispatch/write_http.md +5 -7
  32. package/dispatch/write_python.md +1 -1
  33. package/dispatch/write_rust.md +17 -1
  34. package/dispatch/write_shell.md +1 -1
  35. package/dispatch/write_spec.md +3 -1
  36. package/dispatch/write_sql.md +28 -6
  37. package/dispatch/write_ts_adhere_bun.md +11 -0
  38. package/dispatch/write_zig.md +53 -21
  39. package/docs/CHANGELOG_VOICE.md +11 -2
  40. package/docs/EXECUTE_DOC_READS.md +10 -10
  41. package/docs/HARNESS_VERIFY_OUTPUT.md +6 -3
  42. package/docs/JUDGMENTS.md +153 -0
  43. package/docs/LIFECYCLE_PATTERNS.md +26 -16
  44. package/docs/LOGGING_STANDARD.md +39 -19
  45. package/docs/ORLY_ARCHITECTURE.md +77 -6
  46. package/docs/RELEASE_TEMPLATE.md +21 -5
  47. package/docs/REST_API_DESIGN_GUIDELINES.md +86 -21
  48. package/docs/RULE_ENFORCEMENT.md +6 -6
  49. package/docs/SCHEMA_CONVENTIONS.md +24 -3
  50. package/docs/TEMPLATE.md +4 -4
  51. package/docs/VERIFY_TIERS.md +2 -2
  52. package/docs/greptile-learnings/RULES.md +81 -26
  53. package/llms.txt +37 -34
  54. package/package.json +6 -1
  55. package/packs/language/go/rules.md +1 -1
  56. package/packs/language/python/rules.md +1 -1
  57. package/packs/language/rust/rules.md +17 -1
  58. package/packs/language/shell/rules.md +1 -1
  59. package/registry.json +22 -7
  60. package/schemas/profile.schema.json +48 -5
  61. package/skills/orly-babysit-prs/SKILL.md +87 -34
  62. package/skills/orly-spec-new/SKILL.md +19 -6
  63. package/skills/orly-write-integration-test/SKILL.md +4 -4
  64. package/skills/orly-write-unit-test/SKILL.md +10 -1
  65. package/src/cli.test.ts +2 -2
  66. package/src/cli.ts +19 -75
  67. package/src/cli_help.ts +69 -0
  68. package/src/command_limits.ts +20 -0
  69. package/src/command_process.test.ts +109 -0
  70. package/src/command_process.ts +88 -0
  71. package/src/command_runner.test.ts +88 -0
  72. package/src/command_runner.ts +33 -0
  73. package/src/comprehension_cache.test.ts +123 -0
  74. package/src/config.test.ts +5 -4
  75. package/src/config.ts +26 -164
  76. package/src/config_discovery.ts +159 -0
  77. package/src/config_validation.test.ts +56 -0
  78. package/src/config_validation.ts +25 -0
  79. package/src/criteria.test.ts +1 -1
  80. package/src/criteria.ts +10 -26
  81. package/src/criteria_spec.ts +12 -20
  82. package/src/criteria_support.ts +2 -19
  83. package/src/doc_reads.test.ts +42 -0
  84. package/src/doc_rules.ts +2 -2
  85. package/src/evaluation_resources.test.ts +64 -0
  86. package/src/execution/config.ts +22 -0
  87. package/src/execution/plan.test.ts +65 -0
  88. package/src/execution/plan.ts +51 -0
  89. package/src/gates.ts +39 -24
  90. package/src/gates_spec.test.ts +110 -4
  91. package/src/gates_test_support.ts +9 -7
  92. package/src/install.test.ts +38 -222
  93. package/src/install.ts +117 -308
  94. package/src/install_loaders.test.ts +203 -0
  95. package/src/install_nested.test.ts +2 -2
  96. package/src/install_packs.test.ts +66 -6
  97. package/src/installation/hooks.ts +63 -0
  98. package/src/installation/lock.test.ts +97 -0
  99. package/src/installation/lock.ts +56 -0
  100. package/src/installation/migration.test.ts +212 -0
  101. package/src/installation/migration.ts +65 -0
  102. package/src/installation/plan.ts +96 -0
  103. package/src/installation/transaction.ts +126 -0
  104. package/src/installation_preservation.test.ts +48 -0
  105. package/src/judgments/advice.test.ts +38 -0
  106. package/src/judgments/advice.ts +32 -0
  107. package/src/judgments/command.integration.test.ts +47 -0
  108. package/src/judgments/command.ts +90 -0
  109. package/src/judgments/constants.ts +55 -0
  110. package/src/judgments/evidence.test.ts +48 -0
  111. package/src/judgments/evidence.ts +34 -0
  112. package/src/judgments/files.test.ts +62 -0
  113. package/src/judgments/files.ts +73 -0
  114. package/src/judgments/questions.test.ts +19 -0
  115. package/src/judgments/questions.ts +85 -0
  116. package/src/judgments/replay.test.ts +43 -0
  117. package/src/judgments/replay.ts +66 -0
  118. package/src/judgments/runner.integration.test.ts +165 -0
  119. package/src/judgments/runner.ts +97 -0
  120. package/src/judgments/scan.test.ts +19 -0
  121. package/src/judgments/scan.ts +40 -0
  122. package/src/judgments/syntax.test.ts +115 -0
  123. package/src/judgments/syntax.ts +71 -0
  124. package/src/judgments/syntax_worker.ts +70 -0
  125. package/src/judgments/test_support.ts +60 -0
  126. package/src/judgments/transport.integration.test.ts +63 -0
  127. package/src/judgments/transport.ts +35 -0
  128. package/src/judgments/types.test.ts +43 -0
  129. package/src/judgments/types.ts +66 -0
  130. package/src/judgments/wire.test.ts +48 -0
  131. package/src/judgments/wire.ts +40 -0
  132. package/src/loaders.test.ts +13 -16
  133. package/src/loaders.ts +28 -10
  134. package/src/model.test.ts +20 -3
  135. package/src/model.ts +7 -18
  136. package/src/pack_hygiene.test.ts +42 -1
  137. package/src/pack_hygiene.ts +18 -8
  138. package/src/references.ts +15 -7
  139. package/src/release_inventory.test.ts +80 -0
  140. package/src/render.test.ts +1 -1
  141. package/src/render.ts +3 -8
  142. package/src/required_scanner.test.ts +36 -0
  143. package/src/review_poll.test.ts +158 -0
  144. package/src/scanner_scope.test.ts +202 -0
  145. package/src/setup.test.ts +9 -6
  146. package/src/spec_baseline.test.ts +1 -1
  147. package/src/spec_completion.test.ts +74 -0
  148. package/src/spec_obligations.ts +64 -0
  149. package/src/spec_rehearsal.test.ts +182 -0
  150. package/src/spec_template_io.test.ts +11 -9
  151. package/src/surfaces.test.ts +2 -0
  152. package/src/surfaces.ts +1 -1
  153. package/src/validation.ts +7 -4
  154. package/src/verify.test.ts +18 -2
  155. package/src/verify.ts +7 -3
@@ -1,12 +1,55 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "type": "object",
4
- "required": ["schema_version", "name", "packs", "commands"],
4
+ "required": ["schema_version"],
5
5
  "properties": {
6
6
  "schema_version": {"const": 1},
7
- "name": {"type": "string"},
8
- "packs": {"type": "array", "items": {"type": "string"}},
9
- "commands": {"type": "object"}
7
+ "orly_version": {"type": "string", "pattern": "^$|^\\d+\\.\\d+\\.\\d+(?:-[a-zA-Z0-9.-]+)?(?:\\+[a-zA-Z0-9.-]+)?$"},
8
+ "packs": {"type": "array", "items": {"$ref": "#/$defs/nonempty"}},
9
+ "managed": {"type": "array", "items": {"$ref": "#/$defs/nonempty"}},
10
+ "digests": {
11
+ "type": "object", "propertyNames": {"$ref": "#/$defs/nonempty"},
12
+ "additionalProperties": {"type": "string", "pattern": "^(?:sha256:)?[a-f0-9]{64}$"}
13
+ },
14
+ "commands": {
15
+ "type": "object", "propertyNames": {"$ref": "#/$defs/nonempty"},
16
+ "additionalProperties": {
17
+ "type": "array", "minItems": 1,
18
+ "items": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/nonempty"}}
19
+ }
20
+ },
21
+ "surfaces": {
22
+ "type": "object", "additionalProperties": false,
23
+ "properties": {
24
+ "user": {"type": "array", "items": {"$ref": "#/$defs/nonempty"}},
25
+ "docs": {"type": "array", "items": {"$ref": "#/$defs/nonempty"}}
26
+ }
27
+ },
28
+ "limits": {
29
+ "type": "object", "propertyNames": {"$ref": "#/$defs/nonempty"},
30
+ "additionalProperties": {
31
+ "type": "object", "additionalProperties": false,
32
+ "properties": {
33
+ "timeout_ms": {"type": "integer", "minimum": 1, "maximum": 2147478647},
34
+ "output_bytes": {"type": "integer", "minimum": 1, "maximum": 9007199254740991}
35
+ }
36
+ }
37
+ },
38
+ "execution": {
39
+ "type": "object", "required": ["remote"], "additionalProperties": false,
40
+ "properties": {
41
+ "remote": {
42
+ "type": "object", "required": ["mode"], "additionalProperties": false,
43
+ "properties": {
44
+ "mode": {"const": "disabled"},
45
+ "launcher": {"type": "array", "minItems": 1, "maxItems": 32, "items": {"allOf": [{"$ref": "#/$defs/nonempty"}, {"maxLength": 1024}]}}
46
+ }
47
+ }
48
+ }
49
+ }
10
50
  },
11
- "additionalProperties": false
51
+ "additionalProperties": false,
52
+ "$defs": {
53
+ "nonempty": {"type": "string", "minLength": 1, "pattern": "^[^\\u0000]+$"}
54
+ }
12
55
  }
@@ -15,7 +15,7 @@ At invocation start, record the consent-gated usage signal without blocking the
15
15
  command -v orly >/dev/null 2>&1 && ORLY_INVOCATION=skill orly skill-event orly-babysit-prs >/dev/null 2>&1 || true
16
16
  ```
17
17
 
18
- Review bots (greptile on GitHub; greptile or another review bot on GitLab)
18
+ Greptile reviews on GitHub and GitLab
19
19
  post auto-reviews **asynchronously**. They land as PR review comments / MR
20
20
  discussion threads, not check runs, so `gh pr checks --watch` (or the glab
21
21
  equivalent) doesn't observe them. Without explicit polling on a backoff
@@ -42,8 +42,8 @@ Then walk it section by section:
42
42
  | Triage section | What this skill triggers |
43
43
  |---|---|
44
44
  | `## Fetch` | Run per detected review thread (multi-thread loop below), via the forge layer |
45
- | `## Suppressions Check` | Read `$HOME/.gstack/projects/<slug>/greptile-history.md`; skip lines tagged `fp` matching `repo + file-pattern + category` |
46
- | `## Classify` | Read file ±10 lines around each comment's `path:line`; classify VALID & ACTIONABLE / VALID BUT ALREADY FIXED / FALSE POSITIVE / SUPPRESSED |
45
+ | `## Suppressions Check` | Read history as context; a prior `fp` label never suppresses a current finding. Dismissal requires the owner's item-specific decision and recorded reason. |
46
+ | `## Classify` | Read the finding's code and evidence; classify actionable, already fixed or disputed. A disputed finding stays open for the owner. |
47
47
  | `## Reply APIs` | Tier 1 first response, Tier 2 on re-flag — via the forge layer |
48
48
  | `## Reply Templates` | Use Tier 1 / Tier 2 template verbatim — do not invent new wording |
49
49
  | `## Severity Assessment & Re-ranking` | Re-rank the bot's severity against the project's actual risk profile |
@@ -74,9 +74,8 @@ echo "BABYSIT: forge=$FORGE origin=$ORIGIN"
74
74
  | "walk every…" | every review id | every discussion thread id |
75
75
  | Reply | reply to the review comment | post a note to the discussion |
76
76
 
77
- Both forges' bots are filtered by author name matching `greptile` (or the
78
- project's configured review bot — see the per-project history `bot:` note if
79
- present).
77
+ Both forges filter bot authors by a case-insensitive `greptile` match.
78
+ Completion, review threads and summaries use that same selection.
80
79
 
81
80
  ## STEP 0.75 — CI checks must go green (poll + fix)
82
81
 
@@ -93,27 +92,42 @@ Each cycle, after the review-thread walk, poll CI and act by cause:
93
92
  ### GitHub
94
93
 
95
94
  ```bash
96
- gh pr checks "$PR_NUMBER" --json name,state,bucket,link 2>/dev/null \
97
- | jq -r '.[] | "\(.bucket)\t\(.state)\t\(.name)\t\(.link)"' # bucket ∈ pass|fail|pending|skipping|cancel
95
+ set -euo pipefail
96
+ CHECKS=$(gh pr checks "$PR_NUMBER" --json name,state,bucket,link) \
97
+ || { echo "BABYSIT: check fetch incomplete; reset quiet polls" >&2; exit 1; }
98
+ printf '%s' "$CHECKS" | jq -e 'type == "array" and length > 0 and all(.[]; .bucket == "pass" or .bucket == "skipping")' >/dev/null \
99
+ || { echo "BABYSIT: checks empty or not green; reset quiet polls" >&2; exit 1; }
100
+ CHECK_HEAD=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid')
101
+ [ "$CHECK_HEAD" = "$POLL_HEAD" ] \
102
+ || { echo "BABYSIT: revision changed during checks; reset quiet polls" >&2; exit 1; }
103
+ echo "BABYSIT: ci=green revision=$CHECK_HEAD"
98
104
  ```
99
105
 
100
106
  ### GitLab
101
107
 
102
108
  ```bash
103
- glab ci status 2>/dev/null \
104
- || glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/pipelines" \
105
- | jq -r '.[0] | "\(.status)\t\(.web_url)"' # status ∈ success|failed|running|canceled|pending
109
+ set -euo pipefail
110
+ PIPELINES=$(glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/pipelines" --paginate | jq -sc 'add') \
111
+ || { echo "BABYSIT: pipeline fetch incomplete; reset quiet polls" >&2; exit 1; }
112
+ printf '%s' "$PIPELINES" | jq -e --arg revision "$POLL_HEAD" \
113
+ '[.[] | select(.sha == $revision)] | sort_by(.id) | last | .status == "success"' >/dev/null \
114
+ || { echo "BABYSIT: current revision has no successful pipeline; reset quiet polls" >&2; exit 1; }
115
+ CHECK_HEAD=$(glab mr view -F json | jq -er '.sha // .diff_refs.head_sha')
116
+ [ "$CHECK_HEAD" = "$POLL_HEAD" ] \
117
+ || { echo "BABYSIT: revision changed during check fetch; reset quiet polls" >&2; exit 1; }
118
+ printf 'ci=green revision=%s\n' "$CHECK_HEAD"
106
119
  ```
107
120
 
108
121
  | CI state | Action |
109
122
  |---|---|
110
- | all `pass`/`skipping` (GitLab `success`) | Record `ci=green`. A done-declaration is now allowed (if greptile is also quiet). |
123
+ | nonempty, completely fetched checks all `pass`/`skipping` (GitLab current-revision `success`) | Record `ci=green` for the unchanged pushed revision. |
124
+ | fetch failed, malformed response, or no checks found | Record `ci=incomplete`; reset quiet polls and retry. An empty response is never green. |
111
125
  | any `pending`/`running` | Record `ci=pending`; re-poll on the cadence. **Never declare done while a check is still running.** |
112
126
  | any `fail`/`cancel` **from your changes** | In-scope finding you OWN — treat like a greptile fix. Fetch the failing log (`gh run view <run-id> --log-failed`, run id from the check `link`; GitLab `glab ci trace <job-id>`), fix the code, re-verify with the relevant `make` target, commit, push, re-poll. |
113
127
  | any `fail` **NOT from your changes** (pre-existing red, infra flake, unrelated job) | Surface to the user with job name + link. Do **not** fix blindly, do **not** edit CI config. Pause the done-declaration until they decide. |
114
128
 
115
129
  CI failures caused by your commit are not "surface and wait" — they are the
116
- same contract as a greptile P0: diagnose, fix the code (never the CI config),
130
+ same repair duty as a greptile P0: diagnose, fix the code (never the CI config),
117
131
  push, and re-poll until green.
118
132
 
119
133
  ## Triggers
@@ -123,7 +137,7 @@ push, and re-poll until green.
123
137
  - User says: "babysit", "watch greptile", "poll the PR", "poll the MR",
124
138
  "watch reviews", "follow up on review feedback", "what did greptile say".
125
139
 
126
- ## Output contract
140
+ ## Output rules
127
141
 
128
142
  Per cycle, print one line (`PR` on GitHub, `MR` on GitLab):
129
143
 
@@ -152,17 +166,38 @@ via the agent's native cron/wakeup mechanism.
152
166
  ### GitHub (`FORGE=github`)
153
167
 
154
168
  ```bash
169
+ set -euo pipefail
155
170
  REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
156
171
  PR_NUMBER=$(gh pr view --json number --jq '.number')
172
+ POLL_HEAD=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid')
173
+ [ "$POLL_HEAD" = "$(git rev-parse HEAD)" ] \
174
+ || { echo "BABYSIT: local and pushed revisions differ; reset quiet polls" >&2; exit 1; }
175
+
176
+ REVIEWS=$(gh api "repos/$REPO/pulls/$PR_NUMBER/reviews" --paginate --slurp | jq -c 'add')
177
+ COMMENTS=$(gh api "repos/$REPO/pulls/$PR_NUMBER/comments" --paginate --slurp | jq -c 'add')
178
+ SUMMARIES=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" --paginate --slurp | jq -c 'add')
179
+ for payload in "$REVIEWS" "$COMMENTS" "$SUMMARIES"; do
180
+ printf '%s' "$payload" | jq -e 'type == "array" and all(.[]; (.id | type == "number") and (.user.login | type == "string"))' >/dev/null \
181
+ || { echo "BABYSIT: malformed review response; reset quiet polls" >&2; exit 1; }
182
+ done
183
+ printf '%s' "$REVIEWS" | jq -e --arg revision "$POLL_HEAD" --arg bot "greptile" \
184
+ 'any(.[]; (.user.login | test($bot; "i")) and .commit_id == $revision and (.state == "COMMENTED" or .state == "APPROVED" or .state == "CHANGES_REQUESTED"))' >/dev/null \
185
+ || { echo "BABYSIT: current revision review pending; reset quiet polls" >&2; exit 1; }
186
+ END_HEAD=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid')
187
+ [ "$POLL_HEAD" = "$END_HEAD" ] \
188
+ || { echo "BABYSIT: revision changed during review fetch; reset quiet polls" >&2; exit 1; }
189
+ jq -n --arg revision "$POLL_HEAD" --argjson reviews "$REVIEWS" \
190
+ --argjson comments "$COMMENTS" --argjson summaries "$SUMMARIES" \
191
+ '{revision:$revision,reviews:$reviews,comments:$comments,summaries:$summaries}'
157
192
 
158
193
  # Walk EVERY review id — greptile may post more than one review per push.
159
- REVIEW_IDS=$(gh api "repos/$REPO/pulls/$PR_NUMBER/reviews" \
160
- --jq '.[] | select(.user.login | test("greptile"; "i")) | .id')
194
+ REVIEW_IDS=$(printf '%s' "$REVIEWS" | jq -r \
195
+ '.[] | select(.user.login | test("greptile"; "i")) | .id')
161
196
 
162
197
  for rid in $REVIEW_IDS; do
163
198
  # Follow greptile-triage.md (STEP 0): fetch line+top-level comments for THIS
164
199
  # review id; Suppressions Check; Classify; Reply (Tier 1/Tier 2); history write.
165
- # Fetch: gh api "repos/$REPO/pulls/$PR_NUMBER/reviews/$rid/comments"
200
+ # Fetch from the complete COMMENTS snapshot by pull_request_review_id.
166
201
  # Reply: gh api -X POST "repos/$REPO/pulls/$PR_NUMBER/comments/<cid>/replies" -f body="..."
167
202
  :
168
203
  done
@@ -173,26 +208,32 @@ done
173
208
  # (2) the top-level PR issue comment (greptile's "description reply" / summary,
174
209
  # often with collapsible per-file sections listing concerns inline).
175
210
  # Run each through greptile-triage.md exactly like a line comment.
176
- gh api "repos/$REPO/pulls/$PR_NUMBER/reviews" \
177
- --jq '.[] | select(.user.login|test("greptile";"i")) | select(.body != "") | .body'
178
- gh api "repos/$REPO/issues/$PR_NUMBER/comments" \
179
- --jq '.[] | select(.user.login|test("greptile";"i")) | {id, body}'
211
+ printf '%s' "$REVIEWS" | jq -r '.[] | select(.user.login|test("greptile";"i")) | select(.body != "") | .body'
212
+ printf '%s' "$SUMMARIES" | jq -c '.[] | select(.user.login|test("greptile";"i")) | {id, body}'
180
213
  # Reply to a summary issue comment: gh api -X POST "repos/$REPO/issues/$PR_NUMBER/comments" -f body="..."
181
214
  ```
182
215
 
183
216
  ### GitLab (`FORGE=gitlab`)
184
217
 
185
218
  ```bash
219
+ set -euo pipefail
186
220
  # Project full path (e.g. gpu/console), URL-encoded for the API.
187
- PROJECT_PATH=$(glab repo view -F json 2>/dev/null | jq -r '.path_with_namespace' \
188
- || git remote get-url origin | sed -E 's#.*[:/]([^:/]+/[^/]+)$#\1#; s#\.git$##')
189
- PROJ_ENC=$(printf '%s' "$PROJECT_PATH" | sed 's#/#%2F#g')
190
- MR_IID=$(glab mr view -F json | jq -r '.iid')
221
+ PROJECT_PATH=$(glab repo view -F json | jq -er '.path_with_namespace | strings | select(length > 0)')
222
+ PROJ_ENC=$(printf '%s' "$PROJECT_PATH" | jq -sRr '@uri')
223
+ MR_IID=$(glab mr view -F json | jq -er '.iid | numbers')
224
+ POLL_HEAD=$(glab mr view -F json | jq -er '.sha // .diff_refs.head_sha')
225
+ [ "$POLL_HEAD" = "$(git rev-parse HEAD)" ] \
226
+ || { echo "BABYSIT: local and pushed revisions differ; reset quiet polls" >&2; exit 1; }
191
227
 
192
228
  # Walk EVERY discussion thread from the review bot. GitLab has no "review"
193
229
  # object — a bot posts discussion threads (each with notes[]).
194
- THREAD_IDS=$(glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/discussions" --paginate \
195
- | jq -r '.[] | select(any(.notes[]; .author.username | test("greptile"; "i"))) | .id')
230
+ DISCUSSIONS=$(glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/discussions" --paginate | jq -sc 'add')
231
+ NOTES=$(glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/notes" --paginate | jq -sc 'add')
232
+ printf '%s' "$DISCUSSIONS" | jq -e 'type == "array" and all(.[]; (.id | type == "string") and (.notes | type == "array") and all(.notes[]; (.author.username | type == "string")))' >/dev/null \
233
+ || { echo "BABYSIT: malformed discussion evidence; reset quiet polls" >&2; exit 1; }
234
+ printf '%s' "$NOTES" | jq -e 'type == "array" and all(.[]; (.id | type == "number") and (.author.username | type == "string"))' >/dev/null \
235
+ || { echo "BABYSIT: malformed summary evidence; reset quiet polls" >&2; exit 1; }
236
+ THREAD_IDS=$(printf '%s' "$DISCUSSIONS" | jq -r '.[] | select(any(.notes[]; .author.username | test("greptile"; "i"))) | .id')
196
237
 
197
238
  for tid in $THREAD_IDS; do
198
239
  # Follow greptile-triage.md (STEP 0): fetch the thread's notes (body +
@@ -206,9 +247,13 @@ done
206
247
  # ALSO triage greptile's SUMMARY feedback — the MR-level notes greptile posts
207
248
  # outside a positioned discussion thread (the "description reply" / overview
208
249
  # note, often with inline per-file concerns). Run each through greptile-triage.md.
209
- glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/notes" \
210
- --jq '.[] | select(.author.username|test("greptile";"i")) | select(.type == null) | {id, body}'
250
+ printf '%s' "$NOTES" | jq -c '.[] | select(.author.username|test("greptile";"i")) | select(.type == null) | {id, body}'
211
251
  # Reply to a summary note: glab api -X POST "projects/$PROJ_ENC/merge_requests/$MR_IID/notes" -f body="..."
252
+ END_HEAD=$(glab mr view -F json | jq -er '.sha // .diff_refs.head_sha')
253
+ [ "$POLL_HEAD" = "$END_HEAD" ] \
254
+ || { echo "BABYSIT: revision changed during review fetch; reset quiet polls" >&2; exit 1; }
255
+ jq -n --arg revision "$POLL_HEAD" --argjson discussions "$DISCUSSIONS" --argjson summaries "$NOTES" \
256
+ '{revision: $revision, discussions: $discussions, summaries: $summaries}'
212
257
  ```
213
258
 
214
259
  ### History paths (forge-independent)
@@ -232,16 +277,18 @@ Every triaged comment writes one line to BOTH:
232
277
 
233
278
  | File | Purpose |
234
279
  |---|---|
235
- | `$HOME/.gstack/projects/<slug>/greptile-history.md` | Per-project. Drives Suppressions Check on next cycle — `fp` lines suppress matching comments forever. |
280
+ | `$HOME/.gstack/projects/<slug>/greptile-history.md` | Per-project context; no historical label automatically dismisses a finding. |
236
281
  | `$HOME/.gstack/greptile-history.md` | Global aggregate. Cross-project retro / pattern-mining. |
237
282
 
238
283
  Line format (from the triage helper's "History File Writes" section):
239
284
 
240
285
  ```
241
- <YYYY-MM-DD> | <owner/repo> | <type:fp|fix|already-fixed> | <file-pattern> | <category>
286
+ <YYYY-MM-DD> | <owner/repo> | <type:fp|fix|already-fixed|disputed> | <file-pattern> | <category>
242
287
  ```
243
288
 
244
- - **`fp`** — false positive. Suppressed on future polls.
289
+ - **`fp`** — owner-confirmed dismissal for a named finding, with its quote,
290
+ reason and reviewed revision recorded in Session Notes. Reassess on later revisions.
291
+ - **`disputed`** — awaiting the owner's decision; remains open and blocks done.
245
292
  - **`fix`** — real issue, fixed in this cycle. Not suppressed.
246
293
  - **`already-fixed`** — real issue, fixed in a prior commit on this branch. Not suppressed.
247
294
 
@@ -263,10 +310,16 @@ The categories are a fixed set: `race-condition`, `null-check`, `error-handling`
263
310
 
264
311
  ## Stop conditions
265
312
 
266
- - **Done = two consecutive empty polls AND CI green.** Both must hold: no new
313
+ - **Done = two complete, consecutive empty polls AND CI green on the same pushed revision.** Both must hold: no new
267
314
  reviews/threads/summary findings for two polls **and** STEP 0.75 reports
268
315
  `ci=green`. A quiet bot with a still-`pending` or red CI is NOT done — keep
269
316
  polling on the cadence.
317
+ - Every cycle fetches all pages of reviews, line comments and summaries.
318
+ Fetch failure, missing checks, a disputed finding or a changed revision resets
319
+ the quiet counter; compare the remote revision again after check collection.
320
+ - Missing review completion for the current pushed revision is `review=pending`,
321
+ even when all fetches succeed. Surface it after the recorded retry cadence;
322
+ two empty responses alone cannot prove the reviewer completed its work.
270
323
  - **PR/MR merged or closed** → stop, report.
271
324
  - **CI red FROM your changes** → fix it (STEP 0.75): diagnose, fix the code,
272
325
  push, re-poll. Not a stop condition — it's work to do.
@@ -320,7 +373,7 @@ BABYSIT REPORT — <PR|MR> #<n> @ <SHA>
320
373
  - `docs/greptile-learnings/RULES.md` — project-level named rules
321
374
  (durable principles). New rule added here only when the finding
322
375
  generalizes; per-incident rows go to greptile-history.md.
323
- - this repository's `AGENTS.md` / `AGENTS.orly.md` — CHORE(close) skill
376
+ - this repository's `AGENTS.md` / `.orly/AGENTS.md` — CHORE(close) skill
324
377
  chain step 4 cites this skill.
325
378
  - `gh api` docs: https://cli.github.com/manual/gh_api
326
379
  - `glab api` docs: https://gitlab.com/gitlab-org/cli (`glab api --help`)
@@ -60,6 +60,13 @@ Determinism starts here. Before copying the template:
60
60
 
61
61
  ## Step 2 — Lock review-readiness (so the PR ships clean)
62
62
 
63
+ **Optional Jev advice:** After capturing one concrete requirement, prepare a
64
+ `plan` manifest for `plan.prerequisites` or `plan.observable_result`.
65
+ Use `orly judge plan --input <manifest>` for exact offline replay. Add
66
+ `--refresh` only when selected source upload is authorized; use
67
+ `orly judge --help` for the manifest and limits. Inspect uncertain or adverse answers before accepting
68
+ the plan. Advice never replaces this skill's checklist or owner decisions.
69
+
63
70
  This is the step that prevents greptile findings — the spec becomes a pre-commitment to the rules its code must obey:
64
71
 
65
72
  - **Applicable Rules** — name the *specific* `docs/greptile-learnings/RULES.md` rule IDs the diff will trip (e.g. NDC, NLR, NLG, UFS), plus the per-surface dispatch façades / rule files: `dispatch/write_zig.md` (`*.zig`), `docs/REST_API_DESIGN_GUIDELINES.md` (`src/http/handlers/**`), `docs/SCHEMA_CONVENTIONS.md` (`schema/*`), `dispatch/write_ts_adhere_bun.md`, `docs/LOGGING_STANDARD.md`, `docs/LIFECYCLE_PATTERNS.md`. Generic "follow RULES.md" earns a greptile finding; named IDs the implementer obeys by construction do not. Cite rule docs by their repo-local `docs/…` paths — the selected packs materialise them into every repository; cite only files that exist here (`test -f` each).
@@ -88,7 +95,8 @@ Now the file. Pick the ID and copy the template:
88
95
  # next free M{N}_{WS}
89
96
  ls docs/v*/pending/ docs/v*/active/ docs/v*/done/ 2>/dev/null \
90
97
  | grep -oE 'M[0-9]+_[0-9]+' | sort -u | tail -5
91
- cp docs/TEMPLATE.md docs/v{N}/pending/M{N}_{WS}_P{P}_{CATEGORIES}_{NAME}.md
98
+ sed -n '/^# Spec Body — Copy Everything Below This Line/,$p' docs/TEMPLATE.md \
99
+ | sed '1d' > docs/v{N}/pending/M{N}_{WS}_P{P}_{CATEGORIES}_{NAME}.md
92
100
  ```
93
101
 
94
102
 
@@ -125,17 +133,22 @@ Before the spec leaves `pending/`, it must pass this checklist — the determini
125
133
  - [ ] reporting sections present (Discovery; Acceptance Rubric with Graded column empty)
126
134
  - [ ] `bash audits/spec-template.sh --staged` is clean (it BLOCKs missing required sections and unfilled `{placeholders}`)
127
135
 
128
- Then commit in the current authoring context:
136
+ Commit a new milestone spec on the repository's default branch, as the
137
+ operating model requires. Check the remote and branch before authoring;
138
+ do not infer the default branch name.
129
139
 
130
- - If the skill is running inside an existing branch/worktree, author and commit the spec there.
131
- - If there is no branch/worktree context, use the repo's `main` branch as the fallback.
140
+ - Inside an active stream, revise its existing spec on the active branch.
141
+ - A new, separate milestone inside an active stream needs the owner's decision
142
+ before creating another worktree or changing branches.
143
+ - Never move the active stream's uncommitted work to the default branch.
132
144
 
133
145
  ```bash
134
146
  git add docs/v{N}/pending/M{N}_{WS}_*_{NAME}.md
135
147
  git commit -m "docs(m{N}): add spec — {short title}"
136
148
  ```
137
149
 
138
- The spec lands in `pending/` on the branch/worktree where the skill was invoked. CHORE(open) moves it to `active/` and creates any needed worktree — handled by the lifecycle, not this skill.
150
+ The new spec lands in `pending/` on the default branch.
151
+ CHORE(open) moves it to `active/` and creates the implementation worktree.
139
152
 
140
153
  ---
141
154
 
@@ -143,7 +156,7 @@ The spec lands in `pending/` on the branch/worktree where the skill was invoked.
143
156
 
144
157
  - It does **not** start coding, create a worktree, or move the spec to `active/`. That's CHORE(open), when implementation begins.
145
158
  - It does **not** modify any rule file, ARCHITECTURE doc, or changelog.
146
- - It does **not** create a branch just to author a spec. Spec creation uses the current branch/worktree, falling back to `main` only when no branch/worktree exists.
159
+ - It does not create an implementation branch or silently switch an active stream.
147
160
 
148
161
  ## Failure modes
149
162
 
@@ -68,7 +68,7 @@ Spec asserts "503 on Redis down", code returns 200 → test the spec, flag the c
68
68
 
69
69
  1. **Read the spec** — Failure Modes, Error Contracts, Concurrency Contracts, Resource Limits, Streaming Contracts tables are authoritative.
70
70
  2. **Read `docs/greptile-learnings/RULES.md`** — every rule maps to a regression test.
71
- 3. **Bring up real deps** — `make up` (or stack equivalent). Verify health before writing tests.
71
+ 3. **Bring up real deps** — use the repository's declared local dependency startup command. Verify health before writing tests; do not add a datastore the application does not use.
72
72
  4. **Follow lifecycle timing** — record the comparison revision at opening; measure full unit and integration baselines before the Pull Request. Run focused reproduction and Section proofs while implementing; first application hydration occurs at Section verification. `dispatch/lifecycle.md` owns the sequence.
73
73
  5. **Map the ordered request path** — list every dependency interaction in execution order, including repeated acquisitions of the same resource, plus every `catch`/`orelse`/`except`/`Err` in the chain. That list seeds T4 and its partial-completion matrix.
74
74
 
@@ -80,7 +80,7 @@ Spec asserts "503 on Redis down", code returns 200 → test the spec, flag the c
80
80
  | **Standard** | New service method, new Redis stream/key, schema change with logic | + T4 + T5 + T6 |
81
81
  | **Hardening** | Auth / payment / lease / migration / streaming / anything in the data-loss radius | + T7 (if applicable) + T8 + chaos pass |
82
82
 
83
- Auto-detect from diff: `src/auth/**`, `src/agent/leases/**`, `src/runner/**` + `rustd/crates/afd_fleet/**` (the lease/reclaim/fence + client-daemon surface → T9), schema migrations, streaming handlers → Hardening. CRUD-only against existing schema → Smoke. Default → Standard.
83
+ Auto-detect from the repository's actual authentication, lease/reclaim/fence and client-daemon surfaces, schema migrations and streaming handlers → Hardening. The client-daemon surface also requires T9. CRUD-only against existing schema → Smoke. Default → Standard.
84
84
 
85
85
  Any operation that touches multiple systems or acquires the same resource more than once is **Standard at minimum** and must run T4, even when the endpoint is otherwise CRUD-only.
86
86
 
@@ -201,7 +201,7 @@ idempotency, compensation, reconciliation, and failure tests for each residual s
201
201
  **Parallelism at scale (≥100 connections) — proves there's no hidden global lock.**
202
202
  A single global `Thread.Mutex` keeps every correctness test green while serialising
203
203
  every request; this is the regression that proof exists to catch.
204
- - **≥100 concurrent connections** against real PG + Redis, released together off a
204
+ - **≥100 concurrent connections** against the application's real dependencies, released together off a
205
205
  **barrier** so contention is real and reproducible (not staggered by spawn latency).
206
206
  - **Correctness invariant (deterministic hard gate):** same row / idempotency key / lease
207
207
  under the 100-way race → **exactly one effect**, no lost update.
@@ -393,7 +393,7 @@ Cross-stack failure-injection tools:
393
393
 
394
394
  Stack-specific must-knows:
395
395
 
396
- - **Zig** — `conn.query()` paired with `.drain()` in the same fn before `deinit()`; verify with the project's drain-audit target. Per-request arena, not per-app. `std.testing.allocator` over the full request lifecycle, not just handler unit, to catch cross-thread leaks. If integration tests share the binary with unit tests, an env var / build flag should gate live-deps mode — assert that gate is set in CI, not assumed. Cross-compile (`x86_64-linux`, `aarch64-linux`) before declaring done.
396
+ - **Zig** — drain every query result before releasing its connection, including early exits. Use wrapper-owned draining where the repository provides it. Review the owner's cleanup path and run any actually declared cleanup check. Use a per-request arena for request ownership. Exercise `std.testing.allocator` over the full request lifecycle to catch cross-thread leaks. If integration tests share the binary with unit tests, assert the live-dependency setting in Continuous Integration (CI). Cross-compile the repository's declared platform matrix before declaring done.
397
397
  - **Python** — `pytest-asyncio` event loop scope = `function`, not `session` (avoid leakage). `respx`/`responses` for external HTTP only.
398
398
  - **Rust** — `#[sqlx::test]` for per-test schema; `tokio::test(flavor = "multi_thread")` for real concurrency. `loom` for races, not raw threads.
399
399
  - **Node** — `--isolate` per test if framework supports; otherwise reset module registry.
@@ -83,6 +83,15 @@ codebase, and it's the artifact that makes "is the skill applied?" auditable.
83
83
 
84
84
  ## Three execution modes
85
85
 
86
+ **Optional Jev advice:** Before accepting a linked test as proof, describe its
87
+ required behavior in a `verify.assertion` manifest with complete implementation
88
+ and test evidence. Use `orly judge verify --input <manifest>` for exact replay;
89
+ `--refresh` explicitly permits scanned source upload. Use `orly judge --help`
90
+ for the manifest and limits.
91
+ A weak assertion answer suggests an exact-result assertion. Run that assertion
92
+ against correct and controlled incorrect behavior. Jev advice never supplies
93
+ test results, waives missing coverage or changes a deterministic gate.
94
+
86
95
  Pick one. Don't run all categories on every file.
87
96
 
88
97
  | Mode | When | Required categories |
@@ -275,7 +284,7 @@ When a spec exists with these tables, treat them as authoritative test sources:
275
284
 
276
285
  | Spec table | Generates |
277
286
  |---|---|
278
- | Test Specification | One test per row, name = `dim X.Y: <name>` |
287
+ | Test Specification | One behavior-named test per row; the spec maps Dimension numbers to test names. Keep planning identifiers out of source test names and filenames. |
279
288
  | Error Contracts | One negative test per row asserting exact error |
280
289
  | Failure Modes | One integration test per row with deterministic injection |
281
290
  | Partial Completion | One row per ordered failure point with workflow-ordinal and boundary-call-ordinal injection plus residual-state assertions |
package/src/cli.test.ts CHANGED
@@ -32,7 +32,7 @@ describe("orly command", () => {
32
32
  });
33
33
 
34
34
  // Adversarial review found recordOptIn running before the --dry-run branch,
35
- // so a preview created .oracle/orly.json — and on an installed repository
35
+ // so a preview created .orly/orly.json — and on an installed repository
36
36
  // permanently added the pack. A dry run changes nothing, opt-in included.
37
37
  test("--dry-run records no pack", () => {
38
38
  const repo = mkdtempSync(join(tmpdir(), "orly-dry-"));
@@ -44,7 +44,7 @@ describe("orly command", () => {
44
44
 
45
45
  expect(result.exitCode).toBe(0);
46
46
  expect(result.stdout.toString()).toContain("Generated by");
47
- expect(existsSync(join(repo, ".oracle/orly.json"))).toBe(false);
47
+ expect(existsSync(join(repo, ".orly/orly.json"))).toBe(false);
48
48
  expect(existsSync(join(repo, "AGENTS.orly.md"))).toBe(false);
49
49
  } finally {
50
50
  rmSync(repo, { recursive: true, force: true });
package/src/cli.ts CHANGED
@@ -2,8 +2,11 @@
2
2
  import { existsSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
 
5
- import { CONFIG_PATH, localSelection, managedDrift, readConfig, RepoConfig, seedConfig, selectPacks, staleVersion, writeConfig } from "./config";
5
+ import { printHelp } from "./cli_help";
6
+ import { selectPacks } from "./config_discovery";
7
+ import { CONFIG_PATH, managedDrift, readConfig, RepoConfig, staleVersion, writeConfig } from "./config";
6
8
  import { GateReport, isGateName, recordOverride, runGate, runGates } from "./gates";
9
+ import { readInstallConfig } from "./installation/migration";
7
10
  import { install, InstallResult } from "./install";
8
11
  import { isString, OrlyError, readJsonObject, RulesModel } from "./model";
9
12
  import { Renderer } from "./render";
@@ -33,9 +36,6 @@ const PR_GATE = "pr";
33
36
  const ENGINE_MARKER = "evals/install/run.sh";
34
37
  const VERIFY_COMMAND = "verify";
35
38
  const SKILL_EVENT_COMMAND = "skill-event";
36
- const TELEMETRY_CONFIG_KEY = "telemetry";
37
- const ANONYMOUS_TELEMETRY_CONFIG = JSON.stringify({ [TELEMETRY_CONFIG_KEY]: "anonymous" });
38
- const OFF_TELEMETRY_CONFIG = JSON.stringify({ [TELEMETRY_CONFIG_KEY]: "off" });
39
39
 
40
40
  const { root, arguments: commandArguments } = parseRoot(Bun.argv.slice(2));
41
41
  const startedAt = performance.now();
@@ -75,6 +75,16 @@ async function run(model: RulesModel, args: string[]): Promise<CliResult> {
75
75
  if (command === "init") return cliResult(await materialise(model, rest, true));
76
76
  if (command === "update") return cliResult(await materialise(model, rest, false));
77
77
  if (command === VERIFY_COMMAND) return cliResult(await verify(model, rest));
78
+ if (command === "judge") {
79
+ const { judgmentCommand } = await import("./judgments/command");
80
+ return cliResult(await judgmentCommand(rest));
81
+ }
82
+ if (command === "lifecycle") {
83
+ if (rest.some((argument) => argument !== JSON_FLAG)) throw new OrlyError("usage: orly lifecycle [--json]");
84
+ const { lifecyclePlan } = await import("./execution/plan");
85
+ console.log(JSON.stringify(lifecyclePlan(projectRoot()), undefined, JSON_INDENT));
86
+ return cliResult(0);
87
+ }
78
88
  if (command === "gate") return gate(model, rest);
79
89
  if (command === "override") return cliResult(override(rest));
80
90
  if (command === SKILL_EVENT_COMMAND) {
@@ -131,12 +141,12 @@ async function packageVersion(model: RulesModel): Promise<string> {
131
141
  return manifest.version;
132
142
  }
133
143
 
134
- // Both verbs materialise the same way: the repository's own `.oracle/orly.json`
144
+ // Both verbs materialise the same way: the repository's own `.orly/orly.json`
135
145
  // names its packs and commands, so neither asks the caller who this repo is.
136
146
  async function materialise(model: RulesModel, args: string[], isInit: boolean): Promise<number> {
137
147
  model.validate();
138
148
  const targetRoot = projectRoot();
139
- const existing = await readConfig(targetRoot);
149
+ const existing = await readInstallConfig(targetRoot);
140
150
  if (!isInit && !existing) throw new OrlyError(`no ${CONFIG_PATH} here — run \`orly init\` first`);
141
151
  const requested = optionalValues(args, WITH_FLAG);
142
152
  // --dry-run changes nothing, and recording an opt-in pack is a change. It
@@ -146,9 +156,9 @@ async function materialise(model: RulesModel, args: string[], isInit: boolean):
146
156
  // Otherwise the pack is a property of the repository, recorded before the
147
157
  // render rather than passed through it: the next `orly update` in a
148
158
  // teammate's clone selects the same set with no flag to remember.
149
- await recordOptIn(model, targetRoot, existing, requested);
150
159
  const result = await install(model, {
151
160
  targetRoot,
161
+ requestedPacks: requested,
152
162
  force: args.includes(FORCE_FLAG),
153
163
  installHooks: !args.includes(NO_HOOKS_FLAG),
154
164
  orlyVersion: await packageVersion(model),
@@ -210,7 +220,8 @@ async function doctorInstall(model: RulesModel): Promise<string[]> {
210
220
  // What init would write, without writing it. Replaces the `render` verb: the
211
221
  // preview belongs on the command you are about to run, not beside it.
212
222
  async function preview(model: RulesModel, targetRoot: string, requested: string[] = []): Promise<number> {
213
- const local = await localSelection(model, targetRoot);
223
+ const config = await readInstallConfig(targetRoot);
224
+ const local = { packs: selectPacks(model, targetRoot, config?.packs ?? [], new Set(config?.managed ?? [])), commands: config?.commands ?? {} };
214
225
  // selectPacks validates the requested names and unions them with what the
215
226
  // repository's own sources select — the same call the real run makes, minus
216
227
  // the write.
@@ -220,20 +231,6 @@ async function preview(model: RulesModel, targetRoot: string, requested: string[
220
231
  return 0;
221
232
  }
222
233
 
223
- // Opt-in packs never auto-select from file extensions, so naming one is the
224
- // only way it lands. Validated against the registry here, before anything is
225
- // recorded, so a typo names the available set instead of writing a config the
226
- // next command rejects.
227
- async function recordOptIn(model: RulesModel, targetRoot: string, existing: RepoConfig | undefined, requested: string[]): Promise<void> {
228
- if (requested.length === 0) return;
229
- const config = existing ?? await seedConfig(targetRoot);
230
- const packs = new Set(config.packs);
231
- for (const name of requested) packs.add(name);
232
- // selectPacks throws OrlyError on an unknown name, listing what is available.
233
- selectPacks(model, targetRoot, [...packs]);
234
- await writeConfig(targetRoot, { ...config, packs: [...packs].sort() });
235
- }
236
-
237
234
  async function verify(model: RulesModel, args: string[]): Promise<number> {
238
235
  requireEngineCheckout(model, VERIFY_COMMAND);
239
236
  const checks = await verifyRenders(model);
@@ -290,56 +287,3 @@ function optionalValues(args: string[], name: string): string[] {
290
287
  function cliResult(exitCode: number): CliResult {
291
288
  return { exitCode };
292
289
  }
293
-
294
- function printHelp(): void {
295
- console.log(`orly — prove the boundary; carry the rules
296
-
297
- Gates (read-only; no PR without every criterion green or a recorded override):
298
- orly gate [--accept-dirty] run work → verify → pr; stop at first red
299
- orly gate <work|verify|pr> run one gate
300
- work does this commit conform? the declared conform command; no git
301
- state, so a commit hook's own dirty tree never blocks it
302
- verify does the work hold up? configuration, docs language, and the
303
- non-test verify.* set; unfinished Sections may be pushed
304
- pr can this ship? branch, tree, pushed, every spec criterion, and
305
- every declared verify.* command
306
- orly override <CRITERION> --reason <REASON>
307
- empty commit with an Orly-Override trailer
308
-
309
- Install (the repository is the unit — no checkout of this package required):
310
- orly init [--force] [--no-hooks] [--with <PACK>] [--dry-run] [--json]
311
- materialise rules, gates, hooks, and a
312
- seeded .oracle/orly.json. A repository that
313
- already has an AGENTS.md keeps it: orly's
314
- rules land as AGENTS.orly.md, reached by a
315
- pointer block in the file you own. One
316
- import line lands per runtime that needs
317
- one (CLAUDE.md, opencode.json), so the
318
- rules load rather than wait to be noticed;
319
- a loader you wrote yourself is left alone.
320
- orly update [--force] [--with <PACK>] [--dry-run] [--json]
321
- re-materialise at the installed engine version
322
-
323
- --with <PACK> record an opt-in pack (repeatable) in
324
- .oracle/orly.json, so every clone selects it
325
- --dry-run show what would be written; change nothing
326
-
327
- orly doctor check this repository's installed rules
328
- against what orly would write today
329
- orly --version the installed package version
330
-
331
- Usage telemetry (off by default):
332
- Consent file: ~/.config/agentsfleet/orly/.orly.json
333
- State-root override: AGENTSFLEET_STATE_DIR
334
- Set ${ANONYMOUS_TELEMETRY_CONFIG} to send command, gate, and packaged Orly skill
335
- names, outcome, failed criterion, duration, Orly version, operating system, architecture,
336
- invocation type, timestamps, and random event, session, and installation IDs.
337
- Set ${OFF_TELEMETRY_CONFIG} to write and send nothing.
338
- ORLY_TELEMETRY=off|anonymous changes one process; ORLY_TELEMETRY_OFF=1 wins.
339
- Orly never collects source, paths, repositories, branches, arguments, prompts,
340
- output, environment values, raw errors, or personal and account details.
341
-
342
- Ruleset authoring (an orly checkout only — refused from an installed package):
343
- orly verify the rules render the same twice, and the
344
- committed copy matches that render`);
345
- }