@stage5/lumine 0.3.29 → 0.3.31

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/lib/admin.js CHANGED
@@ -370,7 +370,7 @@ function buildApprovalOperation({ action, target, options }) {
370
370
  // Code agent passes wrote (no model is called), then review what they could
371
371
  // not settle (Mikey 2026-10-06: bulk AI work runs on Claude Code agents).
372
372
  const ENGLISH_USAGE =
373
- "Use: lumine admin english import <verdicts.json> --pass a|b | adjudicate <verdicts.json> | add-questions <questions.json> | relevel <moves.json> | mastery <userId> --reason <why> | backfill-distractors | summary | review [--limit <n>] | accept|retire <questionId> [--reason <text>] | retag <questionId> --skill <code> [--reason <text>] | build-candidates [--after <questionId>] [--limit <n>] | build-import <verdicts.json> | check-candidates [--after <questionId>] [--limit <n>] | check-import <verdicts.json> | check-broken [--after <questionId>] [--limit <n>] | check-apply <fixes.json> | check-verify <checked.json>";
373
+ "Use: lumine admin english import <verdicts.json> --pass a|b | adjudicate <verdicts.json> | add-questions <questions.json> | relevel <moves.json> | mastery <userId> --reason <why> | backfill-distractors | summary | review [--limit <n>] | accept|retire <questionId> [--reason <text>] [--fault] | retag <questionId> --skill <code> [--reason <text>] | build-candidates [--after <questionId>] [--limit <n>] | build-import <verdicts.json> | check-candidates [--after <questionId>] [--limit <n>] | check-import <verdicts.json> | check-broken [--after <questionId>] [--limit <n>] | check-apply <fixes.json> | check-verify <checked.json> | check-follow-faulty [--after <next>] [--limit <n>] | lesson-candidates [--after <skillCode>] [--limit <n>] | lesson-import <lessons.json> | lesson-approve <approvals.json> | lessons [--status draft|approved]";
374
374
  const ENGLISH_IMPORT_MAX = 500;
375
375
 
376
376
  function buildEnglishOperation({ action, target, options }) {
@@ -444,6 +444,34 @@ function buildEnglishOperation({ action, target, options }) {
444
444
  }
445
445
  return writeOperation("english.check_verify", "POST", "/cli/admin/english/item-checks/verify", { items }, { requiresRun: false, timeoutMs: 300000 });
446
446
  }
447
+ // the round-21 catch-up (boss misses uncounted, versions found at fault
448
+ // broken for every learner, every check followed again): one page per
449
+ // call, repeat with the reply's `next` until it is null
450
+ if (action === "check-follow-faulty") {
451
+ const after = options.adminAfter ? String(options.adminAfter) : "";
452
+ if (after && !/^(?:[ufc]:)?\d+:\d+$/.test(after)) throw cliValidationError("--after takes the last reply's next (<phase:id:version>).");
453
+ return writeOperation("english.check_follow_faulty", "POST", "/cli/admin/english/item-checks/follow-faulty", { after, limit: limitOf(200, 100) }, { requiresRun: false, timeoutMs: 300000 });
454
+ }
455
+ // Grammar Quest lessons (Mikey 2026-10-11): what the writing pass draws on
456
+ // (each rule-first point with live sound questions on it), importing
457
+ // drafts, approving reviewed versions [{ skillCode, version }], the list
458
+ if (action === "lesson-candidates") {
459
+ const after = options.adminAfter ? String(options.adminAfter) : "";
460
+ return readOperation("english.lesson_candidates", withQuery("/cli/admin/english/lessons/candidates", { after, limit: limitOf(64, 20) }), { requiresRun: false });
461
+ }
462
+ if (action === "lesson-import" || action === "lesson-approve") {
463
+ if (!target) throw cliValidationError(ENGLISH_USAGE);
464
+ const raw = readAdminJsonFile(target, "<file.json>", { maxBytes: 2 * 1024 * 1024 });
465
+ const items = Array.isArray(raw) ? raw : Array.isArray(raw?.items) ? raw.items : null;
466
+ if (!items || !items.length || items.length > 100) throw cliValidationError("The file must hold 1 to 100 entries (an array, or { items: [...] }).");
467
+ const path = action === "lesson-import" ? "/cli/admin/english/lessons/import" : "/cli/admin/english/lessons/approve";
468
+ return writeOperation(action === "lesson-import" ? "english.lesson_import" : "english.lesson_approve", "POST", path, { items }, { requiresRun: false, timeoutMs: 180000 });
469
+ }
470
+ if (action === "lessons") {
471
+ const status = options.adminStatus || "";
472
+ if (status && !["draft", "approved"].includes(status)) throw cliValidationError("--status is draft or approved.");
473
+ return readOperation("english.lessons", withQuery("/cli/admin/english/lessons", status ? { status } : {}), { requiresRun: false });
474
+ }
447
475
  if (action === "check-apply") {
448
476
  if (!target) throw cliValidationError(ENGLISH_USAGE);
449
477
  const raw = readAdminJsonFile(target, "<fixes.json>", { maxBytes: 4 * 1024 * 1024 });
@@ -487,7 +515,14 @@ function buildEnglishOperation({ action, target, options }) {
487
515
  "english.decide",
488
516
  "POST",
489
517
  `/cli/admin/english/review/${ref}`,
490
- { action, skillCode: options.adminSkill || undefined, reason: String(options.adminReason || "").trim() || undefined },
518
+ {
519
+ action,
520
+ skillCode: options.adminSkill || undefined,
521
+ reason: String(options.adminReason || "").trim() || undefined,
522
+ // retire --fault (Mikey 10-11): the question was at fault, so Grammar
523
+ // Quest forgives every miss on it; a duplicate or off-topic retire isn't
524
+ ...(action === "retire" && options.adminFault ? { fault: true } : {}),
525
+ },
491
526
  { requiresRun: false },
492
527
  );
493
528
  }
package/lib/commands.js CHANGED
@@ -2440,6 +2440,7 @@ export function parseArgs(args) {
2440
2440
  const positional = [];
2441
2441
  const booleanFlags = new Set([
2442
2442
  "remove",
2443
+ "fault",
2443
2444
  "noAnnounce",
2444
2445
  "noLeaseKeeper",
2445
2446
  "noOpen",
@@ -2759,6 +2760,8 @@ export function parseArgs(args) {
2759
2760
  twinkles: raw.twinkles ? String(raw.twinkles) : "",
2760
2761
  adminReason: raw.reason ? String(raw.reason) : "",
2761
2762
  adminSkill: raw.skill ? String(raw.skill) : "",
2763
+ // admin english retire --fault: the question was at fault (Grammar Quest forgives its misses)
2764
+ adminFault: Boolean(raw.fault),
2762
2765
  // --branch may repeat (a building two branches share); --branches A,B too
2763
2766
  adminBranch: [raw.branch, raw.branches].filter(Boolean).join(","),
2764
2767
  adminPass: raw.pass ? String(raw.pass) : "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.3.29",
3
+ "version": "0.3.31",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {