-
Notifications
You must be signed in to change notification settings - Fork 0
fix(sales): block retired offer copy in outbound send packages #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
df7f9a4
623724c
647f4d2
8948f88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,10 +76,13 @@ try { | |
| } | ||
|
|
||
| const { roots, strict } = config; | ||
| const outboundFiles = new Set(["next-message.md", "send-package.md", "outreach.md", "reply-package.md", "call-booked-package.md", "close-package.md"]); | ||
| const outboundFiles = new Set(["next-message.md", "send-package.md", "recording-notes.md", "outreach.md", "reply-package.md", "call-booked-package.md", "close-package.md"]); | ||
| const optOutPattern = /\b(reply no|do not follow up|unsubscribe|opt out|ignore me)\b/i; | ||
| const placeholderPattern = /\[(?:add Loom link|link|specific fault|Name)\]|Here is the Loom:\s*$/i; | ||
| const salesPlaceholderPattern = /\badd (?:meeting link|payment link|call time)\b/i; | ||
| // Retired broad-agency offers must never project into an outbound send package. | ||
| // Mirrors the canonical retired-ask guard in export-recording-rehearsal-check.mjs. | ||
| const retiredOfferPattern = /7[-\s]day (?:site|website) revenue (?:leak|fault) (?:fix )?sprint|tangible revenue (?:leak|fault) sprint|30[-\s]day action plan|growth desk|three pages|\$\s?500\b/i; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When recording notes contain variants such as Useful? React with 👍 / 👎. |
||
|
|
||
| function walk(path) { | ||
| if (!existsSync(path)) return []; | ||
|
|
@@ -124,6 +127,10 @@ for (const file of files) { | |
| findings.push({ file, rule: "sales package still has meeting/payment placeholders" }); | ||
| } | ||
|
|
||
| if (["send-package.md", "recording-notes.md"].includes(filename) && retiredOfferPattern.test(content)) { | ||
| findings.push({ file, rule: "outbound package sells a retired offer" }); | ||
| } | ||
|
Comment on lines
+130
to
+132
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an operator follows the documented Useful? React with 👍 / 👎. |
||
|
|
||
| if (filename === "outreach.md" && !optOutPattern.test(content)) { | ||
| warnings.push({ file, rule: "template outreach lacks opt-out language" }); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,6 +92,16 @@ If useful, I can run a 7-Day Site Revenue Fault Sprint with a 30-day action plan | |
| } | ||
| dnm(loomPackage, retiredOfferPattern) | ||
|
|
||
| const sendResult = spawnSync(process.execPath, [sp("prepare-prospect-send.mjs"), legacyProspectPath, "https://www.loom.com/share/1234567890abcdef1234567890abcdef", "--approved", "--force"], {cwd: repositoryRoot, encoding: "utf8"}) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Running this test invokes Useful? React with 👍 / 👎. |
||
| eq(sendResult.status, 0, `send prep failed: ${sendResult.stderr}`) | ||
| const sendPackage = readFileSync(join(legacyProspectPath, "send-package.md"), "utf8") | ||
| mat(sendPackage, /The Website Correction/) | ||
| mat(sendPackage, /one highest-leverage page/) | ||
| for (const forbiddenOutcome of NO_GUARANTEE_OUTCOMES) { | ||
| mat(sendPackage, new RegExp(forbiddenOutcome.replace("-", "[- ]"), "i")) | ||
| } | ||
| dnm(sendPackage, retiredOfferPattern) | ||
|
|
||
| console.log("Active offer projection checks passed.") | ||
| } finally { | ||
| rmSync(testRoot, {recursive: true, force: true}) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,6 +48,23 @@ try { | |
| assert.notEqual(result.status, 0) | ||
| assert(output(result).findings.some(finding => finding.rule === "send package still has placeholders")) | ||
|
|
||
| // The send package must never sell a retired broad-agency offer, and | ||
| // recording-notes.md is embedded verbatim into the send package. | ||
| for (const [file, content] of [ | ||
| ["send-package.md", `${packageHeader}7-Day Site Revenue Fault Sprint with a 30-day action plan. ${optOut}`], | ||
| ["recording-notes.md", `# Recording Notes\n\n## Quality Notes\n\n- Clean ask: If useful, I can run a Tangible Revenue Fault Sprint with a 30-day action plan for $500.\n`] | ||
|
Comment on lines
+54
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the loop reaches Useful? React with 👍 / 👎. |
||
| ]) { | ||
| writeFileSync(join(prospect, file), content) | ||
| result = run() | ||
| assert.notEqual(result.status, 0, `expected retired-offer failure for ${file}`) | ||
| assert(output(result).findings.some(finding => finding.rule === "outbound package sells a retired offer")) | ||
| } | ||
|
|
||
| writeFileSync(join(prospect, "send-package.md"), `${packageHeader}The Website Correction, one highest-leverage page, 14-day implementation tracking. ${optOut}`) | ||
| writeFileSync(join(prospect, "recording-notes.md"), `# Recording Notes\n\n## Quality Notes\n\n- Clean ask: If useful, I can run a human-reviewed The Website Correction on this one highest-leverage page with a measurement plan.\n`) | ||
| result = run() | ||
| assert.equal(result.status, 0, "canonical send package must not trigger the retired-offer rule") | ||
|
|
||
| result = run(["--roots=alternate"]) | ||
| assert.equal(result.status, 0) | ||
| assert.equal(output(result).filesScanned, 1) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this expression treats standalone
three pagesand$500as retired offers and is tested against the entire send package or recording-notes file, legitimate prospect evidence such as “pricing starts at $500” or “navigation spans three pages” makessend:checkfail with “sells a retired offer.” The rehearsal guard this is intended to mirror scopes the test to the ask; this gate should likewise inspect the clean-ask/message section or require those generic terms to appear with retired-offer context.Useful? React with 👍 / 👎.