@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.
- package/CHANGELOG.md +23 -0
- package/dist/cli.js +2763 -2647
- package/dist/types/config/settings-schema.d.ts +56 -0
- package/dist/types/edit/hashline/filesystem.d.ts +1 -0
- package/dist/types/session/session-context.d.ts +0 -5
- package/dist/types/session/settings-stream-fn.d.ts +2 -2
- package/dist/types/tools/output-schema-validator.d.ts +10 -0
- package/package.json +12 -12
- package/src/config/settings-schema.ts +39 -1
- 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/internal-urls/docs-index.generated.txt +1 -1
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/prompts/tools/bash.md +1 -4
- package/src/session/agent-session.ts +108 -6
- package/src/session/session-context.ts +13 -2
- package/src/session/settings-stream-fn.ts +12 -2
- package/src/tools/grep.ts +19 -1
- package/src/tools/output-schema-validator.ts +38 -0
- package/src/tools/yield.ts +52 -15
|
@@ -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
|
-
*
|
|
6
|
-
*
|
|
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.
|
|
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.
|
|
59
|
-
"@oh-my-pi/omp-stats": "16.2.
|
|
60
|
-
"@oh-my-pi/pi-agent-core": "16.2.
|
|
61
|
-
"@oh-my-pi/pi-ai": "16.2.
|
|
62
|
-
"@oh-my-pi/pi-catalog": "16.2.
|
|
63
|
-
"@oh-my-pi/pi-mnemopi": "16.2.
|
|
64
|
-
"@oh-my-pi/pi-natives": "16.2.
|
|
65
|
-
"@oh-my-pi/pi-tui": "16.2.
|
|
66
|
-
"@oh-my-pi/pi-utils": "16.2.
|
|
67
|
-
"@oh-my-pi/pi-wire": "16.2.
|
|
68
|
-
"@oh-my-pi/snapcompact": "16.2.
|
|
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") {
|