@stage5/lumine 0.2.43 → 0.2.44

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/README.md CHANGED
@@ -217,6 +217,12 @@ lumine admin identity list --json
217
217
  lumine admin identity inspect Jay1216 \
218
218
  --reason "Confirm account family before a quota-bucket change" --json
219
219
  lumine admin daily-run start --identity auto --comment-mode off --json
220
+ lumine admin todo list --json
221
+ lumine admin todo add --kind experiment --status in_progress \
222
+ --title "Validate Zero/Ciel cost optimization" \
223
+ --note "Complete only after old-vs-new response-quality parity." --json
224
+ lumine admin todo update 12 --status blocked \
225
+ --note "Waiting for a complete cost bucket and parity replay." --json
220
226
  lumine admin recommendations list --all --checkpoint recommendations.json --json
221
227
  lumine admin recommendations list --after 2026-08-14T00:00:00Z --all --json
222
228
  lumine admin recommendations list --include-legacy --all --json
@@ -295,14 +301,19 @@ cursors are bound to the original date and effort filters.
295
301
  `news claim` can write both the canonical leased digest and an editable
296
302
  editorial scaffold. `news validate` is local and checks every citation and
297
303
  quote before submission; `news submit --claim` reads the lease identity from
298
- the claim file. `daily-run report` summarizes confirmed mutations, completed
299
- queue coverage, explicitly recorded escalations, and the run brief before the
300
- run is completed.
304
+ the claim file. Every `daily-run start` response includes writer-confirmed
305
+ unfinished private todos, with once-per-run surfacing telemetry, so an agent
306
+ can resume earlier work without relying on conversation memory. Record progress
307
+ with `todo update`; completing a run does not complete its todos. Experiments
308
+ must meet their stated acceptance criteria—lower AI cost with weaker user
309
+ responses is not a successful optimization. `daily-run report` summarizes
310
+ confirmed mutations, completed queue coverage, explicitly recorded escalations,
311
+ unfinished todos, and the run brief before the run is completed.
301
312
 
302
313
  Identity inspection, escalation dispositions, AI-bucket maintenance, and
303
- approved Notable User additions are private operator bookkeeping and do not
304
- require a delegated daily run. Identity inspection always requires an audited
305
- `--reason`; raw email/DOB evidence additionally requires
314
+ approved Notable User additions and todos are private operator bookkeeping and
315
+ do not require a delegated daily run. Identity inspection always requires an
316
+ audited `--reason`; raw email/DOB evidence additionally requires
306
317
  `--include-private-evidence`. Routine briefs omit raw email identities.
307
318
 
308
319
  The complete run lifecycle, command contracts, nullable fields, Karma approval
package/lib/admin.js CHANGED
@@ -23,6 +23,8 @@ const MAX_COMPOSED_TEXT_LENGTH = 10_000;
23
23
  const MAX_NOTABLE_NOTE_LENGTH = 2_000;
24
24
  const MAX_IDENTITY_INSPECTION_REASON_LENGTH = 500;
25
25
  const MAX_ESCALATION_DECISION_NOTE_LENGTH = 2_000;
26
+ const MAX_TODO_TITLE_LENGTH = 200;
27
+ const MAX_TODO_NOTE_LENGTH = 4_000;
26
28
 
27
29
  // Operator-composed persona text (plain UTF-8, not JSON). The agent writes
28
30
  // the content in the bot's persona itself; the server never invokes
@@ -246,6 +248,9 @@ export async function adminCommand(options) {
246
248
  expectedContent: operation.body.content,
247
249
  });
248
250
  }
251
+ if (operation.name === "daily-run.start") {
252
+ assertAdminTodoHandoffResult(result);
253
+ }
249
254
  if (operation.name === "news.claim") {
250
255
  const artifacts = writeNewsClaimArtifacts({
251
256
  result,
@@ -506,7 +511,9 @@ function adminOperationRequiresRun(operation) {
506
511
  "escalation.list",
507
512
  "escalation.set",
508
513
  "notable.add",
509
- ].includes(operation.name) && !operation.name.startsWith("ai-bucket.")
514
+ ].includes(operation.name) &&
515
+ !operation.name.startsWith("ai-bucket.") &&
516
+ !operation.name.startsWith("todo.")
510
517
  );
511
518
  }
512
519
 
@@ -621,6 +628,69 @@ export function parseAdminOperation(options) {
621
628
  }
622
629
  }
623
630
 
631
+ if (namespace === "todo" || namespace === "todos") {
632
+ if (!action || action === "list") {
633
+ return readOperation(
634
+ "todo.list",
635
+ withQuery("/cli/admin/todos", {
636
+ status: parseTodoListStatus(options.adminStatus || "pending"),
637
+ limit: options.limit,
638
+ }),
639
+ );
640
+ }
641
+ if (action === "add" || action === "create") {
642
+ const title = String(options.title || "").trim();
643
+ const details = String(options.note || "").trim();
644
+ if (!title || !details) {
645
+ throw cliValidationError(
646
+ "Usage: lumine admin todo add --title <title> --note <handoff and acceptance criteria> [--kind task|experiment] [--status open|in_progress|blocked].",
647
+ );
648
+ }
649
+ if (title.length > MAX_TODO_TITLE_LENGTH) {
650
+ throw cliValidationError(
651
+ `A todo title must be at most ${MAX_TODO_TITLE_LENGTH} characters.`,
652
+ );
653
+ }
654
+ if (details.length > MAX_TODO_NOTE_LENGTH) {
655
+ throw cliValidationError(
656
+ `Todo details must be at most ${MAX_TODO_NOTE_LENGTH} characters.`,
657
+ );
658
+ }
659
+ return writeOperation("todo.add", "POST", "/cli/admin/todos", {
660
+ kind: parseTodoKind(options.adminKind || "task"),
661
+ title,
662
+ details,
663
+ status: parseTodoInitialStatus(options.adminStatus || "open"),
664
+ });
665
+ }
666
+ if (action === "update") {
667
+ const todoId = parseRequiredInteger(target, "Todo ID", 1);
668
+ const note = String(options.note || "").trim();
669
+ if (!note) {
670
+ throw cliValidationError(
671
+ "Record concrete progress, evidence, or the reason for the state change with --note <text>.",
672
+ );
673
+ }
674
+ if (note.length > MAX_TODO_NOTE_LENGTH) {
675
+ throw cliValidationError(
676
+ `A todo progress note must be at most ${MAX_TODO_NOTE_LENGTH} characters.`,
677
+ );
678
+ }
679
+ return writeOperation(
680
+ "todo.update",
681
+ "PUT",
682
+ `/cli/admin/todos/${todoId}`,
683
+ {
684
+ status: parseTodoStatus(options.adminStatus),
685
+ note,
686
+ },
687
+ );
688
+ }
689
+ throw cliValidationError(
690
+ "Usage: lumine admin todo list [--status pending|open|in_progress|blocked|completed|cancelled|all] | todo add --title <title> --note <details> | todo update <id> --status <status> --note <progress>.",
691
+ );
692
+ }
693
+
624
694
  if (namespace === "daily-run") {
625
695
  if (action === "start") {
626
696
  return writeOperation(
@@ -1240,7 +1310,7 @@ export function parseAdminOperation(options) {
1240
1310
  }
1241
1311
 
1242
1312
  throw cliValidationError(
1243
- "Usage: lumine admin identity|daily-run|escalation|recommendations|builds|post|subjects|subject|featured|comment|announcement|chat|news|audit|brief|bot-output|notable ...",
1313
+ "Usage: lumine admin identity|daily-run|escalation|todo|recommendations|builds|post|subjects|subject|featured|comment|announcement|chat|news|audit|brief|bot-output|notable ...",
1244
1314
  );
1245
1315
  }
1246
1316
 
@@ -1573,6 +1643,28 @@ export function formatAdminJsonError(error) {
1573
1643
  };
1574
1644
  }
1575
1645
 
1646
+ export function assertAdminTodoHandoffResult(result) {
1647
+ const runId = Number(result?.data?.run?.id || 0);
1648
+ const handoff = result?.data?.carryoverTodos;
1649
+ if (
1650
+ !runId ||
1651
+ !handoff ||
1652
+ !Array.isArray(handoff.items) ||
1653
+ Number(handoff.count) !== handoff.items.length ||
1654
+ Number(handoff.surfacedForRunId) !== runId ||
1655
+ !Number.isSafeInteger(Number(handoff.newlySurfacedCount)) ||
1656
+ Number(handoff.newlySurfacedCount) < 0 ||
1657
+ Number(handoff.newlySurfacedCount) > handoff.items.length
1658
+ ) {
1659
+ const error = cliValidationError(
1660
+ "The API did not confirm the canonical carry-over todo handoff. Deploy the todo migration/API before using this Lumine CLI for community management.",
1661
+ );
1662
+ error.code = "LUMINE_ADMIN_TODO_HANDOFF_UNSUPPORTED";
1663
+ throw error;
1664
+ }
1665
+ return handoff;
1666
+ }
1667
+
1576
1668
  function readOperation(name, path, extra = {}) {
1577
1669
  return {
1578
1670
  name,
@@ -1643,6 +1735,56 @@ function parseEscalationListStatus(value) {
1643
1735
  return status;
1644
1736
  }
1645
1737
 
1738
+ function parseTodoKind(value) {
1739
+ const kind = String(value || "task")
1740
+ .trim()
1741
+ .toLowerCase();
1742
+ if (!["task", "experiment"].includes(kind)) {
1743
+ throw cliValidationError("--kind must be task or experiment.");
1744
+ }
1745
+ return kind;
1746
+ }
1747
+
1748
+ function parseTodoInitialStatus(value) {
1749
+ const status = String(value || "open")
1750
+ .trim()
1751
+ .toLowerCase();
1752
+ if (!["open", "in_progress", "blocked"].includes(status)) {
1753
+ throw cliValidationError(
1754
+ "A new todo --status must be open, in_progress, or blocked.",
1755
+ );
1756
+ }
1757
+ return status;
1758
+ }
1759
+
1760
+ function parseTodoStatus(value) {
1761
+ const status = String(value || "")
1762
+ .trim()
1763
+ .toLowerCase();
1764
+ if (
1765
+ ![
1766
+ "open",
1767
+ "in_progress",
1768
+ "blocked",
1769
+ "completed",
1770
+ "cancelled",
1771
+ ].includes(status)
1772
+ ) {
1773
+ throw cliValidationError(
1774
+ "--status must be open, in_progress, blocked, completed, or cancelled.",
1775
+ );
1776
+ }
1777
+ return status;
1778
+ }
1779
+
1780
+ function parseTodoListStatus(value) {
1781
+ const status = String(value || "pending")
1782
+ .trim()
1783
+ .toLowerCase();
1784
+ if (status === "pending" || status === "all") return status;
1785
+ return parseTodoStatus(status);
1786
+ }
1787
+
1646
1788
  function parseOrderedIds(value) {
1647
1789
  const ids = String(value || "")
1648
1790
  .split(",")
@@ -1776,7 +1918,7 @@ function printAdminResult({ operation, result }) {
1776
1918
  if (data.report) {
1777
1919
  const report = data.report;
1778
1920
  console.log(
1779
- `Run #${report.run.id}: ${report.mutations.successfulMutationCount} successful mutation(s), ${report.queueCoverage.length} queue coverage record(s), ${report.escalations.length} escalation(s).`,
1921
+ `Run #${report.run.id}: ${report.mutations.successfulMutationCount} successful mutation(s), ${report.queueCoverage.length} queue coverage record(s), ${report.escalations.length} escalation(s), ${report.carryoverTodos?.count || 0} unfinished todo(s).`,
1780
1922
  );
1781
1923
  for (const coverage of report.queueCoverage) {
1782
1924
  console.log(
@@ -1791,6 +1933,7 @@ function printAdminResult({ operation, result }) {
1791
1933
  ` ${String(escalation.severity || "attention").toUpperCase()} ${target} — ${escalation.summary}`,
1792
1934
  );
1793
1935
  }
1936
+ printTodoItems(report.carryoverTodos?.items || [], "Unfinished work");
1794
1937
  const surfaces = report.brief?.engagementPulse?.surfaces;
1795
1938
  if (surfaces && typeof surfaces === "object") {
1796
1939
  const deltas = Object.entries(surfaces)
@@ -1861,6 +2004,19 @@ function printAdminResult({ operation, result }) {
1861
2004
  );
1862
2005
  return;
1863
2006
  }
2007
+ if (Array.isArray(data.todos)) {
2008
+ printTodoItems(data.todos, "Private carry-over work");
2009
+ if (data.truncated) {
2010
+ console.log(
2011
+ "More matching todos exist than the requested limit; raise --limit or narrow --status.",
2012
+ );
2013
+ }
2014
+ return;
2015
+ }
2016
+ if (data.todo) {
2017
+ printTodoItems([data.todo], "Canonical todo");
2018
+ return;
2019
+ }
1864
2020
  if (data.bucket && Array.isArray(data.memberUserIds)) {
1865
2021
  const added = Array.isArray(data.accounts)
1866
2022
  ? `; added ${data.accounts.length} explicit account(s)`
@@ -1883,6 +2039,9 @@ function printAdminResult({ operation, result }) {
1883
2039
  console.log(
1884
2040
  `Run #${data.run.id}: ${data.run.status}; identity ${data.run.identity.key}; comments ${data.run.commentMode}.`,
1885
2041
  );
2042
+ if (data.carryoverTodos) {
2043
+ printTodoItems(data.carryoverTodos.items || [], "Carry-over work");
2044
+ }
1886
2045
  return;
1887
2046
  }
1888
2047
  if (Array.isArray(data.identities)) {
@@ -2070,6 +2229,19 @@ function printAdminResult({ operation, result }) {
2070
2229
  );
2071
2230
  }
2072
2231
 
2232
+ function printTodoItems(items, heading) {
2233
+ console.log(`${heading}: ${items.length} item(s).`);
2234
+ for (const todo of items) {
2235
+ console.log(
2236
+ ` #${todo.id} ${String(todo.status || "open").toUpperCase()} ${todo.kind || "task"} — ${todo.title || "(untitled)"}`,
2237
+ );
2238
+ if (todo.details) console.log(` ${todo.details}`);
2239
+ if (todo.lastProgressNote) {
2240
+ console.log(` Latest progress: ${todo.lastProgressNote}`);
2241
+ }
2242
+ }
2243
+ }
2244
+
2073
2245
  function printPagination(pagination) {
2074
2246
  if (!pagination) return;
2075
2247
  console.log(
package/lib/commands.js CHANGED
@@ -2720,6 +2720,9 @@ export function printHelp() {
2720
2720
  lumine admin daily-run escalation add --target <target> --note <summary> [--severity attention|urgent] [--json]
2721
2721
  lumine admin escalation list [--status open|acknowledged|resolved|all] [--limit <number>] [--json]
2722
2722
  lumine admin escalation set <audit-id> --status open|acknowledged|resolved --note <decision> [--json]
2723
+ lumine admin todo list [--status pending|open|in_progress|blocked|completed|cancelled|all] [--limit <number>] [--json]
2724
+ lumine admin todo add --title <title> --note <handoff-and-acceptance-criteria> [--kind task|experiment] [--status open|in_progress|blocked] [--json]
2725
+ lumine admin todo update <todo-id> --status open|in_progress|blocked|completed|cancelled --note <progress-or-evidence> [--json]
2723
2726
  lumine admin recommendations list [--since-run|--after <date>|--include-legacy] [--all --checkpoint <file> [--resume]] [--content-types comment,dailyReflection] [--unviewed|--viewed] [--cursor <cursor>] [--json]
2724
2727
  lumine admin builds candidates [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--limit <number>] [--json]
2725
2728
  lumine admin builds review <build-url-or-id> [--output-dir <dir>] [--wait-ms <ms>] [--browser-path <path>] [--json]
@@ -2816,11 +2819,11 @@ Options:
2816
2819
  --target <build> Explicit Build URL or ID for rename/describe/upgrade
2817
2820
  --main With pull/versions/restore: target the team project's main
2818
2821
  --version <n> With pull: read-only checkout of previous save v<n>
2819
- --title <text> Build title for new/rename
2822
+ --title <text> Build title for new/rename, or private todo title
2820
2823
  --description <text> Build description for new/describe
2821
2824
  --no-description Skip New description or clear with describe
2822
2825
  --summary <text> Save summary
2823
- --note <text> Suggestion, notable-user, or AI-bucket context
2826
+ --note <text> Suggestion, notable-user, AI-bucket, or todo context
2824
2827
  --cursor <id> Continue suggestions, Forum activity, or admin listing
2825
2828
  --poll-ms <ms> Forum listener interval (1000-60000; default 3000)
2826
2829
  --after <date> Admin listing: inclusive Unix/ISO creation boundary
@@ -2836,6 +2839,7 @@ Options:
2836
2839
  --target-file <file> JSON array or newline list for audited batch skips
2837
2840
  --review-receipt <f> Confirmed managed Build runtime review receipt
2838
2841
  --severity <level> Run escalation severity: attention or urgent
2842
+ --status <state> Private escalation or todo lifecycle filter/state
2839
2843
  --wait-ms <ms> Managed Build runtime observation time (1000-45000)
2840
2844
  --browser-path <path> Chrome/Chromium executable for managed Build review
2841
2845
  --effort unassigned Admin subjects: show only unassigned effort
@@ -2851,7 +2855,7 @@ Options:
2851
2855
  --label <name> Name for a new unbanned AI identity bucket
2852
2856
  --user-ids <ids> Explicit user IDs for an AI bucket batch (up to 500)
2853
2857
  --type <type> Admin target: subject, comment, build, aiStory, or dailyReflection
2854
- --kind recommend Admin recommendation queue kind
2858
+ --kind <kind> Admin recommendation kind or todo task/experiment kind
2855
2859
  --anyone-can-reward Enable canonical reward eligibility
2856
2860
  --reward-twinkles 3 Pair a recommendation with exactly 3 Twinkles
2857
2861
  --twinkles 3 Give exactly 3 Twinkles through the normal economy
package/lib/constants.js CHANGED
@@ -31,6 +31,18 @@ export const THUMBNAIL_CONTENT_TYPE_BY_EXTENSION = {
31
31
  export const THUMBNAIL_MAX_FILE_SIZE_BYTES = 8 * 1024 * 1024;
32
32
  export const UPDATE_CHECK_TIMEOUT_MS = 1500;
33
33
  export const DEFAULT_PROJECT_LIMIT = 50;
34
+ export const BUILD_VENDOR_THREE_VERSION = "0.184.0";
35
+ export const BUILD_VENDOR_THREE_LEGACY_VERSION = "0.160.0";
36
+ export const BUILD_VENDOR_THREE_PREFIX =
37
+ `/build/vendor/three/${BUILD_VENDOR_THREE_VERSION}/`;
38
+ export const BUILD_VENDOR_THREE_MODULE_IMPORT =
39
+ `${BUILD_VENDOR_THREE_PREFIX}three.module.min.js`;
40
+ export const BUILD_VENDOR_THREE_WEBGPU_MODULE_IMPORT =
41
+ `${BUILD_VENDOR_THREE_PREFIX}three.webgpu.min.js`;
42
+ export const BUILD_VENDOR_THREE_TSL_MODULE_IMPORT =
43
+ `${BUILD_VENDOR_THREE_PREFIX}three.tsl.min.js`;
44
+ export const BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX =
45
+ `${BUILD_VENDOR_THREE_PREFIX}addons/`;
34
46
  export const PROJECT_METADATA_DIR = ".twinkle";
35
47
  export const PROJECT_METADATA_FILE = "lumine-project.json";
36
48
  export const ASSETS_METADATA_FILE = "assets.json";
@@ -120,6 +132,14 @@ Use these current source-of-truth rules:
120
132
  - Use Twinkle.preview for canvas, WebGL, Three.js, fullscreen, and game layout.
121
133
  - Prefer existing documented Twinkle.* methods over guessing names from old code.
122
134
  `;
135
+ export const LUMINE_THREE_VENDOR_GUIDANCE = `- For Three.js, use the first-party core module: import * as THREE from '${BUILD_VENDOR_THREE_MODULE_IMPORT}';
136
+ - Twinkle serves the supported official Three.js ${BUILD_VENDOR_THREE_VERSION} addon tree under ${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}, including controls, loaders, CSS renderers, shaders, physics and WebXR helpers, and WebGLRenderer post-processing modules such as EffectComposer, RenderPass, SSAOPass/GTAOPass, UnrealBloomPass, and OutputPass. Example: import { EffectComposer } from '${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}postprocessing/EffectComposer.js';
137
+ - The workspace file tree does not enumerate vendor modules, so absence there is not evidence that an official addon is unavailable. Use its documented addon subpath and run lumine check; validation checks the exact file and its transitive imports.
138
+ - WebGPU and TSL entry modules are also vendored at ${BUILD_VENDOR_THREE_WEBGPU_MODULE_IMPORT} and ${BUILD_VENDOR_THREE_TSL_MODULE_IMPORT}, but WebGL EffectComposer passes do not work with WebGPURenderer and runtime GPU support varies.
139
+ - Treat addons as available tools, not defaults. Use them when they materially serve the requested experience; for continuously animated mobile builds, include a performance profile that targets about 30 FPS, caps render pixel ratio around 1-1.25, and lowers expensive post-processing resolution or quality only when that preserves the requested visual behavior. Do not remove or disable a requested visual effect as a performance tradeoff without the user's explicit approval.
140
+ - Keep one Three.js version throughout a project. If a project using ${BUILD_VENDOR_THREE_LEGACY_VERSION} needs current addons, migrate every Three.js import to ${BUILD_VENDOR_THREE_VERSION} in one coherent change; never mix the legacy core with current addons.
141
+ - This vendor surface covers supported official Three.js modules, not arbitrary third-party Three.js packages. Do not paste library source into project files or use npm/CDN copies. Loader runtime assets are served too: point decoder/transcoder paths at the addon prefix (example: dracoLoader.setDecoderPath('${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}libs/draco/');).
142
+ - Size both WebGLRenderer and EffectComposer from Twinkle.preview layout dimensions, coalesce resize work, and render with composer.render() when a composer owns the pass chain.`;
123
143
  export const LUMINE_AGENT_INSTRUCTIONS = `${LUMINE_AGENT_INSTRUCTIONS_MARKER}
124
144
  # Lumine Project Agent Guide
125
145
 
@@ -260,14 +280,15 @@ lumine save --summary "Describe the change"
260
280
  - Interface text must not be selectable on touch devices: long-pressing UI on mobile must not highlight it. Apply user-select: none plus -webkit-user-select: none and -webkit-touch-callout: none to interface text (HUD, buttons, labels, menus, scores, game controls). Keep text inputs and genuinely user-copyable content (story text, chat messages, user-written text) selectable. lumine check flags projects whose reachable files have clickable UI but no user-select: none rule.
261
281
  - CAUTION: the preview runtime AUTO-DETECTS "game apps" — any <canvas> in the body (even a decorative background canvas) or game-y words in visible text switch the app to viewport-app mode: html/body get overflow:hidden !important and body becomes a centering flexbox, so tall document-flow pages clip and stop scrolling. Document-style apps that use a canvas must call Twinkle.preview.subscribe (or getLayout/reserveInsets) early at boot — any of those opts out of auto game mode — then pad by layout.safeInsets and scroll within layout.viewport.height.
262
282
  - For canvas, WebGL, Three.js, fullscreen, or game builds, use Twinkle.preview for layout. Do not size roots from 100vh, 100vw, 100dvh, 100dvw, window.innerWidth, window.innerHeight, visualViewport, or document viewport dimensions.
263
- - For Three.js, use import * as THREE from '/build/vendor/three/0.184.0/three.module.min.js';. Addons (OrbitControls, GLTFLoader, ...) live under /build/vendor/three/0.184.0/addons/, e.g. import { OrbitControls } from '/build/vendor/three/0.184.0/addons/controls/OrbitControls.js';. Builds saved with the older /build/vendor/three/0.160.0/ path keep working.
283
+ ${LUMINE_THREE_VENDOR_GUIDANCE}
264
284
  - Do not invent or guess Twinkle.* SDK method names. Use ${SDK_REFERENCE_FILE} as the local SDK reference and prefer Twinkle.capabilities checks for gated features.
265
285
  - Match storage to update frequency. Twinkle.privateDb and Twinkle.sharedDb are for LOW-frequency durable state only — things that change on a user action (settings, inventory checkpoints, completed quests, saved progress; comments, votes, room settings, submitted records). NEVER write high-frequency or per-frame/per-tick state to them (camera or cursor position, animation state, live movement, presence, autosave every frame/tick). Keep live state in client memory, broadcast realtime/presence via Twinkle.world, and for durable per-user state flush an occasional snapshot on an interval or on exit (never per frame) — e.g. the viewer/user DB or a single latest-snapshot key. The server rate-limits these writes per key and returns 429 on excess; never retry-loop a 429.
266
286
 
267
287
  ## Local Testing (Playwright / browser probes)
268
288
 
269
289
  - Serve the workspace with a tiny local HTTP server and drive it with Playwright. NEVER copy probe/vendor files into the workspace dir — lumine save uploads everything here (and binary files fail validation). Build a sibling probe dir that symlinks the workspace files instead.
270
- - Vendored imports like /build/vendor/three/0.184.0/... are absolute paths: mirror that directory under your probe dir's root and fetch the files from the LIVE SITE (e.g. https://www.twin-kle.com/build/vendor/three/0.184.0/three.webgpu.min.js). three 0.184 splits into three.module.min.js + three.core.min.js — mirror BOTH or imports fail. Do NOT use npm/CDN copies — the platform's vendored builds have rewritten import specifiers (npm three.tsl.min.js still imports bare "three/webgpu" and breaks the module graph).
290
+ - Vendored imports like ${BUILD_VENDOR_THREE_PREFIX}... are absolute paths: mirror that directory under your probe dir's root and fetch the files from the LIVE SITE (e.g. ${DEFAULT_SITE_URL}${BUILD_VENDOR_THREE_WEBGPU_MODULE_IMPORT}). Three.js ${BUILD_VENDOR_THREE_VERSION} splits into three.module.min.js + three.core.min.js — mirror BOTH plus every addon's relative import closure or imports fail. Do NOT use npm/CDN copies — the platform's vendored builds have rewritten import specifiers (npm three.tsl.min.js still imports bare "three/webgpu" and breaks the module graph).
291
+ - A local 404 caused by an incomplete vendor mirror is a probe setup failure, not evidence that the addon is unavailable on Twinkle. Check the live same-origin vendor URL or the saved Twinkle preview before reporting a platform limitation.
271
292
  - The three WebGPU renderer falls back to WebGL2 in headless Chromium automatically. Headless software rendering runs at ~2-5fps, so anything time-based (walking a character, timers) takes ~10-20x longer than real time — loop with generous waits instead of fixed short sleeps, and bump navigation timeouts.
272
293
  - To inspect module-scope game state, append debug getters when SERVING main.js (e.g. body += "window.__dbg = () => ({...})") rather than editing workspace files.
273
294
  - SDK calls are absent when serving locally; well-written builds optional-chain window.Twinkle and fall back to localStorage. Seed localStorage in the probe to fake saves.
package/lib/doctor.js CHANGED
@@ -3,6 +3,7 @@ import { createRequire } from "module";
3
3
  import { buildApiJson, mintBuildApiToken } from "./api.js";
4
4
  import { ensureAuth, assertAuthScope } from "./auth.js";
5
5
  import { uploadRuntimeAsset } from "./assets.js";
6
+ import { BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX } from "./constants.js";
6
7
  import { requestJson } from "./http.js";
7
8
  import { resolveSdkBuildId } from "./sdk.js";
8
9
  import { formatBytes, trimTrailingSlash } from "./util.js";
@@ -389,7 +390,7 @@ async function createRuntimeAssetsPreviewSession({
389
390
  };
390
391
  }
391
392
 
392
- function buildRuntimeAssetsProbeHtml({ hdrUrl, glbUrl }) {
393
+ export function buildRuntimeAssetsProbeHtml({ hdrUrl, glbUrl }) {
393
394
  return `<!doctype html>
394
395
  <html>
395
396
  <head>
@@ -429,7 +430,7 @@ function buildRuntimeAssetsProbeHtml({ hdrUrl, glbUrl }) {
429
430
  }
430
431
 
431
432
  async function loadHdr(url) {
432
- const { RGBELoader } = await import('/build/vendor/three/0.184.0/addons/loaders/RGBELoader.js');
433
+ const { RGBELoader } = await import('${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}loaders/RGBELoader.js');
433
434
  const texture = await new Promise((resolve, reject) => {
434
435
  new RGBELoader().load(url, resolve, undefined, reject);
435
436
  });
@@ -442,7 +443,7 @@ function buildRuntimeAssetsProbeHtml({ hdrUrl, glbUrl }) {
442
443
  }
443
444
 
444
445
  async function loadGlb(url) {
445
- const { GLTFLoader } = await import('/build/vendor/three/0.184.0/addons/loaders/GLTFLoader.js');
446
+ const { GLTFLoader } = await import('${BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX}loaders/GLTFLoader.js');
446
447
  const gltf = await new Promise((resolve, reject) => {
447
448
  new GLTFLoader().load(url, resolve, undefined, reject);
448
449
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.43",
3
+ "version": "0.2.44",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -579,7 +579,10 @@ lumine admin escalation set 123 --status resolved \
579
579
  Schemas:
580
580
 
581
581
  ```ts
582
- type DailyRunStart = Success<{ run: DailyRun }>;
582
+ type DailyRunStart = Success<{
583
+ run: DailyRun;
584
+ carryoverTodos: CarryoverTodos;
585
+ }>;
583
586
  type DailyRunStatus = Success<{
584
587
  run: DailyRun | null;
585
588
  lastRun: DailyRun | null;
@@ -632,6 +635,86 @@ mutation when a caller needs the same retry identity across processes. The CLI
632
635
  generates a fresh key for every mutation invocation; if a mutation fails, its
633
636
  JSON error includes `details.retryIdempotencyKey` for a safe exact retry.
634
637
 
638
+ ## Private carry-over todos
639
+
640
+ ```bash
641
+ lumine admin todo list --json
642
+ lumine admin todo list --status all --json
643
+ lumine admin todo add --kind experiment --status in_progress \
644
+ --title "Validate Zero/Ciel cost optimization" \
645
+ --note "Replay baseline and optimized conversations. Complete only after response-quality parity; lower cost with a weaker reply fails." --json
646
+ lumine admin todo update 12 --status blocked \
647
+ --note "Implementation is ready; waiting for a complete cost bucket and old-vs-new quality replay." --json
648
+ lumine admin todo update 12 --status completed \
649
+ --note "Blind parity comparison passed every required dimension; measured cost and latency evidence attached in this note." --json
650
+ ```
651
+
652
+ Todos are private operator work, not Zero/Ciel public actions. They persist
653
+ independently of daily runs and are therefore available before a run starts and
654
+ after it closes. Creating or updating one uses the run-independent transactional
655
+ audit path: canonical todo state and its private `todo.create` / `todo.update`
656
+ audit response commit together, and no public bot, public mutation count, or
657
+ rotation signal is involved.
658
+
659
+ Every successful `daily-run start` response automatically includes all
660
+ unfinished items under `data.carryoverTodos`. The same run ID increments an
661
+ item's surfacing telemetry at most once, even when start is retried. This is the
662
+ canonical handoff: read it before discretionary new work, resume what can safely
663
+ progress after the run's mandatory newspaper/brief/conduct duties, and record a
664
+ concrete progress note before the run closes. The daily-run report includes the
665
+ still-unfinished set again. Completing a daily run never silently completes its
666
+ todos. A CLI carrying this contract rejects a start response that does not echo
667
+ the canonical handoff, so a newer CLI against an API deployed before the todo
668
+ migration cannot quietly treat unsupported telemetry as an empty list.
669
+
670
+ `kind` is `task` or `experiment`. New items may start `open`, `in_progress`, or
671
+ `blocked`; updates may also use `completed` or `cancelled`. A progress note is
672
+ required for every update. For experiments, put the acceptance criteria in the
673
+ initial details and use evidence—not implementation status—as the completion
674
+ boundary. In particular, an AI-cost experiment is not complete until old-vs-new
675
+ response-quality parity is demonstrated; a cheaper but weaker user response is
676
+ a failed experiment. Up to 50 unfinished items may be carried so the automatic
677
+ start payload remains complete and bounded.
678
+
679
+ ```ts
680
+ type AdminTodo = {
681
+ id: number;
682
+ kind: "task" | "experiment";
683
+ title: string;
684
+ details: string;
685
+ status: "open" | "in_progress" | "blocked" | "completed" | "cancelled";
686
+ revision: number;
687
+ createdRunId: number | null;
688
+ lastWorkedRunId: number | null;
689
+ lastSurfacedRunId: number | null;
690
+ surfaceCount: number;
691
+ lastProgressNote: string | null;
692
+ createdAt: number;
693
+ updatedAt: number;
694
+ lastSurfacedAt: number | null;
695
+ completedAt: number | null;
696
+ cancelledAt: number | null;
697
+ };
698
+
699
+ type AdminTodoList = Success<{
700
+ todos: AdminTodo[];
701
+ statusFilter:
702
+ | "pending"
703
+ | "all"
704
+ | AdminTodo["status"];
705
+ truncated: boolean;
706
+ }>;
707
+
708
+ type AdminTodoMutation = Success<{ todo: AdminTodo }>;
709
+
710
+ type CarryoverTodos = {
711
+ items: AdminTodo[];
712
+ count: number;
713
+ surfacedForRunId: number;
714
+ newlySurfacedCount: number;
715
+ };
716
+ ```
717
+
635
718
  ## Canonical lists and inspection
636
719
 
637
720
  ```bash
@@ -2176,9 +2259,12 @@ Public content actions use ordinary Twinkle fan-out:
2176
2259
  - effort/creator changes emit `edit_content`;
2177
2260
  - Featured changes emit a canonical `home_outdated` refresh.
2178
2261
 
2179
- Apply `twinkle-api/scripts/migrations/add-lumine-admin-delegation.sql` and
2180
- then `add-lumine-admin-comment-targets.sql` before deploying the API. They add
2181
- only focused daily-run, rotation, draft, and audit tables/columns and indexes;
2262
+ Apply `twinkle-api/scripts/migrations/add-lumine-admin-delegation.sql`, then
2263
+ `add-lumine-admin-comment-targets.sql`, and apply
2264
+ `add-lumine-admin-todos.sql` before deploying an API that exposes carry-over
2265
+ todos. They add
2266
+ only focused daily-run, rotation, draft, audit, and private-todo tables/columns
2267
+ and indexes;
2182
2268
  there are no runtime schema checks. The comment-targets migration backfills
2183
2269
  existing subject drafts into the generalized target columns. The local CLI changes
2184
2270
  are not available to users until a separately authorized npm publication.