@stage5/lumine 0.3.48 → 0.3.49

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,118 @@
1
+ // `lumine admin build-plans stuck|recheck`: Lumine Build plans whose current
2
+ // milestone is not finishing (twinkle-api controllers/cli/admin/buildPlans.ts).
3
+ // stuck lists running plans with their current milestone's runs, Energy vs
4
+ // estimate, gradings and loop-guard state (flagged ones only unless --all).
5
+ // recheck re-grades the current milestone of up to 10 builds with today's
6
+ // evaluator, free for the creator; it is a dry run unless --confirm is given.
7
+
8
+ export const BUILD_PLANS_USAGE =
9
+ "Usage: lumine admin build-plans stuck [--days <1-90>] [--all] | recheck <buildId> [<buildId> ...] [--confirm] [--json]";
10
+ const RECHECK_MAX = 10;
11
+
12
+ function validationError(message) {
13
+ const error = new Error(message);
14
+ error.code = "CLI_ADMIN_CLI_VALIDATION";
15
+ return error;
16
+ }
17
+
18
+ function confirmed(options) {
19
+ const confirm = options.adminConfirm;
20
+ // absent is "", a bare --confirm is "true"
21
+ if (confirm !== "" && confirm !== "true") {
22
+ throw validationError("Pass --confirm on its own, without a value.");
23
+ }
24
+ return confirm === "true";
25
+ }
26
+
27
+ export function buildBuildPlansOperation({ action, options }) {
28
+ if (!action || action === "stuck") {
29
+ const query = new URLSearchParams();
30
+ if (options.adminDays) {
31
+ const days = Number(options.adminDays);
32
+ if (!Number.isSafeInteger(days) || days < 1 || days > 90) {
33
+ throw validationError("--days must be a whole number from 1 to 90.");
34
+ }
35
+ query.set("days", String(days));
36
+ }
37
+ if (options.adminAll) query.set("all", "1");
38
+ const qs = query.toString();
39
+ return {
40
+ name: "build-plans.stuck",
41
+ method: "GET",
42
+ path: `/cli/admin/build-plans/stuck${qs ? `?${qs}` : ""}`,
43
+ body: undefined,
44
+ mutates: false,
45
+ requiresRun: false,
46
+ };
47
+ }
48
+ if (action === "recheck") {
49
+ const ids = (options.positional || [])
50
+ .slice(2)
51
+ .map((value) => String(value).split(","))
52
+ .flat()
53
+ .filter(Boolean)
54
+ .map((value) => Number(value));
55
+ if (
56
+ !ids.length ||
57
+ ids.length > RECHECK_MAX ||
58
+ ids.some((id) => !Number.isSafeInteger(id) || id <= 0)
59
+ ) {
60
+ throw validationError(`Give 1 to ${RECHECK_MAX} build ids. ${BUILD_PLANS_USAGE}`);
61
+ }
62
+ const confirm = confirmed(options);
63
+ return {
64
+ name: confirm ? "build-plans.recheck" : "build-plans.recheck.preview",
65
+ method: "POST",
66
+ path: "/cli/admin/build-plans/recheck",
67
+ body: { buildIds: [...new Set(ids)], dryRun: !confirm },
68
+ mutates: confirm,
69
+ requiresRun: false,
70
+ // One grading per build, one at a time, each up to a few minutes.
71
+ timeoutMs: 15 * 60 * 1000,
72
+ };
73
+ }
74
+ throw validationError(BUILD_PLANS_USAGE);
75
+ }
76
+
77
+ function formatRow(row) {
78
+ const ratio = row.energyRatio == null ? "?" : `${row.energyRatio}x`;
79
+ const milestone =
80
+ row.milestoneIndex == null ? "-" : `${row.milestoneIndex + 1}/${row.milestones}`;
81
+ const coverage = row.coverage
82
+ ? ` files ${row.coverage.shown}/${row.coverage.total}${row.coverage.toolRounds ? ` +${row.coverage.toolFilesRead} read in ${row.coverage.toolRounds} rounds` : ""}${row.coverage.omittedReferenced ? `, ${row.coverage.omittedReferenced} referenced omitted` : ""}`
83
+ : "";
84
+ const state = row.stalled
85
+ ? ` STALLED(${row.stallReason})`
86
+ : row.wouldStall
87
+ ? ` would stall (${row.wouldStall})`
88
+ : "";
89
+ return `Build ${row.buildId}: milestone ${milestone} (${row.milestonesDone} done), ${row.runs} runs, Energy ${ratio} of estimate, ${row.evaluations ?? "?"} gradings, non-pass streak ${row.consecutiveNonPass ?? "?"}, ${row.passed}/${row.criteria} passing, ${row.unverified} unverified${state};${coverage}${row.flags?.length ? ` [${row.flags.join(", ")}]` : ""}`;
90
+ }
91
+
92
+ export function formatBuildPlansResult({ operation, data }) {
93
+ if (operation.name === "build-plans.stuck") {
94
+ const rows = data?.rows || [];
95
+ return [
96
+ `${data?.running ?? 0} running build plan(s); ${data?.inWindow ?? rows.length} updated in the last ${data?.days} days or stalled (${data?.stalled ?? 0} stalled); ${rows.length} shown.`,
97
+ ...rows.map(formatRow),
98
+ ];
99
+ }
100
+ const lines = [];
101
+ for (const error of data?.errors || []) {
102
+ lines.push(`Skipped "${error.value}": ${error.reason === "duplicate" ? "listed twice" : "not a build id"}`);
103
+ }
104
+ if (data?.dryRun) {
105
+ lines.push(`Dry run: would recheck ${data?.buildIds?.length || 0} build(s).`);
106
+ for (const row of data?.rows || []) lines.push(formatRow(row));
107
+ lines.push("Nothing was graded. Add --confirm to recheck them (free for the creators).");
108
+ return lines;
109
+ }
110
+ for (const result of data?.results || []) {
111
+ lines.push(
112
+ result?.ok
113
+ ? `${result.replayed ? "(already rechecked by this request) " : ""}Build ${result.buildId}: milestone ${result.milestoneIndex + 1} ${result.verdict}, ${result.passed}/${result.criteria} passing, ${result.unverified} unverified${result.milestoneCompleted ? ", milestone completed" : ""}${result.stalled ? `, stalled (${result.stalled.reason})` : ""}${result.fileCoverage ? `; files ${result.fileCoverage.shownFiles}/${result.fileCoverage.totalFiles} + ${result.fileCoverage.toolFilesRead || 0} read in ${result.fileCoverage.toolRounds || 0} rounds` : ""}${result.usage ? `; cost ${result.usage.energyUnits} Energy units (${result.usage.inputTokens} in / ${result.usage.outputTokens} out tokens), not charged to the creator` : ""}`
114
+ : `Build ${result?.buildId}: not rechecked (${result?.reason === "run_in_progress" ? "a Lumine run is in progress; try again later" : result?.reason || "unknown"})`,
115
+ );
116
+ }
117
+ return lines;
118
+ }
package/lib/admin.js CHANGED
@@ -49,6 +49,10 @@ import {
49
49
  buildSlidingBlockOperation,
50
50
  formatSlidingBlockResult,
51
51
  } from "./admin-sliding-block.js";
52
+ import {
53
+ buildBuildPlansOperation,
54
+ formatBuildPlansResult,
55
+ } from "./admin-build-plans.js";
52
56
  import { formatChatReportResult } from "./admin-chat-reports.js";
53
57
  import { formatIdentityDeviceUsage } from "./admin-identity-inspection.js";
54
58
  import {
@@ -2646,6 +2650,10 @@ export function parseAdminOperation(options) {
2646
2650
  if (namespace === "sliding-block") {
2647
2651
  return buildSlidingBlockOperation({ action, options });
2648
2652
  }
2653
+ // Stuck Lumine Build plans and their free recheck (admin-build-plans.js).
2654
+ if (namespace === "build-plans") {
2655
+ return buildBuildPlansOperation({ action, options });
2656
+ }
2649
2657
 
2650
2658
  if (namespace === "correction" || namespace === "corrections") {
2651
2659
  if (!action || action === "status") {
@@ -7389,6 +7397,11 @@ function printAdminResult({ operation, result }) {
7389
7397
  console.log(line);
7390
7398
  return;
7391
7399
  }
7400
+ if (operation.name.startsWith("build-plans.")) {
7401
+ for (const line of formatBuildPlansResult({ operation, data }))
7402
+ console.log(line);
7403
+ return;
7404
+ }
7392
7405
  if (operation.name.startsWith("craft.")) {
7393
7406
  const lines = formatCraftResult({ operation, data });
7394
7407
  if (lines) {
package/lib/commands.js CHANGED
@@ -3261,6 +3261,7 @@ export function printHelp() {
3261
3261
  lumine admin cardcraft-review list [--status pending|approved|all] [--cursor <id>] | show <id> | approve|reject|revoke <id> [--reason <text>]
3262
3262
  lumine admin craft seed --build <id> --file <pairs.json> [--confirm] | stats --build <id> [--limit <1-100>] | revise --build <id> --item <id|name> [--why <text>] [--emoji <emoji>] [--category <field>] [--confirm] | revise --build <id> --pair <pairId|a+b> --why <text> [--confirm] | retire --build <id> --item <id|name> --reason <text> [--confirm] | unfizzle --build <id> [--since <iso>] [--pair <a+b>] [--confirm] (Twinkle.craft curation: dry run without --confirm)
3263
3263
  lumine admin sliding-block status | add --file <levels.json> [--confirm] (sliding-block-v1 campaign refills, RushHour: dry run without --confirm; the server solves each board and appends it)
3264
+ lumine admin build-plans stuck [--days <1-90>] [--all] | recheck <buildId> [<buildId> ...] [--confirm] (Lumine Build plans stuck on one milestone: runs, Energy vs estimate, gradings, loop-guard state; recheck re-grades the current milestone with today's evaluator, free for the creator, dry run without --confirm)
3264
3265
  lumine admin storage list [--status pending|approved|rejected|all] [--cursor <id>] | show <user-id-or-username> | approve <request-id> [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <request-id> [--reason <text>] | grant <user-id-or-username> --size <500MB|1GB|2GB|n MB> [--reason <text>]
3265
3266
  lumine admin meetup list [--status review|active|completed|all] | show <crewId> | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | set-branch <crewId> <userId> --branch "<name>" [--note <text>] | branches [list [--status pending|official|rejected|all] | show <id> | approve <id> | reject <id> [--note <text>] | merge <id> --into <officialId>] | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | funnel [--days <1-90>] | announce-checkins [--send] [--as zero|ciel] (Bridge Builder crews and check-ins)
3266
3267
  lumine admin meetup story list | show <crewId> | approve <crewId> [--note <text>] [--no-announce] | send-back <crewId> --note <text> | unpublish <crewId> --note <text> (Bridge Builder story pages: publishes only what every tagged member's parent allowed)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.3.48",
3
+ "version": "0.3.49",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {