feat: NLP search - #230
Conversation
feat: AI generated user profile details feat: AI GitHub Repository Summarizer
📝 WalkthroughWalkthroughChangesAI services now generate repository pitches, profile enhancements, and local embeddings. Projects and developer profiles persist embeddings and use semantic search with keyword fallback. The web app adds theme selection, AI profile enhancement controls, and mobile dashboard-tour updates. AI and semantic features
Web experience updates
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ProfileForm
participant UsersController
participant UsersService
participant AiService
participant OpenAI
ProfileForm->>UsersController: POST /users/me/enhance
UsersController->>UsersService: enhanceProfile(userId, headline, bio)
UsersService->>AiService: enhanceProfile(currentBio, headline, projects)
AiService->>OpenAI: request structured profile improvements
OpenAI-->>AiService: return bio and headline
AiService-->>UsersService: return enhanced profile
UsersService-->>UsersController: return enhanced profile
UsersController-->>ProfileForm: update form fields
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed: one or more packages not found in the registry. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/api/src/projects/projects.service.ts (2)
790-861: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy liftSemantic search breaks pagination and drops filtered results.
The vector query applies
LIMIT ${take} OFFSET ${skip}before any other filter. Three defects follow.
totalItemsis wrong.wherebecomesid: { in: projectIds }, andprojectIdsholds at mosttakeIDs from the current page.count({ where })therefore returns the page size, not the number of semantic matches.totalPagescollapses to 1 andhasNextPageis alwaysfalse. The client cannot reach page 2 of a semantic result set.
query.userIdandquery.technologyare not applied in the vector query. They are applied afterwards against the already-paginated ID set. A search combined with a technology filter returns fewer thanlimitrows, or zero rows, while matching projects sit on later vector pages.
query.sortis silently ignored whenprojectIdsis set, becauseorderBybecomesundefined.Apply the filters and the count in the vector stage, then paginate. Push
userIdand technology predicates into the raw query, run a separateCOUNT(*)over the same predicate fortotalItems, and keepLIMIT/OFFSETfor the page slice.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/projects/projects.service.ts` around lines 790 - 861, The semantic-search path in the project listing method paginates IDs before applying filters and counts only the current page. Update the vector-search stage to include query.userId and query.technology predicates, run a separate COUNT over the same semantic and filter predicates for totalItems, then apply LIMIT/OFFSET only to the page query. Preserve query.sort ordering when loading the resulting projects, and ensure projectIds represents the paginated slice without corrupting totalPages or hasNextPage.
71-77: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winProvide
AiServicein the spec before constructingProjectsService.The constructor now requires
AiService, but every directnew ProjectsService(...)call inprojects.service.spec.tsonly passes four dependencies. These direct constructors do not receive the sixth required argument, so the spec failures will not come fromAiModuleresolution; fix the constructor calls instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/projects/projects.service.ts` around lines 71 - 77, Update every direct ProjectsService construction in projects.service.spec.ts to pass an AiService instance as the sixth dependency, matching the constructor signature. Provide the mock or test fixture before constructing ProjectsService; do not rely on AiModule resolution.apps/api/src/repository-scanner/github-repository-snapshot.service.ts (1)
98-111: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winValidate model output before returning
aiPitch.
AiService.summarizeRepositoryusesJSON.parseon model output without Zod validation. A valid JSON object can still omit required fields or use invalid field types. This method then returns that value asaiPitch, which can violateGithubRepositoryAnalysisPreviewResponse.Validate the generated object against the contract schema. Convert invalid output to
null.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/repository-scanner/github-repository-snapshot.service.ts` around lines 98 - 111, Validate the result of AiService.summarizeRepository before assigning it to aiPitch in the repository analysis flow. Use the existing GithubRepositoryAnalysisPreviewResponse contract schema to parse the generated object, and convert validation failures to null so the returned response always satisfies its declared shape.
🧹 Nitpick comments (2)
apps/api/src/projects/projects.service.ts (1)
933-940: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the unsound cast with a type predicate.
filter(Boolean)does not narrow(T | undefined)[]toT[]. Theas typeof projectscast at line 940 hides that gap. Use aMaplookup and an explicit type predicate.♻️ Proposed refactor
- const sortedProjects = projectIds - ? projectIds.map(id => projects.find(p => p.id === id)).filter(Boolean) - : projects; + const projectsById = new Map(projects.map((p) => [p.id, p])); + const sortedProjects = projectIds + ? projectIds + .map((id) => projectsById.get(id)) + .filter((p): p is (typeof projects)[number] => p !== undefined) + : projects;- data: sortedProjects as typeof projects, + data: sortedProjects,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/projects/projects.service.ts` around lines 933 - 940, Update the sortedProjects construction in the project-list method to use a Map-based ID lookup and filter results with an explicit type predicate, narrowing the collection to project values without relying on the `as typeof projects` cast. Remove the cast from the returned data while preserving the requested projectIds ordering and existing fallback to projects.apps/web/components/theme-toggle.tsx (1)
26-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse dynamic primary-scale tokens for the icon colors.
text-orange-500andtext-blue-400bypass the light and dark theme token scales. Replace them withtext-primary-baseortext-primary-400, as appropriate.As per coding guidelines, use design tokens defined in
globals.cssinstead of arbitrary color values.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/components/theme-toggle.tsx` around lines 26 - 37, Update the Sun and Moon icon class names in the theme toggle to use the dynamic primary-scale design tokens defined in globals.css instead of text-orange-500 and text-blue-400. Preserve the existing theme-toggle behavior and choose text-primary-base or text-primary-400 as appropriate for each icon.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/ai/ai.service.ts`:
- Line 39: Update apps/api/src/ai/ai.service.ts at lines 39-39 and 67-67: in the
repository-pitch and enhanced-profile response paths, parse the LLM JSON and
validate each result with its corresponding Zod schema from `@repo/contracts`
before returning it. Preserve the existing safe fallback for repository-pitch
validation failures, and throw the appropriate NestJS exception for
enhanced-profile validation failures.
- Around line 77-81: Update the extractor initialization flow around the
extractor field and transformer pipeline creation to use a shared initialization
promise: assign the promise before awaiting it so concurrent requests reuse the
same in-flight initialization, and clear the promise when initialization fails
so later requests can retry. Preserve the existing dynamic import and successful
extractor assignment behavior.
- Around line 10-14: Update AiService construction to instantiate OpenAI only
when process.env.OPENAI_API_KEY is present, and make the openai member nullable
or optional. Guard all OpenAI usage so repository-summary requests return null
without credentials, while profile-enhancement requests throw the appropriate
NestJS service exception when credentials are missing.
In `@apps/api/src/projects/projects.service.ts`:
- Around line 200-206: Move embedding generation at
apps/api/src/projects/projects.service.ts:200-206 and :528-534 before their
respective this.prisma.$transaction calls, using the already available project
input data and passing the computed vector into the transaction; at :693-699,
remove the awaited generation from the transaction and enqueue a BullMQ job
after the transaction commits, following the repository’s constants → module →
service → processor pattern.
- Line 792: Update the threshold value in the Vector Semantic Search comment
near MAX_COSINE_DISTANCE to 0.78, ensuring the documentation matches the
configured constant.
In `@apps/api/src/repository-scanner/github-repository-snapshot.service.ts`:
- Around line 62-69: Update the return type of the relevant repository snapshot
service method to use Promise<GithubRepositoryAnalysisPreviewResponse> directly.
Remove the inline aiPitch intersection because
GithubRepositoryAnalysisPreviewResponse already defines it, and ensure the
response type is imported from `@repo/contracts`.
- Around line 98-100: Update previewRepositoryAnalysis around the
aiService.summarizeRepository call so AI summarization is enqueued through the
existing BullMQ queue instead of awaited inline. Add the corresponding
job/result completion flow to populate aiPitch asynchronously, while preserving
the controller’s immediate response behavior and using the repository’s
established queue and worker symbols.
In `@apps/api/src/users/users.controller.ts`:
- Around line 23-25: Define profile-enhancement request and response Zod schemas
in `@repo/contracts` with exported inferred types. In
apps/api/src/users/users.controller.ts lines 23-25, apply
ZodValidationPipe(profileEnhanceRequestSchema) to the body and validate the
service result with the response schema before returning it. In
apps/web/components/profile-form.tsx lines 152-155, replace the inline response
shape with the inferred response type imported from `@repo/contracts`.
In `@apps/api/src/users/users.service.ts`:
- Around line 80-85: Update the semantic-search flow around the vector query and
its related total-count logic: join or filter the ranked query to confirmed
users before counting or paginating matches, compute totalItems from the full
confirmed match set, then apply take and skip to the confirmed results. Preserve
the existing relevance ordering and ensure the returned page and totalPages
reflect all confirmed semantic matches.
- Around line 26-31: UsersService is missing the NestJS Logger used by other API
services. Update the UsersService class to initialize a private readonly logger
with Logger(UsersService.name), and use that logger for non-sensitive
operational messages only while keeping the existing Prisma and AiService
dependencies unchanged.
- Around line 350-359: Update the embedding logic in updateProfile to build
combinedText from each searchable field in the merged state, using the incoming
data value when provided and user.developerProfile as the fallback. Only call
generateEmbedding and update DeveloperProfile.embedding when at least one
searchable field changed; leave the existing embedding untouched for unrelated
partial updates such as hasSeenDashboardTour.
In `@apps/web/components/profile-form.tsx`:
- Around line 160-161: Update the catch block in the profile enhancement request
to handle ApiError explicitly and pass its message to toast.error instead of
using the static fallback. Preserve the existing generic fallback for
non-ApiError failures and ensure the caught error is no longer unused.
In `@apps/web/components/theme-toggle.tsx`:
- Line 15: Update the useTheme destructuring in the theme toggle component to
bind only setTheme, removing the unused theme variable.
In `@docker-compose.yml`:
- Line 5: Update the PostgreSQL container image declaration to a pgvector
PostgreSQL 18 tag so it matches the mounted version-specific PGDATA path;
preserve the existing volume configuration.
In `@package.json`:
- Around line 30-32: Move the undeclared imports to the workspaces that actually
use them: add openai to apps/api/package.json for the code path that imports it,
and add `@xenova/transformers` to packages/database/package.json for
packages/database/prisma/seeders/seedProjects.ts. If either import is no longer
needed, remove it instead of relying on the root-level dependency, and keep the
affected workspace manifests as the source of truth.
In `@packages/database/prisma/seeders/seedProjects.ts`:
- Around line 15-18: Update generateLocalEmbedding in seedProjects so model-load
or inference errors are not silently converted into an empty vector; either
propagate the failure to stop the seed run or record it for later reporting.
Then adjust the seeding flow that consumes generateLocalEmbedding to surface the
embedding failures in the final seeding summary instead of always reporting
success, while preserving the existing update path for projects that do receive
embeddings.
- Around line 179-202: Update the project upsert in the seeding flow to populate
publishedAt in both the create and update payloads for PUBLISHED projects.
Assign distinct deterministic dates per seeded entry, reusing the same date for
that entry across create and update, so newest and oldest sorting produce
different orders.
- Around line 245-257: Update the project technology creation loop in the
seeding flow to track each slug’s index, set isPrimary only for the first
technology, and assign the index as an incrementing sortOrder. Preserve the
existing projectId, technologyId, and source values while ensuring subsequent
technologies are non-primary and deterministically ordered.
---
Outside diff comments:
In `@apps/api/src/projects/projects.service.ts`:
- Around line 790-861: The semantic-search path in the project listing method
paginates IDs before applying filters and counts only the current page. Update
the vector-search stage to include query.userId and query.technology predicates,
run a separate COUNT over the same semantic and filter predicates for
totalItems, then apply LIMIT/OFFSET only to the page query. Preserve query.sort
ordering when loading the resulting projects, and ensure projectIds represents
the paginated slice without corrupting totalPages or hasNextPage.
- Around line 71-77: Update every direct ProjectsService construction in
projects.service.spec.ts to pass an AiService instance as the sixth dependency,
matching the constructor signature. Provide the mock or test fixture before
constructing ProjectsService; do not rely on AiModule resolution.
In `@apps/api/src/repository-scanner/github-repository-snapshot.service.ts`:
- Around line 98-111: Validate the result of AiService.summarizeRepository
before assigning it to aiPitch in the repository analysis flow. Use the existing
GithubRepositoryAnalysisPreviewResponse contract schema to parse the generated
object, and convert validation failures to null so the returned response always
satisfies its declared shape.
---
Nitpick comments:
In `@apps/api/src/projects/projects.service.ts`:
- Around line 933-940: Update the sortedProjects construction in the
project-list method to use a Map-based ID lookup and filter results with an
explicit type predicate, narrowing the collection to project values without
relying on the `as typeof projects` cast. Remove the cast from the returned data
while preserving the requested projectIds ordering and existing fallback to
projects.
In `@apps/web/components/theme-toggle.tsx`:
- Around line 26-37: Update the Sun and Moon icon class names in the theme
toggle to use the dynamic primary-scale design tokens defined in globals.css
instead of text-orange-500 and text-blue-400. Preserve the existing theme-toggle
behavior and choose text-primary-base or text-primary-400 as appropriate for
each icon.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: f201ccdb-f971-4ae7-924d-e795f9565cc1
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (23)
apps/api/src/ai/ai.module.tsapps/api/src/ai/ai.service.tsapps/api/src/app.module.tsapps/api/src/projects/projects.service.tsapps/api/src/repository-scanner/github-repository-snapshot.service.tsapps/api/src/users/users.controller.tsapps/api/src/users/users.module.tsapps/api/src/users/users.service.tsapps/web/app/globals.cssapps/web/app/layout.tsxapps/web/components/dashboard-tour.tsxapps/web/components/profile-form.tsxapps/web/components/site-header.tsxapps/web/components/theme-provider.tsxapps/web/components/theme-toggle.tsxapps/web/components/top-navbar.tsxdocker-compose.ymlpackage.jsonpackages/contracts/src/github/github-repository-analysis-preview.response.tspackages/database/prisma/migrations/20260805093227_add_ai_features/migration.sqlpackages/database/prisma/migrations/20260805115403_change_vector_dimension/migration.sqlpackages/database/prisma/schema.prismapackages/database/prisma/seeders/seedProjects.ts
| constructor() { | ||
| this.openai = new OpenAI({ | ||
| apiKey: process.env.OPENAI_API_KEY, | ||
| }); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
fd -a 'ai.service.ts|package.json|package-lock.json|pnpm-lock.yaml|yarn.lock' . | sed 's#^\./##' | head -200
echo
echo "== ai.service outline =="
ast-grep outline apps/api/src/ai/ai.service.ts || true
echo
echo "== ai.service lines =="
cat - n apps/api/src/ai/ai.service.ts | sed -n '1,140p'
echo
echo "== openai usages =="
rg -n "new OpenAI|OPENAI_API_KEY|generateEmbedding|generate.*summary|enhanc|profile" apps/api/src packages deps 2>/dev/null | head -200
echo
echo "== package versions =="
cat package.json | jq -r '.dependencies["`@repo/contracts`"], .dependencies["`@repo/database`"], .dependencies["`@repo/shared`"], .dependencies["openai"]' 2>/dev/null || true
if [ -f apps/api/package.json ]; then cat apps/api/package.json | jq -r '.dependencies["`@repo/contracts`"], .dependencies["`@repo/database`"], .dependencies["`@repo/shared`"], .dependencies["openai"]' 2>/dev/null || true; fiRepository: chrisshaddad/bootcamp-starter
Length of output: 4770
🏁 Script executed:
#!/bin/bash
set -euo pipefail
node - <<'JS'
try {
const packageJson = require('./package.json');
const apiKey = process.env.OPENAI_API_KEY;
console.log('OPENAI_API_KEY=', apiKey === undefined ? '<undefined>' : apiKey);
console.log('openai_version=', packageJson.dependencies && packageJson.dependencies.openai || 'missing');
const OpenAI = require('openai');
console.log('OpenAI_default=', OpenAI && OpenAI.default && typeof OpenAI.default);
console.log('OpenAI_default_exported=', typeof module.exports && typeof require('openai').default);
let client;
try {
client = new OpenAI.default({ apiKey });
console.log('OpenAI_default_constructor_apiKey=', client.apiKey);
} catch (err) {
console.log('OpenAI_default_constructor_error=', err && err.message, err && err.constructor && err.constructor.name);
}
try {
const OpenAINamed = require('openai').OpenAI || OpenAI.default;
client = new OpenAINamed({ apiKey });
console.log('OpenAI_named_constructor_apiKey=', client.apiKey);
} catch (err) {
console.log('OpenAI_named_constructor_error=', err && err.message, err && err.constructor && err.constructor.name);
}
} catch (err) {
console.log('module_probe_error=', err && err.message);
console.log('stack=', err && err.stack);
}
JS
echo
echo "== usages context =="
rg -n "AiService|summarizeRepository|enhanceProfile|generateEmbedding|AiController|AiModule" apps/api/src -S
echo
echo "== Nest/express startup relevant snippets =="
rg -n "HttpServer|express|fastify|listen\\(|onApplicationBootstrap|onApplicationShutdown" apps/api/src apps -S | head -120Repository: chrisshaddad/bootcamp-starter
Length of output: 8612
🌐 Web query:
OpenAI Node SDK v7 constructor apiKey undefined behavior no API key
💡 Result:
In the OpenAI Node.js SDK (v7 and other recent versions), the OpenAI client constructor enforces a credential check upon initialization [1][2][3]. If neither apiKey, adminAPIKey, nor workloadIdentity are provided to the constructor, and the corresponding environment variables (such as OPENAI_API_KEY) are not set, the SDK will immediately throw an OpenAIError with the message: "Missing credentials. Please pass an apiKey, workloadIdentity, adminAPIKey, or set the OPENAI_API_KEY or OPENAI_ADMIN_KEY environment variable." [2][3][4]. This validation occurs within the client constructor, meaning the error is thrown before any network request is attempted [1]. Key behaviors to note: - Environment Variable Fallback: If you do not pass an apiKey explicitly in the constructor options, the SDK will automatically attempt to read it from process.env['OPENAI_API_KEY'] [2][3][4]. - Empty Strings: While recent versions of the SDK (following changes in v6.x) treat an explicitly passed empty string apiKey: "" as a missing credential, subsequent fixes have been implemented to handle explicit credentials more consistently [5][1]. - Security: The SDK intentionally restricts browser usage by default to prevent secret leakage, requiring an explicit dangerouslyAllowBrowser: true flag if initialized in a browser-like environment [2][3][6]. To avoid this error, ensure that a valid key is provided in the constructor options or that the appropriate environment variable is configured in your runtime environment [2][7].
Citations:
- 1: apiKey became mandatory in v6.36.0 openai/openai-node#1957
- 2: https://app.unpkg.com/openai@7.3.0/files/src/client.ts
- 3: https://github.com/openai/openai-node/blob/5436f42d/src/client.ts
- 4: https://app.unpkg.com/openai@7.3.0/files/client.mjs
- 5: fix(client): allow explicit empty apiKey string openai/openai-node#1961
- 6: https://github.com/openai/openai-node/blob/main/README.md
- 7: https://github.com/openai/openai-node
🌐 Web query:
NestJS provider constructor error startup startup fails before first method call
💡 Result:
When a NestJS application fails at startup during provider instantiation without an explicit error message, it is often due to silent failures during the dependency injection (DI) process, such as circular dependencies or improper provider registration [1][2][3]. To troubleshoot and resolve this, follow these systematic steps: 1. Enable Debug Logging If the error is swallowed, force NestJS to output dependency resolution logs by setting the environment variable NEST_DEBUG=true [4]. This often reveals the specific point in the dependency tree where the instantiation hangs or fails. 2. Check for Circular Dependencies Circular dependencies are a common cause of silent startup hangs or "cannot resolve dependency" errors [5][6]. - Symptom: NestJS cannot determine which provider to instantiate first because they depend on each other [7][6]. - Fix: Use the forwardRef utility in both classes/modules involved [7][6]. - Note: If using circular dependencies with Request-scoped providers, the behavior is highly unpredictable; refactoring to eliminate the dependency cycle is strongly recommended over relying on workarounds [7][8]. 3. Verify Provider Registration Ensure all providers are correctly registered in the providers array of the appropriate module [5][9]. - Common Error: Placing a provider in the imports array instead of the providers array [5][10]. - Visibility: If a service is in a different module, ensure it is exported from its source module and the source module is imported into the consumer module [9][11]. 4. Inspect Constructor Logic - Self-Injection: Ensure a provider is not accidentally injecting itself, as self-injection is not supported [10]. - Logic Errors: If the application terminates silently, check for errors being thrown directly inside class constructors [1][2]. Wrapped exceptions or configuration that disables the logger (e.g., logger: false in NestFactory.create) can hide these startup errors [1]. Ensure your logger is active to see initialization exceptions [1]. 5. Scope Issues - If you use REQUEST scoped providers, be aware that these are only instantiated when a request hits the application [12]. If a provider depends on a request-scoped provider, it becomes request-scoped itself, meaning its constructor will not execute during standard application startup [12]. Top Results: [1], [5], [2], [3], [4], [7], [6]
Citations:
- 1: no error message within provider-constructors nestjs/nest#2456
- 2: https://stackoverflow.com/questions/64900297/nestjs-start-fails-silently-in-module-initialization
- 3: Module initialization is blocked indefinitely without error message when a module injects one of its own providers into another of its own providers nestjs/nest#11889
- 4: https://github.com/nestjs/docs.nestjs.com/blob/ba85c6a2/content/fundamentals/dependency-injection.md
- 5: https://docs.nestjs.com/faq/common-errors
- 6: https://www.digitalocean.com/community/tutorials/understanding-circular-dependency-in-nestjs
- 7: https://docs.nestjs.com/fundamentals/circular-dependency
- 8: Circular dependency doesn't work if Service has a Request Scoped Provider nestjs/nest#5778
- 9: https://mgregersen.dk/how-to-fix-the-nest-cant-resolve-dependencies-error-in-nestjs/
- 10: https://github.com/nestjs/docs.nestjs.com/blob/ba85c6a2/content/faq/errors.md
- 11: https://dev.to/adamthedeveloper/nestjs-dependency-injection-why-your-services-wont-inject-and-how-to-fix-it-properly-3phf
- 12: https://stackoverflow.com/questions/77071334/nestjs-appservice-instantiation-problems-when-using-dynamic-providers-with-use
Do not construct OpenAI when the API key is absent.
AiService is registered during app bootstrap, so new OpenAI({}) runs even for embedding-only deployments. The OpenAI SDK requires credentials at construction time, which can prevent the local embedding path from starting. Guard construction with process.env.OPENAI_API_KEY, make openai nullable or optional, and handle optional calls: return null for repository summaries, and throw a NestJS service exception when profile enhancement is requested without credentials.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/ai/ai.service.ts` around lines 10 - 14, Update AiService
construction to instantiate OpenAI only when process.env.OPENAI_API_KEY is
present, and make the openai member nullable or optional. Guard all OpenAI usage
so repository-summary requests return null without credentials, while
profile-enhancement requests throw the appropriate NestJS service exception when
credentials are missing.
| response_format: { type: 'json_object' }, | ||
| }); | ||
|
|
||
| return JSON.parse(response.choices[0]?.message?.content || '{}'); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Validate LLM output with contract schemas.
JSON.parse only validates JSON syntax. It allows {}, arrays, and wrong field types to pass through both declared response types. Parse each response with its Zod schema from @repo/contracts. Return the existing safe fallback or throw a NestJS exception when validation fails.
apps/api/src/ai/ai.service.ts#L39-L39: validate the repository pitch before returning it.apps/api/src/ai/ai.service.ts#L67-L67: validate the enhanced profile before returning it.
📍 Affects 1 file
apps/api/src/ai/ai.service.ts#L39-L39(this comment)apps/api/src/ai/ai.service.ts#L67-L67
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/ai/ai.service.ts` at line 39, Update
apps/api/src/ai/ai.service.ts at lines 39-39 and 67-67: in the repository-pitch
and enhanced-profile response paths, parse the LLM JSON and validate each result
with its corresponding Zod schema from `@repo/contracts` before returning it.
Preserve the existing safe fallback for repository-pitch validation failures,
and throw the appropriate NestJS exception for enhanced-profile validation
failures.
Source: Coding guidelines
| if (!this.extractor) { | ||
| // Dynamic import to support NestJS CommonJS runtime | ||
| const { pipeline } = await (eval('import("@xenova/transformers")') as Promise<typeof import('@xenova/transformers')>); | ||
| this.extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2'); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Make model initialization single-flight.
Concurrent first requests all observe this.extractor === null before any request assigns it. Each request can then load a separate transformer pipeline. This can duplicate model downloads and memory use during cold start. Store the initialization promise before awaiting it. Reset that promise if initialization fails.
🧰 Tools
🪛 Biome (2.5.6)
[error] 79-79: eval() exposes to security risks and performance issues.
(lint/security/noGlobalEval)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/ai/ai.service.ts` around lines 77 - 81, Update the extractor
initialization flow around the extractor field and transformer pipeline creation
to use a shared initialization promise: assign the promise before awaiting it so
concurrent requests reuse the same in-flight initialization, and clear the
promise when initialization fails so later requests can retry. Preserve the
existing dynamic import and successful extractor assignment behavior.
| // Save Vector Embedding | ||
| const textToEmbed = `${title} ${shortDescription || ''} ${fullDescription || ''}`; | ||
| const embedding = await this.aiService.generateEmbedding(textToEmbed); | ||
| if (embedding.length > 0) { | ||
| const vectorString = `[${embedding.join(',')}]`; | ||
| await tx.$executeRaw`UPDATE "Project" SET embedding = ${vectorString}::vector WHERE id = ${project.id}`; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Embedding generation runs inside three Prisma interactive transactions. Each site awaits this.aiService.generateEmbedding while a transaction is open. The call performs local model inference or an OpenAI request. The transaction holds its written rows and a pooled database connection for that full latency. A cold model load or a slow API response can exceed the Prisma interactive transaction timeout and abort the write with P2028. Concurrent requests multiply the held connections and can exhaust the pool.
The shared fix: compute the embedding before opening the transaction and pass the vector in, or write the embedding from a BullMQ job after the transaction commits. The BullMQ route also matches the repository guideline for async work.
apps/api/src/projects/projects.service.ts#L200-L206: move thegenerateEmbeddingcall for the imported project abovethis.prisma.$transaction. The input text is already known fromtitle,shortDescription, andfullDescriptioncomputed at lines 128-132.apps/api/src/projects/projects.service.ts#L528-L534: move thegenerateEmbeddingcall abovethis.prisma.$transaction. The input text comes fromdata, which is available before the transaction starts.apps/api/src/projects/projects.service.ts#L693-L699: the input text depends on the updated row, so enqueue a BullMQ job after the transaction commits instead of awaiting the model call inside it.
Based on the coding guideline "Route async work through BullMQ. Follow the pattern: constants → module → service → processor."
📍 Affects 1 file
apps/api/src/projects/projects.service.ts#L200-L206(this comment)apps/api/src/projects/projects.service.ts#L528-L534apps/api/src/projects/projects.service.ts#L693-L699
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/projects/projects.service.ts` around lines 200 - 206, Move
embedding generation at apps/api/src/projects/projects.service.ts:200-206 and
:528-534 before their respective this.prisma.$transaction calls, using the
already available project input data and passing the computed vector into the
transaction; at :693-699, remove the awaited generation from the transaction and
enqueue a BullMQ job after the transaction commits, following the repository’s
constants → module → service → processor pattern.
Source: Coding guidelines
| services: | ||
| postgres: | ||
| image: postgres:18 | ||
| image: pgvector/pgvector:pg16 |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== docker-compose files =="
fd -a 'docker-compose.*|compose.*' . | sed 's#^\./##' | sort
echo
echo "== relevant compose snippets =="
for f in $(fd 'docker-compose.*|compose.*' .); do
echo "--- $f"
nl -ba "$f" | sed -n '1,80p'
done
echo
echo "== search for postgres image tags / volumes =="
rg -n "pgvector/pgvector|postgres:|#\?|PGDATA|/var/lib/postgresql/18|/var/lib/postgresql/16|data:|volumes:" -S .Repository: chrisshaddad/bootcamp-starter
Length of output: 331
🌐 Web query:
pgvector pgvector image tags pg18
💡 Result:
The pgvector extension provides official Docker image tags with full support for PostgreSQL 18 [1][2]. As of the latest releases, pgvector supports PostgreSQL 18, including specific fixes for compatibility with its API changes, such as those related to vacuuming functions [3][4][5]. You can access these images on Docker Hub using tags explicitly designated for PostgreSQL 18 [1][6]. Supported variants typically include both Debian-based images, such as trixie and bookworm [1][2]. Example pull commands include: docker pull pgvector/pgvector:pg18 docker pull pgvector/pgvector:pg18-bookworm docker pull pgvector/pgvector:pg18-trixie You can also pull specific versions of the pgvector extension that support PostgreSQL 18 (e.g., version 0.8.6 or newer) by prefixing the tag: docker pull pgvector/pgvector:0.8.6-pg18 The pgvector changelogs confirm that support for PostgreSQL 18 was introduced to ensure correct functionality, including fixes for Index Searches in EXPLAIN output and performance optimizations for Hamming and Jaccard distances specific to the PostgreSQL 18 environment [3][5].
Citations:
- 1: https://hub.docker.com/r/pgvector/pgvector/tags
- 2: https://github.com/pgvector/pgvector?tab=readme-ov-file
- 3: https://api.pgxn.org/src/vector/vector-0.8.6/CHANGELOG.md
- 4: Build failure on PostgreSQL 18 beta 1 pgvector/pgvector#869
- 5: https://raw.githubusercontent.com/pgvector/pgvector/master/CHANGELOG.md
- 6: https://hub.docker.com/r/pgvector/pgvector
🏁 Script executed:
#!/bin/bash
set -euo pipefail
compose="$(fd 'docker-compose\.yml$' . | head -n 1)"
echo "compose=$compose"
echo "== file section =="
awk 'NR>=1 && NR<=30 {printf "%5d %s\n", NR, $0}' "$compose"
echo "== focused search =="
awk -v re='(pgvector/pgvector|/var/lib/postgresql/18|volumes:)' '
$0 ~ re {printf "%5d %s\n", NR, $0}
' "$compose"
echo "== docker compose config volume targets (read-only if available) =="
if command -v docker >/dev/null 2>&1; then
docker compose -f "$compose" config --volumes 2>/dev/null || true
else
echo "docker command not available"
fiRepository: chrisshaddad/bootcamp-starter
Length of output: 1281
🌐 Web query:
Docker Hub pgvector/pgvector tags pg18 bookworm
💡 Result:
As of August 5, 2026, the official pgvector/pgvector Docker Hub repository provides explicit support and tags for PostgreSQL 18 using the Debian "bookworm" base image [1][2][3]. You can pull the relevant images using the following tags: - pgvector/pgvector:pg18-bookworm [1][2] - pgvector/pgvector:0.8.6-pg18-bookworm [1][2] - pgvector/pgvector:pg18 [1][2] - pgvector/pgvector:0.8.6-pg18 [1][2] These tags are maintained by the pgvector project, with 0.8.6 being the current version providing this support [1][2]. The project also supports "trixie" as an alternative Debian base for Postgres 18 [1][2]. Note that in addition to the standard images, there are also hardened, CIS-compliant images available through the Docker Hardened Images catalog that support PostgreSQL 18 on Debian 13 [4].
Citations:
- 1: https://hub.docker.com/r/pgvector/pgvector/tags
- 2: https://github.com/pgvector/pgvector?tab=readme-ov-file
- 3: https://github.com/pgvector/pgvector/?tab=readme-ov-file
- 4: https://hub.docker.com/hardened-images/catalog/dhi/pgvector/images?order=asc&orderBy=user
🌐 Web query:
PostgreSQL initdb directory version mismatch major version data directory cannot be used
💡 Result:
The error message stating that your data directory is incompatible or cannot be used indicates that you are attempting to use a PostgreSQL data directory initialized by a different major version of the software [1][2][3][4]. PostgreSQL major versions have different internal storage formats and are not backward-compatible [2][5]. If you encounter this error while running initdb, it is because the utility is designed to protect existing data and will refuse to initialize a directory that is not empty [6][7][8]. To resolve this, choose one of the following approaches based on your needs: 1. If you do not need the existing data: You must clear the data directory or point your configuration to a new, empty directory [6][7][4]. PostgreSQL will then initialize a fresh database cluster compatible with the installed server version [2][9][4]. 2. If you need to keep the existing data: You cannot simply point a new PostgreSQL major version to an old data directory [1][2][5]. You must perform a formal upgrade of your data [5][10]: - Use pg_upgrade: This is the recommended tool for upgrading between major versions, as it can transfer data from the old directory to a new one efficiently [11][5][4]. - Use pg_dumpall: Perform a logical backup (export) of all your data from the old version, initialize a new, empty cluster with the new version, and restore your data using psql or the appropriate restore tools [2][5][4]. - Temporary Rollback: If you are in an emergency, you can pin your server version back to the older version to restore access to your data, then plan a formal migration path [2][8][4]. Special Note on Docker: If you are using the official PostgreSQL Docker images, note that recent versions (18+) have changed the default PGDATA path to a version-specific subdirectory (e.g., /var/lib/postgresql/18/docker) to make major version upgrades easier [11][2]. If you are migrating a legacy setup that used /var/lib/postgresql/data, you may need to adjust your volume mounts to match this new structure [11][2][4].
Citations:
- 1: The data directory was initialized by PostgreSQL version 11, which is not compatible with this version 12.2. docker-library/postgres#682
- 2: https://blog.path-finder.jp/troubleshooting/fatal-database-files-are-incompatible-with-server-the-data-directory-was-initial/
- 3: https://www.postgresql.org/message-id/2e0701bf-2cfe-4027-316c-4da76d08935f%40gmail.com
- 4: https://stackoverflow.com/questions/69424563/data-directory-was-initialized-by-postgresql-version-13-which-is-not-compatible
- 5: https://www.postgresql.org/docs/17/upgrading.html
- 6: https://www.postgresql.org/message-id/1707638f-71b1-6b06-5200-cc5f2c899f94%40aklaver.com
- 7: https://www.postgresql.org/message-id/j6hi01%24hud%241%40dough.gmane.org
- 8: https://postgrespro.com/list/thread-id/2455122
- 9: https://dba.stackexchange.com/questions/191328/homebrew-installed-postgresql-could-not-connect-database-files-are-incompatible
- 10: https://www.postgresql.org/docs/devel/upgrading.html
- 11: Migration from v17 to v18 appears impossible docker-library/postgres#1377
🌐 Web query:
Docker PostgreSQL image 16 default Dockerfile PGDATA
💡 Result:
For the official PostgreSQL 16 Docker image, the default PGDATA directory is /var/lib/postgresql/data [1][2][3]. In the Dockerfile for PostgreSQL 16, this is explicitly set via the environment variable: ENV PGDATA /var/lib/postgresql/data [1][2][3] It is important to note that this path is specific to PostgreSQL versions 17 and below [4][5][6]. In PostgreSQL 18 and later, the PGDATA location and VOLUME structure were changed to be version-specific (e.g., /var/lib/postgresql/18/docker) to facilitate easier database upgrades using pg_upgrade --link [4][5][6]. For PostgreSQL 16, you must mount your data volume specifically at /var/lib/postgresql/data [4][7][6]. Mounting at the parent directory (/var/lib/postgresql) will not correctly persist the database files because the Dockerfile declares /var/lib/postgresql/data as a VOLUME [1][7][6]. Failure to mount at the correct path will cause data to be written to an anonymous volume that does not persist across container recreations [4][7][6].
Citations:
- 1: https://github.com/docker-library/postgres/blob/d416768b1a7f03919b9cf0fef6adc9dcad937888/16/bookworm/Dockerfile
- 2: https://github.com/docker-library/postgres/blob/3a94d965ecbe08f4b1b255d3ed9ccae671a7a984/16/bookworm/Dockerfile
- 3: https://github.com/docker-library/postgres/blob/3b6b5fca9ca40c84b77540fc605ea8e8353b13b2/16/trixie/Dockerfile
- 4: https://hub.docker.com/_/postgres
- 5: https://hub.docker.com/_/postgres?ordering=last_updated&page=1&tab=description
- 6: https://github.com/docker-library/docs/blob/master/postgres/README.md
- 7: https://github.com/docker-library/docs/blob/ccc8b3ef/postgres/README.md
🌐 Web query:
pgvector/pgvector Dockerfile pg16 PGDATA /var/lib/postgresql/data
💡 Result:
In the official pgvector/pgvector Docker image for PostgreSQL 16, the PGDATA environment variable is set to /var/lib/postgresql/data [1]. This is the standard path used by the official PostgreSQL image upon which pgvector is based [1][2]. Key technical details: - Default PGDATA: The official pgvector:pg16 image explicitly defines ENV PGDATA=/var/lib/postgresql/data [1]. - Persistence: Because /var/lib/postgresql/data is declared as a VOLUME in the Dockerfile [1], you should mount a named volume or host directory to this path to ensure your database files persist across container restarts [3]. - Best Practices: While you can override PGDATA by setting the environment variable in your docker run command or docker-compose file, it is generally recommended to stick with the default /var/lib/postgresql/data unless you have a specific requirement to change it [4]. When mounting volumes, ensure you target the same path defined in the container to avoid creating anonymous volumes [3]. Please note that newer PostgreSQL versions and some specialized "hardened" images may use version-specific paths (e.g., /var/lib/postgresql/18/data) to facilitate upgrades [5][3][6], but the standard pgvector/pgvector:pg16 tag continues to use the legacy /var/lib/postgresql/data structure [1].
Citations:
- 1: https://hub.docker.com/layers/pgvector/pgvector/pg16/images/sha256-eb2a451bbc37d71947fafac0bb76d2992c6aafb305942a708e0d6c567eb42985
- 2: https://github.com/pgvector/pgvector
- 3: https://hub.docker.com/r/henrybit/postgresql-pgvector
- 4: https://docs.rackspace.com/docs/postgresql-with-vector-support
- 5: https://hub.docker.com/hardened-images/catalog/dhi/pgvector/guides
- 6: https://hub.docker.com/layers/pgvector/pgvector/pg18/images/sha256-766437bbab40c7d0b080d380e2976f9ca2e880ce8fe7544c60e832eceaf43c1c
Align the PostgreSQL image version with the mounted data path.
pgvector/pgvector:pg16 uses PGDATA=/var/lib/postgresql/data, but this volume is mounted at PostgreSQL 18’s version-specific path. PostgreSQL 16 also rejects a PostgreSQL 18 data directory. Use a pgvector PostgreSQL 18 image, or migrate the data and mount it at /var/lib/postgresql/data.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docker-compose.yml` at line 5, Update the PostgreSQL container image
declaration to a pgvector PostgreSQL 18 tag so it matches the mounted
version-specific PGDATA path; preserve the existing volume configuration.
| "dependencies": { | ||
| "@xenova/transformers": "^2.17.2", | ||
| "openai": "^7.4.0" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -HI '^package\.json$' . -x sh -c '
echo "== $1 =="
jq "{name, dependencies, devDependencies}" "$1"
' sh
rg -n -C2 --glob '*.ts' \
"from ['\"]openai['\"]|`@xenova/transformers`" \
apps packagesRepository: chrisshaddad/bootcamp-starter
Length of output: 50385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate package.json files (top-level workspaces only) =="
git ls-files '*/package.json' 'package.json' \
| grep -E '^(package\.json|apps/[^/]*/package\.json|packages/[^/]*/package\.json)$' \
|| true
echo "== root dependency entries =="
if [ -f package.json ]; then
jq '{name, dependencies, devDependencies}' package.json
fi
echo "== workspace package dependency entries =="
for f in $(git ls-files 'apps/*/package.json' 'packages/*/package.json' | sort -u); do
echo "-- $f --"
jq '{name, dependencies, devDependencies, scripts}' "$f"
done
echo "== TypeScript import usages outside node_modules =="
rg -n -C1 --glob '*.ts' \
"from ['\"]`@xenova/transformers`['\"]|import \\(['\"]`@xenova/transformers`['\"]|from ['\"]openai['\"]|import \\(['\"]openai['\"]" \
apps packages package.json 2>/dev/null | head -n 200Repository: chrisshaddad/bootcamp-starter
Length of output: 6106
Declare direct dependencies in the consuming workspaces.
apps/api/package.json imports openai but only declares it at the root. packages/database/package.json runs prisma db seed from packages/database/prisma/seeders/seedProjects.ts, which imports @xenova/transformers, but does not declare it. Add openai to apps/api and @xenova/transformers to packages/database, or remove the unused imports before relying on hoisted dependencies.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` around lines 30 - 32, Move the undeclared imports to the
workspaces that actually use them: add openai to apps/api/package.json for the
code path that imports it, and add `@xenova/transformers` to
packages/database/package.json for
packages/database/prisma/seeders/seedProjects.ts. If either import is no longer
needed, remove it instead of relying on the root-level dependency, and keep the
affected workspace manifests as the source of truth.
| } catch (error) { | ||
| console.warn('Could not generate local vector embedding:', error); | ||
| return []; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Do not swallow embedding failures silently.
If the model load or the inference fails, generateLocalEmbedding returns []. The caller at line 207 then skips the UPDATE, so the project keeps embedding = NULL. A project with a NULL embedding never matches the cosine-distance query in exploreProjects. The seeder still prints 5 Distinct projects seeded with vectors. at line 261, so the operator has no signal that semantic search will not work on the seeded data.
Track the failures and report them in the final message, or fail the seed run.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/database/prisma/seeders/seedProjects.ts` around lines 15 - 18,
Update generateLocalEmbedding in seedProjects so model-load or inference errors
are not silently converted into an empty vector; either propagate the failure to
stop the seed run or record it for later reporting. Then adjust the seeding flow
that consumes generateLocalEmbedding to surface the embedding failures in the
final seeding summary instead of always reporting success, while preserving the
existing update path for projects that do receive embeddings.
| for (const slug of item.techSlugs) { | ||
| const techId = techMap[slug]; | ||
| if (techId) { | ||
| await prisma.projectTechnology.create({ | ||
| data: { | ||
| projectId: project.id, | ||
| technologyId: techId, | ||
| source: 'MANUAL', | ||
| isPrimary: true, | ||
| }, | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Do not mark every technology as primary.
Each projectTechnology row is created with isPrimary: true and no sortOrder. exploreProjects orders technologies by [{ isPrimary: 'desc' }, { sortOrder: 'asc' }]. With every row primary and every sortOrder at the same default, the displayed technology order is arbitrary and the primary badge loses meaning in the seeded data.
Mark only the first slug as primary and assign an incrementing sortOrder.
🐛 Proposed fix
- for (const slug of item.techSlugs) {
- const techId = techMap[slug];
- if (techId) {
- await prisma.projectTechnology.create({
- data: {
- projectId: project.id,
- technologyId: techId,
- source: 'MANUAL',
- isPrimary: true,
- },
- });
- }
- }
+ for (const [sortOrder, slug] of item.techSlugs.entries()) {
+ const techId = techMap[slug];
+ if (techId) {
+ await prisma.projectTechnology.create({
+ data: {
+ projectId: project.id,
+ technologyId: techId,
+ source: 'MANUAL',
+ isPrimary: sortOrder === 0,
+ sortOrder,
+ },
+ });
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (const slug of item.techSlugs) { | |
| const techId = techMap[slug]; | |
| if (techId) { | |
| await prisma.projectTechnology.create({ | |
| data: { | |
| projectId: project.id, | |
| technologyId: techId, | |
| source: 'MANUAL', | |
| isPrimary: true, | |
| }, | |
| }); | |
| } | |
| } | |
| for (const [sortOrder, slug] of item.techSlugs.entries()) { | |
| const techId = techMap[slug]; | |
| if (techId) { | |
| await prisma.projectTechnology.create({ | |
| data: { | |
| projectId: project.id, | |
| technologyId: techId, | |
| source: 'MANUAL', | |
| isPrimary: sortOrder === 0, | |
| sortOrder, | |
| }, | |
| }); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/database/prisma/seeders/seedProjects.ts` around lines 245 - 257,
Update the project technology creation loop in the seeding flow to track each
slug’s index, set isPrimary only for the first technology, and assign the index
as an incrementing sortOrder. Preserve the existing projectId, technologyId, and
source values while ensuring subsequent technologies are non-primary and
deterministically ordered.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/api/src/users/users.controller.ts (1)
14-20: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winRemove dashboard state from the public explore response.
GET /users/exploreis public and returnsgetSafeSelect(). That selection includeshasSeenDashboardTour. Use a public response select and schema that excludes this user-specific state. Keep it only in the authenticated current-user response.As per coding guidelines, define every wire response shape as a Zod schema in
packages/contracts/src/<resource>/.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/users/users.controller.ts` around lines 14 - 20, Update the public explore response flow around UsersController.exploreUsers and UsersService.exploreUsers to use a dedicated public user select and Zod response schema that excludes hasSeenDashboardTour; define the schema under packages/contracts/src/<resource>/ and retain that field only for the authenticated current-user response.Source: Coding guidelines
apps/api/src/projects/projects.service.ts (2)
697-703: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winClear a stale embedding when regeneration fails.
If
generateEmbeddingreturns[], this path keeps the old embedding after title or description changes. Semantic search can then rank the project by obsolete content.Set
embeddingtoNULLwhen generation fails, or mark it stale before an asynchronous regeneration job runs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/projects/projects.service.ts` around lines 697 - 703, Update the embedding persistence logic after aiService.generateEmbedding so an empty result clears the existing Project.embedding value by setting it to NULL. Preserve the current vector update for non-empty embeddings and ensure both operations target updated.id within the existing transaction.
803-810: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftApply active filters before vector pagination.
When
query.userIdorquery.technologyis set, this query ranks and paginates all published projects first. The later Prismawhereclause can remove every selected ID even when matching projects exist beyond this page.Add the user and technology restrictions to the vector query before
LIMITandOFFSET.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/projects/projects.service.ts` around lines 803 - 810, Update the raw vector query in the projects search method to apply the active query.userId and query.technology restrictions alongside the published and embedding conditions before ORDER BY, LIMIT, and OFFSET. Reuse the existing filter semantics and parameterization so vector pagination operates only on eligible projects, while preserving unrestricted behavior when either filter is unset.
🧹 Nitpick comments (1)
apps/api/src/ai/ai.service.ts (1)
103-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove both
eval()wrappers.These imports do not depend on dynamic code derivation, so use direct dynamic import instead.
apps/api/src/ai/ai.service.ts#L103-L105: replace theeval()expression withawait import('@xenova/transformers').packages/database/prisma/seeders/seedProjects.ts#L20-L22: replace the matchingeval()expression withawait import('@xenova/transformers').🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/ai/ai.service.ts` around lines 103 - 105, Remove the eval() wrappers around the transformers imports: in apps/api/src/ai/ai.service.ts lines 103-105 and packages/database/prisma/seeders/seedProjects.ts lines 20-22, update each matching import expression to use await import('`@xenova/transformers`') directly while preserving the existing pipeline destructuring and behavior.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/ai/ai.service.ts`:
- Around line 93-95: Update the catch block in enhanceProfile() to import and
throw ServiceUnavailableException after logging, instead of rethrowing the raw
error. Preserve the existing error log and ensure failures from OpenAI, missing
response content, and JSON parsing all map to this NestJS exception.
---
Outside diff comments:
In `@apps/api/src/projects/projects.service.ts`:
- Around line 697-703: Update the embedding persistence logic after
aiService.generateEmbedding so an empty result clears the existing
Project.embedding value by setting it to NULL. Preserve the current vector
update for non-empty embeddings and ensure both operations target updated.id
within the existing transaction.
- Around line 803-810: Update the raw vector query in the projects search method
to apply the active query.userId and query.technology restrictions alongside the
published and embedding conditions before ORDER BY, LIMIT, and OFFSET. Reuse the
existing filter semantics and parameterization so vector pagination operates
only on eligible projects, while preserving unrestricted behavior when either
filter is unset.
In `@apps/api/src/users/users.controller.ts`:
- Around line 14-20: Update the public explore response flow around
UsersController.exploreUsers and UsersService.exploreUsers to use a dedicated
public user select and Zod response schema that excludes hasSeenDashboardTour;
define the schema under packages/contracts/src/<resource>/ and retain that field
only for the authenticated current-user response.
---
Nitpick comments:
In `@apps/api/src/ai/ai.service.ts`:
- Around line 103-105: Remove the eval() wrappers around the transformers
imports: in apps/api/src/ai/ai.service.ts lines 103-105 and
packages/database/prisma/seeders/seedProjects.ts lines 20-22, update each
matching import expression to use await import('`@xenova/transformers`') directly
while preserving the existing pipeline destructuring and behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 8aeedd8d-eaa3-4d76-adac-38b671ffe3f2
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (13)
apps/api/src/ai/ai.module.tsapps/api/src/ai/ai.service.tsapps/api/src/app.module.tsapps/api/src/projects/projects.service.tsapps/api/src/repository-scanner/github-repository-snapshot.service.tsapps/api/src/users/users.controller.tsapps/api/src/users/users.module.tsapps/api/src/users/users.service.tsapps/web/components/profile-form.tsxapps/web/components/theme-toggle.tsxpackage.jsonpackages/contracts/src/github/github-repository-analysis-preview.response.tspackages/database/prisma/seeders/seedProjects.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- apps/api/src/ai/ai.module.ts
- apps/api/src/app.module.ts
- apps/web/components/theme-toggle.tsx
- apps/api/src/users/users.module.ts
- packages/contracts/src/github/github-repository-analysis-preview.response.ts
- package.json
- apps/api/src/repository-scanner/github-repository-snapshot.service.ts
- apps/web/components/profile-form.tsx
| } catch (error) { | ||
| this.logger.error('Failed to enhance profile with OpenAI', error); | ||
| throw error; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate ai.service.ts =="
fd -a 'ai\.service\.ts$' . || true
echo "== file outline =="
file="$(fd 'ai\.service\.ts$' . | head -n 1)"
if [ -n "${file:-}" ]; then
ast-grep outline "$file" --view expanded || true
echo "== relevant lines 1-150 =="
sed -n '1,150p' "$file" | cat -n
fi
echo "== NestJS exception imports/usages in api services/controllers if available =="
rg -n "ServiceUnavailableException|Catch|`@Catch`|`@Throws`|throw new .*Exception|console\.log|new Logger" apps/api/src packages/contracts 2>/dev/null | head -n 200 || true
echo "== package nest deps =="
for f in package.json apps/api/package.json; do
if [ -f "$f" ]; then
echo "-- $f --"
jq -r '.dependencies += .devDependencies? // {} | with_entries(select(.key|test("`@nestjs/`"))) | . as $x | {dependencies: $x}' "$f" 2>/dev/null || cat "$f"
fi
doneRepository: chrisshaddad/bootcamp-starter
Length of output: 27059
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package JSON files mentioning `@nestjs/common` =="
rg -n '"`@nestjs/common`"|"`@nestjs/microservices`"|bullmq' -g 'package.json' .
echo "== lockfile relevant entries, if any =="
for f in package-lock.json pnpm-lock.yaml yarn.lock bun.lockb; do
if [ -e "$f" ]; then
echo "-- $f --"
if [ "$f" = "bun.lockb" ]; then
echo "binary lockfile"
else
rg -n "(`@nestjs/common`|ServiceUnavailableException)" "$f" | head -n 40 || true
fi
fi
done
echo "== structural catch/import verifier =="
python3 - <<'PY'
from pathlib import Path
p = Path('apps/api/src/ai/ai.service.ts')
text = p.read_text()
expected_in_file = [
'import { Injectable, Logger }',
'async enhanceProfile',
'} catch (error)',
'this.logger.error(\'Failed to enhance profile with OpenAI\', error);',
'throw error;',
]
for segment in expected_in_file:
print(f'MISSING={segment!r}' if segment not in text else f'FOUND={segment!r}')
# Basic semantic check: every bare throw inside enhanceProfile catch is not a Nestjs exception constructor.
import re
m = re.search(r'async enhanceProfile[\s\S]*?^[}\s]*$', text, re.M)
if m:
block = m.group(0)
catches = re.findall(r'}\ caught error[\s\S]*?'}\ caught error', block)
print('enhanceProfile catch blocks:', re.findall(r'\}\s*catch\s*\(([^)]+)\)[\s\S]*?^\s*\}', block, re.M))
throws = re.findall(r'throw\s+([^;\n]+)', block)
print('enhanceProfile throw statements:', throws)
else:
print('enhanceProfile block not found')
PYRepository: chrisshaddad/bootcamp-starter
Length of output: 1275
Map OpenAI and parsing errors to a NestJS exception.
enhanceProfile() catches OpenAI failures, missing response content, and JSON.parse failures, then rethrows them as raw errors. Import ServiceUnavailableException and throw it after logging instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/ai/ai.service.ts` around lines 93 - 95, Update the catch block
in enhanceProfile() to import and throw ServiceUnavailableException after
logging, instead of rethrowing the raw error. Preserve the existing error log
and ensure failures from OpenAI, missing response content, and JSON parsing all
map to this NestJS exception.
Source: Coding guidelines
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/src/users/users.service.ts (1)
76-109: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftMove profile enhancement off the HTTP request path.
Line 109 waits for an external OpenAI request before the controller can respond. A slow provider response keeps the HTTP request open and reduces API capacity.
Queue this work through BullMQ. Use the required constants, module, service, and processor pattern. Return a job result through a defined contract.
As per coding guidelines, “Route async work through BullMQ. Follow the pattern: constants → module → service → processor. See
src/mail/for the canonical example.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/users/users.service.ts` around lines 76 - 109, Refactor enhanceProfile so the controller no longer awaits aiService.enhanceProfile: enqueue the profile data and projectsForAi through a BullMQ queue using the required queue constants, module, service, and processor pattern established by src/mail. Define and use a job payload/result contract, return the queued job result or identifier through that contract, and move the OpenAI call into the processor.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/users/users.service.ts`:
- Around line 207-238: Remove the 100-row caps from the keyword and vector
candidate queries used by the search method, and calculate pagination from the
complete confirmed union in combinedIds. Preserve keyword precedence and vector
relevance while applying query.limit and offset only after deduplication, using
a database-side CTE or equivalent approach to avoid unbounded application-memory
materialization.
---
Outside diff comments:
In `@apps/api/src/users/users.service.ts`:
- Around line 76-109: Refactor enhanceProfile so the controller no longer awaits
aiService.enhanceProfile: enqueue the profile data and projectsForAi through a
BullMQ queue using the required queue constants, module, service, and processor
pattern established by src/mail. Define and use a job payload/result contract,
return the queued job result or identifier through that contract, and move the
OpenAI call into the processor.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 50ff8259-ce77-49e4-8fa4-54e9827080b3
📒 Files selected for processing (3)
apps/api/src/ai/ai.service.tsapps/api/src/users/users.controller.tsapps/api/src/users/users.service.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/api/src/ai/ai.service.ts
- apps/api/src/users/users.controller.ts
| take: 100, | ||
| }); | ||
|
|
||
| const keywordIds = keywordMatches.map((m) => m.id); | ||
|
|
||
| // 3. AI Vector Matches (Lower Priority) | ||
| let vectorIds: string[] = []; | ||
| if (cleanedText.length > 0) { | ||
| try { | ||
| const embedding = await this.aiService.generateEmbedding(cleanedText); | ||
| if (embedding.length > 0) { | ||
| const vectorString = `[${embedding.join(',')}]`; | ||
| const matches = await this.prisma.$queryRaw<{ userId: string }[]>` | ||
| SELECT dp."userId" FROM "DeveloperProfile" dp | ||
| JOIN "User" u ON dp."userId" = u.id | ||
| WHERE dp.embedding IS NOT NULL | ||
| AND u."isConfirmed" = true | ||
| AND (dp.embedding <=> ${vectorString}::vector) < ${MAX_COSINE_DISTANCE} | ||
| ORDER BY dp.embedding <=> ${vectorString}::vector | ||
| LIMIT 100 | ||
| `; | ||
| vectorIds = matches.map((m) => m.userId); | ||
| } | ||
| } catch (error) { | ||
| // Fallback gracefully | ||
| } | ||
| } | ||
|
|
||
| // Combine IDs (Keywords first, then broader vector matches) | ||
| const combinedIds = Array.from(new Set([...keywordIds, ...vectorIds])); | ||
| const totalItems = combinedIds.length; | ||
| const totalPages = Math.ceil(totalItems / query.limit); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Do not cap candidate IDs before calculating pagination.
Line 207 limits keyword matches to 100. Line 226 limits vector matches to 100. Lines 236-238 then calculate totalItems from those truncated sets.
For example, 101 keyword matches report only 100 results, so the last result is inaccessible. Count and rank the complete confirmed match set, then apply pagination after the union. A database CTE can preserve keyword precedence and vector relevance without materializing an unbounded result set in application memory.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/users/users.service.ts` around lines 207 - 238, Remove the
100-row caps from the keyword and vector candidate queries used by the search
method, and calculate pagination from the complete confirmed union in
combinedIds. Preserve keyword precedence and vector relevance while applying
query.limit and offset only after deduplication, using a database-side CTE or
equivalent approach to avoid unbounded application-memory materialization.
api key not needed for NLP search and vectorization of projects
add openAI token if you got one to the .env in apps/api OPENAI_API_KEY="sk-your-openai-api-key-here"
Summary by CodeRabbit
New Features
Improvements