206 lines
6.3 KiB
Markdown
206 lines
6.3 KiB
Markdown
# Pulse — Agent Guide
|
|
|
|
RSS feed aggregator hosted at pulse.eazz.io.
|
|
Built with a strict module-per-agent architecture. Each agent owns one module and communicates with others only through documented interfaces.
|
|
|
|
---
|
|
|
|
## Golden Rules
|
|
|
|
1. **Ask before you build.** If anything is unclear, ask. Do not assume. Do not start coding until you fully understand the task.
|
|
2. **Plan before you execute.** Always produce a written plan first. Wait for approval before writing any code.
|
|
3. **Stay in your module.** Do not read or write files outside your module folder unless you are the Orchestrator.
|
|
4. **Respect the interfaces.** Never call internal functions of another module. Only use what is exported in `interfaces/`.
|
|
5. **Small commits, clear messages.** One logical change per commit.
|
|
6. **No silent assumptions.** If a requirement is ambiguous, surface it explicitly before proceeding.
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
pulse/
|
|
├── AGENTS.md # This file
|
|
├── interfaces/ # Shared contracts — source of truth for all modules
|
|
│ ├── feed.types.ts # Core data types
|
|
│ ├── fetcher.interface.ts # Fetcher module contract
|
|
│ ├── parser.interface.ts # Parser module contract
|
|
│ ├── storage.interface.ts # Storage module contract
|
|
│ ├── dedup.interface.ts # Deduplication module contract
|
|
│ └── formatter.interface.ts # Formatter module contract
|
|
├── modules/
|
|
│ ├── fetcher/ # HTTP fetching of RSS/Atom feeds
|
|
│ ├── parser/ # XML parsing into FeedItem structs
|
|
│ ├── dedup/ # Deduplication of feed items
|
|
│ ├── storage/ # Persistence (SQLite)
|
|
│ └── formatter/ # Output rendering (terminal / HTML)
|
|
├── orchestrator/ # Coordinates modules, owns no business logic
|
|
└── opencode.json # OpenCode agent config
|
|
```
|
|
|
|
---
|
|
|
|
## Core Data Types
|
|
|
|
These are defined in `interfaces/feed.types.ts` and are the lingua franca between all modules.
|
|
|
|
```typescript
|
|
export interface FeedItem {
|
|
id: string // Deterministic hash of url + publishedAt
|
|
source: string // Feed origin URL
|
|
title: string
|
|
url: string
|
|
publishedAt: Date
|
|
content?: string // Optional full content
|
|
summary?: string // Optional short summary
|
|
}
|
|
|
|
export interface FetchError {
|
|
source: string
|
|
reason: string
|
|
code: "NETWORK" | "TIMEOUT" | "PARSE" | "UNKNOWN"
|
|
}
|
|
|
|
export interface FetchResult {
|
|
items: FeedItem[]
|
|
errors: FetchError[]
|
|
fetchedAt: Date
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Module Interfaces
|
|
|
|
### Fetcher
|
|
```typescript
|
|
// interfaces/fetcher.interface.ts
|
|
export interface IFetcher {
|
|
fetch(feedUrl: string): Promise<FetchResult>
|
|
fetchMany(feedUrls: string[]): Promise<FetchResult>
|
|
}
|
|
```
|
|
|
|
### Parser
|
|
```typescript
|
|
// interfaces/parser.interface.ts
|
|
export interface IParser {
|
|
parse(rawXml: string, source: string): Promise<FeedItem[]>
|
|
supports(contentType: string): boolean // rss, atom, json feed
|
|
}
|
|
```
|
|
|
|
### Deduplication
|
|
```typescript
|
|
// interfaces/dedup.interface.ts
|
|
export interface IDedup {
|
|
filter(items: FeedItem[]): Promise<FeedItem[]> // returns only unseen items
|
|
markSeen(items: FeedItem[]): Promise<void>
|
|
}
|
|
```
|
|
|
|
### Storage
|
|
```typescript
|
|
// interfaces/storage.interface.ts
|
|
export interface IStorage {
|
|
save(items: FeedItem[]): Promise<void>
|
|
getRecent(limit: number): Promise<FeedItem[]>
|
|
getBySource(source: string, limit: number): Promise<FeedItem[]>
|
|
search(query: string): Promise<FeedItem[]>
|
|
}
|
|
```
|
|
|
|
### Formatter
|
|
```typescript
|
|
// interfaces/formatter.interface.ts
|
|
export type OutputFormat = "terminal" | "html" | "json"
|
|
|
|
export interface IFormatter {
|
|
format(items: FeedItem[], format: OutputFormat): Promise<string>
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Agent Responsibilities
|
|
|
|
| Agent | Owns | Can read interfaces | Cannot touch |
|
|
|---|---|---|---|
|
|
| `orchestrator` | `orchestrator/` | All | `modules/` internals |
|
|
| `fetcher-agent` | `modules/fetcher/` | `feed.types`, `fetcher.interface` | All other modules |
|
|
| `parser-agent` | `modules/parser/` | `feed.types`, `parser.interface` | All other modules |
|
|
| `dedup-agent` | `modules/dedup/` | `feed.types`, `dedup.interface` | All other modules |
|
|
| `storage-agent` | `modules/storage/` | `feed.types`, `storage.interface` | All other modules |
|
|
| `formatter-agent` | `modules/formatter/` | `feed.types`, `formatter.interface` | All other modules |
|
|
|
|
---
|
|
|
|
## Workflow Every Agent Must Follow
|
|
|
|
### Step 1 — Clarify
|
|
Before doing anything, re-read your task. If any of these are unclear, **ask**:
|
|
- What exact input will I receive?
|
|
- What exact output is expected?
|
|
- Are there edge cases not mentioned?
|
|
- Does this touch an interface that other modules depend on?
|
|
|
|
Do not proceed until you have answers.
|
|
|
|
### Step 2 — Plan
|
|
Write a short implementation plan in plain text:
|
|
- What files will you create or modify?
|
|
- What are the key decisions and why?
|
|
- What could go wrong?
|
|
|
|
Post the plan and **wait for approval** before writing any code.
|
|
|
|
### Step 3 — Implement
|
|
Only after plan approval:
|
|
- Stay inside your module folder
|
|
- Implement against the interface, not against other module internals
|
|
- Write tests alongside the code
|
|
|
|
### Step 4 — Verify
|
|
- Run tests for your module only
|
|
- Confirm the exported interface still matches `interfaces/`
|
|
- Do not break existing interface contracts without flagging it first
|
|
|
|
---
|
|
|
|
## Interface Change Protocol
|
|
|
|
Interfaces in `interfaces/` are shared contracts. Changing them affects all modules.
|
|
|
|
If you need to change an interface:
|
|
1. Stop. Do not change it unilaterally.
|
|
2. Post a proposed change with reasoning to the orchestrator.
|
|
3. Wait for explicit approval.
|
|
4. Only then update the interface file AND all affected modules in the same commit.
|
|
|
|
---
|
|
|
|
## Tech Stack
|
|
|
|
- Runtime: Node.js (TypeScript)
|
|
- Storage: SQLite via `better-sqlite3`
|
|
- HTTP: `undici`
|
|
- XML parsing: `fast-xml-parser`
|
|
- Testing: `vitest`
|
|
- Linting: `eslint` + `prettier`
|
|
- Web: React + Tailwindcss + shadcn/ui
|
|
- Server: Koa
|
|
|
|
|
|
---
|
|
|
|
## Questions Agents Should Ask Themselves
|
|
|
|
Before starting any task:
|
|
- [ ] Do I fully understand what "done" looks like?
|
|
- [ ] Have I read the relevant interface file?
|
|
- [ ] Is my plan written and approved?
|
|
- [ ] Am I staying inside my module?
|
|
- [ ] Will my changes break any existing interface contract?
|
|
|
|
If any answer is "no" or "unsure" — ask before proceeding.
|