feat(ai): optimize tool loops, fix deployments, and integrate new onboarding flow

This commit is contained in:
2026-05-19 12:52:47 -07:00
parent 331312b648
commit 618f7796b2
250 changed files with 2993 additions and 24695 deletions

View File

@@ -0,0 +1,22 @@
// Use the existing API endpoint to check the database
const projectId = 'Sih2VRdZeBglpVNc4eFS'; // Your project ID
async function checkDB() {
try {
// Try to fetch extraction handoff
const response = await fetch(`http://localhost:3000/api/projects/${projectId}/extraction-handoff`);
if (response.ok) {
const data = await response.json();
console.log('\n=== EXTRACTION HANDOFF ===');
console.log(JSON.stringify(data.handoff, null, 2));
} else {
console.log('Extraction handoff response:', response.status, await response.text());
}
} catch (error) {
console.error('Error:', error.message);
}
}
checkDB();

View File

@@ -0,0 +1,74 @@
const admin = require('firebase-admin');
// Initialize Firebase Admin
const serviceAccount = require('./vibn-firebase-admin-key.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
const db = admin.firestore();
async function checkDocuments() {
try {
// Get the most recent project
const projectsSnapshot = await db.collection('projects')
.orderBy('createdAt', 'desc')
.limit(1)
.get();
if (projectsSnapshot.empty) {
console.log('No projects found');
return;
}
const project = projectsSnapshot.docs[0];
const projectId = project.id;
const projectData = project.data();
console.log('\n=== PROJECT INFO ===');
console.log('Project ID:', projectId);
console.log('Project Name:', projectData.name);
console.log('Current Phase:', projectData.currentPhase);
// Check knowledge_items
console.log('\n=== KNOWLEDGE ITEMS ===');
const knowledgeSnapshot = await db.collection('knowledge_items')
.where('projectId', '==', projectId)
.get();
console.log('Total knowledge items:', knowledgeSnapshot.size);
knowledgeSnapshot.docs.forEach((doc, idx) => {
const data = doc.data();
console.log(`\n[${idx + 1}] ${doc.id}`);
console.log(' Title:', data.title);
console.log(' Source Type:', data.sourceType);
console.log(' Content Length:', data.content?.length || 0);
console.log(' Created:', data.createdAt?.toDate?.() || 'unknown');
});
// Check extraction handoff
console.log('\n=== EXTRACTION HANDOFF ===');
const extractionHandoff = projectData.phaseData?.phaseHandoffs?.extraction;
if (extractionHandoff) {
console.log('Phase:', extractionHandoff.phase);
console.log('Ready:', extractionHandoff.readyForNextPhase);
console.log('Confidence:', extractionHandoff.confidence);
console.log('Problems:', extractionHandoff.confirmed?.problems?.length || 0);
console.log('Features:', extractionHandoff.confirmed?.features?.length || 0);
console.log('Users:', extractionHandoff.confirmed?.targetUsers?.length || 0);
console.log('Missing:', extractionHandoff.missing);
console.log('Questions:', extractionHandoff.questionsForUser);
} else {
console.log('No extraction handoff found');
}
} catch (error) {
console.error('Error:', error);
} finally {
process.exit(0);
}
}
checkDocuments();

View File

@@ -0,0 +1,33 @@
const admin = require('firebase-admin');
const serviceAccount = require('./serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
const db = admin.firestore();
async function checkPhase() {
const projectId = 'ivmEUWmdBrY9M5QDuBsJ';
const projectDoc = await db.collection('projects').doc(projectId).get();
if (!projectDoc.exists) {
console.log('Project not found');
return;
}
const data = projectDoc.data();
console.log('\n=== PROJECT PHASE DATA ===');
console.log('Current Phase:', data.currentPhase);
console.log('Phase Status:', data.phaseStatus);
console.log('Phase Data:', JSON.stringify(data.phaseData, null, 2));
console.log('\n=== EXPECTED vs ACTUAL ===');
console.log('Expected Phase: gathering');
console.log('Actual Phase:', data.currentPhase);
console.log('Match:', data.currentPhase === 'gathering' ? '✅ YES' : '❌ NO');
}
checkPhase().then(() => process.exit(0)).catch(err => {
console.error('Error:', err);
process.exit(1);
});

View File

@@ -0,0 +1,13 @@
const fs = require('fs');
const file = 'app/auth/page.tsx';
let code = fs.readFileSync(file, 'utf8');
// Currently it routes to `/${workspace}/projects`
// We will route them to `/onboarding`
code = code.replace(
'router.push(`/${workspace}/projects`);',
'router.push(`/onboarding`);'
);
fs.writeFileSync(file, code);

View File

@@ -0,0 +1,10 @@
const fs = require('fs');
const file = 'app/(onboarding)/onboarding/page.tsx';
let code = fs.readFileSync(file, 'utf8');
code = code.replace(
'const btn = document.querySelector(".btn-primary:not([disabled])") as HTMLElement;\n if (btn)(".btn-primary:not([disabled])");\n if (btn) btn.click();',
'const btn = document.querySelector(".btn-primary:not([disabled])") as HTMLElement;\n if (btn) btn.click();'
);
fs.writeFileSync(file, code);

View File

@@ -0,0 +1,32 @@
const fs = require('fs');
const path = 'lib/auth/workspace-auth.ts';
let content = fs.readFileSync(path, 'utf8');
const replacement = `if (!principal) {
if (isDevBypass) {
const userRow = await queryOne<{ id: string }>(
\`SELECT id FROM fs_users WHERE data->>'email' = $1 LIMIT 1\`,
[process.env.NEXT_PUBLIC_DEV_LOCAL_AUTH_EMAIL]
);
if (userRow) {
const workspace = await getWorkspaceByOwner(userRow.id);
if (workspace) {
return { source: 'api_key', workspace, userId: userRow.id, apiKeyId: 'dev_bypass' };
}
}
}
return NextResponse.json({ error: 'Invalid or revoked API key' }, { status: 401 });
}
if (!matchesTarget(principal.workspace, opts)) {
return NextResponse.json({ error: 'API key not authorized for this workspace' }, { status: 403 });
}
return principal;
}`;
content = content.replace(
/if \(\!principal && !isDevBypass\) \{[\s\S]*?return principal;\n \}\n \}/m,
replacement
);
fs.writeFileSync(path, content);

View File

@@ -0,0 +1,22 @@
const fs = require('fs');
const path = require('path');
const dir = 'app/(onboarding)/onboarding';
// Replace Arrow in onboarding-primitives.tsx
let primCode = fs.readFileSync(path.join(dir, 'onboarding-primitives.tsx'), 'utf8');
primCode = primCode.replace(
/<Arrow size=\{13\} \/>/g,
'<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8h10M9 4l4 4-4 4"/></svg>'
);
fs.writeFileSync(path.join(dir, 'onboarding-primitives.tsx'), primCode);
// Add Field to imports
const filesToFix = ['onboarding-consultant.tsx', 'onboarding-entrepreneur.tsx', 'onboarding-owner.tsx'];
for (const f of filesToFix) {
let code = fs.readFileSync(path.join(dir, f), 'utf8');
if (!code.includes('Field,') && !code.includes(', Field')) {
code = code.replace(/import \{ ([^}]+) \} from "\.\/onboarding-primitives";/, 'import { $1, Field } from "./onboarding-primitives";');
}
fs.writeFileSync(path.join(dir, f), code);
}

View File

@@ -0,0 +1,12 @@
const fs = require('fs');
const path = require('path');
const dir = 'app/(onboarding)/onboarding';
let code = fs.readFileSync(path.join(dir, 'onboarding-build.tsx'), 'utf8');
code = code.replace(
/<Arrow size=\{13\} \/>/g,
'<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8h10M9 4l4 4-4 4"/></svg>'
);
fs.writeFileSync(path.join(dir, 'onboarding-build.tsx'), code);

View File

@@ -0,0 +1,16 @@
const fs = require('fs');
const path = require('path');
const dir = 'app/(onboarding)/onboarding';
const files = fs.readdirSync(dir).filter(f => f.endsWith('.tsx'));
for (const file of files) {
const filePath = path.join(dir, file);
let code = fs.readFileSync(filePath, 'utf8');
// Remove Object.assign(window, {...}) lines since these are now proper React imports
code = code.replace(/Object\.assign\(window,\s*\{[\s\S]*?\}\);/g, '');
fs.writeFileSync(filePath, code);
}
console.log("Removed global window assignments from child components.");

View File

@@ -0,0 +1,15 @@
const fs = require('fs');
const file = 'app/(onboarding)/onboarding/page.tsx';
let code = fs.readFileSync(file, 'utf8');
// Prepend "use client" and imports
const header = `"use client";\n\nimport React, { useState, useEffect, useMemo, Fragment } from "react";\nimport "./onboarding.css";\n\n`;
// Add export default to the function
code = code.replace('function OnboardingApp() {', 'export default function OnboardingApp() {');
// Remove ReactDOM.createRoot
code = code.replace('ReactDOM.createRoot(document.getElementById("root")).render(<OnboardingApp />);', '');
fs.writeFileSync(file, header + code);
console.log("Reformatted page.tsx");

View File

@@ -0,0 +1,26 @@
const { onRequest } = require('firebase-functions/v2/https');
const next = require('next');
const app = next({
dev: false,
conf: { distDir: '.next' },
});
const handle = app.getRequestHandler();
// Prepare the app on module load
const prepared = app.prepare();
exports.nextjsFunc = onRequest(
{
region: 'us-central1',
memory: '2GiB',
timeoutSeconds: 300,
maxInstances: 10,
minInstances: 0,
},
async (req, res) => {
await prepared;
return handle(req, res);
}
);

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env node
/**
* Vibn MCP Server Entry Point
*
* This script starts the Vibn MCP server which exposes project data
* and capabilities to AI assistants through the Model Context Protocol.
*
* Usage:
* npm run mcp:server
*
* Or add to your AI assistant's MCP configuration:
* {
* "mcpServers": {
* "vibn": {
* "command": "node",
* "args": ["path/to/vibn-frontend/mcp-server.js"]
* }
* }
* }
*/
const { spawn } = require('child_process');
const path = require('path');
// Load environment variables
require('dotenv').config({ path: path.join(__dirname, '.env.local') });
// Run the TypeScript server using tsx
const server = spawn('npx', ['tsx', path.join(__dirname, 'lib/mcp/server.ts')], {
stdio: 'inherit',
env: process.env,
});
server.on('error', (error) => {
console.error('Failed to start MCP server:', error);
process.exit(1);
});
server.on('exit', (code) => {
process.exit(code || 0);
});

View File

@@ -0,0 +1,269 @@
#!/usr/bin/env node
/**
* Zed MCP bridge — translates JSON-RPC 2.0 (Zed's MCP client) to Vibn's
* simpler {tool, params} HTTP API at /api/mcp.
*
* Usage:
* node mcp-zed-bridge.js
*
* Env:
* VIBN_MCP_URL — defaults to https://vibnai.com/api/mcp
* VIBN_API_KEY — your vibn_sk_... token
*/
const VIBNDEV_MCP_URL =
process.env.VIBN_MCP_URL || "https://vibnai.com/api/mcp";
const VIBNDEV_API_KEY = process.env.VIBN_API_KEY || "";
if (!VIBNDEV_API_KEY) {
process.stderr.write("VIBN_API_KEY is required\n");
process.exit(1);
}
// ── JSON-RPC helpers ─────────────────────────────────────────────────
let requestId = 0;
function sendJson(obj) {
process.stdout.write(JSON.stringify(obj) + "\n");
}
function log(msg) {
process.stderr.write(`[vibn-bridge] ${msg}\n`);
}
// ── Tool list from the API capability descriptor ──────────────────────
const TOOLS = [
"workspace.describe",
"gitea.credentials",
"projects.list",
"projects.get",
"project.recent_errors",
"project.error_detail",
"project.error_resolve",
"apps.list",
"apps.get",
"apps.create",
"apps.update",
"apps.rewire_git",
"apps.delete",
"apps.deploy",
"apps.deployments",
"apps.domains.list",
"apps.domains.set",
"apps.logs",
"apps.exec",
"apps.volumes.list",
"apps.volumes.wipe",
"apps.containers.up",
"apps.containers.ps",
"apps.repair",
"apps.templates.list",
"apps.templates.search",
"apps.envs.list",
"apps.envs.upsert",
"apps.envs.delete",
"databases.list",
"databases.create",
"databases.get",
"databases.update",
"databases.delete",
"auth.list",
"auth.create",
"auth.delete",
"domains.search",
"domains.list",
"domains.get",
"domains.register",
"domains.attach",
"storage.describe",
"storage.provision",
"storage.inject_env",
"gitea.repos.list",
"gitea.repo.get",
"gitea.repo.create",
"gitea.file.read",
"gitea.file.write",
"gitea.file.delete",
"gitea.branches.list",
"gitea.branch.create",
"devcontainer.ensure",
"devcontainer.status",
"devcontainer.suspend",
"shell.exec",
"fs.read",
"fs.write",
"fs.edit",
"fs.list",
"fs.tree",
"fs.delete",
"fs.glob",
"fs.grep",
"dev_server.start",
"browser.console",
"browser.navigate",
"dev_server.stop",
"dev_server.list",
"dev_server.logs",
"ship",
].map((name) => ({
name,
description: `Vibn workspace tool: ${name}`,
inputSchema: {
type: "object",
properties: {
projectId: { type: "string", description: "Vibn project ID" },
},
},
}));
// ── MCP protocol handlers ────────────────────────────────────────────
async function handleInitialize(id) {
sendJson({
jsonrpc: "2.0",
id,
result: {
protocolVersion: "2024-11-05",
capabilities: { tools: {} },
serverInfo: { name: "vibn-mcp-bridge", version: "1.0.0" },
},
});
}
async function handleToolsList(id) {
sendJson({
jsonrpc: "2.0",
id,
result: { tools: TOOLS },
});
}
async function handleToolsCall(id, params) {
const { name, arguments: args } = params;
try {
const res = await fetch(VIBNDEV_MCP_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${VIBNDEV_API_KEY}`,
},
body: JSON.stringify({ tool: name, params: args }),
});
const data = await res.json();
if (!res.ok) {
sendJson({
jsonrpc: "2.0",
id,
result: {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
isError: true,
},
});
return;
}
sendJson({
jsonrpc: "2.0",
id,
result: {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
},
});
} catch (err) {
sendJson({
jsonrpc: "2.0",
id,
result: {
content: [
{
type: "text",
text: `Error: ${err.message}`,
},
],
isError: true,
},
});
}
}
// ── Main loop ─────────────────────────────────────────────────────────
let buffer = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
buffer += chunk;
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (!line.trim()) continue;
let msg;
try {
msg = JSON.parse(line);
} catch {
continue;
}
if (!msg.jsonrpc || !msg.method) continue;
const id = msg.id;
// Wrap in async IIFE so unhandled rejections don't crash the bridge
(async () => {
try {
switch (msg.method) {
case "initialize":
await handleInitialize(id);
break;
case "notifications/initialized":
// No response needed
break;
case "tools/list":
await handleToolsList(id);
break;
case "tools/call":
await handleToolsCall(id, msg.params);
break;
default:
sendJson({
jsonrpc: "2.0",
id,
error: {
code: -32601,
message: `Method not found: ${msg.method}`,
},
});
}
} catch (err) {
log(`Error handling ${msg.method}: ${err.message}`);
sendJson({
jsonrpc: "2.0",
id,
error: { code: -32603, message: `Internal error: ${err.message}` },
});
}
})();
}
});
process.stdin.on("end", () => {
log("stdin closed, exiting");
process.exit(0);
});
log(`bridge started, proxying to ${VIBNDEV_MCP_URL}`);

View File

@@ -0,0 +1,11 @@
const fs = require('fs');
const file = 'app/(onboarding)/onboarding/page.tsx';
let code = fs.readFileSync(file, 'utf8');
code = code.replace(
'try { return localStorage.getItem("vibn:firstName") || ""; } catch { return ""; }',
'try { return typeof window !== "undefined" ? localStorage.getItem("vibn:firstName") || "" : ""; } catch { return ""; }'
);
fs.writeFileSync(file, code);

View File

@@ -0,0 +1,15 @@
const fs = require('fs');
const file = 'app/(onboarding)/onboarding/page.tsx';
let code = fs.readFileSync(file, 'utf8');
code = code.replace(
'const close = () => { window.location.href = "index.html"; };',
'const close = () => { if (typeof window !== "undefined") window.location.href = "/"; };'
);
code = code.replace(
'const openChat = () => { window.location.href = "index.html"; };',
'const openChat = () => { if (typeof window !== "undefined") window.location.href = "/"; };'
);
fs.writeFileSync(file, code);

View File

@@ -0,0 +1,137 @@
#!/bin/bash
# Helper script to set up and run E2E test
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
echo ""
echo "=========================================="
echo " E2E TEST SETUP HELPER"
echo "=========================================="
echo ""
echo -e "${BLUE}Step 1: Check if server is running...${NC}"
if curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 | grep -q "200\|404"; then
echo -e "${GREEN}✓ Server is running on http://localhost:3000${NC}"
else
echo -e "${YELLOW}⚠ Server not running. Starting it now...${NC}"
npm run dev &
sleep 5
fi
echo ""
echo -e "${BLUE}Step 2: Get your credentials${NC}"
echo ""
echo -e "${CYAN}Please follow these steps:${NC}"
echo ""
echo "1. Open your browser and go to:"
echo " ${BLUE}http://localhost:3000${NC}"
echo ""
echo "2. Sign in and create/open a project"
echo ""
echo "3. Go to AI Chat page"
echo ""
echo "4. Open DevTools (F12 or Cmd+Option+I)"
echo ""
echo "5. Go to Network tab"
echo ""
echo "6. Send any message (e.g., 'test')"
echo ""
echo "7. Find the request to '/api/ai/chat'"
echo ""
echo "8. Click it → Headers tab"
echo ""
echo "9. Copy the 'Authorization' header value"
echo " (It looks like: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ij...)"
echo ""
echo "10. Copy your project ID from the URL"
echo " (Format: /workspace/project/{PROJECT_ID}/v_ai_chat)"
echo ""
echo ""
echo -e "${YELLOW}Ready to input your credentials?${NC}"
read -p "Press Enter when ready..."
echo ""
# Get auth token
echo -e "${CYAN}Paste your Authorization header (including 'Bearer '):${NC}"
read -r AUTH_TOKEN
echo ""
# Get project ID
echo -e "${CYAN}Paste your Project ID:${NC}"
read -r PROJECT_ID
echo ""
# Validate inputs
if [ -z "$AUTH_TOKEN" ]; then
echo -e "${YELLOW}⚠ Auth token is empty. Exiting.${NC}"
exit 1
fi
if [ -z "$PROJECT_ID" ]; then
echo -e "${YELLOW}⚠ Project ID is empty. Exiting.${NC}"
exit 1
fi
# Save to temporary file for reuse
cat > .e2e-test-env << EOF
export AUTH_TOKEN='$AUTH_TOKEN'
export PROJECT_ID='$PROJECT_ID'
EOF
echo -e "${GREEN}✓ Credentials saved to .e2e-test-env${NC}"
echo ""
# Test connection
echo -e "${BLUE}Step 3: Testing connection...${NC}"
response=$(curl -s -X POST "http://localhost:3000/api/ai/chat" \
-H "Authorization: $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"projectId\":\"$PROJECT_ID\",\"message\":\"test\"}")
if echo "$response" | jq -e '.reply' > /dev/null 2>&1; then
echo -e "${GREEN}✓ Connection successful!${NC}"
reply=$(echo "$response" | jq -r '.reply' | head -c 100)
echo -e "${CYAN}AI Response:${NC} $reply..."
echo ""
elif echo "$response" | jq -e '.error' > /dev/null 2>&1; then
error=$(echo "$response" | jq -r '.error')
echo -e "${YELLOW}⚠ API Error: $error${NC}"
echo "Please check your credentials and try again."
exit 1
else
echo -e "${YELLOW}⚠ Unexpected response${NC}"
echo "$response"
exit 1
fi
echo ""
echo -e "${GREEN}=========================================="
echo " READY TO RUN E2E TEST"
echo "==========================================${NC}"
echo ""
echo "Credentials saved! You can now run:"
echo ""
echo -e "${CYAN} source .e2e-test-env${NC}"
echo -e "${CYAN} ./test-e2e-collector.sh${NC}"
echo ""
echo "Or simply:"
echo ""
echo -e "${CYAN} AUTH_TOKEN='$AUTH_TOKEN' PROJECT_ID='$PROJECT_ID' ./test-e2e-collector.sh${NC}"
echo ""
# Offer to run now
echo -e "${YELLOW}Run E2E test now? (y/n)${NC}"
read -r run_now
if [ "$run_now" = "y" ] || [ "$run_now" = "Y" ]; then
echo ""
export AUTH_TOKEN
export PROJECT_ID
./test-e2e-collector.sh
fi

View File

@@ -0,0 +1,161 @@
#!/bin/bash
# Test script simulating actual user conversation flow
# Project: Dr Dave EMR (Rcj5OY2xpQFHAzqUyMim)
# Context: GitHub connected, no documents uploaded
set -e
PROJECT_ID="Rcj5OY2xpQFHAzqUyMim"
BASE_URL="http://localhost:3000"
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Testing Actual User Conversation Flow${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# Get Firebase token
echo -e "${YELLOW}Step 1: Get Firebase Auth Token${NC}"
if [ -z "$FIREBASE_TOKEN" ]; then
echo "Please provide your Firebase ID token:"
echo "(or set FIREBASE_TOKEN environment variable)"
read -r FIREBASE_TOKEN
fi
if [ -z "$FIREBASE_TOKEN" ]; then
echo -e "${RED}Error: Firebase token is required${NC}"
exit 1
fi
echo -e "${GREEN}✓ Token received${NC}"
echo ""
# Function to send chat message
send_message() {
local message="$1"
local step_name="$2"
echo -e "${YELLOW}${step_name}${NC}"
echo -e "User: ${message}"
local response=$(curl -s -X POST "${BASE_URL}/api/ai/chat" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${FIREBASE_TOKEN}" \
-d "{
\"message\": \"${message}\",
\"projectId\": \"${PROJECT_ID}\"
}")
local ai_reply=$(echo "$response" | jq -r '.reply // .error // "No reply"')
echo -e "AI: ${ai_reply}"
echo ""
# Return the full response for checking
echo "$response"
}
# Function to check project phase
check_project_phase() {
local step_name="$1"
echo -e "${YELLOW}${step_name}${NC}"
# Get project data from Firestore via API
local project_data=$(curl -s "${BASE_URL}/api/projects/${PROJECT_ID}" \
-H "Authorization: Bearer ${FIREBASE_TOKEN}")
local current_phase=$(echo "$project_data" | jq -r '.currentPhase // "unknown"')
local handoff_ready=$(echo "$project_data" | jq -r '.phaseData.phaseHandoffs.collector.readyForNextPhase // false')
local has_extraction=$(echo "$project_data" | jq -r '.phaseData.phaseHandoffs.extraction // "null"')
echo "Current Phase: ${current_phase}"
echo "Collector Ready: ${handoff_ready}"
echo "Has Extraction Handoff: $([ "$has_extraction" != "null" ] && echo "YES" || echo "NO")"
echo ""
echo "$project_data"
}
# Simulate the actual user conversation
echo -e "${BLUE}Starting conversation simulation...${NC}"
echo ""
# Message 1: Initial greeting (AI should welcome and ask what they have)
response1=$(send_message "Hello" "Message 1: Initial greeting")
sleep 2
# Message 2: User mentions GitHub repo
response2=$(send_message "I have a GitHub repo connected" "Message 2: User mentions GitHub")
sleep 2
# Message 3: User confirms they have everything
response3=$(send_message "that's everything" "Message 3: User confirms ready")
sleep 2
# Check if handoff was triggered
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Checking Handoff Contract${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
project_state=$(check_project_phase "Step 1: Check project state immediately after 'that's everything'")
# Wait a bit for async extraction to run
echo -e "${YELLOW}Waiting 5 seconds for backend extraction to complete...${NC}"
sleep 5
project_state_after=$(check_project_phase "Step 2: Check project state after backend extraction")
# Extract key values
current_phase=$(echo "$project_state_after" | jq -r '.currentPhase // "unknown"')
ready_for_next=$(echo "$project_state_after" | jq -r '.phaseData.phaseHandoffs.collector.readyForNextPhase // false')
has_extraction=$(echo "$project_state_after" | jq -r '.phaseData.phaseHandoffs.extraction // "null"')
# Message 4: Send another message to see if AI is in extraction review mode
response4=$(send_message "what did you find?" "Message 4: Ask about findings (should be in extraction_review mode)")
# Verify results
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Test Results${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
if [ "$ready_for_next" = "true" ]; then
echo -e "${GREEN}✓ Collector handoff.readyForNextPhase = true${NC}"
else
echo -e "${RED}✗ Collector handoff.readyForNextPhase = false (expected true)${NC}"
fi
if [ "$has_extraction" != "null" ]; then
echo -e "${GREEN}✓ Extraction handoff exists${NC}"
else
echo -e "${RED}✗ Extraction handoff missing${NC}"
fi
if [ "$current_phase" = "extraction_review" ]; then
echo -e "${GREEN}✓ Phase transitioned to extraction_review${NC}"
else
echo -e "${RED}✗ Phase is '${current_phase}' (expected 'extraction_review')${NC}"
fi
# Check if AI's final response mentions "processing" or "analyzing"
if echo "$response4" | jq -r '.reply' | grep -qi "processing\|analyzing\|let me analyze"; then
echo -e "${RED}✗ AI is still saying it's processing/analyzing (should present results)${NC}"
else
echo -e "${GREEN}✓ AI is not hallucinating processing state${NC}"
fi
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Full Project State${NC}"
echo -e "${BLUE}========================================${NC}"
echo "$project_state_after" | jq '.'

View File

@@ -0,0 +1,167 @@
#!/bin/bash
# Test Backend-Led Extraction Flow
# Tests the complete flow: create → collect → backend extract → review
set -e
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
BASE_URL="http://localhost:3000"
TOKEN="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4MDI5MzRmZTBlZWM0NmE1ZWQwMDA2ZDE0YTFiYWIwMWUzNDUwODMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiTWFyayBIZW5kZXJzb24iLCJwaWN0dXJlIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzUzOTU0MjEzP3Y9NCIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9nZW4tbGFuZy1jbGllbnQtMDk4MDA3OTQxMCIsImF1ZCI6Imdlbi1sYW5nLWNsaWVudC0wOTgwMDc5NDEwIiwiYXV0aF90aW1lIjoxNzYzMzI1MDEyLCJ1c2VyX2lkIjoiMmhDdmdXQzJaV2RJMGVlTm5SQVM3SWVKcmg1MiIsInN1YiI6IjJoQ3ZnV0MyWldkSTBlZU5uUkFTN0llSnJoNTIiLCJpYXQiOjE3NjM0MjI1NDUsImV4cCI6MTc2MzQyNjE0NSwiZW1haWwiOiJtYXJrQGdldGFjcXVpcmVkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnaXRodWIuY29tIjpbIjUzOTU0MjEzIl0sImVtYWlsIjpbIm1hcmtAZ2V0YWNxdWlyZWQuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiZ2l0aHViLmNvbSJ9fQ.TpMOORDnPUKkbLlg-KtYBmbarEjAijJ3W4vN8tWT6OslOfwaeDJAtPXIahyQk38UvKY4ZGognQG6t-laSATB8yIC8IdkYbD699axfPSGQqC8Lbux1P6YrFKOPLGDD2XemBtJ-Gb5Ql-nK_DbXKAmygLxIwz019XpLJEucGkBPAN_Rj2xC7125DVexkDSIb6ZnbLiDgCpR_IkImyQb08tqlOoBiHVUa-4VGDhraoBPACJfQXwPToJ1W3nhBiVtMvSq7s_Ekd8Otn8AB_1teu5lxC-rhLdgJuNrmlxO-H6xIMBFZ72bwq7wrvdWd_EijqFQCU99oEhphTNoISoJ3wK-g"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Backend Extraction Flow Test${NC}"
echo -e "${BLUE}========================================${NC}\n"
# Step 1: Create Project
echo -e "${YELLOW}[Step 1]${NC} Creating new project..."
RANDOM_ID=$RANDOM
PROJECT_NAME="Backend Extract Test ${RANDOM_ID}"
CREATE_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/projects/create" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectName\": \"${PROJECT_NAME}\",
\"projectType\": \"scratch\",
\"slug\": \"backend-extract-test-${RANDOM_ID}\",
\"product\": {
\"name\": \"${PROJECT_NAME}\"
}
}")
PROJECT_ID=$(echo "$CREATE_RESPONSE" | jq -r '.projectId // empty')
if [ -z "$PROJECT_ID" ]; then
echo -e "${RED}✗ Failed to create project${NC}"
echo "Response: $CREATE_RESPONSE"
exit 1
fi
echo -e "${GREEN}✓ Project created: ${PROJECT_ID}${NC}\n"
# Step 2: Upload a test document
echo -e "${YELLOW}[Step 2]${NC} Uploading test document..."
TEST_DOC_PATH="/tmp/test-doc-$RANDOM.md"
cat > "$TEST_DOC_PATH" << 'EOF'
# Product Requirements: TaskFlow
## Problem
Freelancers and small agencies struggle to manage multiple client projects simultaneously. They lose track of deadlines, forget client requests, and spend too much time on administrative work instead of billable hours.
## Target Users
- Freelance designers and developers
- Small creative agencies (2-10 people)
- Solo consultants managing multiple clients
## Core Features
1. **Client Dashboard**: One view per client showing all active projects
2. **Smart Task Capture**: Quickly log tasks from emails, calls, or chat
3. **Automated Reminders**: AI suggests when to follow up based on task age
4. **Time Tracking**: Simple timer integrated into task view
5. **Client Portal**: Clients can see progress without asking for updates
## Technical Constraints
- Must work offline (PWA)
- Mobile-first design
- Budget: $10k development budget
- Timeline: Launch MVP in 8 weeks
## Business Model
- $15/month per user
- 14-day free trial
- Annual plans at 20% discount
EOF
UPLOAD_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/projects/${PROJECT_ID}/knowledge/upload-document" \
-H "Authorization: ${TOKEN}" \
-F "file=@${TEST_DOC_PATH}")
UPLOAD_SUCCESS=$(echo "$UPLOAD_RESPONSE" | jq -r '.success // false')
if [ "$UPLOAD_SUCCESS" != "true" ]; then
echo -e "${RED}✗ Document upload failed${NC}"
echo "Response: $UPLOAD_RESPONSE"
rm -f "$TEST_DOC_PATH"
exit 1
fi
echo -e "${GREEN}✓ Document uploaded${NC}"
rm -f "$TEST_DOC_PATH"
# Step 3: Tell AI about docs and confirm ready
echo -e "\n${YELLOW}[Step 3]${NC} Telling AI about documents..."
CHAT1=$(curl -s -X POST "${BASE_URL}/api/ai/chat" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectId\": \"${PROJECT_ID}\",
\"message\": \"I uploaded a requirements document\"
}")
echo -e "${GREEN}✓ AI acknowledged${NC}"
# Step 4: Say "that's everything" to trigger extraction
echo -e "\n${YELLOW}[Step 4]${NC} Confirming ready for extraction..."
CHAT2=$(curl -s -X POST "${BASE_URL}/api/ai/chat" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectId\": \"${PROJECT_ID}\",
\"message\": \"Yes, that's everything. Please analyze it.\"
}")
echo -e "${GREEN}✓ Extraction triggered${NC}"
echo -e "${YELLOW}⏳ Backend extraction running (this takes 10-30 seconds)...${NC}\n"
# Wait for extraction to complete
sleep 15
# Step 5: Check if extraction completed
echo -e "${YELLOW}[Step 5]${NC} Checking extraction results..."
# Send a message to see if AI presents results
CHAT3=$(curl -s -X POST "${BASE_URL}/api/ai/chat" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectId\": \"${PROJECT_ID}\",
\"message\": \"What did you find?\"
}")
AI_REPLY=$(echo "$CHAT3" | jq -r '.reply // empty')
# Check if AI is still saying "processing"
if echo "$AI_REPLY" | grep -qi "processing\|synthesizing\|analyzing"; then
echo -e "${RED}✗ AI still says 'processing' - extraction may not have completed${NC}"
echo -e "\nAI Response:"
echo "$AI_REPLY"
echo -e "\n${YELLOW}Try waiting a bit longer, then check: ${BASE_URL}/default/project/${PROJECT_ID}/v_ai_chat${NC}"
exit 1
fi
# Check if AI is presenting results
if echo "$AI_REPLY" | grep -qi "found\|identified\|problems\|features\|users"; then
echo -e "${GREEN}✓ AI is presenting extraction results!${NC}"
echo -e "\n${BLUE}AI Response:${NC}"
echo "$AI_REPLY" | fold -w 80 -s
echo -e "\n${GREEN}========================================${NC}"
echo -e "${GREEN}✓ Backend extraction working!${NC}"
echo -e "${GREEN}========================================${NC}\n"
echo -e "View full chat: ${BASE_URL}/default/project/${PROJECT_ID}/v_ai_chat"
else
echo -e "${YELLOW}⚠ Unclear if extraction worked${NC}"
echo -e "\nAI Response:"
echo "$AI_REPLY"
echo -e "\n${YELLOW}Check manually: ${BASE_URL}/default/project/${PROJECT_ID}/v_ai_chat${NC}"
fi

View File

@@ -0,0 +1,303 @@
#!/bin/bash
# End-to-End Collector Flow Test
# Simulates a real user journey from welcome → document upload → GitHub → extension → handoff
# set -e # Don't exit on error - show all test results
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Configuration
BASE_URL="http://localhost:3000"
API_BASE="$BASE_URL/api"
# You MUST set these from a real logged-in session
echo ""
echo "=========================================="
echo " E2E COLLECTOR FLOW TEST"
echo "=========================================="
echo ""
echo -e "${YELLOW}SETUP REQUIRED:${NC}"
echo "1. Log into http://localhost:3000"
echo "2. Open DevTools → Network tab"
echo "3. Send a test message in AI Chat"
echo "4. Copy the 'Authorization: Bearer XXX' header"
echo "5. Create a project and copy the projectId"
echo ""
echo -e "${CYAN}Then run:${NC}"
echo " export AUTH_TOKEN='your-token-here'"
echo " export PROJECT_ID='your-project-id-here'"
echo " ./test-e2e-collector.sh"
echo ""
if [ -z "$AUTH_TOKEN" ] || [ -z "$PROJECT_ID" ]; then
echo -e "${RED}ERROR: AUTH_TOKEN and PROJECT_ID must be set${NC}"
echo ""
exit 1
fi
# Test results
TESTS_PASSED=0
TESTS_FAILED=0
FAILED_TESTS=()
# Helper functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[PASS]${NC} $1"
((TESTS_PASSED++))
}
log_error() {
echo -e "${RED}[FAIL]${NC} $1"
((TESTS_FAILED++))
FAILED_TESTS+=("$1")
}
log_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_response() {
echo -e "${CYAN}[RESPONSE]${NC} $1"
}
# Send a chat message and check response
send_chat_message() {
local message="$1"
local expected_keywords="$2"
log_info "Sending: \"$message\""
response=$(curl -s -X POST "$API_BASE/ai/chat" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"projectId\":\"$PROJECT_ID\",\"message\":\"$message\"}")
http_code=$?
if [ $http_code -ne 0 ]; then
log_error "Failed to send message: $message"
return 1
fi
# Check for error in response
if echo "$response" | jq -e '.error' > /dev/null 2>&1; then
error_msg=$(echo "$response" | jq -r '.error')
log_error "API Error: $error_msg"
echo "$response" | jq '.' 2>/dev/null || echo "$response"
return 1
fi
# Extract reply
reply=$(echo "$response" | jq -r '.reply' 2>/dev/null)
if [ -z "$reply" ] || [ "$reply" = "null" ]; then
log_error "No reply received"
echo "$response" | jq '.' 2>/dev/null || echo "$response"
return 1
fi
log_response "$(echo "$reply" | head -c 200)..."
# Check for expected keywords
if [ -n "$expected_keywords" ]; then
all_found=true
IFS='|' read -ra KEYWORDS <<< "$expected_keywords"
for keyword in "${KEYWORDS[@]}"; do
if echo "$reply" | grep -qi "$keyword"; then
log_success "Response contains: '$keyword'"
else
log_error "Response missing expected keyword: '$keyword'"
all_found=false
fi
done
if $all_found; then
return 0
else
return 1
fi
fi
return 0
}
# Check collector handoff state
check_handoff_state() {
local expected_docs="$1"
local expected_github="$2"
local expected_extension="$3"
log_info "Checking collector handoff state..."
# This would require Firestore access or an API endpoint
# For now, we'll just log what we expect
log_info "Expected state:"
echo " - hasDocuments: $expected_docs"
echo " - githubConnected: $expected_github"
echo " - extensionLinked: $expected_extension"
# TODO: Add actual Firestore check or API endpoint
log_warning "Handoff state check not implemented (would need Firestore access)"
}
# Simulate document upload
simulate_document_upload() {
local filename="$1"
log_info "Simulating upload: $filename"
# Create a temporary test file
temp_file=$(mktemp)
echo "This is a test document for QA purposes.
Project Overview:
- We're building a SaaS platform
- Target users: Small businesses
- Key features: User management, billing, analytics
Technical Stack:
- Frontend: React, Next.js
- Backend: Node.js, PostgreSQL
- Infrastructure: AWS
This is test content for $filename" > "$temp_file"
# Upload via API
response=$(curl -s -X POST "$API_BASE/projects/$PROJECT_ID/knowledge/upload-document" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-F "file=@$temp_file;filename=$filename")
rm "$temp_file"
if echo "$response" | jq -e '.success' > /dev/null 2>&1; then
knowledge_id=$(echo "$response" | jq -r '.knowledgeItemId')
log_success "Uploaded: $filename (ID: $knowledge_id)"
return 0
else
error_msg=$(echo "$response" | jq -r '.error' 2>/dev/null || echo "Unknown error")
log_error "Upload failed for $filename: $error_msg"
return 1
fi
}
# Main test flow
main() {
echo ""
echo "=========================================="
echo " RUNNING E2E COLLECTOR TESTS"
echo "=========================================="
echo ""
echo "Project ID: $PROJECT_ID"
echo ""
# Step 1: Initial greeting (auto-sent "Hello")
log_info "=== STEP 1: Welcome Message ==="
send_chat_message "Hello" "Welcome|Step 1|Step 2|Step 3|documents|GitHub|extension"
sleep 2
# Step 2: Upload documents
log_info ""
log_info "=== STEP 2: Upload Documents ==="
docs=(
"project-overview.md"
"user-stories.md"
"technical-requirements.md"
"api-specification.md"
"database-schema.md"
"ui-mockups.md"
"business-requirements.md"
"deployment-plan.md"
)
for doc in "${docs[@]}"; do
simulate_document_upload "$doc"
sleep 1
done
# Step 3: Tell AI about documents
log_info ""
log_info "=== STEP 3: Inform AI About Documents ==="
send_chat_message "I just uploaded 8 documents about my project" "uploaded|document"
sleep 2
# Step 4: Connect GitHub
log_info ""
log_info "=== STEP 4: GitHub Connection ==="
send_chat_message "Yes, I have a GitHub repo. It's called myuser/my-saas-app" "GitHub|repo|connected"
sleep 2
# Note: Actual GitHub connection requires OAuth flow
log_warning "GitHub OAuth flow requires manual browser interaction"
log_info "In real testing, user would click 'Connect GitHub' button"
# Step 5: Extension
log_info ""
log_info "=== STEP 5: Extension Installation ==="
send_chat_message "I want to install the browser extension" "extension|install|capture"
sleep 2
# Step 6: Confirm ready
log_info ""
log_info "=== STEP 6: Confirm Everything ==="
send_chat_message "Yes, that's everything I have for now" "everything|analyze|dig"
sleep 2
# Step 7: Check for auto-transition
log_info ""
log_info "=== STEP 7: Verify Auto-Transition ==="
send_chat_message "What do you need from me?" "extraction|review|important|V1"
sleep 2
# Step 8: Check handoff state
log_info ""
log_info "=== STEP 8: Verify Handoff State ==="
check_handoff_state "true" "true" "false"
# Results
echo ""
echo "=========================================="
echo " TEST RESULTS"
echo "=========================================="
echo -e "${GREEN}Passed:${NC} $TESTS_PASSED"
echo -e "${RED}Failed:${NC} $TESTS_FAILED"
echo ""
if [ $TESTS_FAILED -gt 0 ]; then
echo -e "${RED}Failed checks:${NC}"
for test in "${FAILED_TESTS[@]}"; do
echo " - $test"
done
echo ""
exit 1
else
echo -e "${GREEN}✅ E2E COLLECTOR FLOW COMPLETE!${NC}"
echo ""
echo "Next steps:"
echo "1. Open http://localhost:3000 in browser"
echo "2. Navigate to the project"
echo "3. Check AI Chat page - verify checklist shows:"
echo " ✅ Documents uploaded (8)"
echo " ✅ GitHub connected"
echo " ⭕ Extension linked"
echo "4. Verify mode switched to 'Extraction Review'"
echo ""
exit 0
fi
}
# Run tests
cd "$(dirname "$0")"
main

View File

@@ -0,0 +1,62 @@
const { VertexAI } = require('@google-cloud/vertexai');
async function testGemini3Global() {
console.log('🧪 Testing Gemini 3 Pro Preview in GLOBAL location...\n');
const vertexAI = new VertexAI({
project: 'gen-lang-client-0980079410',
location: 'global', // ← KEY: Use 'global' not regional!
});
console.log('Model: gemini-3-pro-preview');
console.log('Location: global');
console.log('Project: gen-lang-client-0980079410\n');
try {
const model = vertexAI.getGenerativeModel({
model: 'gemini-3-pro-preview',
systemInstruction: 'You are a helpful AI assistant.',
generationConfig: {
temperature: 1.0,
responseMimeType: 'application/json',
},
});
console.log('⏳ Sending test request...\n');
const response = await model.generateContent({
contents: [{
role: 'user',
parts: [{
text: 'Return JSON with this exact structure: {"status": "success", "model": "gemini-3-pro-preview", "message": "Gemini 3 is working!", "features": ["thinking_mode", "1M_context", "multimodal"]}'
}],
}],
});
const text = response.response?.candidates?.[0]?.content?.parts?.[0]?.text || '';
console.log('✅ RAW RESPONSE:\n', text, '\n');
const parsed = JSON.parse(text);
if (parsed.status === 'success') {
console.log('🎉🎉🎉 SUCCESS! GEMINI 3 PRO PREVIEW IS WORKING! 🎉🎉🎉\n');
console.log('✅ Model:', parsed.model);
console.log('✅ Message:', parsed.message);
console.log('✅ Features:', parsed.features?.join(', '));
console.log('\n🚀 You have full access to Gemini 3 Pro Preview!');
console.log('📊 1M token context | Thinking mode | Multimodal');
return true;
}
} catch (error) {
console.error('❌ Error:', error.message);
if (error.message.includes('404')) {
console.log('\n💡 Model still not accessible. You may need to:');
console.log(' 1. Enable the model in the console');
console.log(' 2. Accept usage terms');
console.log(' 3. Wait for API access approval');
}
return false;
}
}
testGemini3Global();

View File

@@ -0,0 +1,39 @@
const { VertexAI } = require('@google-cloud/vertexai');
async function testSimple() {
console.log('🧪 Testing Gemini 3 Pro Preview (simple text)...\n');
const vertexAI = new VertexAI({
project: 'gen-lang-client-0980079410',
location: 'global',
});
try {
const model = vertexAI.getGenerativeModel({
model: 'gemini-3-pro-preview',
generationConfig: { temperature: 1.0 },
});
console.log('⏳ Sending simple text request...\n');
const response = await model.generateContent({
contents: [{
role: 'user',
parts: [{ text: 'Say "Hello from Gemini 3!" in exactly those words.' }],
}],
});
const text = response.response?.candidates?.[0]?.content?.parts?.[0]?.text || 'No response';
console.log('✅ Response:', text, '\n');
if (text.includes('Gemini 3') || text.includes('Hello')) {
console.log('🎉 SUCCESS! Gemini 3 Pro Preview is responding!');
return true;
}
} catch (error) {
console.error('❌ Full error:', error);
return false;
}
}
testSimple();

View File

@@ -0,0 +1,56 @@
#!/bin/bash
# Test Collector Handoff Persistence
# Sends messages and checks if handoff data is being persisted
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
PROJECT_ID="lyOZxelSkjAB6XisIzup"
BASE_URL="http://localhost:3000"
TOKEN="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4MDI5MzRmZTBlZWM0NmE1ZWQwMDA2ZDE0YTFiYWIwMWUzNDUwODMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiTWFyayBIZW5kZXJzb24iLCJwaWN0dXJlIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzUzOTU0MjEzP3Y9NCIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9nZW4tbGFuZy1jbGllbnQtMDk4MDA3OTQxMCIsImF1ZCI6Imdlbi1sYW5nLWNsaWVudC0wOTgwMDc5NDEwIiwiYXV0aF90aW1lIjoxNzYzMzI1MDEyLCJ1c2VyX2lkIjoiMmhDdmdXQzJaV2RJMGVlTm5SQVM3SWVKcmg1MiIsInN1YiI6IjJoQ3ZnV0MyWldkSTBlZU5uUkFTN0llSnJoNTIiLCJpYXQiOjE3NjM0MjI1NDUsImV4cCI6MTc2MzQyNjE0NSwiZW1haWwiOiJtYXJrQGdldGFjcXVpcmVkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnaXRodWIuY29tIjpbIjUzOTU0MjEzIl0sImVtYWlsIjpbIm1hcmtAZ2V0YWNxdWlyZWQuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiZ2l0aHViLmNvbSJ9fQ.TpMOORDnPUKkbLlg-KtYBmbarEjAijJ3W4vN8tWT6OslOfwaeDJAtPXIahyQk38UvKY4ZGognQG6t-laSATB8yIC8IdkYbD699axfPSGQqC8Lbux1P6YrFKOPLGDD2XemBtJ-Gb5Ql-nK_DbXKAmygLxIwz019XpLJEucGkBPAN_Rj2xC7125DVexkDSIb6ZnbLiDgCpR_IkImyQb08tqlOoBiHVUa-4VGDhraoBPACJfQXwPToJ1W3nhBiVtMvSq7s_Ekd8Otn8AB_1teu5lxC-rhLdgJuNrmlxO-H6xIMBFZ72bwq7wrvdWd_EijqFQCU99oEhphTNoISoJ3wK-g"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Testing Handoff Persistence${NC}"
echo -e "${BLUE}========================================${NC}\n"
# Send a message and capture full response with verbose logging
echo -e "${YELLOW}[Test]${NC} Sending message to AI with verbose logging enabled...\n"
# Enable curl verbose output and check server response
curl -v -X POST "${BASE_URL}/api/ai/chat" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"projectId": "'"${PROJECT_ID}"'", "message": "I have uploaded 1 document. I do not have GitHub or the extension yet."}' \
2>&1 | tee /tmp/curl-output.txt
echo -e "\n\n${BLUE}========================================${NC}"
echo -e "${BLUE}Analysis${NC}"
echo -e "${BLUE}========================================${NC}\n"
# Check if the response contains expected fields
if grep -q '"mode":"collector_mode"' /tmp/curl-output.txt; then
echo -e "${GREEN}✓ Mode is collector_mode${NC}"
else
echo -e "${RED}✗ Mode not detected${NC}"
fi
if grep -q '"projectPhase"' /tmp/curl-output.txt; then
echo -e "${GREEN}✓ Project phase returned${NC}"
else
echo -e "${RED}✗ Project phase not returned${NC}"
fi
# Look for any indication of handoff data in server logs
echo -e "\n${YELLOW}Note:${NC} The collectorHandoff data is persisted to Firestore,"
echo -e "but not returned in the API response. This is expected."
echo -e "\nTo verify persistence, check:"
echo -e " 1. Server console logs for '[AI Chat] Collector handoff persisted'"
echo -e " 2. Firebase Console: projects/${PROJECT_ID}/phaseData/phaseHandoffs/collector"
echo -e " 3. UI checklist: ${BASE_URL}/default/project/${PROJECT_ID}/v_ai_chat\n"
rm -f /tmp/curl-output.txt

View File

@@ -0,0 +1,216 @@
#!/bin/bash
# Simplified E2E Test - Create Project → AI Chat → Collector Flow
# This tests the new streamlined project creation flow
set -e
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
BASE_URL="http://localhost:3000"
TOKEN="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4MDI5MzRmZTBlZWM0NmE1ZWQwMDA2ZDE0YTFiYWIwMWUzNDUwODMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiTWFyayBIZW5kZXJzb24iLCJwaWN0dXJlIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzUzOTU0MjEzP3Y9NCIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9nZW4tbGFuZy1jbGllbnQtMDk4MDA3OTQxMCIsImF1ZCI6Imdlbi1sYW5nLWNsaWVudC0wOTgwMDc5NDEwIiwiYXV0aF90aW1lIjoxNzYzMzI1MDEyLCJ1c2VyX2lkIjoiMmhDdmdXQzJaV2RJMGVlTm5SQVM3SWVKcmg1MiIsInN1YiI6IjJoQ3ZnV0MyWldkSTBlZU5uUkFTN0llSnJoNTIiLCJpYXQiOjE3NjM0MjI1NDUsImV4cCI6MTc2MzQyNjE0NSwiZW1haWwiOiJtYXJrQGdldGFjcXVpcmVkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnaXRodWIuY29tIjpbIjUzOTU0MjEzIl0sImVtYWlsIjpbIm1hcmtAZ2V0YWNxdWlyZWQuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiZ2l0aHViLmNvbSJ9fQ.TpMOORDnPUKkbLlg-KtYBmbarEjAijJ3W4vN8tWT6OslOfwaeDJAtPXIahyQk38UvKY4ZGognQG6t-laSATB8yIC8IdkYbD699axfPSGQqC8Lbux1P6YrFKOPLGDD2XemBtJ-Gb5Ql-nK_DbXKAmygLxIwz019XpLJEucGkBPAN_Rj2xC7125DVexkDSIb6ZnbLiDgCpR_IkImyQb08tqlOoBiHVUa-4VGDhraoBPACJfQXwPToJ1W3nhBiVtMvSq7s_Ekd8Otn8AB_1teu5lxC-rhLdgJuNrmlxO-H6xIMBFZ72bwq7wrvdWd_EijqFQCU99oEhphTNoISoJ3wK-g"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Simplified Project Creation Test${NC}"
echo -e "${BLUE}========================================${NC}\n"
# Step 1: Create Project
echo -e "${YELLOW}[Step 1]${NC} Creating new project..."
RANDOM_ID=$RANDOM
PROJECT_NAME="E2E Test ${RANDOM_ID}"
CREATE_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/projects/create" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectName\": \"${PROJECT_NAME}\",
\"projectType\": \"scratch\",
\"slug\": \"e2e-test-${RANDOM_ID}\",
\"product\": {
\"name\": \"${PROJECT_NAME}\"
}
}")
PROJECT_ID=$(echo "$CREATE_RESPONSE" | jq -r '.projectId // empty')
if [ -z "$PROJECT_ID" ]; then
echo -e "${RED}✗ Failed to create project${NC}"
echo "Response: $CREATE_RESPONSE"
exit 1
fi
echo -e "${GREEN}✓ Project created: ${PROJECT_ID}${NC}\n"
# Step 2: Send first message to AI (should trigger welcome)
echo -e "${YELLOW}[Step 2]${NC} Sending first message to AI..."
AI_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/ai/chat" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectId\": \"${PROJECT_ID}\",
\"message\": \"Hello\"
}")
AI_REPLY=$(echo "$AI_RESPONSE" | jq -r '.reply // empty')
if [ -z "$AI_REPLY" ]; then
echo -e "${RED}✗ Failed to get AI response${NC}"
echo "Response: $AI_RESPONSE"
exit 1
fi
echo -e "${GREEN}✓ AI responded${NC}"
echo -e " Reply preview: ${AI_REPLY:0:100}...\n"
# Step 3: Check conversation history
echo -e "${YELLOW}[Step 3]${NC} Checking conversation history..."
HISTORY_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/ai/conversation?projectId=${PROJECT_ID}" \
-H "Authorization: ${TOKEN}")
MESSAGE_COUNT=$(echo "$HISTORY_RESPONSE" | jq '.messages | length')
if [ "$MESSAGE_COUNT" -lt 2 ]; then
echo -e "${RED}✗ Conversation history incomplete (found ${MESSAGE_COUNT} messages)${NC}"
echo "Response: $HISTORY_RESPONSE"
exit 1
fi
echo -e "${GREEN}✓ Conversation history persisted (${MESSAGE_COUNT} messages)${NC}\n"
# Step 4: Check collector handoff state
echo -e "${YELLOW}[Step 4]${NC} Checking collector handoff state..."
HANDOFF_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/ai/conversation?projectId=${PROJECT_ID}" \
-H "Authorization: ${TOKEN}")
# The handoff data should be in the project document, let's just verify the conversation loaded
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ Collector handoff endpoint accessible${NC}\n"
else
echo -e "${YELLOW}⚠ Could not verify handoff state directly${NC}\n"
fi
# Step 5: Simulate user saying they'll upload docs
echo -e "${YELLOW}[Step 5]${NC} Simulating user interaction (uploading docs)..."
DOCS_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/ai/chat" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectId\": \"${PROJECT_ID}\",
\"message\": \"I am going to upload some docs\"
}")
DOCS_REPLY=$(echo "$DOCS_RESPONSE" | jq -r '.reply // empty')
if [ -z "$DOCS_REPLY" ]; then
echo -e "${RED}✗ Failed to get AI response${NC}"
exit 1
fi
echo -e "${GREEN}✓ AI acknowledged document upload${NC}"
echo -e " Reply preview: ${DOCS_REPLY:0:100}...\n"
# Step 6: Simulate uploading a document
echo -e "${YELLOW}[Step 6]${NC} Simulating document upload..."
# Create a test file
TEST_DOC_PATH="/tmp/test-document-$RANDOM.md"
cat > "$TEST_DOC_PATH" << 'EOF'
# Test Product Requirements
## Overview
This is a test document for the E2E flow.
## Features
- Feature 1: User authentication
- Feature 2: Dashboard
- Feature 3: Analytics
## Tech Stack
- Next.js
- TypeScript
- Firebase
EOF
UPLOAD_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/projects/${PROJECT_ID}/knowledge/upload-document" \
-H "Authorization: ${TOKEN}" \
-F "file=@${TEST_DOC_PATH}")
UPLOAD_SUCCESS=$(echo "$UPLOAD_RESPONSE" | jq -r '.success // false')
if [ "$UPLOAD_SUCCESS" != "true" ]; then
echo -e "${RED}✗ Document upload failed${NC}"
echo "Response: $UPLOAD_RESPONSE"
rm -f "$TEST_DOC_PATH"
exit 1
fi
echo -e "${GREEN}✓ Document uploaded successfully${NC}"
rm -f "$TEST_DOC_PATH"
# Get the knowledge item ID
KNOWLEDGE_ITEM_ID=$(echo "$UPLOAD_RESPONSE" | jq -r '.knowledgeItemId // empty')
echo -e " Knowledge item: ${KNOWLEDGE_ITEM_ID}\n"
# Step 7: Tell AI about the upload
echo -e "${YELLOW}[Step 7]${NC} Informing AI about uploaded document..."
INFORM_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/ai/chat" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectId\": \"${PROJECT_ID}\",
\"message\": \"I just uploaded a requirements document\"
}")
INFORM_REPLY=$(echo "$INFORM_RESPONSE" | jq -r '.reply // empty')
if [ -z "$INFORM_REPLY" ]; then
echo -e "${RED}✗ Failed to get AI response${NC}"
exit 1
fi
echo -e "${GREEN}✓ AI acknowledged upload${NC}"
echo -e " Reply preview: ${INFORM_REPLY:0:100}...\n"
# Step 8: Check final conversation state
echo -e "${YELLOW}[Step 8]${NC} Checking final conversation state..."
FINAL_HISTORY=$(curl -s -X GET "${BASE_URL}/api/ai/conversation?projectId=${PROJECT_ID}" \
-H "Authorization: ${TOKEN}")
FINAL_MESSAGE_COUNT=$(echo "$FINAL_HISTORY" | jq '.messages | length')
if [ "$FINAL_MESSAGE_COUNT" -lt 6 ]; then
echo -e "${RED}✗ Final conversation history incomplete (found ${FINAL_MESSAGE_COUNT} messages)${NC}"
exit 1
fi
echo -e "${GREEN}✓ Final conversation history complete (${FINAL_MESSAGE_COUNT} messages)${NC}\n"
# Summary
echo -e "${BLUE}========================================${NC}"
echo -e "${GREEN}✓ All tests passed!${NC}"
echo -e "${BLUE}========================================${NC}\n"
echo -e "Test Summary:"
echo -e " • Project ID: ${PROJECT_ID}"
echo -e " • Total Messages: ${FINAL_MESSAGE_COUNT}"
echo -e " • Documents Uploaded: 1"
echo -e " • Knowledge Items: ${KNOWLEDGE_ITEM_ID}"
echo -e ""
echo -e "Next Steps:"
echo -e " 1. Open ${BASE_URL}/default/project/${PROJECT_ID}/v_ai_chat"
echo -e " 2. Verify the collector checklist shows 1 document"
echo -e " 3. Continue the conversation to test GitHub/extension flow"
echo -e ""

View File

@@ -0,0 +1,342 @@
#!/bin/bash
# Table Stakes Features - Automated QA Test Script
# Tests all implemented features end-to-end
# set -e # Don't exit on error - show all test results
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
BASE_URL="http://localhost:3000"
API_BASE="$BASE_URL/api"
TEST_PROJECT_NAME="QA Test Project $(date +%s)"
TEST_WORKSPACE="test-workspace"
# Test results
TESTS_PASSED=0
TESTS_FAILED=0
FAILED_TESTS=()
# Helper functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[PASS]${NC} $1"
((TESTS_PASSED++))
}
log_error() {
echo -e "${RED}[FAIL]${NC} $1"
((TESTS_FAILED++))
FAILED_TESTS+=("$1")
}
log_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
# Check if server is running
check_server() {
log_info "Checking if server is running at $BASE_URL..."
if curl -s -o /dev/null -w "%{http_code}" "$BASE_URL" | grep -q "200\|404"; then
log_success "Server is running"
return 0
else
log_error "Server is not running at $BASE_URL"
exit 1
fi
}
# Test 1: Create a new project
test_create_project() {
log_info "Test 1: Creating new project..."
# Note: This requires authentication, so we'll check the endpoint exists
response=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API_BASE/projects/create" \
-H "Content-Type: application/json" \
-d '{}')
if [ "$response" = "401" ]; then
log_success "Project creation endpoint exists (401 Unauthorized as expected without token)"
else
log_warning "Project creation endpoint returned $response (expected 401)"
fi
}
# Test 2: Check extensionLinked field initialization
test_extension_field() {
log_info "Test 2: Checking extensionLinked field in project creation..."
# Check the code has the field
if grep -q "extensionLinked: false" app/api/projects/create/route.ts; then
log_success "extensionLinked field present in project creation"
else
log_error "extensionLinked field missing from project creation"
fi
}
# Test 3: Check collector handoff type includes 'collector'
test_collector_handoff_type() {
log_info "Test 3: Checking collector handoff type definition..."
if grep -q "'collector' |" lib/server/chat-context.ts; then
log_success "Collector phase included in phaseHandoffs type"
else
log_error "Collector phase missing from phaseHandoffs type"
fi
}
# Test 4: Check auto-transition logic exists
test_auto_transition() {
log_info "Test 4: Checking auto-transition logic..."
if grep -q "Auto-transitioning project to extraction phase" app/api/ai/chat/route.ts; then
log_success "Auto-transition logic present in chat route"
else
log_error "Auto-transition logic missing from chat route"
fi
if grep -q "currentPhase === 'analyzed'" lib/server/chat-mode-resolver.ts; then
log_success "Mode resolver checks currentPhase field"
else
log_error "Mode resolver doesn't check currentPhase"
fi
}
# Test 5: Check extension linking API exists
test_extension_api() {
log_info "Test 5: Checking extension linking API..."
response=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API_BASE/extension/link-project" \
-H "Content-Type: application/json" \
-d '{}')
if [ "$response" = "401" ]; then
log_success "Extension link API exists (401 Unauthorized as expected)"
else
log_warning "Extension link API returned $response (expected 401)"
fi
}
# Test 6: Check chunking API exists
test_chunking_api() {
log_info "Test 6: Checking extraction chunking API..."
# Check if the file exists
if [ -f "app/api/projects/[projectId]/knowledge/chunk-insight/route.ts" ]; then
log_success "Chunk insight API endpoint file exists"
else
log_error "Chunk insight API endpoint missing"
fi
}
# Test 7: Check auto-chunking is disabled on upload
test_no_auto_chunking() {
log_info "Test 7: Checking auto-chunking is disabled on upload..."
if grep -q "Store whole document as single knowledge_item" app/api/projects/[projectId]/knowledge/upload-document/route.ts; then
log_success "Upload stores whole document (no auto-chunking)"
else
log_error "Upload may still be auto-chunking"
fi
if grep -q "// import { chunkDocument }" app/api/projects/[projectId]/knowledge/upload-document/route.ts; then
log_success "chunkDocument import commented out"
else
log_warning "chunkDocument import may still be active"
fi
}
# Test 8: Check collector checklist component exists
test_checklist_component() {
log_info "Test 8: Checking collector checklist component..."
if [ -f "components/ai/collector-checklist.tsx" ]; then
log_success "Collector checklist component exists"
else
log_error "Collector checklist component missing"
fi
if grep -q "CollectorChecklist" app/[workspace]/project/[projectId]/v_ai_chat/page.tsx; then
log_success "Checklist integrated into AI chat page"
else
log_error "Checklist not integrated into AI chat page"
fi
}
# Test 9: Check Gemini role mapping fix
test_gemini_role_fix() {
log_info "Test 9: Checking Gemini role mapping fix..."
if grep -q "role === 'assistant' ? 'model'" lib/ai/gemini-client.ts; then
log_success "Gemini client translates assistant → model role"
else
log_error "Gemini client missing role translation"
fi
}
# Test 10: Check conversation history persistence
test_conversation_history() {
log_info "Test 10: Checking conversation history loading..."
if grep -q "Load existing conversation history" app/api/ai/chat/route.ts; then
log_success "Chat route loads conversation history"
else
log_error "Chat route doesn't load conversation history"
fi
if grep -q "conversationHistory.map" app/api/ai/chat/route.ts; then
log_success "Conversation history included in messages"
else
log_error "Conversation history not included in messages"
fi
}
# Test 11: Check extensionLinked in context
test_extension_in_context() {
log_info "Test 11: Checking extensionLinked in project context..."
if grep -q "extensionLinked?: boolean" lib/server/chat-context.ts; then
log_success "extensionLinked field in ProjectChatContext type"
else
log_error "extensionLinked field missing from context type"
fi
if grep -q "extensionLinked: projectData.extensionLinked" lib/server/chat-context.ts; then
log_success "extensionLinked passed to AI in context"
else
log_error "extensionLinked not passed to AI"
fi
}
# Test 12: Check collector prompt mentions extensionLinked
test_collector_prompt() {
log_info "Test 12: Checking collector prompt..."
if grep -q "projectContext.project.extensionLinked" lib/ai/prompts/collector.ts; then
log_success "Collector prompt checks extensionLinked field"
else
log_error "Collector prompt doesn't check extensionLinked"
fi
}
# Test 13: Check UI text update
test_ui_text() {
log_info "Test 13: Checking UI text reflects no auto-chunking..."
if grep -q "stored for the Extractor AI to review" app/[workspace]/project/[projectId]/context/page.tsx; then
log_success "UI text updated (no mention of auto-chunking)"
else
log_error "UI text may still mention auto-chunking"
fi
}
# Test 14: Verify linter passes
test_linter() {
log_info "Test 14: Running linter check..."
# This is a simplified check - just verify key files exist and are not empty
files_to_check=(
"app/api/ai/chat/route.ts"
"lib/server/chat-context.ts"
"lib/ai/gemini-client.ts"
"components/ai/collector-checklist.tsx"
)
all_good=true
for file in "${files_to_check[@]}"; do
if [ ! -f "$file" ]; then
log_error "File missing: $file"
all_good=false
elif [ ! -s "$file" ]; then
log_error "File is empty: $file"
all_good=false
fi
done
if $all_good; then
log_success "All critical files exist and are not empty"
fi
}
# Test 15: Check documentation exists
test_documentation() {
log_info "Test 15: Checking documentation..."
docs=(
"TABLE_STAKES_IMPLEMENTATION.md"
"QA_FIXES_APPLIED.md"
"PROJECT_CREATION_FIX.md"
"UPLOAD_CHUNKING_REMOVED.md"
)
for doc in "${docs[@]}"; do
if [ -f "$doc" ]; then
log_success "Documentation exists: $doc"
else
log_warning "Documentation missing: $doc"
fi
done
}
# Main test execution
main() {
echo ""
echo "=========================================="
echo " TABLE STAKES FEATURES - QA TEST SUITE"
echo "=========================================="
echo ""
check_server
echo ""
test_create_project
test_extension_field
test_collector_handoff_type
test_auto_transition
test_extension_api
test_chunking_api
test_no_auto_chunking
test_checklist_component
test_gemini_role_fix
test_conversation_history
test_extension_in_context
test_collector_prompt
test_ui_text
test_linter
test_documentation
echo ""
echo "=========================================="
echo " TEST RESULTS"
echo "=========================================="
echo -e "${GREEN}Passed:${NC} $TESTS_PASSED"
echo -e "${RED}Failed:${NC} $TESTS_FAILED"
echo ""
if [ $TESTS_FAILED -gt 0 ]; then
echo -e "${RED}Failed tests:${NC}"
for test in "${FAILED_TESTS[@]}"; do
echo " - $test"
done
echo ""
exit 1
else
echo -e "${GREEN}✅ ALL TESTS PASSED!${NC}"
echo ""
exit 0
fi
}
# Run tests
cd "$(dirname "$0")"
main

View File

@@ -0,0 +1,105 @@
#!/bin/bash
# Test Vision Flow - Automated testing of 3-question flow
# Usage: ./test-vision-flow.sh <projectId>
set -e
if [ -z "$1" ]; then
echo "❌ Error: Please provide a projectId"
echo "Usage: ./test-vision-flow.sh <projectId>"
exit 1
fi
PROJECT_ID="$1"
WORKSPACE="marks-account"
API_URL="http://localhost:3000/api/ai/chat"
# Load test questions
Q1=$(grep "^q1=" .test-questions | cut -d'=' -f2-)
Q2=$(grep "^q2=" .test-questions | cut -d'=' -f2-)
Q3=$(grep "^q3=" .test-questions | cut -d'=' -f2-)
echo "🧪 Testing Vision Flow"
echo "Project ID: $PROJECT_ID"
echo ""
echo "Questions loaded:"
echo " Q1: ${Q1:0:50}..."
echo " Q2: ${Q2:0:50}..."
echo " Q3: ${Q3:0:50}..."
echo ""
# Function to send chat message
send_message() {
local message="$1"
local step="$2"
echo "[$step] Sending: ${message:0:60}..."
response=$(curl -s -X POST "$API_URL" \
-H "Content-Type: application/json" \
-d "{
\"message\": \"$message\",
\"projectId\": \"$PROJECT_ID\",
\"workspaceId\": \"$WORKSPACE\"
}")
# Check for error
if echo "$response" | grep -q '"error"'; then
echo "❌ Error response:"
echo "$response" | jq '.'
exit 1
fi
# Extract reply
reply=$(echo "$response" | jq -r '.reply // .message // "No reply"')
echo "[$step] AI Reply: ${reply:0:80}..."
echo ""
# Small delay between messages
sleep 2
}
echo "🚀 Starting test..."
echo ""
# Send Q1
send_message "$Q1" "Q1"
# Send Q2
send_message "$Q2" "Q2"
# Send Q3
send_message "$Q3" "Q3"
echo "✅ All 3 questions sent!"
echo ""
echo "🔍 Checking logs for success markers..."
sleep 2
# Check terminal logs for success
TERMINAL_LOG="$HOME/.cursor/projects/Users-markhenderson-ai-proxy/terminals/13.txt"
if tail -100 "$TERMINAL_LOG" | grep -q "All 3 vision answers complete"; then
echo "✅ SUCCESS: Found 'All 3 vision answers complete' in logs"
else
echo "❌ FAILED: Did not find success marker in logs"
echo ""
echo "Last 20 lines of log:"
tail -20 "$TERMINAL_LOG"
exit 1
fi
if tail -100 "$TERMINAL_LOG" | grep -q "readyForMVP.*true\|readyForExtraction.*true"; then
echo "✅ SUCCESS: MVP generation flag set"
else
echo "⚠️ WARNING: MVP generation flag not found in logs"
fi
echo ""
echo "🎉 Vision flow test completed!"
echo ""
echo "📊 Check Firestore to verify data was saved:"
echo " Project: $PROJECT_ID"
echo " Expected fields: visionAnswers.q1, q2, q3, allAnswered, readyForMVP"

View File

@@ -0,0 +1,84 @@
#!/bin/bash
# Simple Vision Flow Test
# Usage: ./test-vision-simple.sh <projectId>
if [ -z "$1" ]; then
echo "❌ Usage: ./test-vision-simple.sh <projectId>"
echo ""
echo "1. Create a new project in the browser"
echo "2. Copy its project ID from the URL"
echo "3. Run: ./test-vision-simple.sh YOUR_PROJECT_ID"
exit 1
fi
PROJECT_ID="$1"
WORKSPACE="marks-account"
API_URL="http://localhost:3000/api/ai/chat"
# Load test questions
Q1=$(grep "^q1=" .test-questions | cut -d'=' -f2-)
Q2=$(grep "^q2=" .test-questions | cut -d'=' -f2-)
Q3=$(grep "^q3=" .test-questions | cut -d'=' -f2-)
echo "🧪 Vision Flow Test"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Project: $PROJECT_ID"
echo ""
# Function to send message and check response
send_and_check() {
local msg="$1"
local step="$2"
echo "[$step] Sending..."
response=$(curl -s -X POST "$API_URL" \
-H "Content-Type: application/json" \
-d @- <<EOF
{
"message": $(echo "$msg" | jq -Rs .),
"projectId": "$PROJECT_ID",
"workspaceId": "$WORKSPACE"
}
EOF
)
if echo "$response" | jq -e '.error' > /dev/null 2>&1; then
echo "❌ Error: $(echo "$response" | jq -r '.error')"
exit 1
fi
echo "✅ [$step] Sent"
sleep 3
}
echo "Sending Q1..."
send_and_check "$Q1" "Q1"
echo "Sending Q2..."
send_and_check "$Q2" "Q2"
echo "Sending Q3..."
send_and_check "$Q3" "Q3"
echo ""
echo "🔍 Checking logs..."
sleep 2
TERMINAL_LOG="$HOME/.cursor/projects/Users-markhenderson-ai-proxy/terminals/13.txt"
if tail -100 "$TERMINAL_LOG" | grep -q "All 3 vision answers complete"; then
echo "✅ SUCCESS: All 3 vision answers stored!"
echo "✅ SUCCESS: MVP generation triggered!"
else
echo "❌ FAILED: Did not find success marker"
echo ""
echo "Last vision-related logs:"
tail -50 "$TERMINAL_LOG" | grep -E "vision|Q1|Q2|Q3|allAnswered"
exit 1
fi
echo ""
echo "🎉 Test PASSED!"

View File

@@ -0,0 +1,37 @@
require('dotenv').config({ path: '.env.local' });
const { Pool } = require('pg');
async function test() {
console.log("BYPASS_AUTH:", process.env.NEXT_PUBLIC_DEV_BYPASS_PROJECT_AUTH);
console.log("LOCAL_EMAIL:", process.env.NEXT_PUBLIC_DEV_LOCAL_AUTH_EMAIL);
if (!process.env.DATABASE_URL) {
console.log("No DATABASE_URL in .env.local");
return;
}
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
try {
const res1 = await pool.query("SELECT id FROM fs_users WHERE data->>'email' = $1 LIMIT 1", [process.env.NEXT_PUBLIC_DEV_LOCAL_AUTH_EMAIL]);
if (res1.rows.length === 0) {
console.log("FAIL: No user found for email", process.env.NEXT_PUBLIC_DEV_LOCAL_AUTH_EMAIL);
return;
}
const userId = res1.rows[0].id;
console.log("User ID:", userId);
const res2 = await pool.query("SELECT id, slug FROM fs_workspaces WHERE owner_id = $1 LIMIT 1", [userId]);
if (res2.rows.length === 0) {
console.log("FAIL: User owns no workspaces.");
return;
}
console.log("Workspace found:", res2.rows[0]);
console.log("SUCCESS: The bypass SHOULD work.");
} catch (e) {
console.log("ERROR:", e.message);
} finally {
pool.end();
}
}
test();

View File

@@ -0,0 +1,25 @@
require('dotenv').config({ path: '.env.local' });
const { Pool } = require('pg');
async function test() {
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
try {
const res1 = await pool.query("SELECT id FROM fs_users WHERE data->>'email' = $1 LIMIT 1", [process.env.NEXT_PUBLIC_DEV_LOCAL_AUTH_EMAIL]);
const userId = res1.rows[0].id;
// The table is vibn_workspaces, not fs_workspaces
const res2 = await pool.query("SELECT id, slug FROM vibn_workspaces WHERE owner_user_id = $1 LIMIT 1", [userId]);
if (res2.rows.length === 0) {
console.log("FAIL: User owns no workspaces.");
return;
}
console.log("Workspace found:", res2.rows[0]);
console.log("SUCCESS: The bypass SHOULD work.");
} catch (e) {
console.log("ERROR:", e.message);
} finally {
pool.end();
}
}
test();

View File

@@ -0,0 +1,13 @@
async function test() {
const res = await fetch('http://localhost:3000/api/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer vibn_sk_fake'
},
body: JSON.stringify({ action: 'workspace.describe' })
});
console.log("Status:", res.status);
console.log("Body:", await res.text());
}
test();

View File

@@ -0,0 +1,27 @@
// Manually trigger extraction to see what happens
const projectId = 'Sih2VRdZeBglpVNc4eFS';
async function triggerExtraction() {
try {
console.log('Triggering extraction for project:', projectId);
console.log('Watch the terminal running "npm run dev" for logs...\n');
const response = await fetch(`http://localhost:3000/api/projects/${projectId}/run-extraction`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer fake-token-for-local-dev' // Will fail auth but trigger logging
}
});
const text = await response.text();
console.log('Response status:', response.status);
console.log('Response body:', text);
} catch (error) {
console.error('Error:', error.message);
}
}
triggerExtraction();

View File

@@ -0,0 +1,89 @@
#!/bin/bash
# Simple Handoff Verification - Uses API endpoint to check handoff state
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
PROJECT_ID="lyOZxelSkjAB6XisIzup"
BASE_URL="http://localhost:3000"
TOKEN="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4MDI5MzRmZTBlZWM0NmE1ZWQwMDA2ZDE0YTFiYWIwMWUzNDUwODMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiTWFyayBIZW5kZXJzb24iLCJwaWN0dXJlIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzUzOTU0MjEzP3Y9NCIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9nZW4tbGFuZy1jbGllbnQtMDk4MDA3OTQxMCIsImF1ZCI6Imdlbi1sYW5nLWNsaWVudC0wOTgwMDc5NDEwIiwiYXV0aF90aW1lIjoxNzYzMzI1MDEyLCJ1c2VyX2lkIjoiMmhDdmdXQzJaV2RJMGVlTm5SQVM3SWVKcmg1MiIsInN1YiI6IjJoQ3ZnV0MyWldkSTBlZU5uUkFTN0llSnJoNTIiLCJpYXQiOjE3NjM0MjI1NDUsImV4cCI6MTc2MzQyNjE0NSwiZW1haWwiOiJtYXJrQGdldGFjcXVpcmVkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnaXRodWIuY29tIjpbIjUzOTU0MjEzIl0sImVtYWlsIjpbIm1hcmtAZ2V0YWNxdWlyZWQuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiZ2l0aHViLmNvbSJ9fQ.TpMOORDnPUKkbLlg-KtYBmbarEjAijJ3W4vN8tWT6OslOfwaeDJAtPXIahyQk38UvKY4ZGognQG6t-laSATB8yIC8IdkYbD699axfPSGQqC8Lbux1P6YrFKOPLGDD2XemBtJ-Gb5Ql-nK_DbXKAmygLxIwz019XpLJEucGkBPAN_Rj2xC7125DVexkDSIb6ZnbLiDgCpR_IkImyQb08tqlOoBiHVUa-4VGDhraoBPACJfQXwPToJ1W3nhBiVtMvSq7s_Ekd8Otn8AB_1teu5lxC-rhLdgJuNrmlxO-H6xIMBFZ72bwq7wrvdWd_EijqFQCU99oEhphTNoISoJ3wK-g"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Collector Handoff Verification${NC}"
echo -e "${BLUE}========================================${NC}\n"
# Send a test message that should trigger handoff update
echo -e "${YELLOW}[Step 1]${NC} Sending message to check handoff state..."
CHAT_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/ai/chat" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectId\": \"${PROJECT_ID}\",
\"message\": \"What do you know about my project so far?\"
}")
AI_REPLY=$(echo "$CHAT_RESPONSE" | jq -r '.reply // empty')
if [ -z "$AI_REPLY" ]; then
echo -e "${RED}✗ Failed to get AI response${NC}"
echo "Response: $CHAT_RESPONSE"
exit 1
fi
echo -e "${GREEN}✓ AI responded${NC}"
echo -e "\nAI Reply:\n${AI_REPLY}\n"
# Check the conversation for handoff indicators
echo -e "${YELLOW}[Step 2]${NC} Checking conversation history..."
HISTORY=$(curl -s -X GET "${BASE_URL}/api/ai/conversation?projectId=${PROJECT_ID}" \
-H "Authorization: ${TOKEN}")
MESSAGE_COUNT=$(echo "$HISTORY" | jq '.messages | length')
echo -e "${GREEN}✓ Found ${MESSAGE_COUNT} messages in history${NC}\n"
# Look for handoff indicators in AI responses
echo -e "${YELLOW}[Step 3]${NC} Analyzing AI responses for handoff tracking...\n"
HAS_DOC_MENTION=$(echo "$AI_REPLY" | grep -i "document\|uploaded" && echo "yes" || echo "no")
HAS_GITHUB_MENTION=$(echo "$AI_REPLY" | grep -i "github\|repo" && echo "yes" || echo "no")
HAS_EXTENSION_MENTION=$(echo "$AI_REPLY" | grep -i "extension\|browser" && echo "yes" || echo "no")
if [ "$HAS_DOC_MENTION" = "yes" ]; then
echo -e "${GREEN}✓ AI is tracking documents${NC}"
else
echo -e "${YELLOW}⚠ AI doesn't mention documents in response${NC}"
fi
if [ "$HAS_GITHUB_MENTION" = "yes" ]; then
echo -e "${GREEN}✓ AI is tracking GitHub connection${NC}"
else
echo -e "${YELLOW}⚠ AI doesn't mention GitHub in response${NC}"
fi
if [ "$HAS_EXTENSION_MENTION" = "yes" ]; then
echo -e "${GREEN}✓ AI is tracking browser extension${NC}"
else
echo -e "${YELLOW}⚠ AI doesn't mention extension in response${NC}"
fi
echo -e "\n${BLUE}========================================${NC}"
echo -e "${BLUE}Summary${NC}"
echo -e "${BLUE}========================================${NC}\n"
echo -e "Project ID: ${PROJECT_ID}"
echo -e "Total Messages: ${MESSAGE_COUNT}"
echo -e "\nTo manually verify handoff data in Firebase Console:"
echo -e " 1. Go to Firestore"
echo -e " 2. Open projects/${PROJECT_ID}"
echo -e " 3. Check phaseData.phaseHandoffs.collector"
echo -e ""
echo -e "Or view in UI:"
echo -e " ${BASE_URL}/default/project/${PROJECT_ID}/v_ai_chat"
echo -e " (Look for the checklist in the left sidebar)\n"

View File

@@ -0,0 +1,111 @@
#!/bin/bash
# Verify Collector Handoff Data
# This script checks if the collector handoff contract is properly persisted
set -e
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
PROJECT_ID="lyOZxelSkjAB6XisIzup"
TOKEN="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4MDI5MzRmZTBlZWM0NmE1ZWQwMDA2ZDE0YTFiYWIwMWUzNDUwODMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiTWFyayBIZW5kZXJzb24iLCJwaWN0dXJlIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzUzOTU0MjEzP3Y9NCIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9nZW4tbGFuZy1jbGllbnQtMDk4MDA3OTQxMCIsImF1ZCI6Imdlbi1sYW5nLWNsaWVudC0wOTgwMDc5NDEwIiwiYXV0aF90aW1lIjoxNzYzMzI1MDEyLCJ1c2VyX2lkIjoiMmhDdmdXQzJaV2RJMGVlTm5SQVM3SWVKcmg1MiIsInN1YiI6IjJoQ3ZnV0MyWldkSTBlZU5uUkFTN0llSnJoNTIiLCJpYXQiOjE3NjM0MjI1NDUsImV4cCI6MTc2MzQyNjE0NSwiZW1haWwiOiJtYXJrQGdldGFjcXVpcmVkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnaXRodWIuY29tIjpbIjUzOTU0MjEzIl0sImVtYWlsIjpbIm1hcmtAZ2V0YWNxdWlyZWQuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiZ2l0aHViLmNvbSJ9fQ.TpMOORDnPUKkbLlg-KtYBmbarEjAijJ3W4vN8tWT6OslOfwaeDJAtPXIahyQk38UvKY4ZGognQG6t-laSATB8yIC8IdkYbD699axfPSGQqC8Lbux1P6YrFKOPLGDD2XemBtJ-Gb5Ql-nK_DbXKAmygLxIwz019XpLJEucGkBPAN_Rj2xC7125DVexkDSIb6ZnbLiDgCpR_IkImyQb08tqlOoBiHVUa-4VGDhraoBPACJfQXwPToJ1W3nhBiVtMvSq7s_Ekd8Otn8AB_1teu5lxC-rhLdgJuNrmlxO-H6xIMBFZ72bwq7wrvdWd_EijqFQCU99oEhphTNoISoJ3wK-g"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Verifying Collector Handoff Data${NC}"
echo -e "${BLUE}========================================${NC}\n"
# Create a temp Node.js script to check Firestore directly
cat > /tmp/check-handoff.js << 'EOFJS'
const admin = require('firebase-admin');
// Initialize Firebase Admin
const serviceAccount = require(process.env.HOME + '/ai-proxy/vibn-frontend/gen-lang-client-0980079410-firebase-adminsdk-fbsvc-c0e0cffc47.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: 'gen-lang-client-0980079410.firebasestorage.app'
});
const db = admin.firestore();
async function checkHandoff() {
const projectId = process.argv[2];
console.log(`\nChecking project: ${projectId}\n`);
// 1. Check project document for phaseData
const projectDoc = await db.collection('projects').doc(projectId).get();
if (!projectDoc.exists) {
console.log('❌ Project not found');
process.exit(1);
}
const projectData = projectDoc.data();
console.log('📋 Project Phase Info:');
console.log(` Current Phase: ${projectData.currentPhase || 'not set'}`);
console.log(` Phase Status: ${projectData.phaseStatus || 'not set'}`);
// 2. Check for collector handoff data
if (projectData.phaseData && projectData.phaseData.phaseHandoffs && projectData.phaseData.phaseHandoffs.collector) {
const handoff = projectData.phaseData.phaseHandoffs.collector;
console.log('\n✅ Collector Handoff Data Found:');
console.log(JSON.stringify(handoff, null, 2));
// Verify the handoff contract
console.log('\n📊 Handoff Contract Status:');
console.log(` ✓ Has Documents: ${handoff.confirmed?.hasDocuments || false}`);
console.log(` ✓ Document Count: ${handoff.confirmed?.documentCount || 0}`);
console.log(` ✓ GitHub Connected: ${handoff.confirmed?.githubConnected || false}`);
console.log(` ✓ Extension Linked: ${handoff.confirmed?.extensionLinked || false}`);
console.log(` ✓ Ready for Extraction: ${handoff.readyForNextPhase || false}`);
if (handoff.readyForNextPhase) {
console.log('\n🎉 Collector phase is complete and ready for extraction!');
} else {
console.log('\n⏳ Collector phase is still in progress');
}
} else {
console.log('\n⚠ No collector handoff data found');
console.log(' This might be normal if the AI hasn\'t returned structured handoff data yet');
}
// 3. Check knowledge items
const knowledgeSnapshot = await db.collection('knowledge_items')
.where('projectId', '==', projectId)
.get();
console.log(`\n📚 Knowledge Items: ${knowledgeSnapshot.size} found`);
if (knowledgeSnapshot.size > 0) {
knowledgeSnapshot.forEach(doc => {
const data = doc.data();
console.log(` - ${data.title || 'Untitled'} (${data.sourceType})`);
});
}
process.exit(0);
}
checkHandoff().catch(err => {
console.error('Error:', err);
process.exit(1);
});
EOFJS
echo -e "${YELLOW}Checking Firestore for handoff data...${NC}\n"
# Run the Node.js script
cd /Users/markhenderson/ai-proxy/vibn-frontend && node /tmp/check-handoff.js "$PROJECT_ID"
# Clean up
rm -f /tmp/check-handoff.js
echo -e "\n${BLUE}========================================${NC}"