Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
26b0cbf
chore(1.2.0): bump the version, add ollama.contextLength, pin glob an…
jaymar921 Nov 6, 2026
7bc5a2c
chore(1.2.0): refresh the lockfile, clear the dev-tree advisories, fi…
jaymar921 Nov 6, 2026
a8e43c5
feat(1.2.0): pick one Ollama context window per model
jaymar921 Nov 6, 2026
36d8b28
fix(1.2.0): send num_ctx on every request so Ollama stops dropping th…
jaymar921 Nov 6, 2026
aa47a19
feat(1.2.0): fit each tier's prompt budget to the model's context window
jaymar921 Nov 6, 2026
52b4895
feat(1.2.0): tell the model to send only changed lines, and fit promp…
jaymar921 Nov 6, 2026
22592a6
feat(1.2.0): parse edit_file actions and their find and replace fields
jaymar921 Nov 6, 2026
30afd8e
feat(1.2.0): read what a file provides, for CommonJS, ES modules, Pyt…
jaymar921 Nov 6, 2026
54580ff
feat(1.2.0): find the lines a request is about without asking the model
jaymar921 Nov 6, 2026
20452e0
feat(1.2.0): read file-change memory off the diff, and restate long r…
jaymar921 Nov 6, 2026
f7447ca
feat(1.2.0): list what each changed file provides in the session memory
jaymar921 Nov 6, 2026
50371c6
feat(1.2.0): add edit_file, which changes part of a file through the …
jaymar921 Nov 6, 2026
9302c32
fix(1.2.0): refuse a change that exports a name defined nowhere
jaymar921 Nov 6, 2026
b0986cb
feat(1.2.0): register edit_file ahead of write_file
jaymar921 Nov 6, 2026
5567b63
feat(1.2.0): track edit_file as a write that does not count as readin…
jaymar921 Nov 6, 2026
d3dcffc
fix(1.2.0): stop adding the generic error paragraph to refusals that …
jaymar921 Nov 6, 2026
41eb95c
feat(1.2.0): ask for edits in plain words and check the answer before…
jaymar921 Nov 6, 2026
f020203
feat(1.2.0): run dictated edits on the constrained tier and pass cont…
jaymar921 Nov 6, 2026
a97abfa
feat(1.2.0): shorten old Tier A history before the window fills, and …
jaymar921 Nov 6, 2026
6308a1a
feat(1.2.0): support edit_file in the Tier B loop, and align the cont…
jaymar921 Nov 6, 2026
5da75d8
feat(1.2.0): read the contextLength setting and give inline completio…
jaymar921 Nov 6, 2026
96b07ad
feat(1.2.0): send context use and remembered memory to the panel
jaymar921 Nov 6, 2026
8fe502c
feat(1.2.0): show context use and a What it remembered panel on each …
jaymar921 Nov 6, 2026
ccdc5ca
feat(1.2.0): wire the memory panel and context reading, and explain t…
jaymar921 Nov 6, 2026
4bbb4b8
feat(1.2.0): split the panel header into two rows, label Thinking, an…
jaymar921 Nov 6, 2026
9ad82d7
feat(1.2.0): style the new header, memory panel and narrow sidebar la…
jaymar921 Nov 6, 2026
764a091
feat(1.2.0): teach the full-tier prompt to use edit_file
jaymar921 Nov 6, 2026
0d22fc0
feat(1.2.0): teach the 1B prompt to send only the lines that change
jaymar921 Nov 6, 2026
a3bc3b1
test(1.2.0): cover context window selection
jaymar921 Nov 6, 2026
8e0196a
test(1.2.0): cover reading what a file provides
jaymar921 Nov 6, 2026
ef5f823
test(1.2.0): cover edit_file matching, refusals and the permission path
jaymar921 Nov 6, 2026
225b56c
test(1.2.0): cover dictated edits and the checks on their answers
jaymar921 Nov 6, 2026
6756b26
test(1.2.0): cover file shapes in memory, the window and Tier A short…
jaymar921 Nov 6, 2026
5d35d25
test(1.2.0): cover diff-derived memory notes and request distilling
jaymar921 Nov 6, 2026
fed1209
test(1.2.0): cover the refusal for exporting an undefined name
jaymar921 Nov 6, 2026
391b1c1
test(1.2.0): separate the Writing and Editing step verbs
jaymar921 Nov 6, 2026
f0fbbc1
bench(1.2.0): add the CommonJS cart fixture for the edit benchmark
jaymar921 Nov 6, 2026
95ea49e
bench(1.2.0): add the held-out Python inventory fixture
jaymar921 Nov 6, 2026
f78c46a
bench(1.2.0): add the edit benchmark, graded by running the edited file
jaymar921 Nov 6, 2026
9a1010d
bench(1.2.0): machine B, llama3.2:1b on cart, tier B, baseline-1.1.0
jaymar921 Nov 6, 2026
310a135
bench(1.2.0): machine B, llama3.2:1b on cart, tier B, 1.2.0
jaymar921 Nov 6, 2026
63514cf
bench(1.2.0): machine B, llama3.2:latest on cart, tier A, baseline-1.1.0
jaymar921 Nov 6, 2026
f7a9919
bench(1.2.0): machine B, llama3.2:latest on cart, tier A, 1.2.0
jaymar921 Nov 6, 2026
8574cb3
bench(1.2.0): machine B, llama3.2:latest on cart, tier B, 1.2.0
jaymar921 Nov 6, 2026
2b5f3c1
bench(1.2.0): machine B, llama3.2:1b on inventory, tier B, baseline-1…
jaymar921 Nov 6, 2026
1edccf9
bench(1.2.0): machine B, llama3.2:1b on inventory, tier B, 1.2.0
jaymar921 Nov 6, 2026
d12a2ba
bench(1.2.0): machine B, llama3.2:latest on inventory, tier A, baseli…
jaymar921 Nov 6, 2026
ac43101
bench(1.2.0): machine B, llama3.2:latest on inventory, tier A, 1.2.0
jaymar921 Nov 6, 2026
58fe2be
bench(1.2.0): machine B, llama3.2:latest on inventory, tier B, 1.2.0
jaymar921 Nov 6, 2026
8bd20e5
docs(1.2.0): document the edit benchmark and the 1.1.0 against 1.2.0 …
jaymar921 Nov 6, 2026
3391563
docs(1.2.0): add the new modules and the dictated edit path to the ar…
jaymar921 Nov 6, 2026
33222f4
docs(1.2.0): mention the edit benchmark
jaymar921 Nov 6, 2026
23166cc
docs(1.2.0): document the contextLength setting
jaymar921 Nov 6, 2026
6380233
docs(1.2.0): explain when small models no longer retype a file
jaymar921 Nov 6, 2026
9c99c3c
docs(1.2.0): add the edit results and the 3B tier override advice
jaymar921 Nov 6, 2026
38a1c48
docs(1.2.0): add the SAST report, with SAST-027 closed before release
jaymar921 Nov 6, 2026
e0b93d6
docs(1.2.0): lead the README with what small models can now do
jaymar921 Nov 6, 2026
81ac334
docs(1.2.0): write the release notes
jaymar921 Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,193 @@ All notable changes to HirayaCoder are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.0] - 2026-11-06

This release is about small models doing agentic work. The question going in was whether
better memory and a better translator would get a 1B model there. They help, and both got
work in this release, but the two biggest problems turned out to be somewhere else:

1. **Ollama was throwing away the start of long prompts.** Nothing set the context window,
so every request got the server's default, which is 4,096 tokens on most laptops.
Anything longer lost its beginning, which is where the instructions and the task sit.
2. **Below about 3B, the action format is the bottleneck, not the coding.** Asked to change
one line in a 164-line file, `llama3.2:1b` could write the new line perfectly well. What
it could not do was copy the old line exactly into a JSON field so the extension could
find it.

Both are fixed. On the new edit benchmark, `llama3.2:1b` went from 0 of 12 to 12 of 12 on
the suite used during development, and from 0 of 12 to 9 of 12 on a second suite written
afterwards in a different language, with no damaged file in any run. That second suite's
first run did find four failures, which were fixed, so it is not fully unseen either; the
details are in [benchmarks/README.md](benchmarks/README.md).

**If you run a 3B model, try forcing the constrained tier for editing.** `llama3.2:latest`
(3.2B) is just over the line, so by default it runs the full tool loop, which does not use
dictated edits. There it passed 0 of 8 and 2 of 8. Forced onto the constrained tier with
`hirayacoder.model.tierOverrides` (`{"llama3.2:latest": "B"}`) it passed 16 of 16. The
default line has not moved, because the tier decides more than editing and the other
benchmarks have not been re-run with it moved.

### Fixed: Ollama was silently cutting the start of long prompts

Until now no request set `num_ctx`. Measured on machine B (Ollama 0.40, GTX 1650 Super):

| Request | Prompt sent | Prompt Ollama kept | What was lost |
|---|---|---|---|
| `llama3.2:1b`, no `num_ctx` | 12,648 tokens | 2,050 | the instructions at the top |
| `qwen3.5:0.8b`, no `num_ctx` | 3,959 tokens | 2,050 | the first 31 of 60 requirements |

The reply shares the same window, so the second request also stopped at 2,046 tokens of
output with `done_reason: "length"`. That is the "cut off before the code block ended" that
0.9.0 put down to `num_predict`.

`core/contextWindow` now picks one window per model and the client sends it on every
request for that model: the agent loop, the translator, the planner and inline completion.
One number per model matters because Ollama reloads the model whenever `num_ctx` changes,
and a reload costs 7 to 10 seconds even for a 1B model. The window is sized for the largest
prompt the model's tier can send plus a full file of output (8k for the constrained tier,
16k for the full one), and capped at 16k by default because the machines this is built for
have 4 GB of VRAM or none. If the window is smaller than the tier would like, the prompt
budget shrinks to fit, so the extension trims by priority instead of Ollama trimming from
the top.

New setting: `hirayacoder.ollama.contextLength`. `0` (the default) picks automatically. Set
a number to go higher if you have memory to spare, or lower to save it.

The full-tool loop (Tier A) keeps the whole conversation, which is fine until it no longer
fits. It now makes room itself before Ollama does: old tool results are cut to their first
line and old `write_file` calls lose the file body they carried (it is on disk, one
`read_file` away). The system prompt, the task, and the last few messages are never touched.

### Added: `edit_file`, for changing part of a file

Until now, changing one line meant sending the whole file back. `edit_file` takes the exact
lines to replace (`find`) and what goes in their place (`replace`). It has no route of its
own to the disk: it works out the new content and hands it to `writeFile`, so the
permission gate, the diff, the content guards and the audit log all apply exactly as before.
It is listed before `write_file` on purpose, because small models lean towards the first
tool that fits.

When `find` does not match, the refusal shows the closest lines in the file so the next try
can copy them. If it matches more than once, it asks for more context. If only the
indentation differs, it matches anyway, re-indents the replacement to fit, and says so in
the result. It is never fuzzier than that.

### Added: dictated edits for small models

`edit_file` alone was not enough for a 1B model. Measured on `llama3.2:1b`: it passed 0 of
12 with whole-file writes, and still 0 of 12 with `edit_file`, because it filled `find` with
words from the request (`"8%"`) instead of text from the file (`const TAX_RATE = 0.08;`).
Once told the text was not there, it lost the JSON format altogether.

So on the constrained tier, when a request names exactly one existing file, the extension
does the finding itself. `core/editRegion` picks the lines the request is about by matching
its words against the file's identifiers (`TAX_RATE` is "tax rate" once split). The model
gets those lines and the request in plain words, and writes the new lines in a code block.
The answer goes in as an ordinary `edit_file`, through the same gate, diff and audit. This
is the 0.9.0 dictation finding (plain words beat JSON for small models) applied to edits.

The answer is checked before anything is written:

- **Nothing dropped.** Asked to make `applyDiscount` also reject percents over 50, the 1B
model replaced `percent < 0` with `percent > 50` three times out of three. A rewrite that
loses an existing check is refused with the check named. If it happens again, the model is
only asked for the new lines to put at the top of the function, next to a check it never
sees and so cannot lose.
- **Nothing out of scope.** New lines at the top of a function can only use what exists
there. Asked to stop overselling, the model wrote `if quantity > self.stock:` in a class
that has no `stock`. That is refused, not written.
- **No placeholders, no action objects, no whole-file rewrites.** `# ... rest unchanged`
and a JSON action pasted as code are both refused.
- **Indentation kept.** A rewritten method is placed at the depth it replaces. Before this, a
method written at column 0 closed its class early.
- **Additions go where additions go.** A new function goes above the export list and its
name is added to the list in one edit. A new method goes inside the class the request
names.

If the region finder is not confident, or the file holds something the secrets scanner
would redact, nothing is dictated and the request goes to the normal loop, as before. After
a dictated edit the loop still runs if the request asked for more than one thing, and starts
out knowing what was already done. A single-part request that the edit finished simply
ends: measured, the 1B model did not recognise finished work and spent eight turns trying to
make the same change again.

### Changed: the translator, in both directions

From the model's work to memory: for a file change, the memory note is now read off the
change itself (`TAX_RATE = 0.08` became `0.1`, added `itemCount(cart)`) with no model call.
That is cheaper (one less inference per write) and it cannot be wrong about what changed.

From the request to the model: the goal reminder used to repeat the first 240 characters of
the request. On a long brief those were the preamble ("You are an autonomous coding agent...")
and the work was further down. `distillTask` now keeps the sentences that ask for work, in
the user's own words and original order. `asksForMore` tells a one-part request from one
with a second part, which is what lets a finished dictated edit end the turn.

### Changed: memory says what each changed file provides

The "files you changed this session" block used to say `edited src/cart.js (+3 / -1)`. That
says the file was touched but not what to call. It now also lists what each file offers,
with arguments (`addItem(cart, product, quantity = 1)`), read from disk when the prompt is
built so it is never stale. `core/codeShape` reads this for CommonJS, ES modules, Python and
Java. The read is confined to the workspace, size-capped, redacted and neutralised, the same
as a tool read.

### Changed: the panel

- The header is two rows. The first says who and what (panel, session, model and tier). The
second has the run settings, with the mode first and the rarely changed toggles (Steps,
permissions, Guide) last and quieter.
- The Thinking control is labelled. Low, Med and High meant nothing on their own.
- Each reply's footer shows how much of the model's window the turn used
(`1.7k of 8k context`), from Ollama's own count, with a warning if the window filled.
- Each reply has a closed **What it remembered** panel showing the exact memory the model
was given. When an answer is wrong, this tells you whether the model had the wrong picture
or reasoned badly from the right one.
- The tier badge's tooltip now explains the window size too.
- The image button is a drawn icon. The emoji showed as a box in some editor fonts.
- In a narrow sidebar the key hint and the run status no longer fight for one line, and the
Steps panel no longer draws a stray rule when nothing is above it.

### Fixed

- **The generic error paragraph was being copied into files.** Refusals that already say
exactly what to send instead (a truncated file, removed exports, a missed edit, and
others) no longer get the generic "this failure does not match anything known" paragraph
appended. The 1B model was answering a missed edit with that paragraph as its `replace`
text. Repeats still escalate as before.
- **A new export with no definition.** Asked to add `itemCount(cart)` and export it, the 1B
model added `itemCount,` to `module.exports` and wrote no function, three runs out of three.
In CommonJS that breaks every file that imports the module. `writeFile` now refuses a
change that exports a name defined nowhere.
- **Three content guards contradicted their own message.** A write that removes exports,
exports a name it never defines, or drops an implementation is refused with a message
asking for the file again with the code intact. The loop then added "do not try that
again", which told the model the opposite. The hint now agrees with the refusal, as it
already did for the four older content guards.

### Security

Reviewed in [security/sast-report-2026-10-09-1.2.0.md](security/sast-report-2026-10-09-1.2.0.md).
Three fixes came out of it: a dictated edit no longer sends a file holding a secret to the
model unredacted, and two patterns that grew with the square of their input (one of them
took 44 seconds on a 200,000-character line) are now linear. The production dependency tree
is still empty.

The development tree is back to zero advisories in `npm audit`, from 13 at the last check.
Nothing here ships, because the packaged extension contains no `node_modules`. Most came
out with `npm audit fix`. The rest needed `glob` 14 and `minimatch` 11 pinned under Mocha
and `vsce` with `overrides`, because no released version of either tool asks for them yet.

### Developer tooling

`tools/bench-edit.js` is a new benchmark: can a model change part of an existing file
without breaking the rest? Each task starts from a fresh fixture, runs through the real
`AgentSession`, and is graded by loading the file and running checks against it, never by
what the model said. Two suites: `cart` (CommonJS, used during development) and `inventory`
(Python with a class, held out). Results and method are in
[benchmarks/README.md](benchmarks/README.md).

## [1.1.0] — 2026-08-21

HirayaCoder can look at pictures. Attach a screenshot, a photo, or a sketch of a screen
Expand Down
64 changes: 37 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,43 @@ works with your Wi-Fi switched off. That is the whole point of it.

---

## New in 1.1.0: it can look at pictures

Attach a screenshot, a photo, or a sketch to your message, and HirayaCoder reads it.

- **Ask a question about it.** *"What is this error saying?"*
- **Give it a job.** *"Build the screen in this mockup."* The picture becomes part of
the task.

The useful part is that **this works even if your coding model cannot see**. Most AI
models only handle text. A few, called vision models, can also take an image. If the
model you picked is a text-only one, HirayaCoder quietly hands the picture to a vision
model instead, gets back a written description of what is in it, and passes that
description to your coding model. You do not have to switch models.

If nothing you have installed can read images, one command fixes it:

```bash
ollama pull minicpm-v4.6
```

That is 1.6 GB, and it sits alongside whatever you already use for coding.

**You can always see what it read.** Under your message there is a panel showing the
exact description the vision model produced. Open it when an answer looks wrong, because
it tells you whether the picture was misread or the answer was.

Full detail, including what it is bad at, is in
## New in 1.2.0: small models can change your files

Ask a small model to *"change the tax rate from 8% to 10% in src/cart.js"* and, until
this release, it usually failed. It knew how to write the new line. What it could not do
was point at the old one precisely enough for the change to be made.

So now HirayaCoder finds the right lines itself, shows them to the model, and asks in
plain words for the rewritten version. Before anything is saved it checks the answer: no
existing check was dropped, nothing refers to something that does not exist, and the rest
of the file is untouched. You still approve the change as before.

On our tests with `llama3.2:1b`, the smallest model we support, edits to an existing file
went from **0 out of 12 to 12**, and from **0 to 9** on a second
set of tests written afterwards to check it was not just tuned to the first.

Also in this release:

- **It no longer loses the start of long requests.** Ollama, the program that runs the
model, was quietly cutting off the beginning of anything long, which is where your
instructions were. HirayaCoder now tells it how much to keep. If you have memory to spare, you can raise it with the
`hirayacoder.ollama.contextLength` setting.
- **You can see what the model was given.** Under each reply there is a line saying how
much of the model's memory the turn used, and a closed **What it remembered** panel
with exactly what it was told about your project. When an answer is wrong, this tells
you whether it had the wrong picture or just reasoned badly.
- **It remembers what it built.** After it writes a function, later requests are told
its name and what it takes, so *"now use it from the main page"* calls it correctly.
- **A tidier panel.** The settings are grouped into two rows, and the ones you rarely
touch are quieter.

**Using a 3B model like `llama3.2`?** It is just big enough to get the other, more
general way of working, which in our tests managed 2 edits out of 16. Add
`"hirayacoder.model.tierOverrides": { "llama3.2:latest": "B" }` to your settings and it
passed all 16 instead.

Still new from 1.1.0: attach a screenshot or a photo and it reads it, even if your coding
model only handles text. See
[IMAGE-RECOGNITION.md](https://github.com/jaymar921/HirayaCoder/blob/main/doc/IMAGE-RECOGNITION.md).

---
Expand Down
Loading
Loading