forgeos 0.1.0-alpha.41 → 0.1.0-alpha.43

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 (66) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +46 -2
  3. package/docs/changelog.md +222 -0
  4. package/package.json +2 -1
  5. package/scripts/field-test-forgeos.mjs +1012 -0
  6. package/src/forge/_generated/releaseManifest.json +1 -1
  7. package/src/forge/_generated/releaseManifest.ts +3 -3
  8. package/src/forge/cli/auth.ts +1 -0
  9. package/src/forge/cli/commands.ts +13 -0
  10. package/src/forge/cli/deploy.ts +109 -18
  11. package/src/forge/cli/dev.ts +176 -10
  12. package/src/forge/cli/field-test.ts +210 -18
  13. package/src/forge/cli/main.ts +16 -5
  14. package/src/forge/cli/new.ts +2 -1
  15. package/src/forge/cli/output.ts +9 -0
  16. package/src/forge/cli/parse.ts +95 -12
  17. package/src/forge/cli/run.ts +1 -0
  18. package/src/forge/cli/seed.ts +581 -0
  19. package/src/forge/cli/workos.ts +349 -16
  20. package/src/forge/compiler/agent-contract/build.ts +58 -0
  21. package/src/forge/compiler/data-graph/build.ts +29 -1
  22. package/src/forge/compiler/diagnostics/codes.ts +2 -0
  23. package/src/forge/compiler/diagnostics/create.ts +7 -0
  24. package/src/forge/compiler/integration/add.ts +305 -5
  25. package/src/forge/compiler/integration/templates/workos.ts +51 -2
  26. package/src/forge/compiler/orchestrator/plan.ts +2 -0
  27. package/src/forge/make/index.ts +2 -0
  28. package/src/forge/make/templates.ts +58 -4
  29. package/src/forge/runtime/external/bridge.ts +14 -0
  30. package/src/forge/server.ts +59 -9
  31. package/src/forge/ui/index.ts +643 -25
  32. package/src/forge/ui/types.ts +2 -1
  33. package/src/forge/version.ts +1 -1
  34. package/templates/agent-workroom/web/index.html +1 -0
  35. package/templates/agent-workroom/web/src/lib/forge.ts +9 -2
  36. package/templates/agent-workroom/web/vite.config.ts +36 -0
  37. package/templates/b2b-support-web/web/app/page.tsx +2 -2
  38. package/templates/b2b-support-web/web/lib/forge.ts +9 -2
  39. package/templates/b2b-support-web/web/next.config.ts +24 -0
  40. package/templates/minimal-web/web/index.html +1 -0
  41. package/templates/minimal-web/web/src/App.tsx +12 -6
  42. package/templates/minimal-web/web/src/lib/forge.ts +9 -2
  43. package/templates/minimal-web/web/src/styles.css +34 -0
  44. package/templates/minimal-web/web/vite.config.ts +36 -0
  45. package/templates/nuxt-web/web/app.vue +1 -1
  46. package/templates/vendor-access/.vscode/settings.json +14 -0
  47. package/templates/vendor-access/README.md +30 -0
  48. package/templates/vendor-access/forge.config.ts +3 -0
  49. package/templates/vendor-access/package.json +34 -0
  50. package/templates/vendor-access/src/commands/addEvidence.ts +42 -0
  51. package/templates/vendor-access/src/commands/approveAccessRequest.ts +43 -0
  52. package/templates/vendor-access/src/commands/createAccessRequest.ts +49 -0
  53. package/templates/vendor-access/src/commands/seedVendorAccessDemo.ts +200 -0
  54. package/templates/vendor-access/src/forge/schema.ts +74 -0
  55. package/templates/vendor-access/src/policies.ts +11 -0
  56. package/templates/vendor-access/src/queries/listVendorAccessDashboard.ts +24 -0
  57. package/templates/vendor-access/src/queries/liveVendorAccessDashboard.ts +24 -0
  58. package/templates/vendor-access/tsconfig.json +17 -0
  59. package/templates/vendor-access/web/index.html +13 -0
  60. package/templates/vendor-access/web/package.json +21 -0
  61. package/templates/vendor-access/web/src/App.tsx +573 -0
  62. package/templates/vendor-access/web/src/lib/forge.ts +20 -0
  63. package/templates/vendor-access/web/src/main.tsx +205 -0
  64. package/templates/vendor-access/web/src/styles.css +682 -0
  65. package/templates/vendor-access/web/tsconfig.json +18 -0
  66. package/templates/vendor-access/web/vite.config.ts +36 -0
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
- import { join } from "node:path";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import { runNewCommand, type NewPackageManager, type NewTemplateName } from "./new.ts";
5
6
  import { normalizeForgeCliCommandsInValue } from "../workspace/forge-cli.ts";
6
7
 
@@ -21,6 +22,8 @@ export interface FieldTestCommandOptions {
21
22
  keep: boolean;
22
23
  runtimeProbes: boolean;
23
24
  authProbes: boolean;
25
+ uiProbes: boolean;
26
+ realistic: boolean;
24
27
  timeoutMs: number;
25
28
  writeReport?: string;
26
29
  }
@@ -40,8 +43,14 @@ export interface FieldTestCommandResult {
40
43
  exitCode: 0 | 1;
41
44
  }
42
45
 
43
- function scriptPath(workspaceRoot: string): string {
44
- return join(workspaceRoot, "scripts", "field-test-forgeos.mjs");
46
+ function scriptCandidates(workspaceRoot: string): string[] {
47
+ const localScript = join(workspaceRoot, "scripts", "field-test-forgeos.mjs");
48
+ const packageScript = join(dirname(fileURLToPath(import.meta.url)), "../../../scripts/field-test-forgeos.mjs");
49
+ return Array.from(new Set([localScript, packageScript]));
50
+ }
51
+
52
+ function scriptPath(workspaceRoot: string): string | null {
53
+ return scriptCandidates(workspaceRoot).find((candidate) => existsSync(candidate)) ?? null;
45
54
  }
46
55
 
47
56
  function compact(text: string, limit = 12000): string {
@@ -54,6 +63,10 @@ function defaultReportPath(): string {
54
63
  return ".forge/field-test-report.json";
55
64
  }
56
65
 
66
+ function resolveReportPath(workspaceRoot: string, reportPath: string): string {
67
+ return isAbsolute(reportPath) ? reportPath : join(workspaceRoot, reportPath);
68
+ }
69
+
57
70
  function parseJsonOutput(text: string): unknown | null {
58
71
  const trimmed = text.trim();
59
72
  if (!trimmed) return null;
@@ -77,19 +90,156 @@ function summarizeReport(data: unknown) {
77
90
  const plannedCases = Array.isArray(root.cases) ? root.cases as Array<Record<string, unknown>> : [];
78
91
  const failed = results.filter((result) => result.ok === false && result.skipped !== true);
79
92
  const skipped = results.filter((result) => result.skipped === true);
93
+ const topLevelSteps = results.flatMap((result) =>
94
+ Array.isArray(result.steps) ? result.steps as unknown[] : [],
95
+ );
80
96
  const runtimeProbeSteps = results.flatMap((result) => {
81
97
  const runtime = result.runtime && typeof result.runtime === "object"
82
98
  ? result.runtime as Record<string, unknown>
83
99
  : {};
84
100
  return Array.isArray(runtime.steps) ? runtime.steps as unknown[] : [];
85
101
  });
102
+ const hasVendorAccessCase = results.some((result) => result.template === "vendor-access");
103
+ const commandOf = (step: unknown): string =>
104
+ step && typeof step === "object" && typeof (step as { command?: unknown }).command === "string"
105
+ ? (step as { command: string }).command
106
+ : "";
107
+ const stdoutOf = (step: unknown): string =>
108
+ step && typeof step === "object" && typeof (step as { stdout?: unknown }).stdout === "string"
109
+ ? (step as { stdout: string }).stdout
110
+ : "";
111
+ const okStep = (step: unknown): boolean =>
112
+ Boolean(step && typeof step === "object" && (step as { ok?: unknown }).ok === true);
113
+ const jsonOfStep = (step: unknown): Record<string, unknown> | null => {
114
+ const stdout = stdoutOf(step).trim();
115
+ if (!stdout) return null;
116
+ const parsed = parseJsonOutput(stdout);
117
+ return parsed && typeof parsed === "object" ? parsed as Record<string, unknown> : null;
118
+ };
119
+ const hasOkRuntimeCommand = (pattern: RegExp): boolean =>
120
+ runtimeProbeSteps.some((step) => okStep(step) && pattern.test(commandOf(step)));
121
+ const hasOkTopLevelCommand = (pattern: RegExp): boolean =>
122
+ topLevelSteps.some((step) => okStep(step) && pattern.test(commandOf(step)));
123
+ const seedProbeSteps = runtimeProbeSteps.filter((step) => commandOf(step).includes("seed-"));
124
+ const seedReadinessSteps = runtimeProbeSteps.filter((step) => commandOf(step).includes("seed-status"));
125
+ const seedReadinessResults = seedReadinessSteps
126
+ .filter((step) => okStep(step))
127
+ .map((step) => {
128
+ const payload = jsonOfStep(step);
129
+ const readiness = payload?.readiness && typeof payload.readiness === "object"
130
+ ? payload.readiness as Record<string, unknown>
131
+ : {};
132
+ const recovery = Array.isArray(readiness.emptyWorkspaceRecovery)
133
+ ? readiness.emptyWorkspaceRecovery.filter((item) => typeof item === "string")
134
+ : [];
135
+ return {
136
+ ready: readiness.ready === true,
137
+ autoSeedOnDev: readiness.autoSeedOnDev === true,
138
+ autoSeedAllTenantsOnDev: readiness.autoSeedAllTenantsOnDev === true,
139
+ autoSeedMode: typeof readiness.autoSeedMode === "string" ? readiness.autoSeedMode : undefined,
140
+ selectedCommand: typeof readiness.selectedCommand === "string" ? readiness.selectedCommand : undefined,
141
+ recoveryCommands: recovery.length,
142
+ };
143
+ });
144
+ const seedReadinessEvidence = !hasVendorAccessCase || seedReadinessResults.some((result) =>
145
+ result.ready &&
146
+ result.selectedCommand === "seedVendorAccessDemo" &&
147
+ result.recoveryCommands >= 2,
148
+ );
149
+ const seedAllTenantsAutoSeedEvidence = !hasVendorAccessCase || seedReadinessResults.some((result) =>
150
+ result.ready &&
151
+ result.selectedCommand === "seedVendorAccessDemo" &&
152
+ (result.autoSeedMode === "all-tenants" || result.autoSeedAllTenantsOnDev),
153
+ );
154
+ const vendorAccessProbeSteps = runtimeProbeSteps.filter((step) => commandOf(step).includes("vendor-access-"));
155
+ const uiProbeSteps = runtimeProbeSteps.filter((step) => /^GET\s+https?:\/\/[^/]+\/$/i.test(commandOf(step)));
156
+ const authMetadataProbeSteps = runtimeProbeSteps.filter((step) =>
157
+ /^(HEAD|GET)\s+https?:\/\/[^/]+\/(auth\.md|\.well-known\/oauth-protected-resource)\b/i.test(commandOf(step)),
158
+ );
159
+ const authSetupProbeSteps = topLevelSteps.filter((step) =>
160
+ /forge\s+(?:--\s+)?(add\s+auth\s+workos|authmd\s+generate|authmd\s+check|workos\s+doctor|workos\s+seed|workos\s+prove|auth\s+prove)/.test(commandOf(step)),
161
+ );
162
+ const uiErgonomicsResults = results
163
+ .map((result) => result.uiErgonomics)
164
+ .filter((value): value is Record<string, unknown> => Boolean(value && typeof value === "object"));
165
+ const uiErgonomicsWarnings = uiErgonomicsResults.reduce((total, result) =>
166
+ total + (typeof result.warnings === "number" ? result.warnings : 0), 0);
167
+ const uiErgonomicsErrors = uiErgonomicsResults.reduce((total, result) =>
168
+ total + (typeof result.errors === "number" ? result.errors : 0), 0);
169
+ const uiErgonomicsWarningCodes = Array.from(new Set(uiErgonomicsResults.flatMap((result) =>
170
+ Array.isArray(result.diagnosticCodes)
171
+ ? result.diagnosticCodes.filter((code): code is string => typeof code === "string")
172
+ : [],
173
+ ))).sort();
174
+ const uiScenarioNames = Array.from(new Set(uiErgonomicsResults.flatMap((result) =>
175
+ Array.isArray(result.scenarioNames)
176
+ ? result.scenarioNames.filter((name): name is string => typeof name === "string")
177
+ : [],
178
+ ))).sort();
179
+ const vendorAccessRequiredUiScenarios = [
180
+ "vendor-access-autoseed-data-visible",
181
+ "vendor-access-local-login",
182
+ "vendor-access-requester-denied-visible",
183
+ "vendor-access-seed-control-visible",
184
+ ];
86
185
  const ok = root.ok === true;
87
186
  const runtimeProbes = root.runtimeProbes === true;
88
187
  const authProbes = root.authProbes === true;
188
+ const uiProbes = root.uiProbes === true;
189
+ const uiErgonomics = !uiProbes || (results.length > 0 && uiErgonomicsResults.length === results.filter((result) => result.skipped !== true).length);
190
+ const runtimeHealthEvidence = !runtimeProbes || hasOkRuntimeCommand(/\bGET\s+.*\/health\b/i);
191
+ const runtimeEntriesEvidence = !runtimeProbes || hasOkRuntimeCommand(/\bGET\s+.*\/entries\b/i);
192
+ const authSetupEvidence = !authProbes || [
193
+ /forge\s+(?:--\s+)?add\s+auth\s+workos\b/,
194
+ /forge\s+(?:--\s+)?authmd\s+generate\b/,
195
+ /forge\s+(?:--\s+)?authmd\s+check\b/,
196
+ /forge\s+(?:--\s+)?workos\s+doctor\b/,
197
+ /forge\s+(?:--\s+)?workos\s+seed\b/,
198
+ /forge\s+(?:--\s+)?workos\s+prove\b/,
199
+ /forge\s+(?:--\s+)?auth\s+prove\b/,
200
+ ].every((pattern) => hasOkTopLevelCommand(pattern));
201
+ const authMetadataEvidence = !authProbes || [
202
+ /^HEAD\s+.*\/auth\.md\b/i,
203
+ /^GET\s+.*\/auth\.md\b/i,
204
+ /^HEAD\s+.*\/\.well-known\/oauth-protected-resource\b/i,
205
+ /^GET\s+.*\/\.well-known\/oauth-protected-resource\b/i,
206
+ ].every((pattern) => hasOkRuntimeCommand(pattern));
207
+ const uiProbeEvidence = !uiProbes || uiProbeSteps.some((step) => okStep(step));
208
+ const vendorAccessSeedEvidence = !hasVendorAccessCase ||
209
+ hasOkRuntimeCommand(/vendor-access-seed-all-tenants:/) ||
210
+ [
211
+ /vendor-access-seed-acme:/,
212
+ /vendor-access-seed-globex:/,
213
+ ].every((pattern) => hasOkRuntimeCommand(pattern));
214
+ const vendorAccessEvidence = !hasVendorAccessCase || (
215
+ vendorAccessSeedEvidence &&
216
+ [
217
+ /vendor-access-query-acme:/,
218
+ /vendor-access-query-globex:/,
219
+ /vendor-access-owner-approve:/,
220
+ /vendor-access-requester-approve-denied:/,
221
+ /vendor-access-cross-tenant-approve-denied:/,
222
+ ].every((pattern) => hasOkRuntimeCommand(pattern))
223
+ );
224
+ const vendorAccessUiScenarioEvidence = !hasVendorAccessCase || !uiProbes ||
225
+ vendorAccessRequiredUiScenarios.every((scenario) => uiScenarioNames.includes(scenario));
89
226
  const productionEvidenceMissing = [
90
227
  ...(!ok ? ["passing field-test report"] : []),
91
228
  ...(!runtimeProbes ? ["runtime probes"] : []),
92
229
  ...(!authProbes ? ["auth probes"] : []),
230
+ ...(!uiProbes ? ["ui probes"] : []),
231
+ ...(!runtimeHealthEvidence ? ["runtime health probe"] : []),
232
+ ...(!runtimeEntriesEvidence ? ["runtime entries probe"] : []),
233
+ ...(!authSetupEvidence ? ["auth setup probes"] : []),
234
+ ...(!authMetadataEvidence ? ["auth metadata endpoint probes"] : []),
235
+ ...(!uiProbeEvidence ? ["web UI probe"] : []),
236
+ ...(!seedReadinessEvidence ? ["seed readiness evidence"] : []),
237
+ ...(!seedAllTenantsAutoSeedEvidence ? ["seed readiness all-tenants auto-seed evidence"] : []),
238
+ ...(!vendorAccessEvidence ? ["vendor-access multi-tenant domain probes"] : []),
239
+ ...(!vendorAccessUiScenarioEvidence ? ["vendor-access UI scenarios"] : []),
240
+ ...(!uiErgonomics ? ["UI ergonomics audit"] : []),
241
+ ...(uiErgonomicsWarnings > 0 ? ["zero UI ergonomics warnings"] : []),
242
+ ...(uiErgonomicsErrors > 0 ? ["zero UI ergonomics errors"] : []),
93
243
  ...(failed.length > 0 ? ["zero failed cases"] : []),
94
244
  ];
95
245
  return {
@@ -102,7 +252,35 @@ function summarizeReport(data: unknown) {
102
252
  skipped: skipped.length,
103
253
  runtimeProbes,
104
254
  authProbes,
255
+ uiProbes,
256
+ uiErgonomics,
257
+ uiErgonomicsWarnings,
258
+ uiErgonomicsErrors,
259
+ uiErgonomicsWarningCodes,
260
+ uiScenarios: {
261
+ names: uiScenarioNames,
262
+ vendorAccessReady: vendorAccessUiScenarioEvidence,
263
+ requiredVendorAccess: hasVendorAccessCase ? vendorAccessRequiredUiScenarios : [],
264
+ },
105
265
  runtimeProbeSteps: runtimeProbeSteps.length,
266
+ seedProbeSteps: seedProbeSteps.length,
267
+ seedReadiness: {
268
+ ready: seedReadinessEvidence,
269
+ steps: seedReadinessSteps.length,
270
+ autoSeedOnDev: seedReadinessResults.some((result) => result.autoSeedOnDev),
271
+ autoSeedAllTenantsOnDev: seedReadinessResults.some((result) => result.autoSeedAllTenantsOnDev),
272
+ allTenantsAutoSeedReady: seedAllTenantsAutoSeedEvidence,
273
+ autoSeedModes: Array.from(new Set(seedReadinessResults
274
+ .map((result) => result.autoSeedMode)
275
+ .filter((value): value is string => Boolean(value)))),
276
+ selectedCommands: Array.from(new Set(seedReadinessResults
277
+ .map((result) => result.selectedCommand)
278
+ .filter((value): value is string => Boolean(value)))),
279
+ },
280
+ authSetupProbeSteps: authSetupProbeSteps.length,
281
+ authMetadataProbeSteps: authMetadataProbeSteps.length,
282
+ uiProbeSteps: uiProbeSteps.length,
283
+ vendorAccessProbeSteps: vendorAccessProbeSteps.length,
106
284
  productionEvidence: {
107
285
  readyForDeployCheck: productionEvidenceMissing.length === 0,
108
286
  missing: productionEvidenceMissing,
@@ -119,11 +297,9 @@ function summarizeReport(data: unknown) {
119
297
 
120
298
  function runCommandForOptions(templates: NewTemplateName[], packageManagers: NewPackageManager[]): string {
121
299
  return [
122
- "forge field-test run",
300
+ "forge field-test run --realistic",
123
301
  `--templates ${templates.join(",")}`,
124
302
  `--package-managers ${packageManagers.join(",")}`,
125
- "--runtime-probes",
126
- "--auth-probes",
127
303
  "--json",
128
304
  ].join(" ");
129
305
  }
@@ -136,7 +312,7 @@ async function createFieldTestApp(options: FieldTestCommandOptions): Promise<Fie
136
312
  kind: "field-test",
137
313
  action: "create",
138
314
  data: { error: "forge field-test create requires an app name" },
139
- nextActions: ["forge field-test create vendor-access --auth workos --template minimal-web --json"],
315
+ nextActions: ["forge field-test create vendor-access --auth workos --json"],
140
316
  exitCode: 1,
141
317
  };
142
318
  }
@@ -152,9 +328,21 @@ async function createFieldTestApp(options: FieldTestCommandOptions): Promise<Fie
152
328
  template: options.template,
153
329
  packageManager: options.packageManager,
154
330
  auth: options.auth,
331
+ install: true,
332
+ git: true,
155
333
  command: `forge new ${options.name} --template ${options.template} --package-manager ${options.packageManager} --field-test --install`,
334
+ goldenPath: [
335
+ `forge field-test create ${options.name} --auth ${options.auth ?? "none"} --template ${options.template} --package-manager ${options.packageManager} --install --git --json`,
336
+ `cd ${options.name}`,
337
+ "forge field-test run --realistic --json",
338
+ "forge field-test report --json",
339
+ "forge deploy plan --target docker --json",
340
+ "forge deploy check --production --json",
341
+ ],
156
342
  },
157
- nextActions: [`forge new ${options.name} --template ${options.template} --package-manager ${options.packageManager} --field-test --install`],
343
+ nextActions: [
344
+ `forge field-test create ${options.name} --auth ${options.auth ?? "none"} --template ${options.template} --package-manager ${options.packageManager} --install --git --json`,
345
+ ],
158
346
  exitCode: 0,
159
347
  });
160
348
  }
@@ -198,9 +386,12 @@ async function createFieldTestApp(options: FieldTestCommandOptions): Promise<Fie
198
386
  `${options.packageManager} run forge -- authmd check --json`,
199
387
  `${options.packageManager} run forge -- workos doctor --json`,
200
388
  `${options.packageManager} run forge -- workos seed --file workos-seed.yml --dry-run --json`,
389
+ `${options.packageManager} run forge -- workos prove --file workos-seed.yml --json`,
201
390
  `${options.packageManager} run forge -- workos setup --file workos-seed.yml --json`,
202
391
  ]
203
392
  : []),
393
+ `${options.packageManager} run forge -- field-test run --realistic --json`,
394
+ `${options.packageManager} run forge -- field-test report --json`,
204
395
  `${options.packageManager} run forge -- dev --once --json`,
205
396
  `${options.packageManager} run forge -- verify --smoke --json`,
206
397
  `${options.packageManager} run forge -- handoff --json`,
@@ -211,14 +402,14 @@ async function createFieldTestApp(options: FieldTestCommandOptions): Promise<Fie
211
402
 
212
403
  function runHarness(options: FieldTestCommandOptions): FieldTestCommandResult {
213
404
  const script = scriptPath(options.workspaceRoot);
214
- if (!existsSync(script)) {
405
+ if (!script) {
215
406
  return {
216
407
  schemaVersion: "0.1.0",
217
408
  ok: false,
218
409
  kind: "field-test",
219
410
  action: "run",
220
- data: { error: "field-test harness script not found", script },
221
- nextActions: ["run from the ForgeOS framework checkout or use npm run field:test"],
411
+ data: { error: "field-test harness script not found", searched: scriptCandidates(options.workspaceRoot) },
412
+ nextActions: ["upgrade forgeos to a version that includes scripts/field-test-forgeos.mjs", "run from the ForgeOS framework checkout with npm run field:test"],
222
413
  exitCode: 1,
223
414
  };
224
415
  }
@@ -236,8 +427,9 @@ function runHarness(options: FieldTestCommandOptions): FieldTestCommandResult {
236
427
  ];
237
428
  if (options.dryRun) args.push("--dry-run");
238
429
  if (options.keep) args.push("--keep");
239
- if (options.runtimeProbes) args.push("--runtime-probes");
240
- if (options.authProbes) args.push("--auth-probes");
430
+ if (options.runtimeProbes || options.realistic) args.push("--runtime-probes");
431
+ if (options.authProbes || options.realistic) args.push("--auth-probes");
432
+ if (options.uiProbes || options.realistic) args.push("--ui-probes");
241
433
  if (options.forgeSpec) args.push("--forge-spec", options.forgeSpec);
242
434
  if (reportPath) args.push("--write-report", reportPath);
243
435
  if (options.json) args.push("--json");
@@ -246,7 +438,7 @@ function runHarness(options: FieldTestCommandOptions): FieldTestCommandResult {
246
438
  encoding: "utf8",
247
439
  windowsHide: true,
248
440
  });
249
- const reportAbsolute = reportPath ? join(options.workspaceRoot, reportPath) : null;
441
+ const reportAbsolute = reportPath ? resolveReportPath(options.workspaceRoot, reportPath) : null;
250
442
  const reportData = reportAbsolute && existsSync(reportAbsolute)
251
443
  ? JSON.parse(readFileSync(reportAbsolute, "utf8")) as unknown
252
444
  : parseJsonOutput(result.stdout ?? "");
@@ -276,7 +468,7 @@ function readReport(options: FieldTestCommandOptions): FieldTestCommandResult {
276
468
  ".forge/field-test-report.json",
277
469
  "field-reports/full-alpha.json",
278
470
  ].filter(Boolean) as string[];
279
- const report = candidates.find((candidate) => existsSync(join(options.workspaceRoot, candidate)));
471
+ const report = candidates.find((candidate) => existsSync(resolveReportPath(options.workspaceRoot, candidate)));
280
472
  if (!report) {
281
473
  return {
282
474
  schemaVersion: "0.1.0",
@@ -284,11 +476,11 @@ function readReport(options: FieldTestCommandOptions): FieldTestCommandResult {
284
476
  kind: "field-test",
285
477
  action: "report",
286
478
  data: { searched: candidates },
287
- nextActions: ["forge field-test run --runtime-probes --auth-probes --write-report .forge/field-test-report.json --json"],
479
+ nextActions: ["forge field-test run --realistic --write-report .forge/field-test-report.json --json"],
288
480
  exitCode: 1,
289
481
  };
290
482
  }
291
- const data = JSON.parse(readFileSync(join(options.workspaceRoot, report), "utf8")) as unknown;
483
+ const data = JSON.parse(readFileSync(resolveReportPath(options.workspaceRoot, report), "utf8")) as unknown;
292
484
  const summary = summarizeReport(data);
293
485
  const productionEvidence = (summary as { productionEvidence?: { readyForDeployCheck?: boolean } }).productionEvidence;
294
486
  return {
@@ -301,7 +493,7 @@ function readReport(options: FieldTestCommandOptions): FieldTestCommandResult {
301
493
  data,
302
494
  nextActions: productionEvidence?.readyForDeployCheck
303
495
  ? ["forge deploy check --production --json"]
304
- : ["forge field-test run --runtime-probes --auth-probes --write-report .forge/field-test-report.json --json"],
496
+ : ["forge field-test run --realistic --write-report .forge/field-test-report.json --json"],
305
497
  exitCode: (data && typeof data === "object" && "ok" in data && (data as { ok?: unknown }).ok !== true) ? 1 : 0,
306
498
  };
307
499
  }
@@ -16,10 +16,14 @@ function formatHelp(): string {
16
16
  " forge changed --authored --json Show only authored changed files, excluding generated artifacts",
17
17
  " forge changed --review --json Show review-focused app/config/docs changes, excluding local agent/browser artifacts",
18
18
  " forge changed --commit-ready --json Show files suitable for git add, excluding generated and operational artifacts",
19
- " forge new my-app --template minimal-web --field-test Create an installed WorkOS/auth.md field-test app",
20
- " forge field-test create vendor-access --auth workos --template minimal-web --json Create a real field-test app",
21
- " forge field-test run --templates minimal-web,nuxt-web --package-managers npm,pnpm --runtime-probes --auth-probes --json",
19
+ " forge new my-app --template vendor-access --field-test Create an installed WorkOS/auth.md field-test app",
20
+ " forge field-test create vendor-access --auth workos --install --git --json Create a real field-test app",
21
+ " forge field-test run --realistic --templates vendor-access --package-managers npm --json",
22
22
  " forge field-test report --json Summarize the machine-readable field-test report",
23
+ " forge seed status --json Discover app seed commands from the generated runtime graph",
24
+ " forge seed dev --json Run the app seed command against the local Forge dev runtime",
25
+ " forge seed dev --all-tenants --json Seed every discovered local tenant/persona profile",
26
+ " forge seed reset --json Run the seed command with args.reset=true against the local dev runtime",
23
27
  " forge diff authored Run the authored-only git diff pathspec",
24
28
  " forge handoff --json Compact work handoff for the next external code agent",
25
29
  " forge agent onboard --target codex --json Prepare adapter, hooks, memory, and dev snapshot",
@@ -37,10 +41,11 @@ function formatHelp(): string {
37
41
  " forge workos doctor --json Check WorkOS AuthKit/FGA files, claims, seed, webhook, and tenant guards",
38
42
  " forge workos doctor --yes --json Run local checks, then delegate to npx --yes workos@latest doctor",
39
43
  " forge workos seed --file workos-seed.yml --dry-run --json Validate WorkOS seed without hosted changes",
44
+ " forge workos prove --file workos-seed.yml --json Prove local WorkOS/AuthKit/FGA/seed readiness without dashboard changes",
40
45
  " forge workos setup --real --file workos-seed.yml --json Apply redirect/CORS/homepage config and seed WorkOS without dashboard",
41
46
  " forge deploy plan --target docker --json Explain production deploy gates and commands",
42
47
  " forge deploy check --production --json Gate auth, DB, metadata, generated artifacts, and liveQuery readiness",
43
- " forge deploy render docker Write Docker production deploy files under deploy/",
48
+ " forge deploy package --target docker Write Docker production deploy files under deploy/",
44
49
  " forge deploy verify --production --url https://app.example.com --json Probe /health and validate public auth metadata",
45
50
  " forge release check --allow-missing-local-release --json Gate release readiness without failing on unprepared local artifacts",
46
51
  " forge self-host check --prepared-only --json Report compose readiness without creating deploy files",
@@ -96,18 +101,24 @@ function formatDevHelp(): string {
96
101
  "Options:",
97
102
  " --db memory|pglite|postgres|none Choose the development database adapter",
98
103
  " --port <port> API runtime port; use 0 for an ephemeral port",
99
- " --web-port <port> Web dev server port",
104
+ " --web-port <port> Web dev server port; use 0 for an ephemeral port",
100
105
  " --host <host> Bind host, default 127.0.0.1",
101
106
  " --no-web Start API/runtime only",
102
107
  " --api-only Start API/runtime only",
103
108
  " --web-only Start web server only, expecting an API runtime",
104
109
  " --no-worker Disable local worker",
105
110
  " --no-watch Disable file watching",
111
+ " --seed Run the discovered seed command after the API runtime starts",
112
+ " --seed-command <name> Select a specific seed command for --seed",
113
+ " --all-tenants With --seed, seed every discovered local tenant/persona",
106
114
  " --once --json Run one-shot diagnostics without starting servers",
107
115
  " --detach --json Start dev in the background with .forge/dev/dev.pid and .forge/dev/dev.log",
108
116
  "",
109
117
  "Examples:",
110
118
  " forge dev --db memory --port 3777 --web-port 5174",
119
+ " forge dev --db memory --port 0 --web-port 0",
120
+ " forge dev --seed --db pglite",
121
+ " forge dev --seed --all-tenants --db pglite",
111
122
  " forge dev --db pglite --once --json",
112
123
  " forge dev --detach --db memory --port 0 --json",
113
124
  " forge dev status --json",
@@ -8,7 +8,7 @@ import { run as runGenerate } from "../compiler/orchestrator/run.ts";
8
8
  import { resolvePackageManagerArgv } from "../compiler/package-manager/executor.ts";
9
9
  import { moduleDir } from "../platform/module.ts";
10
10
 
11
- export type NewTemplateName = "agent-workroom" | "b2b-support-web" | "minimal-web" | "nuxt-web";
11
+ export type NewTemplateName = "agent-workroom" | "b2b-support-web" | "minimal-web" | "nuxt-web" | "vendor-access";
12
12
  export type NewPackageManager = "bun" | "npm" | "pnpm" | "yarn";
13
13
 
14
14
  export interface NewCommandOptions {
@@ -79,6 +79,7 @@ const TEXT_EXTENSIONS = new Set([
79
79
  ".css",
80
80
  ".env",
81
81
  ".example",
82
+ ".html",
82
83
  ".json",
83
84
  ".md",
84
85
  ".ts",
@@ -118,6 +118,8 @@ export function buildGenerateJson(
118
118
  ? {
119
119
  kind: "generated-drift",
120
120
  message: `${result.changed.length} generated artifact(s) would change; run forge generate before handoff or verification.`,
121
+ concurrencyNote:
122
+ "Do not run forge generate, forge generate --check, forge dev --once, or forge verify concurrently in the same workspace. If a generate command was active or just started, wait for it to finish and rerun the check once.",
121
123
  changedGroups: summarizeGeneratedArtifacts(result.changed),
122
124
  sampleChanged: changed.sample,
123
125
  hiddenChanged: changed.hidden,
@@ -127,6 +129,12 @@ export function buildGenerateJson(
127
129
  checkCommand: options.workspaceRoot
128
130
  ? forgeCliCommandsForWorkspace(options.workspaceRoot, ["forge generate --check --json"])[0]
129
131
  : "forge generate --check --json",
132
+ sequentialCommands: options.workspaceRoot
133
+ ? forgeCliCommandsForWorkspace(options.workspaceRoot, [
134
+ "forge generate --json",
135
+ "forge generate --check --json",
136
+ ])
137
+ : ["forge generate --json", "forge generate --check --json"],
130
138
  }
131
139
  : null;
132
140
  return {
@@ -234,6 +242,7 @@ export function buildAddJson(
234
242
  "forge workos doctor --yes --json",
235
243
  "forge workos seed --file workos-seed.yml --dry-run --json",
236
244
  "forge workos seed --file workos-seed.yml --json",
245
+ "forge workos prove --file workos-seed.yml --json",
237
246
  "forge auth check --json",
238
247
  "forge auth prove --json",
239
248
  ]