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.
Files changed (132) hide show
  1. package/demo/.claude/.truth_last_prompt +1 -0
  2. package/demo/.claude/truth_status +1 -0
  3. package/demo/css/style.css +840 -0
  4. package/demo/index.html +78 -0
  5. package/demo/js/chat.js +535 -0
  6. package/demo/js/code-panel.js +206 -0
  7. package/demo/js/preview.js +456 -0
  8. package/demo/js/sse-client.js +600 -0
  9. package/demo/js/state.js +172 -0
  10. package/demo/js/timeline.js +80 -0
  11. package/dist/api/server.d.ts +3 -0
  12. package/dist/api/server.js +127 -20
  13. package/dist/api/server.js.map +1 -1
  14. package/dist/cli.js +11 -0
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/assess.d.ts +2 -0
  17. package/dist/commands/assess.js +132 -164
  18. package/dist/commands/assess.js.map +1 -1
  19. package/dist/commands/demo.d.ts +5 -0
  20. package/dist/commands/demo.js +357 -0
  21. package/dist/commands/demo.js.map +1 -0
  22. package/dist/lib/__tests__/arithmetic-quick-test.d.ts +6 -0
  23. package/dist/lib/__tests__/arithmetic-quick-test.js +197 -0
  24. package/dist/lib/__tests__/arithmetic-quick-test.js.map +1 -0
  25. package/dist/lib/__tests__/arithmetic-real-llm-test.d.ts +13 -0
  26. package/dist/lib/__tests__/arithmetic-real-llm-test.js +284 -0
  27. package/dist/lib/__tests__/arithmetic-real-llm-test.js.map +1 -0
  28. package/dist/lib/__tests__/arithmetic-value-demo.d.ts +10 -0
  29. package/dist/lib/__tests__/arithmetic-value-demo.js +193 -0
  30. package/dist/lib/__tests__/arithmetic-value-demo.js.map +1 -0
  31. package/dist/lib/__tests__/flow-to-claims.test.d.ts +1 -0
  32. package/dist/lib/__tests__/flow-to-claims.test.js +91 -0
  33. package/dist/lib/__tests__/flow-to-claims.test.js.map +1 -0
  34. package/dist/lib/__tests__/formal-verifier-api-misuse.test.d.ts +9 -0
  35. package/dist/lib/__tests__/formal-verifier-api-misuse.test.js +391 -0
  36. package/dist/lib/__tests__/formal-verifier-api-misuse.test.js.map +1 -0
  37. package/dist/lib/__tests__/formal-verifier-arithmetic.test.d.ts +7 -0
  38. package/dist/lib/__tests__/formal-verifier-arithmetic.test.js +318 -0
  39. package/dist/lib/__tests__/formal-verifier-arithmetic.test.js.map +1 -0
  40. package/dist/lib/__tests__/intent-extractor.test.d.ts +1 -0
  41. package/dist/lib/__tests__/intent-extractor.test.js +97 -0
  42. package/dist/lib/__tests__/intent-extractor.test.js.map +1 -0
  43. package/dist/lib/__tests__/intent-reviewer.test.d.ts +1 -0
  44. package/dist/lib/__tests__/intent-reviewer.test.js +55 -0
  45. package/dist/lib/__tests__/intent-reviewer.test.js.map +1 -0
  46. package/dist/lib/__tests__/mr-gsm8k-benchmark.d.ts +11 -0
  47. package/dist/lib/__tests__/mr-gsm8k-benchmark.js +224 -0
  48. package/dist/lib/__tests__/mr-gsm8k-benchmark.js.map +1 -0
  49. package/dist/lib/anthropic.js +25 -33
  50. package/dist/lib/anthropic.js.map +1 -1
  51. package/dist/lib/assessment-reporter.js +9 -13
  52. package/dist/lib/assessment-reporter.js.map +1 -1
  53. package/dist/lib/claim-extractor.js +10 -19
  54. package/dist/lib/claim-extractor.js.map +1 -1
  55. package/dist/lib/code-verifier.js +16 -36
  56. package/dist/lib/code-verifier.js.map +1 -1
  57. package/dist/lib/constraint-engine.js +10 -19
  58. package/dist/lib/constraint-engine.js.map +1 -1
  59. package/dist/lib/formal-verifier.d.ts +1 -1
  60. package/dist/lib/formal-verifier.js +454 -0
  61. package/dist/lib/formal-verifier.js.map +1 -1
  62. package/dist/lib/guided-generator.js +19 -37
  63. package/dist/lib/guided-generator.js.map +1 -1
  64. package/dist/lib/intent-extractor.d.ts +47 -0
  65. package/dist/lib/intent-extractor.js +427 -0
  66. package/dist/lib/intent-extractor.js.map +1 -0
  67. package/dist/lib/intent-reviewer.d.ts +14 -0
  68. package/dist/lib/intent-reviewer.js +148 -0
  69. package/dist/lib/intent-reviewer.js.map +1 -0
  70. package/dist/lib/intent-types.d.ts +89 -0
  71. package/dist/lib/intent-types.js +5 -0
  72. package/dist/lib/intent-types.js.map +1 -0
  73. package/dist/lib/inventory-extractor.js +9 -22
  74. package/dist/lib/inventory-extractor.js.map +1 -1
  75. package/dist/lib/llm-provider.d.ts +23 -0
  76. package/dist/lib/llm-provider.js +130 -0
  77. package/dist/lib/llm-provider.js.map +1 -0
  78. package/dist/lib/remediator.js +20 -28
  79. package/dist/lib/remediator.js.map +1 -1
  80. package/dist/lib/requirements-generator.js +14 -19
  81. package/dist/lib/requirements-generator.js.map +1 -1
  82. package/dist/lib/spec-synthesizer.js +10 -19
  83. package/dist/lib/spec-synthesizer.js.map +1 -1
  84. package/dist/runtime/agents/planner-agent.d.ts +5 -2
  85. package/dist/runtime/agents/planner-agent.js +232 -1
  86. package/dist/runtime/agents/planner-agent.js.map +1 -1
  87. package/dist/runtime/app-create-orchestrator.d.ts +9 -1
  88. package/dist/runtime/app-create-orchestrator.js +265 -87
  89. package/dist/runtime/app-create-orchestrator.js.map +1 -1
  90. package/dist/runtime/check-catalog.js +5 -3
  91. package/dist/runtime/check-catalog.js.map +1 -1
  92. package/dist/runtime/check-definitions.d.ts +10 -0
  93. package/dist/runtime/check-definitions.js +52 -2
  94. package/dist/runtime/check-definitions.js.map +1 -1
  95. package/dist/runtime/composition-verifier.js +8 -12
  96. package/dist/runtime/composition-verifier.js.map +1 -1
  97. package/dist/runtime/gap-detector.js +8 -10
  98. package/dist/runtime/gap-detector.js.map +1 -1
  99. package/dist/runtime/input-validator.d.ts +7 -0
  100. package/dist/runtime/input-validator.js +162 -0
  101. package/dist/runtime/input-validator.js.map +1 -0
  102. package/dist/runtime/model-router.d.ts +10 -0
  103. package/dist/runtime/model-router.js +42 -0
  104. package/dist/runtime/model-router.js.map +1 -0
  105. package/dist/runtime/pattern-extractor.js +8 -10
  106. package/dist/runtime/pattern-extractor.js.map +1 -1
  107. package/dist/runtime/planner.js +11 -16
  108. package/dist/runtime/planner.js.map +1 -1
  109. package/dist/runtime/prompt-guard.d.ts +2 -0
  110. package/dist/runtime/prompt-guard.js +180 -0
  111. package/dist/runtime/prompt-guard.js.map +1 -0
  112. package/dist/runtime/prompt-safety-analyzer.js +8 -13
  113. package/dist/runtime/prompt-safety-analyzer.js.map +1 -1
  114. package/dist/runtime/reasoner.js +19 -33
  115. package/dist/runtime/reasoner.js.map +1 -1
  116. package/dist/runtime/rule-meta-verifier.js +9 -11
  117. package/dist/runtime/rule-meta-verifier.js.map +1 -1
  118. package/dist/runtime/safe-executor.d.ts +23 -0
  119. package/dist/runtime/safe-executor.js +151 -0
  120. package/dist/runtime/safe-executor.js.map +1 -0
  121. package/dist/runtime/specialized-agent.js +10 -14
  122. package/dist/runtime/specialized-agent.js.map +1 -1
  123. package/dist/runtime/strategy-library.js +8 -10
  124. package/dist/runtime/strategy-library.js.map +1 -1
  125. package/dist/runtime/supabase-experience-store.js.map +1 -1
  126. package/dist/runtime/supabase-provisioner.d.ts +35 -0
  127. package/dist/runtime/supabase-provisioner.js +192 -0
  128. package/dist/runtime/supabase-provisioner.js.map +1 -0
  129. package/dist/runtime/types.d.ts +116 -0
  130. package/dist/sdk/forward-verify.js +16 -33
  131. package/dist/sdk/forward-verify.js.map +1 -1
  132. 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 { execSync } from 'node:child_process';
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: plan questioning (refine requirements BEFORE planning)
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.options.models?.planner });
81
- const maxRounds = 3;
82
- let currentConfidence = 0;
83
- const allAnswers = [];
84
- for (let round = 1; round <= maxRounds; round++) {
85
- // Generate questions from the user prompt (no plan yet)
86
- let questions = [];
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
- questions = await planner.generateQuestions(this.description.description);
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: 'plan_questioning',
93
- round,
156
+ phase: 'chat',
157
+ turn,
94
158
  error: err instanceof Error ? err.message : String(err),
95
- reason: 'question_generation_failed',
159
+ reason: 'converse_failed',
96
160
  });
97
- break; // Skip questioning, proceed to planning
98
- }
99
- if (questions.length === 0) {
100
- this.audit('task_status_change', {
101
- phase: 'plan_questioning',
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
- break;
167
+ continue;
107
168
  }
108
- // Emit questions to frontend
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: 'plan_questioning',
111
- questions,
112
- round,
113
- confidence: currentConfidence,
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: 'plan_questioning',
117
- round,
118
- questionsGenerated: questions.length,
119
- });
120
- // Wait for answers from frontend
121
- const answers = await new Promise((resolve) => {
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
- allAnswers.push(...answers);
125
- // Update confidence based on answers received
126
- this.emitPhase({ phase: 'plan_refining', round });
127
- currentConfidence = Math.min(0.5 + (allAnswers.length * 0.15), 1.0);
128
- this.audit('task_status_change', {
129
- phase: 'plan_refining',
130
- round,
131
- confidence: currentConfidence,
132
- answersReceived: answers.length,
133
- });
134
- if (currentConfidence >= 0.95) {
135
- break;
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 string from all answers
139
- if (allAnswers.length > 0) {
140
- refinedRequirements = '\n\nREFINED REQUIREMENTS (from user selections):\n' +
141
- allAnswers.map(a => `- ${a.questionId}: ${a.selectedOptions.join(', ')}${a.customText ? ` (${a.customText})` : ''}`).join('\n');
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 and generate env file
429
+ // Post-build: reconcile dependencies, provision Supabase if needed, generate env file
323
430
  await this.reconcileDependencies(projectPath);
324
- await this.generateEnvFile(projectPath, plan);
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.options.models?.planner });
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.options.models?.code });
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.options.models?.code });
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
- * Only creates the file if it doesn't already exist.
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
- // Don't overwrite if scaffolding already created one
1027
- if (await this.fileExists(envPath))
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
- '# Replace placeholder values with real credentials before running.',
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
- lines.push(`${varName}=${this.getEnvPlaceholder(varName)}`);
1039
- }
1040
- await writeFile(envPath, lines.join('\n') + '\n', 'utf-8');
1041
- this.audit('env_file_generated', { path: '.env.local', vars: envVars.length });
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
- execSync('npx tsc --noEmit --pretty false', {
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
- return [{
1548
- type: 'typescript_compiles',
1549
- description: 'TypeScript compiles without errors',
1550
- verdict: 'PASS',
1551
- evidence: 'npx tsc --noEmit exited with code 0',
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 (err) {
1555
- const stderr = err?.stderr?.toString() ?? '';
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
  /**