forgeos 0.1.0-alpha.30 → 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 +30 -0
- package/docs/changelog.md +55 -0
- 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 +134 -9
- package/src/forge/cli/baseline.ts +112 -0
- package/src/forge/cli/changed.ts +52 -15
- package/src/forge/cli/commands.ts +117 -31
- package/src/forge/cli/db.ts +92 -4
- package/src/forge/cli/dev.ts +229 -44
- package/src/forge/cli/doctor.ts +81 -0
- package/src/forge/cli/handoff.ts +63 -5
- package/src/forge/cli/last-run.ts +84 -0
- 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 +72 -10
- package/src/forge/cli/progress.ts +51 -0
- package/src/forge/cli/studio.ts +15 -14
- package/src/forge/cli/verify.ts +31 -0
- package/src/forge/cli/windows.ts +23 -0
- 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/diagnostics/codes.ts +4 -0
- 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/factory.ts +1 -3
- package/src/forge/runtime/db/memory-adapter.ts +139 -32
- package/src/forge/runtime/db/pglite-adapter.ts +188 -2
- package/src/forge/ui/index.ts +174 -0
- package/src/forge/ui/types.ts +1 -0
- package/src/forge/version.ts +1 -1
- package/src/forge/workspace/baseline.ts +112 -0
- package/src/forge/workspace/change-summary.ts +8 -1
- package/src/forge/workspace/forge-cli.ts +29 -1
- package/src/forge/workspace/git-summary.ts +65 -2
package/src/forge/cli/changed.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
filterCategorizedSummary,
|
|
5
5
|
summarizeChangeTypes,
|
|
6
6
|
} from "../workspace/change-summary.ts";
|
|
7
|
+
import { forgeCliCommandsForWorkspace } from "../workspace/forge-cli.ts";
|
|
7
8
|
import { buildWorkspaceGitSummary, type WorkspaceGitSummary } from "../workspace/git-summary.ts";
|
|
8
9
|
|
|
9
10
|
export interface ChangedCommandResult {
|
|
@@ -44,6 +45,7 @@ interface GeneratedChangeExplanation {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
const AUTHORED_CHANGE_TYPES = ["source", "tests", "docs", "config", "assets", "other"] as const;
|
|
48
|
+
const REVIEW_CHANGE_TYPES = ["source", "tests", "docs", "config", "assets"] as const;
|
|
47
49
|
|
|
48
50
|
function emptyCategory(summary: CategorizedFileSummary, category: keyof CategorizedFileSummary["byType"]): boolean {
|
|
49
51
|
return summary.byType[category].count === 0;
|
|
@@ -87,10 +89,12 @@ function selectDerivedChangeSummary(summary: CategorizedFileSummary): DerivedCha
|
|
|
87
89
|
function buildRisks(git: WorkspaceGitSummary): string[] {
|
|
88
90
|
const risks: string[] = [];
|
|
89
91
|
const changed = git.changeSummary.changed;
|
|
90
|
-
if (!git.available) {
|
|
92
|
+
if (!git.available && git.source === "forge-baseline") {
|
|
93
|
+
risks.push("git status is unavailable; using Forge workspace baseline for non-git change tracking");
|
|
94
|
+
} else if (!git.available) {
|
|
91
95
|
risks.push("git status is unavailable; using filesystem inventory as untracked-file analysis");
|
|
92
96
|
}
|
|
93
|
-
if (git.untracked.count > 0) {
|
|
97
|
+
if (git.untracked.count > 0 && git.source !== "forge-baseline") {
|
|
94
98
|
risks.push(`${git.untracked.count} untracked file(s) are not in git history`);
|
|
95
99
|
}
|
|
96
100
|
if (changed.byType.other.count > 0) {
|
|
@@ -100,15 +104,24 @@ function buildRisks(git: WorkspaceGitSummary): string[] {
|
|
|
100
104
|
if (!emptyCategory(changed, "generated") && humanChanges.total === 0) {
|
|
101
105
|
risks.push("only generated artifacts changed; verify the source edit, generator input, or intentional regeneration that produced them");
|
|
102
106
|
}
|
|
107
|
+
return risks;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildAdvisories(git: WorkspaceGitSummary): string[] {
|
|
111
|
+
const advisories: string[] = [];
|
|
112
|
+
const changed = git.changeSummary.changed;
|
|
103
113
|
if (changed.total.count > 50) {
|
|
104
|
-
|
|
114
|
+
advisories.push(`${changed.total.count} changed file(s) detected; use grouped summaries before reviewing raw diffs`);
|
|
105
115
|
}
|
|
106
|
-
return
|
|
116
|
+
return advisories;
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
function buildRecommendedCommands(git: WorkspaceGitSummary): string[] {
|
|
120
|
+
if (!git.available && git.source === "forge-baseline") {
|
|
121
|
+
return ["forge handoff --json", "forge test plan --changed --json", "forge verify --changed", "git init"];
|
|
122
|
+
}
|
|
110
123
|
if (!git.available) {
|
|
111
|
-
return ["forge status --json", "forge handoff --json", "git init"];
|
|
124
|
+
return ["forge baseline create --reason initial-scaffold --json", "forge status --json", "forge handoff --json", "git init"];
|
|
112
125
|
}
|
|
113
126
|
if (git.changeSummary.changed.total.count === 0) {
|
|
114
127
|
return ["forge status --json", "forge dev --once --json"];
|
|
@@ -122,7 +135,12 @@ function buildRecommendedCommands(git: WorkspaceGitSummary): string[] {
|
|
|
122
135
|
"forge generate --check --json",
|
|
123
136
|
];
|
|
124
137
|
}
|
|
138
|
+
const authoredGeneratedInputs =
|
|
139
|
+
changed.byType.source.count +
|
|
140
|
+
changed.byType.config.count +
|
|
141
|
+
changed.byType.operational.count;
|
|
125
142
|
return [
|
|
143
|
+
...(authoredGeneratedInputs > 0 ? ["forge generate --check --json"] : []),
|
|
126
144
|
"forge handoff --json",
|
|
127
145
|
"forge test plan --changed --json",
|
|
128
146
|
"forge verify --changed",
|
|
@@ -184,7 +202,7 @@ function buildGeneratedChangeExplanation(
|
|
|
184
202
|
};
|
|
185
203
|
}
|
|
186
204
|
|
|
187
|
-
export function runChangedCommand(workspaceRoot: string, options: { authoredOnly?: boolean } = {}): ChangedCommandResult {
|
|
205
|
+
export function runChangedCommand(workspaceRoot: string, options: { authoredOnly?: boolean; reviewOnly?: boolean } = {}): ChangedCommandResult {
|
|
188
206
|
const git = buildWorkspaceGitSummary(workspaceRoot);
|
|
189
207
|
const changed = git.changeSummary.changed;
|
|
190
208
|
const humanChanges = selectHumanChangeSummary(changed);
|
|
@@ -193,20 +211,29 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
193
211
|
const authoredStaged = filterCategorizedSummary(git.changeSummary.staged, [...AUTHORED_CHANGE_TYPES]);
|
|
194
212
|
const authoredUnstaged = filterCategorizedSummary(git.changeSummary.unstaged, [...AUTHORED_CHANGE_TYPES]);
|
|
195
213
|
const authoredUntracked = filterCategorizedSummary(git.changeSummary.untracked, [...AUTHORED_CHANGE_TYPES]);
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
214
|
+
const reviewChanged = filterCategorizedSummary(changed, [...REVIEW_CHANGE_TYPES]);
|
|
215
|
+
const reviewStaged = filterCategorizedSummary(git.changeSummary.staged, [...REVIEW_CHANGE_TYPES]);
|
|
216
|
+
const reviewUnstaged = filterCategorizedSummary(git.changeSummary.unstaged, [...REVIEW_CHANGE_TYPES]);
|
|
217
|
+
const reviewUntracked = filterCategorizedSummary(git.changeSummary.untracked, [...REVIEW_CHANGE_TYPES]);
|
|
218
|
+
const viewHumanChanges = options.reviewOnly
|
|
219
|
+
? selectHumanChangeSummary(reviewChanged)
|
|
220
|
+
: options.authoredOnly
|
|
221
|
+
? selectHumanChangeSummary(authoredChanged)
|
|
222
|
+
: humanChanges;
|
|
223
|
+
const viewChanged = options.reviewOnly ? reviewChanged : options.authoredOnly ? authoredChanged : changed;
|
|
224
|
+
const viewStaged = options.reviewOnly ? reviewStaged : options.authoredOnly ? authoredStaged : git.changeSummary.staged;
|
|
225
|
+
const viewUnstaged = options.reviewOnly ? reviewUnstaged : options.authoredOnly ? authoredUnstaged : git.changeSummary.unstaged;
|
|
226
|
+
const viewUntracked = options.reviewOnly ? reviewUntracked : options.authoredOnly ? authoredUntracked : git.changeSummary.untracked;
|
|
227
|
+
const viewDerivedChanges: DerivedChangeSummary = options.authoredOnly || options.reviewOnly
|
|
202
228
|
? { total: 0, generated: { count: 0, sample: [], hidden: 0 } }
|
|
203
229
|
: derivedChanges;
|
|
204
230
|
const risks = buildRisks(git);
|
|
205
|
-
const
|
|
231
|
+
const advisories = buildAdvisories(git);
|
|
232
|
+
const recommendedCommands = forgeCliCommandsForWorkspace(workspaceRoot, buildRecommendedCommands(git));
|
|
206
233
|
const reviewFocus = buildReviewFocus(viewHumanChanges, viewDerivedChanges);
|
|
207
234
|
const generatedExplanation = buildGeneratedChangeExplanation(viewHumanChanges, viewDerivedChanges);
|
|
208
235
|
const diffPlan: DiffPlan = buildDiffPlanFromChangeSummary(viewChanged);
|
|
209
|
-
const ok = git.available || git.source === "filesystem";
|
|
236
|
+
const ok = git.available || git.source === "filesystem" || git.source === "forge-baseline";
|
|
210
237
|
|
|
211
238
|
return {
|
|
212
239
|
ok,
|
|
@@ -216,7 +243,9 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
216
243
|
summary: {
|
|
217
244
|
branch: git.branch,
|
|
218
245
|
commit: git.commit,
|
|
219
|
-
|
|
246
|
+
workspaceMode: git.workspaceMode ?? (git.available ? "git" : "nonGit"),
|
|
247
|
+
tracking: git.tracking ?? git.source,
|
|
248
|
+
view: options.reviewOnly ? "review" : options.authoredOnly ? "authored" : "all",
|
|
220
249
|
changedFiles: viewChanged.total.count,
|
|
221
250
|
humanFiles: viewHumanChanges.total,
|
|
222
251
|
generatedFiles: viewDerivedChanges.total,
|
|
@@ -229,6 +258,9 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
229
258
|
git: {
|
|
230
259
|
available: git.available,
|
|
231
260
|
source: git.source,
|
|
261
|
+
workspaceMode: git.workspaceMode,
|
|
262
|
+
tracking: git.tracking,
|
|
263
|
+
baseline: git.baseline,
|
|
232
264
|
...(git.error ? { error: git.error } : {}),
|
|
233
265
|
branch: git.branch,
|
|
234
266
|
commit: git.commit,
|
|
@@ -243,6 +275,7 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
243
275
|
generatedExplanation,
|
|
244
276
|
diffPlan,
|
|
245
277
|
risks,
|
|
278
|
+
advisories,
|
|
246
279
|
recommendedCommands,
|
|
247
280
|
nextActions: recommendedCommands,
|
|
248
281
|
},
|
|
@@ -258,6 +291,7 @@ export function formatChangedHuman(result: ChangedCommandResult): string {
|
|
|
258
291
|
const generatedExplanation = result.data.generatedExplanation as { summary?: string } | undefined;
|
|
259
292
|
const diffPlan = result.data.diffPlan as { summary?: string; authoredDiffCommand?: string; generatedDiffCommand?: string; generatedCollapsedByDefault?: boolean } | undefined;
|
|
260
293
|
const risks = (result.data.risks as string[] | undefined) ?? [];
|
|
294
|
+
const advisories = (result.data.advisories as string[] | undefined) ?? [];
|
|
261
295
|
const nextActions = (result.data.nextActions as string[] | undefined) ?? [];
|
|
262
296
|
const lines = [
|
|
263
297
|
`Forge changed: ${result.ok ? "ready" : "git unavailable"}`,
|
|
@@ -300,6 +334,9 @@ export function formatChangedHuman(result: ChangedCommandResult): string {
|
|
|
300
334
|
if (risks.length > 0) {
|
|
301
335
|
lines.push("", "Risks:", ...risks.map((risk) => ` ${risk}`));
|
|
302
336
|
}
|
|
337
|
+
if (advisories.length > 0) {
|
|
338
|
+
lines.push("", "Notes:", ...advisories.map((advisory) => ` ${advisory}`));
|
|
339
|
+
}
|
|
303
340
|
|
|
304
341
|
lines.push("", "Next:", ...nextActions.map((command) => ` ${command}`));
|
|
305
342
|
return `${lines.join("\n")}\n`;
|
|
@@ -181,6 +181,9 @@ import {
|
|
|
181
181
|
import {
|
|
182
182
|
formatDoctorHuman,
|
|
183
183
|
formatDoctorJson,
|
|
184
|
+
formatPgliteDoctorHuman,
|
|
185
|
+
formatPgliteDoctorJson,
|
|
186
|
+
runPgliteDoctorCommand,
|
|
184
187
|
runDoctorCommand,
|
|
185
188
|
} from "./doctor.ts";
|
|
186
189
|
import {
|
|
@@ -202,6 +205,16 @@ import {
|
|
|
202
205
|
formatWorkOSJson,
|
|
203
206
|
runWorkOSCommand,
|
|
204
207
|
} from "./workos.ts";
|
|
208
|
+
import {
|
|
209
|
+
formatLastHuman,
|
|
210
|
+
formatLastJson,
|
|
211
|
+
runLastCommand,
|
|
212
|
+
} from "./last-run.ts";
|
|
213
|
+
import {
|
|
214
|
+
formatBaselineHuman,
|
|
215
|
+
formatBaselineJson,
|
|
216
|
+
runBaselineCommand,
|
|
217
|
+
} from "./baseline.ts";
|
|
205
218
|
import { formatRlsHuman, formatRlsJson, runRlsCommand } from "./rls.ts";
|
|
206
219
|
import {
|
|
207
220
|
formatSecurityHuman,
|
|
@@ -271,7 +284,9 @@ import { resolveAuthFromCli } from "../runtime/auth/resolve.ts";
|
|
|
271
284
|
import { getActiveDbAdapter } from "../runtime/executor.ts";
|
|
272
285
|
import { CLI_VERSION, FORGEOS_VERSION } from "../version.ts";
|
|
273
286
|
import type { CategorizedFileSummary } from "../workspace/change-summary.ts";
|
|
287
|
+
import { forgeCliCommandsForWorkspace } from "../workspace/forge-cli.ts";
|
|
274
288
|
import { buildWorkspaceGitSummary } from "../workspace/git-summary.ts";
|
|
289
|
+
import { startCommandHeartbeat } from "./progress.ts";
|
|
275
290
|
|
|
276
291
|
function readGeneratedJson<T>(workspaceRoot: string, relative: string): T | null {
|
|
277
292
|
const absolute = join(workspaceRoot, relative);
|
|
@@ -708,33 +723,69 @@ export function runStatusCommand(workspaceRoot: string): StatusCommandResult {
|
|
|
708
723
|
const driftClean = driftSummary.ok === true;
|
|
709
724
|
const ok = driftClean && generatedReady;
|
|
710
725
|
const handoffDefaultReady = handoffSummary.defaultReady === true;
|
|
726
|
+
const changed = gitSummary.changeSummary.changed;
|
|
727
|
+
const generatedGitFiles = changed.byType.generated.count;
|
|
728
|
+
const authoredGitFiles = changed.total.count - generatedGitFiles;
|
|
729
|
+
const authoredGeneratedInputs =
|
|
730
|
+
changed.byType.source.count +
|
|
731
|
+
changed.byType.config.count +
|
|
732
|
+
changed.byType.operational.count;
|
|
733
|
+
const generatedNeedsCheck = generatedReady && driftClean && generatedGitFiles === 0 && authoredGeneratedInputs > 0;
|
|
711
734
|
const generatedState = !generatedReady
|
|
712
735
|
? "missing-artifacts"
|
|
736
|
+
: generatedNeedsCheck
|
|
737
|
+
? "check-needed"
|
|
713
738
|
: driftClean
|
|
714
739
|
? "ready"
|
|
715
740
|
: "drift";
|
|
716
|
-
const
|
|
717
|
-
? ["forge dev", "forge generate --check --json"]
|
|
741
|
+
const generatedNextActionsRaw = generatedState === "ready"
|
|
742
|
+
? ["forge dev", "forge dev --once --json", "forge generate --check --json"]
|
|
743
|
+
: generatedState === "check-needed"
|
|
744
|
+
? ["forge generate --check --json", "forge handoff --json", "forge dev --once --json"]
|
|
718
745
|
: ["forge generate", "forge check --json", "forge inspect drift --json"];
|
|
719
|
-
const
|
|
720
|
-
const generatedGitFiles = changed.byType.generated.count;
|
|
721
|
-
const authoredGitFiles = changed.total.count - generatedGitFiles;
|
|
746
|
+
const generatedNextActions = forgeCliCommandsForWorkspace(workspaceRoot, generatedNextActionsRaw);
|
|
722
747
|
const generatedGitExplanation = generatedGitFiles === 0
|
|
723
|
-
?
|
|
748
|
+
? generatedNeedsCheck
|
|
749
|
+
? "git status has no generated artifact changes, but authored source/config changes mean freshness is unverified until forge generate --check runs"
|
|
750
|
+
: "git status has no generated artifact changes"
|
|
724
751
|
: authoredGitFiles === 0
|
|
725
752
|
? "forge generate --check can be clean while git shows generated artifacts changed: generated files match current workspace inputs but differ from HEAD"
|
|
726
753
|
: "git status includes generated artifacts alongside authored changes; review authored inputs first";
|
|
754
|
+
const [safeDevCommand, generatedCheckCommand, generatedRepairCommand] = forgeCliCommandsForWorkspace(workspaceRoot, [
|
|
755
|
+
"forge dev",
|
|
756
|
+
"forge generate --check --json",
|
|
757
|
+
"forge generate",
|
|
758
|
+
]);
|
|
727
759
|
const frontendPresent = summaryBlock.frontendPresent === true;
|
|
760
|
+
const [
|
|
761
|
+
studioOpenCommand,
|
|
762
|
+
studioAttachCommand,
|
|
763
|
+
studioSnapshotCommand,
|
|
764
|
+
studioWatchCommand,
|
|
765
|
+
studioBridgeCommand,
|
|
766
|
+
studioDoctorCommand,
|
|
767
|
+
studioStartTargetAppCommand,
|
|
768
|
+
studioProbeCommand,
|
|
769
|
+
] = forgeCliCommandsForWorkspace(workspaceRoot, [
|
|
770
|
+
"forge studio open . --preview-port 5174 --target codex --json",
|
|
771
|
+
"forge studio attach . --preview-port 5174 --target codex --json",
|
|
772
|
+
"forge studio snapshot . --preview-port 5174 --target codex --json",
|
|
773
|
+
"forge studio watch . --preview-port 5174 --target codex --json",
|
|
774
|
+
"forge studio bridge . --preview-port 5174 --target codex --studio-url http://127.0.0.1:3765 --json",
|
|
775
|
+
"forge studio doctor . --preview-port 5174 --target codex --json",
|
|
776
|
+
"forge dev --port 3766 --web-port 5174",
|
|
777
|
+
"forge dev --once --json",
|
|
778
|
+
]);
|
|
728
779
|
const studio = {
|
|
729
|
-
openCommand:
|
|
730
|
-
attachCommand:
|
|
731
|
-
snapshotCommand:
|
|
732
|
-
watchCommand:
|
|
733
|
-
bridgeCommand:
|
|
734
|
-
doctorCommand:
|
|
780
|
+
openCommand: studioOpenCommand,
|
|
781
|
+
attachCommand: studioAttachCommand,
|
|
782
|
+
snapshotCommand: studioSnapshotCommand,
|
|
783
|
+
watchCommand: studioWatchCommand,
|
|
784
|
+
bridgeCommand: studioBridgeCommand,
|
|
785
|
+
doctorCommand: studioDoctorCommand,
|
|
735
786
|
targetPreviewUrl: "http://127.0.0.1:5174",
|
|
736
|
-
startTargetAppCommand:
|
|
737
|
-
probeCommand:
|
|
787
|
+
startTargetAppCommand: studioStartTargetAppCommand,
|
|
788
|
+
probeCommand: studioProbeCommand,
|
|
738
789
|
useful: frontendPresent,
|
|
739
790
|
note: frontendPresent
|
|
740
791
|
? "Attach this app to Forge Studio as an external-agent workroom; Studio should preview the target app on 5174."
|
|
@@ -750,11 +801,13 @@ export function runStatusCommand(workspaceRoot: string): StatusCommandResult {
|
|
|
750
801
|
state: generatedState,
|
|
751
802
|
ready: generatedReady,
|
|
752
803
|
driftClean,
|
|
804
|
+
freshness: generatedNeedsCheck ? "unverified" : generatedState === "ready" ? "verified-or-unchanged" : "attention",
|
|
805
|
+
authoredGeneratedInputs,
|
|
753
806
|
missingArtifacts,
|
|
754
807
|
tableDrift,
|
|
755
|
-
safeDevCommand
|
|
756
|
-
checkCommand:
|
|
757
|
-
repairCommand:
|
|
808
|
+
safeDevCommand,
|
|
809
|
+
checkCommand: generatedCheckCommand,
|
|
810
|
+
repairCommand: generatedRepairCommand,
|
|
758
811
|
git: {
|
|
759
812
|
changedFiles: changed.total.count,
|
|
760
813
|
authoredFiles: authoredGitFiles,
|
|
@@ -795,8 +848,11 @@ export function runStatusCommand(workspaceRoot: string): StatusCommandResult {
|
|
|
795
848
|
},
|
|
796
849
|
},
|
|
797
850
|
git,
|
|
798
|
-
nextActions:
|
|
799
|
-
|
|
851
|
+
nextActions: forgeCliCommandsForWorkspace(
|
|
852
|
+
workspaceRoot,
|
|
853
|
+
ok
|
|
854
|
+
? [
|
|
855
|
+
...(generatedState === "check-needed" ? ["forge generate --check --json"] : []),
|
|
800
856
|
"forge handoff --json",
|
|
801
857
|
"forge changed --json",
|
|
802
858
|
"forge dev",
|
|
@@ -805,7 +861,7 @@ export function runStatusCommand(workspaceRoot: string): StatusCommandResult {
|
|
|
805
861
|
"forge inspect handoff --json",
|
|
806
862
|
"forge verify --changed",
|
|
807
863
|
]
|
|
808
|
-
|
|
864
|
+
: [
|
|
809
865
|
"forge generate",
|
|
810
866
|
"forge handoff --json",
|
|
811
867
|
"forge changed --json",
|
|
@@ -813,6 +869,7 @@ export function runStatusCommand(workspaceRoot: string): StatusCommandResult {
|
|
|
813
869
|
"forge inspect drift --json",
|
|
814
870
|
"forge agent prepare --target codex --json",
|
|
815
871
|
],
|
|
872
|
+
),
|
|
816
873
|
},
|
|
817
874
|
exitCode: ok ? 0 : 1,
|
|
818
875
|
};
|
|
@@ -1558,6 +1615,16 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
1558
1615
|
}
|
|
1559
1616
|
return 0;
|
|
1560
1617
|
}
|
|
1618
|
+
case "last": {
|
|
1619
|
+
const result = runLastCommand({ workspaceRoot: command.workspaceRoot });
|
|
1620
|
+
process.stdout.write(command.json ? formatLastJson(result) : formatLastHuman(result));
|
|
1621
|
+
return result.exitCode;
|
|
1622
|
+
}
|
|
1623
|
+
case "baseline": {
|
|
1624
|
+
const result = runBaselineCommand(command);
|
|
1625
|
+
process.stdout.write(command.json ? formatBaselineJson(result) : formatBaselineHuman(result));
|
|
1626
|
+
return result.exitCode;
|
|
1627
|
+
}
|
|
1561
1628
|
case "new": {
|
|
1562
1629
|
const result = await runNewCommand({
|
|
1563
1630
|
name: command.name,
|
|
@@ -1565,6 +1632,7 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
1565
1632
|
packageManager: command.packageManager,
|
|
1566
1633
|
install: command.install,
|
|
1567
1634
|
git: command.git,
|
|
1635
|
+
fieldTest: command.fieldTest,
|
|
1568
1636
|
forgePackageSpec: command.forgePackageSpec,
|
|
1569
1637
|
localForge: command.localForge,
|
|
1570
1638
|
workspaceRoot: command.workspaceRoot,
|
|
@@ -1627,7 +1695,7 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
1627
1695
|
concurrency: 4,
|
|
1628
1696
|
});
|
|
1629
1697
|
if (command.json) {
|
|
1630
|
-
process.stdout.write(formatJsonResult(buildGenerateJson(result)));
|
|
1698
|
+
process.stdout.write(formatJsonResult(buildGenerateJson(result, { workspaceRoot: command.workspaceRoot })));
|
|
1631
1699
|
} else {
|
|
1632
1700
|
process.stdout.write(formatAgentContractHuman(command.subcommand, result));
|
|
1633
1701
|
writeHumanGenerate(result);
|
|
@@ -1668,6 +1736,11 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
1668
1736
|
process.stdout.write(command.json ? formatDeltaDoctorJson(result) : formatDeltaDoctorHuman(result));
|
|
1669
1737
|
return result.exitCode;
|
|
1670
1738
|
}
|
|
1739
|
+
if (command.target === "pglite") {
|
|
1740
|
+
const result = await runPgliteDoctorCommand({ workspaceRoot: command.workspaceRoot });
|
|
1741
|
+
process.stdout.write(command.json ? formatPgliteDoctorJson(result) : formatPgliteDoctorHuman(result));
|
|
1742
|
+
return result.exitCode;
|
|
1743
|
+
}
|
|
1671
1744
|
const result = await runDoctorCommand({ workspaceRoot: command.workspaceRoot });
|
|
1672
1745
|
if (command.json) {
|
|
1673
1746
|
process.stdout.write(formatDoctorJson(result));
|
|
@@ -1930,13 +2003,23 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
1930
2003
|
return result.exitCode;
|
|
1931
2004
|
}
|
|
1932
2005
|
case "agent": {
|
|
1933
|
-
const
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
2006
|
+
const heartbeat = command.options.subcommand === "onboard"
|
|
2007
|
+
? startCommandHeartbeat({
|
|
2008
|
+
label: `agent onboard ${command.options.target ?? "codex"}`,
|
|
2009
|
+
initialPhase: "prepare-hooks-memory-and-dev-snapshot",
|
|
2010
|
+
})
|
|
2011
|
+
: null;
|
|
2012
|
+
try {
|
|
2013
|
+
const result = await runAgentCommand(command.options);
|
|
2014
|
+
if (command.options.json) {
|
|
2015
|
+
process.stdout.write(formatAgentJson(result));
|
|
2016
|
+
} else {
|
|
2017
|
+
process.stdout.write(formatAgentHuman(result));
|
|
2018
|
+
}
|
|
2019
|
+
return result.exitCode;
|
|
2020
|
+
} finally {
|
|
2021
|
+
heartbeat?.stop();
|
|
1938
2022
|
}
|
|
1939
|
-
return result.exitCode;
|
|
1940
2023
|
}
|
|
1941
2024
|
case "mcp": {
|
|
1942
2025
|
return runMcpServe(command.workspaceRoot);
|
|
@@ -1976,7 +2059,7 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
1976
2059
|
});
|
|
1977
2060
|
|
|
1978
2061
|
if (command.json) {
|
|
1979
|
-
process.stdout.write(formatJsonResult(buildGenerateJson(result)));
|
|
2062
|
+
process.stdout.write(formatJsonResult(buildGenerateJson(result, { workspaceRoot: command.workspaceRoot })));
|
|
1980
2063
|
} else {
|
|
1981
2064
|
writeHumanGenerate(result);
|
|
1982
2065
|
}
|
|
@@ -1986,7 +2069,7 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
1986
2069
|
case "add": {
|
|
1987
2070
|
const result = await runAddCommand(command.alias, command.options);
|
|
1988
2071
|
if (command.options.json) {
|
|
1989
|
-
process.stdout.write(formatJsonResult(buildAddJson(result)));
|
|
2072
|
+
process.stdout.write(formatJsonResult(buildAddJson(result, { workspaceRoot: command.options.workspaceRoot })));
|
|
1990
2073
|
} else {
|
|
1991
2074
|
writeHumanAdd(result);
|
|
1992
2075
|
}
|
|
@@ -2022,6 +2105,7 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
2022
2105
|
case "changed": {
|
|
2023
2106
|
const result = runChangedCommand(command.workspaceRoot, {
|
|
2024
2107
|
authoredOnly: command.authoredOnly,
|
|
2108
|
+
reviewOnly: command.reviewOnly,
|
|
2025
2109
|
});
|
|
2026
2110
|
if (command.json) {
|
|
2027
2111
|
process.stdout.write(formatJsonResult(result.data));
|
|
@@ -2132,11 +2216,12 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
2132
2216
|
return result.exitCode;
|
|
2133
2217
|
}
|
|
2134
2218
|
case "check": {
|
|
2135
|
-
const
|
|
2219
|
+
const checkWorkspaceRoot = process.cwd();
|
|
2220
|
+
const result = await runCheckCommand(checkWorkspaceRoot, {
|
|
2136
2221
|
strictSecrets: command.strictSecrets,
|
|
2137
2222
|
});
|
|
2138
2223
|
if (command.json) {
|
|
2139
|
-
process.stdout.write(formatJsonResult(buildCheckJson(result)));
|
|
2224
|
+
process.stdout.write(formatJsonResult(buildCheckJson(result, { workspaceRoot: checkWorkspaceRoot })));
|
|
2140
2225
|
} else {
|
|
2141
2226
|
writeHumanGenerate(result);
|
|
2142
2227
|
}
|
|
@@ -2286,6 +2371,7 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
2286
2371
|
workspaceRoot: command.workspaceRoot,
|
|
2287
2372
|
db: command.db,
|
|
2288
2373
|
databaseUrl: command.databaseUrl,
|
|
2374
|
+
local: command.local,
|
|
2289
2375
|
json: command.json,
|
|
2290
2376
|
});
|
|
2291
2377
|
|
package/src/forge/cli/db.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { buildAppGraph } from "../compiler/app-graph/build.ts";
|
|
|
7
7
|
import { createDiagnostic } from "../compiler/diagnostics/create.ts";
|
|
8
8
|
import {
|
|
9
9
|
FORGE_RLS_APPLY_FAILED,
|
|
10
|
+
FORGE_DB_ADAPTER_UNAVAILABLE,
|
|
11
|
+
FORGE_PGLITE_STORE_ABORTED,
|
|
12
|
+
FORGE_PGLITE_STORE_ACTIVE,
|
|
10
13
|
FORGE_RUNTIME_NOT_FOUND,
|
|
11
14
|
} from "../compiler/diagnostics/codes.ts";
|
|
12
15
|
import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
|
|
@@ -23,14 +26,20 @@ import {
|
|
|
23
26
|
resetDatabase,
|
|
24
27
|
} from "../runtime/db/migrate.ts";
|
|
25
28
|
import type { DbAdapterKind } from "../runtime/db/adapter.ts";
|
|
29
|
+
import {
|
|
30
|
+
DEFAULT_PGLITE_DIR,
|
|
31
|
+
repairLocalPgliteStore,
|
|
32
|
+
} from "../runtime/db/pglite-adapter.ts";
|
|
33
|
+
import { normalizeForgeCliCommandsInValue } from "../workspace/forge-cli.ts";
|
|
26
34
|
|
|
27
|
-
export type DbSubcommand = "diff" | "migrate" | "reset" | "status" | "doctor" | "rls-check";
|
|
35
|
+
export type DbSubcommand = "diff" | "migrate" | "reset" | "status" | "doctor" | "repair" | "rls-check";
|
|
28
36
|
|
|
29
37
|
export interface DbCommandOptions {
|
|
30
38
|
subcommand: DbSubcommand;
|
|
31
39
|
workspaceRoot: string;
|
|
32
40
|
db: DbAdapterKind;
|
|
33
41
|
databaseUrl?: string;
|
|
42
|
+
local?: boolean;
|
|
34
43
|
json: boolean;
|
|
35
44
|
}
|
|
36
45
|
|
|
@@ -250,7 +259,7 @@ async function runDbDoctor(
|
|
|
250
259
|
.sort();
|
|
251
260
|
const ok = inspected.diagnostics.length === 0 && missingTables.length === 0 && missingColumns.length === 0;
|
|
252
261
|
|
|
253
|
-
return {
|
|
262
|
+
return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
|
|
254
263
|
ok,
|
|
255
264
|
data: {
|
|
256
265
|
schemaVersion: "0.1.0",
|
|
@@ -279,13 +288,92 @@ async function runDbDoctor(
|
|
|
279
288
|
...inspected.diagnostics,
|
|
280
289
|
],
|
|
281
290
|
exitCode: ok ? 0 : 1,
|
|
282
|
-
};
|
|
291
|
+
});
|
|
283
292
|
} finally {
|
|
284
293
|
await adapter.close();
|
|
285
294
|
}
|
|
286
295
|
}
|
|
287
296
|
|
|
297
|
+
async function runDbRepair(options: DbCommandOptions): Promise<DbCommandResult> {
|
|
298
|
+
if (!options.local) {
|
|
299
|
+
return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
|
|
300
|
+
ok: false,
|
|
301
|
+
data: {
|
|
302
|
+
schemaVersion: "0.1.0",
|
|
303
|
+
repaired: false,
|
|
304
|
+
adapter: options.db,
|
|
305
|
+
local: false,
|
|
306
|
+
nextActions: ["forge db repair --local --adapter pglite --json"],
|
|
307
|
+
},
|
|
308
|
+
diagnostics: [
|
|
309
|
+
createDiagnostic({
|
|
310
|
+
severity: "error",
|
|
311
|
+
code: "FORGE_CLI_USAGE",
|
|
312
|
+
message: "local database repair requires --local",
|
|
313
|
+
fixHint: "Pass --local to confirm you want Forge to repair the local development database store.",
|
|
314
|
+
suggestedCommands: ["forge db repair --local --adapter pglite --json"],
|
|
315
|
+
}),
|
|
316
|
+
],
|
|
317
|
+
exitCode: 1,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (options.db !== "pglite") {
|
|
322
|
+
return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
|
|
323
|
+
ok: false,
|
|
324
|
+
data: {
|
|
325
|
+
schemaVersion: "0.1.0",
|
|
326
|
+
repaired: false,
|
|
327
|
+
adapter: options.db,
|
|
328
|
+
nextActions: ["forge db repair --local --adapter pglite --json"],
|
|
329
|
+
},
|
|
330
|
+
diagnostics: [
|
|
331
|
+
createDiagnostic({
|
|
332
|
+
severity: "error",
|
|
333
|
+
code: FORGE_DB_ADAPTER_UNAVAILABLE,
|
|
334
|
+
message: "local repair currently supports only the pglite adapter",
|
|
335
|
+
fixHint: "Use --adapter pglite for the local Forge dev database store.",
|
|
336
|
+
suggestedCommands: ["forge db repair --local --adapter pglite --json"],
|
|
337
|
+
}),
|
|
338
|
+
],
|
|
339
|
+
exitCode: 1,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const dataDir = join(options.workspaceRoot, DEFAULT_PGLITE_DIR);
|
|
344
|
+
const result = await repairLocalPgliteStore(dataDir);
|
|
345
|
+
const diagnostics = result.ok
|
|
346
|
+
? []
|
|
347
|
+
: [
|
|
348
|
+
createDiagnostic({
|
|
349
|
+
severity: "error",
|
|
350
|
+
code: result.before.state === "active" ? FORGE_PGLITE_STORE_ACTIVE : FORGE_PGLITE_STORE_ABORTED,
|
|
351
|
+
message: result.message,
|
|
352
|
+
fixHint: result.before.state === "active"
|
|
353
|
+
? "Stop the running forge dev process before repairing the PGlite store."
|
|
354
|
+
: "Archive the local PGlite store and retry forge dev, or use --db memory for non-persistent validation.",
|
|
355
|
+
suggestedCommands: result.nextActions,
|
|
356
|
+
}),
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
|
|
360
|
+
ok: result.ok,
|
|
361
|
+
data: {
|
|
362
|
+
schemaVersion: "0.1.0",
|
|
363
|
+
adapter: "pglite",
|
|
364
|
+
local: true,
|
|
365
|
+
...result,
|
|
366
|
+
},
|
|
367
|
+
diagnostics,
|
|
368
|
+
exitCode: result.ok ? 0 : 1,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
288
372
|
export async function runDbCommand(options: DbCommandOptions): Promise<DbCommandResult> {
|
|
373
|
+
if (options.subcommand === "repair") {
|
|
374
|
+
return runDbRepair(options);
|
|
375
|
+
}
|
|
376
|
+
|
|
289
377
|
const { plan, diagnostics: planDiagnostics } = await loadSqlPlan(options.workspaceRoot);
|
|
290
378
|
|
|
291
379
|
if (!plan) {
|
|
@@ -427,7 +515,7 @@ export function formatDbHuman(subcommand: DbSubcommand, result: DbCommandResult)
|
|
|
427
515
|
return "database reset complete\n";
|
|
428
516
|
}
|
|
429
517
|
|
|
430
|
-
if (subcommand === "status" || subcommand === "diff" || subcommand === "doctor") {
|
|
518
|
+
if (subcommand === "status" || subcommand === "diff" || subcommand === "doctor" || subcommand === "repair") {
|
|
431
519
|
return `${JSON.stringify(result.data, null, 2)}\n`;
|
|
432
520
|
}
|
|
433
521
|
|