@agentsfleet/orly 0.14.2 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/audits/msid-ui.sh +136 -38
- package/dispatch/write_ts_adhere_bun.md +2 -0
- package/docs/CHANGELOG.md +15 -0
- package/docs/REST_API_DESIGN_GUIDELINES.md +22 -2
- package/docs/RULE_ENFORCEMENT.md +2 -2
- package/package.json +1 -1
- package/src/scanner_scope.test.ts +37 -0
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ TypeSafe's Jev model supplies runtime advice through explicit `orly judge` reque
|
|
|
78
78
|
|
|
79
79
|
See [releases](https://github.com/agentsfleet/orly/releases) for published versions.
|
|
80
80
|
The install commands below fetch the published package.
|
|
81
|
-
The release workflow publishes 0.14.
|
|
81
|
+
The release workflow publishes 0.14.4 after its version change merges to `main`.
|
|
82
82
|
|
|
83
83
|
---
|
|
84
84
|
|
package/audits/msid-ui.sh
CHANGED
|
@@ -99,7 +99,23 @@ case "$MODE" in
|
|
|
99
99
|
EXPLICIT=("$@"); POST_REF=""; LABEL="explicit files" ;;
|
|
100
100
|
esac
|
|
101
101
|
|
|
102
|
-
#
|
|
102
|
+
# --staged/--diff: one git diff piped into one awk — the scan itself costs
|
|
103
|
+
# two processes however large the staged set, plus at most one `git show`
|
|
104
|
+
# per file holding a candidate hit line (zero for a clean diff). Explicit
|
|
105
|
+
# mode keeps a per-file scan so the reported path is the caller's own argv
|
|
106
|
+
# string (git's --no-index headers strip the leading slash of absolute
|
|
107
|
+
# paths, which would misreport the hit and misread the post-image).
|
|
108
|
+
# This gate runs inside a 30-minute pre-commit budget that the previous
|
|
109
|
+
# per-file fork loop blew through at 1,628 staged files.
|
|
110
|
+
#
|
|
111
|
+
# The awk learns the current file from the diff's own `+++ b/<path>` header
|
|
112
|
+
# instead of a shell loop variable. That parse is made safe on purpose:
|
|
113
|
+
# * `--src-prefix=a/ --dst-prefix=b/` pins the prefixes, because user config
|
|
114
|
+
# (diff.noprefix, diff.mnemonicPrefix) may rename them.
|
|
115
|
+
# * git C-quotes paths containing control characters, `"` or `\` (and with
|
|
116
|
+
# the default core.quotePath, non-ASCII bytes — hence `-c
|
|
117
|
+
# core.quotePath=false` on the git calls), so the header is unquoted in
|
|
118
|
+
# awk. git terminates the header path with one TAB, quoted or not.
|
|
103
119
|
#
|
|
104
120
|
# Carve-out recognition reads the preceding line from the SOURCE, not from the
|
|
105
121
|
# diff. Every carve-out here — the two override markers and the design-system
|
|
@@ -112,29 +128,21 @@ esac
|
|
|
112
128
|
# override comment stopped covering the line it sits above the moment that line
|
|
113
129
|
# was next touched.
|
|
114
130
|
#
|
|
115
|
-
# The hunk header carries the post-image line number, so `line_above`
|
|
116
|
-
#
|
|
117
|
-
#
|
|
131
|
+
# The hunk header carries the post-image line number, so `line_above` answers
|
|
132
|
+
# from the post-image the mode named. The post-image is slurped once per file —
|
|
133
|
+
# on the file's first *candidate* hit line, since only candidates (a line
|
|
134
|
+
# matching the MS-ID or UI regexes in a scoped file) ever read the neighbour —
|
|
135
|
+
# and dropped when the next file's `diff --git` section opens. One `git show`
|
|
136
|
+
# per candidate file, zero for a clean diff, and memory bounded by one file.
|
|
118
137
|
scan_diff() {
|
|
119
|
-
|
|
138
|
+
awk -v post_ref="$POST_REF" '
|
|
120
139
|
# BWK awk (macOS) has no gensub and no --posix quoting help; APOSTROPHE is
|
|
121
140
|
# built rather than written, because this program is inside single quotes.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
function line_above(path, n, key, cmd, out) {
|
|
128
|
-
if (n <= 1) return ""
|
|
129
|
-
key = path SUBSEP n
|
|
130
|
-
if (key in above) return above[key]
|
|
131
|
-
above[key] = ""
|
|
132
|
-
if (post_ref == "") cmd = "sed -n " (n - 1) "p " shell_quote(path)
|
|
133
|
-
else cmd = "git show " shell_quote(post_ref path) " 2>/dev/null | sed -n " (n - 1) "p"
|
|
134
|
-
if ((cmd | getline out) > 0) above[key] = out
|
|
135
|
-
close(cmd)
|
|
136
|
-
return above[key]
|
|
137
|
-
}
|
|
141
|
+
# Explicit mode names its file through the environment (see the driver) and
|
|
142
|
+
# the header parse below then never overrides it; the diff modes pass an
|
|
143
|
+
# empty value and every `+++` header re-attributes f.
|
|
144
|
+
BEGIN { APOSTROPHE = sprintf("%c", 39); in_hunk = 0; loaded = 0; imgn = 0
|
|
145
|
+
f = ENVIRON["ORLY_AUDIT_PATH"]; fixed_f = (f != "") }
|
|
138
146
|
|
|
139
147
|
function shell_quote(s, i,c,out) {
|
|
140
148
|
out = APOSTROPHE
|
|
@@ -145,23 +153,113 @@ ORLY_AUDIT_PATH="$1" awk -v post_ref="$POST_REF" '
|
|
|
145
153
|
}
|
|
146
154
|
return out APOSTROPHE
|
|
147
155
|
}
|
|
156
|
+
|
|
157
|
+
# The post-image path a `+++ ` header names: C-quote resolved (git wraps
|
|
158
|
+
# the whole "b/<path>" in quotes), the pinned b/ prefix removed. /dev/null
|
|
159
|
+
# maps to the empty path, which matches no scope below.
|
|
160
|
+
function dst_path(header, rest,out,i,c,nxt,q,oct,v,j) {
|
|
161
|
+
rest = substr(header, 5)
|
|
162
|
+
if (rest == "/dev/null") return ""
|
|
163
|
+
if (substr(rest, 1, 1) == "\"") {
|
|
164
|
+
q = 0
|
|
165
|
+
for (i = length(rest); i >= 2; i--) if (substr(rest, i, 1) == "\"") { q = i; break }
|
|
166
|
+
rest = substr(rest, 2, q - 2)
|
|
167
|
+
out = ""
|
|
168
|
+
i = 1
|
|
169
|
+
while (i <= length(rest)) {
|
|
170
|
+
c = substr(rest, i, 1)
|
|
171
|
+
if (c == "\\") {
|
|
172
|
+
nxt = substr(rest, i + 1, 1)
|
|
173
|
+
if (nxt ~ /[0-7]/) {
|
|
174
|
+
# \ooo octal escape, one to three digits (only control bytes reach
|
|
175
|
+
# here: core.quotePath=false passes non-ASCII through raw).
|
|
176
|
+
oct = nxt
|
|
177
|
+
while (length(oct) < 3 && substr(rest, i + 1 + length(oct), 1) ~ /[0-7]/)
|
|
178
|
+
oct = oct substr(rest, i + 1 + length(oct), 1)
|
|
179
|
+
v = 0
|
|
180
|
+
for (j = 1; j <= length(oct); j++) v = v * 8 + substr(oct, j, 1)
|
|
181
|
+
out = out sprintf("%c", v)
|
|
182
|
+
i += 1 + length(oct)
|
|
183
|
+
} else {
|
|
184
|
+
if (nxt == "a") out = out sprintf("%c", 7)
|
|
185
|
+
else if (nxt == "b") out = out sprintf("%c", 8)
|
|
186
|
+
else if (nxt == "f") out = out "\f"
|
|
187
|
+
else if (nxt == "n") out = out "\n"
|
|
188
|
+
else if (nxt == "r") out = out "\r"
|
|
189
|
+
else if (nxt == "t") out = out "\t"
|
|
190
|
+
else if (nxt == "v") out = out "\v"
|
|
191
|
+
else out = out nxt
|
|
192
|
+
i += 2
|
|
193
|
+
}
|
|
194
|
+
} else { out = out c; i++ }
|
|
195
|
+
}
|
|
196
|
+
rest = out
|
|
197
|
+
} else {
|
|
198
|
+
# Unquoted: drop the single trailing TAB git appends to the header
|
|
199
|
+
# path. A literal tab in a filename forces C-quoting, so a trailing
|
|
200
|
+
# tab here is never name content; a trailing SPACE is, and is kept.
|
|
201
|
+
sub(/\t$/, "", rest)
|
|
202
|
+
}
|
|
203
|
+
sub(/^b\//, "", rest)
|
|
204
|
+
return rest
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function load_postimage( cmd,l) {
|
|
208
|
+
imgn = 0
|
|
209
|
+
if (post_ref == "") {
|
|
210
|
+
while ((getline l < f) > 0) { imgn++; img[imgn] = l }
|
|
211
|
+
close(f)
|
|
212
|
+
} else {
|
|
213
|
+
cmd = "git show " shell_quote(post_ref f) " 2>/dev/null"
|
|
214
|
+
while ((cmd | getline l) > 0) { imgn++; img[imgn] = l }
|
|
215
|
+
close(cmd)
|
|
216
|
+
}
|
|
217
|
+
loaded = 1
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# The source line above post-image line `n` of the current file, or "" when
|
|
221
|
+
# there is none. Reads the slurped post-image; the slurp happens at most
|
|
222
|
+
# once per file section.
|
|
223
|
+
function line_above(n) {
|
|
224
|
+
if (n <= 1) return ""
|
|
225
|
+
if (!loaded) load_postimage()
|
|
226
|
+
if (n - 1 > imgn) return ""
|
|
227
|
+
return img[n - 1]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# A new file section: reset hunk state and drop the previous post-image —
|
|
231
|
+
# lookups only ever concern the file of the open section.
|
|
232
|
+
/^diff --git / {
|
|
233
|
+
in_hunk = 0
|
|
234
|
+
if (loaded) { for (i = 1; i <= imgn; i++) delete img[i]; imgn = 0; loaded = 0 }
|
|
235
|
+
next
|
|
236
|
+
}
|
|
237
|
+
!in_hunk && /^\+\+\+ / { if (!fixed_f) f = dst_path($0); next }
|
|
148
238
|
/^\+\+\+/ { next }
|
|
149
239
|
# `@@ -a,b +c,d @@` — $3 is the post-image start line for this hunk.
|
|
150
|
-
/^@@/ { start=$3; sub(/^\+/,"",start); split(start,parts,","); nl=parts[1]+0; next }
|
|
240
|
+
/^@@/ { in_hunk = 1; start=$3; sub(/^\+/,"",start); split(start,parts,","); nl=parts[1]+0; next }
|
|
151
241
|
/^[^+]/ { next }
|
|
152
242
|
/^\+/ {
|
|
153
243
|
line=$0
|
|
154
244
|
sub(/^\+/,"",line)
|
|
155
|
-
|
|
245
|
+
# The line above is fetched only for candidate hits: every reader of
|
|
246
|
+
# `prev` sits behind one of these two guards, so a line that matches
|
|
247
|
+
# neither regex never pays for the post-image.
|
|
248
|
+
msid_cand = f ~ /\.(zig|sql|ts|tsx|js|jsx|py|rs|go|sh|toml|yaml|json)$/ &&
|
|
249
|
+
f !~ /^(docs|node_modules|vendor|third_party)\// &&
|
|
250
|
+
match(line, /M[0-9]+_[0-9]+|§[0-9]+(\.[0-9]+)+|(^|[^A-Za-z0-9_])T[0-9]+([^A-Za-z0-9_]|$)|(^|[^A-Za-z0-9_])dim [0-9]+\.[0-9]+([^A-Za-z0-9_]|$)/)
|
|
251
|
+
ui_cand = f ~ /^ui\/packages\/app\/.*\.(tsx|jsx)$/ &&
|
|
252
|
+
line ~ /<(section|button|input|dialog|article|nav|header|form)[ \t>\/]/
|
|
253
|
+
if (msid_cand || ui_cand) {
|
|
254
|
+
prev = line_above(nl)
|
|
255
|
+
ms_id_override = (prev ~ /MILESTONE ID ALLOWED per user override/)
|
|
256
|
+
ui_override = (prev ~ /UI GATE: SKIPPED per user override/)
|
|
257
|
+
} else {
|
|
258
|
+
prev = ""; ms_id_override = 0; ui_override = 0
|
|
259
|
+
}
|
|
156
260
|
nl++
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (!ms_id_override &&
|
|
160
|
-
f ~ /\.(zig|sql|ts|tsx|js|jsx|py|rs|go|sh|toml|yaml|json)$/ &&
|
|
161
|
-
f !~ /^(docs|node_modules|vendor|third_party)\//) {
|
|
162
|
-
if (match(line, /M[0-9]+_[0-9]+|§[0-9]+(\.[0-9]+)+|(^|[^A-Za-z0-9_])T[0-9]+([^A-Za-z0-9_]|$)|(^|[^A-Za-z0-9_])dim [0-9]+\.[0-9]+([^A-Za-z0-9_]|$)/)) {
|
|
163
|
-
print "MS-ID " f ": " line
|
|
164
|
-
}
|
|
261
|
+
if (msid_cand && !ms_id_override) {
|
|
262
|
+
print "MS-ID " f ": " line
|
|
165
263
|
}
|
|
166
264
|
# react-hook-form caveat: its prescribed shape is a raw
|
|
167
265
|
# <form onSubmit={form.handleSubmit(...)}> inside the design-system <Form>
|
|
@@ -179,9 +277,7 @@ ORLY_AUDIT_PATH="$1" awk -v post_ref="$POST_REF" '
|
|
|
179
277
|
# is above it in the file whether or not this diff touched it. A
|
|
180
278
|
# bare/unwrapped <section> still trips.
|
|
181
279
|
ds_section = (line ~ /<section[ \t]/ && prev ~ /<Section asChild>/)
|
|
182
|
-
if (!ui_override && !rhf_form && !ds_section
|
|
183
|
-
f ~ /^ui\/packages\/app\/.*\.(tsx|jsx)$/ &&
|
|
184
|
-
line ~ /<(section|button|input|dialog|article|nav|header|form)[ \t>\/]/) {
|
|
280
|
+
if (ui_cand && !ui_override && !rhf_form && !ds_section) {
|
|
185
281
|
print "UI " f ": " line
|
|
186
282
|
}
|
|
187
283
|
}
|
|
@@ -193,12 +289,14 @@ hits=$(
|
|
|
193
289
|
for path in "${EXPLICIT[@]}"; do
|
|
194
290
|
[ -f "$path" ] || continue
|
|
195
291
|
# diff returns one for differences; that is expected input here.
|
|
196
|
-
{ git diff --no-index --no-ext-diff -U0 -- /dev/null "$path" || [ "$?" -eq 1 ]; } |
|
|
292
|
+
{ git -c core.quotePath=false diff --no-index --no-ext-diff -U0 --src-prefix=a/ --dst-prefix=b/ -- /dev/null "$path" || [ "$?" -eq 1 ]; } | ORLY_AUDIT_PATH="$path" scan_diff
|
|
197
293
|
done
|
|
198
294
|
else
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
295
|
+
# --no-renames pins a rename to its delete+add shape: this gate scores
|
|
296
|
+
# added lines, and its verdicts are defined over renamed files being
|
|
297
|
+
# re-scanned as whole-file additions. Rename detection would silently
|
|
298
|
+
# stop scanning their contents.
|
|
299
|
+
git -c core.quotePath=false diff --no-ext-diff -U0 --no-renames --diff-filter=ACMRT --src-prefix=a/ --dst-prefix=b/ "${DIFF_ARGS[@]}" | ORLY_AUDIT_PATH="" scan_diff
|
|
202
300
|
fi
|
|
203
301
|
)
|
|
204
302
|
|
|
@@ -313,6 +313,8 @@ The TypeScript-relevant gate cards (`ui-substitution`, `design-token`) dissolve
|
|
|
313
313
|
|
|
314
314
|
The design-system package (`ui/packages/design-system/src/index.ts` exports — `Section`, `Card`, `Badge`, `Button`, `Input`, `Dialog`, `Pagination`, `EmptyState`, `Tooltip`, `List`/`ListItem`, `WakePulse`, `Terminal`, `InstallBlock`, etc.) is the source of truth for visual primitives. Raw HTML in either dashboard or marketing files drifts from those tokens silently.
|
|
315
315
|
|
|
316
|
+
**Record dates.** Label creation timestamps **Created** across tables and detail views. Other timestamps name their event: **Updated**, **Last used**, **Occurred**, **Expires**, or **Joined / invited**. Reuse shared app labels; avoid generic **Time** or **Date** headings. Render dates with the shared `Time` component.
|
|
317
|
+
|
|
316
318
|
**Marketing-display typography — primitives now exist.** `<DisplayXL>` (marketing hero `<h1>`, `text-fluid-hero`) and `<DisplayLG>` (section heads `<h2>`, `text-fluid-display-lg`) ship from `@agentsfleet/design-system` and compose `font-mono` + the fluid text/leading/tracking tokens internally. Use them on marketing-display surfaces. Raw `<h1>+utilities` is no longer the carve-out — UI GATE blocks it. The dashboard's `<PageTitle>` (`text-heading ≈ 18px`) remains the correct primitive for app surfaces.
|
|
317
319
|
|
|
318
320
|
This gate enforces "use the primitive when one exists" without enumerating the primitives in this rule (so the rule scales as the design-system grows).
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.4 — 2026-10-16
|
|
4
|
+
|
|
5
|
+
The milestone-ID and UI-substitution audit no longer forks per staged file. A pre-commit stage of ~1,600 files that ran over 27 minutes now finishes in about two seconds, with verdicts unchanged.
|
|
6
|
+
|
|
7
|
+
- **Scan** — one `git diff` and one `awk` cover the whole staged set. Reading the line above an added line now costs at most one `git show` per file with a candidate hit, down from one fork pair per added line.
|
|
8
|
+
- **Compatibility** — verdicts, hit order and exit codes match 0.14.3 byte-for-byte across staged, `--diff` and explicit-file modes, including spaced, quoted and non-ASCII filenames, renames re-scanned as whole-file additions, and absolute explicit paths.
|
|
9
|
+
|
|
10
|
+
## 0.14.3 — 2026-10-15
|
|
11
|
+
|
|
12
|
+
The REST API guide carries agentsfleet's conversation and reviewed-setup exceptions, and the TypeScript rules name how timestamps are labelled.
|
|
13
|
+
|
|
14
|
+
- **Conversation sends** — fleet and draft `/messages` sends take one UUIDv7 `operation_id` in the body and refuse an `Idempotency-Key` header; replay, conflict and `202` answers are defined.
|
|
15
|
+
- **Stale reviews** — a reviewed setup mutation refused with `409` also returns the current `review_digest` and `current_state`.
|
|
16
|
+
- **Record dates** — creation timestamps are labelled **Created**; other timestamps name their event.
|
|
17
|
+
|
|
3
18
|
## 0.14.2 — 2026-10-15
|
|
4
19
|
|
|
5
20
|
The release inventory reads npm 12's pack output, and the API and logging guides cite agentsfleet's current module paths.
|
|
@@ -142,7 +142,19 @@ If the parent ID is in the path, never repeat it in the body.
|
|
|
142
142
|
|
|
143
143
|
**PATCH idempotency guarantee** — PATCH MUST be idempotent unless the spec's "Failure Modes" section explicitly declares otherwise with a reason. Idempotent PATCH means: issuing the same body twice in succession produces identical row state and identical 200 responses. A negative test that issues the same PATCH twice and asserts equality is required (§10).
|
|
144
144
|
|
|
145
|
-
**Idempotency keys for non-idempotent POSTs.** Any POST that creates a billable, externally-visible, or side-effecting resource MUST accept an `Idempotency-Key` request header (UUIDv7 from the client). The server stores `(workspace_id, key) → response` for at least 24 hours and replays the prior response on duplicate. Endpoints exempt: pure RPC reads, internal-only POSTs. List which endpoints honor the key in the OpenAPI description.
|
|
145
|
+
**Idempotency keys for non-idempotent POSTs.** Any POST that creates a billable, externally-visible, or side-effecting resource MUST accept an `Idempotency-Key` request header (UUIDv7 from the client), except the conversation rules below. The server stores `(workspace_id, key) → response` for at least 24 hours and replays the prior response on duplicate. Endpoints exempt: pure RPC reads, internal-only POSTs. List which endpoints honor the key in the OpenAPI description.
|
|
146
|
+
|
|
147
|
+
**Owner-approved conversation exception.** Fleet and creator-private draft
|
|
148
|
+
`/messages` sends require one UUIDv7 `operation_id` in the JSON body. They MUST
|
|
149
|
+
reject an `Idempotency-Key` header. Draft creation uses the same body key and
|
|
150
|
+
atomically records its optional first message. Authorization and lifecycle
|
|
151
|
+
checks apply before replay. The same key, normalized message and actor recover
|
|
152
|
+
the accepted message; changed content or actor conflicts. Sends return
|
|
153
|
+
`202 {status, sequence, replayed}` for drafts, or the fleet's accepted event
|
|
154
|
+
receipt, with `Location` pointing to that conversation's messages collection.
|
|
155
|
+
Pending state, answers and usage are read through messages and its authorized
|
|
156
|
+
live stream. These resources create neither a public turn-status resource nor
|
|
157
|
+
a generic operation. Creator-private text never appears in workspace streams.
|
|
146
158
|
|
|
147
159
|
**Owner-approved workspace exception.** `POST /v1/workspaces` accepts no replay
|
|
148
160
|
key. It requires a tenant-unique name and performs one tenant-scoped insert with
|
|
@@ -156,7 +168,7 @@ generalize this exception to other POST endpoints.
|
|
|
156
168
|
|
|
157
169
|
### Long-running operations
|
|
158
170
|
|
|
159
|
-
Endpoints that can't complete inside the request window MUST follow this convention
|
|
171
|
+
Endpoints that can't complete inside the request window MUST follow this convention, except conversation acceptance described above:
|
|
160
172
|
|
|
161
173
|
1. The kicking POST returns `202 Accepted` with `Location: /v1/operations/{operation_id}` and a body containing `operation_id` and `status: "pending"`.
|
|
162
174
|
2. `GET /v1/operations/{operation_id}` returns:
|
|
@@ -434,6 +446,14 @@ the caller gets the registry's 500 and no internal detail.
|
|
|
434
446
|
- `409` MUST include `current_state: <string>` naming the state that forbade the transition (e.g. `"current_state": "approved"` when the caller tried to approve an already-approved gate).
|
|
435
447
|
- `412` MUST include `etag: <string>` carrying the resource's current ETag so the client can refetch and retry.
|
|
436
448
|
|
|
449
|
+
<!-- oracle-packs:start product.agentsfleet -->
|
|
450
|
+
Reviewed setup mutations in `agentsfleet` additionally return `review_digest`
|
|
451
|
+
on a stale-review `409`. The daemon computes this opaque digest from the current
|
|
452
|
+
effective setup and access/model resolution versions. The response also carries
|
|
453
|
+
`current_state`; clients fetch the current review before submitting again.
|
|
454
|
+
This extension grants no setup or runtime authority.
|
|
455
|
+
<!-- oracle-packs:end -->
|
|
456
|
+
|
|
437
457
|
Don't invent other extensions without amending this doc.
|
|
438
458
|
|
|
439
459
|
---
|
package/docs/RULE_ENFORCEMENT.md
CHANGED
|
@@ -7,14 +7,14 @@ regenerates this file and fails on any difference. To update it, run
|
|
|
7
7
|
| Rule document | enforced by | judged | acknowledged | classified | trigger |
|
|
8
8
|
|---|---|--:|--:|--:|---|
|
|
9
9
|
| `docs/LOGGING_STANDARD.md` | `LOG` → `logging.sh` | 10 | 13 | 30/30 | mechanical |
|
|
10
|
-
| `docs/REST_API_DESIGN_GUIDELINES.md` | — | 0 | 0 | 0/
|
|
10
|
+
| `docs/REST_API_DESIGN_GUIDELINES.md` | — | 0 | 0 | 0/69 | mechanical |
|
|
11
11
|
| `docs/SCHEMA_CONVENTIONS.md` | — | 0 | 0 | 0/17 | mechanical |
|
|
12
12
|
| `docs/DOCUMENTATION_RULES.md` | — | 0 | 0 | 0/22 | latent |
|
|
13
13
|
| `docs/LIFECYCLE_PATTERNS.md` | — | 0 | 0 | 0/22 | mechanical |
|
|
14
14
|
| `docs/CHANGELOG_VOICE.md` | — | 0 | 0 | 0/3 | latent |
|
|
15
15
|
| `docs/VERIFY_TIERS.md` | — | 0 | 0 | 0/4 | uncited |
|
|
16
16
|
| `docs/greptile-learnings/RULES.md` | — | 0 | 0 | 0/115 | mechanical |
|
|
17
|
-
| **corpus** | | | | **30/
|
|
17
|
+
| **corpus** | | | | **30/282** | |
|
|
18
18
|
|
|
19
19
|
## How to read this
|
|
20
20
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentsfleet/orly",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
4
4
|
"description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary \u2014 for any coding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,10 @@ const ERROR_TYPE = "pub struct Error;\n";
|
|
|
30
30
|
const VALUE_INIT = "pub fn init() Self { return .{}; }\n";
|
|
31
31
|
const ALLOCATING_INIT = "pub fn init(a: std.mem.Allocator) !Self { const owned = try a.alloc(u8, COUNT); return .{ .owned = owned }; }\n";
|
|
32
32
|
const DIFF = "--diff";
|
|
33
|
+
const SPACED = "lib/has space.ts";
|
|
34
|
+
const TRAILING_SPACE = "lib/trail .ts";
|
|
35
|
+
const OVERRIDE = "MILESTONE ID ALLOWED per user override (reason: pin)";
|
|
36
|
+
const MARKED_T = 'const t = "T' + '7";\n';
|
|
33
37
|
const SCANNERS = ["ufs.sh", "logging.sh", "deinit-pairs.sh", "sql-mod.sh", "error-codes.sh", "rust-error.sh", "design-tokens.sh"];
|
|
34
38
|
|
|
35
39
|
setDefaultTimeout(TEST_TIMEOUT_MS);
|
|
@@ -189,6 +193,39 @@ test("diff marker explicit files use working bytes and missing comparisons refus
|
|
|
189
193
|
expect(missing.text).toContain("comparison revision is missing: missing-revision");
|
|
190
194
|
});
|
|
191
195
|
|
|
196
|
+
test("diff markers survive spaced and trailing-space filenames", async () => {
|
|
197
|
+
const project = newRepository();
|
|
198
|
+
await staged(project, SPACED, MARKED);
|
|
199
|
+
await staged(project, TRAILING_SPACE, MARKED);
|
|
200
|
+
const result = scan(project, "audits/msid-ui.sh", [STAGED]);
|
|
201
|
+
expect(result.code).toBe(1);
|
|
202
|
+
expect(result.text).toContain(`MS-ID ${SPACED}: // ${MARKER}`);
|
|
203
|
+
expect(result.text).toContain(`MS-ID ${TRAILING_SPACE}: // ${MARKER}`);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("pure rename re-scans the file as a whole-file addition", async () => {
|
|
207
|
+
const project = newRepository();
|
|
208
|
+
await staged(project, "lib/old.ts", MARKED);
|
|
209
|
+
git(project, COMMIT, "-qm", "test: seed rename");
|
|
210
|
+
git(project, "mv", "lib/old.ts", "lib/new.ts");
|
|
211
|
+
const result = scan(project, "audits/msid-ui.sh", [STAGED]);
|
|
212
|
+
expect(result.code).toBe(1);
|
|
213
|
+
expect(result.text).toContain(`MS-ID lib/new.ts: // ${MARKER}`);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("explicit absolute paths keep the caller path and read its post-image", async () => {
|
|
217
|
+
const project = newRepository();
|
|
218
|
+
const absolute = join(temporaryDirectory(), "outside.ts");
|
|
219
|
+
await Bun.write(absolute, `// ${OVERRIDE}\n${MARKED_T}`);
|
|
220
|
+
const suppressed = scan(project, "audits/msid-ui.sh", [absolute]);
|
|
221
|
+
expect(suppressed.code).toBe(0);
|
|
222
|
+
expect(suppressed.text).toContain("0 hits");
|
|
223
|
+
await Bun.write(absolute, MARKED_T);
|
|
224
|
+
const flagged = scan(project, "audits/msid-ui.sh", [absolute]);
|
|
225
|
+
expect(flagged.code).toBe(1);
|
|
226
|
+
expect(flagged.text).toContain(`MS-ID ${absolute}: const t = "T` + `7";`);
|
|
227
|
+
});
|
|
228
|
+
|
|
192
229
|
test("indexed source links cannot read outside their owned snapshot", async () => {
|
|
193
230
|
const project = newRepository();
|
|
194
231
|
const external = join(temporaryDirectory(), "owner.ts");
|