@oh-my-pi/pi-coding-agent 16.2.7 → 16.2.8

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.
@@ -4962,6 +4962,62 @@ export declare const SETTINGS_SCHEMA: {
4962
4962
  }];
4963
4963
  };
4964
4964
  };
4965
+ readonly "providers.streamFirstEventTimeoutSeconds": {
4966
+ readonly type: "number";
4967
+ readonly default: -1;
4968
+ readonly ui: {
4969
+ readonly tab: "providers";
4970
+ readonly group: "Timeouts";
4971
+ readonly label: "Stream First Event Timeout";
4972
+ readonly description: "Seconds to wait for the first model stream event; -1 uses provider/env defaults, 0 disables the watchdog";
4973
+ readonly options: readonly [{
4974
+ readonly value: "-1";
4975
+ readonly label: "Auto";
4976
+ readonly description: "Use provider defaults and PI_* timeout env vars";
4977
+ }, {
4978
+ readonly value: "0";
4979
+ readonly label: "Off";
4980
+ readonly description: "Disable first-event timeout";
4981
+ }, {
4982
+ readonly value: "300";
4983
+ readonly label: "5 minutes";
4984
+ }, {
4985
+ readonly value: "600";
4986
+ readonly label: "10 minutes";
4987
+ }, {
4988
+ readonly value: "1800";
4989
+ readonly label: "30 minutes";
4990
+ }];
4991
+ };
4992
+ };
4993
+ readonly "providers.streamIdleTimeoutSeconds": {
4994
+ readonly type: "number";
4995
+ readonly default: -1;
4996
+ readonly ui: {
4997
+ readonly tab: "providers";
4998
+ readonly group: "Timeouts";
4999
+ readonly label: "Stream Idle Timeout";
5000
+ readonly description: "Seconds a model stream may stay silent between events; -1 uses provider/env defaults, 0 disables the watchdog";
5001
+ readonly options: readonly [{
5002
+ readonly value: "-1";
5003
+ readonly label: "Auto";
5004
+ readonly description: "Use provider defaults and PI_* timeout env vars";
5005
+ }, {
5006
+ readonly value: "0";
5007
+ readonly label: "Off";
5008
+ readonly description: "Disable idle timeout";
5009
+ }, {
5010
+ readonly value: "300";
5011
+ readonly label: "5 minutes";
5012
+ }, {
5013
+ readonly value: "600";
5014
+ readonly label: "10 minutes";
5015
+ }, {
5016
+ readonly value: "1800";
5017
+ readonly label: "30 minutes";
5018
+ }];
5019
+ };
5020
+ };
4965
5021
  readonly "providers.openrouterVariant": {
4966
5022
  readonly type: "enum";
4967
5023
  readonly values: readonly ["default", "nitro", "floor", "online", "exacto"];
@@ -34,6 +34,7 @@ export declare class HashlineFilesystem extends Filesystem {
34
34
  canonicalPath(relativePath: string): string;
35
35
  allowTagPathRecovery(authoredPath: string, resolvedPath: string): boolean;
36
36
  readText(relativePath: string): Promise<string>;
37
+ readBinary(relativePath: string): Promise<Uint8Array | undefined>;
37
38
  preflightWrite(relativePath: string, options?: PreflightWriteOptions): Promise<void>;
38
39
  delete(relativePath: string): Promise<void>;
39
40
  move(fromRelative: string, toRelative: string, content?: string): Promise<void>;
@@ -41,9 +41,4 @@ export interface BuildSessionContextOptions {
41
41
  /** In transcript mode, elide entries replaced by the latest compaction. */
42
42
  collapseCompactedHistory?: boolean;
43
43
  }
44
- /**
45
- * Build the session context from entries using tree traversal.
46
- * If leafId is provided, walks from that entry to root.
47
- * Handles compaction and branch summaries along the path.
48
- */
49
44
  export declare function buildSessionContext(entries: SessionEntry[], leafId?: string | null, byId?: Map<string, SessionEntry>, options?: BuildSessionContextOptions): SessionContext;
@@ -2,8 +2,8 @@
2
2
  * Settings-aware stream wrapper shared by the main agent (sdk.ts) and the
3
3
  * advisor agent (AgentSession.#buildAdvisorRuntime).
4
4
  *
5
- * Reads OpenRouter / Antigravity routing variants, Responses-family text
6
- * verbosity, per-provider in-flight caps, and the loop guard out of `Settings`
5
+ * verbosity, stream watchdog budgets, per-provider in-flight caps, and the loop
6
+ * guard out of `Settings`
7
7
  * per request, layering them onto whatever options the caller passed. Before
8
8
  * this helper existed, advisor turns called bare `streamSimple` while the main
9
9
  * turn went through an inline closure that read these settings — so an advisor on
@@ -14,6 +14,16 @@ export interface OutputValidator {
14
14
  validate(value: unknown): JsonSchemaValidationResult;
15
15
  /** Top-level required property names. Empty if the schema has no `required` array at root. */
16
16
  readonly requiredFields: readonly string[];
17
+ /**
18
+ * Per-label validators for incremental yields (`type: ["<label>"]`). Each entry validates the
19
+ * `data` payload of a single section against the matching top-level property's sub-schema —
20
+ * array-typed properties (e.g. `findings`) use the items schema since each yield contributes
21
+ * one element, while scalar properties use the property schema directly. Unknown labels (not
22
+ * top-level properties) have no entry and skip per-call validation. Lets the yield tool give
23
+ * the model retry feedback on a section as soon as it arrives, instead of deferring every
24
+ * mismatch to the parent's post-mortem `schema_violation`.
25
+ */
26
+ readonly validateSection: ReadonlyMap<string, (value: unknown) => JsonSchemaValidationResult>;
17
27
  }
18
28
  export interface BuildOutputValidatorResult {
19
29
  /** Present when the schema produced a usable validator (i.e. constraining schemas). Absent for missing/unconstrained schemas. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "16.2.7",
4
+ "version": "16.2.8",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -55,17 +55,17 @@
55
55
  "@agentclientprotocol/sdk": "0.25.0",
56
56
  "@babel/parser": "^7.29.7",
57
57
  "@mozilla/readability": "^0.6.0",
58
- "@oh-my-pi/hashline": "16.2.7",
59
- "@oh-my-pi/omp-stats": "16.2.7",
60
- "@oh-my-pi/pi-agent-core": "16.2.7",
61
- "@oh-my-pi/pi-ai": "16.2.7",
62
- "@oh-my-pi/pi-catalog": "16.2.7",
63
- "@oh-my-pi/pi-mnemopi": "16.2.7",
64
- "@oh-my-pi/pi-natives": "16.2.7",
65
- "@oh-my-pi/pi-tui": "16.2.7",
66
- "@oh-my-pi/pi-utils": "16.2.7",
67
- "@oh-my-pi/pi-wire": "16.2.7",
68
- "@oh-my-pi/snapcompact": "16.2.7",
58
+ "@oh-my-pi/hashline": "16.2.8",
59
+ "@oh-my-pi/omp-stats": "16.2.8",
60
+ "@oh-my-pi/pi-agent-core": "16.2.8",
61
+ "@oh-my-pi/pi-ai": "16.2.8",
62
+ "@oh-my-pi/pi-catalog": "16.2.8",
63
+ "@oh-my-pi/pi-mnemopi": "16.2.8",
64
+ "@oh-my-pi/pi-natives": "16.2.8",
65
+ "@oh-my-pi/pi-tui": "16.2.8",
66
+ "@oh-my-pi/pi-utils": "16.2.8",
67
+ "@oh-my-pi/pi-wire": "16.2.8",
68
+ "@oh-my-pi/snapcompact": "16.2.8",
69
69
  "@opentelemetry/api": "^1.9.1",
70
70
  "@opentelemetry/context-async-hooks": "^2.7.1",
71
71
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -144,7 +144,7 @@ export const TAB_GROUPS: Record<SettingTab, readonly string[]> = {
144
144
  "Developer",
145
145
  ],
146
146
  tasks: ["Modes", "Subagents", "Isolation", "Commands & Skills"],
147
- providers: ["Services", "Fireworks", "Tiny Model", "Protocol", "Privacy"],
147
+ providers: ["Services", "Fireworks", "Tiny Model", "Protocol", "Timeouts", "Privacy"],
148
148
  };
149
149
 
150
150
  /** Status line segment identifiers */
@@ -4554,6 +4554,44 @@ export const SETTINGS_SCHEMA = {
4554
4554
  },
4555
4555
  },
4556
4556
 
4557
+ "providers.streamFirstEventTimeoutSeconds": {
4558
+ type: "number",
4559
+ default: -1,
4560
+ ui: {
4561
+ tab: "providers",
4562
+ group: "Timeouts",
4563
+ label: "Stream First Event Timeout",
4564
+ description:
4565
+ "Seconds to wait for the first model stream event; -1 uses provider/env defaults, 0 disables the watchdog",
4566
+ options: [
4567
+ { value: "-1", label: "Auto", description: "Use provider defaults and PI_* timeout env vars" },
4568
+ { value: "0", label: "Off", description: "Disable first-event timeout" },
4569
+ { value: "300", label: "5 minutes" },
4570
+ { value: "600", label: "10 minutes" },
4571
+ { value: "1800", label: "30 minutes" },
4572
+ ],
4573
+ },
4574
+ },
4575
+
4576
+ "providers.streamIdleTimeoutSeconds": {
4577
+ type: "number",
4578
+ default: -1,
4579
+ ui: {
4580
+ tab: "providers",
4581
+ group: "Timeouts",
4582
+ label: "Stream Idle Timeout",
4583
+ description:
4584
+ "Seconds a model stream may stay silent between events; -1 uses provider/env defaults, 0 disables the watchdog",
4585
+ options: [
4586
+ { value: "-1", label: "Auto", description: "Use provider defaults and PI_* timeout env vars" },
4587
+ { value: "0", label: "Off", description: "Disable idle timeout" },
4588
+ { value: "300", label: "5 minutes" },
4589
+ { value: "600", label: "10 minutes" },
4590
+ { value: "1800", label: "30 minutes" },
4591
+ ],
4592
+ },
4593
+ },
4594
+
4557
4595
  "providers.openrouterVariant": {
4558
4596
  type: "enum",
4559
4597
  values: ["default", "nitro", "floor", "online", "exacto"] as const,
@@ -0,0 +1,32 @@
1
+ ---
2
+ description: "Prefer runtime.AddCleanup over runtime.SetFinalizer for new code (Go 1.24)"
3
+ condition: 'runtime\.SetFinalizer'
4
+ scope: "tool:edit(*.go), tool:write(*.go)"
5
+ ---
6
+
7
+ Go 1.24 added `runtime.AddCleanup`, a finalization mechanism that is more flexible and less error-prone than `runtime.SetFinalizer`. The release notes state plainly: **new code should prefer `AddCleanup` over `SetFinalizer`.**
8
+
9
+ ## Why AddCleanup wins
10
+
11
+ - Multiple cleanups may attach to one object; `SetFinalizer` allows only one.
12
+ - Cleanups may attach to interior pointers.
13
+ - Objects that form a reference cycle still get cleaned up — finalizers leak them.
14
+ - A cleanup does not resurrect its object or delay freeing it (and what it points to) by an extra GC cycle.
15
+
16
+ ## Migration
17
+
18
+ ```go
19
+ // Before
20
+ runtime.SetFinalizer(obj, func(o *T) { o.release() })
21
+
22
+ // After — the cleanup func receives a value you supply, NOT the object,
23
+ // so it cannot accidentally keep the object alive.
24
+ runtime.AddCleanup(obj, func(h handle) { h.release() }, obj.handle)
25
+ ```
26
+
27
+ The cleanup argument must not reference `obj` itself (that would keep it reachable forever). Capture only the data the cleanup needs — a file descriptor, handle, or pointer that is independent of `obj`.
28
+
29
+ ## Keep SetFinalizer only when
30
+
31
+ - The module targets a Go release older than 1.24.
32
+ - You depend on finalizer-specific behavior (e.g. object resurrection) that `AddCleanup` deliberately does not provide.
@@ -0,0 +1,36 @@
1
+ ---
2
+ description: "Use for b.Loop() in benchmarks instead of the for i := 0; i < b.N; i++ loop (Go 1.24)"
3
+ interruptMode: never
4
+ scope: "tool:edit(*_test.go), tool:write(*_test.go)"
5
+ astCondition:
6
+ - "func $F($B *testing.B) { $$$PRE for $I := 0; $I < $B.N; $I++ { $$$BODY } $$$POST }"
7
+ ---
8
+
9
+ Go 1.24 added `testing.B.Loop`. Write `for b.Loop() { ... }` instead of looping over `b.N`.
10
+
11
+ ## Why
12
+
13
+ - Setup and teardown outside the loop run exactly once per `-count`, not once per `b.N` re-estimation, so expensive fixtures are no longer timed or repeated.
14
+ - The compiler keeps the loop's parameters and results alive, so it can't optimize away the body you are trying to measure — a classic `b.N` benchmarking footgun.
15
+
16
+ ## Avoid
17
+
18
+ ```go
19
+ func BenchmarkEncode(b *testing.B) {
20
+ for i := 0; i < b.N; i++ {
21
+ Encode(input)
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## Use
27
+
28
+ ```go
29
+ func BenchmarkEncode(b *testing.B) {
30
+ for b.Loop() {
31
+ Encode(input)
32
+ }
33
+ }
34
+ ```
35
+
36
+ Requires Go 1.24+. If the module targets an older Go, keep the `b.N` loop.
@@ -0,0 +1,39 @@
1
+ ---
2
+ description: "Use the standard library slices and maps packages instead of golang.org/x/exp/{slices,maps}"
3
+ condition:
4
+ - '"golang.org/x/exp/slices"'
5
+ - '"golang.org/x/exp/maps"'
6
+ scope: "tool:edit(*.go), tool:write(*.go)"
7
+ ---
8
+
9
+ `golang.org/x/exp/slices` and `golang.org/x/exp/maps` were promoted into the standard library as `slices` and `maps` in Go 1.21. Import the stdlib packages in new code instead of the experimental ones.
10
+
11
+ ## Migration
12
+
13
+ ```go
14
+ // Before
15
+ import (
16
+ "golang.org/x/exp/slices"
17
+ "golang.org/x/exp/maps"
18
+ )
19
+
20
+ // After
21
+ import (
22
+ "slices"
23
+ "maps"
24
+ )
25
+ ```
26
+
27
+ Most call sites are unchanged: `slices.Sort`, `slices.Contains`, `slices.Index`, `slices.Equal`, `maps.Clone`, etc.
28
+
29
+ ## Watch the signature differences
30
+
31
+ The promoted APIs were tweaked, so a blind path swap can break the build:
32
+
33
+ - `x/exp/maps.Keys(m)` / `Values(m)` returned a slice; the stdlib `maps.Keys(m)` / `maps.Values(m)` return an **iterator** (`iter.Seq`). Use `slices.Collect(maps.Keys(m))` to recover a slice, or range over the iterator.
34
+ - `slices.SortFunc` takes a comparison returning `int` (cmp-style), matching the stdlib signature.
35
+
36
+ ## Keep x/exp when
37
+
38
+ - The module's `go` directive is below 1.21 (stdlib `slices`/`maps` don't exist yet).
39
+ - You need an `x/exp` helper that was not promoted (e.g. parts of `x/exp/constraints` still live outside the stdlib).
@@ -0,0 +1,36 @@
1
+ ---
2
+ description: "Use io and os instead of the deprecated io/ioutil package"
3
+ condition: '"io/ioutil"'
4
+ scope: "tool:edit(*.go), tool:write(*.go)"
5
+ ---
6
+
7
+ `io/ioutil` has been deprecated since Go 1.16. Every function moved to `io` or `os` with the same behavior. Do not import it in new code.
8
+
9
+ ## Mapping
10
+
11
+ | io/ioutil | Replacement |
12
+ | --- | --- |
13
+ | `ioutil.ReadAll` | `io.ReadAll` |
14
+ | `ioutil.ReadFile` | `os.ReadFile` |
15
+ | `ioutil.WriteFile` | `os.WriteFile` |
16
+ | `ioutil.ReadDir` | `os.ReadDir` (returns `[]os.DirEntry`, not `[]os.FileInfo`) |
17
+ | `ioutil.TempFile` | `os.CreateTemp` |
18
+ | `ioutil.TempDir` | `os.MkdirTemp` |
19
+ | `ioutil.NopCloser` | `io.NopCloser` |
20
+ | `ioutil.Discard` | `io.Discard` |
21
+
22
+ ## Migration
23
+
24
+ ```go
25
+ // Before
26
+ import "io/ioutil"
27
+ data, err := ioutil.ReadFile(path)
28
+ _ = ioutil.WriteFile(out, data, 0o644)
29
+
30
+ // After
31
+ import "os"
32
+ data, err := os.ReadFile(path)
33
+ _ = os.WriteFile(out, data, 0o644)
34
+ ```
35
+
36
+ `os.ReadDir` returns `[]os.DirEntry` rather than `[]os.FileInfo` — call `entry.Info()` if you need the old `FileInfo`. Everything else is a drop-in rename.
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: "Build network addresses with net.JoinHostPort, not fmt.Sprintf(\"%s:%d\", host, port) — the Sprintf form breaks on IPv6"
3
+ condition: 'fmt\.Sprintf\("%s:%d"'
4
+ scope: "tool:edit(*.go), tool:write(*.go)"
5
+ ---
6
+
7
+ Use `net.JoinHostPort(host, port)` to assemble a `host:port` address. `fmt.Sprintf("%s:%d", host, port)` produces invalid addresses for IPv6 hosts, which must be bracketed (`[::1]:80`). Go 1.25's `go vet` `hostport` analyzer flags exactly this pattern.
8
+
9
+ ## Why
10
+
11
+ - An IPv6 literal like `::1` has its own colons, so `fmt.Sprintf("%s:%d", "::1", 80)` yields `::1:80` — unparseable by `net.Dial`.
12
+ - `net.JoinHostPort` adds the brackets when the host contains a colon and leaves IPv4/hostnames untouched.
13
+
14
+ ## Avoid
15
+
16
+ ```go
17
+ addr := fmt.Sprintf("%s:%d", host, port)
18
+ conn, err := net.Dial("tcp", addr)
19
+ ```
20
+
21
+ ## Use
22
+
23
+ ```go
24
+ // port is a string here; convert an int with strconv.Itoa.
25
+ addr := net.JoinHostPort(host, strconv.Itoa(port))
26
+ conn, err := net.Dial("tcp", addr)
27
+ ```
28
+
29
+ `net.JoinHostPort` takes the port as a string. For an `int` port, wrap it in `strconv.Itoa`. The function is available in every supported Go version.
@@ -0,0 +1,44 @@
1
+ ---
2
+ description: "Use new(expr) for pointer-to-value helpers instead of `func ptr[T any](v T) *T { return &v }` (Go 1.26)"
3
+ interruptMode: never
4
+ scope: "tool:edit(*.go), tool:write(*.go)"
5
+ astCondition:
6
+ - "func $F($V $T) *$T { return &$V }"
7
+ - "func $F[$$$TP]($V $T) *$T { return &$V }"
8
+ ---
9
+
10
+ Go 1.26 lets `new` take an expression: `new(expr)` allocates, stores `expr`, and returns its `*T`. That removes the need for hand-written `Ptr`/`boolPtr`/`Int64`-style helpers and the `x := v; p := &x` two-step.
11
+
12
+ ## Why
13
+
14
+ - One builtin replaces a helper per type (`boolPtr`, `strPtr`, `int64Ptr`, …) and the generic `func Ptr[T any](v T) *T`.
15
+ - No extra function-call frame and no separate heap escape — the value is constructed directly in the allocation.
16
+ - The intent (`new(false)`) reads at the call site instead of hiding behind a helper name.
17
+
18
+ ## Avoid
19
+
20
+ ```go
21
+ // A helper that just takes a value and returns its address.
22
+ func boolPtr(v bool) *bool { return &v }
23
+ func strPtr(v string) *string { return &v }
24
+ func Ptr[T any](v T) *T { return &v }
25
+
26
+ cfg := Config{Enabled: boolPtr(true), Name: strPtr("svc")}
27
+ ```
28
+
29
+ ## Use
30
+
31
+ ```go
32
+ cfg := Config{Enabled: new(true), Name: new("svc")}
33
+
34
+ // Was: x := int64(300); p := &x
35
+ p := new(int64(300))
36
+ ```
37
+
38
+ `new(true)` / `new(false)` give you `*bool`; `new(expr)` works for any expression, including function results (`new(time.Now())`).
39
+
40
+ ## Notes
41
+
42
+ - Requires Go 1.26+. If the module's `go` directive is older, keep the helper or the temp-variable form until the toolchain is bumped.
43
+ - This is for helpers that *only* take a value and return its address. A function that does real work before taking an address is not in scope.
44
+ - `new(T)` (a bare type) is unchanged and still zero-initializes.
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: Prefer math/rand/v2 over the legacy math/rand package
3
+ condition: '"math/rand"'
4
+ scope: "tool:edit(*.go), tool:write(*.go)"
5
+ ---
6
+
7
+ Use `math/rand/v2` instead of the legacy `math/rand` package (stable since Go 1.22).
8
+
9
+ ## Why
10
+
11
+ - No global `Seed`: `math/rand`'s top-level functions read a process-global generator (auto-seeded since Go 1.20), so a fixed seed is global mutable state that's easy to misuse; `v2` drops the global `Seed` entirely.
12
+ - Cleaner, better-bounded API: `rand.IntN(n)` / generic `rand.N(n)` replace `rand.Intn(n)`, and `Shuffle`, `Perm`, `Float64` carry over with clearer names.
13
+ - Modern generators: `v2` exposes `PCG` and `ChaCha8` sources instead of the old default LCG.
14
+
15
+ ## Migration
16
+
17
+ ```go
18
+ // Before
19
+ import "math/rand"
20
+ n := rand.Intn(100)
21
+ f := rand.Float64()
22
+
23
+ // After
24
+ import "math/rand/v2"
25
+ n := rand.IntN(100)
26
+ f := rand.Float64()
27
+ ```
28
+
29
+ | math/rand | math/rand/v2 |
30
+ | --- | --- |
31
+ | `rand.Intn(n)` | `rand.IntN(n)` |
32
+ | `rand.Int63n(n)` | `rand.Int64N(n)` |
33
+ | `rand.Intn`/`Int31n` on a `*Rand` | `(*Rand).IntN` / `Int32N` |
34
+ | `rand.Seed(x)` | drop it — `v2` has no global seed |
35
+ | explicit `rand.New(rand.NewSource(seed))` | `rand.New(rand.NewPCG(s1, s2))` or `rand.NewChaCha8(seed)` |
36
+
37
+ ## Keep math/rand only when
38
+
39
+ - You need a reproducible stream from a fixed seed via the classic `NewSource`/`Seed` API that a caller already depends on.
40
+ - Reach for `crypto/rand` instead when the values are security-sensitive — neither `math/rand` variant is cryptographically secure.
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: "Use for i := range n instead of the C-style for i := 0; i < n; i++ loop (Go 1.22)"
3
+ interruptMode: never
4
+ scope: "tool:edit(*.go), tool:write(*.go)"
5
+ astCondition:
6
+ - "for $I := 0; $I < $N; $I++ { $$$BODY }"
7
+ ---
8
+
9
+ Go 1.22 lets `for` range over an integer. A plain counting loop from `0` to `n` with step `1` reads better as `for i := range n` (or `for range n` when the index is unused).
10
+
11
+ ## Avoid
12
+
13
+ ```go
14
+ for i := 0; i < n; i++ {
15
+ use(i)
16
+ }
17
+
18
+ for i := 0; i < len(s); i++ {
19
+ use(s[i])
20
+ }
21
+ ```
22
+
23
+ ## Use
24
+
25
+ ```go
26
+ for i := range n {
27
+ use(i)
28
+ }
29
+
30
+ // Ranging the slice directly is usually clearer than indexing.
31
+ for i := range s {
32
+ use(s[i])
33
+ }
34
+
35
+ // Index unused → drop it entirely.
36
+ for range n {
37
+ tick()
38
+ }
39
+ ```
40
+
41
+ ## When it does not apply
42
+
43
+ - Non-zero start, step other than `++`, or a descending loop (`for i := n - 1; i >= 0; i--`) — keep the explicit form.
44
+ - The body reassigns the loop variable or depends on `i` surviving past the loop.
45
+ - Requires Go 1.22+. If the module's `go` directive is older, keep the classic loop.
@@ -8,6 +8,14 @@
8
8
  * Registered by the lowest-priority `builtin-defaults` rule provider so any
9
9
  * user/project/tool rule with the same name overrides the bundled copy.
10
10
  */
11
+ import goAddCleanup from "./go-add-cleanup.md" with { type: "text" };
12
+ import goBenchLoop from "./go-bench-loop.md" with { type: "text" };
13
+ import goExpPromoted from "./go-exp-promoted.md" with { type: "text" };
14
+ import goIoutil from "./go-ioutil.md" with { type: "text" };
15
+ import goJoinHostport from "./go-join-hostport.md" with { type: "text" };
16
+ import goNewExpr from "./go-new-expr.md" with { type: "text" };
17
+ import goRandV2 from "./go-rand-v2.md" with { type: "text" };
18
+ import goRangeInt from "./go-range-int.md" with { type: "text" };
11
19
  import rsBoxLeak from "./rs-box-leak.md" with { type: "text" };
12
20
  import rsFuturePrelude from "./rs-future-prelude.md" with { type: "text" };
13
21
  import rsLazylock from "./rs-lazylock.md" with { type: "text" };
@@ -35,6 +43,14 @@ export interface BuiltinRuleSource {
35
43
 
36
44
  /** All bundled default rules, ordered by name. */
37
45
  export const BUILTIN_RULE_SOURCES: readonly BuiltinRuleSource[] = [
46
+ { name: "go-add-cleanup", content: goAddCleanup },
47
+ { name: "go-bench-loop", content: goBenchLoop },
48
+ { name: "go-exp-promoted", content: goExpPromoted },
49
+ { name: "go-ioutil", content: goIoutil },
50
+ { name: "go-join-hostport", content: goJoinHostport },
51
+ { name: "go-new-expr", content: goNewExpr },
52
+ { name: "go-rand-v2", content: goRandV2 },
53
+ { name: "go-range-int", content: goRangeInt },
38
54
  { name: "rs-box-leak", content: rsBoxLeak },
39
55
  { name: "rs-future-prelude", content: rsFuturePrelude },
40
56
  { name: "rs-lazylock", content: rsLazylock },
@@ -28,6 +28,7 @@ import { invalidateFsScanAfterWrite } from "../../tools/fs-cache-invalidation";
28
28
  import { isInternalUrlPath } from "../../tools/path-utils";
29
29
  import { enforcePlanModeWrite, resolvePlanPath, targetsLocalSandbox } from "../../tools/plan-mode-guard";
30
30
  import { canonicalSnapshotKey } from "../file-snapshot-store";
31
+ import { isNotebookPath } from "../notebook";
31
32
  import { readEditFileText, serializeEditFileText } from "../read-file";
32
33
  import type { LspBatchRequest } from "../renderer";
33
34
 
@@ -123,6 +124,17 @@ export class HashlineFilesystem extends Filesystem {
123
124
  return content;
124
125
  }
125
126
 
127
+ async readBinary(relativePath: string): Promise<Uint8Array | undefined> {
128
+ const absolutePath = this.resolveAbsolute(relativePath);
129
+ if (isNotebookPath(absolutePath)) return undefined;
130
+ try {
131
+ return await fs.readFile(absolutePath);
132
+ } catch (error) {
133
+ if (isEnoent(error)) throw new NotFoundError(relativePath, error);
134
+ throw error;
135
+ }
136
+ }
137
+
126
138
  async preflightWrite(relativePath: string, options?: PreflightWriteOptions): Promise<void> {
127
139
  const fileOp = options?.fileOp;
128
140
  if (fileOp?.kind === "rem") {