@lasso-ai/cli 1.5.0 → 2.0.0
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/dist/cli/agent.d.ts +1 -0
- package/dist/cli/backends.d.ts +53 -0
- package/dist/cli/backends.js +267 -0
- package/dist/cli/bridge.d.ts +8 -5
- package/dist/cli/bridge.js +357 -151
- package/dist/cli/host/install.js +12 -1
- package/dist/cli/local-runtime.d.ts +28 -0
- package/dist/cli/local-runtime.js +294 -0
- package/dist/cli/oneshot.js +28 -10
- package/dist/cli/runtime-client.d.ts +91 -10
- package/dist/cli/runtime-client.js +38 -3
- package/dist/overlay.js +44 -28
- package/dist/runtime/__tests__/adapter-normalization.test.d.ts +1 -0
- package/dist/runtime/__tests__/adapter-normalization.test.js +392 -0
- package/dist/runtime/__tests__/state-machine.test.d.ts +1 -0
- package/dist/runtime/__tests__/state-machine.test.js +107 -0
- package/dist/runtime/__tests__/verification-engine.test.d.ts +1 -0
- package/dist/runtime/__tests__/verification-engine.test.js +91 -0
- package/dist/runtime/__tests__/workspace-guard.test.d.ts +1 -0
- package/dist/runtime/__tests__/workspace-guard.test.js +104 -0
- package/dist/runtime/adapters/claude-code-adapter.d.ts +46 -0
- package/dist/runtime/adapters/claude-code-adapter.js +230 -0
- package/dist/runtime/adapters/codex-cli-adapter.d.ts +44 -0
- package/dist/runtime/adapters/codex-cli-adapter.js +202 -0
- package/dist/runtime/adapters/cursor-adapter.d.ts +47 -0
- package/dist/runtime/adapters/cursor-adapter.js +214 -0
- package/dist/runtime/adapters/factory.d.ts +19 -0
- package/dist/runtime/adapters/factory.js +48 -0
- package/dist/runtime/adapters/local-agent-adapter.d.ts +143 -0
- package/dist/runtime/adapters/local-agent-adapter.js +408 -0
- package/dist/runtime/adapters/opencode-adapter.d.ts +43 -0
- package/dist/runtime/adapters/opencode-adapter.js +210 -0
- package/dist/runtime/agent-runtime.d.ts +286 -0
- package/dist/runtime/agent-runtime.js +8 -0
- package/dist/runtime/agent-state-machine.d.ts +67 -0
- package/dist/runtime/agent-state-machine.js +234 -0
- package/dist/runtime/backend-interface.d.ts +272 -0
- package/dist/runtime/backend-interface.js +38 -0
- package/dist/runtime/event-mapper.d.ts +11 -0
- package/dist/runtime/event-mapper.js +40 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.js +43 -0
- package/dist/runtime/local-session-runner.d.ts +60 -0
- package/dist/runtime/local-session-runner.js +185 -0
- package/dist/runtime/review.d.ts +24 -0
- package/dist/runtime/review.js +30 -0
- package/dist/runtime/verification-engine.d.ts +71 -0
- package/dist/runtime/verification-engine.js +374 -0
- package/dist/runtime/workspace-guard.d.ts +70 -0
- package/dist/runtime/workspace-guard.js +321 -0
- package/docs/local-coding-agents.md +183 -0
- package/package.json +22 -3
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkspaceGuard diff → review payload normalization.
|
|
3
|
+
*
|
|
4
|
+
* Shared by the server orchestrator and the CLI LocalRuntime: the guard diff
|
|
5
|
+
* is the authoritative record of what an agent changed, normalized into the
|
|
6
|
+
* pendingChanges contract consumed by the review UI and the apply flow.
|
|
7
|
+
*/
|
|
8
|
+
import { FileChange } from "./workspace-guard";
|
|
9
|
+
import { AgentAction } from "./agent-runtime";
|
|
10
|
+
export interface PendingChange {
|
|
11
|
+
filePath: string;
|
|
12
|
+
oldString: string;
|
|
13
|
+
newString: string;
|
|
14
|
+
operation: "created" | "modified" | "deleted";
|
|
15
|
+
}
|
|
16
|
+
export interface WorkspaceReview {
|
|
17
|
+
/** Reviewable (text) changes in the wire format used by the review UI. */
|
|
18
|
+
changes: PendingChange[];
|
|
19
|
+
/** Paths of binary changes — preserved for rollback, not shown as text. */
|
|
20
|
+
binaryPaths: string[];
|
|
21
|
+
/** Normalized session actions matching the reviewable changes. */
|
|
22
|
+
actions: AgentAction[];
|
|
23
|
+
}
|
|
24
|
+
export declare function diffToReview(rawChanges: FileChange[]): WorkspaceReview;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WorkspaceGuard diff → review payload normalization.
|
|
4
|
+
*
|
|
5
|
+
* Shared by the server orchestrator and the CLI LocalRuntime: the guard diff
|
|
6
|
+
* is the authoritative record of what an agent changed, normalized into the
|
|
7
|
+
* pendingChanges contract consumed by the review UI and the apply flow.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.diffToReview = diffToReview;
|
|
11
|
+
function diffToReview(rawChanges) {
|
|
12
|
+
const binaryPaths = rawChanges.filter((c) => c.binary).map((c) => c.filePath);
|
|
13
|
+
const applyable = rawChanges.filter((c) => !c.binary);
|
|
14
|
+
const changes = applyable.map((change) => ({
|
|
15
|
+
filePath: change.filePath,
|
|
16
|
+
oldString: change.operation === "created" ? "" : change.oldContent ?? "",
|
|
17
|
+
newString: change.operation === "deleted" ? "" : change.newContent ?? "",
|
|
18
|
+
operation: change.operation,
|
|
19
|
+
}));
|
|
20
|
+
const actions = applyable.map((change) => ({
|
|
21
|
+
id: crypto.randomUUID(),
|
|
22
|
+
type: change.operation === "created" ? "create_file" : change.operation === "deleted" ? "delete_file" : "edit_file",
|
|
23
|
+
data: change.operation === "created"
|
|
24
|
+
? { filePath: change.filePath, content: change.newContent }
|
|
25
|
+
: change.operation === "deleted"
|
|
26
|
+
? { filePath: change.filePath }
|
|
27
|
+
: { filePath: change.filePath, oldString: change.oldContent ?? "", newString: change.newContent ?? "" },
|
|
28
|
+
}));
|
|
29
|
+
return { changes, binaryPaths, actions };
|
|
30
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Observation, AgentError, AgentPhase } from "./agent-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Verification Engine - Verifies changes after execution
|
|
4
|
+
*
|
|
5
|
+
* Mandatory verification step in the agent loop.
|
|
6
|
+
* Timeouts are failures or inconclusive observations, never proof of success.
|
|
7
|
+
*/
|
|
8
|
+
export interface VerificationConfig {
|
|
9
|
+
typecheckTimeout: number;
|
|
10
|
+
buildTimeout: number;
|
|
11
|
+
devServerTimeout: number;
|
|
12
|
+
browserTimeout: number;
|
|
13
|
+
}
|
|
14
|
+
export interface VerificationResult {
|
|
15
|
+
type: "typecheck" | "build" | "dev_server" | "browser";
|
|
16
|
+
success: boolean;
|
|
17
|
+
/** Set when the check could not meaningfully run (no tsconfig, no build script). */
|
|
18
|
+
skipped?: boolean;
|
|
19
|
+
/** Command that produced this result, when applicable. */
|
|
20
|
+
command?: string;
|
|
21
|
+
output?: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
observations: Observation[];
|
|
24
|
+
errors: AgentError[];
|
|
25
|
+
duration: number;
|
|
26
|
+
}
|
|
27
|
+
export declare class VerificationEngine {
|
|
28
|
+
private projectPath;
|
|
29
|
+
private config;
|
|
30
|
+
constructor(projectPath: string, config?: Partial<VerificationConfig>);
|
|
31
|
+
/**
|
|
32
|
+
* Run all verifications
|
|
33
|
+
*/
|
|
34
|
+
verifyAll(phase: AgentPhase): Promise<VerificationResult[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Verify TypeScript type checking
|
|
37
|
+
*/
|
|
38
|
+
verifyTypecheck(phase: AgentPhase): Promise<VerificationResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Verify build
|
|
41
|
+
*/
|
|
42
|
+
verifyBuild(phase: AgentPhase): Promise<VerificationResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Verify dev server health
|
|
45
|
+
*/
|
|
46
|
+
verifyDevServer(phase: AgentPhase): Promise<VerificationResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Verify browser/runtime state
|
|
49
|
+
*/
|
|
50
|
+
verifyBrowser(phase: AgentPhase): Promise<VerificationResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Check if verification passed overall
|
|
53
|
+
*/
|
|
54
|
+
allPassed(results: VerificationResult[]): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Get all verification errors
|
|
57
|
+
*/
|
|
58
|
+
getErrors(results: VerificationResult[]): AgentError[];
|
|
59
|
+
/**
|
|
60
|
+
* Get all verification observations
|
|
61
|
+
*/
|
|
62
|
+
getObservations(results: VerificationResult[]): Observation[];
|
|
63
|
+
/**
|
|
64
|
+
* Generate a unique ID
|
|
65
|
+
*/
|
|
66
|
+
private generateId;
|
|
67
|
+
/** tsconfig.json (or any tsconfig.*.json) present in the project root. */
|
|
68
|
+
private hasTypeScriptConfig;
|
|
69
|
+
/** package.json with a non-empty build script. */
|
|
70
|
+
private hasBuildScript;
|
|
71
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.VerificationEngine = void 0;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_util_1 = require("node:util");
|
|
11
|
+
const exec = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
12
|
+
class VerificationEngine {
|
|
13
|
+
projectPath;
|
|
14
|
+
config;
|
|
15
|
+
constructor(projectPath, config) {
|
|
16
|
+
this.projectPath = projectPath;
|
|
17
|
+
this.config = {
|
|
18
|
+
typecheckTimeout: config?.typecheckTimeout || 60000,
|
|
19
|
+
buildTimeout: config?.buildTimeout || 120000,
|
|
20
|
+
devServerTimeout: config?.devServerTimeout || 30000,
|
|
21
|
+
browserTimeout: config?.browserTimeout || 60000,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Run all verifications
|
|
26
|
+
*/
|
|
27
|
+
async verifyAll(phase) {
|
|
28
|
+
const results = [];
|
|
29
|
+
// Type checking
|
|
30
|
+
const typecheckResult = await this.verifyTypecheck(phase);
|
|
31
|
+
results.push(typecheckResult);
|
|
32
|
+
// If typecheck failed, skip build
|
|
33
|
+
if (!typecheckResult.success) {
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
// Build
|
|
37
|
+
const buildResult = await this.verifyBuild(phase);
|
|
38
|
+
results.push(buildResult);
|
|
39
|
+
// If build failed, skip dev server check
|
|
40
|
+
if (!buildResult.success) {
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
// Dev server health
|
|
44
|
+
const devServerResult = await this.verifyDevServer(phase);
|
|
45
|
+
results.push(devServerResult);
|
|
46
|
+
// Browser check (placeholder)
|
|
47
|
+
const browserResult = await this.verifyBrowser(phase);
|
|
48
|
+
results.push(browserResult);
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Verify TypeScript type checking
|
|
53
|
+
*/
|
|
54
|
+
async verifyTypecheck(phase) {
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
const observations = [];
|
|
57
|
+
const errors = [];
|
|
58
|
+
// Inconclusive without a tsconfig: never a pass and never a failure.
|
|
59
|
+
if (!this.hasTypeScriptConfig()) {
|
|
60
|
+
observations.push({
|
|
61
|
+
id: this.generateId(),
|
|
62
|
+
type: "build",
|
|
63
|
+
summary: "TypeScript check skipped (no tsconfig.json)",
|
|
64
|
+
data: { type: "typecheck", success: true, skipped: true },
|
|
65
|
+
timestamp: Date.now(),
|
|
66
|
+
phase,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
type: "typecheck",
|
|
70
|
+
success: true,
|
|
71
|
+
skipped: true,
|
|
72
|
+
observations,
|
|
73
|
+
errors,
|
|
74
|
+
duration: Date.now() - startTime,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const command = "npx tsc --noEmit";
|
|
79
|
+
const { stdout, stderr } = await exec(command, {
|
|
80
|
+
cwd: this.projectPath,
|
|
81
|
+
timeout: this.config.typecheckTimeout,
|
|
82
|
+
});
|
|
83
|
+
const duration = Date.now() - startTime;
|
|
84
|
+
// Non-zero exit throws (caught below), so reaching here means tsc
|
|
85
|
+
// reported no type errors — stderr may still hold npm notices.
|
|
86
|
+
observations.push({
|
|
87
|
+
id: this.generateId(),
|
|
88
|
+
type: "build",
|
|
89
|
+
summary: "TypeScript check passed",
|
|
90
|
+
data: {
|
|
91
|
+
type: "typecheck",
|
|
92
|
+
success: true,
|
|
93
|
+
output: stdout + stderr,
|
|
94
|
+
},
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
phase,
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
type: "typecheck",
|
|
100
|
+
success: true,
|
|
101
|
+
command: "npx tsc --noEmit",
|
|
102
|
+
output: stdout + stderr,
|
|
103
|
+
observations,
|
|
104
|
+
errors,
|
|
105
|
+
duration,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const duration = Date.now() - startTime;
|
|
110
|
+
observations.push({
|
|
111
|
+
id: this.generateId(),
|
|
112
|
+
type: "build",
|
|
113
|
+
summary: "TypeScript check failed",
|
|
114
|
+
data: {
|
|
115
|
+
type: "typecheck",
|
|
116
|
+
success: false,
|
|
117
|
+
error: error.message,
|
|
118
|
+
},
|
|
119
|
+
timestamp: Date.now(),
|
|
120
|
+
phase,
|
|
121
|
+
});
|
|
122
|
+
errors.push({
|
|
123
|
+
id: this.generateId(),
|
|
124
|
+
type: "verification",
|
|
125
|
+
message: error.message,
|
|
126
|
+
phase,
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
details: { stderr: error.stderr, stdout: error.stdout },
|
|
129
|
+
recoverable: true,
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
type: "typecheck",
|
|
133
|
+
success: false,
|
|
134
|
+
command: "npx tsc --noEmit",
|
|
135
|
+
error: error.message,
|
|
136
|
+
observations,
|
|
137
|
+
errors,
|
|
138
|
+
duration,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Verify build
|
|
144
|
+
*/
|
|
145
|
+
async verifyBuild(phase) {
|
|
146
|
+
const startTime = Date.now();
|
|
147
|
+
const observations = [];
|
|
148
|
+
const errors = [];
|
|
149
|
+
// Inconclusive when the project has no build script.
|
|
150
|
+
if (!this.hasBuildScript()) {
|
|
151
|
+
observations.push({
|
|
152
|
+
id: this.generateId(),
|
|
153
|
+
type: "build",
|
|
154
|
+
summary: "Build skipped (no build script)",
|
|
155
|
+
data: { type: "build", success: true, skipped: true },
|
|
156
|
+
timestamp: Date.now(),
|
|
157
|
+
phase,
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
type: "build",
|
|
161
|
+
success: true,
|
|
162
|
+
skipped: true,
|
|
163
|
+
observations,
|
|
164
|
+
errors,
|
|
165
|
+
duration: Date.now() - startTime,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
// Try common build commands
|
|
170
|
+
const commands = ["npm run build", "pnpm build", "yarn build"];
|
|
171
|
+
let result;
|
|
172
|
+
let usedCommand = "";
|
|
173
|
+
let lastError = "";
|
|
174
|
+
for (const cmd of commands) {
|
|
175
|
+
try {
|
|
176
|
+
result = await exec(cmd, {
|
|
177
|
+
cwd: this.projectPath,
|
|
178
|
+
timeout: this.config.buildTimeout,
|
|
179
|
+
});
|
|
180
|
+
usedCommand = cmd;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
lastError = err?.stderr || err?.message || `${cmd} failed`;
|
|
185
|
+
const managerMissing = err?.code === 127 || /command not found|ENOENT/i.test(String(err?.message || ""));
|
|
186
|
+
if (!managerMissing) {
|
|
187
|
+
// The build script ran and failed — don't mask it by trying
|
|
188
|
+
// other package managers.
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const duration = Date.now() - startTime;
|
|
195
|
+
if (!result) {
|
|
196
|
+
observations.push({
|
|
197
|
+
id: this.generateId(),
|
|
198
|
+
type: "build",
|
|
199
|
+
summary: "Build failed",
|
|
200
|
+
data: { type: "build", success: false, error: lastError },
|
|
201
|
+
timestamp: Date.now(),
|
|
202
|
+
phase,
|
|
203
|
+
});
|
|
204
|
+
errors.push({
|
|
205
|
+
id: this.generateId(),
|
|
206
|
+
type: "verification",
|
|
207
|
+
message: "Build failed",
|
|
208
|
+
phase,
|
|
209
|
+
timestamp: Date.now(),
|
|
210
|
+
details: { output: lastError },
|
|
211
|
+
recoverable: true,
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
type: "build",
|
|
215
|
+
success: false,
|
|
216
|
+
command: "npm run build",
|
|
217
|
+
error: "Build failed",
|
|
218
|
+
output: lastError,
|
|
219
|
+
observations,
|
|
220
|
+
errors,
|
|
221
|
+
duration,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
observations.push({
|
|
225
|
+
id: this.generateId(),
|
|
226
|
+
type: "build",
|
|
227
|
+
summary: `Build succeeded: ${usedCommand}`,
|
|
228
|
+
data: {
|
|
229
|
+
type: "build",
|
|
230
|
+
success: true,
|
|
231
|
+
command: usedCommand,
|
|
232
|
+
},
|
|
233
|
+
timestamp: Date.now(),
|
|
234
|
+
phase,
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
type: "build",
|
|
238
|
+
success: true,
|
|
239
|
+
command: usedCommand,
|
|
240
|
+
output: result.stdout,
|
|
241
|
+
observations,
|
|
242
|
+
errors,
|
|
243
|
+
duration,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
const duration = Date.now() - startTime;
|
|
248
|
+
observations.push({
|
|
249
|
+
id: this.generateId(),
|
|
250
|
+
type: "build",
|
|
251
|
+
summary: "Build failed",
|
|
252
|
+
data: {
|
|
253
|
+
type: "build",
|
|
254
|
+
success: false,
|
|
255
|
+
error: error.message,
|
|
256
|
+
},
|
|
257
|
+
timestamp: Date.now(),
|
|
258
|
+
phase,
|
|
259
|
+
});
|
|
260
|
+
errors.push({
|
|
261
|
+
id: this.generateId(),
|
|
262
|
+
type: "verification",
|
|
263
|
+
message: "Build failed",
|
|
264
|
+
phase,
|
|
265
|
+
timestamp: Date.now(),
|
|
266
|
+
details: { stderr: error.stderr, stdout: error.stdout },
|
|
267
|
+
recoverable: true,
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
type: "build",
|
|
271
|
+
success: false,
|
|
272
|
+
error: error.message,
|
|
273
|
+
observations,
|
|
274
|
+
errors,
|
|
275
|
+
duration,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Verify dev server health
|
|
281
|
+
*/
|
|
282
|
+
async verifyDevServer(phase) {
|
|
283
|
+
const startTime = Date.now();
|
|
284
|
+
const observations = [];
|
|
285
|
+
const errors = [];
|
|
286
|
+
// TODO: Implement actual dev server health check
|
|
287
|
+
// For now, just check if a dev server is likely running
|
|
288
|
+
observations.push({
|
|
289
|
+
id: this.generateId(),
|
|
290
|
+
type: "build",
|
|
291
|
+
summary: "Dev server health check skipped (not implemented)",
|
|
292
|
+
data: { type: "dev_server", success: true },
|
|
293
|
+
timestamp: Date.now(),
|
|
294
|
+
phase,
|
|
295
|
+
});
|
|
296
|
+
return {
|
|
297
|
+
type: "dev_server",
|
|
298
|
+
success: true,
|
|
299
|
+
observations,
|
|
300
|
+
errors,
|
|
301
|
+
duration: Date.now() - startTime,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Verify browser/runtime state
|
|
306
|
+
*/
|
|
307
|
+
async verifyBrowser(phase) {
|
|
308
|
+
const startTime = Date.now();
|
|
309
|
+
const observations = [];
|
|
310
|
+
const errors = [];
|
|
311
|
+
// TODO: Implement actual browser checking via Puppeteer or similar
|
|
312
|
+
// For now, this is a placeholder
|
|
313
|
+
observations.push({
|
|
314
|
+
id: this.generateId(),
|
|
315
|
+
type: "browser",
|
|
316
|
+
summary: "Browser check skipped (not implemented)",
|
|
317
|
+
data: { type: "browser", success: true },
|
|
318
|
+
timestamp: Date.now(),
|
|
319
|
+
phase,
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
type: "browser",
|
|
323
|
+
success: true,
|
|
324
|
+
observations,
|
|
325
|
+
errors,
|
|
326
|
+
duration: Date.now() - startTime,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Check if verification passed overall
|
|
331
|
+
*/
|
|
332
|
+
allPassed(results) {
|
|
333
|
+
return results.every((r) => r.success);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Get all verification errors
|
|
337
|
+
*/
|
|
338
|
+
getErrors(results) {
|
|
339
|
+
return results.flatMap((r) => r.errors);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Get all verification observations
|
|
343
|
+
*/
|
|
344
|
+
getObservations(results) {
|
|
345
|
+
return results.flatMap((r) => r.observations);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Generate a unique ID
|
|
349
|
+
*/
|
|
350
|
+
generateId() {
|
|
351
|
+
return `ver_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
352
|
+
}
|
|
353
|
+
/** tsconfig.json (or any tsconfig.*.json) present in the project root. */
|
|
354
|
+
hasTypeScriptConfig() {
|
|
355
|
+
try {
|
|
356
|
+
return node_fs_1.default.readdirSync(this.projectPath).some((name) => /^tsconfig(\..+)?\.json$/.test(name));
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** package.json with a non-empty build script. */
|
|
363
|
+
hasBuildScript() {
|
|
364
|
+
try {
|
|
365
|
+
const raw = node_fs_1.default.readFileSync(node_path_1.default.join(this.projectPath, "package.json"), "utf8");
|
|
366
|
+
const pkg = JSON.parse(raw);
|
|
367
|
+
return typeof pkg?.scripts?.build === "string" && pkg.scripts.build.trim().length > 0;
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
exports.VerificationEngine = VerificationEngine;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Guard - Capture, diff, and roll back project state.
|
|
3
|
+
*
|
|
4
|
+
* Lasso's non-negotiable review workflow requires:
|
|
5
|
+
* 1. Capture the original project state before an agent runs.
|
|
6
|
+
* 2. Collect resulting file changes after the agent runs.
|
|
7
|
+
* 3. Roll back to the original state so nothing is applied before review.
|
|
8
|
+
* 4. On approval, re-apply the changes; on rejection, leave the project untouched.
|
|
9
|
+
*
|
|
10
|
+
* The guard backs up every non-ignored file (in memory, or on disk for
|
|
11
|
+
* oversized files), computes a normalized diff against the captured state,
|
|
12
|
+
* and can restore the workspace exactly (content + permission bits + deletions).
|
|
13
|
+
*/
|
|
14
|
+
export interface FileChange {
|
|
15
|
+
/** Project-relative path using the platform separator */
|
|
16
|
+
filePath: string;
|
|
17
|
+
operation: "created" | "modified" | "deleted";
|
|
18
|
+
/** Full original text content (text files only) */
|
|
19
|
+
oldContent?: string;
|
|
20
|
+
/** Full new text content (text files only) */
|
|
21
|
+
newContent?: string;
|
|
22
|
+
/** True when the file is binary — contents are preserved for rollback but not reviewable as text */
|
|
23
|
+
binary?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface WorkspaceGuardOptions {
|
|
26
|
+
/** Directory names that are never captured (defaults cover Node/tooling outputs) */
|
|
27
|
+
ignoreDirs?: string[];
|
|
28
|
+
/** Extra ignore predicates (relative path, isDirectory) */
|
|
29
|
+
ignore?: (relPath: string, isDir: boolean) => boolean;
|
|
30
|
+
/** Max bytes held in memory per file; larger files are copied to a temp dir (default 4MB) */
|
|
31
|
+
maxMemoryFileSize?: number;
|
|
32
|
+
/** Max total bytes held in memory before oversized handling kicks in per file (default 64MB) */
|
|
33
|
+
maxTotalBytes?: number;
|
|
34
|
+
/** Max directory recursion depth (default 32) */
|
|
35
|
+
maxDepth?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare class WorkspaceGuard {
|
|
38
|
+
readonly root: string;
|
|
39
|
+
private readonly files;
|
|
40
|
+
private readonly skipped;
|
|
41
|
+
private readonly tempDir;
|
|
42
|
+
private readonly ignoreDirs;
|
|
43
|
+
private readonly ignoreFn?;
|
|
44
|
+
private tempDirCreated;
|
|
45
|
+
private disposed;
|
|
46
|
+
private constructor();
|
|
47
|
+
/**
|
|
48
|
+
* Capture the full current state of the workspace.
|
|
49
|
+
*/
|
|
50
|
+
static capture(projectPath: string, options?: WorkspaceGuardOptions): Promise<WorkspaceGuard>;
|
|
51
|
+
/** Number of files captured (excludes ignored/skipped entries). */
|
|
52
|
+
get fileCount(): number;
|
|
53
|
+
/** Paths that could not be captured (symlinks, unreadable entries). */
|
|
54
|
+
get skippedPaths(): string[];
|
|
55
|
+
/**
|
|
56
|
+
* Compare the current on-disk state against the captured state.
|
|
57
|
+
* Must be called BEFORE restore() when collecting an agent's changes.
|
|
58
|
+
*/
|
|
59
|
+
diff(): FileChange[];
|
|
60
|
+
/**
|
|
61
|
+
* Restore the workspace to the captured state:
|
|
62
|
+
* modified files are reverted, created files are deleted, deleted files are recreated.
|
|
63
|
+
*/
|
|
64
|
+
restore(): Promise<void>;
|
|
65
|
+
/** Restore without comparing (used by dispose paths). */
|
|
66
|
+
dispose(): Promise<void>;
|
|
67
|
+
private listNewFiles;
|
|
68
|
+
private ensureTempDir;
|
|
69
|
+
private cleanupTemp;
|
|
70
|
+
}
|