A tool for parsing traffic on the jetstream and applying a moderation workstream based on regexp based rules

Fix: Remove unused import and formatting

- Removed an unused import statement in `src/tests/moderation.test.ts` -
Standardized formatting across several files to improve readability.
Fix: Remove unused imports and formatting

This commit removes an unused import and applies minor formatting
changes for improved code readability.

Skywatch 88ab0a30 f9aa1bd8

+15 -1
.claude/.agents/code-reviewer.md
··· 4 4 tools: Bash, Glob, Grep, LS, Read, WebFetch, TodoWrite, WebSearch, BashOutput, KillBash, mcp__git-mcp-server__git_add, mcp__git-mcp-server__git_branch, mcp__git-mcp-server__git_checkout, mcp__git-mcp-server__git_cherry_pick, mcp__git-mcp-server__git_clean, mcp__git-mcp-server__git_clear_working_dir, mcp__git-mcp-server__git_clone, mcp__git-mcp-server__git_commit, mcp__git-mcp-server__git_diff, mcp__git-mcp-server__git_fetch, mcp__git-mcp-server__git_init, mcp__git-mcp-server__git_log, mcp__git-mcp-server__git_merge, mcp__git-mcp-server__git_pull, mcp__git-mcp-server__git_push, mcp__git-mcp-server__git_rebase, mcp__git-mcp-server__git_remote, mcp__git-mcp-server__git_reset, mcp__git-mcp-server__git_set_working_dir, mcp__git-mcp-server__git_show, mcp__git-mcp-server__git_stash, mcp__git-mcp-server__git_status, mcp__git-mcp-server__git_tag, mcp__git-mcp-server__git_worktree, mcp__git-mcp-server__git_wrapup_instructions 5 5 color: green 6 6 --- 7 + 7 8 **All imports in this document should be treated as if they were in the main prompt file.** 8 9 9 10 You are a comprehensive code review agent examining a piece of code that has been created by the main agent that calls you. Your role is to provide thorough, constructive feedback that ensures code quality, maintainability, and alignment with established patterns and decisions, while also suggesting ways to improve both the code in question but also our stored memory bank for future iterations. ··· 13 14 ## Review Methodology 14 15 15 16 ### Phase 1: Context Gathering 17 + 16 18 1. Check the repository's Git status, both staged and unstaged 17 19 2. Examine the full diff to understand what's changing 18 - 4. Search the codebase for similar patterns or implementations that might be reusable 20 + 3. Search the codebase for similar patterns or implementations that might be reusable 19 21 20 22 ### Phase 2: Comprehensive Review 23 + 21 24 #### Code Quality & Patterns 25 + 22 26 - **Compilation**: For all touched packages and apps, make sure the code compiles and all tests pass 23 27 - **DRY Violations**: Search for similar code patterns elsewhere in the codebase 24 28 - **Consistency**: Does this follow established patterns in the project? ··· 26 30 - **Naming**: Are names clear, consistent, and follow project conventions? 27 31 28 32 #### Engineering Excellence 33 + 29 34 - **Error Handling**: How are errors caught, logged, and recovered from? 30 35 - **Edge Cases**: What happens with null/undefined/empty/malformed inputs? 31 36 - **Performance**: Will this scale with realistic data volumes? ··· 36 41 - Our system is entirely built around a dependency injector; we can create (and make DRY and reusable) stub implementations of our services in order to allow for more integrated tests. Recommend this proactively. 37 42 38 43 #### Integration & Dependencies 44 + 39 45 - **Codebase Fit**: Does this integrate well with existing modules? 40 46 - **Dependencies**: Are we adding unnecessary dependencies when existing utilities could work? 41 47 - **Side Effects**: What other parts of the system might this affect? ··· 45 51 Identify knowledge gaps and opportunities: 46 52 47 53 #### Flag for Documentation 54 + 48 55 - **New Techniques**: "This retry mechanism is well-implemented and reusable. 49 56 - **Missing Decisions**: "Choosing WebSockets over SSE here seems like an architectural decision that should be recorded" 50 57 - **Complex Logic**: "This order processing logic should be captured as a detail entry" ··· 55 62 Structure your review as: 56 63 57 64 ### Summary 65 + 58 66 Brief overview of the changes and overall assessment 59 67 60 68 ### Critical Issues 🔴 69 + 61 70 Must-fix problems (security, bugs, broken functionality) 62 71 63 72 ### Important Suggestions 🟡 73 + 64 74 Should-fix issues (performance, maintainability, patterns) 65 75 66 76 ### Minor Improvements 🟢 77 + 67 78 Nice-to-have enhancements (style, optimization, clarity) 68 79 69 80 ### Knowledge Management 81 + 70 82 - **Alignment Check**: How this aligns with existing knowledge 71 83 - **Documentation Opportunities**: What should be added to Basic Memory 72 84 - **Updates Needed**: What existing entries need updating 73 85 74 86 ### Code Reuse Opportunities 87 + 75 88 Specific suggestions for using existing code instead of reimplementing 76 89 77 90 ## Review Tone 78 91 79 92 Be constructive and specific: 93 + 80 94 - ✅ "Consider using the cursor pagination technique from `src/api/utils.ts:142` instead" 81 95 - ❌ "This pagination is wrong" 82 96
+15 -1
.claude/agents/code-reviewer.md
··· 4 4 tools: Bash, Glob, Grep, LS, Read, WebFetch, TodoWrite, WebSearch, BashOutput, KillBash, mcp__git-mcp-server__git_add, mcp__git-mcp-server__git_branch, mcp__git-mcp-server__git_checkout, mcp__git-mcp-server__git_cherry_pick, mcp__git-mcp-server__git_clean, mcp__git-mcp-server__git_clear_working_dir, mcp__git-mcp-server__git_clone, mcp__git-mcp-server__git_commit, mcp__git-mcp-server__git_diff, mcp__git-mcp-server__git_fetch, mcp__git-mcp-server__git_init, mcp__git-mcp-server__git_log, mcp__git-mcp-server__git_merge, mcp__git-mcp-server__git_pull, mcp__git-mcp-server__git_push, mcp__git-mcp-server__git_rebase, mcp__git-mcp-server__git_remote, mcp__git-mcp-server__git_reset, mcp__git-mcp-server__git_set_working_dir, mcp__git-mcp-server__git_show, mcp__git-mcp-server__git_stash, mcp__git-mcp-server__git_status, mcp__git-mcp-server__git_tag, mcp__git-mcp-server__git_worktree, mcp__git-mcp-server__git_wrapup_instructions 5 5 color: green 6 6 --- 7 + 7 8 **All imports in this document should be treated as if they were in the main prompt file.** 8 9 9 10 You are a comprehensive code review agent examining a piece of code that has been created by the main agent that calls you. Your role is to provide thorough, constructive feedback that ensures code quality, maintainability, and alignment with established patterns and decisions, while also suggesting ways to improve both the code in question but also our stored memory bank for future iterations. ··· 13 14 ## Review Methodology 14 15 15 16 ### Phase 1: Context Gathering 17 + 16 18 1. Check the repository's Git status, both staged and unstaged 17 19 2. Examine the full diff to understand what's changing 18 - 4. Search the codebase for similar patterns or implementations that might be reusable 20 + 3. Search the codebase for similar patterns or implementations that might be reusable 19 21 20 22 ### Phase 2: Comprehensive Review 23 + 21 24 #### Code Quality & Patterns 25 + 22 26 - **Compilation**: For all touched packages and apps, make sure the code compiles and all tests pass 23 27 - **DRY Violations**: Search for similar code patterns elsewhere in the codebase 24 28 - **Consistency**: Does this follow established patterns in the project? ··· 26 30 - **Naming**: Are names clear, consistent, and follow project conventions? 27 31 28 32 #### Engineering Excellence 33 + 29 34 - **Error Handling**: How are errors caught, logged, and recovered from? 30 35 - **Edge Cases**: What happens with null/undefined/empty/malformed inputs? 31 36 - **Performance**: Will this scale with realistic data volumes? ··· 36 41 - Our system is entirely built around a dependency injector; we can create (and make DRY and reusable) stub implementations of our services in order to allow for more integrated tests. Recommend this proactively. 37 42 38 43 #### Integration & Dependencies 44 + 39 45 - **Codebase Fit**: Does this integrate well with existing modules? 40 46 - **Dependencies**: Are we adding unnecessary dependencies when existing utilities could work? 41 47 - **Side Effects**: What other parts of the system might this affect? ··· 45 51 Identify knowledge gaps and opportunities: 46 52 47 53 #### Flag for Documentation 54 + 48 55 - **New Techniques**: "This retry mechanism is well-implemented and reusable. 49 56 - **Missing Decisions**: "Choosing WebSockets over SSE here seems like an architectural decision that should be recorded" 50 57 - **Complex Logic**: "This order processing logic should be captured as a detail entry" ··· 55 62 Structure your review as: 56 63 57 64 ### Summary 65 + 58 66 Brief overview of the changes and overall assessment 59 67 60 68 ### Critical Issues 🔴 69 + 61 70 Must-fix problems (security, bugs, broken functionality) 62 71 63 72 ### Important Suggestions 🟡 73 + 64 74 Should-fix issues (performance, maintainability, patterns) 65 75 66 76 ### Minor Improvements 🟢 77 + 67 78 Nice-to-have enhancements (style, optimization, clarity) 68 79 69 80 ### Knowledge Management 81 + 70 82 - **Alignment Check**: How this aligns with existing knowledge 71 83 - **Documentation Opportunities**: What should be added to Basic Memory 72 84 - **Updates Needed**: What existing entries need updating 73 85 74 86 ### Code Reuse Opportunities 87 + 75 88 Specific suggestions for using existing code instead of reimplementing 76 89 77 90 ## Review Tone 78 91 79 92 Be constructive and specific: 93 + 80 94 - ✅ "Consider using the cursor pagination technique from `src/api/utils.ts:142` instead" 81 95 - ❌ "This pagination is wrong" 82 96
+1 -3
.claude/settings.local.json
··· 16 16 "ask": [] 17 17 }, 18 18 "enableAllProjectMcpServers": true, 19 - "enabledMcpjsonServers": [ 20 - "git-mcp-server" 21 - ] 19 + "enabledMcpjsonServers": ["git-mcp-server"] 22 20 }
+2 -2
.github/workflows/ci.yml
··· 18 18 - name: Setup Node.js 19 19 uses: actions/setup-node@v4 20 20 with: 21 - node-version: '20' 22 - cache: 'npm' 21 + node-version: "20" 22 + cache: "npm" 23 23 24 24 - name: Install dependencies 25 25 run: npm ci
+1 -2
CLAUDE.md
··· 13 13 These steps help ensure quality and prevent common issues: 14 14 15 15 1. Context Check: Start by confirming the model and re-reading relevant 16 - documentation. If there is a PRD.md, review it to understand the requirements and constraints. 16 + documentation. If there is a PRD.md, review it to understand the requirements and constraints. 17 17 2. Plan First: Explain your approach before implementing changes. Output your plan to PLAN.md in addition to explaining it to the user. 18 18 3. Seek Alignment: Confirm the approach makes sense before coding 19 19 4. Focused Changes: Keep modifications minimal and targeted ··· 26 26 Provide responses that are intelligent and slightly humorous (WITHOUT being cringe), while maintaining a casual and modern tone. 27 27 28 28 Before responding, take a moment to consider how to best address the user's input while adhering to your personality traits and communication style. 29 - 30 29 31 30 When formulating your response, follow these guidelines: 32 31
+3 -2
src/developing_checks.md
··· 1 1 # How to build checks for skywatch-automod 2 2 3 3 ## Introduction 4 + 4 5 Constants.ts defines three types of types of checks: `HANDLE_CHECKS`, `POST_CHECKS`, and `PROFILE_CHECKS`. 5 6 6 7 For each check, users need to define a set of regular expressions that will be used to match against the content of the post, handle, or profile. A maximal example of a check is as follows: ··· 16 17 commentOnly: false, // Poorly named, if true, will generate an account level comment from flagged posts, rather than a report. Intended for use when reportOnly is false, and on posts only where the flag may generate a high volume of reports.. 17 18 check: new RegExp("example", "i"), // Regular expression to match against the content 18 19 whitelist: new RegExp("example.com", "i"), // Optional, regular expression to whitelist content 19 - ignoredDIDs: ["did:plc:example"] // Optional, array of DIDs to ignore if they match the check. Useful for folks who reclaim words. 20 - } 20 + ignoredDIDs: ["did:plc:example"], // Optional, array of DIDs to ignore if they match the check. Useful for folks who reclaim words. 21 + }, 21 22 ]; 22 23 ``` 23 24
+8 -2
src/metrics.ts
··· 16 16 res.send(metrics); 17 17 }) 18 18 .catch((ex: unknown) => { 19 - logger.error({ process: "METRICS", error: (ex as Error).message }, "Error serving metrics"); 19 + logger.error( 20 + { process: "METRICS", error: (ex as Error).message }, 21 + "Error serving metrics", 22 + ); 20 23 res.status(500).end((ex as Error).message); 21 24 }); 22 25 }); 23 26 24 27 export const startMetricsServer = (port: number, host = "127.0.0.1") => { 25 28 return app.listen(port, host, () => { 26 - logger.info({ process: "METRICS", host, port }, "Metrics server is listening"); 29 + logger.info( 30 + { process: "METRICS", host, port }, 31 + "Metrics server is listening", 32 + ); 27 33 }); 28 34 };
+14 -14
src/rules/account/tests/age.test.ts
··· 37 37 38 38 import { agent } from "../../../agent.js"; 39 39 import { logger } from "../../../logger.js"; 40 - import { 41 - createAccountLabel, 42 - checkAccountLabels, 43 - } from "../../../moderation.js"; 40 + import { createAccountLabel, checkAccountLabels } from "../../../moderation.js"; 44 41 import { GLOBAL_ALLOW } from "../../../constants.js"; 45 42 46 43 describe("Account Age Module", () => { ··· 214 211 215 212 await checkAccountAge({ 216 213 actorDid: "did:plc:inwindow", 217 - replyToDid: "did:plc:monitored", 214 + replyToDid: "did:plc:monitored", 218 215 replyingDid: "did:plc:inwindow", 219 216 atURI: TEST_REPLY_URI, 220 217 time: TEST_TIME, ··· 236 233 237 234 await checkAccountAge({ 238 235 actorDid: "did:plc:beforewindow", 239 - replyToDid: "did:plc:monitored", 236 + replyToDid: "did:plc:monitored", 240 237 replyingDid: "did:plc:beforewindow", 241 238 atURI: TEST_REPLY_URI, 242 239 time: TEST_TIME, ··· 254 251 255 252 await checkAccountAge({ 256 253 actorDid: "did:plc:afterwindow", 257 - replyToDid: "did:plc:monitored", 254 + replyToDid: "did:plc:monitored", 258 255 replyingDid: "did:plc:afterwindow", 259 256 atURI: TEST_REPLY_URI, 260 257 time: TEST_TIME, ··· 272 269 273 270 await checkAccountAge({ 274 271 actorDid: "did:plc:startofwindow", 275 - replyToDid: "did:plc:monitored", 272 + replyToDid: "did:plc:monitored", 276 273 replyingDid: "did:plc:startofwindow", 277 274 atURI: TEST_REPLY_URI, 278 275 time: TEST_TIME, ··· 290 287 291 288 await checkAccountAge({ 292 289 actorDid: "did:plc:endofwindow", 293 - replyToDid: "did:plc:monitored", 290 + replyToDid: "did:plc:monitored", 294 291 replyingDid: "did:plc:endofwindow", 295 292 atURI: TEST_REPLY_URI, 296 293 time: TEST_TIME, ··· 479 476 480 477 expect(createAccountLabel).not.toHaveBeenCalled(); 481 478 expect(logger.debug).toHaveBeenCalledWith( 482 - { process: "ACCOUNT_AGE", did: "did:plc:allowlisted", atURI: TEST_REPLY_URI }, 479 + { 480 + process: "ACCOUNT_AGE", 481 + did: "did:plc:allowlisted", 482 + atURI: TEST_REPLY_URI, 483 + }, 483 484 "Global allowlisted DID", 484 485 ); 485 486 }); ··· 613 614 replyingDid: "did:plc:newaccount", 614 615 atURI: TEST_REPLY_URI, 615 616 time: TEST_TIME, 616 - replyToPostURI: "at://did:plc:monitored/app.bsky.feed.post/specificpost", 617 + replyToPostURI: 618 + "at://did:plc:monitored/app.bsky.feed.post/specificpost", 617 619 }); 618 620 619 621 expect(createAccountLabel).toHaveBeenCalledWith( ··· 763 765 764 766 it("should label account when quoting a monitored post URI", async () => { 765 767 ACCOUNT_AGE_CHECKS.push({ 766 - monitoredPostURIs: [ 767 - "at://did:plc:target/app.bsky.feed.post/targeted", 768 - ], 768 + monitoredPostURIs: ["at://did:plc:target/app.bsky.feed.post/targeted"], 769 769 anchorDate: "2025-10-15", 770 770 maxAgeDays: 7, 771 771 label: "brigading-suspect",
+2 -5
src/rules/facets/facets.ts
··· 27 27 ): Promise<void> => { 28 28 // Check allowlist 29 29 if (FACET_SPAM_ALLOWLIST.includes(did)) { 30 - logger.debug( 31 - { process: "FACET_SPAM", did, atURI }, 32 - "Allowlisted DID", 33 - ); 30 + logger.debug({ process: "FACET_SPAM", did, atURI }, "Allowlisted DID"); 34 31 return; 35 32 } 36 33 ··· 46 43 for (const facet of facets) { 47 44 // Only check mentions for spam detection 48 45 const mentionFeature = facet.features.find( 49 - (feature) => feature.$type === "app.bsky.richtext.facet#mention" 46 + (feature) => feature.$type === "app.bsky.richtext.facet#mention", 50 47 ); 51 48 52 49 if (mentionFeature && "did" in mentionFeature) {
+102 -33
src/rules/facets/tests/facets.test.ts
··· 1 1 import { describe, it, expect, vi, beforeEach } from "vitest"; 2 - import { checkFacetSpam, FACET_SPAM_THRESHOLD, FACET_SPAM_LABEL, FACET_SPAM_COMMENT, FACET_SPAM_ALLOWLIST } from "../facets.js"; 2 + import { 3 + checkFacetSpam, 4 + FACET_SPAM_THRESHOLD, 5 + FACET_SPAM_LABEL, 6 + FACET_SPAM_COMMENT, 7 + FACET_SPAM_ALLOWLIST, 8 + } from "../facets.js"; 3 9 import { Facet } from "../../../types.js"; 4 10 5 11 // Mock dependencies ··· 47 53 const facets: Facet[] = [ 48 54 { 49 55 index: { byteStart: 0, byteEnd: 10 }, 50 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 56 + features: [ 57 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 58 + ], 51 59 }, 52 60 ]; 53 61 ··· 61 69 const facets: Facet[] = [ 62 70 { 63 71 index: { byteStart: 0, byteEnd: 10 }, 64 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 72 + features: [ 73 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 74 + ], 65 75 }, 66 76 { 67 77 index: { byteStart: 11, byteEnd: 20 }, 68 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }], 78 + features: [ 79 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }, 80 + ], 69 81 }, 70 82 { 71 83 index: { byteStart: 21, byteEnd: 30 }, 72 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user3" }], 84 + features: [ 85 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user3" }, 86 + ], 73 87 }, 74 88 ]; 75 89 ··· 84 98 const facets: Facet[] = [ 85 99 { 86 100 index: { byteStart: 0, byteEnd: 1 }, 87 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 101 + features: [ 102 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 103 + ], 88 104 }, 89 105 ]; 90 106 ··· 116 132 const facets: Facet[] = [ 117 133 { 118 134 index: { byteStart: 0, byteEnd: 10 }, 119 - features: [{ $type: "app.bsky.richtext.facet#link", uri: "https://example.com" }], 135 + features: [ 136 + { 137 + $type: "app.bsky.richtext.facet#link", 138 + uri: "https://example.com", 139 + }, 140 + ], 120 141 }, 121 142 { 122 143 index: { byteStart: 0, byteEnd: 10 }, 123 - features: [{ $type: "app.bsky.richtext.facet#link", uri: "https://example.org" }], 144 + features: [ 145 + { 146 + $type: "app.bsky.richtext.facet#link", 147 + uri: "https://example.org", 148 + }, 149 + ], 124 150 }, 125 151 ]; 126 152 ··· 135 161 const facets: Facet[] = [ 136 162 { 137 163 index: { byteStart: 0, byteEnd: 1 }, 138 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 164 + features: [ 165 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 166 + ], 139 167 }, 140 168 { 141 169 index: { byteStart: 0, byteEnd: 1 }, 142 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 170 + features: [ 171 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 172 + ], 143 173 }, 144 174 { 145 175 index: { byteStart: 0, byteEnd: 1 }, 146 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 176 + features: [ 177 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 178 + ], 147 179 }, 148 180 ]; 149 181 ··· 161 193 const facets: Facet[] = [ 162 194 { 163 195 index: { byteStart: 0, byteEnd: 1 }, 164 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 196 + features: [ 197 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 198 + ], 165 199 }, 166 200 { 167 201 index: { byteStart: 0, byteEnd: 1 }, 168 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }], 202 + features: [ 203 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }, 204 + ], 169 205 }, 170 206 ]; 171 207 ··· 175 211 expect(createAccountLabel).not.toHaveBeenCalled(); 176 212 expect(logger.debug).toHaveBeenCalledWith( 177 213 { process: "FACET_SPAM", did: TEST_DID, atURI: TEST_URI }, 178 - "Allowlisted DID" 214 + "Allowlisted DID", 179 215 ); 180 216 181 217 // Clean up ··· 188 224 const facets: Facet[] = [ 189 225 { 190 226 index: { byteStart: 0, byteEnd: 1 }, 191 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 227 + features: [ 228 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 229 + ], 192 230 }, 193 231 { 194 232 index: { byteStart: 0, byteEnd: 1 }, 195 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }], 233 + features: [ 234 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }, 235 + ], 196 236 }, 197 237 ]; 198 238 ··· 206 246 position: "0:1", 207 247 count: 2, 208 248 }, 209 - "Facet spam detected" 249 + "Facet spam detected", 210 250 ); 211 251 212 252 expect(createAccountLabel).toHaveBeenCalledWith( 213 253 TEST_DID, 214 254 FACET_SPAM_LABEL, 215 - `${TEST_TIME}: ${FACET_SPAM_COMMENT} - 2 unique mentions at position 0:1 in ${TEST_URI}` 255 + `${TEST_TIME}: ${FACET_SPAM_COMMENT} - 2 unique mentions at position 0:1 in ${TEST_URI}`, 216 256 ); 217 257 }); 218 258 ··· 220 260 // Simulates the example from facets.json with 100+ mentions at position 0:1 221 261 const facets: Facet[] = Array.from({ length: 100 }, (_, i) => ({ 222 262 index: { byteStart: 0, byteEnd: 1 }, 223 - features: [{ $type: "app.bsky.richtext.facet#mention", did: `did:plc:user${i}` }], 263 + features: [ 264 + { $type: "app.bsky.richtext.facet#mention", did: `did:plc:user${i}` }, 265 + ], 224 266 })); 225 267 226 268 await checkFacetSpam(TEST_DID, TEST_TIME, TEST_URI, facets); ··· 233 275 position: "0:1", 234 276 count: 100, 235 277 }), 236 - "Facet spam detected" 278 + "Facet spam detected", 237 279 ); 238 280 239 281 expect(createAccountLabel).toHaveBeenCalledOnce(); ··· 244 286 // First spam position 245 287 { 246 288 index: { byteStart: 0, byteEnd: 1 }, 247 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 289 + features: [ 290 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 291 + ], 248 292 }, 249 293 { 250 294 index: { byteStart: 0, byteEnd: 1 }, 251 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }], 295 + features: [ 296 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }, 297 + ], 252 298 }, 253 299 // Second spam position 254 300 { 255 301 index: { byteStart: 5, byteEnd: 10 }, 256 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user3" }], 302 + features: [ 303 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user3" }, 304 + ], 257 305 }, 258 306 { 259 307 index: { byteStart: 5, byteEnd: 10 }, 260 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user4" }], 308 + features: [ 309 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user4" }, 310 + ], 261 311 }, 262 312 ]; 263 313 ··· 271 321 const facets: Facet[] = [ 272 322 { 273 323 index: { byteStart: 0, byteEnd: 1 }, 274 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 324 + features: [ 325 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 326 + ], 275 327 }, 276 328 { 277 329 index: { byteStart: 0, byteEnd: 1 }, 278 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }], 330 + features: [ 331 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }, 332 + ], 279 333 }, 280 334 { 281 335 index: { byteStart: 0, byteEnd: 1 }, 282 - features: [{ $type: "app.bsky.richtext.facet#link", uri: "https://example.com" }], 336 + features: [ 337 + { 338 + $type: "app.bsky.richtext.facet#link", 339 + uri: "https://example.com", 340 + }, 341 + ], 283 342 }, 284 343 ]; 285 344 ··· 298 357 299 358 it("should use correct label and comment constants", () => { 300 359 expect(FACET_SPAM_LABEL).toBe("suspect-inauthentic"); 301 - expect(FACET_SPAM_COMMENT).toBe("Abusive facet usage detected (hidden mentions)"); 360 + expect(FACET_SPAM_COMMENT).toBe( 361 + "Abusive facet usage detected (hidden mentions)", 362 + ); 302 363 }); 303 364 }); 304 365 ··· 307 368 const facets: Facet[] = [ 308 369 { 309 370 index: { byteStart: 0, byteEnd: 5 }, 310 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 371 + features: [ 372 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 373 + ], 311 374 }, 312 375 { 313 376 index: { byteStart: 0, byteEnd: 10 }, 314 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }], 377 + features: [ 378 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }, 379 + ], 315 380 }, 316 381 ]; 317 382 ··· 325 390 const facets: Facet[] = [ 326 391 { 327 392 index: { byteStart: 1000000, byteEnd: 1000100 }, 328 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }], 393 + features: [ 394 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user1" }, 395 + ], 329 396 }, 330 397 { 331 398 index: { byteStart: 1000000, byteEnd: 1000100 }, 332 - features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }], 399 + features: [ 400 + { $type: "app.bsky.richtext.facet#mention", did: "did:plc:user2" }, 401 + ], 333 402 }, 334 403 ]; 335 404 ··· 338 407 expect(createAccountLabel).toHaveBeenCalledWith( 339 408 TEST_DID, 340 409 FACET_SPAM_LABEL, 341 - expect.stringContaining("1000000:1000100") 410 + expect.stringContaining("1000000:1000100"), 342 411 ); 343 412 }); 344 413 });
+1 -5
src/rules/handles/checkHandles.test.ts
··· 261 261 }); 262 262 263 263 it("should handle special characters in handles", async () => { 264 - await checkHandle( 265 - "did:plc:user1", 266 - "spam-!@#$%^&*()", 267 - Date.now(), 268 - ); 264 + await checkHandle("did:plc:user1", "spam-!@#$%^&*()", Date.now()); 269 265 270 266 expect(createAccountReport).toHaveBeenCalled(); 271 267 });
+1 -4
src/rules/handles/constants.example.ts
··· 62 62 reportAcct: false, 63 63 commentAcct: false, 64 64 toLabel: true, 65 - check: new RegExp( 66 - "[a-z]{2,}[0-9]{6,}|random.*?numbers.*?[0-9]{4,}", 67 - "i", 68 - ), 65 + check: new RegExp("[a-z]{2,}[0-9]{6,}|random.*?numbers.*?[0-9]{4,}", "i"), 69 66 whitelist: new RegExp("year[0-9]{4}", "i"), 70 67 ignoredDIDs: [ 71 68 "did:plc:example789", // Legitimate account with number pattern
+3 -1
src/rules/posts/tests/checkPosts.test.ts
··· 148 148 149 149 describe("URL shortener resolution", () => { 150 150 it("should resolve shortened URLs", async () => { 151 - const post = createMockPost({ text: "Check this out https://tinyurl.com/test123" }); 151 + const post = createMockPost({ 152 + text: "Check this out https://tinyurl.com/test123", 153 + }); 152 154 vi.mocked(getFinalUrl).mockResolvedValue("https://example.com/full-url"); 153 155 154 156 await checkPosts(post);
+7 -7
src/rules/profiles/tests/checkProfiles.test.ts
··· 454 454 }); 455 455 456 456 it("should not label profiles without matching display names", async () => { 457 - await checkDisplayName(mockDid, mockTime, "Normal User", mockDescription); 457 + await checkDisplayName( 458 + mockDid, 459 + mockTime, 460 + "Normal User", 461 + mockDescription, 462 + ); 458 463 459 464 expect(createAccountLabel).not.toHaveBeenCalledWith( 460 465 mockDid, ··· 491 496 it("should check language-specific patterns for matching languages", async () => { 492 497 vi.mocked(getLanguage).mockResolvedValue("eng"); 493 498 494 - await checkDisplayName( 495 - mockDid, 496 - mockTime, 497 - "hello world", 498 - "description", 499 - ); 499 + await checkDisplayName(mockDid, mockTime, "hello world", "description"); 500 500 501 501 // language-specific check has description: true, displayName: false 502 502 // so it won't match on displayName
+6 -22
src/tests/moderation.test.ts
··· 51 51 }, 52 52 }); 53 53 54 - const result = await checkAccountLabels( 55 - "did:plc:test123", 56 - "window-reply", 57 - ); 54 + const result = await checkAccountLabels("did:plc:test123", "window-reply"); 58 55 59 56 expect(result).toBe(true); 60 57 expect(agent.tools.ozone.moderation.getRepo).toHaveBeenCalledWith( ··· 62 59 { 63 60 headers: { 64 61 "atproto-proxy": "did:plc:moderator123#atproto_labeler", 65 - "atproto-accept-labelers": 66 - "did:plc:ar7c4by46qjdydhdevvrndac;redact", 62 + "atproto-accept-labelers": "did:plc:ar7c4by46qjdydhdevvrndac;redact", 67 63 }, 68 64 }, 69 65 ); ··· 76 72 }, 77 73 }); 78 74 79 - const result = await checkAccountLabels( 80 - "did:plc:test123", 81 - "window-reply", 82 - ); 75 + const result = await checkAccountLabels("did:plc:test123", "window-reply"); 83 76 84 77 expect(result).toBe(false); 85 78 }); ··· 91 84 }, 92 85 }); 93 86 94 - const result = await checkAccountLabels( 95 - "did:plc:test123", 96 - "window-reply", 97 - ); 87 + const result = await checkAccountLabels("did:plc:test123", "window-reply"); 98 88 99 89 expect(result).toBe(false); 100 90 }); ··· 104 94 data: {}, 105 95 }); 106 96 107 - const result = await checkAccountLabels( 108 - "did:plc:test123", 109 - "window-reply", 110 - ); 97 + const result = await checkAccountLabels("did:plc:test123", "window-reply"); 111 98 112 99 expect(result).toBe(false); 113 100 }); ··· 117 104 new Error("API Error"), 118 105 ); 119 106 120 - const result = await checkAccountLabels( 121 - "did:plc:test123", 122 - "window-reply", 123 - ); 107 + const result = await checkAccountLabels("did:plc:test123", "window-reply"); 124 108 125 109 expect(result).toBe(false); 126 110 expect(logger.error).toHaveBeenCalledWith(
+6 -9
src/utils/getLanguage.test.ts
··· 56 56 }); 57 57 58 58 it("should detect Japanese text", async () => { 59 - const text = "これは日本語のテストです。十分なテキストで言語を検出します。"; 59 + const text = 60 + "これは日本語のテストです。十分なテキストで言語を検出します。"; 60 61 const result = await getLanguage(text); 61 62 expect(result).toBe("jpn"); 62 63 }); ··· 118 119 119 120 describe("trimming behavior", () => { 120 121 it("should trim leading whitespace", async () => { 121 - const text = 122 - " Hello world, this is a test of the English language."; 122 + const text = " Hello world, this is a test of the English language."; 123 123 const result = await getLanguage(text); 124 124 expect(result).toBe("eng"); 125 125 }); 126 126 127 127 it("should trim trailing whitespace", async () => { 128 - const text = 129 - "Hello world, this is a test of the English language. "; 128 + const text = "Hello world, this is a test of the English language. "; 130 129 const result = await getLanguage(text); 131 130 expect(result).toBe("eng"); 132 131 }); 133 132 134 133 it("should trim both leading and trailing whitespace", async () => { 135 - const text = 136 - " Hello world, this is a test of the English language. "; 134 + const text = " Hello world, this is a test of the English language. "; 137 135 const result = await getLanguage(text); 138 136 expect(result).toBe("eng"); 139 137 }); ··· 148 146 }); 149 147 150 148 it("should handle code mixed with text", async () => { 151 - const text = 152 - "Here is some English text with const x = 123; code in it."; 149 + const text = "Here is some English text with const x = 123; code in it."; 153 150 const result = await getLanguage(text); 154 151 expect(result).toBe("eng"); 155 152 });
-2
src/utils/normalizeUnicode.ts
··· 39 39 // Final NFKC normalization to handle any remaining special characters. 40 40 return withoutDiacritics.normalize("NFKC"); 41 41 } 42 - 43 -