@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,353 @@
1
+ #!/usr/bin/env bash
2
+ # Deterministic audit for AGENTS.md.
3
+ # Exit 0 = all checks pass. Non-zero = regression — see FAIL lines on stderr.
4
+ #
5
+ # Run before every AGENTS.md change (and from the pre-commit hook).
6
+ # Each check asserts an invariant that, if broken, would re-introduce a
7
+ # concrete failure mode the operating model already paid for.
8
+
9
+ set -uo pipefail
10
+
11
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
12
+ AGENTS="$ROOT/AGENTS.md"
13
+ FIXTURES="$ROOT/audits/fixtures"
14
+ FAIL=0
15
+
16
+ # Expectation tables (EXPECTED_LABELS, REQUIRED_GATES, EXTS, FORBIDDEN_KEYS,
17
+ # CONFORM_KEYS, DOTFILES_RESIDENT, LIFECYCLE_HEADERS, NAMED_SCENARIOS,
18
+ # RULE_EXTENSION_STEPS, AWK_PROG) live in the sibling data file — split out
19
+ # to keep this audit under the LENGTH GATE cap. Data drift is itself caught
20
+ # by the gate-parity + named-scenario-parity checks below.
21
+ DATA="$ROOT/audits/data.sh"
22
+ [[ -f "$DATA" ]] || { echo "FAIL: $DATA missing (audit data tables)" >&2; exit 2; }
23
+ # shellcheck source=audits/data.sh
24
+ . "$DATA"
25
+ SEEN_LABELS=()
26
+
27
+ # Colours — disable when stdout is not a TTY (e.g. piped, hook).
28
+ if [[ -t 1 ]]; then
29
+ C_GREEN=$'\033[32m'; C_RED=$'\033[31m'; C_YELLOW=$'\033[33m'
30
+ C_BLUE=$'\033[34m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m'
31
+ else
32
+ C_GREEN=''; C_RED=''; C_YELLOW=''; C_BLUE=''; C_BOLD=''; C_RESET=''
33
+ fi
34
+
35
+ fail() { printf '%s🔴 FAIL%s: %s\n' "$C_RED" "$C_RESET" "$*" >&2; FAIL=1; SEEN_LABELS+=("$*"); }
36
+ pass() { printf '%s🟢 PASS%s: %s\n' "$C_GREEN" "$C_RESET" "$*"; SEEN_LABELS+=("$*"); }
37
+ warn() { printf '%s🟡 WARN%s: %s\n' "$C_YELLOW" "$C_RESET" "$*" >&2; }
38
+ info() { printf '%s🔆 %s%s\n' "$C_BLUE" "$*" "$C_RESET"; }
39
+
40
+ info "Auditing $AGENTS"
41
+
42
+ [[ -f "$AGENTS" ]] || { echo "FAIL: $AGENTS missing" >&2; exit 2; }
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # 1. Dispatch inventory — every dispatch entry (REQUIRED_DISPATCH in data) has
46
+ # a row in the AGENTS.md dispatch table, with the entry name as the table's
47
+ # SECOND column ("| <trigger> | `<entry>` | …"). This is the same column the
48
+ # parity check (check 9b) counts, so inventory and parity agree by
49
+ # construction — a renamed/dropped entry fails here AND there.
50
+ # ---------------------------------------------------------------------------
51
+ bt='`' # backtick held in a var — keeps it explicit inside the regex
52
+ missing_dispatch=0
53
+ for e in "${REQUIRED_DISPATCH[@]}"; do
54
+ grep -qE "^\|[^|]*\| *${bt}${e}${bt} *\|" "$AGENTS" \
55
+ || { fail "dispatch entry missing from table: $e"; missing_dispatch=1; }
56
+ done
57
+ [[ $missing_dispatch -eq 0 ]] && pass "dispatch inventory (${#REQUIRED_DISPATCH[@]} entries present in table)"
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # 2. Trigger surface — every source/config language has a mention (EXTS in data).
61
+ # ---------------------------------------------------------------------------
62
+ missing_exts=0
63
+ for ext in "${EXTS[@]}"; do
64
+ grep -qF "$ext" "$AGENTS" || { fail "trigger surface missing extension: $ext"; missing_exts=1; }
65
+ done
66
+ [[ $missing_exts -eq 0 ]] && pass "trigger surface (${#EXTS[@]} extensions named)"
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # 3. Override syntax present file-wide (every gate either has an Override:
70
+ # line or an explicit "no override" marker).
71
+ # ---------------------------------------------------------------------------
72
+ override_hits=$(grep -cE 'Override|no override|SKIPPED per' "$AGENTS")
73
+ if [[ $override_hits -lt 7 ]]; then
74
+ fail "override-syntax mentions only $override_hits — expected ≥ 7"
75
+ else
76
+ pass "override syntax ($override_hits mentions)"
77
+ fi
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # 4. Always-forbidden invariance — six hard bans (FORBIDDEN_KEYS in data).
81
+ # ---------------------------------------------------------------------------
82
+ missing_bans=0
83
+ for k in "${FORBIDDEN_KEYS[@]}"; do
84
+ grep -qF "$k" "$AGENTS" || { fail "always-forbidden item missing: $k"; missing_bans=1; }
85
+ done
86
+ [[ $missing_bans -eq 0 ]] && pass "always-forbidden list (${#FORBIDDEN_KEYS[@]} bans present)"
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # 5. Skill-chain order — /orly-write-unit-test → runtime review → babysit.
90
+ # Anchored to within the CHORE(close) section so a stray earlier mention
91
+ # cannot satisfy the check.
92
+ # ---------------------------------------------------------------------------
93
+ awk '
94
+ /^### CHORE \(close\)/ { in_section=1; next }
95
+ /^### |^## / { if (in_section) in_section=0 }
96
+ in_section && /\/orly-write-unit-test/ && !a { a=NR }
97
+ in_section && /gstack/ && a && !b { b=NR }
98
+ in_section && /orly-babysit-prs/&& b && !c { c=NR }
99
+ END { exit (a && b && c && a<b && b<c) ? 0 : 1 }
100
+ ' "$AGENTS" \
101
+ && pass "skill-chain ordering (anchored to CHORE(close))" \
102
+ || fail "skill chain not in order within CHORE(close): /orly-write-unit-test → gstack /review → orly-babysit-prs"
103
+
104
+ # One route for every runtime. A per-runtime split re-introduces the
105
+ # collapsing-two-near-named-steps failure SOUL.md records; assert the
106
+ # single route and that both pre-PR test skills are named.
107
+ review_routes_missing=0
108
+ grep -qF 'One route, every runtime: gstack `/review`.' "$AGENTS" \
109
+ || { fail "single gstack review route missing"; review_routes_missing=1; }
110
+ grep -qF '/orly-write-integration-test' "$AGENTS" \
111
+ || { fail "/orly-write-integration-test missing from the pre-PR skill chain"; review_routes_missing=1; }
112
+ [[ $review_routes_missing -eq 0 ]] && pass "unified review route + both test skills"
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # 6. CONFORM rows — every gate keyword in the verdict block (CONFORM_KEYS).
116
+ # ---------------------------------------------------------------------------
117
+ missing_rows=0
118
+ for kw in "${CONFORM_KEYS[@]}"; do
119
+ grep -qF "$kw" "$AGENTS" || { fail "CONFORM row missing: $kw"; missing_rows=1; }
120
+ done
121
+ [[ $missing_rows -eq 0 ]] && pass "CONFORM coverage (${#CONFORM_KEYS[@]} rows present)"
122
+
123
+ # ---------------------------------------------------------------------------
124
+ # 7. Cross-references — dotfiles-resident docs must exist (DOTFILES_RESIDENT
125
+ # in data). Project-side docs (AUTH.md, architecture/, changelog.mdx) live
126
+ # in each project repo, not here, so they're excluded from this check.
127
+ # ---------------------------------------------------------------------------
128
+ broken_refs=0
129
+ for doc in "${DOTFILES_RESIDENT[@]}"; do
130
+ [[ -f "$ROOT/$doc" ]] || { fail "dotfiles-resident ref missing: $doc"; broken_refs=1; }
131
+ done
132
+ [[ $broken_refs -eq 0 ]] && pass "cross-references (dotfiles-resident docs exist)"
133
+
134
+ # ---------------------------------------------------------------------------
135
+ # 8. Combined-audit smoke — the awk pass (AWK_PROG in data) flags real
136
+ # violations and ignores clean diffs. Fixtures live in audits/fixtures/.
137
+ # ---------------------------------------------------------------------------
138
+ if [[ -f "$FIXTURES/dirty.diff" && -f "$FIXTURES/clean.diff" ]]; then
139
+ # Fixture content fingerprint — guards against well-meaning rewrites
140
+ # that "fix" dirty.diff to be clean (which would silently break the smoke).
141
+ grep -qF "M40_001" "$FIXTURES/dirty.diff" || fail "fixture dirty.diff missing M40_001 marker"
142
+ grep -qE '<button>' "$FIXTURES/dirty.diff" || fail "fixture dirty.diff missing raw <button>"
143
+ ! grep -qF "M40_001" "$FIXTURES/clean.diff" || fail "fixture clean.diff has milestone-id leak"
144
+ ! grep -qE '<button>' "$FIXTURES/clean.diff" || fail "fixture clean.diff has raw <button>"
145
+
146
+ dirty_hits=$(awk "$AWK_PROG" "$FIXTURES/dirty.diff" | wc -l | tr -d ' ')
147
+ clean_hits=$(awk "$AWK_PROG" "$FIXTURES/clean.diff" | wc -l | tr -d ' ')
148
+
149
+ # Pin exact expected counts — protects against false-positive "smoke ok"
150
+ # where a rigged dirty.diff produces ≥1 hit on an unrelated token.
151
+ if [[ $dirty_hits -eq 4 ]]; then
152
+ pass "audit fixture: dirty diff flagged exactly 4 hits"
153
+ else
154
+ fail "audit fixture: dirty diff flagged $dirty_hits hits (expected 4)"
155
+ fi
156
+ if [[ $clean_hits -eq 0 ]]; then
157
+ pass "audit fixture: clean diff produced 0 hits"
158
+ else
159
+ fail "audit fixture: clean diff falsely flagged ($clean_hits hits)"
160
+ fi
161
+ else
162
+ fail "audit fixture files missing under $FIXTURES/"
163
+ fi
164
+
165
+ # ---------------------------------------------------------------------------
166
+ # 9b. Dispatch parity — the three sources of truth must agree: dispatch/*.md
167
+ # bodies on disk, dispatch-table rows in AGENTS.md, and REQUIRED_DISPATCH
168
+ # (check #1). The check also asserts docs/gates/ is EMPTY — the dissolved-
169
+ # gate end state — so a half-done switchover (a leftover card) hard-fails.
170
+ # The logic lives in the sibling parity-dispatch.sh so the model-B sandbox
171
+ # test (evals/test-dispatch-parity.sh) can exercise it in isolation; this
172
+ # keeps the body completeness + existence guarantee that the old per-card
173
+ # gate-body check used to provide (each REQUIRED_DISPATCH entry must resolve
174
+ # to a real dispatch/<entry>.md).
175
+ # ---------------------------------------------------------------------------
176
+ . "$ROOT/audits/parity-dispatch.sh"
177
+ parity_out=$(check_dispatch_parity "$ROOT"); parity_rc=$?
178
+ if [[ $parity_rc -eq 0 ]]; then
179
+ pass "dispatch parity ${parity_out#PASS: dispatch parity }"
180
+ else
181
+ printf '%s\n' "$parity_out" >&2
182
+ fail "dispatch parity mismatch (see FAIL lines above)"
183
+ fi
184
+
185
+ # ---------------------------------------------------------------------------
186
+ # 10. audits/agents-md.md presence + basic shape (questionnaire layer).
187
+ # ---------------------------------------------------------------------------
188
+ INV="$ROOT/audits/agents-md.md"
189
+ if [[ -f "$INV" ]]; then
190
+ inv_scenarios=$(grep -cE '^### Scenario [0-9]+' "$INV")
191
+ if [[ $inv_scenarios -ge 8 ]]; then
192
+ pass "audits/agents-md.md present ($inv_scenarios scenarios)"
193
+ else
194
+ fail "audits/agents-md.md only $inv_scenarios scenarios — expected ≥ 8"
195
+ fi
196
+ else
197
+ fail "audits/agents-md.md missing"
198
+ fi
199
+
200
+ # ---------------------------------------------------------------------------
201
+ # 11. Lifecycle stage headers — all present in AGENTS.md (LIFECYCLE_HEADERS).
202
+ # ---------------------------------------------------------------------------
203
+ missing_stages=0
204
+ for h in "${LIFECYCLE_HEADERS[@]}"; do
205
+ grep -qF "$h" "$AGENTS" || { fail "lifecycle stage missing: $h"; missing_stages=1; }
206
+ done
207
+ [[ $missing_stages -eq 0 ]] && pass "lifecycle stages (${#LIFECYCLE_HEADERS[@]} headers present)"
208
+
209
+ # 11b. Lifecycle anchors — load-bearing strings in BOTH essence + runbook.
210
+ . "$ROOT/audits/lifecycle-anchors.sh"
211
+ anchors_out=$(check_lifecycle_anchors "$ROOT")
212
+ if [[ $? -eq 0 ]]; then pass "lifecycle anchors (${#LIFECYCLE_ANCHORS[@]} strings in essence + runbook)"; else
213
+ printf '%s\n' "$anchors_out" >&2; fail "lifecycle anchors (see FAIL lines above)"; fi
214
+
215
+ # 12. Named scenarios exist by title (NAMED_SCENARIOS in data) — catches a
216
+ # renamed/removed high-value scenario hiding behind a stable count.
217
+ INV="$ROOT/audits/agents-md.md"
218
+ missing_scenarios=0
219
+ if [[ -f "$INV" ]]; then
220
+ for s in "${NAMED_SCENARIOS[@]}"; do
221
+ grep -qF "$s" "$INV" || { fail "audits/agents-md.md missing scenario keyword: $s"; missing_scenarios=1; }
222
+ done
223
+ # Parity — the keyword array must keep pace with the actual scenario count,
224
+ # so a newly-added scenario can't sit unguarded (this is exactly how
225
+ # scenarios 20-22 slipped past the keyword list before this check existed).
226
+ actual_scenarios=$(grep -cE '^### Scenario [0-9]+' "$INV")
227
+ if [[ "${#NAMED_SCENARIOS[@]}" -ne "$actual_scenarios" ]]; then
228
+ fail "named-scenario parity: array has ${#NAMED_SCENARIOS[@]} keywords but audits/agents-md.md has $actual_scenarios scenarios (every scenario needs a keyword guard)"
229
+ missing_scenarios=1
230
+ fi
231
+ [[ $missing_scenarios -eq 0 ]] && pass "named scenarios (${#NAMED_SCENARIOS[@]} keywords ↔ $actual_scenarios scenarios, parity holds)"
232
+ else
233
+ fail "audits/agents-md.md missing"
234
+ fi
235
+
236
+ # ---------------------------------------------------------------------------
237
+ # 13. Hook trigger coverage — pre-commit AND pre-push must reference
238
+ # AGENTS.md AND dispatch/ so ruleset changes can never silently
239
+ # bypass either layer (dispatch/ replaces the dissolved docs/gates/).
240
+ # ---------------------------------------------------------------------------
241
+ PRE_COMMIT="$ROOT/.githooks/pre-commit"
242
+ PRE_PUSH="$ROOT/.githooks/pre-push"
243
+ hook_fail=0
244
+ for hook in "$PRE_COMMIT" "$PRE_PUSH"; do
245
+ name=$(basename "$hook")
246
+ if [[ ! -f "$hook" ]]; then
247
+ fail "hook missing: .githooks/$name"; hook_fail=1; continue
248
+ fi
249
+ grep -qE 'AGENTS(\\)?\.md' "$hook" || { fail "$name missing AGENTS.md trigger reference"; hook_fail=1; }
250
+ grep -qF "dispatch/" "$hook" || { fail "$name missing dispatch/ trigger reference"; hook_fail=1; }
251
+ for token in 'package\.json' 'bun\.lock' 'tsconfig\.json' 'fixtures/'; do
252
+ grep -qF "$token" "$hook" || { fail "$name missing $token trigger reference"; hook_fail=1; }
253
+ done
254
+ done
255
+ [[ $hook_fail -eq 0 ]] && pass "hook triggers (.githooks/pre-commit + pre-push both gate rules, package, toolchain, and fixtures)"
256
+
257
+ # ---------------------------------------------------------------------------
258
+ # 14. Rule extension protocol — AGENTS.md MUST document the 4-step recipe
259
+ # for landing a new rules file or gate body, AND that recipe must still
260
+ # enumerate all four wiring steps. Mere section-presence is not enough:
261
+ # the protocol is the only thing tying a new gate to the doc-reads table,
262
+ # the questionnaire, the DOTFILES_RESIDENT audit list, and `make audit`.
263
+ # If a step silently drops out of the recipe, gates can be added without
264
+ # that linkage — the same class of drift that hid UFS/DESIGN-TOKEN.
265
+ # ---------------------------------------------------------------------------
266
+ if grep -qF "Rule extension protocol" "$AGENTS"; then
267
+ protocol_line=$(grep -F "Rule extension protocol" "$AGENTS" | head -1)
268
+ rep_fail=0
269
+ for step in "${RULE_EXTENSION_STEPS[@]}"; do
270
+ grep -qF "$step" <<<"$protocol_line" || { fail "rule extension protocol missing step keyword: $step"; rep_fail=1; }
271
+ done
272
+ [[ $rep_fail -eq 0 ]] && pass "rule extension protocol section present (all 4 wiring steps enumerated)"
273
+ else
274
+ fail "AGENTS.md missing 'Rule extension protocol' section"
275
+ fi
276
+
277
+ # ---------------------------------------------------------------------------
278
+ # 15. Identity handles — AGENTS.md MUST identify the human (Kishore / Indy)
279
+ # and the agent (Oracle / Orly) so addressing resolves unambiguously.
280
+ # ---------------------------------------------------------------------------
281
+ if grep -qF "human is Kishore" "$AGENTS" && grep -qF "Orly" "$AGENTS"; then
282
+ pass "identity handles (Kishore/Indy ↔ Oracle/Orly present)"
283
+ else
284
+ fail "identity handles: AGENTS.md missing 'human is Kishore' or 'Orly'"
285
+ fi
286
+
287
+ # ---------------------------------------------------------------------------
288
+ # 15b. Memory discipline — auto-memory is retired in favour of dispatch + repo
289
+ # docs + HANDOFF. The section MUST keep three load-bearing facts so a
290
+ # future edit can't silently re-enable the per-session memory tax or drop
291
+ # the routing that replaces it: the disable flag, the never-write rule, and
292
+ # the dispatch-first routing target.
293
+ # ---------------------------------------------------------------------------
294
+ md_fail=0
295
+ grep -qF "## Memory Discipline" "$AGENTS" || { fail "memory discipline: section header missing"; md_fail=1; }
296
+ grep -qF "autoMemoryEnabled" "$AGENTS" || { fail "memory discipline: autoMemoryEnabled disable flag not documented"; md_fail=1; }
297
+ grep -qiE "NEVER write to .*memory" "$AGENTS" || { fail "memory discipline: never-write-memory rule missing"; md_fail=1; }
298
+ grep -qF 'dispatch/<entry>.md' "$AGENTS" || { fail "memory discipline: dispatch-first routing missing"; md_fail=1; }
299
+ [[ $md_fail -eq 0 ]] && pass "memory discipline (disable flag + never-write + dispatch routing present)"
300
+
301
+ # ---------------------------------------------------------------------------
302
+ # 15c. Rule-path residence — citations resolve inside the installing repo
303
+ # (audits/rule-paths.sh; incident: an M143 worktree agent resolved a
304
+ # dotfiles rule doc against the product repo and designed from memory).
305
+ # ---------------------------------------------------------------------------
306
+ . "$ROOT/audits/rule-paths.sh"
307
+ res_out=$(check_rule_residence "$ROOT")
308
+ if [[ $? -eq 0 ]]; then pass "rule-path residence (doctrine + anchors + zero relative hrefs)"; else
309
+ printf '%s\n' "$res_out" >&2; fail "rule-path residence (see FAIL lines above)"; fi
310
+
311
+ # ---------------------------------------------------------------------------
312
+ # 16. Size cap — soft guard against drift back to bloat.
313
+ # Generated metadata is included in the measured file.
314
+ #
315
+ # 40 KiB, raised from 32 KiB (Aug 22, 2026) on Indy's call: the render had
316
+ # reached the old cap exactly, so every new rule cost an existing one, and
317
+ # the next thing to cut would have been triggers rather than gloss. The cap
318
+ # exists for attention, not capacity — a rule the agent skims past enforces
319
+ # nothing — so it stays a real ceiling, just one with room to work in.
320
+ # ---------------------------------------------------------------------------
321
+ SIZE=$(wc -c < "$AGENTS" | tr -d ' ')
322
+ LIMIT=${AGENTS_MD_SIZE_LIMIT:-40960}
323
+ if [[ $SIZE -le $LIMIT ]]; then
324
+ pass "size $SIZE bytes (limit $LIMIT)"
325
+ else
326
+ fail "size $SIZE bytes exceeds limit $LIMIT (override via AGENTS_MD_SIZE_LIMIT=...)"
327
+ fi
328
+
329
+ # ---------------------------------------------------------------------------
330
+ # Self-fingerprint — every EXPECTED_LABELS entry must have been emitted by
331
+ # at least one pass/fail call. Catches the case where someone deletes a
332
+ # check from the script — the script then runs successfully but with
333
+ # fewer invariants enforced, which is exactly the kind of silent
334
+ # weakening this audit exists to prevent.
335
+ # ---------------------------------------------------------------------------
336
+ fp_fail=0
337
+ for label in "${EXPECTED_LABELS[@]}"; do
338
+ found=0
339
+ for seen in "${SEEN_LABELS[@]}"; do
340
+ [[ "$seen" == *"$label"* ]] && { found=1; break; }
341
+ done
342
+ [[ $found -eq 1 ]] || { fail "self-fingerprint: expected check '$label' did not run"; fp_fail=1; }
343
+ done
344
+ [[ $fp_fail -eq 0 ]] && pass "self-fingerprint (${#EXPECTED_LABELS[@]} expected checks ran)"
345
+
346
+ echo
347
+ if [[ $FAIL -eq 0 ]]; then
348
+ printf '%s✅ ALL CHECKS PASSED%s\n' "$C_GREEN$C_BOLD" "$C_RESET"
349
+ exit 0
350
+ else
351
+ printf '%s🔴 REGRESSION DETECTED%s — see FAIL lines above\n' "$C_RED$C_BOLD" "$C_RESET"
352
+ exit 1
353
+ fi
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # cross-tier-rates.sh — pin numeric parity of the rate constants
3
+ # across Zig + three TypeScript surfaces.
4
+ #
5
+ # RULE UFS extension. The shipped billing surface depends on these constants
6
+ # having identical numeric values everywhere they appear:
7
+ #
8
+ # RUN_NANOS_PER_SEC — per-second run rate (both postures) in nanos
9
+ #
10
+ # Two free-trial constants were pinned here and are both gone. FREE_TRIAL_END_MS
11
+ # went when the boundary moved onto the tenant row; FREE_TRIAL_STAGE_NANOS went
12
+ # when the trial mechanism was removed outright, because a rate that could
13
+ # silently price a metered slice to zero was worth deleting rather than
14
+ # guarding. The free allowance is wallet credit now — a balance, not a rate —
15
+ # so there is no cross-tier number to pin for it.
16
+ #
17
+ # Files (one definition site per constant per file):
18
+ #
19
+ # src/agentsfleetd/state/tenant_billing.zig — server enforces the charge
20
+ # ui/packages/website/src/lib/rates.ts — pricing page display
21
+ # ui/packages/app/lib/types.ts — dashboard display
22
+ # cli/src/constants/billing.ts — `agentsfleet doctor --json` billing block
23
+ #
24
+ # A drift between any two = a billing-display lie or a server-vs-CLI disagreement.
25
+ # Zig is the source of truth (server enforces); the three TS surfaces echo it.
26
+ #
27
+ # Fires in: make harness-verify, make harness-verify-all.
28
+ # Exits 0 clean, 1 on any drift.
29
+
30
+ set -euo pipefail
31
+
32
+ ROOT="$(git rev-parse --show-toplevel)"
33
+ cd "$ROOT"
34
+
35
+ readonly NAMES=(
36
+ RUN_NANOS_PER_SEC
37
+ )
38
+
39
+ readonly FILES=(
40
+ src/agentsfleetd/state/tenant_billing.zig
41
+ ui/packages/website/src/lib/rates.ts
42
+ ui/packages/app/lib/types.ts
43
+ cli/src/constants/billing.ts
44
+ )
45
+
46
+ # Extract the numeric value bound to `name` in `file`. Matches definition
47
+ # sites only — `^(pub |export )?const NAME` — not usages elsewhere in the
48
+ # file. Normalises: strips underscores (Zig `1_000_000`, TS `1_000_000`),
49
+ # strips the bigint `n` suffix (TS `1_000_000n`), trims whitespace.
50
+ extract_value() {
51
+ local file="$1" name="$2"
52
+ grep -E "^[[:space:]]*(pub |export )?const ${name}\\b" "$file" 2>/dev/null \
53
+ | head -1 \
54
+ | sed -E "s|.*=[[:space:]]*||; s|[;,].*||; s|//.*||; s|[[:space:]_n]||g"
55
+ }
56
+
57
+ FAIL=0
58
+ violations=()
59
+
60
+ printf "audit-cross-tier-rates: pinning %d constants across %d files\n" \
61
+ "${#NAMES[@]}" "${#FILES[@]}"
62
+
63
+ for name in "${NAMES[@]}"; do
64
+ expected=""
65
+ expected_file=""
66
+ for file in "${FILES[@]}"; do
67
+ if [[ ! -f "$file" ]]; then
68
+ violations+=("MISSING_FILE: $file")
69
+ FAIL=1
70
+ continue
71
+ fi
72
+ val="$(extract_value "$file" "$name")"
73
+ if [[ -z "$val" ]]; then
74
+ violations+=("MISSING_CONST: $name not defined in $file")
75
+ FAIL=1
76
+ continue
77
+ fi
78
+ if [[ -z "$expected" ]]; then
79
+ expected="$val"
80
+ expected_file="$file"
81
+ continue
82
+ fi
83
+ if [[ "$val" != "$expected" ]]; then
84
+ violations+=("DRIFT: $name = $val in $file vs $expected in $expected_file")
85
+ FAIL=1
86
+ fi
87
+ done
88
+ if [[ -n "$expected" ]]; then
89
+ printf " OK %-26s = %s\n" "$name" "$expected"
90
+ fi
91
+ done
92
+
93
+ if [[ $FAIL -ne 0 ]]; then
94
+ printf "\nFAIL: cross-tier rate drift detected:\n" >&2
95
+ for v in "${violations[@]}"; do
96
+ printf " %s\n" "$v" >&2
97
+ done
98
+ printf "\nFix: align all four files on the canonical Zig value\n" >&2
99
+ printf " (src/agentsfleetd/state/tenant_billing.zig is the server-side enforcer).\n" >&2
100
+ exit 1
101
+ fi
102
+
103
+ printf "OK: all %d rate constants pin across %d files\n" \
104
+ "${#NAMES[@]}" "${#FILES[@]}"
105
+ exit 0
package/audits/data.sh ADDED
@@ -0,0 +1,185 @@
1
+ # shellcheck shell=bash
2
+ # Data tables for audits/agents-md.sh — the expectation lists every
3
+ # check asserts against, plus the combined-audit awk program. Split out of
4
+ # the main audit (LENGTH GATE: keeps agents-md.sh under the 350-line
5
+ # cap; data and logic separate cleanly). Sourced, never executed directly.
6
+ #
7
+ # Adding a dispatch entry/scenario/rule? Update the matching array HERE; the
8
+ # audit's dispatch-parity + named-scenario-parity checks fail loudly if a table
9
+ # drifts out of sync with AGENTS.md / dispatch/ / audits/agents-md.md.
10
+
11
+ # Self-fingerprint — every label here MUST be emitted by a pass/fail call in
12
+ # the audit. Deleting a check without updating this list trips the final
13
+ # self-fingerprint check. This is the script auditing itself.
14
+ EXPECTED_LABELS=(
15
+ "dispatch inventory"
16
+ "trigger surface"
17
+ "override syntax"
18
+ "always-forbidden list"
19
+ "skill-chain ordering"
20
+ "CONFORM coverage"
21
+ "cross-references"
22
+ "audit fixture: dirty diff"
23
+ "audit fixture: clean diff"
24
+ "dispatch parity"
25
+ "audits/agents-md.md present"
26
+ "lifecycle stages"
27
+ "named scenarios"
28
+ "hook triggers"
29
+ "rule extension protocol"
30
+ "identity handles"
31
+ "memory discipline"
32
+ "rule-path residence"
33
+ "lifecycle anchors"
34
+ "size"
35
+ )
36
+
37
+ # Check 1 (dispatch inventory) + check 9b (dispatch parity) — dispatch
38
+ # entries that replaced the 20 dissolved gate cards. Source of truth for both
39
+ # the inventory loop in agents-md.sh and check_dispatch_parity
40
+ # (audits/parity-dispatch.sh). Each name has a dispatch/<name>.md and a row in
41
+ # the AGENTS.md dispatch table (entry name in the 2nd column). Keep 1:1 with
42
+ # dispatch/*.md.
43
+ REQUIRED_DISPATCH=(
44
+ "write_zig" "write_ts_adhere_bun" "write_sql" "write_any"
45
+ "write_spec" "write_http" "write_auth" "write_changelog"
46
+ "write_documentation" "verify" "name_architecture" "edit_rules"
47
+ "write_rust" "write_go" "write_python" "write_shell" "write_mdx"
48
+ "lifecycle"
49
+ )
50
+
51
+ # Check 2 — every source/config language has at least one mention.
52
+ EXTS=( ".zig" ".ts" ".tsx" ".js" ".jsx" ".py" ".rs" ".go" ".sh" ".sql" )
53
+
54
+ # Check 4 — the six hard bans must remain.
55
+ FORBIDDEN_KEYS=(
56
+ "no-verify" # hooks/signing
57
+ "Plaintext secrets" # entity tables
58
+ "Static strings in SQL" # schema literals
59
+ "Resolving/printing credentials"
60
+ "Force-push default"
61
+ "core paths" # install-process launches in core paths
62
+ )
63
+
64
+ # Check 6 — every gate's keyword appears in the CONFORM verdict block.
65
+ CONFORM_KEYS=(
66
+ "FILE SHAPE" "PUB GATE" "LENGTH GATE" "MILESTONE-ID GATE"
67
+ "ZIG GATE" "UI GATE" "DESIGN TOKEN GATE"
68
+ "SCHEMA GUARD" "GREPTILE GATE"
69
+ "Architecture consult"
70
+ )
71
+
72
+ # Check 7 — dotfiles-resident docs + dispatch façades referenced by AGENTS.md
73
+ # must exist on disk. The per-language Zig/Bun standards docs were dropped —
74
+ # dissolved into dispatch/write_zig.md and dispatch/write_ts_adhere_bun.md
75
+ # respectively. The dispatch façades are listed so the AGENTS.md dispatch-table
76
+ # refs are checked. The docs/* entries double as the residence classifier for
77
+ # check 15c (audits/rule-paths.sh): these paths are cited through the
78
+ # ~/Projects/dotfiles/ anchor in every consumer-facing surface.
79
+ DOTFILES_RESIDENT=(
80
+ "docs/TEMPLATE.md"
81
+ "docs/REST_API_DESIGN_GUIDELINES.md"
82
+ "docs/LOGGING_STANDARD.md"
83
+ "docs/LIFECYCLE_PATTERNS.md"
84
+ "docs/DOCUMENTATION_RULES.md"
85
+ "docs/ORLY_ARCHITECTURE.md"
86
+ "docs/CHANGELOG_VOICE.md"
87
+ "docs/EXECUTE_DOC_READS.md"
88
+ "docs/VERIFY_TIERS.md"
89
+ "docs/HARNESS_VERIFY_OUTPUT.md"
90
+ "docs/SCHEMA_CONVENTIONS.md"
91
+ "docs/greptile-learnings/RULES.md"
92
+ "dispatch/write_zig.md"
93
+ "dispatch/write_ts_adhere_bun.md"
94
+ "dispatch/write_rust.md"
95
+ "dispatch/write_go.md"
96
+ "dispatch/write_python.md"
97
+ "dispatch/write_shell.md"
98
+ "dispatch/write_mdx.md"
99
+ "dispatch/write_sql.md"
100
+ "dispatch/write_any.md"
101
+ "dispatch/write_spec.md"
102
+ "dispatch/write_http.md"
103
+ "dispatch/write_auth.md"
104
+ "dispatch/write_changelog.md"
105
+ "dispatch/write_documentation.md"
106
+ "dispatch/verify.md"
107
+ "dispatch/name_architecture.md"
108
+ "dispatch/edit_rules.md"
109
+ "dispatch/lifecycle.md"
110
+ )
111
+
112
+ # Check 11 — every lifecycle stage header is present in AGENTS.md.
113
+ LIFECYCLE_HEADERS=(
114
+ "### CHORE (open)"
115
+ "### PLAN"
116
+ "### EXECUTE"
117
+ "### CONFORM"
118
+ "### VERIFY"
119
+ "### REVIEW"
120
+ "### DOCUMENT"
121
+ "### COMMIT"
122
+ "### CHORE (close)"
123
+ )
124
+
125
+ # Check 11b — load-bearing lifecycle strings that must exist in BOTH the
126
+ # rendered AGENTS.md (stage essence) and dispatch/lifecycle.md (the runbook).
127
+ # Consumed by audits/lifecycle-anchors.sh; a string dropped from either side
128
+ # is semantic drift the inventory parity check cannot see.
129
+ LIFECYCLE_ANCHORS=(
130
+ "Test Baseline:"
131
+ '`pwd` + `git worktree list`'
132
+ "user-acked verbatim quote"
133
+ "make check-version"
134
+ "RULE ORP"
135
+ "git status -uall"
136
+ "never force-push an open PR branch"
137
+ )
138
+
139
+ # Check 12 — each scenario must exist by keyword; array size must equal the
140
+ # actual scenario count (parity). Keep this list 1:1 with audits/agents-md.md.
141
+ NAMED_SCENARIOS=(
142
+ "New spec" # Scenario 1
143
+ "Brainstorming" # Scenario 2
144
+ "human spots and steers" # Scenario 3
145
+ "UI" # Scenario 4 (covers UI/Zig/TS/JS/shell/CI)
146
+ "Handover" # Scenario 5
147
+ "Conformance and verification lifecycle" # Scenario 6
148
+ "orly-babysit-prs" # Scenario 7
149
+ "/orly-write-unit-test" # Scenario 8
150
+ "Hot-fix" # Scenario 9
151
+ "Dotfiles" # Scenario 10
152
+ "Schema" # Scenario 11
153
+ "Auto-mode boundary" # Scenario 12
154
+ "Invariance Suite" # Scenario 13
155
+ "Communication" # Scenario 14
156
+ "Architecture-edit" # Scenario 15
157
+ "Credentials" # Scenario 16
158
+ "DB discipline" # Scenario 17
159
+ "worktree isolation" # Scenario 18
160
+ "combined audit" # Scenario 19
161
+ "Rule extension protocol" # Scenario 20
162
+ "Gate-flag triage" # Scenario 21
163
+ "Pre-commit audit scope" # Scenario 22
164
+ "Agent comprehension" # Scenario 23
165
+ "Memory routing" # Scenario 24
166
+ "Allocator & concurrency discipline" # Scenario 25
167
+ "Rules propagation" # Scenario 26
168
+ "Rule-path" # Scenario 27
169
+ "Rule-enforcement ledger" # Scenario 28
170
+ )
171
+
172
+ # Check 14 — the Rule-extension protocol must enumerate all four wiring steps.
173
+ RULE_EXTENSION_STEPS=( "doc-reads" "audits/agents-md.md" "DOTFILES_RESIDENT" "make audit" )
174
+
175
+ # Check 8 — combined-audit smoke: flags MS-ID / PUB / UI violations in a diff.
176
+ read -r -d '' AWK_PROG <<'AWKEOF' || true
177
+ /^\+\+\+ b\// { f=$2; sub("^b/","",f); next }
178
+ /^\+/ {
179
+ if (f ~ /\.(zig|sql|ts|tsx|js|jsx|py|rs|go|sh|toml|yaml|json)$/ && f !~ /^(docs|node_modules|vendor|third_party)\//) {
180
+ if (match($0, /M[0-9]+_[0-9]+|§[0-9]+(\.[0-9]+)+|\bT[0-9]+\b|\bdim [0-9]+\.[0-9]+\b/)) print "MS-ID:" f
181
+ }
182
+ if (f ~ /\.zig$/ && $0 ~ /^\+(pub | *pub fn | *[A-Z][a-zA-Z]+,$)/) print "PUB:" f
183
+ if (f ~ /^ui\/packages\/app\/.*\.(tsx|jsx)$/ && $0 ~ /<(section|button|input|dialog|article|nav|header|form)\b/) print "UI:" f
184
+ }
185
+ AWKEOF