@niro-divya/niro-uat 0.0.0-stage → 0.17.0-uat.1376acf
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 +918 -0
- package/README.md +183 -2
- package/bin/niro.js +13 -0
- package/docs/cloud-README.md +372 -0
- package/docs/connect-your-ai-assistant.md +206 -0
- package/docs/uninstall.md +65 -0
- package/docs/working-on-branches.md +162 -0
- package/package.json +41 -4
- package/src/commands/_resolveProject.js +198 -0
- package/src/commands/ab.js +198 -0
- package/src/commands/add-project.js +198 -0
- package/src/commands/add-repo.js +303 -0
- package/src/commands/auditSend.js +67 -0
- package/src/commands/build.js +124 -0
- package/src/commands/cleanup.js +689 -0
- package/src/commands/delete.js +81 -0
- package/src/commands/docsSend.js +73 -0
- package/src/commands/edit.js +601 -0
- package/src/commands/engine.js +120 -0
- package/src/commands/engineRoutes.js +104 -0
- package/src/commands/info.js +291 -0
- package/src/commands/init.js +1412 -0
- package/src/commands/initLocal.js +96 -0
- package/src/commands/journalSend.js +47 -0
- package/src/commands/login.js +311 -0
- package/src/commands/logout.js +33 -0
- package/src/commands/mcp.js +93 -0
- package/src/commands/plan.js +89 -0
- package/src/commands/projects.js +76 -0
- package/src/commands/release.js +210 -0
- package/src/commands/remove.js +108 -0
- package/src/commands/repoPush.js +33 -0
- package/src/commands/start.js +612 -0
- package/src/commands/status.js +136 -0
- package/src/commands/takeover.js +414 -0
- package/src/commands/uninstall.js +180 -0
- package/src/commands/upgrade.js +421 -0
- package/src/commands/watch.js +1349 -0
- package/src/commands/whoami.js +74 -0
- package/src/index.js +558 -0
- package/src/lib/agentApi.js +307 -0
- package/src/lib/api.js +238 -0
- package/src/lib/auditUpload.js +275 -0
- package/src/lib/branchDrift.js +149 -0
- package/src/lib/browser.js +30 -0
- package/src/lib/choose.js +116 -0
- package/src/lib/cloudGuard.js +74 -0
- package/src/lib/color.js +46 -0
- package/src/lib/config.js +38 -0
- package/src/lib/credentials.js +73 -0
- package/src/lib/docsMarked.js +55 -0
- package/src/lib/docsState.js +213 -0
- package/src/lib/docsUpload.js +481 -0
- package/src/lib/engine.js +613 -0
- package/src/lib/folderSync.js +585 -0
- package/src/lib/git.js +231 -0
- package/src/lib/graphCheck.js +40 -0
- package/src/lib/impactLog.js +251 -0
- package/src/lib/journalUpload.js +147 -0
- package/src/lib/lease.js +362 -0
- package/src/lib/localBuild.js +285 -0
- package/src/lib/localEngine.js +210 -0
- package/src/lib/localOnboard.js +124 -0
- package/src/lib/localWorktrees.js +240 -0
- package/src/lib/machineCheck.js +58 -0
- package/src/lib/mcpClients.js +250 -0
- package/src/lib/migrationNotice.js +118 -0
- package/src/lib/modelFetch.js +153 -0
- package/src/lib/moduleExcludeSuggestions.js +57 -0
- package/src/lib/nestedOutput.js +63 -0
- package/src/lib/niroProjectFile.js +76 -0
- package/src/lib/onboardTarget.js +267 -0
- package/src/lib/onboardingLog.js +34 -0
- package/src/lib/pendingRequests.js +99 -0
- package/src/lib/periodicClaim.js +100 -0
- package/src/lib/planUsage.js +137 -0
- package/src/lib/printError.js +46 -0
- package/src/lib/progressBar.js +46 -0
- package/src/lib/prompt.js +121 -0
- package/src/lib/receiptStore.js +85 -0
- package/src/lib/redact.js +155 -0
- package/src/lib/release.js +33 -0
- package/src/lib/repoPush.js +392 -0
- package/src/lib/resolveContext.js +214 -0
- package/src/lib/secret.js +114 -0
- package/src/lib/service.js +494 -0
- package/src/lib/spinner.js +134 -0
- package/src/lib/takeoverRestore.js +156 -0
- package/src/lib/tempProject.js +61 -0
- package/src/lib/watchDaemon.js +100 -0
- package/src/lib/watchState.js +269 -0
- package/src/mcp/lineCheck.js +382 -0
- package/src/mcp/lineName.js +92 -0
- package/src/mcp/localBackend.js +751 -0
- package/src/mcp/localMode.js +360 -0
- package/src/mcp/localSkill.js +164 -0
- package/src/mcp/server.js +1392 -0
- package/src/mcp/setup.js +3172 -0
- package/src/mcp/teardown.js +743 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to `@niroai/niro`. Dates are release dates.
|
|
4
|
+
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
### The coverage audit sends a few lines, not the whole flagged file
|
|
8
|
+
|
|
9
|
+
When a build flags a line it thinks the parser missed, only the method that holds that line (or, when
|
|
10
|
+
no method holds it, at most 10 lines before to 40 after) and one line for each name those lines use
|
|
11
|
+
from the same file are sent, with secrets masked. Every other line of the file goes as an empty line,
|
|
12
|
+
so line numbers stay where they were. `niro status` and `niro plan` say so. niro-cli refuses to post
|
|
13
|
+
a snippet that holds any other line. Removing a copyright holder's name no longer drops a line after a
|
|
14
|
+
`*/` header, which used to move every flag below it one line up on the server.
|
|
15
|
+
|
|
16
|
+
### The background watcher no longer saves your whole shell environment
|
|
17
|
+
|
|
18
|
+
`niro-watch` runs under PM2, and PM2 saves the environment it was started with in
|
|
19
|
+
`~/.pm2/dump.pm2`. That used to be the whole shell, cloud keys and tokens included, in a file other
|
|
20
|
+
users on the machine could read. The watcher is now started with only what it reads (HOME, PATH,
|
|
21
|
+
locale, temp folders, proxy and certificate settings, and the Niro settings), and `dump.pm2` and its
|
|
22
|
+
backup are made readable by you only. An install made by an earlier version is started again with
|
|
23
|
+
the smaller environment the next time you run `niro start` or `niro service install`, which also
|
|
24
|
+
rewrites the saved files.
|
|
25
|
+
|
|
26
|
+
`niro status` and `niro plan` now say what leaves the machine: your code stays on it, and the files
|
|
27
|
+
the coverage audit flags are sent with secrets masked, on every plan.
|
|
28
|
+
|
|
29
|
+
### `niro watch` re-checks a locally indexed folder every few minutes
|
|
30
|
+
|
|
31
|
+
A file event that fs.watch dropped, or a file added while one was lost, was not built into the local
|
|
32
|
+
graph until some later edit arrived. The watcher now lists a locally indexed folder every five minutes
|
|
33
|
+
(file names, sizes and modification times; no file is read) and builds it when the listing changed
|
|
34
|
+
since the last build started. A folder with a build running or due is left to that build, so two
|
|
35
|
+
builds never run at once. Set `NIRO_LOCAL_RECHECK_MS` to change the interval.
|
|
36
|
+
|
|
37
|
+
### The MCP bridge speaks Streamable HTTP
|
|
38
|
+
|
|
39
|
+
`niro mcp serve` now talks to the Niro server over MCP Streamable HTTP (`POST /mcp`) when the server
|
|
40
|
+
offers it: each call is answered in its own HTTP response, so there is no long-lived stream to drop.
|
|
41
|
+
An expired session is renewed and the call resent once; a failed call returns an error instead of
|
|
42
|
+
hanging. A dropped connection is retried only when the call cannot run twice (it never left, or it
|
|
43
|
+
is a read like `tools/list`); a `tools/call` that may have run returns an error instead. Servers without it (404/405 on `/mcp`) get the old SSE transport, unchanged. Force one with
|
|
44
|
+
`NIRO_MCP_TRANSPORT=http` or `sse`. Editor configs do not change.
|
|
45
|
+
|
|
46
|
+
### `niro audit-push-repo`, for the plans that include it
|
|
47
|
+
|
|
48
|
+
Sends this folder's whole source to Niro, so we can measure what the local engine's parser
|
|
49
|
+
misses on code we never see rather than only on the files it already failed to parse. It lands
|
|
50
|
+
in the account-scoped object store, the same place a repository uploaded through `niro add repo`
|
|
51
|
+
goes, and nothing in that chain creates a project.
|
|
52
|
+
|
|
53
|
+
It is explicit: no watcher runs it and no other command triggers it, and it prints the folder,
|
|
54
|
+
the file count and the size, then asks. With no terminal it refuses unless you pass `--yes`.
|
|
55
|
+
|
|
56
|
+
Permission is a claim in the licence lease, and anything that is not a clear yes is a refusal,
|
|
57
|
+
including a licence service that cannot be reached. The folder goes through the same gate as
|
|
58
|
+
`niro init`, which refuses your home directory, the filesystem root and a folder that holds no
|
|
59
|
+
project of its own. The file set is the manifest the CLI already uses, so `.gitignore` is
|
|
60
|
+
honoured and, when git cannot answer on a work tree, every file whose name could carry a secret
|
|
61
|
+
is dropped rather than uploaded unverified; the service then decides what it will take, as it
|
|
62
|
+
does for every other upload. A folder that has been sent is remembered in
|
|
63
|
+
`~/.niro/audit-repos.json`; a second run says when the first one was, and `--force` sends it
|
|
64
|
+
again into the same workspace.
|
|
65
|
+
|
|
66
|
+
### A machine that cannot run the local engine is offered Niro's cloud, and asked first
|
|
67
|
+
|
|
68
|
+
The local engine keeps its graph in Memgraph, which runs as a container, and some machines
|
|
69
|
+
forbid a container runtime outright. `niro engine install` now finds that out and says so in the
|
|
70
|
+
engine's own words, with that platform's install instructions, then offers to index in Niro's
|
|
71
|
+
cloud instead and asks before switching. The answer is remembered in `~/.niro/config.json`, so
|
|
72
|
+
it is asked once per machine. With no terminal and no recorded answer nothing happens and the
|
|
73
|
+
command exits 1, because uploading somebody's source when nobody was there to say no is the one
|
|
74
|
+
outcome this must never produce; a script approves it deliberately with
|
|
75
|
+
`NIRO_APPROVE_CLOUD_INDEXING=1`.
|
|
76
|
+
|
|
77
|
+
A developer who runs Memgraph themselves, natively or on their network, is not asked at all: the
|
|
78
|
+
check passes as soon as a graph database answers, whatever started it.
|
|
79
|
+
|
|
80
|
+
`niro engine status` gained two lines, `database` and `route`, and now prints them whichever way
|
|
81
|
+
the engine was found, including from `NIRO_LOCAL_BIN`.
|
|
82
|
+
|
|
83
|
+
### A folder indexed on your own machine is no longer indexed in the cloud as well
|
|
84
|
+
|
|
85
|
+
`niro build`, `niro init` and `niro add repo` now refuse a folder that the local engine
|
|
86
|
+
(`niro-local`) already indexes, and name the two ways out: `niro-local build --path <folder>` to
|
|
87
|
+
update the local index, or `niro-local remove --path <folder>` first if you do want it in the
|
|
88
|
+
cloud. Before this, either command uploaded the folder's source and indexed it a second time,
|
|
89
|
+
which is the one thing the local engine exists to avoid, and the second index was charged for and
|
|
90
|
+
then went stale, because questions in that folder are answered from the local graph.
|
|
91
|
+
|
|
92
|
+
A machine without the engine is unaffected, and so is any folder the engine does not index.
|
|
93
|
+
`niro new-temp-project` is deliberately **not** refused: a temporary project is a cloud project on
|
|
94
|
+
purpose, because it serves pull request review and root cause analysis, which the local graph does
|
|
95
|
+
not answer. It now says so in one line before it uploads.
|
|
96
|
+
|
|
97
|
+
## 0.17.0 — 2026-09-23
|
|
98
|
+
|
|
99
|
+
### `niro init --defaults` still asks which project
|
|
100
|
+
|
|
101
|
+
`--defaults` accepts default values: env vars, indexing scope, the project name. It also used to
|
|
102
|
+
skip the "new project, or add to an existing one?" question and always create a new project, which
|
|
103
|
+
put repos in the wrong place for anyone who already had a project for them. Which project is not a
|
|
104
|
+
default value, so in a terminal the question is now asked with `--defaults` too.
|
|
105
|
+
|
|
106
|
+
Unattended runs are unchanged. With no terminal (CI, a piped script, an agent's shell) there is no
|
|
107
|
+
question and a new project is created, as before. A script that wants an existing project passes
|
|
108
|
+
`--add-to <alias|id>`; `--project-name <name>` still skips the question everywhere.
|
|
109
|
+
|
|
110
|
+
## 0.16.0 — 2026-09-22
|
|
111
|
+
|
|
112
|
+
### A reverse proxy's routing table is offered for upload
|
|
113
|
+
|
|
114
|
+
Most services do not call each other directly. They call a proxy, and a routing table decides who
|
|
115
|
+
really answers. That table is the only place the hop is written down, and the CLI was not offering
|
|
116
|
+
it, so the server never saw it and the graph stopped at the proxy.
|
|
117
|
+
|
|
118
|
+
Newly offered:
|
|
119
|
+
|
|
120
|
+
- `nginx.conf`, `haproxy.cfg`, `httpd.conf`, `apache2.conf`
|
|
121
|
+
- compose files named for their environment, not just `docker-compose.yml`: a Traefik routing table
|
|
122
|
+
lives in container labels, and those labels are routinely in `docker-compose.production.yml`
|
|
123
|
+
- Kubernetes Ingress manifests under the names teams actually use: `ingress.yaml`,
|
|
124
|
+
`*-ingress.yaml`, `*_ingress.yaml`
|
|
125
|
+
|
|
126
|
+
Deliberately still NOT offered: every `.yaml`. An Ingress can be in any file, and claiming all of
|
|
127
|
+
them would upload every Helm chart, kustomization and CI file in a repository to find the occasional
|
|
128
|
+
routing table. An Ingress under an unconventional name is a stated limit rather than a silent one.
|
|
129
|
+
|
|
130
|
+
Envoy was left out on purpose. A file that is uploaded and then read by nothing is cost with no
|
|
131
|
+
benefit, and the server has no Envoy reader yet.
|
|
132
|
+
|
|
133
|
+
### A drift guard for the config list
|
|
134
|
+
|
|
135
|
+
`CONFIG_FILENAMES` is a hand-kept copy of the server's list and nothing compared the two.
|
|
136
|
+
`test/configFilenameDrift.test.js` now does, alongside the existing extension guard. This matters
|
|
137
|
+
more than it did: if the copies drift now, the CLI stops offering `nginx.conf`, the server never
|
|
138
|
+
receives it, and every connection through that proxy quietly disappears from the graph. The symptom
|
|
139
|
+
reads as "proxy support is broken" and is nowhere near the list that caused it.
|
|
140
|
+
|
|
141
|
+
### Upgrade order
|
|
142
|
+
|
|
143
|
+
The server's filter remains the final say, so either order is safe: an older CLI offers fewer files,
|
|
144
|
+
and a newer CLI against an older server has its extra offers declined. The routing tables reach the
|
|
145
|
+
graph once both sides are current (ai-orchestrator `25131e94` or later). Repositories the server
|
|
146
|
+
clones itself are unaffected by this release; their files never went through the CLI.
|
|
147
|
+
|
|
148
|
+
## 0.15.0 — 2026-09-21
|
|
149
|
+
|
|
150
|
+
### Twenty more file types are offered for upload
|
|
151
|
+
|
|
152
|
+
`niro init` and the folder sync decide which files to offer the server from a fixed list of
|
|
153
|
+
extensions (`src/lib/folderSync.js`). That list was a hand-kept copy of the server's own list and
|
|
154
|
+
had fallen behind it in two places:
|
|
155
|
+
|
|
156
|
+
- the ten C++ extensions the parser routes to its C++ grammar besides `.cpp`, `.h` and `.hpp`:
|
|
157
|
+
`.cc`, `.cxx`, `.hxx`, `.hh`, `.h++`, `.c++`, `.tcc`, `.ipp`, `.inl`, `.tpp`. A C++ project with
|
|
158
|
+
`include/widget.hxx` and `detail/vector.tcc` never had those two files in its graph, because the
|
|
159
|
+
CLI never offered them.
|
|
160
|
+
- ten more the parser has always understood but the CLI never offered: `.kts`, `.csx`, `.rake`,
|
|
161
|
+
`.gemspec`, `.phtml`, `.php3`, `.php4`, `.php5`, `.pyi`, `.pyw`.
|
|
162
|
+
|
|
163
|
+
A test now reads the server's Java list and fails if the two drift apart again
|
|
164
|
+
(`test/sourceExtensionDrift.test.js`); the server carries the mirror-image test.
|
|
165
|
+
|
|
166
|
+
The server's own filter remains the final say, so an older CLI simply offers fewer files and a
|
|
167
|
+
newer CLI against an older server has its extra offers declined. Either order of upgrade is safe;
|
|
168
|
+
the new files reach the graph once both sides are current (ai-orchestrator `cadb5360` or later).
|
|
169
|
+
|
|
170
|
+
Note: 0.14.0 was cut in this repository but never published to npm, so upgrading from 0.13.0 brings
|
|
171
|
+
the 0.14.0 change below as well.
|
|
172
|
+
|
|
173
|
+
### A file whose name contains `+` no longer stops a folder syncing
|
|
174
|
+
|
|
175
|
+
After `niro init`, the server replies with the patterns saying which paths belong to the
|
|
176
|
+
project, and the watch daemon checks every path against them: on each file event, on each
|
|
177
|
+
delta and backstop pass, and once more before it asks the server to delete anything.
|
|
178
|
+
|
|
179
|
+
For a file at the top level of a repository, the pattern the server sends is the filename
|
|
180
|
+
itself. The daemon turned patterns into regular expressions but escaped only the dot, so a
|
|
181
|
+
name carrying a character that means something in a regular expression was read as an
|
|
182
|
+
instruction rather than as a name. With `main.c++` at a repository root, `main.c++` became
|
|
183
|
+
`/^main\.c++$/`, which is not a valid expression. Every sync pass for that folder then failed.
|
|
184
|
+
The daemon stayed up and printed nothing, so the folder simply went quiet.
|
|
185
|
+
|
|
186
|
+
The `.c++` and `.h++` file types added in this same release are what would have exposed it. The
|
|
187
|
+
same flaw had a quieter form that needed no C++ at all: a root-level file called `app (copy).js`
|
|
188
|
+
built a valid expression with a capture group, so the file stopped matching its own pattern and
|
|
189
|
+
was never synced again. A directory named `libstdc++` hit the throwing form, because saying yes
|
|
190
|
+
to "New directory detected. Add to sync?" stores the directory's own name as a pattern.
|
|
191
|
+
|
|
192
|
+
The matcher now escapes every character that has a special meaning, leaving only `*` and `**`
|
|
193
|
+
as wildcards, so a pattern can no longer fail to compile. Behaviour for patterns that already
|
|
194
|
+
worked is unchanged: old and new were compared over 10,701 path-and-pattern pairs taken from
|
|
195
|
+
three real repositories and agreed on every one.
|
|
196
|
+
|
|
197
|
+
## 0.14.0 — 2026-09-19
|
|
198
|
+
|
|
199
|
+
### `niro init` on a folder of repos now indexes each repo, not the folder
|
|
200
|
+
|
|
201
|
+
If you run `niro init` in a folder that is not itself a git repository but contains repositories —
|
|
202
|
+
an `apps/` folder, a `~/projects/github/<org>/` folder, anything that groups checkouts — each
|
|
203
|
+
repository is now onboarded in its own right, with its own git URL and its own branch, into one
|
|
204
|
+
project.
|
|
205
|
+
|
|
206
|
+
Before this, the folder itself was registered as a single repo. A folder has no git remote, so it
|
|
207
|
+
got a synthetic `local://<folder-name>` URL and no branch at all, and the monorepo splitter then
|
|
208
|
+
tried to recover the structure by reading folder layout. On one customer's tree that produced 92
|
|
209
|
+
internal repos from 14 real ones: 92 of 92 carried `local://apps`, none had a branch, 58 of them
|
|
210
|
+
were vendored copies and reference checkouts of other people's projects, and 45% of the indexing
|
|
211
|
+
spend went on code the customer had never written. Because no repo had a URL of its own, MCP could
|
|
212
|
+
not match a repo from inside its own folder, and kept offering to create a temporary project in
|
|
213
|
+
code that was already indexed.
|
|
214
|
+
|
|
215
|
+
The repositories underneath were the boundaries the customer had already declared. Reading them is
|
|
216
|
+
better than re-deriving them.
|
|
217
|
+
|
|
218
|
+
What you will see:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
apps is not a git repository, but contains 14 that are.
|
|
222
|
+
Niro will index each repo separately, with its own git URL and branch.
|
|
223
|
+
|
|
224
|
+
+ voibe-backend
|
|
225
|
+
+ voibe_app
|
|
226
|
+
- legal-docs — not a git repository, NOT indexed
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Folders holding code but no repository are reported rather than skipped in silence: from that point
|
|
230
|
+
on, editing a file in one of them does nothing, and you should know which ones they are. Run
|
|
231
|
+
`niro init` inside one to add it.
|
|
232
|
+
|
|
233
|
+
The scan stops at the first repository it finds down any path, so a repository vendored inside
|
|
234
|
+
another — a submodule, a `third_party/` checkout — is indexed as part of its parent rather than as a
|
|
235
|
+
service of its own.
|
|
236
|
+
|
|
237
|
+
Unchanged: a folder that IS a git repository still indexes whole, submodules and all. A folder with
|
|
238
|
+
no git anywhere still uploads as one repo, as before.
|
|
239
|
+
|
|
240
|
+
### A folder whose repositories sit deeper than one level is no longer refused
|
|
241
|
+
|
|
242
|
+
`niro init` requires a build file at the folder or just below it, which is right for a folder of
|
|
243
|
+
documents and wrong for `~/projects/github/<org>/<repo>`, where the repositories are three levels
|
|
244
|
+
down and every folder above them is empty. That was refused with "No project found" and a pointer to
|
|
245
|
+
`--force`, and `--force` would then upload the entire tree as one repo. A folder holding
|
|
246
|
+
repositories is now exempt from that rule, because it has already declared its projects.
|
|
247
|
+
|
|
248
|
+
The refusal message for a folder that genuinely holds nothing indexable now says what Niro looks
|
|
249
|
+
for, and suggests running from the folder where the code actually is.
|
|
250
|
+
|
|
251
|
+
### Fixed
|
|
252
|
+
|
|
253
|
+
- The background watcher was being installed once per repository when onboarding several at once,
|
|
254
|
+
asking for your password each time. It is one process; it is now set up once per run.
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
## 0.13.0 — 2026-09-08
|
|
258
|
+
|
|
259
|
+
### Upgrading
|
|
260
|
+
|
|
261
|
+
You do not have `niro upgrade` yet, so this one takes two commands:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
npm install -g @niroai/niro@latest
|
|
265
|
+
niro upgrade --finish
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The first gets the package. The second is the half npm cannot do: it re-runs the installer for
|
|
269
|
+
every assistant you have configured and restarts the background watcher. Then restart your
|
|
270
|
+
assistant. Skipping the second command leaves you on this version with the previous version's
|
|
271
|
+
hooks, skills and instruction files, which is the exact problem this release exists to fix.
|
|
272
|
+
|
|
273
|
+
From here on it is one command, `niro upgrade`, which does all of it.
|
|
274
|
+
|
|
275
|
+
### `niro upgrade` gets the new version AND the things a new version cannot bring with it
|
|
276
|
+
|
|
277
|
+
Upgrading was `npm install -g @niroai/niro@latest`, and every release note in this file said so and
|
|
278
|
+
nothing else. That command is not enough, and has never been enough. The MCP entry is fine: it runs
|
|
279
|
+
plain `niro` off your `PATH` with no version in it, so the new server is picked up for free. But the
|
|
280
|
+
hooks, the skills and the CLAUDE.md/AGENTS.md/rules text are written into your home folder as fixed
|
|
281
|
+
text at install time, not read from the package as it runs. npm cannot touch them. Nothing told you
|
|
282
|
+
that, and nothing re-ran the installer, so a machine that installed once in June was still running
|
|
283
|
+
June's instructions in September while reporting the current version. The background watcher had the
|
|
284
|
+
same problem from the other end: it is a long-running process holding the code it loaded at start,
|
|
285
|
+
so replacing files on disk left it on the old version until something restarted it.
|
|
286
|
+
|
|
287
|
+
`niro upgrade` does all three. It replaces the package, re-runs the installer for every assistant it
|
|
288
|
+
can prove is configured, and restarts the watcher if it is running.
|
|
289
|
+
|
|
290
|
+
It runs as two processes, and the reason is worth writing down. Every command in this CLI is loaded
|
|
291
|
+
lazily, inside its own action handler, and `npm install -g` replaces the folder those files are
|
|
292
|
+
loaded from. One process that ran npm and then carried on would be running the modules it loaded
|
|
293
|
+
before the swap alongside modules loaded after it. So phase one runs npm, executes the NEW binary
|
|
294
|
+
with `--finish`, and stops, requiring nothing in between; phase two is a fresh process reading the
|
|
295
|
+
new tree. `niro start` has always dodged the same trap the same way, by only installing globally
|
|
296
|
+
when it is running from the npx cache and then handing over.
|
|
297
|
+
|
|
298
|
+
Which assistants to refresh is worked out by looking, because nothing on disk records what you
|
|
299
|
+
chose. All 14 clients are covered, because the installer leaves a file this repo owns in every case:
|
|
300
|
+
eight write a JSON config whose path `setup.js` builds, and the other six also get a marker appended
|
|
301
|
+
to a markdown file written here and stripped by `teardown.js` (`~/.claude/CLAUDE.md`,
|
|
302
|
+
`~/.gemini/GEMINI.md`, `~/.copilot/copilot-instructions.md`, `~/.factory/AGENTS.md`), plus codex's
|
|
303
|
+
`[mcp_servers.niro]` table and goose's `extensions:` entry. The marker is appended only after the
|
|
304
|
+
registration command succeeded, so its presence means the client really was configured. Detection is
|
|
305
|
+
read-only, and it is deliberately strict: a client whose entry is `enabled: false` or
|
|
306
|
+
`disabled: true` is one you switched off, so it is left alone rather than switched back on; a
|
|
307
|
+
marker has to be on a line of its own, because a sentence mentioning it is not an install; and every
|
|
308
|
+
path has to be absolute.
|
|
309
|
+
|
|
310
|
+
`niro upgrade --finish` on its own skips npm and only refreshes the assistant files and the watcher,
|
|
311
|
+
which is what you want if you already ran `npm install -g` by hand.
|
|
312
|
+
|
|
313
|
+
Without a usable home folder it checks nothing and says so. Every path would otherwise resolve
|
|
314
|
+
against the current folder, which is how the reverted serve-time self-heal came to write into a
|
|
315
|
+
project's own `.claude/settings.json` (see the 2026-07-30 revert). "Usable" means absolute, and it
|
|
316
|
+
also means rejecting a `HOME` set to the empty string even when `USERPROFILE` would fill in, because
|
|
317
|
+
four of the client path builders read `HOME` directly and would otherwise hand back a relative path.
|
|
318
|
+
This command never writes a settings file itself: refreshing hooks means spawning the existing
|
|
319
|
+
installer, once per client.
|
|
320
|
+
|
|
321
|
+
It refuses to upgrade a copy you are not running. The first real run of this command was from a
|
|
322
|
+
checkout linked with `npm link`, and `npm install -g` deleted that link and put the published
|
|
323
|
+
package in its place. So before npm is allowed to touch anything, `niro upgrade` looks at which
|
|
324
|
+
copy is running: the global package folder being a symlink means `npm link`, and it stops and says
|
|
325
|
+
so; a checkout or an npx copy that is not the global install stops too. `--force` overrides either.
|
|
326
|
+
|
|
327
|
+
It also asks the version it just installed whether it has an `upgrade` command before handing over
|
|
328
|
+
to it, because the newest published version can predate the one you are running. Without that
|
|
329
|
+
check the hand-over fails with a bare `error: unknown command 'upgrade'` and the advice printed is
|
|
330
|
+
to re-run a command that does not exist. Now it prints the two commands that do work.
|
|
331
|
+
|
|
332
|
+
Failures are visible. Each one has its own exit code (npm failed, no binary after npm, hand-over
|
|
333
|
+
failed, refresh incomplete), an interrupted npm is reported as interrupted rather than as "nothing
|
|
334
|
+
changed", a hand-over that cannot start prints why, and the hand-over carries a marker so a version
|
|
335
|
+
that does not understand `--finish` cannot loop back into running npm again.
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
## 0.12.0 — 2026-09-06
|
|
339
|
+
|
|
340
|
+
### Before an edit to indexed source, Claude is asked to map the method and to verify those flows afterwards
|
|
341
|
+
|
|
342
|
+
Every instruction file niro writes tells the assistant to map who depends on a method before changing
|
|
343
|
+
it. In practice the assistant finds the file, reads it, and edits; the rule is read early in a session
|
|
344
|
+
and forgotten by the middle of it. And the map was always meant for afterwards: the point is to go back
|
|
345
|
+
to the callers and flows once the edit is made and check they still hold.
|
|
346
|
+
|
|
347
|
+
Claude Code now gets a third niro hook, on Edit and Write. Before an edit to source in an indexed repo
|
|
348
|
+
it puts a short reminder in front of the assistant: decide whether the change can alter behaviour (a log
|
|
349
|
+
line or a comment cannot, say so and go ahead); if it can, map who depends on the method now with
|
|
350
|
+
get_blast_radius or get_impact_analysis and keep that list as the checklist for after the edit; and do
|
|
351
|
+
not re-query the graph to verify, because it updates only after the edit is synced and rebuilt. If an
|
|
352
|
+
impact call already named the file this session, the reminder instead says to go back to that list
|
|
353
|
+
after the edit. Each reminder appears once per file per session. It is advisory: it never approves or
|
|
354
|
+
refuses an edit. The evidence comes from the bridge, which writes one line per answered impact call
|
|
355
|
+
under `~/.niro/impact/`. New files, tests, config and docs are never mentioned. Off switch:
|
|
356
|
+
`touch ~/.niro/impact-gate-off`. `niro mcp uninstall` removes the hook and the evidence files.
|
|
357
|
+
|
|
358
|
+
The same three points were added to every instruction surface niro writes: the rule sentence in each
|
|
359
|
+
client's always-on file, the niro skill, the Cursor, Cline and Kiro rules, and the PostCompact and
|
|
360
|
+
prompt hooks. The clients without hooks read them there. Details: `docs/usecases/impact-gate.md`.
|
|
361
|
+
|
|
362
|
+
### `niro mcp install` now refreshes Claude Code hooks on a machine where niro was already set up
|
|
363
|
+
|
|
364
|
+
Re-running the install on a machine with an existing registration failed at the first step, because
|
|
365
|
+
`claude mcp add` refuses a name that already exists, and the installer read that refusal as "Claude
|
|
366
|
+
Code is not installed" and stopped before writing any hook or the CLAUDE.md section. So the step the
|
|
367
|
+
out-of-date notice asks for did nothing for anyone who had installed once. The installer now reads the
|
|
368
|
+
existing registration first: same URL, it keeps it; a different `--url`, it replaces it; none, it adds
|
|
369
|
+
it. Hooks and sections are written in every case.
|
|
370
|
+
|
|
371
|
+
### CommonJS and ES-module files (.cjs, .mjs, .cts, .mts) are now uploaded for indexing
|
|
372
|
+
|
|
373
|
+
When niro decided which files to upload for indexing, it skipped `.cjs`, `.mjs`, `.cts` and `.mts`,
|
|
374
|
+
so a `build.cjs` or an `index.mjs` was never indexed and never appeared in the graph. Those four
|
|
375
|
+
extensions are now included, matching the server's own file filter.
|
|
376
|
+
|
|
377
|
+
### The niro rules now name get_exception_flow
|
|
378
|
+
|
|
379
|
+
The explore-first rule niro writes into each client now mentions `get_exception_flow`, the tool that
|
|
380
|
+
reports which exceptions can escape a method (and who must handle one). It is served by the niro
|
|
381
|
+
backend; this only makes the assistants aware it exists.
|
|
382
|
+
|
|
383
|
+
## 0.11.0 — 2026-09-03
|
|
384
|
+
|
|
385
|
+
### The watcher stops pausing and resuming your uploads over and over
|
|
386
|
+
|
|
387
|
+
The background watcher holds back uploads from a folder whose branch disagrees with the branch its
|
|
388
|
+
private copy is labelled with, so two branches cannot be written into one copy. One developer's log
|
|
389
|
+
carried 306 pauses against 286 resumes. Those counts being nearly equal is the whole story: it was
|
|
390
|
+
toggling, not failing 306 times.
|
|
391
|
+
|
|
392
|
+
Once a minute the watcher asks which branch the copy is on. A request that failed returned nothing,
|
|
393
|
+
which was read as "everything is fine again": it printed "resuming uploads" and cleared the pause, so
|
|
394
|
+
the next check paused and warned all over again. It also let a folder that genuinely should not be
|
|
395
|
+
uploading start writing again on any slow request. Now a question the server could not answer changes
|
|
396
|
+
nothing at all, in either direction. The same six alternating checks went from 3 warnings and 3 resumes
|
|
397
|
+
down to 1 and 0.
|
|
398
|
+
|
|
399
|
+
The message was wrong too. When it paused it looked for another folder on the same *project* and blamed
|
|
400
|
+
it. A project can hold several repositories and each folder can have its own, so it regularly named a
|
|
401
|
+
folder that was writing nowhere near yours and told you to discard it. It now matches on the repository
|
|
402
|
+
actually being written to, so it either names the folder that really shares your copy or tells you
|
|
403
|
+
plainly that you switched branch in that folder yourself.
|
|
404
|
+
|
|
405
|
+
**The pause itself is unchanged, deliberately.** While a folder's branch and its copy's label disagree,
|
|
406
|
+
your assistant's questions are not sent to that copy anyway, so uploading into it would only fill it
|
|
407
|
+
with the wrong branch's files. Run `niro new-temp-project` in that folder to put it back in step.
|
|
408
|
+
|
|
409
|
+
### Servlet apps declare their endpoints in a file that was never sent
|
|
410
|
+
|
|
411
|
+
A Java web app written before Spring Boot lists its whole HTTP surface in `WEB-INF/web.xml` rather than
|
|
412
|
+
in annotations. That file was missing from the set the CLI offers, so such a project indexed as a
|
|
413
|
+
servlet application with no endpoints at all. Measured on one repository: 11 real endpoints, 0 detected,
|
|
414
|
+
against an otherwise complete and healthy index.
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
### Stale pointers clean themselves up
|
|
418
|
+
|
|
419
|
+
Delete a project, or let an unused private copy expire, and your machine is never told. The folder
|
|
420
|
+
carries on naming something that is gone. That is what makes `niro new-temp-project` in another
|
|
421
|
+
worktree say the slot is taken, naming a copy that no longer exists.
|
|
422
|
+
|
|
423
|
+
That command never shipped: it went in under this same Unreleased section and has been renamed since,
|
|
424
|
+
so `niro doctor sync-registry` is its first public name. It also only helped if you remembered to run
|
|
425
|
+
it. Now the background watcher runs it every six hours by itself, and it covers two more cases:
|
|
426
|
+
|
|
427
|
+
- **Ordinary registrations**, not just private copies. A folder registered against a deleted project
|
|
428
|
+
used to sit there for ever: the daemon retried it on every start and nothing could clear it. One
|
|
429
|
+
developer asked whether they should edit `~/.niro/projects.json` by hand. They were right that there
|
|
430
|
+
was no supported way.
|
|
431
|
+
- **`.niro-project` files** naming something the server does not recognise. Nothing has ever surfaced
|
|
432
|
+
these, and they are not inert: that file is read before your git remote, so a stale one keeps
|
|
433
|
+
answering. They are REPORTED, never deleted — the file can hold a project's name rather than its id,
|
|
434
|
+
and a renamed project is indistinguishable from a deleted one from the client, so the right repair
|
|
435
|
+
(update it, or remove it) is yours to choose.
|
|
436
|
+
|
|
437
|
+
**What it refuses to do matters more than what it does.** A deleted project is not enough to
|
|
438
|
+
unregister a folder, because a project can be deleted while its repos are moved to another one and
|
|
439
|
+
that folder is still syncing fine, so the repo is checked too. On the machine this was built on that
|
|
440
|
+
took the count from 18 folders to 1. And a `.niro-project` may hold an *alias* rather than an id, which
|
|
441
|
+
the direct lookup reports as "not found" for a project that is perfectly alive; the account's project
|
|
442
|
+
list is now the authority. Both of those were caught by dry runs against real data after the unit tests
|
|
443
|
+
were green.
|
|
444
|
+
|
|
445
|
+
The watcher remembers when it last swept, so restarting it does not re-run the job, and a `niro watch`
|
|
446
|
+
left in a terminal cannot sweep at the same moment as the background one. It stays silent when there
|
|
447
|
+
was nothing to do.
|
|
448
|
+
|
|
449
|
+
### When you run out of private copies, it says where they are
|
|
450
|
+
|
|
451
|
+
The server already tells you which branches are holding your copies. It cannot tell you where those
|
|
452
|
+
branches are checked out, because that is a fact about your machine. The CLI now adds the folder next
|
|
453
|
+
to each branch, so you can go straight to one and discard it.
|
|
454
|
+
|
|
455
|
+
### Multi-line error messages are no longer cut off
|
|
456
|
+
|
|
457
|
+
Errors printed only their first line, so any message built over several lines lost the rest. Two were
|
|
458
|
+
affected: "Multiple projects match this repo — pass --project:" showed no projects, and the
|
|
459
|
+
working-copy-limit refusal showed no folders. The text was always correct and simply never reached the
|
|
460
|
+
screen. Found by running the real CLI against a real backend until it refused; no unit test could have
|
|
461
|
+
caught it, because both features assert on the message and the message was right.
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
### `niro doctor sync-registry` tidies up copies the server deleted for you
|
|
465
|
+
|
|
466
|
+
A temporary project can go without this machine ever being told: somebody deletes it in the web
|
|
467
|
+
app, or the sweeper reaps it after two weeks. The folder keeps a registration pointing at an id
|
|
468
|
+
that resolves to nothing. That is not harmless, because it is exactly what makes
|
|
469
|
+
`niro new-temp-project` in a second worktree refuse with "already held by another folder".
|
|
470
|
+
|
|
471
|
+
`niro discard-temp-project` already repaired one folder in that state. What nothing did was find
|
|
472
|
+
them. On the machine this was built on, 13 of 14 registered copies had gone and every one of the
|
|
473
|
+
14 folders was still pointing at a dead id.
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
niro doctor sync-registry # check and repair
|
|
477
|
+
niro doctor sync-registry --dry-run # list, change nothing
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
It asks the server once per copy and moves the folders whose copy has gone back onto their
|
|
481
|
+
original project. Three things it will not do. It never treats an unreachable server as an
|
|
482
|
+
answer: only a definitive "Project not found" counts, so a 500, a timeout, or a project you are
|
|
483
|
+
simply not allowed to see leaves every entry exactly as it was. It never unregisters a folder,
|
|
484
|
+
because the watch daemon still needs somewhere to sync it. And it never guesses: a copy that no
|
|
485
|
+
folder recorded a source project for is reported and left alone.
|
|
486
|
+
|
|
487
|
+
`--json` for scripts, `--dry-run` to look first, `-y` to skip the prompt. At a terminal, `--json`
|
|
488
|
+
asks for one of the last two rather than writing without a prompt it cannot show.
|
|
489
|
+
|
|
490
|
+
Two guards came out of review and apply to `niro discard-temp-project` as well, because both commands
|
|
491
|
+
now share one repair routine. A folder re-registered by another process while the command runs is
|
|
492
|
+
left alone and reported, instead of being overwritten by a stale snapshot. And within a copy that
|
|
493
|
+
covers several folders, the one holding the record of the original project is always written last, so
|
|
494
|
+
a run interrupted half way through can still be finished by running it again.
|
|
495
|
+
|
|
496
|
+
Folders that had no local registration before the copy was made are now listed separately from the
|
|
497
|
+
ones that keep syncing. They go back to not having one, which is where they started, but calling that
|
|
498
|
+
"repaired" and stopping there implied the daemon was still watching them.
|
|
499
|
+
|
|
500
|
+
## 0.10.0 — 2026-08-28
|
|
501
|
+
|
|
502
|
+
One command to get started, and a way to check what Niro is worth.
|
|
503
|
+
|
|
504
|
+
### Upgrading
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
npm install -g @niroai/niro@latest
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Nothing extra is needed from 0.9.0. Every existing command works exactly as before; the new
|
|
511
|
+
ones sit alongside them.
|
|
512
|
+
|
|
513
|
+
**`niro start` replaces four steps with one.** Getting started used to mean learning a sequence
|
|
514
|
+
from a README: install, `niro login`, change into the right folder, `niro init`, then
|
|
515
|
+
`niro mcp install`. Each step was fine on its own; the sequence was the problem, because the
|
|
516
|
+
entry point was documentation and documentation is optional. One user ran `niro init` in their
|
|
517
|
+
home folder. Typing `niro` on its own printed an eighteen-command help listing, which is the
|
|
518
|
+
worst possible first screen for someone who knows none of them.
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
npx @niroai/niro start
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
That signs you in, installs itself properly, shows you what it is about to index and asks
|
|
525
|
+
before uploading anything, builds the graph, connects your assistant, and offers to measure
|
|
526
|
+
the difference. Run it again in any other repo and it skips whatever is already done. It opens
|
|
527
|
+
by saying what Niro does and roughly how long this takes, and it asks whether you have an
|
|
528
|
+
access code before it installs anything, so nobody without one fills in a form that rejects
|
|
529
|
+
them at the end.
|
|
530
|
+
|
|
531
|
+
Two screens are worth calling out. Before uploading, it shows you the folder, the build files
|
|
532
|
+
it recognised, the module count and the git branch, and waits: the one screen worth reading
|
|
533
|
+
rather than skipping. And a folder it refuses, such as your home folder, now asks for a
|
|
534
|
+
different one instead of exiting, which is what made that mistake so easy to make.
|
|
535
|
+
|
|
536
|
+
`niro init`, `niro login` and `niro mcp install` are untouched. They are the scriptable path,
|
|
537
|
+
they take flags, and `niro start` simply calls them in order.
|
|
538
|
+
|
|
539
|
+
**`niro ab` measures what Niro changes.** Rather than take our word for it, Niro can run
|
|
540
|
+
*itself off* for half your sessions and show you the two halves side by side. While a window is
|
|
541
|
+
open each session lands in an "on" or "off" bucket by a deterministic split; the off half works
|
|
542
|
+
without Niro.
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
niro ab enable # start a 14-day window (it states the scope and asks first)
|
|
546
|
+
niro ab report # the with-Niro / without-Niro comparison
|
|
547
|
+
niro ab disable # stop early
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
It applies to your whole account, not just the repo you are standing in, and it says so before
|
|
551
|
+
starting. `niro start` offers it once, at the end of a first setup, and defaults to no.
|
|
552
|
+
|
|
553
|
+
**Setup prints about half as much.** A real run printed roughly 230 lines and only half were
|
|
554
|
+
ours. PM2's process table and generated systemd unit file, about 55 lines, are captured now and
|
|
555
|
+
shown only if something fails. The assistant list shows five clients and a count rather than all
|
|
556
|
+
fourteen with their config paths (`niro mcp install --list` for the rest). `niro init` stopped
|
|
557
|
+
telling you to run `niro mcp install` two seconds before the wizard did it for you.
|
|
558
|
+
|
|
559
|
+
**Output is indented consistently.** Printed lines used eight different indents across the CLI,
|
|
560
|
+
so nothing said what belonged to what. Everything now sits on one ladder: headings and step
|
|
561
|
+
markers, their messages one level in, detail one level further. Output from `niro init`, and
|
|
562
|
+
from the editor CLIs it shells out to, is folded into that ladder instead of printing flush
|
|
563
|
+
left in the middle of a step.
|
|
564
|
+
|
|
565
|
+
**Fixes.** The spinner and the upload progress bar wrote straight to the terminal and so
|
|
566
|
+
ignored indentation entirely. `niro mcp install --list` was added. Two em-dashes and one
|
|
567
|
+
invented URL were removed from text you read on screen.
|
|
568
|
+
|
|
569
|
+
## 0.9.0 — 2026-08-26
|
|
570
|
+
|
|
571
|
+
One change to how the client waits, and two fixes for cases where Niro and your machine
|
|
572
|
+
disagreed about what still exists.
|
|
573
|
+
|
|
574
|
+
### Upgrading
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
npm install -g @niroai/niro@latest
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Nothing extra is needed from 0.8.0. Against an older server the waiting change simply does not
|
|
581
|
+
engage, because the client decides from the server's answer rather than from a version.
|
|
582
|
+
|
|
583
|
+
**Creating a working copy no longer times out.** It takes 44 to 60 seconds, and it used to run
|
|
584
|
+
inside a single request, against a gateway that gives up at 60. Three at once on 26 August all
|
|
585
|
+
came back as gateway error pages, and because such an error says nothing about whether the work
|
|
586
|
+
finished, the real cause underneath went unread for an afternoon. The client now asks the server
|
|
587
|
+
to do it in the background and waits for it to finish, showing progress. A dropped connection
|
|
588
|
+
while waiting is not treated as a failure, because the copy is still being made on the server; an
|
|
589
|
+
expired login is, because waiting cannot fix it.
|
|
590
|
+
|
|
591
|
+
**Discarding a working copy the server had already deleted.** A working copy can go away
|
|
592
|
+
without your machine being told: someone deletes it in the web UI, or the server reaps it
|
|
593
|
+
after it has sat idle for two weeks. The folder on your machine kept a registration pointing
|
|
594
|
+
at it, and `niro discard-temp-project`, which is what clears that registration, failed before
|
|
595
|
+
it got that far. The registration is also what makes `niro new-temp-project` in a second
|
|
596
|
+
worktree refuse, advising you to run discard in the other folder, so the advice named the one
|
|
597
|
+
command that could not work. Discard now treats a project the server says is missing as
|
|
598
|
+
nothing left to delete: it skips the server call and does the local half, pointing the folder
|
|
599
|
+
back at the original project. A server it could not reach, or one that refuses because the
|
|
600
|
+
copy belongs to another developer, still fails loudly and changes nothing, which matters
|
|
601
|
+
because the server answers both of those with the same status code as a deleted project.
|
|
602
|
+
|
|
603
|
+
**A detached checkout stopped Niro answering.** During `git bisect`, or after checking out a
|
|
604
|
+
tag, your repo is on no branch. `git rev-parse --abbrev-ref HEAD` does not fail there, it
|
|
605
|
+
prints the word `HEAD`, and Niro passed that on as though it were a branch name. Nothing
|
|
606
|
+
indexes a branch called `HEAD`, so the server treated every such question as coming from a
|
|
607
|
+
diverged branch and declined to answer it, for the whole bisect. Niro now reports no branch,
|
|
608
|
+
which is what the server already had a rule for, and answers from the indexed project.
|
|
609
|
+
|
|
610
|
+
## 0.8.0 — 2026-08-26
|
|
611
|
+
|
|
612
|
+
Needed before a server has the layered store turned on. On an older client the new
|
|
613
|
+
server-side setting is not just unusable, it is misleading.
|
|
614
|
+
|
|
615
|
+
### Upgrading
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
npm install -g @niroai/niro@latest
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Nothing extra is needed from 0.7.0.
|
|
622
|
+
|
|
623
|
+
### Why this release exists
|
|
624
|
+
|
|
625
|
+
A working copy is a private index of the branch in your folder, including edits you have not
|
|
626
|
+
committed. The server can now keep several of them per developer, and can store one as a
|
|
627
|
+
layer over the shared project rather than a full copy. Three things in 0.7.0 get in the way.
|
|
628
|
+
|
|
629
|
+
**A second working copy was refused on your own machine.** 0.7.0 decided by itself that one
|
|
630
|
+
folder already held the project, and never asked the server. Once the server allows several,
|
|
631
|
+
that refusal is simply wrong. Worse, run unattended, by an agent or a script, it threw an
|
|
632
|
+
error advising `niro discard-temp-project` in the other folder. Following that advice
|
|
633
|
+
destroys a working copy that did not need destroying. The client now asks the server and
|
|
634
|
+
does what it says.
|
|
635
|
+
|
|
636
|
+
**A git worktree of a repo with no remote could not be onboarded at all.** Niro records
|
|
637
|
+
onboarding against the folder that was onboarded, which is the main checkout. A linked
|
|
638
|
+
worktree has no entry of its own, so the repo could not be named and `niro new-temp-project`
|
|
639
|
+
failed. It now resolves the main working tree through `git rev-parse --git-common-dir`.
|
|
640
|
+
That combination, a worktree of a remote-less repo, is the layered store's main use case.
|
|
641
|
+
|
|
642
|
+
**Large files were dropped from a working copy without telling you.** The size warning
|
|
643
|
+
existed only in `niro init`. A developer ran `niro new-temp-project` and lost their three
|
|
644
|
+
largest source files, the ones the whole branch was about. Niro then answered questions
|
|
645
|
+
about those files from the shared project's older copy, confidently, with nothing on screen
|
|
646
|
+
to say the working copy did not contain them. The warning now runs on both commands, and it
|
|
647
|
+
quotes the limit the server reports rather than a number compiled into the client, which
|
|
648
|
+
said 256 KiB while a server raised to 2 MiB was accepting the file happily.
|
|
649
|
+
|
|
650
|
+
### Compatibility
|
|
651
|
+
|
|
652
|
+
Against an older server, the per-file limit is not reported and the client falls back to its
|
|
653
|
+
built-in figure, so the warning quotes an estimate rather than the server's real cap. Nothing
|
|
654
|
+
else changes.
|
|
655
|
+
|
|
656
|
+
## 0.7.0 — 2026-08-17
|
|
657
|
+
|
|
658
|
+
A data-loss release. If you use `niro watch`, upgrade.
|
|
659
|
+
|
|
660
|
+
### Upgrading
|
|
661
|
+
|
|
662
|
+
```bash
|
|
663
|
+
npm install -g @niroai/niro@latest
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Nothing extra is needed from 0.6.0. **From 0.4.x or earlier, upgrade urgently** — every fix
|
|
667
|
+
below is client-side, so a server update alone does not protect you.
|
|
668
|
+
|
|
669
|
+
### Why this release exists
|
|
670
|
+
|
|
671
|
+
On 31 July a repository lost 494 source files. The client reported them deleted, the server
|
|
672
|
+
removed the working copy and the durable backup, and the client's own record then said
|
|
673
|
+
"deleted", so nothing ever re-sent them. Eleven days passed before anyone noticed.
|
|
674
|
+
|
|
675
|
+
The cause was a `git checkout`. Git removed a directory of test files and wrote it straight
|
|
676
|
+
back a moment later. In that gap the watcher looked, saw nothing, and reported 249 files
|
|
677
|
+
deleted. Everything below follows from that one sentence: **the watcher believed a file was
|
|
678
|
+
gone because it could not see it at one instant.**
|
|
679
|
+
|
|
680
|
+
### Fixed — the client no longer infers a deletion from a failure to look
|
|
681
|
+
|
|
682
|
+
- **Deletes now wait for the folder to go quiet.** A file that disappears is queued, not
|
|
683
|
+
reported. One timer per folder resets on every new disappearance, so a three-file checkout
|
|
684
|
+
holds for a few seconds and a ninety-second rebase holds for ninety seconds. When the folder
|
|
685
|
+
settles, whatever is still missing is deleted and whatever came back is dropped.
|
|
686
|
+
|
|
687
|
+
This covers `git checkout`, `git stash`, rebases, `npm ci`, `make clean`, a Docker remount and
|
|
688
|
+
editors that save by deleting and rewriting — without needing to know which of them is
|
|
689
|
+
happening. A real deletion now reaches the server about five seconds later than before.
|
|
690
|
+
Set `NIRO_DELETE_SETTLE_MS` to change that.
|
|
691
|
+
|
|
692
|
+
- **A file that cannot be READ is no longer treated as deleted.** A permission error, a
|
|
693
|
+
transient lock, a file being rewritten or simply too many open files all made the hash return
|
|
694
|
+
nothing, and the file was then reported gone. It now keeps its previous state.
|
|
695
|
+
|
|
696
|
+
- **A DIRECTORY that cannot be read is no longer treated as "everything under it was deleted".**
|
|
697
|
+
The same mistake one level up, and worse: an unreadable folder lists as empty, which is
|
|
698
|
+
indistinguishable from one whose files were all removed. Renaming a watched folder and
|
|
699
|
+
restarting reported the entire project as deleted, in a pass that reported itself successful.
|
|
700
|
+
An unreadable root now aborts the pass; an unreadable subdirectory keeps its files while the
|
|
701
|
+
rest of the tree syncs.
|
|
702
|
+
|
|
703
|
+
- **A delete the server refuses is retried instead of forgotten.** It was marked done regardless
|
|
704
|
+
of the outcome, and once out of the local record it could never be re-attempted — so a
|
|
705
|
+
transient error became permanent.
|
|
706
|
+
|
|
707
|
+
- **`niro discard-temp-project` no longer loses the work done in the temporary project.** The
|
|
708
|
+
file record followed the folder back to the source repository, so the source appeared to
|
|
709
|
+
already hold edits it had never received, and the next sync uploaded nothing. Discarding now
|
|
710
|
+
clears that record and re-uploads once.
|
|
711
|
+
|
|
712
|
+
- **Excluding a path removes it again.** A guard added in the same work stopped an excluded file
|
|
713
|
+
ever being removed from the index, which mattered most for a path excluded *because* it holds
|
|
714
|
+
secrets.
|
|
715
|
+
|
|
716
|
+
- **No delete calls for files that were never uploaded.** Editor scratch files are rejected on
|
|
717
|
+
the way in, but a delete was still sent when the editor renamed them away — 21% of all file
|
|
718
|
+
events on one real repository, every one of them pointless traffic.
|
|
719
|
+
|
|
720
|
+
### Fixed — the retry flood
|
|
721
|
+
|
|
722
|
+
- **The 60-second re-scan now backs off.** It retried at full rate forever, so a two-day server
|
|
723
|
+
problem became roughly 1,400 failed requests a day per folder and buried the real error. The
|
|
724
|
+
delay now doubles to a 30-minute ceiling and resets on success. It never gives up entirely, so
|
|
725
|
+
a recovered server is picked up within half an hour on its own.
|
|
726
|
+
|
|
727
|
+
- **A pass whose file operations failed is no longer counted as a recovery**, which previously
|
|
728
|
+
reset the backoff mid-incident and printed a green "Sync recovered" while the outage continued.
|
|
729
|
+
|
|
730
|
+
### Changed — worktrees and temporary projects
|
|
731
|
+
|
|
732
|
+
You get one temporary project per project, and creating another moves it rather than adding one.
|
|
733
|
+
With several worktrees open that meant they silently took it from each other.
|
|
734
|
+
|
|
735
|
+
- A second worktree is no longer told to create one. It is told which branch currently holds it
|
|
736
|
+
and to use its own tools for that folder.
|
|
737
|
+
- `niro discard-temp-project` asks before moving the slot away from another folder.
|
|
738
|
+
- A folder that has lost the slot stops uploading, instead of blending two branches into one copy.
|
|
739
|
+
- A branch switch is now told apart from another folder taking the slot.
|
|
740
|
+
- `niro info` shows which local folder and branch syncs each project, and marks the one you are
|
|
741
|
+
standing in.
|
|
742
|
+
|
|
743
|
+
### Also fixed
|
|
744
|
+
|
|
745
|
+
- **A sync finishing after the folder was re-pointed no longer writes its stale record.** A pass takes
|
|
746
|
+
minutes on a large tree; if `niro new-temp-project` or `niro discard-temp-project` re-pointed the
|
|
747
|
+
folder while it ran, its results were written anyway, against a repository they did not describe.
|
|
748
|
+
For discard that undid the clean slate above and the re-upload was skipped. The record now belongs
|
|
749
|
+
to one repository and a pass that no longer matches is dropped, the same way a folder release has
|
|
750
|
+
always been handled.
|
|
751
|
+
|
|
752
|
+
- **A folder whose root cannot be read now says so once**, and says when it can be read again. It used
|
|
753
|
+
to repeat the same error every sixty seconds for as long as the folder stayed away.
|
|
754
|
+
|
|
755
|
+
## 0.6.0 — 2026-08-11
|
|
756
|
+
|
|
757
|
+
### Upgrading
|
|
758
|
+
|
|
759
|
+
```bash
|
|
760
|
+
npm install -g @niroai/niro@latest
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
If you are coming from 0.3.x you also still need `niro mcp install` once per assistant — see
|
|
764
|
+
the 0.5.0 notes below. Upgrading from 0.5.0 needs nothing extra.
|
|
765
|
+
|
|
766
|
+
### Changed — read this before upgrading
|
|
767
|
+
|
|
768
|
+
- **`--api-url` now applies to `niro login` only.** Every other command refuses it and exits 1.
|
|
769
|
+
|
|
770
|
+
It was a global flag, but only half of the code honoured it. Ordinary API calls took it,
|
|
771
|
+
while the file-upload path built its own address from your stored credentials and never saw
|
|
772
|
+
it. So `niro init --api-url http://localhost:8095` while logged in to another backend listed
|
|
773
|
+
projects from localhost and uploaded your source to the backend you were logged in to. One
|
|
774
|
+
command, two servers, no warning.
|
|
775
|
+
|
|
776
|
+
The backend is a property of a session, not of a single command: your credentials, tokens,
|
|
777
|
+
repo ids and sync records are all scoped to one backend, so a per-command override could
|
|
778
|
+
only ever mix them. Choose it once instead:
|
|
779
|
+
|
|
780
|
+
```bash
|
|
781
|
+
niro login --api-url <url> # log in to that backend
|
|
782
|
+
NIRO_API_URL=<url> niro <command> # or set it for every command
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
`NIRO_API_URL` and the `apiUrl` config setting still work everywhere, because both feed the
|
|
786
|
+
same resolution the uploader uses. **If you script `--api-url` on any command other than
|
|
787
|
+
`login`, that script now fails with a message** telling it which of the two forms to use.
|
|
788
|
+
|
|
789
|
+
- **`niro init` and `niro add repo` now refuse a folder that is not a project.** Onboarding a
|
|
790
|
+
folder used to work anywhere, including your home folder, which uploaded source from every
|
|
791
|
+
project on the machine, pinned `~/.niro-project` and started a watcher over your whole home
|
|
792
|
+
directory. Two rules now apply before anything is uploaded:
|
|
793
|
+
|
|
794
|
+
- Your home folder, the filesystem root, a parent of your home folder and the system folders
|
|
795
|
+
are refused outright. `--force` does **not** override this.
|
|
796
|
+
- Any other folder must look like a project: a build file (`package.json`, `pom.xml`, `go.mod`,
|
|
797
|
+
`*.csproj`, and the rest of the set the indexer already understands) at the folder itself or
|
|
798
|
+
just below it. A monorepo root whose build files sit one or two levels down is fine.
|
|
799
|
+
|
|
800
|
+
**If you script either command**, a folder with no build file now exits `1` where it used to
|
|
801
|
+
onboard. Pass `--force` for that case. `niro add repo` gains `--force` for this; on `niro init`
|
|
802
|
+
the existing `--force` now covers it too.
|
|
803
|
+
|
|
804
|
+
A folder this backend already has a repo for is exempt from the build-file rule, so an existing
|
|
805
|
+
sync keeps working. The home and root refusal still applies to it.
|
|
806
|
+
|
|
807
|
+
The check runs before the login and before any server call, so a refused folder leaves nothing
|
|
808
|
+
behind: no registered repo, no orphan project, no pin file, no watcher.
|
|
809
|
+
|
|
810
|
+
- **`niro init` now notices nested repos deeper in the tree.** The "this folder contains repos
|
|
811
|
+
already indexed in Niro" refusal only looked two levels down, so it never fired for the common
|
|
812
|
+
`~/projects/<host>/<org>/<repo>` layout. It now looks four.
|
|
813
|
+
|
|
814
|
+
## 0.5.0 — 2026-08-06
|
|
815
|
+
|
|
816
|
+
### Upgrading from 0.3.x — one manual step
|
|
817
|
+
|
|
818
|
+
```bash
|
|
819
|
+
npm install -g @niroai/niro@latest
|
|
820
|
+
niro mcp install # once per assistant you use
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
**The second command is required.** niro's hooks and instruction files are written to your disk at
|
|
824
|
+
install time, so upgrading the package alone leaves the old versions in place — including the
|
|
825
|
+
once-per-session reminder this release fixes. Restart your assistant afterwards; hooks are read at
|
|
826
|
+
startup.
|
|
827
|
+
|
|
828
|
+
Nothing else is required.
|
|
829
|
+
|
|
830
|
+
### Added
|
|
831
|
+
|
|
832
|
+
- **You can now see what Niro did, without asking.** At the end of a task you get one line:
|
|
833
|
+
|
|
834
|
+
```
|
|
835
|
+
[NIRO] Mapped 29 callers across 8 files that a grep would have missed · 3 calls instead of ~14 searches
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
The call count is measured by the server. The number of searches it stands in for, and the
|
|
839
|
+
description, are the assistant's own account of its own work, so that number always carries a `~`
|
|
840
|
+
to mark it as an estimate. We would rather you trusted a smaller number than doubted a bigger one.
|
|
841
|
+
|
|
842
|
+
On a turn where Niro found nothing useful, the description is dropped rather than invented, and
|
|
843
|
+
you see the bare call count.
|
|
844
|
+
|
|
845
|
+
- **It also tells you when Niro went unused.** On a turn where the assistant searched your code
|
|
846
|
+
without asking Niro, you get `[NIRO] Not consulted: this turn used grep and file reads instead`.
|
|
847
|
+
Until now a skipped turn looked exactly like Niro not being installed, which meant every one of
|
|
848
|
+
our failures was invisible. Nothing is printed after a turn that did no code work.
|
|
849
|
+
|
|
850
|
+
- **On by default, including on upgrade.** Turn it off with `niro mcp receipt --off` and back on
|
|
851
|
+
with `--on`; run it with no flags to see the current state. The setting is per machine.
|
|
852
|
+
|
|
853
|
+
Works the same in all twelve supported assistants: the line travels back inside the
|
|
854
|
+
`mark_task_complete` tool result and your assistant prints it, wrapped in backticks so it renders
|
|
855
|
+
as code rather than as more of its own prose. If the assistant never calls `mark_task_complete`,
|
|
856
|
+
no line appears.
|
|
857
|
+
|
|
858
|
+
### Fixed
|
|
859
|
+
|
|
860
|
+
- **Codex no longer asks permission for every Niro tool call.** Two causes, both fixed by re-running
|
|
861
|
+
`niro mcp install`:
|
|
862
|
+
|
|
863
|
+
The approval mode was set to `"auto"`, which asks Codex to decide for itself — and it decides to
|
|
864
|
+
ask. It is now `"writes"`: prompt only for tools that write, and Niro writes nothing. An existing
|
|
865
|
+
`"auto"` is migrated; a value you chose yourself is left alone.
|
|
866
|
+
|
|
867
|
+
Codex also records a per-tool override whenever you answer a prompt with "approve once" rather than
|
|
868
|
+
"always allow", and a per-tool setting beats the default. Those are now cleared on install.
|
|
869
|
+
|
|
870
|
+
- **A malformed tool response when a reply contained a backslash, tab or quote.** The
|
|
871
|
+
`mark_task_complete` response was assembled by a hand-written JSON builder that escaped only the
|
|
872
|
+
double quote. That was survivable while every value was a fixed keyword, but any free text
|
|
873
|
+
containing a Windows path or a tab produced JSON the client could not parse, losing the whole
|
|
874
|
+
response. All control characters are now escaped.
|
|
875
|
+
|
|
876
|
+
- **The reminder to prefer niro's tools no longer goes quiet mid-session.** Claude Code's
|
|
877
|
+
`Grep|Glob|Read` hook deduplicated to once per session, per repo, per branch — so it fired early
|
|
878
|
+
in a session and then said nothing, which is precisely when an agent drifts back to `grep`. It now
|
|
879
|
+
fires on every native search.
|
|
880
|
+
- **The same reminder was permanently silent in some folders.** It resolved the repo with
|
|
881
|
+
`git rev-parse --show-toplevel` and gave up when that was empty, so a folder tracked by niro but
|
|
882
|
+
not a git checkout never got a reminder at all. It now walks up for the `.niro-project` marker
|
|
883
|
+
directly, and resolves symlinks, so a path reached through a symlinked directory works too.
|
|
884
|
+
- **The reminder's wording let the agent excuse itself.** It offered an "out of scope" escape hatch,
|
|
885
|
+
which is the exit drift actually takes. Removed; the legitimate native-tool cases (config, docs,
|
|
886
|
+
logs, generated files) are now named explicitly instead.
|
|
887
|
+
- **A typed project name is now treated as an account-scoped alias.** It was sent as `project_id` and
|
|
888
|
+
checked with a global lookup, so a name already used by an unrelated account read as taken — or
|
|
889
|
+
returned a 403 — for a name that was genuinely free for you. The server now assigns the opaque id,
|
|
890
|
+
and availability is checked against your own account.
|
|
891
|
+
- **`niro init` no longer records a broken project pin.** If the create response carried no
|
|
892
|
+
`project_id`, the literal text `undefined` was written into `.niro-project` while init reported
|
|
893
|
+
success; every later tool call then failed with `Project 'undefined' not found`. It now fails
|
|
894
|
+
loudly at that boundary and pins nothing.
|
|
895
|
+
|
|
896
|
+
### Added
|
|
897
|
+
|
|
898
|
+
- The bridge now reports its version and identifies itself (`X-Niro-Cli-Version`,
|
|
899
|
+
`X-Niro-Bridge`) so the server can tell an out-of-date install to upgrade. The notice is off until
|
|
900
|
+
a server is deployed with `niro.mcp.cli.min-version` set.
|
|
901
|
+
|
|
902
|
+
### Not shipped
|
|
903
|
+
|
|
904
|
+
Two features were built during this cycle and pulled before release. Both are recorded here so the
|
|
905
|
+
reasoning is in the repo rather than only in commit messages.
|
|
906
|
+
|
|
907
|
+
- **A serve-time self-heal** that would have refreshed stale hooks automatically. Three independent
|
|
908
|
+
reviews found it deleting users' own settings, widening file permissions on a file that can hold
|
|
909
|
+
API keys, replacing symlinked configs with regular files, and — with `HOME` unset, the normal case
|
|
910
|
+
on Windows — writing into the current project folder instead of the home directory. Refreshing
|
|
911
|
+
hooks stays a manual `niro mcp install`.
|
|
912
|
+
- **A per-prompt reminder hook for Codex.** Codex does support hooks, and the approach works, but
|
|
913
|
+
installing one means editing `~/.codex/config.toml` — the same file that holds
|
|
914
|
+
`[mcp_servers.niro]`, so a malformed edit makes Codex reject the whole file and takes niro's
|
|
915
|
+
registration down with it. Three review rounds found successive defects in that editing, including
|
|
916
|
+
an uninstall that could delete a hook of the user's own and leave a config Codex would not load.
|
|
917
|
+
Held back rather than shipped on a fourth attempt; the risk sat entirely in the config surgery,
|
|
918
|
+
not in the idea.
|