@oh-my-pi/pi-coding-agent 16.2.6 → 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.
Files changed (78) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/cli.js +2781 -2660
  3. package/dist/types/cli/bench-cli.d.ts +3 -3
  4. package/dist/types/commands/bench.d.ts +1 -1
  5. package/dist/types/config/service-tier.d.ts +39 -24
  6. package/dist/types/config/settings-schema.d.ts +92 -36
  7. package/dist/types/edit/hashline/filesystem.d.ts +1 -0
  8. package/dist/types/mcp/config-writer.d.ts +48 -0
  9. package/dist/types/mcp/types.d.ts +3 -0
  10. package/dist/types/session/agent-session.d.ts +33 -13
  11. package/dist/types/session/messages.d.ts +1 -1
  12. package/dist/types/session/session-context.d.ts +2 -7
  13. package/dist/types/session/session-entries.d.ts +2 -2
  14. package/dist/types/session/session-manager.d.ts +2 -2
  15. package/dist/types/session/settings-stream-fn.d.ts +2 -2
  16. package/dist/types/system-prompt.test.d.ts +1 -0
  17. package/dist/types/task/executor.d.ts +6 -6
  18. package/dist/types/task/types.d.ts +6 -0
  19. package/dist/types/task/worktree.d.ts +32 -6
  20. package/dist/types/tiny/title-client.d.ts +5 -1
  21. package/dist/types/tools/index.d.ts +3 -3
  22. package/dist/types/tools/output-schema-validator.d.ts +10 -0
  23. package/dist/types/utils/git.d.ts +17 -0
  24. package/package.json +12 -12
  25. package/src/cli/bench-cli.ts +19 -12
  26. package/src/cli/tiny-models-cli.ts +18 -4
  27. package/src/commands/bench.ts +3 -3
  28. package/src/config/mcp-schema.json +10 -1
  29. package/src/config/service-tier.ts +85 -56
  30. package/src/config/settings-schema.ts +81 -37
  31. package/src/config/settings.ts +47 -0
  32. package/src/discovery/builtin-rules/go-add-cleanup.md +32 -0
  33. package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
  34. package/src/discovery/builtin-rules/go-exp-promoted.md +39 -0
  35. package/src/discovery/builtin-rules/go-ioutil.md +36 -0
  36. package/src/discovery/builtin-rules/go-join-hostport.md +29 -0
  37. package/src/discovery/builtin-rules/go-new-expr.md +44 -0
  38. package/src/discovery/builtin-rules/go-rand-v2.md +40 -0
  39. package/src/discovery/builtin-rules/go-range-int.md +45 -0
  40. package/src/discovery/builtin-rules/index.ts +16 -0
  41. package/src/edit/hashline/filesystem.ts +12 -0
  42. package/src/eval/agent-bridge.ts +4 -2
  43. package/src/internal-urls/docs-index.generated.txt +1 -1
  44. package/src/main.ts +1 -1
  45. package/src/mcp/config-writer.ts +121 -0
  46. package/src/mcp/config.ts +10 -6
  47. package/src/mcp/types.ts +3 -0
  48. package/src/modes/components/extensions/extension-dashboard.ts +46 -0
  49. package/src/modes/components/extensions/state-manager.ts +24 -3
  50. package/src/modes/controllers/event-controller.ts +7 -0
  51. package/src/modes/controllers/tool-args-reveal.ts +1 -1
  52. package/src/modes/utils/transcript-render-helpers.ts +2 -2
  53. package/src/prompts/tools/bash.md +1 -4
  54. package/src/sdk.ts +12 -11
  55. package/src/session/agent-session.ts +294 -82
  56. package/src/session/messages.ts +1 -1
  57. package/src/session/session-context.ts +17 -6
  58. package/src/session/session-entries.ts +2 -2
  59. package/src/session/session-manager.ts +9 -2
  60. package/src/session/settings-stream-fn.ts +12 -2
  61. package/src/slash-commands/builtin-registry.ts +2 -10
  62. package/src/system-prompt.test.ts +158 -0
  63. package/src/system-prompt.ts +69 -26
  64. package/src/task/executor.ts +23 -16
  65. package/src/task/index.ts +7 -5
  66. package/src/task/isolation-runner.ts +15 -1
  67. package/src/task/types.ts +6 -0
  68. package/src/task/worktree.ts +219 -38
  69. package/src/tiny/title-client.ts +19 -13
  70. package/src/tools/grep.ts +19 -1
  71. package/src/tools/index.ts +3 -3
  72. package/src/tools/irc.ts +9 -3
  73. package/src/tools/output-schema-validator.ts +38 -0
  74. package/src/tools/read.ts +28 -28
  75. package/src/tools/yield.ts +52 -15
  76. package/src/utils/file-mentions.ts +10 -1
  77. package/src/utils/git.ts +38 -0
  78. package/src/web/search/providers/duckduckgo.ts +17 -3
@@ -36,10 +36,14 @@ import {
36
36
  import { EDIT_MODES } from "../utils/edit-mode";
37
37
  import { SEARCH_PROVIDER_OPTIONS, SEARCH_PROVIDER_PREFERENCES, type SearchProviderId } from "../web/search/types";
38
38
  import {
39
+ SERVICE_TIER_ANTHROPIC_OPTIONS,
40
+ SERVICE_TIER_ANTHROPIC_VALUES,
41
+ SERVICE_TIER_GOOGLE_OPTIONS,
42
+ SERVICE_TIER_GOOGLE_VALUES,
39
43
  SERVICE_TIER_INHERIT_OPTIONS,
40
44
  SERVICE_TIER_INHERIT_SETTING_VALUES,
41
- SERVICE_TIER_OPTIONS,
42
- SERVICE_TIER_SETTING_VALUES,
45
+ SERVICE_TIER_OPENAI_OPTIONS,
46
+ SERVICE_TIER_OPENAI_VALUES,
43
47
  } from "./service-tier";
44
48
 
45
49
  /** Unified settings schema - single source of truth for all settings.
@@ -140,7 +144,7 @@ export const TAB_GROUPS: Record<SettingTab, readonly string[]> = {
140
144
  "Developer",
141
145
  ],
142
146
  tasks: ["Modes", "Subagents", "Isolation", "Commands & Skills"],
143
- providers: ["Services", "Fireworks", "Tiny Model", "Protocol", "Privacy"],
147
+ providers: ["Services", "Fireworks", "Tiny Model", "Protocol", "Timeouts", "Privacy"],
144
148
  };
145
149
 
146
150
  /** Status line segment identifiers */
@@ -1214,72 +1218,74 @@ export const SETTINGS_SCHEMA = {
1214
1218
  },
1215
1219
  },
1216
1220
 
1217
- serviceTier: {
1221
+ "tier.openai": {
1218
1222
  type: "enum",
1219
- values: SERVICE_TIER_SETTING_VALUES,
1223
+ values: SERVICE_TIER_OPENAI_VALUES,
1220
1224
  default: "none",
1221
1225
  ui: {
1222
1226
  tab: "model",
1223
1227
  group: "Sampling",
1224
- label: "Service Tier",
1228
+ label: "Service Tier — OpenAI",
1225
1229
  description:
1226
- 'Processing priority hint (none = omit). OpenAI accepts the tier values directly; Anthropic realizes `priority` as `speed: "fast"` on supported Opus models. Scoped values target one family.',
1227
- options: SERVICE_TIER_OPTIONS,
1230
+ "Processing tier for OpenAI / OpenAI-Codex requests, and OpenAI-family models routed via OpenRouter (none = omit). Sent as `service_tier`.",
1231
+ options: SERVICE_TIER_OPENAI_OPTIONS,
1228
1232
  },
1229
1233
  },
1230
1234
 
1231
- serviceTierSubagent: {
1235
+ "tier.anthropic": {
1232
1236
  type: "enum",
1233
- values: SERVICE_TIER_INHERIT_SETTING_VALUES,
1234
- default: "inherit",
1237
+ values: SERVICE_TIER_ANTHROPIC_VALUES,
1238
+ default: "none",
1235
1239
  ui: {
1236
1240
  tab: "model",
1237
1241
  group: "Sampling",
1238
- label: "Service Tier - Subagent",
1242
+ label: "Service Tier Anthropic",
1239
1243
  description:
1240
- "Service Tier for spawned task/eval subagents. Inherit = match the main agent's live tier (tracks /fast); pick a value to scope subagents independently.",
1241
- options: SERVICE_TIER_INHERIT_OPTIONS,
1244
+ 'Processing tier for Claude requests. `priority` realizes fast mode (`speed: "fast"`) on supported direct Anthropic models; ignored on Bedrock/Vertex Claude and via OpenRouter.',
1245
+ options: SERVICE_TIER_ANTHROPIC_OPTIONS,
1242
1246
  },
1243
1247
  },
1244
1248
 
1245
- serviceTierAdvisor: {
1249
+ "tier.google": {
1246
1250
  type: "enum",
1247
- values: SERVICE_TIER_INHERIT_SETTING_VALUES,
1251
+ values: SERVICE_TIER_GOOGLE_VALUES,
1248
1252
  default: "none",
1249
1253
  ui: {
1250
1254
  tab: "model",
1251
1255
  group: "Sampling",
1252
- label: "Service Tier - Advisor",
1256
+ label: "Service Tier Google",
1257
+ description:
1258
+ "Processing tier for Gemini (Google AI Studio + Vertex) requests, and Google-family models routed via OpenRouter (none = omit). Sent as the top-level `serviceTier` field.",
1259
+ options: SERVICE_TIER_GOOGLE_OPTIONS,
1260
+ },
1261
+ },
1262
+
1263
+ "tier.subagent": {
1264
+ type: "enum",
1265
+ values: SERVICE_TIER_INHERIT_SETTING_VALUES,
1266
+ default: "inherit",
1267
+ ui: {
1268
+ tab: "model",
1269
+ group: "Sampling",
1270
+ label: "Service Tier — Subagent",
1253
1271
  description:
1254
- "Service Tier for the advisor model. None = standard processing; Inherit = match the main agent's live tier; pick a value (e.g. Priority) to run the advisor on a faster serving path.",
1272
+ "Service Tier for spawned task/eval subagents. Inherit = match the main agent's live per-family tiers (tracks /fast); pick a value to apply it to whichever family the subagent's model belongs to.",
1255
1273
  options: SERVICE_TIER_INHERIT_OPTIONS,
1256
- condition: "advisorEnabled",
1257
1274
  },
1258
1275
  },
1259
1276
 
1260
- fastModeScope: {
1277
+ "tier.advisor": {
1261
1278
  type: "enum",
1262
- values: ["both", "openai", "claude"] as const,
1263
- default: "both",
1279
+ values: SERVICE_TIER_INHERIT_SETTING_VALUES,
1280
+ default: "none",
1264
1281
  ui: {
1265
1282
  tab: "model",
1266
1283
  group: "Sampling",
1267
- label: "Fast Mode Scope",
1284
+ label: "Service Tier — Advisor",
1268
1285
  description:
1269
- 'Which providers `/fast on` (and the fast-mode toggle) target. "both" = priority on every supported provider; "openai"/"claude" scope it to one family (mirrors serviceTier openai-only/claude-only).',
1270
- options: [
1271
- { value: "both", label: "Both", description: "Priority on every supported provider" },
1272
- {
1273
- value: "openai",
1274
- label: "OpenAI only",
1275
- description: "Priority on OpenAI/OpenAI-Codex requests; ignored elsewhere",
1276
- },
1277
- {
1278
- value: "claude",
1279
- label: "Claude only",
1280
- description: "Anthropic fast mode on direct Claude requests; ignored elsewhere",
1281
- },
1282
- ],
1286
+ "Service Tier for the advisor model. None = standard processing; Inherit = match the main agent's live per-family tiers; pick a value to apply it to the advisor model's family.",
1287
+ options: SERVICE_TIER_INHERIT_OPTIONS,
1288
+ condition: "advisorEnabled",
1283
1289
  },
1284
1290
  },
1285
1291
 
@@ -4548,6 +4554,44 @@ export const SETTINGS_SCHEMA = {
4548
4554
  },
4549
4555
  },
4550
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
+
4551
4595
  "providers.openrouterVariant": {
4552
4596
  type: "enum",
4553
4597
  values: ["default", "nitro", "floor", "online", "exacto"] as const,
@@ -1148,6 +1148,53 @@ export class Settings {
1148
1148
  // the incoherent "hashline edits without addressable anchors" state.
1149
1149
  delete raw.readHashLines;
1150
1150
 
1151
+ // serviceTier (single enum with scoped openai-only/claude-only sentinels)
1152
+ // → per-family tier.openai/tier.anthropic/tier.google; serviceTierSubagent
1153
+ // → tier.subagent; serviceTierAdvisor → tier.advisor. `fastModeScope` is
1154
+ // dropped — per-family scoping is now expressed by the three tier settings.
1155
+ const tierObj = isRecord(raw.tier) ? raw.tier : {};
1156
+ let tierTouched = false;
1157
+ const setTier = (family: string, value: unknown): void => {
1158
+ if (value !== undefined && !(family in tierObj)) {
1159
+ tierObj[family] = value;
1160
+ tierTouched = true;
1161
+ }
1162
+ };
1163
+ if (typeof raw.serviceTier === "string") {
1164
+ switch (raw.serviceTier) {
1165
+ case "priority":
1166
+ setTier("openai", "priority");
1167
+ setTier("anthropic", "priority");
1168
+ setTier("google", "priority");
1169
+ break;
1170
+ case "openai-only":
1171
+ setTier("openai", "priority");
1172
+ break;
1173
+ case "claude-only":
1174
+ setTier("anthropic", "priority");
1175
+ break;
1176
+ case "auto":
1177
+ case "default":
1178
+ case "flex":
1179
+ case "scale":
1180
+ setTier("openai", raw.serviceTier);
1181
+ break;
1182
+ }
1183
+ delete raw.serviceTier;
1184
+ }
1185
+ const mapInheritTier = (value: unknown): unknown =>
1186
+ value === "openai-only" || value === "claude-only" ? "priority" : value;
1187
+ if ("serviceTierSubagent" in raw) {
1188
+ setTier("subagent", mapInheritTier(raw.serviceTierSubagent));
1189
+ delete raw.serviceTierSubagent;
1190
+ }
1191
+ if ("serviceTierAdvisor" in raw) {
1192
+ setTier("advisor", mapInheritTier(raw.serviceTierAdvisor));
1193
+ delete raw.serviceTierAdvisor;
1194
+ }
1195
+ if (tierTouched) raw.tier = tierObj;
1196
+ delete raw.fastModeScope;
1197
+
1151
1198
  return raw;
1152
1199
  }
1153
1200
 
@@ -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") {
@@ -405,8 +405,10 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
405
405
  parentMnemopiSessionState: options.session.getMnemopiSessionState?.(),
406
406
  parentTelemetry: options.session.getTelemetry?.(),
407
407
  parentAgentId: options.session.getAgentId?.() ?? MAIN_AGENT_ID,
408
- // Live source of truth for `serviceTierSubagent: inherit` (null = explicit none).
409
- parentServiceTier: options.session.getServiceTier ? (options.session.getServiceTier() ?? null) : undefined,
408
+ // Live source of truth for `tier.subagent: inherit` (null = explicit none).
409
+ parentServiceTier: options.session.getServiceTierByFamily
410
+ ? (options.session.getServiceTierByFamily() ?? null)
411
+ : undefined,
410
412
  // Deliberately omit parentEvalSessionId: the parent's Python kernel is
411
413
  // blocked on this bridge call, so sharing the eval session would deadlock
412
414
  // (subagent queues behind the parent's in-flight execution, parent waits