@bli-cockpit/cli 0.2.245 → 0.2.246

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.
@@ -0,0 +1,37 @@
1
+ import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor } from "./agent-door.js";
2
+ import { writeLine } from "./cli-io.js";
3
+ import { parseNamedArgs, optionalNonEmpty, optionalUrl } from "./local-arg-values.js";
4
+ const USAGE = "Usage: cockpit delivery report [--days <1-30>] [--end <YYYY-MM-DD>] [--review-minutes <1-240>] [--json]";
5
+ function bounded(raw, min, max) {
6
+ if (raw === undefined)
7
+ return undefined;
8
+ const n = Number(raw);
9
+ if (!Number.isInteger(n) || n < min || n > max)
10
+ throw new Error(USAGE);
11
+ return n;
12
+ }
13
+ export function parseDeliveryArgs(args) {
14
+ const values = parseNamedArgs(args, { allowedFlags: ["--days", "--end", "--review-minutes", "--json", "--home", "--dashboard-url"], valueFlags: ["--days", "--end", "--review-minutes", "--home", "--dashboard-url"] });
15
+ const end = values.flags.get("--end");
16
+ if (values.positionals.length !== 1 || values.positionals[0] !== "report" || (end !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(end)))
17
+ throw new Error(USAGE);
18
+ return { kind: "delivery", action: "report", days: bounded(values.flags.get("--days"), 1, 30), end, reviewMinutes: bounded(values.flags.get("--review-minutes"), 1, 240), json: values.booleans.has("--json"), homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")) };
19
+ }
20
+ export async function runDelivery(command, io) {
21
+ const door = await openAgentDoor("delivery", command, io);
22
+ const query = new URLSearchParams();
23
+ if (command.days !== undefined)
24
+ query.set("days", String(command.days));
25
+ if (command.end !== undefined)
26
+ query.set("end", command.end);
27
+ if (command.reviewMinutes !== undefined)
28
+ query.set("review_minutes", String(command.reviewMinutes));
29
+ const answer = await askAgentDoor(door, { path: `/api/delivery${query.size ? `?${query}` : ""}`, method: "GET", label: "delivery report", timeoutMs: 30_000 });
30
+ if (!answer.ok)
31
+ return failAgentDoor(door, "[delivery cli]", answer.reason, answer.detail);
32
+ if (command.json)
33
+ return emitAgentDoor(door, answer.body);
34
+ for (const line of answer.body.lines)
35
+ writeLine(io.stdout, line);
36
+ return 0;
37
+ }
@@ -56,4 +56,5 @@ export { parseRenameArgs } from './local-args-tower-rename.js';
56
56
  export { parseGlossaryArgs } from "./glossary.js";
57
57
  export { parseSpendArgs } from "./spend.js";
58
58
  export { parsePushArgs } from "./push.js";
59
- export { parseCiArgs } from "./ci.js";
59
+ export { parseCiArgs } from "./ci.js";
60
+ export { parseDeliveryArgs } from "./delivery.js";
@@ -22,7 +22,7 @@ import { parseRootsArgs } from "./roots.js";
22
22
  import { parseMilestoneArgs } from "./local-args-tower-milestone.js";
23
23
  import { SURFACE_ALIASES } from "./local-help.js";
24
24
  import { parseAgentRulesArgs, parseAnalyzeArgs, parseAutostartArgs, parseBackfillArgs, parseCleanArgs, parseDoctorArgs, parseInstallArgs, parseLoginArgs, parseMemoryArgs, parseLogoutArgs, parseOnboardArgs, parseReleaseArgs, parseServeArgs, parseSessionsArgs, parseSourcesArgs, parseStartArgs, parseStatusArgs, parseSyncArgs, parseUpdateArgs, } from "./local-args-collector.js";
25
- import { parseCiArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
25
+ import { parseCiArgs, parseDeliveryArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
26
26
  // `normalizeUrl` has always been part of this module's surface — `local.ts` and
27
27
  // `local-auth.ts` import it from here — so it stays exported from this address
28
28
  // even though it now lives next door. The same goes for the four names the
@@ -65,6 +65,7 @@ const COMMAND_PARSERS = new Map([
65
65
  ["models", (_command, rest) => parseModelsArgs(rest)],
66
66
  ["scout", (_command, rest) => parseScoutArgs(rest)],
67
67
  ["ci", (_command, rest) => parseCiArgs(rest)],
68
+ ["delivery", (_command, rest) => parseDeliveryArgs(rest)],
68
69
  ["ops", (_command, rest) => parseOpsArgs(rest)],
69
70
  ["slack", (_command, rest) => parseSlackArgs(rest)],
70
71
  ["settings", (_command, rest) => parseSettingsArgs(rest)],
@@ -395,6 +395,14 @@ export const TOWER_OPS_COMMAND_HELP = [
395
395
  "ci",
396
396
  ["Usage: cockpit ci report [--repo <owner/name>] [--json]", "Reads the daily stored CI report, as-of age and coverage. MCP twin: ci_report."],
397
397
  ],
398
+ [
399
+ "delivery",
400
+ [
401
+ "Usage: cockpit delivery report [--days <1-30>] [--end <YYYY-MM-DD>] [--review-minutes <1-240>] [--json]",
402
+ "Delivery scoreboard: two equal periods (before and after, default 7 days each, ending before --end, default today UTC) of merged PRs split gated and ungated, ready-to-merged median and p90, main CI red time, failed production builds, coverage and the ruleset as GitHub returned it.",
403
+ "--review-minutes is an assumption for the review-hours line, not a measurement. Counts show their denominators; no verdict is given. MCP twin: delivery_read. Web: TEAM > Delivery.",
404
+ ],
405
+ ],
398
406
  [
399
407
  "ops",
400
408
  [
@@ -53,6 +53,7 @@ export const rootCommandNames = new Set([
53
53
  "models",
54
54
  "scout",
55
55
  "ci",
56
+ "delivery",
56
57
  "ops",
57
58
  "slack",
58
59
  "settings",
@@ -121,6 +122,7 @@ const COMMAND_SUMMARY_LINES = [
121
122
  " cockpit models [list|show <provider:model>|compare <provider:model> <provider:model> [...]] [--highlight] [--dashboard-url <url>] [--json]",
122
123
  " cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
123
124
  " cockpit ci report [--repo <owner/name>] [--json]",
125
+ " cockpit delivery report [--days <1-30>] [--end <YYYY-MM-DD>] [--review-minutes <1-240>] [--json]",
124
126
  " cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
125
127
  " cockpit slack [connect [--workspace bli|blue_pearl] | disconnect --workspace bli|blue_pearl | coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [--person <p>] [--channel <c>] [--query <text> [--word]] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--format compact|full]] [--json]",
126
128
  " cockpit settings [personal [--chat-model <key>] [--brief-model <key>] | switches [set <key> <value>] | models [set --chat <key>] [--memory <id>] | cli-floor [<version>] | env list|set --project <p> --file <f> --content-stdin|delete --id <uuid> [--yes]] [--json]",
@@ -1,11 +1,13 @@
1
1
  import { runScout } from "./scout.js";
2
2
  import { runCi } from "./ci.js";
3
+ import { runDelivery } from "./delivery.js";
3
4
  import { runOps } from "./ops.js";
4
5
  /** Dispatch the operational report commands. */
5
6
  export function runOpsCommand(command, io) {
6
7
  switch (command.kind) {
7
8
  case "scout": return runScout(command, io);
8
9
  case "ci": return runCi(command, io);
10
+ case "delivery": return runDelivery(command, io);
9
11
  case "ops": return runOps(command, io);
10
12
  }
11
13
  }
@@ -187,6 +187,7 @@ async function runTowerCommand(command, io) {
187
187
  case "models": return await runModels(command, io);
188
188
  case "scout":
189
189
  case "ci":
190
+ case "delivery":
190
191
  case "ops":
191
192
  return await runOpsCommand(command, io);
192
193
  case "slack": return await runSlack(command, io);
@@ -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.245");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.246");
25
25
  return 0;
26
26
  }
27
27
 
@@ -15,7 +15,8 @@
15
15
  *
16
16
  * Three reasons close the gate, most urgent first:
17
17
  *
18
- * - `low_disk`: the volume's free space is under max(5 GiB, 5% of the volume).
18
+ * - `low_disk`: the volume's free space is under max(5 GiB, 5% of the volume),
19
+ * for a file bigger than 1% of the free space left (smaller files are admitted).
19
20
  * - `state_over_ceiling`: everything the collector writes under its state dir
20
21
  * (staging, spool, logs, cursors, state; never the study folders
21
22
  * `state-dir-audit.ts` recognises) would pass the hard ceiling, 512 MiB by
@@ -43,6 +44,12 @@ export const STATE_CEILING_ENV = "COCKPIT_STATE_CEILING_BYTES";
43
44
  export const MIN_FREE_ENV = "COCKPIT_MIN_FREE_BYTES";
44
45
  export const LOW_DISK_FLOOR_BYTES = 5 * 1024 * 1024 * 1024;
45
46
  export const LOW_DISK_FLOOR_FRACTION = 0.05;
47
+ /**
48
+ * A file at most this fraction of the free space left is never refused for low
49
+ * disk: a 7 MB upload does not threaten a volume with gigabytes free, even one
50
+ * under the floor. Only a copy that would eat a real share of what is left waits.
51
+ */
52
+ export const LOW_DISK_SMALL_FILE_FRACTION = 0.01;
46
53
  /**
47
54
  * Each pack's `manifest.json` is staged beside the payload and its size grows
48
55
  * with the gap ledger, so it is not known when a file is admitted. A tenth of
@@ -174,9 +181,22 @@ export function admitToStaging(allowance, byteLength, options = {}) {
174
181
  const spent = allowance.admitted_bytes;
175
182
  const stagedNow = snapshot.staging_bytes + spent;
176
183
  let reason = null;
177
- if (snapshot.free_bytes !== null &&
184
+ const freeNow = snapshot.free_bytes === null ? null : snapshot.free_bytes - spent;
185
+ const belowFloor = freeNow !== null &&
178
186
  snapshot.floor_bytes !== null &&
179
- snapshot.free_bytes - spent - byteLength < snapshot.floor_bytes) {
187
+ freeNow - byteLength < snapshot.floor_bytes;
188
+ const smallForFree = freeNow !== null && freeNow > 0 && byteLength <= freeNow * LOW_DISK_SMALL_FILE_FRACTION;
189
+ if (belowFloor && smallForFree) {
190
+ console.error("[staging-gate] low-disk floor waived, file is under 1% of free space", JSON.stringify({
191
+ event: STAGING_DEFERRED,
192
+ reason: "low_disk_waived_small_file",
193
+ kind: options.kind ?? null,
194
+ file_bytes: byteLength,
195
+ free_bytes: freeNow,
196
+ floor_bytes: snapshot.floor_bytes,
197
+ }));
198
+ }
199
+ if (belowFloor && !smallForFree) {
180
200
  reason = "low_disk";
181
201
  }
182
202
  else if (snapshot.collector_bytes + spent + byteLength > snapshot.ceiling_bytes) {
@@ -202,6 +222,7 @@ export function admitToStaging(allowance, byteLength, options = {}) {
202
222
  reason,
203
223
  kind: options.kind ?? null,
204
224
  file_bytes: byteLength,
225
+ needed_bytes: byteLength,
205
226
  staging_bytes: snapshot.staging_bytes,
206
227
  collector_bytes: snapshot.collector_bytes,
207
228
  free_bytes: snapshot.free_bytes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.245",
3
+ "version": "0.2.246",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@bli-cockpit/memory-mcp": "0.1.72",
31
- "@bli-cockpit/mcp": "0.1.150",
31
+ "@bli-cockpit/mcp": "0.1.151",
32
32
  "@bli-cockpit/telemetry-core": "0.1.79"
33
33
  },
34
34
  "repository": {