@stage5/lumine 0.2.29 → 0.2.30

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/sdk/LUMINE_ADMIN.md +181 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.29",
3
+ "version": "0.2.30",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1063,7 +1063,8 @@ end every run report with an **"Insights for Mikey"** section carrying only
1063
1063
  the deltas and anomalies worth his time, next to the escalation list. Never
1064
1064
  dump raw sections at him.
1065
1065
 
1066
- Four sections (Mikey's chosen cut, 2026-08-10):
1066
+ Nine sections (Mikey's chosen cut 2026-08-10; behavioral-insight and
1067
+ farm-signal sections added the same day):
1067
1068
 
1068
1069
  - `economy` — `topGainers` (coin-ledger aggregation over the window: gained,
1069
1070
  spent, net, current balance per user, Zero/Ciel excluded) and `topBalances`
@@ -1117,14 +1118,62 @@ Four sections (Mikey's chosen cut, 2026-08-10):
1117
1118
  calendar day before the exact `window.sinceTs`.
1118
1119
  Report the contrast between the buckets, and treat `genuinelyInterested` as
1119
1120
  notable-users-but-for-teachers.
1121
+ - `engagementPulse` — distinct users per surface (activeUsers, subjects,
1122
+ comments, recommendations, wordle, reflections, dailyTasks, aiChat,
1123
+ lumineBuildChat, buildsEdited, buildsPlayed) for the current window vs the
1124
+ equal-length previous window, each as `{ current, previous, delta }`.
1125
+ Presence, build edits, and build plays come from durable action/version/view
1126
+ events, not mutable `lastActive`/`updatedAt` snapshots. Zero/Ciel are excluded
1127
+ from authored surfaces. Wordle, daily tasks, and AI chat use the equal
1128
+ calendar-bucket ranges in `dayWindow`; those can begin before the exact
1129
+ timestamp window but always compare the same number of days. This is the
1130
+ "where do users actually live, and is it shifting?" section: report only the
1131
+ deltas that mean something, and read a surface's absolute size before
1132
+ dramatizing a small delta.
1133
+ - `launchMetrics` — readouts for recently shipped features so nothing ships
1134
+ unmeasured. v1 carries `firstBuildRescue` (offers recorded and redemptions
1135
+ in the window, each `{ total, byEventType }`, plus first-Lumine-exchange
1136
+ claims) and `wordleSkipShield` (`startDayIndex`, `active`, judged dodges,
1137
+ and skip covers split `earned` vs `fromRescue`). Wordle metrics cover only
1138
+ completed, actually judged days in `judgedWindow`; today's in-progress game
1139
+ is never called a dodge. Offers without redemptions are a reason to inspect
1140
+ sample size, event type, and offer age — not proof of a broken funnel by
1141
+ themselves.
1142
+ - `goneQuiet` — the inverse of `notableCandidates`: users whose `lastActive`
1143
+ fell in the 14 days before the window (so they were around, then stopped),
1144
+ ranked by how regular they were in the prior 30 days (daily tasks and
1145
+ Wordle), capped at 15 with `daysQuiet`. Use it for product signal (what did
1146
+ they stop doing?) and gentle outreach candidates; never guilt a child in
1147
+ public about absence.
1148
+ - `newUserFunnel` — signups in the window with `activeOnDayOne` (any
1149
+ XP-ledger event within 24h of joining) and `returnedAfterDayOne`
1150
+ (`lastActive` beyond their first day), plus the newest few accounts.
1151
+ Deliberately coarse: it is an onboarding health check, not per-child
1152
+ session tracking.
1153
+ - `farmSignals` — AI-cost farm signatures derivable with ZERO new data
1154
+ collection: `inboxFamilies` (verified emails from accounts active in the
1155
+ last `inboxFamilyActivityDays`, with only Gmail/googlemail's documented
1156
+ plus-tag and dot aliases collapsed, flagging inboxes behind 3+ accounts) and
1157
+ `youngAccountAiUsage` (accounts under 30 days old drawing battery in the
1158
+ whole-day `aiUsageDayWindow`). SIGNAL ONLY: siblings legitimately share a
1159
+ parent inbox, so an inbox family is a reason to look, never proof or grounds
1160
+ for action. Feed real suspicions to the AI-cost escalation category. Shared
1161
+ AI device/IP risk evidence is already in `aiSpending.topRiskGroups`; do not
1162
+ guess it from inbox similarity.
1120
1163
 
1121
1164
  The command needs only an active run's `content:read` scope and mutates
1122
1165
  nothing; reading the brief is not audited content action. Window boundaries
1123
1166
  on the big append-only ledgers are found by binary-searching the PRIMARY key
1124
1167
  (several tables have no timeStamp index), avoiding lifetime scans; aggregation
1125
- is still bounded to the selected 1–30 day window.
1168
+ is still bounded to the selected 1–30 day window. The optional sections run
1169
+ serially around the existing core report so this low-frequency command cannot
1170
+ occupy the production reader pool; one unavailable section does not suppress
1171
+ the others.
1126
1172
 
1127
1173
  ```ts
1174
+ type InsightUnavailable = { unavailable: true; error: string };
1175
+ type WindowDelta = { current: number; previous: number; delta: number };
1176
+
1128
1177
  type TeacherInsight = {
1129
1178
  userId: number;
1130
1179
  username: string | null;
@@ -1200,7 +1249,115 @@ type InsightsBrief = Success<{
1200
1249
  topAccounts: unknown[];
1201
1250
  topRiskGroups: unknown[];
1202
1251
  }
1203
- | { unavailable: true; error: string };
1252
+ | InsightUnavailable;
1253
+ engagementPulse:
1254
+ | {
1255
+ windowDays: number;
1256
+ dayWindow: {
1257
+ currentStartDayIndex: number;
1258
+ currentEndDayIndex: number;
1259
+ previousStartDayIndex: number;
1260
+ previousEndDayIndex: number;
1261
+ dayCount: number;
1262
+ };
1263
+ surfaces: {
1264
+ activeUsers: WindowDelta;
1265
+ subjects: WindowDelta;
1266
+ comments: WindowDelta;
1267
+ recommendations: WindowDelta;
1268
+ wordle: WindowDelta;
1269
+ reflections: WindowDelta;
1270
+ dailyTasks: WindowDelta;
1271
+ aiChat: WindowDelta;
1272
+ lumineBuildChat: WindowDelta;
1273
+ buildsEdited: WindowDelta;
1274
+ buildsPlayed: WindowDelta;
1275
+ };
1276
+ }
1277
+ | InsightUnavailable;
1278
+ launchMetrics:
1279
+ | {
1280
+ firstBuildRescue: {
1281
+ offersRecorded: {
1282
+ total: number;
1283
+ byEventType: Record<string, number>;
1284
+ };
1285
+ redemptions: {
1286
+ total: number;
1287
+ byEventType: Record<string, number>;
1288
+ };
1289
+ firstLumineExchangeClaims: number;
1290
+ };
1291
+ wordleSkipShield: {
1292
+ startDayIndex: number;
1293
+ active: boolean;
1294
+ judgedWindow: {
1295
+ startDayIndex: number;
1296
+ endDayIndex: number;
1297
+ } | null;
1298
+ judgedDodges: number;
1299
+ skipCovers: { earned: number; fromRescue: number };
1300
+ };
1301
+ }
1302
+ | InsightUnavailable;
1303
+ goneQuiet:
1304
+ | {
1305
+ users: Array<{
1306
+ userId: number;
1307
+ username: string | null;
1308
+ lastActive: number | null;
1309
+ daysQuiet: number | null;
1310
+ dailyTasksPrior30d: number;
1311
+ wordlePlaysPrior30d: number;
1312
+ regularityScore: number;
1313
+ }>;
1314
+ totals: { wentQuiet: number; previouslyRegular: number };
1315
+ }
1316
+ | InsightUnavailable;
1317
+ newUserFunnel:
1318
+ | {
1319
+ totals: {
1320
+ signups: number;
1321
+ activeOnDayOne: number;
1322
+ returnedAfterDayOne: number;
1323
+ };
1324
+ newest: Array<{
1325
+ userId: number;
1326
+ username: string | null;
1327
+ joinedAt: number | null;
1328
+ activeOnDayOne: boolean;
1329
+ returnedAfterDayOne: boolean;
1330
+ }>;
1331
+ }
1332
+ | InsightUnavailable;
1333
+ farmSignals:
1334
+ | {
1335
+ inboxFamilyActivityDays: number;
1336
+ inboxFamilies: Array<{
1337
+ inbox: string;
1338
+ accounts: Array<{
1339
+ userId: number;
1340
+ username: string | null;
1341
+ joinedAt: number | null;
1342
+ lastActive: number | null;
1343
+ }>;
1344
+ accountCount: number;
1345
+ youngAccounts: number;
1346
+ }>;
1347
+ aiUsageDayWindow: {
1348
+ startDayIndex: number;
1349
+ endDayIndex: number;
1350
+ };
1351
+ youngAccountAiUsage: Array<{
1352
+ userId: number;
1353
+ username: string | null;
1354
+ joinedAt: number | null;
1355
+ energyUnits: number;
1356
+ replies: number;
1357
+ }>;
1358
+ notes: string;
1359
+ }
1360
+ | InsightUnavailable;
1204
1361
  }>;
1205
1362
  ```
1206
1363
 
@@ -1334,6 +1491,27 @@ tried it — ask the author about it instead. This is the standing rule for
1334
1491
  every composed comment, applied to apps: never claim an experience the
1335
1492
  session did not actually have.
1336
1493
 
1494
+ **Offer a Lumine prompt when the moment invites it (Mikey's direction,
1495
+ 2026-08-10).** Zero and Ciel may include one concrete, copy-pasteable Lumine
1496
+ prompt in a comment or reply — a genuinely powerful one, tailored to what the
1497
+ kid is already doing — when the occasion naturally calls for it. Appropriate
1498
+ occasions: a kid describes an idea they wish existed, asks how something on
1499
+ the site was made, hits the edge of what a post/drawing/story can do, shares
1500
+ a Build app that could grow a specific feature, or shows an interest (space,
1501
+ cats, chess, comics) that maps cleanly onto something Lumine could build with
1502
+ them. On those occasions, the prompt IS the helpful answer: quote it so it
1503
+ can be copied as-is, keep it specific to their interest, and mention it works
1504
+ in the Build workspace chat. Restraint rules: never more than one prompt per
1505
+ comment; never in condolence, conflict, wellbeing, or moderation-adjacent
1506
+ threads; never as a reflex closing line on ordinary comments — if the comment
1507
+ is complete without the prompt, post it without the prompt. A run where only
1508
+ a few comments carry a prompt is healthy; a run where most do is shameless
1509
+ plugging, which is exactly what Mikey asked to avoid. SDK-aware prompt ideas
1510
+ are especially good ("ask Lumine to make a magazine that pulls real Twinkle
1511
+ posts with Twinkle.subjects.search") because kids do not know the content
1512
+ APIs exist — but only suggest SDK capabilities that actually exist; check
1513
+ TWINKLE_BUILD_SDK.md if unsure.
1514
+
1337
1515
  A composed draft (`--file`, plain UTF-8 text, at most the website's 10,000
1338
1516
  character comment limit) flows through the identical draft lifecycle —
1339
1517
  reservation, idempotency, context-revision CAS, publish fencing, audit