mirror-mind 0.33.0 → 0.33.1
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/docs/releases/index.md +1 -0
- package/docs/releases/v0.33.1.md +174 -0
- package/package.json +1 -1
- package/plugins/mirror-mind/.claude-plugin/plugin.json +1 -1
- package/ts/src/frontDoor/runtimeRoute.ts +142 -24
- package/ts/src/runtime/git.ts +8 -2
- package/ts/src/runtime/releaseNotes.ts +230 -52
- package/ts/src/runtime/strategies/clone.ts +10 -3
- package/ts/src/runtime/strategies/package.ts +112 -23
- package/ts/src/util/tarEntries.ts +128 -0
- package/ts/src/welcome/card.ts +182 -60
- package/ts/src/welcome/statusLine.ts +12 -2
- package/ts/src/welcome/updateCache.ts +45 -27
package/docs/releases/index.md
CHANGED
|
@@ -12,6 +12,7 @@ Future release notes should use the structure defined in [Release Notes](../proc
|
|
|
12
12
|
|
|
13
13
|
## Releases
|
|
14
14
|
|
|
15
|
+
- [v0.33.1 — The Install Answers For Itself](v0.33.1.md) — patch release of six RS010 Change Requests: the welcome card, the status line, and both `release-notes` readers answer for the install that is running, never for the cwd or the repository around the install. A package asks its own tree and the npm registry, a clone its own checkout and remote; `pending` says `unknown` when it cannot tell, and a `--ref` value can no longer run a command through git. No migration.
|
|
15
16
|
- [v0.33.0 — One Plugin, Three Runtimes](v0.33.0.md) — minor release closing CV21.DS2: the one plugin works as it is on Claude Code, Codex, and Grok. Its MCP server starts on all three, the explicit Mirror form is recognized in every runtime's spelling, and each session is logged under its runtime with every reply beside its prompt. Also: a faster Pi session start, and notice that Gemini CLI is retired in favor of Antigravity.
|
|
16
17
|
- [v0.32.1 — The Release That Shipped](v0.32.1.md) — patch release that is the published CV22 release: `v0.32.0` was tagged and its publication refused by the retired-surface tripwire over one sentence in Getting Started, now rephrased. No code, behavior, or schema differs from `v0.32.0`; read that note for the release itself.
|
|
17
18
|
- [v0.32.0 — One Language, One Install](v0.32.0.md) — minor release that closes CV22, the TypeScript Core Port. Mirror Mind is one TypeScript package run by Node.js 24, installed with `npm install -g mirror-mind` and wired to Pi, Claude Code, Gemini CLI, or Codex by one documented step each; no Python remains in the repository or the shipped artifact; every command answers as it did. Eleven surfaces are retired with cutoffs, recorded in [Pending Cutoffs](pending-cutoffs.md), which this note carries. A new home imports only the Pi sessions newer than itself.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
digest: >
|
|
3
|
+
v0.33.1 is a maintenance release: six Change Requests from RS010, closed
|
|
4
|
+
since v0.33.0, and no story or epic. Its subject is one defect in four
|
|
5
|
+
surfaces. The welcome card, the status line, and both release-notes readers
|
|
6
|
+
answered for the directory the person was in, or for the git repository
|
|
7
|
+
around the install, instead of for the install that was running. Each now
|
|
8
|
+
asks the install itself: a package its own tree and the npm registry, a
|
|
9
|
+
clone its own checkout and remote. `release-notes pending` says `unknown`
|
|
10
|
+
when it cannot tell, and a `--ref` value can no longer run a command through
|
|
11
|
+
git. Upgrade with `mirror runtime update`; there is no migration.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# v0.33.1 — The Install Answers For Itself
|
|
15
|
+
|
|
16
|
+
**Date:** 2026-10-15
|
|
17
|
+
|
|
18
|
+
## Highlights
|
|
19
|
+
|
|
20
|
+
- **The welcome card and the status line announce the update the running install
|
|
21
|
+
has** (CR135). A package asks the registry for its channel's version, a clone its own
|
|
22
|
+
checkout and remote. Before, both asked the git repository of whatever directory the
|
|
23
|
+
session started in: another project's tag could be announced, a package was never told
|
|
24
|
+
of a release, and the status line kept a stale `⬆` after the update.
|
|
25
|
+
- **npm runs in the install's own directory**, for the card and for `runtime update`, so
|
|
26
|
+
a project's `.npmrc` can no longer choose the registry the card reads and the update
|
|
27
|
+
installs from.
|
|
28
|
+
- **`release-notes latest` and `vX.Y.Z` read the notes the package ships** (CR133). On a
|
|
29
|
+
Homebrew install on Apple Silicon, where `/opt/homebrew` is itself a git repository,
|
|
30
|
+
they answered `Release notes: not found`.
|
|
31
|
+
- **`release-notes pending` answers for the install, never the cwd** (CR134). A clone
|
|
32
|
+
reads its own channel's ref in its own checkout. A package asks the registry which
|
|
33
|
+
version its channel holds and reads that version's notes from its tarball. When
|
|
34
|
+
nothing can answer it says `Pending releases: unknown` with a reason and exits 1, so
|
|
35
|
+
`none` always means a source answered.
|
|
36
|
+
- **`--ref` can no longer run a command.** A value like `--upload-pack=<command>`
|
|
37
|
+
reached `git fetch` as an option, and git ran it. Refs beginning with `-` are refused,
|
|
38
|
+
and every git call takes `--end-of-options` before its operands.
|
|
39
|
+
- **A clone's update fetch has git's network budget again** (CR132): 120 seconds, not
|
|
40
|
+
the 2 seconds a local git call gets.
|
|
41
|
+
|
|
42
|
+
## Where We Started
|
|
43
|
+
|
|
44
|
+
`v0.33.0` was installed onto a Homebrew package on 2026-10-14, and three surfaces
|
|
45
|
+
answered wrongly the same day. `mirror runtime release-notes v0.33.0` said `not found`, with
|
|
46
|
+
the note sitting in the package. `release-notes pending` said `none` from `/tmp` and,
|
|
47
|
+
from inside an unrelated project, listed that project's releases after fetching from its
|
|
48
|
+
remote. The welcome card asked the same wrong repository whether Mirror had an update.
|
|
49
|
+
|
|
50
|
+
The three had one root. The readers were ported faithfully from a Python core that ran
|
|
51
|
+
from a checkout, where the cwd, git's top level, and the install were usually one
|
|
52
|
+
directory. Under npm distribution they are three: a skill runs in the person's project,
|
|
53
|
+
and a global prefix can sit inside someone else's repository. CV22 had already moved the
|
|
54
|
+
version and the welcome card's release note onto the running tree, and handed `latest`
|
|
55
|
+
that tree. But the reader still climbed out of it to git's top level, and `pending` and
|
|
56
|
+
the update check still began at the cwd.
|
|
57
|
+
|
|
58
|
+
The promotion of `v0.33.0` itself had stumbled on the same day. Its push timed out at two
|
|
59
|
+
seconds and was finished by hand (CR132), and npm's `latest` had needed a move nobody's
|
|
60
|
+
route printed (CR125).
|
|
61
|
+
|
|
62
|
+
## What Changed
|
|
63
|
+
|
|
64
|
+
**Each install asks about itself.** The card, the status line, and `pending` detect the
|
|
65
|
+
install from where the front door lives, as `runtime channel` does, with nothing spawned
|
|
66
|
+
and no cwd involved. A package reads its channel's dist-tag through the same call `runtime
|
|
67
|
+
update --check` makes. A clone runs its check, its tag lookup, and its "commits behind"
|
|
68
|
+
count in its own checkout. The channel shown is the install's. On `stable` the card checks
|
|
69
|
+
at every render; on `main` it never checks and shows the cached verdict.
|
|
70
|
+
|
|
71
|
+
**npm's directory is a trust decision.** A four-lens review of CR135's plan found that npm
|
|
72
|
+
reads the `.npmrc` of the directory it runs in. A cloned project that ships
|
|
73
|
+
`registry=` could therefore choose what the card announced and what `runtime update`
|
|
74
|
+
installed. Every npm call the product makes now runs in the install's own root, whose
|
|
75
|
+
project config is the package's own.
|
|
76
|
+
|
|
77
|
+
**One bound for the session start.** The card's remote reads share one 5-second deadline,
|
|
78
|
+
and the registry read does not retry, so an unreachable registry costs at most that. A
|
|
79
|
+
cached update the install already has is shown by neither surface, and a successful
|
|
80
|
+
`runtime update` records `up_to_date`.
|
|
81
|
+
|
|
82
|
+
**Release notes come from the install.** `latest` and `vX.Y.Z` read `docs/releases` under
|
|
83
|
+
the running tree and ask git nothing. `pending` reads, for a clone, `origin/<channel>`
|
|
84
|
+
(or `--ref`) in the clone's own repository, after fetching it there. For a package, it
|
|
85
|
+
resolves the channel's dist-tag to a version, as `runtime update` does when it plans.
|
|
86
|
+
When that version is newer, `npm pack --ignore-scripts` fetches that exact tarball into a
|
|
87
|
+
private directory, and its notes are read in memory and the directory removed. When it is
|
|
88
|
+
not newer, nothing is downloaded.
|
|
89
|
+
|
|
90
|
+
Three choices shaped the package path:
|
|
91
|
+
|
|
92
|
+
- the registry tarball, not the GitHub branch: the branch is a second authority that can
|
|
93
|
+
disagree with the dist-tag, as a half-promoted release does;
|
|
94
|
+
- not version numbers alone, which would not answer "what's new in this update?";
|
|
95
|
+
- a reader of its own over `node:zlib` rather than a spawned `tar`, which would take
|
|
96
|
+
archive-chosen names as arguments. It stops at 64 MB inflated. It refuses an archive
|
|
97
|
+
that ends before its end-of-archive block, so a cut tarball cannot pass for a whole one
|
|
98
|
+
missing its newest notes.
|
|
99
|
+
|
|
100
|
+
**`unknown` is said.** A failed fetch, a ref or dist-tag that does not resolve, an
|
|
101
|
+
unreachable registry, or an install that cannot be identified answers `Pending releases:
|
|
102
|
+
unknown` with a `Reason:` line and exit 1. `--no-fetch` reads a clone's ref as last
|
|
103
|
+
fetched; a package keeps no copy of its channel, so it cannot answer offline.
|
|
104
|
+
|
|
105
|
+
**The ref is an operand.** `--ref` was split at its first slash into a remote and a
|
|
106
|
+
branch for `git fetch`, so `--upload-pack=touch X/y`, or `origin/--upload-pack=touch X`,
|
|
107
|
+
reached git as an option, and git ran the command. A test claimed this could not happen.
|
|
108
|
+
It passed only because its marker path was absolute, which the split cuts at the leading
|
|
109
|
+
`/`. The exposure was local: whoever can pass `--ref` can run commands anyway. Refs
|
|
110
|
+
beginning with `-` are now refused, and `fetch`, `ls-tree`, and `show` take
|
|
111
|
+
`--end-of-options`, which covers the half after the slash.
|
|
112
|
+
|
|
113
|
+
### What you will notice
|
|
114
|
+
|
|
115
|
+
- On a package, `release-notes pending` shows `Source: npm mirror-mind@stable (<version>)`.
|
|
116
|
+
On a clone it shows `Source: origin/<channel>`, so a clone on `main` now reads
|
|
117
|
+
`origin/main`, where it used to read `origin/stable`.
|
|
118
|
+
- A package on `main` answers `unknown`: npm has no `main` dist-tag, as `runtime update
|
|
119
|
+
--check` already says.
|
|
120
|
+
- A `⬆` that the status line kept after an update is no longer shown once the install
|
|
121
|
+
has that version.
|
|
122
|
+
|
|
123
|
+
## Upgrading
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
mirror runtime update # or: npm install -g mirror-mind
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
There is no database migration, and no runtime wiring changes. The update itself is run
|
|
130
|
+
by the version you have now, so this release's changes to it apply from the next update
|
|
131
|
+
on: npm running in the install's root, and recording `up_to_date` when it succeeds. A
|
|
132
|
+
Homebrew package on Apple Silicon reads its release notes again from the first command
|
|
133
|
+
after the update.
|
|
134
|
+
|
|
135
|
+
## Also In This Release
|
|
136
|
+
|
|
137
|
+
- **For maintainers.** `release:promote` pushes with the network budget, and a failed push
|
|
138
|
+
shows git's message with a recovery that says what is where (CR132). A `stable` release
|
|
139
|
+
now ends, in both places the route is printed, with `npm dist-tag add
|
|
140
|
+
mirror-mind@<version> latest`. The publish workflow stages under `stable` or `next` and
|
|
141
|
+
nothing else (CR125).
|
|
142
|
+
- **For contributors.** The updater smoke gives the same result on any machine, whatever
|
|
143
|
+
the shell exports (CR137). The npm package smoke installs into a prefix that is a git
|
|
144
|
+
repository, the Homebrew shape. Its `npm` is a stub after the install, so it never
|
|
145
|
+
reaches the live registry.
|
|
146
|
+
|
|
147
|
+
## Conscious Exclusions
|
|
148
|
+
|
|
149
|
+
- **`runtime status` and `runtime version` on a clone**, which still read the clone's
|
|
150
|
+
repository and channel from the cwd, so a healthy clone reads `attention needed` outside
|
|
151
|
+
its checkout. It is the same family, captured as CR138.
|
|
152
|
+
- **The `release` environment's ref check.** It accepts a dispatch from any ref, so a
|
|
153
|
+
branch's own copy of the publish workflow could stage (CR136).
|
|
154
|
+
- **A `--channel` option on `pending`**, which `update --check` and `version` have.
|
|
155
|
+
- **The `mm-release-notes` skill's wording**, which still says "the current stable
|
|
156
|
+
release", true on the default channel.
|
|
157
|
+
|
|
158
|
+
## What We Learned
|
|
159
|
+
|
|
160
|
+
A faithful port inherits its oracle's idea of where it runs. Each of these readers matched
|
|
161
|
+
the Python core's goldens, and each was wrong once the program stopped living in the
|
|
162
|
+
directory it was asked about. The tests agreed with the oracle for the same reason: they
|
|
163
|
+
ran from a clone's root, where the cwd, git, and the install coincide. The new tests for
|
|
164
|
+
the card and for `pending` run every case from two places, outside any repository and
|
|
165
|
+
inside an unrelated one, and the answer must not move.
|
|
166
|
+
|
|
167
|
+
A validation route must say which install it tests. On a machine with both a package and
|
|
168
|
+
a linked checkout, a bare `mirror` runs whichever comes first on the PATH, and both
|
|
169
|
+
report the same version.
|
|
170
|
+
|
|
171
|
+
## Next Horizon
|
|
172
|
+
|
|
173
|
+
CR138, the same defect in `runtime status` and `runtime version`, is the visible next
|
|
174
|
+
step. CR136 would harden the publish route. RS010's other hygiene requests follow.
|
package/package.json
CHANGED
|
@@ -46,13 +46,18 @@ import { detectInstallKind, type InstallKind } from "#runtime/installKind.ts";
|
|
|
46
46
|
import { MIGRATE_DECLINED_EXIT, renderMigrate, runMigrate } from "#runtime/migrate.ts";
|
|
47
47
|
import {
|
|
48
48
|
buildPendingReleaseNotes,
|
|
49
|
+
buildPendingReleaseNotesFromRegistry,
|
|
50
|
+
type ReleaseNotesBundle,
|
|
49
51
|
readReleaseNote,
|
|
50
52
|
renderReleaseNote,
|
|
51
53
|
renderReleaseNotesBundle,
|
|
54
|
+
unknownReleaseNotes,
|
|
52
55
|
} from "#runtime/releaseNotes.ts";
|
|
53
56
|
import { buildRuntimeStatus, renderRuntimeStatus, statusVerdict } from "#runtime/status.ts";
|
|
54
57
|
import {
|
|
55
58
|
checkPackageUpdateAvailability,
|
|
59
|
+
defaultNpmRunner,
|
|
60
|
+
type NpmRunner,
|
|
56
61
|
npmRootGlobal,
|
|
57
62
|
renderPackageUpdateAvailability,
|
|
58
63
|
} from "#runtime/strategies/package.ts";
|
|
@@ -61,8 +66,9 @@ import { frontDoorSpawner, postUpdateHints, runUpdate, type UpdateSpawn } from "
|
|
|
61
66
|
import { statusAllowsUpdatePreflight } from "#runtime/updateGate.ts";
|
|
62
67
|
import { renderUpdateResult, updateLogDetail } from "#runtime/updatePipeline.ts";
|
|
63
68
|
import { expandHome } from "#util/paths.ts";
|
|
64
|
-
import { composeWelcome, welcomeDisabled } from "#welcome/card.ts";
|
|
69
|
+
import { composeWelcome, type RemoteCheck, welcomeDisabled } from "#welcome/card.ts";
|
|
65
70
|
import { composeStatusLine } from "#welcome/statusLine.ts";
|
|
71
|
+
import { isoNow, recordUpToDate } from "#welcome/updateCache.ts";
|
|
66
72
|
|
|
67
73
|
/** The `runtime` subcommands this story ports. */
|
|
68
74
|
export const TS_RUNTIME_READ_SUBCOMMANDS = new Set([
|
|
@@ -165,18 +171,39 @@ function writeErr(io: RuntimeRouteIo, text: string): void {
|
|
|
165
171
|
(io.stderr ?? ((value: string) => process.stderr.write(value)))(text);
|
|
166
172
|
}
|
|
167
173
|
|
|
168
|
-
/**
|
|
169
|
-
|
|
174
|
+
/**
|
|
175
|
+
* The seams of the routes that answer for the running install, beyond the
|
|
176
|
+
* route's own: the install and its npm, for tests. The welcome (CR135) and
|
|
177
|
+
* `release-notes pending` (CR134) take them.
|
|
178
|
+
*/
|
|
179
|
+
export interface InstallRouteIo extends RuntimeRouteIo {
|
|
180
|
+
install?: InstallKind;
|
|
181
|
+
npm?: NpmRunner;
|
|
182
|
+
/** The OS home whose config directory holds a package's channel file. */
|
|
183
|
+
osHome?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export type WelcomeRouteIo = InstallRouteIo;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Port of `welcome.main`. Always exits 0, like the oracle.
|
|
190
|
+
*
|
|
191
|
+
* The card and the status line answer for the install that is running, never
|
|
192
|
+
* for the cwd (CR135): Pi renders both in the person's project.
|
|
193
|
+
*/
|
|
194
|
+
export function runWelcomeRoute(argv: readonly string[], io: WelcomeRouteIo = {}): number {
|
|
170
195
|
const env = io.env ?? process.env;
|
|
171
|
-
const cwd = io.cwd ?? process.cwd();
|
|
172
196
|
const args = argv.slice(1);
|
|
173
197
|
if (welcomeDisabled(env)) return 0;
|
|
174
198
|
|
|
175
199
|
const home = resolveWelcomeHome(optionValue(args, "--mirror-home"), env);
|
|
200
|
+
// The running tree's manifest: a file read, so the status line still spawns nothing.
|
|
201
|
+
const version = packageVersion(runningTreeRoot()) ?? "unknown";
|
|
176
202
|
|
|
177
203
|
if (args.includes("--status-line")) {
|
|
178
204
|
const line = composeStatusLine({
|
|
179
205
|
mirrorHome: home,
|
|
206
|
+
installedVersion: version,
|
|
180
207
|
sessionId: optionValue(args, "--session-id"),
|
|
181
208
|
env,
|
|
182
209
|
});
|
|
@@ -184,23 +211,37 @@ export function runWelcomeRoute(argv: readonly string[], io: RuntimeRouteIo = {}
|
|
|
184
211
|
return 0;
|
|
185
212
|
}
|
|
186
213
|
|
|
187
|
-
const
|
|
188
|
-
const card = composeWelcome({
|
|
214
|
+
const { card, check } = composeWelcome({
|
|
189
215
|
mirrorHome: home,
|
|
190
|
-
|
|
191
|
-
treeRoot: runningTreeRoot(),
|
|
216
|
+
install: io.install ?? detectThisInstall(),
|
|
192
217
|
env,
|
|
193
218
|
version,
|
|
194
|
-
|
|
219
|
+
...(io.npm === undefined ? {} : { npm: io.npm }),
|
|
220
|
+
...(io.osHome === undefined ? {} : { osHome: io.osHome }),
|
|
195
221
|
});
|
|
196
222
|
if (card) writeOut(io, `${card}\n`);
|
|
223
|
+
if (home !== null && check !== null) logWelcomeCheck(home, env, check);
|
|
197
224
|
return 0;
|
|
198
225
|
}
|
|
199
226
|
|
|
227
|
+
/**
|
|
228
|
+
* One front-door line per remote check, so a slow session start can be
|
|
229
|
+
* explained later: what was asked, how it answered, how long it took. Never a
|
|
230
|
+
* path or a version.
|
|
231
|
+
*/
|
|
232
|
+
function logWelcomeCheck(home: string, env: NodeJS.ProcessEnv, check: RemoteCheck): void {
|
|
233
|
+
logFrontDoor(frontDoorLogPath(join(home, dbNameForEnv(env.MEMORY_ENV || "production"))), {
|
|
234
|
+
command: "welcome",
|
|
235
|
+
route: "ts",
|
|
236
|
+
exitCode: 0,
|
|
237
|
+
detail: `update-check source=${check.source} verdict=${check.verdict} ms=${check.ms}`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
200
241
|
/** Port of the read half of `cmd_runtime`. */
|
|
201
242
|
export async function runRuntimeReadRoute(
|
|
202
243
|
argv: readonly string[],
|
|
203
|
-
io:
|
|
244
|
+
io: InstallRouteIo = {},
|
|
204
245
|
): Promise<number> {
|
|
205
246
|
const env = io.env ?? process.env;
|
|
206
247
|
const cwd = io.cwd ?? process.cwd();
|
|
@@ -280,19 +321,14 @@ export async function runRuntimeReadRoute(
|
|
|
280
321
|
if (subcommand === "release-notes") {
|
|
281
322
|
const target = firstPositional(args.slice(1)) ?? "latest";
|
|
282
323
|
if (target === "pending") {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
fetch: !args.includes("--no-fetch"),
|
|
292
|
-
start: cwd,
|
|
293
|
-
}),
|
|
294
|
-
),
|
|
295
|
-
);
|
|
324
|
+
const pending = pendingReleaseNotes(args, io, env, version);
|
|
325
|
+
if ("usage" in pending) {
|
|
326
|
+
writeErr(io, pending.usage);
|
|
327
|
+
return 2;
|
|
328
|
+
}
|
|
329
|
+
writeOut(io, renderReleaseNotesBundle(pending.bundle));
|
|
330
|
+
// `unknown` exits 1, as `update --check` does on `unresolved` (CR134, decision 3).
|
|
331
|
+
return pending.bundle.unknown_reason === null ? 0 : 1;
|
|
296
332
|
} else {
|
|
297
333
|
// The notes ship in the package at the same path as in the checkout
|
|
298
334
|
// (US3 D8), read from the tree that holds the running front door --
|
|
@@ -459,9 +495,13 @@ function runRuntimeUpdate(
|
|
|
459
495
|
// The update lane asks npm, once, whether `-g` addresses this install: a
|
|
460
496
|
// package outside npm's own global root is not the one `npm install -g`
|
|
461
497
|
// would replace. The read routes do not pay for the probe.
|
|
498
|
+
//
|
|
499
|
+
// npm runs in the tree the front door runs from -- for a package, the
|
|
500
|
+
// install's own root -- and never in the cwd, whose `.npmrc` could name
|
|
501
|
+
// another registry or prefix (CR135). The strategy's own calls do the same.
|
|
462
502
|
const install = detectInstallKind({
|
|
463
503
|
frontDoorPath: frontDoorSelfPath(),
|
|
464
|
-
npmRootGlobal: npmRootGlobal(),
|
|
504
|
+
npmRootGlobal: npmRootGlobal(runningTreeRoot()),
|
|
465
505
|
});
|
|
466
506
|
const start = install.kind === "clone" ? install.repository : cwd;
|
|
467
507
|
const channel = channelFor(install, env, homedir(), channelOverride);
|
|
@@ -548,6 +588,16 @@ function runRuntimeUpdate(
|
|
|
548
588
|
});
|
|
549
589
|
|
|
550
590
|
writeOut(io, renderUpdateResult(result));
|
|
591
|
+
// The install now holds its channel's version: say so where the welcome card
|
|
592
|
+
// and the status line read it, or they keep announcing what was just
|
|
593
|
+
// installed (CR135).
|
|
594
|
+
if (result.success && !args.includes("--dry-run")) {
|
|
595
|
+
recordUpToDate(mirrorHome, {
|
|
596
|
+
channel: channel.value,
|
|
597
|
+
commit: install.kind === "clone" ? result.newRef : null,
|
|
598
|
+
checkedAt: isoNow(),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
551
601
|
if (repair && result.success) {
|
|
552
602
|
// After the stages, not before them: the instruction is what to do NEXT,
|
|
553
603
|
// and it reads as a caption on a result the operator has already seen.
|
|
@@ -596,6 +646,74 @@ function detectThisInstall(): InstallKind {
|
|
|
596
646
|
return detectInstallKind({ frontDoorPath: frontDoorSelfPath() });
|
|
597
647
|
}
|
|
598
648
|
|
|
649
|
+
/**
|
|
650
|
+
* `release-notes pending` for the install that is running, never for the cwd
|
|
651
|
+
* (CR134), which for a skill is the person's project. A clone reads its own
|
|
652
|
+
* channel's ref -- or `--ref` -- in its own repository; a package asks the
|
|
653
|
+
* registry what its channel holds; an install that cannot be identified has no
|
|
654
|
+
* source, and says so.
|
|
655
|
+
*/
|
|
656
|
+
function pendingReleaseNotes(
|
|
657
|
+
args: readonly string[],
|
|
658
|
+
io: InstallRouteIo,
|
|
659
|
+
env: NodeJS.ProcessEnv,
|
|
660
|
+
version: string,
|
|
661
|
+
): { bundle: ReleaseNotesBundle } | { usage: string } {
|
|
662
|
+
const install = io.install ?? detectThisInstall();
|
|
663
|
+
const osHome = io.osHome ?? homedir();
|
|
664
|
+
const ref = optionValue(args, "--ref");
|
|
665
|
+
const fetch = !args.includes("--no-fetch");
|
|
666
|
+
|
|
667
|
+
if (install.kind === "clone") {
|
|
668
|
+
return {
|
|
669
|
+
bundle: buildPendingReleaseNotes({
|
|
670
|
+
// `--from` omitted falls back to the running tree's own version.
|
|
671
|
+
currentVersion: optionValue(args, "--from") ?? version,
|
|
672
|
+
ref: ref ?? upstreamFor(channelFor(install, env, osHome)),
|
|
673
|
+
repository: install.repository,
|
|
674
|
+
fetch,
|
|
675
|
+
}),
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (install.kind === "package") {
|
|
680
|
+
if (ref !== null) {
|
|
681
|
+
return {
|
|
682
|
+
usage:
|
|
683
|
+
`release-notes pending: --ref names a git ref, and this install is a package ` +
|
|
684
|
+
`(${install.name}@${install.version}), which reads its channel from the npm registry\n`,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
const currentVersion = optionValue(args, "--from") ?? install.version;
|
|
688
|
+
const channel = channelFor(install, env, osHome).value;
|
|
689
|
+
if (!fetch) {
|
|
690
|
+
return {
|
|
691
|
+
bundle: unknownReleaseNotes(
|
|
692
|
+
currentVersion,
|
|
693
|
+
`npm ${install.name}@${channel}`,
|
|
694
|
+
"a package keeps no copy of its channel to read offline; run without --no-fetch",
|
|
695
|
+
),
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
return {
|
|
699
|
+
bundle: buildPendingReleaseNotesFromRegistry({
|
|
700
|
+
currentVersion,
|
|
701
|
+
install,
|
|
702
|
+
channel,
|
|
703
|
+
npm: io.npm ?? defaultNpmRunner,
|
|
704
|
+
}),
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return {
|
|
709
|
+
bundle: unknownReleaseNotes(
|
|
710
|
+
optionValue(args, "--from") ?? version,
|
|
711
|
+
null,
|
|
712
|
+
`install kind unknown (${install.reason})`,
|
|
713
|
+
),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
599
717
|
/**
|
|
600
718
|
* argparse's positional for `release-notes`: the first bare token that is not
|
|
601
719
|
* consumed as the VALUE of `--from` or `--ref`. Walking the list is the only
|
package/ts/src/runtime/git.ts
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
// Timeouts follow the oracle's two budgets, and for the reason recorded there:
|
|
14
14
|
// local inspections are near-instant, while a single shared 2 s budget once
|
|
15
15
|
// made a network operation report failure after it had already succeeded
|
|
16
|
-
// (the v0.30.1 release incident). Network reads get 120 s
|
|
16
|
+
// (the v0.30.1 release incident). Network reads get 120 s, except where a
|
|
17
|
+
// caller holds a tighter budget of its own: the welcome card's update check
|
|
18
|
+
// runs inside the session start and passes what is left of its 5 s (CR135).
|
|
17
19
|
|
|
18
20
|
import { spawnSync } from "node:child_process";
|
|
19
21
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
@@ -260,11 +262,15 @@ function availability(
|
|
|
260
262
|
* Is the channel's remote branch ahead of HEAD? The remote query is the only
|
|
261
263
|
* authority: remote-tracking refs are only as fresh as the last fetch, and
|
|
262
264
|
* trusting them is how a clone two releases behind gets told it is ahead.
|
|
265
|
+
*
|
|
266
|
+
* `networkTimeoutMs` bounds that query; `runtime update --check` keeps the
|
|
267
|
+
* network budget, and the welcome card passes what is left of its own.
|
|
263
268
|
*/
|
|
264
269
|
export function checkUpdateAvailability(
|
|
265
270
|
start: string,
|
|
266
271
|
channel: string | null,
|
|
267
272
|
version: string,
|
|
273
|
+
networkTimeoutMs: number = GIT_NETWORK_TIMEOUT_MS,
|
|
268
274
|
): UpdateAvailability {
|
|
269
275
|
const startPath = resolve(start);
|
|
270
276
|
const git = inspectGit(startPath);
|
|
@@ -303,7 +309,7 @@ export function checkUpdateAvailability(
|
|
|
303
309
|
const lsRemote = runGit(
|
|
304
310
|
["ls-remote", remote, `refs/heads/${branch}`],
|
|
305
311
|
git.repository,
|
|
306
|
-
|
|
312
|
+
networkTimeoutMs,
|
|
307
313
|
);
|
|
308
314
|
if (lsRemote.code !== 0 || !lsRemote.stdout) {
|
|
309
315
|
return availability(version, updateChannel, {
|