@stage5/lumine 0.2.63 → 0.2.64

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
@@ -381,6 +381,8 @@ lumine admin featured list --json
381
381
  lumine admin subject feature 123 --json
382
382
  lumine admin subject unfeature 123 --json
383
383
  lumine admin featured reorder --subject-ids 30,20,10 --json
384
+ lumine admin featured rotate --remove-subject-ids 30,20 \
385
+ --add-subject-ids 50,40 --json
384
386
  lumine admin brief --days 3 --json
385
387
  lumine admin notable add Minecrarft_guy --note "Created 8 thoughtful subjects and helped peers in 23 comments this window." --json
386
388
  lumine admin post recommend comment:456 --anyone-can-reward --reward-twinkles 3 --json
package/lib/admin.js CHANGED
@@ -1333,6 +1333,9 @@ export function parseAdminOperation(options) {
1333
1333
  if (action === "reorder") {
1334
1334
  return featuredReorderOperation(options);
1335
1335
  }
1336
+ if (action === "rotate") {
1337
+ return featuredRotateOperation(options);
1338
+ }
1336
1339
  }
1337
1340
 
1338
1341
  if (namespace === "comments" && action === "get") {
@@ -1862,6 +1865,7 @@ function legacySubjectsOperation({ action, target, options }) {
1862
1865
  );
1863
1866
  }
1864
1867
  if (action === "reorder") return featuredReorderOperation(options);
1868
+ if (action === "rotate") return featuredRotateOperation(options);
1865
1869
  throw cliValidationError(
1866
1870
  `Unknown subjects action: ${action || "(missing)"}.`,
1867
1871
  );
@@ -1886,6 +1890,37 @@ function featuredReorderOperation(options) {
1886
1890
  );
1887
1891
  }
1888
1892
 
1893
+ function featuredRotateOperation(options) {
1894
+ const removeIds = parseFeaturedSubjectIds(
1895
+ options.adminRemoveIds,
1896
+ "--remove-subject-ids",
1897
+ );
1898
+ const addIds = parseFeaturedSubjectIds(
1899
+ options.adminAddIds,
1900
+ "--add-subject-ids",
1901
+ );
1902
+ if (removeIds.length !== addIds.length) {
1903
+ throw cliValidationError(
1904
+ "Featured rotation requires the same number of removal and addition IDs.",
1905
+ );
1906
+ }
1907
+ const removeSet = new Set(removeIds);
1908
+ if (addIds.some((id) => removeSet.has(id))) {
1909
+ throw cliValidationError(
1910
+ "Featured rotation removal and addition IDs must not overlap.",
1911
+ );
1912
+ }
1913
+ return writeOperation(
1914
+ "featured.rotate",
1915
+ "PUT",
1916
+ "/cli/admin/subjects/featured/rotation",
1917
+ {
1918
+ removeIds,
1919
+ addIds,
1920
+ },
1921
+ );
1922
+ }
1923
+
1889
1924
  function recommendOperation(target, options) {
1890
1925
  const recommendationTarget = parseRecommendationTarget({
1891
1926
  target,
@@ -2259,18 +2294,24 @@ function parseTodoListStatus(value) {
2259
2294
  }
2260
2295
 
2261
2296
  function parseOrderedIds(value) {
2297
+ return parseFeaturedSubjectIds(value, "--subject-ids", true);
2298
+ }
2299
+
2300
+ function parseFeaturedSubjectIds(value, flag, completeList = false) {
2262
2301
  const ids = String(value || "")
2263
2302
  .split(",")
2264
2303
  .map((part) => part.trim())
2265
2304
  .filter(Boolean)
2266
2305
  .map((part) => parseRequiredInteger(part, "Featured subject ID", 1));
2267
- if (!String(value || "").trim()) {
2306
+ if (!String(value || "").trim() || ids.length === 0) {
2268
2307
  throw cliValidationError(
2269
- "Pass the complete ordered list with --subject-ids <id,id,...>.",
2308
+ completeList
2309
+ ? "Pass the complete ordered list with --subject-ids <id,id,...>."
2310
+ : `Pass at least one subject with ${flag} <id,id,...>.`,
2270
2311
  );
2271
2312
  }
2272
2313
  if (new Set(ids).size !== ids.length) {
2273
- throw cliValidationError("Featured subject IDs must be unique.");
2314
+ throw cliValidationError(`${flag} subject IDs must be unique.`);
2274
2315
  }
2275
2316
  return ids;
2276
2317
  }
package/lib/commands.js CHANGED
@@ -2436,6 +2436,16 @@ export function parseArgs(args) {
2436
2436
  : raw.ids
2437
2437
  ? String(raw.ids)
2438
2438
  : "",
2439
+ adminRemoveIds: raw.removeSubjectIds
2440
+ ? String(raw.removeSubjectIds)
2441
+ : raw.removeIds
2442
+ ? String(raw.removeIds)
2443
+ : "",
2444
+ adminAddIds: raw.addSubjectIds
2445
+ ? String(raw.addSubjectIds)
2446
+ : raw.addIds
2447
+ ? String(raw.addIds)
2448
+ : "",
2439
2449
  adminBucketId: raw.bucketId ? String(raw.bucketId) : "",
2440
2450
  adminLabel: raw.label ? String(raw.label) : "",
2441
2451
  adminUserIds: raw.userIds ? String(raw.userIds) : "",
@@ -2834,6 +2844,7 @@ export function printHelp() {
2834
2844
  lumine admin subject feature|unfeature <subject-id> [--json]
2835
2845
  lumine admin featured list [--unviewed|--viewed] [--json]
2836
2846
  lumine admin featured reorder --subject-ids <id,id,...> [--json]
2847
+ lumine admin featured rotate --remove-subject-ids <id,id,...> --add-subject-ids <id,id,...> [--json]
2837
2848
  lumine admin post get <target> [--type subject|comment|aiStory|dailyReflection] [--json]
2838
2849
  lumine admin post comments <target> [--type subject|aiStory|dailyReflection] [--unviewed|--viewed] [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--json]
2839
2850
  lumine admin post recommend <target> [--type subject|comment|aiStory|dailyReflection] [--anyone-can-reward] [--reward-twinkles 3] [--json]
@@ -2954,6 +2965,8 @@ Options:
2954
2965
  --idempotency-key <k> Stable retry key for one admin mutation
2955
2966
  --level <1|2|3> Admin subject effort level
2956
2967
  --subject-ids <ids> Complete ordered Featured subject IDs
2968
+ --remove-subject-ids <ids> Featured subjects approved for rotation removal
2969
+ --add-subject-ids <ids> Ordered replacement subjects for Featured rotation
2957
2970
  --bucket-id <id> Unbanned AI identity bucket for account consolidation
2958
2971
  --label <name> Name for a new unbanned AI identity bucket
2959
2972
  --user-ids <ids> Explicit user IDs for an AI bucket batch (up to 500)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.63",
3
+ "version": "0.2.64",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1202,6 +1202,8 @@ lumine admin subject feature 123 --json
1202
1202
  lumine admin subject unfeature 123 --json
1203
1203
  lumine admin featured list --json
1204
1204
  lumine admin featured reorder --subject-ids 30,20,10 --json
1205
+ lumine admin featured rotate --remove-subject-ids 30,20 \
1206
+ --add-subject-ids 50,40 --json
1205
1207
  ```
1206
1208
 
1207
1209
  Schemas:
@@ -1238,6 +1240,17 @@ type SubjectFeature = FeaturedList & {
1238
1240
 
1239
1241
  type SubjectUnfeature = SubjectFeature;
1240
1242
  type FeaturedReorder = SubjectFeature;
1243
+ type FeaturedRotate = FeaturedList & {
1244
+ status: "success" | "already_done";
1245
+ changed: boolean;
1246
+ data: FeaturedList["data"] & {
1247
+ rotation: {
1248
+ removeSubjectIds: number[];
1249
+ addSubjectIds: number[];
1250
+ finalSubjectIds: number[];
1251
+ };
1252
+ };
1253
+ };
1241
1254
  ```
1242
1255
 
1243
1256
  `reveal` publishes the existing hidden “viewed without responding” notification
@@ -1254,6 +1267,29 @@ unknown/deleted IDs, missing current members, non-subject rows, and more than
1254
1267
  20 subjects. Permanent pins and editorial ordering policy are deliberately not
1255
1268
  hardcoded.
1256
1269
 
1270
+ Featured rotate is the direct, atomic replacement verb for an approved
1271
+ rotation. `--remove-subject-ids` names the exact current members Mikey approved
1272
+ for removal; `--add-subject-ids` names the same number of replacements in
1273
+ descending editorial relevance. The API locks the canonical board, requires
1274
+ every removal to still be present and every addition to still be absent,
1275
+ validates every subject, then commits one complete-set replacement. Additions
1276
+ become the front of the board in the supplied order and all surviving members
1277
+ keep their relative order. A stale or partly mismatched plan fails without
1278
+ changing anything; an exact retry returns the canonical completed response.
1279
+ The response includes the canonical final board and the confirmed rotation
1280
+ IDs, so callers never synthesize Featured state locally.
1281
+
1282
+ The two lists must have the same nonzero length, so rotation never changes the
1283
+ board's count. That also lets an approved swap proceed when the website has
1284
+ left a pre-existing board above the delegated maximum without growing it;
1285
+ repair the inherited overflow separately with an approved `subject unfeature`.
1286
+
1287
+ This mutation deliberately does not choose candidates or override the
1288
+ editorial gates above. Before invoking it, the management agent must have
1289
+ freshly reviewed the board, shown Mikey the removals and replacements, received
1290
+ his go-ahead, and verified that every proposed new addition is recent and has
1291
+ never previously been Featured from canonical evidence.
1292
+
1257
1293
  ## Recommendation, Karma approval, and Twinkle rewards
1258
1294
 
1259
1295
  ```bash
@@ -3004,6 +3040,9 @@ and indexes;
3004
3040
  there are no runtime schema checks. The comment-targets migration backfills
3005
3041
  existing subject drafts into the generalized target columns. The local CLI changes
3006
3042
  are not available to users until a separately authorized npm publication.
3043
+ Deploy and verify the API's `/cli/admin/subjects/featured/rotation` route before
3044
+ publishing a CLI release that exposes `featured rotate`; an older API rejects
3045
+ the command without changing Featured state.
3007
3046
 
3008
3047
  Legacy aliases such as `subjects list`, `subjects get`, `subjects featured`,
3009
3048
  `comments get`, and `recommend` remain accepted, but the singular command forms