@stage5/lumine 0.3.13 → 0.3.14
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 +103 -0
- package/lib/commands.js +4 -0
- package/package.json +1 -1
package/lib/admin.js
CHANGED
|
@@ -281,6 +281,63 @@ function buildApprovalOperation({ action, target, options }) {
|
|
|
281
281
|
throw cliValidationError(APPROVAL_USAGE);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
// English skill tags for the Grammarbles bank: import the verdicts two Claude
|
|
285
|
+
// Code agent passes wrote (no model is called), then review what they could
|
|
286
|
+
// not settle (Mikey 2026-10-06: bulk AI work runs on Claude Code agents).
|
|
287
|
+
const ENGLISH_USAGE =
|
|
288
|
+
"Use: lumine admin english import <verdicts.json> --pass a|b | adjudicate <verdicts.json> | add-questions <questions.json> | relevel <moves.json> | summary | review [--limit <n>] | accept|retire <questionId> [--reason <text>] | retag <questionId> --skill <code> [--reason <text>]";
|
|
289
|
+
const ENGLISH_IMPORT_MAX = 500;
|
|
290
|
+
|
|
291
|
+
function buildEnglishOperation({ action, target, options }) {
|
|
292
|
+
const limitOf = (max, fallback) =>
|
|
293
|
+
options.adminLimit != null ? parseRequiredInteger(options.adminLimit, "--limit", 1, max) : fallback;
|
|
294
|
+
if (action === "import") {
|
|
295
|
+
const pass = String(options.adminPass || "");
|
|
296
|
+
if (!["a", "b"].includes(pass) || !target) throw cliValidationError(ENGLISH_USAGE);
|
|
297
|
+
const raw = readAdminJsonFile(target, "<verdicts.json>", { maxBytes: 4 * 1024 * 1024 });
|
|
298
|
+
const items = Array.isArray(raw) ? raw : Array.isArray(raw?.items) ? raw.items : null;
|
|
299
|
+
if (!items || !items.length || items.length > ENGLISH_IMPORT_MAX) {
|
|
300
|
+
throw cliValidationError(`The file must hold 1 to ${ENGLISH_IMPORT_MAX} verdicts (an array, or { items: [...] }).`);
|
|
301
|
+
}
|
|
302
|
+
return writeOperation("english.import", "POST", "/cli/admin/english/import", { pass, items }, { requiresRun: false, timeoutMs: 120000 });
|
|
303
|
+
}
|
|
304
|
+
// adjudicate / add-questions / relevel: one JSON file of up to 500 entries.
|
|
305
|
+
const BANK_FILES = {
|
|
306
|
+
adjudicate: ["english.adjudicate", "/cli/admin/english/adjudicate", "items"],
|
|
307
|
+
"add-questions": ["english.add_questions", "/cli/admin/english/questions/import", "questions"],
|
|
308
|
+
relevel: ["english.relevel", "/cli/admin/english/relevel", "items"],
|
|
309
|
+
};
|
|
310
|
+
if (BANK_FILES[action]) {
|
|
311
|
+
const [name, path, key] = BANK_FILES[action];
|
|
312
|
+
if (!target) throw cliValidationError(ENGLISH_USAGE);
|
|
313
|
+
const raw = readAdminJsonFile(target, "<file.json>", { maxBytes: 4 * 1024 * 1024 });
|
|
314
|
+
const list = Array.isArray(raw) ? raw : Array.isArray(raw?.[key]) ? raw[key] : null;
|
|
315
|
+
if (!list || !list.length || list.length > ENGLISH_IMPORT_MAX) {
|
|
316
|
+
throw cliValidationError(`The file must hold 1 to ${ENGLISH_IMPORT_MAX} entries (an array, or { ${key}: [...] }).`);
|
|
317
|
+
}
|
|
318
|
+
return writeOperation(name, "POST", path, { [key]: list }, { requiresRun: false, timeoutMs: 180000 });
|
|
319
|
+
}
|
|
320
|
+
if (action === "summary") {
|
|
321
|
+
return readOperation("english.summary", "/cli/admin/english/tag-summary", { requiresRun: false });
|
|
322
|
+
}
|
|
323
|
+
if (action === "review" && !target) {
|
|
324
|
+
return readOperation("english.review", withQuery("/cli/admin/english/review", { limit: limitOf(100, 20) }), { requiresRun: false });
|
|
325
|
+
}
|
|
326
|
+
if (["accept", "retire", "retag"].includes(action)) {
|
|
327
|
+
const ref = String(target || "");
|
|
328
|
+
if (!/^\d+$/.test(ref)) throw cliValidationError(ENGLISH_USAGE);
|
|
329
|
+
if (action === "retag" && !options.adminSkill) throw cliValidationError(ENGLISH_USAGE);
|
|
330
|
+
return writeOperation(
|
|
331
|
+
"english.decide",
|
|
332
|
+
"POST",
|
|
333
|
+
`/cli/admin/english/review/${ref}`,
|
|
334
|
+
{ action, skillCode: options.adminSkill || undefined, reason: String(options.adminReason || "").trim() || undefined },
|
|
335
|
+
{ requiresRun: false },
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
throw cliValidationError(ENGLISH_USAGE);
|
|
339
|
+
}
|
|
340
|
+
|
|
284
341
|
function buildTeacherOperation({ action, target, options }) {
|
|
285
342
|
if (!action || action === "audit") {
|
|
286
343
|
return readOperation(
|
|
@@ -2172,6 +2229,9 @@ export function parseAdminOperation(options) {
|
|
|
2172
2229
|
// Mikey's decisions, any time; each comes with its evidence (approvals.ts).
|
|
2173
2230
|
return buildApprovalOperation({ action, target, options });
|
|
2174
2231
|
}
|
|
2232
|
+
if (namespace === "english") {
|
|
2233
|
+
return buildEnglishOperation({ action, target, options });
|
|
2234
|
+
}
|
|
2175
2235
|
if (namespace === "teachers" || namespace === "teacher") {
|
|
2176
2236
|
// Approved teachers: an audit any time, and a clean removal of the status.
|
|
2177
2237
|
return buildTeacherOperation({ action, target, options });
|
|
@@ -5813,6 +5873,45 @@ function printApprovalResult({ operation, data }) {
|
|
|
5813
5873
|
}
|
|
5814
5874
|
}
|
|
5815
5875
|
|
|
5876
|
+
function printEnglishResult({ operation, data }) {
|
|
5877
|
+
if (operation.name === "english.import") {
|
|
5878
|
+
console.log(`Pass ${data.pass}: ${data.stored} stored of ${data.received} (${data.invalid} invalid, ${data.unknownQuestion} unknown questions) · ${data.decided} decided: ${data.auto} accepted automatically, ${data.review} for review (${Object.entries(data.reasons || {}).map(([k, v]) => `${k} ${v}`).join(", ") || "-"})`);
|
|
5879
|
+
return;
|
|
5880
|
+
}
|
|
5881
|
+
if (operation.name === "english.adjudicate") {
|
|
5882
|
+
console.log(`Adjudicated ${data.received}: ${data.keep} kept · ${data.fix} fixed (new versions) · ${data.retire} retired · ${data.skipped?.length || 0} skipped${data.skipped?.length ? ` (${data.skipped.slice(0, 5).map((s) => `${s.ref}: ${s.why}`).join("; ")})` : ""}`);
|
|
5883
|
+
return;
|
|
5884
|
+
}
|
|
5885
|
+
if (operation.name === "english.add_questions") {
|
|
5886
|
+
console.log(`Added ${data.added} of ${data.received} questions · ${data.duplicate} duplicates skipped · ${data.invalid?.length || 0} invalid${data.invalid?.length ? ` (${data.invalid.slice(0, 5).map((s) => `#${s.index}: ${s.why}`).join("; ")})` : ""}`);
|
|
5887
|
+
return;
|
|
5888
|
+
}
|
|
5889
|
+
if (operation.name === "english.relevel") {
|
|
5890
|
+
console.log(`Re-levelled ${data.moved} of ${data.received} · ${data.unchanged} already in place · ${data.invalid} invalid`);
|
|
5891
|
+
return;
|
|
5892
|
+
}
|
|
5893
|
+
if (operation.name === "english.summary") {
|
|
5894
|
+
console.log(`${data.total} questions · passes: ${(data.passes || []).map((row) => `${row.model} ${row.items}`).join(" · ") || "none yet"}`);
|
|
5895
|
+
console.log(`Tag status: ${(data.byStatus || []).map((row) => `${row.status} ${row.items}`).join(" · ") || "none yet"}`);
|
|
5896
|
+
(data.bySkill || []).slice(0, 40).forEach((row) => console.log(` ${row.skillCode}: ${row.items}`));
|
|
5897
|
+
return;
|
|
5898
|
+
}
|
|
5899
|
+
if (operation.name === "english.review") {
|
|
5900
|
+
const items = data.items || [];
|
|
5901
|
+
if (!items.length) return console.log("Nothing waiting for review.");
|
|
5902
|
+
items.forEach((item) => {
|
|
5903
|
+
console.log(`\n#${item.itemRef} v${item.itemVersion} · ${item.skillCode} · ${item.cefr || "?"}`);
|
|
5904
|
+
console.log(` ${item.question}`);
|
|
5905
|
+
["a", "b", "c", "d"].forEach((letter) => console.log(` ${letter === item.correct_choice ? "*" : " "} ${letter}) ${item[`choice_${letter}`]}`));
|
|
5906
|
+
(item.verdicts || []).forEach((v) => console.log(` ${v.model}: ${v.primarySkill}${v.ambiguous ? ` · AMBIGUOUS: ${v.ambiguityReason}` : ""}`));
|
|
5907
|
+
});
|
|
5908
|
+
return;
|
|
5909
|
+
}
|
|
5910
|
+
if (operation.name === "english.decide") {
|
|
5911
|
+
console.log(`#${data.ref} v${data.version}: ${data.action} · ${data.skillCode}${data.reason ? ` (${data.reason})` : ""}`);
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5816
5915
|
function printReviewRequestResult({ operation, data, result }) {
|
|
5817
5916
|
if (operation.name === "review.list") {
|
|
5818
5917
|
const items = Array.isArray(data.items) ? data.items : [];
|
|
@@ -5999,6 +6098,10 @@ function printAdminResult({ operation, result }) {
|
|
|
5999
6098
|
printApprovalResult({ operation, data });
|
|
6000
6099
|
return;
|
|
6001
6100
|
}
|
|
6101
|
+
if (operation.name.startsWith("english.")) {
|
|
6102
|
+
printEnglishResult({ operation, data });
|
|
6103
|
+
return;
|
|
6104
|
+
}
|
|
6002
6105
|
if (operation.name.startsWith("review.")) {
|
|
6003
6106
|
printReviewRequestResult({ operation, data, result });
|
|
6004
6107
|
return;
|
package/lib/commands.js
CHANGED
|
@@ -2606,6 +2606,9 @@ export function parseArgs(args) {
|
|
|
2606
2606
|
draftId: raw.draftId ? String(raw.draftId) : "",
|
|
2607
2607
|
twinkles: raw.twinkles ? String(raw.twinkles) : "",
|
|
2608
2608
|
adminReason: raw.reason ? String(raw.reason) : "",
|
|
2609
|
+
adminSkill: raw.skill ? String(raw.skill) : "",
|
|
2610
|
+
adminPass: raw.pass ? String(raw.pass) : "",
|
|
2611
|
+
adminLimit: raw.limit === undefined ? null : raw.limit,
|
|
2609
2612
|
storageSize: raw.size ? String(raw.size) : "",
|
|
2610
2613
|
adminAttended: raw.attended ? String(raw.attended) : "",
|
|
2611
2614
|
adminConfirm: raw.confirm === undefined ? "" : String(raw.confirm),
|
|
@@ -3016,6 +3019,7 @@ export function printHelp() {
|
|
|
3016
3019
|
lumine admin review list [--type rewards|project|storage|cardcraft] [--status queue|pending|approved|rejected|all] [--cursor <c>] | show <type:id> [--dir <path>] | approve <type:id> [--config <rules.json>] [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject|revoke <type:id> [--reason <text>] | propose rewards:<id> --dir <edited-snapshot> --config <rules.json>
|
|
3017
3020
|
lumine admin approvals list [--status pending|approved|rejected|all] [--type dob|mentor|meetup] | show <id> | approve <id> [--reason <text>] | reject <id> [--reason <text>] | supermod <id> [--reason <text>] (management approvals with evidence: birthdates, teacher signups, meetups)
|
|
3018
3021
|
lumine admin teachers audit [--limit <1-100>] | revoke <userId> --reason <text> (approved teachers, riskiest first; revoke removes the status cleanly)
|
|
3022
|
+
lumine admin english import <verdicts.json> --pass a|b | adjudicate <file> | add-questions <file> | relevel <file> | summary | review [--limit <n>] | accept|retire <questionId> | retag <questionId> --skill <code> (English skill tags and bank upkeep for Grammarbles; content written by Claude Code agents)
|
|
3019
3023
|
lumine admin reward-review list [--status pending|approved|all] [--cursor <id>] [--json]
|
|
3020
3024
|
lumine admin cardcraft-review list [--status pending|approved|all] [--cursor <id>] | show <id> | approve|reject|revoke <id> [--reason <text>]
|
|
3021
3025
|
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>]
|