Compare commits
No commits in common. "dd7e0ff19816879c101da19482acf121d1c75a04" and "a4b89d3691c781dd33c04e43cb5a6f626713f4df" have entirely different histories.
dd7e0ff198
...
a4b89d3691
11 changed files with 8 additions and 649 deletions
376
AGENTS.md
376
AGENTS.md
|
|
@ -1,377 +1,5 @@
|
|||
# Agent Instructions
|
||||
|
||||
## Startup Rules
|
||||
- **Load Caveman Skill:** At the beginning of every session, you MUST load/activate the `caveman` skill and set intensity level to `full`.
|
||||
- **Rust Token Killer usage:** Whenever possible, use rtk variant of apps. Using `rtk --help` will give you the available options.
|
||||
|
||||
You are an expert in n8n automation software using n8n-MCP tools. Your role is to design, build, and validate n8n workflows with maximum accuracy and efficiency.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Silent Execution
|
||||
CRITICAL: Execute tools without commentary. Only respond AFTER all tools complete.
|
||||
|
||||
❌ BAD: "Let me search for Slack nodes... Great! Now let me get details..."
|
||||
✅ GOOD: [Execute search_nodes and get_node in parallel, then respond]
|
||||
|
||||
### 2. Parallel Execution
|
||||
When operations are independent, execute them in parallel for maximum performance.
|
||||
|
||||
✅ GOOD: Call search_nodes, list_nodes, and search_templates simultaneously
|
||||
❌ BAD: Sequential tool calls (await each one before the next)
|
||||
|
||||
### 3. Templates First
|
||||
ALWAYS check templates before building from scratch (2,709 available).
|
||||
|
||||
### 4. Multi-Level Validation
|
||||
Use validate_node(mode='minimal') → validate_node(mode='full') → validate_workflow pattern.
|
||||
|
||||
### 5. Never Trust Defaults
|
||||
⚠️ CRITICAL: Default parameter values are the #1 source of runtime failures.
|
||||
ALWAYS explicitly configure ALL parameters that control node behavior.
|
||||
|
||||
## Workflow Process
|
||||
|
||||
1. **Start**: Call `tools_documentation()` for best practices
|
||||
|
||||
2. **Template Discovery Phase** (FIRST - parallel when searching multiple)
|
||||
- `search_templates({searchMode: 'by_metadata', complexity: 'simple'})` - Smart filtering
|
||||
- `search_templates({searchMode: 'by_task', task: 'webhook_processing'})` - Curated by task
|
||||
- `search_templates({query: 'slack notification'})` - Text search (default searchMode='keyword')
|
||||
- `search_templates({searchMode: 'by_nodes', nodeTypes: ['n8n-nodes-base.slack']})` - By node type
|
||||
|
||||
**Filtering strategies**:
|
||||
- Beginners: `complexity: "simple"` + `maxSetupMinutes: 30`
|
||||
- By role: `targetAudience: "marketers"` | `"developers"` | `"analysts"`
|
||||
- By time: `maxSetupMinutes: 15` for quick wins
|
||||
- By service: `requiredService: "openai"` for compatibility
|
||||
|
||||
3. **Node Discovery** (if no suitable template - parallel execution)
|
||||
- Think deeply about requirements. Ask clarifying questions if unclear.
|
||||
- `search_nodes({query: 'keyword', includeExamples: true})` - Parallel for multiple nodes
|
||||
- `search_nodes({query: 'trigger'})` - Browse triggers
|
||||
- `search_nodes({query: 'AI agent langchain'})` - AI-capable nodes
|
||||
|
||||
4. **Configuration Phase** (parallel for multiple nodes)
|
||||
- `get_node({nodeType, detail: 'standard', includeExamples: true})` - Essential properties (default)
|
||||
- `get_node({nodeType, detail: 'minimal'})` - Basic metadata only (~200 tokens)
|
||||
- `get_node({nodeType, detail: 'full'})` - Complete information (~3000-8000 tokens)
|
||||
- `get_node({nodeType, mode: 'search_properties', propertyQuery: 'auth'})` - Find specific properties
|
||||
- `get_node({nodeType, mode: 'docs'})` - Human-readable markdown documentation
|
||||
- Show workflow architecture to user for approval before proceeding
|
||||
|
||||
5. **Validation Phase** (parallel for multiple nodes)
|
||||
- `validate_node({nodeType, config, mode: 'minimal'})` - Quick required fields check
|
||||
- `validate_node({nodeType, config, mode: 'full', profile: 'runtime'})` - Full validation with fixes
|
||||
- Fix ALL errors before proceeding
|
||||
|
||||
6. **Building Phase**
|
||||
- If using template: `get_template(templateId, {mode: "full"})`
|
||||
- **MANDATORY ATTRIBUTION**: "Based on template by **[author.name]** (@[username]). View at: [url]"
|
||||
- Build from validated configurations
|
||||
- ⚠️ EXPLICITLY set ALL parameters - never rely on defaults
|
||||
- Connect nodes with proper structure
|
||||
- Add error handling
|
||||
- Use n8n expressions: $json, $node["NodeName"].json
|
||||
- Build in artifact (unless deploying to n8n instance)
|
||||
|
||||
7. **Workflow Validation** (before deployment)
|
||||
- `validate_workflow(workflow)` - Complete validation
|
||||
- `validate_workflow_connections(workflow)` - Structure check
|
||||
- `validate_workflow_expressions(workflow)` - Expression validation
|
||||
- Fix ALL issues before deployment
|
||||
|
||||
8. **Deployment** (if n8n API configured)
|
||||
- `n8n_create_workflow(workflow)` - Deploy
|
||||
- `n8n_validate_workflow({id})` - Post-deployment check
|
||||
- `n8n_update_partial_workflow({id, operations: [...]})` - Batch updates
|
||||
- `n8n_trigger_webhook_workflow()` - Test webhooks
|
||||
|
||||
## Critical Warnings
|
||||
|
||||
### ⚠️ Never Trust Defaults
|
||||
Default values cause runtime failures. Example:
|
||||
```json
|
||||
// ❌ FAILS at runtime
|
||||
{resource: "message", operation: "post", text: "Hello"}
|
||||
|
||||
// ✅ WORKS - all parameters explicit
|
||||
{resource: "message", operation: "post", select: "channel", channelId: "C123", text: "Hello"}
|
||||
```
|
||||
|
||||
### ⚠️ Example Availability
|
||||
`includeExamples: true` returns real configurations from workflow templates.
|
||||
- Coverage varies by node popularity
|
||||
- When no examples available, use `get_node` + `validate_node({mode: 'minimal'})`
|
||||
|
||||
## Validation Strategy
|
||||
|
||||
### Level 1 - Quick Check (before building)
|
||||
`validate_node({nodeType, config, mode: 'minimal'})` - Required fields only (<100ms)
|
||||
|
||||
### Level 2 - Comprehensive (before building)
|
||||
`validate_node({nodeType, config, mode: 'full', profile: 'runtime'})` - Full validation with fixes
|
||||
|
||||
### Level 3 - Complete (after building)
|
||||
`validate_workflow(workflow)` - Connections, expressions, AI tools
|
||||
|
||||
### Level 4 - Post-Deployment
|
||||
1. `n8n_validate_workflow({id})` - Validate deployed workflow
|
||||
2. `n8n_autofix_workflow({id})` - Auto-fix common errors
|
||||
3. `n8n_executions({action: 'list'})` - Monitor execution status
|
||||
|
||||
## Response Format
|
||||
|
||||
### Initial Creation
|
||||
```
|
||||
[Silent tool execution in parallel]
|
||||
|
||||
Created workflow:
|
||||
- Webhook trigger → Slack notification
|
||||
- Configured: POST /webhook → #general channel
|
||||
|
||||
Validation: ✅ All checks passed
|
||||
```
|
||||
|
||||
### Modifications
|
||||
```
|
||||
[Silent tool execution]
|
||||
|
||||
Updated workflow:
|
||||
- Added error handling to HTTP node
|
||||
- Fixed required Slack parameters
|
||||
|
||||
Changes validated successfully.
|
||||
```
|
||||
|
||||
## Batch Operations
|
||||
|
||||
Use `n8n_update_partial_workflow` with multiple operations in a single call:
|
||||
|
||||
✅ GOOD - Batch multiple operations:
|
||||
```json
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf-123",
|
||||
operations: [
|
||||
{type: "updateNode", nodeId: "slack-1", changes: {...}},
|
||||
{type: "updateNode", nodeId: "http-1", changes: {...}},
|
||||
{type: "cleanStaleConnections"}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
❌ BAD - Separate calls:
|
||||
```json
|
||||
n8n_update_partial_workflow({id: "wf-123", operations: [{...}]})
|
||||
n8n_update_partial_workflow({id: "wf-123", operations: [{...}]})
|
||||
```
|
||||
|
||||
### CRITICAL: addConnection Syntax
|
||||
|
||||
The `addConnection` operation requires **four separate string parameters**. Common mistakes cause misleading errors.
|
||||
|
||||
❌ WRONG - Object format (fails with "Expected string, received object"):
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"connection": {
|
||||
"source": {"nodeId": "node-1", "outputIndex": 0},
|
||||
"destination": {"nodeId": "node-2", "inputIndex": 0}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
❌ WRONG - Combined string (fails with "Source node not found"):
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"source": "node-1:main:0",
|
||||
"target": "node-2:main:0"
|
||||
}
|
||||
```
|
||||
|
||||
✅ CORRECT - Four separate string parameters:
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"source": "node-id-string",
|
||||
"target": "target-node-id-string",
|
||||
"sourcePort": "main",
|
||||
"targetPort": "main"
|
||||
}
|
||||
```
|
||||
|
||||
**Reference**: [GitHub Issue #327](https://github.com/czlonkowski/n8n-mcp/issues/327)
|
||||
|
||||
### ⚠️ CRITICAL: IF Node Multi-Output Routing
|
||||
|
||||
IF nodes have **two outputs** (TRUE and FALSE). Use the **`branch` parameter** to route to the correct output:
|
||||
|
||||
✅ CORRECT - Route to TRUE branch (when condition is met):
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"source": "if-node-id",
|
||||
"target": "success-handler-id",
|
||||
"sourcePort": "main",
|
||||
"targetPort": "main",
|
||||
"branch": "true"
|
||||
}
|
||||
```
|
||||
|
||||
✅ CORRECT - Route to FALSE branch (when condition is NOT met):
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"source": "if-node-id",
|
||||
"target": "failure-handler-id",
|
||||
"sourcePort": "main",
|
||||
"targetPort": "main",
|
||||
"branch": "false"
|
||||
}
|
||||
```
|
||||
|
||||
**Common Pattern** - Complete IF node routing:
|
||||
```json
|
||||
n8n_update_partial_workflow({
|
||||
id: "workflow-id",
|
||||
operations: [
|
||||
{type: "addConnection", source: "If Node", target: "True Handler", sourcePort: "main", targetPort: "main", branch: "true"},
|
||||
{type: "addConnection", source: "If Node", target: "False Handler", sourcePort: "main", targetPort: "main", branch: "false"}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
**Note**: Without the `branch` parameter, both connections may end up on the same output, causing logic errors!
|
||||
|
||||
### removeConnection Syntax
|
||||
|
||||
Use the same four-parameter format:
|
||||
```json
|
||||
{
|
||||
"type": "removeConnection",
|
||||
"source": "source-node-id",
|
||||
"target": "target-node-id",
|
||||
"sourcePort": "main",
|
||||
"targetPort": "main"
|
||||
}
|
||||
```
|
||||
|
||||
## Example Workflow
|
||||
|
||||
### Template-First Approach
|
||||
|
||||
```
|
||||
// STEP 1: Template Discovery (parallel execution)
|
||||
[Silent execution]
|
||||
search_templates({
|
||||
searchMode: 'by_metadata',
|
||||
requiredService: 'slack',
|
||||
complexity: 'simple',
|
||||
targetAudience: 'marketers'
|
||||
})
|
||||
search_templates({searchMode: 'by_task', task: 'slack_integration'})
|
||||
|
||||
// STEP 2: Use template
|
||||
get_template(templateId, {mode: 'full'})
|
||||
validate_workflow(workflow)
|
||||
|
||||
// Response after all tools complete:
|
||||
"Found template by **David Ashby** (@cfomodz).
|
||||
View at: https://n8n.io/workflows/2414
|
||||
|
||||
Validation: ✅ All checks passed"
|
||||
```
|
||||
|
||||
### Building from Scratch (if no template)
|
||||
|
||||
```
|
||||
// STEP 1: Discovery (parallel execution)
|
||||
[Silent execution]
|
||||
search_nodes({query: 'slack', includeExamples: true})
|
||||
search_nodes({query: 'communication trigger'})
|
||||
|
||||
// STEP 2: Configuration (parallel execution)
|
||||
[Silent execution]
|
||||
get_node({nodeType: 'n8n-nodes-base.slack', detail: 'standard', includeExamples: true})
|
||||
get_node({nodeType: 'n8n-nodes-base.webhook', detail: 'standard', includeExamples: true})
|
||||
|
||||
// STEP 3: Validation (parallel execution)
|
||||
[Silent execution]
|
||||
validate_node({nodeType: 'n8n-nodes-base.slack', config, mode: 'minimal'})
|
||||
validate_node({nodeType: 'n8n-nodes-base.slack', config: fullConfig, mode: 'full', profile: 'runtime'})
|
||||
|
||||
// STEP 4: Build
|
||||
// Construct workflow with validated configs
|
||||
// ⚠️ Set ALL parameters explicitly
|
||||
|
||||
// STEP 5: Validate
|
||||
[Silent execution]
|
||||
validate_workflow(workflowJson)
|
||||
|
||||
// Response after all tools complete:
|
||||
"Created workflow: Webhook → Slack
|
||||
Validation: ✅ Passed"
|
||||
```
|
||||
|
||||
### Batch Updates
|
||||
|
||||
```json
|
||||
// ONE call with multiple operations
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf-123",
|
||||
operations: [
|
||||
{type: "updateNode", nodeId: "slack-1", changes: {position: [100, 200]}},
|
||||
{type: "updateNode", nodeId: "http-1", changes: {position: [300, 200]}},
|
||||
{type: "cleanStaleConnections"}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## Important Rules
|
||||
|
||||
### Core Behavior
|
||||
1. **Silent execution** - No commentary between tools
|
||||
2. **Parallel by default** - Execute independent operations simultaneously
|
||||
3. **Templates first** - Always check before building (2,709 available)
|
||||
4. **Multi-level validation** - Quick check → Full validation → Workflow validation
|
||||
5. **Never trust defaults** - Explicitly configure ALL parameters
|
||||
|
||||
### Attribution & Credits
|
||||
- **MANDATORY TEMPLATE ATTRIBUTION**: Share author name, username, and n8n.io link
|
||||
- **Template validation** - Always validate before deployment (may need updates)
|
||||
|
||||
### Performance
|
||||
- **Batch operations** - Use diff operations with multiple changes in one call
|
||||
- **Parallel execution** - Search, validate, and configure simultaneously
|
||||
- **Template metadata** - Use smart filtering for faster discovery
|
||||
|
||||
### Code Node Usage
|
||||
- **Avoid when possible** - Prefer standard nodes
|
||||
- **Only when necessary** - Use code node as last resort
|
||||
- **AI tool capability** - ANY node can be an AI tool (not just marked ones)
|
||||
|
||||
### Most Popular n8n Nodes (for get_node):
|
||||
|
||||
1. **n8n-nodes-base.code** - JavaScript/Python scripting
|
||||
2. **n8n-nodes-base.httpRequest** - HTTP API calls
|
||||
3. **n8n-nodes-base.webhook** - Event-driven triggers
|
||||
4. **n8n-nodes-base.set** - Data transformation
|
||||
5. **n8n-nodes-base.if** - Conditional routing
|
||||
6. **n8n-nodes-base.manualTrigger** - Manual workflow execution
|
||||
7. **n8n-nodes-base.respondToWebhook** - Webhook responses
|
||||
8. **n8n-nodes-base.scheduleTrigger** - Time-based triggers
|
||||
9. **@n8n/n8n-nodes-langchain.agent** - AI agents
|
||||
10. **n8n-nodes-base.googleSheets** - Spreadsheet integration
|
||||
11. **n8n-nodes-base.merge** - Data merging
|
||||
12. **n8n-nodes-base.switch** - Multi-branch routing
|
||||
13. **n8n-nodes-base.telegram** - Telegram bot integration
|
||||
14. **@n8n/n8n-nodes-langchain.lmChatOpenAi** - OpenAI chat models
|
||||
15. **n8n-nodes-base.splitInBatches** - Batch processing
|
||||
16. **n8n-nodes-base.openAi** - OpenAI legacy node
|
||||
17. **n8n-nodes-base.gmail** - Email automation
|
||||
18. **n8n-nodes-base.function** - Custom functions
|
||||
19. **n8n-nodes-base.stickyNote** - Workflow documentation
|
||||
20. **n8n-nodes-base.executeWorkflowTrigger** - Sub-workflow calls
|
||||
|
||||
**Note:** LangChain nodes use the `@n8n/n8n-nodes-langchain.` prefix, core nodes use `n8n-nodes-base.`
|
||||
- **Load Caveman Skill:** At the beginning of every session, you MUST load/activate the `caveman` skill (e.g., set intensity level to `lite` or `full`).
|
||||
- **Output Constraints:** Strictly adhere to the Caveman output constraints (drop articles, filler words, and pleasantries; respond terse like a smart caveman; keep technical terms exact; code must remain unchanged).
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { PDFParse } from "pdf-parse";
|
||||
// @ts-ignore
|
||||
import pdf from "pdf-parse";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
|
|
@ -16,11 +17,9 @@ export async function POST(request: NextRequest) {
|
|||
const arrayBuffer = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
// Extract text from PDF using PDFParse v2 API
|
||||
const parser = new PDFParse({ data: buffer });
|
||||
const pdfData = await parser.getText();
|
||||
// Extract text from PDF
|
||||
const pdfData = await pdf(buffer);
|
||||
const text = pdfData.text;
|
||||
await parser.destroy();
|
||||
|
||||
// Extract candidate name from file name (strip extension)
|
||||
const name = file.name.replace(/\.[^/.]+$/, "");
|
||||
|
|
@ -76,9 +75,8 @@ export async function POST(request: NextRequest) {
|
|||
message: "CVs processed and forwarded to n8n successfully",
|
||||
data: responseData,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
console.error("Error in parse-cv route:", error);
|
||||
const errorMessage = error instanceof Error ? error.message : "Internal server error";
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
||||
return NextResponse.json({ error: error.message || "Internal server error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
interface DataCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function DataCard({ title, description, children }: DataCardProps) {
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-slate-50">
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-1">{title}</h3>
|
||||
<p className="text-slate-600 text-sm mb-4">{description}</p>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
interface PrimaryButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function PrimaryButton({ children, className = "", ...props }: PrimaryButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={`bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md shadow-sm transition-colors text-sm ${className}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
interface StatusBadgeProps {
|
||||
label: string;
|
||||
variant?: "primary" | "secondary";
|
||||
}
|
||||
|
||||
export function StatusBadge({ label, variant = "primary" }: StatusBadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
variant === "primary"
|
||||
? "bg-blue-600 text-white"
|
||||
: "bg-slate-50 text-slate-600"
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
# Phase 1: Discovery & Scope
|
||||
|
||||
This document establishes the business boundaries, user personas, backlog of user stories, and Definition of Done (DoD) for the AI Recruitment Platform.
|
||||
|
||||
## User Personas
|
||||
|
||||
* **Technical Recruiter**: Focuses on sourcing, intake, initial AI-assisted screening, seniority verification, and tracking candidates through the hiring pipeline.
|
||||
* **Hiring Manager**: Focuses on reviewing pre-screened and scored candidates, evaluating role suitability, and conducting structured interviews.
|
||||
* **Candidate**: Interacts with the platform to submit applications/CVs and receives automated status updates.
|
||||
|
||||
## Prioritized Backlog of User Stories
|
||||
|
||||
1. **CV Intake**
|
||||
* *User Story*: As a recruiter, I want to upload candidate PDFs via the Web UI so they can be processed and stored.
|
||||
* *Acceptance Criteria*:
|
||||
* Drag-and-drop or file selector for PDF CVs.
|
||||
* Successful text extraction and ingestion.
|
||||
* Parsing and storage of extracted candidate info.
|
||||
2. **Semantic Ranking**
|
||||
* *User Story*: As a recruiter, I want a ranked list of candidates matching an open job description based on contextual relevance.
|
||||
* *Acceptance Criteria*:
|
||||
* Compute cosine distance between job embeddings and candidate embeddings.
|
||||
* Display ranked matches with normalized similarity score between -1 and 1.
|
||||
3. **Profile Summary**
|
||||
* *User Story*: As a recruiter, I want a terse AI-generated candidate summary to speed up screening.
|
||||
* *Acceptance Criteria*:
|
||||
* Display a short summary on candidate profiles.
|
||||
* Synthesized from parsed resume details.
|
||||
4. **Seniority Guard**
|
||||
* *User Story*: As a recruiter, I want automatic seniority detection to route candidates to the correct interview pool.
|
||||
* *Acceptance Criteria*:
|
||||
* AI-based classification (e.g., Junior, Mid, Senior).
|
||||
* Correct pool routing matches candidate seniority level.
|
||||
5. **Unified Scoring**
|
||||
* *User Story*: As a hiring manager, I want to compare candidate suitability using a standardized score.
|
||||
* *Acceptance Criteria*:
|
||||
* Standardized scoring schema with AI evaluation.
|
||||
* JSON structure containing keys: `summary`, `classification`, `suggestions`, and `riskLevel`.
|
||||
6. **Stage Progression**
|
||||
* *User Story*: As a recruiter, I want candidate stages to update dynamically, triggering confirmation emails.
|
||||
* *Acceptance Criteria*:
|
||||
* Visual stage progression pipeline.
|
||||
* Updating stage triggers background event notifications.
|
||||
|
||||
## Definition of Done (DoD)
|
||||
|
||||
* **Type Safety**: Fully typed Next.js App Router with TypeScript (no `any` types where avoidable).
|
||||
* **Database Schema**: Supabase relational database schema complete with verified `pgvector` distance metrics.
|
||||
* **Linting & Quality**: Zero TypeScript compilation or linting warnings/errors.
|
||||
* **Git Quality Gates**: Active Git quality gates (hooks) verifying builds, conventional commit messages, linting, and formatting.
|
||||
|
|
@ -12,7 +12,6 @@ const eslintConfig = defineConfig([
|
|||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
"scripts/**",
|
||||
]),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
export interface LogPayload {
|
||||
message: string;
|
||||
error?: Error | unknown;
|
||||
latencyMs?: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
private static format(level: "INFO" | "WARN" | "ERROR", payload: LogPayload): string {
|
||||
const timestamp = new Date().toISOString();
|
||||
const parts: string[] = [`[${timestamp}] [${level}] ${payload.message}`];
|
||||
|
||||
if (payload.latencyMs !== undefined) {
|
||||
parts.push(`(Latency: ${payload.latencyMs}ms)`);
|
||||
}
|
||||
|
||||
if (payload.error) {
|
||||
const errorMsg =
|
||||
payload.error instanceof Error
|
||||
? payload.error.stack || payload.error.message
|
||||
: String(payload.error);
|
||||
parts.push(`\nError: ${errorMsg}`);
|
||||
}
|
||||
|
||||
if (payload.metadata && Object.keys(payload.metadata).length > 0) {
|
||||
parts.push(`\nMetadata: ${JSON.stringify(payload.metadata, null, 2)}`);
|
||||
}
|
||||
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
static info(message: string, metadata?: Record<string, unknown>, latencyMs?: number) {
|
||||
console.log(this.format("INFO", { message, metadata, latencyMs }));
|
||||
}
|
||||
|
||||
static warn(message: string, metadata?: Record<string, unknown>, latencyMs?: number) {
|
||||
console.warn(this.format("WARN", { message, metadata, latencyMs }));
|
||||
}
|
||||
|
||||
static error(
|
||||
message: string,
|
||||
error?: Error | unknown,
|
||||
metadata?: Record<string, unknown>,
|
||||
latencyMs?: number
|
||||
) {
|
||||
console.error(this.format("ERROR", { message, error, metadata, latencyMs }));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
-- Enable pgvector extension
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
|
||||
-- Recruiters table
|
||||
CREATE TABLE IF NOT EXISTS recruiters (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- Jobs table
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title TEXT NOT NULL,
|
||||
requirements JSONB NOT NULL,
|
||||
embedding vector(1536),
|
||||
recruiter_id UUID REFERENCES recruiters(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- Candidates table
|
||||
CREATE TABLE IF NOT EXISTS candidates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
contact_info JSONB NOT NULL,
|
||||
embedding vector(1536),
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- Interviews table
|
||||
CREATE TABLE IF NOT EXISTS interviews (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
candidate_id UUID NOT NULL REFERENCES candidates(id) ON DELETE CASCADE,
|
||||
job_id UUID NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
||||
interview_date TIMESTAMPTZ NOT NULL,
|
||||
stage TEXT NOT NULL, -- Technical, Cultural, etc.
|
||||
feedback TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- Scores table
|
||||
CREATE TABLE IF NOT EXISTS scores (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
candidate_id UUID NOT NULL REFERENCES candidates(id) ON DELETE CASCADE,
|
||||
interview_id UUID REFERENCES interviews(id) ON DELETE CASCADE,
|
||||
ai_score FLOAT NOT NULL,
|
||||
evaluation JSONB NOT NULL, -- Hold summary, classification, suggestions, riskLevel
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
CONSTRAINT check_evaluation_schema CHECK (
|
||||
evaluation ? 'summary' AND
|
||||
evaluation ? 'classification' AND
|
||||
evaluation ? 'suggestions' AND
|
||||
evaluation ? 'riskLevel'
|
||||
)
|
||||
);
|
||||
|
||||
-- Cosine distance match function
|
||||
CREATE OR REPLACE FUNCTION match_candidates(
|
||||
query_embedding vector(1536),
|
||||
match_threshold float,
|
||||
match_count int
|
||||
)
|
||||
RETURNS TABLE (
|
||||
id uuid,
|
||||
name text,
|
||||
contact_info jsonb,
|
||||
embedding vector(1536),
|
||||
similarity float
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
candidates.id,
|
||||
candidates.name,
|
||||
candidates.contact_info,
|
||||
candidates.embedding,
|
||||
(1 - (candidates.embedding <=> query_embedding))::float AS similarity
|
||||
FROM candidates
|
||||
WHERE (1 - (candidates.embedding <=> query_embedding)) > match_threshold
|
||||
ORDER BY similarity DESC
|
||||
LIMIT match_count;
|
||||
END;
|
||||
$$;
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
// Overriding the default theme colors to restrict to the whitelist.
|
||||
colors: {
|
||||
transparent: "transparent",
|
||||
current: "currentColor",
|
||||
white: "#ffffff",
|
||||
slate: {
|
||||
50: "#f8fafc",
|
||||
600: "#475569",
|
||||
900: "#0f172a",
|
||||
},
|
||||
blue: {
|
||||
600: "#2563eb",
|
||||
700: "#1d4ed8",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
export default config;
|
||||
1
types.d.ts
vendored
1
types.d.ts
vendored
|
|
@ -1 +0,0 @@
|
|||
declare module 'pdf-parse';
|
||||
Loading…
Reference in a new issue