mirror-mind 0.33.0 → 0.33.2

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.
@@ -21,7 +21,7 @@ import {
21
21
  renderRuntimeBackupCreated,
22
22
  verifyBackupArchive,
23
23
  } from "#runtime/backup.ts";
24
- import { channelFor, renderChannel, setChannel } from "#runtime/channel.ts";
24
+ import { channelFor, channelForReport, renderChannel, setChannel } from "#runtime/channel.ts";
25
25
  import {
26
26
  diagnoseRuntime,
27
27
  hookFailureFindings,
@@ -33,26 +33,31 @@ import {
33
33
  } from "#runtime/diagnose.ts";
34
34
  import {
35
35
  checkUpdateAvailability,
36
- inspectCloneRole,
36
+ cloneRoleFor,
37
37
  inspectGitFor,
38
38
  inspectGitWorktree,
39
- inspectUpdateChannel,
40
39
  packageVersion,
41
40
  renderRuntimeUpdateAvailability,
42
41
  renderRuntimeVersion,
42
+ unavailableUpdateCheck,
43
43
  upstreamFor,
44
44
  } from "#runtime/git.ts";
45
- import { detectInstallKind, type InstallKind } from "#runtime/installKind.ts";
45
+ import { detectInstallKind, type InstallKind, inspectionRootFor } 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([
@@ -150,6 +156,11 @@ function resolveWelcomeHome(explicit: string | null, env: NodeJS.ProcessEnv): st
150
156
  }
151
157
 
152
158
  export interface RuntimeRouteIo {
159
+ /**
160
+ * Where the command was run. No `runtime` read route consults it: since
161
+ * CR134 and CR138 they answer for the install, wherever they run. Tests pass
162
+ * it to show that it does not matter.
163
+ */
153
164
  cwd?: string;
154
165
  env?: NodeJS.ProcessEnv;
155
166
  stdout?: (text: string) => void;
@@ -165,18 +176,39 @@ function writeErr(io: RuntimeRouteIo, text: string): void {
165
176
  (io.stderr ?? ((value: string) => process.stderr.write(value)))(text);
166
177
  }
167
178
 
168
- /** Port of `welcome.main`. Always exits 0, like the oracle. */
169
- export function runWelcomeRoute(argv: readonly string[], io: RuntimeRouteIo = {}): number {
179
+ /**
180
+ * The seams of the routes that answer for the running install, beyond the
181
+ * route's own: the install and its npm, for tests. The welcome (CR135) and
182
+ * `release-notes pending` (CR134) take them.
183
+ */
184
+ export interface InstallRouteIo extends RuntimeRouteIo {
185
+ install?: InstallKind;
186
+ npm?: NpmRunner;
187
+ /** The OS home whose config directory holds a package's channel file. */
188
+ osHome?: string;
189
+ }
190
+
191
+ export type WelcomeRouteIo = InstallRouteIo;
192
+
193
+ /**
194
+ * Port of `welcome.main`. Always exits 0, like the oracle.
195
+ *
196
+ * The card and the status line answer for the install that is running, never
197
+ * for the cwd (CR135): Pi renders both in the person's project.
198
+ */
199
+ export function runWelcomeRoute(argv: readonly string[], io: WelcomeRouteIo = {}): number {
170
200
  const env = io.env ?? process.env;
171
- const cwd = io.cwd ?? process.cwd();
172
201
  const args = argv.slice(1);
173
202
  if (welcomeDisabled(env)) return 0;
174
203
 
175
204
  const home = resolveWelcomeHome(optionValue(args, "--mirror-home"), env);
205
+ // The running tree's manifest: a file read, so the status line still spawns nothing.
206
+ const version = packageVersion(runningTreeRoot()) ?? "unknown";
176
207
 
177
208
  if (args.includes("--status-line")) {
178
209
  const line = composeStatusLine({
179
210
  mirrorHome: home,
211
+ installedVersion: version,
180
212
  sessionId: optionValue(args, "--session-id"),
181
213
  env,
182
214
  });
@@ -184,26 +216,39 @@ export function runWelcomeRoute(argv: readonly string[], io: RuntimeRouteIo = {}
184
216
  return 0;
185
217
  }
186
218
 
187
- const version = packageVersion(runningTreeRoot()) ?? "unknown";
188
- const card = composeWelcome({
219
+ const { card, check } = composeWelcome({
189
220
  mirrorHome: home,
190
- cwd,
191
- treeRoot: runningTreeRoot(),
221
+ install: io.install ?? detectThisInstall(),
192
222
  env,
193
223
  version,
194
- updateChannel: inspectUpdateChannel(cwd, null),
224
+ ...(io.npm === undefined ? {} : { npm: io.npm }),
225
+ ...(io.osHome === undefined ? {} : { osHome: io.osHome }),
195
226
  });
196
227
  if (card) writeOut(io, `${card}\n`);
228
+ if (home !== null && check !== null) logWelcomeCheck(home, env, check);
197
229
  return 0;
198
230
  }
199
231
 
232
+ /**
233
+ * One front-door line per remote check, so a slow session start can be
234
+ * explained later: what was asked, how it answered, how long it took. Never a
235
+ * path or a version.
236
+ */
237
+ function logWelcomeCheck(home: string, env: NodeJS.ProcessEnv, check: RemoteCheck): void {
238
+ logFrontDoor(frontDoorLogPath(join(home, dbNameForEnv(env.MEMORY_ENV || "production"))), {
239
+ command: "welcome",
240
+ route: "ts",
241
+ exitCode: 0,
242
+ detail: `update-check source=${check.source} verdict=${check.verdict} ms=${check.ms}`,
243
+ });
244
+ }
245
+
200
246
  /** Port of the read half of `cmd_runtime`. */
201
247
  export async function runRuntimeReadRoute(
202
248
  argv: readonly string[],
203
- io: RuntimeRouteIo = {},
249
+ io: InstallRouteIo = {},
204
250
  ): Promise<number> {
205
251
  const env = io.env ?? process.env;
206
- const cwd = io.cwd ?? process.cwd();
207
252
  const args = argv.slice(1);
208
253
  const subcommand = args[0] ?? "";
209
254
  // The version is the running tree's, never the cwd's: for a skill the cwd
@@ -213,19 +258,24 @@ export async function runRuntimeReadRoute(
213
258
 
214
259
  if (subcommand === "version") {
215
260
  const startArg = optionValue(args, "--start");
216
- const start = startArg ? expandHome(startArg) : cwd;
217
- const install = detectThisInstall();
261
+ const install = io.install ?? detectThisInstall();
262
+ // The install's own repository, or the one `--start` names: the one
263
+ // explicit way these commands run git outside the install (CR138).
264
+ const root = inspectionRootFor(install, startArg ? expandHome(startArg) : null);
218
265
  writeOut(
219
266
  io,
220
267
  renderRuntimeVersion({
221
268
  version,
222
269
  install,
223
- git: inspectGitFor(install, start),
224
- cloneRole: inspectCloneRole(start),
225
- updateChannel:
226
- install.kind === "package"
227
- ? channelFor(install, env, homedir(), optionValue(args, "--channel"))
228
- : inspectUpdateChannel(start, optionValue(args, "--channel")),
270
+ git: inspectGitFor(install, root),
271
+ cloneRole: cloneRoleFor(install, root),
272
+ updateChannel: channelForReport(
273
+ install,
274
+ root,
275
+ env,
276
+ io.osHome ?? homedir(),
277
+ optionValue(args, "--channel"),
278
+ ),
229
279
  }),
230
280
  );
231
281
  return 0;
@@ -233,24 +283,24 @@ export async function runRuntimeReadRoute(
233
283
 
234
284
  if (subcommand === "status") {
235
285
  const report = buildRuntimeStatus({
236
- start: cwd,
237
286
  mirrorHome: optionValue(args, "--mirror-home"),
238
287
  channel: optionValue(args, "--channel"),
239
288
  env,
240
289
  version,
241
- install: detectThisInstall(),
290
+ install: io.install ?? detectThisInstall(),
242
291
  });
243
292
  writeOut(io, renderRuntimeStatus(report, env));
244
293
  return statusVerdict(report) === "ready" ? 0 : 1;
245
294
  }
246
295
 
247
296
  if (subcommand === "diagnose") {
297
+ // The install's own tree, so its worktree findings are Mirror's and never
298
+ // the cwd project's (CR138).
248
299
  const report = buildRuntimeStatus({
249
- start: cwd,
250
300
  mirrorHome: optionValue(args, "--mirror-home"),
251
301
  env,
252
302
  version,
253
- install: detectThisInstall(),
303
+ install: io.install ?? detectThisInstall(),
254
304
  });
255
305
  const findings = [
256
306
  ...diagnoseRuntime(report, inspectGitWorktree(report.git.repository)),
@@ -280,19 +330,14 @@ export async function runRuntimeReadRoute(
280
330
  if (subcommand === "release-notes") {
281
331
  const target = firstPositional(args.slice(1)) ?? "latest";
282
332
  if (target === "pending") {
283
- writeOut(
284
- io,
285
- renderReleaseNotesBundle(
286
- buildPendingReleaseNotes({
287
- // `--from` omitted falls back to the checkout's own version,
288
- // exactly as the oracle does before normalizing.
289
- currentVersion: optionValue(args, "--from") ?? version,
290
- ref: optionValue(args, "--ref") ?? "origin/stable",
291
- fetch: !args.includes("--no-fetch"),
292
- start: cwd,
293
- }),
294
- ),
295
- );
333
+ const pending = pendingReleaseNotes(args, io, env, version);
334
+ if ("usage" in pending) {
335
+ writeErr(io, pending.usage);
336
+ return 2;
337
+ }
338
+ writeOut(io, renderReleaseNotesBundle(pending.bundle));
339
+ // `unknown` exits 1, as `update --check` does on `unresolved` (CR134, decision 3).
340
+ return pending.bundle.unknown_reason === null ? 0 : 1;
296
341
  } else {
297
342
  // The notes ship in the package at the same path as in the checkout
298
343
  // (US3 D8), read from the tree that holds the running front door --
@@ -304,7 +349,7 @@ export async function runRuntimeReadRoute(
304
349
 
305
350
  if (subcommand === "backup") return runRuntimeBackup(args.slice(1), io, env);
306
351
  if (subcommand === "migrate") return runRuntimeMigrate(args.slice(1), io, env);
307
- if (subcommand === "update") return runRuntimeUpdate(args.slice(1), io, env, cwd);
352
+ if (subcommand === "update") return runRuntimeUpdate(args.slice(1), io, env);
308
353
  if (subcommand === "channel") return runRuntimeChannel(args.slice(1), io, env);
309
354
 
310
355
  // Routing answers every name it does not recognize before dispatch (the
@@ -450,20 +495,28 @@ function runRuntimeChannel(
450
495
  */
451
496
  function runRuntimeUpdate(
452
497
  args: readonly string[],
453
- io: RuntimeRouteIo,
498
+ io: InstallRouteIo,
454
499
  env: NodeJS.ProcessEnv,
455
- cwd: string,
456
500
  ): number {
457
501
  const version = packageVersion(runningTreeRoot()) ?? "unknown";
458
502
  const channelOverride = optionValue(args, "--channel");
459
503
  // The update lane asks npm, once, whether `-g` addresses this install: a
460
504
  // package outside npm's own global root is not the one `npm install -g`
461
505
  // would replace. The read routes do not pay for the probe.
462
- const install = detectInstallKind({
463
- frontDoorPath: frontDoorSelfPath(),
464
- npmRootGlobal: npmRootGlobal(),
465
- });
466
- const start = install.kind === "clone" ? install.repository : cwd;
506
+ //
507
+ // npm runs in the tree the front door runs from -- for a package, the
508
+ // install's own root -- and never in the cwd, whose `.npmrc` could name
509
+ // another registry or prefix (CR135). The strategy's own calls do the same.
510
+ //
511
+ // Git runs in the clone's own checkout and nowhere else: the gate's report
512
+ // and `--check` name no cwd (CR138), whose `.git/config` could name a
513
+ // command for git to run.
514
+ const install =
515
+ io.install ??
516
+ detectInstallKind({
517
+ frontDoorPath: frontDoorSelfPath(),
518
+ npmRootGlobal: npmRootGlobal(runningTreeRoot()),
519
+ });
467
520
  const channel = channelFor(install, env, homedir(), channelOverride);
468
521
 
469
522
  if (args.includes("--check")) {
@@ -477,7 +530,12 @@ function runRuntimeUpdate(
477
530
  ? 0
478
531
  : 1;
479
532
  }
480
- const availability = checkUpdateAvailability(start, channelOverride, version);
533
+ // An install that cannot be identified has no checkout to ask, and the
534
+ // cwd is not one (CR138, decision 2). `runtime update` refuses it too.
535
+ const availability =
536
+ install.kind === "clone"
537
+ ? checkUpdateAvailability(install.repository, channelOverride, version)
538
+ : unavailableUpdateCheck(version, channel, `install kind unknown: ${install.reason}`);
481
539
  writeOut(io, renderRuntimeUpdateAvailability(availability));
482
540
  return availability.status === "up_to_date" || availability.status === "update_available"
483
541
  ? 0
@@ -501,7 +559,6 @@ function runRuntimeUpdate(
501
559
 
502
560
  const gate = (): { ready: boolean; allowed: boolean; detail: string } => {
503
561
  const report = buildRuntimeStatus({
504
- start,
505
562
  mirrorHome,
506
563
  channel: channelOverride,
507
564
  env,
@@ -548,6 +605,16 @@ function runRuntimeUpdate(
548
605
  });
549
606
 
550
607
  writeOut(io, renderUpdateResult(result));
608
+ // The install now holds its channel's version: say so where the welcome card
609
+ // and the status line read it, or they keep announcing what was just
610
+ // installed (CR135).
611
+ if (result.success && !args.includes("--dry-run")) {
612
+ recordUpToDate(mirrorHome, {
613
+ channel: channel.value,
614
+ commit: install.kind === "clone" ? result.newRef : null,
615
+ checkedAt: isoNow(),
616
+ });
617
+ }
551
618
  if (repair && result.success) {
552
619
  // After the stages, not before them: the instruction is what to do NEXT,
553
620
  // and it reads as a caption on a result the operator has already seen.
@@ -596,6 +663,74 @@ function detectThisInstall(): InstallKind {
596
663
  return detectInstallKind({ frontDoorPath: frontDoorSelfPath() });
597
664
  }
598
665
 
666
+ /**
667
+ * `release-notes pending` for the install that is running, never for the cwd
668
+ * (CR134), which for a skill is the person's project. A clone reads its own
669
+ * channel's ref -- or `--ref` -- in its own repository; a package asks the
670
+ * registry what its channel holds; an install that cannot be identified has no
671
+ * source, and says so.
672
+ */
673
+ function pendingReleaseNotes(
674
+ args: readonly string[],
675
+ io: InstallRouteIo,
676
+ env: NodeJS.ProcessEnv,
677
+ version: string,
678
+ ): { bundle: ReleaseNotesBundle } | { usage: string } {
679
+ const install = io.install ?? detectThisInstall();
680
+ const osHome = io.osHome ?? homedir();
681
+ const ref = optionValue(args, "--ref");
682
+ const fetch = !args.includes("--no-fetch");
683
+
684
+ if (install.kind === "clone") {
685
+ return {
686
+ bundle: buildPendingReleaseNotes({
687
+ // `--from` omitted falls back to the running tree's own version.
688
+ currentVersion: optionValue(args, "--from") ?? version,
689
+ ref: ref ?? upstreamFor(channelFor(install, env, osHome)),
690
+ repository: install.repository,
691
+ fetch,
692
+ }),
693
+ };
694
+ }
695
+
696
+ if (install.kind === "package") {
697
+ if (ref !== null) {
698
+ return {
699
+ usage:
700
+ `release-notes pending: --ref names a git ref, and this install is a package ` +
701
+ `(${install.name}@${install.version}), which reads its channel from the npm registry\n`,
702
+ };
703
+ }
704
+ const currentVersion = optionValue(args, "--from") ?? install.version;
705
+ const channel = channelFor(install, env, osHome).value;
706
+ if (!fetch) {
707
+ return {
708
+ bundle: unknownReleaseNotes(
709
+ currentVersion,
710
+ `npm ${install.name}@${channel}`,
711
+ "a package keeps no copy of its channel to read offline; run without --no-fetch",
712
+ ),
713
+ };
714
+ }
715
+ return {
716
+ bundle: buildPendingReleaseNotesFromRegistry({
717
+ currentVersion,
718
+ install,
719
+ channel,
720
+ npm: io.npm ?? defaultNpmRunner,
721
+ }),
722
+ };
723
+ }
724
+
725
+ return {
726
+ bundle: unknownReleaseNotes(
727
+ optionValue(args, "--from") ?? version,
728
+ null,
729
+ `install kind unknown (${install.reason})`,
730
+ ),
731
+ };
732
+ }
733
+
599
734
  /**
600
735
  * argparse's positional for `release-notes`: the first bare token that is not
601
736
  * consumed as the VALUE of `--from` or `--ref`. Walking the list is the only
@@ -82,6 +82,24 @@ export function channelFor(
82
82
  return { value: DEFAULT_CHANNEL, source: null, note: "install kind unknown" };
83
83
  }
84
84
 
85
+ /**
86
+ * The channel row of `status` and `version` (CR138). A package's is its
87
+ * config file's. A clone's is the marker in the repository the rows come from
88
+ * (`inspectionRootFor`): its own checkout, or `version --start PATH`, the
89
+ * flag's documented meaning. With no root, the install's own answer, which for
90
+ * a clone without `--start` is the same marker, read the same way.
91
+ */
92
+ export function channelForReport(
93
+ install: InstallKind,
94
+ root: string | null,
95
+ env: ConfigEnv = process.env,
96
+ home: string = homedir(),
97
+ override: string | null = null,
98
+ ): MarkerValue {
99
+ if (install.kind === "package" || root === null) return channelFor(install, env, home, override);
100
+ return inspectUpdateChannel(root, override);
101
+ }
102
+
85
103
  export type ChannelWrite =
86
104
  | { ok: true; path: string; value: string; previous: string }
87
105
  | { ok: false; reason: string; exitCode: 1 | 2 };
@@ -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
- GIT_NETWORK_TIMEOUT_MS,
312
+ networkTimeoutMs,
307
313
  );
308
314
  if (lsRemote.code !== 0 || !lsRemote.stdout) {
309
315
  return availability(version, updateChannel, {
@@ -430,12 +436,52 @@ export const NO_REPOSITORY: GitStatus = {
430
436
  };
431
437
 
432
438
  /**
433
- * The git facts of `start`, for the install's kind: a clone's are inspected,
434
- * a package's are `NO_REPOSITORY` (CV22.DS10.US3 plateau 4). Grading the cwd
435
- * of a package install as a tree read "not a git repository" as a git error.
439
+ * The git facts of the install's `root` (`inspectionRootFor`): a package's
440
+ * are `NO_REPOSITORY` (CV22.DS10.US3 plateau 4), since grading the cwd of a
441
+ * package install as a tree read "not a git repository" as a git error. With
442
+ * no root -- an install that cannot be identified, and no `--start` -- no
443
+ * repository is inspected at all, and the rows say why (CR138, decision 2).
444
+ */
445
+ export function inspectGitFor(install: InstallKind, root: string | null): GitStatus {
446
+ if (install.kind === "package") return NO_REPOSITORY;
447
+ if (root === null) {
448
+ const reason =
449
+ install.kind === "unknown" ? `install kind unknown: ${install.reason}` : "no repository";
450
+ return { ...NO_REPOSITORY, error: reason };
451
+ }
452
+ return inspectGit(root);
453
+ }
454
+
455
+ /**
456
+ * The clone role the install's checkout declares (CR138). A package has none
457
+ * to declare. With no root, nothing is read, and the oracle's answer for no
458
+ * repository stands: the default, with the note that says why.
459
+ */
460
+ export function cloneRoleFor(install: InstallKind, root: string | null): MarkerValue {
461
+ if (install.kind === "package") return { value: "none", source: null, note: "package install" };
462
+ if (root === null) return { value: DEFAULT_CLONE_ROLE, source: null, note: "no repository" };
463
+ return inspectCloneRole(root);
464
+ }
465
+
466
+ /** The clone-role rows of `status` and `version`, one renderer for both (CR138). */
467
+ export function renderCloneRoleLines(install: InstallKind, role: MarkerValue): string[] {
468
+ if (install.kind === "package") return ["Clone role: none (package install)"];
469
+ const lines = [`Clone role: ${role.value}`];
470
+ if (role.note) lines.push(`Clone role note: ${role.note}`);
471
+ return lines;
472
+ }
473
+
474
+ /**
475
+ * `update --check` for an install whose repository is not asked: the same
476
+ * shape as the check's own `unknown`, with the reason (CR138). An install that
477
+ * cannot be identified has no checkout to ask, and the cwd is not one.
436
478
  */
437
- export function inspectGitFor(install: InstallKind, start: string): GitStatus {
438
- return install.kind === "package" ? NO_REPOSITORY : inspectGit(start);
479
+ export function unavailableUpdateCheck(
480
+ version: string,
481
+ updateChannel: MarkerValue,
482
+ note: string,
483
+ ): UpdateAvailability {
484
+ return availability(version, updateChannel, { note });
439
485
  }
440
486
 
441
487
  /**
@@ -464,8 +510,7 @@ export function renderRuntimeVersion(report: RuntimeVersionReport): string {
464
510
  lines.push(`Version: ${report.version}`);
465
511
  lines.push(...renderInstallLines(report.install));
466
512
  lines.push(...renderRepositoryLines(report.install, report.git, false));
467
- lines.push(`Clone role: ${report.cloneRole.value}`);
468
- if (report.cloneRole.note) lines.push(`Clone role note: ${report.cloneRole.note}`);
513
+ lines.push(...renderCloneRoleLines(report.install, report.cloneRole));
469
514
  lines.push(`Update channel: ${report.updateChannel.value}`);
470
515
  if (report.updateChannel.note) lines.push(`Update channel note: ${report.updateChannel.note}`);
471
516
  return `${lines.join("\n")}\n`;
@@ -201,6 +201,21 @@ export function detectInstallKind(probe: InstallKindProbe): InstallKind {
201
201
  };
202
202
  }
203
203
 
204
+ /**
205
+ * Where the repository rows of `status`, `version`, and `diagnose` come from
206
+ * (CR138): `start` when one is given -- `version --start PATH`, or a test --
207
+ * else a clone's own checkout, else nowhere. Never the cwd. For a skill the
208
+ * cwd is the person's project, and git run there reads that project's config,
209
+ * where `core.fsmonitor` names a command git runs on `status`.
210
+ */
211
+ export function inspectionRootFor(
212
+ install: InstallKind,
213
+ start: string | null | undefined,
214
+ ): string | null {
215
+ if (start !== null && start !== undefined) return resolve(start);
216
+ return install.kind === "clone" ? install.repository : null;
217
+ }
218
+
204
219
  /** One line for a render or a log entry. */
205
220
  export function describeInstallKind(install: InstallKind): string {
206
221
  if (install.kind === "clone") return `clone (${install.repository})`;