@sellable/mcp 0.1.238 → 0.1.240

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.
@@ -58,6 +58,7 @@ export declare const contentPostToolDefinitions: ({
58
58
  keywords?: undefined;
59
59
  sourcePosts?: undefined;
60
60
  selectedPatterns?: undefined;
61
+ previewBudget?: undefined;
61
62
  notes?: undefined;
62
63
  createdAt?: undefined;
63
64
  draftId?: undefined;
@@ -130,6 +131,11 @@ export declare const contentPostToolDefinitions: ({
130
131
  type: string;
131
132
  };
132
133
  };
134
+ previewBudget: {
135
+ type: string;
136
+ description: string;
137
+ additionalProperties: boolean;
138
+ };
133
139
  notes: {
134
140
  type: string;
135
141
  };
@@ -192,6 +198,7 @@ export declare const contentPostToolDefinitions: ({
192
198
  keywords?: undefined;
193
199
  sourcePosts?: undefined;
194
200
  selectedPatterns?: undefined;
201
+ previewBudget?: undefined;
195
202
  notes?: undefined;
196
203
  };
197
204
  required: string[];
@@ -245,6 +252,7 @@ export declare function saveHookResearchTool(input: {
245
252
  keywords?: string[];
246
253
  sourcePosts?: Array<Record<string, unknown>>;
247
254
  selectedPatterns?: string[];
255
+ previewBudget?: Record<string, unknown>;
248
256
  notes?: string;
249
257
  createdAt?: string;
250
258
  }): {
@@ -66,6 +66,11 @@ export const contentPostToolDefinitions = [
66
66
  type: "array",
67
67
  items: { type: "string" },
68
68
  },
69
+ previewBudget: {
70
+ type: "object",
71
+ description: "Optional structured LinkedIn preview-budget summary for studied hooks and selected patterns.",
72
+ additionalProperties: true,
73
+ },
69
74
  notes: { type: "string" },
70
75
  createdAt: { type: "string" },
71
76
  },
@@ -202,11 +207,14 @@ export function capturePostIdeaTool(input) {
202
207
  const markdown = buildMarkdown(metadata, [
203
208
  ["Distilled Brief", input.distilledBrief || ""],
204
209
  ["Raw Source", rawBlock(input.rawSource)],
205
- ["Source Metadata", jsonBlock(stripUndefined({
210
+ [
211
+ "Source Metadata",
212
+ jsonBlock(stripUndefined({
206
213
  sourceType: input.sourceType,
207
214
  sourceUrl: input.sourceUrl,
208
215
  capturedAt: now,
209
- }))],
216
+ })),
217
+ ],
210
218
  ]);
211
219
  writeArtifact(relativePath, markdown);
212
220
  return {
@@ -246,6 +254,7 @@ export function saveHookResearchTool(input) {
246
254
  const markdown = buildMarkdown(metadata, [
247
255
  ["Keywords", listBlock(input.keywords ?? [])],
248
256
  ["Selected Patterns", listBlock(input.selectedPatterns ?? [])],
257
+ ["Preview Budget", jsonBlock(input.previewBudget ?? {})],
249
258
  ["Source Posts", jsonBlock(input.sourcePosts ?? [])],
250
259
  ["Notes", input.notes || ""],
251
260
  ]);
@@ -332,18 +341,24 @@ export function markPostPublishedTool(input) {
332
341
  };
333
342
  const markdown = buildMarkdown(metadata, [
334
343
  ["Final Text", input.finalText || ""],
335
- ["Publish Metadata", jsonBlock(stripUndefined({
344
+ [
345
+ "Publish Metadata",
346
+ jsonBlock(stripUndefined({
336
347
  draftId: safeDraftId,
337
348
  publishUrl: input.publishUrl,
338
349
  activityId: activityId || undefined,
339
350
  publishedAt,
340
- }))],
341
- ["Future Metrics", jsonBlock({
351
+ })),
352
+ ],
353
+ [
354
+ "Future Metrics",
355
+ jsonBlock({
342
356
  impressions: null,
343
357
  reactions: null,
344
358
  comments: null,
345
359
  snapshots: [],
346
- })],
360
+ }),
361
+ ],
347
362
  ]);
348
363
  writeArtifact(relativePath, markdown);
349
364
  return {
@@ -483,7 +498,8 @@ function validateRelativePath(relativePath) {
483
498
  }
484
499
  function isPathInside(candidate, root) {
485
500
  const relative = path.relative(root, candidate);
486
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
501
+ return (relative === "" ||
502
+ (!relative.startsWith("..") && !path.isAbsolute(relative)));
487
503
  }
488
504
  function normalizeArtifactId(id, label) {
489
505
  requireString(id, label);
@@ -1214,6 +1214,7 @@ export declare const allTools: ({
1214
1214
  keywords?: undefined;
1215
1215
  sourcePosts?: undefined;
1216
1216
  selectedPatterns?: undefined;
1217
+ previewBudget?: undefined;
1217
1218
  notes?: undefined;
1218
1219
  createdAt?: undefined;
1219
1220
  draftId?: undefined;
@@ -1279,6 +1280,7 @@ export declare const allTools: ({
1279
1280
  keywords?: undefined;
1280
1281
  sourcePosts?: undefined;
1281
1282
  selectedPatterns?: undefined;
1283
+ previewBudget?: undefined;
1282
1284
  notes?: undefined;
1283
1285
  };
1284
1286
  required: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.238",
3
+ "version": "0.1.240",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "build": "tsc",
14
+ "prepack": "npm run build",
14
15
  "start": "node dist/index.js",
15
16
  "start:dev": "node dist/index-dev.js",
16
17
  "dev": "ts-node src/index.ts"
@@ -93,10 +93,10 @@ Required:
93
93
  select a blocked candidate. If all 3 candidates fail, route to
94
94
  `revise-message` with the failure reasons enumerated per candidate (cite the
95
95
  rule/filter name and the offending line).
96
- - **Concrete Language Audit.** The selected draft must highlight every abstract
97
- verb, abstract noun, abstract adjective, abstract adverb, and cliche, then
98
- either replace it with a buyer-visible action, object, workflow, artifact,
99
- risk, result, or next step from the approved evidence, cut it, or block with
96
+ - **Concrete Language Audit.** The selected draft must highlight every abstract verb,
97
+ abstract noun, abstract adjective, abstract adverb, and cliche, then either
98
+ replace it with a buyer-visible action, object, workflow, artifact, risk,
99
+ result, or next step from the approved evidence, cut it, or block with
100
100
  `revise-message` / `revise-filter`. Do not leave a vague phrase in place just
101
101
  because it sounds polished.
102
102
 
@@ -20,7 +20,7 @@ Hard fail patterns:
20
20
  - drafts that skip raw idea capture
21
21
  - hooks copied verbatim from another creator
22
22
  - using `~/.sellable/configs/content/linkedin-posts-drafts.md` as the normal save target
23
- </role>
23
+ </role>
24
24
 
25
25
  <scope>
26
26
  V1 is posts-only and hooks-first.
@@ -42,7 +42,7 @@ Do not:
42
42
  - optimize a comment workflow
43
43
  - move identity/proof/story memory into content files
44
44
  - append new drafts to the legacy flat file
45
- </scope>
45
+ </scope>
46
46
 
47
47
  <required_assets>
48
48
  Before drafting, load all required assets with `mcp__sellable__get_subskill_asset`:
@@ -82,6 +82,7 @@ Use these MCP tools when available:
82
82
  - `mcp__sellable__fetch_linkedin_posts`
83
83
  - `mcp__sellable__fetch_linkedin_profile`
84
84
  - `mcp__sellable__record_engage_proven_search`
85
+ - `mcp__sellable__upsert_engage_tracked_person`
85
86
 
86
87
  Do not call outbound/campaign tools from this skill. Do not call message-generation prompts or tools. This skill reuses the quality architecture of the message pipeline: required assets, candidate set, finalizer pass, simplifier/concrete-language audit, anti-AI audit, proof/voice validation, and blocked/retry-needed states.
87
88
  </tools>
@@ -90,7 +91,7 @@ Do not call outbound/campaign tools from this skill. Do not call message-generat
90
91
  Load user memory before hook generation or drafting:
91
92
 
92
93
  1. Call `mcp__sellable__get_engage_memory`.
93
- 2. Read or use the returned core memory fields:
94
+ 2. Read or use the returned core identity memory and company memory fields:
94
95
  - `core/about-me.md`
95
96
  - `core/my-company.md`
96
97
  - `core/anti-ai-writing-style.md`
@@ -115,6 +116,39 @@ Load user memory before hook generation or drafting:
115
116
  New users may have blank or missing core files until they run `$sellable:interview`. If story, proof, identity, or company memory is missing, ask for the missing material or ask the user to run `$sellable:interview`. Never fabricate what should have come from `story-bank.md`, `proof-ledger.md`, or `answer-bank.md`.
116
117
 
117
118
  The interview/core memory files remain the durable source for stories and proof. This skill may read them, but it must not move them into `~/.sellable/content/**`.
119
+
120
+ Use core context modes as drafting constraints, not decorative labels. If a
121
+ post idea exposes durable memory that should help future writing, propose a
122
+ core write-back before saving it. The user must be able to approve, edit, or
123
+ reject each proposed memory update before it is written.
124
+
125
+ Durable write-back targets:
126
+
127
+ - `core/answer-bank.md` for reusable user answers, positioning, or taste rules
128
+ - `core/wins-ledger.md` for verified wins and outcomes
129
+ - `core/change-log.md` for approved corrections, rejected-but-useful proposals,
130
+ privacy decisions, and downstream prompt/operator notes
131
+ - `core/story-bank.md` for reusable first-person stories
132
+ - `core/transcripts/INDEX.md` for source interview or voice-memo references
133
+ - `core/references/linkedin-posts/INDEX.md` and adjacent files for approved
134
+ gold-standard post references
135
+ - `discovery/influencers.md` for approved creators/persons the user wants the
136
+ system to keep learning from
137
+
138
+ Write-backs must use stable source keys such as
139
+ `create-post:{date}:{ideaId}:{slug}` or
140
+ `create-post-research:{date}:{sourcePostHash}:{slug}`. Check existing copied
141
+ paths before adding references, create no duplicate reference rows, and keep
142
+ manual sections preserved. If the user says to mark private, store only the
143
+ minimum private-safe metadata and do not promote it into public proof,
144
+ references, or examples.
145
+
146
+ When the user says a creator/person is worth following, learning from, or using
147
+ again, persist them with `mcp__sellable__upsert_engage_tracked_person` before
148
+ continuing. Use the canonical LinkedIn profile URL, a concise reason that names
149
+ the content lane, and no senderId unless the user explicitly scopes the person
150
+ to one sender. If the person is already tracked, update the reason instead of
151
+ creating a duplicate.
118
152
  </memory_contract>
119
153
 
120
154
  <modes>
@@ -149,6 +183,29 @@ Normal ad hoc mode still creates an ID'd idea artifact first with `mcp__sellable
149
183
 
150
184
  If the user explicitly says not to save anything, label the output `unsaved_preview`, do not call it draft-ready, and do not mark it as validated for publishing.
151
185
 
186
+ ## Research Checkpoint Mode
187
+
188
+ Use when the user asks to research hooks, research bodies, "show me what it
189
+ learned", "run the research phase", "what is working in the space", or anything
190
+ similar.
191
+
192
+ 1. Capture or load the idea/topic if one is provided.
193
+ 2. Run the hook/body research playbook.
194
+ 3. Save the research with `mcp__sellable__save_hook_research`.
195
+ 4. Present a `Research Learning Report` to the user.
196
+ 5. Do not draft until the user approves a direction or explicitly says to run
197
+ the draft phase.
198
+
199
+ The report must include specific words, phrase shapes, sentence patterns, and
200
+ body moves from the research. Do not only report abstract labels like
201
+ "contrarian hook" or "tool-stack enemy."
202
+
203
+ When the host supports background agents or Task workers, run the heavy research
204
+ in a dedicated research worker so the main orchestrator stays clean. The
205
+ orchestrator should keep only the compressed research packet, final selected
206
+ examples, and user-facing learning report in context. Do not paste the entire
207
+ raw source-post corpus into the orchestrator conversation.
208
+
152
209
  ## Gold Standard Pack Mode
153
210
 
154
211
  Use when the user asks to import their best posts, research best posts in the space, build a gold-standard pack, or add inspiration examples.
@@ -169,8 +226,11 @@ Space benchmark research:
169
226
  1. Use `mcp__sellable__search_engagement_posts` and the hook research playbook.
170
227
  2. Shortlist high-performing posts from the user's space using the weighted signal rules.
171
228
  3. Penalize lead magnets, giveaways, engagement bait, celebrity-only reach, and poor voice fit.
172
- 4. Show the user the candidates and ask: "These look good. Should we use any as gold standards?"
173
- 5. Add only the user-approved selections.
229
+ 4. When a creator/person appears repeatedly strong, include a `track_person`
230
+ recommendation in the candidate card.
231
+ 5. Show the user the candidates and ask: "These look good. Should we use any as gold standards or track any of these people?"
232
+ 6. Add only the user-approved post selections.
233
+ 7. For each approved tracked person, call `mcp__sellable__upsert_engage_tracked_person`.
174
234
 
175
235
  The approved pack is capped at 20 gold standards. If adding new approved examples would exceed 20, ask which existing item to replace or skip the overflow. Candidate lists can be longer, but approved saved standards cannot exceed 20.
176
236
  </modes>
@@ -196,6 +256,27 @@ If local idea capture succeeds but auth/workspace is missing, keep the idea and
196
256
 
197
257
  Use `references/hook-research-playbook.md`.
198
258
 
259
+ If the host supports background agents, delegate the search/fetch/autopsy work
260
+ to one bounded `research-worker` before hook candidate generation. The worker
261
+ owns broad search, profile fetches, full-text matching, source filtering,
262
+ language extraction, and body-structure extraction. The orchestrator owns the
263
+ final save call, user-visible `Research Learning Report`, direction selection,
264
+ and drafting gate.
265
+
266
+ The research worker must return a compact packet only:
267
+
268
+ - source examples kept and rejected
269
+ - full adapted hook blocks
270
+ - exact phrase patterns and sentence shapes
271
+ - body structures and exact body language moves
272
+ - preview measurements
273
+ - track-person and gold-standard recommendations
274
+ - blocked states or confidence gaps
275
+
276
+ The research worker must not return all raw post bodies unless a specific full
277
+ text is required as a gold-standard candidate. Prefer summaries plus URLs and
278
+ the exact extracted phrase shapes.
279
+
199
280
  Default flow:
200
281
 
201
282
  1. Convert the idea into 3-8 search keywords.
@@ -206,7 +287,8 @@ Default flow:
206
287
  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`.
207
288
  7. Penalize lead-magnet or giveaway mechanics unless the user explicitly asks for a lead magnet post.
208
289
  8. For story posts, extract the story mechanism that made the post work, not just the first line.
209
- 9. Extract hook structures, not wording.
290
+ 9. Extract hook structures plus specific reusable words, phrases, sentence
291
+ shapes, transitions, and body language patterns.
210
292
  10. Save the research with `mcp__sellable__save_hook_research`.
211
293
 
212
294
  Record provenance:
@@ -221,9 +303,79 @@ Record provenance:
221
303
  - lead-magnet or engagement-bait penalties
222
304
  - story mechanism when relevant
223
305
  - full-text match status
306
+ - source hook preview measurements and whether they came from full text or a search preview
224
307
  - selected hook patterns
308
+ - exact phrase patterns and sentence shapes
309
+ - body structures and body language patterns
225
310
  - why each pattern fits the user's idea and voice
226
311
 
312
+ ## Step 1.5: Research Learning Report
313
+
314
+ After saving research and before drafting, show the user what the system
315
+ learned when either:
316
+
317
+ - the user asked for research/checkpoint mode
318
+ - the research materially changes the likely hook/body direction
319
+ - the researched examples include new people worth tracking or examples worth
320
+ adding as gold standards
321
+
322
+ Default ad hoc drafting may proceed after this report only when the user clearly
323
+ asked for an immediate draft. In research checkpoint mode, stop here and ask
324
+ which direction to use.
325
+
326
+ The `Research Learning Report` must include:
327
+
328
+ ```text
329
+ Research status:
330
+ - idea/topic:
331
+ - research artifact:
332
+ - search window:
333
+ - keywords:
334
+ - full-text coverage:
335
+ - repost/share data:
336
+
337
+ Best source examples:
338
+ 1. author, URL, engagement, why kept, why not copied
339
+
340
+ Hook patterns learned:
341
+ 1. full adapted hook block
342
+ - source mechanism:
343
+ - preview budget:
344
+ - internal question:
345
+ - why it fits / why it does not:
346
+
347
+ Specific words and phrase shapes:
348
+ 1. "phrase or phrase shape"
349
+ - role:
350
+ - source:
351
+ - reusable forms:
352
+ - adapted Sellable form:
353
+ - do not copy:
354
+
355
+ Body structures learned:
356
+ 1. structure name
357
+ - source:
358
+ - sequence:
359
+ - exact language moves:
360
+ - adapted Sellable body move:
361
+
362
+ Rejected examples:
363
+ - author/source:
364
+ - reason rejected:
365
+
366
+ Save recommendations:
367
+ - track people:
368
+ - gold-standard candidates:
369
+
370
+ Recommended draft directions:
371
+ 1. hook block + body structure
372
+ 2. hook block + body structure
373
+ 3. hook block + body structure
374
+ ```
375
+
376
+ Keep this report concise enough to read, but concrete enough that another agent
377
+ could draft from it without redoing research.
378
+
227
379
  ## Step 2: Hook Candidates
228
380
 
229
381
  Generate at least 12 hook candidates unless the user requested a smaller set.
@@ -233,8 +385,14 @@ Each hook must include:
233
385
  - source hook pattern
234
386
  - why it fits this idea
235
387
  - `charCount`
388
+ - `charCountIncludingNewlines`
236
389
  - `firstLineChars`
237
390
  - `firstTwoLinesChars`
391
+ - `physicalLineCount`
392
+ - `contentLineCount`
393
+ - `longestNonblankLineChars`
394
+ - `blankLineVisualRisk`
395
+ - `previewBudgetStatus`
238
396
  - `mobilePreviewFit`
239
397
  - `desktopPreviewFit`
240
398
  - `lineCountEstimate`
@@ -246,14 +404,22 @@ Each hook must include:
246
404
 
247
405
  Do not copy source wording. Copy only the structure.
248
406
 
249
- Use these LinkedIn preview budgets as conservative scoring constraints. LinkedIn rendering varies by device, font, and line break, so these are planning budgets, not exact guarantees:
407
+ Use this conservative mobile-first LinkedIn preview gate. LinkedIn does not
408
+ publish exact "see more" cutoff rules, and rendering varies by device, app
409
+ version, font, media, and line break. These are v1 safety budgets, not claims
410
+ about an official LinkedIn limit:
411
+
412
+ - `pass`: hook is <= 110 chars including newlines, every nonblank line is <= 45 chars, and the hook's core point lands before likely truncation.
413
+ - `warn`: hook is 111-140 chars including newlines, any nonblank line is 46-55 chars, or blank lines create visual-line risk. Blank lines are allowed, but they count as physical lines.
414
+ - `fail`: hook is > 140 chars including newlines, any nonblank line is > 55 chars, or the hook's point depends on text after likely truncation.
250
415
 
251
- - mobile first line: 35-45 chars
252
- - mobile first two lines: 80-100 chars
253
- - desktop first line: 70-90 chars
254
- - desktop first two lines: 140-180 chars
416
+ Desktop preview usually has more room. Still record `desktopPreviewFit`, but
417
+ never let desktop fit compensate for a mobile `fail`.
255
418
 
256
- If a hook's point depends on text after the likely preview, rewrite it before selecting it.
419
+ If a hook's point depends on text after the likely preview, rewrite it before
420
+ selecting it. A selected hook may carry a `warn` only when the warning is about
421
+ intentional blank-line rhythm or a slight line-length overage; include a compact
422
+ fallback in the validation receipt.
257
423
 
258
424
  ## Step 3: Draft
259
425
 
@@ -287,6 +453,7 @@ Every saved draft needs a validation receipt with:
287
453
  - voice audit findings
288
454
  - anti-AI audit findings
289
455
  - outbound AI-tell audit findings
456
+ - hook preview pass/warn/fail status and compact fallback when warned
290
457
  - finalizer changes
291
458
  - blocked/retry-needed reasons, if any
292
459
 
@@ -319,6 +486,7 @@ Published post records live under:
319
486
  ```text
320
487
  ~/.sellable/content/linkedin/published/
321
488
  ```
489
+
322
490
  </pipeline>
323
491
 
324
492
  <legacy>
@@ -341,8 +509,9 @@ validation_summary:
341
509
  proof: pass | needs_user_input | blocked
342
510
  voice: pass | needs_revision
343
511
  anti_ai: pass | needs_revision
344
- linkedin_preview: pass | needs_revision
512
+ linkedin_preview: pass | warn | needs_revision
345
513
  concrete_language: pass | needs_revision
346
514
  next_step: <what the user should do next>
347
515
  ```
516
+
348
517
  </response_shape>
@@ -35,6 +35,42 @@ Do not store gold standards under `~/.sellable/content/linkedin/**`. Content
35
35
  folders are for ideas, hook research, drafts, published records, and future
36
36
  comment history. Gold standards are durable writing memory.
37
37
 
38
+ ## Creator Tracking
39
+
40
+ When an outside creator/person is approved as someone the user wants to keep
41
+ learning from, save the person separately from the gold-standard post.
42
+
43
+ Use:
44
+
45
+ ```text
46
+ mcp__sellable__upsert_engage_tracked_person({
47
+ name,
48
+ linkedinUrl,
49
+ reason
50
+ })
51
+ ```
52
+
53
+ Tracked people live in:
54
+
55
+ ```text
56
+ ~/.sellable/configs/discovery/influencers.md
57
+ ```
58
+
59
+ Gold-standard post records live in:
60
+
61
+ ```text
62
+ ~/.sellable/configs/core/references/linkedin-posts/
63
+ ```
64
+
65
+ Save both when both are approved:
66
+
67
+ - tracked person: "keep fetching this creator's new posts"
68
+ - gold-standard post: "use this specific post as decomposed writing memory"
69
+
70
+ Do not conflate the two. A person can be tracked without any one post becoming
71
+ a gold standard, and a post can be saved as a gold standard without tracking the
72
+ author.
73
+
38
74
  ## Import Modes
39
75
 
40
76
  ### Personal Best Import
@@ -65,7 +101,11 @@ gold standards.
65
101
  reach, and examples that cannot be adapted to the user's voice.
66
102
  5. Present candidates with a clear "should we use these as gold standards?"
67
103
  approval gate.
68
- 6. Add only the user-approved selections.
104
+ 6. Include a `track person?` recommendation for authors with repeated strong
105
+ topic-fit posts or when the user says they like the person.
106
+ 7. Add only the user-approved post selections.
107
+ 8. Call `mcp__sellable__upsert_engage_tracked_person` only for user-approved
108
+ tracked people.
69
109
 
70
110
  ## Candidate Presentation
71
111
 
@@ -78,6 +118,7 @@ Show compact candidate cards. Include:
78
118
  - engagement breakdown when available
79
119
  - why it might belong in the pack
80
120
  - hook mechanism
121
+ - hook preview budget status and measurement basis
81
122
  - content/body mechanism
82
123
  - rhythm notes
83
124
  - sentence-structure notes
@@ -85,6 +126,7 @@ Show compact candidate cards. Include:
85
126
  - voice fit
86
127
  - risks
87
128
  - allowed use recommendation
129
+ - track person recommendation and reason
88
130
 
89
131
  Approval prompt:
90
132
 
@@ -92,7 +134,7 @@ Approval prompt:
92
134
  Gold-standard pack: <current_count>/20 approved.
93
135
 
94
136
  These look like candidates worth saving. Which should I add?
95
- Reply with numbers to add, "skip all", or "replace <existing> with <new>" if the pack is full.
137
+ Reply with numbers to add, "track <author>", "skip all", or "replace <existing> with <new>" if the pack is full.
96
138
  ```
97
139
 
98
140
  Do not write candidates to the approved pack before the user chooses.
@@ -144,8 +186,15 @@ Tags:
144
186
 
145
187
  - hook text:
146
188
  - char count:
189
+ - char count including newlines:
147
190
  - first-line chars:
148
191
  - first-two-line chars:
192
+ - physical line count:
193
+ - content line count:
194
+ - longest nonblank line chars:
195
+ - blank-line visual risk:
196
+ - source text basis:
197
+ - preview budget status:
149
198
  - mobile preview fit:
150
199
  - desktop preview fit:
151
200
  - hook mechanism:
@@ -11,6 +11,58 @@ Derive 3-8 keyword searches from:
11
11
  - proven search terms in engage memory
12
12
  - tracked people/inspiration references when relevant
13
13
 
14
+ When research reveals a creator/person the user likes, keep their profile URL
15
+ with the source post. If the user approves tracking them, save them with
16
+ `mcp__sellable__upsert_engage_tracked_person` so future sessions can fetch
17
+ their posts directly instead of rediscovering them ad hoc.
18
+
19
+ ## Background Research Worker
20
+
21
+ Use a background research worker when the host supports it and the research
22
+ needs more than a small handful of posts. This keeps the main orchestrator
23
+ focused on decisions instead of carrying a large source corpus.
24
+
25
+ Worker owns:
26
+
27
+ - broad keyword search
28
+ - tracked-person post fetches
29
+ - full-text matching by URL/activity ID
30
+ - duplicate removal
31
+ - lead-magnet, giveaway, engagement-bait, and off-voice filtering
32
+ - hook opening measurement
33
+ - exact phrase-pattern extraction
34
+ - body-structure extraction
35
+ - rejected-example notes
36
+ - track-person and gold-standard recommendations
37
+
38
+ Orchestrator owns:
39
+
40
+ - deciding whether research is enough
41
+ - saving `mcp__sellable__save_hook_research`
42
+ - showing the `Research Learning Report`
43
+ - asking which direction to draft
44
+ - drafting and validation
45
+
46
+ Worker output must be a compressed research packet, not a data dump:
47
+
48
+ ```text
49
+ Research packet:
50
+ - sources kept: max 8
51
+ - sources rejected: max 8
52
+ - full adapted hook blocks: max 12
53
+ - exact phrase patterns: max 20
54
+ - body patterns: max 8
55
+ - source URLs and author profile URLs
56
+ - preview measurements
57
+ - confidence gaps
58
+ - save recommendations
59
+ ```
60
+
61
+ Do not return the full raw text of every post to the orchestrator. Include only
62
+ short source excerpts needed to explain a phrase pattern, plus URLs. Full text
63
+ belongs in the saved research artifact only when the user approves a specific
64
+ gold-standard candidate and it is safe to store.
65
+
14
66
  ## Default Search
15
67
 
16
68
  Use `mcp__sellable__search_engagement_posts` with practical constraints:
@@ -74,19 +126,65 @@ When full hook/body text matters:
74
126
 
75
127
  If full text is unavailable, record `full_text_unavailable`. Use only the preview for hook analysis and do not infer missing body details.
76
128
 
129
+ ## Opening Preview Measurement
130
+
131
+ Measure the visible opening for every shortlisted source post before extracting
132
+ the hook pattern. This makes the study useful for LinkedIn, not just generally
133
+ "good writing."
134
+
135
+ LinkedIn does not publish exact "see more" cutoff rules. Treat these as
136
+ conservative v1 planning budgets:
137
+
138
+ - `pass`: opening hook is <= 110 chars including newlines, every nonblank line
139
+ is <= 45 chars, and the hook's core point lands before likely truncation.
140
+ - `warn`: opening hook is 111-140 chars including newlines, any nonblank line is
141
+ 46-55 chars, or blank lines create visual-line risk. Blank lines are allowed,
142
+ but they count as physical lines.
143
+ - `fail`: opening hook is > 140 chars including newlines, any nonblank line is
144
+ > 55 chars, or the hook's core point depends on text after likely truncation.
145
+
146
+ Desktop preview has more room, so record it separately, but never let desktop
147
+ fit compensate for a mobile `fail`.
148
+
149
+ For each source, record:
150
+
151
+ - `sourceTextBasis`: `full_text`, `search_preview`, or `manual_user_source`
152
+ - `openingTextUsed`
153
+ - `charCountIncludingNewlines`
154
+ - `physicalLineCount`
155
+ - `contentLineCount`
156
+ - `firstLineChars`
157
+ - `firstTwoPhysicalLinesChars`
158
+ - `firstTwoContentLinesChars`
159
+ - `longestNonblankLineChars`
160
+ - `blankLineCountBeforeFold`
161
+ - `mobilePreviewBudget`: `pass`, `warn`, or `fail`
162
+ - `desktopPreviewBudget`: `pass`, `warn`, or `fail`
163
+ - `blankLineVisualRisk`
164
+ - `corePointBeforeLikelyTruncation`
165
+
166
+ If only a search preview is available, do not pretend the opening is complete.
167
+ Record `sourceTextBasis: search_preview` and lower confidence when the hook
168
+ appears cut off or body context is unavailable.
169
+
77
170
  ## Hook Extraction
78
171
 
79
- Extract structure, not wording.
172
+ Extract structure and reusable language patterns, not copied prose. The goal is
173
+ to learn the exact kinds of words, phrase shapes, sentence rhythms, and body
174
+ moves that are working, then adapt them into the user's voice.
80
175
 
81
176
  For each shortlisted source post, record:
82
177
 
83
178
  - URL
84
179
  - author
180
+ - author profile URL when available
85
181
  - engagement totals and available likes/comments/shares breakdown
86
182
  - creator repeat evidence
87
183
  - visible hook text or preview
88
- - line count and mobile preview fit
184
+ - opening preview measurement fields from the section above
89
185
  - hook mechanism
186
+ - exact hook language patterns: reusable words, phrase shapes, contrast forms,
187
+ sentence shapes, and transition moves
90
188
  - story mechanism when the post is a story
91
189
  - internal question created
92
190
  - emotional trigger
@@ -94,6 +192,81 @@ For each shortlisted source post, record:
94
192
  - lead magnet or engagement bait penalty
95
193
  - weighted signal notes
96
194
  - replicability score
195
+ - track person recommendation: `yes`, `no`, or `ask_user`
196
+ - tracking reason when recommended
197
+
198
+ ## Specific Language Extraction
199
+
200
+ For each keeper, extract the source's specific language mechanics in this
201
+ format:
202
+
203
+ ```text
204
+ Phrase pattern:
205
+ "<phrase or phrase shape>"
206
+
207
+ Role:
208
+ <what the words do: create contrast, compress pain, name the enemy, make timing
209
+ the villain, move from surface action to system outcome, etc.>
210
+
211
+ Source:
212
+ <author + URL>
213
+
214
+ Reusable forms:
215
+ - <template form 1>
216
+ - <template form 2>
217
+ - <template form 3>
218
+
219
+ Adapted to user's idea:
220
+ - <new phrase in the user's voice>
221
+
222
+ Do not copy:
223
+ <words, proof, joke, or company context that belongs to the source>
224
+ ```
225
+
226
+ Examples of the level of specificity required:
227
+
228
+ - "not because [obvious reason]. because [real reason]."
229
+ - "most teams stop at [surface step]."
230
+ - "the [thing] was [state]. you just never [saw/used/launched] it in time."
231
+ - "the maintenance is the tax."
232
+ - "[thing] does not fail in your head. it fails in the gap between [A] and [B]."
233
+ - "the important part is not that AI [surface action]. the important part is that [system outcome]."
234
+ - "one [constrained operator] can run the whole [machine] if [constraint]."
235
+
236
+ Never reduce this section to high-level labels like "contrarian" or
237
+ "founder story." Those labels are allowed only after the exact phrase mechanics
238
+ are captured.
239
+
240
+ ## Body Structure Extraction
241
+
242
+ For each keeper with full text available, extract the body in this format:
243
+
244
+ ```text
245
+ Body pattern:
246
+ <short name>
247
+
248
+ Source:
249
+ <author + URL>
250
+
251
+ Sequence:
252
+ 1. <paragraph/job 1>
253
+ 2. <paragraph/job 2>
254
+ 3. <paragraph/job 3>
255
+
256
+ Exact language moves:
257
+ - "<phrase shape or transition>"
258
+ - "<sentence pattern>"
259
+ - "<closing move>"
260
+
261
+ Adapted body move:
262
+ <how this would show up in the user's post>
263
+
264
+ Replicability:
265
+ high | medium | low
266
+ ```
267
+
268
+ If full text is unavailable, record `full_text_unavailable` and do not infer
269
+ body structure beyond the visible preview.
97
270
 
98
271
  ## Story Mechanism
99
272
 
@@ -124,3 +297,19 @@ Draft-ready output must block or retry when:
124
297
  ## Save Requirement
125
298
 
126
299
  Save the research with `mcp__sellable__save_hook_research` before drafting.
300
+
301
+ The saved research must contain enough detail to support a user-visible
302
+ `Research Learning Report`: source examples, full adapted hook blocks, exact
303
+ phrase patterns, body structures, rejected examples, tracked-person
304
+ recommendations, and gold-standard recommendations.
305
+
306
+ If the user approves a creator/person as a recurring inspiration source, also
307
+ call `mcp__sellable__upsert_engage_tracked_person` with:
308
+
309
+ - `name`: display name
310
+ - `linkedinUrl`: canonical LinkedIn profile URL
311
+ - `reason`: concise lane and why they are worth tracking
312
+
313
+ Do not track a person only because one post was viral. Track them when they have
314
+ repeated strong posts, unusually high topic fit, or the user explicitly says
315
+ they like them.
@@ -59,6 +59,9 @@ Hook research files must preserve:
59
59
  - author/profile URLs
60
60
  - engagement totals
61
61
  - full-text availability
62
+ - source hook preview measurements, including text basis, char count including
63
+ newlines, physical/content line counts, longest nonblank line, blank-line
64
+ visual risk, and mobile/desktop preview budget status
62
65
  - extracted hook patterns
63
66
  - selected hook basis
64
67
 
@@ -67,7 +70,8 @@ Draft files must preserve:
67
70
  - source idea ID
68
71
  - hook research ID
69
72
  - draft body
70
- - validation receipt
73
+ - validation receipt, including LinkedIn preview pass/warn/fail status and
74
+ compact fallback when the selected hook carries a warning
71
75
  - status: `draft`, `ready`, or `needs_revision`
72
76
 
73
77
  Published files must preserve:
@@ -32,7 +32,9 @@ Each candidate should include:
32
32
  - hook text
33
33
  - source pattern
34
34
  - score
35
- - char count and first-line / first-two-line preview measurements
35
+ - char count including newlines and first-line / first-two-line preview measurements
36
+ - physical line count, content line count, longest nonblank line, and blank-line risk
37
+ - `previewBudgetStatus`: `pass`, `warn`, or `fail`
36
38
  - mobile and desktop preview fit
37
39
  - proof/story dependency
38
40
  - AI-tell risk
@@ -52,25 +54,44 @@ After the first draft:
52
54
 
53
55
  ## LinkedIn Preview Audit
54
56
 
55
- Audit the selected hook and top candidates against conservative LinkedIn preview budgets:
57
+ Audit the selected hook and top candidates against conservative LinkedIn
58
+ preview budgets. LinkedIn does not publish exact "see more" cutoff rules, and
59
+ rendering varies by device, app version, font, media, and line break. This audit
60
+ is a mobile-first safety gate, not a claim about an official LinkedIn limit.
56
61
 
57
- - mobile first line: 35-45 chars
58
- - mobile first two lines: 80-100 chars
59
- - desktop first line: 70-90 chars
60
- - desktop first two lines: 140-180 chars
62
+ Use:
63
+
64
+ - `pass`: hook is <= 110 chars including newlines, every nonblank line is <= 45 chars, and the hook's core point lands before likely truncation.
65
+ - `warn`: hook is 111-140 chars including newlines, any nonblank line is 46-55 chars, or blank lines create visual-line risk. Blank lines are allowed, but they count as physical lines.
66
+ - `fail`: hook is > 140 chars including newlines, any nonblank line is > 55 chars, or the hook's point depends on text after likely truncation.
67
+
68
+ Desktop preview usually has more room. Still record desktop fit, but never let
69
+ desktop fit compensate for a mobile `fail`.
61
70
 
62
71
  Record:
63
72
 
64
73
  - `charCount`
74
+ - `charCountIncludingNewlines`
65
75
  - `firstLineChars`
66
76
  - `firstTwoLinesChars`
77
+ - `physicalLineCount`
78
+ - `contentLineCount`
79
+ - `longestNonblankLineChars`
80
+ - `blankLineCountBeforeFold`
81
+ - `blankLineVisualRisk`
82
+ - `corePointBeforeLikelyTruncation`
83
+ - `previewBudgetStatus`
67
84
  - `mobilePreviewFit`
68
85
  - `desktopPreviewFit`
69
86
  - `lineCountEstimate`
70
87
  - `truncationRisk`
71
88
  - `rewriteIfTruncated`
89
+ - `compactFallback` when `previewBudgetStatus` is `warn`
72
90
 
73
- LinkedIn rendering varies by device and line break, so this is a planning audit. If the hook only works after likely truncation, rewrite it.
91
+ If the hook only works after likely truncation, rewrite it. A draft cannot be
92
+ `ready` with `previewBudgetStatus: fail`. A draft may be `ready` with
93
+ `previewBudgetStatus: warn` only when the warning is explicit, usually because
94
+ the user prefers blank-line rhythm, and the receipt includes a compact fallback.
74
95
 
75
96
  ## Simplifier / Concrete-Language Audit
76
97
 
@@ -45,6 +45,11 @@ message assets through Sellable MCP tools:
45
45
  After candidate generation and revision, and before returning `ready`, load
46
46
  `create-campaign-v2-validation` through Sellable MCP as the final gate.
47
47
 
48
+ Both live and dry runs must produce or validate `message-validation.md` with
49
+ the same quality gates. The selected winner must pass the `Concrete Language Audit`:
50
+ identify and replace or cut abstract verbs, abstract nouns, abstract adjectives,
51
+ abstract adverbs, and cliches before the message is treated as ready.
52
+
48
53
  Never reconstruct that branch from `brief.md`, `lead-review.md`,
49
54
  `lead-sample.json`, `lead-filter.md`, `message-validation.md`, local files, or
50
55
  direct database reads. If any required message asset cannot be loaded through MCP