forgeos 0.1.0-alpha.31 → 0.1.0-alpha.32

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 (39) hide show
  1. package/AGENTS.md +3 -3
  2. package/CHANGELOG.md +19 -0
  3. package/docs/changelog.md +40 -1
  4. package/package.json +1 -1
  5. package/src/forge/_generated/releaseManifest.json +1 -1
  6. package/src/forge/_generated/releaseManifest.ts +3 -3
  7. package/src/forge/agent-adapters/index.ts +28 -2
  8. package/src/forge/cli/auth.ts +74 -4
  9. package/src/forge/cli/changed.ts +38 -11
  10. package/src/forge/cli/commands.ts +71 -25
  11. package/src/forge/cli/db.ts +9 -8
  12. package/src/forge/cli/dev.ts +137 -56
  13. package/src/forge/cli/doctor.ts +3 -2
  14. package/src/forge/cli/handoff.ts +50 -3
  15. package/src/forge/cli/main.ts +3 -1
  16. package/src/forge/cli/new.ts +130 -12
  17. package/src/forge/cli/output.ts +94 -12
  18. package/src/forge/cli/parse.ts +13 -1
  19. package/src/forge/cli/studio.ts +15 -14
  20. package/src/forge/cli/windows.ts +2 -1
  21. package/src/forge/cli/workos.ts +5 -4
  22. package/src/forge/compiler/agent-contract/build.ts +3 -3
  23. package/src/forge/compiler/data-graph/sql/ddl.ts +11 -1
  24. package/src/forge/compiler/integration/templates/render.ts +1 -0
  25. package/src/forge/compiler/integration/templates/workos.ts +5 -5
  26. package/src/forge/compiler/make-registry/build.ts +2 -2
  27. package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
  28. package/src/forge/compiler/recipes/definitions.ts +1 -1
  29. package/src/forge/delta/status.ts +79 -12
  30. package/src/forge/dev/server.ts +4 -4
  31. package/src/forge/dev-console/cycle.ts +55 -20
  32. package/src/forge/dev-console/types.ts +1 -0
  33. package/src/forge/make/fields.ts +26 -0
  34. package/src/forge/make/index.ts +6 -1
  35. package/src/forge/runtime/db/memory-adapter.ts +139 -32
  36. package/src/forge/ui/index.ts +129 -0
  37. package/src/forge/version.ts +1 -1
  38. package/src/forge/workspace/change-summary.ts +7 -1
  39. package/src/forge/workspace/forge-cli.ts +29 -1
@@ -30,6 +30,7 @@ import {
30
30
  DEFAULT_PGLITE_DIR,
31
31
  repairLocalPgliteStore,
32
32
  } from "../runtime/db/pglite-adapter.ts";
33
+ import { normalizeForgeCliCommandsInValue } from "../workspace/forge-cli.ts";
33
34
 
34
35
  export type DbSubcommand = "diff" | "migrate" | "reset" | "status" | "doctor" | "repair" | "rls-check";
35
36
 
@@ -258,7 +259,7 @@ async function runDbDoctor(
258
259
  .sort();
259
260
  const ok = inspected.diagnostics.length === 0 && missingTables.length === 0 && missingColumns.length === 0;
260
261
 
261
- return {
262
+ return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
262
263
  ok,
263
264
  data: {
264
265
  schemaVersion: "0.1.0",
@@ -287,7 +288,7 @@ async function runDbDoctor(
287
288
  ...inspected.diagnostics,
288
289
  ],
289
290
  exitCode: ok ? 0 : 1,
290
- };
291
+ });
291
292
  } finally {
292
293
  await adapter.close();
293
294
  }
@@ -295,7 +296,7 @@ async function runDbDoctor(
295
296
 
296
297
  async function runDbRepair(options: DbCommandOptions): Promise<DbCommandResult> {
297
298
  if (!options.local) {
298
- return {
299
+ return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
299
300
  ok: false,
300
301
  data: {
301
302
  schemaVersion: "0.1.0",
@@ -314,11 +315,11 @@ async function runDbRepair(options: DbCommandOptions): Promise<DbCommandResult>
314
315
  }),
315
316
  ],
316
317
  exitCode: 1,
317
- };
318
+ });
318
319
  }
319
320
 
320
321
  if (options.db !== "pglite") {
321
- return {
322
+ return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
322
323
  ok: false,
323
324
  data: {
324
325
  schemaVersion: "0.1.0",
@@ -336,7 +337,7 @@ async function runDbRepair(options: DbCommandOptions): Promise<DbCommandResult>
336
337
  }),
337
338
  ],
338
339
  exitCode: 1,
339
- };
340
+ });
340
341
  }
341
342
 
342
343
  const dataDir = join(options.workspaceRoot, DEFAULT_PGLITE_DIR);
@@ -355,7 +356,7 @@ async function runDbRepair(options: DbCommandOptions): Promise<DbCommandResult>
355
356
  }),
356
357
  ];
357
358
 
358
- return {
359
+ return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
359
360
  ok: result.ok,
360
361
  data: {
361
362
  schemaVersion: "0.1.0",
@@ -365,7 +366,7 @@ async function runDbRepair(options: DbCommandOptions): Promise<DbCommandResult>
365
366
  },
366
367
  diagnostics,
367
368
  exitCode: result.ok ? 0 : 1,
368
- };
369
+ });
369
370
  }
370
371
 
371
372
  export async function runDbCommand(options: DbCommandOptions): Promise<DbCommandResult> {
@@ -31,6 +31,7 @@ import { createAmbientDeltaRecorder } from "../delta/index.ts";
31
31
  import { resetCompileSessions } from "../compiler/orchestrator/session.ts";
32
32
  import { FORGE_PGLITE_STORE_ABORTED } from "../compiler/diagnostics/codes.ts";
33
33
  import { isPgliteAbortMessage } from "../runtime/db/pglite-adapter.ts";
34
+ import { forgeCliCommandForWorkspace, forgeCliCommandsForWorkspace } from "../workspace/forge-cli.ts";
34
35
  import { writeLastRunRecord } from "./last-run.ts";
35
36
 
36
37
  export interface DevCommandOptions {
@@ -95,6 +96,7 @@ interface DevStartupSummary {
95
96
  schemaVersion: "0.1.0";
96
97
  ok: true;
97
98
  mode: "dev";
99
+ warnings: Array<{ code: string; message: string; nextAction?: string }>;
98
100
  api: {
99
101
  url: string;
100
102
  host: string;
@@ -289,6 +291,63 @@ function classifyDevStartFailure(input: {
289
291
  };
290
292
  }
291
293
 
294
+ function writeDevStartFailure(input: {
295
+ workspaceRoot: string;
296
+ startedAt: Date;
297
+ finishedAt: Date;
298
+ options: DevCommandOptions;
299
+ host: string;
300
+ port: number;
301
+ failure: DevStartFailure;
302
+ }): DevCommandResult {
303
+ const nextActions = forgeCliCommandsForWorkspace(input.workspaceRoot, input.failure.nextActions);
304
+ const busy = input.failure.busy
305
+ ? {
306
+ ...input.failure.busy,
307
+ suggestedCommands: forgeCliCommandsForWorkspace(input.workspaceRoot, input.failure.busy.suggestedCommands),
308
+ }
309
+ : undefined;
310
+ writeLastRunRecord(input.workspaceRoot, {
311
+ schemaVersion: "0.1.0",
312
+ command: forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev"),
313
+ ok: false,
314
+ startedAt: input.startedAt.toISOString(),
315
+ finishedAt: input.finishedAt.toISOString(),
316
+ durationMs: input.finishedAt.getTime() - input.startedAt.getTime(),
317
+ ...(input.failure.code ? { code: input.failure.code } : {}),
318
+ failureKind: input.failure.failureKind,
319
+ message: input.failure.message,
320
+ nextActions,
321
+ details: {
322
+ db: input.options.db,
323
+ host: input.host,
324
+ port: input.port,
325
+ },
326
+ });
327
+ if (input.options.json) {
328
+ process.stdout.write(
329
+ `${JSON.stringify({
330
+ ok: false,
331
+ error: input.failure.message,
332
+ code: input.failure.code,
333
+ failureKind: input.failure.failureKind,
334
+ busy,
335
+ nextActions,
336
+ exitCode: 1,
337
+ })}\n`,
338
+ );
339
+ } else {
340
+ console.error(`error${input.failure.code ? ` ${input.failure.code}` : ""}: ${input.failure.message}`);
341
+ if (nextActions.length > 0) {
342
+ console.error("next:");
343
+ for (const action of nextActions) {
344
+ console.error(` ${action}`);
345
+ }
346
+ }
347
+ }
348
+ return { exitCode: 1 };
349
+ }
350
+
292
351
  export async function resolveAvailableWebPort(input: {
293
352
  host: string;
294
353
  preferredPort: number;
@@ -493,6 +552,16 @@ function buildStartupSummary(input: {
493
552
  ? [...new Set(frontend.clientBindings.map((binding) => `${binding.kind}:${binding.name}`))].sort()
494
553
  : [];
495
554
  const browserUrl = input.web?.url ?? input.handle.url;
555
+ const warnings = input.handle.state.db.kind === "memory"
556
+ ? [
557
+ {
558
+ code: "FORGE_DEV_MEMORY_DB_FIDELITY",
559
+ message:
560
+ "Memory DB is fast and non-persistent; PGlite/Postgres remain the authoritative checks for full SQL constraints and adapter fidelity.",
561
+ nextAction: `rerun with ${forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev --db pglite --once --json")} before treating the result as database-realistic`,
562
+ },
563
+ ]
564
+ : [];
496
565
  const preview = previewSummaryFor({
497
566
  workspaceRoot: input.workspaceRoot,
498
567
  host: input.handle.host,
@@ -502,6 +571,7 @@ function buildStartupSummary(input: {
502
571
  schemaVersion: "0.1.0",
503
572
  ok: true,
504
573
  mode: "dev",
574
+ warnings,
505
575
  api: {
506
576
  url: input.handle.url,
507
577
  host: input.handle.host,
@@ -553,15 +623,15 @@ function buildStartupSummary(input: {
553
623
  changedFiles: input.generated?.changedFiles ?? 0,
554
624
  sampleChanged: input.generated?.sampleChanged ?? [],
555
625
  hiddenChanged: input.generated?.hiddenChanged ?? 0,
556
- command: input.generated?.command ?? "forge generate",
557
- checkCommand: input.generated?.checkCommand ?? "forge generate --check --json",
626
+ command: input.generated?.command ?? forgeCliCommandForWorkspace(input.workspaceRoot, "forge generate"),
627
+ checkCommand: input.generated?.checkCommand ?? forgeCliCommandForWorkspace(input.workspaceRoot, "forge generate --check --json"),
558
628
  message: input.generated?.message ?? (buildInfoRaw ? "generated artifacts are loaded" : "generated build info is missing"),
559
629
  },
560
630
  next: {
561
631
  browserUrl,
562
632
  apiIndex: input.handle.url,
563
- inspect: "forge inspect summary --json",
564
- verify: "forge dev --once --json",
633
+ inspect: forgeCliCommandForWorkspace(input.workspaceRoot, "forge inspect summary --json"),
634
+ verify: forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev --once --json"),
565
635
  },
566
636
  pid: process.pid,
567
637
  };
@@ -582,6 +652,12 @@ function printStartupHuman(summary: DevStartupSummary): void {
582
652
  lines.push(` Generated: ${summary.generated.state}${summary.generated.changedFiles > 0 ? ` (${summary.generated.changedFiles} changed)` : ""}${summary.generated.runtimeStaleRisk ? " (stale risk)" : ""}`);
583
653
  lines.push(` Generated note: ${summary.generated.message}`);
584
654
  lines.push(` Generated check: ${summary.generated.checkCommand}`);
655
+ for (const warning of summary.warnings) {
656
+ lines.push(` Warning ${warning.code}: ${warning.message}`);
657
+ if (warning.nextAction) {
658
+ lines.push(` Next: ${warning.nextAction}`);
659
+ }
660
+ }
585
661
  lines.push("");
586
662
 
587
663
  lines.push("Web app");
@@ -683,12 +759,15 @@ export function generatedEvidenceFromCycle(cycle: DevConsoleCycle): DevStartupGe
683
759
  sampleChanged,
684
760
  hiddenChanged,
685
761
  message: phase?.message ?? "generated phase did not report a message",
686
- command: "forge generate",
687
- checkCommand: "forge generate --check --json",
762
+ command: cycle.summary.generated.command,
763
+ checkCommand: cycle.summary.generated.checkCommand,
688
764
  };
689
765
  }
690
766
 
691
- export function generatedEvidenceFromGenerateResult(result: DevGenerateResult): DevStartupGeneratedEvidence {
767
+ export function generatedEvidenceFromGenerateResult(
768
+ result: DevGenerateResult,
769
+ options: { workspaceRoot?: string } = {},
770
+ ): DevStartupGeneratedEvidence {
692
771
  return {
693
772
  ok: result.ok,
694
773
  state: result.ok ? result.changed.length > 0 ? "regenerated" : "fresh" : "stale-risk",
@@ -700,8 +779,12 @@ export function generatedEvidenceFromGenerateResult(result: DevGenerateResult):
700
779
  ? `regenerated ${result.changed.length} generated artifacts`
701
780
  : "generated artifacts are up to date"
702
781
  : "generated artifacts could not be regenerated",
703
- command: "forge generate",
704
- checkCommand: "forge generate --check --json",
782
+ command: options.workspaceRoot
783
+ ? forgeCliCommandForWorkspace(options.workspaceRoot, "forge generate")
784
+ : "forge generate",
785
+ checkCommand: options.workspaceRoot
786
+ ? forgeCliCommandForWorkspace(options.workspaceRoot, "forge generate --check --json")
787
+ : "forge generate --check --json",
705
788
  };
706
789
  }
707
790
 
@@ -725,16 +808,20 @@ export function buildDevWatchGenerateFailureEvent(input: {
725
808
  changedCount: number;
726
809
  changedPaths: string[];
727
810
  result: DevGenerateResult;
811
+ workspaceRoot?: string;
728
812
  }): DevWatchGenerateFailureEvent {
813
+ const nextActions = input.workspaceRoot
814
+ ? forgeCliCommandsForWorkspace(input.workspaceRoot, ["forge dev --once --json", "forge check --json"])
815
+ : ["forge dev --once --json", "forge check --json"];
729
816
  return {
730
817
  schemaVersion: "0.1.0",
731
818
  event: "dev.generate_failed",
732
819
  ok: false,
733
820
  changedFiles: input.changedCount,
734
821
  changedPaths: input.changedPaths,
735
- generated: generatedEvidenceFromGenerateResult(input.result),
822
+ generated: generatedEvidenceFromGenerateResult(input.result, { workspaceRoot: input.workspaceRoot }),
736
823
  diagnostics: input.result.diagnostics,
737
- nextActions: ["forge dev --once --json", "forge check --json"],
824
+ nextActions,
738
825
  };
739
826
  }
740
827
 
@@ -744,7 +831,11 @@ export function buildDevWatchReloadEvent(input: {
744
831
  generated: DevGenerateResult;
745
832
  reload: Awaited<ReturnType<DevServerHandle["reload"]>>;
746
833
  cycle: DevConsoleCycle;
834
+ workspaceRoot?: string;
747
835
  }): DevWatchReloadEvent {
836
+ const failureNextActions = input.workspaceRoot
837
+ ? forgeCliCommandsForWorkspace(input.workspaceRoot, ["forge dev --once --json", "forge check --json"])
838
+ : ["forge dev --once --json", "forge check --json"];
748
839
  return {
749
840
  schemaVersion: "0.1.0",
750
841
  event: "dev.reload",
@@ -756,12 +847,12 @@ export function buildDevWatchReloadEvent(input: {
756
847
  runtimeEntries: input.reload.runtimeEntries,
757
848
  worker: input.reload.worker,
758
849
  diagnostics: input.reload.diagnostics,
759
- generated: generatedEvidenceFromGenerateResult(input.generated),
850
+ generated: generatedEvidenceFromGenerateResult(input.generated, { workspaceRoot: input.workspaceRoot }),
760
851
  preview: input.cycle.summary.preview,
761
852
  agentContext: input.cycle.summary.agentContext,
762
853
  nextActions: input.reload.ok
763
854
  ? input.cycle.summary.agentContext.recommendedCommands
764
- : ["forge dev --once --json", "forge check --json"],
855
+ : failureNextActions,
765
856
  };
766
857
  }
767
858
 
@@ -784,6 +875,24 @@ export async function runDevCommand(
784
875
 
785
876
  const host = resolveDevHost(options.host);
786
877
  const port = resolveDevPort(options.port);
878
+ if (!options.webOnly && !(await isPortAvailable(host, port))) {
879
+ const failure = classifyDevStartFailure({
880
+ rawMessage: `listen EADDRINUSE: address already in use ${host}:${port}`,
881
+ host,
882
+ port,
883
+ webPort: options.webPort,
884
+ db: options.db,
885
+ });
886
+ return writeDevStartFailure({
887
+ workspaceRoot,
888
+ startedAt,
889
+ finishedAt: new Date(),
890
+ options,
891
+ host,
892
+ port,
893
+ failure,
894
+ });
895
+ }
787
896
  const requestedWebPort = options.webPort ?? detectDefaultWebPort(workspaceRoot);
788
897
  const shouldStartWeb =
789
898
  options.webOnly === true ||
@@ -913,45 +1022,15 @@ export async function runDevCommand(
913
1022
  webPort: options.webPort,
914
1023
  db: options.db,
915
1024
  });
916
- writeLastRunRecord(workspaceRoot, {
917
- schemaVersion: "0.1.0",
918
- command: "forge dev",
919
- ok: false,
920
- startedAt: startedAt.toISOString(),
921
- finishedAt: finishedAt.toISOString(),
922
- durationMs: finishedAt.getTime() - startedAt.getTime(),
923
- ...(failure.code ? { code: failure.code } : {}),
924
- failureKind: failure.failureKind,
925
- message: failure.message,
926
- nextActions: failure.nextActions,
927
- details: {
928
- db: options.db,
929
- host,
930
- port,
931
- },
1025
+ return writeDevStartFailure({
1026
+ workspaceRoot,
1027
+ startedAt,
1028
+ finishedAt,
1029
+ options,
1030
+ host,
1031
+ port,
1032
+ failure,
932
1033
  });
933
- if (options.json) {
934
- process.stdout.write(
935
- `${JSON.stringify({
936
- ok: false,
937
- error: failure.message,
938
- code: failure.code,
939
- failureKind: failure.failureKind,
940
- busy: failure.busy,
941
- nextActions: failure.nextActions,
942
- exitCode: 1,
943
- })}\n`,
944
- );
945
- } else {
946
- console.error(`error${failure.code ? ` ${failure.code}` : ""}: ${failure.message}`);
947
- if (failure.nextActions.length > 0) {
948
- console.error("next:");
949
- for (const action of failure.nextActions) {
950
- console.error(` ${action}`);
951
- }
952
- }
953
- }
954
- return { exitCode: 1 };
955
1034
  }
956
1035
 
957
1036
  const webHandle = !shouldStartWeb
@@ -990,11 +1069,11 @@ export async function runDevCommand(
990
1069
  ok: true,
991
1070
  startedAt: startedAt.toISOString(),
992
1071
  finishedAt: finishedAt.toISOString(),
993
- durationMs: finishedAt.getTime() - startedAt.getTime(),
994
- nextActions: [
995
- webHandle?.url ?? handle.url,
996
- "forge last --json",
997
- ],
1072
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
1073
+ nextActions: [
1074
+ webHandle?.url ?? handle.url,
1075
+ forgeCliCommandForWorkspace(workspaceRoot, "forge last --json"),
1076
+ ],
998
1077
  details: {
999
1078
  db: handle.state.db.kind,
1000
1079
  apiUrl: handle.url,
@@ -1059,6 +1138,7 @@ export async function runDevCommand(
1059
1138
  },
1060
1139
  reload,
1061
1140
  cycle,
1141
+ workspaceRoot,
1062
1142
  }))}\n`);
1063
1143
  }
1064
1144
  if (!reload.ok && !options.json) {
@@ -1083,6 +1163,7 @@ export async function runDevCommand(
1083
1163
  diagnostics: [...result.errors, ...result.warnings],
1084
1164
  exitCode: 1,
1085
1165
  },
1166
+ workspaceRoot,
1086
1167
  }))}\n`);
1087
1168
  } else {
1088
1169
  console.error(`[forge dev] regeneration failed after ${changedCount} changed files; runtime was not reloaded`);
@@ -11,6 +11,7 @@ import {
11
11
  inspectPgliteStore,
12
12
  type PgliteStoreInspection,
13
13
  } from "../runtime/db/pglite-adapter.ts";
14
+ import { normalizeForgeCliCommandsInValue } from "../workspace/forge-cli.ts";
14
15
 
15
16
  export interface DoctorCheck {
16
17
  name: string;
@@ -241,13 +242,13 @@ export async function runPgliteDoctorCommand(options: {
241
242
  }
242
243
 
243
244
  const exitOk = checks.every((check) => check.ok || check.severity === "warning");
244
- return {
245
+ return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
245
246
  ok: exitOk,
246
247
  inspection,
247
248
  checks,
248
249
  nextActions: inspection.nextActions,
249
250
  exitCode: exitOk ? 0 : 1,
250
- };
251
+ });
251
252
  }
252
253
 
253
254
  export function formatDoctorJson(result: DoctorResult): string {
@@ -6,6 +6,7 @@ import type { DevConsoleCycle, DevConsolePhase } from "../dev-console/types.ts";
6
6
  import type { TestRunRecord } from "../impact/types.ts";
7
7
  import type { UiRunReport } from "../ui/types.ts";
8
8
  import { summarizeChangeTypes, type CategorizedFileSummary } from "../workspace/change-summary.ts";
9
+ import { forgeCliCommandsForWorkspace } from "../workspace/forge-cli.ts";
9
10
  import { buildWorkspaceGitSummary } from "../workspace/git-summary.ts";
10
11
 
11
12
  export interface HandoffCommandOptions {
@@ -92,6 +93,14 @@ export interface HandoffCommandResult {
92
93
  recommendedCommands: string[];
93
94
  risks: string[];
94
95
  };
96
+ diagnosticSummary: {
97
+ total: number;
98
+ sample: Diagnostic[];
99
+ hidden: number;
100
+ bySeverity: Record<string, number>;
101
+ byCode: Record<string, number>;
102
+ fullDiagnosticsCommands: string[];
103
+ };
95
104
  diagnostics: Diagnostic[];
96
105
  nextActions: string[];
97
106
  exitCode: 0 | 1;
@@ -162,11 +171,37 @@ function buildOpeningBrief(input: {
162
171
  ].join(" ");
163
172
  }
164
173
 
174
+ function compactDiagnostics(
175
+ workspaceRoot: string,
176
+ diagnostics: Diagnostic[],
177
+ sampleSize = 8,
178
+ ): HandoffCommandResult["diagnosticSummary"] {
179
+ const bySeverity: Record<string, number> = {};
180
+ const byCode: Record<string, number> = {};
181
+ for (const diagnostic of diagnostics) {
182
+ bySeverity[diagnostic.severity] = (bySeverity[diagnostic.severity] ?? 0) + 1;
183
+ byCode[diagnostic.code] = (byCode[diagnostic.code] ?? 0) + 1;
184
+ }
185
+ return {
186
+ total: diagnostics.length,
187
+ sample: diagnostics.slice(0, sampleSize),
188
+ hidden: Math.max(0, diagnostics.length - sampleSize),
189
+ bySeverity,
190
+ byCode,
191
+ fullDiagnosticsCommands: forgeCliCommandsForWorkspace(workspaceRoot, [
192
+ "forge dev --once --json",
193
+ "forge inspect all --full --json",
194
+ "forge generate --check --json",
195
+ ]),
196
+ };
197
+ }
198
+
165
199
  export async function runHandoffCommand(options: HandoffCommandOptions): Promise<HandoffCommandResult> {
166
200
  const workspaceRoot = options.workspaceRoot.replace(/\\/g, "/");
167
201
  const dev = await runDevConsoleCycle({
168
202
  workspaceRoot,
169
203
  mode: "once",
204
+ generatedMode: "check",
170
205
  includeImpact: true,
171
206
  });
172
207
  const git = buildWorkspaceGitSummary(workspaceRoot);
@@ -185,14 +220,17 @@ export async function runHandoffCommand(options: HandoffCommandOptions): Promise
185
220
  ];
186
221
  const nextActions = [
187
222
  ...agent.recommendedCommands,
188
- ...(git.changed.count > 0 ? ["forge review run --changed --json"] : []),
189
- "forge handoff --json",
223
+ ...forgeCliCommandsForWorkspace(workspaceRoot, [
224
+ ...(git.changed.count > 0 ? ["forge review run --changed --json"] : []),
225
+ "forge handoff --json",
226
+ ]),
190
227
  ];
191
228
  const ok = dev.ok &&
192
229
  agent.blockingIssues.length === 0 &&
193
230
  (!recentRuns.test || recentRuns.test.ok) &&
194
231
  (!recentRuns.ui || recentRuns.ui.ok);
195
232
  const changedFiles = Math.max(agent.changedFiles, git.changed.count);
233
+ const diagnosticSummary = compactDiagnostics(workspaceRoot, dev.diagnostics);
196
234
 
197
235
  return {
198
236
  schemaVersion: "0.1.0",
@@ -231,7 +269,8 @@ export async function runHandoffCommand(options: HandoffCommandOptions): Promise
231
269
  recommendedCommands: [...new Set(nextActions)].slice(0, 10),
232
270
  risks,
233
271
  },
234
- diagnostics: dev.diagnostics,
272
+ diagnosticSummary,
273
+ diagnostics: diagnosticSummary.sample,
235
274
  nextActions: [...new Set(nextActions)].slice(0, 10),
236
275
  exitCode: ok ? 0 : 1,
237
276
  };
@@ -256,6 +295,14 @@ export function formatHandoffHuman(result: HandoffCommandResult): string {
256
295
  lines.push("", "Risks:");
257
296
  lines.push(...result.nextAgent.risks.slice(0, 6).map((risk) => ` ${risk}`));
258
297
  }
298
+ if (result.diagnosticSummary.total > 0) {
299
+ lines.push(
300
+ "",
301
+ "Diagnostics:",
302
+ ` ${result.diagnosticSummary.total} total` +
303
+ (result.diagnosticSummary.hidden > 0 ? ` (${result.diagnosticSummary.hidden} hidden in compact handoff)` : ""),
304
+ );
305
+ }
259
306
  const changedTypes = summarizeChangeTypes(result.git.changeSummary.changed);
260
307
  if (changedTypes) {
261
308
  lines.push("", "Change types:", ` ${changedTypes}`);
@@ -14,6 +14,8 @@ function formatHelp(): string {
14
14
  " forge status --json Compact project health, handoff state, and next actions",
15
15
  " forge changed --json Group changed files into human, generated, and risk buckets",
16
16
  " forge changed --authored --json Show only authored changed files, excluding generated artifacts",
17
+ " forge changed --review --json Show review-focused app/config/docs changes, excluding local agent/browser artifacts",
18
+ " forge new my-app --template minimal-web --field-test Create an installed WorkOS/auth.md field-test app",
17
19
  " forge diff authored Run the authored-only git diff pathspec",
18
20
  " forge handoff --json Compact work handoff for the next external code agent",
19
21
  " forge agent onboard --target codex --json Prepare adapter, hooks, memory, and dev snapshot",
@@ -28,7 +30,7 @@ function formatHelp(): string {
28
30
  " forge workos install --yes --json Delegate AuthKit setup to npx --yes workos@latest install",
29
31
  " forge workos doctor --json Check WorkOS AuthKit/FGA files, claims, seed, webhook, and tenant guards",
30
32
  " forge workos doctor --yes --json Run local checks, then delegate to npx --yes workos@latest doctor",
31
- " forge workos seed --file src/forge/_generated/integrations/workos/workos-seed.yml --json Plan WorkOS CLI seed",
33
+ " forge workos seed --file workos-seed.yml --json Plan WorkOS CLI seed",
32
34
  " forge release check --allow-missing-local-release --json Gate release readiness without failing on unprepared local artifacts",
33
35
  " forge self-host check --prepared-only --json Report compose readiness without creating deploy files",
34
36
  " forge delta status --verbose --json Include Delta schema, lock, and aggregate count details",