@stage5/lumine 0.2.33 → 0.2.34

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
@@ -169,6 +169,7 @@ lumine admin daily-run start --identity auto --comment-mode off --json
169
169
  lumine admin recommendations list --cursor '<cursor>' --json
170
170
  lumine admin subjects candidates --after 2026-08-01T00:00:00Z --json
171
171
  lumine admin subjects candidates --effort unassigned --json
172
+ lumine admin builds candidates --cursor '<cursor>' --json
172
173
  lumine admin subject get 123 --include-comments --json
173
174
  lumine admin post get https://www.twin-kle.com/ai-stories/88 --json
174
175
  lumine admin post comments dailyReflection:99 --json
@@ -184,6 +185,9 @@ lumine admin brief --days 3 --json
184
185
  lumine admin notable add Minecrarft_guy --note "Created 8 thoughtful subjects and helped peers in 23 comments this window." --json
185
186
  lumine admin post recommend comment:456 --anyone-can-reward --reward-twinkles 3 --json
186
187
  lumine admin post reward comment:456 --twinkles 3 --json
188
+ lumine admin comment draft build:884 --file comment.md \
189
+ --reviewed-version 4512 --reviewed-via runtime --json
190
+ lumine admin comment post --draft-id 77 --json
187
191
  lumine admin daily-run complete --json
188
192
  ```
189
193
 
@@ -195,6 +199,13 @@ and posts use the selected bot's server-owned canonical persona; a later human
195
199
  reply is handled by Twinkle's existing autonomous Zero/Ciel responder without
196
200
  Lumine remaining active.
197
201
 
202
+ Management agents also inspect recent public Build candidates during each run.
203
+ A direct Build comment is never server-generated: first open and try the
204
+ published app or pull and read its code, then compose with `--file` and bind
205
+ the draft to the exact `publishedArtifactVersionId` using
206
+ `--reviewed-version` and `--reviewed-via runtime|code`. The server rejects
207
+ missing or stale review evidence and any app/thread change before publication.
208
+
198
209
  Every operation is noninteractive when its required arguments are present.
199
210
  `--json` prints exactly one uncolored JSON value and returns a nonzero status
200
211
  for forbidden, not-found, validation, and partial-failure responses. Pass a
package/lib/admin.js CHANGED
@@ -125,6 +125,12 @@ export async function adminCommand(options) {
125
125
  },
126
126
  timeoutMs: options.timeoutMs,
127
127
  });
128
+ if (operation.name === "builds.candidates") {
129
+ result = normalizeAdminBuildCandidatesResult({
130
+ result,
131
+ siteUrl: options.siteUrl,
132
+ });
133
+ }
128
134
  if (recommendationContentTypes) {
129
135
  result = filterRecommendationQueueResult({
130
136
  result,
@@ -179,6 +185,38 @@ export async function adminCommand(options) {
179
185
  return result;
180
186
  }
181
187
 
188
+ export function normalizeAdminBuildCandidatesResult({ result, siteUrl }) {
189
+ const builds = Array.isArray(result?.builds) ? result.builds : [];
190
+ const nextCursor = String(result?.cursor || "").trim() || null;
191
+ return {
192
+ ok: true,
193
+ status: "success",
194
+ data: {
195
+ builds: builds.map((build) => {
196
+ const id = Number(build?.id || 0);
197
+ return {
198
+ ...build,
199
+ url:
200
+ id > 0
201
+ ? `${String(siteUrl || "").replace(/\/$/, "")}/app/${id}`
202
+ : null,
203
+ review: {
204
+ publishedArtifactVersionId:
205
+ Number(build?.publishedArtifactVersionId || 0) || null,
206
+ codePullAvailable: build?.collaborationMode === "open_source",
207
+ requiredBeforeComment: true,
208
+ },
209
+ };
210
+ }),
211
+ pagination: {
212
+ nextCursor,
213
+ hasMore: Boolean(nextCursor),
214
+ exhausted: !nextCursor,
215
+ },
216
+ },
217
+ };
218
+ }
219
+
182
220
  export function assertComposedCommentDraftResult({ result, expectedContent }) {
183
221
  const draft = result?.data?.draft;
184
222
  if (
@@ -385,6 +423,25 @@ export function parseAdminOperation(options) {
385
423
  },
386
424
  );
387
425
  }
426
+ if (action === "note" && target === "set") {
427
+ const note = String(options.note || "").trim();
428
+ if (!note) {
429
+ throw cliValidationError(
430
+ "Pass the quota-only context with --note <text>.",
431
+ );
432
+ }
433
+ if (note.length > 255) {
434
+ throw cliValidationError(
435
+ "An AI bucket note can be at most 255 characters.",
436
+ );
437
+ }
438
+ return writeOperation(
439
+ "ai-bucket.note.set",
440
+ "PUT",
441
+ `/cli/admin/ai-buckets/${bucketId}/note`,
442
+ { note },
443
+ );
444
+ }
388
445
  }
389
446
 
390
447
  if (namespace === "daily-run") {
@@ -434,6 +491,18 @@ export function parseAdminOperation(options) {
434
491
  );
435
492
  }
436
493
 
494
+ if (namespace === "builds" && action === "candidates") {
495
+ return readOperation(
496
+ "builds.candidates",
497
+ withQuery("/build/public/list", {
498
+ sort: "recent",
499
+ scope: "all",
500
+ cursor: options.adminCursor,
501
+ limit: options.limit,
502
+ }),
503
+ );
504
+ }
505
+
437
506
  if (
438
507
  namespace === "subjects" &&
439
508
  (action === "list" || action === "candidates")
@@ -671,7 +740,7 @@ export function parseAdminOperation(options) {
671
740
  }
672
741
 
673
742
  if (namespace === "bot-output" && !action) {
674
- if (options.adminDays !== undefined) {
743
+ if (options.adminDays) {
675
744
  const days = Number(options.adminDays);
676
745
  if (!Number.isInteger(days) || days < 1 || days > 30) {
677
746
  throw cliValidationError("--days must be an integer between 1 and 30.");
@@ -703,7 +772,7 @@ export function parseAdminOperation(options) {
703
772
 
704
773
  if (namespace === "comment") {
705
774
  if (action === "draft" || action === "reply") {
706
- const parsedTarget = parseRecommendationTarget({
775
+ const parsedTarget = parseAdminCommentTarget({
707
776
  target,
708
777
  explicitType: options.adminType,
709
778
  });
@@ -712,6 +781,35 @@ export function parseAdminOperation(options) {
712
781
  "comment reply targets a comment: lumine admin comment reply comment:<id>.",
713
782
  );
714
783
  }
784
+ const reviewedBuildVersionId = options.adminReviewedBuildVersion
785
+ ? parseRequiredInteger(
786
+ options.adminReviewedBuildVersion,
787
+ "--reviewed-version",
788
+ 1,
789
+ )
790
+ : undefined;
791
+ const buildReviewMethod = options.adminBuildReviewMethod
792
+ ? parseAdminBuildReviewMethod(options.adminBuildReviewMethod)
793
+ : undefined;
794
+ if (parsedTarget.type === "build") {
795
+ if (!options.adminFile) {
796
+ throw cliValidationError(
797
+ "Build comments are management-agent composed only; pass --file <comment.md> after reviewing the project.",
798
+ );
799
+ }
800
+ if (!reviewedBuildVersionId || !buildReviewMethod) {
801
+ throw cliValidationError(
802
+ "After reviewing the project, pass both --reviewed-version <published-artifact-id> and --reviewed-via runtime|code.",
803
+ );
804
+ }
805
+ } else if (
806
+ parsedTarget.type !== "comment" &&
807
+ (reviewedBuildVersionId || buildReviewMethod)
808
+ ) {
809
+ throw cliValidationError(
810
+ "Build review evidence applies only to build:<id> or a comment:<id> inside a Build.",
811
+ );
812
+ }
715
813
  return writeOperation(
716
814
  "comment.draft",
717
815
  "POST",
@@ -725,6 +823,8 @@ export function parseAdminOperation(options) {
725
823
  ...(options.adminFile
726
824
  ? { content: readComposedCommentFile(options.adminFile) }
727
825
  : {}),
826
+ ...(reviewedBuildVersionId ? { reviewedBuildVersionId } : {}),
827
+ ...(buildReviewMethod ? { buildReviewMethod } : {}),
728
828
  },
729
829
  );
730
830
  }
@@ -768,7 +868,7 @@ export function parseAdminOperation(options) {
768
868
  }
769
869
 
770
870
  throw cliValidationError(
771
- "Usage: lumine admin identity|daily-run|recommendations|post|subjects|subject|featured|comment|chat|news|audit|brief|bot-output|notable ...",
871
+ "Usage: lumine admin identity|daily-run|recommendations|builds|post|subjects|subject|featured|comment|chat|news|audit|brief|bot-output|notable ...",
772
872
  );
773
873
  }
774
874
 
@@ -947,6 +1047,50 @@ export function parseRecommendationTarget({ target, explicitType = "" }) {
947
1047
  return { type, id };
948
1048
  }
949
1049
 
1050
+ export function parseAdminCommentTarget({ target, explicitType = "" }) {
1051
+ const normalized = String(target || "").trim();
1052
+ const prefixed = normalized.match(
1053
+ /^(subject|comment|build|aistory|dailyreflection):(\d+)$/i,
1054
+ );
1055
+ const buildUrl = normalized.match(/\/(?:app|build)\/(\d+)(?:[/?#]|$)/i);
1056
+ const baseTarget = parseRecommendationTarget({
1057
+ target:
1058
+ prefixed?.[1]?.toLowerCase() === "build" || buildUrl
1059
+ ? `subject:${prefixed?.[2] || buildUrl?.[1]}`
1060
+ : normalized,
1061
+ explicitType:
1062
+ String(explicitType || "").toLowerCase() === "build"
1063
+ ? "subject"
1064
+ : explicitType,
1065
+ });
1066
+ const inferredBuild = prefixed?.[1]?.toLowerCase() === "build" || !!buildUrl;
1067
+ const explicitBuild = String(explicitType || "").toLowerCase() === "build";
1068
+ if (explicitType && !explicitBuild && inferredBuild) {
1069
+ throw cliValidationError(
1070
+ "The target and --type identify different content types.",
1071
+ );
1072
+ }
1073
+ if (explicitBuild && !inferredBuild && !/^\d+$/.test(normalized)) {
1074
+ throw cliValidationError(
1075
+ "The target and --type identify different content types.",
1076
+ );
1077
+ }
1078
+ if (inferredBuild || explicitBuild) {
1079
+ return { type: "build", id: baseTarget.id };
1080
+ }
1081
+ return baseTarget;
1082
+ }
1083
+
1084
+ function parseAdminBuildReviewMethod(value) {
1085
+ const normalized = String(value || "")
1086
+ .trim()
1087
+ .toLowerCase();
1088
+ if (normalized !== "runtime" && normalized !== "code") {
1089
+ throw cliValidationError("--reviewed-via must be runtime or code.");
1090
+ }
1091
+ return normalized;
1092
+ }
1093
+
950
1094
  function normalizeRecommendationTargetType(value) {
951
1095
  const normalized = String(value || "")
952
1096
  .trim()
package/lib/commands.js CHANGED
@@ -2194,6 +2194,10 @@ export function parseArgs(args) {
2194
2194
  adminEditionId: raw.editionId ? String(raw.editionId) : "",
2195
2195
  adminLeaseToken: raw.leaseToken ? String(raw.leaseToken) : "",
2196
2196
  adminFile: raw.file ? String(raw.file) : "",
2197
+ adminReviewedBuildVersion: raw.reviewedVersion
2198
+ ? String(raw.reviewedVersion)
2199
+ : "",
2200
+ adminBuildReviewMethod: raw.reviewedVia ? String(raw.reviewedVia) : "",
2197
2201
  adminFull: parseBoolean(raw.full, false),
2198
2202
  rewardTwinkles: raw.rewardTwinkles ? String(raw.rewardTwinkles) : "",
2199
2203
  includeComments: parseBoolean(raw.includeComments, false),
@@ -2511,9 +2515,11 @@ export function printHelp() {
2511
2515
  lumine admin identity list|status|use <zero|ciel|auto> [--json]
2512
2516
  lumine admin ai-bucket get --bucket-id <id> [--json]
2513
2517
  lumine admin ai-bucket accounts add --bucket-id <id> --user-ids <id,id,...> [--note <text>] [--json]
2518
+ lumine admin ai-bucket note set --bucket-id <id> --note <text> [--json]
2514
2519
  lumine admin daily-run start [--identity zero|ciel|auto] [--comment-mode off|draft|post] [--run-key <key>] [--json]
2515
2520
  lumine admin daily-run status|complete|fail [--reason <text>] [--json]
2516
2521
  lumine admin recommendations list [--content-types comment,dailyReflection] [--unviewed|--viewed] [--cursor <cursor>] [--json]
2522
+ lumine admin builds candidates [--cursor <cursor>] [--limit <number>] [--json]
2517
2523
  lumine admin subjects candidates [--after <date>] [--effort unassigned] [--unviewed|--viewed] [--cursor <cursor>] [--json]
2518
2524
  lumine admin subject get|reveal <subject-url-or-id> [--json]
2519
2525
  lumine admin subject comments <subject-url-or-id> [--unviewed|--viewed] [--cursor <cursor>] [--json]
@@ -2527,8 +2533,8 @@ export function printHelp() {
2527
2533
  lumine admin post recommend <target> [--type subject|comment|aiStory|dailyReflection] [--anyone-can-reward] [--reward-twinkles 3] [--json]
2528
2534
  lumine admin post skip <target> [--type comment|aiStory|dailyReflection] [--reason <text>] [--json]
2529
2535
  lumine admin post reward <target> [--type subject|comment|aiStory|dailyReflection] --twinkles 3 [--json]
2530
- lumine admin comment draft <target> [--type subject|comment|aiStory|dailyReflection] [--identity zero|ciel|auto] [--json]
2531
- lumine admin comment reply comment:<id> [--identity zero|ciel|auto] [--json]
2536
+ lumine admin comment draft <target> [--type subject|comment|build|aiStory|dailyReflection] [--file <comment.md>] [--reviewed-version <id> --reviewed-via runtime|code] [--identity zero|ciel|auto] [--json]
2537
+ lumine admin comment reply comment:<id> [--file <reply.md>] [--reviewed-version <id> --reviewed-via runtime|code] [--identity zero|ciel|auto] [--json]
2532
2538
  lumine admin comment post --draft-id <id> [--json]
2533
2539
  lumine admin comment edit <comment-id> --file <comment.md> [--json]
2534
2540
  lumine admin brief [--days <1..30>] [--json]
@@ -2597,7 +2603,7 @@ Options:
2597
2603
  --description <text> Build description for new/describe
2598
2604
  --no-description Skip New description or clear with describe
2599
2605
  --summary <text> Save summary
2600
- --note <text> Branch-suggestion message or notable-user rationale
2606
+ --note <text> Suggestion, notable-user, or AI-bucket context
2601
2607
  --cursor <id> Continue an owner suggestion inbox listing
2602
2608
  --after <date> Admin subjects: inclusive Unix/ISO creation boundary
2603
2609
  --effort unassigned Admin subjects: show only unassigned effort
@@ -2611,13 +2617,15 @@ Options:
2611
2617
  --subject-ids <ids> Complete ordered Featured subject IDs
2612
2618
  --bucket-id <id> Unbanned AI identity bucket for account consolidation
2613
2619
  --user-ids <ids> Explicit user IDs for an AI bucket batch (up to 500)
2614
- --type <type> Admin target: subject, comment, aiStory, or dailyReflection
2620
+ --type <type> Admin target: subject, comment, build, aiStory, or dailyReflection
2615
2621
  --kind recommend Admin recommendation queue kind
2616
2622
  --anyone-can-reward Enable canonical reward eligibility
2617
2623
  --reward-twinkles 3 Pair a recommendation with exactly 3 Twinkles
2618
2624
  --twinkles 3 Give exactly 3 Twinkles through the normal economy
2619
2625
  --draft-id <id> Canonical delegated comment draft ID
2620
2626
  --file <path> Editorial JSON or composed comment text file
2627
+ --reviewed-version <id> Published Build artifact version actually reviewed
2628
+ --reviewed-via <method> Build review method: runtime or code
2621
2629
  --reason <text> Reason when marking an admin run failed
2622
2630
  --force Overwrite server files even if this workspace is stale or missing filesHash
2623
2631
  --search <text> Search public open-source Builds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.33",
3
+ "version": "0.2.34",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -59,6 +59,8 @@ lumine admin ai-bucket get --bucket-id 10 --json
59
59
  lumine admin ai-bucket accounts add --bucket-id 10 \
60
60
  --user-ids 3127,13037,15410,16288 \
61
61
  --note "operator-confirmed account family" --json
62
+ lumine admin ai-bucket note set --bucket-id 10 \
63
+ --note "Quota accounting only; not a moderation flag." --json
62
64
  ```
63
65
 
64
66
  `accounts add` accepts 1-500 unique positive user IDs, preflights the complete
@@ -67,6 +69,10 @@ re-attributes current-day AI usage, and returns the canonical bucket members.
67
69
  It is idempotent to retry. The API records the real operator in the private
68
70
  Lumine audit log; no public bot identity is involved.
69
71
 
72
+ `note set` records up to 255 characters of private operational context on the
73
+ canonical bucket. Use it to distinguish quota bookkeeping from moderation;
74
+ the note itself changes no access, ban, or identity rules.
75
+
70
76
  This surface intentionally accepts only an existing **unbanned** bucket. It
71
77
  cannot ban accounts, block signup, add IP/device/risk-key rules, or infer an
72
78
  account family. Identification remains a human/LLM evidence judgment and must
@@ -484,6 +490,7 @@ lumine admin subjects candidates --after 2026-08-01T00:00:00Z \
484
490
  --cursor '<cursor>' --json
485
491
  lumine admin subjects candidates --effort unassigned --json
486
492
  lumine admin subjects candidates --unviewed --json
493
+ lumine admin builds candidates --cursor '<cursor>' --limit 50 --json
487
494
  ```
488
495
 
489
496
  Schemas:
@@ -529,6 +536,29 @@ type SubjectCandidates = Success<{
529
536
  subjects: Subject[];
530
537
  pagination: Pagination & { scannedCount: number };
531
538
  }>;
539
+
540
+ type BuildCandidates = Success<{
541
+ builds: Array<{
542
+ id: number;
543
+ title: string | null;
544
+ description: string | null;
545
+ username: string | null;
546
+ publishedAt: number | null;
547
+ publishedArtifactVersionId: number | null;
548
+ collaborationMode: "private" | "open_source";
549
+ url: string;
550
+ review: {
551
+ publishedArtifactVersionId: number | null;
552
+ codePullAvailable: boolean;
553
+ requiredBeforeComment: true;
554
+ };
555
+ }>;
556
+ pagination: {
557
+ nextCursor: string | null;
558
+ hasMore: boolean;
559
+ exhausted: boolean;
560
+ };
561
+ }>;
532
562
  ```
533
563
 
534
564
  Both cursors freeze a primary-key high-water mark and traverse descending IDs,
@@ -538,6 +568,24 @@ filters, so a page — recommendation or subject — can be empty while `hasMore
538
568
  remains true; continue until `exhausted`. Subject `--after` is inclusive, and
539
569
  the opaque cursor is bound to its original date and effort filters.
540
570
 
571
+ `builds candidates` is a management-agent discovery view over the canonical
572
+ public Build browser, ordered by the current published release. It is
573
+ available through the `admin` namespace only while a delegated run is active;
574
+ page until `pagination.exhausted`. Each item includes its canonical app URL,
575
+ published artifact version, and whether its code is pullable. This list does
576
+ not decide that an app deserves a comment. The management agent must open and
577
+ genuinely try the published runtime, or pull and read an open-source project,
578
+ before making that judgment. Direct API/persona automation is never a review
579
+ substitute.
580
+
581
+ During every management run, scan recent Build candidates back through the
582
+ run's review window alongside Subjects and the recommendation queue. An app
583
+ that is thin, broken, private, unchanged since a prior substantive bot
584
+ comment, or not meaningfully understood may be left alone. A new or materially
585
+ updated app where specific, truthful feedback would help is comment-worthy.
586
+ This is editorial attention, not a quota: do not manufacture a Build comment
587
+ just to prove the queue was visited.
588
+
541
589
  `--content-types` is sent to APIs that support server-side filtering so excluded
542
590
  types do not run their eligibility/content queries. The local CLI also filters
543
591
  the returned page defensively for deployment compatibility. The server cursor
@@ -1653,7 +1701,7 @@ from memory:
1653
1701
  decisions are yours to make and record with `post skip` or in the run
1654
1702
  report).
1655
1703
 
1656
- **Lumine Build app posts are composed-only, and only after actually looking
1704
+ **Lumine Build apps and app posts are composed-only, and only after actually looking
1657
1705
  (Mikey's direction, 2026-08-10).** When a post is about a Build app — the
1658
1706
  author shares their app, announces an update, or asks for feedback on their
1659
1707
  build — never use the server-generated draft path: the API persona cannot
@@ -1671,6 +1719,29 @@ tried it — ask the author about it instead. This is the standing rule for
1671
1719
  every composed comment, applied to apps: never claim an experience the
1672
1720
  session did not actually have.
1673
1721
 
1722
+ The same rule covers comments placed **directly on a Build app**. This is
1723
+ deliberately management-agent-only: no server-generated draft and no pure API
1724
+ persona may author one. After reviewing the project, bind the composed comment
1725
+ to the exact published artifact you saw:
1726
+
1727
+ ```bash
1728
+ lumine admin comment draft build:884 --file comment.md \
1729
+ --reviewed-version 4512 --reviewed-via runtime --json
1730
+ lumine admin comment post --draft-id 77 --json
1731
+ ```
1732
+
1733
+ Use `--reviewed-via code` only when you actually pulled and read the project;
1734
+ `runtime` means you opened and tried the published app. Replies to human
1735
+ comments inside a Build use `comment:<id>` plus the same review flags. The API
1736
+ rejects missing review evidence, a server-generated Build draft, a mismatched
1737
+ published version, a private/noncanonical Build, or any Build/comment-context
1738
+ change between draft and publication. A changed version means review the new
1739
+ project state and compose again. The flags are an auditable statement of what
1740
+ the management agent did, not permission to infer experience from metadata.
1741
+ Autonomous mention/reply generation and sponsored AI replies are disabled in
1742
+ Build threads. The generic `comment edit` shortcut is also disabled there;
1743
+ review the current version and post a version-bound correction reply instead.
1744
+
1674
1745
  **Offer a Lumine prompt when the moment invites it (Mikey's direction,
1675
1746
  2026-08-10).** Zero and Ciel may include one concrete, copy-pasteable Lumine
1676
1747
  prompt in a comment or reply — a genuinely powerful one, tailored to what the
@@ -1710,15 +1781,18 @@ containers, and publication applies to both kinds of draft.
1710
1781
  A draft targets one of:
1711
1782
 
1712
1783
  - `subject:<id>` (or a bare numeric ID) — a top-level comment on the subject;
1784
+ - `build:<id>` — a top-level comment on a public canonical Build, composed
1785
+ after reviewing its exact published version;
1713
1786
  - `aiStory:<id>` / `dailyReflection:<id>` — a top-level comment on the
1714
1787
  standalone post;
1715
1788
  - `comment:<id>` — a public reply to that specific comment. `comment reply`
1716
1789
  is the same operation and requires a comment target.
1717
1790
 
1718
1791
  A reply's container resolves canonically from the target comment: its subject,
1719
- or its AI Story / Daily Reflection root. Comments under any other root are
1720
- rejected with `CLI_ADMIN_UNSUPPORTED_REPLY_ROOT`. Replies to Zero/Ciel
1721
- comments and to notification comments are rejected with
1792
+ Build, or AI Story / Daily Reflection root. Comments under any other root are
1793
+ rejected with `CLI_ADMIN_UNSUPPORTED_REPLY_ROOT`. Build replies require the
1794
+ same exact-version review evidence as top-level Build comments. Replies to
1795
+ Zero/Ciel comments and to notification comments are rejected with
1722
1796
  `CLI_ADMIN_INVALID_REPLY_TARGET` — the bots never thread with themselves or
1723
1797
  each other, and a human's later reply to a delegated comment still enters the
1724
1798
  existing autonomous comment-assistant pipeline. Published replies carry the
@@ -1730,7 +1804,7 @@ type CommentDraft = Success<{
1730
1804
  draft: {
1731
1805
  id: number;
1732
1806
  runId: number;
1733
- targetType: "subject" | "comment" | "aiStory" | "dailyReflection";
1807
+ targetType: "subject" | "comment" | "build" | "aiStory" | "dailyReflection";
1734
1808
  targetId: number;
1735
1809
  targetUrl: string;
1736
1810
  subjectId: number | null; // container subject; null for standalone posts
@@ -1761,7 +1835,8 @@ type CommentPost = CommentGet & {
1761
1835
  };
1762
1836
  published: {
1763
1837
  commentId: number;
1764
- targetType: "subject" | "comment" | "aiStory" | "dailyReflection";
1838
+ targetType:
1839
+ "subject" | "comment" | "build" | "aiStory" | "dailyReflection";
1765
1840
  targetId: number;
1766
1841
  subjectId: number | null;
1767
1842
  subjectUrl: string | null;
@@ -1772,12 +1847,13 @@ type CommentPost = CommentGet & {
1772
1847
  };
1773
1848
  ```
1774
1849
 
1775
- The server loads the canonical container (subject or standalone post) and its
1776
- complete visible comment context, then invokes the existing exact Zero/Ciel
1777
- system prompt through the shared response assembler with a mode-specific
1778
- decision policy (comment vs reply). The raw prompt is never returned or
1779
- audited. The model—not regexes or keyword rules—chooses `draft` or `skip`
1780
- under the run policy.
1850
+ For Subjects and standalone posts, the server loads the canonical container
1851
+ and its complete visible comment context, then invokes the existing exact
1852
+ Zero/Ciel system prompt through the shared response assembler with a
1853
+ mode-specific decision policy (comment vs reply). The raw prompt is never
1854
+ returned or audited. The model—not regexes or keyword rules—chooses `draft`
1855
+ or `skip` under the run policy. Build comments never enter that generated
1856
+ path; they require management-agent-composed content and review evidence.
1781
1857
 
1782
1858
  Draft IDs are bound to operator, run, public bot, target, comment mode,
1783
1859
  context revision, persona revision, expiry, and idempotency key. The context