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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export type DeltaIdPrefix =
|
|
4
|
+
| "actor"
|
|
5
|
+
| "sess"
|
|
6
|
+
| "op"
|
|
7
|
+
| "txn"
|
|
8
|
+
| "filechg"
|
|
9
|
+
| "cmdrun"
|
|
10
|
+
| "proof"
|
|
11
|
+
| "rtcall"
|
|
12
|
+
| "artifact"
|
|
13
|
+
| "gitmap"
|
|
14
|
+
| "aevt"
|
|
15
|
+
| "amem"
|
|
16
|
+
| "worksess"
|
|
17
|
+
| "wssig"
|
|
18
|
+
| "wssum";
|
|
19
|
+
|
|
20
|
+
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
21
|
+
|
|
22
|
+
function encodeTime(time: number): string {
|
|
23
|
+
let value = BigInt(time);
|
|
24
|
+
let output = "";
|
|
25
|
+
for (let index = 0; index < 10; index++) {
|
|
26
|
+
output = CROCKFORD[Number(value % 32n)] + output;
|
|
27
|
+
value = value / 32n;
|
|
28
|
+
}
|
|
29
|
+
return output;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function encodeRandom(bytes: Uint8Array): string {
|
|
33
|
+
let value = BigInt(`0x${Buffer.from(bytes).toString("hex")}`);
|
|
34
|
+
let output = "";
|
|
35
|
+
for (let index = 0; index < 16; index++) {
|
|
36
|
+
output = CROCKFORD[Number(value % 32n)] + output;
|
|
37
|
+
value = value / 32n;
|
|
38
|
+
}
|
|
39
|
+
return output;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createDeltaId(prefix: DeltaIdPrefix): string {
|
|
43
|
+
return `${prefix}_${encodeTime(Date.now())}${encodeRandom(randomBytes(10))}`;
|
|
44
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { runDeltaStatus, formatDeltaStatusHuman, formatDeltaStatusJson } from "./status.ts";
|
|
2
|
+
export { runDeltaTimeline, formatDeltaTimelineHuman, formatDeltaTimelineJson } from "./timeline.ts";
|
|
3
|
+
export { runDeltaExplain, formatDeltaExplainHuman, formatDeltaExplainJson } from "./explain.ts";
|
|
4
|
+
export { runDeltaSessionCommand, formatDeltaSessionHuman, formatDeltaSessionJson } from "./session.ts";
|
|
5
|
+
export { createAmbientDeltaRecorder, recordParsedCliCommand, isDeltaDisabled } from "./recorder.ts";
|
|
6
|
+
export { DeltaStore, getDeltaStorePath } from "./store.ts";
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import type { ForgeCommand } from "../cli/parse.ts";
|
|
4
|
+
import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
|
|
5
|
+
import { normalizePath } from "../compiler/primitives/paths.ts";
|
|
6
|
+
import { hashUtf8Bytes } from "../compiler/primitives/hash.ts";
|
|
7
|
+
import { DeltaStore, type DeltaRuntimeCallInput } from "./store.ts";
|
|
8
|
+
import { classifyArtifactKind } from "./classifier.ts";
|
|
9
|
+
import { readDeltaGitSnapshot } from "./git-observer.ts";
|
|
10
|
+
|
|
11
|
+
export interface AmbientDeltaRecorder {
|
|
12
|
+
sessionId?: string;
|
|
13
|
+
recordRuntimeCall(input: DeltaRuntimeCallInput & { diagnostics?: unknown[] }): Promise<void>;
|
|
14
|
+
recordAgentTool(input: { toolName: string; risk?: string; status: "completed" | "failed"; traceId?: string; durationMs?: number }): Promise<void>;
|
|
15
|
+
recordFileChanged(path: string, changeType?: "created" | "modified" | "deleted" | "renamed" | "generated"): Promise<void>;
|
|
16
|
+
close(summary?: string): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isDeltaDisabled(argv: string[] = process.argv.slice(2)): boolean {
|
|
20
|
+
return argv.includes("--no-delta") || process.env.FORGE_DELTA === "0";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function createAmbientDeltaRecorder(
|
|
24
|
+
workspaceRoot: string,
|
|
25
|
+
source: "forge-dev" | "forge-command" | "auto",
|
|
26
|
+
summary?: string,
|
|
27
|
+
): Promise<AmbientDeltaRecorder> {
|
|
28
|
+
if (isDeltaDisabled()) {
|
|
29
|
+
return noopRecorder;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const store = await DeltaStore.open(workspaceRoot);
|
|
33
|
+
const actorId = await store.ensureActor("forge", "forge-cli", { pid: process.pid });
|
|
34
|
+
const sessionId = await store.createSession({
|
|
35
|
+
source,
|
|
36
|
+
summary,
|
|
37
|
+
metadata: { actorId },
|
|
38
|
+
git: readDeltaGitSnapshot(workspaceRoot),
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
sessionId,
|
|
42
|
+
async recordRuntimeCall(input) {
|
|
43
|
+
await safeDelta(async () => {
|
|
44
|
+
const failedCode = input.diagnosticCode ?? diagnosticCode(input.diagnostics);
|
|
45
|
+
await store.appendOperation({
|
|
46
|
+
sessionId,
|
|
47
|
+
actorId,
|
|
48
|
+
kind: input.result === "denied"
|
|
49
|
+
? "runtime.entry.denied"
|
|
50
|
+
: input.result === "failed"
|
|
51
|
+
? "runtime.entry.failed"
|
|
52
|
+
: "runtime.entry.executed",
|
|
53
|
+
summary: `${input.entryName} ${input.result ?? "executed"}`,
|
|
54
|
+
data: {
|
|
55
|
+
entryName: input.entryName,
|
|
56
|
+
entryKind: input.entryKind,
|
|
57
|
+
result: input.result,
|
|
58
|
+
traceId: input.traceId,
|
|
59
|
+
diagnosticCode: failedCode,
|
|
60
|
+
},
|
|
61
|
+
runtimeCall: { ...input, diagnosticCode: failedCode },
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
async recordAgentTool(input) {
|
|
66
|
+
await safeDelta(async () => {
|
|
67
|
+
await store.appendOperation({
|
|
68
|
+
sessionId,
|
|
69
|
+
actorId,
|
|
70
|
+
kind: "agent.tool.called",
|
|
71
|
+
summary: `${input.toolName} ${input.status}`,
|
|
72
|
+
data: {
|
|
73
|
+
toolName: input.toolName,
|
|
74
|
+
risk: input.risk,
|
|
75
|
+
status: input.status,
|
|
76
|
+
traceId: input.traceId,
|
|
77
|
+
durationMs: input.durationMs,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
async recordFileChanged(path, changeType = "modified") {
|
|
83
|
+
await safeDelta(() => store.recordFilePath(sessionId, path, changeType));
|
|
84
|
+
},
|
|
85
|
+
async close(closeSummary) {
|
|
86
|
+
await safeDelta(async () => {
|
|
87
|
+
await store.endSession(sessionId, closeSummary);
|
|
88
|
+
await store.close();
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
} catch {
|
|
93
|
+
return noopRecorder;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function recordParsedCliCommand(input: {
|
|
98
|
+
command: ForgeCommand;
|
|
99
|
+
argv: string[];
|
|
100
|
+
exitCode: number;
|
|
101
|
+
durationMs: number;
|
|
102
|
+
}): Promise<void> {
|
|
103
|
+
if (
|
|
104
|
+
isDeltaDisabled(input.argv) ||
|
|
105
|
+
input.command.kind === "delta" ||
|
|
106
|
+
input.command.kind === "session" ||
|
|
107
|
+
input.command.kind === "timeline" ||
|
|
108
|
+
input.command.kind === "explain"
|
|
109
|
+
) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await safeDelta(async () => {
|
|
113
|
+
const workspaceRoot = commandWorkspaceRoot(input.command);
|
|
114
|
+
const store = await DeltaStore.open(workspaceRoot);
|
|
115
|
+
const actorId = await store.ensureActor("forge", "forge-cli", { pid: process.pid });
|
|
116
|
+
const sessionId = await store.createSession({
|
|
117
|
+
source: "forge-command",
|
|
118
|
+
summary: `forge ${input.command.kind}`,
|
|
119
|
+
metadata: { argv: input.argv },
|
|
120
|
+
git: readDeltaGitSnapshot(workspaceRoot),
|
|
121
|
+
});
|
|
122
|
+
const commandName = commandDisplayName(input.command);
|
|
123
|
+
const exitKind = input.exitCode === 0 ? "forge.command.completed" : "forge.command.failed";
|
|
124
|
+
const data = {
|
|
125
|
+
command: commandName,
|
|
126
|
+
exitCode: input.exitCode,
|
|
127
|
+
durationMs: input.durationMs,
|
|
128
|
+
git: readDeltaGitSnapshot(workspaceRoot),
|
|
129
|
+
};
|
|
130
|
+
await store.appendOperation({
|
|
131
|
+
sessionId,
|
|
132
|
+
actorId,
|
|
133
|
+
kind: exitKind,
|
|
134
|
+
summary: `${commandName} ${input.exitCode === 0 ? "completed" : "failed"}`,
|
|
135
|
+
data,
|
|
136
|
+
commandRun: {
|
|
137
|
+
commandName,
|
|
138
|
+
argv: input.argv,
|
|
139
|
+
exitCode: input.exitCode,
|
|
140
|
+
durationMs: input.durationMs,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await recordSpecializedCommand(store, sessionId, actorId, input.command, input.exitCode, commandName);
|
|
145
|
+
await store.endSession(sessionId, `forge ${input.command.kind} exited ${input.exitCode}`);
|
|
146
|
+
await store.close();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function recordSpecializedCommand(
|
|
151
|
+
store: DeltaStore,
|
|
152
|
+
sessionId: string,
|
|
153
|
+
actorId: string,
|
|
154
|
+
command: ForgeCommand,
|
|
155
|
+
exitCode: number,
|
|
156
|
+
commandName: string,
|
|
157
|
+
): Promise<void> {
|
|
158
|
+
if (command.kind === "generate" && exitCode === 0) {
|
|
159
|
+
const artifacts = listGeneratedArtifacts(process.cwd()).map((path) => ({
|
|
160
|
+
path,
|
|
161
|
+
artifactKind: classifyArtifactKind(path),
|
|
162
|
+
generated: true,
|
|
163
|
+
hash: hashFileIfPresent(process.cwd(), path),
|
|
164
|
+
}));
|
|
165
|
+
await store.appendOperation({
|
|
166
|
+
sessionId,
|
|
167
|
+
actorId,
|
|
168
|
+
kind: "artifact.generated",
|
|
169
|
+
summary: `Generated ${artifacts.length} Forge artifacts`,
|
|
170
|
+
data: { count: artifacts.length, generator: "forge generate" },
|
|
171
|
+
artifacts,
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (command.kind === "manifest") {
|
|
177
|
+
await store.appendOperation({
|
|
178
|
+
sessionId,
|
|
179
|
+
actorId,
|
|
180
|
+
kind: command.subcommand === "import" ? "manifest.imported" : "manifest.validated",
|
|
181
|
+
summary: `${command.subcommand} ${command.path}`,
|
|
182
|
+
data: { path: command.path, subcommand: command.subcommand, exitCode },
|
|
183
|
+
artifacts: command.subcommand === "import"
|
|
184
|
+
? ["externalServices.json", "agentContract.json", "api.json"].map((name) => ({
|
|
185
|
+
path: `${GENERATED_DIR}/${name}`,
|
|
186
|
+
generated: true,
|
|
187
|
+
}))
|
|
188
|
+
: undefined,
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (command.kind === "run" && command.name) {
|
|
194
|
+
const queryMode = command.queryMode === true;
|
|
195
|
+
await store.appendOperation({
|
|
196
|
+
sessionId,
|
|
197
|
+
actorId,
|
|
198
|
+
kind: exitCode === 0 ? "runtime.entry.executed" : "runtime.entry.failed",
|
|
199
|
+
summary: `${command.name} ${exitCode === 0 ? "success" : "failed"}`,
|
|
200
|
+
data: { entryName: command.name, entryKind: queryMode ? "query" : "command", exitCode },
|
|
201
|
+
runtimeCall: {
|
|
202
|
+
entryName: command.name,
|
|
203
|
+
entryKind: queryMode ? "query" : "command",
|
|
204
|
+
result: exitCode === 0 ? "success" : "failed",
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (command.kind === "query" && command.subcommand === "run" && command.name) {
|
|
211
|
+
await store.appendOperation({
|
|
212
|
+
sessionId,
|
|
213
|
+
actorId,
|
|
214
|
+
kind: exitCode === 0 ? "runtime.entry.executed" : "runtime.entry.failed",
|
|
215
|
+
summary: `${command.name} ${exitCode === 0 ? "success" : "failed"}`,
|
|
216
|
+
data: { entryName: command.name, entryKind: "query", exitCode },
|
|
217
|
+
runtimeCall: {
|
|
218
|
+
entryName: command.name,
|
|
219
|
+
entryKind: "query",
|
|
220
|
+
result: exitCode === 0 ? "success" : "failed",
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (command.kind === "security" && command.subcommand === "prove") {
|
|
227
|
+
await store.appendOperation({
|
|
228
|
+
sessionId,
|
|
229
|
+
actorId,
|
|
230
|
+
kind: "proof.run",
|
|
231
|
+
summary: `security prove ${exitCode === 0 ? "passed" : "failed"}`,
|
|
232
|
+
data: { command: commandName, exitCode },
|
|
233
|
+
proof: {
|
|
234
|
+
proofKind: "security-prove",
|
|
235
|
+
command: commandName,
|
|
236
|
+
result: exitCode === 0 ? "passed" : "failed",
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (command.kind === "ai" && command.subcommand === "redteam") {
|
|
243
|
+
await store.appendOperation({
|
|
244
|
+
sessionId,
|
|
245
|
+
actorId,
|
|
246
|
+
kind: "proof.run",
|
|
247
|
+
summary: `ai redteam ${exitCode === 0 ? "passed" : "failed"}`,
|
|
248
|
+
data: { command: commandName, exitCode },
|
|
249
|
+
proof: {
|
|
250
|
+
proofKind: "ai-redteam",
|
|
251
|
+
command: commandName,
|
|
252
|
+
result: exitCode === 0 ? "passed" : "failed",
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (command.kind === "check" || command.kind === "verify") {
|
|
259
|
+
await store.appendOperation({
|
|
260
|
+
sessionId,
|
|
261
|
+
actorId,
|
|
262
|
+
kind: "proof.run",
|
|
263
|
+
summary: `${commandName} ${exitCode === 0 ? "passed" : "failed"}`,
|
|
264
|
+
data: { command: commandName, exitCode },
|
|
265
|
+
proof: {
|
|
266
|
+
proofKind: command.kind === "check" ? "forge-check" : "forge-verify",
|
|
267
|
+
command: commandName,
|
|
268
|
+
result: exitCode === 0 ? "passed" : "failed",
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const noopRecorder: AmbientDeltaRecorder = {
|
|
275
|
+
async recordRuntimeCall() {},
|
|
276
|
+
async recordAgentTool() {},
|
|
277
|
+
async recordFileChanged() {},
|
|
278
|
+
async close() {},
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
async function safeDelta(fn: () => Promise<void>): Promise<void> {
|
|
282
|
+
try {
|
|
283
|
+
await fn();
|
|
284
|
+
} catch {
|
|
285
|
+
// Delta recording is ambient. It must never change primary command behavior.
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function diagnosticCode(diagnostics: unknown[] | undefined): string | undefined {
|
|
290
|
+
const first = diagnostics?.find((diagnostic) =>
|
|
291
|
+
diagnostic && typeof diagnostic === "object" && "code" in diagnostic,
|
|
292
|
+
) as { code?: unknown } | undefined;
|
|
293
|
+
return typeof first?.code === "string" ? first.code : undefined;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function commandWorkspaceRoot(command: ForgeCommand): string {
|
|
297
|
+
if ("workspaceRoot" in command && typeof command.workspaceRoot === "string") {
|
|
298
|
+
return command.workspaceRoot;
|
|
299
|
+
}
|
|
300
|
+
if ("options" in command && command.options && typeof command.options === "object" && "workspaceRoot" in command.options) {
|
|
301
|
+
const root = (command.options as { workspaceRoot?: unknown }).workspaceRoot;
|
|
302
|
+
if (typeof root === "string") {
|
|
303
|
+
return root;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return process.cwd().replace(/\\/g, "/");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function commandDisplayName(command: ForgeCommand): string {
|
|
310
|
+
if ("subcommand" in command && typeof command.subcommand === "string") {
|
|
311
|
+
return `forge ${command.kind} ${command.subcommand}`;
|
|
312
|
+
}
|
|
313
|
+
if ("options" in command && command.options && typeof command.options === "object" && "subcommand" in command.options) {
|
|
314
|
+
const subcommand = (command.options as { subcommand?: unknown }).subcommand;
|
|
315
|
+
if (typeof subcommand === "string") {
|
|
316
|
+
return `forge ${command.kind} ${subcommand}`;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return `forge ${command.kind}`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function listGeneratedArtifacts(workspaceRoot: string): string[] {
|
|
323
|
+
const root = join(workspaceRoot, GENERATED_DIR);
|
|
324
|
+
try {
|
|
325
|
+
return walk(root).map((path) => normalizePath(relative(workspaceRoot, path)));
|
|
326
|
+
} catch {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function walk(root: string): string[] {
|
|
332
|
+
const entries: string[] = [];
|
|
333
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
334
|
+
const path = join(root, entry.name);
|
|
335
|
+
if (entry.isDirectory()) {
|
|
336
|
+
entries.push(...walk(path));
|
|
337
|
+
} else if (entry.isFile()) {
|
|
338
|
+
entries.push(path);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return entries;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function hashFileIfPresent(workspaceRoot: string, path: string): string | undefined {
|
|
345
|
+
try {
|
|
346
|
+
return hashUtf8Bytes(readFileSync(join(workspaceRoot, path)));
|
|
347
|
+
} catch {
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { scrubEnvelopePayload } from "../runtime/telemetry/scrubber.ts";
|
|
2
|
+
|
|
3
|
+
const SECRET_KEY_PATTERN = /password|secret|token|apikey|apiKey|api_key|authorization|cookie/i;
|
|
4
|
+
|
|
5
|
+
export interface DeltaRedactionResult<T> {
|
|
6
|
+
value: T;
|
|
7
|
+
redaction: {
|
|
8
|
+
diagnostics: string[];
|
|
9
|
+
redacted: boolean;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function collectSecretLikeKeys(value: unknown, path: string[] = [], keys: string[] = []): string[] {
|
|
14
|
+
if (!value || typeof value !== "object") {
|
|
15
|
+
return keys;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
value.forEach((item, index) => collectSecretLikeKeys(item, [...path, String(index)], keys));
|
|
19
|
+
return keys;
|
|
20
|
+
}
|
|
21
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
22
|
+
const next = [...path, key];
|
|
23
|
+
if (SECRET_KEY_PATTERN.test(key)) {
|
|
24
|
+
keys.push(next.join("."));
|
|
25
|
+
}
|
|
26
|
+
collectSecretLikeKeys(child, next, keys);
|
|
27
|
+
}
|
|
28
|
+
return keys;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function redactDeltaPayload<T extends Record<string, unknown>>(
|
|
32
|
+
value: T,
|
|
33
|
+
options: { secretValues?: string[] } = {},
|
|
34
|
+
): DeltaRedactionResult<T> {
|
|
35
|
+
const secretKeys = collectSecretLikeKeys(value);
|
|
36
|
+
const scrubbed = scrubEnvelopePayload(value, {
|
|
37
|
+
secretValues: options.secretValues,
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
value: scrubbed.value,
|
|
41
|
+
redaction: {
|
|
42
|
+
diagnostics: [
|
|
43
|
+
...secretKeys.map((key) => `redacted secret-like key ${key}`),
|
|
44
|
+
...scrubbed.diagnostics.map((diagnostic) => diagnostic.message),
|
|
45
|
+
],
|
|
46
|
+
redacted: secretKeys.length > 0 || scrubbed.diagnostics.length > 0,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
export const DELTA_SCHEMA_VERSION = "0.3.0";
|
|
2
|
+
|
|
3
|
+
export const DELTA_SCHEMA_SQL = [
|
|
4
|
+
`CREATE TABLE IF NOT EXISTS delta_meta (
|
|
5
|
+
key text PRIMARY KEY,
|
|
6
|
+
value text NOT NULL,
|
|
7
|
+
updated_at text NOT NULL
|
|
8
|
+
)`,
|
|
9
|
+
`CREATE TABLE IF NOT EXISTS actors (
|
|
10
|
+
id text PRIMARY KEY,
|
|
11
|
+
kind text NOT NULL,
|
|
12
|
+
name text,
|
|
13
|
+
metadata_json text,
|
|
14
|
+
created_at text NOT NULL
|
|
15
|
+
)`,
|
|
16
|
+
`CREATE TABLE IF NOT EXISTS sessions (
|
|
17
|
+
id text PRIMARY KEY,
|
|
18
|
+
workspace_root text NOT NULL,
|
|
19
|
+
source text NOT NULL,
|
|
20
|
+
branch text,
|
|
21
|
+
started_at text NOT NULL,
|
|
22
|
+
ended_at text,
|
|
23
|
+
summary text,
|
|
24
|
+
metadata_json text
|
|
25
|
+
)`,
|
|
26
|
+
`CREATE TABLE IF NOT EXISTS operations (
|
|
27
|
+
id text PRIMARY KEY,
|
|
28
|
+
session_id text,
|
|
29
|
+
txn_id text,
|
|
30
|
+
kind text NOT NULL,
|
|
31
|
+
timestamp text NOT NULL,
|
|
32
|
+
actor_id text,
|
|
33
|
+
summary text,
|
|
34
|
+
data_json text NOT NULL,
|
|
35
|
+
redaction_json text,
|
|
36
|
+
hash text,
|
|
37
|
+
prev_hash text
|
|
38
|
+
)`,
|
|
39
|
+
`CREATE TABLE IF NOT EXISTS file_changes (
|
|
40
|
+
id text PRIMARY KEY,
|
|
41
|
+
operation_id text NOT NULL,
|
|
42
|
+
path text NOT NULL,
|
|
43
|
+
change_type text NOT NULL,
|
|
44
|
+
hash_before text,
|
|
45
|
+
hash_after text,
|
|
46
|
+
diff_summary text,
|
|
47
|
+
semantic_hints_json text
|
|
48
|
+
)`,
|
|
49
|
+
`CREATE TABLE IF NOT EXISTS command_runs (
|
|
50
|
+
id text PRIMARY KEY,
|
|
51
|
+
operation_id text NOT NULL,
|
|
52
|
+
command_name text NOT NULL,
|
|
53
|
+
argv_redacted_json text,
|
|
54
|
+
exit_code integer,
|
|
55
|
+
duration_ms integer,
|
|
56
|
+
diagnostics_json text
|
|
57
|
+
)`,
|
|
58
|
+
`CREATE TABLE IF NOT EXISTS proofs (
|
|
59
|
+
id text PRIMARY KEY,
|
|
60
|
+
operation_id text NOT NULL,
|
|
61
|
+
proof_kind text NOT NULL,
|
|
62
|
+
command text,
|
|
63
|
+
result text NOT NULL,
|
|
64
|
+
assurance text,
|
|
65
|
+
diagnostics_json text,
|
|
66
|
+
artifact_paths_json text
|
|
67
|
+
)`,
|
|
68
|
+
`CREATE TABLE IF NOT EXISTS runtime_calls (
|
|
69
|
+
id text PRIMARY KEY,
|
|
70
|
+
operation_id text NOT NULL,
|
|
71
|
+
entry_name text NOT NULL,
|
|
72
|
+
entry_kind text,
|
|
73
|
+
risk text,
|
|
74
|
+
policy text,
|
|
75
|
+
tenant_scoped integer,
|
|
76
|
+
result text,
|
|
77
|
+
diagnostic_code text,
|
|
78
|
+
trace_id text,
|
|
79
|
+
service text,
|
|
80
|
+
language text
|
|
81
|
+
)`,
|
|
82
|
+
`CREATE TABLE IF NOT EXISTS artifacts (
|
|
83
|
+
id text PRIMARY KEY,
|
|
84
|
+
operation_id text NOT NULL,
|
|
85
|
+
path text NOT NULL,
|
|
86
|
+
artifact_kind text,
|
|
87
|
+
hash text,
|
|
88
|
+
generated integer
|
|
89
|
+
)`,
|
|
90
|
+
`CREATE TABLE IF NOT EXISTS git_mappings (
|
|
91
|
+
id text PRIMARY KEY,
|
|
92
|
+
operation_id text,
|
|
93
|
+
commit_sha text,
|
|
94
|
+
branch text,
|
|
95
|
+
detected_at text,
|
|
96
|
+
confidence real,
|
|
97
|
+
metadata_json text
|
|
98
|
+
)`,
|
|
99
|
+
`CREATE TABLE IF NOT EXISTS work_sessions (
|
|
100
|
+
id text PRIMARY KEY,
|
|
101
|
+
workspace_root text NOT NULL,
|
|
102
|
+
kind text NOT NULL,
|
|
103
|
+
status text NOT NULL,
|
|
104
|
+
title text,
|
|
105
|
+
inferred_intent text,
|
|
106
|
+
confidence real NOT NULL,
|
|
107
|
+
started_at text NOT NULL,
|
|
108
|
+
ended_at text,
|
|
109
|
+
actor_ids_json text,
|
|
110
|
+
git_branch text,
|
|
111
|
+
git_head_start text,
|
|
112
|
+
git_head_end text,
|
|
113
|
+
summary text,
|
|
114
|
+
metadata_json text,
|
|
115
|
+
created_at text NOT NULL,
|
|
116
|
+
updated_at text NOT NULL
|
|
117
|
+
)`,
|
|
118
|
+
`CREATE TABLE IF NOT EXISTS work_session_operations (
|
|
119
|
+
work_session_id text NOT NULL,
|
|
120
|
+
operation_id text NOT NULL,
|
|
121
|
+
link_type text NOT NULL,
|
|
122
|
+
confidence real NOT NULL,
|
|
123
|
+
reason_json text,
|
|
124
|
+
created_at text NOT NULL,
|
|
125
|
+
PRIMARY KEY (work_session_id, operation_id)
|
|
126
|
+
)`,
|
|
127
|
+
`CREATE TABLE IF NOT EXISTS work_session_signals (
|
|
128
|
+
id text PRIMARY KEY,
|
|
129
|
+
work_session_id text,
|
|
130
|
+
operation_id text,
|
|
131
|
+
signal_type text NOT NULL,
|
|
132
|
+
weight real NOT NULL,
|
|
133
|
+
value text,
|
|
134
|
+
metadata_json text,
|
|
135
|
+
created_at text NOT NULL
|
|
136
|
+
)`,
|
|
137
|
+
`CREATE TABLE IF NOT EXISTS work_session_summaries (
|
|
138
|
+
id text PRIMARY KEY,
|
|
139
|
+
work_session_id text NOT NULL,
|
|
140
|
+
summary_type text NOT NULL,
|
|
141
|
+
content text NOT NULL,
|
|
142
|
+
generated_by text,
|
|
143
|
+
created_at text NOT NULL,
|
|
144
|
+
redaction_json text
|
|
145
|
+
)`,
|
|
146
|
+
`CREATE TABLE IF NOT EXISTS timeline_events (
|
|
147
|
+
id text PRIMARY KEY,
|
|
148
|
+
operation_id text,
|
|
149
|
+
session_id text,
|
|
150
|
+
change_id text,
|
|
151
|
+
timestamp text NOT NULL,
|
|
152
|
+
event_kind text NOT NULL,
|
|
153
|
+
title text NOT NULL,
|
|
154
|
+
summary text,
|
|
155
|
+
severity text,
|
|
156
|
+
confidence real NOT NULL,
|
|
157
|
+
data_json text NOT NULL
|
|
158
|
+
)`,
|
|
159
|
+
`CREATE TABLE IF NOT EXISTS timeline_entities (
|
|
160
|
+
id text PRIMARY KEY,
|
|
161
|
+
timeline_event_id text NOT NULL,
|
|
162
|
+
entity_kind text NOT NULL,
|
|
163
|
+
entity_name text NOT NULL,
|
|
164
|
+
role text NOT NULL,
|
|
165
|
+
confidence real NOT NULL
|
|
166
|
+
)`,
|
|
167
|
+
`CREATE TABLE IF NOT EXISTS timeline_edges (
|
|
168
|
+
id text PRIMARY KEY,
|
|
169
|
+
from_event_id text NOT NULL,
|
|
170
|
+
to_event_id text NOT NULL,
|
|
171
|
+
edge_kind text NOT NULL,
|
|
172
|
+
confidence real NOT NULL,
|
|
173
|
+
reason_json text
|
|
174
|
+
)`,
|
|
175
|
+
`CREATE TABLE IF NOT EXISTS timeline_projection_state (
|
|
176
|
+
id text PRIMARY KEY,
|
|
177
|
+
last_operation_id text,
|
|
178
|
+
last_rebuild_at text,
|
|
179
|
+
projection_version text,
|
|
180
|
+
graph_hash text
|
|
181
|
+
)`,
|
|
182
|
+
`CREATE TABLE IF NOT EXISTS agent_event_sources (
|
|
183
|
+
id text PRIMARY KEY,
|
|
184
|
+
source_name text NOT NULL,
|
|
185
|
+
source_kind text NOT NULL,
|
|
186
|
+
integration_kind text NOT NULL,
|
|
187
|
+
trust_level text NOT NULL,
|
|
188
|
+
config_json text,
|
|
189
|
+
created_at text NOT NULL
|
|
190
|
+
)`,
|
|
191
|
+
`CREATE TABLE IF NOT EXISTS external_agent_events (
|
|
192
|
+
id text PRIMARY KEY,
|
|
193
|
+
source_id text NOT NULL,
|
|
194
|
+
external_session_id text,
|
|
195
|
+
external_turn_id text,
|
|
196
|
+
event_kind text NOT NULL,
|
|
197
|
+
captured_at text NOT NULL,
|
|
198
|
+
payload_redacted_json text NOT NULL,
|
|
199
|
+
payload_hash text,
|
|
200
|
+
raw_stored integer DEFAULT 0,
|
|
201
|
+
normalization_status text NOT NULL
|
|
202
|
+
)`,
|
|
203
|
+
`CREATE TABLE IF NOT EXISTS agent_memory_events (
|
|
204
|
+
id text PRIMARY KEY,
|
|
205
|
+
external_event_id text,
|
|
206
|
+
forge_session_id text,
|
|
207
|
+
forge_change_id text,
|
|
208
|
+
operation_id text,
|
|
209
|
+
normalized_kind text NOT NULL,
|
|
210
|
+
summary text,
|
|
211
|
+
confidence real NOT NULL,
|
|
212
|
+
data_json text NOT NULL
|
|
213
|
+
)`,
|
|
214
|
+
`CREATE INDEX IF NOT EXISTS operations_timestamp_idx ON operations(timestamp)`,
|
|
215
|
+
`CREATE INDEX IF NOT EXISTS operations_kind_idx ON operations(kind)`,
|
|
216
|
+
`CREATE INDEX IF NOT EXISTS operations_session_idx ON operations(session_id)`,
|
|
217
|
+
`CREATE INDEX IF NOT EXISTS file_changes_path_idx ON file_changes(path)`,
|
|
218
|
+
`CREATE INDEX IF NOT EXISTS runtime_calls_entry_idx ON runtime_calls(entry_name)`,
|
|
219
|
+
`CREATE INDEX IF NOT EXISTS artifacts_path_idx ON artifacts(path)`,
|
|
220
|
+
`CREATE INDEX IF NOT EXISTS work_sessions_status_idx ON work_sessions(status, updated_at)`,
|
|
221
|
+
`CREATE INDEX IF NOT EXISTS work_sessions_branch_idx ON work_sessions(git_branch)`,
|
|
222
|
+
`CREATE INDEX IF NOT EXISTS work_session_operations_operation_idx ON work_session_operations(operation_id)`,
|
|
223
|
+
`CREATE INDEX IF NOT EXISTS work_session_signals_session_idx ON work_session_signals(work_session_id)`,
|
|
224
|
+
`CREATE INDEX IF NOT EXISTS timeline_events_time_idx ON timeline_events(timestamp)`,
|
|
225
|
+
`CREATE INDEX IF NOT EXISTS timeline_events_kind_idx ON timeline_events(event_kind)`,
|
|
226
|
+
`CREATE INDEX IF NOT EXISTS timeline_events_operation_idx ON timeline_events(operation_id)`,
|
|
227
|
+
`CREATE INDEX IF NOT EXISTS timeline_events_change_idx ON timeline_events(change_id)`,
|
|
228
|
+
`CREATE INDEX IF NOT EXISTS timeline_events_session_idx ON timeline_events(session_id)`,
|
|
229
|
+
`CREATE INDEX IF NOT EXISTS timeline_entities_entity_idx ON timeline_entities(entity_kind, entity_name)`,
|
|
230
|
+
`CREATE INDEX IF NOT EXISTS timeline_entities_event_idx ON timeline_entities(timeline_event_id)`,
|
|
231
|
+
`CREATE INDEX IF NOT EXISTS timeline_edges_from_idx ON timeline_edges(from_event_id)`,
|
|
232
|
+
`CREATE INDEX IF NOT EXISTS timeline_edges_to_idx ON timeline_edges(to_event_id)`,
|
|
233
|
+
`CREATE INDEX IF NOT EXISTS agent_event_sources_name_idx ON agent_event_sources(source_name, integration_kind)`,
|
|
234
|
+
`CREATE INDEX IF NOT EXISTS external_agent_events_source_idx ON external_agent_events(source_id, captured_at)`,
|
|
235
|
+
`CREATE INDEX IF NOT EXISTS external_agent_events_kind_idx ON external_agent_events(event_kind)`,
|
|
236
|
+
`CREATE INDEX IF NOT EXISTS agent_memory_events_kind_idx ON agent_memory_events(normalized_kind)`,
|
|
237
|
+
`CREATE INDEX IF NOT EXISTS agent_memory_events_operation_idx ON agent_memory_events(operation_id)`,
|
|
238
|
+
];
|