forgeos 0.1.0-alpha.12 → 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 (134) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/package.json +1 -1
  4. package/src/forge/_generated/actionSubscriptions.json +1 -1
  5. package/src/forge/_generated/actionSubscriptions.ts +3 -3
  6. package/src/forge/_generated/agentAdapterManifest.json +1 -1
  7. package/src/forge/_generated/agentAdapterManifest.ts +3 -3
  8. package/src/forge/_generated/agentContract.json +1 -1
  9. package/src/forge/_generated/agentContract.ts +2 -2
  10. package/src/forge/_generated/agentQuickstart.md +1 -1
  11. package/src/forge/_generated/agentTools.json +1 -1
  12. package/src/forge/_generated/agentTools.md +1 -1
  13. package/src/forge/_generated/agentTools.ts +2 -2
  14. package/src/forge/_generated/aiContext.ts +1 -1
  15. package/src/forge/_generated/aiModels.ts +1 -1
  16. package/src/forge/_generated/aiProviders.ts +1 -1
  17. package/src/forge/_generated/aiRegistry.json +1 -1
  18. package/src/forge/_generated/aiRegistry.ts +3 -3
  19. package/src/forge/_generated/api.json +1 -1
  20. package/src/forge/_generated/api.ts +1 -1
  21. package/src/forge/_generated/appGraph.json +1 -1
  22. package/src/forge/_generated/appGraph.ts +288 -19
  23. package/src/forge/_generated/appMap.md +1 -1
  24. package/src/forge/_generated/artifactManifest.json +1 -1
  25. package/src/forge/_generated/artifactManifest.ts +2 -2
  26. package/src/forge/_generated/authClaims.ts +1 -1
  27. package/src/forge/_generated/authConfig.ts +1 -1
  28. package/src/forge/_generated/authContext.ts +1 -1
  29. package/src/forge/_generated/authRegistry.ts +1 -1
  30. package/src/forge/_generated/buildInfo.json +1 -1
  31. package/src/forge/_generated/buildInfo.ts +4 -4
  32. package/src/forge/_generated/capabilityMap.json +1 -1
  33. package/src/forge/_generated/capabilityMap.md +1 -1
  34. package/src/forge/_generated/capabilityMap.ts +2 -2
  35. package/src/forge/_generated/client.ts +1 -1
  36. package/src/forge/_generated/clientApi.ts +1 -1
  37. package/src/forge/_generated/clientManifest.json +1 -1
  38. package/src/forge/_generated/clientManifest.ts +3 -3
  39. package/src/forge/_generated/clientTypes.ts +1 -1
  40. package/src/forge/_generated/configRegistry.ts +1 -1
  41. package/src/forge/_generated/dataGraph.json +1 -1
  42. package/src/forge/_generated/dataGraph.ts +3 -3
  43. package/src/forge/_generated/db.ts +1 -1
  44. package/src/forge/_generated/dbSecurityManifest.ts +1 -1
  45. package/src/forge/_generated/dbSessionContext.ts +1 -1
  46. package/src/forge/_generated/deployManifest.json +1 -1
  47. package/src/forge/_generated/deployManifest.ts +7 -7
  48. package/src/forge/_generated/devManifest.json +1 -1
  49. package/src/forge/_generated/devManifest.ts +3 -3
  50. package/src/forge/_generated/envSchema.ts +1 -1
  51. package/src/forge/_generated/externalServices.ts +1 -1
  52. package/src/forge/_generated/frontendGraph.ts +1 -1
  53. package/src/forge/_generated/importGuards.ts +1 -1
  54. package/src/forge/_generated/index.ts +1 -1
  55. package/src/forge/_generated/liveProductionManifest.ts +1 -1
  56. package/src/forge/_generated/liveProtocol.ts +1 -1
  57. package/src/forge/_generated/liveQueryRegistry.json +1 -1
  58. package/src/forge/_generated/liveQueryRegistry.ts +3 -3
  59. package/src/forge/_generated/liveTransportConfig.ts +1 -1
  60. package/src/forge/_generated/makeRegistry.json +1 -1
  61. package/src/forge/_generated/makeRegistry.ts +2 -2
  62. package/src/forge/_generated/makeTemplates.ts +1 -1
  63. package/src/forge/_generated/mockMap.ts +1 -1
  64. package/src/forge/_generated/operationPlaybooks.md +1 -1
  65. package/src/forge/_generated/packageGraph.json +1 -1
  66. package/src/forge/_generated/packageGraph.ts +2 -2
  67. package/src/forge/_generated/packageUpgradeRegistry.json +1 -1
  68. package/src/forge/_generated/packageUpgradeRegistry.ts +2 -2
  69. package/src/forge/_generated/permissionMatrix.json +1 -1
  70. package/src/forge/_generated/permissionMatrix.ts +3 -3
  71. package/src/forge/_generated/policyRegistry.json +1 -1
  72. package/src/forge/_generated/policyRegistry.ts +3 -3
  73. package/src/forge/_generated/queryRegistry.json +1 -1
  74. package/src/forge/_generated/queryRegistry.ts +3 -3
  75. package/src/forge/_generated/react.d.ts +1 -1
  76. package/src/forge/_generated/react.ts +1 -1
  77. package/src/forge/_generated/reactManifest.json +1 -1
  78. package/src/forge/_generated/reactManifest.ts +3 -3
  79. package/src/forge/_generated/releaseManifest.json +1 -1
  80. package/src/forge/_generated/releaseManifest.ts +3 -3
  81. package/src/forge/_generated/rlsPolicies.sql +1 -1
  82. package/src/forge/_generated/rlsPolicies.ts +1 -1
  83. package/src/forge/_generated/runtimeGraph.json +1 -1
  84. package/src/forge/_generated/runtimeGraph.ts +3 -3
  85. package/src/forge/_generated/runtimeMatrix.ts +1 -1
  86. package/src/forge/_generated/runtimeRegistry.ts +1 -1
  87. package/src/forge/_generated/runtimeRules.md +1 -1
  88. package/src/forge/_generated/secretRegistry.ts +1 -1
  89. package/src/forge/_generated/secretsContext.ts +1 -1
  90. package/src/forge/_generated/serverApi.ts +1 -1
  91. package/src/forge/_generated/sourceMapManifest.json +1 -1
  92. package/src/forge/_generated/sourceMapManifest.ts +2 -2
  93. package/src/forge/_generated/sqlPlan.ts +1 -1
  94. package/src/forge/_generated/subscriptionManifest.json +1 -1
  95. package/src/forge/_generated/subscriptionManifest.ts +3 -3
  96. package/src/forge/_generated/symbolicationManifest.json +1 -1
  97. package/src/forge/_generated/symbolicationManifest.ts +2 -2
  98. package/src/forge/_generated/telemetryRegistry.json +1 -1
  99. package/src/forge/_generated/telemetryRegistry.ts +3 -3
  100. package/src/forge/_generated/telemetrySinks.json +1 -1
  101. package/src/forge/_generated/telemetrySinks.ts +2 -2
  102. package/src/forge/_generated/tenantScope.json +1 -1
  103. package/src/forge/_generated/tenantScope.ts +3 -3
  104. package/src/forge/_generated/testGraph.json +1 -1
  105. package/src/forge/_generated/testGraph.ts +21 -3
  106. package/src/forge/_generated/testPlanRegistry.json +1 -1
  107. package/src/forge/_generated/testPlanRegistry.ts +2 -2
  108. package/src/forge/_generated/uiRoutes.ts +1 -1
  109. package/src/forge/_generated/uiScenarios.ts +1 -1
  110. package/src/forge/_generated/uiTestManifest.json +1 -1
  111. package/src/forge/_generated/uiTestManifest.ts +2 -2
  112. package/src/forge/_generated/workflowRegistry.json +1 -1
  113. package/src/forge/_generated/workflowRegistry.ts +3 -3
  114. package/src/forge/_generated/workflowSubscriptions.json +1 -1
  115. package/src/forge/_generated/workflowSubscriptions.ts +3 -3
  116. package/src/forge/agent-adapters/index.ts +36 -2
  117. package/src/forge/agent-adapters/types.ts +10 -1
  118. package/src/forge/agent-memory/bridge.ts +228 -0
  119. package/src/forge/agent-memory/context-pack.ts +104 -0
  120. package/src/forge/agent-memory/mcp.ts +224 -0
  121. package/src/forge/agent-memory/normalize.ts +249 -0
  122. package/src/forge/agent-memory/redaction.ts +94 -0
  123. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  124. package/src/forge/agent-memory/sources/codex.ts +58 -0
  125. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  126. package/src/forge/agent-memory/types.ts +112 -0
  127. package/src/forge/cli/build.ts +19 -3
  128. package/src/forge/cli/commands.ts +4 -0
  129. package/src/forge/cli/main.ts +2 -0
  130. package/src/forge/cli/parse.ts +47 -2
  131. package/src/forge/delta/ids.ts +2 -0
  132. package/src/forge/delta/schema.ts +38 -1
  133. package/src/forge/delta/store.ts +235 -2
  134. package/src/forge/version.ts +1 -1
@@ -0,0 +1,249 @@
1
+ import { normalizePath } from "../compiler/primitives/paths.ts";
2
+ import { readDeltaGitSnapshot } from "../delta/git-observer.ts";
3
+ import { redactAgentPayload } from "./redaction.ts";
4
+ import {
5
+ AGENT_EVENT_SCHEMA,
6
+ type AgentEventEnvelope,
7
+ type AgentMemoryIntegrationKind,
8
+ type AgentMemorySourceName,
9
+ type AgentMemoryTrustLevel,
10
+ } from "./types.ts";
11
+
12
+ export interface NormalizeAgentEventInput {
13
+ workspaceRoot: string;
14
+ source: AgentMemorySourceName | string;
15
+ eventName?: string;
16
+ integration?: AgentMemoryIntegrationKind | string;
17
+ raw: Record<string, unknown>;
18
+ now?: string;
19
+ }
20
+
21
+ export function normalizeAgentEvent(input: NormalizeAgentEventInput): AgentEventEnvelope {
22
+ const source = normalizeSource(input.source);
23
+ const rawEventName = input.eventName ?? stringField(input.raw, "hook_event_name") ?? stringField(input.raw, "event") ?? "unknown";
24
+ const normalizedKind = normalizeEventKind(source, rawEventName, input.raw);
25
+ const redacted = redactAgentPayload(input.raw);
26
+ const git = readDeltaGitSnapshot(input.workspaceRoot);
27
+ const actorName = source === "claude-code"
28
+ ? "Claude Code"
29
+ : source === "codex"
30
+ ? "Codex"
31
+ : source === "cursor"
32
+ ? "Cursor"
33
+ : "External Agent";
34
+ return {
35
+ schema: AGENT_EVENT_SCHEMA,
36
+ source: {
37
+ agent: source,
38
+ integration: input.integration ?? defaultIntegrationForSource(source),
39
+ version: stringField(input.raw, "version") ?? "unknown",
40
+ },
41
+ workspace: {
42
+ root: normalizePath(input.workspaceRoot),
43
+ gitBranch: git.branch,
44
+ gitHead: git.head,
45
+ },
46
+ session: {
47
+ externalSessionId: stringField(input.raw, "session_id") ?? stringField(input.raw, "sessionId") ?? stringField(input.raw, "conversation_id"),
48
+ forgeSessionId: stringField(input.raw, "forgeSessionId"),
49
+ turnId: stringField(input.raw, "turn_id") ?? stringField(input.raw, "turnId"),
50
+ },
51
+ event: {
52
+ kind: normalizedKind,
53
+ timestamp: stringField(input.raw, "timestamp") ?? input.now ?? new Date().toISOString(),
54
+ },
55
+ actor: {
56
+ kind: "agent",
57
+ name: stringField(input.raw, "actor") ?? actorName,
58
+ model: stringField(input.raw, "model"),
59
+ },
60
+ payload: redacted.value,
61
+ privacy: {
62
+ rawPromptStored: false,
63
+ rawCompletionStored: false,
64
+ rawToolArgsStored: false,
65
+ transcriptImported: false,
66
+ redacted: true,
67
+ sensitiveFieldsRemoved: redacted.sensitiveFieldsRemoved,
68
+ },
69
+ capture: {
70
+ trustLevel: trustLevelForIntegration(input.integration ?? defaultIntegrationForSource(source)),
71
+ confidence: confidenceForSource(source, input.integration ?? defaultIntegrationForSource(source)),
72
+ },
73
+ };
74
+ }
75
+
76
+ export function summarizeAgentEvent(envelope: AgentEventEnvelope): string {
77
+ const tool = stringField(envelope.payload, "toolName") ??
78
+ stringField(envelope.payload, "tool_name") ??
79
+ stringField(envelope.payload, "tool");
80
+ const command = stringField(envelope.payload, "command");
81
+ const promptSummary = stringField(envelope.payload, "promptSummary") ?? stringField(envelope.payload, "userPromptSummary");
82
+ if (envelope.event.kind === "agent.prompt.submitted" && promptSummary) {
83
+ return promptSummary;
84
+ }
85
+ if (tool) {
86
+ return `${envelope.source.agent} ${tool} ${statusFromKind(envelope.event.kind)}`;
87
+ }
88
+ if (command) {
89
+ return `${envelope.source.agent} ran ${command}`;
90
+ }
91
+ return `${envelope.source.agent} ${envelope.event.kind}`;
92
+ }
93
+
94
+ export function extractAgentEventBindings(envelope: AgentEventEnvelope): {
95
+ toolName?: string;
96
+ command?: string;
97
+ exitCode?: number;
98
+ files: string[];
99
+ entries: string[];
100
+ proofs: string[];
101
+ status?: string;
102
+ } {
103
+ const payload = envelope.payload;
104
+ return {
105
+ toolName: stringField(payload, "toolName") ?? stringField(payload, "tool_name") ?? stringField(payload, "tool"),
106
+ command: stringField(payload, "command"),
107
+ exitCode: numberField(payload, "exitCode") ?? numberField(payload, "exit_code"),
108
+ files: uniqueStrings([
109
+ ...arrayOfStrings(payload.files),
110
+ ...arrayOfStrings(payload.paths),
111
+ stringField(payload, "file_path"),
112
+ stringField(payload, "filePath"),
113
+ stringField(payload, "path"),
114
+ ].map((value) => value ? normalizePath(value) : value)),
115
+ entries: uniqueStrings([
116
+ ...arrayOfStrings(payload.entries),
117
+ stringField(payload, "entryName"),
118
+ stringField(payload, "entry_name"),
119
+ stringField(payload, "runtimeEntry"),
120
+ ]),
121
+ proofs: uniqueStrings([
122
+ ...arrayOfStrings(payload.proofs),
123
+ stringField(payload, "proofKind"),
124
+ stringField(payload, "proof_kind"),
125
+ ]),
126
+ status: statusFromKind(envelope.event.kind),
127
+ };
128
+ }
129
+
130
+ function normalizeSource(source: string): AgentMemorySourceName | string {
131
+ if (source === "claude" || source === "claude-code") {
132
+ return "claude-code";
133
+ }
134
+ if (source === "codex" || source === "cursor") {
135
+ return source;
136
+ }
137
+ return source || "generic";
138
+ }
139
+
140
+ function defaultIntegrationForSource(source: string): AgentMemoryIntegrationKind {
141
+ return source === "cursor" ? "mcp" : "native-hook";
142
+ }
143
+
144
+ function trustLevelForIntegration(integration: string): AgentMemoryTrustLevel {
145
+ if (integration === "native-hook") {
146
+ return "direct-hook";
147
+ }
148
+ if (integration === "mcp") {
149
+ return "mcp-tool";
150
+ }
151
+ if (integration === "cli-wrapper") {
152
+ return "forge-command";
153
+ }
154
+ if (integration === "file-watcher") {
155
+ return "file-watcher";
156
+ }
157
+ if (integration === "git-hook") {
158
+ return "git-observer";
159
+ }
160
+ return "manual-import";
161
+ }
162
+
163
+ function confidenceForSource(source: string, integration: string): number {
164
+ if (integration === "native-hook") {
165
+ return source === "generic" ? 0.78 : 0.94;
166
+ }
167
+ if (integration === "mcp") {
168
+ return 0.9;
169
+ }
170
+ if (integration === "cli-wrapper") {
171
+ return 0.84;
172
+ }
173
+ return 0.68;
174
+ }
175
+
176
+ function normalizeEventKind(source: string, eventName: string, raw: Record<string, unknown>): string {
177
+ const canonical = eventName.replace(/\s+/g, "");
178
+ switch (canonical) {
179
+ case "SessionStart":
180
+ return "agent.session.started";
181
+ case "SessionEnd":
182
+ return "agent.session.ended";
183
+ case "UserPromptSubmit":
184
+ return "agent.prompt.submitted";
185
+ case "PreToolUse":
186
+ return "agent.tool.requested";
187
+ case "PermissionRequest":
188
+ return "approval.requested";
189
+ case "PermissionDenied":
190
+ return "approval.denied";
191
+ case "PostToolUseFailure":
192
+ return "agent.tool.failed";
193
+ case "PostToolUse":
194
+ return "agent.tool.completed";
195
+ case "SubagentStart":
196
+ return "agent.subagent.started";
197
+ case "SubagentStop":
198
+ return "agent.subagent.ended";
199
+ case "PreCompact":
200
+ return "agent.memory.compaction.requested";
201
+ case "PostCompact":
202
+ return "agent.memory.compaction.completed";
203
+ case "FileChanged":
204
+ return "agent.file.changed";
205
+ case "Stop":
206
+ return source === "codex" ? "agent.turn.stopped" : "agent.turn.completed";
207
+ default: {
208
+ const tool = stringField(raw, "toolName") ?? stringField(raw, "tool_name");
209
+ if (tool && (canonical === "tool.call" || canonical === "tool_call" || canonical === "unknown")) {
210
+ return "agent.tool.called";
211
+ }
212
+ return canonical.includes(".") ? canonical : `agent.${canonical || "event"}`;
213
+ }
214
+ }
215
+ }
216
+
217
+ function statusFromKind(kind: string): string | undefined {
218
+ if (kind.endsWith(".completed") || kind === "agent.tool.called") {
219
+ return "completed";
220
+ }
221
+ if (kind.endsWith(".failed")) {
222
+ return "failed";
223
+ }
224
+ if (kind.endsWith(".requested")) {
225
+ return "requested";
226
+ }
227
+ if (kind.endsWith(".denied")) {
228
+ return "denied";
229
+ }
230
+ return undefined;
231
+ }
232
+
233
+ function stringField(value: Record<string, unknown>, key: string): string | undefined {
234
+ const field = value[key];
235
+ return typeof field === "string" && field.length > 0 ? field : undefined;
236
+ }
237
+
238
+ function numberField(value: Record<string, unknown>, key: string): number | undefined {
239
+ const field = value[key];
240
+ return typeof field === "number" && Number.isFinite(field) ? field : undefined;
241
+ }
242
+
243
+ function arrayOfStrings(value: unknown): string[] {
244
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.length > 0) : [];
245
+ }
246
+
247
+ function uniqueStrings(values: Array<string | undefined>): string[] {
248
+ return [...new Set(values.filter((value): value is string => typeof value === "string" && value.length > 0))];
249
+ }
@@ -0,0 +1,94 @@
1
+ import { hashStable } from "../compiler/primitives/hash.ts";
2
+ import { redactDeltaPayload } from "../delta/redaction.ts";
3
+
4
+ const RAW_TEXT_KEYS = new Set([
5
+ "prompt",
6
+ "userPrompt",
7
+ "last_assistant_message",
8
+ "lastAssistantMessage",
9
+ "completion",
10
+ "message",
11
+ "transcript",
12
+ "transcript_path",
13
+ "transcriptPath",
14
+ "output",
15
+ "stdout",
16
+ "stderr",
17
+ "result",
18
+ ]);
19
+
20
+ const RAW_ARGS_KEYS = new Set(["args", "arguments", "tool_input", "toolInput", "input"]);
21
+
22
+ export interface AgentPayloadRedaction {
23
+ value: Record<string, unknown>;
24
+ sensitiveFieldsRemoved: string[];
25
+ }
26
+
27
+ export function redactAgentPayload(payload: Record<string, unknown>): AgentPayloadRedaction {
28
+ const removed: string[] = [];
29
+ const coarse = stripRawPayload(payload, [], removed) as Record<string, unknown>;
30
+ const redacted = redactDeltaPayload(coarse);
31
+ return {
32
+ value: redacted.value,
33
+ sensitiveFieldsRemoved: [...new Set([...removed, ...redacted.redaction.diagnostics])],
34
+ };
35
+ }
36
+
37
+ function stripRawPayload(value: unknown, path: string[], removed: string[]): unknown {
38
+ if (Array.isArray(value)) {
39
+ return value.slice(0, 50).map((item, index) => stripRawPayload(item, [...path, String(index)], removed));
40
+ }
41
+ if (!value || typeof value !== "object") {
42
+ return value;
43
+ }
44
+ const output: Record<string, unknown> = {};
45
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
46
+ const nextPath = [...path, key];
47
+ if (RAW_TEXT_KEYS.has(key)) {
48
+ removed.push(nextPath.join("."));
49
+ output[`${key}Hash`] = typeof child === "string" ? hashStable(child) : hashStable(JSON.stringify(child ?? null));
50
+ output[`${key}Stored`] = false;
51
+ if (typeof child === "string" && !isPromptLikeKey(key)) {
52
+ const summary = summarizeText(child);
53
+ if (summary) {
54
+ output[`${key}Summary`] = redactDeltaPayload({ summary }).value.summary;
55
+ }
56
+ }
57
+ continue;
58
+ }
59
+ if (RAW_ARGS_KEYS.has(key)) {
60
+ removed.push(nextPath.join("."));
61
+ output[`${key}Hash`] = hashStable(JSON.stringify(child ?? null));
62
+ output[`${key}Stored`] = false;
63
+ output[`${key}Shape`] = describeShape(child);
64
+ continue;
65
+ }
66
+ output[key] = stripRawPayload(child, nextPath, removed);
67
+ }
68
+ return output;
69
+ }
70
+
71
+ function isPromptLikeKey(key: string): boolean {
72
+ return key.toLowerCase().includes("prompt") || key.toLowerCase().includes("completion") || key.toLowerCase().includes("message");
73
+ }
74
+
75
+ function summarizeText(value: string): string | undefined {
76
+ const normalized = value.replace(/\s+/g, " ").trim();
77
+ if (!normalized) {
78
+ return undefined;
79
+ }
80
+ return normalized.length > 160 ? `${normalized.slice(0, 157)}...` : normalized;
81
+ }
82
+
83
+ function describeShape(value: unknown): unknown {
84
+ if (Array.isArray(value)) {
85
+ return { kind: "array", length: value.length };
86
+ }
87
+ if (value && typeof value === "object") {
88
+ return {
89
+ kind: "object",
90
+ keys: Object.keys(value as Record<string, unknown>).slice(0, 20).sort(),
91
+ };
92
+ }
93
+ return { kind: typeof value };
94
+ }
@@ -0,0 +1,51 @@
1
+ import type { AgentInstallResult } from "../types.ts";
2
+ import { privacyDefaults } from "./codex.ts";
3
+
4
+ const CLAUDE_EVENTS = [
5
+ "SessionStart",
6
+ "UserPromptSubmit",
7
+ "PreToolUse",
8
+ "PermissionRequest",
9
+ "PostToolUse",
10
+ "PostToolUseFailure",
11
+ "PermissionDenied",
12
+ "FileChanged",
13
+ "SubagentStart",
14
+ "SubagentStop",
15
+ "Stop",
16
+ "SessionEnd",
17
+ ];
18
+
19
+ export function claudeCodeInstallFiles(): Array<{ path: string; content: string }> {
20
+ const settings = {
21
+ hooks: Object.fromEntries(CLAUDE_EVENTS.map((event) => [
22
+ event,
23
+ [
24
+ {
25
+ matcher: "*",
26
+ hooks: [
27
+ {
28
+ type: "command",
29
+ command: `forge agent ingest claude-code --event ${event}`,
30
+ },
31
+ ],
32
+ },
33
+ ],
34
+ ])),
35
+ };
36
+ return [
37
+ { path: ".claude/settings.json", content: `${JSON.stringify(settings, null, 2)}\n` },
38
+ ];
39
+ }
40
+
41
+ export function claudeCodeInstallResult(filesWritten: string[], filesPlanned: string[]): AgentInstallResult {
42
+ return {
43
+ ok: true,
44
+ target: "claude-code",
45
+ filesWritten,
46
+ filesPlanned,
47
+ privacy: privacyDefaults(),
48
+ warnings: ["Claude transcript imports are opt-in only; hooks store redacted project memory."],
49
+ exitCode: 0,
50
+ };
51
+ }
@@ -0,0 +1,58 @@
1
+ import type { AgentInstallResult } from "../types.ts";
2
+
3
+ const CODEX_EVENTS = [
4
+ "SessionStart",
5
+ "UserPromptSubmit",
6
+ "PreToolUse",
7
+ "PermissionRequest",
8
+ "PostToolUse",
9
+ "SubagentStart",
10
+ "SubagentStop",
11
+ "PreCompact",
12
+ "PostCompact",
13
+ "Stop",
14
+ ];
15
+
16
+ export function codexInstallFiles(): Array<{ path: string; content: string }> {
17
+ const hook = {
18
+ hooks: Object.fromEntries(CODEX_EVENTS.map((event) => [
19
+ event,
20
+ [
21
+ {
22
+ matcher: "*",
23
+ hooks: [
24
+ {
25
+ type: "command",
26
+ command: `forge agent ingest codex --event ${event}`,
27
+ },
28
+ ],
29
+ },
30
+ ],
31
+ ])),
32
+ };
33
+ return [
34
+ { path: ".codex/hooks.json", content: `${JSON.stringify(hook, null, 2)}\n` },
35
+ ];
36
+ }
37
+
38
+ export function codexInstallResult(filesWritten: string[], filesPlanned: string[]): AgentInstallResult {
39
+ return {
40
+ ok: true,
41
+ target: "codex",
42
+ filesWritten,
43
+ filesPlanned,
44
+ privacy: privacyDefaults(),
45
+ warnings: ["Codex memories and transcripts are not imported automatically."],
46
+ exitCode: 0,
47
+ };
48
+ }
49
+
50
+ export function privacyDefaults(): AgentInstallResult["privacy"] {
51
+ return {
52
+ rawPrompts: "off",
53
+ rawCompletions: "off",
54
+ rawToolArgs: "off",
55
+ transcriptImport: "off",
56
+ cloudSync: "off",
57
+ };
58
+ }
@@ -0,0 +1,35 @@
1
+ import type { AgentInstallResult } from "../types.ts";
2
+ import { privacyDefaults } from "./codex.ts";
3
+
4
+ export function cursorInstallFiles(): Array<{ path: string; content: string }> {
5
+ return [
6
+ {
7
+ path: ".cursor/mcp.json",
8
+ content: `${JSON.stringify({
9
+ mcpServers: {
10
+ forgeos: {
11
+ type: "stdio",
12
+ command: "forge",
13
+ args: ["mcp", "serve"],
14
+ },
15
+ },
16
+ }, null, 2)}\n`,
17
+ },
18
+ {
19
+ path: ".cursor/rules/forgeos-agent-memory.mdc",
20
+ content: `---\ndescription: ForgeOS Agent Memory Bridge context and MCP workflow.\nalwaysApply: true\n---\n\n# ForgeOS Agent Memory\n\n- Before modifying runtime entries, call the ForgeOS MCP tool \`agent_context\` or run \`forge agent context --current --json\`.\n- Prefer ForgeOS MCP tools for inspect, timeline, check, verify, and agent context.\n- Do not edit \`src/forge/_generated/**\` directly.\n- Do not expose destructive or write tools without approval metadata.\n- After changes, run \`forge check --json\`; use \`forge verify --strict\` before handoff.\n- Do not ask ForgeOS to read Cursor internal chats, checkpoints, or editor databases.\n`,
21
+ },
22
+ ];
23
+ }
24
+
25
+ export function cursorInstallResult(filesWritten: string[], filesPlanned: string[]): AgentInstallResult {
26
+ return {
27
+ ok: true,
28
+ target: "cursor",
29
+ filesWritten,
30
+ filesPlanned,
31
+ privacy: privacyDefaults(),
32
+ warnings: ["Cursor internal chats, checkpoints, and databases are not read by ForgeOS."],
33
+ exitCode: 0,
34
+ };
35
+ }
@@ -0,0 +1,112 @@
1
+ export const AGENT_EVENT_SCHEMA = "forge.agent-event.v1" as const;
2
+
3
+ export type AgentMemorySourceName = "claude-code" | "codex" | "cursor" | "generic";
4
+ export type AgentMemoryIntegrationKind = "native-hook" | "mcp" | "cli-wrapper" | "file-watcher" | "git-hook" | "manual-import";
5
+ export type AgentMemoryTrustLevel =
6
+ | "direct-hook"
7
+ | "mcp-tool"
8
+ | "forge-command"
9
+ | "file-watcher"
10
+ | "git-observer"
11
+ | "manual-import"
12
+ | "inferred";
13
+
14
+ export interface AgentEventEnvelope {
15
+ schema: typeof AGENT_EVENT_SCHEMA;
16
+ source: {
17
+ agent: AgentMemorySourceName | string;
18
+ integration: AgentMemoryIntegrationKind | string;
19
+ version: string;
20
+ };
21
+ workspace: {
22
+ root: string;
23
+ gitBranch?: string;
24
+ gitHead?: string;
25
+ };
26
+ session: {
27
+ externalSessionId?: string;
28
+ forgeSessionId?: string;
29
+ turnId?: string;
30
+ };
31
+ event: {
32
+ kind: string;
33
+ timestamp: string;
34
+ };
35
+ actor: {
36
+ kind: "agent" | "human" | "forge" | "unknown";
37
+ name: string;
38
+ model?: string;
39
+ };
40
+ payload: Record<string, unknown>;
41
+ privacy: {
42
+ rawPromptStored: false;
43
+ rawCompletionStored: false;
44
+ rawToolArgsStored: false;
45
+ transcriptImported: false;
46
+ redacted: true;
47
+ sensitiveFieldsRemoved: string[];
48
+ };
49
+ capture: {
50
+ trustLevel: AgentMemoryTrustLevel;
51
+ confidence: number;
52
+ };
53
+ }
54
+
55
+ export interface AgentMemoryEventRecord {
56
+ id: string;
57
+ externalEventId: string;
58
+ sourceName: string;
59
+ integrationKind: string;
60
+ trustLevel: string;
61
+ externalSessionId?: string;
62
+ externalTurnId?: string;
63
+ eventKind: string;
64
+ normalizedKind: string;
65
+ summary?: string;
66
+ confidence: number;
67
+ capturedAt: string;
68
+ operationId?: string;
69
+ data: Record<string, unknown>;
70
+ }
71
+
72
+ export interface AgentMemoryContextPack {
73
+ ok: true;
74
+ scope: "current" | "entry";
75
+ entry?: string;
76
+ currentState: Record<string, unknown>;
77
+ agentMemory: {
78
+ goals: Array<{ source: string; summary: string; confidence: number }>;
79
+ toolCalls: Array<{ source: string; tool: string; status?: string; summary?: string }>;
80
+ files: string[];
81
+ entries: string[];
82
+ approvals: Array<{ source: string; status: string; summary?: string }>;
83
+ proofs: Array<{ kind: string; result?: string }>;
84
+ events: AgentMemoryEventRecord[];
85
+ openQuestions: string[];
86
+ };
87
+ exitCode: 0;
88
+ }
89
+
90
+ export interface AgentInstallResult {
91
+ ok: boolean;
92
+ target: string;
93
+ filesWritten: string[];
94
+ filesPlanned: string[];
95
+ privacy: {
96
+ rawPrompts: "off";
97
+ rawCompletions: "off";
98
+ rawToolArgs: "off";
99
+ transcriptImport: "off";
100
+ cloudSync: "off";
101
+ };
102
+ warnings: string[];
103
+ exitCode: 0 | 1;
104
+ }
105
+
106
+ export interface AgentIngestResult {
107
+ ok: boolean;
108
+ event?: AgentMemoryEventRecord;
109
+ envelope?: AgentEventEnvelope;
110
+ exitCode: 0 | 1;
111
+ error?: string;
112
+ }
@@ -46,6 +46,22 @@ async function runOptionalScript(
46
46
  return { skipped: false, exitCode };
47
47
  }
48
48
 
49
+ function readPackageScripts(workspaceRoot: string): Record<string, string> {
50
+ const packageJsonPath = join(workspaceRoot, "package.json");
51
+ if (!nodeFileSystem.exists(packageJsonPath)) {
52
+ return {};
53
+ }
54
+ const pkg = JSON.parse(nodeFileSystem.readText(packageJsonPath) ?? "{}") as {
55
+ scripts?: Record<string, string>;
56
+ };
57
+ return pkg.scripts ?? {};
58
+ }
59
+
60
+ function shouldTypecheckBuild(workspaceRoot: string): boolean {
61
+ const scripts = readPackageScripts(workspaceRoot);
62
+ return Boolean(scripts.typecheck || nodeFileSystem.exists(join(workspaceRoot, "tsconfig.json")));
63
+ }
64
+
49
65
  export async function runBuildCommand(options: BuildCommandOptions): Promise<BuildCommandResult> {
50
66
  const steps: BuildCommandResult["steps"] = [];
51
67
  const generate = await runGenerateCommand({
@@ -63,9 +79,9 @@ export async function runBuildCommand(options: BuildCommandOptions): Promise<Bui
63
79
  const verify = await runVerifyCommand({
64
80
  workspaceRoot: options.workspaceRoot,
65
81
  json: false,
66
- skipTests: false,
67
- skipTypecheck: false,
68
- skipEslint: false,
82
+ skipTests: true,
83
+ skipTypecheck: !shouldTypecheckBuild(options.workspaceRoot),
84
+ skipEslint: true,
69
85
  strict: false,
70
86
  });
71
87
  steps.push({ name: "verify", ok: verify.exitCode === 0, exitCode: verify.exitCode });
@@ -165,6 +165,7 @@ import {
165
165
  formatAgentJson,
166
166
  runAgentCommand,
167
167
  } from "../agent-adapters/index.ts";
168
+ import { runMcpServe } from "../agent-memory/mcp.ts";
168
169
  import {
169
170
  formatReviewHuman,
170
171
  formatReviewJson,
@@ -909,6 +910,9 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
909
910
  }
910
911
  return result.exitCode;
911
912
  }
913
+ case "mcp": {
914
+ return runMcpServe(command.workspaceRoot);
915
+ }
912
916
  case "review": {
913
917
  const result = runReviewCommand(command.options);
914
918
  if (command.options.json) {
@@ -15,6 +15,8 @@ function formatHelp(): string {
15
15
  " forge dev --once --json One-shot health/diagnostic loop for agents and CI",
16
16
  " forge do \"fix\" --json Ask ForgeOS for the right workflow and commands",
17
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",
18
20
  " forge doctor windows --json Diagnose native Windows setup and Bun shims",
19
21
  " forge bench compiler --json Measure public compiler phase timings",
20
22
  " forge manifest validate ./forge.manifest.json --json Validate an external runtime manifest",