@bendyline/gilde 0.1.22 → 0.1.24

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 (58) hide show
  1. package/data/connector-types/ai/airtable-records/versions/1.0.1/manifest.json +81 -0
  2. package/data/connector-types/bl/bluesky-posts/versions/1.0.0/manifest.json +7 -1
  3. package/data/connector-types/bl/bluesky-posts/versions/1.0.1/manifest.json +93 -0
  4. package/data/connector-types/ca/calendar-google/versions/1.0.0/manifest.json +14 -0
  5. package/data/connector-types/gi/github-issues/versions/1.0.2/manifest.json +82 -0
  6. package/data/connector-types/gi/github-pulls/versions/1.0.1/manifest.json +52 -0
  7. package/data/connector-types/gi/github-releases/versions/1.0.1/manifest.json +86 -0
  8. package/data/connector-types/gi/github-wiki/versions/1.0.1/manifest.json +56 -0
  9. package/data/connector-types/in/instagram-media/manifest.json +1 -1
  10. package/data/connector-types/in/instagram-media/versions/1.0.0/manifest.json +29 -3
  11. package/data/connector-types/index.json +1 -1
  12. package/data/connector-types/li/linear-issues/versions/1.0.1/manifest.json +70 -0
  13. package/data/connector-types/li/linkedin-posts/manifest.json +16 -0
  14. package/data/connector-types/li/linkedin-posts/versions/1.0.0/manifest.json +54 -0
  15. package/data/connector-types/ma/mail-gmail/versions/1.0.0/manifest.json +14 -0
  16. package/data/connector-types/ma/mail-imap/versions/1.0.1/manifest.json +71 -0
  17. package/data/connector-types/ma/mail-microsoft365/versions/1.0.0/manifest.json +14 -1
  18. package/data/connector-types/ma/mail-outlook/versions/1.0.0/manifest.json +14 -1
  19. package/data/connector-types/x-/x-posts/manifest.json +1 -1
  20. package/data/connector-types/x-/x-posts/versions/1.0.0/manifest.json +30 -4
  21. package/data/craftbook-templates/an/anniversary-cut/manifest.json +3 -1
  22. package/data/craftbook-templates/an/anniversary-cut/versions/1.0.2/craftbook.json +212 -0
  23. package/data/craftbook-templates/an/anniversary-cut/versions/1.0.2/test.json +157 -0
  24. package/data/craftbook-templates/au/audiobook-master-pack/manifest.json +3 -1
  25. package/data/craftbook-templates/au/audiobook-master-pack/versions/1.0.2/craftbook.json +197 -0
  26. package/data/craftbook-templates/au/audiobook-master-pack/versions/1.0.2/test.json +221 -0
  27. package/data/craftbook-templates/dr/draft-social-post/logo.webp +0 -0
  28. package/data/craftbook-templates/dr/draft-social-post/manifest.json +3 -1
  29. package/data/craftbook-templates/dr/draft-social-post/versions/1.0.0/craftbook.json +8 -6
  30. package/data/craftbook-templates/index.json +1 -1
  31. package/data/craftbook-templates/re/reception-report/logo.webp +0 -0
  32. package/data/craftbook-templates/re/reception-report/manifest.json +1 -0
  33. package/data/craftbook-templates/so/social-digest/logo.webp +0 -0
  34. package/data/craftbook-templates/so/social-digest/manifest.json +1 -0
  35. package/data/project-types/ca/caregiving-binder/versions/1.1.0/about.md +8 -0
  36. package/data/project-types/ca/caregiving-binder/versions/1.1.0/care.json +4 -0
  37. package/data/project-types/ca/caregiving-binder/versions/1.1.0/manifest.json +178 -0
  38. package/data/project-types/ca/caregiving-binder/versions/1.1.0/meds.json +8 -0
  39. package/data/project-types/ca/caregiving-binder/versions/1.1.0/mission.md +8 -0
  40. package/data/project-types/ca/caregiving-binder/versions/1.1.0/pages/dashboard/index.html +265 -0
  41. package/data/project-types/ch/checkers/versions/1.2.0/about.md +9 -0
  42. package/data/project-types/ch/checkers/versions/1.2.0/game.json +77 -0
  43. package/data/project-types/ch/checkers/versions/1.2.0/manifest.json +167 -0
  44. package/data/project-types/ch/checkers/versions/1.2.0/mission.md +9 -0
  45. package/data/project-types/ch/checkers/versions/1.2.0/pages/board/index.html +1157 -0
  46. package/data/project-types/fl/flashcards/versions/1.1.0/about.md +8 -0
  47. package/data/project-types/fl/flashcards/versions/1.1.0/deck.json +7 -0
  48. package/data/project-types/fl/flashcards/versions/1.1.0/manifest.json +168 -0
  49. package/data/project-types/fl/flashcards/versions/1.1.0/mission.md +6 -0
  50. package/data/project-types/fl/flashcards/versions/1.1.0/pages/review/index.html +476 -0
  51. package/data/project-types/im/image-feed/versions/1.0.0/pages/gallery/index.html +2 -1
  52. package/data/project-types/index.json +1 -1
  53. package/data/project-types/so/social-feed/manifest.json +1 -0
  54. package/data/project-types/so/social-feed/versions/1.0.0/about.md +2 -2
  55. package/data/project-types/so/social-feed/versions/1.0.0/manifest.json +6 -0
  56. package/data/project-types/so/social-feed/versions/1.0.0/pages/dashboard/index.html +4 -2
  57. package/package.json +1 -1
  58. package/schemas/connector-type-version.schema.json +31 -0
@@ -0,0 +1,197 @@
1
+ {
2
+ "id": "audiobook-master-pack",
3
+ "name": "Master the Audiobook",
4
+ "description": "Assemble the finished chapters into a release: completeness swept against the manuscript first, the metadata manifest built and verified chapter by chapter, the master assembled through the project's media pipeline with a run log, and the listener-facing description written — the moment the folder of takes becomes a book. Locks the packaging rules FIRST (order, metadata fields, total duration), then the manifest, then the master, then the release copy. Use for mastering an audiobook, packaging the chapters, audiobook release prep, and 'all the chapters are rendered, now what'.\n\nA gallery craftbook generated from an archetype spec. It runs\n`phase → (per-phase gate) → … → evaluate → (loop) → finish`. Each build\nphase that produces a checkable artifact is followed by a **runtime\ngate-checkpoint** — the runtime verifies the artifact and routes with no\nmodel turn, looping back to redo the phase on a miss. The final `evaluate`\nstep holds a static deliverable gate plus a reviewer QA pass. What it adds\nover the generic `build-loop`: a specialist role per phase, a\ndomain-correct ordering, and a concrete per-phase quality bar.\n\nPhases:\n\n1. Sweep for completeness (producer) — chapters on disk checked against the manuscript, rules locked → gated on `notes/scope.md` (markdown-notes)\n2. Build the manifest (data-analyst) — master/manifest.json: chapter, file, duration, order → gated on `master/manifest.json` (json)\n3. Assemble the master (developer) — the chapters concatenated into the master with its log → gated on `master/book.m4b` (generic-file)\n4. Write the release copy (copywriter) — listener description and release notes for the finished book → gated on `master/release-notes.md` (markdown-report)\n\nThe gates never advance with an unmet criterion, and loop back to the\nowning phase to fix named gaps.\n",
5
+ "entryStepId": "scope",
6
+ "triggers": [
7
+ "master the audiobook",
8
+ "package the chapters",
9
+ "assemble the book",
10
+ "audiobook release prep",
11
+ "finish the audiobook"
12
+ ],
13
+ "steps": [
14
+ {
15
+ "id": "scope",
16
+ "name": "Sweep for completeness",
17
+ "description": "chapters on disk checked against the manuscript, rules locked",
18
+ "prompt": "Establish that the book is actually finished before packaging it. Step 1: read the manuscript's table of contents under manuscript/ and list every chapter the book should have. Step 2: sweep the rendered audio under audio/ — each chapter's audio file and its run log — and check the two lists against each other; a missing or failed chapter stops the run here, reported plainly, because a master with a hole in it is not a master. Step 3: lock the packaging rules — the manifest at master/manifest.json records every chapter with its audio file, duration, and play order; order follows the manuscript; durations come from the chapters' run logs, never estimated; and the master is assembled through the project's pipeline script via the run_installed_script tool with a status-ok log. Step 4: write an acceptance-criteria checklist ('every manuscript chapter has rendered audio', 'manifest complete with file, duration, and order per chapter', 'no duplicate chapters', 'master assembled with a status-ok log', 'total duration equals the sum of the chapters', 'listener description written'). Call write_task_note with the chapter counts + checklist and write notes/scope.md.",
19
+ "suggestedRole": "producer",
20
+ "advanceWhen": {
21
+ "file": "notes/scope.md",
22
+ "minBytes": 1,
23
+ "sniff": "nonempty"
24
+ },
25
+ "gate": {
26
+ "at": "completion",
27
+ "checks": [
28
+ {
29
+ "kind": "minBytes",
30
+ "file": "notes/scope.md",
31
+ "bytes": 120
32
+ },
33
+ {
34
+ "kind": "sniff",
35
+ "file": "notes/scope.md",
36
+ "sniff": "nonempty"
37
+ }
38
+ ],
39
+ "onReject": "scope",
40
+ "maxAttempts": 3
41
+ },
42
+ "next": "manifest"
43
+ },
44
+ {
45
+ "id": "manifest",
46
+ "name": "Build the manifest",
47
+ "description": "master/manifest.json: chapter, file, duration, order",
48
+ "prompt": "Build the metadata manifest the master run and every player will trust. Step 1: write master/manifest.json as a JSON array, one record per chapter — the chapter title as the manuscript names it, the audio file path exactly as it exists under audio/, the duration taken from that chapter's run log, and the play order as a number following the manuscript. Step 2: verify as you go — every file path must point at a file that exists, no chapter appears twice, and the orders run without gaps. Step 3: total the durations and note the book's full running time for the release copy. Call write_task_note with the record count and the total duration.",
49
+ "suggestedRole": "data-analyst",
50
+ "advanceWhen": {
51
+ "file": "master/manifest.json",
52
+ "minBytes": 1,
53
+ "sniff": "json-valid"
54
+ },
55
+ "gate": {
56
+ "at": "completion",
57
+ "checks": [
58
+ {
59
+ "kind": "minBytes",
60
+ "file": "master/manifest.json",
61
+ "bytes": 2
62
+ },
63
+ {
64
+ "kind": "recordSchema",
65
+ "file": "master/manifest.json",
66
+ "fields": [
67
+ {
68
+ "name": "chapter",
69
+ "type": "nonempty"
70
+ },
71
+ {
72
+ "name": "file",
73
+ "type": "nonempty"
74
+ },
75
+ {
76
+ "name": "duration"
77
+ },
78
+ {
79
+ "name": "order"
80
+ }
81
+ ],
82
+ "minRows": 3,
83
+ "uniqueBy": "chapter"
84
+ }
85
+ ],
86
+ "scripts": [
87
+ {
88
+ "name": "checkJsonValid",
89
+ "scope": "standard",
90
+ "inputs": {
91
+ "file": "master/manifest.json"
92
+ }
93
+ }
94
+ ],
95
+ "onReject": "manifest",
96
+ "maxAttempts": 3
97
+ },
98
+ "next": "assemble"
99
+ },
100
+ {
101
+ "id": "assemble",
102
+ "name": "Assemble the master",
103
+ "description": "the chapters concatenated into the master with its log",
104
+ "prompt": "Assemble the release master. Step 1: the mastering pipeline is a project script — call list_scripts to find it, then run it with run_installed_script({ name: \"<script name>\" }); never run it with run_nodejs_script and never reimplement it. It reads master/manifest.json, verifies every referenced audio file exists, joins the chapters in manifest order into master/book.m4b, and writes its run log to master/assembly-log.json with a status field, the chapter count, and the total duration it computed. Step 2: if run_installed_script reports an error, read the error text, fix what it names — usually a manifest path or order — and run it again; never write master/assembly-log.json yourself — the log is evidence the pipeline ran. Step 3: once it succeeds, read the log and confirm status ok, the chapter count matching the manifest, and the total duration matching the manifest's sum. Call write_task_note with the run outcome and the master's total duration.",
105
+ "suggestedRole": "developer",
106
+ "advanceWhen": {
107
+ "file": "master/book.m4b",
108
+ "minBytes": 1,
109
+ "sniff": "nonempty"
110
+ },
111
+ "gate": {
112
+ "at": "completion",
113
+ "checks": [
114
+ {
115
+ "kind": "minBytes",
116
+ "file": "master/book.m4b",
117
+ "bytes": 200
118
+ },
119
+ {
120
+ "kind": "sniff",
121
+ "file": "master/book.m4b",
122
+ "sniff": "nonempty"
123
+ },
124
+ {
125
+ "kind": "sniff",
126
+ "file": "master/assembly-log.json",
127
+ "sniff": "json-valid"
128
+ },
129
+ {
130
+ "kind": "jsonPathEquals",
131
+ "file": "master/assembly-log.json",
132
+ "path": "status",
133
+ "value": "ok",
134
+ "label": "the assembly log reports status ok"
135
+ }
136
+ ],
137
+ "onReject": "assemble",
138
+ "maxAttempts": 3
139
+ },
140
+ "next": "package"
141
+ },
142
+ {
143
+ "id": "package",
144
+ "name": "Write the release copy",
145
+ "description": "listener description and release notes for the finished book",
146
+ "prompt": "Write the words that travel with the book. Step 1: write the listener-facing description — what the book is about and who it is for, in the voice a listener browsing a store would actually read, without spoiling it. Step 2: add the release facts — the total running time from the master log, the chapter count, and the chapter list with per-chapter durations from the manifest. Step 3: add the release notes for the owner — what was assembled, anything flagged during the sweep, and the one-line confirmation that the master's numbers match the manifest. Write master/release-notes.md and call write_task_note with the path and the total running time.",
147
+ "suggestedRole": "copywriter",
148
+ "advanceWhen": {
149
+ "file": "master/release-notes.md",
150
+ "minBytes": 1,
151
+ "sniff": "nonempty"
152
+ },
153
+ "gate": {
154
+ "at": "completion",
155
+ "checks": [
156
+ {
157
+ "kind": "minBytes",
158
+ "file": "master/release-notes.md",
159
+ "bytes": 600
160
+ }
161
+ ],
162
+ "scripts": [
163
+ {
164
+ "name": "checkContains",
165
+ "scope": "standard",
166
+ "inputs": {
167
+ "file": "master/release-notes.md",
168
+ "pattern": "(?:^|\\n)#{1,3}\\s+\\S",
169
+ "flags": "i"
170
+ }
171
+ }
172
+ ],
173
+ "onReject": "package",
174
+ "maxAttempts": 4
175
+ },
176
+ "next": "evaluate"
177
+ },
178
+ {
179
+ "id": "evaluate",
180
+ "name": "Evaluate",
181
+ "description": "Grade the deliverable against every acceptance criterion. All pass → finish; any fail → loop back and fix the gap.",
182
+ "prompt": "Open master/release-notes.md, master/manifest.json, and master/assembly-log.json and verify every criterion from notes/scope.md: every manuscript chapter appears in the manifest exactly once with file, duration, and order, the referenced audio files exist, the log reports status ok with a chapter count and total duration matching the manifest, the release copy states the total running time, and the listener description reads like store copy rather than an internal note. Write PASS/FAIL per criterion; on any failure, name the gap and loop back to package — or to manifest if the metadata is the problem.\n\nThen route — this is the whole point of the loop:\n\n- **Every criterion PASSES →** call `advance_task_step({ ref, stepId: \"evaluate\", next: \"finish\" })`.\n- **Any criterion FAILS →** write the specific gaps to notes, then call `advance_task_step({ ref, stepId: \"evaluate\", next: \"package\" })` to loop back. The builder fixes exactly those gaps.\n\nNever route to `finish` while any criterion is unmet. The build phase's completion gate already blocked a grossly-incomplete deliverable; your job is the judgment an automated check cannot make (does it actually work, read well, look right). After ~3 unproductive loops, stop and report DONE_WITH_CONCERNS so the user can step in.",
183
+ "suggestedRole": "reviewer",
184
+ "next": "package"
185
+ },
186
+ {
187
+ "id": "finish",
188
+ "name": "Finish",
189
+ "description": "All acceptance criteria met. Stamp a short summary and report DONE.",
190
+ "prompt": "Every acceptance criterion passed. Write a one-paragraph DONE summary to task notes via `write_task_note`: what was built, the deliverable path(s), and a one-line confirmation that each criterion is met. Then report DONE.",
191
+ "suggestedRole": "developer",
192
+ "terminal": true
193
+ }
194
+ ],
195
+ "version": "1.0.2",
196
+ "releasedAt": "2026-08-12T00:00:00Z"
197
+ }
@@ -0,0 +1,221 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "title": "Master the Audiobook smoke eval",
4
+ "objective": "Task-specific eval for the Master the Audiobook craftbook: six rendered chapters with run logs are swept against the table of contents, described in a verified manifest, concatenated through the fake mastering pipeline, and packaged with listener copy.",
5
+ "tags": [
6
+ "external"
7
+ ],
8
+ "prompt": "all six chapters are rendered — can you master the audiobok and get the metadata sorted? theres a fake concat pipeline wired up",
9
+ "setup": {
10
+ "projectName": "Audiobook Studio — Mastering Eval",
11
+ "about": "Self-contained eval project for audiobook-master-pack. The table of contents is manuscript/toc.md; the six rendered chapters sit under audio/ as chapter-XX.wav with a chapter-XX-log.json run log each (durations in durationSeconds). The manifest goes to master/manifest.json; the fake mastering pipeline is the ready-made mock-probe project script (writes master/book.m4b plus master/assembly-log.json, and cross-checks manifest durations against the chapter logs). The listener description and release notes go to master/release-notes.md.",
12
+ "missionObjectives": "Use the Master the Audiobook craftbook/template, sweep the chapters against the table of contents, build master/manifest.json with file, duration, and order per chapter taken from the run logs, assemble through the provided mock-probe script, and write master/release-notes.md. No real audio tools or services — only the wired fake pipeline.",
13
+ "files": [
14
+ {
15
+ "path": "manuscript/toc.md",
16
+ "content": "# A Field Guide to Vanishing - table of contents\n\n1. The Door in the Sea Wall\n2. The Ferry at Dusk\n3. The Handwriting House\n4. What the Buoy Knew\n5. The Smallest Lantern\n6. Come Anyway\n"
17
+ },
18
+ {
19
+ "path": "audio/chapter-01.wav",
20
+ "content": "MOCKWAV chapter-01 rendered audio stub (The Door in the Sea Wall)\n0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n"
21
+ },
22
+ {
23
+ "path": "audio/chapter-01-log.json",
24
+ "content": "{\n \"status\": \"ok\",\n \"engine\": \"mock-tts\",\n \"chapter\": \"The Door in the Sea Wall\",\n \"file\": \"audio/chapter-01.wav\",\n \"durationSeconds\": 1712.4,\n \"wordCount\": 4310\n}\n"
25
+ },
26
+ {
27
+ "path": "audio/chapter-02.wav",
28
+ "content": "MOCKWAV chapter-02 rendered audio stub (The Ferry at Dusk)\n0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n"
29
+ },
30
+ {
31
+ "path": "audio/chapter-02-log.json",
32
+ "content": "{\n \"status\": \"ok\",\n \"engine\": \"mock-tts\",\n \"chapter\": \"The Ferry at Dusk\",\n \"file\": \"audio/chapter-02.wav\",\n \"durationSeconds\": 1544.8,\n \"wordCount\": 3890\n}\n"
33
+ },
34
+ {
35
+ "path": "audio/chapter-03.wav",
36
+ "content": "MOCKWAV chapter-03 rendered audio stub (The Handwriting House)\n0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n"
37
+ },
38
+ {
39
+ "path": "audio/chapter-03-log.json",
40
+ "content": "{\n \"status\": \"ok\",\n \"engine\": \"mock-tts\",\n \"chapter\": \"The Handwriting House\",\n \"file\": \"audio/chapter-03.wav\",\n \"durationSeconds\": 1836.2,\n \"wordCount\": 4620\n}\n"
41
+ },
42
+ {
43
+ "path": "audio/chapter-04.wav",
44
+ "content": "MOCKWAV chapter-04 rendered audio stub (What the Buoy Knew)\n0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n"
45
+ },
46
+ {
47
+ "path": "audio/chapter-04-log.json",
48
+ "content": "{\n \"status\": \"ok\",\n \"engine\": \"mock-tts\",\n \"chapter\": \"What the Buoy Knew\",\n \"file\": \"audio/chapter-04.wav\",\n \"durationSeconds\": 1490.5,\n \"wordCount\": 3750\n}\n"
49
+ },
50
+ {
51
+ "path": "audio/chapter-05.wav",
52
+ "content": "MOCKWAV chapter-05 rendered audio stub (The Smallest Lantern)\n0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n"
53
+ },
54
+ {
55
+ "path": "audio/chapter-05-log.json",
56
+ "content": "{\n \"status\": \"ok\",\n \"engine\": \"mock-tts\",\n \"chapter\": \"The Smallest Lantern\",\n \"file\": \"audio/chapter-05.wav\",\n \"durationSeconds\": 1671.9,\n \"wordCount\": 4205\n}\n"
57
+ },
58
+ {
59
+ "path": "audio/chapter-06.wav",
60
+ "content": "MOCKWAV chapter-06 rendered audio stub (Come Anyway)\n0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n"
61
+ },
62
+ {
63
+ "path": "audio/chapter-06-log.json",
64
+ "content": "{\n \"status\": \"ok\",\n \"engine\": \"mock-tts\",\n \"chapter\": \"Come Anyway\",\n \"file\": \"audio/chapter-06.wav\",\n \"durationSeconds\": 1922.7,\n \"wordCount\": 4840\n}\n"
65
+ }
66
+ ],
67
+ "worker": {
68
+ "name": "Ruben",
69
+ "role": "Producer"
70
+ }
71
+ },
72
+ "mocks": [
73
+ {
74
+ "kind": "cli",
75
+ "id": "probe",
76
+ "description": "Provenance-trusted project script `mock-probe` that runs the fake mastering pipeline: verifies master/manifest.json against the audio files and their run logs, then writes the stub master and its run log.",
77
+ "shim": {
78
+ "path": "scripts/mock-probe.ts",
79
+ "content": "import { defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'mock-probe',\n description:\n 'Fake mastering pipeline for this eval: reads master/manifest.json, verifies every referenced audio file exists with a sane duration and order, cross-checks durations against the per-chapter run logs, then writes the stub master to master/book.m4b and its run log to master/assembly-log.json.',\n inputs: {},\n outputs: {\n log: { type: 'string', description: 'The mastering run log JSON written to master/assembly-log.json.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n});\n\nconst manifest = JSON.parse(await gezel.fs.read('master/manifest.json'));\nif (!Array.isArray(manifest) || manifest.length === 0) {\n throw new Error('master/manifest.json must be a non-empty JSON array');\n}\nconst entries = await gezel.fs.list('audio');\nconst present = new Set(entries.filter((entry) => !entry.isDirectory).map((entry) => entry.name));\nlet total = 0;\nconst orders = new Set();\nfor (const row of manifest) {\n const base = String(row.file ?? '').split('/').pop();\n if (!base || !present.has(base)) {\n throw new Error(`manifest references a missing audio file: ${row.file}`);\n }\n if (typeof row.duration !== 'number' || row.duration <= 0) {\n throw new Error(`bad duration for chapter ${row.chapter}: ${row.duration}`);\n }\n if (typeof row.order !== 'number' || orders.has(row.order)) {\n throw new Error(`bad or duplicate order for chapter ${row.chapter}: ${row.order}`);\n }\n orders.add(row.order);\n const logName = base.replace(/\\.wav$/, '-log.json');\n if (present.has(logName)) {\n const chapterLog = JSON.parse(await gezel.fs.read(`audio/${logName}`));\n if (\n typeof chapterLog.durationSeconds === 'number' &&\n Math.abs(chapterLog.durationSeconds - row.duration) > 0.01\n ) {\n throw new Error(`manifest duration for chapter ${row.chapter} does not match its run log`);\n }\n }\n total += row.duration;\n}\nconst log = {\n status: 'ok',\n engine: 'mock-concat',\n chapters: manifest.length,\n totalDurationSeconds: Math.round(total * 100) / 100,\n};\nawait gezel.fs.write('master/book.m4b', `MOCKM4B audiobook master stub\\n${'0'.repeat(2048)}\\n`);\nawait gezel.fs.write('master/assembly-log.json', `${JSON.stringify(log, null, 2)}\\n`);\ngezel.output({ log: JSON.stringify(log) });\n"
80
+ }
81
+ }
82
+ ],
83
+ "success": {
84
+ "summary": "master/manifest.json describes all six chapters exactly once with log-true durations, the fake pipeline produced the master and a status-ok log counting six chapters, and the release notes carry the total running time plus listener copy.",
85
+ "deliverables": [
86
+ {
87
+ "path": "master/manifest.json",
88
+ "kind": "json",
89
+ "checks": [
90
+ {
91
+ "kind": "recordSchema",
92
+ "file": "master/manifest.json",
93
+ "fields": [
94
+ {
95
+ "name": "chapter",
96
+ "type": "nonempty"
97
+ },
98
+ {
99
+ "name": "file",
100
+ "type": "nonempty"
101
+ },
102
+ {
103
+ "name": "duration"
104
+ },
105
+ {
106
+ "name": "order"
107
+ }
108
+ ],
109
+ "minRows": 6,
110
+ "uniqueBy": "chapter"
111
+ },
112
+ {
113
+ "kind": "valuesSubsetOf",
114
+ "file": "master/manifest.json",
115
+ "sourceFiles": [
116
+ "audio/chapter-01-log.json",
117
+ "audio/chapter-02-log.json",
118
+ "audio/chapter-03-log.json",
119
+ "audio/chapter-04-log.json",
120
+ "audio/chapter-05-log.json",
121
+ "audio/chapter-06-log.json"
122
+ ],
123
+ "pattern": "\"file\"\\s*:\\s*\"([^\"]+)\"",
124
+ "label": "every manifest file reference matches a rendered chapter from the run logs"
125
+ }
126
+ ]
127
+ },
128
+ {
129
+ "path": "master/book.m4b",
130
+ "kind": "generic-file",
131
+ "minBytes": 200
132
+ },
133
+ {
134
+ "path": "master/assembly-log.json",
135
+ "kind": "json",
136
+ "checks": [
137
+ {
138
+ "kind": "jsonPathEquals",
139
+ "file": "master/assembly-log.json",
140
+ "path": "status",
141
+ "value": "ok",
142
+ "label": "the mastering log reports status ok"
143
+ },
144
+ {
145
+ "kind": "jsonPathEquals",
146
+ "file": "master/assembly-log.json",
147
+ "path": "chapters",
148
+ "value": 6,
149
+ "label": "all six chapters were assembled"
150
+ }
151
+ ]
152
+ },
153
+ {
154
+ "path": "master/release-notes.md",
155
+ "kind": "markdown-report",
156
+ "minBytes": 500,
157
+ "checks": [
158
+ {
159
+ "kind": "contains",
160
+ "file": "master/release-notes.md",
161
+ "pattern": "(?:^|\\n)#{1,3}\\s+\\S",
162
+ "flags": "i"
163
+ },
164
+ {
165
+ "kind": "contains",
166
+ "file": "master/release-notes.md",
167
+ "pattern": "duration|running time",
168
+ "flags": "i",
169
+ "label": "the total running time is stated"
170
+ },
171
+ {
172
+ "kind": "contains",
173
+ "file": "master/release-notes.md",
174
+ "pattern": "chapter",
175
+ "flags": "i",
176
+ "label": "the chapter list is present"
177
+ },
178
+ {
179
+ "kind": "judge",
180
+ "file": "master/release-notes.md",
181
+ "rubric": "The listener-facing description reads like store copy for the book (warm, no spoilers, aimed at a browsing listener), and the stated total running time matches the mastering log rather than being invented.",
182
+ "sourceFiles": [
183
+ "master/assembly-log.json",
184
+ "manuscript/toc.md"
185
+ ],
186
+ "label": "store-copy quality and log-true numbers"
187
+ }
188
+ ]
189
+ }
190
+ ]
191
+ },
192
+ "rubric": {
193
+ "artifact": {
194
+ "path": "master/release-notes.md",
195
+ "kind": "markdown"
196
+ },
197
+ "axes": [
198
+ {
199
+ "name": "completeness",
200
+ "description": "All six chapters swept, present, and in manuscript order — no holes."
201
+ },
202
+ {
203
+ "name": "metadata-quality",
204
+ "description": "Manifest durations and orders come from the run logs, never estimated."
205
+ },
206
+ {
207
+ "name": "listener-copy",
208
+ "description": "The description sells the book to a browsing listener without spoiling it."
209
+ },
210
+ {
211
+ "name": "release-readiness",
212
+ "description": "The notes confirm the master's numbers match the manifest."
213
+ }
214
+ ]
215
+ },
216
+ "qualityFocus": [
217
+ "completeness sweep",
218
+ "log-true metadata",
219
+ "listener-facing copy"
220
+ ]
221
+ }
@@ -4,7 +4,7 @@
4
4
  "id": "draft-social-post",
5
5
  "role": "general",
6
6
  "name": "Draft a Social Post",
7
- "description": "Draft one social post from idea to approved copy. Reads your voice guide and recent posts, grounds the angle in what your feeds actually responded to, drafts the base message, then fits a variant to each platform's limits — Bluesky 300 graphemes, X 280 characters, Instagram captions with the hook in the first 125 characters — with hashtags where idiomatic, alt text for media, and thread splits when the copy runs long. Then it stops for your review: approve, ask for a revision round, or approve and queue to Bluesky through the outbox. Covers social media copywriting, tweet and thread drafting, Bluesky posts, Instagram captions, post scheduling, and a human approval gate. Nothing is ever published without your say-so.",
7
+ "description": "Draft one social post from idea to approved copy. Reads your voice guide and recent posts, grounds the angle in what your feeds actually responded to, drafts the base message, then fits a variant to each platform's limits — Bluesky 300 graphemes, X 280 characters, Instagram captions with the hook in the first 125 characters, LinkedIn up to 3000 characters in a professional register — with hashtags where idiomatic, alt text for media, and thread splits when the copy runs long. Then it stops for your review: approve, ask for a revision round, or approve and queue to Bluesky through the outbox. Covers social media copywriting, tweet and thread drafting, Bluesky posts, Instagram captions, post scheduling, and a human approval gate. Nothing is ever published without your say-so.",
8
8
  "tags": [
9
9
  "social",
10
10
  "post",
@@ -12,11 +12,13 @@
12
12
  "bluesky",
13
13
  "twitter",
14
14
  "instagram",
15
+ "linkedin",
15
16
  "approval"
16
17
  ],
17
18
  "maintainer": {
18
19
  "name": "Gezel"
19
20
  },
21
+ "logo": "logo.webp",
20
22
  "license": "MIT",
21
23
  "yankedVersions": [],
22
24
  "workflow": "build-loop"
@@ -25,7 +25,8 @@
25
25
  "enum": [
26
26
  "bluesky",
27
27
  "x",
28
- "instagram"
28
+ "instagram",
29
+ "linkedin"
29
30
  ]
30
31
  },
31
32
  "description": "Target platforms. When absent, use whatever handles the project's synced corpora declare, defaulting to bluesky."
@@ -60,7 +61,7 @@
60
61
  "description": "Resolve the topic and angle, read the voice, and write the drafting brief.",
61
62
  "suggestedRole": "omroeper",
62
63
  "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
+ "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, LinkedIn up to 3000 characters with the hook in the first ~200), 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
65
  "advanceWhen": {
65
66
  "file": "posts/_drafting/brief.md",
66
67
  "minBytes": 200
@@ -83,7 +84,7 @@
83
84
  "name": "Draft the post and variants",
84
85
  "description": "Write the base copy and one platform-fitted variant per target, each within its limit.",
85
86
  "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
+ "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. LinkedIn (`posts/_drafting/variants/linkedin.md`): at most 3000 characters, professional register — the same voice dressed for a work audience, short paragraphs with the hook in the first two lines, hashtags sparing (3 or fewer) at the end; at 3000 characters the copy should never need splitting. 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
88
  "advanceWhen": {
88
89
  "file": "posts/_drafting/post.md",
89
90
  "minBytes": 200,
@@ -106,13 +107,14 @@
106
107
  {
107
108
  "kind": "judge",
108
109
  "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
+ "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, LinkedIn 3000), and the copy follows the project's voice guide when one exists.",
110
111
  "sourceFiles": [
111
112
  "voice-guide.md",
112
113
  "posts/_drafting/brief.md",
113
114
  "posts/_drafting/variants/bluesky.md",
114
115
  "posts/_drafting/variants/x.md",
115
- "posts/_drafting/variants/instagram.md"
116
+ "posts/_drafting/variants/instagram.md",
117
+ "posts/_drafting/variants/linkedin.md"
116
118
  ],
117
119
  "label": "variants fit their platforms and the voice"
118
120
  }
@@ -159,7 +161,7 @@
159
161
  "name": "Queue to Bluesky (only when asked)",
160
162
  "description": "Queue the approved Bluesky variant through the outbox — skipped unless the review answer asked for it.",
161
163
  "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.",
164
+ "prompt": "First read the task notes with `read_task_notes`. If the recorded review decision does NOT ask to queue anything — neither \"approve-and-queue-bluesky\" nor a write-in naming another connected platform (for example LinkedIn) — 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\nLinkedIn publishes through the same flow: when a linkedin variant exists and a live LinkedIn connection puts `draft_post`/`queue_post` in your function schema AND the user's review answer asked to queue it, draft and queue that variant the same way (accountId the linkedin binding) — otherwise it stays `approved` for manual posting. X queues through the same flow too: when an x variant exists, a live X connection puts the tools in your function schema, and the review answer asked for it, draft and queue that variant (accountId the x binding) X posts publish directly once the user's connection allows publishing. Instagram is the exception: Meta only publishes media fetched from a public https URL, so gezel can queue an Instagram post only when the user has provided an already-hosted image URL — record it as a note if they have one, otherwise the instagram variant stays `approved` for posting by hand. For every variant that was NOT queued, 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
165
  "next": "finish"
164
166
  },
165
167
  {