forgeos 0.1.0-alpha.11 → 0.1.0-alpha.13
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 +22 -1
- package/README.md +7 -0
- package/docs/changelog.md +1 -1
- package/package.json +1 -1
- package/src/forge/_generated/actionSubscriptions.json +1 -1
- package/src/forge/_generated/actionSubscriptions.ts +3 -3
- package/src/forge/_generated/agentAdapterManifest.json +1 -1
- package/src/forge/_generated/agentAdapterManifest.ts +3 -3
- package/src/forge/_generated/agentContract.json +1 -1
- package/src/forge/_generated/agentContract.ts +2 -2
- package/src/forge/_generated/agentQuickstart.md +1 -1
- package/src/forge/_generated/agentTools.json +1 -1
- package/src/forge/_generated/agentTools.md +1 -1
- package/src/forge/_generated/agentTools.ts +2 -2
- package/src/forge/_generated/aiContext.ts +1 -1
- package/src/forge/_generated/aiModels.ts +1 -1
- package/src/forge/_generated/aiProviders.ts +1 -1
- package/src/forge/_generated/aiRegistry.json +1 -1
- package/src/forge/_generated/aiRegistry.ts +3 -3
- package/src/forge/_generated/api.json +1 -1
- package/src/forge/_generated/api.ts +1 -1
- package/src/forge/_generated/appGraph.json +1 -1
- package/src/forge/_generated/appGraph.ts +626 -71
- package/src/forge/_generated/appMap.md +1 -1
- package/src/forge/_generated/artifactManifest.json +1 -1
- package/src/forge/_generated/artifactManifest.ts +2 -2
- package/src/forge/_generated/authClaims.ts +1 -1
- package/src/forge/_generated/authConfig.ts +1 -1
- package/src/forge/_generated/authContext.ts +1 -1
- package/src/forge/_generated/authRegistry.ts +1 -1
- package/src/forge/_generated/buildInfo.json +1 -1
- package/src/forge/_generated/buildInfo.ts +4 -4
- package/src/forge/_generated/capabilityMap.json +1 -1
- package/src/forge/_generated/capabilityMap.md +1 -1
- package/src/forge/_generated/capabilityMap.ts +2 -2
- package/src/forge/_generated/client.ts +1 -1
- package/src/forge/_generated/clientApi.ts +1 -1
- package/src/forge/_generated/clientManifest.json +1 -1
- package/src/forge/_generated/clientManifest.ts +3 -3
- package/src/forge/_generated/clientTypes.ts +1 -1
- package/src/forge/_generated/configRegistry.ts +1 -1
- package/src/forge/_generated/dataGraph.json +1 -1
- package/src/forge/_generated/dataGraph.ts +3 -3
- package/src/forge/_generated/db.ts +1 -1
- package/src/forge/_generated/dbSecurityManifest.ts +1 -1
- package/src/forge/_generated/dbSessionContext.ts +1 -1
- package/src/forge/_generated/deployManifest.json +1 -1
- package/src/forge/_generated/deployManifest.ts +7 -7
- package/src/forge/_generated/devManifest.json +1 -1
- package/src/forge/_generated/devManifest.ts +3 -3
- package/src/forge/_generated/envSchema.ts +1 -1
- package/src/forge/_generated/externalServices.ts +1 -1
- package/src/forge/_generated/frontendGraph.ts +1 -1
- package/src/forge/_generated/importGuards.ts +1 -1
- package/src/forge/_generated/index.ts +1 -1
- package/src/forge/_generated/liveProductionManifest.ts +1 -1
- package/src/forge/_generated/liveProtocol.ts +1 -1
- package/src/forge/_generated/liveQueryRegistry.json +1 -1
- package/src/forge/_generated/liveQueryRegistry.ts +3 -3
- package/src/forge/_generated/liveTransportConfig.ts +1 -1
- package/src/forge/_generated/makeRegistry.json +1 -1
- package/src/forge/_generated/makeRegistry.ts +2 -2
- package/src/forge/_generated/makeTemplates.ts +1 -1
- package/src/forge/_generated/mockMap.ts +1 -1
- package/src/forge/_generated/operationPlaybooks.md +1 -1
- package/src/forge/_generated/packageGraph.json +1 -1
- package/src/forge/_generated/packageGraph.ts +2 -2
- package/src/forge/_generated/packageUpgradeRegistry.json +1 -1
- package/src/forge/_generated/packageUpgradeRegistry.ts +2 -2
- package/src/forge/_generated/permissionMatrix.json +1 -1
- package/src/forge/_generated/permissionMatrix.ts +3 -3
- package/src/forge/_generated/policyRegistry.json +1 -1
- package/src/forge/_generated/policyRegistry.ts +3 -3
- package/src/forge/_generated/queryRegistry.json +1 -1
- package/src/forge/_generated/queryRegistry.ts +3 -3
- package/src/forge/_generated/react.d.ts +1 -1
- package/src/forge/_generated/react.ts +1 -1
- package/src/forge/_generated/reactManifest.json +1 -1
- package/src/forge/_generated/reactManifest.ts +3 -3
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/_generated/rlsPolicies.sql +1 -1
- package/src/forge/_generated/rlsPolicies.ts +1 -1
- package/src/forge/_generated/runtimeGraph.json +1 -1
- package/src/forge/_generated/runtimeGraph.ts +3 -3
- package/src/forge/_generated/runtimeMatrix.ts +1 -1
- package/src/forge/_generated/runtimeRegistry.ts +1 -1
- package/src/forge/_generated/runtimeRules.md +1 -1
- package/src/forge/_generated/secretRegistry.ts +1 -1
- package/src/forge/_generated/secretsContext.ts +1 -1
- package/src/forge/_generated/serverApi.ts +1 -1
- package/src/forge/_generated/sourceMapManifest.json +1 -1
- package/src/forge/_generated/sourceMapManifest.ts +2 -2
- package/src/forge/_generated/sqlPlan.ts +1 -1
- package/src/forge/_generated/subscriptionManifest.json +1 -1
- package/src/forge/_generated/subscriptionManifest.ts +3 -3
- package/src/forge/_generated/symbolicationManifest.json +1 -1
- package/src/forge/_generated/symbolicationManifest.ts +2 -2
- package/src/forge/_generated/telemetryRegistry.json +1 -1
- package/src/forge/_generated/telemetryRegistry.ts +3 -3
- package/src/forge/_generated/telemetrySinks.json +1 -1
- package/src/forge/_generated/telemetrySinks.ts +2 -2
- package/src/forge/_generated/tenantScope.json +1 -1
- package/src/forge/_generated/tenantScope.ts +3 -3
- package/src/forge/_generated/testGraph.json +1 -1
- package/src/forge/_generated/testGraph.ts +75 -3
- package/src/forge/_generated/testPlanRegistry.json +1 -1
- package/src/forge/_generated/testPlanRegistry.ts +2 -2
- package/src/forge/_generated/uiRoutes.ts +1 -1
- package/src/forge/_generated/uiScenarios.ts +1 -1
- package/src/forge/_generated/uiTestManifest.json +1 -1
- package/src/forge/_generated/uiTestManifest.ts +2 -2
- package/src/forge/_generated/workflowRegistry.json +1 -1
- package/src/forge/_generated/workflowRegistry.ts +3 -3
- package/src/forge/_generated/workflowSubscriptions.json +1 -1
- package/src/forge/_generated/workflowSubscriptions.ts +3 -3
- package/src/forge/agent-adapters/index.ts +36 -2
- package/src/forge/agent-adapters/types.ts +10 -1
- package/src/forge/agent-memory/bridge.ts +228 -0
- package/src/forge/agent-memory/context-pack.ts +104 -0
- package/src/forge/agent-memory/mcp.ts +224 -0
- package/src/forge/agent-memory/normalize.ts +249 -0
- package/src/forge/agent-memory/redaction.ts +94 -0
- package/src/forge/agent-memory/sources/claude-code.ts +51 -0
- package/src/forge/agent-memory/sources/codex.ts +58 -0
- package/src/forge/agent-memory/sources/cursor.ts +35 -0
- package/src/forge/agent-memory/types.ts +112 -0
- package/src/forge/cli/build.ts +19 -3
- package/src/forge/cli/commands.ts +56 -0
- package/src/forge/cli/dev.ts +17 -8
- package/src/forge/cli/main.ts +12 -1
- package/src/forge/cli/new.ts +1 -0
- package/src/forge/cli/parse.ts +159 -2
- package/src/forge/delta/classifier.ts +52 -0
- package/src/forge/delta/explain.ts +126 -0
- package/src/forge/delta/git-observer.ts +43 -0
- package/src/forge/delta/ids.ts +44 -0
- package/src/forge/delta/index.ts +6 -0
- package/src/forge/delta/recorder.ts +350 -0
- package/src/forge/delta/redaction.ts +50 -0
- package/src/forge/delta/schema.ts +238 -0
- package/src/forge/delta/session.ts +141 -0
- package/src/forge/delta/status.ts +68 -0
- package/src/forge/delta/store.ts +2573 -0
- package/src/forge/delta/timeline.ts +104 -0
- package/src/forge/dev/server.ts +39 -0
- package/src/forge/dev/types.ts +2 -0
- package/src/forge/dev/watch.ts +17 -7
- package/src/forge/version.ts +1 -1
|
@@ -78,6 +78,20 @@ import {
|
|
|
78
78
|
formatTelemetryJson,
|
|
79
79
|
runTelemetryCommand,
|
|
80
80
|
} from "./telemetry.ts";
|
|
81
|
+
import {
|
|
82
|
+
formatDeltaExplainHuman,
|
|
83
|
+
formatDeltaExplainJson,
|
|
84
|
+
formatDeltaStatusHuman,
|
|
85
|
+
formatDeltaStatusJson,
|
|
86
|
+
formatDeltaSessionHuman,
|
|
87
|
+
formatDeltaSessionJson,
|
|
88
|
+
formatDeltaTimelineHuman,
|
|
89
|
+
formatDeltaTimelineJson,
|
|
90
|
+
runDeltaExplain,
|
|
91
|
+
runDeltaSessionCommand,
|
|
92
|
+
runDeltaStatus,
|
|
93
|
+
runDeltaTimeline,
|
|
94
|
+
} from "../delta/index.ts";
|
|
81
95
|
import {
|
|
82
96
|
formatPolicyHuman,
|
|
83
97
|
formatPolicyJson,
|
|
@@ -151,6 +165,7 @@ import {
|
|
|
151
165
|
formatAgentJson,
|
|
152
166
|
runAgentCommand,
|
|
153
167
|
} from "../agent-adapters/index.ts";
|
|
168
|
+
import { runMcpServe } from "../agent-memory/mcp.ts";
|
|
154
169
|
import {
|
|
155
170
|
formatReviewHuman,
|
|
156
171
|
formatReviewJson,
|
|
@@ -848,6 +863,44 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
848
863
|
);
|
|
849
864
|
return result.exitCode;
|
|
850
865
|
}
|
|
866
|
+
case "delta": {
|
|
867
|
+
const result = await runDeltaStatus(command.workspaceRoot);
|
|
868
|
+
process.stdout.write(command.json ? formatDeltaStatusJson(result) : formatDeltaStatusHuman(result));
|
|
869
|
+
return result.exitCode;
|
|
870
|
+
}
|
|
871
|
+
case "session": {
|
|
872
|
+
const result = await runDeltaSessionCommand({
|
|
873
|
+
workspaceRoot: command.workspaceRoot,
|
|
874
|
+
subcommand: command.subcommand,
|
|
875
|
+
sessionId: command.sessionId,
|
|
876
|
+
sourceSessionId: command.sourceSessionId,
|
|
877
|
+
operationId: command.operationId,
|
|
878
|
+
title: command.title,
|
|
879
|
+
limit: command.limit,
|
|
880
|
+
});
|
|
881
|
+
process.stdout.write(command.json ? formatDeltaSessionJson(result) : formatDeltaSessionHuman(result));
|
|
882
|
+
return result.exitCode;
|
|
883
|
+
}
|
|
884
|
+
case "timeline": {
|
|
885
|
+
const result = await runDeltaTimeline({
|
|
886
|
+
workspaceRoot: command.workspaceRoot,
|
|
887
|
+
target: command.target,
|
|
888
|
+
kind: command.kindFilter,
|
|
889
|
+
session: command.sessionId,
|
|
890
|
+
limit: command.limit,
|
|
891
|
+
rebuild: command.rebuild,
|
|
892
|
+
});
|
|
893
|
+
process.stdout.write(command.json ? formatDeltaTimelineJson(result) : formatDeltaTimelineHuman(result));
|
|
894
|
+
return result.exitCode;
|
|
895
|
+
}
|
|
896
|
+
case "explain": {
|
|
897
|
+
const result = await runDeltaExplain({
|
|
898
|
+
workspaceRoot: command.workspaceRoot,
|
|
899
|
+
thing: command.thing,
|
|
900
|
+
});
|
|
901
|
+
process.stdout.write(command.json ? formatDeltaExplainJson(result) : formatDeltaExplainHuman(result));
|
|
902
|
+
return result.exitCode;
|
|
903
|
+
}
|
|
851
904
|
case "agent": {
|
|
852
905
|
const result = await runAgentCommand(command.options);
|
|
853
906
|
if (command.options.json) {
|
|
@@ -857,6 +910,9 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
857
910
|
}
|
|
858
911
|
return result.exitCode;
|
|
859
912
|
}
|
|
913
|
+
case "mcp": {
|
|
914
|
+
return runMcpServe(command.workspaceRoot);
|
|
915
|
+
}
|
|
860
916
|
case "review": {
|
|
861
917
|
const result = runReviewCommand(command.options);
|
|
862
918
|
if (command.options.json) {
|
package/src/forge/cli/dev.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "../dev/server.ts";
|
|
20
20
|
import { startDevWatch } from "../dev/watch.ts";
|
|
21
21
|
import type { DevServerHandle } from "../dev/types.ts";
|
|
22
|
+
import { createAmbientDeltaRecorder } from "../delta/index.ts";
|
|
22
23
|
|
|
23
24
|
export interface DevCommandOptions {
|
|
24
25
|
workspaceRoot: string;
|
|
@@ -443,6 +444,8 @@ export async function runDevCommand(
|
|
|
443
444
|
return { web: webHandle, exitCode: 0 };
|
|
444
445
|
}
|
|
445
446
|
|
|
447
|
+
const deltaRecorder = await createAmbientDeltaRecorder(workspaceRoot, "forge-dev", "forge dev");
|
|
448
|
+
|
|
446
449
|
let handle: DevServerHandle;
|
|
447
450
|
try {
|
|
448
451
|
handle = await startDevServer({
|
|
@@ -460,8 +463,10 @@ export async function runDevCommand(
|
|
|
460
463
|
mode: options.mode,
|
|
461
464
|
allowDevAuth: options.allowDevAuth,
|
|
462
465
|
webUrl,
|
|
466
|
+
deltaRecorder,
|
|
463
467
|
});
|
|
464
468
|
} catch (error) {
|
|
469
|
+
await deltaRecorder.close("forge dev failed to start");
|
|
465
470
|
const message =
|
|
466
471
|
error instanceof Error ? error.message : "failed to start dev server";
|
|
467
472
|
if (options.json) {
|
|
@@ -507,7 +512,10 @@ export async function runDevCommand(
|
|
|
507
512
|
}
|
|
508
513
|
|
|
509
514
|
if (options.watch) {
|
|
510
|
-
watchHandle = startDevWatch(workspaceRoot, async (changedCount) => {
|
|
515
|
+
watchHandle = startDevWatch(workspaceRoot, async (changedCount, changedPaths) => {
|
|
516
|
+
for (const changedPath of changedPaths) {
|
|
517
|
+
await deltaRecorder.recordFileChanged(changedPath);
|
|
518
|
+
}
|
|
511
519
|
const result = await run({
|
|
512
520
|
workspaceRoot,
|
|
513
521
|
check: false,
|
|
@@ -543,13 +551,14 @@ export async function runDevCommand(
|
|
|
543
551
|
}
|
|
544
552
|
|
|
545
553
|
await new Promise<void>((resolve) => {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
554
|
+
const shutdown = () => {
|
|
555
|
+
watchHandle?.stop();
|
|
556
|
+
outboxWorkerHandle?.stop();
|
|
557
|
+
webHandle?.stop();
|
|
558
|
+
handle.stop();
|
|
559
|
+
void deltaRecorder.close("forge dev stopped");
|
|
560
|
+
resolve();
|
|
561
|
+
};
|
|
553
562
|
|
|
554
563
|
process.once("SIGINT", shutdown);
|
|
555
564
|
process.once("SIGTERM", shutdown);
|
package/src/forge/cli/main.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { isMainModule } from "../platform/module.ts";
|
|
|
4
4
|
import { executeCommand } from "./commands.ts";
|
|
5
5
|
import { hasUnknownOption, parseCli } from "./parse.ts";
|
|
6
6
|
import { formatJsonResult } from "./output.ts";
|
|
7
|
+
import { recordParsedCliCommand } from "../delta/index.ts";
|
|
7
8
|
|
|
8
9
|
function formatHelp(): string {
|
|
9
10
|
return [
|
|
@@ -14,6 +15,8 @@ function formatHelp(): string {
|
|
|
14
15
|
" forge dev --once --json One-shot health/diagnostic loop for agents and CI",
|
|
15
16
|
" forge do \"fix\" --json Ask ForgeOS for the right workflow and commands",
|
|
16
17
|
" forge inspect all --json Read the generated machine contract",
|
|
18
|
+
" forge mcp serve Serve ForgeOS Agent Memory tools over MCP stdio",
|
|
19
|
+
" forge agent context --current --json Read the Agent Memory context pack",
|
|
17
20
|
" forge doctor windows --json Diagnose native Windows setup and Bun shims",
|
|
18
21
|
" forge bench compiler --json Measure public compiler phase timings",
|
|
19
22
|
" forge manifest validate ./forge.manifest.json --json Validate an external runtime manifest",
|
|
@@ -86,7 +89,15 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<numb
|
|
|
86
89
|
return 1;
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
const startedAt = Date.now();
|
|
93
|
+
const exitCode = await executeCommand(parsed.command);
|
|
94
|
+
await recordParsedCliCommand({
|
|
95
|
+
command: parsed.command,
|
|
96
|
+
argv,
|
|
97
|
+
exitCode,
|
|
98
|
+
durationMs: Date.now() - startedAt,
|
|
99
|
+
});
|
|
100
|
+
return exitCode;
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
if (isMainModule(import.meta)) {
|
package/src/forge/cli/new.ts
CHANGED
package/src/forge/cli/parse.ts
CHANGED
|
@@ -164,9 +164,24 @@ export type ForgeCommand =
|
|
|
164
164
|
| { kind: "do"; options: ForgeDoOptions }
|
|
165
165
|
| { kind: "bench"; options: BenchCommandOptions }
|
|
166
166
|
| { kind: "agent"; options: AgentCommandOptions }
|
|
167
|
+
| { kind: "mcp"; subcommand: "serve"; workspaceRoot: string }
|
|
167
168
|
| { kind: "review"; options: ReviewCommandOptions }
|
|
168
169
|
| { kind: "ui"; options: UiCommandOptions }
|
|
169
170
|
| { kind: "manifest"; subcommand: "validate" | "import"; path: string; json: boolean; workspaceRoot: string }
|
|
171
|
+
| { kind: "delta"; subcommand: "status"; json: boolean; workspaceRoot: string }
|
|
172
|
+
| { kind: "timeline"; target?: string; kindFilter?: string; sessionId?: string; limit?: number; json: boolean; rebuild: boolean; forAgent: boolean; workspaceRoot: string }
|
|
173
|
+
| { kind: "explain"; thing: string; json: boolean; workspaceRoot: string }
|
|
174
|
+
| {
|
|
175
|
+
kind: "session";
|
|
176
|
+
subcommand: "list" | "show" | "rename" | "merge" | "split" | "detach";
|
|
177
|
+
sessionId?: string;
|
|
178
|
+
sourceSessionId?: string;
|
|
179
|
+
operationId?: string;
|
|
180
|
+
title?: string;
|
|
181
|
+
limit?: number;
|
|
182
|
+
json: boolean;
|
|
183
|
+
workspaceRoot: string;
|
|
184
|
+
}
|
|
170
185
|
| { kind: "generate"; check: boolean; dryRun: boolean; json: boolean; concurrency: number }
|
|
171
186
|
| { kind: "add"; alias: string; options: AddOptions & { workspaceRoot: string } }
|
|
172
187
|
| { kind: "inspect"; target: InspectTarget; json: boolean; dryRun: boolean }
|
|
@@ -322,6 +337,7 @@ export const TOP_LEVEL_COMMANDS = [
|
|
|
322
337
|
"self-host",
|
|
323
338
|
"agent-contract",
|
|
324
339
|
"agent",
|
|
340
|
+
"mcp",
|
|
325
341
|
"review",
|
|
326
342
|
"ui",
|
|
327
343
|
"doctor",
|
|
@@ -339,6 +355,10 @@ export const TOP_LEVEL_COMMANDS = [
|
|
|
339
355
|
"repair",
|
|
340
356
|
"do",
|
|
341
357
|
"bench",
|
|
358
|
+
"delta",
|
|
359
|
+
"session",
|
|
360
|
+
"timeline",
|
|
361
|
+
"explain",
|
|
342
362
|
"manifest",
|
|
343
363
|
"generate",
|
|
344
364
|
"add",
|
|
@@ -515,6 +535,10 @@ const AGENT_SUBCOMMANDS: AgentSubcommand[] = [
|
|
|
515
535
|
"doctor",
|
|
516
536
|
"print-context",
|
|
517
537
|
"clean",
|
|
538
|
+
"install",
|
|
539
|
+
"ingest",
|
|
540
|
+
"context",
|
|
541
|
+
"memory",
|
|
518
542
|
];
|
|
519
543
|
const REVIEW_SUBCOMMANDS: ReviewSubcommand[] = ["inspect", "list", "explain"];
|
|
520
544
|
const REVIEW_MODES: ReviewMode[] = ["quick", "standard", "strict"];
|
|
@@ -872,12 +896,27 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
872
896
|
case "agent": {
|
|
873
897
|
const subcommand = rest[0] as AgentSubcommand | undefined;
|
|
874
898
|
if (!subcommand || !AGENT_SUBCOMMANDS.includes(subcommand)) {
|
|
875
|
-
errors.push("forge agent requires subcommand: list-targets, export, check, doctor, print-context, or
|
|
899
|
+
errors.push("forge agent requires subcommand: list-targets, export, check, doctor, print-context, clean, install, ingest, context, or memory");
|
|
876
900
|
return { command: null, workspaceRoot, errors };
|
|
877
901
|
}
|
|
902
|
+
const inputRaw = parseOptionValue(argv, "--input");
|
|
903
|
+
let input: unknown;
|
|
904
|
+
if (inputRaw !== undefined) {
|
|
905
|
+
try {
|
|
906
|
+
input = JSON.parse(inputRaw);
|
|
907
|
+
} catch {
|
|
908
|
+
errors.push("--input must be valid JSON");
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
const limitRaw = parseOptionValue(argv, "--limit");
|
|
912
|
+
const limit = limitRaw ? Number(limitRaw) : undefined;
|
|
913
|
+
if (limitRaw !== undefined && (!Number.isFinite(limit) || limit! < 1)) {
|
|
914
|
+
errors.push("--limit must be a number >= 1");
|
|
915
|
+
}
|
|
878
916
|
const target =
|
|
879
917
|
(parseOptionValue(argv, "--target") as AgentAdapterTarget | undefined) ??
|
|
880
|
-
(subcommand === "
|
|
918
|
+
(subcommand === "install" || subcommand === "ingest" ? rest[1] : undefined) ??
|
|
919
|
+
"generic";
|
|
881
920
|
return {
|
|
882
921
|
command: {
|
|
883
922
|
kind: "agent",
|
|
@@ -891,12 +930,33 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
891
930
|
preserveUserSections: !parseFlag(argv, "--no-preserve-user-sections"),
|
|
892
931
|
skills: !parseFlag(argv, "--no-skills"),
|
|
893
932
|
rules: !parseFlag(argv, "--no-rules"),
|
|
933
|
+
eventName: parseOptionValue(argv, "--event"),
|
|
934
|
+
input,
|
|
935
|
+
entry: parseOptionValue(argv, "--entry") ?? (subcommand === "context" && rest[1] !== "--current" ? rest[1] : undefined),
|
|
936
|
+
current: parseFlag(argv, "--current"),
|
|
937
|
+
limit: limit ? Math.floor(limit) : undefined,
|
|
894
938
|
},
|
|
895
939
|
},
|
|
896
940
|
workspaceRoot,
|
|
897
941
|
errors,
|
|
898
942
|
};
|
|
899
943
|
}
|
|
944
|
+
case "mcp": {
|
|
945
|
+
const subcommand = rest[0];
|
|
946
|
+
if (subcommand !== "serve") {
|
|
947
|
+
errors.push("forge mcp requires subcommand: serve");
|
|
948
|
+
return { command: null, workspaceRoot, errors };
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
command: {
|
|
952
|
+
kind: "mcp",
|
|
953
|
+
subcommand,
|
|
954
|
+
workspaceRoot,
|
|
955
|
+
},
|
|
956
|
+
workspaceRoot,
|
|
957
|
+
errors,
|
|
958
|
+
};
|
|
959
|
+
}
|
|
900
960
|
case "review": {
|
|
901
961
|
const requested = rest[0] as ReviewSubcommand | undefined;
|
|
902
962
|
const subcommand =
|
|
@@ -1540,6 +1600,94 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1540
1600
|
errors,
|
|
1541
1601
|
};
|
|
1542
1602
|
}
|
|
1603
|
+
case "delta": {
|
|
1604
|
+
const subcommand = rest[0];
|
|
1605
|
+
if (subcommand !== "status") {
|
|
1606
|
+
errors.push("forge delta requires subcommand: status");
|
|
1607
|
+
return { command: null, workspaceRoot, errors };
|
|
1608
|
+
}
|
|
1609
|
+
return {
|
|
1610
|
+
command: {
|
|
1611
|
+
kind: "delta",
|
|
1612
|
+
subcommand,
|
|
1613
|
+
json: parseFlag(argv, "--json"),
|
|
1614
|
+
workspaceRoot,
|
|
1615
|
+
},
|
|
1616
|
+
workspaceRoot,
|
|
1617
|
+
errors,
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
case "session": {
|
|
1621
|
+
const subcommand = rest[0];
|
|
1622
|
+
if (subcommand !== "list" && subcommand !== "show" && subcommand !== "rename" && subcommand !== "merge" && subcommand !== "split" && subcommand !== "detach") {
|
|
1623
|
+
errors.push("forge session requires subcommand: list, show, rename, merge, split, or detach");
|
|
1624
|
+
return { command: null, workspaceRoot, errors };
|
|
1625
|
+
}
|
|
1626
|
+
const limitRaw = parseOptionValue(argv, "--limit");
|
|
1627
|
+
const limit = limitRaw ? Number(limitRaw) : undefined;
|
|
1628
|
+
if (limitRaw !== undefined && (!Number.isFinite(limit) || limit! < 1)) {
|
|
1629
|
+
errors.push("--limit must be a number >= 1");
|
|
1630
|
+
}
|
|
1631
|
+
const sessionId = rest[1];
|
|
1632
|
+
return {
|
|
1633
|
+
command: {
|
|
1634
|
+
kind: "session",
|
|
1635
|
+
subcommand,
|
|
1636
|
+
sessionId: subcommand === "detach" ? undefined : sessionId,
|
|
1637
|
+
sourceSessionId: subcommand === "merge" ? rest[2] : undefined,
|
|
1638
|
+
operationId: subcommand === "split" ? rest[2] : subcommand === "detach" ? rest[1] : undefined,
|
|
1639
|
+
title: subcommand === "rename" ? rest.slice(2).join(" ") : undefined,
|
|
1640
|
+
limit: limit ? Math.floor(limit) : undefined,
|
|
1641
|
+
json: parseFlag(argv, "--json"),
|
|
1642
|
+
workspaceRoot,
|
|
1643
|
+
},
|
|
1644
|
+
workspaceRoot,
|
|
1645
|
+
errors,
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
case "timeline": {
|
|
1649
|
+
const limitRaw = parseOptionValue(argv, "--limit");
|
|
1650
|
+
const kindFilter = parseOptionValue(argv, "--kind");
|
|
1651
|
+
const sessionId = parseOptionValue(argv, "--session");
|
|
1652
|
+
const rebuild = rest[0] === "rebuild";
|
|
1653
|
+
const target = rebuild ? undefined : rest.find((item) => item !== kindFilter && item !== limitRaw && item !== sessionId);
|
|
1654
|
+
const limit = limitRaw ? Number(limitRaw) : undefined;
|
|
1655
|
+
if (limitRaw !== undefined && (!Number.isFinite(limit) || limit! < 1)) {
|
|
1656
|
+
errors.push("--limit must be a number >= 1");
|
|
1657
|
+
}
|
|
1658
|
+
return {
|
|
1659
|
+
command: {
|
|
1660
|
+
kind: "timeline",
|
|
1661
|
+
target,
|
|
1662
|
+
kindFilter,
|
|
1663
|
+
sessionId,
|
|
1664
|
+
limit: limit ? Math.floor(limit) : undefined,
|
|
1665
|
+
json: parseFlag(argv, "--json"),
|
|
1666
|
+
rebuild,
|
|
1667
|
+
forAgent: parseFlag(argv, "--for-agent"),
|
|
1668
|
+
workspaceRoot,
|
|
1669
|
+
},
|
|
1670
|
+
workspaceRoot,
|
|
1671
|
+
errors,
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
case "explain": {
|
|
1675
|
+
const thing = rest[0] === "session" ? `session:${rest[1] ?? "current"}` : rest[0];
|
|
1676
|
+
if (!thing) {
|
|
1677
|
+
errors.push("forge explain requires a target");
|
|
1678
|
+
return { command: null, workspaceRoot, errors };
|
|
1679
|
+
}
|
|
1680
|
+
return {
|
|
1681
|
+
command: {
|
|
1682
|
+
kind: "explain",
|
|
1683
|
+
thing,
|
|
1684
|
+
json: parseFlag(argv, "--json"),
|
|
1685
|
+
workspaceRoot,
|
|
1686
|
+
},
|
|
1687
|
+
workspaceRoot,
|
|
1688
|
+
errors,
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1543
1691
|
case "manifest": {
|
|
1544
1692
|
const subcommand = rest[0];
|
|
1545
1693
|
const path = rest[1];
|
|
@@ -2144,6 +2292,7 @@ export function hasUnknownOption(argv: string[]): string | null {
|
|
|
2144
2292
|
"--version",
|
|
2145
2293
|
"--check",
|
|
2146
2294
|
"--json",
|
|
2295
|
+
"--for-agent",
|
|
2147
2296
|
"--dry-run",
|
|
2148
2297
|
"--plan",
|
|
2149
2298
|
"--staged",
|
|
@@ -2197,6 +2346,8 @@ export function hasUnknownOption(argv: string[]): string | null {
|
|
|
2197
2346
|
"--policy",
|
|
2198
2347
|
"--emit",
|
|
2199
2348
|
"--event",
|
|
2349
|
+
"--entry",
|
|
2350
|
+
"--current",
|
|
2200
2351
|
"--trigger",
|
|
2201
2352
|
"--component",
|
|
2202
2353
|
"--framework",
|
|
@@ -2264,6 +2415,8 @@ export function hasUnknownOption(argv: string[]): string | null {
|
|
|
2264
2415
|
"--no-worker",
|
|
2265
2416
|
"--once",
|
|
2266
2417
|
"--limit",
|
|
2418
|
+
"--kind",
|
|
2419
|
+
"--session",
|
|
2267
2420
|
"--input",
|
|
2268
2421
|
"--args",
|
|
2269
2422
|
"--step",
|
|
@@ -2310,6 +2463,7 @@ export function hasUnknownOption(argv: string[]): string | null {
|
|
|
2310
2463
|
"--run-tests",
|
|
2311
2464
|
"--model-level",
|
|
2312
2465
|
"--live",
|
|
2466
|
+
"--no-delta",
|
|
2313
2467
|
]);
|
|
2314
2468
|
|
|
2315
2469
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -2332,6 +2486,7 @@ export function hasUnknownOption(argv: string[]): string | null {
|
|
|
2332
2486
|
arg === "--policy" ||
|
|
2333
2487
|
arg === "--emit" ||
|
|
2334
2488
|
arg === "--event" ||
|
|
2489
|
+
arg === "--entry" ||
|
|
2335
2490
|
arg === "--trigger" ||
|
|
2336
2491
|
arg === "--component" ||
|
|
2337
2492
|
arg === "--package" ||
|
|
@@ -2375,6 +2530,8 @@ export function hasUnknownOption(argv: string[]): string | null {
|
|
|
2375
2530
|
arg === "--db" ||
|
|
2376
2531
|
arg === "--database-url" ||
|
|
2377
2532
|
arg === "--limit" ||
|
|
2533
|
+
arg === "--kind" ||
|
|
2534
|
+
arg === "--session" ||
|
|
2378
2535
|
arg === "--input" ||
|
|
2379
2536
|
arg === "--args" ||
|
|
2380
2537
|
arg === "--step" ||
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { extname } from "node:path";
|
|
2
|
+
import { normalizePath } from "../compiler/primitives/paths.ts";
|
|
3
|
+
|
|
4
|
+
export interface DeltaSemanticHint {
|
|
5
|
+
kind: string;
|
|
6
|
+
confidence: "high" | "medium" | "low";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function classifyDeltaPath(path: string): DeltaSemanticHint[] {
|
|
10
|
+
const normalized = normalizePath(path);
|
|
11
|
+
const hints: DeltaSemanticHint[] = [];
|
|
12
|
+
|
|
13
|
+
if (normalized.startsWith("src/forge/_generated/")) {
|
|
14
|
+
hints.push({ kind: "artifact.generated", confidence: "high" });
|
|
15
|
+
} else if (normalized === "src/policies.ts" || normalized.includes("/policies/")) {
|
|
16
|
+
hints.push({ kind: "policy.change", confidence: "high" });
|
|
17
|
+
} else if (normalized.startsWith("src/commands/")) {
|
|
18
|
+
hints.push({ kind: "command.change", confidence: "high" });
|
|
19
|
+
} else if (normalized.startsWith("src/queries/")) {
|
|
20
|
+
hints.push({ kind: "query.change", confidence: "high" });
|
|
21
|
+
} else if (normalized.startsWith("src/actions/")) {
|
|
22
|
+
hints.push({ kind: "action.change", confidence: "high" });
|
|
23
|
+
} else if (normalized.startsWith("src/workflows/")) {
|
|
24
|
+
hints.push({ kind: "workflow.change", confidence: "high" });
|
|
25
|
+
} else if (normalized === "src/forge/schema.ts") {
|
|
26
|
+
hints.push({ kind: "schema.change", confidence: "high" });
|
|
27
|
+
} else if (normalized.endsWith(".manifest.json") || normalized === "forge.manifest.json") {
|
|
28
|
+
hints.push({ kind: "manifest.change", confidence: "high" });
|
|
29
|
+
} else if (normalized === "package.json" || normalized.endsWith("/package.json")) {
|
|
30
|
+
hints.push({ kind: "dependency.change", confidence: "medium" });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (hints.length === 0) {
|
|
34
|
+
hints.push({ kind: `file.${extname(normalized).slice(1) || "unknown"}`, confidence: "low" });
|
|
35
|
+
}
|
|
36
|
+
return hints;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function classifyArtifactKind(path: string): string {
|
|
40
|
+
const normalized = normalizePath(path);
|
|
41
|
+
if (normalized.startsWith("src/forge/_generated/")) {
|
|
42
|
+
return "generated-contract";
|
|
43
|
+
}
|
|
44
|
+
if (normalized.includes("security") || normalized.includes("proof")) {
|
|
45
|
+
return "evidence";
|
|
46
|
+
}
|
|
47
|
+
if (normalized.startsWith(".forge/")) {
|
|
48
|
+
return "local-state";
|
|
49
|
+
}
|
|
50
|
+
return "source";
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { DeltaStore } from "./store.ts";
|
|
2
|
+
|
|
3
|
+
export interface DeltaExplainResult {
|
|
4
|
+
ok: true;
|
|
5
|
+
thing: string;
|
|
6
|
+
explanation: Record<string, unknown>;
|
|
7
|
+
exitCode: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function runDeltaExplain(input: {
|
|
11
|
+
workspaceRoot: string;
|
|
12
|
+
thing: string;
|
|
13
|
+
}): Promise<DeltaExplainResult> {
|
|
14
|
+
const store = await DeltaStore.open(input.workspaceRoot);
|
|
15
|
+
try {
|
|
16
|
+
return {
|
|
17
|
+
ok: true,
|
|
18
|
+
thing: input.thing,
|
|
19
|
+
explanation: await store.explain(input.thing),
|
|
20
|
+
exitCode: 0,
|
|
21
|
+
};
|
|
22
|
+
} finally {
|
|
23
|
+
await store.close();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function formatDeltaExplainHuman(result: DeltaExplainResult): string {
|
|
28
|
+
const explanation = result.explanation;
|
|
29
|
+
if (explanation.type === "work-session") {
|
|
30
|
+
const session = explanation.session as Record<string, unknown> | null | undefined;
|
|
31
|
+
const lines = [result.thing, ""];
|
|
32
|
+
if (!session) {
|
|
33
|
+
lines.push("work session not found");
|
|
34
|
+
return `${lines.join("\n")}\n`;
|
|
35
|
+
}
|
|
36
|
+
lines.push("Work session:");
|
|
37
|
+
lines.push(` ${String(session.id)}`);
|
|
38
|
+
lines.push(` title: ${String(session.title ?? "Work session")}`);
|
|
39
|
+
lines.push(` status: ${String(session.status ?? "unknown")}`);
|
|
40
|
+
lines.push(` confidence: ${Number(session.confidence ?? 0).toFixed(2)}`);
|
|
41
|
+
if (session.summary) {
|
|
42
|
+
lines.push(` summary: ${String(session.summary)}`);
|
|
43
|
+
}
|
|
44
|
+
const operations = Array.isArray(session.operations) ? session.operations as Array<Record<string, unknown>> : [];
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push("Operations:");
|
|
47
|
+
for (const operation of operations.slice(-12)) {
|
|
48
|
+
const timestamp = typeof operation.timestamp === "string" ? operation.timestamp.slice(11, 16) : "??:??";
|
|
49
|
+
lines.push(` ${timestamp} ${String(operation.kind)}${operation.summary ? ` ${String(operation.summary)}` : ""}`);
|
|
50
|
+
}
|
|
51
|
+
return `${lines.join("\n")}\n`;
|
|
52
|
+
}
|
|
53
|
+
const runtime = explanation.runtime as Record<string, unknown> | null | undefined;
|
|
54
|
+
const semanticTimeline = explanation.semanticTimeline as Record<string, unknown> | null | undefined;
|
|
55
|
+
const semanticEvents = semanticTimeline && Array.isArray(semanticTimeline.events)
|
|
56
|
+
? semanticTimeline.events as Array<Record<string, unknown>>
|
|
57
|
+
: [];
|
|
58
|
+
const currentState = semanticTimeline && semanticTimeline.currentState && typeof semanticTimeline.currentState === "object"
|
|
59
|
+
? semanticTimeline.currentState as Record<string, unknown>
|
|
60
|
+
: {};
|
|
61
|
+
const proofs = Array.isArray(explanation.proofs) ? explanation.proofs as Array<Record<string, unknown>> : [];
|
|
62
|
+
const workSessions = Array.isArray(explanation.workSessions) ? explanation.workSessions as Array<Record<string, unknown>> : [];
|
|
63
|
+
const lines = [result.thing, ""];
|
|
64
|
+
lines.push("Type:");
|
|
65
|
+
lines.push(` ${String(explanation.type ?? "unknown")}`);
|
|
66
|
+
lines.push("");
|
|
67
|
+
if (runtime) {
|
|
68
|
+
lines.push("Runtime:");
|
|
69
|
+
lines.push(` kind: ${String(runtime.entry_kind ?? "unknown")}`);
|
|
70
|
+
lines.push(` result: ${String(runtime.result ?? "unknown")}`);
|
|
71
|
+
if (runtime.diagnostic_code) {
|
|
72
|
+
lines.push(` diagnostic: ${String(runtime.diagnostic_code)}`);
|
|
73
|
+
}
|
|
74
|
+
if (runtime.trace_id) {
|
|
75
|
+
lines.push(` trace: ${String(runtime.trace_id)}`);
|
|
76
|
+
}
|
|
77
|
+
lines.push("");
|
|
78
|
+
}
|
|
79
|
+
lines.push("Semantic timeline:");
|
|
80
|
+
if (semanticEvents.length === 0) {
|
|
81
|
+
lines.push(" no matching operations");
|
|
82
|
+
} else {
|
|
83
|
+
for (const item of semanticEvents.slice(-12)) {
|
|
84
|
+
const timestamp = typeof item.timestamp === "string" ? item.timestamp.slice(11, 16) : "??:??";
|
|
85
|
+
lines.push(` ${timestamp} ${String(item.kind)} ${String(item.title ?? item.summary ?? "")}`.trimEnd());
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (Object.keys(currentState).length > 0) {
|
|
89
|
+
lines.push("");
|
|
90
|
+
lines.push("Current state:");
|
|
91
|
+
for (const [key, value] of Object.entries(currentState)) {
|
|
92
|
+
if (value !== undefined) {
|
|
93
|
+
lines.push(` ${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
lines.push("");
|
|
98
|
+
lines.push("Introduced in:");
|
|
99
|
+
if (workSessions.length === 0) {
|
|
100
|
+
lines.push(" no inferred work session linked");
|
|
101
|
+
} else {
|
|
102
|
+
const session = workSessions[0]!;
|
|
103
|
+
lines.push(` ${String(session.id)} - ${String(session.title ?? "Work session")}`);
|
|
104
|
+
lines.push(` confidence: ${Number(session.confidence ?? 0).toFixed(2)}`);
|
|
105
|
+
if (session.summary) {
|
|
106
|
+
lines.push(` ${String(session.summary)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
lines.push("");
|
|
110
|
+
lines.push("Proofs:");
|
|
111
|
+
if (proofs.length === 0) {
|
|
112
|
+
lines.push(" none recorded");
|
|
113
|
+
} else {
|
|
114
|
+
for (const proof of proofs.slice(-6)) {
|
|
115
|
+
lines.push(` ${String(proof.proof_kind)} -> ${String(proof.result)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
lines.push("");
|
|
119
|
+
lines.push("Git:");
|
|
120
|
+
lines.push(explanation.git ? " linked metadata recorded" : " no linked commit yet");
|
|
121
|
+
return `${lines.join("\n")}\n`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function formatDeltaExplainJson(result: DeltaExplainResult): string {
|
|
125
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
126
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
function git(workspaceRoot: string, args: string[]): string | null {
|
|
4
|
+
try {
|
|
5
|
+
return execFileSync("git", args, {
|
|
6
|
+
cwd: workspaceRoot,
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
9
|
+
}).trim();
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DeltaGitSnapshot {
|
|
16
|
+
branch?: string;
|
|
17
|
+
head?: string;
|
|
18
|
+
dirty?: boolean;
|
|
19
|
+
changedPaths?: string[];
|
|
20
|
+
changedPathCount?: number;
|
|
21
|
+
changedPathsTruncated?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readDeltaGitSnapshot(workspaceRoot: string): DeltaGitSnapshot {
|
|
25
|
+
const branch = git(workspaceRoot, ["branch", "--show-current"]) ?? undefined;
|
|
26
|
+
const head = git(workspaceRoot, ["rev-parse", "--short=12", "HEAD"]) ?? undefined;
|
|
27
|
+
const status = git(workspaceRoot, ["status", "--porcelain"]);
|
|
28
|
+
const allChangedPaths = status
|
|
29
|
+
? status
|
|
30
|
+
.split(/\r?\n/)
|
|
31
|
+
.map((line) => line.slice(3).trim())
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
: [];
|
|
34
|
+
const changedPaths = allChangedPaths.slice(0, 50);
|
|
35
|
+
return {
|
|
36
|
+
branch,
|
|
37
|
+
head,
|
|
38
|
+
dirty: allChangedPaths.length > 0,
|
|
39
|
+
changedPaths,
|
|
40
|
+
changedPathCount: allChangedPaths.length,
|
|
41
|
+
changedPathsTruncated: allChangedPaths.length > changedPaths.length,
|
|
42
|
+
};
|
|
43
|
+
}
|