release: 1.0.0 - the guide, the wiring test, and the security pass behind the version number - #17
Merged
Conversation
…ind out how The panel assumed its reader had already read the README. For a 1.0.0 release that is the wrong assumption to ship: the most likely reader is someone whose first run went nowhere because Ollama is not running, or who is four minutes into a task and does not know whether that is normal. `components/guideCard.js` holds both halves — the four setup steps and, more importantly, what to expect once they are done. The expectations are blunt on purpose. A user told to expect ChatGPT concludes the extension is broken; one told a task takes 1-5 minutes on a laptop waits for it. It renders locally rather than posting to the host, because unlike every other control here it has nothing to ask for: the text is identical on every machine. And it is a card in the transcript rather than a modal, so it can be read beside the run that prompted it instead of covering it.
… see Asked to confirm every control in the panel is clickable and does something, the honest answer was that no test could tell. The component tests build nodes and assert their shape. The integration tests drive the host. Neither notices a button in index.html that nobody listened to, or a control posting `attach-flie` into a switch that falls through to `default` — both of which look entirely normal on screen until someone clicks. So this checks the seams as text: every interactive element is resolved in main.js, every button has a way to be activated (own listener, delegated container, or the form it submits), and the message protocol closes in both directions. Also the reverse — a handler delegating on [data-mode] when no button carries it is the same dead control seen from the other side. 52 checks, covering 13 webview-to-host message types and all 16 coming back. The extraction regexes are guarded by a count assertion, since a broken regex would otherwise make every check pass against an empty set.
…o the composer
SAST-014, found by measuring the 22 regexes ESLint flags as unsafe rather than
reading them. `stepBrief.PATH_TOKEN` scans an item's text with /g for filenames, and
scanning for a token that is not there costs one attempt per start position — so an
unbroken run of word characters is O(n^2). Measured on a single run of `a`, which is
what a pasted data URI, minified line or hash looks like to this expression:
3,200 chars 23 ms
51,200 chars 6.1 s
204,800 chars 85.2 s extension host frozen throughout
`core/commonSense` has the same expression, and has documented and bounded this since
it was measured there. `stepBrief` copied the comment about a single match being
linear — which is true — and not the bound, so nothing here contradicted the code; it
just answered a different question than the one that mattered.
Bounding each segment to 120 characters caps the work per start position and makes
the sweep linear: 244 ms at 204,800 characters, down from 85 s. Verified identical
output on the benchmark briefs and on every path in this repo's own docs, and the
timing is pinned by a test, because the bound is the only thing between that input
and the freeze.
…ing yet Four 1:1 cards at 2160x2160, rendered from HTML like the README images and sharing their palette so the set reads as one product. One argument each, in the order a carousel should carry them: your code never leaves your machine (the hook), agentic down to 1B (the proof it works), you approve every write (the objection), 1.0 is out and here is how to start (the ask). Captions live beside them in ADS-1.0.0.md — one per image, plus a single caption for posting all four together with five hashtags. Two things worth recording. Ad 3's diff header said +6/-2 over a diff showing five added lines and one removed; a mock that gets its own arithmetic wrong is a bad advert for a tool whose pitch is that it shows you the diff. And ad 4 was drafted with "Search HirayaCoder in the VS Code Extensions view" in the footer, which is exactly the instruction capabilities.html shipped through 0.7.0 and which could not work, because the tag is cut before the Marketplace listing exists. Both fixed before anything was rendered; the second is now a note in ADS-1.0.0.md and src/README.md saying what to swap on the day the listing is live.
…pre-release Version badges to v1.0.0 and re-rendered. Three deliberate non-changes: The "After" badge in knows-what-it-has.html stays at v0.9.0. It labels the release the measurement was taken on, not the current version, and bumping it would make the picture claim a number it never measured. The "New in 0.9.0" tag is removed rather than moved. It travels to whichever card the release rewrote, and 1.0.0 rewrote none of these six — it is the release that stabilises them. Leaving it would have let this release take credit for the last one's work; moving it would have needed somewhere true to move it to. The install route stays GitHub Releases. The version number and the Marketplace listing are separate events and the tag is cut before the listing exists, so the route that is true today and stays true afterwards is the one to ship. What did change is the wording: "Pre-release" was about distribution, not stability, and a 1.0.0 calling itself a pre-release reads as a mistake. The footers now say "Free & MIT" and the hero's note says "Free and MIT-licensed" — neither asserts a listing that is not there. src/README.md records what to swap on the day it is. (docs/images$name.png — 71 KB of an unexpanded shell variable committed by accident in 0.8.0 — was removed in the previous commit, where the staged deletion rode along.)
The classification itself was already right and already automatic: the workflow reads the version from package.json and flags `0.x` or any `-suffix` build as a pre-release, so 1.0.0 comes out as a full release with no switch to remember. Verified against the versions either side of the line. The hole was one path further down. `FLAGS` was empty for a full release, which is correct for `gh release create` — it defaults that way — but the same variable is reused where the tag already has a release, and there it was guarded by `if [ -n "$FLAGS" ]`. So a release drafted by hand as a pre-release, or left behind by a half-finished earlier run, would keep that flag forever and the workflow would silently agree. 1.0.0 is exactly the release where that is wrong, and nobody would think to check it. Both directions are now stated: `--latest` on create, `--prerelease=false --latest` on edit, and the edit is unconditional. `--latest` also matters on its own, since /releases/latest is where install instructions point. Pinned by a test, because this is checkable exactly once — on the tag push, in a job that cannot be re-run against the same tag.
…fter" The guide had one linear path with a note that steps 1-3 were one-time. For the release that actually does those steps once, that is the wrong shape: a first publish and an update share the commands but not the risk, and the parts that are hard to undo were not marked as such. Now Part A is the one-time publisher and token setup, Part B is the repeating release, and a table up front says which one you are doing. A second table separates the three things people conflate at a release — the GitHub Release (automated, reversible), the Marketplace listing (manual, permanent), and the version number — because confusing them is how this goes wrong. The substantial addition is Step 5. Bumping the version means ten image sources, the README's hero alt text, the changelog and a new SAST report, none of which fail loudly when they go stale — they just ship a picture reading v0.9.0 on a 1.2.0 release. It also records the two traps found while doing exactly that this release: one version badge labels the release a measurement was taken on and must not be bumped, and the "New in" tag either moves to the card the release changed or comes off. Step 9b is new and deliberately late: flip the install route to the Marketplace only once the listing is live, with the four files that carry it listed. Step 10 now states that release-vs-pre-release comes from the version and is covered by a test. Also corrected: the manifest sample had drifted from the real package.json in five fields, including the licence, and the CI figures said 573 unit and 12 integration tests against an actual 1,545 and 16.
package.json to 1.0.0, which is what the release workflow reads to decide the tag is a full release rather than a pre-release. The changelog entry leads with what 1.0.0 does not add — no tool, no permission, no network call, no dependency — because that is the honest shape of it. The version number is a claim about stability, and the three things that back it are the guide, the wiring test, and a SAST pass that measured what earlier passes had read. The report itself is mostly one table: all 22 expressions ESLint flags as unsafe in app/, each timed against its own worst-case input at four doubling lengths. Twenty are linear. One was already found and bounded when it was written. One was SAST-014, and that only turned up because the sibling module it was copied from had a bound this one did not. Reading the shapes would not have found it — the comment above it was correct. Also dates the 0.9.0 heading, which said "unreleased" over a version tagged the same day.
…ntent Windows CI: 1,544 passing, one failing, on the one platform the test was not written on. The assertion matched a fixed string containing `\n`, and this repository pins no line endings in .gitattributes — so a Windows checkout hands back release.yml with CRLF and the pattern misses against byte-identical content. It passed locally because the working copy had just been written with LF. Newlines are now normalised on read, which covers every assertion in the file rather than the one that happened to break. The guard assertion also becomes a pattern instead of a fixed string, so re-indenting that block is a formatting change rather than a failed release check. Verified by converting release.yml to CRLF in place and running the suite against it — 5 passing where it previously failed, and 1,545 overall — then restoring LF and running it again.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Takes HirayaCoder from 0.9.0 to 1.0.0.
The version number is the headline and it is a claim about stability, not features:
this PR adds no tool, no permission, no network call, and no dependency.
pathGuard,permissionGate,scriptRunnerand the allow-list are untouched. What it adds is thethings a release needs before strangers install it.
What's in it
A setup guide in the panel — a
Guidebutton in the chat header opening the foursetup steps and, more usefully, what to expect: a task takes 1–5 minutes on a laptop, a
refused write is usually the checks working, a small model is good at one file and bad at
a whole app. The panel previously assumed its reader had read the README; for the release
where strangers arrive, that is the wrong assumption. It renders locally — unlike every
other control here it has nothing to ask the host for — which is also why it adds no new
entry point on the privileged side of the boundary.
A test that every button does something —
webviewWiring.test.js, 52 checks. Readsindex.html,main.jsandchatTab.jsas text and asserts every interactive elementresolves, every button can be activated (own listener, delegated container, or the form
it submits), and the message protocol closes both ways: 13 webview→host types, 16 back.
A dead button was the one UI defect nothing here could see — component tests assert node
shape, integration tests drive the host, and neither notices a control nobody listened to.
A ReDoS fix found by measuring, not reading (SAST-014) —
stepBrief.PATH_TOKENscans for filenames with
/g, and scanning for a token that isn't there costs one attemptper start position, so an unbroken run of word characters was O(n²):
a)That's the extension host frozen for 85 seconds, reached by pasting a data URI, minified
line or hash into the composer.
core/commonSenseholds the same expression and hasbounded it since it was measured there;
stepBriefinherited the comment explaining thata single match is linear — which is true, and answers a different question. Bounding each
segment to 120 chars restores linear scanning with identical output on every path in this
repo. Pinned by a timing test.
A 1.0.0 that can't land as a pre-release — the classification was already derived from
the version (
0.xand-rc/-betaare flagged, nothing else), so the normal path wasfine. The hole was the other path: where a release for the tag already exists, the flag was
only ever added, never cleared, so a hand-drafted pre-release would stay one forever.
Both directions are now explicit, the edit is unconditional, and full releases get
--latest— which is what/releases/latestresolves to and therefore what installinstructions point at. Pinned by a test, since this is checkable exactly once per tag.
Marketing — six image sources bumped to v1.0.0 and re-rendered, plus four new 1:1
launch ads with captions in
docs/images/ADS-1.0.0.md. Also removesdocs/images$name.png, 71 KB of an unexpanded shell variable committed by accident in0.8.0 and referenced nowhere.
Docs —
doc/PUBLISHING.mdsplit into a one-time first publish (Part A) and therepeating release (Part B), with a full account of what a version bump touches beyond
package.json. Its manifest sample had drifted from the real one in five fields includingthe licence, and its CI figures said 573 unit / 12 integration against an actual 1,545 / 16.
Deliberate non-changes, and why
cut before the listing exists.
capabilities.htmlshipped a "search the Extensionsview" footer through 0.7.0 when there was nothing to find;
ad-4-launch.htmlwas draftedwith the same mistake and corrected before rendering.
PUBLISHING.mdStep 9b lists thefour files to flip on the day the listing is live.
knows-what-it-has.htmlstays at v0.9.0. It labels the releasea measurement was taken on. Bumping it would make the image claim a number it never
measured.
release rewrote, and 1.0.0 rewrote none of them.
detect-unsafe-regexwarnings after the fix, including thebounded expression —
safe-regexobjects to the shape, not the behaviour. The countdidn't move and an 85-second freeze was removed, which is the argument for reviewing
these rather than counting them.
Security
Full report:
security/sast-report-2026-08-20-1.0.0.md. Zero production dependencies,zero advisories (
npm audit --omit=devand full), no Critical or High findings. All 22detect-unsafe-regexexpressions inapp/were timed against purpose-built worst-caseinputs at four doubling lengths: 20 linear, 1 already bounded, 1 was SAST-014 above. The
4 retire.js findings are DOMPurify inside
.vscode-test/— Microsoft's bundled copies ina gitignored directory, never packaged.
Verification
npm run test:allgreen: lint (0 errors), 1,545 unit, 16 integration against areal VS Code.
toolRegistry.forModewithholds mutating toolsoutside Agent,
AgentSession._executerefuses any name it wasn't offered, and Askbuilds no loop at all.
npm run package→ 515.39 KB, 98 files, withguideCard.jsincluded anddocs/images/**excluded, so the new ads add nothing to the.vsix.releaseWorkflow.test.jsinitially failed on Windows CI only — afixed-string match containing
\nagainst a CRLF checkout. Fixed by normalisingnewlines on read, and verified by converting
release.ymlto CRLF in place andre-running rather than assuming.
Reviewing
The riskiest change is the one-character-class edit in
app/agent/stepBrief.js:80— italters what a shipped regex matches. Verified identical output against the benchmark
briefs and every path in this repo's docs, and covered by two tests, but it's the line to
look at first.