@stage5/lumine 0.3.33 → 0.3.35
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-daily.js +33 -8
- package/lib/admin.js +11 -0
- package/lib/builderStance.generated.js +3 -0
- package/lib/commands.js +4 -0
- package/lib/constants.js +5 -0
- package/lib/rewards.js +42 -3
- package/package.json +3 -2
- package/sdk/LUMINE_ADMIN.md +44 -5
package/lib/admin-daily.js
CHANGED
|
@@ -23,6 +23,7 @@ export function runAdmin(
|
|
|
23
23
|
args,
|
|
24
24
|
{ outputFile, extraEnv, retryRead = false, spawnImpl = spawnSync } = {},
|
|
25
25
|
) {
|
|
26
|
+
const startedAt = performance.now();
|
|
26
27
|
const attemptFile = outputFile
|
|
27
28
|
? `${outputFile}.attempt-${randomUUID()}.json`
|
|
28
29
|
: null;
|
|
@@ -78,6 +79,7 @@ export function runAdmin(
|
|
|
78
79
|
featured: ["list", "history"],
|
|
79
80
|
sponsor: ["applications"],
|
|
80
81
|
"bot-output": [null],
|
|
82
|
+
"math-lab": ["queue"],
|
|
81
83
|
subjects: ["candidates"],
|
|
82
84
|
subject: ["get"],
|
|
83
85
|
};
|
|
@@ -96,6 +98,7 @@ export function runAdmin(
|
|
|
96
98
|
const retried = runAdmin(args, { outputFile, extraEnv, spawnImpl });
|
|
97
99
|
return {
|
|
98
100
|
...retried,
|
|
101
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
99
102
|
attempts: 2,
|
|
100
103
|
recovered: retried.ok,
|
|
101
104
|
firstError:
|
|
@@ -104,6 +107,7 @@ export function runAdmin(
|
|
|
104
107
|
}
|
|
105
108
|
return {
|
|
106
109
|
ok,
|
|
110
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
107
111
|
attempts: 1,
|
|
108
112
|
status: result.status,
|
|
109
113
|
json: error ? { ok: false, status: "unconfirmed", error } : parsed,
|
|
@@ -159,6 +163,7 @@ export function renderBotOutput({ chatMessages, comments }, file) {
|
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
export async function dailyRunGather(options) {
|
|
166
|
+
const startedAt = performance.now();
|
|
162
167
|
const dir = ensureDir(
|
|
163
168
|
path.resolve(options.adminOutputDir || options.adminDir || "."),
|
|
164
169
|
);
|
|
@@ -172,6 +177,7 @@ export async function dailyRunGather(options) {
|
|
|
172
177
|
file,
|
|
173
178
|
ok: res.ok,
|
|
174
179
|
attempts: res.attempts,
|
|
180
|
+
durationMs: res.durationMs,
|
|
175
181
|
recovered: Boolean(res.recovered),
|
|
176
182
|
...(res.ok ? {} : { error: res.json?.error || res.stderr }),
|
|
177
183
|
});
|
|
@@ -180,6 +186,7 @@ export async function dailyRunGather(options) {
|
|
|
180
186
|
step("energy-budget", ["energy-budget"], "energy-budget.json");
|
|
181
187
|
step("news", ["news"], "news.json");
|
|
182
188
|
step("brief", ["brief"], "brief.json");
|
|
189
|
+
step("math-lab-queue", ["math-lab", "queue"], "math-lab-queue.json");
|
|
183
190
|
step("ai-costs-monthly", ["ai-costs", "monthly"], "ai-monthly.json");
|
|
184
191
|
step("ai-costs-day", ["ai-costs", "day", yesterday], "ai-day.json");
|
|
185
192
|
step("media-costs", ["media-costs", "monthly"], "media.json");
|
|
@@ -239,6 +246,8 @@ export async function dailyRunGather(options) {
|
|
|
239
246
|
if (botOk) renderBotOutput({ chatMessages, comments }, path.join(dir, "bot-chats.txt"));
|
|
240
247
|
|
|
241
248
|
const summary = {
|
|
249
|
+
collectedAt: new Date().toISOString(),
|
|
250
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
242
251
|
dir,
|
|
243
252
|
yesterdayUtc: yesterday,
|
|
244
253
|
botOutput: {
|
|
@@ -377,13 +386,19 @@ fetch('daily-management-report.md',{cache:'no-store'}).then(r=>{if(!r.ok)throw n
|
|
|
377
386
|
export function waitForReportServer(child, timeoutMs = 10000) {
|
|
378
387
|
return new Promise((resolve, reject) => {
|
|
379
388
|
let buf = "";
|
|
389
|
+
let stderr = "";
|
|
380
390
|
let settled = false;
|
|
391
|
+
const clean = value => String(value || "")
|
|
392
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
393
|
+
.replace(/[\u0000-\u001f\u007f]+/g, " ").trim().slice(-2048);
|
|
394
|
+
const diagnostic = message => new Error(`${message}${clean(stderr) ? ` ${clean(stderr)}` : ""}`);
|
|
381
395
|
const finish = (error, url) => {
|
|
382
396
|
if (settled) return;
|
|
383
397
|
settled = true;
|
|
384
398
|
clearTimeout(timer);
|
|
385
399
|
child.stdout.removeListener("data", onData);
|
|
386
|
-
child.removeListener("
|
|
400
|
+
child.stderr?.removeListener("data", onStderr);
|
|
401
|
+
child.removeListener("close", onClose);
|
|
387
402
|
child.removeListener("error", onError);
|
|
388
403
|
if (error) {
|
|
389
404
|
child.kill();
|
|
@@ -399,29 +414,36 @@ export function waitForReportServer(child, timeoutMs = 10000) {
|
|
|
399
414
|
if (!buf.includes("\n")) return;
|
|
400
415
|
try {
|
|
401
416
|
const parsed = JSON.parse(buf.split("\n")[0]);
|
|
417
|
+
if (parsed.ok === false) {
|
|
418
|
+
const reason = clean(parsed.error?.message || parsed.message || parsed.error);
|
|
419
|
+
return finish(diagnostic(`Report server failed to start${reason ? `: ${reason}` : "."}`));
|
|
420
|
+
}
|
|
402
421
|
const url = new URL(parsed.data?.url);
|
|
403
422
|
if (
|
|
404
423
|
parsed.ok !== true ||
|
|
405
424
|
parsed.status !== "success" ||
|
|
406
425
|
url.protocol !== "http:" ||
|
|
407
|
-
url.hostname !== "127.0.0.1"
|
|
426
|
+
url.hostname !== "127.0.0.1" || url.username || url.password
|
|
408
427
|
)
|
|
409
428
|
throw new Error("invalid receipt");
|
|
410
429
|
finish(null, url.href);
|
|
411
430
|
} catch {
|
|
412
|
-
finish(
|
|
431
|
+
finish(diagnostic("Report server returned an invalid startup receipt."));
|
|
413
432
|
}
|
|
414
433
|
};
|
|
415
|
-
const
|
|
416
|
-
|
|
434
|
+
const onStderr = chunk => { stderr = (stderr + chunk).slice(-8192); };
|
|
435
|
+
// close follows drained stdio; exit can arrive before the error text.
|
|
436
|
+
const onClose = (code, signal) =>
|
|
437
|
+
finish(diagnostic(`Report server exited (${signal || code}) before it was ready.`));
|
|
417
438
|
const onError = (error) => finish(error);
|
|
418
439
|
const timer = setTimeout(
|
|
419
440
|
() =>
|
|
420
|
-
finish(
|
|
441
|
+
finish(diagnostic("Report server did not start before its deadline.")),
|
|
421
442
|
timeoutMs,
|
|
422
443
|
);
|
|
423
444
|
child.stdout.on("data", onData);
|
|
424
|
-
child.
|
|
445
|
+
child.stderr?.on("data", onStderr);
|
|
446
|
+
child.once("close", onClose);
|
|
425
447
|
child.once("error", onError);
|
|
426
448
|
});
|
|
427
449
|
}
|
|
@@ -488,10 +510,13 @@ export async function reportServe(
|
|
|
488
510
|
"--json",
|
|
489
511
|
"--no-update-check",
|
|
490
512
|
],
|
|
491
|
-
{ detached: true, stdio: ["ignore", "pipe", "
|
|
513
|
+
{ detached: true, stdio: ["ignore", "pipe", "pipe"] },
|
|
492
514
|
);
|
|
493
515
|
const url = await waitForReportServer(child);
|
|
494
516
|
child.stdout.destroy();
|
|
517
|
+
// Keep draining diagnostics without retaining them or keeping the CLI up.
|
|
518
|
+
child.stderr?.resume();
|
|
519
|
+
child.stderr?.unref?.();
|
|
495
520
|
child.unref();
|
|
496
521
|
const opened = options.adminOpen
|
|
497
522
|
? openImpl("open", ["-a", "Google Chrome", url])
|
package/lib/admin.js
CHANGED
|
@@ -3973,6 +3973,9 @@ export function parseAdminOperation(options) {
|
|
|
3973
3973
|
},
|
|
3974
3974
|
);
|
|
3975
3975
|
}
|
|
3976
|
+
if (namespace === "math-lab" && action === "queue") {
|
|
3977
|
+
return readOperation("math-lab.queue", "/cli/admin/math-lab/queue");
|
|
3978
|
+
}
|
|
3976
3979
|
if (namespace === "builds" && action === "review") {
|
|
3977
3980
|
const parsed = parseAdminCommentTarget({ target, explicitType: "build" });
|
|
3978
3981
|
if (parsed.type !== "build") {
|
|
@@ -7330,6 +7333,14 @@ function printAiBucketRuleResult({ operation, data }) {
|
|
|
7330
7333
|
|
|
7331
7334
|
function printAdminResult({ operation, result }) {
|
|
7332
7335
|
const data = result?.data || {};
|
|
7336
|
+
if (operation.name === "math-lab.queue") {
|
|
7337
|
+
console.log(`Math Lab: published artifact ${data.publishedArtifactVersionId}, review ${data.reviewId}, day ${data.dayKey} UTC.`);
|
|
7338
|
+
for (const grade of data.grades || []) console.log(grade.available
|
|
7339
|
+
? `${grade.ruleId}: ${grade.current || "exhausted"}${grade.clearedToday ? " (cleared today)" : ""}; ${grade.remaining} unearned of ${grade.sets}${grade.refillDue ? `; refill to ${data.refillTarget}` : ""}`
|
|
7340
|
+
: `${grade.ruleId}: queue unavailable`);
|
|
7341
|
+
if (!data.complete) console.log("Queue coverage is incomplete; unavailable grades are not empty queues.");
|
|
7342
|
+
return;
|
|
7343
|
+
}
|
|
7333
7344
|
if (printChatSafetyResult({ operation, data })) return;
|
|
7334
7345
|
const chatReportLines = formatChatReportResult({ operation, data });
|
|
7335
7346
|
if (chatReportLines) {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// GENERATED from CORE_VALUES.md ("For building agents") by
|
|
2
|
+
// scripts/generate-builder-stance.mjs. Do not edit by hand.
|
|
3
|
+
export const LUMINE_BUILDER_STANCE = "You are a tool for the creator. Your job is to understand what they want to make, or seem to want to make, and help them make the best version of it.\n\n- What to make is the creator's call. Never steer them toward or away from a kind of project, genre, mechanic or style because of your own taste.\n- Be opinionated about how to make their idea great. Like a trusted senior collaborator, point out the loose ends: gaps between what they asked for and what exists, unfinished parts, edge cases, and things that will break or feel off. Then say clearly what you would do next if you were them, and why. Let them decide.\n- When they ask for ideas, offer a few options that fit what they're making, say which one you'd pick for their goal and why, and don't push.\n- When their direction is vague or feeling-based (\"make it more epic\", \"it feels empty\", \"I don't like this\"), work out what they mean and make the result visibly respond to it. Ask one short question only when you genuinely can't tell.\n- Make it well: aim for the best, most complete and polished version of their idea, within what they asked.\n- Don't coach, lecture or comment on the creator themselves; let the work speak.";
|
package/lib/commands.js
CHANGED
|
@@ -2479,6 +2479,7 @@ export function parseArgs(args) {
|
|
|
2479
2479
|
"foreground",
|
|
2480
2480
|
"open",
|
|
2481
2481
|
"dryRun",
|
|
2482
|
+
"preflight",
|
|
2482
2483
|
"confirmEmailRule",
|
|
2483
2484
|
"includePrefix",
|
|
2484
2485
|
"allowSharedNetwork",
|
|
@@ -2802,6 +2803,8 @@ export function parseArgs(args) {
|
|
|
2802
2803
|
adminEditionId: raw.editionId ? String(raw.editionId) : "",
|
|
2803
2804
|
adminLeaseToken: raw.leaseToken ? String(raw.leaseToken) : "",
|
|
2804
2805
|
adminFile: raw.file ? String(raw.file) : "",
|
|
2806
|
+
rewardPreflight: Boolean(raw.preflight),
|
|
2807
|
+
rewardApprovalFile: raw.approvalFile ? String(raw.approvalFile) : "",
|
|
2805
2808
|
// admin craft revise --item <id|name> [--why] [--emoji] [--category]
|
|
2806
2809
|
// (kept raw: a flag given without a text value is refused in
|
|
2807
2810
|
// admin-craft.js rather than sent as the string "true")
|
|
@@ -3255,6 +3258,7 @@ export function printHelp() {
|
|
|
3255
3258
|
lumine admin chat-reports export <report-id> | --hold <hold-id> --out <new-dir> [--json] (evidence package + SHA-256 manifest)
|
|
3256
3259
|
lumine admin chat-reports suspend --user <id> --note <why> [--json] (full ban: signs them out)
|
|
3257
3260
|
lumine admin reward-review show <review-id> [--dir <path>] [--json]
|
|
3261
|
+
lumine admin math-lab queue [--json] (published sets remaining per grade, all-time claims, refill flags)
|
|
3258
3262
|
lumine admin reward-review approve <review-id> [--config <rules.json>] [--reason <text>] [--json] (approval publishes the approved version)
|
|
3259
3263
|
lumine admin reward-review propose <review-id> --dir <edited-snapshot> --config <rules.json> [--reason <text>] [--title <new app name>] [--thumbnail <image|workspace>] [--json]
|
|
3260
3264
|
lumine admin reward-review reject|revoke <review-id> --reason <text> [--json]
|
package/lib/constants.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os from "os";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { LUMINE_BUILDER_STANCE } from "./builderStance.generated.js";
|
|
3
4
|
|
|
4
5
|
export const DEFAULT_API_URL = "https://api.twinkle.network";
|
|
5
6
|
export const DEFAULT_SITE_URL = "https://www.twin-kle.com";
|
|
@@ -173,6 +174,10 @@ export const LUMINE_AGENT_INSTRUCTIONS = `${LUMINE_AGENT_INSTRUCTIONS_MARKER}
|
|
|
173
174
|
This directory contains Twinkle Build project files pulled by Lumine CLI. Use
|
|
174
175
|
Lumine CLI as the source of truth for saving this workspace back to Twinkle.
|
|
175
176
|
|
|
177
|
+
## Building For The Creator
|
|
178
|
+
|
|
179
|
+
${LUMINE_BUILDER_STANCE}
|
|
180
|
+
|
|
176
181
|
## Source Of Truth
|
|
177
182
|
|
|
178
183
|
- Read .twinkle/lumine-project.json before changing files.
|
package/lib/rewards.js
CHANGED
|
@@ -18,6 +18,15 @@ import { resolveRequiredBuildIdOrSelected } from "./commands.js";
|
|
|
18
18
|
|
|
19
19
|
const REWARDS_FILE = "rewards.json";
|
|
20
20
|
|
|
21
|
+
export function reviewApprovalFromReceipt(receipt, buildId) {
|
|
22
|
+
if (!receipt || receipt.ready !== true || receipt.buildId !== buildId ||
|
|
23
|
+
!Number.isSafeInteger(receipt.sourceVersionId) || receipt.sourceVersionId <= 0 ||
|
|
24
|
+
!["sourceHash", "sheetHash", "configHash"].every(key => /^[a-f0-9]{64}$/.test(receipt[key] || ""))) {
|
|
25
|
+
throw new Error("Use the complete, ready preflight JSON for this Build as --approval-file.");
|
|
26
|
+
}
|
|
27
|
+
return Object.fromEntries(["buildId", "sourceVersionId", "sourceHash", "sheetHash", "configHash"].map(key => [key, receipt[key]]));
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
async function readWorkspaceRewardsJson(options) {
|
|
22
31
|
const localProject = await findLocalProjectMetadata(
|
|
23
32
|
path.resolve(options.dir || process.cwd()),
|
|
@@ -208,13 +217,39 @@ export async function rewardsCommand(options) {
|
|
|
208
217
|
return;
|
|
209
218
|
}
|
|
210
219
|
if (action === "review") {
|
|
220
|
+
if (options.rewardPreflight && options.rewardApprovalFile)
|
|
221
|
+
throw new Error("Choose --preflight or --approval-file, not both.");
|
|
222
|
+
let receipt;
|
|
223
|
+
if (options.rewardApprovalFile) {
|
|
224
|
+
receipt = JSON.parse(await fs.readFile(path.resolve(options.rewardApprovalFile), "utf8"));
|
|
225
|
+
} else {
|
|
226
|
+
receipt = await requestJson({
|
|
227
|
+
url: `${options.apiUrl}/build/${buildId}/rewards/review-preflight`,
|
|
228
|
+
authToken: auth.token, timeoutMs: options.timeoutMs,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (options.rewardPreflight) {
|
|
232
|
+
if (options.json) console.log(JSON.stringify(receipt, null, 2));
|
|
233
|
+
else {
|
|
234
|
+
console.log(`Build ${buildId}, saved v${receipt.savedVersion} (artifact ${receipt.sourceVersionId}).`);
|
|
235
|
+
console.log(receipt.publicationBehavior);
|
|
236
|
+
console.log(`Source: ${receipt.sourceHash}\nQuestion sheet: ${receipt.sheetHash}\nReward configuration: ${receipt.configHash}`);
|
|
237
|
+
for (const error of receipt.errors || []) console.log(` - ${error}`);
|
|
238
|
+
console.log("Save this preflight with --json, review the candidate, then pass --approval-file <receipt.json> after approval.");
|
|
239
|
+
}
|
|
240
|
+
if (!receipt.ready) process.exitCode = 1;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const expectedApproval = reviewApprovalFromReceipt(receipt, buildId);
|
|
244
|
+
if (receipt.willPublishImmediately && !options.rewardApprovalFile)
|
|
245
|
+
throw new Error("This owner's reward review publishes immediately. Run `lumine rewards review --preflight --json`, approve that exact candidate, then use `--approval-file <receipt.json>`.");
|
|
211
246
|
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
212
247
|
const result = await requestJson({
|
|
213
248
|
url: `${options.apiUrl}/build/${buildId}/rewards/reviews`,
|
|
214
249
|
method: "POST",
|
|
215
250
|
authToken: auth.token,
|
|
216
251
|
timeoutMs: options.timeoutMs,
|
|
217
|
-
body: {},
|
|
252
|
+
body: { expectedApproval },
|
|
218
253
|
});
|
|
219
254
|
if (options.json) {
|
|
220
255
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -222,10 +257,12 @@ export async function rewardsCommand(options) {
|
|
|
222
257
|
}
|
|
223
258
|
const reviewId = result?.policy?.latestReviewId;
|
|
224
259
|
console.log(
|
|
225
|
-
`
|
|
260
|
+
`Reward review for Build ${buildId}${reviewId ? ` (request #${reviewId})` : ""}. Server state: ${result?.state || "unknown"}.`,
|
|
226
261
|
);
|
|
227
262
|
console.log(
|
|
228
|
-
|
|
263
|
+
receipt.willPublishImmediately
|
|
264
|
+
? "The server attempted automatic approval and publication. Check the returned state and published artifact before reporting it live."
|
|
265
|
+
: "The approved candidate is frozen for the reviewer. Saving again closes this request.",
|
|
229
266
|
);
|
|
230
267
|
return;
|
|
231
268
|
}
|
|
@@ -239,6 +276,8 @@ function printRewardsHelp() {
|
|
|
239
276
|
lumine rewards sheet <file.json> Upload the private question sheet ({ rules: { <ruleId>: { questions?, sets? } } })
|
|
240
277
|
lumine rewards sheet --show Summarize the sheet on file (never prints answer keys)
|
|
241
278
|
lumine rewards review Send the saved version (with the sheet on file) for XP/Coin review, like "Send for review" on the website
|
|
279
|
+
lumine rewards review --preflight --json Read exact version/source/sheet/config hashes and publication behavior; no mutation
|
|
280
|
+
lumine rewards review --approval-file <receipt.json> Request only the exact approved preflight candidate; owner auto-approval publishes immediately
|
|
242
281
|
|
|
243
282
|
rewards.json (project root) declares the economy the reviewer approves:
|
|
244
283
|
{ "userDailyXP", "userDailyCoins", "userDailyClaims"?,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stage5/lumine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.35",
|
|
4
4
|
"description": "Command line tools for launching Lumine builds on Twinkle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "node --test"
|
|
16
|
+
"test": "node --test",
|
|
17
|
+
"prepublishOnly": "node --test test/builderStanceSync.test.js"
|
|
17
18
|
},
|
|
18
19
|
"publishConfig": {
|
|
19
20
|
"access": "public"
|
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -622,6 +622,10 @@ additions are not the live inventory. At two or fewer remaining, prepare a
|
|
|
622
622
|
refill to at least ten, with complete interactive guides, and track it until
|
|
623
623
|
approved publication. Report one or zero remaining prominently. Details are
|
|
624
624
|
in the linked guide's **Daily queue monitoring and refill** section.
|
|
625
|
+
Read this inventory with `lumine admin math-lab queue --json`. Its receipt
|
|
626
|
+
binds the published artifact and live reward review, includes all twelve
|
|
627
|
+
grades, and reports incomplete coverage explicitly. Today’s cleared set stays
|
|
628
|
+
current until UTC midnight, but no longer counts as unearned inventory.
|
|
625
629
|
This is not part of Featured-only or newspaper-only work and is not a new
|
|
626
630
|
scheduler, delegated API scope, or automatic extension of admin permissions.
|
|
627
631
|
Use the expressly authorized owner Build workflow for Math Lab; retain the
|
|
@@ -878,6 +882,8 @@ type StandalonePost = {
|
|
|
878
882
|
updatedAt: null;
|
|
879
883
|
title: string | null;
|
|
880
884
|
question: string | null;
|
|
885
|
+
grade?: "Pass" | "Fail" | "Masterpiece" | null; // Daily Reflection only
|
|
886
|
+
masterpieceType?: "heart" | "mind" | "heart_and_mind" | null;
|
|
881
887
|
content: string | null;
|
|
882
888
|
explanation: string | null;
|
|
883
889
|
imagePath: string | null;
|
|
@@ -1152,7 +1158,7 @@ lumine admin report serve --file <dir>/daily-management-report.md [--port <n>] [
|
|
|
1152
1158
|
```
|
|
1153
1159
|
|
|
1154
1160
|
These compose the documented commands in this contract, running them as child processes, so every scope check, audit and pagination rule applies unchanged.
|
|
1155
|
-
- **`gather`** saves each read-only report into `<dir
|
|
1161
|
+
- **`gather`** saves each read-only report into `<dir>`, including the canonical published Math Lab queue in `math-lab-queue.json`. It follows `bot-output` to exhaustion and renders `bot-chats.txt`, then lists any failed step in `gather.json`. Total/per-step `durationMs`, attempts and recovered reads make recurring slow or failing collection steps visible during the daily review. It also creates `review-state.json`: every source is fetched, not read. Children bypass update checks, require matching fresh stdout/file receipts, and retry an unconfirmed opted-in read at most once. Mutations never auto-retry. It needs the active run for run-scoped reads.
|
|
1156
1162
|
- **`progress`** reads local review state and explicit operator acknowledgments, writes private `progress.json`, and keeps reading, canonical effort mutations, the run lease, and browser checks separate. Add all required duties for the actual run scope and its carryovers; collection alone is not a full review. A completed run lease cannot complete a pending duty. Reading acknowledgments bind to the current file bytes and are operator attestations, not proof of comprehension.
|
|
1157
1163
|
- **`featured candidates`** returns Subjects posted after the Bangkok-midnight cutoff that are off the board and provably never Featured. Each one comes with its human/bot comment counts, explicit comment truncation/completeness, and whether Mikey has viewed it. Counts from truncated context are partial. An unavailable Subject context fails the collection. Eligibility only: ranking stays editorial.
|
|
1158
1164
|
- **`report serve`** detaches a loopback server that answers only `/`, `marked.min.js` and the report Markdown. `--open` opens Chrome and reports whether the OS accepted the request. Render, navigation and original-source verification remain pending until actually checked. Startup failures stop only the child this command started.
|
|
@@ -1479,9 +1485,8 @@ Rule fields (all server-enforced, none inferred from app code):
|
|
|
1479
1485
|
- Top-level `userDailyClaims`: receipts one learner may earn per site day
|
|
1480
1486
|
across all rules. `1` is "one bounty a day".
|
|
1481
1487
|
|
|
1482
|
-
Math Lab's economy (Mikey, 2026-
|
|
1483
|
-
day,
|
|
1484
|
-
100,000 + 10,000; `retry` 50 % XP / 0 % Coins; `maxAttempts` null;
|
|
1488
|
+
Math Lab's economy (Mikey, equal pay 2026-10-04): twelve level rules, one per grade per
|
|
1489
|
+
day, all paying 70,000 XP + 5,000 Coins; `retry` 50 % XP / 0 % Coins; `maxAttempts` null;
|
|
1485
1490
|
`userDailyClaims` 1; until-earned sets authored from the Korean curriculum.
|
|
1486
1491
|
Arcade Typing (Mikey, 2026-09-12): XP for clearing campaign stages, up to
|
|
1487
1492
|
10,000 XP per learner per day, no Coins. Platform ceilings: 100,000 XP /
|
|
@@ -1497,6 +1502,18 @@ also closes itself on that save). Rejection and revocation require a
|
|
|
1497
1502
|
needs a new request. Never approve without reading the code; never approve a
|
|
1498
1503
|
request whose `isLatest` is false.
|
|
1499
1504
|
|
|
1505
|
+
Mikey-owned apps automatically attempt approval and publication when the owner
|
|
1506
|
+
requests reward review. Before that write, run
|
|
1507
|
+
`lumine rewards review --preflight --json` and retain its complete receipt.
|
|
1508
|
+
It identifies the saved version, artifact ID, source hash, private sheet hash
|
|
1509
|
+
and effective reward configuration hash, and states the publication behavior.
|
|
1510
|
+
After Mikey approves that exact candidate, use
|
|
1511
|
+
`lumine rewards review --approval-file <receipt.json>`. The API checks those
|
|
1512
|
+
values under the build lock before freezing content; a change returns
|
|
1513
|
+
`build_reward_approval_stale`. There is no silent fallback to an unbound request.
|
|
1514
|
+
Saving a new preflight is read-only and is not approval. These commands require
|
|
1515
|
+
the matching API and CLI release.
|
|
1516
|
+
|
|
1500
1517
|
`propose <id> --dir <edited> --config rules.json [--reason]` sends the edited
|
|
1501
1518
|
directory (text files only; dotfiles and tool folders skipped) as the
|
|
1502
1519
|
reviewer's proposal: the review moves to `changes_offered`, the creator's card
|
|
@@ -2480,6 +2497,12 @@ Inspection never silently bypasses secret semantics. Until the selected bot is
|
|
|
2480
2497
|
the author or has canonically responded/revealed, secret values and comments
|
|
2481
2498
|
remain unavailable.
|
|
2482
2499
|
|
|
2500
|
+
Daily Reflection post reads and recommendation candidates include the stored
|
|
2501
|
+
`grade` and `masterpieceType` (nullable), independently of XP and recommendations.
|
|
2502
|
+
An absent grade is unknown. Verify `grade === "Masterpiece"` before placing one
|
|
2503
|
+
of Mikey's reflections anywhere on the newspaper front page; lower or missing
|
|
2504
|
+
grades can be considered for inside pages. The CLI does not infer a grade.
|
|
2505
|
+
|
|
2483
2506
|
## Subject and Featured mutations
|
|
2484
2507
|
|
|
2485
2508
|
```bash
|
|
@@ -2673,6 +2696,12 @@ never previously been Featured from canonical evidence.
|
|
|
2673
2696
|
|
|
2674
2697
|
### Exact approved refresh and resumable comment encouragement
|
|
2675
2698
|
|
|
2699
|
+
Keep current manual pins from the past two days (Mikey, October 13, 2026).
|
|
2700
|
+
Verify canonical pin history and exclude those subjects from removal proposals.
|
|
2701
|
+
Subject 3529, “What is special about Twinkle?”, also has an explicit keep
|
|
2702
|
+
instruction. An absent pin is still never restored. This supersedes the older
|
|
2703
|
+
instruction to offer yesterday's manual pins for rotation.
|
|
2704
|
+
|
|
2676
2705
|
For the daily old-to-new proposal, prepare a canonical plan without changing pins:
|
|
2677
2706
|
|
|
2678
2707
|
```bash
|
|
@@ -2982,6 +3011,14 @@ to join in. Games, art, puzzles, friendships, shared reflections, and community
|
|
|
2982
3011
|
discussions can all supply good stories; read the actual source to find the
|
|
2983
3012
|
substance.
|
|
2984
3013
|
|
|
3014
|
+
**Community significance (Mikey, 2026-10-13):** substantive messages about
|
|
3015
|
+
Twinkle's shared values, purpose and direction deserve explicit front-page
|
|
3016
|
+
priority, including the lead when they are the day's most consequential story.
|
|
3017
|
+
Judge what the message means for members, regardless of who wrote it. Immediate
|
|
3018
|
+
entertainment, likely comments and opportunities to join in are not the only
|
|
3019
|
+
measures of importance. Explain the values being communicated; do not reduce
|
|
3020
|
+
them to founder biography or an administrative announcement.
|
|
3021
|
+
|
|
2985
3022
|
Use clear, lively language that respects readers' intelligence. Give enough
|
|
2986
3023
|
context for someone who missed the original post, and make the interesting
|
|
2987
3024
|
part clear in the headline and opening. Avoid talking down to readers, forced
|
|
@@ -3101,7 +3138,9 @@ make the call by reading:
|
|
|
3101
3138
|
|
|
3102
3139
|
- **Choose the lead for readers aged 10–15.** Lead with the eligible front
|
|
3103
3140
|
event whose substance is most likely to catch their interest and reward
|
|
3104
|
-
reading
|
|
3141
|
+
reading, weighing community significance as well as immediate appeal.
|
|
3142
|
+
A substantive statement of shared values or purpose, a thoughtful
|
|
3143
|
+
conversation, a funny or relatable reflection, a
|
|
3105
3144
|
striking creation, or an inviting community challenge can all qualify.
|
|
3106
3145
|
Read the source and any supplied replies to understand the appeal; priority,
|
|
3107
3146
|
recency, and the mere presence of an argument do not decide the lead.
|