@agentsfleet/orly 0.11.0 → 0.12.1

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 CHANGED
@@ -42,22 +42,29 @@ Works with Claude Code, Codex, OpenCode, and Amp.
42
42
 
43
43
  ---
44
44
 
45
- ## 0.11.0 experiment: explicit Jev advice
45
+ ## 0.12: migration verification
46
46
 
47
- The Bun command line adds `orly judge` for selected planning, test, review and
48
- documentation questions.
47
+ 0.12.0 shipped migration verification and setup guidance. The `.orly/` layout
48
+ and six existing `orly judge` questions originally shipped in 0.11.0.
49
+ This release focuses on package verification, migration evidence and clearer setup guidance; it adds no new runtime judgment question.
49
50
 
50
- - **Judgments** — selected evidence produces a fixed next action; existing checks retain control of gate results.
51
- - **Installation** — `bunx` runs the source package through Bun, continuing the `0.10.x` setup on macOS and Linux.
51
+ The `agentsfleet` trial upgraded a dedicated worktree from 0.10.14 using the packed candidate.
52
+ Owner files, hooks and verification commands survived; a second update wrote nothing.
53
+ See the [release report](evals/release/release-report.md) for exact proof and remaining release checks.
52
54
 
53
- Use `orly judge --help` and the [judgment reference](docs/JUDGMENTS.md) for the
54
- manifest, atomic criteria and explicit source upload. This page describes the
55
- 0.11.0 source package, which is not yet published. The commands below fetch the
56
- published package; use them for this behavior after 0.11.0 is published.
55
+ Jev, TypeSafe's bounded judgment model, supplies advice only when requested.
56
+ Existing deterministic checks retain control of gate results.
57
+ The comparison evaluator is parked for proposed 0.13 work; model accuracy and candidate benefits remain unmeasured.
58
+ See the [judgment reference](docs/JUDGMENTS.md) for evidence selection, offline replay and explicit source upload.
59
+
60
+ 0.12.1 avoids copying repository files for staged audits with no matching files
61
+ and makes hosted-check monitoring show job states and links before reporting a failure.
62
+ See [releases](https://github.com/agentsfleet/orly/releases) for published versions.
63
+ The install commands below fetch the published package.
57
64
 
58
65
  ---
59
66
 
60
- ## Prerequisites for 0.11.0
67
+ ## Prerequisites
61
68
 
62
69
  | You need | Why | Version |
63
70
  |---|---|---|
@@ -71,7 +78,7 @@ published package; use them for this behavior after 0.11.0 is published.
71
78
 
72
79
  ---
73
80
 
74
- ## Install 0.11.0 after publication
81
+ ## Install the published package
75
82
 
76
83
  Run this inside the repository you want governed.
77
84
 
@@ -120,6 +127,32 @@ Unknown settings and malformed values are refused; `schemas/profile.schema.json`
120
127
 
121
128
  ---
122
129
 
130
+ ## Upgrade from 0.10.x
131
+
132
+ Run the update from the consumer repository. If the repository owns its hooks, preserve them explicitly:
133
+
134
+ ```bash
135
+ bunx --bun @agentsfleet/orly update --no-hooks
136
+ ```
137
+
138
+ Expected: managed files move under `.orly/`; owner hooks remain unchanged. Conflicting owner edits stop the update and name the affected path.
139
+ Use the installed candidate executable for an unpublished release trial; the command above fetches the published version.
140
+
141
+ Update repository-owned callers that still read `.oracle/orly.json`, root `dispatch/`, or managed root audit paths.
142
+ Their replacements are `.orly/orly.json`, `.orly/dispatch/`, and `.orly/audits/`.
143
+ Repository-owned audit scripts and project documents retain their existing locations.
144
+ Review existing hooks before choosing an update without `--no-hooks`.
145
+
146
+ ```bash
147
+ bunx --bun @agentsfleet/orly doctor
148
+ ```
149
+
150
+ Expected: installed rules match `.orly/orly.json` and work and verification commands are declared.
151
+ Then run the repository's declared checks; `doctor` alone does not run them.
152
+ An empty old directory can remain after its files move. Git does not track empty directories; remove one only after checking it is empty.
153
+
154
+ ---
155
+
123
156
  ## What lands in your repository
124
157
 
125
158
  ```text
@@ -48,7 +48,7 @@ set -euo pipefail
48
48
 
49
49
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
50
50
  audit_scope_init --all "$@"
51
- audit_index_snapshot "$@"
51
+ audit_index_snapshot '*.zig'
52
52
 
53
53
  MODE="${1:-${SCOPE:-all}}"
54
54
  ROOT="$(git rev-parse --show-toplevel)"
@@ -39,7 +39,7 @@ set -euo pipefail
39
39
 
40
40
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
41
41
  audit_scope_init --all "$@"
42
- audit_index_snapshot "$@"
42
+ audit_index_snapshot '*.tsx' '*.jsx' 'ui/packages/app/*' 'ui/packages/website/*' 'ui/packages/design-system/*'
43
43
 
44
44
  MODE="$AUDIT_MODE"
45
45
  ROOT="$(git rev-parse --show-toplevel)"
@@ -51,7 +51,7 @@ set -euo pipefail
51
51
 
52
52
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
53
53
  audit_scope_init --all "$@"
54
- audit_index_snapshot "$@"
54
+ audit_index_snapshot
55
55
 
56
56
  MODE="$AUDIT_MODE"
57
57
  ROOT="$(git rev-parse --show-toplevel)"
package/audits/logging.sh CHANGED
@@ -43,7 +43,7 @@ set -euo pipefail
43
43
 
44
44
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
45
45
  audit_scope_init --all "$@"
46
- audit_index_snapshot "$@"
46
+ audit_index_snapshot '*.zig' '*.rs' 'agentsfleet/src/*.js' 'agentsfleet/src/*.jsx' 'agentsfleet/src/*.ts' 'agentsfleet/src/*.tsx'
47
47
 
48
48
  MODE="--all"
49
49
  STRICT=0
@@ -44,7 +44,7 @@ set -euo pipefail
44
44
 
45
45
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
46
46
  audit_scope_init --all "$@"
47
- audit_index_snapshot "$@"
47
+ audit_index_snapshot '*.rs'
48
48
 
49
49
  MODE_STAGED="--staged"
50
50
  MODE_ALL="--all"
package/audits/scope.sh CHANGED
@@ -37,7 +37,7 @@ audit_scope_init() {
37
37
  # the index copy and checked-out files belong to this invocation alone.
38
38
  audit_index_snapshot() {
39
39
  [ "$AUDIT_MODE" = --staged ] || return 0
40
- local gitdir index
40
+ local gitdir index changed=0
41
41
  AUDIT_SNAPSHOT_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/orly-index.XXXXXX")" || return 1
42
42
  trap 'rm -rf -- "$AUDIT_SNAPSHOT_ROOT"' EXIT
43
43
  trap 'exit 130' INT
@@ -46,6 +46,14 @@ audit_index_snapshot() {
46
46
  index="$(git rev-parse --git-path index)" || exit 1
47
47
  cp "$index" "$AUDIT_SNAPSHOT_ROOT/index" || exit 1
48
48
  export GIT_DIR="$gitdir" GIT_INDEX_FILE="$AUDIT_SNAPSHOT_ROOT/index" GIT_OPTIONAL_LOCKS=0
49
+ if [ "$#" -gt 0 ]; then
50
+ git diff --cached --quiet --diff-filter=ACMRT -- "$@" || changed=$?
51
+ case "$changed" in
52
+ 0) return 0 ;;
53
+ 1) ;;
54
+ *) printf 'cannot select staged audit files (git exit %s)\n' "$changed" >&2; exit 2 ;;
55
+ esac
56
+ fi
49
57
  export GIT_WORK_TREE="$AUDIT_SNAPSHOT_ROOT/tree"
50
58
  mkdir "$GIT_WORK_TREE" || exit 1
51
59
  git checkout-index --all --prefix="$GIT_WORK_TREE/" || exit 1
package/audits/sql-mod.sh CHANGED
@@ -25,7 +25,7 @@ set -euo pipefail
25
25
 
26
26
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
27
27
  audit_scope_init --all "$@"
28
- audit_index_snapshot "$@"
28
+ audit_index_snapshot '*.zig'
29
29
 
30
30
  MODE="${1:-${SCOPE:---all}}"
31
31
  ROOT="$(git rev-parse --show-toplevel)"
package/audits/ufs.sh CHANGED
@@ -55,7 +55,7 @@ set -euo pipefail
55
55
 
56
56
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
57
57
  audit_scope_init --all "$@"
58
- audit_index_snapshot "$@"
58
+ audit_index_snapshot '*.zig' '*.ts' '*.tsx' '*.js' '*.jsx' '*.rs' '*.go'
59
59
 
60
60
  # Default: full-codebase scan (`--all` is an explicit alias). `--staged` is the
61
61
  # pre-commit lens — it narrows the per-file checks to `git diff --cached`. The
package/dispatch/lib.sh CHANGED
@@ -125,7 +125,7 @@ dispatch_managed_paths() {
125
125
  dispatch_resolve_files() {
126
126
  DISPATCH_FILES=()
127
127
  audit_scope_init --all "$@"
128
- audit_index_snapshot "$@"
128
+ audit_index_snapshot
129
129
  DISPATCH_STAGED=0
130
130
  if [ "${1:-}" = "--staged" ]; then
131
131
  DISPATCH_STAGED=1
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 0.12.1
4
+
5
+ Staged audits avoid copying repository files when their filename scope is empty.
6
+
7
+ - **Audit preflight** — selects filenames from a copied index before materialising repository files; relevant audits retain coherent staged reads.
8
+ - **Check diagnostics** — prints hosted job or pipeline states and links before reporting pending or failed checks.
9
+ - **Release status** — corrects documentation that still described published 0.12.0 as an unpublished candidate.
10
+
11
+ ## 0.12.0 — 2026-10-07
12
+
13
+ Setup guidance now distinguishes the published package from the local release candidate.
14
+
15
+ - **Migration guidance** — names `.orly/` destinations and explains preserving repository-owned hooks with `--no-hooks`.
16
+ - **Release scope** — retains the six existing judgment questions and records the comparison evaluator as parked for proposed 0.13 work.
17
+
18
+ The `.orly/` layout and judgment commands already shipped in 0.11.0; this entry claims no new runtime behavior.
package/docs/JUDGMENTS.md CHANGED
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  type: reference
3
3
  audience: contributor
4
- verified: 2026-10-04
5
- product_version: 0.11.0
4
+ verified: 2026-10-07
5
+ product_version: 0.12.0
6
6
  executable: false
7
7
  ---
8
8
 
@@ -10,6 +10,9 @@ executable: false
10
10
 
11
11
  ## Synopsis
12
12
 
13
+ The 0.12.0 candidate retains the six questions shipped in 0.11.0.
14
+ The comparison evaluator is parked for proposed 0.13 work; this release makes no new model-quality claim.
15
+
13
16
  `orly judge` asks one bounded question about evidence you select.
14
17
  It accompanies planning, verification, review and documentation.
15
18
  Its answer suggests an inspection or a fixed next action.
@@ -84,6 +84,13 @@ Every criterion is mechanical — it reads an exit code or a file. Claims that
84
84
  cannot be proven that way stay prose and are graded by the spec's rubric; they
85
85
  never become fake criteria.
86
86
 
87
+ Filename-scoped staged audits copy Git's index before selecting relevant paths.
88
+ An empty selection skips the repository checkout; matching paths still trigger
89
+ a private checkout, dependency reads and indexed-link checks. Each audit owns
90
+ its temporary files and cleanup. Audits with unconditional registry checks
91
+ retain a complete checkout. Separate relevant audits retain separate snapshots;
92
+ this optimization does not introduce a shared snapshot cache.
93
+
87
94
  Four behaviours worth knowing:
88
95
 
89
96
  - **No spec, no problem — but closing is not escaping.** Spec criteria skip
package/llms.txt CHANGED
@@ -12,9 +12,13 @@ Package: `@agentsfleet/orly` (npm)
12
12
  Source: https://github.com/agentsfleet/orly
13
13
  License: Massachusetts Institute of Technology (MIT)
14
14
 
15
- This guide describes the unpublished 0.11.0 source package. The `bunx` commands
16
- below fetch the published package, not this source checkout. Use this install
17
- sequence for the described behavior after 0.11.0 is published.
15
+ 0.12.0 shipped migration verification; the runtime commands and `.orly/` layout
16
+ originally shipped in 0.11.0. The 0.12.1 patch avoids irrelevant staged audit
17
+ snapshots and preserves hosted-check diagnostics. The `bunx` commands below
18
+ fetch the published package; available versions are listed at
19
+ https://github.com/agentsfleet/orly/releases. Use an installed local tarball for candidate checks.
20
+ The comparison evaluator is parked for proposed 0.13 work; do not claim measured
21
+ judgment improvement or enable new questions from its unfinished results.
18
22
 
19
23
  ---
20
24
 
@@ -79,6 +83,13 @@ suites. `init` reports missing configuration; `doctor` rejects it.
79
83
 
80
84
  ---
81
85
 
86
+ For a 0.10.x consumer with repository-owned hooks, run `orly update --no-hooks`
87
+ using the intended installed version. Change repository callers from
88
+ `.oracle/orly.json` to `.orly/orly.json` and shared rule/audit paths to `.orly/`.
89
+ Keep repository-owned scripts and documents in place. Run `orly doctor` and the
90
+ declared checks before accepting the migration; doctor alone does not run checks.
91
+ Empty old directories may remain, but contain no tracked managed files.
92
+
82
93
  ## 4. Read order before you edit anything
83
94
 
84
95
  1. `AGENTS.md` — the user's own rules. **These win on any conflict.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsfleet/orly",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
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",
@@ -93,8 +93,17 @@ Each cycle, after the review-thread walk, poll CI and act by cause:
93
93
 
94
94
  ```bash
95
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; }
96
+ CHECK_STATUS=0
97
+ CHECKS=$(gh pr checks "$PR_NUMBER" --json name,state,bucket,link) || CHECK_STATUS=$?
98
+ printf '%s' "$CHECKS" | jq -e 'type == "array" and length > 0 and all(.[];
99
+ (.name | type == "string") and (.state | type == "string") and
100
+ (.link | type == "string") and (.bucket | IN("pass", "fail", "pending", "skipping", "cancel")))' >/dev/null \
101
+ || { printf 'BABYSIT: invalid check response (exit %s); reset quiet polls\n%s\n' "$CHECK_STATUS" "$CHECKS" >&2; exit 1; }
102
+ printf '%s' "$CHECKS" | jq -r '.[] | "\(.name) | \(.state) | \(.bucket) | \(.link)"'
103
+ case "$CHECK_STATUS" in
104
+ 0|1|8) ;; # GitHub reports failed and pending checks with nonzero statuses.
105
+ *) printf 'BABYSIT: check fetch incomplete (exit %s); reset quiet polls\n' "$CHECK_STATUS" >&2; exit 1 ;;
106
+ esac
98
107
  printf '%s' "$CHECKS" | jq -e 'type == "array" and length > 0 and all(.[]; .bucket == "pass" or .bucket == "skipping")' >/dev/null \
99
108
  || { echo "BABYSIT: checks empty or not green; reset quiet polls" >&2; exit 1; }
100
109
  CHECK_HEAD=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid')
@@ -107,8 +116,14 @@ echo "BABYSIT: ci=green revision=$CHECK_HEAD"
107
116
 
108
117
  ```bash
109
118
  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; }
119
+ PIPELINE_STATUS=0
120
+ PIPELINES=$(glab api "projects/$PROJ_ENC/merge_requests/$MR_IID/pipelines" --paginate | jq -sc 'add') || PIPELINE_STATUS=$?
121
+ printf '%s' "$PIPELINES" | jq -e 'type == "array" and length > 0 and all(.[];
122
+ (.id | type == "number") and (.sha | type == "string") and (.status | type == "string"))' >/dev/null \
123
+ || { printf 'BABYSIT: invalid pipeline response (exit %s); reset quiet polls\n%s\n' "$PIPELINE_STATUS" "$PIPELINES" >&2; exit 1; }
124
+ printf '%s' "$PIPELINES" | jq -r '.[] | "pipeline \(.id) | \(.status) | \(.web_url // "URL not reported")"'
125
+ [ "$PIPELINE_STATUS" -eq 0 ] \
126
+ || { printf 'BABYSIT: pipeline fetch incomplete (exit %s); reset quiet polls\n' "$PIPELINE_STATUS" >&2; exit 1; }
112
127
  printf '%s' "$PIPELINES" | jq -e --arg revision "$POLL_HEAD" \
113
128
  '[.[] | select(.sha == $revision)] | sort_by(.id) | last | .status == "success"' >/dev/null \
114
129
  || { echo "BABYSIT: current revision has no successful pipeline; reset quiet polls" >&2; exit 1; }
@@ -30,7 +30,11 @@ else if(args[0]==="pr" && args[1]==="view") {
30
30
  else { const prior=(await Bun.file(process.env.POLL_CALLS).text()).split("\\n").filter(x=>x.includes("headRefOid")).length; console.log((process.env.POLL_MODE==="changed" && prior>1) || (process.env.POLL_MODE==="changed-check" && prior>2) ? "changed-revision" : process.env.POLL_HEAD); }
31
31
  } else if(args[0]==="pr" && args[1]==="checks") {
32
32
  if(process.env.POLL_STATE==="fetch-failed"){console.error("temporary fetch refusal");process.exit(2);}
33
- console.log(JSON.stringify(process.env.POLL_STATE==="empty" ? [] : [{name:"tests",state:"SUCCESS",bucket:process.env.POLL_STATE==="failed" ? "fail" : "pass",link:"https://example.invalid/check"}]));
33
+ const state=process.env.POLL_STATE;
34
+ const bucket=state==="failed" ? "fail" : state==="pending" ? "pending" : "pass";
35
+ console.log(JSON.stringify(state==="empty" ? [] : state==="malformed-check" ? [{name:"tests"}] : [{name:"tests",state:bucket==="pass" ? "SUCCESS" : bucket==="fail" ? "FAILURE" : "IN_PROGRESS",bucket,link:"https://example.invalid/check"}]));
36
+ if(state==="failed") process.exit(1);
37
+ if(state==="pending") process.exit(8);
34
38
  } else if(args[0]==="api") {
35
39
  if(process.env.POLL_MODE==="fetch-failed" && args[1].includes("/issues/")){console.error("temporary summary refusal");process.exit(2);}
36
40
  const revision=process.env.POLL_MODE==="stale" ? "old-revision" : process.env.POLL_HEAD;
@@ -75,7 +79,7 @@ for (const mode of ["fetch-failed", "malformed", "changed", "changed-check", "st
75
79
  });
76
80
  }
77
81
 
78
- for (const state of ["empty", "fetch-failed", "failed"]) {
82
+ for (const state of ["empty", "fetch-failed", "failed", "pending", "malformed-check"]) {
79
83
  test(`${state} check discovery stays incomplete`, () => {
80
84
  const result = poll("complete", state);
81
85
  expect(result.code).not.toBe(0);
@@ -84,6 +88,17 @@ for (const state of ["empty", "fetch-failed", "failed"]) {
84
88
  });
85
89
  }
86
90
 
91
+ for (const state of ["failed", "pending"]) {
92
+ test(`${state} GitHub checks retain job details despite nonzero status`, () => {
93
+ const result = poll("complete", state);
94
+ expect(result.code).not.toBe(0);
95
+ expect(result.output).toContain("tests |");
96
+ expect(result.output).toContain("https://example.invalid/check");
97
+ expect(result.output).toContain(state === "failed" ? "FAILURE | fail" : "IN_PROGRESS | pending");
98
+ expect(result.output).not.toContain("check fetch incomplete");
99
+ });
100
+ }
101
+
87
102
  function gitlabPoll(mode = "complete", state = "green") {
88
103
  const root = newRepository();
89
104
  const tools = join(root, "tools");
@@ -105,7 +120,7 @@ else if(args[0]==="mr") {
105
120
  if(process.env.POLL_MODE==="fetch-failed" && endpoint.endsWith("/notes")){console.error("temporary summary refusal");process.exit(2);}
106
121
  if(endpoint.endsWith("/pipelines")) {
107
122
  if(process.env.POLL_STATE==="fetch-failed"){console.error("temporary pipeline refusal");process.exit(2);}
108
- console.log(JSON.stringify(process.env.POLL_STATE==="empty" ? [] : [{id:3,sha:process.env.POLL_HEAD,status:process.env.POLL_STATE==="failed" ? "failed" : "success"}]));
123
+ console.log(JSON.stringify(process.env.POLL_STATE==="empty" ? [] : [{id:3,sha:process.env.POLL_HEAD,status:process.env.POLL_STATE==="failed" ? "failed" : process.env.POLL_STATE==="pending" ? "running" : "success",web_url:"https://example.invalid/pipeline"}]));
109
124
  } else if(process.env.POLL_MODE==="malformed") console.log(JSON.stringify({message:"not review evidence"}));
110
125
  else {
111
126
  const note=id=>({id,author:{username:"greptile"},body:id===1 ? "first-page finding" : "second-page finding",type:null});
@@ -148,7 +163,7 @@ for (const mode of ["fetch-failed", "malformed", "changed", "changed-check"]) {
148
163
  });
149
164
  }
150
165
 
151
- for (const state of ["empty", "fetch-failed", "failed"]) {
166
+ for (const state of ["empty", "fetch-failed", "failed", "pending"]) {
152
167
  test(`GitLab ${state} pipeline discovery stays incomplete`, () => {
153
168
  const result = gitlabPoll("complete", state);
154
169
  expect(result.code).not.toBe(0);
@@ -156,3 +171,12 @@ for (const state of ["empty", "fetch-failed", "failed"]) {
156
171
  expect(result.output).not.toContain("ci=green");
157
172
  });
158
173
  }
174
+
175
+ for (const state of ["failed", "pending"]) {
176
+ test(`GitLab ${state} pipeline retains its state and link`, () => {
177
+ const result = gitlabPoll("complete", state);
178
+ expect(result.code).not.toBe(0);
179
+ expect(result.output).toContain(`pipeline 3 | ${state === "failed" ? "failed" : "running"}`);
180
+ expect(result.output).toContain("https://example.invalid/pipeline");
181
+ });
182
+ }
@@ -0,0 +1,82 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ import { cleanupTemporaryDirectories, git, newRepository, ROOT, temporaryDirectory } from "./gates_test_support";
6
+ import { UNSCOPED_ENVIRONMENT } from "./git_env";
7
+
8
+ const SCOPED_AUDITS = ["ufs.sh", "logging.sh", "rust-error.sh", "deinit-pairs.sh", "sql-mod.sh", "design-tokens.sh"];
9
+ const SOURCE = "lib/odd 'quoted.ts";
10
+ const CLEAN = "export const MESSAGE = 'staged';\n";
11
+ const BAD = 'export function first() { return "duplicate value"; }\nexport function second() { return "duplicate value"; }\n';
12
+ const CHECKOUT = "checkout-index";
13
+ const TIMEOUT_MS = 30_000;
14
+ const TOOL_MODE = 0o755;
15
+
16
+ afterEach(cleanupTemporaryDirectories);
17
+
18
+ async function runScan(project: string, leaf: string, failPreflight = false) {
19
+ const tools = temporaryDirectory();
20
+ const scratch = temporaryDirectory();
21
+ const calls = join(tools, "calls.jsonl");
22
+ const realGit = Bun.which("git")!;
23
+ mkdirSync(join(tools, "bin"));
24
+ writeFileSync(join(tools, "bin/git"), `#!${process.execPath}
25
+ const args = Bun.argv.slice(2);
26
+ const file = Bun.file(process.env.SNAPSHOT_CALLS);
27
+ await Bun.write(file, (await file.exists() ? await file.text() : "") + JSON.stringify(args) + "\\n");
28
+ if (process.env.FAIL_PREFLIGHT === "1" && args.includes("--quiet")) process.exit(2);
29
+ const result = Bun.spawnSync([process.env.REAL_GIT, ...args], { stdin: "inherit", stdout: "inherit", stderr: "inherit" });
30
+ process.exit(result.exitCode);
31
+ `, { mode: TOOL_MODE });
32
+ const result = Bun.spawnSync(["/bin/bash", join(ROOT, "audits", leaf), "--staged"], {
33
+ cwd: project, env: { ...UNSCOPED_ENVIRONMENT, PATH: `${join(tools, "bin")}:${process.env.PATH}`, TMPDIR: scratch,
34
+ SNAPSHOT_CALLS: calls, REAL_GIT: realGit, FAIL_PREFLIGHT: failPreflight ? "1" : "0" },
35
+ stdout: "pipe", stderr: "pipe", timeout: TIMEOUT_MS,
36
+ });
37
+ const commands: string[][] = (await Bun.file(calls).text()).trim().split("\n").map((line) => JSON.parse(line));
38
+ return { code: result.exitCode, output: result.stdout.toString() + result.stderr.toString(),
39
+ checkouts: commands.filter((args) => args[0] === CHECKOUT).length, leftovers: readdirSync(scratch) };
40
+ }
41
+
42
+ test.each(SCOPED_AUDITS)("%s skips repository checkout for a documentation-only index", async (leaf) => {
43
+ const project = newRepository();
44
+ await Bun.write(join(project, "README.md"), "Changed documentation.\n");
45
+ git(project, "add", "README.md");
46
+ const result = await runScan(project, leaf);
47
+ expect(result.code, result.output).toBe(0);
48
+ expect(result.checkouts).toBe(0);
49
+ expect(result.leftovers).toEqual([]);
50
+ });
51
+
52
+ test.each([true, false])("relevant source snapshot checks staged bytes with violation %s", async (badIndex) => {
53
+ const project = newRepository();
54
+ await Bun.write(join(project, SOURCE), badIndex ? BAD : CLEAN);
55
+ git(project, "add", SOURCE);
56
+ await Bun.write(join(project, SOURCE), badIndex ? CLEAN : BAD);
57
+ const result = await runScan(project, "ufs.sh");
58
+ expect(result.code, result.output).toBe(badIndex ? 1 : 0);
59
+ expect(result.checkouts).toBe(1);
60
+ expect(result.leftovers).toEqual([]);
61
+ });
62
+
63
+ test("a failed staged filename query refuses instead of reporting an empty scope", async () => {
64
+ const project = newRepository();
65
+ const result = await runScan(project, "ufs.sh", true);
66
+ expect(result.code).toBe(2);
67
+ expect(result.output).toContain("cannot select staged audit files");
68
+ expect(result.checkouts).toBe(0);
69
+ expect(result.leftovers).toEqual([]);
70
+ });
71
+
72
+ test("a deleted source path needs no repository checkout", async () => {
73
+ const project = newRepository();
74
+ await Bun.write(join(project, SOURCE), CLEAN);
75
+ git(project, "add", SOURCE);
76
+ git(project, "commit", "-qm", "test: seed source");
77
+ git(project, "rm", SOURCE);
78
+ const result = await runScan(project, "ufs.sh");
79
+ expect(result.code, result.output).toBe(0);
80
+ expect(result.checkouts).toBe(0);
81
+ expect(result.leftovers).toEqual([]);
82
+ });