Skip to content

Commit 61e2cc4

Browse files
fix: implement actionable review feedback from pending PRs
Addresses unresolved review comments from PRs #6, #13, #25, #26: - src/index.ts: Add console.log→stderr redirect with unknown[] type - src/services/xapi.ts: Use URLSearchParams, add Array.isArray guard, add since_id pagination, use unknown type, avoid mutating sort - src/services/agent.ts: Add bounded memory pruning with safe iterator, process mentions oldest-first for chronological ordering - .github/workflows: Add permissions blocks, check labels exist before adding, use pull_request_target for fork PR support - README.md: Add Contributing section with absolute paths - Add CONTRIBUTING.md, PR_TITLE_GUIDE.md, pull_request_template.md Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com>
1 parent cc1e3a3 commit 61e2cc4

10 files changed

Lines changed: 255 additions & 14 deletions

File tree

.github/PR_TITLE_GUIDE.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# PR Title Quick Reference
2+
3+
## Conventional Commits Format
4+
5+
All PR titles should follow the [Conventional Commits](https://www.conventionalcommits.org/) format:
6+
7+
**Format**: `<type>(<scope>): <description>`
8+
9+
### Types and Examples
10+
11+
| Type | When to Use | Example |
12+
|------|-------------|---------|
13+
| `feat` | New feature | `feat(agent): add autonomous reply functionality` |
14+
| `fix` | Bug fix | `fix(xapi): correct mention polling interval` |
15+
| `docs` | Documentation only | `docs: update README.md and simplify xAI instructions` |
16+
| `refactor` | Code restructuring | `refactor(grok): simplify AI decision logic` |
17+
| `chore` | Maintenance | `chore: update dependencies to latest versions` |
18+
| `ci` | CI/CD changes | `ci: add PR validation workflow` |
19+
| `style` | Formatting changes | `style: fix indentation in config files` |
20+
| `test` | Test updates | `test: add unit tests for agent service` |
21+
| `perf` | Performance improvements | `perf(xapi): optimize mention polling` |
22+
23+
### How to Update PR Title
24+
25+
1. Go to your PR page on GitHub
26+
2. Click the "Edit" button next to the PR title
27+
3. Update the title to follow the format above
28+
4. Save changes
29+
30+
The PR validation workflow will then pass with no warnings.
31+
32+
## Common Scenarios
33+
34+
### Documentation Updates
35+
**Problem**: PR title like "updates to the `README.md`"
36+
**Solution**: `docs: update README.md and simplify xAI instructions`
37+
38+
### Multiple File Changes
39+
**Problem**: PR title like "various fixes"
40+
**Solution**: Choose the primary change type:
41+
- `fix: resolve polling and authentication issues`
42+
- `refactor: restructure API client and services`
43+
44+
### Feature Additions
45+
**Problem**: PR title like "new stuff"
46+
**Solution**: `feat(agent): implement autonomous decision-making`
47+
48+
## Full Guidelines
49+
50+
For comprehensive contribution guidelines, see [CONTRIBUTING.md](/CONTRIBUTING.md).
51+
52+
---
53+
54+
**Note**: The PR validation workflow treats conventional commits format as a **warning** (not an error), so PRs will not be blocked. However, following this format improves project maintainability and changelog generation.

.github/pull_request_template.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
## Description
2+
3+
<!-- Provide a clear description of what this PR does -->
4+
5+
## Motivation
6+
7+
<!-- Explain why this change is needed -->
8+
9+
## Changes
10+
11+
<!-- List the main changes made in this PR -->
12+
-
13+
-
14+
-
15+
16+
## Related Issues
17+
18+
<!-- Link related issues using #issue-number -->
19+
Closes #
20+
21+
## Testing
22+
23+
<!-- Describe how you tested these changes -->
24+
- [ ] Builds successfully (`npm run build`)
25+
- [ ] Tested in simulation mode
26+
- [ ] Tested with real API calls (if applicable)
27+
28+
## Checklist
29+
30+
- [ ] PR title follows [conventional commits format](/CONTRIBUTING.md#pr-title-format) (e.g., `feat:`, `fix:`, `docs:`)
31+
- [ ] PR description is clear and complete
32+
- [ ] Code follows project style guidelines
33+
- [ ] Changes are focused and reasonably sized
34+
- [ ] Documentation updated (if needed)
35+
- [ ] No sensitive information (API keys, tokens) committed
36+
37+
## Screenshots / Logs (if applicable)
38+
39+
<!-- Add screenshots or relevant log output -->

.github/workflows/auto-label.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
name: Auto Label
22
on:
3-
pull_request:
4-
types: [opened, reopened, synchronized]
3+
pull_request_target:
4+
types: [opened, reopened, synchronize]
5+
6+
permissions:
7+
contents: read
8+
pull-requests: write
9+
510
jobs:
611
label:
712
runs-on: ubuntu-latest

.github/workflows/issue-triage.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ name: Issue Triage
22
on:
33
issues:
44
types: [opened]
5+
6+
permissions:
7+
issues: write
8+
59
jobs:
610
triage:
711
runs-on: ubuntu-latest
@@ -21,12 +25,21 @@ jobs:
2125
2226
labels.push('needs-triage');
2327
24-
if (labels.length > 0) {
28+
// Fetch existing labels to avoid "Label does not exist" errors
29+
const { data: repoLabels } = await github.rest.issues.listLabelsForRepo({
30+
owner: context.repo.owner,
31+
repo: context.repo.repo,
32+
per_page: 100,
33+
});
34+
const existingLabelNames = new Set(repoLabels.map(label => label.name));
35+
const labelsToAdd = labels.filter(label => existingLabelNames.has(label));
36+
37+
if (labelsToAdd.length > 0) {
2538
await github.rest.issues.addLabels({
2639
owner: context.repo.owner,
2740
repo: context.repo.repo,
2841
issue_number: issue.number,
29-
labels: labels
42+
labels: labelsToAdd
3043
});
3144
}
3245

.github/workflows/pr-checks.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ name: PR Checks
22
on:
33
pull_request:
44
types: [opened, reopened, synchronize, edited]
5+
6+
permissions:
7+
contents: read
8+
pull-requests: write
9+
510
jobs:
611
validate:
712
runs-on: ubuntu-latest

CONTRIBUTING.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Contributing to MyXstack
2+
3+
Thank you for contributing to MyXstack! This guide will help you understand our contribution process and conventions.
4+
5+
## Pull Request Guidelines
6+
7+
### PR Title Format
8+
9+
We follow the [Conventional Commits](https://www.conventionalcommits.org/) format for PR titles. This helps us automatically generate changelogs and understand the nature of changes at a glance.
10+
11+
**Format**: `<type>(<scope>): <description>`
12+
13+
**Types**:
14+
- `feat`: A new feature
15+
- `fix`: A bug fix
16+
- `docs`: Documentation changes only
17+
- `style`: Code style changes (formatting, semicolons, etc.) that don't affect functionality
18+
- `refactor`: Code changes that neither fix bugs nor add features
19+
- `perf`: Performance improvements
20+
- `test`: Adding or updating tests
21+
- `chore`: Maintenance tasks, dependency updates
22+
- `ci`: Changes to CI/CD configuration
23+
- `build`: Changes to build system or dependencies
24+
- `revert`: Reverting a previous commit
25+
26+
**Scope** (optional): The area of the codebase affected (e.g., `agent`, `xapi`, `grok`, `mcp`)
27+
28+
**Examples**:
29+
- `docs: update README.md and simplify xAI instructions`
30+
- `feat(agent): add autonomous reply functionality`
31+
- `fix(xapi): correct mention polling interval`
32+
- `chore: update dependencies to latest versions`
33+
- `ci: add PR validation workflow`
34+
35+
### PR Description
36+
37+
- Provide a clear description of what the PR does (minimum 20 characters)
38+
- Reference related issues using `#issue-number`
39+
- Explain the motivation for the change
40+
- List any breaking changes
41+
- Include testing steps if applicable
42+
43+
### PR Size
44+
45+
- Try to keep PRs focused and under 500 lines of changes
46+
- Large PRs (>500 lines) will trigger a warning
47+
- Consider breaking large changes into smaller, reviewable chunks
48+
- If a large PR is unavoidable, provide extra context in the description
49+
50+
## Code Style
51+
52+
Follow the guidelines in `.github/copilot-instructions.md`:
53+
- Use TypeScript strict mode
54+
- Prefer async/await over raw promises
55+
- Always wrap API calls in try-catch blocks
56+
- Use explicit types; avoid `any`
57+
- Follow naming conventions:
58+
- Classes: PascalCase (e.g., `XAPIClient`)
59+
- Functions: camelCase (e.g., `fetchMentions`)
60+
- Constants: UPPER_SNAKE_CASE (e.g., `DEFAULT_POLLING_INTERVAL`)
61+
62+
## Testing
63+
64+
- Run `npm run build` to verify TypeScript compilation
65+
- Test changes in simulation mode when possible
66+
- Ensure existing tests pass before submitting
67+
68+
## Questions?
69+
70+
If you have questions, feel free to:
71+
- Open an issue for discussion
72+
- Ask in your PR comments
73+
- Check the existing documentation in `ARCHITECTURE.md` or `USAGE.md`

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,13 @@ A2A:
4747
- `POST /v1/a2a/agents`
4848
- `GET /v1/a2a/agents/{id}/messages`
4949
- `POST /v1/a2a/messages`
50+
51+
## Contributing
52+
53+
We welcome contributions! Please see [CONTRIBUTING.md](/CONTRIBUTING.md) for guidelines on:
54+
- PR title format (conventional commits)
55+
- Code style and conventions
56+
- Testing requirements
57+
- How to submit pull requests
58+
59+
For quick reference on PR titles, see [.github/PR_TITLE_GUIDE.md](/.github/PR_TITLE_GUIDE.md).

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import { AutonomousAgent } from './services/agent.js';
88
import { XMCPServer } from './mcp/server.js';
99

1010
async function main() {
11+
// Redirect console.log to stderr so it doesn't conflict with
12+
// MCP StdioServerTransport which uses stdout for protocol messages
13+
console.log = (...args: unknown[]) => console.error(...args);
14+
1115
console.log('═══════════════════════════════════════════════════');
1216
console.log(' MyXstack - Autonomous AI Agent on X (Twitter)');
1317
console.log('═══════════════════════════════════════════════════\n');

src/services/agent.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export class AutonomousAgent {
1111
private grokService: GrokService;
1212
private config: AgentConfig;
1313
private processedMentions: Set<string> = new Set();
14+
private static readonly MAX_PROCESSED_MENTIONS = 10000;
1415
private isRunning: boolean = false;
1516
private pollingIntervalId: NodeJS.Timeout | null = null;
1617
private isProcessing: boolean = false;
@@ -90,11 +91,24 @@ export class AutonomousAgent {
9091

9192
console.log(`\n📬 [${new Date().toLocaleTimeString()}] Found ${newMentions.length} new mention(s)!\n`);
9293

93-
// Process each mention
94-
for (const mention of newMentions) {
94+
// Process mentions oldest-first for chronological Set insertion order
95+
for (const mention of [...newMentions].reverse()) {
9596
await this.processMention(mention);
9697
this.processedMentions.add(mention.post.id);
9798
}
99+
100+
// Prune oldest entries to prevent unbounded memory growth
101+
if (this.processedMentions.size > AutonomousAgent.MAX_PROCESSED_MENTIONS) {
102+
const excess = this.processedMentions.size - AutonomousAgent.MAX_PROCESSED_MENTIONS;
103+
const iter = this.processedMentions.values();
104+
for (let i = 0; i < excess; i++) {
105+
const { value, done } = iter.next();
106+
if (done) {
107+
break;
108+
}
109+
this.processedMentions.delete(value);
110+
}
111+
}
98112
} catch (error) {
99113
console.error('❌ Error in processing loop:', error);
100114
} finally {

src/services/xapi.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,31 @@ export class XAPIClient {
4444
throw new Error('Failed to get user ID from response');
4545
}
4646

47-
const mentionsResponse = await this.makeXAPIRequest(
48-
`https://api.twitter.com/2/users/${userId}/mentions?max_results=10&expansions=author_id&tweet.fields=created_at,conversation_id,in_reply_to_user_id,referenced_tweets`,
49-
'GET'
50-
);
47+
const params = new URLSearchParams({
48+
max_results: '10',
49+
expansions: 'author_id',
50+
'tweet.fields': 'created_at,conversation_id,in_reply_to_user_id,referenced_tweets',
51+
});
52+
if (this.lastMentionId) {
53+
params.set('since_id', this.lastMentionId);
54+
}
55+
const mentionsUrl = `https://api.twitter.com/2/users/${userId}/mentions?${params.toString()}`;
56+
57+
const mentionsResponse = await this.makeXAPIRequest(mentionsUrl, 'GET');
5158

5259
if (!mentionsResponse || !Array.isArray(mentionsResponse.data)) {
5360
console.warn('Invalid response from X API (mentions)');
5461
return [];
5562
}
5663

57-
return this.parseMentions(mentionsResponse.data);
64+
const mentions = this.parseMentions(mentionsResponse.data);
65+
66+
// Track the newest mention ID for pagination on the next poll
67+
if (mentionsResponse.data.length > 0) {
68+
this.lastMentionId = mentionsResponse.data[0].id;
69+
}
70+
71+
return mentions;
5872
} catch (error) {
5973
console.error('Error fetching mentions:', error);
6074
return [];
@@ -77,7 +91,17 @@ export class XAPIClient {
7791
'GET'
7892
);
7993

80-
return this.parseThread(response.data || []);
94+
if (!response || !response.data) {
95+
console.warn('Invalid response from X API (thread)');
96+
return null;
97+
}
98+
99+
if (!Array.isArray(response.data)) {
100+
console.warn('Unexpected response shape from X API (thread): data is not an array');
101+
return null;
102+
}
103+
104+
return this.parseThread(response.data);
81105
} catch (error) {
82106
console.error('Error fetching thread:', error);
83107
return null;
@@ -181,10 +205,10 @@ export class XAPIClient {
181205
};
182206
}
183207

184-
private parseThread(tweets: { created_at: string; [key: string]: any }[]): XThread | null {
208+
private parseThread(tweets: { created_at: string; [key: string]: unknown }[]): XThread | null {
185209
if (tweets.length === 0) return null;
186210

187-
const sorted = tweets.sort((a, b) =>
211+
const sorted = [...tweets].sort((a, b) =>
188212
new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
189213
);
190214

0 commit comments

Comments
 (0)