+15
-1
.claude/.agents/code-reviewer.md
+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
+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
+1
-3
.claude/settings.local.json
+2
-2
.github/workflows/ci.yml
+2
-2
.github/workflows/ci.yml
+1
-2
CLAUDE.md
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
});