forgeos 0.1.0-alpha.31 → 0.1.0-alpha.33
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 +27 -0
- package/docs/changelog.md +46 -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/templates/nuxt-web/web/package.json +2 -1
- package/templates/nuxt-web/web/tsconfig.json +4 -1
package/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.33 input=618472d83d84f1f735eeaaec13559692c94a492c7f592b20882b5ebac7a1c378 content=721818a6f9a664aa898092d4aa5cd68cbbd8ad6e150c7d3ad3ef785a2e3fcf53
|
|
2
2
|
# AGENTS.md
|
|
3
3
|
|
|
4
4
|
<!-- forge-generated:start -->
|
|
@@ -205,8 +205,8 @@ node bin/forge.mjs do verify --json
|
|
|
205
205
|
Use:
|
|
206
206
|
|
|
207
207
|
```bash
|
|
208
|
-
node bin/forge.mjs make resource <name> --fields title:text,status:enum
|
|
209
|
-
node bin/forge.mjs make resource <name> --fields title:text,status:enum
|
|
208
|
+
node bin/forge.mjs make resource <name> --fields title:text,status:enum=open+closed --dry-run --json
|
|
209
|
+
node bin/forge.mjs make resource <name> --fields title:text,status:enum=open+closed --with-ui --yes
|
|
210
210
|
node bin/forge.mjs make ui --framework vite --dry-run --json
|
|
211
211
|
node bin/forge.mjs make ui --framework nuxt --dry-run --json
|
|
212
212
|
node bin/forge.mjs make ai-chat support --dry-run --json
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# forgeos
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.33
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix the Nuxt template smoke by adding the explicit `vue-tsc` dev dependency
|
|
8
|
+
required by `nuxt typecheck`, so newly scaffolded `nuxt-web` apps can run
|
|
9
|
+
`npm run typecheck` after install without manual package repair.
|
|
10
|
+
|
|
11
|
+
## 0.1.0-alpha.32
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Harden the alpha field-test loop after real Codex, WorkOS, PGlite, and app-server exercises.
|
|
16
|
+
|
|
17
|
+
- Accept `create-forgeos-app --git` as a supported/no-op compatibility flag when git initialization is already handled by the scaffold path, and keep `npm create forgeos-app@alpha .` working from empty current directories.
|
|
18
|
+
- Reject schema definitions that try to model `id` as a normal text field so generated SQL cannot silently create the wrong primary-key shape.
|
|
19
|
+
- Fix tenant-scoped updates in the in-memory database adapter and bring memory timestamp behavior closer to PGlite by rejecting empty timestamp strings and returning `Date` objects for timestamp columns.
|
|
20
|
+
- Improve smoke/generate drift diagnostics and keep `forge handoff` read-only with respect to generated artifacts.
|
|
21
|
+
- Copy generated WorkOS seed data to a root-level `workos-seed.yml` for app DX, serve `HEAD /auth.md` and `HEAD /.well-known/oauth-protected-resource`, and make local-vs-production auth posture more explicit.
|
|
22
|
+
- Normalize suggested Forge commands in framework checkouts so `agent`, `delta`, `studio`, `status`, `changed`, `handoff`, doctors, and WorkOS/auth helpers recommend `node bin/forge.mjs ...` instead of a possibly stale global `forge`.
|
|
23
|
+
- Return structured JSON diagnostics for Delta/PGlite export/open failures instead of leaking raw PGlite stack output, and improve Windows/PGlite repair guidance.
|
|
24
|
+
- Expand `forge ui audit` with static UX/auth-readiness warnings for missing semantic landmarks, unlabeled form controls, unnamed buttons, missing loading/error/empty states, and tenant/prod-auth apps that still only show local dev auth posture.
|
|
25
|
+
|
|
3
26
|
## 0.1.0-alpha.31
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
|
@@ -10,6 +33,10 @@
|
|
|
10
33
|
- Add `forge auth status` and production-focused `forge auth prove --prod` output so `dev-headers` is clearly labeled as local-only while JWT/OIDC proofs show production auth posture.
|
|
11
34
|
- Expand `forge doctor windows` with local PGlite store posture and cleanup guidance, including safer PGlite abort inspection that does not poison the surrounding process exit code.
|
|
12
35
|
- Add `forge ui audit` and run it during `forge verify --smoke` when a web app is present, catching missing UI scenarios, missing stable Forge test IDs, and missing policy-denied coverage for sensitive routes.
|
|
36
|
+
- Expand `forge ui audit` with static UX/auth-readiness warnings for missing semantic landmarks, unlabeled form controls, unnamed buttons, missing loading/error/empty states, and tenant/prod-auth apps that still only show local dev auth posture.
|
|
37
|
+
- Bring the in-memory DB adapter closer to PGlite for timestamp fields by rejecting empty timestamp values and returning `Date` objects for `timestamp`/`timestamptz` columns.
|
|
38
|
+
- Make `forge handoff` use a read-only generated-artifact check so preparing a handoff no longer rewrites `src/forge/_generated/**`, `forge.lock`, or generated `AGENTS.md` noise; `forge dev --once` still self-heals stale artifacts.
|
|
39
|
+
- Allow `forge new .` / `npm create forgeos-app@alpha .` from an empty current directory, while refusing non-empty directories to avoid overwriting existing apps.
|
|
13
40
|
|
|
14
41
|
## 0.1.0-alpha.30
|
|
15
42
|
|
package/docs/changelog.md
CHANGED
|
@@ -6,6 +6,38 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.1.0-alpha.33
|
|
10
|
+
|
|
11
|
+
- Fixed the Nuxt template smoke by adding the explicit `vue-tsc` dev
|
|
12
|
+
dependency required by `nuxt typecheck`, so newly scaffolded `nuxt-web` apps
|
|
13
|
+
can run `npm run typecheck` after install without manual package repair.
|
|
14
|
+
|
|
15
|
+
## 0.1.0-alpha.32
|
|
16
|
+
|
|
17
|
+
- Accepted `create-forgeos-app --git` as a supported/no-op compatibility flag
|
|
18
|
+
when git initialization is already handled by the scaffold path, and kept
|
|
19
|
+
`npm create forgeos-app@alpha .` working from empty current directories.
|
|
20
|
+
- Added stronger schema validation for `id` so apps cannot accidentally model
|
|
21
|
+
the primary key as a normal text field and generate incorrect SQL.
|
|
22
|
+
- Fixed tenant-scoped updates in the in-memory database adapter and made memory
|
|
23
|
+
timestamp handling closer to PGlite by rejecting empty timestamp strings and
|
|
24
|
+
returning `Date` objects for timestamp columns.
|
|
25
|
+
- Improved smoke/generate drift diagnostics and kept `forge handoff` read-only
|
|
26
|
+
with respect to generated artifacts.
|
|
27
|
+
- Improved WorkOS/auth app DX: root-level `workos-seed.yml`, `HEAD /auth.md`,
|
|
28
|
+
`HEAD /.well-known/oauth-protected-resource`, and clearer local-vs-production
|
|
29
|
+
auth posture.
|
|
30
|
+
- Normalized suggested Forge commands in framework checkouts so `agent`,
|
|
31
|
+
`delta`, `studio`, `status`, `changed`, `handoff`, doctors, and WorkOS/auth
|
|
32
|
+
helpers recommend `node bin/forge.mjs ...` instead of a possibly stale global
|
|
33
|
+
`forge`.
|
|
34
|
+
- Returned structured JSON diagnostics for Delta/PGlite export/open failures
|
|
35
|
+
instead of raw PGlite stack output, and expanded Windows/PGlite repair hints.
|
|
36
|
+
- Expanded `forge ui audit` with static UX/auth-readiness warnings for missing
|
|
37
|
+
semantic landmarks, unlabeled form controls, unnamed buttons, missing
|
|
38
|
+
loading/error/empty states, and tenant/prod-auth apps that still only expose
|
|
39
|
+
local dev auth posture.
|
|
40
|
+
|
|
9
41
|
## 0.1.0-alpha.31
|
|
10
42
|
|
|
11
43
|
- Added Forge workspace baselines for non-git app directories:
|
|
@@ -20,7 +52,20 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
20
52
|
exit code.
|
|
21
53
|
- Added `forge ui audit` and wired it into `forge verify --smoke` for web apps
|
|
22
54
|
to catch missing route scenarios, missing stable Forge test IDs, and missing
|
|
23
|
-
policy-denied coverage for sensitive flows.
|
|
55
|
+
policy-denied coverage for sensitive flows. It now also reports static
|
|
56
|
+
UX/auth-readiness warnings for missing semantic landmarks, unlabeled form
|
|
57
|
+
controls, unnamed buttons, missing loading/error/empty states, and
|
|
58
|
+
tenant/prod-auth apps that still only expose local dev auth posture.
|
|
59
|
+
- Brought the in-memory DB adapter closer to PGlite for timestamp fields by
|
|
60
|
+
rejecting empty timestamp values and returning `Date` objects for
|
|
61
|
+
`timestamp`/`timestamptz` columns.
|
|
62
|
+
- Made `forge handoff` use a read-only generated-artifact check so preparing a
|
|
63
|
+
handoff no longer rewrites `src/forge/_generated/**`, `forge.lock`, or
|
|
64
|
+
generated `AGENTS.md` noise; `forge dev --once` still self-heals stale
|
|
65
|
+
artifacts.
|
|
66
|
+
- Allowed `forge new .` / `npm create forgeos-app@alpha .` from an empty
|
|
67
|
+
current directory, while refusing non-empty directories to avoid overwriting
|
|
68
|
+
existing apps.
|
|
24
69
|
|
|
25
70
|
## 0.1.0-alpha.30
|
|
26
71
|
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.
|
|
1
|
+
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.33","releaseId":"forgeos@0.1.0-alpha.33+unknown","schemaVersion":"0.1.0"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.33 input=618472d83d84f1f735eeaaec13559692c94a492c7f592b20882b5ebac7a1c378 content=2cfb24f552edc1fade82dc4abca3f6085ae94a0ac9ae70f5b0cffab11c1c3e7c
|
|
2
2
|
export const releaseManifest = {
|
|
3
3
|
"defaultProvider": "local",
|
|
4
4
|
"diagnostics": [],
|
|
@@ -19,7 +19,7 @@ export const releaseManifest = {
|
|
|
19
19
|
"custom"
|
|
20
20
|
],
|
|
21
21
|
"packageName": "forgeos",
|
|
22
|
-
"packageVersion": "0.1.0-alpha.
|
|
23
|
-
"releaseId": "forgeos@0.1.0-alpha.
|
|
22
|
+
"packageVersion": "0.1.0-alpha.33",
|
|
23
|
+
"releaseId": "forgeos@0.1.0-alpha.33+unknown",
|
|
24
24
|
"schemaVersion": "0.1.0"
|
|
25
25
|
} as const;
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
} from "../agent-memory/hook-runner.ts";
|
|
57
57
|
import { releaseManifest } from "../_generated/releaseManifest.ts";
|
|
58
58
|
import { runDevConsoleCycle } from "../dev-console/cycle.ts";
|
|
59
|
+
import { normalizeForgeCliCommandsInValue } from "../workspace/forge-cli.ts";
|
|
59
60
|
|
|
60
61
|
export const AGENT_ADAPTER_VERSION = "agent-adapter-0.1.0";
|
|
61
62
|
export const AGENT_FORMAT_VERSION = "2026-06";
|
|
@@ -77,6 +78,25 @@ function diagnostic(
|
|
|
77
78
|
return createDiagnostic({ severity, code, message, ...(file ? { file } : {}) });
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
function normalizeAgentCliCommandHints<T>(workspaceRoot: string, result: T): T {
|
|
82
|
+
if (!result || typeof result !== "object") {
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
const value = result as Record<string, unknown>;
|
|
86
|
+
const normalized: Record<string, unknown> = { ...value };
|
|
87
|
+
for (const key of ["nextActions", "diagnostics", "commands", "checks", "steps"]) {
|
|
88
|
+
if (key in normalized) {
|
|
89
|
+
normalized[key] = normalizeForgeCliCommandsInValue(workspaceRoot, normalized[key], key);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const key of ["installResult", "ingestResult"]) {
|
|
93
|
+
if (key in normalized) {
|
|
94
|
+
normalized[key] = normalizeAgentCliCommandHints(workspaceRoot, normalized[key]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return normalized as T;
|
|
98
|
+
}
|
|
99
|
+
|
|
80
100
|
function readText(workspaceRoot: string, relative: string): string | null {
|
|
81
101
|
const path = join(workspaceRoot, relative);
|
|
82
102
|
if (!nodeFileSystem.exists(path)) {
|
|
@@ -2222,9 +2242,11 @@ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<
|
|
|
2222
2242
|
};
|
|
2223
2243
|
}
|
|
2224
2244
|
|
|
2225
|
-
|
|
2245
|
+
type AgentCommandResult =
|
|
2226
2246
|
AgentExportResult | AgentCheckResult | AgentTargetsResult | AgentPrintContextResult | AgentDoctorResult | AgentPrepareResult | AgentOnboardResult | AgentHooksSmokeResult | AgentHooksStatusResult | AgentTimelineResult | AgentMemoryCommandResult
|
|
2227
|
-
|
|
2247
|
+
;
|
|
2248
|
+
|
|
2249
|
+
async function runAgentCommandRaw(options: AgentCommandOptions): Promise<AgentCommandResult> {
|
|
2228
2250
|
if (options.subcommand === "list-targets") {
|
|
2229
2251
|
return runAgentListTargets(options.workspaceRoot);
|
|
2230
2252
|
}
|
|
@@ -2295,6 +2317,10 @@ export async function runAgentCommand(options: AgentCommandOptions): Promise<
|
|
|
2295
2317
|
};
|
|
2296
2318
|
}
|
|
2297
2319
|
|
|
2320
|
+
export async function runAgentCommand(options: AgentCommandOptions): Promise<AgentCommandResult> {
|
|
2321
|
+
return normalizeAgentCliCommandHints(options.workspaceRoot, await runAgentCommandRaw(options));
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2298
2324
|
export function formatAgentJson(result: Awaited<ReturnType<typeof runAgentCommand>>): string {
|
|
2299
2325
|
if ("timeline" in result && result.timeline === "agent") {
|
|
2300
2326
|
return `${JSON.stringify(result, null, 2)}\n`;
|
package/src/forge/cli/auth.ts
CHANGED
|
@@ -9,6 +9,9 @@ import { mapClaimsToAuthContext } from "../runtime/auth/claims.ts";
|
|
|
9
9
|
import { ForgeAuthError } from "../runtime/auth/errors.ts";
|
|
10
10
|
import { verifyJwtToken } from "../runtime/auth/verifier.ts";
|
|
11
11
|
import { loadSecretRegistry } from "../runtime/secrets/check.ts";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { normalizeForgeCliCommandsInValue } from "../workspace/forge-cli.ts";
|
|
12
15
|
|
|
13
16
|
export type AuthSubcommand = "check" | "config" | "decode" | "test-token" | "jwks" | "prove" | "status";
|
|
14
17
|
|
|
@@ -18,6 +21,7 @@ export interface AuthCommandOptions {
|
|
|
18
21
|
json: boolean;
|
|
19
22
|
token?: string;
|
|
20
23
|
prod?: boolean;
|
|
24
|
+
scenario?: string;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export interface AuthCommandResult {
|
|
@@ -88,7 +92,7 @@ function buildAuthPosture(workspaceRoot: string) {
|
|
|
88
92
|
const productionMode = config.mode === "jwt" || config.mode === "oidc";
|
|
89
93
|
const errors = configErrors(config);
|
|
90
94
|
const configReady = errors.length === 0;
|
|
91
|
-
return {
|
|
95
|
+
return normalizeForgeCliCommandsInValue(workspaceRoot, {
|
|
92
96
|
schemaVersion: "0.1.0",
|
|
93
97
|
mode: config.mode,
|
|
94
98
|
localOnly: config.mode === "dev-headers",
|
|
@@ -96,6 +100,15 @@ function buildAuthPosture(workspaceRoot: string) {
|
|
|
96
100
|
requiresTenant: config.requiresTenant,
|
|
97
101
|
bearerHeader: productionMode ? "Authorization: Bearer <token>" : null,
|
|
98
102
|
tenantClaim: config.claims.tenantId ?? "tenant_id",
|
|
103
|
+
productionChecklist: [
|
|
104
|
+
{ item: "auth mode is jwt or oidc", ok: productionMode },
|
|
105
|
+
{ item: "FORGE_AUTH_ISSUER configured", ok: Boolean(config.issuer) },
|
|
106
|
+
{ item: "FORGE_AUTH_AUDIENCE configured", ok: Boolean(config.audience) },
|
|
107
|
+
{ item: "FORGE_AUTH_JWKS_URI or OIDC discovery configured", ok: config.mode === "oidc" || Boolean(config.jwksUri) },
|
|
108
|
+
{ item: "tenant claim mapped", ok: Boolean(config.claims.tenantId) },
|
|
109
|
+
{ item: "permission claim mapped", ok: Boolean(config.claims.permissions) },
|
|
110
|
+
{ item: "dev-headers disabled for public runtime", ok: config.mode !== "dev-headers" },
|
|
111
|
+
],
|
|
99
112
|
reason: productionMode
|
|
100
113
|
? configReady
|
|
101
114
|
? "jwt/oidc production auth configuration is present"
|
|
@@ -106,7 +119,58 @@ function buildAuthPosture(workspaceRoot: string) {
|
|
|
106
119
|
? ["forge auth prove --prod --token <jwt> --json", "forge serve --json"]
|
|
107
120
|
: ["forge auth check --json", "configure FORGE_AUTH_ISSUER, FORGE_AUTH_AUDIENCE, and FORGE_AUTH_JWKS_URI or OIDC issuer"]
|
|
108
121
|
: ["set FORGE_AUTH_MODE=jwt or oidc for production", "forge auth prove --prod --token <jwt> --json"],
|
|
109
|
-
};
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildMultiTenantProof(workspaceRoot: string, workos: ReturnType<typeof detectWorkOS>, requiresTenant: boolean) {
|
|
126
|
+
const rootSeedPresent = existsSync(join(workspaceRoot, "workos-seed.yml"));
|
|
127
|
+
const generatedSeedPresent = existsSync(join(workspaceRoot, "src/forge/_generated/integrations/workos/workos-seed.yml"));
|
|
128
|
+
const authMdPresent = existsSync(join(workspaceRoot, "public/auth.md"));
|
|
129
|
+
const metadataPresent = existsSync(join(workspaceRoot, "public/.well-known/oauth-protected-resource"));
|
|
130
|
+
const permissions = ["onboarding:read", "invitations:create", "tasks:update"];
|
|
131
|
+
const permissionVocabularyPresent = workos.detected && workos.claimStatus.some((claim) => claim.name === "permissions" && claim.ok);
|
|
132
|
+
const checks = [
|
|
133
|
+
{
|
|
134
|
+
id: "tenant-claim",
|
|
135
|
+
ok: requiresTenant && workos.claimStatus.some((claim) => claim.name === "tenantId" && claim.ok),
|
|
136
|
+
evidence: "Forge tenant claim maps to WorkOS organization_id and tenant auth is required.",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "permission-claim",
|
|
140
|
+
ok: permissionVocabularyPresent,
|
|
141
|
+
evidence: "Forge permissions claim maps to WorkOS permissions.",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "seed-organizations",
|
|
145
|
+
ok: rootSeedPresent || generatedSeedPresent,
|
|
146
|
+
evidence: rootSeedPresent ? "workos-seed.yml exists at app root." : "generated WorkOS seed exists.",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "agent-auth-metadata",
|
|
150
|
+
ok: authMdPresent && metadataPresent,
|
|
151
|
+
evidence: "public/auth.md and protected resource metadata are present.",
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
return normalizeForgeCliCommandsInValue(workspaceRoot, {
|
|
155
|
+
scenario: "multi-tenant",
|
|
156
|
+
ok: checks.every((check) => check.ok),
|
|
157
|
+
claims: {
|
|
158
|
+
acme: { organization_id: "org_acme", role: "owner", permissions },
|
|
159
|
+
globex: { organization_id: "org_globex", role: "member", permissions: ["onboarding:read", "tasks:update"] },
|
|
160
|
+
},
|
|
161
|
+
invariants: [
|
|
162
|
+
"Acme and Globex must use different organization_id claim values.",
|
|
163
|
+
"Tenant-scoped reads must include the active organization_id.",
|
|
164
|
+
"Tenant-scoped writes must verify the resource tenant before mutation.",
|
|
165
|
+
"Role-only UI affordances are not sufficient; policies must use permissions/claims.",
|
|
166
|
+
],
|
|
167
|
+
checks,
|
|
168
|
+
nextActions: [
|
|
169
|
+
"forge workos doctor --json",
|
|
170
|
+
"forge workos seed --file workos-seed.yml --dry-run --json",
|
|
171
|
+
"run an HTTP E2E with Acme and Globex tokens before production",
|
|
172
|
+
],
|
|
173
|
+
});
|
|
110
174
|
}
|
|
111
175
|
|
|
112
176
|
function validateConfig(workspaceRoot: string): AuthCommandResult {
|
|
@@ -258,8 +322,12 @@ export async function runAuthCommand(
|
|
|
258
322
|
? tokenProof.error
|
|
259
323
|
: undefined;
|
|
260
324
|
const proofOk = checked.ok && (!options.prod || (productionMode && tokenProof?.ok === true));
|
|
325
|
+
const multiTenantProof = options.scenario === "multi-tenant"
|
|
326
|
+
? buildMultiTenantProof(options.workspaceRoot, workos, config.requiresTenant)
|
|
327
|
+
: null;
|
|
328
|
+
const scenarioOk = !multiTenantProof || multiTenantProof.ok;
|
|
261
329
|
return {
|
|
262
|
-
ok: proofOk,
|
|
330
|
+
ok: proofOk && scenarioOk,
|
|
263
331
|
mode: config.mode,
|
|
264
332
|
data: {
|
|
265
333
|
schemaVersion: "0.1.0",
|
|
@@ -268,8 +336,10 @@ export async function runAuthCommand(
|
|
|
268
336
|
mode: config.mode,
|
|
269
337
|
productionReady: productionMode && checked.ok,
|
|
270
338
|
prod: options.prod === true,
|
|
339
|
+
scenario: options.scenario ?? null,
|
|
271
340
|
authPosture: posture,
|
|
272
341
|
...(tokenProof ? { tokenProof: tokenProof.ok ? tokenProof.data : tokenProof.error } : {}),
|
|
342
|
+
...(multiTenantProof ? { multiTenantProof } : {}),
|
|
273
343
|
invariants: [
|
|
274
344
|
{
|
|
275
345
|
id: "INV-001",
|
|
@@ -305,7 +375,7 @@ export async function runAuthCommand(
|
|
|
305
375
|
checkedAt: "deterministic",
|
|
306
376
|
},
|
|
307
377
|
error: prodError ?? checked.error,
|
|
308
|
-
exitCode: proofOk ? 0 : 1,
|
|
378
|
+
exitCode: proofOk && scenarioOk ? 0 : 1,
|
|
309
379
|
};
|
|
310
380
|
}
|
|
311
381
|
if (options.subcommand === "config") {
|
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;
|
|
@@ -102,10 +104,16 @@ function buildRisks(git: WorkspaceGitSummary): string[] {
|
|
|
102
104
|
if (!emptyCategory(changed, "generated") && humanChanges.total === 0) {
|
|
103
105
|
risks.push("only generated artifacts changed; verify the source edit, generator input, or intentional regeneration that produced them");
|
|
104
106
|
}
|
|
107
|
+
return risks;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildAdvisories(git: WorkspaceGitSummary): string[] {
|
|
111
|
+
const advisories: string[] = [];
|
|
112
|
+
const changed = git.changeSummary.changed;
|
|
105
113
|
if (changed.total.count > 50) {
|
|
106
|
-
|
|
114
|
+
advisories.push(`${changed.total.count} changed file(s) detected; use grouped summaries before reviewing raw diffs`);
|
|
107
115
|
}
|
|
108
|
-
return
|
|
116
|
+
return advisories;
|
|
109
117
|
}
|
|
110
118
|
|
|
111
119
|
function buildRecommendedCommands(git: WorkspaceGitSummary): string[] {
|
|
@@ -127,7 +135,12 @@ function buildRecommendedCommands(git: WorkspaceGitSummary): string[] {
|
|
|
127
135
|
"forge generate --check --json",
|
|
128
136
|
];
|
|
129
137
|
}
|
|
138
|
+
const authoredGeneratedInputs =
|
|
139
|
+
changed.byType.source.count +
|
|
140
|
+
changed.byType.config.count +
|
|
141
|
+
changed.byType.operational.count;
|
|
130
142
|
return [
|
|
143
|
+
...(authoredGeneratedInputs > 0 ? ["forge generate --check --json"] : []),
|
|
131
144
|
"forge handoff --json",
|
|
132
145
|
"forge test plan --changed --json",
|
|
133
146
|
"forge verify --changed",
|
|
@@ -189,7 +202,7 @@ function buildGeneratedChangeExplanation(
|
|
|
189
202
|
};
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
export function runChangedCommand(workspaceRoot: string, options: { authoredOnly?: boolean } = {}): ChangedCommandResult {
|
|
205
|
+
export function runChangedCommand(workspaceRoot: string, options: { authoredOnly?: boolean; reviewOnly?: boolean } = {}): ChangedCommandResult {
|
|
193
206
|
const git = buildWorkspaceGitSummary(workspaceRoot);
|
|
194
207
|
const changed = git.changeSummary.changed;
|
|
195
208
|
const humanChanges = selectHumanChangeSummary(changed);
|
|
@@ -198,16 +211,25 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
198
211
|
const authoredStaged = filterCategorizedSummary(git.changeSummary.staged, [...AUTHORED_CHANGE_TYPES]);
|
|
199
212
|
const authoredUnstaged = filterCategorizedSummary(git.changeSummary.unstaged, [...AUTHORED_CHANGE_TYPES]);
|
|
200
213
|
const authoredUntracked = filterCategorizedSummary(git.changeSummary.untracked, [...AUTHORED_CHANGE_TYPES]);
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
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
|
|
207
228
|
? { total: 0, generated: { count: 0, sample: [], hidden: 0 } }
|
|
208
229
|
: derivedChanges;
|
|
209
230
|
const risks = buildRisks(git);
|
|
210
|
-
const
|
|
231
|
+
const advisories = buildAdvisories(git);
|
|
232
|
+
const recommendedCommands = forgeCliCommandsForWorkspace(workspaceRoot, buildRecommendedCommands(git));
|
|
211
233
|
const reviewFocus = buildReviewFocus(viewHumanChanges, viewDerivedChanges);
|
|
212
234
|
const generatedExplanation = buildGeneratedChangeExplanation(viewHumanChanges, viewDerivedChanges);
|
|
213
235
|
const diffPlan: DiffPlan = buildDiffPlanFromChangeSummary(viewChanged);
|
|
@@ -223,7 +245,7 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
223
245
|
commit: git.commit,
|
|
224
246
|
workspaceMode: git.workspaceMode ?? (git.available ? "git" : "nonGit"),
|
|
225
247
|
tracking: git.tracking ?? git.source,
|
|
226
|
-
view: options.authoredOnly ? "authored" : "all",
|
|
248
|
+
view: options.reviewOnly ? "review" : options.authoredOnly ? "authored" : "all",
|
|
227
249
|
changedFiles: viewChanged.total.count,
|
|
228
250
|
humanFiles: viewHumanChanges.total,
|
|
229
251
|
generatedFiles: viewDerivedChanges.total,
|
|
@@ -253,6 +275,7 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
253
275
|
generatedExplanation,
|
|
254
276
|
diffPlan,
|
|
255
277
|
risks,
|
|
278
|
+
advisories,
|
|
256
279
|
recommendedCommands,
|
|
257
280
|
nextActions: recommendedCommands,
|
|
258
281
|
},
|
|
@@ -268,6 +291,7 @@ export function formatChangedHuman(result: ChangedCommandResult): string {
|
|
|
268
291
|
const generatedExplanation = result.data.generatedExplanation as { summary?: string } | undefined;
|
|
269
292
|
const diffPlan = result.data.diffPlan as { summary?: string; authoredDiffCommand?: string; generatedDiffCommand?: string; generatedCollapsedByDefault?: boolean } | undefined;
|
|
270
293
|
const risks = (result.data.risks as string[] | undefined) ?? [];
|
|
294
|
+
const advisories = (result.data.advisories as string[] | undefined) ?? [];
|
|
271
295
|
const nextActions = (result.data.nextActions as string[] | undefined) ?? [];
|
|
272
296
|
const lines = [
|
|
273
297
|
`Forge changed: ${result.ok ? "ready" : "git unavailable"}`,
|
|
@@ -310,6 +334,9 @@ export function formatChangedHuman(result: ChangedCommandResult): string {
|
|
|
310
334
|
if (risks.length > 0) {
|
|
311
335
|
lines.push("", "Risks:", ...risks.map((risk) => ` ${risk}`));
|
|
312
336
|
}
|
|
337
|
+
if (advisories.length > 0) {
|
|
338
|
+
lines.push("", "Notes:", ...advisories.map((advisory) => ` ${advisory}`));
|
|
339
|
+
}
|
|
313
340
|
|
|
314
341
|
lines.push("", "Next:", ...nextActions.map((command) => ` ${command}`));
|
|
315
342
|
return `${lines.join("\n")}\n`;
|