chore+feat: reconcile Phase 0-6 state, add Azure OpenAI provider#1
Conversation
This commit brings the git history in sync with the actual working tree after Phases 0, 2, 3, 5, and 6 were executed on disk without being committed. Phase 0 (Cleanup & Preserve) - Remove Branches/StackFast-101, StackfastPro, StackWiseAI, StackFastold - Remove WebAILyzerAPI (deferred — preserved in docs/deferred/webailyzer.md) - Remove root client/, server/, shared/ (replaced by apps/ and packages/) - Remove legacy root build configs (vite/tailwind/drizzle/tsconfig/postcss/components) - Remove developer-tools-api, attached_assets, stale Referencedocs - Remove debris: fix-scores.js, export.csv, dev.log, run.log, package-lock.json, Dockerfile - .gitignore already covers .env, dist/, node_modules, build artifacts Phase 5 (Blueprint AI layer) - packages/ai: Gemini provider via Vercel AI SDK with Zod-validated responses - Heuristic fallback wrapper on every method (explainStack, tradeoffs, whyNot, roadmap) - Cost estimator pulled from registry pricing - apps/api: /blueprints fans out AI calls in parallel, returns rationale + keyReasons + confidence + tradeoffs + whyNot + roadmap + costEstimate Phase 6 (Registry expansion) - Registry at v1.1.0: 97 tools, 93 rules, 20 categories - New schema-supported categories: agent frameworks, AI model providers, vector databases, eval/observability, backend frameworks, queues/workflows, testing, monorepos, CMS, mobile - lastVerified, sourceUrls, confidence, capabilities[] on every tool Phase 3 hardening on /admin, /internal, generation auth, and rate limiting - Admin API key middleware; production fails closed when auth isn't configured - In-memory rate-limit with periodic cleanup (TODO Phase 8: move to Upstash/Redis) - CORS configured per environment, credentialed; no wildcard - OpenAPI document with all MVP paths, component schemas, security schemes Quality gate (runs locally, green) - apps/api: 40 tests pass (auth, rate-limit, zod, admin protection, shape) - Registry validation passes - Type-check, lint, build pass across all 8 workspace projects - Playwright E2E suite covers the four primary MVP flows Docs - docs/decisions/001-authentication-strategy.md (Better Auth + Neon) - docs/deferred/webailyzer.md (post-MVP SSRF review required) - docs/backlog/INTEGRATION_PLAN.md (moved from Referencedocs) - ROADMAP.md and SALVAGE_MANIFEST.md updated to reflect actual state Not committed (intentionally) - .env (gitignored; secrets)
There was a problem hiding this comment.
🟡 Inconsistent JSON fence stripping in summarizeTradeoffs
In packages/ai/src/providers/gemini.ts, the summarizeTradeoffs method at line 99 uses inline regex to strip markdown fences (result.text.replace(/^\``(?:json)?\n?/, "").replace(/\n?```$/, "").trim()), while all three other methods (explainStack, explainWhyNot, generateRoadmap) use the stripJsonFences()helper function. The inline regex is functionally equivalent, but it represents an incomplete refactor. IfstripJsonFencesis ever updated to handle additional edge cases (e.g., extra trailing whitespace, nested fences),summarizeTradeoffs` would silently diverge.
(Refers to line 99)
Was this helpful? React with 👍 or 👎 to provide feedback.
| img-src 'self' data: https:; | ||
| font-src 'self' data:; | ||
| connect-src 'self'; | ||
| connect-src 'self' http://localhost:* http://127.0.0.1:*; |
There was a problem hiding this comment.
🔴 CSP connect-src blocks API requests in production
The Content Security Policy meta tag in apps/web/index.html sets connect-src 'self' http://localhost:* http://127.0.0.1:*. In production the web app will be served from a domain like stackfast.app, and the API will be at a separate origin (e.g., https://api.stackfast.app). Since only 'self' and http://localhost:* / http://127.0.0.1:* are allowed, all cross-origin fetch() calls to the production API will be blocked by the browser's CSP enforcement. Additionally, the http:// scheme entries don't cover https://localhost:* either. The meta CSP comment says "Vercel headers take precedence" but the project deploys to Railway, not Vercel, so this CSP will actually be enforced.
Prompt for agents
The connect-src directive in the CSP meta tag at apps/web/index.html:30 only allows 'self', http://localhost:*, and http://127.0.0.1:*. In production, when the API is on a different origin, all API calls will be blocked by the browser. The fix needs to either: (1) remove the restrictive meta CSP and rely on server-side headers that can be dynamically configured per environment, (2) add the production API origin to the connect-src, or (3) use a Vite environment variable to inject the correct API origin at build time. Additionally, the http:// entries should probably also include https:// for local HTTPS dev setups. The comment on line 23 references Vercel but the project deploys to Railway.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
7 issues found across 186 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".env.example">
<violation number="1" location=".env.example:16">
P2: Auth bypass comments are inaccurate: bypass applies to all non-production environments unless `ALLOW_AUTH_BYPASS=false`, not only when `DATABASE_URL` is missing.</violation>
</file>
<file name="apps/api/src/middleware/auth.ts">
<violation number="1" location="apps/api/src/middleware/auth.ts:98">
P1: Security: when auth IS configured, this new bypass skips session validation in all non-production environments by default. The old code only bypassed when auth was unavailable (`!auth`). Now staging/preview deployments with a real database have all protected routes wide open unless `ALLOW_AUTH_BYPASS=false` is explicitly set. Consider requiring an explicit opt-in (`ALLOW_AUTH_BYPASS=true`) instead of opt-out, so that configured auth systems aren't silently disabled.</violation>
</file>
<file name="package.json">
<violation number="1" location="package.json:23">
P2: `@types/node` is pinned to v25 while the repo supports/runs on Node 20.x. This can hide runtime compatibility issues by allowing newer Node APIs in type-checking.</violation>
</file>
<file name="apps/web/index.html">
<violation number="1" location="apps/web/index.html:30">
P1: The CSP `connect-src` now permits localhost/127.0.0.1, which weakens the fallback security policy and can allow XSS payloads to reach local machine services. Keep localhost allowances out of the production CSP fallback.</violation>
<violation number="2" location="apps/web/index.html:30">
P1: `connect-src` is restricted to `self` and localhost, which blocks browser requests to a production API on a different origin. Add the production API origin (and local HTTPS variants) to this directive or move CSP to environment-specific response headers.</violation>
</file>
<file name="packages/schemas/src/db.ts">
<violation number="1" location="packages/schemas/src/db.ts:21">
P2: Missing `.$onUpdate(() => new Date())` on `updatedAt` fields. The better-auth canonical Drizzle schema includes this hook so that UPDATE operations automatically refresh the timestamp. Without it, `updatedAt` will remain at the row's creation time after updates.</violation>
</file>
<file name="readme.md">
<violation number="1" location="readme.md:105">
P2: The README labels auth-required endpoints as “Public MVP routes,” which is inaccurate for `/api/v1/blueprints` and `/api/v1/scaffolds` and can mislead API consumers.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
| { error: "Authentication is not configured", requestId: c.get("requestId") }, | ||
| 503 as ContentfulStatusCode, | ||
| ); | ||
| if (canBypassAuthForLocalDev(c.env)) { |
There was a problem hiding this comment.
P1: Security: when auth IS configured, this new bypass skips session validation in all non-production environments by default. The old code only bypassed when auth was unavailable (!auth). Now staging/preview deployments with a real database have all protected routes wide open unless ALLOW_AUTH_BYPASS=false is explicitly set. Consider requiring an explicit opt-in (ALLOW_AUTH_BYPASS=true) instead of opt-out, so that configured auth systems aren't silently disabled.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/middleware/auth.ts, line 98:
<comment>Security: when auth IS configured, this new bypass skips session validation in all non-production environments by default. The old code only bypassed when auth was unavailable (`!auth`). Now staging/preview deployments with a real database have all protected routes wide open unless `ALLOW_AUTH_BYPASS=false` is explicitly set. Consider requiring an explicit opt-in (`ALLOW_AUTH_BYPASS=true`) instead of opt-out, so that configured auth systems aren't silently disabled.</comment>
<file context>
@@ -91,13 +91,23 @@ export function requireSession(): MiddlewareHandler<{
- { error: "Authentication is not configured", requestId: c.get("requestId") },
- 503 as ContentfulStatusCode,
- );
+ if (canBypassAuthForLocalDev(c.env)) {
+ await next();
+ return;
</file context>
| img-src 'self' data: https:; | ||
| font-src 'self' data:; | ||
| connect-src 'self'; | ||
| connect-src 'self' http://localhost:* http://127.0.0.1:*; |
There was a problem hiding this comment.
P1: The CSP connect-src now permits localhost/127.0.0.1, which weakens the fallback security policy and can allow XSS payloads to reach local machine services. Keep localhost allowances out of the production CSP fallback.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/index.html, line 30:
<comment>The CSP `connect-src` now permits localhost/127.0.0.1, which weakens the fallback security policy and can allow XSS payloads to reach local machine services. Keep localhost allowances out of the production CSP fallback.</comment>
<file context>
@@ -27,7 +27,7 @@
img-src 'self' data: https:;
font-src 'self' data:;
- connect-src 'self';
+ connect-src 'self' http://localhost:* http://127.0.0.1:*;
worker-src 'self' blob:;
child-src 'self' blob:;
</file context>
| connect-src 'self' http://localhost:* http://127.0.0.1:*; | |
| connect-src 'self'; |
| img-src 'self' data: https:; | ||
| font-src 'self' data:; | ||
| connect-src 'self'; | ||
| connect-src 'self' http://localhost:* http://127.0.0.1:*; |
There was a problem hiding this comment.
P1: connect-src is restricted to self and localhost, which blocks browser requests to a production API on a different origin. Add the production API origin (and local HTTPS variants) to this directive or move CSP to environment-specific response headers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/index.html, line 30:
<comment>`connect-src` is restricted to `self` and localhost, which blocks browser requests to a production API on a different origin. Add the production API origin (and local HTTPS variants) to this directive or move CSP to environment-specific response headers.</comment>
<file context>
@@ -27,7 +27,7 @@
img-src 'self' data: https:;
font-src 'self' data:;
- connect-src 'self';
+ connect-src 'self' http://localhost:* http://127.0.0.1:*;
worker-src 'self' blob:;
child-src 'self' blob:;
</file context>
| connect-src 'self' http://localhost:* http://127.0.0.1:*; | |
| connect-src 'self' https://api.stackfast.app http://localhost:* https://localhost:* http://127.0.0.1:* https://127.0.0.1:*; |
| # Local catalog-only dev may bypass auth when no DATABASE_URL is set. | ||
| # Production MUST set this to "false" so auth failures fail closed. |
There was a problem hiding this comment.
P2: Auth bypass comments are inaccurate: bypass applies to all non-production environments unless ALLOW_AUTH_BYPASS=false, not only when DATABASE_URL is missing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .env.example, line 16:
<comment>Auth bypass comments are inaccurate: bypass applies to all non-production environments unless `ALLOW_AUTH_BYPASS=false`, not only when `DATABASE_URL` is missing.</comment>
<file context>
@@ -4,30 +4,36 @@ NODE_ENV=development
BETTER_AUTH_URL=http://localhost:3000
-# Local catalog-only development can bypass auth when no DATABASE_URL is set.
-# Production always fails closed if auth/database is unavailable.
+# Local catalog-only dev may bypass auth when no DATABASE_URL is set.
+# Production MUST set this to "false" so auth failures fail closed.
ALLOW_AUTH_BYPASS=true
</file context>
| # Local catalog-only dev may bypass auth when no DATABASE_URL is set. | |
| # Production MUST set this to "false" so auth failures fail closed. | |
| # Local catalog-only dev may bypass auth in non-production when ALLOW_AUTH_BYPASS is not "false". | |
| # Set ALLOW_AUTH_BYPASS=false anywhere you want session auth enforced (including staging). |
| "validate:registry": "pnpm --filter @stackfast/registry validate" | ||
| }, | ||
| "devDependencies": { | ||
| "@eslint/js": "^9.17.0", | ||
| "@playwright/test": "^1.59.1", | ||
| "@types/node": "^25.6.2", |
There was a problem hiding this comment.
P2: @types/node is pinned to v25 while the repo supports/runs on Node 20.x. This can hide runtime compatibility issues by allowing newer Node APIs in type-checking.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 23:
<comment>`@types/node` is pinned to v25 while the repo supports/runs on Node 20.x. This can hide runtime compatibility issues by allowing newer Node APIs in type-checking.</comment>
<file context>
@@ -6,22 +6,31 @@
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@playwright/test": "^1.59.1",
+ "@types/node": "^25.6.2",
+ "concurrently": "^9.2.1",
+ "dotenv-cli": "^11.0.0",
</file context>
| "@types/node": "^25.6.2", | |
| "@types/node": "^20.19.0", |
| emailVerified: boolean("email_verified").notNull().default(false), | ||
| image: text("image"), | ||
| createdAt: timestamp("created_at").notNull().defaultNow(), | ||
| updatedAt: timestamp("updated_at").notNull().defaultNow(), |
There was a problem hiding this comment.
P2: Missing .$onUpdate(() => new Date()) on updatedAt fields. The better-auth canonical Drizzle schema includes this hook so that UPDATE operations automatically refresh the timestamp. Without it, updatedAt will remain at the row's creation time after updates.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/schemas/src/db.ts, line 21:
<comment>Missing `.$onUpdate(() => new Date())` on `updatedAt` fields. The better-auth canonical Drizzle schema includes this hook so that UPDATE operations automatically refresh the timestamp. Without it, `updatedAt` will remain at the row's creation time after updates.</comment>
<file context>
@@ -1,9 +1,82 @@
+ emailVerified: boolean("email_verified").notNull().default(false),
+ image: text("image"),
+ createdAt: timestamp("created_at").notNull().defaultNow(),
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
+});
+
</file context>
| updatedAt: timestamp("updated_at").notNull().defaultNow(), | |
| updatedAt: timestamp("updated_at").notNull().defaultNow().$onUpdate(() => new Date()), |
|
|
||
| ## API Surface | ||
|
|
||
| Public MVP routes: |
There was a problem hiding this comment.
P2: The README labels auth-required endpoints as “Public MVP routes,” which is inaccurate for /api/v1/blueprints and /api/v1/scaffolds and can mislead API consumers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At readme.md, line 105:
<comment>The README labels auth-required endpoints as “Public MVP routes,” which is inaccurate for `/api/v1/blueprints` and `/api/v1/scaffolds` and can mislead API consumers.</comment>
<file context>
@@ -1,69 +1,152 @@
+
+## API Surface
+
+Public MVP routes:
+
+- `GET /health`
</file context>
| Public MVP routes: | |
| MVP routes: |
Phase 5 close-out. The operator's Azure AI Foundry resource (with gpt-5.5 and gpt-4.1 deployments) is now a first-class blueprint provider alongside Gemini and the heuristic fallback. What changed - packages/ai: new AzureOpenAIExplainer using @ai-sdk/azure + Vercel AI SDK, same four methods as GeminiExplainer, same Zod validation on every response, wrapped in FallbackExplainer. - packages/ai: createExplainer() now accepts provider = "azure-openai" with apiKey, azureResourceName, model (deployment name), and an optional azureApiVersion. Missing config falls back to heuristic with a warning instead of throwing. - packages/ai: removed the "openai" stub branch (it only logged and returned heuristic; now replaced by real azure-openai support). - packages/ai: new index.test.ts covering factory fallback paths and heuristic roadmap shape (6 tests). - apps/api: wires AZURE_OPENAI_RESOURCE_NAME / AZURE_OPENAI_API_KEY / AZURE_OPENAI_DEPLOYMENT / AZURE_OPENAI_API_VERSION into createExplainer. Docs and config - .env.example: added AZURE_OPENAI_* variables and reorganized the AI section by provider. - docs/decisions/002-ai-provider-strategy.md: new ADR capturing the heuristic + Gemini + Azure OpenAI strategy and the Zod-validated fallback contract. - ROADMAP.md: Phase 5 marked complete for MVP scope; ADR-in-response is the only remaining checkbox, deferred to v1.1. - readme.md: updated env var table to list the Azure keys. Quality gate (local) - pnpm -r type-check: 0 errors - pnpm -r lint: 0 errors - pnpm -r test: 60 tests pass (40 api + 6 ai + 5 rules-engine + 5 exporter + 4 registry) - pnpm validate:registry: passes - pnpm -r build: web + api + all packages build Notes - Azure deployments are customer-named, so the deployment name is provided by the operator via env (AZURE_OPENAI_DEPLOYMENT), not hardcoded to gpt-5.5. - No production environment changes. Provider is selected by AI_PROVIDER env; default remains heuristic.
There was a problem hiding this comment.
1 issue found across 11 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="readme.md">
<violation number="1" location="readme.md:81">
P2: The new Azure AI provider docs are inconsistent with Setup guidance, which still mentions only optional `GEMINI_API_KEY`. Update Setup text to also mention Azure OpenAI variables when `AI_PROVIDER=azure-openai`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| | `ALLOW_AUTH_BYPASS` | Local non-production auth bypass when no database is configured | | ||
| | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` | GitHub OAuth credentials | | ||
| | `ADMIN_API_KEY` | Protects `/admin/*` and `/internal/*` routes | | ||
| | `AI_PROVIDER` | `heuristic`, `gemini`, or `azure-openai` | |
There was a problem hiding this comment.
P2: The new Azure AI provider docs are inconsistent with Setup guidance, which still mentions only optional GEMINI_API_KEY. Update Setup text to also mention Azure OpenAI variables when AI_PROVIDER=azure-openai.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At readme.md, line 81:
<comment>The new Azure AI provider docs are inconsistent with Setup guidance, which still mentions only optional `GEMINI_API_KEY`. Update Setup text to also mention Azure OpenAI variables when `AI_PROVIDER=azure-openai`.</comment>
<file context>
@@ -78,8 +78,11 @@ Common local variables are documented in `.env.example`:
| `ADMIN_API_KEY` | Protects `/admin/*` and `/internal/*` routes |
-| `AI_PROVIDER` | `heuristic`, `gemini`, or `openai` |
-| `GEMINI_API_KEY`, `OPENAI_API_KEY` | Optional AI provider keys |
+| `AI_PROVIDER` | `heuristic`, `gemini`, or `azure-openai` |
+| `GEMINI_API_KEY` | Gemini API key (when `AI_PROVIDER=gemini`) |
+| `AZURE_OPENAI_RESOURCE_NAME` | Azure Foundry resource subdomain (when `AI_PROVIDER=azure-openai`) |
</file context>
Phase 4 gap #1: the Blueprint Builder was a single form with idea + constraints. This PR splits the flow into a guided 4-step wizard and adds an integrated scaffold export step so users can go from idea to downloadable starter repo in one page. Steps 1. Idea — required textarea, focused and distraction-free 2. Constraints — optional free-form constraints plus structured fields for budget, timeline, and team size (all wired to the BlueprintRequest schema) 3. Results — existing BlueprintOutputCard rendered inside the wizard 4. Export — scaffold generation via /api/v1/scaffolds with project name input, file list preview, warnings panel, and client-side ZIP download using the existing archive-generator UX - Progress indicator shows completed/active/pending steps with a data-testid for E2E assertions - Back / Next controls with per-step loading states and a "Start over" action on the final step - Errors from blueprint generation and scaffold generation are surfaced inline with retry affordances - Regenerate button on the export step in case the user tweaks the project name after scaffolding E2E coverage - tests/e2e/mvp-flows.spec.ts walks the full wizard: idea → constraints (with budget/timeline selects) → results (asserts "Recommended Architecture", "Primary Stack", "Harmony Score") → export (asserts "Download ZIP" is visible) - Saves a screenshot at each step to test-results/wizard-step-*.png for visual evidence (test-results/ is gitignored) - Bumps this test's timeout to 180s because blueprint generation can take 25-30s when Gemini provider does the full fan-out Quality gate (local) - pnpm -r type-check: 0 errors - pnpm -r lint: 0 errors - pnpm -r test: 60 unit/API tests pass - pnpm -r build: web + api + packages - pnpm test:e2e: 4 passed, including the new multi-step wizard flow
Summary
Reconciles
mainwith the on-disk state of Phases 0, 2, 3, 5, and 6, and completes Phase 5 by shipping a real Azure OpenAI provider alongside Gemini and the heuristic fallback.Phase 0 — Cleanup
client/server/shared/, legacy build configs, attached assets, stale reference docs, debris files.Phase 5 — Blueprint AI (complete for MVP)
packages/aiGemini provider (@ai-sdk/google) with Zod-validated responses and heuristic fallback on every method.packages/aiAzure OpenAI provider (@ai-sdk/azure) using the operator's Azure AI Foundry deployment (e.g.gpt-5.5orgpt-4.1). Deployment name is operator-configured viaAZURE_OPENAI_DEPLOYMENT."openai"stub branch./api/v1/blueprintsfans out AI calls in parallel and returns rationale, key reasons, confidence, tradeoffs, whyNot, roadmap, and cost estimates.docs/decisions/002-ai-provider-strategy.md.Phase 6 — Registry expansion
lastVerified,sourceUrls,confidence,capabilities[].Phase 3 hardening
Quality gate (ran locally)
pnpm -r type-check— 0 errors across 8 projectspnpm -r lint— 0 errors across 8 projectspnpm -r build— web + api + all packages builtpnpm -r test— 60 tests pass (40 API + 6 AI + 5 rules-engine + 5 exporter + 4 registry)pnpm validate:registry— passesConfig for Azure OpenAI
Missing config falls back to the heuristic explainer at startup with a warning; the API never fails closed on AI configuration.
Not in this PR
.envremains gitignored.