@bendyline/gilde 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/authoring/chat-models/nemotron3.5-lightning-30b-q4.json +3 -2
- package/authoring/chat-models/nemotron3.5-lightning-30b-q8.json +3 -2
- package/authoring/page-demo-stub.js +85 -0
- package/data/chat-models/index.json +1 -1
- package/data/chat-models/ne/nemotron3.5-lightning-30b-q4/manifest.json +3 -2
- package/data/chat-models/ne/nemotron3.5-lightning-30b-q8/manifest.json +3 -2
- package/data/connector-types/bl/bluesky-posts/manifest.json +16 -0
- package/data/connector-types/bl/bluesky-posts/versions/1.0.0/manifest.json +76 -0
- package/data/connector-types/ca/calendar-google/versions/1.0.0/manifest.json +14 -0
- package/data/connector-types/gi/github-issues/versions/1.0.1/manifest.json +67 -0
- package/data/connector-types/in/instagram-media/manifest.json +17 -0
- package/data/connector-types/in/instagram-media/versions/1.0.0/manifest.json +96 -0
- package/data/connector-types/index.json +1 -1
- package/data/connector-types/li/linkedin-posts/manifest.json +16 -0
- package/data/connector-types/li/linkedin-posts/versions/1.0.0/manifest.json +54 -0
- package/data/connector-types/ma/mail-gmail/versions/1.0.0/manifest.json +14 -0
- package/data/connector-types/ma/mail-microsoft365/versions/1.0.0/manifest.json +14 -1
- package/data/connector-types/ma/mail-outlook/versions/1.0.0/manifest.json +14 -1
- package/data/connector-types/x-/x-posts/manifest.json +17 -0
- package/data/connector-types/x-/x-posts/versions/1.0.0/manifest.json +92 -0
- package/data/craftbook-templates/dr/draft-social-post/manifest.json +24 -0
- package/data/craftbook-templates/dr/draft-social-post/versions/1.0.0/craftbook.json +178 -0
- package/data/craftbook-templates/dr/draft-social-post/versions/1.0.0/test.json +161 -0
- package/data/craftbook-templates/index.json +1 -1
- package/data/craftbook-templates/re/reception-report/manifest.json +21 -0
- package/data/craftbook-templates/re/reception-report/versions/1.0.0/craftbook.json +170 -0
- package/data/craftbook-templates/re/reception-report/versions/1.0.0/test.json +168 -0
- package/data/craftbook-templates/so/social-digest/manifest.json +21 -0
- package/data/craftbook-templates/so/social-digest/versions/1.0.0/craftbook.json +133 -0
- package/data/craftbook-templates/so/social-digest/versions/1.0.0/test.json +168 -0
- package/data/gezel-templates/index.json +1 -1
- package/data/gezel-templates/om/omroeper/manifest.json +20 -0
- package/data/gezel-templates/om/omroeper/versions/1.0.0/about.md +33 -0
- package/data/gezel-templates/om/omroeper/versions/1.0.0/manifest.json +23 -0
- package/data/project-types/ca/caregiving-binder/versions/1.1.0/about.md +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.1.0/care.json +4 -0
- package/data/project-types/ca/caregiving-binder/versions/1.1.0/manifest.json +178 -0
- package/data/project-types/ca/caregiving-binder/versions/1.1.0/meds.json +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.1.0/mission.md +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.1.0/pages/dashboard/index.html +265 -0
- package/data/project-types/ch/checkers/versions/1.2.0/about.md +9 -0
- package/data/project-types/ch/checkers/versions/1.2.0/game.json +77 -0
- package/data/project-types/ch/checkers/versions/1.2.0/manifest.json +167 -0
- package/data/project-types/ch/checkers/versions/1.2.0/mission.md +9 -0
- package/data/project-types/ch/checkers/versions/1.2.0/pages/board/index.html +1157 -0
- package/data/project-types/fl/flashcards/versions/1.1.0/about.md +8 -0
- package/data/project-types/fl/flashcards/versions/1.1.0/deck.json +7 -0
- package/data/project-types/fl/flashcards/versions/1.1.0/manifest.json +168 -0
- package/data/project-types/fl/flashcards/versions/1.1.0/mission.md +6 -0
- package/data/project-types/fl/flashcards/versions/1.1.0/pages/review/index.html +476 -0
- package/data/project-types/im/image-feed/manifest.json +20 -0
- package/data/project-types/im/image-feed/versions/1.0.0/about.md +21 -0
- package/data/project-types/im/image-feed/versions/1.0.0/manifest.json +330 -0
- package/data/project-types/im/image-feed/versions/1.0.0/mission.md +10 -0
- package/data/project-types/im/image-feed/versions/1.0.0/pages/gallery/index.html +832 -0
- package/data/project-types/im/image-feed/versions/1.0.0/posts/index.json +4 -0
- package/data/project-types/im/image-feed/versions/1.0.0/style-guide.md +24 -0
- package/data/project-types/index.json +1 -1
- package/data/project-types/so/social-feed/manifest.json +21 -0
- package/data/project-types/so/social-feed/versions/1.0.0/about.md +19 -0
- package/data/project-types/so/social-feed/versions/1.0.0/manifest.json +336 -0
- package/data/project-types/so/social-feed/versions/1.0.0/mission.md +10 -0
- package/data/project-types/so/social-feed/versions/1.0.0/pages/dashboard/index.html +756 -0
- package/data/project-types/so/social-feed/versions/1.0.0/posts/index.json +4 -0
- package/data/project-types/so/social-feed/versions/1.0.0/voice-guide.md +22 -0
- package/package.json +1 -1
- package/schemas/connector-type-version.schema.json +6 -0
- package/schemas/craftbook-test.schema.json +1 -0
- package/schemas/project-type-version.schema.json +4 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"releasedAt": "2026-08-12T00:00:00Z",
|
|
5
|
+
"driver": "native",
|
|
6
|
+
"source": {
|
|
7
|
+
"adapterId": "linkedin-posts"
|
|
8
|
+
},
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"allowPublish": {
|
|
13
|
+
"type": "boolean",
|
|
14
|
+
"title": "Allow this project's gezels to queue posts for publishing (each publish still needs your consent)",
|
|
15
|
+
"default": false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"secretShape": {
|
|
20
|
+
"kind": "oauth2",
|
|
21
|
+
"authorizeUrl": "https://www.linkedin.com/oauth/v2/authorization",
|
|
22
|
+
"tokenUrl": "https://www.linkedin.com/oauth/v2/accessToken",
|
|
23
|
+
"scopes": "openid profile w_member_social",
|
|
24
|
+
"clientIdEnv": "GEZEL_LINKEDIN_CLIENT_ID",
|
|
25
|
+
"clientSecretEnv": "GEZEL_LINKEDIN_CLIENT_SECRET",
|
|
26
|
+
"longLived": true,
|
|
27
|
+
"clientSetup": {
|
|
28
|
+
"providerLabel": "LinkedIn",
|
|
29
|
+
"docsUrl": "https://www.linkedin.com/developers/apps",
|
|
30
|
+
"secretRequired": true,
|
|
31
|
+
"appTypeNote": "Create a LinkedIn developer app and request the 'Share on LinkedIn' and 'Sign in with LinkedIn using OpenID Connect' products. LinkedIn requires the client secret; it stays in your OS keychain.",
|
|
32
|
+
"redirectPort": 6243,
|
|
33
|
+
"redirectNote": "LinkedIn matches redirect URLs exactly. Register the URI shown here character-for-character. Tokens last about 60 days; reconnect when prompted.",
|
|
34
|
+
"steps": [
|
|
35
|
+
"Sign in at linkedin.com/developers/apps and create an app — it must be tied to a LinkedIn Page (create a free one if you have none).",
|
|
36
|
+
"On the Products tab, request 'Share on LinkedIn' and 'Sign in with LinkedIn using OpenID Connect'; both are approved instantly.",
|
|
37
|
+
"On the Auth tab, add the redirect URI shown below under 'Authorized redirect URLs for your app', exactly.",
|
|
38
|
+
"Copy the Client ID and Client Secret from the Auth tab.",
|
|
39
|
+
"Paste both below — the secret stays in your OS keychain."
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"normalize": {
|
|
44
|
+
"kind": "native"
|
|
45
|
+
},
|
|
46
|
+
"actions": [
|
|
47
|
+
{
|
|
48
|
+
"name": "publish",
|
|
49
|
+
"consentScope": "social-publish"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"completeness": "window",
|
|
53
|
+
"notes": "Publishing-only, by LinkedIn's design: reading a member's posts requires LinkedIn's restricted Community Management partner program, which ordinary developer apps cannot join, so nothing syncs back from the feed — no post history, no engagement counts, no mentions. The corpus root holds only what Gezel itself did: the _meta.json marker plus a receipt for every published post under _actions/_sent/; reception and analytics stay on linkedin.com. Publishing works with the standard 'Share on LinkedIn' product: text posts up to 3000 characters, posted to the linked member's own feed with a permalink returned for the record. Publishing is off by default — a gezel can only draft until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Requires your own LinkedIn developer app: set GEZEL_LINKEDIN_CLIENT_ID and GEZEL_LINKEDIN_CLIENT_SECRET, or register the app under Settings — LinkedIn requires the client secret (it has no public-client PKCE support; Gezel still sends PKCE params, which LinkedIn ignores). LinkedIn issues no refresh token to ordinary apps, so the connection lasts about 60 days and then asks to be reconnected."
|
|
54
|
+
}
|
|
@@ -53,6 +53,20 @@
|
|
|
53
53
|
"authParams": {
|
|
54
54
|
"access_type": "offline",
|
|
55
55
|
"prompt": "consent"
|
|
56
|
+
},
|
|
57
|
+
"clientSetup": {
|
|
58
|
+
"providerLabel": "Google",
|
|
59
|
+
"docsUrl": "https://console.cloud.google.com/apis/credentials",
|
|
60
|
+
"secretRequired": true,
|
|
61
|
+
"appTypeNote": "Create an OAuth client ID of type Desktop app in Google Cloud Console. Desktop clients are issued a client secret Google does not treat as confidential — enter both here. Any loopback port works for the redirect.",
|
|
62
|
+
"steps": [
|
|
63
|
+
"Sign in at console.cloud.google.com and create (or pick) a project.",
|
|
64
|
+
"Under APIs & Services, open Library and enable the Gmail API.",
|
|
65
|
+
"Under APIs & Services, open OAuth consent screen: configure the app as External and add yourself as a test user.",
|
|
66
|
+
"Set the publishing status to In production — apps left in Testing get their sign-ins expired every 7 days. An unverified-app warning on your own app is safe to click through.",
|
|
67
|
+
"Under APIs & Services, open Credentials and choose Create credentials, then OAuth client ID, application type 'Desktop app'.",
|
|
68
|
+
"Copy the Client ID and Client Secret from the created client and paste them below. Desktop clients accept any 127.0.0.1 port, so there is no redirect URI to register."
|
|
69
|
+
]
|
|
56
70
|
}
|
|
57
71
|
},
|
|
58
72
|
"normalize": {
|
|
@@ -52,7 +52,20 @@
|
|
|
52
52
|
"authorizeUrl": "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize",
|
|
53
53
|
"tokenUrl": "https://login.microsoftonline.com/organizations/oauth2/v2.0/token",
|
|
54
54
|
"scopes": "offline_access https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Send",
|
|
55
|
-
"clientIdEnv": "GEZEL_MICROSOFT_CLIENT_ID"
|
|
55
|
+
"clientIdEnv": "GEZEL_MICROSOFT_CLIENT_ID",
|
|
56
|
+
"clientSetup": {
|
|
57
|
+
"providerLabel": "Microsoft",
|
|
58
|
+
"docsUrl": "https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
|
|
59
|
+
"secretRequired": false,
|
|
60
|
+
"appTypeNote": "Register an app in Microsoft Entra (Azure portal) and add the Mobile and desktop applications platform — a public client needs no client secret. Any loopback port works for the redirect.",
|
|
61
|
+
"steps": [
|
|
62
|
+
"Sign in at portal.azure.com and open Microsoft Entra ID, then App registrations, then New registration.",
|
|
63
|
+
"For supported account types choose 'Accounts in any organizational directory and personal Microsoft accounts'.",
|
|
64
|
+
"Under Authentication, add the 'Mobile and desktop applications' platform and enable the loopback redirect — any 127.0.0.1 port works.",
|
|
65
|
+
"No client secret is needed: this registration is a public client.",
|
|
66
|
+
"Copy the Application (client) ID from the Overview page and paste it below."
|
|
67
|
+
]
|
|
68
|
+
}
|
|
56
69
|
},
|
|
57
70
|
"normalize": {
|
|
58
71
|
"kind": "native"
|
|
@@ -52,7 +52,20 @@
|
|
|
52
52
|
"authorizeUrl": "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize",
|
|
53
53
|
"tokenUrl": "https://login.microsoftonline.com/consumers/oauth2/v2.0/token",
|
|
54
54
|
"scopes": "offline_access https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Send",
|
|
55
|
-
"clientIdEnv": "GEZEL_MICROSOFT_CLIENT_ID"
|
|
55
|
+
"clientIdEnv": "GEZEL_MICROSOFT_CLIENT_ID",
|
|
56
|
+
"clientSetup": {
|
|
57
|
+
"providerLabel": "Microsoft",
|
|
58
|
+
"docsUrl": "https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
|
|
59
|
+
"secretRequired": false,
|
|
60
|
+
"appTypeNote": "Register an app in Microsoft Entra (Azure portal) and add the Mobile and desktop applications platform — a public client needs no client secret. Any loopback port works for the redirect.",
|
|
61
|
+
"steps": [
|
|
62
|
+
"Sign in at portal.azure.com and open Microsoft Entra ID, then App registrations, then New registration.",
|
|
63
|
+
"For supported account types choose 'Accounts in any organizational directory and personal Microsoft accounts'.",
|
|
64
|
+
"Under Authentication, add the 'Mobile and desktop applications' platform and enable the loopback redirect — any 127.0.0.1 port works.",
|
|
65
|
+
"No client secret is needed: this registration is a public client.",
|
|
66
|
+
"Copy the Application (client) ID from the Overview page and paste it below."
|
|
67
|
+
]
|
|
68
|
+
}
|
|
56
69
|
},
|
|
57
70
|
"normalize": {
|
|
58
71
|
"kind": "native"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "connector-type",
|
|
4
|
+
"id": "x-posts",
|
|
5
|
+
"name": "X Posts",
|
|
6
|
+
"description": "Keep a searchable copy of your X (Twitter) presence: your posts with their like, repost, and view counts and a daily engagement history, optional mentions, and a daily follower count. Links through X's own sign-in. X charges for API reads, so syncs are spaced out by default. Your crew can draft posts for review and — once you allow publishing on the connector — post them with your per-post consent; posting fits X's free tier.",
|
|
7
|
+
"tags": [
|
|
8
|
+
"x",
|
|
9
|
+
"twitter",
|
|
10
|
+
"social",
|
|
11
|
+
"posts",
|
|
12
|
+
"native"
|
|
13
|
+
],
|
|
14
|
+
"maintainer": {
|
|
15
|
+
"name": "Gezel"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"releasedAt": "2026-08-12T00:00:00Z",
|
|
5
|
+
"driver": "native",
|
|
6
|
+
"source": {
|
|
7
|
+
"adapterId": "x-posts"
|
|
8
|
+
},
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"syncMentions": {
|
|
13
|
+
"type": "boolean",
|
|
14
|
+
"title": "Sync mentions of the account (costs extra paid reads)",
|
|
15
|
+
"default": false
|
|
16
|
+
},
|
|
17
|
+
"allowPublish": {
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"title": "Allow this project's gezels to queue posts for publishing (each publish still needs your consent)",
|
|
20
|
+
"default": false
|
|
21
|
+
},
|
|
22
|
+
"syncIntervalHours": {
|
|
23
|
+
"type": "number",
|
|
24
|
+
"title": "Hours between post syncs (X reads are paid — keep this generous)",
|
|
25
|
+
"default": 4,
|
|
26
|
+
"minimum": 0.25,
|
|
27
|
+
"maximum": 720
|
|
28
|
+
},
|
|
29
|
+
"metricsWindowDays": {
|
|
30
|
+
"type": "integer",
|
|
31
|
+
"title": "Days of recent posts to keep engagement counts fresh for",
|
|
32
|
+
"default": 7,
|
|
33
|
+
"minimum": 1,
|
|
34
|
+
"maximum": 365
|
|
35
|
+
},
|
|
36
|
+
"metricsRefreshHours": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"title": "Hours between engagement-count refreshes",
|
|
39
|
+
"default": 24,
|
|
40
|
+
"minimum": 0.25,
|
|
41
|
+
"maximum": 720
|
|
42
|
+
},
|
|
43
|
+
"metricsHistory": {
|
|
44
|
+
"type": "boolean",
|
|
45
|
+
"title": "Record daily engagement history records",
|
|
46
|
+
"default": true
|
|
47
|
+
},
|
|
48
|
+
"statsIntervalHours": {
|
|
49
|
+
"type": "number",
|
|
50
|
+
"title": "Hours between account-stats snapshots",
|
|
51
|
+
"default": 6,
|
|
52
|
+
"minimum": 0.25,
|
|
53
|
+
"maximum": 720
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"secretShape": {
|
|
58
|
+
"kind": "oauth2",
|
|
59
|
+
"authorizeUrl": "https://x.com/i/oauth2/authorize",
|
|
60
|
+
"tokenUrl": "https://api.x.com/2/oauth2/token",
|
|
61
|
+
"scopes": "tweet.read users.read offline.access tweet.write",
|
|
62
|
+
"clientIdEnv": "GEZEL_X_CLIENT_ID",
|
|
63
|
+
"clientSecretEnv": "GEZEL_X_CLIENT_SECRET",
|
|
64
|
+
"clientSetup": {
|
|
65
|
+
"providerLabel": "X (Twitter)",
|
|
66
|
+
"docsUrl": "https://developer.x.com/en/portal/dashboard",
|
|
67
|
+
"secretRequired": false,
|
|
68
|
+
"appTypeNote": "Create a free developer app and set its type to a Native/Public client — PKCE needs no client secret. Your API tier and read limits are your own.",
|
|
69
|
+
"redirectPort": 6241,
|
|
70
|
+
"redirectNote": "X matches callback URLs exactly. Register the URI shown here character-for-character.",
|
|
71
|
+
"steps": [
|
|
72
|
+
"Sign in at developer.x.com and create a project with an app — the free plan works for publishing.",
|
|
73
|
+
"In the app's settings, open 'User authentication settings' and click Set up.",
|
|
74
|
+
"Choose the 'Native App' (public client) type and enable OAuth 2.0.",
|
|
75
|
+
"Paste the redirect URI shown below into Callback URI / Redirect URL, character-for-character.",
|
|
76
|
+
"Fill the required website URL field with any page of yours and save.",
|
|
77
|
+
"Copy the OAuth 2.0 Client ID from the app's Keys and tokens page and paste it below — native apps use PKCE, so there is no secret to copy."
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"normalize": {
|
|
82
|
+
"kind": "native"
|
|
83
|
+
},
|
|
84
|
+
"actions": [
|
|
85
|
+
{
|
|
86
|
+
"name": "publish",
|
|
87
|
+
"consentScope": "social-publish"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"completeness": "window",
|
|
91
|
+
"notes": "Syncs three things from the linked account: the account's own posts (text with links expanded, language, reply threading, plus like/repost/reply/quote/bookmark/view counts), mentions of the account when enabled, and a once-per-day account-stats record (followers, following, total posts). Be blunt about cost: X charges for API reads on every tier, and the free tier allows roughly 100 reads per month — that fits only the default generous intervals (posts every 4 hours, stats every 6, engagement refresh daily); for real day-to-day use you need the paid Basic tier or higher. Every read scope is interval-gated (syncIntervalHours for posts and mentions, statsIntervalHours for stats) so background syncing can never burn reads faster than configured, and mentions are OFF by default to conserve reads. Engagement counts on posts from the last metricsWindowDays are re-observed roughly every metricsRefreshHours as one extra page per refresh, updating existing files in place; each observation also appends a per-day metrics-history record per post (the engagement curve over time) unless metricsHistory is turned off. The first sync captures up to 500 recent posts; older history is not backfilled. Publishing works even on the free tier — X budgets writes separately at roughly 500 posts per month — and is off by default: a gezel can only draft posts until allowPublish is enabled on the binding, and every publish is individually consented through the daemon (night shift defers publishes to the outbox for daytime approval). Accounts linked before publishing existed must reconnect from the connector's settings to grant the new tweet.write scope. Requires your own X developer app: set GEZEL_X_CLIENT_ID (and GEZEL_X_CLIENT_SECRET for confidential apps) to enable linking."
|
|
92
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "craftbook-template",
|
|
4
|
+
"id": "draft-social-post",
|
|
5
|
+
"role": "general",
|
|
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, 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
|
+
"tags": [
|
|
9
|
+
"social",
|
|
10
|
+
"post",
|
|
11
|
+
"copywriting",
|
|
12
|
+
"bluesky",
|
|
13
|
+
"twitter",
|
|
14
|
+
"instagram",
|
|
15
|
+
"linkedin",
|
|
16
|
+
"approval"
|
|
17
|
+
],
|
|
18
|
+
"maintainer": {
|
|
19
|
+
"name": "Gezel"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"yankedVersions": [],
|
|
23
|
+
"workflow": "build-loop"
|
|
24
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
"linkedin"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"description": "Target platforms. When absent, use whatever handles the project's synced corpora declare, defaulting to bluesky."
|
|
33
|
+
},
|
|
34
|
+
"campaign": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Optional campaign tag stamped into the post frontmatter."
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"connectors": [
|
|
41
|
+
{
|
|
42
|
+
"typeId": "bluesky-posts",
|
|
43
|
+
"optional": true,
|
|
44
|
+
"reason": "Ground voice research in recent posts and queue approved posts for publishing."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"typeId": "x-posts",
|
|
48
|
+
"optional": true,
|
|
49
|
+
"reason": "Ground voice research in what recently resonated on X when fitting the 280-character variant."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"typeId": "instagram-media",
|
|
53
|
+
"optional": true,
|
|
54
|
+
"reason": "Ground caption and media-brief choices in the synced Instagram record (draft-only; publishing stays manual)."
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"steps": [
|
|
58
|
+
{
|
|
59
|
+
"id": "brief",
|
|
60
|
+
"name": "Brief",
|
|
61
|
+
"description": "Resolve the topic and angle, read the voice, and write the drafting brief.",
|
|
62
|
+
"suggestedRole": "omroeper",
|
|
63
|
+
"capabilityFloor": "small",
|
|
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.",
|
|
65
|
+
"advanceWhen": {
|
|
66
|
+
"file": "posts/_drafting/brief.md",
|
|
67
|
+
"minBytes": 200
|
|
68
|
+
},
|
|
69
|
+
"gate": {
|
|
70
|
+
"at": "completion",
|
|
71
|
+
"checks": [
|
|
72
|
+
{
|
|
73
|
+
"kind": "minBytes",
|
|
74
|
+
"file": "posts/_drafting/brief.md",
|
|
75
|
+
"bytes": 200
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"maxAttempts": 3
|
|
79
|
+
},
|
|
80
|
+
"next": "draft"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "draft",
|
|
84
|
+
"name": "Draft the post and variants",
|
|
85
|
+
"description": "Write the base copy and one platform-fitted variant per target, each within its limit.",
|
|
86
|
+
"suggestedRole": "copywriter",
|
|
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.",
|
|
88
|
+
"advanceWhen": {
|
|
89
|
+
"file": "posts/_drafting/post.md",
|
|
90
|
+
"minBytes": 200,
|
|
91
|
+
"requireChange": true
|
|
92
|
+
},
|
|
93
|
+
"gate": {
|
|
94
|
+
"at": "completion",
|
|
95
|
+
"checks": [
|
|
96
|
+
{
|
|
97
|
+
"kind": "minBytes",
|
|
98
|
+
"file": "posts/_drafting/post.md",
|
|
99
|
+
"bytes": 200
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"kind": "contains",
|
|
103
|
+
"file": "posts/_drafting/post.md",
|
|
104
|
+
"pattern": "status: in-review",
|
|
105
|
+
"label": "draft carries in-review status"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"kind": "judge",
|
|
109
|
+
"file": "posts/_drafting/post.md",
|
|
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.",
|
|
111
|
+
"sourceFiles": [
|
|
112
|
+
"voice-guide.md",
|
|
113
|
+
"posts/_drafting/brief.md",
|
|
114
|
+
"posts/_drafting/variants/bluesky.md",
|
|
115
|
+
"posts/_drafting/variants/x.md",
|
|
116
|
+
"posts/_drafting/variants/instagram.md",
|
|
117
|
+
"posts/_drafting/variants/linkedin.md"
|
|
118
|
+
],
|
|
119
|
+
"label": "variants fit their platforms and the voice"
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
"onReject": "draft",
|
|
123
|
+
"maxAttempts": 4
|
|
124
|
+
},
|
|
125
|
+
"next": "review"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "review",
|
|
129
|
+
"name": "User review",
|
|
130
|
+
"description": "The human gate. Present the draft and variants; wait for an explicit answer. The scheduler stands down while the question is pending.",
|
|
131
|
+
"suggestedRole": "omroeper",
|
|
132
|
+
"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.",
|
|
133
|
+
"next": "finalize"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "finalize",
|
|
137
|
+
"name": "Commit the post",
|
|
138
|
+
"description": "Move the approved draft out of staging into posts/<created>-<slug>/ and update the index.",
|
|
139
|
+
"suggestedRole": "omroeper",
|
|
140
|
+
"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.",
|
|
141
|
+
"advanceWhen": {
|
|
142
|
+
"file": "posts/index.json",
|
|
143
|
+
"minBytes": 2,
|
|
144
|
+
"requireChange": true
|
|
145
|
+
},
|
|
146
|
+
"gate": {
|
|
147
|
+
"at": "completion",
|
|
148
|
+
"checks": [
|
|
149
|
+
{
|
|
150
|
+
"kind": "minBytes",
|
|
151
|
+
"file": "posts/index.json",
|
|
152
|
+
"bytes": 2
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
"maxAttempts": 3
|
|
156
|
+
},
|
|
157
|
+
"next": "queue-publish"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"id": "queue-publish",
|
|
161
|
+
"name": "Queue to Bluesky (only when asked)",
|
|
162
|
+
"description": "Queue the approved Bluesky variant through the outbox — skipped unless the review answer asked for it.",
|
|
163
|
+
"suggestedRole": "omroeper",
|
|
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.",
|
|
165
|
+
"next": "finish"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"id": "finish",
|
|
169
|
+
"name": "Finish",
|
|
170
|
+
"description": "Summarize the outcome and stop.",
|
|
171
|
+
"suggestedRole": "omroeper",
|
|
172
|
+
"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.",
|
|
173
|
+
"terminal": true
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"version": "1.0.0",
|
|
177
|
+
"releasedAt": "2026-08-12T00:00:00Z"
|
|
178
|
+
}
|
|
@@ -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
|
+
}
|