@bli-cockpit/cli 0.2.148 → 0.2.149

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.
@@ -1,7 +1,5 @@
1
1
  import os from "node:os";
2
- import { readRootWorkScan, outsideWorkDirectories, readRootApprovals, rootsAddCommand, rootPathApi, sessionCoverageLine } from "../root-work.js";
3
- import { writeJsonFile } from "../local-state-files.js";
4
- import { containsPath } from "../root-normalization.js";
2
+ import { readRootWorkScan, outsideWorkDirectories, rootsAddCommand, sessionCoverageLine } from "../root-work.js";
5
3
  import { scanWorkDirectories } from "./local-discovery.js";
6
4
  import { sendCollectorHeartbeatBestEffort } from "./heartbeat.js";
7
5
  import { isInteractiveDoctorFix } from "./doctor-report.js";
@@ -157,55 +155,36 @@ function shellQuote(value) {
157
155
  return `'${value.replace(/'/gu, "'\\''")}'`;
158
156
  }
159
157
  /**
160
- * The last scan names work missed by the saved boundary. Diagnosis never
161
- * grants consent — and since BLI-10734 there is nothing left to consent TO
162
- * for the session report itself, only for raw evidence: every session this
163
- * scan saw now reaches the server (`sessionCoverageLine`), labelled
164
- * `root_scope: "outside"` when its directory sits outside every approved
165
- * root. Widening the boundary still matters for the redacted transcript
166
- * BYTES, which stay root-gated, so this row keeps flagging the gap and
167
- * offering `roots add` — it is just no longer offering to rescue a session
168
- * that would otherwise vanish.
158
+ * The last scan names where this machine's work happens. Diagnosis never
159
+ * grants consent, and since BLI-10734 there is nothing left to consent TO for
160
+ * the session report itself: every session this scan saw already reached the
161
+ * server (`sessionCoverageLine`), labelled `root_scope: "outside"` when its
162
+ * directory sits outside every approved root. BLI-10740 finishes that ruling
163
+ * on the doctor row itself — approved roots are a LABEL on the upload, never
164
+ * a gate on it, so a session sitting outside one is not a defect doctor found
165
+ * and cannot repair. This row is therefore always informational (`ok`): it
166
+ * reports how many of this scan's sessions were inside vs. outside, never
167
+ * asks for approval, and attempts no fix. Widening the boundary still buys
168
+ * the redacted transcript BYTES for those outside directories (those stay
169
+ * root-gated), so the message still names `cockpit roots add` as something an
170
+ * operator can choose to do, never something doctor is stuck waiting on. The
171
+ * row only turns red the ordinary way every other doctor check does: the scan
172
+ * itself throwing (`work_directory_scan_incomplete` from unreadable session
173
+ * folders), which `runDoctorWithDeps`'s existing `check_threw` handling
174
+ * already turns into a `fail` row — a real collection problem, not a roots
175
+ * problem.
169
176
  */
170
177
  export async function readRootsCoverWork(context) {
171
178
  const homeDir = context.command.homeDir ?? os.homedir();
172
179
  const paths = getCollectorRuntimePaths(homeDir);
173
180
  const scan = await readRootWorkScan(paths) ?? await scanWorkDirectories(homeDir, false);
174
181
  const outside = outsideWorkDirectories(scan, (await readLocalCollectorConfig(paths))?.default_repo_paths ?? []);
175
- const count = outside.reduce((sum, entry) => sum + entry.count, 0);
176
- // ticket 10727 (ticket 4382 measured) then BLI-10734 (roots are labels, not
177
- // gates): the coverage sentence a person asked doctor for, appended rather
178
- // than replacing the existing per-directory breakdown below. Since
179
- // BLI-10734, widening a root no longer rescues a session from oblivion —
180
- // its metadata already reached the server either way — it buys the
181
- // redacted transcript content instead, so the "needs fix" branch says so.
182
- if (!count)
183
- return ok("roots-cover-work", "work_covered", `${scan.observed} sessions seen; no known working directories outside approved roots. ${sessionCoverageLine(scan, count)}`);
182
+ const outsideCount = outside.reduce((sum, entry) => sum + entry.count, 0);
183
+ const insideCount = scan.observed - outsideCount;
184
+ if (!outsideCount)
185
+ return ok("roots-cover-work", "work_covered", `${scan.observed} sessions seen; no known working directories outside approved roots. ${sessionCoverageLine(scan, outsideCount)}`);
184
186
  return {
185
- ...needsFix("roots-cover-work", "work_outside_roots", `${count} of ${scan.observed} sessions have working directories outside every approved root — their redacted transcript content stays unuploaded until the root widens, though their metadata already reaches the server with an outside label (scan ${scan.generated_at}):\n${outside.map(entry => `${entry.count}: ${entry.directory}\n ${rootsAddCommand(entry.directory, process.platform, homeDir)}`).join("\n")}\n\n${sessionCoverageLine(scan, count)}`),
187
+ ...ok("roots-cover-work", "work_outside_roots_labelled", `${insideCount} of ${scan.observed} sessions inside approved roots, ${outsideCount} outside — every one already reached the server, the outside ones carrying an outside label. Their redacted transcript content stays unuploaded until a root widens (scan ${scan.generated_at}):\n${outside.map(entry => `${entry.count}: ${entry.directory}\n ${rootsAddCommand(entry.directory, process.platform, homeDir)}`).join("\n")}\n\n${sessionCoverageLine(scan, outsideCount)}`),
186
188
  nextAction: rootsAddCommand(outside[0].directory, process.platform, homeDir),
187
189
  };
188
- }
189
- export async function fixRootsCoverWork(context) {
190
- const homeDir = context.command.homeDir ?? os.homedir();
191
- const paths = getCollectorRuntimePaths(homeDir);
192
- const config = await readLocalCollectorConfig(paths);
193
- if (!config)
194
- return readRootsCoverWork(context);
195
- const scan = await readRootWorkScan(paths) ?? await scanWorkDirectories(homeDir);
196
- const approvals = await readRootApprovals(paths);
197
- const outside = outsideWorkDirectories(scan, config.default_repo_paths);
198
- const additions = outside.filter(entry => approvals.some(root => containsPath(root, entry.directory, rootPathApi())));
199
- if (additions.length) {
200
- // Restore the precise approved directories, preserving every other config option.
201
- const roots = normalizeCollectionRoots([...config.default_repo_paths, ...additions.map(entry => entry.directory)], rootPathApi());
202
- await writeJsonFile(paths.config_file, { ...config, default_repo_paths: roots });
203
- console.error("[doctor] roots-cover-work repaired", JSON.stringify({ added: additions.length }));
204
- await scanWorkDirectories(homeDir);
205
- }
206
- else {
207
- console.error("[doctor] roots-cover-work needs approval", JSON.stringify({ outside: outside.length, added: 0 }));
208
- }
209
- const checked = await readRootsCoverWork(context);
210
- return { ...checked, fixed: additions.length > 0 && checked.status === "ok" };
211
190
  }
@@ -2,7 +2,7 @@ import { LOCAL_COLLECTOR_VERSION } from "../local-state.js";
2
2
  import { isSemverBelow } from "../scheduled-self-update.js";
3
3
  import { describeError } from "../health-detail.js";
4
4
  import { withStage } from "../crash-guard.js";
5
- import { checkSingleInstallState, readRootsCoverWork, fixRootsCoverWork, fixAuthState, fixRootState, readAuthState, readRootState, } from "./doctor-access.js";
5
+ import { checkSingleInstallState, readRootsCoverWork, fixAuthState, fixRootState, readAuthState, readRootState, } from "./doctor-access.js";
6
6
  import { backfillCompletionStepState, backfillFixVerdict, checkBackfillState, checkDiskState, checkGcState, checkSyncState, fixBackfillState, fixDiskState, fixGcState, fixSyncState, syncBacklogDrainingVerdict, syncStandAsideVerdict, } from "./doctor-pipeline.js";
7
7
  import { checkMcpAnswersState } from "./doctor-mcp.js";
8
8
  import { checkMemoryDaemonState } from "./doctor-memory-daemon.js";
@@ -119,7 +119,10 @@ function doctorInvariants() {
119
119
  fix: fixRootState,
120
120
  requiresInteractiveFix: true,
121
121
  },
122
- { id: "roots-cover-work", check: context => context.deps.readRootsCoverWork(context), fix: context => context.deps.fixRootsCoverWork(context) },
122
+ // BLI-10740: roots are a label on the upload, never a gate — this row has
123
+ // no fix because a session outside an approved root is not a defect
124
+ // doctor can repair; it names the counts and moves on.
125
+ { id: "roots-cover-work", check: context => context.deps.readRootsCoverWork(context) },
123
126
  // Deliberately has NO fix: uninstalling software the operator did not ask
124
127
  // to have uninstalled is not a repair (BLI-3218/BLI-3553). Doctor names the
125
128
  // other install and the exact command; the person decides.
@@ -194,7 +197,6 @@ function defaultDoctorDeps(hooks) {
194
197
  runLogin: (context) => hooks.runLogin(context.command, context.io),
195
198
  readRoots: readRootState,
196
199
  readRootsCoverWork,
197
- fixRootsCoverWork,
198
200
  resolveAndSaveRoots: (context) => hooks.resolveAndSaveRoots(context.command, context.io),
199
201
  checkSingleInstall: checkSingleInstallState,
200
202
  checkAutostart: checkAutostartState,
@@ -91,7 +91,7 @@ export function localSubcommandHelp(command) {
91
91
  "",
92
92
  "Diagnose, repair, and verify this machine.",
93
93
  "Repairs CLI, background sync, sign-in, roots, memory and hooks, agent rules, catch-up, uploads, and cleanup.",
94
- "roots-cover-work names sessions outside saved roots and gives cockpit roots add commands. Previously approved directories are restored automatically.",
94
+ "roots-cover-work is informational: it names how many sessions are inside vs. outside saved roots (all already uploaded, outside ones labelled) and gives cockpit roots add commands for widening a root's redacted transcript content. It never needs a fix.",
95
95
  "--check or --no-repair diagnoses without repairs. --dry-run previews repairs.",
96
96
  "--backfill-budget defaults to 900 seconds; catch-up repeats chunks until complete, with progress every 30 seconds.",
97
97
  "--lock-wait defaults to 600 seconds; progress prints every 30 seconds.",
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
21
21
  }
22
22
 
23
23
  if (command === "--version" || command === "-V" || command === "version") {
24
- writeLine(io?.stdout ?? process.stdout, "0.2.148");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.149");
25
25
  return 0;
26
26
  }
27
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.148",
3
+ "version": "0.2.149",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,8 +27,8 @@
27
27
  "test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-surface-doors.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
28
28
  },
29
29
  "dependencies": {
30
- "@bli-cockpit/memory-mcp": "0.1.37",
31
- "@bli-cockpit/mcp": "0.1.71",
32
- "@bli-cockpit/telemetry-core": "0.1.49"
30
+ "@bli-cockpit/memory-mcp": "0.1.38",
31
+ "@bli-cockpit/mcp": "0.1.72",
32
+ "@bli-cockpit/telemetry-core": "0.1.50"
33
33
  }
34
34
  }