Interactive study + AI-powered interview practice tool for the 8 core AI Engineer interview topics.
https://sartor87.github.io/ai-engineer-interview-concept/
https://victorious-ocean-023a55403.7.azurestaticapps.net/
| Layer | Technology |
|---|---|
| Frontend | React 18 + Vite |
| AI Chat Backend | Azure Functions (.NET 8 isolated) |
| Hosting | Azure Static Web Apps (Free tier) |
| AI Model | NVIDIA Nemotron-Mini-4B-Instruct via NIM |
| Guardrails | Stihia real-time threat detection |
| IaC | Terraform (azurerm) |
| Code Quality | SonarQube (architecture analysis + C# + JS) |
/
├── src/
│ ├── main.jsx # React entry point
│ └── App.jsx # Full app (topics + chat panel)
├── api/
│ ├── ChatFunction.cs # Azure Function — guardrail check + NIM proxy + SSE streaming
│ ├── SessionFunction.cs # Azure Function — API key session management
│ ├── StihiaService.cs # Stihia guardrail integration
│ ├── TopicData.cs # Server-side topic registry + system prompt builder
│ ├── Program.cs # .NET 8 isolated worker host
│ ├── host.json
│ ├── local.settings.json # Local secrets — gitignored
│ └── ai-interview-guide.csproj
├── architecture/
│ ├── workspace.dsl # Structurizr C4 model (System Context, Containers, Components)
│ ├── docs/
│ │ ├── 01-nvidia-nim-setup.md
│ │ └── 02-stihia-guardrails-setup.md
│ └── ADRs/
│ ├── 0000-use-adrs.md
│ ├── 0001-cloud-provider-azure.md
│ ├── 0002-backend-runtime-dotnet8.md
│ ├── 0003-iac-terraform.md
│ ├── 0004-inference-provider.md
│ ├── 0005-user-supplied-api-key.md
│ ├── 0006-guardrails-stihia.md
│ ├── 0007-agent-orchestration-platform.md # Azure AI Foundry vs custom vs local
│ │
│ │ # ADR H1 format MUST be `# N. Title` (adr-tools standard) — not `# ADR-NNNN: Title`
│ │ # The Structurizr `!decisions` importer parses the leading integer.
│ └── 0008-agent-count-and-boundaries.md # Three-agent design (Interviewer/Examiner/Aftersales)
├── infra/
│ ├── main.tf # Terraform — resource group + Static Web App
│ └── terraform.tfvars.example # Copy to terraform.tfvars and fill in secrets
├── .claude/
│ ├── settings.json # Claude Code hooks config (PreToolUse + UserPromptSubmit)
│ ├── agents/
│ │ ├── code-reviewer.md # Proactive code review against project standards (JSX + C#)
│ │ └── github-workflow.md # Conventional Commits, branch naming, PR template
│ ├── commands/
│ │ ├── code-quality.md # Run ESLint + dotnet build + manual checklist
│ │ ├── docs-sync.md # Check if docs match recent code changes
│ │ ├── onboard.md # Onboard to a task with codebase exploration
│ │ ├── pr-review.md # Review a PR using code-reviewer.md standards
│ │ ├── pr-summary.md # Generate PR body from branch diff
│ │ └── ticket.md # Full ticket workflow: read → explore → branch → PR
│ └── hooks/
│ ├── pre-commit.sh # Pre-commit: secrets + ESLint + dotnet build + architecture sync
│ ├── skill-eval.js # UserPromptSubmit: rule-based skill activation suggestions
│ ├── skill-rules.json # Skill detection rules tailored to this stack
│ └── skill-rules.schema.json # JSON Schema for skill-rules.json
├── .github/
│ ├── workflows/
│ │ ├── azure-static-web-apps.yml # Build, architecture drift check, deploy to SWA + SonarQube analysis
│ │ └── structurizr-pages.yml # Mermaid export + static site + deploy to GitHub Pages
│ ├── scripts/
│ │ └── build-architecture-site.sh # Builds site/ from .mmd exports + ADRs + template
│ └── templates/
│ └── architecture-index.html # HTML shell with Mermaid.js CDN for the Pages site
├── architecture.json # SonarQube architecture analysis config (generated — do not edit)
├── Convert-DslToArchitecture.ps1 # Generates architecture.json from workspace.dsl
├── CLAUDE.md # Claude Code project context
├── .eslintrc.json # ESLint config (react + react-hooks)
├── index.html
├── vite.config.js
├── package.json
├── staticwebapp.config.json # SWA routing config
└── swa-cli.config.json # Local dev config
- Node.js 20+
- .NET 8 SDK
- Azure Functions Core Tools v4:
npm install -g azure-functions-core-tools@4 - SWA CLI:
npm install -g @azure/static-web-apps-cli - Gitleaks (optional, upgrades pre-commit secret scanning):
winget install Gitleaks.Gitleaks --source winget
The Azure Function reads secrets from api/local.settings.json (gitignored). Add values with:
cd api
func settings add STIHIA_API_KEY "sk_your_key_here"
func settings add DISABLE_GUARDRAILS "false"| Variable | Required | Description |
|---|---|---|
STIHIA_API_KEY |
Yes (if guardrails enabled) | API key from app.stihia.ai → Organization → API Keys |
DISABLE_GUARDRAILS |
No | Set "true" to bypass Stihia checks (dev/testing only) |
Note: Never commit
api/local.settings.json— it is gitignored and the pre-commit hook will block it.
Terminal 1 — Start the Azure Function:
cd api
dotnet build
func start
# Runs on http://localhost:7071Terminal 2 — Start the full SWA dev server (proxies /api → Function):
swa start
# Runs on http://localhost:4280Or use plain Vite dev (proxies /api → port 7071 via vite.config.js):
npm install
npm run dev
# Runs on http://localhost:5173npm run lintThe C4 model lives in architecture/workspace.dsl and covers three views:
| View | Description |
|---|---|
| System Context | Candidate, AI Interview Guide, Stihia, NVIDIA NIM |
| Containers | Frontend SPA + API Backend + external dependencies |
| Components | Internal structure of the Azure Functions backend |
cd architecture
podman run -it --rm -p 9999:8080 -v "${PWD}:/usr/local/structurizr" docker.io/structurizr/liteOpen http://localhost:9999.
cd architecture
docker run -it --rm -p 9999:8080 -v "${PWD}:/usr/local/structurizr" structurizr/litePushing any change under architecture/ to main triggers the structurizr-pages.yml workflow:
- Export views —
structurizr/structurizrDocker image runsexport -format mermaidand writes one.mmdfile per C4 view intoarchitecture/. - Build static site —
.github/scripts/build-architecture-site.shwraps each.mmdin a<div class="mermaid">block, lists ADRs with their## Status, and injects everything into.github/templates/architecture-index.html. Mermaid renders client-side via CDN. - Render ADRs —
pandocconverts each ADR markdown to standalone HTML undersite/adrs/. - Deploy —
actions/upload-pages-artifact+actions/deploy-pagesship thesite/directory.
Important: ADR files MUST use the adr-tools H1 format —
# N. Title— not# ADR-NNNN: Title. The Structurizr!decisionsimporter parses the leading integer from the H1; any other prefix throwsNumberFormatException: For input string: "ADR-"and the export step fails.
One-time setup: GitHub repo → Settings → Pages → Source: GitHub Actions
Note:
structurizr/cliwas deprecated in 2025 in favour of the consolidatedstructurizr/structurizrimage — the workflow uses the new image.
architecture.json maps the C4 model to SonarQube component groups and deny constraints. It is generated — do not edit manually.
After changing workspace.dsl, regenerate:
.\Convert-DslToArchitecture.ps1The CI pipeline (azure-static-web-apps.yml) validates that architecture.json matches workspace.dsl on every push and blocks the build if they drift.
The main workflow (.github/workflows/azure-static-web-apps.yml) runs three jobs in parallel:
| Job | What it does |
|---|---|
build_and_deploy |
Validates architecture.json sync, builds React app, deploys to Azure SWA |
sonarqube |
Builds .NET project, runs SonarQube analysis (C# + JS + architecture) |
close_pull_request |
Closes SWA staging environments when a PR is merged/closed |
| Type | Name | Description |
|---|---|---|
| Secret | AZURE_STATIC_WEB_APPS_API_TOKEN |
SWA deployment token (from Terraform output or Azure Portal) |
| Secret | SONAR_TOKEN |
SonarQube → My Account → Security → Generate Token |
| Variable | SONAR_PROJECT_KEY |
SonarQube project key |
| Variable | SONAR_ORGANIZATION |
SonarQube organization key |
Infrastructure is managed with Terraform (infra/main.tf). The Terraform config creates the resource group and Static Web App with all required app settings in one step.
cd infra
cp terraform.tfvars.example terraform.tfvars
# edit terraform.tfvars — set stihia_api_key and other values
terraform init
terraform applyThen grab the deployment token and add it to GitHub Actions:
terraform output -raw deployment_token1. Create the Static Web App
az staticwebapp create \
--name ai-interview-guide \
--resource-group rg-ai-interview \
--location "West Europe" \
--sku Free \
--source https://github.com/<YOUR_ORG>/<YOUR_REPO> \
--branch main \
--app-location "/" \
--api-location "api" \
--output-location "dist" \
--login-with-github2. Copy the deployment token
az staticwebapp secrets list \
--name ai-interview-guide \
--resource-group rg-ai-interview \
--query "properties.apiKey" -o tsv3. Add app settings
In Azure Portal → Static Web App → Configuration → Application settings:
| Name | Value |
|---|---|
STIHIA_API_KEY |
sk_your_key_here |
DISABLE_GUARDRAILS |
false |
In your GitHub repo → Settings → Secrets → Actions:
- Name:
AZURE_STATIC_WEB_APPS_API_TOKEN - Value: deployment token from step above
See the CI/CD Pipeline section for all required secrets and variables.
The GitHub Actions workflow builds the React app, compiles the .NET 8 Function, and deploys both automatically.
- User opens any topic section and clicks 🤖 PRACTICE
- User enters their NVIDIA NIM API key →
POST /api/sessionstores it server-side (30-min sliding cache), returns asessionId- Get a free key at https://build.nvidia.com → Sign in → Generate API Key
- Free tier: 1,000 inference credits, 40 req/min
- Each message calls
POST /api/chatwith{ sessionId, topicId, triggerEval, messages[] } - The Function: resolves the API key from cache → runs Stihia guardrail check → builds system prompt server-side → forwards to NIM
- NIM response streams back as SSE → displayed token by token
| Trigger | Behaviour |
|---|---|
| User says "evaluate me" / "score me" / "grade my answers" | Immediate per-answer scoring |
| Silent (no evaluation request) | Auto-evaluation fires after question 10 |
Each scored answer shows a coloured X/10 badge inline in the chat bubble, using the topic's accent colour with opacity proportional to the score.
Once evaluation has run, a ↓ PDF button appears in the chat header. Clicking it downloads the full session transcript as a .txt file (named interview-<tag>-<timestamp>.txt).
Every user message is checked by Stihia before reaching NVIDIA NIM.
| Severity | Action |
|---|---|
low / medium |
Allowed — message forwarded to NIM |
high / critical |
Blocked — 403 returned, NIM never called |
| Stihia unreachable | Fail-open — message forwarded to NIM |
Set DISABLE_GUARDRAILS=true to bypass all checks (useful for local dev without a Stihia key).
- Model:
nvidia/nemotron-mini-4b-instruct - Endpoint:
https://integrate.api.nvidia.com/v1/chat/completions - Prompt format: Custom
<extra_id_N>Nemotron template (applied server-side in the Azure Function) - Context window: 4,096 tokens
- Optimised for: RAG, instruction following, conversational Q&A
Three specialised agents orchestrated via Azure AI Foundry Agent Service (see ADR-0007 and ADR-0008):
| Agent | Role |
|---|---|
| Interviewer | Conducts the mock interview; adversarial tone; no scoring |
| Examiner | Scores the transcript against a rubric; human-in-the-loop gate before next step |
| Aftersales | Recommends learning resources based on concept gaps; fires only when score < threshold |
EU AI Act (Article 14) human oversight gate sits between Examiner and Aftersales. All candidate names are obfuscated before the transcript reaches the Examiner (data minimisation).
- The NVIDIA API key is sent from browser → Function once on session creation (
POST /api/session), stored inIMemoryCachewith a 30-min sliding expiration, and never logged. Subsequent requests send only asessionId. - The system prompt is built server-side from a static topic registry (
TopicData.cs). The client sends only a numerictopicId— arbitrary prompt injection from the client is not possible. - Stihia guardrail check runs on every user message before the NIM call. On Stihia outage the service fails open to avoid blocking users.
- All conversation history is held in React
useState; no session state is persisted server-side beyond the API key cache.
This repo ships a .claude/ directory with project-specific agents, slash commands, and hooks.
| Agent | Purpose |
|---|---|
code-reviewer |
Proactive code review against JSX + C# project standards (security, async patterns, SSE cleanup, state-order rules) |
github-workflow |
Branch naming, Conventional Commits, PR template enforcement |
Invoke via Claude Code's Agent tool — code-reviewer is configured to run proactively after writing or modifying code.
| Command | What it does |
|---|---|
/code-quality [path] |
Runs ESLint + dotnet build + manual checklist scan |
/docs-sync |
Compares git log (30 days) against README/ADRs/CLAUDE.md for drift |
/onboard <task> |
Explores the codebase and writes .claude/tasks/<task>/onboarding.md |
/pr-review <pr#> |
Fetches PR via gh pr view, applies code-reviewer standards, posts feedback |
/pr-summary |
Generates a PR body (Summary / Changes / Test Plan) from git log main..HEAD |
/ticket <id> |
Full workflow: read ticket → explore → branch → implement → PR |
| Hook | Trigger | What it does |
|---|---|---|
pre-commit.sh |
PreToolUse on Bash | Intercepts git commit and runs: secret scan (gitleaks or pattern grep), npm run lint, dotnet build, and architecture drift check (regenerates architecture.json and compares to staged version). Exit 2 blocks the commit. |
skill-eval.js |
UserPromptSubmit | Scores the prompt + mentioned file paths against skill-rules.json and suggests relevant skills (e.g., dotnet-backend, architecture, react-ui-patterns) before implementation begins. |
Customise skills by editing .claude/hooks/skill-rules.json. The schema is in skill-rules.schema.json.
CLAUDE.md provides Claude Code with stack details, key commands, critical security rules, and the session flow. It is auto-loaded into every Claude Code conversation in this repository.