forgeos 0.1.0-alpha.32 → 0.1.0-alpha.34
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 +1 -1
- package/CHANGELOG.md +23 -0
- package/docs/changelog.md +33 -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/cli/changed.ts +102 -8
- package/src/forge/cli/commands.ts +116 -4
- package/src/forge/cli/dev.ts +228 -0
- package/src/forge/cli/handoff.ts +17 -0
- package/src/forge/cli/main.ts +36 -0
- package/src/forge/cli/new.ts +4 -0
- package/src/forge/cli/parse.ts +24 -6
- package/src/forge/cli/workos.ts +250 -23
- package/src/forge/compiler/app-graph/build.ts +44 -1
- package/src/forge/compiler/client-sdk/render-client.ts +31 -0
- package/src/forge/compiler/data-graph/parse.ts +17 -1
- package/src/forge/compiler/data-graph/sql/ddl.ts +24 -13
- package/src/forge/compiler/diagnostics/codes.ts +6 -0
- package/src/forge/compiler/diagnostics/create.ts +14 -0
- package/src/forge/compiler/integration/plan.ts +6 -0
- package/src/forge/compiler/integration/render.ts +9 -0
- package/src/forge/compiler/integration/templates/render.ts +2 -0
- package/src/forge/compiler/integration/templates/types.ts +3 -0
- package/src/forge/compiler/integration/templates/workos.ts +165 -99
- package/src/forge/compiler/orchestrator/plan.ts +7 -0
- package/src/forge/delta/status.ts +10 -1
- package/src/forge/impact/index.ts +158 -1
- package/src/forge/impact/types.ts +27 -1
- package/src/forge/react/index.ts +19 -2
- package/src/forge/runtime/auth/resolve.ts +33 -3
- package/src/forge/runtime/auth/types.ts +1 -0
- package/src/forge/server.ts +5 -1
- package/src/forge/version.ts +1 -1
- package/src/forge/vue/index.ts +16 -1
- package/src/forge/workspace/git-summary.ts +1 -1
- package/templates/nuxt-web/web/package.json +2 -1
- package/templates/nuxt-web/web/tsconfig.json +4 -1
package/src/forge/cli/dev.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { closeSync, openSync } from "node:fs";
|
|
2
3
|
import { createServer as createNetServer } from "node:net";
|
|
3
4
|
import { run } from "../compiler/orchestrator/run.ts";
|
|
4
5
|
import { basename, join } from "node:path";
|
|
@@ -56,6 +57,8 @@ export interface DevCommandOptions {
|
|
|
56
57
|
mode?: "dev" | "serve";
|
|
57
58
|
allowDevAuth?: boolean;
|
|
58
59
|
skipStartupConsole?: boolean;
|
|
60
|
+
detach?: boolean;
|
|
61
|
+
lifecycle?: "status" | "stop";
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
export interface DevCommandResult {
|
|
@@ -173,6 +176,221 @@ interface DevStartFailure {
|
|
|
173
176
|
};
|
|
174
177
|
}
|
|
175
178
|
|
|
179
|
+
const DEV_STATE_DIR = ".forge/dev";
|
|
180
|
+
const DEV_PID_FILE = `${DEV_STATE_DIR}/dev.pid`;
|
|
181
|
+
const DEV_LOG_FILE = `${DEV_STATE_DIR}/dev.log`;
|
|
182
|
+
|
|
183
|
+
function devStatePaths(workspaceRoot: string): { dir: string; pidFile: string; logFile: string } {
|
|
184
|
+
return {
|
|
185
|
+
dir: join(workspaceRoot, DEV_STATE_DIR),
|
|
186
|
+
pidFile: join(workspaceRoot, DEV_PID_FILE),
|
|
187
|
+
logFile: join(workspaceRoot, DEV_LOG_FILE),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function readDetachedDevPid(workspaceRoot: string): number | null {
|
|
192
|
+
const text = nodeFileSystem.readText(devStatePaths(workspaceRoot).pidFile);
|
|
193
|
+
if (text === null) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const pid = Number(text.trim());
|
|
197
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isProcessRunning(pid: number): boolean {
|
|
201
|
+
try {
|
|
202
|
+
process.kill(pid, 0);
|
|
203
|
+
return true;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function removeDevPidFile(workspaceRoot: string): void {
|
|
210
|
+
const paths = devStatePaths(workspaceRoot);
|
|
211
|
+
if (nodeFileSystem.exists(paths.pidFile)) {
|
|
212
|
+
nodeFileSystem.remove(paths.pidFile);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function printDevLifecycleResult(input: {
|
|
217
|
+
options: DevCommandOptions;
|
|
218
|
+
ok: boolean;
|
|
219
|
+
action: "detach" | "status" | "stop";
|
|
220
|
+
running: boolean;
|
|
221
|
+
pid?: number;
|
|
222
|
+
logFile: string;
|
|
223
|
+
message: string;
|
|
224
|
+
exitCode: 0 | 1;
|
|
225
|
+
}): DevCommandResult {
|
|
226
|
+
const payload = {
|
|
227
|
+
ok: input.ok,
|
|
228
|
+
action: input.action,
|
|
229
|
+
running: input.running,
|
|
230
|
+
...(input.pid !== undefined ? { pid: input.pid } : {}),
|
|
231
|
+
logFile: input.logFile,
|
|
232
|
+
message: input.message,
|
|
233
|
+
nextActions: input.running
|
|
234
|
+
? ["forge dev status --json", "forge dev stop --json", `tail -f ${input.logFile}`]
|
|
235
|
+
: ["forge dev --detach --json"],
|
|
236
|
+
exitCode: input.exitCode,
|
|
237
|
+
};
|
|
238
|
+
if (input.options.json) {
|
|
239
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
240
|
+
} else {
|
|
241
|
+
process.stdout.write(`${input.message}\n`);
|
|
242
|
+
if (input.pid !== undefined) process.stdout.write(`pid: ${input.pid}\n`);
|
|
243
|
+
process.stdout.write(`log: ${input.logFile}\n`);
|
|
244
|
+
}
|
|
245
|
+
return { exitCode: input.exitCode };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function buildDetachedDevArgs(options: DevCommandOptions): string[] {
|
|
249
|
+
const args = ["dev", "--skip-startup-console"];
|
|
250
|
+
if (options.host) args.push("--host", options.host);
|
|
251
|
+
if (options.port !== undefined) args.push("--port", String(options.port));
|
|
252
|
+
if (options.mock) args.push("--mock");
|
|
253
|
+
if (options.mockAi) args.push("--mock-ai");
|
|
254
|
+
if (!options.watch) args.push("--no-watch");
|
|
255
|
+
if (options.db) args.push("--db", options.db);
|
|
256
|
+
if (options.databaseUrl) args.push("--database-url", options.databaseUrl);
|
|
257
|
+
if (!options.worker) args.push("--no-worker");
|
|
258
|
+
if (options.withWeb === false) args.push("--no-web");
|
|
259
|
+
if (options.apiOnly) args.push("--api-only");
|
|
260
|
+
if (options.webOnly) args.push("--web-only");
|
|
261
|
+
if (options.open) args.push("--open");
|
|
262
|
+
if (options.webPort !== undefined) args.push("--web-port", String(options.webPort));
|
|
263
|
+
if (options.telemetry.length > 0) args.push("--telemetry", options.telemetry.join(","));
|
|
264
|
+
if (options.envFile) args.push("--env-file", options.envFile);
|
|
265
|
+
return args;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function runDevStatus(options: DevCommandOptions): DevCommandResult {
|
|
269
|
+
const paths = devStatePaths(options.workspaceRoot);
|
|
270
|
+
const pid = readDetachedDevPid(options.workspaceRoot);
|
|
271
|
+
const running = pid !== null && isProcessRunning(pid);
|
|
272
|
+
if (pid !== null && !running) {
|
|
273
|
+
removeDevPidFile(options.workspaceRoot);
|
|
274
|
+
}
|
|
275
|
+
return printDevLifecycleResult({
|
|
276
|
+
options,
|
|
277
|
+
ok: true,
|
|
278
|
+
action: "status",
|
|
279
|
+
running,
|
|
280
|
+
...(pid !== null ? { pid } : {}),
|
|
281
|
+
logFile: paths.logFile,
|
|
282
|
+
message: running ? "forge dev detached server is running" : "forge dev detached server is not running",
|
|
283
|
+
exitCode: 0,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function runDevStop(options: DevCommandOptions): DevCommandResult {
|
|
288
|
+
const paths = devStatePaths(options.workspaceRoot);
|
|
289
|
+
const pid = readDetachedDevPid(options.workspaceRoot);
|
|
290
|
+
if (pid === null || !isProcessRunning(pid)) {
|
|
291
|
+
removeDevPidFile(options.workspaceRoot);
|
|
292
|
+
return printDevLifecycleResult({
|
|
293
|
+
options,
|
|
294
|
+
ok: true,
|
|
295
|
+
action: "stop",
|
|
296
|
+
running: false,
|
|
297
|
+
...(pid !== null ? { pid } : {}),
|
|
298
|
+
logFile: paths.logFile,
|
|
299
|
+
message: "no detached forge dev server was running",
|
|
300
|
+
exitCode: 0,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
process.kill(pid, "SIGTERM");
|
|
304
|
+
removeDevPidFile(options.workspaceRoot);
|
|
305
|
+
return printDevLifecycleResult({
|
|
306
|
+
options,
|
|
307
|
+
ok: true,
|
|
308
|
+
action: "stop",
|
|
309
|
+
running: false,
|
|
310
|
+
pid,
|
|
311
|
+
logFile: paths.logFile,
|
|
312
|
+
message: "stopped detached forge dev server",
|
|
313
|
+
exitCode: 0,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function runDevDetach(options: DevCommandOptions): DevCommandResult {
|
|
318
|
+
if (options.once) {
|
|
319
|
+
return printDevLifecycleResult({
|
|
320
|
+
options,
|
|
321
|
+
ok: false,
|
|
322
|
+
action: "detach",
|
|
323
|
+
running: false,
|
|
324
|
+
logFile: devStatePaths(options.workspaceRoot).logFile,
|
|
325
|
+
message: "forge dev --detach cannot be combined with --once",
|
|
326
|
+
exitCode: 1,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
const existingPid = readDetachedDevPid(options.workspaceRoot);
|
|
330
|
+
const paths = devStatePaths(options.workspaceRoot);
|
|
331
|
+
nodeFileSystem.mkdirp(paths.dir);
|
|
332
|
+
if (existingPid !== null && isProcessRunning(existingPid)) {
|
|
333
|
+
return printDevLifecycleResult({
|
|
334
|
+
options,
|
|
335
|
+
ok: true,
|
|
336
|
+
action: "detach",
|
|
337
|
+
running: true,
|
|
338
|
+
pid: existingPid,
|
|
339
|
+
logFile: paths.logFile,
|
|
340
|
+
message: "forge dev detached server is already running",
|
|
341
|
+
exitCode: 0,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const cliPath = process.argv[1];
|
|
346
|
+
if (!cliPath) {
|
|
347
|
+
return printDevLifecycleResult({
|
|
348
|
+
options,
|
|
349
|
+
ok: false,
|
|
350
|
+
action: "detach",
|
|
351
|
+
running: false,
|
|
352
|
+
logFile: paths.logFile,
|
|
353
|
+
message: "could not resolve current forge CLI path for detached dev server",
|
|
354
|
+
exitCode: 1,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const fd = openSync(paths.logFile, "a");
|
|
359
|
+
try {
|
|
360
|
+
const child = spawn(process.execPath, [cliPath, ...buildDetachedDevArgs(options)], {
|
|
361
|
+
cwd: options.workspaceRoot,
|
|
362
|
+
detached: true,
|
|
363
|
+
stdio: ["ignore", fd, fd],
|
|
364
|
+
windowsHide: true,
|
|
365
|
+
});
|
|
366
|
+
if (child.pid === undefined) {
|
|
367
|
+
return printDevLifecycleResult({
|
|
368
|
+
options,
|
|
369
|
+
ok: false,
|
|
370
|
+
action: "detach",
|
|
371
|
+
running: false,
|
|
372
|
+
logFile: paths.logFile,
|
|
373
|
+
message: "detached forge dev process started without a pid",
|
|
374
|
+
exitCode: 1,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
child.unref();
|
|
378
|
+
nodeFileSystem.writeText(paths.pidFile, `${child.pid}\n`);
|
|
379
|
+
return printDevLifecycleResult({
|
|
380
|
+
options,
|
|
381
|
+
ok: true,
|
|
382
|
+
action: "detach",
|
|
383
|
+
running: true,
|
|
384
|
+
pid: child.pid,
|
|
385
|
+
logFile: paths.logFile,
|
|
386
|
+
message: "started detached forge dev server",
|
|
387
|
+
exitCode: 0,
|
|
388
|
+
});
|
|
389
|
+
} finally {
|
|
390
|
+
closeSync(fd);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
176
394
|
function nextPreviewPort(webUrl?: string): number {
|
|
177
395
|
if (!webUrl) {
|
|
178
396
|
return 5174;
|
|
@@ -862,6 +1080,16 @@ export async function runDevCommand(
|
|
|
862
1080
|
const workspaceRoot = options.workspaceRoot.replace(/\\/g, "/");
|
|
863
1081
|
const startedAt = new Date();
|
|
864
1082
|
|
|
1083
|
+
if (options.lifecycle === "status") {
|
|
1084
|
+
return runDevStatus({ ...options, workspaceRoot });
|
|
1085
|
+
}
|
|
1086
|
+
if (options.lifecycle === "stop") {
|
|
1087
|
+
return runDevStop({ ...options, workspaceRoot });
|
|
1088
|
+
}
|
|
1089
|
+
if (options.detach) {
|
|
1090
|
+
return runDevDetach({ ...options, workspaceRoot });
|
|
1091
|
+
}
|
|
1092
|
+
|
|
865
1093
|
if (options.once) {
|
|
866
1094
|
const cycle = await runDevConsoleCycle({
|
|
867
1095
|
workspaceRoot,
|
package/src/forge/cli/handoff.ts
CHANGED
|
@@ -8,10 +8,12 @@ import type { UiRunReport } from "../ui/types.ts";
|
|
|
8
8
|
import { summarizeChangeTypes, type CategorizedFileSummary } from "../workspace/change-summary.ts";
|
|
9
9
|
import { forgeCliCommandsForWorkspace } from "../workspace/forge-cli.ts";
|
|
10
10
|
import { buildWorkspaceGitSummary } from "../workspace/git-summary.ts";
|
|
11
|
+
import { runChangedCommand } from "./changed.ts";
|
|
11
12
|
|
|
12
13
|
export interface HandoffCommandOptions {
|
|
13
14
|
workspaceRoot: string;
|
|
14
15
|
json: boolean;
|
|
16
|
+
commitReady?: boolean;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export interface HandoffCommandResult {
|
|
@@ -93,6 +95,10 @@ export interface HandoffCommandResult {
|
|
|
93
95
|
recommendedCommands: string[];
|
|
94
96
|
risks: string[];
|
|
95
97
|
};
|
|
98
|
+
commitReady?: {
|
|
99
|
+
count: number;
|
|
100
|
+
files: string[];
|
|
101
|
+
};
|
|
96
102
|
diagnosticSummary: {
|
|
97
103
|
total: number;
|
|
98
104
|
sample: Diagnostic[];
|
|
@@ -205,6 +211,9 @@ export async function runHandoffCommand(options: HandoffCommandOptions): Promise
|
|
|
205
211
|
includeImpact: true,
|
|
206
212
|
});
|
|
207
213
|
const git = buildWorkspaceGitSummary(workspaceRoot);
|
|
214
|
+
const commitReady = options.commitReady
|
|
215
|
+
? runChangedCommand(workspaceRoot, { commitReady: true }).data.commitReady as { count: number; files: string[] } | undefined
|
|
216
|
+
: undefined;
|
|
208
217
|
const recentRuns = summarizeRecentRuns(workspaceRoot);
|
|
209
218
|
const agent = dev.summary.agentContext;
|
|
210
219
|
const risks = [
|
|
@@ -269,6 +278,7 @@ export async function runHandoffCommand(options: HandoffCommandOptions): Promise
|
|
|
269
278
|
recommendedCommands: [...new Set(nextActions)].slice(0, 10),
|
|
270
279
|
risks,
|
|
271
280
|
},
|
|
281
|
+
...(commitReady ? { commitReady } : {}),
|
|
272
282
|
diagnosticSummary,
|
|
273
283
|
diagnostics: diagnosticSummary.sample,
|
|
274
284
|
nextActions: [...new Set(nextActions)].slice(0, 10),
|
|
@@ -303,6 +313,13 @@ export function formatHandoffHuman(result: HandoffCommandResult): string {
|
|
|
303
313
|
(result.diagnosticSummary.hidden > 0 ? ` (${result.diagnosticSummary.hidden} hidden in compact handoff)` : ""),
|
|
304
314
|
);
|
|
305
315
|
}
|
|
316
|
+
if (result.commitReady) {
|
|
317
|
+
lines.push("", "Commit-ready files:", ` ${result.commitReady.count}`);
|
|
318
|
+
lines.push(...result.commitReady.files.slice(0, 8).map((file) => ` ${file}`));
|
|
319
|
+
if (result.commitReady.files.length > 8) {
|
|
320
|
+
lines.push(` ... ${result.commitReady.files.length - 8} more`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
306
323
|
const changedTypes = summarizeChangeTypes(result.git.changeSummary.changed);
|
|
307
324
|
if (changedTypes) {
|
|
308
325
|
lines.push("", "Change types:", ` ${changedTypes}`);
|
package/src/forge/cli/main.ts
CHANGED
|
@@ -15,6 +15,7 @@ function formatHelp(): string {
|
|
|
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
17
|
" forge changed --review --json Show review-focused app/config/docs changes, excluding local agent/browser artifacts",
|
|
18
|
+
" forge changed --commit-ready --json Show files suitable for git add, excluding generated and operational artifacts",
|
|
18
19
|
" forge new my-app --template minimal-web --field-test Create an installed WorkOS/auth.md field-test app",
|
|
19
20
|
" forge diff authored Run the authored-only git diff pathspec",
|
|
20
21
|
" forge handoff --json Compact work handoff for the next external code agent",
|
|
@@ -75,7 +76,42 @@ function formatHelp(): string {
|
|
|
75
76
|
].join("\n");
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
function formatDevHelp(): string {
|
|
80
|
+
return [
|
|
81
|
+
"ForgeOS dev",
|
|
82
|
+
"",
|
|
83
|
+
"Usage:",
|
|
84
|
+
" forge dev [status|stop] [options]",
|
|
85
|
+
"",
|
|
86
|
+
"Options:",
|
|
87
|
+
" --db memory|pglite|postgres|none Choose the development database adapter",
|
|
88
|
+
" --port <port> API runtime port; use 0 for an ephemeral port",
|
|
89
|
+
" --web-port <port> Web dev server port",
|
|
90
|
+
" --host <host> Bind host, default 127.0.0.1",
|
|
91
|
+
" --no-web Start API/runtime only",
|
|
92
|
+
" --api-only Start API/runtime only",
|
|
93
|
+
" --web-only Start web server only, expecting an API runtime",
|
|
94
|
+
" --no-worker Disable local worker",
|
|
95
|
+
" --no-watch Disable file watching",
|
|
96
|
+
" --once --json Run one-shot diagnostics without starting servers",
|
|
97
|
+
" --detach --json Start dev in the background with .forge/dev/dev.pid and .forge/dev/dev.log",
|
|
98
|
+
"",
|
|
99
|
+
"Examples:",
|
|
100
|
+
" forge dev --db memory --port 3777 --web-port 5174",
|
|
101
|
+
" forge dev --db pglite --once --json",
|
|
102
|
+
" forge dev --detach --db memory --port 0 --json",
|
|
103
|
+
" forge dev status --json",
|
|
104
|
+
" forge dev stop --json",
|
|
105
|
+
"",
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
78
109
|
export async function main(argv: string[] = process.argv.slice(2)): Promise<number> {
|
|
110
|
+
if (argv[0] === "dev" && (argv.includes("--help") || argv.includes("-h"))) {
|
|
111
|
+
process.stdout.write(formatDevHelp());
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
79
115
|
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
80
116
|
process.stdout.write(formatHelp());
|
|
81
117
|
return 0;
|
package/src/forge/cli/new.ts
CHANGED
|
@@ -49,12 +49,16 @@ export interface NewCommandResult {
|
|
|
49
49
|
const REQUIRED_GITIGNORE_PATHS = [
|
|
50
50
|
"src/forge/_generated/",
|
|
51
51
|
"forge.lock",
|
|
52
|
+
".codex/",
|
|
52
53
|
".forge/cache/",
|
|
53
54
|
".forge/pglite/",
|
|
54
55
|
".forge/delta/",
|
|
56
|
+
".forge/agent/",
|
|
55
57
|
".forge/agent/*.ndjson",
|
|
56
58
|
".forge/agent/*.history",
|
|
59
|
+
".forge/last-run.json",
|
|
57
60
|
".forge/local/",
|
|
61
|
+
".forge/runtime-cache/",
|
|
58
62
|
".forge/test-cache/",
|
|
59
63
|
".forge/test-runs/",
|
|
60
64
|
".forge/ui-runs/",
|
package/src/forge/cli/parse.ts
CHANGED
|
@@ -222,9 +222,9 @@ export type ForgeCommand =
|
|
|
222
222
|
redacted: boolean;
|
|
223
223
|
}
|
|
224
224
|
| { kind: "status"; json: boolean; workspaceRoot: string }
|
|
225
|
-
| { kind: "changed"; json: boolean; authoredOnly: boolean; reviewOnly: boolean; workspaceRoot: string }
|
|
225
|
+
| { kind: "changed"; json: boolean; authoredOnly: boolean; reviewOnly: boolean; commitReady: boolean; workspaceRoot: string }
|
|
226
226
|
| { kind: "diff"; target: "authored" | "generated" | "full"; json: boolean; workspaceRoot: string }
|
|
227
|
-
| { kind: "handoff"; json: boolean; workspaceRoot: string }
|
|
227
|
+
| { kind: "handoff"; json: boolean; commitReady: boolean; workspaceRoot: string }
|
|
228
228
|
| {
|
|
229
229
|
kind: "studio";
|
|
230
230
|
subcommand: "attach" | "snapshot" | "watch" | "open" | "doctor" | "bridge" | "codex-server";
|
|
@@ -264,7 +264,7 @@ export type ForgeCommand =
|
|
|
264
264
|
}
|
|
265
265
|
| { kind: "generate"; check: boolean; dryRun: boolean; json: boolean; concurrency: number; workspaceRoot: string }
|
|
266
266
|
| { kind: "add"; alias: string; options: AddOptions & { workspaceRoot: string } }
|
|
267
|
-
| { kind: "inspect"; target: InspectTarget; json: boolean; dryRun: boolean; full: boolean; brief: boolean }
|
|
267
|
+
| { kind: "inspect"; target: InspectTarget; json: boolean; dryRun: boolean; full: boolean; brief: boolean; ergonomics: boolean; workspaceRoot: string }
|
|
268
268
|
| { kind: "check"; json: boolean; dryRun: boolean; strictSecrets: boolean }
|
|
269
269
|
| { kind: "verify"; options: VerifyOptions }
|
|
270
270
|
| { kind: "run"; name?: string; list: boolean; json: boolean; mock: boolean; userId?: string; tenantId?: string; role?: string; envFile?: string; workspaceRoot: string; queryMode?: boolean; args?: unknown }
|
|
@@ -288,6 +288,8 @@ export type ForgeCommand =
|
|
|
288
288
|
telemetry: string[];
|
|
289
289
|
envFile?: string;
|
|
290
290
|
skipStartupConsole: boolean;
|
|
291
|
+
detach: boolean;
|
|
292
|
+
lifecycle?: "status" | "stop";
|
|
291
293
|
workspaceRoot: string;
|
|
292
294
|
}
|
|
293
295
|
| {
|
|
@@ -618,7 +620,7 @@ const RENAME_TARGETS: RenameTarget[] = [
|
|
|
618
620
|
"workflow",
|
|
619
621
|
"event",
|
|
620
622
|
];
|
|
621
|
-
const TEST_SUBCOMMANDS: TestSubcommand[] = ["plan", "run", "explain"];
|
|
623
|
+
const TEST_SUBCOMMANDS: TestSubcommand[] = ["plan", "run", "explain", "authz"];
|
|
622
624
|
const TEST_COSTS: TestCost[] = ["instant", "fast", "standard", "slow", "docker", "browser"];
|
|
623
625
|
const REPAIR_SUBCOMMANDS: RepairSubcommand[] = [
|
|
624
626
|
"diagnose",
|
|
@@ -1696,7 +1698,7 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1696
1698
|
case "test": {
|
|
1697
1699
|
const subcommand = rest[0] as TestSubcommand | undefined;
|
|
1698
1700
|
if (!subcommand || !TEST_SUBCOMMANDS.includes(subcommand)) {
|
|
1699
|
-
errors.push("forge test requires subcommand: plan, run, or
|
|
1701
|
+
errors.push("forge test requires subcommand: plan, run, explain, or authz");
|
|
1700
1702
|
return { command: null, workspaceRoot, errors };
|
|
1701
1703
|
}
|
|
1702
1704
|
const timeoutRaw = parseOptionValue(argv, "--timeout-ms");
|
|
@@ -1715,7 +1717,7 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1715
1717
|
workspaceRoot,
|
|
1716
1718
|
json: parseFlag(argv, "--json"),
|
|
1717
1719
|
write: parseFlag(argv, "--write"),
|
|
1718
|
-
changed: parseFlag(argv, "--changed") || (!parseFlag(argv, "--staged") && !parseOptionValue(argv, "--since") && !parseOptionValue(argv, "--feature") && !parseOptionValue(argv, "--refactor") && !parseOptionValue(argv, "--upgrade") && !parseOptionValue(argv, "--plan") && subcommand !== "explain"),
|
|
1720
|
+
changed: parseFlag(argv, "--changed") || (!parseFlag(argv, "--staged") && !parseOptionValue(argv, "--since") && !parseOptionValue(argv, "--feature") && !parseOptionValue(argv, "--refactor") && !parseOptionValue(argv, "--upgrade") && !parseOptionValue(argv, "--plan") && subcommand !== "explain" && subcommand !== "authz"),
|
|
1719
1721
|
staged: parseFlag(argv, "--staged"),
|
|
1720
1722
|
since: parseOptionValue(argv, "--since"),
|
|
1721
1723
|
featureId: parseOptionValue(argv, "--feature"),
|
|
@@ -1729,6 +1731,8 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1729
1731
|
bail: parseFlag(argv, "--bail"),
|
|
1730
1732
|
report: parseOptionValue(argv, "--report"),
|
|
1731
1733
|
timeoutMs: timeoutMs ? Math.floor(timeoutMs) : undefined,
|
|
1734
|
+
tenant: parseOptionValue(argv, "--tenant") ?? "acme",
|
|
1735
|
+
otherTenant: parseOptionValue(argv, "--other-tenant") ?? "globex",
|
|
1732
1736
|
},
|
|
1733
1737
|
},
|
|
1734
1738
|
workspaceRoot,
|
|
@@ -1908,6 +1912,7 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1908
1912
|
json: parseFlag(argv, "--json"),
|
|
1909
1913
|
authoredOnly: parseFlag(argv, "--authored"),
|
|
1910
1914
|
reviewOnly: parseFlag(argv, "--review"),
|
|
1915
|
+
commitReady: parseFlag(argv, "--commit-ready"),
|
|
1911
1916
|
workspaceRoot,
|
|
1912
1917
|
},
|
|
1913
1918
|
workspaceRoot,
|
|
@@ -1935,6 +1940,7 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1935
1940
|
command: {
|
|
1936
1941
|
kind: "handoff",
|
|
1937
1942
|
json: parseFlag(argv, "--json"),
|
|
1943
|
+
commitReady: parseFlag(argv, "--commit-ready"),
|
|
1938
1944
|
workspaceRoot,
|
|
1939
1945
|
},
|
|
1940
1946
|
workspaceRoot,
|
|
@@ -2220,6 +2226,8 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
2220
2226
|
dryRun: parseFlag(argv, "--dry-run"),
|
|
2221
2227
|
full: parseFlag(argv, "--full"),
|
|
2222
2228
|
brief: parseFlag(argv, "--brief"),
|
|
2229
|
+
ergonomics: parseFlag(argv, "--ergonomics"),
|
|
2230
|
+
workspaceRoot,
|
|
2223
2231
|
},
|
|
2224
2232
|
workspaceRoot,
|
|
2225
2233
|
errors,
|
|
@@ -2472,6 +2480,7 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
2472
2480
|
};
|
|
2473
2481
|
}
|
|
2474
2482
|
case "dev": {
|
|
2483
|
+
const lifecycle = rest[0] === "status" || rest[0] === "stop" ? rest[0] : undefined;
|
|
2475
2484
|
const portRaw = parseOptionValue(argv, "--port");
|
|
2476
2485
|
const port = portRaw !== undefined ? Number(portRaw) : undefined;
|
|
2477
2486
|
if (portRaw !== undefined && (!Number.isFinite(port) || port! < 0)) {
|
|
@@ -2509,6 +2518,8 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
2509
2518
|
.filter(Boolean),
|
|
2510
2519
|
envFile: parseOptionValue(argv, "--env-file"),
|
|
2511
2520
|
skipStartupConsole: parseFlag(argv, "--skip-startup-console"),
|
|
2521
|
+
detach: parseFlag(argv, "--detach"),
|
|
2522
|
+
lifecycle,
|
|
2512
2523
|
workspaceRoot,
|
|
2513
2524
|
},
|
|
2514
2525
|
workspaceRoot,
|
|
@@ -2956,6 +2967,8 @@ export function hasUnknownOption(argv: string[]): string | null {
|
|
|
2956
2967
|
"--telemetry",
|
|
2957
2968
|
"--user-id",
|
|
2958
2969
|
"--tenant-id",
|
|
2970
|
+
"--tenant",
|
|
2971
|
+
"--other-tenant",
|
|
2959
2972
|
"--role",
|
|
2960
2973
|
"--strict-policies",
|
|
2961
2974
|
"--strict",
|
|
@@ -2981,6 +2994,9 @@ export function hasUnknownOption(argv: string[]): string | null {
|
|
|
2981
2994
|
"--git",
|
|
2982
2995
|
"--no-git",
|
|
2983
2996
|
"--field-test",
|
|
2997
|
+
"--commit-ready",
|
|
2998
|
+
"--detach",
|
|
2999
|
+
"--ergonomics",
|
|
2984
3000
|
"--with-web",
|
|
2985
3001
|
"--no-web",
|
|
2986
3002
|
"--api-only",
|
|
@@ -3093,6 +3109,8 @@ export function hasUnknownOption(argv: string[]): string | null {
|
|
|
3093
3109
|
arg === "--file" ||
|
|
3094
3110
|
arg === "--telemetry" ||
|
|
3095
3111
|
arg === "--user-id" ||
|
|
3112
|
+
arg === "--tenant" ||
|
|
3113
|
+
arg === "--other-tenant" ||
|
|
3096
3114
|
arg === "--tenant-id" ||
|
|
3097
3115
|
arg === "--role" ||
|
|
3098
3116
|
arg === "--strict-policies" ||
|