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.
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +19 -0
- package/docs/changelog.md +40 -1
- package/package.json +1 -1
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-adapters/index.ts +28 -2
- package/src/forge/cli/auth.ts +74 -4
- package/src/forge/cli/changed.ts +38 -11
- package/src/forge/cli/commands.ts +71 -25
- package/src/forge/cli/db.ts +9 -8
- package/src/forge/cli/dev.ts +137 -56
- package/src/forge/cli/doctor.ts +3 -2
- package/src/forge/cli/handoff.ts +50 -3
- package/src/forge/cli/main.ts +3 -1
- package/src/forge/cli/new.ts +130 -12
- package/src/forge/cli/output.ts +94 -12
- package/src/forge/cli/parse.ts +13 -1
- package/src/forge/cli/studio.ts +15 -14
- package/src/forge/cli/windows.ts +2 -1
- package/src/forge/cli/workos.ts +5 -4
- package/src/forge/compiler/agent-contract/build.ts +3 -3
- package/src/forge/compiler/data-graph/sql/ddl.ts +11 -1
- package/src/forge/compiler/integration/templates/render.ts +1 -0
- package/src/forge/compiler/integration/templates/workos.ts +5 -5
- package/src/forge/compiler/make-registry/build.ts +2 -2
- package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
- package/src/forge/compiler/recipes/definitions.ts +1 -1
- package/src/forge/delta/status.ts +79 -12
- package/src/forge/dev/server.ts +4 -4
- package/src/forge/dev-console/cycle.ts +55 -20
- package/src/forge/dev-console/types.ts +1 -0
- package/src/forge/make/fields.ts +26 -0
- package/src/forge/make/index.ts +6 -1
- package/src/forge/runtime/db/memory-adapter.ts +139 -32
- package/src/forge/ui/index.ts +129 -0
- package/src/forge/version.ts +1 -1
- package/src/forge/workspace/change-summary.ts +7 -1
- package/src/forge/workspace/forge-cli.ts +29 -1
package/src/forge/cli/db.ts
CHANGED
|
@@ -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> {
|
package/src/forge/cli/dev.ts
CHANGED
|
@@ -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:
|
|
687
|
-
checkCommand:
|
|
762
|
+
command: cycle.summary.generated.command,
|
|
763
|
+
checkCommand: cycle.summary.generated.checkCommand,
|
|
688
764
|
};
|
|
689
765
|
}
|
|
690
766
|
|
|
691
|
-
export function generatedEvidenceFromGenerateResult(
|
|
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:
|
|
704
|
-
|
|
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
|
|
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
|
-
:
|
|
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
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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`);
|
package/src/forge/cli/doctor.ts
CHANGED
|
@@ -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 {
|
package/src/forge/cli/handoff.ts
CHANGED
|
@@ -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
|
-
...(
|
|
189
|
-
|
|
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
|
-
|
|
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}`);
|
package/src/forge/cli/main.ts
CHANGED
|
@@ -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
|
|
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",
|