@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.
- package/CHANGELOG.md +46 -0
- package/dist/cli.js +2781 -2660
- package/dist/types/cli/bench-cli.d.ts +3 -3
- package/dist/types/commands/bench.d.ts +1 -1
- package/dist/types/config/service-tier.d.ts +39 -24
- package/dist/types/config/settings-schema.d.ts +92 -36
- package/dist/types/edit/hashline/filesystem.d.ts +1 -0
- package/dist/types/mcp/config-writer.d.ts +48 -0
- package/dist/types/mcp/types.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +33 -13
- package/dist/types/session/messages.d.ts +1 -1
- package/dist/types/session/session-context.d.ts +2 -7
- package/dist/types/session/session-entries.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +2 -2
- package/dist/types/session/settings-stream-fn.d.ts +2 -2
- package/dist/types/system-prompt.test.d.ts +1 -0
- package/dist/types/task/executor.d.ts +6 -6
- package/dist/types/task/types.d.ts +6 -0
- package/dist/types/task/worktree.d.ts +32 -6
- package/dist/types/tiny/title-client.d.ts +5 -1
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tools/output-schema-validator.d.ts +10 -0
- package/dist/types/utils/git.d.ts +17 -0
- package/package.json +12 -12
- package/src/cli/bench-cli.ts +19 -12
- package/src/cli/tiny-models-cli.ts +18 -4
- package/src/commands/bench.ts +3 -3
- package/src/config/mcp-schema.json +10 -1
- package/src/config/service-tier.ts +85 -56
- package/src/config/settings-schema.ts +81 -37
- package/src/config/settings.ts +47 -0
- package/src/discovery/builtin-rules/go-add-cleanup.md +32 -0
- package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
- package/src/discovery/builtin-rules/go-exp-promoted.md +39 -0
- package/src/discovery/builtin-rules/go-ioutil.md +36 -0
- package/src/discovery/builtin-rules/go-join-hostport.md +29 -0
- package/src/discovery/builtin-rules/go-new-expr.md +44 -0
- package/src/discovery/builtin-rules/go-rand-v2.md +40 -0
- package/src/discovery/builtin-rules/go-range-int.md +45 -0
- package/src/discovery/builtin-rules/index.ts +16 -0
- package/src/edit/hashline/filesystem.ts +12 -0
- package/src/eval/agent-bridge.ts +4 -2
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/main.ts +1 -1
- package/src/mcp/config-writer.ts +121 -0
- package/src/mcp/config.ts +10 -6
- package/src/mcp/types.ts +3 -0
- package/src/modes/components/extensions/extension-dashboard.ts +46 -0
- package/src/modes/components/extensions/state-manager.ts +24 -3
- package/src/modes/controllers/event-controller.ts +7 -0
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/modes/utils/transcript-render-helpers.ts +2 -2
- package/src/prompts/tools/bash.md +1 -4
- package/src/sdk.ts +12 -11
- package/src/session/agent-session.ts +294 -82
- package/src/session/messages.ts +1 -1
- package/src/session/session-context.ts +17 -6
- package/src/session/session-entries.ts +2 -2
- package/src/session/session-manager.ts +9 -2
- package/src/session/settings-stream-fn.ts +12 -2
- package/src/slash-commands/builtin-registry.ts +2 -10
- package/src/system-prompt.test.ts +158 -0
- package/src/system-prompt.ts +69 -26
- package/src/task/executor.ts +23 -16
- package/src/task/index.ts +7 -5
- package/src/task/isolation-runner.ts +15 -1
- package/src/task/types.ts +6 -0
- package/src/task/worktree.ts +219 -38
- package/src/tiny/title-client.ts +19 -13
- package/src/tools/grep.ts +19 -1
- package/src/tools/index.ts +3 -3
- package/src/tools/irc.ts +9 -3
- package/src/tools/output-schema-validator.ts +38 -0
- package/src/tools/read.ts +28 -28
- package/src/tools/yield.ts +52 -15
- package/src/utils/file-mentions.ts +10 -1
- package/src/utils/git.ts +38 -0
- 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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
1221
|
+
"tier.openai": {
|
|
1218
1222
|
type: "enum",
|
|
1219
|
-
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
|
-
|
|
1227
|
-
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
|
-
|
|
1235
|
+
"tier.anthropic": {
|
|
1232
1236
|
type: "enum",
|
|
1233
|
-
values:
|
|
1234
|
-
default: "
|
|
1237
|
+
values: SERVICE_TIER_ANTHROPIC_VALUES,
|
|
1238
|
+
default: "none",
|
|
1235
1239
|
ui: {
|
|
1236
1240
|
tab: "model",
|
|
1237
1241
|
group: "Sampling",
|
|
1238
|
-
label: "Service Tier
|
|
1242
|
+
label: "Service Tier — Anthropic",
|
|
1239
1243
|
description:
|
|
1240
|
-
|
|
1241
|
-
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
|
-
|
|
1249
|
+
"tier.google": {
|
|
1246
1250
|
type: "enum",
|
|
1247
|
-
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
|
|
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
|
|
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
|
-
|
|
1277
|
+
"tier.advisor": {
|
|
1261
1278
|
type: "enum",
|
|
1262
|
-
values:
|
|
1263
|
-
default: "
|
|
1279
|
+
values: SERVICE_TIER_INHERIT_SETTING_VALUES,
|
|
1280
|
+
default: "none",
|
|
1264
1281
|
ui: {
|
|
1265
1282
|
tab: "model",
|
|
1266
1283
|
group: "Sampling",
|
|
1267
|
-
label: "
|
|
1284
|
+
label: "Service Tier — Advisor",
|
|
1268
1285
|
description:
|
|
1269
|
-
|
|
1270
|
-
options:
|
|
1271
|
-
|
|
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,
|
package/src/config/settings.ts
CHANGED
|
@@ -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") {
|
package/src/eval/agent-bridge.ts
CHANGED
|
@@ -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 `
|
|
409
|
-
parentServiceTier: options.session.
|
|
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
|