Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .claude/commands/context-save.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Save current session context to MEMORY.md before compaction or heavy work.

Do the following:
1. Read the current MEMORY.md at `~/.claude/projects/-Users-kumardivyarajat-WebstormProjects-Notiflo/memory/MEMORY.md`
2. Run `git status` and `git log --oneline -5` to capture current state
3. Update MEMORY.md with:
- Current branch and its purpose
- What was built/changed THIS session (list files modified)
- Key decisions made this session
- Any errors encountered and how they were resolved
- What's in progress / next steps
- Any new gotchas discovered
4. Keep MEMORY.md under 200 lines total
5. Confirm what was saved
13 changes: 13 additions & 0 deletions .claude/commands/progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Show current project progress and update MEMORY.md.

Do the following:
1. Read MEMORY.md to understand last known state
2. Run `git status` and `git log --oneline -10`
3. Check if there's an active plan file in `~/.claude/plans/`
4. Summarize:
- Current branch and uncommitted changes
- What's been built (from git log + MEMORY.md)
- What's pending / in progress
- Any active plan and its status
5. Update MEMORY.md with the current state
6. Present the summary to the user
13 changes: 13 additions & 0 deletions .claude/commands/recover-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Recover full context after a session restart or context loss.

Do the following:
1. Read CLAUDE.md (project root) for stable project truth
2. Read MEMORY.md for last known session state
3. Run `git status` and `git log --oneline -15` to see what actually happened
4. If MEMORY.md seems stale or incomplete:
a. Find JSONL transcript files: `ls -lt ~/.claude/projects/-Users-kumardivyarajat-WebstormProjects-Notiflo/*.jsonl`
b. Read the most recent transcript to extract user messages and key decisions
c. Update MEMORY.md with recovered context
5. Check for active plan files in `~/.claude/plans/`
6. Present a summary of: where we are, what's done, what's next
7. Do NOT ask the user to re-explain anything — recover it from the files
13 changes: 13 additions & 0 deletions .claude/commands/verify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Run the full test suite across all projects and report results.

Do the following:
1. Run all three test suites in parallel:
- `npx nx test notiflo`
- `npx nx test napi-bridge`
- `npx nx test pipeline-pipeline`
2. Report results in this format:
- notiflo: X passing, Y failing
- napi-bridge: X passing, Y failing
- pipeline: X passing, Y failing
3. If there are NEW failures (not pre-existing), flag them prominently
4. If all tests pass (excluding known pre-existing failures), confirm the system is healthy
34 changes: 34 additions & 0 deletions .claude/rules/bridge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
paths:
- "libs/bridge/**"
---

# Rust-JS Bridge Rules

## Architecture
- `EngineBridgeService` — wraps the real Rust napi addon (`require('engine-core')`)
- `MockEngineBridgeService` — pure TypeScript implementation for testing
- `IEngineBridge` — interface contract both implement
- `ENGINE_BRIDGE` — DI token string constant

## Data Flow: Rust <-> Node.js
- NestJS calls napi functions via `EngineBridgeService`
- Data crosses the boundary as JSON strings (serialized in TS, deserialized in Rust)
- Rust match callbacks use `ThreadsafeFunction` to emit events back to NestJS `EventEmitter2`
- Event name: `engine.condition.match`

## Module Setup
`NapiBridgeModule` is `@Global()` — available everywhere without explicit imports.
Provides both `EngineBridgeService` (class) and `ENGINE_BRIDGE` (string token).

## Testing Bridge Changes
1. Write tests using `MockEngineBridgeService` first
2. Verify mock behavior matches expected Rust behavior
3. Only test with real addon when specifically testing napi interop
4. Bridge tests: `npx nx test napi-bridge`

## When Modifying the Bridge Interface
If you change `IEngineBridge`, you MUST update BOTH:
- `EngineBridgeService` (real addon wrapper)
- `MockEngineBridgeService` (test mock)
- All barrel exports in `src/index.ts`
53 changes: 53 additions & 0 deletions .claude/rules/nestjs-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
paths:
- "apps/notiflo/**/*.ts"
- "!apps/notiflo/**/*.spec.ts"
---

# NestJS Service & Module Rules

## Module Pattern
Every module that exports a service MUST provide both class and string token:
```typescript
@Module({
providers: [
MyService,
{ provide: 'MyService', useExisting: MyService },
],
exports: [MyService, 'MyService'],
})
```
Forgetting the string token alias causes "can't resolve dependencies" errors in consuming modules.

## Service Pattern
```typescript
@Injectable()
export class MyService implements OnModuleInit {
private readonly logger = new Logger(MyService.name);

async onModuleInit() { /* startup logic */ }
}
```

## Controller Pattern
- `@Controller('feature-name')` for route prefix
- Return objects directly — NestJS serializes to JSON
- Use `class-validator` decorators on DTOs
- `@UsePipes(new ValidationPipe({ transform: true }))` or global pipe

## Mongoose Model Registration
**Critical:** The name in `@InjectModel('X')` must EXACTLY match `MongooseModule.forFeature([{ name: 'X', schema }])`.
This has caused real bugs. Always verify both files when creating or modifying a schema.

## Dependency Injection
- Use `@Optional()` with `@Inject(TOKEN)` when a dependency might not exist
- Use `forwardRef(() => Module)` for circular module dependencies
- The `ENGINE_BRIDGE` token uses `@Optional()` so the app works without the Rust addon

## Scaffolding — Use NX CLI
NEVER manually create modules, services, or controllers. Always:
```bash
npx nx generate @nx/nest:resource feature-name --project=notiflo
npx nx generate @nx/nest:service service-name --project=notiflo
npx nx generate @nx/nest:module module-name --project=notiflo
```
44 changes: 44 additions & 0 deletions .claude/rules/pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
paths:
- "libs/pipeline/**"
---

# Pipeline Library Rules

## Architecture
The pipeline processes notifications through 4 sequential stages:
1. **Fanout** — resolves subscribers, fans out to channels
2. **Render** — resolves templates, renders with Handlebars per channel
3. **Deliver** — sends through channel providers with resilience patterns
4. **Status** — tracks delivery status, updates notification records

Kafka connects the stages. Each worker consumes from one topic and produces to the next.

## Worker Pattern
All workers implement `IWorker`:
```typescript
interface IWorker {
start(): Promise<void>;
stop(): Promise<void>;
isRunning(): boolean;
getMetrics(): WorkerMetrics;
}
```
Workers track: `processedCount`, `failedCount`, `totalLatencyMs`, `lastProcessedAt`.

## Resilience Patterns
- `CircuitBreaker` — CLOSED -> OPEN (after failures) -> HALF_OPEN (after timeout) -> CLOSED (on success)
- `RateLimiter` — token bucket with configurable refill
- `RetryHandler` — exponential backoff with jitter, configurable retryable errors
- `BatchAccumulator` — accumulates messages, flushes at size threshold or time interval
- `DeadLetterQueue` — failed messages after all retries exhausted

## Delivery Worker Specifics
- Per-channel batch accumulators
- Redis-based deduplication with TTL
- Provider registry: `registerProvider(channel, IChannelDeliveryProvider)`
- Publishes status messages after delivery (success or failure)

## Kafka is OFF the Hot Path
Kafka is for durability, replay, and analytics ONLY. The real-time alert path goes:
tick -> Rust engine -> EventEmitter -> AlertDeliveryListener -> OrchestratorService -> channel provider
44 changes: 44 additions & 0 deletions .claude/rules/rust-engine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
paths:
- "libs/engine/**/*.rs"
- "libs/engine/**/Cargo.toml"
- "Cargo.toml"
---

# Rust Engine Rules

## Architecture
- `engine-core` is a cdylib (Node.js addon via napi-rs) AND rlib (for Rust tests/benches)
- `shared-types` is an rlib defining the `EvaluationStrategy` trait and domain types
- The hot path target is <1us per no-match evaluation, 2-6ms tick-to-delivery

## Conventions
- Gate all napi exports behind `#[cfg(feature = "napi_binding")]` in `napi_exports.rs`
- Never put napi dependencies in the default feature set — benchmarks must compile without Node.js symbols
- Use `DashMap` for concurrent condition storage, `crossbeam-channel` for ring buffers
- `parking_lot` mutexes over `std::sync` for performance
- `Option<String>` in napi structs means JS must pass `undefined`, NOT `null`

## Adding a New Evaluation Strategy
1. Implement `EvaluationStrategy` trait from `shared-types/src/strategy.rs`
2. Add strategy type variant to the strategy enum
3. Register in the evaluator dispatcher (`engine-core/src/condition/evaluator.rs`)
4. Write Rust unit tests in the same file
5. Add benchmark in `benches/condition_bench.rs`
6. Add corresponding handling in `MockEngineBridgeService` (TypeScript side)
7. Write bridge tests in `libs/bridge/napi-bridge/`

## Build & Test
```bash
cargo check --workspace # Fast compilation check
cargo test -p engine-core # Unit tests
cargo test -p shared-types # Shared type tests
cargo clippy --workspace # Lint
cargo bench --bench condition_bench --no-default-features # Benchmarks
npx nx build engine-core # Build cdylib for Node.js
```

## The .node Addon
- Built artifact: `target/release/libengine_core.dylib`
- Must be copied/symlinked to `engine-core.darwin-arm64.node` for `require('engine-core')` to work
- For ALL TypeScript tests, use `MockEngineBridgeService` instead — never depend on the compiled addon in Jest
59 changes: 59 additions & 0 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
paths:
- "**/*.spec.ts"
- "**/*.e2e.spec.ts"
- "**/test-utils/**"
---

# Testing Rules

## TDD Is Non-Negotiable
- Write ALL test expectations FIRST — they must FAIL (RED)
- Then implement minimum code to pass (GREEN)
- Then refactor if needed
- Tests are the source of truth for the entire platform

## Writing Non-Tautological Tests
- Don't just test that a mock was called — test it was called with the RIGHT arguments
- Test error paths, not just happy paths
- Test state transitions (e.g., campaign DRAFT -> RUNNING -> PAUSED)
- Test edge cases: empty arrays, null values, missing fields
- Integration tests should verify the full event chain works

## Unit Tests (per service/controller)
- Mock ALL dependencies with `jest.fn()` objects
- For Mongoose models:
```typescript
const mockModel = {
create: jest.fn(),
find: jest.fn().mockReturnValue({
skip: jest.fn().mockReturnValue({
limit: jest.fn().mockReturnValue({ exec: jest.fn() })
})
}),
findById: jest.fn().mockReturnValue({ exec: jest.fn() }),
};
```
- Provide mock with `getModelToken('ModelName')` in test module
- For string DI tokens: `{ provide: 'ServiceName', useValue: mockService }`

## Integration Tests
- Use real `EventEmitter2`, `MockEngineBridgeService`, mocked DB
- Test the feature flow end-to-end within a module

## E2E Tests
- Use `MongoMemoryServer` for real MongoDB
- Set `process.env.MONGODB_URI` BEFORE module compilation
- Override engine bridge: `.overrideProvider(EngineBridgeService).useClass(MockEngineBridgeService)`
- Use `import request from 'supertest'` (default import, NOT namespace `import *`)
- After any significant code change, run: `npx nx run-many --target=test --all`

## MockEngineBridgeService
- Pure TypeScript implementation of `IEngineBridge`
- Used in ALL Jest tests — never depend on compiled Rust addon
- Mirrors Rust engine behavior (threshold evaluation, match events)
- Injected via `ENGINE_BRIDGE` token with `@Optional()`

## Test Naming
- Use: `'should [action] when [condition]'`
- Group with `describe()` blocks by feature/method
24 changes: 24 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# =============================================================================
# Notiflo — Infrastructure Configuration
# =============================================================================
# Copy this file to .env and adjust for your environment.
#
# NOTE: Provider credentials (SendGrid, Twilio, FCM, etc.) are NOT configured
# here. They are per-organization settings managed through the UI/API/MCP/CLI
# and stored in the database.

# ── Core ─────────────────────────────────────────────────────────────────────
PORT=3000
CORS_ORIGIN=*

# ── Database ─────────────────────────────────────────────────────────────────
MONGODB_URI=mongodb://localhost/notiflo

# ── MCP Server ───────────────────────────────────────────────────────────────
NOTIFLO_API_URL=http://localhost:3000/api

# ── Redis ────────────────────────────────────────────────────────────────────
REDIS_URL=redis://localhost:6379

# ── Frontend ─────────────────────────────────────────────────────────────────
NEXT_PUBLIC_API_URL=/api
34 changes: 34 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: Bug Report
about: Report a bug in Notiflo
title: "[Bug] "
labels: bug
assignees: ''
---

**Component**
- [ ] Rust runtime (`notiflo-runtime`)
- [ ] NestJS API (`notiflo-api`)
- [ ] Docker / Infrastructure
- [ ] Documentation

**Describe the bug**
A clear description of what the bug is.

**Steps to reproduce**
1.
2.
3.

**Expected behavior**
What you expected to happen.

**Actual behavior**
What actually happened. Include error messages or logs if available.

**Environment**
- OS:
- Rust version (if applicable):
- Node.js version (if applicable):
- Docker version (if applicable):
- Notiflo version:
19 changes: 19 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: Feature Request
about: Suggest a new feature or improvement
title: "[Feature] "
labels: enhancement
assignees: ''
---

**Is this related to a problem?**
A clear description of the problem or limitation.

**Proposed solution**
Describe the feature or change you'd like.

**Alternatives considered**
Any alternative approaches you've considered.

**Additional context**
Any other context, mockups, or references.
18 changes: 18 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Summary

<!-- Brief description of what this PR does -->

## Changes

-

## Testing

- [ ] `cargo test --workspace --no-default-features`
- [ ] `cargo clippy --workspace --no-default-features -- -D warnings`
- [ ] `npx nx test notiflo`
- [ ] New tests added for new functionality

## Related Issues

<!-- Closes #123 -->
Loading