opencode-harness-kit 0.5.3 → 0.5.5
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 +207 -17
- package/bin/oc-harness.mjs +278 -45
- package/dist/config.d.ts +5 -0
- package/dist/denylist.d.ts +5 -1
- package/dist/guard.d.ts +17 -1
- package/dist/health.d.ts +69 -7
- package/dist/index.js +452 -68
- package/dist/profile.d.ts +32 -2
- package/dist/telemetry-store.d.ts +22 -0
- package/docs/EXIT-CODES.md +7 -0
- package/docs/KNOWN-LIMITS.md +18 -7
- package/eval/measure-prune.mjs +13 -4
- package/package.json +1 -1
- package/src/config.js +126 -3
- package/src/denylist.js +46 -2
- package/src/guard.ts +78 -7
- package/src/health.js +130 -5
- package/src/lane-usage.js +13 -6
- package/src/profile.js +187 -25
- package/src/prune.ts +58 -13
- package/src/router.ts +217 -17
- package/src/rules.js +329 -19
- package/src/sqlite.js +80 -0
- package/src/tasks.js +193 -40
- package/src/telemetry-store.ts +27 -4
- package/src/trust.js +182 -54
package/CHANGELOG.md
CHANGED
|
@@ -12,25 +12,194 @@ is not happening.
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
## [0.5.
|
|
15
|
+
## [0.5.5] — 2026-10-21
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
written for it shipped in 0.5.3.
|
|
17
|
+
Published. The tag is what cut it: `git tag v0.5.5 && git push origin main
|
|
18
|
+
v0.5.5`, and the publish job watches for exactly that.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
was not
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
`pham-canh/opencode-harness-kit`. The package's name is in the error, and
|
|
27
|
-
the field that was wrong is not.
|
|
20
|
+
A patch, and a large one: thirty-odd defects, nearly all of them the kind
|
|
21
|
+
this kit exists to catch - a command that reported success while the thing
|
|
22
|
+
it promised was not happening. Most of them were found by reading the code
|
|
23
|
+
with the question "what does this say when it did nothing?", and every one
|
|
24
|
+
of them now has a test that fails without the fix.
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
**The installer**
|
|
29
|
+
|
|
30
|
+
- **The install said "installed into" when it had linked nothing.** A link
|
|
31
|
+
that could not be created was a warning in a corner of the output, and the
|
|
32
|
+
command exited 0. A config directory the user could not write to produced a
|
|
33
|
+
green install and a kit that was not there. A failed link is now exit 1.
|
|
34
|
+
|
|
35
|
+
- **A file of the user's own was left alone, and the install said
|
|
36
|
+
"installed".** The installer refuses to destroy a file it did not put
|
|
37
|
+
there, which is right; the tick was not. A target that was left alone is
|
|
38
|
+
now named, in place of the tick, and `verify` reports the file that is not
|
|
39
|
+
the kit's.
|
|
40
|
+
|
|
41
|
+
- **Answering "yes" at the overwrite prompt replaced nothing.** The answer
|
|
42
|
+
was collected and thrown away: the linker refused to replace a real file
|
|
43
|
+
whatever the user had said. "yes" now replaces the file, and "no" leaves
|
|
44
|
+
it.
|
|
45
|
+
|
|
46
|
+
- **`verify` called a prompt installed when it was the user's own file.** The
|
|
47
|
+
check asked whether a file existed at the path, not whether it was the
|
|
48
|
+
kit's - so a user with their own `prompts/build.md` had a lane running on
|
|
49
|
+
their own instructions, with a green tick next to it. The same is now
|
|
50
|
+
checked for `GLOBAL.md` and for the kit's skills.
|
|
51
|
+
|
|
52
|
+
- **`verify` failed an agent file that mentioned its own config.** The
|
|
53
|
+
pattern read `opencode/opencode.jsonc` as a model that was not in the
|
|
54
|
+
profile.
|
|
55
|
+
|
|
56
|
+
- **`verify` called a relative instruction path resolved.** opencode ignores
|
|
57
|
+
relative instruction paths, and the check resolved it against the
|
|
58
|
+
directory the command happened to be run from - so the verdict was a
|
|
59
|
+
statement about the shell's working directory.
|
|
60
|
+
|
|
61
|
+
- **`uninstall` reported links it could not remove.** A directory the user
|
|
62
|
+
could not write to was reported as "removed prompts/ (10 link(s))" while
|
|
63
|
+
all ten links were still there.
|
|
64
|
+
|
|
65
|
+
- **`use` overwrote the config when the backup could not be written.** The
|
|
66
|
+
prompt promises "a dated backup is kept in backups/", and the backup was
|
|
67
|
+
best-effort: a failure was not even mentioned. The overwrite is refused
|
|
68
|
+
now, and the backups directory keeps ten configs instead of one.
|
|
69
|
+
|
|
70
|
+
- **One malformed line lost the whole prune log.** The parse was wrapped
|
|
71
|
+
around the loop, so a single bad record threw the file away and `cost`
|
|
72
|
+
said the pruner had never run.
|
|
73
|
+
|
|
74
|
+
**The guard**
|
|
75
|
+
|
|
76
|
+
- **The pipe-to-a-shell rule missed `curl … | sh; echo done`.** A `;` inside
|
|
77
|
+
a pipe stage left the shell's name glued to the punctuation, so the stage
|
|
78
|
+
was not read as a shell - and the guard was the only layer that had it.
|
|
79
|
+
|
|
80
|
+
- **`bash -c"rm -rf /"` was never inspected.** The glued form of the flag
|
|
81
|
+
defeated the payload extraction.
|
|
82
|
+
|
|
83
|
+
- **A wrapper named by its path defeated the prefix stripping.**
|
|
84
|
+
`/usr/bin/env rm -rf /` was allowed, while `env rm -rf /` was blocked.
|
|
85
|
+
|
|
86
|
+
- **`rm --recursive --force /` was allowed** by the rule that exists to
|
|
87
|
+
block it, which knew only the short flags.
|
|
88
|
+
|
|
89
|
+
- **A credential name with a prefix was not a credential name.**
|
|
90
|
+
`GITHUB_TOKEN=$(curl …)` was allowed, because the word boundary never
|
|
91
|
+
fires after an underscore.
|
|
92
|
+
|
|
93
|
+
- **`chmod 0777` was allowed** by the rule that blocks `chmod 777`.
|
|
94
|
+
|
|
95
|
+
- **A tool's own global flags stepped over the denylist.**
|
|
96
|
+
`gh -R owner/repo pr create` and `npm --prefix /tmp publish` were allowed.
|
|
97
|
+
|
|
98
|
+
- **A patch that deleted a file outside the project was allowed.** The
|
|
99
|
+
deletion names the file in the `---` header, and that header was not read,
|
|
100
|
+
so the path was never scope-checked.
|
|
101
|
+
|
|
102
|
+
**The merge and the profiles**
|
|
103
|
+
|
|
104
|
+
- **The user's own bash rules were deleted from their config.** The kit's
|
|
105
|
+
allowlist and denylist replaced the map wholesale, so every rule the user
|
|
106
|
+
had was gone after the next `oc-harness use`. The user's rules are merged
|
|
107
|
+
in, and the kit's deny rules still win.
|
|
108
|
+
|
|
109
|
+
- **A string-valued `instructions` was dropped** from the user's config.
|
|
110
|
+
|
|
111
|
+
- **The utility route was not counted towards the retention the file
|
|
112
|
+
claimed.** A may-train model could be written into the config as
|
|
113
|
+
`small_model` while the header said "zero-retention" - and the file says
|
|
114
|
+
"zero-retention" to a human who reads it.
|
|
115
|
+
|
|
116
|
+
- **`resolveEffort` could snap a request to `none`**, which is reasoning
|
|
117
|
+
off rather than a rung.
|
|
118
|
+
|
|
119
|
+
- **`validate()` accepted a limit that was not an integer** (a context of
|
|
120
|
+
`1.5` produced a negative prune threshold), an effort level that is not
|
|
121
|
+
on the scale, a retention of "unknown", a base model nobody had recorded,
|
|
122
|
+
and a retention claim that was safer than the models allowed. A claim that
|
|
123
|
+
is *worse* than reality is still allowed: cautious is not wrong.
|
|
124
|
+
|
|
125
|
+
- **A profile whose `id` did not match its file name was loaded anyway**,
|
|
126
|
+
and the config then told the user to edit a file that does not exist.
|
|
127
|
+
|
|
128
|
+
**The runtime**
|
|
129
|
+
|
|
130
|
+
- **The router never recorded a spent route.** It read the model from
|
|
131
|
+
`properties.modelID`, and no opencode event carries a model there - the
|
|
132
|
+
event that has one nests it under `properties.info`. So the failover was
|
|
133
|
+
dead code: the health file was never written, the warning never fired, and
|
|
134
|
+
the fallback chain was never walked.
|
|
135
|
+
|
|
136
|
+
- **Any "429" anywhere in an event spent a live route.** The classifier ran
|
|
137
|
+
over the whole serialised event, so a message whose token count was 429
|
|
138
|
+
was recorded as a failed route.
|
|
139
|
+
|
|
140
|
+
- **A spent route never came back.** The entry carries a timestamp and
|
|
141
|
+
nothing ever read it, so one transient rate limit demoted the model for
|
|
142
|
+
the life of the project directory. The window is the provider's day.
|
|
143
|
+
|
|
144
|
+
- **The pruner booked a saving on turns where the request got bigger.** The
|
|
145
|
+
stub's own metadata is part of the request that goes out, and the log
|
|
146
|
+
measured the text inside the part - so the number was a claim about
|
|
147
|
+
something other than the request. It is the request's size now, and a turn
|
|
148
|
+
that grew reports a negative saving.
|
|
149
|
+
|
|
150
|
+
- **A task marked `blocked` lost its block** when it had an `output:` field:
|
|
151
|
+
the status line was written inside the output block, and the parser folded
|
|
152
|
+
the output back into it. A task nested under another task's output was
|
|
153
|
+
DELETED by the rewrite.
|
|
154
|
+
|
|
155
|
+
- **The telemetry rotation destroyed the previous generation.** The writer
|
|
156
|
+
renamed the current file straight to `.1`, and a rename replaces the
|
|
157
|
+
destination - so the oldest history was gone after two rotations and the
|
|
158
|
+
third generation the reader looks for never existed.
|
|
159
|
+
|
|
160
|
+
- **The trusted-publishing setup could hang for hours.** The wait for the
|
|
161
|
+
browser answer had no timeout, so the documented window did not bound it.
|
|
162
|
+
The list of trusted publishers is also read in npm's own format now, which
|
|
163
|
+
is one JSON object per line rather than an array.
|
|
164
|
+
|
|
165
|
+
- **The two sqlite backends disagreed** about a query with two statements:
|
|
166
|
+
the command line ran both and the python fallback refused.
|
|
167
|
+
|
|
168
|
+
### Changed
|
|
169
|
+
|
|
170
|
+
- **The install's exit code follows `docs/EXIT-CODES.md`**: a target left
|
|
171
|
+
alone is exit 0 with the reason printed, and a step that failed is exit 1.
|
|
172
|
+
|
|
173
|
+
- **`savedChars` is the request's size delta**, not the text delta, and it
|
|
174
|
+
can be negative. `cost` says "characters ADDED" when it is negative,
|
|
175
|
+
because a number with the wrong sign on it is a lie in a different
|
|
176
|
+
direction.
|
|
177
|
+
|
|
178
|
+
## [0.5.4] — 2026-10-21
|
|
179
|
+
|
|
180
|
+
Published. The tag is what cut it: `git tag v0.5.4 && git push origin main
|
|
181
|
+
v0.5.4`, and the publish job watches for exactly that.
|
|
182
|
+
|
|
183
|
+
A patch, and the patch is the publish path. 0.5.3 was the first release to
|
|
184
|
+
reach the registry since the rename, and it reached it through trusted
|
|
185
|
+
publishing with a signed provenance statement - so the path works, and this
|
|
186
|
+
release is the one that says so where a reader will look for it.
|
|
187
|
+
|
|
188
|
+
### Changed
|
|
189
|
+
|
|
190
|
+
- **The npm trusted publisher is configured for
|
|
191
|
+
`pham-canh/opencode-harness-kit`**: `ci.yml`, no environment, publish and
|
|
192
|
+
stage. It is the one thing that was missing when 0.5.2 was refused, and it
|
|
193
|
+
is set now. The re-point is an account change on npm, so it needed the
|
|
194
|
+
account's second factor - and a rename is not a thing npm does for you.
|
|
195
|
+
|
|
196
|
+
- **The publish token secret is gone.** The job can publish with a token
|
|
197
|
+
when there is one, and the secret was set while 0.5.3 was cut, because a
|
|
198
|
+
release that cannot publish is not a release. It is removed now that the
|
|
199
|
+
OIDC path is proven: a long-lived token in the repository is a secret to
|
|
200
|
+
rotate for a path that is not being used, and npm is restricting the
|
|
201
|
+
tokens that bypass two-factor authentication for direct publishing in
|
|
202
|
+
January 2027, so the token path was on its way out anyway.
|
|
34
203
|
|
|
35
204
|
## [0.5.3] — 2026-10-21
|
|
36
205
|
|
|
@@ -90,6 +259,25 @@ rename was written for - never reached the registry.
|
|
|
90
259
|
the moment the thing it films changes. The recording guide is on the
|
|
91
260
|
machine that records.
|
|
92
261
|
|
|
262
|
+
## [0.5.2] — 2026-10-21
|
|
263
|
+
|
|
264
|
+
**Not published.** The tag was cut and pushed, the publish job ran, and npm
|
|
265
|
+
refused the publish - so there is no 0.5.2 on the registry. The changes
|
|
266
|
+
written for it shipped in 0.5.3.
|
|
267
|
+
|
|
268
|
+
What npm said was `OIDC token exchange error - package not found`, which
|
|
269
|
+
reads like a missing package. The package was there. The trusted publisher
|
|
270
|
+
was not: npm reports no trusted publisher configured for this package, and
|
|
271
|
+
the repository had been renamed, so a publisher that named the old
|
|
272
|
+
repository would not have matched the claim the runner presents either. The
|
|
273
|
+
package's name is in the error, and the field that was wrong is not.
|
|
274
|
+
|
|
275
|
+
The entry this release was going to carry said that "the npm trusted
|
|
276
|
+
publisher was re-pointed at the new name". It had not been. The re-point is
|
|
277
|
+
an account change on npm and it needs the account's second factor, and
|
|
278
|
+
nothing had done it. A changelog that says a thing has been done that has
|
|
279
|
+
not been done is worse than one that says nothing.
|
|
280
|
+
|
|
93
281
|
## [0.5.1] — 2026-10-21
|
|
94
282
|
|
|
95
283
|
Published. The tag is what cut it: `git tag v0.5.1 && git push origin main
|
|
@@ -1208,7 +1396,9 @@ silent in the worst way:
|
|
|
1208
1396
|
|
|
1209
1397
|
---
|
|
1210
1398
|
|
|
1211
|
-
[Unreleased]: https://github.com/pham-canh/opencode-harness-kit/compare/v0.5.
|
|
1399
|
+
[Unreleased]: https://github.com/pham-canh/opencode-harness-kit/compare/v0.5.5...HEAD
|
|
1400
|
+
[0.5.5]: https://github.com/pham-canh/opencode-harness-kit/compare/v0.5.4...v0.5.5
|
|
1401
|
+
[0.5.4]: https://github.com/pham-canh/opencode-harness-kit/compare/v0.5.3...v0.5.4
|
|
1212
1402
|
[0.5.3]: https://github.com/pham-canh/opencode-harness-kit/compare/v0.5.2...v0.5.3
|
|
1213
1403
|
[0.5.2]: https://github.com/pham-canh/opencode-harness-kit/compare/v0.5.1...v0.5.2
|
|
1214
1404
|
[0.5.1]: https://github.com/pham-canh/opencode-harness-kit/compare/v0.5.0...v0.5.1
|