@vercel/dream 0.3.3 → 0.4.0

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 (2) hide show
  1. package/dist/dream.js +110 -9
  2. package/package.json +1 -1
package/dist/dream.js CHANGED
@@ -44,10 +44,12 @@ Environment variables required by the project are injected into the runtime envi
44
44
 
45
45
  ## Implementation Artifact Policy
46
46
 
47
- 1. The deliverable is project source code that satisfies specs.
48
- 2. For Vercel deployment, final build artifacts must conform to Build Output API in \`.vercel/output/\`, including a generated \`.vercel/output/config.json\`.
49
- 3. Build Output API artifacts must be generated by the framework/app build pipeline (for example \`vercel build\` or framework-integrated build), not hand-authored as a shortcut.
50
- 4. If required prerequisites are missing (for example env vars, credentials, unavailable services), do not produce a static fallback. Record a blocker in \`PROGRESS.md\` and stop the iteration.
47
+ 1. The deliverable is \`.vercel/output/\` conforming to Build Output API v3.
48
+ 2. \`.vercel/output/config.json\` must exist with \`{ "version": 3 }\`.
49
+ 3. Static assets go in \`.vercel/output/static/\`. Serverless functions go in \`.vercel/output/functions/\`.
50
+ 4. You may use any method to produce this: hand-write HTML, use a framework build, or any other approach.
51
+ 5. Run \`dream validate\` to verify the output before signaling completion.
52
+ 6. If required prerequisites are missing (for example env vars, credentials, unavailable services), do not produce a static fallback. Record a blocker in \`PROGRESS.md\` and stop the iteration.
51
53
 
52
54
  ## Critical: State Lives on Disk
53
55
 
@@ -165,7 +167,7 @@ Each iteration should complete a coherent unit (not trivial single-file churn),
165
167
 
166
168
  **When all work is done**, output completion signal. The bar is:
167
169
  1. All tasks in \`PROGRESS.md\` are checked off
168
- 2. \`npm run build\` (or equivalent) exits 0
170
+ 2. \`dream validate\` passes
169
171
  3. No blockers in \`PROGRESS.md\`
170
172
 
171
173
  That's it. Do NOT loop on cosmetic verification (curling pages, grepping HTML, re-reading files you just wrote). If it builds clean and tasks are done, you are done.
@@ -276,6 +278,21 @@ ${INDENT_MAIN}${dim("specs")} ${dim(`(${specFiles.length})`)}`
276
278
  }
277
279
  printLine();
278
280
  });
281
+ program.command("validate").description("Validate .vercel/output/ conforms to Build Output API v3").action(() => {
282
+ const workDir = path.resolve(program.opts().dir);
283
+ const result = validateBuildOutput(workDir);
284
+ if (result.valid) {
285
+ printSuccess("Build output is valid");
286
+ printLine();
287
+ process.exit(0);
288
+ }
289
+ printError("Build output validation failed:");
290
+ for (const error of result.errors) {
291
+ printListItem(error);
292
+ }
293
+ printLine();
294
+ process.exit(1);
295
+ });
279
296
  program.command("models").description("List available models and check provider auth").action(async () => {
280
297
  printTitle("models");
281
298
  printStep("Starting OpenCode...");
@@ -449,12 +466,67 @@ ${INDENT_MAIN}${red("\u2717")} Timeout after ${formatTime(elapsed)}
449
466
  printLine(
450
467
  `${INDENT_MAIN}${cyan(`[${iteration}]`)} ${green("\u2713")} Done ${dim(`(${formatTime(iterElapsed)})`)}`
451
468
  );
452
- printLine(
453
- `
469
+ let validation = validateBuildOutput(workDir);
470
+ if (validation.valid) {
471
+ printSuccess("Build output validated");
472
+ printLine(
473
+ `
454
474
  ${INDENT_MAIN}${green("\u2713")} Completed in ${bold(String(iteration))} iteration(s) ${dim(`(${formatTime(Date.now() - startTime)})`)}
455
475
  `
456
- );
457
- process.exit(0);
476
+ );
477
+ process.exit(0);
478
+ }
479
+ const MAX_REMEDIATION_ATTEMPTS = 3;
480
+ for (let attempt = 1; attempt <= MAX_REMEDIATION_ATTEMPTS; attempt++) {
481
+ const remElapsed = Date.now() - startTime;
482
+ if (remElapsed >= timeout) {
483
+ printError(
484
+ `Timeout during remediation after ${formatTime(remElapsed)}`
485
+ );
486
+ printLine();
487
+ process.exit(1);
488
+ }
489
+ printError("Build output validation failed:");
490
+ for (const error of validation.errors) {
491
+ printListItem(error);
492
+ }
493
+ printLine(
494
+ `${INDENT_MAIN}${cyan(`[remediate ${attempt}/${MAX_REMEDIATION_ATTEMPTS}]`)} Running remediation session...`
495
+ );
496
+ const remediationMessage = [
497
+ "Build output validation failed:",
498
+ ...validation.errors.map((e) => `- ${e}`),
499
+ "",
500
+ "Produce .vercel/output/ conforming to Build Output API v3.",
501
+ "Run `dream validate` to verify before signaling completion."
502
+ ].join("\n");
503
+ await runSession(
504
+ client,
505
+ title,
506
+ `${effectivePrompt}
507
+
508
+ ## Remediation
509
+
510
+ ${remediationMessage}`,
511
+ verbose
512
+ );
513
+ validation = validateBuildOutput(workDir);
514
+ if (validation.valid) {
515
+ printSuccess("Build output validated after remediation");
516
+ printLine(
517
+ `
518
+ ${INDENT_MAIN}${green("\u2713")} Completed in ${bold(String(iteration))} iteration(s) ${dim(`(${formatTime(Date.now() - startTime)})`)}
519
+ `
520
+ );
521
+ process.exit(0);
522
+ }
523
+ }
524
+ printError("Build output validation failed after remediation");
525
+ for (const error of validation.errors) {
526
+ printListItem(error);
527
+ }
528
+ printLine();
529
+ process.exit(1);
458
530
  }
459
531
  if (result === "error") {
460
532
  printLine(
@@ -684,6 +756,35 @@ function toolContext(tool, input) {
684
756
  return void 0;
685
757
  }
686
758
  }
759
+ function validateBuildOutput(workDir) {
760
+ const errors = [];
761
+ const outputDir = path.join(workDir, ".vercel", "output");
762
+ const configPath = path.join(outputDir, "config.json");
763
+ if (!fs.existsSync(configPath)) {
764
+ errors.push(".vercel/output/config.json does not exist");
765
+ } else {
766
+ try {
767
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
768
+ if (config.version !== 3) {
769
+ errors.push(
770
+ `.vercel/output/config.json has version ${config.version}, expected 3`
771
+ );
772
+ }
773
+ } catch {
774
+ errors.push(".vercel/output/config.json is not valid JSON");
775
+ }
776
+ }
777
+ const staticDir = path.join(outputDir, "static");
778
+ const functionsDir = path.join(outputDir, "functions");
779
+ const hasStatic = fs.existsSync(staticDir) && fs.readdirSync(staticDir).length > 0;
780
+ const hasFunctions = fs.existsSync(functionsDir) && fs.readdirSync(functionsDir).length > 0;
781
+ if (!hasStatic && !hasFunctions) {
782
+ errors.push(
783
+ ".vercel/output/ must contain a non-empty static/ or functions/ directory"
784
+ );
785
+ }
786
+ return { valid: errors.length === 0, errors };
787
+ }
687
788
  function loadProjectPrompt(specsDir) {
688
789
  for (const file of PROJECT_PROMPT_FILES) {
689
790
  const fullPath = path.join(specsDir, file);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/dream",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "A CLI that runs OpenCode in a loop until specs are complete",
5
5
  "type": "module",
6
6
  "bin": {