@bendyline/gilde 0.1.21 → 0.1.22

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 (47) hide show
  1. package/authoring/chat-models/nemotron3.5-lightning-30b-q4.json +3 -2
  2. package/authoring/chat-models/nemotron3.5-lightning-30b-q8.json +3 -2
  3. package/authoring/page-demo-stub.js +85 -0
  4. package/data/chat-models/index.json +1 -1
  5. package/data/chat-models/ne/nemotron3.5-lightning-30b-q4/manifest.json +3 -2
  6. package/data/chat-models/ne/nemotron3.5-lightning-30b-q8/manifest.json +3 -2
  7. package/data/connector-types/bl/bluesky-posts/manifest.json +16 -0
  8. package/data/connector-types/bl/bluesky-posts/versions/1.0.0/manifest.json +70 -0
  9. package/data/connector-types/gi/github-issues/versions/1.0.1/manifest.json +67 -0
  10. package/data/connector-types/in/instagram-media/manifest.json +17 -0
  11. package/data/connector-types/in/instagram-media/versions/1.0.0/manifest.json +70 -0
  12. package/data/connector-types/index.json +1 -1
  13. package/data/connector-types/x-/x-posts/manifest.json +17 -0
  14. package/data/connector-types/x-/x-posts/versions/1.0.0/manifest.json +66 -0
  15. package/data/craftbook-templates/dr/draft-social-post/manifest.json +23 -0
  16. package/data/craftbook-templates/dr/draft-social-post/versions/1.0.0/craftbook.json +176 -0
  17. package/data/craftbook-templates/dr/draft-social-post/versions/1.0.0/test.json +161 -0
  18. package/data/craftbook-templates/index.json +1 -1
  19. package/data/craftbook-templates/re/reception-report/manifest.json +21 -0
  20. package/data/craftbook-templates/re/reception-report/versions/1.0.0/craftbook.json +170 -0
  21. package/data/craftbook-templates/re/reception-report/versions/1.0.0/test.json +168 -0
  22. package/data/craftbook-templates/so/social-digest/manifest.json +21 -0
  23. package/data/craftbook-templates/so/social-digest/versions/1.0.0/craftbook.json +133 -0
  24. package/data/craftbook-templates/so/social-digest/versions/1.0.0/test.json +168 -0
  25. package/data/gezel-templates/index.json +1 -1
  26. package/data/gezel-templates/om/omroeper/manifest.json +20 -0
  27. package/data/gezel-templates/om/omroeper/versions/1.0.0/about.md +33 -0
  28. package/data/gezel-templates/om/omroeper/versions/1.0.0/manifest.json +23 -0
  29. package/data/project-types/im/image-feed/manifest.json +20 -0
  30. package/data/project-types/im/image-feed/versions/1.0.0/about.md +21 -0
  31. package/data/project-types/im/image-feed/versions/1.0.0/manifest.json +330 -0
  32. package/data/project-types/im/image-feed/versions/1.0.0/mission.md +10 -0
  33. package/data/project-types/im/image-feed/versions/1.0.0/pages/gallery/index.html +831 -0
  34. package/data/project-types/im/image-feed/versions/1.0.0/posts/index.json +4 -0
  35. package/data/project-types/im/image-feed/versions/1.0.0/style-guide.md +24 -0
  36. package/data/project-types/index.json +1 -1
  37. package/data/project-types/so/social-feed/manifest.json +20 -0
  38. package/data/project-types/so/social-feed/versions/1.0.0/about.md +19 -0
  39. package/data/project-types/so/social-feed/versions/1.0.0/manifest.json +330 -0
  40. package/data/project-types/so/social-feed/versions/1.0.0/mission.md +10 -0
  41. package/data/project-types/so/social-feed/versions/1.0.0/pages/dashboard/index.html +754 -0
  42. package/data/project-types/so/social-feed/versions/1.0.0/posts/index.json +4 -0
  43. package/data/project-types/so/social-feed/versions/1.0.0/voice-guide.md +22 -0
  44. package/package.json +1 -1
  45. package/schemas/connector-type-version.schema.json +6 -0
  46. package/schemas/craftbook-test.schema.json +1 -0
  47. package/schemas/project-type-version.schema.json +4 -0
@@ -0,0 +1,176 @@
1
+ {
2
+ "id": "draft-social-post",
3
+ "name": "Draft a Social Post",
4
+ "description": "Take one post from idea to approved, committed copy: brief (voice, recent posts, what resonated on the synced feeds), draft (base copy plus one platform-fitted variant per target, each within its character limit), a human review gate (approve / revise / approve-and-queue-to-Bluesky), then commit to the posts folder and — only when the user asked for it — queue the Bluesky variant through the outbox. The post-folder convention: drafts stage in posts/_drafting/, approved posts live at posts/<created>-<slug>/, and posts/index.json lists them. Works with or without a connected social account: with no synced corpus the brief proceeds from workspace content alone and says so.",
5
+ "entryStepId": "brief",
6
+ "triggers": [
7
+ "draft a social post",
8
+ "write a social post",
9
+ "draft a post about",
10
+ "draft a bluesky post",
11
+ "draft a tweet"
12
+ ],
13
+ "command": "draft-social-post",
14
+ "paramSchema": {
15
+ "type": "object",
16
+ "properties": {
17
+ "topic": {
18
+ "type": "string",
19
+ "description": "What the post is about. When absent, the brief step asks the user interactively with 2-3 angle options."
20
+ },
21
+ "platforms": {
22
+ "type": "array",
23
+ "items": {
24
+ "type": "string",
25
+ "enum": [
26
+ "bluesky",
27
+ "x",
28
+ "instagram"
29
+ ]
30
+ },
31
+ "description": "Target platforms. When absent, use whatever handles the project's synced corpora declare, defaulting to bluesky."
32
+ },
33
+ "campaign": {
34
+ "type": "string",
35
+ "description": "Optional campaign tag stamped into the post frontmatter."
36
+ }
37
+ }
38
+ },
39
+ "connectors": [
40
+ {
41
+ "typeId": "bluesky-posts",
42
+ "optional": true,
43
+ "reason": "Ground voice research in recent posts and queue approved posts for publishing."
44
+ },
45
+ {
46
+ "typeId": "x-posts",
47
+ "optional": true,
48
+ "reason": "Ground voice research in what recently resonated on X when fitting the 280-character variant."
49
+ },
50
+ {
51
+ "typeId": "instagram-media",
52
+ "optional": true,
53
+ "reason": "Ground caption and media-brief choices in the synced Instagram record (draft-only; publishing stays manual)."
54
+ }
55
+ ],
56
+ "steps": [
57
+ {
58
+ "id": "brief",
59
+ "name": "Brief",
60
+ "description": "Resolve the topic and angle, read the voice, and write the drafting brief.",
61
+ "suggestedRole": "omroeper",
62
+ "capabilityFloor": "small",
63
+ "prompt": "You are preparing the brief for one social post.\n\n1. Resolve the topic. The topic parameter is: \"{{topic}}\". If that value is empty or still reads as a raw {{...}} placeholder, no topic was provided: do the reading below first, pick 2-3 concrete angles worth posting about, and ask the user to choose. Ask with `ask_user_question` if it is in your function schema — one call, `choices` = your angles, `allowWriteIn` true. If `ask_user_question` is NOT in your function schema, post the numbered angles as a short prose reply and end the turn — the user's next message is the answer.\n2. Read the voice: open `voice-guide.md` and `style-guide.md` in the workspace when they exist (`read_file`). Note register, phrases to use or avoid, and the hashtag policy.\n3. Read what we posted lately: call `list_posts` if it is in your function schema, otherwise read `posts/index.json`. Note the last few titles and dates so this post does not repeat one.\n4. Read what resonates: call `list_artifacts` on `data/` in the project artifacts. Each subfolder there is one synced feed corpus (its `_meta.json` says which feed). Read the freshest few records with `read_artifact` and note which recent posts drew likes, reposts, or replies. If there is no `data/` corpus, write \"no synced corpus — working from workspace content alone\" in the brief and continue; never invent engagement facts.\n5. Write `posts/_drafting/brief.md` with `write_file`: the chosen angle (one short paragraph), the target platforms with their limits (Bluesky 300 graphemes, X 280 characters, Instagram caption up to 2200 characters with only the first 125 shown in feed), the key message in one sentence, the campaign tag when \"{{campaign}}\" resolved to a real value, and anything the voice guide demands.\n\nTarget platforms parameter: \"{{platforms}}\". If it is empty or a raw placeholder, use the platforms the project's synced corpora imply, defaulting to bluesky.\n\nWhen `posts/_drafting/brief.md` is written, call `advance_task_step` to move to the draft step.",
64
+ "advanceWhen": {
65
+ "file": "posts/_drafting/brief.md",
66
+ "minBytes": 200
67
+ },
68
+ "gate": {
69
+ "at": "completion",
70
+ "checks": [
71
+ {
72
+ "kind": "minBytes",
73
+ "file": "posts/_drafting/brief.md",
74
+ "bytes": 200
75
+ }
76
+ ],
77
+ "maxAttempts": 3
78
+ },
79
+ "next": "draft"
80
+ },
81
+ {
82
+ "id": "draft",
83
+ "name": "Draft the post and variants",
84
+ "description": "Write the base copy and one platform-fitted variant per target, each within its limit.",
85
+ "suggestedRole": "copywriter",
86
+ "prompt": "Write the post from `posts/_drafting/brief.md`. Read the brief and `voice-guide.md` (when present) first. If `posts/_drafting/review.md` exists, this is a revision round — read it and honor every note in it.\n\n1. Write `posts/_drafting/post.md` with `write_file`. Frontmatter (YAML between `---` lines): `status: in-review`, `title`, `slug` (lowercase-hyphenated, derived from the title), `platforms` (the target list from the brief), `created` (today, YYYY-MM-DD), `campaign` (only when the brief names one), `tags` (a short YAML list). Body: the base copy — the full message in the project's voice, not yet fitted to any platform.\n2. For each target platform, write `posts/_drafting/variants/<platform>.md` (for example `posts/_drafting/variants/bluesky.md`). Frontmatter: `platform: <name>` and `charCount: <n>` — the honest character count of the variant body. Body: the platform-fitted copy. Bluesky: at most 300 graphemes; hashtags only when they earn their place. X: at most 280 characters. Instagram: caption up to 2200 characters, hook inside the first 125 characters (that is all the feed shows), hashtags grouped at the end where idiomatic. If the copy cannot fit its limit, split it into a numbered thread (1/3, 2/3, ...) inside the same variant file and set `charCount` to the longest part. Where the post carries media, add an `alt-text:` line under the body describing the image for screen readers.\n3. If instagram or another image-led platform is a target, write `posts/_drafting/media/brief.md`: what the image or short clip should show, format and aspect ratio, and any text overlay.\n\nKeep every claim grounded in the brief or workspace files — invent no product facts, prices, or dates. When all files are written, call `advance_task_step` to move to review.",
87
+ "advanceWhen": {
88
+ "file": "posts/_drafting/post.md",
89
+ "minBytes": 200,
90
+ "requireChange": true
91
+ },
92
+ "gate": {
93
+ "at": "completion",
94
+ "checks": [
95
+ {
96
+ "kind": "minBytes",
97
+ "file": "posts/_drafting/post.md",
98
+ "bytes": 200
99
+ },
100
+ {
101
+ "kind": "contains",
102
+ "file": "posts/_drafting/post.md",
103
+ "pattern": "status: in-review",
104
+ "label": "draft carries in-review status"
105
+ },
106
+ {
107
+ "kind": "judge",
108
+ "file": "posts/_drafting/post.md",
109
+ "rubric": "Each platform variant under posts/_drafting/variants/ respects its platform's character limit (Bluesky 300 graphemes, X 280 characters, Instagram caption 2200 with the hook in the first 125), and the copy follows the project's voice guide when one exists.",
110
+ "sourceFiles": [
111
+ "voice-guide.md",
112
+ "posts/_drafting/brief.md",
113
+ "posts/_drafting/variants/bluesky.md",
114
+ "posts/_drafting/variants/x.md",
115
+ "posts/_drafting/variants/instagram.md"
116
+ ],
117
+ "label": "variants fit their platforms and the voice"
118
+ }
119
+ ],
120
+ "onReject": "draft",
121
+ "maxAttempts": 4
122
+ },
123
+ "next": "review"
124
+ },
125
+ {
126
+ "id": "review",
127
+ "name": "User review",
128
+ "description": "The human gate. Present the draft and variants; wait for an explicit answer. The scheduler stands down while the question is pending.",
129
+ "suggestedRole": "omroeper",
130
+ "prompt": "This is the human approval gate. Do not publish, finalize, or edit any post file this turn.\n\nRead `posts/_drafting/post.md` and every file under `posts/_drafting/variants/`. Compose a short review request: the base copy first, then each variant with its `charCount`, all quoted inline so the user can read everything without opening files.\n\nFirst save the full review request (base copy + every variant) as an artifact via `write_artifact` to `reviews/draft-post-review.md` — `documentPath` only resolves documents and artifacts, never workspace paths, so this snapshot is what the user’s review card previews. Then ask with `ask_user_question` if it is in your function schema — one call: `question` = the review request (markdown), `documentPath` = \"reviews/draft-post-review.md\" (the current task attaches automatically), `allowWriteIn` = true, `choices` = [\"Approve\", \"Revise\"], adding the choice \"Approve and queue to Bluesky\" ONLY when `posts/_drafting/variants/bluesky.md` exists and this project has a live Bluesky connection with publishing enabled (the `draft_post` and `queue_post` tools are in your function schema). If `ask_user_question` is NOT in your function schema: save the same review request with the choices as a numbered list via `write_task_note`, post it as a short prose reply, and end the turn — the task waits for the user's next message.\n\nWhen the answer arrives, act on it:\n- Revise → append the user's notes verbatim to `posts/_drafting/review.md` (create it if missing; keep earlier rounds above), record the decision with `write_task_note` (\"review round N: revise\"), then call `advance_task_step` with `next: \"draft\"` to loop back.\n- Approve → record \"review decision: approve\" with `write_task_note`, then call `advance_task_step` to move to finalize.\n- Approve and queue to Bluesky → record \"review decision: approve-and-queue-bluesky\" with `write_task_note`, then call `advance_task_step` to move to finalize.\n\nNever advance without an explicit user answer.",
131
+ "next": "finalize"
132
+ },
133
+ {
134
+ "id": "finalize",
135
+ "name": "Commit the post",
136
+ "description": "Move the approved draft out of staging into posts/<created>-<slug>/ and update the index.",
137
+ "suggestedRole": "omroeper",
138
+ "prompt": "Commit the approved draft.\n\nPreferred: call `create_post` if it is in your function schema — it commits `posts/_drafting/` to `posts/<created>-<slug>/` and updates `posts/index.json` in one step. Then set the committed post's status to `approved` with `update_post`.\n\nFallback (no `create_post` in your function schema): do it with file tools. Read `posts/_drafting/post.md` for `created` and `slug`, then create `posts/<created>-<slug>/` with `make_dir` and recreate each staged file there with `read_file` + `write_file` — `post.md` at the folder root (set `status: approved` in its frontmatter), the `variants/` files, and `media/` when present. Update `posts/index.json` with `write_file`: append an entry with `slug`, `folder` (the new posts/<created>-<slug> path), `title`, `status` (\"approved\"), `platforms`, and `created` to the `entries` array, creating the file with an `entries` array when it does not exist. Finally remove the copied staging files under `posts/_drafting/` with `delete_path` so the next post starts clean.\n\nDo not publish anything in this step. When the post folder and the index update are in place, call `advance_task_step` to continue.",
139
+ "advanceWhen": {
140
+ "file": "posts/index.json",
141
+ "minBytes": 2,
142
+ "requireChange": true
143
+ },
144
+ "gate": {
145
+ "at": "completion",
146
+ "checks": [
147
+ {
148
+ "kind": "minBytes",
149
+ "file": "posts/index.json",
150
+ "bytes": 2
151
+ }
152
+ ],
153
+ "maxAttempts": 3
154
+ },
155
+ "next": "queue-publish"
156
+ },
157
+ {
158
+ "id": "queue-publish",
159
+ "name": "Queue to Bluesky (only when asked)",
160
+ "description": "Queue the approved Bluesky variant through the outbox — skipped unless the review answer asked for it.",
161
+ "suggestedRole": "omroeper",
162
+ "prompt": "First read the task notes with `read_task_notes`. If the recorded review decision was NOT \"approve-and-queue-bluesky\", this step is a no-op: call `advance_task_step` with `next: \"finish\"` immediately and do nothing else.\n\nOtherwise queue the Bluesky publish:\n1. Read the committed bluesky variant (`posts/<created>-<slug>/variants/bluesky.md`).\n2. Call `draft_post` with the variant body (and its alt text when present), then `queue_post` on the returned draft. Queued posts ride the project's outbox, so the user's standing consent settings decide when it actually goes out. These tools exist only when a Bluesky connection is live: if they are not in your function schema, record \"Bluesky queue requested but no live connection — post manually\" with `write_task_note` and continue to the bookkeeping below.\n3. Bookkeeping: set the queued variant's status to `scheduled` and stamp whatever the draft/queue response returned (for example `scheduledFor` and a queue or post id) into the bluesky variant frontmatter — with `update_post` when it is in your function schema, otherwise by editing the variant file with `write_file`.\n\nVariants for X and Instagram stay `approved` — they are posted by hand. Write a short manual-posting checklist with `write_task_note`: which variant file carries each platform's copy, each platform's limit, and the media brief path when one exists. Then call `advance_task_step` to finish.",
163
+ "next": "finish"
164
+ },
165
+ {
166
+ "id": "finish",
167
+ "name": "Finish",
168
+ "description": "Summarize the outcome and stop.",
169
+ "suggestedRole": "omroeper",
170
+ "prompt": "Wrap up. Write one `write_task_note` summary: the final post folder path, the per-platform disposition (queued to Bluesky via the outbox / manual post needed / not targeted), and any follow-ups — media still to produce, a good time to post, replies to watch for. Report DONE.",
171
+ "terminal": true
172
+ }
173
+ ],
174
+ "version": "1.0.0",
175
+ "releasedAt": "2026-08-12T00:00:00Z"
176
+ }
@@ -0,0 +1,161 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "title": "Draft, revise, and commit a reviewed social post",
4
+ "objective": "Prove that draft-social-post grounds the brief in voice guide and synced corpus, fits per-platform variants, honors a scripted revision round before approval, and commits the approved post with the index updated.",
5
+ "tags": [
6
+ "corpus"
7
+ ],
8
+ "prompt": "Run the Draft a Social Post craftbook for this project. The topic is announcing our new returns desk. Title the post \"Announcing our new returns desk\", use the slug `returns-desk`, and use `2026-08-12` as the created date so the final folder is `posts/2026-08-12-returns-desk/`. Target Bluesky and X. The user's review answers are scripted in `source/scripted-review-answers.md` — apply them in order: the first review round asks for a revision with a concrete note, the second approves. Complete the real craftbook task through its review gate, apply the revision before finalizing, and commit the approved post with posts/index.json updated.",
9
+ "setup": {
10
+ "projectName": "Draft Social Post Eval",
11
+ "about": "Social drafting eval for a small furniture workshop's feeds. The workspace carries a voice guide, a post index with two published posts, and scripted review answers; the artifacts drawer carries a small synced Bluesky corpus with engagement records.",
12
+ "missionObjectives": "Draft the returns-desk announcement in the workshop voice, fit a Bluesky variant (max 300 graphemes) and an X variant (max 280 characters) each with honest charCount frontmatter, run the human review loop treating source/scripted-review-answers.md as the user's responses in order (round 1: revise per the note; round 2: approve), and commit the approved post to posts/2026-08-12-returns-desk/ with posts/index.json updated. A generic live answer to a review question means: apply the next scripted answer.",
13
+ "files": [
14
+ {
15
+ "path": "voice-guide.md",
16
+ "content": "# Voice guide — Bank & Beitel workshop\n\n- Plain, warm, first person plural (\"we\", \"our bench\").\n- Short sentences. No exclamation marks, no emojis, no marketing superlatives.\n- Always name the concrete benefit or number; never say \"amazing\" when \"ready in 10 minutes\" is true.\n- At most one hashtag per post on Bluesky and X, and only when it earns its place.\n- We talk about the work and the people, not \"content\".\n"
17
+ },
18
+ {
19
+ "path": "posts/index.json",
20
+ "content": "{\n \"entries\": [\n {\n \"slug\": \"shop-refresh\",\n \"folder\": \"posts/2026-07-20-shop-refresh\",\n \"title\": \"The shop got a fresh coat\",\n \"status\": \"published\",\n \"platforms\": [\"bluesky\", \"x\"],\n \"created\": \"2026-07-20\"\n },\n {\n \"slug\": \"meet-the-makers\",\n \"folder\": \"posts/2026-08-01-meet-the-makers\",\n \"title\": \"Meet the makers\",\n \"status\": \"published\",\n \"platforms\": [\"bluesky\"],\n \"created\": \"2026-08-01\"\n }\n ]\n}\n"
21
+ },
22
+ {
23
+ "path": "source/scripted-review-answers.md",
24
+ "content": "# Scripted review answers\n\nApply these in order, one per review round.\n\nRound 1 — Revise. Note: Good start, but say the concrete thing: returns are handled at the desk within 10 minutes, and store credit needs no receipt. Work the 10-minute promise into the post and into every variant.\n\nRound 2 — Approve.\n"
25
+ },
26
+ {
27
+ "path": "data/bluesky/_meta.json",
28
+ "surface": "artifact",
29
+ "content": "{\n \"bindingId\": \"bluesky-workshop\",\n \"typeId\": \"bluesky-posts\",\n \"displayName\": \"Bluesky — @bankenbeitel.bsky.social\",\n \"completeness\": \"window\",\n \"lastSyncedAt\": \"2026-08-11T05:00:00Z\"\n}\n"
30
+ },
31
+ {
32
+ "path": "data/bluesky/posts/2026-07/2026-07-21-shop-refresh.md",
33
+ "surface": "artifact",
34
+ "content": "---\nrecord: post\nplatform: bluesky\npostId: at://did:plc:bankenbeitel/app.bsky.feed.post/3kshop001\npermalink: https://bsky.app/profile/bankenbeitel.bsky.social/post/3kshop001\nauthor: bankenbeitel.bsky.social\ncreatedAt: 2026-07-21T09:05:00Z\nlikes: 14\nreposts: 2\nreplies: 1\nmetrics_updated_at: 2026-08-11T05:00:00Z\n---\nThe shop got a fresh coat over the weekend. Same bench, better light. Come by and see.\n"
35
+ },
36
+ {
37
+ "path": "data/bluesky/posts/2026-08/2026-08-02-meet-the-makers.md",
38
+ "surface": "artifact",
39
+ "content": "---\nrecord: post\nplatform: bluesky\npostId: at://did:plc:bankenbeitel/app.bsky.feed.post/3kmake001\npermalink: https://bsky.app/profile/bankenbeitel.bsky.social/post/3kmake001\nauthor: bankenbeitel.bsky.social\ncreatedAt: 2026-08-02T10:00:00Z\nlikes: 63\nreposts: 19\nreplies: 8\nmetrics_updated_at: 2026-08-11T05:00:00Z\n---\nMeet the makers: three of us, one bench, thirty years of chisels between us. People-first posts keep landing for this account.\n"
40
+ }
41
+ ],
42
+ "craftbookParams": {
43
+ "topic": "Announcing our new returns desk",
44
+ "platforms": "bluesky,x"
45
+ },
46
+ "worker": {
47
+ "name": "Jules",
48
+ "role": "Omroeper"
49
+ }
50
+ },
51
+ "mocks": [],
52
+ "success": {
53
+ "summary": "The approved post sits in posts/2026-08-12-returns-desk/ with status approved, both platform variants carry charCount frontmatter, the scripted 10-minute revision landed in the copy, and posts/index.json lists the new slug.",
54
+ "deliverables": [
55
+ {
56
+ "path": "posts/2026-08-12-returns-desk/post.md",
57
+ "kind": "markdown-doc",
58
+ "minBytes": 150,
59
+ "checks": [
60
+ {
61
+ "kind": "contains",
62
+ "file": "posts/2026-08-12-returns-desk/post.md",
63
+ "pattern": "status: approved",
64
+ "label": "committed post is approved"
65
+ },
66
+ {
67
+ "kind": "contains",
68
+ "file": "posts/2026-08-12-returns-desk/post.md",
69
+ "pattern": "10[- ]minute|10 minutes|within 10",
70
+ "flags": "i",
71
+ "label": "the scripted revision (10-minute promise) landed"
72
+ },
73
+ {
74
+ "kind": "contains",
75
+ "file": "posts/2026-08-12-returns-desk/post.md",
76
+ "pattern": "slug: returns-desk",
77
+ "label": "slug matches the brief"
78
+ }
79
+ ]
80
+ },
81
+ {
82
+ "path": "posts/2026-08-12-returns-desk/variants/bluesky.md",
83
+ "kind": "markdown-doc",
84
+ "minBytes": 60,
85
+ "checks": [
86
+ {
87
+ "kind": "contains",
88
+ "file": "posts/2026-08-12-returns-desk/variants/bluesky.md",
89
+ "pattern": "charCount:",
90
+ "label": "bluesky variant counts its characters"
91
+ },
92
+ {
93
+ "kind": "judge",
94
+ "file": "posts/2026-08-12-returns-desk/variants/bluesky.md",
95
+ "rubric": "The variant body is at most 300 characters, reads in the workshop's plain warm voice (no exclamation marks, no emojis, at most one hashtag), and mentions the 10-minute returns promise.",
96
+ "sourceFiles": [
97
+ "voice-guide.md"
98
+ ],
99
+ "label": "bluesky variant fits the platform and the voice"
100
+ }
101
+ ]
102
+ },
103
+ {
104
+ "path": "posts/2026-08-12-returns-desk/variants/x.md",
105
+ "kind": "markdown-doc",
106
+ "minBytes": 60,
107
+ "checks": [
108
+ {
109
+ "kind": "contains",
110
+ "file": "posts/2026-08-12-returns-desk/variants/x.md",
111
+ "pattern": "charCount:",
112
+ "label": "x variant counts its characters"
113
+ }
114
+ ]
115
+ }
116
+ ],
117
+ "checks": [
118
+ {
119
+ "kind": "contains",
120
+ "file": "posts/index.json",
121
+ "pattern": "returns-desk",
122
+ "label": "the index lists the new post"
123
+ }
124
+ ],
125
+ "taskGraph": {
126
+ "requireCraftbookTask": true,
127
+ "requireTerminalStep": true
128
+ },
129
+ "unchangedFixtures": [
130
+ "voice-guide.md",
131
+ "source/scripted-review-answers.md"
132
+ ]
133
+ },
134
+ "rubric": {
135
+ "artifact": {
136
+ "path": "posts/2026-08-12-returns-desk/post.md",
137
+ "kind": "markdown"
138
+ },
139
+ "axes": [
140
+ {
141
+ "name": "voice adherence",
142
+ "description": "The copy follows the voice guide: plain, warm, first person plural, no exclamation marks or emojis, concrete benefit named."
143
+ },
144
+ {
145
+ "name": "character discipline",
146
+ "description": "Each variant respects its platform limit and its charCount frontmatter is an honest count of the body."
147
+ },
148
+ {
149
+ "name": "revision responsiveness",
150
+ "description": "The round-1 revision note (the 10-minute promise, no-receipt store credit) is worked into the post and variants, not just acknowledged."
151
+ }
152
+ ],
153
+ "contextNote": "The review answers were scripted in source/scripted-review-answers.md: round 1 revise, round 2 approve. The drafting staging under posts/_drafting/ should be left clean after the commit."
154
+ },
155
+ "qualityFocus": [
156
+ "voice guide adherence",
157
+ "platform character discipline",
158
+ "scripted revision honored before approval",
159
+ "drafting staging left clean after commit"
160
+ ]
161
+ }