@bendyline/gilde 0.1.23 → 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 (24) 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.1/manifest.json +93 -0
  3. package/data/connector-types/gi/github-issues/versions/1.0.2/manifest.json +82 -0
  4. package/data/connector-types/gi/github-pulls/versions/1.0.1/manifest.json +52 -0
  5. package/data/connector-types/gi/github-releases/versions/1.0.1/manifest.json +86 -0
  6. package/data/connector-types/gi/github-wiki/versions/1.0.1/manifest.json +56 -0
  7. package/data/connector-types/index.json +1 -1
  8. package/data/connector-types/li/linear-issues/versions/1.0.1/manifest.json +70 -0
  9. package/data/connector-types/ma/mail-imap/versions/1.0.1/manifest.json +71 -0
  10. package/data/craftbook-templates/an/anniversary-cut/manifest.json +3 -1
  11. package/data/craftbook-templates/an/anniversary-cut/versions/1.0.2/craftbook.json +212 -0
  12. package/data/craftbook-templates/an/anniversary-cut/versions/1.0.2/test.json +157 -0
  13. package/data/craftbook-templates/au/audiobook-master-pack/manifest.json +3 -1
  14. package/data/craftbook-templates/au/audiobook-master-pack/versions/1.0.2/craftbook.json +197 -0
  15. package/data/craftbook-templates/au/audiobook-master-pack/versions/1.0.2/test.json +221 -0
  16. package/data/craftbook-templates/dr/draft-social-post/logo.webp +0 -0
  17. package/data/craftbook-templates/dr/draft-social-post/manifest.json +1 -0
  18. package/data/craftbook-templates/index.json +1 -1
  19. package/data/craftbook-templates/re/reception-report/logo.webp +0 -0
  20. package/data/craftbook-templates/re/reception-report/manifest.json +1 -0
  21. package/data/craftbook-templates/so/social-digest/logo.webp +0 -0
  22. package/data/craftbook-templates/so/social-digest/manifest.json +1 -0
  23. package/package.json +1 -1
  24. package/schemas/connector-type-version.schema.json +31 -0
@@ -0,0 +1,212 @@
1
+ {
2
+ "id": "anniversary-cut",
3
+ "name": "Anniversary Cut",
4
+ "description": "Build the highlight reel for the occasion: the archive queried for the person and the years that matter, clips chosen with stated reasons, assembled through the project's media pipeline with a run log, and delivered with a chapter list and a dedication — the reel that makes the birthday. Locks the occasion and the selection criteria FIRST (era coverage, the famous moments in, a length budget), then selects from the archive with no invented footage, then assembles, then writes the presentation notes. Use for birthday reels, anniversary videos, memorial montages, 'make a highlight reel', and milestone tributes.\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. Scope the occasion (filmarchivaris) — occasion, person, selection criteria and length budget locked → gated on `notes/scope.md` (markdown-notes)\n2. Select the clips (curator) — the cut list: archive clips chosen with reasons, in reel order → gated on `cutlist.json` (json)\n3. Assemble the reel (developer) — the reel rendered through the media pipeline, log on disk → gated on `renders/assembly-log.json` (json)\n4. Chapters and dedication (copywriter) — the chapter list, the dedication, how to play it on the day → gated on `report.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
+ "make a highlight reel",
8
+ "anniversary video",
9
+ "birthday montage",
10
+ "put together a tribute",
11
+ "clips of grandma over the years"
12
+ ],
13
+ "steps": [
14
+ {
15
+ "id": "scope",
16
+ "name": "Scope the occasion",
17
+ "description": "occasion, person, selection criteria and length budget locked",
18
+ "prompt": "Define the reel before pulling a single clip. Step 1: name the occasion and the person it honors, and read family.md so names and relationships are right. Step 2: read clips.json — the archive's indexed moments — and survey what exists for this person: which tapes, which years, which of the family-famous moments are on file. Step 3: lock the selection criteria — the reel covers the person's eras rather than clustering in one year, the moments the family always retells make the cut, every selection carries a stated reason, only clips that exist in the archive may be used (no invented footage, ever), and the total sticks to a length budget you set now from the occasion. Step 4: write an acceptance-criteria checklist ('every selected clip exists in clips.json', 'every selection has a reason', 'eras covered per the criteria', 'total within the length budget', 'assembled through the project's pipeline script via run_installed_script with a status-ok log', 'chapter list and dedication delivered'). Call write_task_note with the occasion + archive counts + checklist and write notes/scope.md.",
19
+ "suggestedRole": "filmarchivaris",
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": "select"
43
+ },
44
+ {
45
+ "id": "select",
46
+ "name": "Select the clips",
47
+ "description": "the cut list: archive clips chosen with reasons, in reel order",
48
+ "prompt": "Choose the reel's clips from the archive against the locked criteria. Step 1: sweep clips.json for every candidate moment featuring the person, across all tapes and years. Step 2: select — the famous moments first, then the quieter ones that show an era the famous ones miss; cut candidates that repeat a beat already covered. Every clip id must come from clips.json exactly as indexed; if a moment the family remembers is not in the archive, note it as missing footage rather than inventing a reference. Step 3: order the selections into the reel's emotional arc — chronology is the default, but a strong opening and a strong close outrank strict order. Step 4: write cutlist.json as a JSON array, each record with the clip id from the archive and the reason it made the cut; keep the running total inside the length budget. Call write_task_note with the selection count, the era coverage in one line, and anything remembered-but-missing.",
49
+ "suggestedRole": "curator",
50
+ "advanceWhen": {
51
+ "file": "cutlist.json",
52
+ "minBytes": 1,
53
+ "sniff": "json-valid"
54
+ },
55
+ "gate": {
56
+ "at": "completion",
57
+ "checks": [
58
+ {
59
+ "kind": "minBytes",
60
+ "file": "cutlist.json",
61
+ "bytes": 2
62
+ },
63
+ {
64
+ "kind": "recordSchema",
65
+ "file": "cutlist.json",
66
+ "fields": [
67
+ {
68
+ "name": "clip",
69
+ "type": "nonempty"
70
+ },
71
+ {
72
+ "name": "reason",
73
+ "type": "nonempty"
74
+ }
75
+ ],
76
+ "minRows": 5
77
+ },
78
+ {
79
+ "kind": "valuesSubsetOf",
80
+ "file": "cutlist.json",
81
+ "sourceFiles": [
82
+ "clips.json"
83
+ ],
84
+ "pattern": "\"clip\"\\s*:\\s*\"([^\"]+)\""
85
+ }
86
+ ],
87
+ "scripts": [
88
+ {
89
+ "name": "checkJsonValid",
90
+ "scope": "standard",
91
+ "inputs": {
92
+ "file": "cutlist.json"
93
+ }
94
+ }
95
+ ],
96
+ "onReject": "select",
97
+ "maxAttempts": 3
98
+ },
99
+ "next": "assemble"
100
+ },
101
+ {
102
+ "id": "assemble",
103
+ "name": "Assemble the reel",
104
+ "description": "the reel rendered through the media pipeline, log on disk",
105
+ "prompt": "Assemble the reel from the cut list. Step 1: the assembly 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 cutlist.json and the archive index, refuses any clip it cannot find, writes the render into renders/, and writes its run log to renders/assembly-log.json with a status field. Step 2: if run_installed_script reports an error, read the error text, fix what it names, and run it again — never write renders/assembly-log.json yourself; a fabricated log on a family keepsake is still a fabricated log. Step 3: once it succeeds, read the log and confirm status ok, the clip list matching the cut list, and the computed duration inside the length budget. Call write_task_note with the run outcome and the reel's duration.",
106
+ "suggestedRole": "developer",
107
+ "advanceWhen": {
108
+ "file": "renders/assembly-log.json",
109
+ "minBytes": 1,
110
+ "sniff": "json-valid"
111
+ },
112
+ "gate": {
113
+ "at": "completion",
114
+ "checks": [
115
+ {
116
+ "kind": "minBytes",
117
+ "file": "renders/assembly-log.json",
118
+ "bytes": 2
119
+ },
120
+ {
121
+ "kind": "fileCount",
122
+ "ext": [
123
+ "mp4"
124
+ ],
125
+ "min": 1,
126
+ "dir": "renders"
127
+ },
128
+ {
129
+ "kind": "jsonPathEquals",
130
+ "file": "renders/assembly-log.json",
131
+ "path": "status",
132
+ "value": "ok",
133
+ "label": "the assembly log reports status ok"
134
+ }
135
+ ],
136
+ "scripts": [
137
+ {
138
+ "name": "checkJsonValid",
139
+ "scope": "standard",
140
+ "inputs": {
141
+ "file": "renders/assembly-log.json"
142
+ }
143
+ }
144
+ ],
145
+ "onReject": "assemble",
146
+ "maxAttempts": 3
147
+ },
148
+ "next": "report"
149
+ },
150
+ {
151
+ "id": "report",
152
+ "name": "Chapters and dedication",
153
+ "description": "the chapter list, the dedication, how to play it on the day",
154
+ "prompt": "Write the presentation notes that turn a render into a gift. Step 1: write the chapter list — each selected moment in reel order with its rough position, who is in it, and one warm line about why it is there (the reasons from the cut list, written for the family rather than the archive). Step 2: write the dedication — a short paragraph to open or close the reel, in the family's voice, naming the occasion and the person. Step 3: add the practical notes — the reel's duration, where the render lives, and anything remembered-but-missing from the archive so nobody wonders why a famous moment is absent. Write report.md and call write_task_note with the report path and the chapter count.",
155
+ "suggestedRole": "copywriter",
156
+ "advanceWhen": {
157
+ "file": "report.md",
158
+ "minBytes": 1,
159
+ "sniff": "nonempty"
160
+ },
161
+ "gate": {
162
+ "at": "completion",
163
+ "checks": [
164
+ {
165
+ "kind": "minBytes",
166
+ "file": "report.md",
167
+ "bytes": 800
168
+ },
169
+ {
170
+ "kind": "contains",
171
+ "file": "report.md",
172
+ "pattern": "chapter",
173
+ "flags": "i",
174
+ "label": "a chapter list for the reel"
175
+ }
176
+ ],
177
+ "scripts": [
178
+ {
179
+ "name": "checkContains",
180
+ "scope": "standard",
181
+ "inputs": {
182
+ "file": "report.md",
183
+ "pattern": "(?:^|\\n)#{1,3}\\s+\\S",
184
+ "flags": "i"
185
+ }
186
+ }
187
+ ],
188
+ "onReject": "report",
189
+ "maxAttempts": 4
190
+ },
191
+ "next": "evaluate"
192
+ },
193
+ {
194
+ "id": "evaluate",
195
+ "name": "Evaluate",
196
+ "description": "Grade the deliverable against every acceptance criterion. All pass → finish; any fail → loop back and fix the gap.",
197
+ "prompt": "Open report.md, cutlist.json, renders/assembly-log.json, and clips.json and verify every criterion from notes/scope.md: every selected clip id exists in the archive, every selection carries a real reason, the eras are covered per the criteria, the log reports status ok with a duration inside the length budget, the chapter list matches the assembled order, and the dedication names the occasion and the person warmly and correctly. Write PASS/FAIL per criterion; on any failure, name the gap and loop back to report — or to select if the selection itself 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: \"report\" })` 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.",
198
+ "suggestedRole": "reviewer",
199
+ "next": "report"
200
+ },
201
+ {
202
+ "id": "finish",
203
+ "name": "Finish",
204
+ "description": "All acceptance criteria met. Stamp a short summary and report DONE.",
205
+ "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.",
206
+ "suggestedRole": "developer",
207
+ "terminal": true
208
+ }
209
+ ],
210
+ "version": "1.0.2",
211
+ "releasedAt": "2026-08-12T00:00:00Z"
212
+ }
@@ -0,0 +1,157 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "title": "Anniversary Cut smoke eval",
4
+ "objective": "Task-specific eval for the Anniversary Cut craftbook: a 30-record family archive is curated into a birthday reel for Ria's 70th — clips selected with reasons and era coverage, assembled through the fake pipeline, delivered with chapters and a dedication.",
5
+ "tags": [
6
+ "external"
7
+ ],
8
+ "prompt": "moms 70th is next month — can you put together a highlight reel from the archive with the fake pipeline? the clips index has everythign tagged",
9
+ "setup": {
10
+ "projectName": "Home Video Archive — Anniversary Reel Eval",
11
+ "about": "Self-contained eval project for anniversary-cut. The archive index is clips.json (30 indexed moments across tapes 01-09, 1986-2024) and the family roster is family.md — mam is Ria, the reel is for her 70th. The selection goes to cutlist.json, the fake media-assembly pipeline is the ready-made mock-probe project script (writes renders/anniversary-cut.mp4 plus renders/assembly-log.json), and the chapter list with the dedication goes to report.md.",
12
+ "missionObjectives": "Use the Anniversary Cut craftbook/template, select the reel from clips.json with a stated reason per clip and coverage across the eras, assemble through the provided mock-probe script, and deliver report.md with a chapter list and dedication. No invented footage, no real media tools — only the wired fake pipeline.",
13
+ "files": [
14
+ {
15
+ "path": "clips.json",
16
+ "content": "[\n {\n \"id\": \"t01-c01\",\n \"tape\": \"tape-01\",\n \"year\": 1986,\n \"timecode\": \"00:04:12\",\n \"durationSeconds\": 41,\n \"people\": [\n \"Ria\",\n \"Opa Cor\"\n ],\n \"description\": \"Ria and her father at the harbor, gulls everywhere\"\n },\n {\n \"id\": \"t01-c02\",\n \"tape\": \"tape-01\",\n \"year\": 1986,\n \"timecode\": \"00:17:40\",\n \"durationSeconds\": 28,\n \"people\": [\n \"Ria\"\n ],\n \"description\": \"Ria winning the village quiz, arms up\"\n },\n {\n \"id\": \"t01-c03\",\n \"tape\": \"tape-01\",\n \"year\": 1987,\n \"timecode\": \"00:29:05\",\n \"durationSeconds\": 35,\n \"people\": [\n \"Ria\",\n \"Henk\"\n ],\n \"description\": \"the first appearance of Henk, very nervous at the door\"\n },\n {\n \"id\": \"t02-c01\",\n \"tape\": \"tape-02\",\n \"year\": 1988,\n \"timecode\": \"00:03:55\",\n \"durationSeconds\": 74,\n \"people\": [\n \"Ria\",\n \"Henk\"\n ],\n \"description\": \"wedding day - the vows, rain starting right on cue\"\n },\n {\n \"id\": \"t02-c02\",\n \"tape\": \"tape-02\",\n \"year\": 1988,\n \"timecode\": \"00:21:18\",\n \"durationSeconds\": 52,\n \"people\": [\n \"Ria\",\n \"Henk\",\n \"Opa Cor\"\n ],\n \"description\": \"wedding toast by Opa Cor, the whole tent laughing\"\n },\n {\n \"id\": \"t02-c03\",\n \"tape\": \"tape-02\",\n \"year\": 1988,\n \"timecode\": \"00:40:02\",\n \"durationSeconds\": 33,\n \"people\": [\n \"Ria\"\n ],\n \"description\": \"Ria dancing in the wedding dress, shoes off\"\n },\n {\n \"id\": \"t03-c01\",\n \"tape\": \"tape-03\",\n \"year\": 1990,\n \"timecode\": \"00:02:33\",\n \"durationSeconds\": 47,\n \"people\": [\n \"Ria\",\n \"Daan\"\n ],\n \"description\": \"bringing Daan home, Ria narrating the whole drive\"\n },\n {\n \"id\": \"t03-c02\",\n \"tape\": \"tape-03\",\n \"year\": 1991,\n \"timecode\": \"00:15:20\",\n \"durationSeconds\": 26,\n \"people\": [\n \"Ria\",\n \"Daan\"\n ],\n \"description\": \"first birthday cake, most of it on Daan\"\n },\n {\n \"id\": \"t03-c03\",\n \"tape\": \"tape-03\",\n \"year\": 1992,\n \"timecode\": \"00:31:44\",\n \"durationSeconds\": 39,\n \"people\": [\n \"Henk\",\n \"Daan\"\n ],\n \"description\": \"Henk and Daan asleep in the same chair\"\n },\n {\n \"id\": \"t04-c01\",\n \"tape\": \"tape-04\",\n \"year\": 1993,\n \"timecode\": \"00:05:10\",\n \"durationSeconds\": 44,\n \"people\": [\n \"Ria\",\n \"Fleur\"\n ],\n \"description\": \"Fleur arrives - Daan inspecting his sister very seriously\"\n },\n {\n \"id\": \"t04-c02\",\n \"tape\": \"tape-04\",\n \"year\": 1994,\n \"timecode\": \"00:19:27\",\n \"durationSeconds\": 31,\n \"people\": [\n \"Ria\",\n \"Daan\",\n \"Fleur\"\n ],\n \"description\": \"the paddling pool afternoon, hose chaos\"\n },\n {\n \"id\": \"t04-c03\",\n \"tape\": \"tape-04\",\n \"year\": 1995,\n \"timecode\": \"00:36:51\",\n \"durationSeconds\": 58,\n \"people\": [\n \"Ria\"\n ],\n \"description\": \"Ria singing at Oma's birthday, surprising everyone\"\n },\n {\n \"id\": \"t05-c01\",\n \"tape\": \"tape-05\",\n \"year\": 1997,\n \"timecode\": \"00:08:14\",\n \"durationSeconds\": 42,\n \"people\": [\n \"Ria\",\n \"Henk\",\n \"Daan\",\n \"Fleur\"\n ],\n \"description\": \"the flooded-kitchen incident, all four bailing with pans\"\n },\n {\n \"id\": \"t05-c02\",\n \"tape\": \"tape-05\",\n \"year\": 1998,\n \"timecode\": \"00:22:39\",\n \"durationSeconds\": 27,\n \"people\": [\n \"Daan\",\n \"Fleur\"\n ],\n \"description\": \"school play - Fleur as the sun, Daan as a reluctant tree\"\n },\n {\n \"id\": \"t05-c03\",\n \"tape\": \"tape-05\",\n \"year\": 1999,\n \"timecode\": \"00:41:03\",\n \"durationSeconds\": 36,\n \"people\": [\n \"Ria\"\n ],\n \"description\": \"Ria finishing her first half marathon, red-faced and delighted\"\n },\n {\n \"id\": \"t06-c01\",\n \"tape\": \"tape-06\",\n \"year\": 2002,\n \"timecode\": \"00:06:30\",\n \"durationSeconds\": 49,\n \"people\": [\n \"Ria\",\n \"Fleur\"\n ],\n \"description\": \"camping in the dunes, tent collapsing at dawn\"\n },\n {\n \"id\": \"t06-c02\",\n \"tape\": \"tape-06\",\n \"year\": 2004,\n \"timecode\": \"00:18:22\",\n \"durationSeconds\": 38,\n \"people\": [\n \"Ria\",\n \"Henk\"\n ],\n \"description\": \"silver-grey Vespa arrives, Ria refuses to let Henk drive it\"\n },\n {\n \"id\": \"t06-c03\",\n \"tape\": \"tape-06\",\n \"year\": 2006,\n \"timecode\": \"00:33:47\",\n \"durationSeconds\": 30,\n \"people\": [\n \"Daan\"\n ],\n \"description\": \"Daan's graduation, Ria filming and crying at the same time\"\n },\n {\n \"id\": \"t07-c01\",\n \"tape\": \"tape-07\",\n \"year\": 2009,\n \"timecode\": \"00:04:58\",\n \"durationSeconds\": 45,\n \"people\": [\n \"Ria\",\n \"Fleur\"\n ],\n \"description\": \"Fleur's graduation, the hat that would not stay on\"\n },\n {\n \"id\": \"t07-c02\",\n \"tape\": \"tape-07\",\n \"year\": 2011,\n \"timecode\": \"00:20:15\",\n \"durationSeconds\": 34,\n \"people\": [\n \"Ria\",\n \"Henk\"\n ],\n \"description\": \"25th anniversary dance in the kitchen, radio on\"\n },\n {\n \"id\": \"t07-c03\",\n \"tape\": \"tape-07\",\n \"year\": 2013,\n \"timecode\": \"00:38:29\",\n \"durationSeconds\": 29,\n \"people\": [\n \"Ria\"\n ],\n \"description\": \"Ria teaching the neighbourhood kids to bake, chaos and flour\"\n },\n {\n \"id\": \"t08-c01\",\n \"tape\": \"tape-08\",\n \"year\": 2015,\n \"timecode\": \"00:07:21\",\n \"durationSeconds\": 40,\n \"people\": [\n \"Ria\",\n \"Daan\"\n ],\n \"description\": \"Daan's wedding - Ria's speech, short and devastatingly funny\"\n },\n {\n \"id\": \"t08-c02\",\n \"tape\": \"tape-08\",\n \"year\": 2017,\n \"timecode\": \"00:23:44\",\n \"durationSeconds\": 32,\n \"people\": [\n \"Ria\",\n \"Henk\"\n ],\n \"description\": \"the allotment garden's first real harvest, absurd zucchini\"\n },\n {\n \"id\": \"t08-c03\",\n \"tape\": \"tape-08\",\n \"year\": 2019,\n \"timecode\": \"00:39:56\",\n \"durationSeconds\": 51,\n \"people\": [\n \"Ria\",\n \"Fleur\",\n \"Noor\"\n ],\n \"description\": \"meeting Noor - Ria holding her granddaughter for the first time\"\n },\n {\n \"id\": \"t09-c01\",\n \"tape\": \"tape-09\",\n \"year\": 2020,\n \"timecode\": \"00:03:40\",\n \"durationSeconds\": 25,\n \"people\": [\n \"Ria\",\n \"Noor\"\n ],\n \"description\": \"window visit, Noor waving both arms\"\n },\n {\n \"id\": \"t09-c02\",\n \"tape\": \"tape-09\",\n \"year\": 2021,\n \"timecode\": \"00:16:12\",\n \"durationSeconds\": 37,\n \"people\": [\n \"Ria\",\n \"Noor\"\n ],\n \"description\": \"first baking lesson for Noor, history repeating in flour\"\n },\n {\n \"id\": \"t09-c03\",\n \"tape\": \"tape-09\",\n \"year\": 2022,\n \"timecode\": \"00:29:33\",\n \"durationSeconds\": 43,\n \"people\": [\n \"Ria\",\n \"Henk\",\n \"Daan\",\n \"Fleur\",\n \"Noor\"\n ],\n \"description\": \"the whole family at the coast, the five-person kite disaster\"\n },\n {\n \"id\": \"t09-c04\",\n \"tape\": \"tape-09\",\n \"year\": 2023,\n \"timecode\": \"00:44:18\",\n \"durationSeconds\": 28,\n \"people\": [\n \"Ria\"\n ],\n \"description\": \"Ria back on the Vespa for her 68th, victory lap of the block\"\n },\n {\n \"id\": \"t09-c05\",\n \"tape\": \"tape-09\",\n \"year\": 2024,\n \"timecode\": \"00:52:02\",\n \"durationSeconds\": 33,\n \"people\": [\n \"Ria\",\n \"Noor\"\n ],\n \"description\": \"Noor reading her first book aloud to Ria on the bench\"\n },\n {\n \"id\": \"t09-c06\",\n \"tape\": \"tape-09\",\n \"year\": 2024,\n \"timecode\": \"00:58:41\",\n \"durationSeconds\": 30,\n \"people\": [\n \"Henk\",\n \"Ria\"\n ],\n \"description\": \"Henk failing to surprise Ria with breakfast, caught mid-plan\"\n }\n]\n"
17
+ },
18
+ {
19
+ "path": "family.md",
20
+ "content": "# Family roster\n\n- Ria - mam, turns 70 next month, the reel is for her\n- Henk - pap, married Ria in 1988\n- Daan - eldest, born 1990\n- Fleur - daughter, born 1993\n- Noor - Fleur's daughter, born 2019\n- Opa Cor - Ria's father, on the earliest tapes\n"
21
+ }
22
+ ],
23
+ "worker": {
24
+ "name": "Noor",
25
+ "role": "Curator"
26
+ }
27
+ },
28
+ "mocks": [
29
+ {
30
+ "kind": "cli",
31
+ "id": "probe",
32
+ "description": "Provenance-trusted project script `mock-probe` that executes the fake media-assembly pipeline: validates cutlist.json against the clips.json archive and writes the stub reel plus its run log.",
33
+ "shim": {
34
+ "path": "scripts/mock-probe.ts",
35
+ "content": "import { defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'mock-probe',\n description:\n 'Fake media-assembly pipeline for this eval: validates cutlist.json against the clips.json archive index, refuses any clip that is not indexed, then writes the stub reel to renders/anniversary-cut.mp4 and its run log to renders/assembly-log.json.',\n inputs: {},\n outputs: {\n log: { type: 'string', description: 'The assembly run log JSON written to renders/assembly-log.json.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n});\n\nconst cutlist = JSON.parse(await gezel.fs.read('cutlist.json'));\nconst archive = JSON.parse(await gezel.fs.read('clips.json'));\nif (!Array.isArray(cutlist) || cutlist.length === 0) {\n throw new Error('cutlist.json must be a non-empty JSON array');\n}\nconst byId = new Map(archive.map((clip) => [clip.id, clip]));\nlet total = 0;\nconst assembled = [];\nfor (const row of cutlist) {\n const clip = byId.get(row.clip);\n if (!clip) throw new Error(`unknown clip in cutlist: ${row.clip} is not indexed in clips.json`);\n total += typeof clip.durationSeconds === 'number' ? clip.durationSeconds : 0;\n assembled.push(row.clip);\n}\nconst log = {\n status: 'ok',\n engine: 'mock-assemble',\n segments: assembled.length,\n clips: assembled,\n totalDurationSeconds: Math.round(total * 100) / 100,\n};\nawait gezel.fs.write('renders/anniversary-cut.mp4', `MOCKMP4 anniversary reel stub\\n${'0'.repeat(2048)}\\n`);\nawait gezel.fs.write('renders/assembly-log.json', `${JSON.stringify(log, null, 2)}\\n`);\ngezel.output({ log: JSON.stringify(log) });\n"
36
+ }
37
+ }
38
+ ],
39
+ "success": {
40
+ "summary": "cutlist.json selects real archive clips with reasons, the fake pipeline produced the reel and a status-ok log, and report.md carries a chapter list and a dedication for Ria.",
41
+ "deliverables": [
42
+ {
43
+ "path": "cutlist.json",
44
+ "kind": "json",
45
+ "checks": [
46
+ {
47
+ "kind": "recordSchema",
48
+ "file": "cutlist.json",
49
+ "fields": [
50
+ {
51
+ "name": "clip",
52
+ "type": "nonempty"
53
+ },
54
+ {
55
+ "name": "reason",
56
+ "type": "nonempty"
57
+ }
58
+ ],
59
+ "minRows": 5
60
+ },
61
+ {
62
+ "kind": "valuesSubsetOf",
63
+ "file": "cutlist.json",
64
+ "sourceFiles": [
65
+ "clips.json"
66
+ ],
67
+ "pattern": "\"clip\"\\s*:\\s*\"([^\"]+)\"",
68
+ "label": "every selected clip exists in the archive — no invented footage"
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ "path": "renders/anniversary-cut.mp4",
74
+ "kind": "generic-file",
75
+ "minBytes": 200
76
+ },
77
+ {
78
+ "path": "renders/assembly-log.json",
79
+ "kind": "json",
80
+ "checks": [
81
+ {
82
+ "kind": "jsonPathEquals",
83
+ "file": "renders/assembly-log.json",
84
+ "path": "status",
85
+ "value": "ok",
86
+ "label": "the assembly log reports status ok"
87
+ }
88
+ ]
89
+ },
90
+ {
91
+ "path": "report.md",
92
+ "kind": "markdown-report",
93
+ "minBytes": 700,
94
+ "checks": [
95
+ {
96
+ "kind": "contains",
97
+ "file": "report.md",
98
+ "pattern": "(?:^|\\n)#{1,3}\\s+\\S",
99
+ "flags": "i"
100
+ },
101
+ {
102
+ "kind": "contains",
103
+ "file": "report.md",
104
+ "pattern": "chapter",
105
+ "flags": "i",
106
+ "label": "a chapter list for the reel"
107
+ },
108
+ {
109
+ "kind": "contains",
110
+ "file": "report.md",
111
+ "pattern": "Ria",
112
+ "label": "the dedication names Ria"
113
+ },
114
+ {
115
+ "kind": "judge",
116
+ "file": "report.md",
117
+ "rubric": "The selection covers Ria across her eras (1980s through the 2020s) rather than clustering in one period, the family-famous moments (the wedding, meeting Noor) are in, each chapter's line gives a warm reason, and the dedication fits a 70th birthday.",
118
+ "sourceFiles": [
119
+ "clips.json",
120
+ "family.md"
121
+ ],
122
+ "label": "era coverage and warmth"
123
+ }
124
+ ]
125
+ }
126
+ ]
127
+ },
128
+ "rubric": {
129
+ "artifact": {
130
+ "path": "report.md",
131
+ "kind": "markdown"
132
+ },
133
+ "axes": [
134
+ {
135
+ "name": "emotional-arc",
136
+ "description": "The reel opens and closes strong and moves through Ria's life with intent."
137
+ },
138
+ {
139
+ "name": "selection-reasons",
140
+ "description": "Every clip earns its place with a stated reason, not a shrug."
141
+ },
142
+ {
143
+ "name": "era-coverage",
144
+ "description": "Decades are represented; the reel is a life, not one summer."
145
+ },
146
+ {
147
+ "name": "presentation",
148
+ "description": "Chapters and dedication turn a render into a gift."
149
+ }
150
+ ]
151
+ },
152
+ "qualityFocus": [
153
+ "no invented footage",
154
+ "era coverage",
155
+ "dedication warmth"
156
+ ]
157
+ }
@@ -19,6 +19,8 @@
19
19
  },
20
20
  "logo": "logo.webp",
21
21
  "license": "MIT",
22
- "yankedVersions": [],
22
+ "yankedVersions": [
23
+ "1.0.1"
24
+ ],
23
25
  "workflow": "build-loop"
24
26
  }
@@ -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
+ }