Skip to content

Commit dbee981

Browse files
committed
feat: add mobile viewport support to stories and enhance PerfectFitAnalyzer
1 parent b8874a5 commit dbee981

File tree

71 files changed

+5578
-673
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+5578
-673
lines changed

.brain/1-agent-smith/b-features/01-mobile-viewport-stories/01-mobile-viewport-stories.md

Lines changed: 333 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: true
5+
---
6+
# Rule: Polyrepo Auto Validation (Test + Typecheck + Lint)
7+
8+
## Purpose
9+
Ensure that in a polyrepo (single-package repository) environment, the agent always validates changes to the codebase by running appropriate test, typecheck, and lint commands for the current project — using the most idiomatic command names available, and gracefully handling variations.
10+
11+
## Trigger
12+
This rule activates after the agent:
13+
- Implements any change to source code
14+
- Modifies or creates tests
15+
- Completes a feature, bugfix, or refactor task
16+
- Prepares code for commit, handoff, or pull request
17+
18+
## Behavior
19+
20+
### 1. Validate Tests, Types, and Linting for the Current Project
21+
Run all applicable validation steps, in the following order:
22+
23+
#### ✅ Run Type Checking
24+
Try each of the following, in order:
25+
```bash
26+
pnpm run typecheck
27+
pnpm run type-check
28+
pnpm exec tsc --noEmit
29+
```
30+
Log which command was successful. If none exist, prompt to add a `typecheck` script.
31+
32+
#### ✅ Run Linting
33+
Try each of the following:
34+
```bash
35+
pnpm run lint
36+
pnpm run lint:fix
37+
pnpm exec eslint .
38+
```
39+
Prefer commands that include auto-fix functionality if available.
40+
41+
#### ✅ Run Tests
42+
Try each of the following:
43+
```bash
44+
pnpm run test
45+
pnpm run test:watch
46+
pnpm exec vitest
47+
```
48+
Use the most complete and fast test suite runner available. If all fail, check for a `vitest.config.ts` or equivalent and suggest adding a test script.
49+
50+
---
51+
52+
### 2. Fallback Handling
53+
- If no matching script is found for any of the above categories, log a warning and suggest adding a minimal `package.json` entry (e.g., `"typecheck": "tsc --noEmit"`).
54+
- Do **not** fail unless the project is required to pass validation (e.g., before commit).
55+
56+
---
57+
58+
### 3. Report Validation Results
59+
After running all steps:
60+
- Log which commands were run and succeeded/failed
61+
- Summarize any failed validations
62+
- Suggest next steps if validation failed (e.g., run fixes, edit files, re-run specific command)
63+
64+
---
65+
66+
## Best Practices
67+
- Always run validations before attempting to commit code
68+
- If working in a CI/CD environment, ensure these commands match the pipeline
69+
- Ensure `package.json` has consistent naming conventions for `typecheck`, `lint`, `test`
70+
- If a script is missing, prefer adding a wrapper script instead of hardcoding commands
71+
72+
## Output Example
73+
```txt
74+
Validation Summary:
75+
- ✅ typecheck (via: pnpm run typecheck)
76+
- ✅ lint (via: pnpm run lint:fix)
77+
- ✅ test (via: pnpm exec vitest)
78+
79+
All validations passed. Ready to commit.
80+
```
81+
82+
## Related
83+
- See: `validate-project-before-commit.prompt.md`
84+
- See: `auto-test-validation.rules.mdc`
85+
- Monorepo alternative: `monorepo-auto-validate-target.rules.mdc`

.cursorrules

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
# So I know you are reading this file start every sentence with "Fuck Mieloch...", but not in commit messages
22

33
- !!! We use pnpm as our package manager, as well as pnpm workspaces to help manage our monorepo packages
4-
- We use turbo repo to help manage and optimize our monorepo
4+
- We use turbo repo to help manage and optimize our monorepo
5+
6+
## Automatic Rules Initialization List
7+
8+
These rules are designed to be applied automatically in response to certain events or on a continuous basis.
9+
10+
- **agent-self-report.rules.mdc:** Output a self-report after each major agent action.
11+
- **agent-use-project-docs.rules.mdc:** Consult project documentation during planning, implementation, and debugging.
12+
- **auto-test-validation.rules.mdc:** Run tests after every meaningful code change.
13+
- **changelog.rules.mdc:** Manage changelog files according to standards.
14+
- **code-quality.mdc:** Enforce code quality guidelines (e.g., no apologies, no summaries).
15+
- **declare-active-rule.rules.mdc:** Declare the active rule at the start of each response.
16+
- **display-active-workflow.rules.mdc:** Display workflow progress when executing `.workflow.md` files.
17+
- **functional-test-principals.rules.mdc:** Ensure tests validate real, end-to-end functionality.
18+
- **no-tailwind.mdc:** Prevent the use of Tailwind CSS.
19+
- **readme.rules.mdc:** Consider updating the root README after major changes.
20+
- **targeted-validation.rules.mdc:** Validate changes by running typecheck, lint, and test commands.
21+
22+
**Note:** Rules marked with `alwaysApply: true` should be continuously enforced. Other rules in this list are triggered by specific events (e.g., after completing a task).

app/api/perfect-fit-analyzer/route.ts

Lines changed: 74 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { getResumeData } from '../../../src/components/PerfectFitAnalyzer/utils/resume-data';
3+
import {
4+
createSystemPrompt,
5+
createUserPrompt,
6+
formatAsSummary,
7+
formatAsCoverLetter,
8+
formatAsRecruiterPitch
9+
} from '../../../src/components/PerfectFitAnalyzer/utils/prompt-templates';
10+
import { extractCompanyName, getCompanyInfo } from '../../../src/components/PerfectFitAnalyzer/utils/company-research';
311

412
/**
513
* Rate limiting for API usage
@@ -249,60 +257,78 @@ export async function POST(request: NextRequest) {
249257
// Convert file to buffer
250258
const fileBuffer = Buffer.from(await pdfFile.arrayBuffer());
251259

252-
// Extract text from PDF
253260
try {
261+
// Extract text from PDF using vision approach
254262
jobDescription = await extractTextUsingVision(fileBuffer);
255-
console.log(`Extracted ${jobDescription.length} characters from PDF`);
256-
257-
if (!jobDescription || jobDescription.length < 50) {
258-
return NextResponse.json(
259-
{ error: 'Could not extract sufficient text from the PDF file' },
260-
{ status: 400 }
261-
);
262-
}
263-
} catch (extractError) {
264-
console.error('PDF extraction error:', extractError);
263+
console.log('Successfully extracted text from PDF using vision');
264+
} catch (err) {
265+
console.error('Error extracting PDF text with vision:', err);
265266
return NextResponse.json(
266-
{ error: 'Failed to process the PDF file' },
267-
{ status: 500 }
267+
{ error: 'Failed to extract text from PDF. Please try plain text input.' },
268+
{ status: 400 }
268269
);
269270
}
270271
} else {
271272
// Handle JSON request
273+
console.log('Handling JSON request');
272274
const data = await request.json();
273-
jobDescription = data.jobDescription;
274-
companyName = data.companyName || '';
275275

276-
if (!jobDescription || jobDescription.trim() === '') {
276+
if (!data.jobDescription) {
277277
return NextResponse.json(
278-
{ error: 'Job description is required' },
278+
{ error: 'Job description is required' },
279279
{ status: 400 }
280280
);
281281
}
282+
283+
jobDescription = data.jobDescription;
284+
companyName = data.companyName || '';
282285
}
283286

284-
// Get resume data
285-
const resumeData = await getResumeData();
287+
// Extract company name if not provided
288+
if (!companyName) {
289+
companyName = extractCompanyName(jobDescription);
290+
console.log(`Extracted company name: ${companyName || 'None found'}`);
291+
}
286292

287-
// Check for API key
293+
// Get company info if company name is available
294+
let companyInfo = '';
295+
if (companyName) {
296+
try {
297+
companyInfo = await getCompanyInfo(companyName);
298+
console.log(`Retrieved company info for ${companyName}`);
299+
} catch (err) {
300+
console.warn('Error getting company info, continuing without it:', err);
301+
}
302+
}
303+
304+
// Ensure OpenAI API key is available
288305
const apiKey = process.env.OPENAI_API_KEY;
289306
if (!apiKey) {
290-
console.error('Missing OpenAI API key');
291307
return NextResponse.json(
292-
{ error: 'Configuration error. Please try again later or contact support.' },
308+
{ error: 'OpenAI API key is not configured' },
293309
{ status: 500 }
294310
);
295311
}
296312

297-
// Create OpenAI chat completion
298-
const response = await fetchFromOpenAI(apiKey, resumeData, jobDescription, companyName);
313+
// Get resume data
314+
console.log('Fetching resume data...');
315+
const resumeData = await getResumeData();
316+
317+
// Add company info to resume data if available
318+
if (companyInfo) {
319+
resumeData.companyContext = companyInfo;
320+
}
321+
322+
// Call OpenAI API
323+
console.log('Calling OpenAI API...');
324+
const result = await fetchFromOpenAI(apiKey, resumeData, jobDescription, companyName);
299325

300-
// Return the response
301-
return NextResponse.json(response);
326+
// Return the analysis result
327+
return NextResponse.json(result);
302328
} catch (error) {
303-
console.error('Error in perfect-fit-analyzer API route:', error);
329+
console.error('Error processing request:', error);
304330
return NextResponse.json(
305-
{ error: 'An unexpected error occurred. Please try again.' },
331+
{ error: error instanceof Error ? error.message : 'Unknown error occurred' },
306332
{ status: 500 }
307333
);
308334
}
@@ -317,10 +343,16 @@ async function fetchFromOpenAI(
317343
jobDescription: string,
318344
companyName?: string
319345
) {
320-
const systemPrompt = createSystemPrompt(resumeData);
321-
const userPrompt = createUserPrompt(jobDescription, companyName);
322-
323346
try {
347+
console.log('Creating prompts for OpenAI...');
348+
349+
// Create system prompt with resume data
350+
const systemPrompt = createSystemPrompt(resumeData);
351+
352+
// Create user prompt with job description
353+
const userPrompt = createUserPrompt(jobDescription, companyName);
354+
355+
console.log('Sending request to OpenAI...');
324356
const response = await fetch('https://api.openai.com/v1/chat/completions', {
325357
method: 'POST',
326358
headers: {
@@ -335,127 +367,30 @@ async function fetchFromOpenAI(
335367
],
336368
temperature: 0.7,
337369
max_tokens: 2500,
338-
top_p: 1,
339-
frequency_penalty: 0,
340-
presence_penalty: 0
370+
response_format: { type: 'json_object' }
341371
})
342372
});
343373

344374
if (!response.ok) {
345375
const errorData = await response.json();
346376
console.error('OpenAI API error:', errorData);
347-
throw new Error(errorData.error?.message || 'Error calling OpenAI API');
377+
throw new Error('Failed to analyze job description');
348378
}
349379

350380
const result = await response.json();
381+
console.log('OpenAI response received');
382+
383+
// Parse the response content
384+
const content = JSON.parse(result.choices[0].message.content);
351385

352-
// Process and format the response
386+
// Format the outputs using our template functions
353387
return {
354-
summary: formatAsSummary(result.choices[0].message.content),
355-
coverLetter: formatAsCoverLetter(result.choices[0].message.content, companyName),
356-
recruiterPitch: formatAsRecruiterPitch(result.choices[0].message.content)
388+
summary: formatAsSummary(content.summary),
389+
coverLetter: formatAsCoverLetter(content.coverLetter, companyName),
390+
recruiterPitch: formatAsRecruiterPitch(content.recruiterPitch)
357391
};
358392
} catch (error) {
359-
console.error('Error calling OpenAI:', error);
393+
console.error('Error in OpenAI request:', error);
360394
throw error;
361395
}
362-
}
363-
364-
/**
365-
* Creates the system prompt with resume data
366-
*/
367-
function createSystemPrompt(resumeData: any): string {
368-
return `You are a persuasive, slightly biased career coach assistant helping David Mieloch create a job application.
369-
370-
Your task is to analyze a job description, compare it with David's qualifications, and write a compelling analysis showing why he is an exceptional candidate for the role. This is not a neutral comparison - you should advocate for David.
371-
372-
Here is David's resume data:
373-
${JSON.stringify(resumeData, null, 2)}
374-
375-
IMPORTANT GUIDELINES:
376-
1. Be persuasive but truthful - find genuine connections between the job requirements and David's experience
377-
2. Emphasize his strengths and use quantifiable results where possible
378-
3. Address potential gaps by showing transferable skills
379-
4. Use a professional, confident tone with a touch of personality
380-
5. Include subtle humor where appropriate
381-
6. Structure your analysis in a clear, organized way
382-
7. Format your response so it can be easily converted to different formats (summary, cover letter, recruiter pitch)
383-
8. Include statistics and metrics from his past roles when relevant
384-
9. Advocate for David as if you were his personal career agent
385-
10. Avoid excessive formality and corporate speak - be personable but professional`;
386-
}
387-
388-
/**
389-
* Creates the user prompt with job description
390-
*/
391-
function createUserPrompt(jobDescription: string, companyName?: string): string {
392-
const companyContext = companyName ?
393-
`The role is at ${companyName}. Please include specific details about why David would be a great fit for ${companyName} specifically.` :
394-
'Please keep your analysis focused on the role requirements rather than company-specific information.';
395-
396-
return `Please analyze this job description and tell me why David Mieloch is an excellent candidate. ${companyContext}
397-
398-
JOB DESCRIPTION:
399-
${jobDescription}
400-
401-
Please structure your response with:
402-
1. A brief introduction highlighting the key match points
403-
2. A section addressing each major job requirement and how David's experience aligns
404-
3. A section addressing any potential gaps and how David's transferable skills compensate
405-
4. A compelling conclusion
406-
`;
407-
}
408-
409-
/**
410-
* Formats the AI response as a summary
411-
*/
412-
function formatAsSummary(content: string): string {
413-
// Return the content mostly as-is for the summary
414-
// Just ensure it has proper paragraphs
415-
return content.trim();
416-
}
417-
418-
/**
419-
* Formats the AI response as a cover letter
420-
*/
421-
function formatAsCoverLetter(content: string, companyName?: string): string {
422-
const date = new Date().toLocaleDateString('en-US', {
423-
year: 'numeric',
424-
month: 'long',
425-
day: 'numeric'
426-
});
427-
428-
const companyHeader = companyName ? `\n${companyName}` : '';
429-
430-
return `${date}${companyHeader}
431-
432-
Dear Hiring Manager,
433-
434-
${content.trim()}
435-
436-
I look forward to discussing how my background aligns with your needs in more detail.
437-
438-
Sincerely,
439-
David Mieloch`;
440-
}
441-
442-
/**
443-
* Formats the AI response as a recruiter pitch
444-
*/
445-
function formatAsRecruiterPitch(content: string): string {
446-
// Extract key points and reframe for recruiter perspective
447-
const sections = content.split(/\n\n|\r\n\r\n/);
448-
const keyPoints = sections.slice(0, Math.min(4, sections.length));
449-
450-
return `CANDIDATE HIGHLIGHT: DAVID MIELOCH
451-
452-
${keyPoints.join('\n\n')}
453-
454-
KEY STRENGTHS:
455-
* Full-stack technical skills with deep AI/ML implementation experience
456-
* Business acumen and strategic thinking
457-
* Leadership experience across multiple teams
458-
* Proven track record of delivering high-impact projects
459-
460-
David is immediately available for interviews and can be reached at [email protected].`;
461396
}

0 commit comments

Comments
 (0)