@sellable/mcp 0.1.137 → 0.1.139

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.
@@ -0,0 +1,344 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { CORE_MEMORY_PATHS, resolveIdentityConfigsDir, } from "./identity-memory.js";
4
+ export function applyIdentityCompilerProposal(proposal) {
5
+ const configsDir = proposal.configsDir ?? resolveIdentityConfigsDir();
6
+ const changed = new Set();
7
+ for (const section of proposal.generatedSections ?? []) {
8
+ if (upsertGeneratedSection(configsDir, section.relativePath, section.key, section.markdown)) {
9
+ changed.add(section.relativePath);
10
+ }
11
+ }
12
+ upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.proofLedger, "Proof Ledger", proposal.proofEntries, (entry) => stableKey("proof", [entry.key, entry.claim, entry.source]).toLowerCase(), (entry) => renderBlock(`Claim: ${entry.claim.trim()}`, {
13
+ Source: entry.source,
14
+ Confidence: entry.confidence,
15
+ "Allowed use": entry.allowedUse,
16
+ Privacy: entry.privacy,
17
+ Notes: entry.notes,
18
+ }), changed);
19
+ upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.winsLedger, "Wins Ledger", proposal.winsEntries, (entry) => stableKey("win", [entry.key, entry.title, entry.source]), (entry) => renderBlock(`Win: ${entry.title.trim()}`, {
20
+ "Proof type": entry.proofType,
21
+ Outcome: entry.outcome,
22
+ Source: entry.source,
23
+ Permission: entry.permission,
24
+ Confidence: entry.confidence,
25
+ "Public/private safety": entry.publicSafety,
26
+ "Allowed use": entry.allowedUse,
27
+ Notes: entry.notes,
28
+ }), changed);
29
+ upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.storyBank, "Story Bank", proposal.storyEntries, (entry) => stableKey("story", [entry.key, entry.title, entry.source]), (entry) => renderBlock(`Story: ${entry.title.trim()}`, {
30
+ Setup: entry.setup,
31
+ Moment: entry.moment,
32
+ Lesson: entry.lesson,
33
+ Source: entry.source,
34
+ Contexts: entry.contexts,
35
+ Privacy: entry.privacy,
36
+ Notes: entry.notes,
37
+ }), changed);
38
+ upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.answerBank, "Answer Bank", proposal.answerEntries, (entry) => stableKey("answer", [
39
+ entry.key,
40
+ entry.question,
41
+ entry.topicTags,
42
+ entry.source,
43
+ ]), (entry) => renderBlock(`Question: ${entry.question.trim()}`, {
44
+ Answer: entry.answer,
45
+ Topics: entry.topicTags,
46
+ Source: entry.source,
47
+ "Use contexts": entry.useContexts,
48
+ Confidence: entry.confidence,
49
+ Privacy: entry.privacy,
50
+ "Derived memory links": entry.derivedMemoryLinks,
51
+ }), changed);
52
+ upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.decisionRules, "Decision Rules", proposal.decisionRules, (entry) => stableKey("rule", [entry.key, entry.title, entry.source]), (entry) => renderBlock(`Rule: ${entry.title.trim()}`, {
53
+ Rule: entry.rule,
54
+ Source: entry.source,
55
+ }), changed);
56
+ upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.antiAiStyle, "Anti-AI Writing Style", proposal.antiAiBans, (entry) => stableKey("anti-ai", [entry.key, entry.pattern, entry.context]), (entry) => renderBlock(`Banned Pattern: ${entry.pattern.trim()}`, {
57
+ Rule: entry.rule,
58
+ Context: entry.context,
59
+ Source: entry.source,
60
+ }), changed);
61
+ upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.changeLog, "Change Log", proposal.changeLogEntries, (entry) => stableKey("change", [entry.key, entry.date, entry.changed, entry.source]), (entry) => renderBlock(`Change: ${entry.date} - ${entry.changed.trim()}`, {
62
+ Source: entry.source,
63
+ "New rule": entry.newRule,
64
+ }), changed);
65
+ if (upsertTableRows(configsDir, "core/transcripts/INDEX.md", "Transcript Index", [
66
+ "Title",
67
+ "Source",
68
+ "Date",
69
+ "Speakers",
70
+ "Target Speaker",
71
+ "Topics",
72
+ "Context Mode",
73
+ "Sample Type",
74
+ "Sensitivity",
75
+ "Key",
76
+ ], (proposal.transcriptIndexRows ?? []).map((row) => {
77
+ const key = stableKey("transcript", [
78
+ row.key,
79
+ row.sourcePath,
80
+ row.date,
81
+ row.targetSpeaker,
82
+ row.topics,
83
+ row.title,
84
+ ]);
85
+ return {
86
+ key,
87
+ row: {
88
+ Title: row.title,
89
+ Source: row.sourcePath,
90
+ Date: row.date ?? "",
91
+ Speakers: row.speakers ?? "",
92
+ "Target Speaker": row.targetSpeaker ?? "",
93
+ Topics: row.topics ?? "",
94
+ "Context Mode": row.contextMode ?? "",
95
+ "Sample Type": row.sampleType ?? "",
96
+ Sensitivity: row.sensitivity ?? "",
97
+ Key: key,
98
+ },
99
+ };
100
+ }))) {
101
+ changed.add("core/transcripts/INDEX.md");
102
+ }
103
+ for (const topic of proposal.topicTranscripts ?? []) {
104
+ const topicSlug = normalizeSlug(topic.topicSlug);
105
+ const relativePath = `core/transcripts/topics/${topicSlug}.md`;
106
+ const key = stableKey("topic", [
107
+ topic.key,
108
+ topicSlug,
109
+ topic.sourcePath,
110
+ topic.targetSpeaker,
111
+ topic.title,
112
+ ]);
113
+ const block = renderBlock(`Transcript: ${topic.title.trim()}`, {
114
+ Source: topic.sourcePath,
115
+ "Target speaker": topic.targetSpeaker,
116
+ Excerpt: topic.excerpt,
117
+ });
118
+ if (upsertGeneratedSection(configsDir, relativePath, key, block, "Topic Transcript")) {
119
+ changed.add(relativePath);
120
+ }
121
+ }
122
+ const referenceRowsByPath = new Map();
123
+ for (const reference of proposal.referenceRows ?? []) {
124
+ const relativePath = reference.indexPath ??
125
+ `core/references/${normalizeSlug(reference.category ?? "misc")}/INDEX.md`;
126
+ const key = stableKey("reference", [
127
+ reference.key,
128
+ reference.source,
129
+ reference.title,
130
+ reference.copiedPath,
131
+ ]);
132
+ const rows = referenceRowsByPath.get(relativePath) ?? [];
133
+ rows.push({
134
+ key,
135
+ row: {
136
+ Title: reference.title,
137
+ "Source Path/URL": reference.source,
138
+ "Copied Path": reference.copiedPath ?? "indexed only",
139
+ Tags: reference.tags ?? "",
140
+ "Why It Matters": reference.whyItMatters ?? "",
141
+ "Usage Contexts": reference.usageContexts ?? "",
142
+ "Last Reviewed": reference.lastReviewed ?? "",
143
+ Key: key,
144
+ },
145
+ });
146
+ referenceRowsByPath.set(relativePath, rows);
147
+ }
148
+ for (const [relativePath, rows] of referenceRowsByPath) {
149
+ if (upsertTableRows(configsDir, relativePath, "Reference Index", [
150
+ "Title",
151
+ "Source Path/URL",
152
+ "Copied Path",
153
+ "Tags",
154
+ "Why It Matters",
155
+ "Usage Contexts",
156
+ "Last Reviewed",
157
+ "Key",
158
+ ], rows)) {
159
+ changed.add(relativePath);
160
+ }
161
+ }
162
+ return {
163
+ configsDir,
164
+ filesChanged: [...changed].sort(),
165
+ };
166
+ }
167
+ function upsertBlockEntries(configsDir, relativePath, title, entries, keyForEntry, markdownForEntry, changed) {
168
+ if (!entries?.length)
169
+ return;
170
+ const seen = new Set();
171
+ let fileChanged = false;
172
+ for (const entry of entries) {
173
+ const key = keyForEntry(entry);
174
+ if (seen.has(key))
175
+ continue;
176
+ seen.add(key);
177
+ fileChanged =
178
+ upsertGeneratedSection(configsDir, relativePath, key, markdownForEntry(entry), title) || fileChanged;
179
+ }
180
+ if (fileChanged) {
181
+ changed.add(relativePath);
182
+ }
183
+ }
184
+ function upsertGeneratedSection(configsDir, relativePath, key, markdown, defaultTitle) {
185
+ const cleanKey = key.trim();
186
+ const filePath = resolveConfigPath(configsDir, relativePath);
187
+ const existing = readMarkdown(filePath);
188
+ const base = existing || (defaultTitle ? `# ${defaultTitle}\n` : "");
189
+ const block = generatedBlock(cleanKey, markdown);
190
+ const blockPattern = new RegExp(`${escapeRegExp(startMarker(cleanKey))}\\n[\\s\\S]*?\\n${escapeRegExp(END_MARKER)}`, "m");
191
+ const next = blockPattern.test(base)
192
+ ? base.replace(blockPattern, block)
193
+ : appendMarkdown(base, block);
194
+ return writeMarkdownIfChanged(filePath, next);
195
+ }
196
+ function upsertTableRows(configsDir, relativePath, title, headers, rows) {
197
+ if (rows.length === 0)
198
+ return false;
199
+ const filePath = resolveConfigPath(configsDir, relativePath);
200
+ const existing = readMarkdown(filePath) || `# ${title}\n`;
201
+ const table = parseFirstMarkdownTable(existing);
202
+ const existingRows = table ? table.rows : [];
203
+ const rowByKey = new Map();
204
+ for (const row of existingRows) {
205
+ const key = row.Key ||
206
+ stableKey("row", headers.map((header) => row[header] ?? ""));
207
+ rowByKey.set(key, row);
208
+ }
209
+ const seenInputKeys = new Set();
210
+ for (const { key, row } of rows) {
211
+ if (seenInputKeys.has(key))
212
+ continue;
213
+ seenInputKeys.add(key);
214
+ rowByKey.set(key, row);
215
+ }
216
+ const nextTable = buildMarkdownTable(headers, [...rowByKey.values()]);
217
+ const next = table
218
+ ? `${existing.slice(0, table.start)}${nextTable}${existing.slice(table.end)}`
219
+ : appendMarkdown(existing, nextTable);
220
+ return writeMarkdownIfChanged(filePath, next);
221
+ }
222
+ const END_MARKER = "<!-- sellable:generated:end -->";
223
+ function startMarker(key) {
224
+ return `<!-- sellable:generated:start key="${key}" -->`;
225
+ }
226
+ function generatedBlock(key, markdown) {
227
+ return `${startMarker(key)}\n${markdown.trim()}\n${END_MARKER}`;
228
+ }
229
+ function renderBlock(title, fields) {
230
+ const lines = [`## ${title.trim()}`];
231
+ for (const [label, value] of Object.entries(fields)) {
232
+ const cleanValue = value?.trim();
233
+ if (!cleanValue)
234
+ continue;
235
+ lines.push(`- ${label}: ${cleanValue}`);
236
+ }
237
+ return lines.join("\n");
238
+ }
239
+ function parseFirstMarkdownTable(markdown) {
240
+ const lines = markdown.split(/\n/);
241
+ let offset = 0;
242
+ for (let index = 0; index < lines.length - 1; index++) {
243
+ const line = lines[index];
244
+ const next = lines[index + 1];
245
+ if (isTableLine(line) && /^\|[\s\-:|]+\|$/.test(next.trim())) {
246
+ const headers = splitTableLine(line);
247
+ const rows = [];
248
+ let tableEndLine = index + 2;
249
+ for (; tableEndLine < lines.length; tableEndLine++) {
250
+ const rowLine = lines[tableEndLine];
251
+ if (!isTableLine(rowLine))
252
+ break;
253
+ const cells = splitTableLine(rowLine);
254
+ const row = {};
255
+ headers.forEach((header, headerIndex) => {
256
+ row[header] = cells[headerIndex] ?? "";
257
+ });
258
+ rows.push(row);
259
+ }
260
+ const start = offset;
261
+ const end = lines.slice(0, tableEndLine).join("\n").length;
262
+ return { start, end, headers, rows };
263
+ }
264
+ offset += line.length + 1;
265
+ }
266
+ return undefined;
267
+ }
268
+ function buildMarkdownTable(headers, rows) {
269
+ const header = `| ${headers.join(" | ")} |`;
270
+ const separator = `| ${headers.map(() => "---").join(" | ")} |`;
271
+ const body = rows.map((row) => `| ${headers.map((headerName) => tableCell(row[headerName])).join(" | ")} |`);
272
+ return [header, separator, ...body].join("\n");
273
+ }
274
+ function isTableLine(line) {
275
+ const trimmed = line.trim();
276
+ return trimmed.startsWith("|") && trimmed.endsWith("|");
277
+ }
278
+ function splitTableLine(line) {
279
+ return line
280
+ .trim()
281
+ .slice(1, -1)
282
+ .split("|")
283
+ .map((cell) => cell.trim());
284
+ }
285
+ function tableCell(value) {
286
+ return (value ?? "").replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
287
+ }
288
+ function readMarkdown(filePath) {
289
+ if (!fs.existsSync(filePath))
290
+ return "";
291
+ return fs.readFileSync(filePath, "utf-8");
292
+ }
293
+ function writeMarkdownIfChanged(filePath, markdown) {
294
+ const next = `${markdown.trimEnd()}\n`;
295
+ if (readMarkdown(filePath) === next)
296
+ return false;
297
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
298
+ fs.writeFileSync(filePath, next);
299
+ return true;
300
+ }
301
+ function appendMarkdown(existing, addition) {
302
+ const trimmedExisting = existing.trimEnd();
303
+ if (!trimmedExisting)
304
+ return addition;
305
+ return `${trimmedExisting}\n\n${addition}`;
306
+ }
307
+ function resolveConfigPath(configsDir, relativePath) {
308
+ const cleanRelativePath = relativePath.replace(/\\/g, "/").trim();
309
+ if (cleanRelativePath === "" ||
310
+ cleanRelativePath.startsWith("/") ||
311
+ cleanRelativePath.split("/").includes("..")) {
312
+ throw new Error(`Invalid identity compiler path: ${relativePath}`);
313
+ }
314
+ const root = path.resolve(configsDir);
315
+ const resolved = path.resolve(root, cleanRelativePath);
316
+ const relative = path.relative(root, resolved);
317
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
318
+ throw new Error(`Invalid identity compiler path: ${relativePath}`);
319
+ }
320
+ return resolved;
321
+ }
322
+ function stableKey(prefix, parts) {
323
+ const normalizedParts = parts
324
+ .filter((part) => Boolean(part?.trim()))
325
+ .map(normalizeKeyPart)
326
+ .filter(Boolean);
327
+ return `${normalizeKeyPart(prefix)}:${normalizedParts.join(":") || "entry"}`;
328
+ }
329
+ function normalizeSlug(value) {
330
+ return normalizeKeyPart(value) || "misc";
331
+ }
332
+ function normalizeKeyPart(value) {
333
+ return value
334
+ .normalize("NFKD")
335
+ .toLowerCase()
336
+ .replace(/['"]/g, "")
337
+ .replace(/https?:\/\//g, "")
338
+ .replace(/[^a-z0-9]+/g, "-")
339
+ .replace(/^-+|-+$/g, "")
340
+ .slice(0, 120);
341
+ }
342
+ function escapeRegExp(value) {
343
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
344
+ }
@@ -0,0 +1,49 @@
1
+ export declare const CORE_MEMORY_PATHS: {
2
+ readonly identity: "core/about-me.md";
3
+ readonly company: "core/my-company.md";
4
+ readonly antiAiStyle: "core/anti-ai-writing-style.md";
5
+ readonly proofLedger: "core/proof-ledger.md";
6
+ readonly winsLedger: "core/wins-ledger.md";
7
+ readonly storyBank: "core/story-bank.md";
8
+ readonly answerBank: "core/answer-bank.md";
9
+ readonly contextModes: "core/context-modes.md";
10
+ readonly decisionRules: "core/decision-rules.md";
11
+ readonly changeLog: "core/change-log.md";
12
+ readonly transcripts: "core/transcripts";
13
+ readonly references: "core/references";
14
+ };
15
+ export interface IdentityMemorySource {
16
+ relativePath: string;
17
+ exists: boolean;
18
+ }
19
+ export interface IdentityMemoryChunk {
20
+ markdown: string;
21
+ source: IdentityMemorySource;
22
+ }
23
+ export interface IdentityMemoryDirectory {
24
+ source: IdentityMemorySource;
25
+ index?: IdentityMemoryChunk;
26
+ }
27
+ export interface IdentityMemory {
28
+ identity?: IdentityMemoryChunk;
29
+ company?: IdentityMemoryChunk;
30
+ antiAiStyle?: IdentityMemoryChunk;
31
+ proofLedger?: IdentityMemoryChunk;
32
+ winsLedger?: IdentityMemoryChunk;
33
+ storyBank?: IdentityMemoryChunk;
34
+ answerBank?: IdentityMemoryChunk;
35
+ contextModes?: IdentityMemoryChunk;
36
+ decisionRules?: IdentityMemoryChunk;
37
+ changeLog?: IdentityMemoryChunk;
38
+ transcripts?: IdentityMemoryDirectory;
39
+ references?: IdentityMemoryDirectory;
40
+ }
41
+ export interface ReadIdentityMemoryOptions {
42
+ /**
43
+ * Core identity/company memory is workspace-level in v1. Connected sender
44
+ * IDs remain engage compatibility overlays, not separate identity profiles.
45
+ */
46
+ connectedSenderId?: string;
47
+ }
48
+ export declare function resolveIdentityConfigsDir(): string;
49
+ export declare function readIdentityMemory(_options?: ReadIdentityMemoryOptions): IdentityMemory;
@@ -0,0 +1,89 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ export const CORE_MEMORY_PATHS = {
5
+ identity: "core/about-me.md",
6
+ company: "core/my-company.md",
7
+ antiAiStyle: "core/anti-ai-writing-style.md",
8
+ proofLedger: "core/proof-ledger.md",
9
+ winsLedger: "core/wins-ledger.md",
10
+ storyBank: "core/story-bank.md",
11
+ answerBank: "core/answer-bank.md",
12
+ contextModes: "core/context-modes.md",
13
+ decisionRules: "core/decision-rules.md",
14
+ changeLog: "core/change-log.md",
15
+ transcripts: "core/transcripts",
16
+ references: "core/references",
17
+ };
18
+ export function resolveIdentityConfigsDir() {
19
+ const candidates = [];
20
+ if (process.env.SELLABLE_CONFIGS_DIR) {
21
+ candidates.push(path.resolve(process.env.SELLABLE_CONFIGS_DIR));
22
+ }
23
+ if (process.argv[1]) {
24
+ candidates.push(path.resolve(path.dirname(process.argv[1]), "../../.sellable/configs"));
25
+ }
26
+ candidates.push(path.resolve(process.cwd(), ".sellable/configs"));
27
+ candidates.push(path.resolve(os.homedir(), ".sellable/configs"));
28
+ for (const candidate of candidates) {
29
+ if (fs.existsSync(candidate)) {
30
+ return candidate;
31
+ }
32
+ }
33
+ return candidates[candidates.length - 1];
34
+ }
35
+ export function readIdentityMemory(_options = {}) {
36
+ const configsDir = resolveIdentityConfigsDir();
37
+ const memory = {};
38
+ const fields = [
39
+ { key: "identity", relativePath: CORE_MEMORY_PATHS.identity },
40
+ { key: "company", relativePath: CORE_MEMORY_PATHS.company },
41
+ { key: "antiAiStyle", relativePath: CORE_MEMORY_PATHS.antiAiStyle },
42
+ { key: "proofLedger", relativePath: CORE_MEMORY_PATHS.proofLedger },
43
+ { key: "winsLedger", relativePath: CORE_MEMORY_PATHS.winsLedger },
44
+ { key: "storyBank", relativePath: CORE_MEMORY_PATHS.storyBank },
45
+ { key: "answerBank", relativePath: CORE_MEMORY_PATHS.answerBank },
46
+ { key: "contextModes", relativePath: CORE_MEMORY_PATHS.contextModes },
47
+ { key: "decisionRules", relativePath: CORE_MEMORY_PATHS.decisionRules },
48
+ { key: "changeLog", relativePath: CORE_MEMORY_PATHS.changeLog },
49
+ ];
50
+ for (const field of fields) {
51
+ const chunk = readMarkdownChunk(configsDir, field.relativePath);
52
+ if (chunk) {
53
+ memory[field.key] = chunk;
54
+ }
55
+ }
56
+ const transcripts = readDirectory(configsDir, CORE_MEMORY_PATHS.transcripts);
57
+ if (transcripts) {
58
+ memory.transcripts = transcripts;
59
+ }
60
+ const references = readDirectory(configsDir, CORE_MEMORY_PATHS.references);
61
+ if (references) {
62
+ memory.references = references;
63
+ }
64
+ return memory;
65
+ }
66
+ function readMarkdownChunk(configsDir, relativePath) {
67
+ const fullPath = path.join(configsDir, relativePath);
68
+ if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
69
+ return undefined;
70
+ }
71
+ return {
72
+ markdown: fs.readFileSync(fullPath, "utf-8"),
73
+ source: { relativePath, exists: true },
74
+ };
75
+ }
76
+ function readDirectory(configsDir, relativePath) {
77
+ const fullPath = path.join(configsDir, relativePath);
78
+ if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isDirectory()) {
79
+ return undefined;
80
+ }
81
+ const directory = {
82
+ source: { relativePath, exists: true },
83
+ };
84
+ const index = readMarkdownChunk(configsDir, `${relativePath}/INDEX.md`);
85
+ if (index) {
86
+ directory.index = index;
87
+ }
88
+ return directory;
89
+ }
package/dist/index-dev.js CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
@@ -2,13 +2,13 @@ import { copySenderConfig, getEngageMemory, migrateFlatConfigs, recordProvenSear
2
2
  export const engageMemoryToolDefinitions = [
3
3
  {
4
4
  name: "get_engage_memory",
5
- description: "Load engage memory from .sellable/configs/: style guide, proven search keywords, and tracked people. All data lives in readable markdown files the user can also edit directly. When senderId is provided, reads from senders/{senderId}/ with flat-path fallback.",
5
+ description: "Load backward-compatible engage memory from .sellable/configs/: style guide, proven search keywords, tracked people, plus optional core identity/company memory. All data lives in readable markdown files the user can also edit directly. When senderId is provided, reads compatibility overrides from senders/{senderId}/ with flat-path fallback.",
6
6
  inputSchema: {
7
7
  type: "object",
8
8
  properties: {
9
9
  senderId: {
10
10
  type: "string",
11
- description: "Optional sender ID. When provided, reads per-sender configs from senders/{senderId}/. Falls back to flat configs if sender dir doesn't exist.",
11
+ description: "Optional sender ID. When provided, reads per-sender compatibility configs from senders/{senderId}/. Falls back to flat configs for missing sender files.",
12
12
  },
13
13
  },
14
14
  required: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.137",
3
+ "version": "0.1.139",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -78,7 +78,7 @@ Parallel execution:
78
78
 
79
79
  Config tools (built-in, free):
80
80
 
81
- - `Read` - Load voice configs, outbound rules, and cross-skill insights from `.sellable/`
81
+ - `Read` - Load core identity/company memory, proof/wins ledgers, anti-AI audit, outbound rules, and cross-skill insights from `.sellable/`
82
82
 
83
83
  Research tools (free - user's Claude tokens):
84
84
 
@@ -111,8 +111,14 @@ Parallel batch (6 calls):
111
111
  ├── get_rows(rowId) → prospect data
112
112
  ├── get_message_prompt() → REPLY framework
113
113
  ├── get_subskill_prompt({ subskillName: "research-prospect" }) → research protocol
114
+ ├── Read .sellable/configs/core/about-me.md → core identity memory
115
+ ├── Read .sellable/configs/core/my-company.md → company truth
116
+ ├── Read .sellable/configs/core/context-modes.md → outbound context mode
117
+ ├── Read .sellable/configs/core/proof-ledger.md → safe proof claims
118
+ ├── Read .sellable/configs/core/wins-ledger.md → social proof and wins
119
+ ├── Read .sellable/configs/core/anti-ai-writing-style.md → anti-AI audit
114
120
  ├── Read .sellable/configs/writing/outbound.md → outbound rules
115
- ├── Read .sellable/configs/writing/styleguide-core.md → sender voice
121
+ ├── Read .sellable/configs/writing/styleguide-core.md → legacy sender voice fallback
116
122
  └── Read .sellable/insights/cross-skill.md → cross-skill learnings
117
123
  ```
118
124
 
@@ -161,6 +167,8 @@ Extract the following from the REPLY framework, configs, and research into a sin
161
167
  - Core thesis: {1 line from styleguide-core.md}
162
168
  - Signature phrases: {3-5 key phrases from styleguide-core.md}
163
169
  - Offer: {1 line from campaign brief}
170
+ - Core memory: {1-2 lines from core/about-me.md, my-company.md, and context-modes.md}
171
+ - Safe proof: {best usable proof from proof-ledger.md or campaign brief}
164
172
 
165
173
  ### Message Rules
166
174
  - Structure: observation about THEM → why it's relevant → soft ask
@@ -271,7 +279,7 @@ After all 10 angle agents return, **you (the Opus orchestrator) act as the final
271
279
  - Does the bridge prove the sender has a unique perspective? The message should make the prospect think "this person actually gets it" — not "this person read my LinkedIn headline."
272
280
  - Is there at least one sentence that would make the prospect stop scrolling? Something surprising, specific, or contrarian.
273
281
  - Kill any line that reads like a template. If you've seen it in a cold outreach before, rewrite it.
274
- 5. **Voice-check** — does it sound like the sender? Use their signature phrases from `styleguide-core.md` (the FULL file, not the condensed brief).
282
+ 5. **Voice-check** — does it sound like the sender? Use core identity/context modes first, then legacy signature phrases from `styleguide-core.md` as fallback/source material.
275
283
  6. **Full 12-gate check using the REPLY framework** — this is the only place the full framework is applied. All gates must pass on the combined message. The angle agents drafted creatively; you enforce compliance.
276
284
  7. **Polish** — one final pass for flow, tone, and length
277
285
 
@@ -379,7 +387,7 @@ After saving (and optionally approving), return control for:
379
387
 
380
388
  ## Critical Rules
381
389
 
382
- 1. **Load configs first** — read `.sellable/configs/writing/outbound.md` and `styleguide-core.md` before crafting. The message must sound like the sender.
390
+ 1. **Load core memory first** — read `.sellable/configs/core/about-me.md`, `my-company.md`, `context-modes.md`, `proof-ledger.md`, `wins-ledger.md`, and `anti-ai-writing-style.md` before crafting. Then read `.sellable/configs/writing/outbound.md` and legacy `styleguide-core.md` as fallback/source material. The message must sound like the sender and stay inside safe proof.
383
391
  2. **Load the framework first** — always call `get_message_prompt()` before crafting
384
392
  3. **Always fan out 10 angles** — never skip the angle exploration step. More angles = better final message. This is the core differentiator.
385
393
  4. **Launch agents in ONE message, no `run_in_background`** — all 10 angle agents (and all 6 research agents) must be launched as regular Task calls in a single message. They run concurrently and all results return together. Never use `run_in_background: true` — it causes notification spam.
@@ -392,6 +400,7 @@ After saving (and optionally approving), return control for:
392
400
  11. **Research deeply** — this is the value (5+ min per message). Compute time is the quality knob.
393
401
  12. Credits awareness — note when using LinkedIn tools
394
402
  13. **Use cross-skill insights** — if `insights/cross-skill.md` shows what hooks/angles resonate, use those patterns in the angle exploration
403
+ 14. **Anti-AI/proof final gate** — before showing a message, check `anti-ai-writing-style.md`, `proof-ledger.md`, `wins-ledger.md`, and `context-modes.md`; remove unsupported proof, stale wins, fake-depth reframes, and context-inappropriate voice.
395
404
 
396
405
  ## Developer Note
397
406
 
@@ -40,8 +40,9 @@ Example: "In a 15-minute call, we will audit your top 10 accounts and show where
40
40
  - **Lead with:** [Hook based on their posts/news]
41
41
  - **Bridge to:** [How it connects to their pain]
42
42
  - **Avoid:** [Generic phrases to skip - "best-in-class", "industry-leading", etc.]
43
- - **Voice:** [Reference sender's voice from `.sellable/configs/writing/styleguide-core.md` if loaded]
43
+ - **Voice:** [Reference core memory / composed engage memory first. Use legacy writing memory only as fallback.]
44
44
  - **Outbound rules:** [Reference `.sellable/configs/writing/outbound.md` if loaded -- observation -> relevance -> ask]
45
+ - **Proof boundaries:** [Reference `.sellable/configs/core/proof-ledger.md` and `.sellable/configs/core/wins-ledger.md` if loaded. Do not upgrade or invent claims.]
45
46
 
46
47
  ## Do NOT Target
47
48