autark-cli 0.5.0 → 0.5.1
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/autark.mjs +22 -18
- package/package.json +1 -1
package/autark.mjs
CHANGED
|
@@ -831,48 +831,52 @@ async function mailLint(rest) {
|
|
|
831
831
|
'the key insight', 'what this means is', 'the answer is', 'the trick is',
|
|
832
832
|
'that\'s exactly', 'that\'s precisely', 'that\'s the whole point',
|
|
833
833
|
]
|
|
834
|
+
const WHY = {
|
|
835
|
+
'structure-word': 'AI-fingerprint vocabulary. Humans don\'t write "structurally different" or "fundamentally" in cold email; LLMs reach for them when hedging or sounding profound. Describe the thing directly instead.',
|
|
836
|
+
'em-dash': 'Em-dashes in cold outreach are a strong AI tell. Real people use commas, periods, or a new line break.',
|
|
837
|
+
'body-url': 'URLs mid-paragraph read like an A/B test variant or a campaign send. The product link belongs in the signature anchor; if it\'s anywhere else, the recipient classifies the email as automated.',
|
|
838
|
+
'too-many-questions': 'Asking >1 question is hedging. Pick the one question that, if answered, gives you the most signal. The rest can wait.',
|
|
839
|
+
'compound-question': 'Compound a/b questions ("are you X or Y...") read as AI hedging across alternatives. Humans ask one specific thing. Drop the "or" and pick the more specific half.',
|
|
840
|
+
'too-long': 'Cold outreach over 400 chars (~4-6 short lines) reads as a pitch deck, not a peer note. If you wrote more, you\'re explaining instead of asking. Cut to the single sharpest sentence.',
|
|
841
|
+
'no-anchor-sig': 'Without a clickable name in the signature, the recipient has to type your name into Google to figure out who you are. The HTML <a href> / markdown [name](url) sig is the single biggest "who is this person?" mitigator. Plain "Kushal" alone is a tell.',
|
|
842
|
+
}
|
|
834
843
|
|
|
835
844
|
const violations = []
|
|
836
845
|
const lower = body.toLowerCase()
|
|
837
846
|
|
|
838
|
-
// 1. structure-words
|
|
839
847
|
for (const w of STRUCTURE_WORDS) {
|
|
840
|
-
if (lower.includes(w)) violations.push({ rule: 'structure-word', detail: w })
|
|
848
|
+
if (lower.includes(w)) violations.push({ rule: 'structure-word', detail: w, why: WHY['structure-word'] })
|
|
841
849
|
}
|
|
842
|
-
|
|
843
|
-
if (/—/.test(body)) violations.push({ rule: 'em-dash', detail: 'use a period or comma instead' })
|
|
844
|
-
// 3. URLs in body (allow only inside markdown anchor [text](url) or HTML <a href>)
|
|
850
|
+
if (/—/.test(body)) violations.push({ rule: 'em-dash', detail: 'em-dash detected', why: WHY['em-dash'] })
|
|
845
851
|
const urlMatches = body.match(/https?:\/\/[^\s<>"')]+/g) || []
|
|
846
852
|
for (const u of urlMatches) {
|
|
847
853
|
const idx = body.indexOf(u)
|
|
848
854
|
const before = body.slice(Math.max(0, idx - 40), idx)
|
|
849
|
-
const after = body.slice(idx + u.length, idx + u.length + 10)
|
|
850
855
|
const insideMdAnchor = /\]\(\s*$/.test(before)
|
|
851
856
|
const insideHtmlAnchor = /href\s*=\s*["'][^"']*$/.test(before) || /<a\b[^>]*$/.test(before)
|
|
852
|
-
if (!insideMdAnchor && !insideHtmlAnchor) violations.push({ rule: 'body-url', detail: u })
|
|
857
|
+
if (!insideMdAnchor && !insideHtmlAnchor) violations.push({ rule: 'body-url', detail: u, why: WHY['body-url'] })
|
|
853
858
|
}
|
|
854
|
-
// 4. multiple question marks (more than one ? in the body)
|
|
855
859
|
const qmarks = (body.match(/\?/g) || []).length
|
|
856
|
-
if (qmarks > 1) violations.push({ rule: 'too-many-questions', detail: `${qmarks} question marks
|
|
857
|
-
// 5. compound question with "or" connecting clauses
|
|
860
|
+
if (qmarks > 1) violations.push({ rule: 'too-many-questions', detail: `${qmarks} question marks`, why: WHY['too-many-questions'] })
|
|
858
861
|
const compoundQ = /\b(are|is|do|does|did|will|would|can|could|should|have|has)\b[^?]{0,200}\bor\b[^?]{0,200}\?/i
|
|
859
|
-
if (compoundQ.test(body)) violations.push({ rule: 'compound-question', detail: 'a/b question with "or"
|
|
860
|
-
// 6. body length (excluding signature heuristically)
|
|
862
|
+
if (compoundQ.test(body)) violations.push({ rule: 'compound-question', detail: 'a/b question with "or"', why: WHY['compound-question'] })
|
|
861
863
|
const sigSplit = body.split(/\n\s*\n(?:best|thanks|cheers|—|-)[\s,]*\n/i)[0] || body
|
|
862
864
|
const len = sigSplit.trim().length
|
|
863
|
-
if (len > 400) violations.push({ rule: 'too-long', detail: `${len} chars
|
|
864
|
-
// 7. signature presence: should have `- <name>` and an anchor (md or html)
|
|
865
|
+
if (len > 400) violations.push({ rule: 'too-long', detail: `${len} chars`, why: WHY['too-long'] })
|
|
865
866
|
const hasMdAnchor = /\[[^\]]+\]\(https?:\/\/[^)]+\)/.test(body)
|
|
866
867
|
const hasHtmlAnchor = /<a\b[^>]*href\s*=\s*["']https?:\/\/[^"']+["'][^>]*>[^<]+<\/a>/i.test(body)
|
|
867
|
-
if (!hasMdAnchor && !hasHtmlAnchor) {
|
|
868
|
-
violations.push({ rule: 'no-anchor-sig', detail: 'signature must include a clickable link via [name](url) or <a href="url">name</a>' })
|
|
869
|
-
}
|
|
868
|
+
if (!hasMdAnchor && !hasHtmlAnchor) violations.push({ rule: 'no-anchor-sig', detail: 'no clickable name in signature', why: WHY['no-anchor-sig'] })
|
|
870
869
|
|
|
871
870
|
if (violations.length === 0) {
|
|
872
871
|
console.log(JSON.stringify({ clean: true, length: len }, null, 2))
|
|
873
872
|
return
|
|
874
873
|
}
|
|
875
|
-
console.error(JSON.stringify({
|
|
874
|
+
console.error(JSON.stringify({
|
|
875
|
+
clean: false,
|
|
876
|
+
length: len,
|
|
877
|
+
violations,
|
|
878
|
+
next: 'Rewrite the draft so each violation\'s "why" is internalized, not just regex-fixed. If you can\'t make it pass after 2 rewrites, abstain — re-read ~/.claude/skills/outreach/SKILL.md AI-tells section and ask whether you should send this at all.',
|
|
879
|
+
}, null, 2))
|
|
876
880
|
process.exit(1)
|
|
877
881
|
}
|
|
878
882
|
|
package/package.json
CHANGED