@niroai/niro 0.10.1 → 0.12.0
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 +164 -0
- package/README.md +81 -4
- package/docs/working-on-branches.md +63 -0
- package/package.json +1 -1
- package/src/commands/_resolveProject.js +59 -3
- package/src/commands/cleanup.js +689 -0
- package/src/commands/delete.js +16 -1
- package/src/commands/info.js +51 -2
- package/src/commands/release.js +13 -60
- package/src/commands/remove.js +13 -0
- package/src/commands/takeover.js +52 -2
- package/src/commands/watch.js +93 -25
- package/src/index.js +23 -3
- package/src/lib/folderSync.js +7 -0
- package/src/lib/impactLog.js +251 -0
- package/src/lib/periodicClaim.js +100 -0
- package/src/lib/printError.js +46 -0
- package/src/lib/receiptStore.js +1 -1
- package/src/lib/takeoverRestore.js +156 -0
- package/src/mcp/server.js +30 -10
- package/src/mcp/setup.js +441 -23
- package/src/mcp/teardown.js +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,170 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to `@niroai/niro`. Dates are release dates.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## 0.12.0 — 2026-09-06
|
|
8
|
+
|
|
9
|
+
### Before an edit to indexed source, Claude is asked to map the method and to verify those flows afterwards
|
|
10
|
+
|
|
11
|
+
Every instruction file niro writes tells the assistant to map who depends on a method before changing
|
|
12
|
+
it. In practice the assistant finds the file, reads it, and edits; the rule is read early in a session
|
|
13
|
+
and forgotten by the middle of it. And the map was always meant for afterwards: the point is to go back
|
|
14
|
+
to the callers and flows once the edit is made and check they still hold.
|
|
15
|
+
|
|
16
|
+
Claude Code now gets a third niro hook, on Edit and Write. Before an edit to source in an indexed repo
|
|
17
|
+
it puts a short reminder in front of the assistant: decide whether the change can alter behaviour (a log
|
|
18
|
+
line or a comment cannot, say so and go ahead); if it can, map who depends on the method now with
|
|
19
|
+
get_blast_radius or get_impact_analysis and keep that list as the checklist for after the edit; and do
|
|
20
|
+
not re-query the graph to verify, because it updates only after the edit is synced and rebuilt. If an
|
|
21
|
+
impact call already named the file this session, the reminder instead says to go back to that list
|
|
22
|
+
after the edit. Each reminder appears once per file per session. It is advisory: it never approves or
|
|
23
|
+
refuses an edit. The evidence comes from the bridge, which writes one line per answered impact call
|
|
24
|
+
under `~/.niro/impact/`. New files, tests, config and docs are never mentioned. Off switch:
|
|
25
|
+
`touch ~/.niro/impact-gate-off`. `niro mcp uninstall` removes the hook and the evidence files.
|
|
26
|
+
|
|
27
|
+
The same three points were added to every instruction surface niro writes: the rule sentence in each
|
|
28
|
+
client's always-on file, the niro skill, the Cursor, Cline and Kiro rules, and the PostCompact and
|
|
29
|
+
prompt hooks. The clients without hooks read them there. Details: `docs/usecases/impact-gate.md`.
|
|
30
|
+
|
|
31
|
+
### `niro mcp install` now refreshes Claude Code hooks on a machine where niro was already set up
|
|
32
|
+
|
|
33
|
+
Re-running the install on a machine with an existing registration failed at the first step, because
|
|
34
|
+
`claude mcp add` refuses a name that already exists, and the installer read that refusal as "Claude
|
|
35
|
+
Code is not installed" and stopped before writing any hook or the CLAUDE.md section. So the step the
|
|
36
|
+
out-of-date notice asks for did nothing for anyone who had installed once. The installer now reads the
|
|
37
|
+
existing registration first: same URL, it keeps it; a different `--url`, it replaces it; none, it adds
|
|
38
|
+
it. Hooks and sections are written in every case.
|
|
39
|
+
|
|
40
|
+
### CommonJS and ES-module files (.cjs, .mjs, .cts, .mts) are now uploaded for indexing
|
|
41
|
+
|
|
42
|
+
When niro decided which files to upload for indexing, it skipped `.cjs`, `.mjs`, `.cts` and `.mts`,
|
|
43
|
+
so a `build.cjs` or an `index.mjs` was never indexed and never appeared in the graph. Those four
|
|
44
|
+
extensions are now included, matching the server's own file filter.
|
|
45
|
+
|
|
46
|
+
### The niro rules now name get_exception_flow
|
|
47
|
+
|
|
48
|
+
The explore-first rule niro writes into each client now mentions `get_exception_flow`, the tool that
|
|
49
|
+
reports which exceptions can escape a method (and who must handle one). It is served by the niro
|
|
50
|
+
backend; this only makes the assistants aware it exists.
|
|
51
|
+
|
|
52
|
+
## 0.11.0 — 2026-09-03
|
|
53
|
+
|
|
54
|
+
### The watcher stops pausing and resuming your uploads over and over
|
|
55
|
+
|
|
56
|
+
The background watcher holds back uploads from a folder whose branch disagrees with the branch its
|
|
57
|
+
private copy is labelled with, so two branches cannot be written into one copy. One developer's log
|
|
58
|
+
carried 306 pauses against 286 resumes. Those counts being nearly equal is the whole story: it was
|
|
59
|
+
toggling, not failing 306 times.
|
|
60
|
+
|
|
61
|
+
Once a minute the watcher asks which branch the copy is on. A request that failed returned nothing,
|
|
62
|
+
which was read as "everything is fine again": it printed "resuming uploads" and cleared the pause, so
|
|
63
|
+
the next check paused and warned all over again. It also let a folder that genuinely should not be
|
|
64
|
+
uploading start writing again on any slow request. Now a question the server could not answer changes
|
|
65
|
+
nothing at all, in either direction. The same six alternating checks went from 3 warnings and 3 resumes
|
|
66
|
+
down to 1 and 0.
|
|
67
|
+
|
|
68
|
+
The message was wrong too. When it paused it looked for another folder on the same *project* and blamed
|
|
69
|
+
it. A project can hold several repositories and each folder can have its own, so it regularly named a
|
|
70
|
+
folder that was writing nowhere near yours and told you to discard it. It now matches on the repository
|
|
71
|
+
actually being written to, so it either names the folder that really shares your copy or tells you
|
|
72
|
+
plainly that you switched branch in that folder yourself.
|
|
73
|
+
|
|
74
|
+
**The pause itself is unchanged, deliberately.** While a folder's branch and its copy's label disagree,
|
|
75
|
+
your assistant's questions are not sent to that copy anyway, so uploading into it would only fill it
|
|
76
|
+
with the wrong branch's files. Run `niro new-temp-project` in that folder to put it back in step.
|
|
77
|
+
|
|
78
|
+
### Servlet apps declare their endpoints in a file that was never sent
|
|
79
|
+
|
|
80
|
+
A Java web app written before Spring Boot lists its whole HTTP surface in `WEB-INF/web.xml` rather than
|
|
81
|
+
in annotations. That file was missing from the set the CLI offers, so such a project indexed as a
|
|
82
|
+
servlet application with no endpoints at all. Measured on one repository: 11 real endpoints, 0 detected,
|
|
83
|
+
against an otherwise complete and healthy index.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### Stale pointers clean themselves up
|
|
87
|
+
|
|
88
|
+
Delete a project, or let an unused private copy expire, and your machine is never told. The folder
|
|
89
|
+
carries on naming something that is gone. That is what makes `niro new-temp-project` in another
|
|
90
|
+
worktree say the slot is taken, naming a copy that no longer exists.
|
|
91
|
+
|
|
92
|
+
That command never shipped: it went in under this same Unreleased section and has been renamed since,
|
|
93
|
+
so `niro doctor sync-registry` is its first public name. It also only helped if you remembered to run
|
|
94
|
+
it. Now the background watcher runs it every six hours by itself, and it covers two more cases:
|
|
95
|
+
|
|
96
|
+
- **Ordinary registrations**, not just private copies. A folder registered against a deleted project
|
|
97
|
+
used to sit there for ever: the daemon retried it on every start and nothing could clear it. One
|
|
98
|
+
developer asked whether they should edit `~/.niro/projects.json` by hand. They were right that there
|
|
99
|
+
was no supported way.
|
|
100
|
+
- **`.niro-project` files** naming something the server does not recognise. Nothing has ever surfaced
|
|
101
|
+
these, and they are not inert: that file is read before your git remote, so a stale one keeps
|
|
102
|
+
answering. They are REPORTED, never deleted — the file can hold a project's name rather than its id,
|
|
103
|
+
and a renamed project is indistinguishable from a deleted one from the client, so the right repair
|
|
104
|
+
(update it, or remove it) is yours to choose.
|
|
105
|
+
|
|
106
|
+
**What it refuses to do matters more than what it does.** A deleted project is not enough to
|
|
107
|
+
unregister a folder, because a project can be deleted while its repos are moved to another one and
|
|
108
|
+
that folder is still syncing fine, so the repo is checked too. On the machine this was built on that
|
|
109
|
+
took the count from 18 folders to 1. And a `.niro-project` may hold an *alias* rather than an id, which
|
|
110
|
+
the direct lookup reports as "not found" for a project that is perfectly alive; the account's project
|
|
111
|
+
list is now the authority. Both of those were caught by dry runs against real data after the unit tests
|
|
112
|
+
were green.
|
|
113
|
+
|
|
114
|
+
The watcher remembers when it last swept, so restarting it does not re-run the job, and a `niro watch`
|
|
115
|
+
left in a terminal cannot sweep at the same moment as the background one. It stays silent when there
|
|
116
|
+
was nothing to do.
|
|
117
|
+
|
|
118
|
+
### When you run out of private copies, it says where they are
|
|
119
|
+
|
|
120
|
+
The server already tells you which branches are holding your copies. It cannot tell you where those
|
|
121
|
+
branches are checked out, because that is a fact about your machine. The CLI now adds the folder next
|
|
122
|
+
to each branch, so you can go straight to one and discard it.
|
|
123
|
+
|
|
124
|
+
### Multi-line error messages are no longer cut off
|
|
125
|
+
|
|
126
|
+
Errors printed only their first line, so any message built over several lines lost the rest. Two were
|
|
127
|
+
affected: "Multiple projects match this repo — pass --project:" showed no projects, and the
|
|
128
|
+
working-copy-limit refusal showed no folders. The text was always correct and simply never reached the
|
|
129
|
+
screen. Found by running the real CLI against a real backend until it refused; no unit test could have
|
|
130
|
+
caught it, because both features assert on the message and the message was right.
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
### `niro doctor sync-registry` tidies up copies the server deleted for you
|
|
134
|
+
|
|
135
|
+
A temporary project can go without this machine ever being told: somebody deletes it in the web
|
|
136
|
+
app, or the sweeper reaps it after two weeks. The folder keeps a registration pointing at an id
|
|
137
|
+
that resolves to nothing. That is not harmless, because it is exactly what makes
|
|
138
|
+
`niro new-temp-project` in a second worktree refuse with "already held by another folder".
|
|
139
|
+
|
|
140
|
+
`niro discard-temp-project` already repaired one folder in that state. What nothing did was find
|
|
141
|
+
them. On the machine this was built on, 13 of 14 registered copies had gone and every one of the
|
|
142
|
+
14 folders was still pointing at a dead id.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
niro doctor sync-registry # check and repair
|
|
146
|
+
niro doctor sync-registry --dry-run # list, change nothing
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
It asks the server once per copy and moves the folders whose copy has gone back onto their
|
|
150
|
+
original project. Three things it will not do. It never treats an unreachable server as an
|
|
151
|
+
answer: only a definitive "Project not found" counts, so a 500, a timeout, or a project you are
|
|
152
|
+
simply not allowed to see leaves every entry exactly as it was. It never unregisters a folder,
|
|
153
|
+
because the watch daemon still needs somewhere to sync it. And it never guesses: a copy that no
|
|
154
|
+
folder recorded a source project for is reported and left alone.
|
|
155
|
+
|
|
156
|
+
`--json` for scripts, `--dry-run` to look first, `-y` to skip the prompt. At a terminal, `--json`
|
|
157
|
+
asks for one of the last two rather than writing without a prompt it cannot show.
|
|
158
|
+
|
|
159
|
+
Two guards came out of review and apply to `niro discard-temp-project` as well, because both commands
|
|
160
|
+
now share one repair routine. A folder re-registered by another process while the command runs is
|
|
161
|
+
left alone and reported, instead of being overwritten by a stale snapshot. And within a copy that
|
|
162
|
+
covers several folders, the one holding the record of the original project is always written last, so
|
|
163
|
+
a run interrupted half way through can still be finished by running it again.
|
|
164
|
+
|
|
165
|
+
Folders that had no local registration before the copy was made are now listed separately from the
|
|
166
|
+
ones that keep syncing. They go back to not having one, which is where they started, but calling that
|
|
167
|
+
"repaired" and stopping there implied the daemon was still watching them.
|
|
168
|
+
|
|
5
169
|
## 0.10.0 — 2026-08-28
|
|
6
170
|
|
|
7
171
|
One command to get started, and a way to check what Niro is worth.
|
package/README.md
CHANGED
|
@@ -45,7 +45,9 @@ Then restart your assistant and ask it something:
|
|
|
45
45
|
```bash
|
|
46
46
|
niro status
|
|
47
47
|
```
|
|
48
|
-
Is my index up to date?
|
|
48
|
+
Is my index up to date? It answers about the project this folder is synced to **on the branch you
|
|
49
|
+
are on**, so once you have run `niro new-temp-project` it reports that copy rather than the shared
|
|
50
|
+
project. Name one explicitly to ask about a different project: `niro status <project-id>`.
|
|
49
51
|
|
|
50
52
|
```bash
|
|
51
53
|
niro start
|
|
@@ -65,7 +67,16 @@ Every project in your account.
|
|
|
65
67
|
```bash
|
|
66
68
|
niro build
|
|
67
69
|
```
|
|
68
|
-
Rebuild the index by hand and watch it finish.
|
|
70
|
+
Rebuild the index by hand and watch it finish. Like `niro status`, it picks the project for the
|
|
71
|
+
branch you are on, so on a branch with a temporary project it rebuilds that one. Name a project to
|
|
72
|
+
rebuild a different one.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
niro doctor
|
|
76
|
+
```
|
|
77
|
+
Check this machine against the server and repair what is safe to repair. Today that is
|
|
78
|
+
`niro doctor sync-registry`, which fixes folders and `.niro-project` files left pointing at projects
|
|
79
|
+
that have gone or been renamed. The background watcher runs it for you every six hours.
|
|
69
80
|
|
|
70
81
|
---
|
|
71
82
|
|
|
@@ -85,10 +96,72 @@ niro discard-temp-project
|
|
|
85
96
|
```
|
|
86
97
|
Run when the branch is merged. Forgotten copies are cleaned up after two weeks.
|
|
87
98
|
|
|
99
|
+
If a copy is deleted for you, or a project is renamed, your machine is not told and the folder keeps
|
|
100
|
+
pointing at something that is gone. `niro doctor sync-registry` sorts that out, and the background
|
|
101
|
+
watcher runs it every six hours — see [When a project is deleted](#when-a-project-is-deleted).
|
|
102
|
+
|
|
88
103
|
Full guide: [docs/working-on-branches.md](https://unpkg.com/@niroai/niro/docs/working-on-branches.md)
|
|
89
104
|
|
|
90
105
|
---
|
|
91
106
|
|
|
107
|
+
## When a project is deleted
|
|
108
|
+
|
|
109
|
+
Delete a project, or let an unused private copy expire, and your machine is not told. The folder
|
|
110
|
+
carries on pointing at something that is no longer there.
|
|
111
|
+
|
|
112
|
+
That is not just untidy. It is what makes `niro new-temp-project` in another folder say the slot is
|
|
113
|
+
already taken, naming a copy that no longer exists. And a `.niro-project` file left naming a deleted
|
|
114
|
+
project keeps answering, because it is read before your git remote.
|
|
115
|
+
|
|
116
|
+
The background watcher checks every six hours and sorts this out for you. `niro
|
|
117
|
+
doctor sync-registry` does the same thing on demand.
|
|
118
|
+
|
|
119
|
+
What it does depends on what went missing:
|
|
120
|
+
|
|
121
|
+
| What is gone | What happens to your folder |
|
|
122
|
+
|---|---|
|
|
123
|
+
| A private copy of a project | Goes back to the original project and carries on syncing |
|
|
124
|
+
| The project itself | Left alone if the repo still exists; unregistered only when both are gone |
|
|
125
|
+
| Neither, just a stale `.niro-project` | Repaired if your git remote says which project it should name; removed if it names nothing and is not shared; otherwise you are told |
|
|
126
|
+
|
|
127
|
+
It never deletes anything on the server, and it never touches a `.niro-project` file. Everything it
|
|
128
|
+
acts on has already gone. It only acts on what it can check, too: a folder git cannot read, or a
|
|
129
|
+
project it could not reach, is left exactly as it is and reported. A bad connection can never cost
|
|
130
|
+
you a working folder.
|
|
131
|
+
|
|
132
|
+
**How it decides what to do with a stale `.niro-project`.** That file can hold a project's name
|
|
133
|
+
rather than its id, so a renamed project looks exactly like a deleted one. Niro uses a second signal:
|
|
134
|
+
your folder's git remote. If the remote points at exactly one project, the pin is rewritten to that
|
|
135
|
+
project's id, which cannot be broken by a future rename. If the remote points at nothing either, the
|
|
136
|
+
pin identifies nothing and is removed.
|
|
137
|
+
|
|
138
|
+
**Unless the file is committed.** A `.niro-project` in git is shared with everyone who clones the
|
|
139
|
+
repo, and what looks like a dead project to you may be a colleague's. Niro never changes a committed
|
|
140
|
+
one for you. It tells you, and you decide.
|
|
141
|
+
|
|
142
|
+
`niro info` also mentions it when you are standing in an affected folder, so you do not have to go
|
|
143
|
+
looking.
|
|
144
|
+
|
|
145
|
+
## When you run out of private copies
|
|
146
|
+
|
|
147
|
+
You get a limited number of private copies of each project. If you hit that limit, Niro tells you
|
|
148
|
+
which branches are holding them, and the CLI adds where each one is checked out:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
You already have 5 working copies of this project, which is the limit (5).
|
|
152
|
+
They are for: feature/a, feature/b, feature/c, feature/d, feature/e.
|
|
153
|
+
|
|
154
|
+
You have 5 working copies of this project on this machine:
|
|
155
|
+
feature/a /home/you/work/service
|
|
156
|
+
feature/b /home/you/work/service-wt2
|
|
157
|
+
...
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Run `niro discard-temp-project` in one you have finished with, then try again. Copies nobody has
|
|
161
|
+
touched for a while are also cleared automatically.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
92
165
|
## Measure what Niro changes
|
|
93
166
|
|
|
94
167
|
```bash
|
|
@@ -155,7 +228,10 @@ Detach a repo from a project. Nothing is deleted.
|
|
|
155
228
|
```bash
|
|
156
229
|
niro remove --project --confirm-name <name>
|
|
157
230
|
```
|
|
158
|
-
Delete a whole project. Destructive, hence the confirmation.
|
|
231
|
+
Delete a whole project. Destructive, hence the confirmation. It refuses on a temporary project and
|
|
232
|
+
sends you to `niro discard-temp-project` instead, because that removes the copy **and** points this
|
|
233
|
+
folder back at the shared project; deleting it here would leave the folder aimed at a project that
|
|
234
|
+
no longer exists. `niro delete` refuses for the same reason.
|
|
159
235
|
|
|
160
236
|
---
|
|
161
237
|
|
|
@@ -181,7 +257,8 @@ Watch this folder in the foreground instead, for one session.
|
|
|
181
257
|
## Reference
|
|
182
258
|
|
|
183
259
|
Add `--help` to any command for its flags. Add `--json` to `whoami`, `info`, `status`,
|
|
184
|
-
`projects`, `build`, `ab report
|
|
260
|
+
`projects`, `build`, `ab report`, `edit --list-env` and `doctor sync-registry` for machine-readable
|
|
261
|
+
output.
|
|
185
262
|
|
|
186
263
|
| File | What it holds |
|
|
187
264
|
|---|---|
|
|
@@ -46,6 +46,14 @@ It creates your own private copy of the project inside Niro.
|
|
|
46
46
|
|
|
47
47
|
From then on, answers come from YOUR code.
|
|
48
48
|
|
|
49
|
+
The commands follow too. `niro status` and `niro build` act on the copy for the branch you are on,
|
|
50
|
+
not on the shared project, so "is my index up to date" and "rebuild it" mean what you expect while
|
|
51
|
+
you are working on a branch. Name a project explicitly if you want a different one.
|
|
52
|
+
|
|
53
|
+
`niro delete` and `niro remove --project` will not delete a private copy. They send you to
|
|
54
|
+
`niro discard-temp-project`, which removes the copy AND points your folder back at the shared
|
|
55
|
+
project. Deleting it any other way leaves your folder aimed at something that is gone.
|
|
56
|
+
|
|
49
57
|
## Switching branches
|
|
50
58
|
|
|
51
59
|
Nothing to do. Niro checks which branch you are on for every question.
|
|
@@ -63,6 +71,61 @@ Run this in the repo folder:
|
|
|
63
71
|
It deletes your private copy. Your local files are never touched.
|
|
64
72
|
If you forget, Niro deletes unused copies by itself after two weeks.
|
|
65
73
|
|
|
74
|
+
## Tidying up copies that were deleted for you
|
|
75
|
+
|
|
76
|
+
When a copy goes after two weeks, or somebody deletes it in the web app,
|
|
77
|
+
your machine is not told. The folder is left pointing at a copy that is
|
|
78
|
+
no longer there. Nothing breaks, but "niro new-temp-project" in another
|
|
79
|
+
worktree of the same repo will say the slot is already taken.
|
|
80
|
+
|
|
81
|
+
Run this from anywhere to sort it out:
|
|
82
|
+
|
|
83
|
+
niro doctor sync-registry
|
|
84
|
+
|
|
85
|
+
You will rarely need to type it. The background watcher runs the same
|
|
86
|
+
check every six hours by itself.
|
|
87
|
+
|
|
88
|
+
It sorts out three things, and treats them differently:
|
|
89
|
+
|
|
90
|
+
- A folder pointing at a private copy that has gone goes back to the
|
|
91
|
+
original project, and carries on syncing.
|
|
92
|
+
- A folder pointing at a project that has gone is only unregistered when
|
|
93
|
+
the repo has gone too. A project can be deleted while its repos are
|
|
94
|
+
moved somewhere else, and that folder is still syncing perfectly well.
|
|
95
|
+
- A ".niro-project" file naming something the server does not know is
|
|
96
|
+
repaired where possible. That file can hold a project's NAME rather
|
|
97
|
+
than its id, so a renamed project looks exactly like a deleted one.
|
|
98
|
+
Niro checks your folder's git remote instead: if that points at one
|
|
99
|
+
project, the file is rewritten to that project's id, which a future
|
|
100
|
+
rename cannot break. If the remote points at nothing either, the file
|
|
101
|
+
names nothing and is removed.
|
|
102
|
+
|
|
103
|
+
Not if the file is committed to git, though. Then it is shared with
|
|
104
|
+
everyone who clones the repo, and a project that means nothing to you
|
|
105
|
+
may be a colleague's. Niro tells you and leaves the choice to you.
|
|
106
|
+
"niro info" mentions it too, when you are standing in that folder.
|
|
107
|
+
|
|
108
|
+
Copies that are still there are left alone, and so is anything it could
|
|
109
|
+
not check, so a dropped connection never costs you a working folder. Add
|
|
110
|
+
"--dry-run" to see the list without changing anything.
|
|
111
|
+
|
|
112
|
+
## When you run out of private copies
|
|
113
|
+
|
|
114
|
+
You get a limited number of private copies of each project. When you hit
|
|
115
|
+
the limit, Niro says which branches are holding them, and the CLI adds
|
|
116
|
+
where each one is checked out on this machine:
|
|
117
|
+
|
|
118
|
+
You already have 5 working copies of this project, which is
|
|
119
|
+
the limit (5). They are for: feature/a, feature/b, ...
|
|
120
|
+
|
|
121
|
+
You have 5 working copies of this project on this machine:
|
|
122
|
+
feature/a /home/you/work/service
|
|
123
|
+
feature/b /home/you/work/service-wt2
|
|
124
|
+
|
|
125
|
+
Run "niro discard-temp-project" in one you have finished with, then try
|
|
126
|
+
again. Copies nobody has touched for a while are cleared automatically,
|
|
127
|
+
so this usually sorts itself out.
|
|
128
|
+
|
|
66
129
|
## A feature that spans several repos
|
|
67
130
|
|
|
68
131
|
Run "niro new-temp-project" in each repo folder you branch.
|
package/package.json
CHANGED
|
@@ -7,11 +7,16 @@
|
|
|
7
7
|
* - an alias or git URL -> resolved via GET /api/projects/resolve.
|
|
8
8
|
* If an alias/URL maps to MULTIPLE projects, a clear ambiguity error is
|
|
9
9
|
* thrown listing the candidates (we never silently pick the first).
|
|
10
|
-
* 2.
|
|
10
|
+
* 2. The project THIS FOLDER IS CURRENTLY SYNCED TO, when it was recorded for the
|
|
11
|
+
* branch that is checked out. See resolveFromSyncedFolder for why this comes
|
|
12
|
+
* before the pin.
|
|
13
|
+
* 3. Walk up from cwd looking for .niro-project (same logic the MCP bridge uses).
|
|
11
14
|
*/
|
|
12
15
|
const api = require("../lib/api");
|
|
13
16
|
const prompt = require("../lib/prompt");
|
|
14
17
|
const niroProjectFile = require("../lib/niroProjectFile");
|
|
18
|
+
const git = require("../lib/git");
|
|
19
|
+
const watchState = require("../lib/watchState");
|
|
15
20
|
const { normalizeProjects, resolveContext } = require("../lib/resolveContext");
|
|
16
21
|
|
|
17
22
|
// A canonical project_id is a UUID. Anything that doesn't look like one and
|
|
@@ -119,6 +124,36 @@ async function resolveProjectFromGit(opts = {}) {
|
|
|
119
124
|
throw e;
|
|
120
125
|
}
|
|
121
126
|
|
|
127
|
+
/**
|
|
128
|
+
* The project this folder is actually synced to right now, when the recorded branch is the
|
|
129
|
+
* one checked out. Null when nothing is recorded, when the branch has moved on, or on any error.
|
|
130
|
+
*
|
|
131
|
+
* <p>Why this has to come BEFORE the .niro-project pin. The pin holds the SHARED project's id and
|
|
132
|
+
* never changes. When a developer runs `niro new-temp-project`, the folder starts syncing to a
|
|
133
|
+
* private temporary project that indexes their branch, and only ~/.niro/projects.json records it.
|
|
134
|
+
* Reading the pin therefore answers about a project the developer is not working in. Measured on
|
|
135
|
+
* 2026-09-08: in a repo on branch feat/shipping-wrap-rethrow, `niro status` reported the shared
|
|
136
|
+
* project with "Updated 2026-09-06T09:28", two days stale, while the branch's own project had
|
|
137
|
+
* finished a build four minutes earlier. `niro build` had the same fault and would have rebuilt the
|
|
138
|
+
* shared project.
|
|
139
|
+
*
|
|
140
|
+
* <p>The branch comparison is the safety catch. An entry recorded on another branch must not
|
|
141
|
+
* redirect the command, so a mismatch falls through to the pin, which is the old behaviour.
|
|
142
|
+
*/
|
|
143
|
+
function resolveFromSyncedFolder(opts = {}) {
|
|
144
|
+
try {
|
|
145
|
+
const ctx = git.inspect(process.cwd());
|
|
146
|
+
if (!ctx || !ctx.repoRoot) return null;
|
|
147
|
+
const entry = watchState.get(ctx.repoRoot, opts.apiUrl || api.currentBaseUrl());
|
|
148
|
+
if (!entry || !entry.projectId) return null;
|
|
149
|
+
if (entry.branch && ctx.branch && entry.branch !== ctx.branch) return null;
|
|
150
|
+
return entry.projectId;
|
|
151
|
+
} catch (_) {
|
|
152
|
+
// Never block a command on this: falling through to the pin is the previous behaviour.
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
122
157
|
async function resolveProjectId(explicit, opts = {}) {
|
|
123
158
|
if (explicit) {
|
|
124
159
|
// A UUID is used directly — unchanged behavior. Anything else is an
|
|
@@ -126,7 +161,10 @@ async function resolveProjectId(explicit, opts = {}) {
|
|
|
126
161
|
if (looksLikeProjectId(explicit)) return explicit;
|
|
127
162
|
return resolveAliasOrUrl(explicit, opts);
|
|
128
163
|
}
|
|
129
|
-
// No explicit arg:
|
|
164
|
+
// No explicit arg: the project this folder is synced to on THIS branch wins over the pin.
|
|
165
|
+
const fromFolder = resolveFromSyncedFolder(opts);
|
|
166
|
+
if (fromFolder) return fromFolder;
|
|
167
|
+
// Then a .niro-project pin (walk up from cwd).
|
|
130
168
|
const fromFile = readNiroProjectFile(process.cwd());
|
|
131
169
|
if (fromFile) {
|
|
132
170
|
// A pin that is already a project_id is used directly. A pin holding an
|
|
@@ -139,4 +177,22 @@ async function resolveProjectId(explicit, opts = {}) {
|
|
|
139
177
|
return resolveProjectFromGit(opts);
|
|
140
178
|
}
|
|
141
179
|
|
|
142
|
-
|
|
180
|
+
/**
|
|
181
|
+
* The temporary project this id names, or null when it is an ordinary shared project.
|
|
182
|
+
*
|
|
183
|
+
* <p>Used by the destructive commands. Deleting a temporary project through `niro delete` leaves
|
|
184
|
+
* the folder pinned to a project that is gone and skips the local cleanup that
|
|
185
|
+
* `niro discard-temp-project` does, so those commands send the developer there instead.
|
|
186
|
+
* Never throws: if the project cannot be fetched, the caller carries on as before.
|
|
187
|
+
*/
|
|
188
|
+
async function tempProjectSourceId(projectId, opts = {}) {
|
|
189
|
+
try {
|
|
190
|
+
const project = await api.get(`/api/projects/${encodeURIComponent(projectId)}`, { apiUrl: opts.apiUrl });
|
|
191
|
+
return (project && (project.cloneOfProjectId || project.clone_of_project_id)) || null;
|
|
192
|
+
} catch (_) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { resolveProjectId, readNiroProjectFile, resolveAliasOrUrl, looksLikeProjectId,
|
|
198
|
+
resolveFromSyncedFolder, tempProjectSourceId };
|