tryassay 0.21.2 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demo/.claude/.truth_last_prompt +1 -0
- package/demo/.claude/truth_status +1 -0
- package/demo/css/style.css +840 -0
- package/demo/index.html +78 -0
- package/demo/js/chat.js +535 -0
- package/demo/js/code-panel.js +206 -0
- package/demo/js/preview.js +456 -0
- package/demo/js/sse-client.js +600 -0
- package/demo/js/state.js +172 -0
- package/demo/js/timeline.js +80 -0
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.js +127 -20
- package/dist/api/server.js.map +1 -1
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/assess.d.ts +2 -0
- package/dist/commands/assess.js +132 -164
- package/dist/commands/assess.js.map +1 -1
- package/dist/commands/demo.d.ts +5 -0
- package/dist/commands/demo.js +357 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/lib/__tests__/arithmetic-quick-test.d.ts +6 -0
- package/dist/lib/__tests__/arithmetic-quick-test.js +197 -0
- package/dist/lib/__tests__/arithmetic-quick-test.js.map +1 -0
- package/dist/lib/__tests__/arithmetic-real-llm-test.d.ts +13 -0
- package/dist/lib/__tests__/arithmetic-real-llm-test.js +284 -0
- package/dist/lib/__tests__/arithmetic-real-llm-test.js.map +1 -0
- package/dist/lib/__tests__/arithmetic-value-demo.d.ts +10 -0
- package/dist/lib/__tests__/arithmetic-value-demo.js +193 -0
- package/dist/lib/__tests__/arithmetic-value-demo.js.map +1 -0
- package/dist/lib/__tests__/flow-to-claims.test.d.ts +1 -0
- package/dist/lib/__tests__/flow-to-claims.test.js +91 -0
- package/dist/lib/__tests__/flow-to-claims.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.d.ts +9 -0
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.js +391 -0
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-arithmetic.test.d.ts +7 -0
- package/dist/lib/__tests__/formal-verifier-arithmetic.test.js +318 -0
- package/dist/lib/__tests__/formal-verifier-arithmetic.test.js.map +1 -0
- package/dist/lib/__tests__/intent-extractor.test.d.ts +1 -0
- package/dist/lib/__tests__/intent-extractor.test.js +97 -0
- package/dist/lib/__tests__/intent-extractor.test.js.map +1 -0
- package/dist/lib/__tests__/intent-reviewer.test.d.ts +1 -0
- package/dist/lib/__tests__/intent-reviewer.test.js +55 -0
- package/dist/lib/__tests__/intent-reviewer.test.js.map +1 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.d.ts +11 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.js +224 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.js.map +1 -0
- package/dist/lib/anthropic.js +25 -33
- package/dist/lib/anthropic.js.map +1 -1
- package/dist/lib/assessment-reporter.js +9 -13
- package/dist/lib/assessment-reporter.js.map +1 -1
- package/dist/lib/claim-extractor.js +10 -19
- package/dist/lib/claim-extractor.js.map +1 -1
- package/dist/lib/code-verifier.js +16 -36
- package/dist/lib/code-verifier.js.map +1 -1
- package/dist/lib/constraint-engine.js +10 -19
- package/dist/lib/constraint-engine.js.map +1 -1
- package/dist/lib/formal-verifier.d.ts +1 -1
- package/dist/lib/formal-verifier.js +454 -0
- package/dist/lib/formal-verifier.js.map +1 -1
- package/dist/lib/guided-generator.js +19 -37
- package/dist/lib/guided-generator.js.map +1 -1
- package/dist/lib/intent-extractor.d.ts +47 -0
- package/dist/lib/intent-extractor.js +427 -0
- package/dist/lib/intent-extractor.js.map +1 -0
- package/dist/lib/intent-reviewer.d.ts +14 -0
- package/dist/lib/intent-reviewer.js +148 -0
- package/dist/lib/intent-reviewer.js.map +1 -0
- package/dist/lib/intent-types.d.ts +89 -0
- package/dist/lib/intent-types.js +5 -0
- package/dist/lib/intent-types.js.map +1 -0
- package/dist/lib/inventory-extractor.js +9 -22
- package/dist/lib/inventory-extractor.js.map +1 -1
- package/dist/lib/llm-provider.d.ts +23 -0
- package/dist/lib/llm-provider.js +130 -0
- package/dist/lib/llm-provider.js.map +1 -0
- package/dist/lib/remediator.js +20 -28
- package/dist/lib/remediator.js.map +1 -1
- package/dist/lib/requirements-generator.js +14 -19
- package/dist/lib/requirements-generator.js.map +1 -1
- package/dist/lib/spec-synthesizer.js +10 -19
- package/dist/lib/spec-synthesizer.js.map +1 -1
- package/dist/runtime/agents/planner-agent.d.ts +5 -2
- package/dist/runtime/agents/planner-agent.js +232 -1
- package/dist/runtime/agents/planner-agent.js.map +1 -1
- package/dist/runtime/app-create-orchestrator.d.ts +9 -1
- package/dist/runtime/app-create-orchestrator.js +265 -87
- package/dist/runtime/app-create-orchestrator.js.map +1 -1
- package/dist/runtime/check-catalog.js +5 -3
- package/dist/runtime/check-catalog.js.map +1 -1
- package/dist/runtime/check-definitions.d.ts +10 -0
- package/dist/runtime/check-definitions.js +52 -2
- package/dist/runtime/check-definitions.js.map +1 -1
- package/dist/runtime/composition-verifier.js +8 -12
- package/dist/runtime/composition-verifier.js.map +1 -1
- package/dist/runtime/gap-detector.js +8 -10
- package/dist/runtime/gap-detector.js.map +1 -1
- package/dist/runtime/input-validator.d.ts +7 -0
- package/dist/runtime/input-validator.js +162 -0
- package/dist/runtime/input-validator.js.map +1 -0
- package/dist/runtime/model-router.d.ts +10 -0
- package/dist/runtime/model-router.js +42 -0
- package/dist/runtime/model-router.js.map +1 -0
- package/dist/runtime/pattern-extractor.js +8 -10
- package/dist/runtime/pattern-extractor.js.map +1 -1
- package/dist/runtime/planner.js +11 -16
- package/dist/runtime/planner.js.map +1 -1
- package/dist/runtime/prompt-guard.d.ts +2 -0
- package/dist/runtime/prompt-guard.js +180 -0
- package/dist/runtime/prompt-guard.js.map +1 -0
- package/dist/runtime/prompt-safety-analyzer.js +8 -13
- package/dist/runtime/prompt-safety-analyzer.js.map +1 -1
- package/dist/runtime/reasoner.js +19 -33
- package/dist/runtime/reasoner.js.map +1 -1
- package/dist/runtime/rule-meta-verifier.js +9 -11
- package/dist/runtime/rule-meta-verifier.js.map +1 -1
- package/dist/runtime/safe-executor.d.ts +23 -0
- package/dist/runtime/safe-executor.js +151 -0
- package/dist/runtime/safe-executor.js.map +1 -0
- package/dist/runtime/specialized-agent.js +10 -14
- package/dist/runtime/specialized-agent.js.map +1 -1
- package/dist/runtime/strategy-library.js +8 -10
- package/dist/runtime/strategy-library.js.map +1 -1
- package/dist/runtime/supabase-experience-store.js.map +1 -1
- package/dist/runtime/supabase-provisioner.d.ts +35 -0
- package/dist/runtime/supabase-provisioner.js +192 -0
- package/dist/runtime/supabase-provisioner.js.map +1 -0
- package/dist/runtime/types.d.ts +116 -0
- package/dist/sdk/forward-verify.js +16 -33
- package/dist/sdk/forward-verify.js.map +1 -1
- package/package.json +2 -1
|
@@ -7,7 +7,7 @@ import { EventEmitter } from 'node:events';
|
|
|
7
7
|
import { mkdir, writeFile, readFile, readdir, stat } from 'node:fs/promises';
|
|
8
8
|
import { join, dirname } from 'node:path';
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
-
import {
|
|
10
|
+
import { safeExecSync } from './safe-executor.js';
|
|
11
11
|
import { PlannerAgent } from './agents/planner-agent.js';
|
|
12
12
|
import { CodeAgent } from './agents/code-agent.js';
|
|
13
13
|
import { MessageBus } from './message-bus.js';
|
|
@@ -20,6 +20,8 @@ import { CheckStore } from './check-store.js';
|
|
|
20
20
|
import { FailureClassifier } from './failure-classifier.js';
|
|
21
21
|
import { PlanRefiner } from './plan-refiner.js';
|
|
22
22
|
import { FunctionalTester } from './functional-tester.js';
|
|
23
|
+
import { SupabaseProvisioner } from './supabase-provisioner.js';
|
|
24
|
+
import { ModelRouter } from './model-router.js';
|
|
23
25
|
// ── Default safety policy ───────────────────────────────────
|
|
24
26
|
const DEFAULT_SAFETY = {
|
|
25
27
|
formalOverridesConsensus: true,
|
|
@@ -41,10 +43,14 @@ export class AppCreateOrchestrator extends EventEmitter {
|
|
|
41
43
|
planSummaryCache = null;
|
|
42
44
|
approvalResolve = null;
|
|
43
45
|
answersResolve = null;
|
|
46
|
+
chatResolve = null;
|
|
47
|
+
conversationHistory = [];
|
|
48
|
+
modelRouter;
|
|
44
49
|
constructor(description, options) {
|
|
45
50
|
super();
|
|
46
51
|
this.description = description;
|
|
47
52
|
this.options = options;
|
|
53
|
+
this.modelRouter = new ModelRouter(options.models);
|
|
48
54
|
}
|
|
49
55
|
/** Get the current plan summary (available after planning phase). */
|
|
50
56
|
getPlanSummary() {
|
|
@@ -64,6 +70,13 @@ export class AppCreateOrchestrator extends EventEmitter {
|
|
|
64
70
|
this.answersResolve = null;
|
|
65
71
|
}
|
|
66
72
|
}
|
|
73
|
+
/** Submit a chat message. Resolves the waiting conversation loop. */
|
|
74
|
+
submitChatMessage(message, cardAnswers) {
|
|
75
|
+
if (this.chatResolve) {
|
|
76
|
+
this.chatResolve({ message, cardAnswers });
|
|
77
|
+
this.chatResolve = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
67
80
|
/** Run the full app creation pipeline. */
|
|
68
81
|
async run() {
|
|
69
82
|
this.startTime = Date.now();
|
|
@@ -73,72 +86,166 @@ export class AppCreateOrchestrator extends EventEmitter {
|
|
|
73
86
|
await mkdir(projectPath, { recursive: true });
|
|
74
87
|
await mkdir(join(projectPath, '.assay'), { recursive: true });
|
|
75
88
|
this.audit('task_status_change', { phase: 'initializing', projectPath });
|
|
76
|
-
// Phase:
|
|
89
|
+
// Phase: chat-based requirements gathering (replaces quiz-style rounds)
|
|
77
90
|
let refinedRequirements = '';
|
|
78
91
|
if (!this.options.skipPlanQuestions && !this.options.autoApprove) {
|
|
79
92
|
const messageBus = new MessageBus();
|
|
80
|
-
const planner = new PlannerAgent(messageBus, { model: this.
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
const planner = new PlannerAgent(messageBus, { model: this.modelRouter.selectModel('planning', 'planner') });
|
|
94
|
+
const maxTurns = 20; // Safety valve
|
|
95
|
+
this.conversationHistory = [];
|
|
96
|
+
// Initial turn — architect responds to the raw prompt
|
|
97
|
+
let response;
|
|
98
|
+
try {
|
|
99
|
+
response = await planner.converse(this.description.description, []);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
this.audit('task_status_change', {
|
|
103
|
+
phase: 'chat',
|
|
104
|
+
error: err instanceof Error ? err.message : String(err),
|
|
105
|
+
reason: 'initial_converse_failed',
|
|
106
|
+
});
|
|
107
|
+
// Fall through to planning with no refinement
|
|
108
|
+
response = { message: '', readiness: { confidence: 1, ready: true, gaps: [], summary: '' } };
|
|
109
|
+
}
|
|
110
|
+
// Add architect's first message to history
|
|
111
|
+
const firstArchitectMsg = {
|
|
112
|
+
role: 'architect',
|
|
113
|
+
content: response.message,
|
|
114
|
+
cards: response.cards,
|
|
115
|
+
};
|
|
116
|
+
this.conversationHistory.push(firstArchitectMsg);
|
|
117
|
+
// Emit first chat message
|
|
118
|
+
this.emitPhase({
|
|
119
|
+
phase: 'chat_message',
|
|
120
|
+
message: response.message,
|
|
121
|
+
cards: response.cards,
|
|
122
|
+
readiness: response.readiness,
|
|
123
|
+
});
|
|
124
|
+
this.audit('task_status_change', {
|
|
125
|
+
phase: 'chat',
|
|
126
|
+
turn: 1,
|
|
127
|
+
ready: response.readiness.ready,
|
|
128
|
+
confidence: response.readiness.confidence,
|
|
129
|
+
gaps: response.readiness.gaps,
|
|
130
|
+
});
|
|
131
|
+
// Conversation loop
|
|
132
|
+
let turn = 1;
|
|
133
|
+
while (!response.readiness.ready && turn < maxTurns) {
|
|
134
|
+
// Wait for user message
|
|
135
|
+
const userInput = await new Promise((resolve) => {
|
|
136
|
+
this.chatResolve = resolve;
|
|
137
|
+
});
|
|
138
|
+
// Check for build signal
|
|
139
|
+
if (userInput.message === '__START_BUILD__') {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
turn++;
|
|
143
|
+
// Add user message to history
|
|
144
|
+
const userMsg = {
|
|
145
|
+
role: 'user',
|
|
146
|
+
content: userInput.message,
|
|
147
|
+
cardAnswers: userInput.cardAnswers,
|
|
148
|
+
};
|
|
149
|
+
this.conversationHistory.push(userMsg);
|
|
150
|
+
// Get architect's response
|
|
87
151
|
try {
|
|
88
|
-
|
|
152
|
+
response = await planner.converse(this.description.description, this.conversationHistory);
|
|
89
153
|
}
|
|
90
154
|
catch (err) {
|
|
91
155
|
this.audit('task_status_change', {
|
|
92
|
-
phase: '
|
|
93
|
-
|
|
156
|
+
phase: 'chat',
|
|
157
|
+
turn,
|
|
94
158
|
error: err instanceof Error ? err.message : String(err),
|
|
95
|
-
reason: '
|
|
159
|
+
reason: 'converse_failed',
|
|
96
160
|
});
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
round,
|
|
103
|
-
questionsGenerated: 0,
|
|
104
|
-
reason: 'no_questions',
|
|
161
|
+
// Show error in chat
|
|
162
|
+
this.emitPhase({
|
|
163
|
+
phase: 'chat_message',
|
|
164
|
+
message: 'Something went wrong. Try sending your message again.',
|
|
165
|
+
readiness: { confidence: 0, ready: false, gaps: ['Internal error'], summary: '' },
|
|
105
166
|
});
|
|
106
|
-
|
|
167
|
+
continue;
|
|
107
168
|
}
|
|
108
|
-
//
|
|
169
|
+
// Add architect's response to history
|
|
170
|
+
const architectMsg = {
|
|
171
|
+
role: 'architect',
|
|
172
|
+
content: response.message,
|
|
173
|
+
cards: response.cards,
|
|
174
|
+
};
|
|
175
|
+
this.conversationHistory.push(architectMsg);
|
|
176
|
+
// Emit chat message to frontend
|
|
109
177
|
this.emitPhase({
|
|
110
|
-
phase: '
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
178
|
+
phase: 'chat_message',
|
|
179
|
+
message: response.message,
|
|
180
|
+
cards: response.cards,
|
|
181
|
+
readiness: response.readiness,
|
|
114
182
|
});
|
|
115
183
|
this.audit('task_status_change', {
|
|
116
|
-
phase: '
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.answersResolve = resolve;
|
|
184
|
+
phase: 'chat',
|
|
185
|
+
turn,
|
|
186
|
+
ready: response.readiness.ready,
|
|
187
|
+
confidence: response.readiness.confidence,
|
|
188
|
+
gaps: response.readiness.gaps,
|
|
189
|
+
summary: response.readiness.summary,
|
|
123
190
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
191
|
+
}
|
|
192
|
+
// If ready, wait for "Start Building" click (user may keep chatting)
|
|
193
|
+
if (response.readiness.ready) {
|
|
194
|
+
let readyTurns = 0;
|
|
195
|
+
while (readyTurns < 10) {
|
|
196
|
+
const userInput = await new Promise((resolve) => {
|
|
197
|
+
this.chatResolve = resolve;
|
|
198
|
+
});
|
|
199
|
+
if (userInput.message === '__START_BUILD__') {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
readyTurns++;
|
|
203
|
+
// User is still chatting — continue conversation
|
|
204
|
+
const userMsg = {
|
|
205
|
+
role: 'user',
|
|
206
|
+
content: userInput.message,
|
|
207
|
+
cardAnswers: userInput.cardAnswers,
|
|
208
|
+
};
|
|
209
|
+
this.conversationHistory.push(userMsg);
|
|
210
|
+
try {
|
|
211
|
+
response = await planner.converse(this.description.description, this.conversationHistory);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const architectMsg = {
|
|
217
|
+
role: 'architect',
|
|
218
|
+
content: response.message,
|
|
219
|
+
cards: response.cards,
|
|
220
|
+
};
|
|
221
|
+
this.conversationHistory.push(architectMsg);
|
|
222
|
+
this.emitPhase({
|
|
223
|
+
phase: 'chat_message',
|
|
224
|
+
message: response.message,
|
|
225
|
+
cards: response.cards,
|
|
226
|
+
readiness: response.readiness,
|
|
227
|
+
});
|
|
228
|
+
// Nudge after many ready turns
|
|
229
|
+
if (readyTurns >= 5 && response.readiness.ready) {
|
|
230
|
+
this.emitPhase({
|
|
231
|
+
phase: 'chat_message',
|
|
232
|
+
message: "I've had enough context for a while now — click Start Building whenever you're ready, or keep refining.",
|
|
233
|
+
readiness: response.readiness,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
136
236
|
}
|
|
137
237
|
}
|
|
138
|
-
// Build refined requirements
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
238
|
+
// Build refined requirements from conversation history
|
|
239
|
+
const userMessages = this.conversationHistory.filter(m => m.role === 'user');
|
|
240
|
+
if (userMessages.length > 0) {
|
|
241
|
+
refinedRequirements = '\n\nREFINED REQUIREMENTS (from conversation):\n' +
|
|
242
|
+
userMessages.map(m => {
|
|
243
|
+
let line = `- User: ${m.content}`;
|
|
244
|
+
if (m.cardAnswers && m.cardAnswers.length > 0) {
|
|
245
|
+
line += ` [Selections: ${m.cardAnswers.map(a => `${a.questionId}=${a.selectedOptions.join(', ')}`).join('; ')}]`;
|
|
246
|
+
}
|
|
247
|
+
return line;
|
|
248
|
+
}).join('\n');
|
|
142
249
|
}
|
|
143
250
|
}
|
|
144
251
|
// Phase: planning (now with refined requirements)
|
|
@@ -319,9 +426,50 @@ export class AppCreateOrchestrator extends EventEmitter {
|
|
|
319
426
|
}
|
|
320
427
|
}
|
|
321
428
|
}
|
|
322
|
-
// Post-build: reconcile dependencies
|
|
429
|
+
// Post-build: reconcile dependencies, provision Supabase if needed, generate env file
|
|
323
430
|
await this.reconcileDependencies(projectPath);
|
|
324
|
-
|
|
431
|
+
let supabaseResult;
|
|
432
|
+
if (this.description.techStack.database === 'supabase') {
|
|
433
|
+
const orgId = this.options.supabaseOrgId || process.env.SUPABASE_ORG_ID || 'rxngiskxlcbwzpyuaihh';
|
|
434
|
+
const region = this.options.supabaseRegion || process.env.SUPABASE_REGION || 'us-east-1';
|
|
435
|
+
this.emitPhase({ phase: 'provisioning_supabase' });
|
|
436
|
+
const provisioner = new SupabaseProvisioner({
|
|
437
|
+
orgId,
|
|
438
|
+
region,
|
|
439
|
+
projectName: `assay-${this.slugify(this.description.name)}`,
|
|
440
|
+
});
|
|
441
|
+
supabaseResult = await provisioner.provision();
|
|
442
|
+
this.audit('task_status_change', {
|
|
443
|
+
phase: 'provisioning_supabase',
|
|
444
|
+
status: supabaseResult.status,
|
|
445
|
+
projectRef: supabaseResult.projectRef,
|
|
446
|
+
durationMs: supabaseResult.durationMs,
|
|
447
|
+
error: supabaseResult.error,
|
|
448
|
+
});
|
|
449
|
+
if (supabaseResult.status === 'success') {
|
|
450
|
+
// Push migrations if the scaffolding generated any
|
|
451
|
+
const migrationsDir = join(projectPath, 'supabase', 'migrations');
|
|
452
|
+
if (await this.fileExists(migrationsDir)) {
|
|
453
|
+
this.emitPhase({ phase: 'supabase_migrations' });
|
|
454
|
+
const migResult = provisioner.pushMigrations(projectPath, supabaseResult.projectRef);
|
|
455
|
+
this.audit('task_status_change', {
|
|
456
|
+
phase: 'supabase_migrations',
|
|
457
|
+
status: migResult.success ? 'completed' : 'failed',
|
|
458
|
+
error: migResult.error,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
// Save project ref for cleanup
|
|
462
|
+
const assayDir = join(projectPath, '.assay');
|
|
463
|
+
await mkdir(assayDir, { recursive: true });
|
|
464
|
+
await writeFile(join(assayDir, 'supabase-project.json'), JSON.stringify({
|
|
465
|
+
projectRef: supabaseResult.projectRef,
|
|
466
|
+
url: supabaseResult.url,
|
|
467
|
+
region,
|
|
468
|
+
createdAt: new Date().toISOString(),
|
|
469
|
+
}, null, 2), 'utf-8');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
await this.generateEnvFile(projectPath, plan, supabaseResult);
|
|
325
473
|
// Phase: build verification (install, build, start, health check with repair loop)
|
|
326
474
|
let buildVerification = null;
|
|
327
475
|
if (!this.options.skipBuildVerification) {
|
|
@@ -429,7 +577,7 @@ export class AppCreateOrchestrator extends EventEmitter {
|
|
|
429
577
|
// ── Planning Phase ─────────────────────────────────────────
|
|
430
578
|
async runPlanningPhase(projectPath) {
|
|
431
579
|
const messageBus = new MessageBus();
|
|
432
|
-
const planner = new PlannerAgent(messageBus, { model: this.
|
|
580
|
+
const planner = new PlannerAgent(messageBus, { model: this.modelRouter.selectModel('planning', 'planner') });
|
|
433
581
|
const maxRetries = this.options.maxRetries ?? 3;
|
|
434
582
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
435
583
|
const result = await planner.executeTask({
|
|
@@ -447,7 +595,10 @@ export class AppCreateOrchestrator extends EventEmitter {
|
|
|
447
595
|
...(this.description.constraints ?? []),
|
|
448
596
|
],
|
|
449
597
|
dependencies: [],
|
|
450
|
-
contextRefs:
|
|
598
|
+
contextRefs: this.options.documents?.map(doc => ({
|
|
599
|
+
summary: `Attached document: ${doc.name}`,
|
|
600
|
+
content: doc.content,
|
|
601
|
+
})) ?? [],
|
|
451
602
|
});
|
|
452
603
|
if (result.status === 'completed' && result.artifacts.length > 0) {
|
|
453
604
|
try {
|
|
@@ -545,7 +696,7 @@ Create the project foundation files. Do NOT implement features — only the base
|
|
|
545
696
|
Include a .env.local file with placeholder values for all required environment variables so the app can start without crashing.`;
|
|
546
697
|
// Direct mode: single CodeAgent call for scaffolding (bypasses full team)
|
|
547
698
|
const messageBus = new MessageBus();
|
|
548
|
-
const codeAgent = new CodeAgent(messageBus, { model: this.
|
|
699
|
+
const codeAgent = new CodeAgent(messageBus, { model: this.modelRouter.selectModel('scaffolding', 'code') });
|
|
549
700
|
try {
|
|
550
701
|
const result = await codeAgent.executeTask({
|
|
551
702
|
taskId: `scaffold-${randomUUID().slice(0, 8)}`,
|
|
@@ -772,7 +923,7 @@ Include a .env.local file with placeholder values for all required environment v
|
|
|
772
923
|
async buildFeatureDirect(projectPath, plan, feature, completedFeatures) {
|
|
773
924
|
const goal = this.buildFeatureGoal(plan, feature, completedFeatures);
|
|
774
925
|
const messageBus = new MessageBus();
|
|
775
|
-
const codeAgent = new CodeAgent(messageBus, { model: this.
|
|
926
|
+
const codeAgent = new CodeAgent(messageBus, { model: this.modelRouter.selectModel('code_generation', 'code') });
|
|
776
927
|
try {
|
|
777
928
|
const result = await codeAgent.executeTask({
|
|
778
929
|
taskId: `direct-${feature.id}-${randomUUID().slice(0, 8)}`,
|
|
@@ -1019,26 +1170,50 @@ ${importNote}`;
|
|
|
1019
1170
|
// ── Post-Build: Environment File Generation ─────────────
|
|
1020
1171
|
/**
|
|
1021
1172
|
* Generate .env.local from the architecture plan's deployConfig.envVars.
|
|
1022
|
-
*
|
|
1173
|
+
* When real Supabase credentials are available, overwrites any existing
|
|
1174
|
+
* placeholder file and emits code_generated so WebContainer receives it.
|
|
1023
1175
|
*/
|
|
1024
|
-
async generateEnvFile(projectPath, plan) {
|
|
1176
|
+
async generateEnvFile(projectPath, plan, supabaseResult) {
|
|
1025
1177
|
const envPath = join(projectPath, '.env.local');
|
|
1026
|
-
|
|
1027
|
-
if
|
|
1178
|
+
const hasRealCreds = supabaseResult?.status === 'success';
|
|
1179
|
+
// Don't overwrite if scaffolding already created one — unless we have real credentials
|
|
1180
|
+
if (!hasRealCreds && await this.fileExists(envPath))
|
|
1028
1181
|
return;
|
|
1029
1182
|
const envVars = plan.deployConfig?.envVars;
|
|
1030
1183
|
if (!envVars || envVars.length === 0)
|
|
1031
1184
|
return;
|
|
1032
1185
|
const lines = [
|
|
1033
1186
|
'# Generated by Assay Verified App Creator',
|
|
1034
|
-
|
|
1187
|
+
hasRealCreds
|
|
1188
|
+
? '# Real Supabase credentials — auto-provisioned by Assay'
|
|
1189
|
+
: '# Replace placeholder values with real credentials before running.',
|
|
1035
1190
|
'',
|
|
1036
1191
|
];
|
|
1037
1192
|
for (const varName of envVars) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1193
|
+
const realValue = hasRealCreds ? this.getSupabaseEnvValue(varName, supabaseResult) : undefined;
|
|
1194
|
+
lines.push(`${varName}=${realValue ?? this.getEnvPlaceholder(varName)}`);
|
|
1195
|
+
}
|
|
1196
|
+
const content = lines.join('\n') + '\n';
|
|
1197
|
+
await writeFile(envPath, content, 'utf-8');
|
|
1198
|
+
this.audit('env_file_generated', { path: '.env.local', vars: envVars.length, hasRealCreds });
|
|
1199
|
+
// Emit code_generated so .env.local reaches WebContainer via SSE
|
|
1200
|
+
this.audit('code_generated', {
|
|
1201
|
+
path: '.env.local',
|
|
1202
|
+
language: 'env',
|
|
1203
|
+
content,
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
/** Map env var names to real Supabase provisioned values. */
|
|
1207
|
+
getSupabaseEnvValue(varName, result) {
|
|
1208
|
+
const name = varName.toUpperCase();
|
|
1209
|
+
if (name.includes('SUPABASE_URL') || name === 'NEXT_PUBLIC_SUPABASE_URL')
|
|
1210
|
+
return result.url;
|
|
1211
|
+
if (name.includes('SUPABASE') && name.includes('ANON') && name.includes('KEY'))
|
|
1212
|
+
return result.anonKey;
|
|
1213
|
+
if (name.includes('SUPABASE') && name.includes('SERVICE') && name.includes('KEY'))
|
|
1214
|
+
return result.serviceRoleKey;
|
|
1215
|
+
// Don't return values for non-Supabase env vars
|
|
1216
|
+
return undefined;
|
|
1042
1217
|
}
|
|
1043
1218
|
/** Return a sensible placeholder value for known env var patterns. */
|
|
1044
1219
|
getEnvPlaceholder(varName) {
|
|
@@ -1132,7 +1307,8 @@ ${importNote}`;
|
|
|
1132
1307
|
return name
|
|
1133
1308
|
.toLowerCase()
|
|
1134
1309
|
.replace(/[^a-z0-9]+/g, '-')
|
|
1135
|
-
.replace(/^-+|-+$/g, '')
|
|
1310
|
+
.replace(/^-+|-+$/g, '')
|
|
1311
|
+
.slice(0, 40);
|
|
1136
1312
|
}
|
|
1137
1313
|
/** Get framework-specific directory convention instruction for agent prompts. */
|
|
1138
1314
|
getDirectoryConvention() {
|
|
@@ -1539,31 +1715,33 @@ The renderer process communicates with main via contextBridge APIs exposed in sr
|
|
|
1539
1715
|
return []; // No tsconfig — skip
|
|
1540
1716
|
}
|
|
1541
1717
|
try {
|
|
1542
|
-
|
|
1718
|
+
const tscResult = safeExecSync('npx', ['tsc', '--noEmit', '--pretty', 'false'], {
|
|
1543
1719
|
cwd: projectPath,
|
|
1544
1720
|
timeout: 60_000,
|
|
1545
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1546
1721
|
});
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1722
|
+
if (tscResult.exitCode === 0) {
|
|
1723
|
+
return [{
|
|
1724
|
+
type: 'typescript_compiles',
|
|
1725
|
+
description: 'TypeScript compiles without errors',
|
|
1726
|
+
verdict: 'PASS',
|
|
1727
|
+
evidence: 'npx tsc --noEmit exited with code 0',
|
|
1728
|
+
}];
|
|
1729
|
+
}
|
|
1730
|
+
{
|
|
1731
|
+
const output = tscResult.stderr || tscResult.stdout;
|
|
1732
|
+
const errorLines = output.split('\n').filter(l => l.includes('error TS'));
|
|
1733
|
+
return [{
|
|
1734
|
+
type: 'typescript_compiles',
|
|
1735
|
+
description: 'TypeScript compiles without errors',
|
|
1736
|
+
verdict: 'FAIL',
|
|
1737
|
+
evidence: errorLines.length > 0
|
|
1738
|
+
? `tsc --noEmit failed with ${errorLines.length} error(s): ${errorLines.slice(0, 5).join('; ')}${errorLines.length > 5 ? ` ... and ${errorLines.length - 5} more` : ''}`
|
|
1739
|
+
: `tsc --noEmit failed: ${output.slice(0, 500)}`,
|
|
1740
|
+
}];
|
|
1741
|
+
}
|
|
1553
1742
|
}
|
|
1554
|
-
catch
|
|
1555
|
-
|
|
1556
|
-
const stdout = err?.stdout?.toString() ?? '';
|
|
1557
|
-
const output = stderr || stdout;
|
|
1558
|
-
const errorLines = output.split('\n').filter(l => l.includes('error TS'));
|
|
1559
|
-
return [{
|
|
1560
|
-
type: 'typescript_compiles',
|
|
1561
|
-
description: 'TypeScript compiles without errors',
|
|
1562
|
-
verdict: 'FAIL',
|
|
1563
|
-
evidence: errorLines.length > 0
|
|
1564
|
-
? `tsc --noEmit failed with ${errorLines.length} error(s): ${errorLines.slice(0, 5).join('; ')}${errorLines.length > 5 ? ` ... and ${errorLines.length - 5} more` : ''}`
|
|
1565
|
-
: `tsc --noEmit failed: ${output.slice(0, 500)}`,
|
|
1566
|
-
}];
|
|
1743
|
+
catch {
|
|
1744
|
+
return []; // Unexpected error — skip
|
|
1567
1745
|
}
|
|
1568
1746
|
}
|
|
1569
1747
|
/**
|