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.
Files changed (150) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +22 -1
  3. package/README.md +7 -0
  4. package/docs/changelog.md +1 -1
  5. package/package.json +1 -1
  6. package/src/forge/_generated/actionSubscriptions.json +1 -1
  7. package/src/forge/_generated/actionSubscriptions.ts +3 -3
  8. package/src/forge/_generated/agentAdapterManifest.json +1 -1
  9. package/src/forge/_generated/agentAdapterManifest.ts +3 -3
  10. package/src/forge/_generated/agentContract.json +1 -1
  11. package/src/forge/_generated/agentContract.ts +2 -2
  12. package/src/forge/_generated/agentQuickstart.md +1 -1
  13. package/src/forge/_generated/agentTools.json +1 -1
  14. package/src/forge/_generated/agentTools.md +1 -1
  15. package/src/forge/_generated/agentTools.ts +2 -2
  16. package/src/forge/_generated/aiContext.ts +1 -1
  17. package/src/forge/_generated/aiModels.ts +1 -1
  18. package/src/forge/_generated/aiProviders.ts +1 -1
  19. package/src/forge/_generated/aiRegistry.json +1 -1
  20. package/src/forge/_generated/aiRegistry.ts +3 -3
  21. package/src/forge/_generated/api.json +1 -1
  22. package/src/forge/_generated/api.ts +1 -1
  23. package/src/forge/_generated/appGraph.json +1 -1
  24. package/src/forge/_generated/appGraph.ts +626 -71
  25. package/src/forge/_generated/appMap.md +1 -1
  26. package/src/forge/_generated/artifactManifest.json +1 -1
  27. package/src/forge/_generated/artifactManifest.ts +2 -2
  28. package/src/forge/_generated/authClaims.ts +1 -1
  29. package/src/forge/_generated/authConfig.ts +1 -1
  30. package/src/forge/_generated/authContext.ts +1 -1
  31. package/src/forge/_generated/authRegistry.ts +1 -1
  32. package/src/forge/_generated/buildInfo.json +1 -1
  33. package/src/forge/_generated/buildInfo.ts +4 -4
  34. package/src/forge/_generated/capabilityMap.json +1 -1
  35. package/src/forge/_generated/capabilityMap.md +1 -1
  36. package/src/forge/_generated/capabilityMap.ts +2 -2
  37. package/src/forge/_generated/client.ts +1 -1
  38. package/src/forge/_generated/clientApi.ts +1 -1
  39. package/src/forge/_generated/clientManifest.json +1 -1
  40. package/src/forge/_generated/clientManifest.ts +3 -3
  41. package/src/forge/_generated/clientTypes.ts +1 -1
  42. package/src/forge/_generated/configRegistry.ts +1 -1
  43. package/src/forge/_generated/dataGraph.json +1 -1
  44. package/src/forge/_generated/dataGraph.ts +3 -3
  45. package/src/forge/_generated/db.ts +1 -1
  46. package/src/forge/_generated/dbSecurityManifest.ts +1 -1
  47. package/src/forge/_generated/dbSessionContext.ts +1 -1
  48. package/src/forge/_generated/deployManifest.json +1 -1
  49. package/src/forge/_generated/deployManifest.ts +7 -7
  50. package/src/forge/_generated/devManifest.json +1 -1
  51. package/src/forge/_generated/devManifest.ts +3 -3
  52. package/src/forge/_generated/envSchema.ts +1 -1
  53. package/src/forge/_generated/externalServices.ts +1 -1
  54. package/src/forge/_generated/frontendGraph.ts +1 -1
  55. package/src/forge/_generated/importGuards.ts +1 -1
  56. package/src/forge/_generated/index.ts +1 -1
  57. package/src/forge/_generated/liveProductionManifest.ts +1 -1
  58. package/src/forge/_generated/liveProtocol.ts +1 -1
  59. package/src/forge/_generated/liveQueryRegistry.json +1 -1
  60. package/src/forge/_generated/liveQueryRegistry.ts +3 -3
  61. package/src/forge/_generated/liveTransportConfig.ts +1 -1
  62. package/src/forge/_generated/makeRegistry.json +1 -1
  63. package/src/forge/_generated/makeRegistry.ts +2 -2
  64. package/src/forge/_generated/makeTemplates.ts +1 -1
  65. package/src/forge/_generated/mockMap.ts +1 -1
  66. package/src/forge/_generated/operationPlaybooks.md +1 -1
  67. package/src/forge/_generated/packageGraph.json +1 -1
  68. package/src/forge/_generated/packageGraph.ts +2 -2
  69. package/src/forge/_generated/packageUpgradeRegistry.json +1 -1
  70. package/src/forge/_generated/packageUpgradeRegistry.ts +2 -2
  71. package/src/forge/_generated/permissionMatrix.json +1 -1
  72. package/src/forge/_generated/permissionMatrix.ts +3 -3
  73. package/src/forge/_generated/policyRegistry.json +1 -1
  74. package/src/forge/_generated/policyRegistry.ts +3 -3
  75. package/src/forge/_generated/queryRegistry.json +1 -1
  76. package/src/forge/_generated/queryRegistry.ts +3 -3
  77. package/src/forge/_generated/react.d.ts +1 -1
  78. package/src/forge/_generated/react.ts +1 -1
  79. package/src/forge/_generated/reactManifest.json +1 -1
  80. package/src/forge/_generated/reactManifest.ts +3 -3
  81. package/src/forge/_generated/releaseManifest.json +1 -1
  82. package/src/forge/_generated/releaseManifest.ts +3 -3
  83. package/src/forge/_generated/rlsPolicies.sql +1 -1
  84. package/src/forge/_generated/rlsPolicies.ts +1 -1
  85. package/src/forge/_generated/runtimeGraph.json +1 -1
  86. package/src/forge/_generated/runtimeGraph.ts +3 -3
  87. package/src/forge/_generated/runtimeMatrix.ts +1 -1
  88. package/src/forge/_generated/runtimeRegistry.ts +1 -1
  89. package/src/forge/_generated/runtimeRules.md +1 -1
  90. package/src/forge/_generated/secretRegistry.ts +1 -1
  91. package/src/forge/_generated/secretsContext.ts +1 -1
  92. package/src/forge/_generated/serverApi.ts +1 -1
  93. package/src/forge/_generated/sourceMapManifest.json +1 -1
  94. package/src/forge/_generated/sourceMapManifest.ts +2 -2
  95. package/src/forge/_generated/sqlPlan.ts +1 -1
  96. package/src/forge/_generated/subscriptionManifest.json +1 -1
  97. package/src/forge/_generated/subscriptionManifest.ts +3 -3
  98. package/src/forge/_generated/symbolicationManifest.json +1 -1
  99. package/src/forge/_generated/symbolicationManifest.ts +2 -2
  100. package/src/forge/_generated/telemetryRegistry.json +1 -1
  101. package/src/forge/_generated/telemetryRegistry.ts +3 -3
  102. package/src/forge/_generated/telemetrySinks.json +1 -1
  103. package/src/forge/_generated/telemetrySinks.ts +2 -2
  104. package/src/forge/_generated/tenantScope.json +1 -1
  105. package/src/forge/_generated/tenantScope.ts +3 -3
  106. package/src/forge/_generated/testGraph.json +1 -1
  107. package/src/forge/_generated/testGraph.ts +75 -3
  108. package/src/forge/_generated/testPlanRegistry.json +1 -1
  109. package/src/forge/_generated/testPlanRegistry.ts +2 -2
  110. package/src/forge/_generated/uiRoutes.ts +1 -1
  111. package/src/forge/_generated/uiScenarios.ts +1 -1
  112. package/src/forge/_generated/uiTestManifest.json +1 -1
  113. package/src/forge/_generated/uiTestManifest.ts +2 -2
  114. package/src/forge/_generated/workflowRegistry.json +1 -1
  115. package/src/forge/_generated/workflowRegistry.ts +3 -3
  116. package/src/forge/_generated/workflowSubscriptions.json +1 -1
  117. package/src/forge/_generated/workflowSubscriptions.ts +3 -3
  118. package/src/forge/agent-adapters/index.ts +36 -2
  119. package/src/forge/agent-adapters/types.ts +10 -1
  120. package/src/forge/agent-memory/bridge.ts +228 -0
  121. package/src/forge/agent-memory/context-pack.ts +104 -0
  122. package/src/forge/agent-memory/mcp.ts +224 -0
  123. package/src/forge/agent-memory/normalize.ts +249 -0
  124. package/src/forge/agent-memory/redaction.ts +94 -0
  125. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  126. package/src/forge/agent-memory/sources/codex.ts +58 -0
  127. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  128. package/src/forge/agent-memory/types.ts +112 -0
  129. package/src/forge/cli/build.ts +19 -3
  130. package/src/forge/cli/commands.ts +56 -0
  131. package/src/forge/cli/dev.ts +17 -8
  132. package/src/forge/cli/main.ts +12 -1
  133. package/src/forge/cli/new.ts +1 -0
  134. package/src/forge/cli/parse.ts +159 -2
  135. package/src/forge/delta/classifier.ts +52 -0
  136. package/src/forge/delta/explain.ts +126 -0
  137. package/src/forge/delta/git-observer.ts +43 -0
  138. package/src/forge/delta/ids.ts +44 -0
  139. package/src/forge/delta/index.ts +6 -0
  140. package/src/forge/delta/recorder.ts +350 -0
  141. package/src/forge/delta/redaction.ts +50 -0
  142. package/src/forge/delta/schema.ts +238 -0
  143. package/src/forge/delta/session.ts +141 -0
  144. package/src/forge/delta/status.ts +68 -0
  145. package/src/forge/delta/store.ts +2573 -0
  146. package/src/forge/delta/timeline.ts +104 -0
  147. package/src/forge/dev/server.ts +39 -0
  148. package/src/forge/dev/types.ts +2 -0
  149. package/src/forge/dev/watch.ts +17 -7
  150. 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) {
@@ -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
- const shutdown = () => {
547
- watchHandle?.stop();
548
- outboxWorkerHandle?.stop();
549
- webHandle?.stop();
550
- handle.stop();
551
- resolve();
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);
@@ -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
- return executeCommand(parsed.command);
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)) {
@@ -45,6 +45,7 @@ const REQUIRED_GITIGNORE_PATHS = [
45
45
  "forge.lock",
46
46
  ".forge/cache/",
47
47
  ".forge/pglite/",
48
+ ".forge/delta/",
48
49
  ".forge/local/",
49
50
  ".forge/test-cache/",
50
51
  ".forge/test-runs/",
@@ -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 clean");
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 === "export" || subcommand === "clean" ? "generic" : "generic");
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
+ }