@stage5/lumine 0.2.67 → 0.2.69

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
@@ -387,6 +387,17 @@ lumine admin subject unfeature 123 --json
387
387
  lumine admin featured reorder --subject-ids 30,20,10 --json
388
388
  lumine admin featured rotate --remove-subject-ids 30,20 \
389
389
  --add-subject-ids 50,40 --json
390
+ lumine admin featured plan --remove-subject-ids 30,20 \
391
+ --add-subject-ids 50,40 --subject-ids 40,50,10 \
392
+ --posted-after 2026-09-01T00:00:00+07:00 --output featured-plan.json --json
393
+ # Only after Mikey approves the exact mapping and final order:
394
+ lumine admin featured apply --file featured-plan.json --approve <exact-plan-hash> --json
395
+ lumine admin featured comments scan --checkpoint featured-read.json --json
396
+ # Read every page file before acknowledging; downloads are not reviews.
397
+ lumine admin featured comments acknowledge --checkpoint featured-read.json --reviewed --json
398
+ lumine admin featured comments recommend --file featured-decisions.json \
399
+ --checkpoint featured-recommend.json --json
400
+ lumine admin featured comments report --checkpoint featured-read.json --json
390
401
  lumine admin brief --days 3 --json
391
402
  lumine admin notable add Minecrarft_guy --note "Created 8 thoughtful subjects and helped peers in 23 comments this window." --json
392
403
  lumine admin post recommend comment:456 --anyone-can-reward --reward-twinkles 3 --json
@@ -418,7 +429,9 @@ Zero/Ciel responder without Lumine remaining active.
418
429
 
419
430
  `--scope full` is the default and authorizes the complete daily-management
420
431
  workflow. `--scope featured` is a deliberately narrow run for a requested
421
- Featured-only slice: it cannot comment, does not surface carry-over todos, does
432
+ Featured-only slice: it can review and encourage snapshot-bound Featured
433
+ comments, but cannot publish comments or use generic recommendation/reward
434
+ commands. It does not surface carry-over todos, does
422
435
  not write full-run queue coverage, does not require sponsor review, and never
423
436
  advances full-run review windows. Its dedicated start endpoint also prevents an
424
437
  older API from silently interpreting it as a full run. The
@@ -427,6 +440,18 @@ the server to prove both a strict posting-date boundary and complete
427
440
  never-Featured history. `featured history` returns that evidence without the
428
441
  large repeated board snapshots in the general audit trail.
429
442
 
443
+ `featured plan`/`apply` bind approval to the exact server-stored board and
444
+ history revision; all approved replacements and ordering commit together.
445
+ Equal-size swaps and reorders support the website's 100-Subject capacity while
446
+ delegated growth remains capped at 20. The CLI verifies the final live order.
447
+ `featured comments scan` walks every thread (including old/nested replies),
448
+ with private, hashed pages and `--resume` recovery. `acknowledge --reviewed`
449
+ records actual reading separately from downloading. `recommend` applies only
450
+ agent-selected comments from acknowledged threads, with reward eligibility off
451
+ by default, and resumes the exact decision file/checkpoint safely. See
452
+ [the admin contract](sdk/LUMINE_ADMIN.md#exact-approved-refresh-and-resumable-comment-encouragement)
453
+ for the decision-file format, partial-coverage rules, and deployment prerequisites.
454
+
430
455
  Management agents also inspect recent public Build candidates during each full
431
456
  daily review.
432
457
  `builds review` opens one published app in an isolated temporary Chromium
@@ -0,0 +1,185 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { readAdminJsonFile, writeAdminJsonFile } from "./admin-news.js";
6
+ import {
7
+ acquireCheckpointLock,
8
+ releaseCheckpointLock,
9
+ runAutomaticPagination,
10
+ combinePaginatedResults,
11
+ writePaginatedResultFile,
12
+ } from "./admin-workflows.js";
13
+
14
+ // API read bound, deliberately unrelated to the delegated addition policy.
15
+ export const FEATURED_HISTORY_BATCH_SIZE = 100;
16
+
17
+ function invalid(message) {
18
+ const error = new Error(message);
19
+ error.code = "CLI_ADMIN_CLI_VALIDATION";
20
+ return error;
21
+ }
22
+
23
+ export async function runBatchedFeaturedHistory(args) {
24
+ const { options, operation, runId } = args;
25
+ if (options.adminCursor) {
26
+ throw invalid("Use --resume with --all, not a single-batch --cursor.");
27
+ }
28
+ const subjectIds = operation.pagination.filters.subjectIds;
29
+ const fingerprint = createHash("sha256")
30
+ .update(
31
+ JSON.stringify({
32
+ version: 1,
33
+ runId,
34
+ apiUrl: options.apiUrl,
35
+ path: operation.path,
36
+ }),
37
+ )
38
+ .digest("hex");
39
+ const checkpointPath = path.resolve(
40
+ options.adminCheckpoint ||
41
+ path.join(
42
+ os.tmpdir(),
43
+ `lumine-admin-run-${runId}-featured-history-${fingerprint}.json`,
44
+ ),
45
+ );
46
+ const outputPath = options.adminOutput
47
+ ? path.resolve(options.adminOutput)
48
+ : null;
49
+ if (
50
+ outputPath === checkpointPath ||
51
+ outputPath?.startsWith(`${checkpointPath}.`)
52
+ ) {
53
+ throw invalid(
54
+ "History output must not overwrite its checkpoint or batch files.",
55
+ );
56
+ }
57
+ const lock = acquireCheckpointLock(checkpointPath, fingerprint);
58
+ try {
59
+ let generation;
60
+ if (options.adminResume) {
61
+ const saved = readAdminJsonFile(
62
+ checkpointPath,
63
+ "the Featured history batch checkpoint",
64
+ );
65
+ if (
66
+ saved.kind !== "featured-history-batches" ||
67
+ saved.fingerprint !== fingerprint ||
68
+ !/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(
69
+ saved.generation || "",
70
+ )
71
+ ) {
72
+ throw invalid(
73
+ "The checkpoint does not belong to this run and exact history request.",
74
+ );
75
+ }
76
+ generation = saved.generation;
77
+ } else {
78
+ generation = randomUUID();
79
+ writeAdminJsonFile(
80
+ checkpointPath,
81
+ { kind: "featured-history-batches", fingerprint, generation },
82
+ { privateFile: true },
83
+ );
84
+ }
85
+ const results = [];
86
+ for (
87
+ let offset = 0;
88
+ offset < subjectIds.length;
89
+ offset += FEATURED_HISTORY_BATCH_SIZE
90
+ ) {
91
+ const ids = subjectIds.slice(
92
+ offset,
93
+ offset + FEATURED_HISTORY_BATCH_SIZE,
94
+ );
95
+ const batchIndex = offset / FEATURED_HISTORY_BATCH_SIZE;
96
+ const batchCheckpoint = `${checkpointPath}.${generation}.batch-${batchIndex}.json`;
97
+ const url = new URL(operation.path, "https://lumine.invalid");
98
+ url.searchParams.set("subjectIds", ids.join(","));
99
+ const result = await runAutomaticPagination({
100
+ ...args,
101
+ options: {
102
+ ...options,
103
+ adminOutput: undefined,
104
+ adminCheckpoint: batchCheckpoint,
105
+ adminResume: !!options.adminResume && existsSync(batchCheckpoint),
106
+ },
107
+ operation: {
108
+ ...operation,
109
+ path: `${url.pathname}${url.search}`,
110
+ pagination: { ...operation.pagination, filters: { subjectIds: ids } },
111
+ },
112
+ });
113
+ if (
114
+ JSON.stringify(result.data.subjects?.map((subject) => subject.id)) !==
115
+ JSON.stringify(ids)
116
+ ) {
117
+ throw invalid(
118
+ "The API did not return a canonical summary for every requested subject.",
119
+ );
120
+ }
121
+ results.push(result);
122
+ }
123
+ const firstCoverage = results[0].data.coverage;
124
+ const uniformCoverage = results.every(
125
+ (result) =>
126
+ result.data.coverage?.complete === firstCoverage.complete &&
127
+ result.data.coverage?.startedAt === firstCoverage.startedAt,
128
+ );
129
+ const result = combinePaginatedResults(
130
+ {
131
+ ok: true,
132
+ status: "success",
133
+ data: {
134
+ coverage: uniformCoverage
135
+ ? firstCoverage
136
+ : { complete: false, startedAt: null },
137
+ subjects: results.flatMap((result) => result.data.subjects),
138
+ pagination: {
139
+ nextCursor: null,
140
+ hasMore: false,
141
+ exhausted: true,
142
+ snapshotMaxId: null,
143
+ snapshotTimeStamp: null,
144
+ after: null,
145
+ snapshotScope: "per-batch",
146
+ eventOrder: "subject-batch-then-id-descending",
147
+ },
148
+ scan: {
149
+ checkpointPath,
150
+ resumed: !!options.adminResume,
151
+ pages: results.reduce(
152
+ (sum, result) => sum + result.data.scan.pages,
153
+ 0,
154
+ ),
155
+ scannedCount: results.reduce(
156
+ (sum, result) => sum + result.data.scan.scannedCount,
157
+ 0,
158
+ ),
159
+ candidateCount: results.reduce(
160
+ (sum, result) => sum + result.data.scan.candidateCount,
161
+ 0,
162
+ ),
163
+ batches: results.map((result, index) => ({
164
+ subjectIds: subjectIds.slice(
165
+ index * FEATURED_HISTORY_BATCH_SIZE,
166
+ (index + 1) * FEATURED_HISTORY_BATCH_SIZE,
167
+ ),
168
+ coverage: result.data.coverage,
169
+ pagination: result.data.pagination,
170
+ scan: result.data.scan,
171
+ })),
172
+ },
173
+ },
174
+ },
175
+ results,
176
+ );
177
+ if (outputPath) {
178
+ result.data.scan.outputPath = outputPath;
179
+ await writePaginatedResultFile(outputPath, result);
180
+ }
181
+ return result;
182
+ } finally {
183
+ releaseCheckpointLock(lock);
184
+ }
185
+ }