Created
February 23, 2026 21:34
-
-
Save JosXa/7d94f32e80e2b67503f0756b2672599d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // import type { Plugin } from "@opencode-ai/plugin" | |
| // // AI writing patterns detection and humanization | |
| // const AI_PATTERNS = { | |
| // content: { | |
| // significance: /\b(stands|serves) as\b|\btestament\b|\bpivotal (moment|role)\b|\bevolving landscape\b|\bindelible mark\b/gi, | |
| // notability: /\bcited in\b.*\b(NYT|BBC|Times|Post)\b|\bactive social media presence\b/gi, | |
| // superficial: /\b(highlighting|underscoring|emphasizing|ensuring|reflecting|symbolizing|showcasing)\b[^.]{0,50}\.\s*$/gim, | |
| // promotional: /\b(nestled|breathtaking|vibrant|stunning|must-visit|renowned|boasts)\b/gi, | |
| // vague: /\b(Experts|Industry reports|Observers|Some critics)\s+(believe|argue|suggest)\b/gi, | |
| // challenges: /Despite (its|these) (challenges|obstacles).*continues to (thrive|grow|excel)/gi, | |
| // }, | |
| // language: { | |
| // aiVocab: /\b(Additionally|crucial|delve|enhance|fostering|garner|landscape|pivotal|showcase|tapestry|testament|underscore|vibrant)\b/gi, | |
| // copulaAvoid: /\b(serves as|stands as|boasts|features)\s+[a-z]/gi, | |
| // negativeParallel: /(It's|This is) not (just|only|merely)\s+\w+,\s+(it's|they're)/gi, | |
| // ruleOfThree: /\b(\w+),\s+(\w+),\s+and\s+(\w+)\b/g, | |
| // }, | |
| // style: { | |
| // emDash: /—/g, | |
| // boldface: /\*\*(\w+)\*\*/g, | |
| // inlineHeader: /^\s*[-*]\s+\*\*([^*]+):\*\*\s+/gm, | |
| // titleCase: /^#+\s+([A-Z][a-z]+\s+){2,}[A-Z][a-z]+$/gm, | |
| // emoji: /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]/gu, | |
| // curlyQuotes: /[""'']/g, | |
| // }, | |
| // communication: { | |
| // chatbot: /\b(I hope this helps|Let me know|Would you like|Of course!|Certainly!)\b/gi, | |
| // cutoff: /\b(as of|up to my last|while (specific )?details are (limited|scarce)|based on available)\b/gi, | |
| // sycophantic: /\b(Great question|You're absolutely right|Excellent point)\b/gi, | |
| // }, | |
| // filler: { | |
| // phrases: /\b(in order to|due to the fact that|at this point in time|in the event that|has the ability to)\b/gi, | |
| // hedging: /\b(could potentially possibly|might possibly|may potentially)\b/gi, | |
| // generic: /\b(The future looks bright|exciting times (lie )?ahead|major step in the right direction)\b/gi, | |
| // }, | |
| // } | |
| // interface PatternMatch { | |
| // category: string | |
| // pattern: string | |
| // matches: string[] | |
| // count: number | |
| // } | |
| // function analyzeText(text: string): PatternMatch[] { | |
| // const results: PatternMatch[] = [] | |
| // for (const [category, patterns] of Object.entries(AI_PATTERNS)) { | |
| // for (const [patternName, regex] of Object.entries(patterns)) { | |
| // const matches = text.match(regex) | |
| // if (matches && matches.length > 0) { | |
| // results.push({ | |
| // category, | |
| // pattern: patternName, | |
| // matches: [...new Set(matches)], | |
| // count: matches.length, | |
| // }) | |
| // } | |
| // } | |
| // } | |
| // return results.sort((a, b) => b.count - a.count) | |
| // } | |
| // function generateReport(analysis: PatternMatch[]): string { | |
| // if (analysis.length === 0) { | |
| // return "✅ No obvious AI patterns detected" | |
| // } | |
| // let report = `🤖 AI Writing Patterns Detected (${analysis.length} types):\n\n` | |
| // for (const match of analysis) { | |
| // report += `• ${match.category}/${match.pattern} (${match.count}x)\n` | |
| // const preview = match.matches.slice(0, 3).join(", ") | |
| // report += ` Examples: ${preview}${match.matches.length > 3 ? "..." : ""}\n` | |
| // } | |
| // report += `\nRun \`opencode run --command "humanize" "<your-text>"\` to get humanized version.` | |
| // return report | |
| // } | |
| // export const HumanizerPlugin: Plugin = async (ctx) => { | |
| // const toolExecuteBefore = async (input: any) => { | |
| // // Only analyze text from read/write/edit operations | |
| // const textTools = ["read", "write", "edit"] | |
| // if (!textTools.includes(input.tool?.toLowerCase())) return | |
| // const content = input?.args?.content ?? input?.args?.newString | |
| // if (!content || typeof content !== "string") return | |
| // // Only analyze substantial text (>200 chars) | |
| // if (content.length < 200) return | |
| // const analysis = analyzeText(content) | |
| // // Only report if significant patterns found (3+ types or 5+ total matches) | |
| // const totalMatches = analysis.reduce((sum, a) => sum + a.count, 0) | |
| // if (analysis.length < 3 && totalMatches < 5) return | |
| // const report = generateReport(analysis) | |
| // // Log to console for visibility | |
| // console.log("\n" + report + "\n") | |
| // } | |
| // const eventHandler = async (input: any) => { | |
| // // Could hook into session events if needed | |
| // if (input.event?.type === "message.created") { | |
| // const content = input.event?.properties?.message?.content?.[0]?.text | |
| // if (!content || content.length < 200) return | |
| // const analysis = analyzeText(content) | |
| // const totalMatches = analysis.reduce((sum, a) => sum + a.count, 0) | |
| // if (analysis.length >= 3 || totalMatches >= 5) { | |
| // // Could inject a warning into the conversation here | |
| // // For now, just console log | |
| // console.log("\n⚠️ AI patterns detected in message\n") | |
| // } | |
| // } | |
| // } | |
| // return { | |
| // "tool.execute.before": toolExecuteBefore, | |
| // event: eventHandler, | |
| // } | |
| // } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment