@sellable/mcp 0.1.256 → 0.1.258
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/dist/tools/engage-discovery.d.ts +21 -0
- package/dist/tools/engage-discovery.js +136 -9
- package/dist/tools/registry.d.ts +8 -0
- package/package.json +1 -1
- package/skills/create-post/SKILL.md +82 -23
- package/skills/create-post/references/hook-research-playbook.md +264 -8
- package/skills/create-post/references/linkedin-preview-rendering.md +24 -11
- package/skills/create-post/references/post-file-contract.md +14 -0
- package/skills/create-post/references/post-validation.md +149 -12
|
@@ -7,6 +7,7 @@ export type EngagementPost = {
|
|
|
7
7
|
name: string;
|
|
8
8
|
headline: string;
|
|
9
9
|
profileUrl: string;
|
|
10
|
+
followerCount: number | null;
|
|
10
11
|
};
|
|
11
12
|
engagement: {
|
|
12
13
|
likes: number;
|
|
@@ -14,6 +15,16 @@ export type EngagementPost = {
|
|
|
14
15
|
shares: number;
|
|
15
16
|
total: number;
|
|
16
17
|
};
|
|
18
|
+
reachSignals: {
|
|
19
|
+
targetFollowerMin: number | null;
|
|
20
|
+
targetFollowerMax: number | null;
|
|
21
|
+
followerBandFit: "in_target_band" | "below_target_band" | "above_target_band" | "unknown";
|
|
22
|
+
weightedEngagement: number;
|
|
23
|
+
engagementPer1kFollowers: number | null;
|
|
24
|
+
weightedEngagementPer1kFollowers: number | null;
|
|
25
|
+
reachPenaltyMultiplier: number;
|
|
26
|
+
reachAdjustedScore: number | null;
|
|
27
|
+
};
|
|
17
28
|
contentPreview: string;
|
|
18
29
|
};
|
|
19
30
|
export type SearchEngagementPostsInput = {
|
|
@@ -22,6 +33,8 @@ export type SearchEngagementPostsInput = {
|
|
|
22
33
|
maxAgeDays?: number;
|
|
23
34
|
minTotalEngagement?: number;
|
|
24
35
|
maxPosts?: number;
|
|
36
|
+
targetFollowerMin?: number;
|
|
37
|
+
targetFollowerMax?: number;
|
|
25
38
|
excludePostUrls?: string[];
|
|
26
39
|
};
|
|
27
40
|
export type SearchEngagementPostsResponse = {
|
|
@@ -63,6 +76,14 @@ export declare const engageDiscoveryToolDefinitions: {
|
|
|
63
76
|
type: string;
|
|
64
77
|
description: string;
|
|
65
78
|
};
|
|
79
|
+
targetFollowerMin: {
|
|
80
|
+
type: string;
|
|
81
|
+
description: string;
|
|
82
|
+
};
|
|
83
|
+
targetFollowerMax: {
|
|
84
|
+
type: string;
|
|
85
|
+
description: string;
|
|
86
|
+
};
|
|
66
87
|
excludePostUrls: {
|
|
67
88
|
type: string;
|
|
68
89
|
items: {
|
|
@@ -27,6 +27,14 @@ export const engageDiscoveryToolDefinitions = [
|
|
|
27
27
|
type: "number",
|
|
28
28
|
description: "Max posts to return after filtering (default 25).",
|
|
29
29
|
},
|
|
30
|
+
targetFollowerMin: {
|
|
31
|
+
type: "number",
|
|
32
|
+
description: "Optional lower bound for the author's follower count when comparing reach-normalized hook performance. Does not hard-filter; adds reach signals and prioritizes the target band when follower data is available.",
|
|
33
|
+
},
|
|
34
|
+
targetFollowerMax: {
|
|
35
|
+
type: "number",
|
|
36
|
+
description: "Optional upper bound for the author's follower count when comparing reach-normalized hook performance. Does not hard-filter; adds reach signals and prioritizes the target band when follower data is available.",
|
|
37
|
+
},
|
|
30
38
|
excludePostUrls: {
|
|
31
39
|
type: "array",
|
|
32
40
|
items: { type: "string" },
|
|
@@ -63,6 +71,101 @@ function safeNumber(value) {
|
|
|
63
71
|
const n = typeof value === "number" ? value : Number(value);
|
|
64
72
|
return Number.isFinite(n) ? n : 0;
|
|
65
73
|
}
|
|
74
|
+
function parseFollowerCount(value) {
|
|
75
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
76
|
+
return Math.round(value);
|
|
77
|
+
}
|
|
78
|
+
if (typeof value !== "string")
|
|
79
|
+
return null;
|
|
80
|
+
const cleaned = value.trim().toLowerCase().replace(/,/g, "");
|
|
81
|
+
if (!cleaned)
|
|
82
|
+
return null;
|
|
83
|
+
const match = cleaned.match(/(\d+(?:\.\d+)?)\s*([kmb])?/);
|
|
84
|
+
if (!match)
|
|
85
|
+
return null;
|
|
86
|
+
const base = Number(match[1]);
|
|
87
|
+
if (!Number.isFinite(base) || base <= 0)
|
|
88
|
+
return null;
|
|
89
|
+
const suffix = match[2] || "";
|
|
90
|
+
const multiplier = suffix === "b" ? 1_000_000_000 : suffix === "m" ? 1_000_000 : suffix === "k" ? 1_000 : 1;
|
|
91
|
+
return Math.round(base * multiplier);
|
|
92
|
+
}
|
|
93
|
+
function extractFollowerCount(post) {
|
|
94
|
+
const candidates = [
|
|
95
|
+
post?.author?.followerCount,
|
|
96
|
+
post?.author?.followersCount,
|
|
97
|
+
post?.author?.followers,
|
|
98
|
+
post?.author?.follower_count,
|
|
99
|
+
post?.author?.linkedinFollowers,
|
|
100
|
+
post?.author?.stats?.followers,
|
|
101
|
+
post?.followerCount,
|
|
102
|
+
post?.followersCount,
|
|
103
|
+
post?.followers,
|
|
104
|
+
post?.authorFollowerCount,
|
|
105
|
+
];
|
|
106
|
+
for (const candidate of candidates) {
|
|
107
|
+
const parsed = parseFollowerCount(candidate);
|
|
108
|
+
if (parsed !== null)
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function normalizeFollowerBound(value) {
|
|
114
|
+
const parsed = parseFollowerCount(value);
|
|
115
|
+
return parsed && parsed > 0 ? parsed : null;
|
|
116
|
+
}
|
|
117
|
+
function followerBandFit(followerCount, targetFollowerMin, targetFollowerMax) {
|
|
118
|
+
if (!followerCount)
|
|
119
|
+
return "unknown";
|
|
120
|
+
if (targetFollowerMin && followerCount < targetFollowerMin) {
|
|
121
|
+
return "below_target_band";
|
|
122
|
+
}
|
|
123
|
+
if (targetFollowerMax && followerCount > targetFollowerMax) {
|
|
124
|
+
return "above_target_band";
|
|
125
|
+
}
|
|
126
|
+
if (targetFollowerMin || targetFollowerMax)
|
|
127
|
+
return "in_target_band";
|
|
128
|
+
return "unknown";
|
|
129
|
+
}
|
|
130
|
+
function bandMultiplier(fit, followerCount, targetFollowerMax) {
|
|
131
|
+
if (fit === "in_target_band")
|
|
132
|
+
return 1;
|
|
133
|
+
if (fit === "below_target_band")
|
|
134
|
+
return 0.75;
|
|
135
|
+
if (fit === "unknown")
|
|
136
|
+
return 0.4;
|
|
137
|
+
if (!followerCount || !targetFollowerMax)
|
|
138
|
+
return 0.45;
|
|
139
|
+
if (followerCount <= targetFollowerMax * 2)
|
|
140
|
+
return 0.65;
|
|
141
|
+
if (followerCount <= targetFollowerMax * 5)
|
|
142
|
+
return 0.35;
|
|
143
|
+
return 0.15;
|
|
144
|
+
}
|
|
145
|
+
function reachSignals(engagement, followerCount, targetFollowerMin, targetFollowerMax) {
|
|
146
|
+
const weightedEngagement = engagement.likes + engagement.comments * 4 + engagement.shares * 12;
|
|
147
|
+
const fit = followerBandFit(followerCount, targetFollowerMin, targetFollowerMax);
|
|
148
|
+
const engagementPer1kFollowers = followerCount
|
|
149
|
+
? Number(((engagement.total / followerCount) * 1000).toFixed(3))
|
|
150
|
+
: null;
|
|
151
|
+
const weightedEngagementPer1kFollowers = followerCount
|
|
152
|
+
? Number(((weightedEngagement / followerCount) * 1000).toFixed(3))
|
|
153
|
+
: null;
|
|
154
|
+
const reachPenaltyMultiplier = bandMultiplier(fit, followerCount, targetFollowerMax);
|
|
155
|
+
const reachAdjustedScore = weightedEngagementPer1kFollowers === null
|
|
156
|
+
? null
|
|
157
|
+
: Number((weightedEngagementPer1kFollowers * reachPenaltyMultiplier).toFixed(3));
|
|
158
|
+
return {
|
|
159
|
+
targetFollowerMin,
|
|
160
|
+
targetFollowerMax,
|
|
161
|
+
followerBandFit: fit,
|
|
162
|
+
weightedEngagement,
|
|
163
|
+
engagementPer1kFollowers,
|
|
164
|
+
weightedEngagementPer1kFollowers,
|
|
165
|
+
reachPenaltyMultiplier,
|
|
166
|
+
reachAdjustedScore,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
66
169
|
export async function searchEngagementPosts(input) {
|
|
67
170
|
const api = getApi();
|
|
68
171
|
const keywords = (input.keywords || [])
|
|
@@ -79,6 +182,10 @@ export async function searchEngagementPosts(input) {
|
|
|
79
182
|
const maxPosts = typeof input.maxPosts === "number" && input.maxPosts > 0
|
|
80
183
|
? Math.min(50, input.maxPosts)
|
|
81
184
|
: 25;
|
|
185
|
+
const targetFollowerMin = normalizeFollowerBound(input.targetFollowerMin);
|
|
186
|
+
const targetFollowerMax = normalizeFollowerBound(input.targetFollowerMax);
|
|
187
|
+
const useTargetFollowerBand = Boolean(targetFollowerMin || targetFollowerMax);
|
|
188
|
+
const collectLimit = useTargetFollowerBand ? 50 : maxPosts;
|
|
82
189
|
const exclude = new Set((input.excludePostUrls || [])
|
|
83
190
|
.map((u) => normalizePostUrl(u))
|
|
84
191
|
.filter(Boolean));
|
|
@@ -87,11 +194,13 @@ export async function searchEngagementPosts(input) {
|
|
|
87
194
|
keywords: keywords.map((keyword) => ({ keyword })),
|
|
88
195
|
page,
|
|
89
196
|
});
|
|
90
|
-
const rawPosts = Array.isArray(response?.
|
|
91
|
-
? response.
|
|
92
|
-
: Array.isArray(response?.
|
|
93
|
-
? response.
|
|
94
|
-
:
|
|
197
|
+
const rawPosts = useTargetFollowerBand && Array.isArray(response?.posts)
|
|
198
|
+
? response.posts
|
|
199
|
+
: Array.isArray(response?.topPostsForLLM)
|
|
200
|
+
? response.topPostsForLLM
|
|
201
|
+
: Array.isArray(response?.posts)
|
|
202
|
+
? response.posts
|
|
203
|
+
: [];
|
|
95
204
|
const now = Date.now();
|
|
96
205
|
const oldestMs = now - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
97
206
|
let tooOld = 0;
|
|
@@ -111,6 +220,7 @@ export async function searchEngagementPosts(input) {
|
|
|
111
220
|
const comments = safeNumber(p?.engagement?.comments);
|
|
112
221
|
const shares = safeNumber(p?.engagement?.shares);
|
|
113
222
|
const total = likes + comments + shares;
|
|
223
|
+
const engagement = { likes, comments, shares, total };
|
|
114
224
|
if (total < minTotalEngagement) {
|
|
115
225
|
tooLowEngagement += 1;
|
|
116
226
|
continue;
|
|
@@ -121,6 +231,7 @@ export async function searchEngagementPosts(input) {
|
|
|
121
231
|
tooOld += 1;
|
|
122
232
|
continue;
|
|
123
233
|
}
|
|
234
|
+
const followerCount = extractFollowerCount(p);
|
|
124
235
|
kept.push({
|
|
125
236
|
postId: String(p?.id || ""),
|
|
126
237
|
url,
|
|
@@ -132,15 +243,31 @@ export async function searchEngagementPosts(input) {
|
|
|
132
243
|
name: String(p?.author?.name || ""),
|
|
133
244
|
headline: String(p?.author?.headline || ""),
|
|
134
245
|
profileUrl: String(p?.author?.profileUrl || ""),
|
|
246
|
+
followerCount,
|
|
135
247
|
},
|
|
136
|
-
engagement
|
|
248
|
+
engagement,
|
|
249
|
+
reachSignals: reachSignals(engagement, followerCount, targetFollowerMin, targetFollowerMax),
|
|
137
250
|
contentPreview: previewText(String(p?.content || ""), 220),
|
|
138
251
|
});
|
|
139
|
-
if (kept.length >=
|
|
252
|
+
if (kept.length >= collectLimit)
|
|
140
253
|
break;
|
|
141
254
|
}
|
|
142
|
-
|
|
143
|
-
|
|
255
|
+
if (useTargetFollowerBand) {
|
|
256
|
+
kept.sort((a, b) => {
|
|
257
|
+
const scoreDelta = (b.reachSignals.reachAdjustedScore ?? -1) -
|
|
258
|
+
(a.reachSignals.reachAdjustedScore ?? -1);
|
|
259
|
+
if (scoreDelta !== 0)
|
|
260
|
+
return scoreDelta;
|
|
261
|
+
return b.engagement.total - a.engagement.total;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
// Sort by total engagement desc for a predictable shortlist.
|
|
266
|
+
kept.sort((a, b) => b.engagement.total - a.engagement.total);
|
|
267
|
+
}
|
|
268
|
+
if (kept.length > maxPosts) {
|
|
269
|
+
kept.length = maxPosts;
|
|
270
|
+
}
|
|
144
271
|
return {
|
|
145
272
|
success: true,
|
|
146
273
|
posts: kept,
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1742,6 +1742,14 @@ export declare const allTools: ({
|
|
|
1742
1742
|
type: string;
|
|
1743
1743
|
description: string;
|
|
1744
1744
|
};
|
|
1745
|
+
targetFollowerMin: {
|
|
1746
|
+
type: string;
|
|
1747
|
+
description: string;
|
|
1748
|
+
};
|
|
1749
|
+
targetFollowerMax: {
|
|
1750
|
+
type: string;
|
|
1751
|
+
description: string;
|
|
1752
|
+
};
|
|
1745
1753
|
excludePostUrls: {
|
|
1746
1754
|
type: string;
|
|
1747
1755
|
items: {
|
package/package.json
CHANGED
|
@@ -60,8 +60,9 @@ Do:
|
|
|
60
60
|
|
|
61
61
|
- capture rough ideas, voice memos, freestyle notes, and ad hoc prompts
|
|
62
62
|
- research currently working LinkedIn hooks
|
|
63
|
+
- reverse engineer why source hooks work, what shape to keep, and what not to copy
|
|
63
64
|
- develop premise cards with real story/observed tension and reader value
|
|
64
|
-
-
|
|
65
|
+
- explore many hook angle territories before choosing the winning hook
|
|
65
66
|
- draft a post body that stays true to the source idea
|
|
66
67
|
- run validation before calling a draft ready
|
|
67
68
|
- save ideas, hook research, drafts, and published records under `~/.sellable/content/linkedin/**`
|
|
@@ -318,9 +319,13 @@ and drafting gate.
|
|
|
318
319
|
The research worker must return a compact packet only:
|
|
319
320
|
|
|
320
321
|
- source examples kept and rejected
|
|
322
|
+
- hook reverse-engineering autopsies: why each source hook works, which shape to
|
|
323
|
+
keep, what not to copy, and what payoff the body must deliver
|
|
321
324
|
- full adapted hook blocks
|
|
325
|
+
- angle territories tested before drafting
|
|
322
326
|
- market belief map: resonating ideas, implicit beliefs, audience wants, resentments, fears, and credible controversy angles
|
|
323
327
|
- premise inputs: real scenes, observed tensions, reader value openings, and proof gaps
|
|
328
|
+
- reach-normalized signal notes, including follower-band fit when available
|
|
324
329
|
- exact phrase patterns and sentence shapes
|
|
325
330
|
- body structures and exact body language moves
|
|
326
331
|
- rendered mobile and desktop preview records
|
|
@@ -334,20 +339,24 @@ the exact extracted phrase shapes.
|
|
|
334
339
|
Default flow:
|
|
335
340
|
|
|
336
341
|
1. Convert the idea into 3-8 search keywords.
|
|
337
|
-
2. Call `mcp__sellable__search_engagement_posts` with an explicit multi-month window. Default to `maxAgeDays: 120`, tightening to 30-60 days only when the topic is trend-sensitive.
|
|
338
|
-
3. Shortlist
|
|
339
|
-
4. Because search results may only include previews, call `mcp__sellable__fetch_linkedin_posts` for shortlisted authors/profile URLs and match recent posts by URL/activity ID when full text is needed.
|
|
342
|
+
2. Call `mcp__sellable__search_engagement_posts` with an explicit multi-month window. Default to `maxAgeDays: 120`, tightening to 30-60 days only when the topic is trend-sensitive. When the user gives a follower range, pass it as `targetFollowerMin` and `targetFollowerMax`; for example, "8k-20k followers" means `targetFollowerMin: 8000` and `targetFollowerMax: 20000`.
|
|
343
|
+
3. Shortlist posts by topic fit, rendered hook strength, content pattern replicability, creator repeat evidence, weighted engagement quality, and reach-normalized signal quality. Do not sort by raw engagement alone, and do not let the numeric reach score choose the final hook by itself.
|
|
344
|
+
4. Because search results may only include previews, call `mcp__sellable__fetch_linkedin_posts` for shortlisted authors/profile URLs and match recent posts by URL/activity ID when full text is needed. If a promising source is missing follower count and the user requested reach normalization, call `mcp__sellable__fetch_linkedin_profile` on a bounded shortlist before treating the source as a keeper.
|
|
340
345
|
5. If full text cannot be matched, record `full_text_unavailable` and use only the preview. Do not invent missing body details.
|
|
341
|
-
6. Weigh shares/reposts above comments, comments above reactions, and reactions as weak reach unless paired with stronger signals. If shares/reposts are unavailable, record `repost_data_unavailable`.
|
|
346
|
+
6. Weigh shares/reposts above comments, comments above reactions, and reactions as weak reach unless paired with stronger signals. Normalize engagement against follower count when available: record `authorFollowerCount`, `targetFollowerBand`, `followerBandFit`, `engagementPer1kFollowers`, `weightedEngagementPer1kFollowers`, `reachPenaltyMultiplier`, `reachAdjustedScore`, and `baselineLift` when repeated creator posts make it possible. If follower count is unavailable, record `follower_count_unavailable`; if shares/reposts are unavailable, record `repost_data_unavailable`.
|
|
342
347
|
7. Penalize lead-magnet or giveaway mechanics unless the user explicitly asks for a lead magnet post.
|
|
343
348
|
8. Build a market belief map before hook generation: what the space is rewarding, what the audience implicitly believes, what they want permission to say, what they resent or fear, what they will argue with, and which controversial angle the user can credibly own. If the user's raw idea is internally coherent but not attached to a live market tension, do not draft from the internal idea alone; present stronger directions or rewrite the draft angle around the external tension.
|
|
344
349
|
9. Extract premise inputs: real story/scene possibilities, observed tensions, useful reader takeaways, and proof gaps. A good hook cannot rescue a premise with no value.
|
|
345
350
|
10. For story posts, extract the story mechanism that made the post work, not just the first line.
|
|
346
351
|
11. Extract hook structures plus specific reusable words, phrases, sentence
|
|
347
352
|
shapes, transitions, and body language patterns.
|
|
348
|
-
12.
|
|
353
|
+
12. For each keeper, create a `hookAutopsy`: the source mechanism, open-loop
|
|
354
|
+
type, click question, promised payoff, specificity anchor, tension opened,
|
|
355
|
+
source shape to keep, source words not to copy, and adaptation rule for the
|
|
356
|
+
user's idea.
|
|
357
|
+
13. Render each kept source hook and adapted hook block through the LinkedIn
|
|
349
358
|
preview rendering contract. Character counts alone are not enough.
|
|
350
|
-
|
|
359
|
+
14. Save the research with `mcp__sellable__save_hook_research`.
|
|
351
360
|
|
|
352
361
|
Record provenance:
|
|
353
362
|
|
|
@@ -356,7 +365,9 @@ Record provenance:
|
|
|
356
365
|
- search window
|
|
357
366
|
- source post URLs
|
|
358
367
|
- authors/profile URLs
|
|
368
|
+
- author follower counts and target follower-band fit when available
|
|
359
369
|
- engagement totals and available likes/comments/shares breakdown
|
|
370
|
+
- reach-normalized scoring fields and confidence notes
|
|
360
371
|
- creator repeat evidence
|
|
361
372
|
- lead-magnet or engagement-bait penalties
|
|
362
373
|
- story mechanism when relevant
|
|
@@ -364,11 +375,19 @@ Record provenance:
|
|
|
364
375
|
- source hook rendered preview records and whether they came from full text,
|
|
365
376
|
authenticated LinkedIn screenshots, or a search preview
|
|
366
377
|
- selected hook patterns
|
|
378
|
+
- hook reverse-engineering autopsies, including `clickQuestion`,
|
|
379
|
+
`promisedPayoff`, `sourceShapeToKeep`, `sourceWordsNotToCopy`, and
|
|
380
|
+
`adaptationRule`
|
|
367
381
|
- market belief map and selected controversy
|
|
368
382
|
- premise cards and selected premise
|
|
369
383
|
- exact phrase patterns and sentence shapes
|
|
370
384
|
- body structures and body language patterns
|
|
371
385
|
- why each pattern fits the user's idea and voice
|
|
386
|
+
- `whyTheHookCarries`: why the selected hook works from the words, tension, and
|
|
387
|
+
content pattern independent of the source creator's reach
|
|
388
|
+
- `whyTheReachEvidenceIsTrustworthy`: follower-band fit, reach-adjusted score,
|
|
389
|
+
baseline lift, share/comment quality, or why a large-account example is only
|
|
390
|
+
secondary pattern evidence
|
|
372
391
|
|
|
373
392
|
## Step 1.5: Research Learning Report
|
|
374
393
|
|
|
@@ -394,6 +413,9 @@ Research status:
|
|
|
394
413
|
- keywords:
|
|
395
414
|
- full-text coverage:
|
|
396
415
|
- repost/share data:
|
|
416
|
+
- target follower band:
|
|
417
|
+
- reach-normalized winners:
|
|
418
|
+
- big-account examples used only as secondary pattern evidence:
|
|
397
419
|
|
|
398
420
|
Best source examples:
|
|
399
421
|
1. author, URL, engagement, why kept, why not copied
|
|
@@ -414,6 +436,11 @@ Premise cards:
|
|
|
414
436
|
Hook patterns learned:
|
|
415
437
|
1. full adapted hook block
|
|
416
438
|
- source mechanism:
|
|
439
|
+
- open-loop type:
|
|
440
|
+
- click question:
|
|
441
|
+
- promised payoff:
|
|
442
|
+
- source shape to keep:
|
|
443
|
+
- source words not to copy:
|
|
417
444
|
- rendered preview:
|
|
418
445
|
- mobile visible block:
|
|
419
446
|
- desktop visible block:
|
|
@@ -445,9 +472,9 @@ Save recommendations:
|
|
|
445
472
|
- gold-standard candidates:
|
|
446
473
|
|
|
447
474
|
Recommended draft directions:
|
|
448
|
-
1. premise card + hook block + body structure
|
|
449
|
-
2. premise card + hook block + body structure
|
|
450
|
-
3. premise card + hook block + body structure
|
|
475
|
+
1. premise card + hook territory + hook block + click question + body structure
|
|
476
|
+
2. premise card + hook territory + hook block + click question + body structure
|
|
477
|
+
3. premise card + hook territory + hook block + click question + body structure
|
|
451
478
|
```
|
|
452
479
|
|
|
453
480
|
Keep this report concise enough to read, but concrete enough that another agent
|
|
@@ -481,16 +508,32 @@ unless the premise still has concrete reader value.
|
|
|
481
508
|
|
|
482
509
|
## Step 2: Hook Candidates
|
|
483
510
|
|
|
484
|
-
Generate
|
|
485
|
-
|
|
486
|
-
|
|
511
|
+
Generate a hook angle matrix from the selected premise before writing the post
|
|
512
|
+
body. Do not generate hooks directly from the raw idea before the premise is
|
|
513
|
+
selected.
|
|
514
|
+
|
|
515
|
+
Minimum exploration:
|
|
516
|
+
|
|
517
|
+
- normal drafting: at least 8 distinct angle territories and 24 hook candidates
|
|
518
|
+
- hook-critical requests, "nail the hook," "a ton of angles," or space research:
|
|
519
|
+
at least 12 distinct angle territories and 40 hook candidates
|
|
520
|
+
- smaller set only when the user explicitly asks for fewer options
|
|
521
|
+
|
|
522
|
+
Angle territories must be genuinely different. Use options such as enemy/tool
|
|
523
|
+
contrast, hidden asset, first-person build proof, workflow reveal, contrarian
|
|
524
|
+
category claim, mistake/confession, market timing, operator pain,
|
|
525
|
+
asset/free-reveal without comment bait, specific person/company signal,
|
|
526
|
+
before/after transformation, and question hook.
|
|
487
527
|
|
|
488
528
|
Each hook must include:
|
|
489
529
|
|
|
490
530
|
- selected premise
|
|
531
|
+
- angle territory
|
|
491
532
|
- premise tension opened
|
|
492
533
|
- reader value implied
|
|
493
534
|
- source hook pattern
|
|
535
|
+
- source shape kept
|
|
536
|
+
- source words not copied
|
|
494
537
|
- why it fits this idea
|
|
495
538
|
- `renderedPreview` using `references/linkedin-preview-rendering.md`
|
|
496
539
|
- `mobileRenderedPreviewBlock`
|
|
@@ -498,6 +541,12 @@ Each hook must include:
|
|
|
498
541
|
- `mobileRenderedLines`
|
|
499
542
|
- `desktopRenderedLines`
|
|
500
543
|
- `firstScreenPromise`
|
|
544
|
+
- `intentionalOpenLoop`
|
|
545
|
+
- `specificClickQuestionVisible`
|
|
546
|
+
- `seeMoreClickReason`
|
|
547
|
+
- `clickQuestion`
|
|
548
|
+
- `payoffAfterFold`
|
|
549
|
+
- `bodyProofObligation`
|
|
501
550
|
- `corePainProofOrCuriosityVisibleMobile`
|
|
502
551
|
- `corePointVisibleMobile`
|
|
503
552
|
- `pointAfterMobileClamp`
|
|
@@ -517,6 +566,8 @@ Each hook must include:
|
|
|
517
566
|
- `rewriteIfTruncated`
|
|
518
567
|
- proof/story dependency
|
|
519
568
|
- AI-tell risk
|
|
569
|
+
- why it should win
|
|
570
|
+
- why it should lose
|
|
520
571
|
- score
|
|
521
572
|
|
|
522
573
|
Do not copy source wording. Copy only the structure.
|
|
@@ -537,23 +588,28 @@ available:
|
|
|
537
588
|
Use the rendered gates from `references/linkedin-preview-rendering.md`:
|
|
538
589
|
|
|
539
590
|
- `pass`: the mobile rendered preview shows the pain, proof, or curiosity by
|
|
540
|
-
the end of the first 3 rendered lines, and the core point is
|
|
541
|
-
without opening "see more"
|
|
591
|
+
the end of the first 3 rendered lines, and either the core point is
|
|
592
|
+
understandable without opening "see more" or an intentional open loop creates
|
|
593
|
+
a specific click question with an immediate planned payoff.
|
|
542
594
|
- `warn`: the mobile rendered preview creates useful curiosity but the core
|
|
543
|
-
point is slightly softened by wrapping, blank-line rhythm,
|
|
544
|
-
context word. A compact fallback is required.
|
|
595
|
+
point or click question is slightly softened by wrapping, blank-line rhythm,
|
|
596
|
+
or one missing context word. A compact fallback is required.
|
|
545
597
|
- `fail`: the hook's real point appears after the first 3 mobile rendered
|
|
546
|
-
lines
|
|
547
|
-
|
|
548
|
-
looks
|
|
598
|
+
lines without an intentional open-loop plan, the visible open loop is vague,
|
|
599
|
+
the first rendered line is generic setup, blank lines consume the preview
|
|
600
|
+
before the reader sees the point, or desktop fit is the only reason it looks
|
|
601
|
+
good.
|
|
549
602
|
|
|
550
603
|
Desktop preview usually has more room. Still record `desktopPreviewFit`, but
|
|
551
604
|
never let desktop fit compensate for a mobile `fail`.
|
|
552
605
|
|
|
553
606
|
If a hook's point depends on text after the rendered mobile preview clamp,
|
|
554
|
-
rewrite it before selecting it
|
|
555
|
-
|
|
556
|
-
|
|
607
|
+
rewrite it before selecting it unless the hook is intentionally using a
|
|
608
|
+
see-more open loop. Intentional open loops must show a specific click question
|
|
609
|
+
inside the mobile clamp and must define the first payoff line after the fold. A
|
|
610
|
+
selected hook may carry a `warn` only when the warning is explicit and the
|
|
611
|
+
validation receipt includes a compact fallback. A hook with no rendered mobile
|
|
612
|
+
and desktop preview record cannot be selected.
|
|
557
613
|
|
|
558
614
|
## Step 3: Draft
|
|
559
615
|
|
|
@@ -583,6 +639,9 @@ Every saved draft needs a validation receipt with:
|
|
|
583
639
|
- selected premise card
|
|
584
640
|
- candidate hooks considered
|
|
585
641
|
- selected hook and why
|
|
642
|
+
- hook reverse-engineering audit
|
|
643
|
+
- hook angle matrix summary
|
|
644
|
+
- see-more click audit
|
|
586
645
|
- proof claims used and source
|
|
587
646
|
- story/proof files consulted
|
|
588
647
|
- gold standards consulted
|
|
@@ -28,6 +28,7 @@ Worker owns:
|
|
|
28
28
|
- tracked-person post fetches
|
|
29
29
|
- full-text matching by URL/activity ID
|
|
30
30
|
- duplicate removal
|
|
31
|
+
- follower-band and reach-normalized signal scoring
|
|
31
32
|
- lead-magnet, giveaway, engagement-bait, and off-voice filtering
|
|
32
33
|
- market belief mapping across kept and rejected examples
|
|
33
34
|
- premise input extraction: real scenes, observed tensions, reader value, proof gaps
|
|
@@ -54,10 +55,13 @@ Research packet:
|
|
|
54
55
|
- market belief map: max 8 bullets
|
|
55
56
|
- controversy candidates: max 8
|
|
56
57
|
- premise inputs: max 8
|
|
58
|
+
- hook reverse-engineering autopsies: max 12
|
|
57
59
|
- full adapted hook blocks: max 12
|
|
60
|
+
- angle territories tested: max 12
|
|
58
61
|
- exact phrase patterns: max 20
|
|
59
62
|
- body patterns: max 8
|
|
60
63
|
- source URLs and author profile URLs
|
|
64
|
+
- reach-normalized signal notes
|
|
61
65
|
- rendered preview records
|
|
62
66
|
- confidence gaps
|
|
63
67
|
- save recommendations
|
|
@@ -74,6 +78,13 @@ Use `mcp__sellable__search_engagement_posts` with practical constraints:
|
|
|
74
78
|
|
|
75
79
|
- explicit `maxAgeDays: 120` by default so research covers the past 30-120 days, not only this week's posts
|
|
76
80
|
- tighten to 30-60 days for trend-sensitive topics; widen only when the topic has low volume
|
|
81
|
+
- when the user gives a follower range, pass it as `targetFollowerMin` and
|
|
82
|
+
`targetFollowerMax` so the tool can expose reach-normalized signals and
|
|
83
|
+
prioritize comparable creators
|
|
84
|
+
- if a source hook looks promising but follower count is missing, use
|
|
85
|
+
`mcp__sellable__fetch_linkedin_profile` on a bounded shortlist before calling
|
|
86
|
+
the source a keeper; if profile fetch is unavailable or too slow, mark
|
|
87
|
+
`follower_count_unavailable`
|
|
77
88
|
- high enough engagement to matter
|
|
78
89
|
- narrow enough to match the idea
|
|
79
90
|
- enough result depth to see whether a creator has repeated winners, not just one viral outlier
|
|
@@ -82,14 +93,19 @@ Record every keyword, filter, search window, page, and result count.
|
|
|
82
93
|
|
|
83
94
|
## Weighted Signals
|
|
84
95
|
|
|
85
|
-
Rank source posts with a weighted signal view. Do not sort by total engagement
|
|
96
|
+
Rank source posts with a weighted signal view. Do not sort by total engagement
|
|
97
|
+
alone. Use the numeric score to decide which sources deserve study; do not let
|
|
98
|
+
the number choose the final hook by itself.
|
|
86
99
|
|
|
87
100
|
Use this scoring shape:
|
|
88
101
|
|
|
89
102
|
```text
|
|
90
103
|
hook_score =
|
|
91
104
|
topic_fit
|
|
105
|
+
+ rendered_mobile_preview_strength
|
|
92
106
|
+ hook_clarity
|
|
107
|
+
+ content_pattern_replicability
|
|
108
|
+
+ reach_adjusted_evidence
|
|
93
109
|
+ creator_repeat_success
|
|
94
110
|
+ repost_share_strength
|
|
95
111
|
+ comment_quality
|
|
@@ -107,6 +123,135 @@ Guidance:
|
|
|
107
123
|
- If share or repost data is unavailable, record `repost_data_unavailable` instead of inventing it.
|
|
108
124
|
- Prefer creators with repeated high-performing posts in the same lane over one-off viral posts.
|
|
109
125
|
|
|
126
|
+
## Reach-Normalized Hook Scoring
|
|
127
|
+
|
|
128
|
+
Raw engagement is not the clearest signal when source creators have wildly
|
|
129
|
+
different audience sizes. A post from a 100k+ follower creator can win on
|
|
130
|
+
distribution even when the hook is ordinary. A post from a creator near the
|
|
131
|
+
user's follower range that overperforms is a clearer signal that LinkedIn and
|
|
132
|
+
readers rewarded the hook/premise.
|
|
133
|
+
|
|
134
|
+
This calculation is a source-quality filter, not the final creative decision.
|
|
135
|
+
After reach is controlled, the LLM still chooses the hooks to steal based on the
|
|
136
|
+
visible hook, rendered first-screen promise, content pattern, premise fit, and
|
|
137
|
+
whether the idea can carry without the source creator's distribution.
|
|
138
|
+
|
|
139
|
+
When the user gives a target range, default to that range. For example, if the
|
|
140
|
+
user says "8k to 20k followers," call `mcp__sellable__search_engagement_posts`
|
|
141
|
+
with:
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
targetFollowerMin: 8000
|
|
145
|
+
targetFollowerMax: 20000
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
If the user gives no range, use the user's known follower count from memory or
|
|
149
|
+
ask only when the decision depends on it. If no follower count is available, use
|
|
150
|
+
raw engagement as a fallback and mark `target_follower_band_unknown`.
|
|
151
|
+
|
|
152
|
+
For every shortlisted source post, record:
|
|
153
|
+
|
|
154
|
+
- `authorFollowerCount`
|
|
155
|
+
- `targetFollowerBand`
|
|
156
|
+
- `followerBandFit`: `in_target_band`, `below_target_band`,
|
|
157
|
+
`above_target_band`, or `unknown`
|
|
158
|
+
- `engagementPer1kFollowers`
|
|
159
|
+
- `weightedEngagementPer1kFollowers`
|
|
160
|
+
- `reachAdjustedScore`
|
|
161
|
+
- `creatorBaselineMedianEngagement` when repeated posts from that creator are
|
|
162
|
+
available
|
|
163
|
+
- `baselineLift`: this post's engagement divided by the creator's recent median
|
|
164
|
+
engagement in the same lane, or `creator_baseline_unavailable`
|
|
165
|
+
- `confidence`: `high`, `medium`, or `low`
|
|
166
|
+
- `normalizationNotes`
|
|
167
|
+
|
|
168
|
+
Use this Harvest-compatible calculation when follower counts are available:
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
weightedEngagement =
|
|
172
|
+
likes
|
|
173
|
+
+ (comments * 4)
|
|
174
|
+
+ (shares * 12)
|
|
175
|
+
|
|
176
|
+
engagementPer1kFollowers =
|
|
177
|
+
totalEngagement / authorFollowerCount * 1000
|
|
178
|
+
|
|
179
|
+
weightedEngagementPer1kFollowers =
|
|
180
|
+
weightedEngagement / authorFollowerCount * 1000
|
|
181
|
+
|
|
182
|
+
reachPenaltyMultiplier =
|
|
183
|
+
1.00 when in target follower band
|
|
184
|
+
0.75 when below target band
|
|
185
|
+
0.65 when above target band but <= 2x target max
|
|
186
|
+
0.35 when above target band but <= 5x target max
|
|
187
|
+
0.15 when above target band and > 5x target max
|
|
188
|
+
0.40 when follower count is unknown
|
|
189
|
+
|
|
190
|
+
reachAdjustedScore =
|
|
191
|
+
weightedEngagementPer1kFollowers * reachPenaltyMultiplier
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
When repeated posts from the same creator are available, calculate:
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
creatorBaselineMedianEngagement =
|
|
198
|
+
median weightedEngagement across recent same-lane posts
|
|
199
|
+
|
|
200
|
+
baselineLift =
|
|
201
|
+
post weightedEngagement / creatorBaselineMedianEngagement
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Use this scoring shape when deciding which hooks to study:
|
|
205
|
+
|
|
206
|
+
```text
|
|
207
|
+
hook_score =
|
|
208
|
+
topic_fit
|
|
209
|
+
+ rendered_mobile_preview_strength
|
|
210
|
+
+ hook_clarity
|
|
211
|
+
+ same_follower_band_bonus
|
|
212
|
+
+ weighted_engagement_per_1k_followers
|
|
213
|
+
+ creator_baseline_lift
|
|
214
|
+
+ repost_share_strength
|
|
215
|
+
+ substantive_comment_quality
|
|
216
|
+
+ creator_repeat_success
|
|
217
|
+
- celebrity_reach_penalty
|
|
218
|
+
- tiny_sample_penalty
|
|
219
|
+
- lead_magnet_penalty
|
|
220
|
+
- engagement_bait_penalty
|
|
221
|
+
- off_voice_penalty
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Rules:
|
|
225
|
+
|
|
226
|
+
- Prefer in-band creators when the hook is strong and the engagement is real.
|
|
227
|
+
- Use near-band creators as secondary evidence.
|
|
228
|
+
- Penalize creators far above the target range, especially accounts above 5x
|
|
229
|
+
the target max or above 100k followers when the target range is 8k-20k.
|
|
230
|
+
- Do not automatically reject large-account posts; they can still teach body
|
|
231
|
+
structure, topic heat, or category language. Do not treat them as primary hook
|
|
232
|
+
proof unless they also overperform their creator baseline.
|
|
233
|
+
- Large-account hooks can be selected only when the hook carries without the
|
|
234
|
+
creator: the rendered mobile opening is strong, the premise is reusable by the
|
|
235
|
+
user, the body pattern does not depend on celebrity authority, and the source
|
|
236
|
+
either beats baseline or has unusually high reach-adjusted quality after the
|
|
237
|
+
penalty.
|
|
238
|
+
- Do not overvalue tiny-account anomalies. Very high engagement per follower
|
|
239
|
+
with low absolute engagement is interesting but lower confidence.
|
|
240
|
+
- When follower data is unavailable, do not invent it. Mark
|
|
241
|
+
`follower_count_unavailable`, lower confidence, and rely more on creator repeat
|
|
242
|
+
evidence, repost/share strength, comment quality, and rendered preview quality.
|
|
243
|
+
- A source hook is strongest when it is in/near the target follower band, has
|
|
244
|
+
real absolute engagement, beats the creator's apparent baseline, has
|
|
245
|
+
share/comment quality, and passes rendered mobile preview.
|
|
246
|
+
|
|
247
|
+
Final hook selection must explain both:
|
|
248
|
+
|
|
249
|
+
- `whyTheHookCarries`: the visible words, specificity, tension, and first-screen
|
|
250
|
+
promise that work independent of reach
|
|
251
|
+
- `whyTheReachEvidenceIsTrustworthy`: follower-band fit, reach-adjusted score,
|
|
252
|
+
baseline lift, share/comment quality, or why a large-account example is only
|
|
253
|
+
secondary pattern evidence
|
|
254
|
+
|
|
110
255
|
Penalize lead-magnet and engagement-bait mechanics unless the user explicitly asks for that style:
|
|
111
256
|
|
|
112
257
|
- `comment "template"` / `comment "guide"` / `comment "playbook"`
|
|
@@ -118,6 +263,104 @@ Penalize lead-magnet and engagement-bait mechanics unless the user explicitly as
|
|
|
118
263
|
- "like and comment"
|
|
119
264
|
- broad giveaway framing that makes engagement inflate without proving the hook/body worked
|
|
120
265
|
|
|
266
|
+
## Hook Reverse Engineering Autopsy
|
|
267
|
+
|
|
268
|
+
For any space or LinkedIn topic, do not stop at "this is a good hook." Reverse
|
|
269
|
+
engineer why the opening earned attention, which shape can be reused, and which
|
|
270
|
+
parts belong to the source and must not be copied.
|
|
271
|
+
|
|
272
|
+
For each keeper source hook, record a `hookAutopsy`:
|
|
273
|
+
|
|
274
|
+
```text
|
|
275
|
+
hookAutopsy:
|
|
276
|
+
sourceHook: <exact visible opening used for analysis>
|
|
277
|
+
renderedFirstScreen: <mobile and desktop visible blocks>
|
|
278
|
+
sourceMechanism: <what the hook does structurally>
|
|
279
|
+
openLoopType: none | hidden_payoff | contradiction | proof_gap | workflow_reveal | asset_reveal | story_gap
|
|
280
|
+
clickQuestion: <the question a reader has before clicking see more>
|
|
281
|
+
promisedPayoff: <what the body/video must deliver after the click>
|
|
282
|
+
curiosityGap: <what is withheld and why that is fair>
|
|
283
|
+
specificityAnchor: <tool, person, number, object, scene, or enemy named>
|
|
284
|
+
tensionOpened: <belief, resentment, fear, status, cost, or contradiction>
|
|
285
|
+
proofImplied: <what proof the source suggests in the first screen>
|
|
286
|
+
whyItWorks: <specific words and structure, not a vibe label>
|
|
287
|
+
whyItMightBeInflated: <reach, giveaway, celebrity, outrage, or topic heat>
|
|
288
|
+
sourceShapeToKeep: <portable structure, written as a template>
|
|
289
|
+
sourceWordsNotToCopy: <phrases, jokes, proof, or context owned by source>
|
|
290
|
+
adaptationRule: <how to make it true for the user's idea and voice>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Good `sourceShapeToKeep` examples:
|
|
294
|
+
|
|
295
|
+
- "The best [desired asset] I have right now is not in [default tool]."
|
|
296
|
+
- "I gave [system/person] one prompt: [specific request]."
|
|
297
|
+
- "[Common vanity metric] is useless until it becomes [business outcome]."
|
|
298
|
+
- "I built [familiar workflow] inside [new interface]."
|
|
299
|
+
- "Most teams [surface behavior]. Then they miss [hidden opportunity]."
|
|
300
|
+
|
|
301
|
+
Bad autopsies:
|
|
302
|
+
|
|
303
|
+
- "contrarian hook"
|
|
304
|
+
- "strong curiosity"
|
|
305
|
+
- "good storytelling"
|
|
306
|
+
- "viral format"
|
|
307
|
+
|
|
308
|
+
Those labels are allowed only after the concrete shape, click question, and
|
|
309
|
+
payoff obligation are recorded.
|
|
310
|
+
|
|
311
|
+
## Angle Exploration Before Drafting
|
|
312
|
+
|
|
313
|
+
After selecting a premise, create a hook angle matrix before drafting the post.
|
|
314
|
+
This is mandatory when the user asks to nail the hook, study hooks, explore a
|
|
315
|
+
space, or write from a specific idea.
|
|
316
|
+
|
|
317
|
+
Default matrix:
|
|
318
|
+
|
|
319
|
+
- at least 8 angle territories for normal drafting
|
|
320
|
+
- at least 12 angle territories when the user asks for "a ton of angles" or
|
|
321
|
+
says the hook is the main constraint
|
|
322
|
+
- at least 24 generated hooks across those territories for normal drafting
|
|
323
|
+
- at least 40 generated hooks when the user explicitly asks to optimize hooks
|
|
324
|
+
|
|
325
|
+
Angle territories should be materially different, not synonyms. Use territories
|
|
326
|
+
such as:
|
|
327
|
+
|
|
328
|
+
- enemy/tool contrast
|
|
329
|
+
- hidden asset
|
|
330
|
+
- first-person build proof
|
|
331
|
+
- workflow reveal
|
|
332
|
+
- contrarian category claim
|
|
333
|
+
- mistake/confession
|
|
334
|
+
- market timing
|
|
335
|
+
- customer/operator pain
|
|
336
|
+
- asset/free reveal without comment bait
|
|
337
|
+
- specific person/company signal
|
|
338
|
+
- before/after transformation
|
|
339
|
+
- question hook
|
|
340
|
+
|
|
341
|
+
For each territory, record:
|
|
342
|
+
|
|
343
|
+
```text
|
|
344
|
+
angleTerritory:
|
|
345
|
+
name:
|
|
346
|
+
sourceShapesUsed:
|
|
347
|
+
premiseTensionOpened:
|
|
348
|
+
readerValueImplied:
|
|
349
|
+
bestHook:
|
|
350
|
+
mobileRenderedPreview:
|
|
351
|
+
desktopRenderedPreview:
|
|
352
|
+
clickQuestion:
|
|
353
|
+
payoffAfterFold:
|
|
354
|
+
whyThisAngleShouldWin:
|
|
355
|
+
whyThisAngleShouldLose:
|
|
356
|
+
proofNeeded:
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Do not write the post body until the winning hook territory is chosen. The post
|
|
360
|
+
body must then pay off the selected hook quickly. If the best hook says the list
|
|
361
|
+
is not in Apollo, the next lines must reveal where it is. If the hook says "I
|
|
362
|
+
gave Sellable one prompt," the next lines must show the prompt or the demo.
|
|
363
|
+
|
|
121
364
|
## Market Belief Map
|
|
122
365
|
|
|
123
366
|
Before selecting a hook or writing a draft, synthesize a market belief map from
|
|
@@ -239,6 +482,12 @@ For each source, record:
|
|
|
239
482
|
- `corePainProofOrCuriosityVisibleDesktop`
|
|
240
483
|
- `corePointVisibleMobile`
|
|
241
484
|
- `corePointVisibleDesktop`
|
|
485
|
+
- `intentionalOpenLoop`
|
|
486
|
+
- `specificClickQuestionVisible`
|
|
487
|
+
- `payoffPlannedImmediatelyAfterClamp`
|
|
488
|
+
- `seeMoreClickReason`
|
|
489
|
+
- `clickQuestion`
|
|
490
|
+
- `payoffAfterFold`
|
|
242
491
|
- `pointAfterMobileClamp`
|
|
243
492
|
- `charCountIncludingNewlines`
|
|
244
493
|
- `physicalLineCount`
|
|
@@ -252,6 +501,7 @@ For each source, record:
|
|
|
252
501
|
- `desktopPreviewBudget`: `pass`, `warn`, or `fail`
|
|
253
502
|
- `blankLineVisualRisk`
|
|
254
503
|
- `corePointBeforeLikelyTruncation`
|
|
504
|
+
- `openLoopType`
|
|
255
505
|
- `renderedPreviewVerdict`: `pass`, `warn`, or `fail`
|
|
256
506
|
|
|
257
507
|
If only a search preview is available, do not pretend the opening is complete.
|
|
@@ -261,15 +511,17 @@ appears cut off or body context is unavailable.
|
|
|
261
511
|
Pass/warn/fail is based on rendered output:
|
|
262
512
|
|
|
263
513
|
- `pass`: the mobile rendered preview shows the pain, proof, or curiosity by
|
|
264
|
-
the end of the first 3 rendered lines, and the core point is
|
|
265
|
-
without opening "see more"
|
|
514
|
+
the end of the first 3 rendered lines, and either the core point is
|
|
515
|
+
understandable without opening "see more" or an intentional open loop creates
|
|
516
|
+
a specific click question with an immediate planned payoff.
|
|
266
517
|
- `warn`: the mobile rendered preview creates useful curiosity but the core
|
|
267
|
-
point is slightly softened by wrapping, blank-line rhythm,
|
|
268
|
-
context word. A compact fallback is required.
|
|
518
|
+
point or click question is slightly softened by wrapping, blank-line rhythm,
|
|
519
|
+
or one missing context word. A compact fallback is required.
|
|
269
520
|
- `fail`: the hook's real point appears after the first 3 mobile rendered
|
|
270
|
-
lines
|
|
271
|
-
|
|
272
|
-
looks
|
|
521
|
+
lines without an intentional open-loop plan, the visible open loop is vague,
|
|
522
|
+
the first rendered line is generic setup, blank lines consume the preview
|
|
523
|
+
before the reader sees the point, or desktop fit is the only reason it looks
|
|
524
|
+
good.
|
|
273
525
|
|
|
274
526
|
## Hook Extraction
|
|
275
527
|
|
|
@@ -283,7 +535,10 @@ For each shortlisted source post, record:
|
|
|
283
535
|
- URL
|
|
284
536
|
- author
|
|
285
537
|
- author profile URL when available
|
|
538
|
+
- author follower count and follower-band fit when available
|
|
286
539
|
- engagement totals and available likes/comments/shares breakdown
|
|
540
|
+
- reach-normalized metrics: engagement per 1k followers, weighted engagement
|
|
541
|
+
per 1k followers, reach-adjusted score, baseline lift, and confidence
|
|
287
542
|
- creator repeat evidence
|
|
288
543
|
- visible hook text or preview
|
|
289
544
|
- rendered preview fields from the section above
|
|
@@ -296,6 +551,7 @@ For each shortlisted source post, record:
|
|
|
296
551
|
- proof/story dependency
|
|
297
552
|
- lead magnet or engagement bait penalty
|
|
298
553
|
- weighted signal notes
|
|
554
|
+
- reach-normalized signal notes
|
|
299
555
|
- replicability score
|
|
300
556
|
- track person recommendation: `yes`, `no`, or `ask_user`
|
|
301
557
|
- tracking reason when recommended
|
|
@@ -63,6 +63,10 @@ renderedPreview:
|
|
|
63
63
|
blankLinesBeforeClamp: <number>
|
|
64
64
|
corePainProofOrCuriosityVisible: true | false
|
|
65
65
|
corePointVisible: true | false
|
|
66
|
+
intentionalOpenLoop: true | false
|
|
67
|
+
specificClickQuestionVisible: true | false
|
|
68
|
+
payoffPlannedImmediatelyAfterClamp: true | false
|
|
69
|
+
seeMoreClickReason: <why a reader would click see more>
|
|
66
70
|
seeMoreRisk: pass | warn | fail
|
|
67
71
|
screenshotPath: <optional local path>
|
|
68
72
|
desktop:
|
|
@@ -88,6 +92,7 @@ renderedPreview:
|
|
|
88
92
|
longestNonblankLineChars: <number>
|
|
89
93
|
blankLineVisualRisk: none | low | medium | high
|
|
90
94
|
pointAfterMobileClamp: true | false
|
|
95
|
+
openLoopType: none | hidden_payoff | contradiction | proof_gap | workflow_reveal | asset_reveal | story_gap
|
|
91
96
|
rewriteIfTruncated: <short fallback>
|
|
92
97
|
```
|
|
93
98
|
|
|
@@ -112,31 +117,39 @@ For generated hooks:
|
|
|
112
117
|
|
|
113
118
|
- Generate the hook from the selected premise first.
|
|
114
119
|
- Render the hook for mobile and desktop before scoring it.
|
|
115
|
-
- Score the rendered first-screen
|
|
116
|
-
-
|
|
120
|
+
- Score the rendered first-screen click reason before scoring cleverness.
|
|
121
|
+
- If the goal is see-more clicks, the hook may intentionally hide the payoff
|
|
122
|
+
after the mobile clamp, but the visible block must create a specific question
|
|
123
|
+
the target reader wants answered.
|
|
124
|
+
- Rewrite any candidate whose real point appears after the mobile clamp unless
|
|
125
|
+
that point is the planned payoff for an intentional open loop.
|
|
117
126
|
|
|
118
127
|
## Pass, Warn, Fail
|
|
119
128
|
|
|
120
129
|
Use these rendered gates:
|
|
121
130
|
|
|
122
131
|
- `pass`: the mobile rendered preview shows the pain, proof, or curiosity by the
|
|
123
|
-
end of the first 3 rendered lines, and the core point is
|
|
124
|
-
without opening "see more"
|
|
132
|
+
end of the first 3 rendered lines, and either the core point is
|
|
133
|
+
understandable without opening "see more" or an intentional open loop creates
|
|
134
|
+
a specific click question with an immediate planned payoff.
|
|
125
135
|
- `warn`: the mobile rendered preview creates useful curiosity but the core
|
|
126
|
-
point is slightly softened by wrapping, blank-line rhythm,
|
|
127
|
-
context word. A compact fallback is required.
|
|
136
|
+
point or click question is slightly softened by wrapping, blank-line rhythm,
|
|
137
|
+
or one missing context word. A compact fallback is required.
|
|
128
138
|
- `fail`: the hook's real point appears after the first 3 mobile rendered lines,
|
|
129
|
-
the first rendered line is generic setup,
|
|
130
|
-
before the reader sees the point, or desktop
|
|
131
|
-
good.
|
|
139
|
+
the visible open loop is vague, the first rendered line is generic setup,
|
|
140
|
+
blank lines consume the preview before the reader sees the point, or desktop
|
|
141
|
+
fit is the only reason it looks good.
|
|
132
142
|
|
|
133
143
|
A draft cannot be `ready` when the selected hook has:
|
|
134
144
|
|
|
135
145
|
- no `renderedPreview`
|
|
136
146
|
- `mobile.seeMoreRisk: fail`
|
|
137
147
|
- `mobile.corePainProofOrCuriosityVisible: false`
|
|
138
|
-
- `mobile.corePointVisible: false`
|
|
139
|
-
|
|
148
|
+
- `mobile.corePointVisible: false` unless `mobile.intentionalOpenLoop: true`,
|
|
149
|
+
`mobile.specificClickQuestionVisible: true`, and
|
|
150
|
+
`mobile.payoffPlannedImmediatelyAfterClamp: true`
|
|
151
|
+
- `pointAfterMobileClamp: true` unless the point after the clamp is the
|
|
152
|
+
intentional payoff for a specific open loop
|
|
140
153
|
|
|
141
154
|
## Report Format
|
|
142
155
|
|
|
@@ -59,6 +59,11 @@ Hook research files must preserve:
|
|
|
59
59
|
- source post URLs
|
|
60
60
|
- author/profile URLs
|
|
61
61
|
- engagement totals
|
|
62
|
+
- author follower counts when available, target follower band, follower-band
|
|
63
|
+
fit, engagement per 1k followers, weighted engagement per 1k followers,
|
|
64
|
+
reach penalty multiplier, reach-adjusted score, baseline lift when available,
|
|
65
|
+
why the hook carries independent of creator reach, and normalization
|
|
66
|
+
confidence notes
|
|
62
67
|
- full-text availability
|
|
63
68
|
- source hook preview measurements, including text basis, char count including
|
|
64
69
|
newlines, physical/content line counts, longest nonblank line, blank-line
|
|
@@ -67,6 +72,12 @@ Hook research files must preserve:
|
|
|
67
72
|
including literal mobile/desktop preview blocks, rendered line wraps, render
|
|
68
73
|
basis, CSS contract version, text widths, first-screen promise visibility,
|
|
69
74
|
core point visibility, and whether the point lands after the mobile clamp
|
|
75
|
+
- hook reverse-engineering autopsies for kept source hooks, including source
|
|
76
|
+
mechanism, open-loop type, click question, promised payoff, specificity
|
|
77
|
+
anchor, source shape to keep, source words not to copy, and adaptation rule
|
|
78
|
+
- hook angle matrix summary, including angle territories tested, top generated
|
|
79
|
+
hooks, winning territory, runner-up territories, and why the winning angle
|
|
80
|
+
beat the alternatives
|
|
70
81
|
- extracted hook patterns
|
|
71
82
|
- selected hook basis
|
|
72
83
|
|
|
@@ -87,6 +98,9 @@ Draft files must preserve:
|
|
|
87
98
|
- draft body
|
|
88
99
|
- validation receipt, including LinkedIn preview pass/warn/fail status and
|
|
89
100
|
compact fallback when the selected hook carries a warning
|
|
101
|
+
- selected hook reverse-engineering audit, hook angle matrix summary, and
|
|
102
|
+
see-more click audit when the selected hook intentionally hides the payoff
|
|
103
|
+
after the mobile fold
|
|
90
104
|
- rendered mobile and desktop preview blocks for the selected hook; drafts
|
|
91
105
|
cannot be ready when this rendered-preview audit is missing or fails mobile
|
|
92
106
|
visibility
|
|
@@ -11,6 +11,10 @@ Every saved draft needs a validation receipt. A draft without this receipt is no
|
|
|
11
11
|
- `candidateHooksConsidered`
|
|
12
12
|
- `selectedHook`
|
|
13
13
|
- `selectedHookWhy`
|
|
14
|
+
- `sourceHookReachAudit`
|
|
15
|
+
- `hookReverseEngineeringAudit`
|
|
16
|
+
- `hookAngleMatrix`
|
|
17
|
+
- `seeMoreClickAudit`
|
|
14
18
|
- `proofClaimsUsed`
|
|
15
19
|
- `proofClaimSources`
|
|
16
20
|
- `storyFilesConsulted`
|
|
@@ -68,20 +72,44 @@ set the verdict to `revise` or `reject`.
|
|
|
68
72
|
|
|
69
73
|
## Candidate Set
|
|
70
74
|
|
|
71
|
-
Generate
|
|
75
|
+
Generate many hook candidates before drafting. Do not lock onto the first draft.
|
|
76
|
+
|
|
77
|
+
Minimum candidate set:
|
|
78
|
+
|
|
79
|
+
- normal drafting: at least 8 angle territories and 24 hook candidates
|
|
80
|
+
- hook-critical requests: at least 12 angle territories and 40 hook candidates
|
|
81
|
+
- smaller set only when the user explicitly asks for fewer options
|
|
82
|
+
|
|
83
|
+
An angle territory is a distinct reason the reader might care, such as hidden
|
|
84
|
+
asset, tool/enemy contrast, first-person build proof, workflow reveal,
|
|
85
|
+
contrarian category claim, mistake/confession, market timing, operator pain,
|
|
86
|
+
specific person/company signal, before/after transformation, or question hook.
|
|
72
87
|
|
|
73
88
|
Each candidate should include:
|
|
74
89
|
|
|
75
90
|
- hook text
|
|
76
91
|
- selected premise
|
|
92
|
+
- angle territory
|
|
77
93
|
- premise tension opened
|
|
78
94
|
- reader value implied
|
|
79
95
|
- source pattern
|
|
96
|
+
- source shape kept
|
|
97
|
+
- source words not copied
|
|
98
|
+
- source pattern reach signals: author follower count when available, target
|
|
99
|
+
follower band, follower-band fit, engagement per 1k followers, weighted
|
|
100
|
+
engagement per 1k followers, reach-adjusted score, baseline lift, and
|
|
101
|
+
confidence
|
|
80
102
|
- score
|
|
81
103
|
- `renderedPreview` using `references/linkedin-preview-rendering.md`
|
|
82
104
|
- literal mobile and desktop rendered preview blocks
|
|
83
105
|
- mobile and desktop rendered line wraps
|
|
84
106
|
- first-screen promise: pain, proof, or curiosity visible by the mobile clamp
|
|
107
|
+
- `intentionalOpenLoop`
|
|
108
|
+
- `specificClickQuestionVisible`
|
|
109
|
+
- `seeMoreClickReason`
|
|
110
|
+
- `clickQuestion`
|
|
111
|
+
- `payoffAfterFold`
|
|
112
|
+
- `bodyProofObligation`
|
|
85
113
|
- whether the core point is visible in the mobile rendered preview
|
|
86
114
|
- whether the point lands after the mobile clamp
|
|
87
115
|
- char count including newlines and first-line / first-two-line preview measurements
|
|
@@ -92,6 +120,55 @@ Each candidate should include:
|
|
|
92
120
|
- AI-tell risk
|
|
93
121
|
- why it should win or lose
|
|
94
122
|
|
|
123
|
+
## Hook Reverse Engineering Audit
|
|
124
|
+
|
|
125
|
+
Before a draft can be `ready`, validate that the selected hook came from a real
|
|
126
|
+
source-shape analysis, not from generic brainstorming.
|
|
127
|
+
|
|
128
|
+
Record:
|
|
129
|
+
|
|
130
|
+
- `sourceHooksStudied`
|
|
131
|
+
- `sourceHookAutopsies`
|
|
132
|
+
- `selectedSourceShape`
|
|
133
|
+
- `sourceShapeKept`
|
|
134
|
+
- `sourceWordsNotCopied`
|
|
135
|
+
- `whyItWorks`
|
|
136
|
+
- `openLoopType`: `none`, `hidden_payoff`, `contradiction`, `proof_gap`,
|
|
137
|
+
`workflow_reveal`, `asset_reveal`, or `story_gap`
|
|
138
|
+
- `clickQuestion`
|
|
139
|
+
- `promisedPayoff`
|
|
140
|
+
- `specificityAnchor`
|
|
141
|
+
- `tensionOpened`
|
|
142
|
+
- `proofImplied`
|
|
143
|
+
- `adaptationRule`
|
|
144
|
+
- `bodyPayoffObligation`
|
|
145
|
+
|
|
146
|
+
The selected draft cannot be `ready` if the hook was copied verbatim from
|
|
147
|
+
another creator, if the receipt only says "contrarian" or "curiosity" without
|
|
148
|
+
the concrete shape, or if the body does not pay off the hook quickly.
|
|
149
|
+
|
|
150
|
+
## Hook Angle Matrix Audit
|
|
151
|
+
|
|
152
|
+
Before a draft can be `ready`, validate that the hook was chosen after exploring
|
|
153
|
+
materially different angles.
|
|
154
|
+
|
|
155
|
+
Record:
|
|
156
|
+
|
|
157
|
+
- `angleTerritoriesTested`
|
|
158
|
+
- `hookCandidatesGenerated`
|
|
159
|
+
- `winningTerritory`
|
|
160
|
+
- `runnerUpTerritories`
|
|
161
|
+
- `whyWinningAngleBeatsAlternatives`
|
|
162
|
+
- `whyRejectedHooksLost`
|
|
163
|
+
- `mobileRenderedPreviewBlocksForTopCandidates`
|
|
164
|
+
- `desktopRenderedPreviewBlocksForTopCandidates`
|
|
165
|
+
- `proofNeededByWinningAngle`
|
|
166
|
+
|
|
167
|
+
If the user asked to "nail the hook," "focus on hooks," or explore "a ton of
|
|
168
|
+
angles," the draft cannot be `ready` unless at least 12 territories and 40 hook
|
|
169
|
+
candidates were considered or the receipt explains why the user explicitly
|
|
170
|
+
requested fewer.
|
|
171
|
+
|
|
95
172
|
## Finalizer Pass
|
|
96
173
|
|
|
97
174
|
After the first draft:
|
|
@@ -153,6 +230,35 @@ If `selectedControversy` is missing, if the audience belief is only a generic
|
|
|
153
230
|
label like "founders want growth," or if `credibleWhyUs` depends on borrowed
|
|
154
231
|
proof from another creator, save as `needs_revision`.
|
|
155
232
|
|
|
233
|
+
## Source Hook Reach Audit
|
|
234
|
+
|
|
235
|
+
Before a draft can be `ready`, validate that the selected source hook pattern
|
|
236
|
+
was chosen for a reach-normalized reason, not raw audience size.
|
|
237
|
+
|
|
238
|
+
Record:
|
|
239
|
+
|
|
240
|
+
- `targetFollowerBand`
|
|
241
|
+
- `authorFollowerCount` for each source pattern when available
|
|
242
|
+
- `followerBandFit`: `in_target_band`, `below_target_band`,
|
|
243
|
+
`above_target_band`, or `unknown`
|
|
244
|
+
- `engagementPer1kFollowers`
|
|
245
|
+
- `weightedEngagementPer1kFollowers`
|
|
246
|
+
- `reachAdjustedScore`
|
|
247
|
+
- `reachPenaltyMultiplier`
|
|
248
|
+
- `creatorBaselineMedianEngagement` or `creator_baseline_unavailable`
|
|
249
|
+
- `baselineLift` or `creator_baseline_unavailable`
|
|
250
|
+
- `bigAccountPenaltyApplied`
|
|
251
|
+
- `whyTheHookCarries`
|
|
252
|
+
- `followerCountUnavailableSources`
|
|
253
|
+
- `whyThisHookIsAReachAdjustedSignal`
|
|
254
|
+
|
|
255
|
+
If the user asked for a follower range and the selected source pattern is from a
|
|
256
|
+
large account above the target range, the receipt must explain why it survived
|
|
257
|
+
the celebrity reach penalty. Use large-account hooks as secondary pattern
|
|
258
|
+
evidence unless they clearly overperform the creator's baseline or the hook
|
|
259
|
+
itself carries after reach is controlled. A draft cannot be `ready` when the
|
|
260
|
+
selected hook is justified only by raw total engagement or follower count.
|
|
261
|
+
|
|
156
262
|
## LinkedIn Preview Audit
|
|
157
263
|
|
|
158
264
|
Audit the selected hook and top candidates against
|
|
@@ -182,15 +288,17 @@ review clamp: first 3 rendered text lines
|
|
|
182
288
|
Use rendered gates:
|
|
183
289
|
|
|
184
290
|
- `pass`: the mobile rendered preview shows the pain, proof, or curiosity by
|
|
185
|
-
the end of the first 3 rendered lines, and the core point is
|
|
186
|
-
without opening "see more"
|
|
291
|
+
the end of the first 3 rendered lines, and either the core point is
|
|
292
|
+
understandable without opening "see more" or an intentional open loop creates
|
|
293
|
+
a specific click question with an immediate planned payoff.
|
|
187
294
|
- `warn`: the mobile rendered preview creates useful curiosity but the core
|
|
188
|
-
point is slightly softened by wrapping, blank-line rhythm,
|
|
189
|
-
context word. A compact fallback is required.
|
|
295
|
+
point or click question is slightly softened by wrapping, blank-line rhythm,
|
|
296
|
+
or one missing context word. A compact fallback is required.
|
|
190
297
|
- `fail`: the hook's real point appears after the first 3 mobile rendered
|
|
191
|
-
lines
|
|
192
|
-
|
|
193
|
-
looks
|
|
298
|
+
lines without an intentional open-loop plan, the visible open loop is vague,
|
|
299
|
+
the first rendered line is generic setup, blank lines consume the preview
|
|
300
|
+
before the reader sees the point, or desktop fit is the only reason it looks
|
|
301
|
+
good.
|
|
194
302
|
|
|
195
303
|
Desktop preview usually has more room. Still record desktop fit, but never let
|
|
196
304
|
desktop fit compensate for a mobile `fail`.
|
|
@@ -213,6 +321,12 @@ Record:
|
|
|
213
321
|
- `corePainProofOrCuriosityVisibleDesktop`
|
|
214
322
|
- `corePointVisibleMobile`
|
|
215
323
|
- `corePointVisibleDesktop`
|
|
324
|
+
- `intentionalOpenLoop`
|
|
325
|
+
- `specificClickQuestionVisible`
|
|
326
|
+
- `payoffPlannedImmediatelyAfterClamp`
|
|
327
|
+
- `seeMoreClickReason`
|
|
328
|
+
- `clickQuestion`
|
|
329
|
+
- `payoffAfterFold`
|
|
216
330
|
- `pointAfterMobileClamp`
|
|
217
331
|
- `charCount`
|
|
218
332
|
- `charCountIncludingNewlines`
|
|
@@ -236,10 +350,33 @@ If the hook only works after the rendered mobile clamp, rewrite it. A draft
|
|
|
236
350
|
cannot be `ready` with `previewBudgetStatus: fail`,
|
|
237
351
|
`mobilePreviewFit: fail`, missing `renderedPreview`,
|
|
238
352
|
`corePainProofOrCuriosityVisibleMobile: false`,
|
|
239
|
-
`corePointVisibleMobile: false
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
353
|
+
`corePointVisibleMobile: false` unless the receipt has
|
|
354
|
+
`intentionalOpenLoop: true`, `specificClickQuestionVisible: true`, and
|
|
355
|
+
`payoffPlannedImmediatelyAfterClamp: true`, or `pointAfterMobileClamp: true`
|
|
356
|
+
unless the post is intentionally hiding the payoff for a specific see-more
|
|
357
|
+
click. A draft may be `ready` with `previewBudgetStatus: warn` only when the
|
|
358
|
+
warning is explicit, usually because the user prefers blank-line rhythm, and
|
|
359
|
+
the receipt includes a compact fallback.
|
|
360
|
+
|
|
361
|
+
## See-More Click Audit
|
|
362
|
+
|
|
363
|
+
When the selected hook intentionally hides the payoff after the mobile fold,
|
|
364
|
+
record:
|
|
365
|
+
|
|
366
|
+
- `intentionalOpenLoop: true`
|
|
367
|
+
- `openLoopType`
|
|
368
|
+
- `visibleBeforeFold`
|
|
369
|
+
- `clickQuestion`
|
|
370
|
+
- `payoffAfterFold`
|
|
371
|
+
- `firstPayoffLine`
|
|
372
|
+
- `payoffArrivesWithinFirstBodySection`
|
|
373
|
+
- `readerValueAfterClick`
|
|
374
|
+
- `baitRisk`
|
|
375
|
+
- `baitRiskMitigation`
|
|
376
|
+
|
|
377
|
+
Open loops are allowed only when the body actually delivers the promised payoff.
|
|
378
|
+
If the hook withholds the answer but the body switches topics, save as
|
|
379
|
+
`needs_revision`.
|
|
243
380
|
|
|
244
381
|
## Simplifier / Concrete-Language Audit
|
|
245
382
|
|