@flowdular/sdk 0.1.0 → 0.2.1
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/.ai/skills/test-hardening/SKILL.md +1 -1
- package/README.md +69 -12
- package/assets/flowdular-banner.png +0 -0
- package/modules/agents/package.json +1 -1
- package/modules/auth/package.json +1 -1
- package/modules/auth/src/client/AuthenticationCore.tsrx +3 -1
- package/modules/auth/src/server/endpoints.ts +3 -1
- package/modules/auth/src/server/runtime.ts +4 -0
- package/modules/automations/package.json +1 -1
- package/modules/automations-workflows-integration/package.json +1 -1
- package/modules/profile/package.json +1 -1
- package/modules/sandbox/package.json +1 -1
- package/modules/system/package.json +1 -1
- package/modules/users/package.json +1 -1
- package/modules/workflows/package.json +1 -1
- package/package.json +5 -13
- package/packages/ai-provider/package.json +1 -1
- package/packages/cli-protocol/package.json +1 -1
- package/packages/client/package.json +1 -1
- package/packages/client/src/routing.ts +39 -2
- package/packages/client/src/shell/BrandMark.tsrx +5 -1
- package/packages/client/src/shell/navigation.ts +3 -1
- package/packages/client/src/state.ts +6 -2
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/schemas/project.schema.json +16 -42
- package/packages/database/package.json +1 -1
- package/packages/database-pglite/package.json +2 -2
- package/packages/database-pglite/src/driver.ts +21 -3
- package/packages/database-testing/package.json +1 -1
- package/packages/dev-console/package.json +1 -1
- package/packages/harness/package.json +1 -1
- package/packages/kernel/package.json +1 -1
- package/packages/server/package.json +1 -1
- package/packages/server/src/application-routes.ts +80 -8
- package/packages/server/src/index.ts +4 -1
- package/packages/server/src/web-html.ts +9 -5
- package/packages/server/src/web.ts +59 -7
- package/packages/ui/package.json +1 -1
- package/packages/coding-agent/package.json +0 -44
- package/packages/coding-agent/src/drivers/byok.ts +0 -322
- package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
- package/packages/coding-agent/src/drivers/codex.ts +0 -281
- package/packages/coding-agent/src/index.ts +0 -56
- package/packages/coding-agent/src/registry.ts +0 -119
- package/packages/coding-agent/src/roles/contract.ts +0 -60
- package/packages/coding-agent/src/roles/defaults.ts +0 -134
- package/packages/coding-agent/src/roles/handoff.ts +0 -41
- package/packages/coding-agent/src/roles/registry.ts +0 -156
- package/packages/coding-agent/src/roles/skills.ts +0 -72
- package/packages/coding-agent/src/roles/sync.ts +0 -88
- package/packages/coding-agent/src/types.ts +0 -102
- package/packages/coding-agent/src/workspace.ts +0 -275
- package/packages/sandbox/README.md +0 -691
- package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
- package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
- package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
- package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
- package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
- package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
- package/packages/sandbox/dist/client/favicon.svg +0 -1
- package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
- package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
- package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
- package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
- package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
- package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
- package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
- package/packages/sandbox/dist/server/entry.js +0 -91671
- package/packages/sandbox/dist/server/favicon.svg +0 -1
- package/packages/sandbox/dist/server/index.html +0 -161
- package/packages/sandbox/index.html +0 -159
- package/packages/sandbox/octane.config.ts +0 -36
- package/packages/sandbox/package.json +0 -83
- package/packages/sandbox/public/favicon.svg +0 -1
- package/packages/sandbox/public/og.png +0 -0
- package/packages/sandbox/src/App.tsrx +0 -924
- package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
- package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
- package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
- package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
- package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
- package/packages/sandbox/src/client/GateResults.tsrx +0 -60
- package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
- package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
- package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
- package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
- package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
- package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
- package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
- package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
- package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
- package/packages/sandbox/src/client/api.ts +0 -797
- package/packages/sandbox/src/client/attachments.ts +0 -73
- package/packages/sandbox/src/client/dashboard.ts +0 -46
- package/packages/sandbox/src/client/github-settings.ts +0 -48
- package/packages/sandbox/src/client/highlight.ts +0 -105
- package/packages/sandbox/src/client/i18n.ts +0 -32
- package/packages/sandbox/src/client/locales/en.json +0 -466
- package/packages/sandbox/src/client/locales/pl.json +0 -466
- package/packages/sandbox/src/client/session-labels.ts +0 -77
- package/packages/sandbox/src/client/state.ts +0 -108
- package/packages/sandbox/src/index.ts +0 -10
- package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
- package/packages/sandbox/src/preview/load-module.ts +0 -16
- package/packages/sandbox/src/server/attachments.ts +0 -308
- package/packages/sandbox/src/server/auto-review.ts +0 -181
- package/packages/sandbox/src/server/checkpoints.ts +0 -90
- package/packages/sandbox/src/server/config.ts +0 -460
- package/packages/sandbox/src/server/dashboard.ts +0 -245
- package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
- package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
- package/packages/sandbox/src/server/delivery/index.ts +0 -83
- package/packages/sandbox/src/server/delivery/local.ts +0 -151
- package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
- package/packages/sandbox/src/server/delivery/plan.ts +0 -218
- package/packages/sandbox/src/server/delivery/policies.ts +0 -239
- package/packages/sandbox/src/server/delivery/record.ts +0 -41
- package/packages/sandbox/src/server/delivery/steps.ts +0 -464
- package/packages/sandbox/src/server/delivery/types.ts +0 -158
- package/packages/sandbox/src/server/dependencies.ts +0 -88
- package/packages/sandbox/src/server/diff.ts +0 -230
- package/packages/sandbox/src/server/gates.ts +0 -388
- package/packages/sandbox/src/server/index.ts +0 -139
- package/packages/sandbox/src/server/path-guard.ts +0 -282
- package/packages/sandbox/src/server/planning.ts +0 -749
- package/packages/sandbox/src/server/platform-client.ts +0 -189
- package/packages/sandbox/src/server/preview-database-host.ts +0 -340
- package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
- package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
- package/packages/sandbox/src/server/preview-database.ts +0 -109
- package/packages/sandbox/src/server/preview-ipc.ts +0 -15
- package/packages/sandbox/src/server/preview-modules.ts +0 -83
- package/packages/sandbox/src/server/preview-revision.ts +0 -44
- package/packages/sandbox/src/server/preview-runtime.ts +0 -467
- package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
- package/packages/sandbox/src/server/preview-worker.ts +0 -168
- package/packages/sandbox/src/server/reference.ts +0 -251
- package/packages/sandbox/src/server/routes.ts +0 -2258
- package/packages/sandbox/src/server/runtime.ts +0 -188
- package/packages/sandbox/src/server/session-owner.ts +0 -23
- package/packages/sandbox/src/server/sessions.ts +0 -849
- package/packages/sandbox/src/server/spec.ts +0 -471
- package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
- package/packages/sandbox/src/server/turns.ts +0 -993
- package/packages/sandbox/src/server/workspace-install.ts +0 -266
- package/packages/sandbox/src/server/workspace-root.ts +0 -73
- package/packages/sandbox/src/styles.css +0 -1573
- package/packages/sandbox/vite.config.ts +0 -133
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
2
|
-
import { join, relative } from 'node:path';
|
|
3
|
-
|
|
4
|
-
export type FileChange = 'created' | 'modified' | 'deleted';
|
|
5
|
-
|
|
6
|
-
export interface DiffLine {
|
|
7
|
-
readonly type: 'context' | 'add' | 'del';
|
|
8
|
-
readonly text: string;
|
|
9
|
-
readonly oldLine: number | null;
|
|
10
|
-
readonly newLine: number | null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface DiffHunk {
|
|
14
|
-
readonly header: string;
|
|
15
|
-
readonly lines: readonly DiffLine[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface FileDiff {
|
|
19
|
-
readonly path: string;
|
|
20
|
-
readonly change: FileChange;
|
|
21
|
-
readonly additions: number;
|
|
22
|
-
readonly deletions: number;
|
|
23
|
-
readonly binary: boolean;
|
|
24
|
-
readonly truncated: boolean;
|
|
25
|
-
readonly hunks: readonly DiffHunk[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const IGNORED = new Set(['node_modules', 'dist', '.git']);
|
|
29
|
-
const MAX_DIFF_LINES = 4_000;
|
|
30
|
-
const CONTEXT_LINES = 3;
|
|
31
|
-
|
|
32
|
-
async function listTree(root: string, directory = root): Promise<string[]> {
|
|
33
|
-
let entries;
|
|
34
|
-
try {
|
|
35
|
-
entries = await readdir(directory, { withFileTypes: true });
|
|
36
|
-
} catch {
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
const files: string[] = [];
|
|
40
|
-
for (const entry of entries) {
|
|
41
|
-
if (IGNORED.has(entry.name)) continue;
|
|
42
|
-
const path = join(directory, entry.name);
|
|
43
|
-
if (entry.isSymbolicLink()) continue;
|
|
44
|
-
if (entry.isDirectory()) files.push(...(await listTree(root, path)));
|
|
45
|
-
else files.push(relative(root, path));
|
|
46
|
-
}
|
|
47
|
-
return files;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function readIfPresent(path: string): Promise<string | null> {
|
|
51
|
-
try {
|
|
52
|
-
const info = await stat(path);
|
|
53
|
-
if (!info.isFile()) return null;
|
|
54
|
-
return await readFile(path, 'utf8');
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/* Longest common subsequence over lines. Files above the guard are reported as
|
|
61
|
-
changed without hunks so one generated lock file cannot stall the sandbox. */
|
|
62
|
-
function lineDiff(
|
|
63
|
-
before: readonly string[],
|
|
64
|
-
after: readonly string[],
|
|
65
|
-
): readonly DiffLine[] {
|
|
66
|
-
const rows = before.length;
|
|
67
|
-
const columns = after.length;
|
|
68
|
-
const table: number[][] = Array.from({ length: rows + 1 }, () =>
|
|
69
|
-
new Array<number>(columns + 1).fill(0),
|
|
70
|
-
);
|
|
71
|
-
for (let row = rows - 1; row >= 0; row -= 1) {
|
|
72
|
-
for (let column = columns - 1; column >= 0; column -= 1) {
|
|
73
|
-
table[row]![column] =
|
|
74
|
-
before[row] === after[column]
|
|
75
|
-
? table[row + 1]![column + 1]! + 1
|
|
76
|
-
: Math.max(table[row + 1]![column]!, table[row]![column + 1]!);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
const lines: DiffLine[] = [];
|
|
80
|
-
let row = 0;
|
|
81
|
-
let column = 0;
|
|
82
|
-
while (row < rows && column < columns) {
|
|
83
|
-
if (before[row] === after[column]) {
|
|
84
|
-
lines.push({
|
|
85
|
-
type: 'context',
|
|
86
|
-
text: before[row]!,
|
|
87
|
-
oldLine: row + 1,
|
|
88
|
-
newLine: column + 1,
|
|
89
|
-
});
|
|
90
|
-
row += 1;
|
|
91
|
-
column += 1;
|
|
92
|
-
} else if (table[row + 1]![column]! >= table[row]![column + 1]!) {
|
|
93
|
-
lines.push({
|
|
94
|
-
type: 'del',
|
|
95
|
-
text: before[row]!,
|
|
96
|
-
oldLine: row + 1,
|
|
97
|
-
newLine: null,
|
|
98
|
-
});
|
|
99
|
-
row += 1;
|
|
100
|
-
} else {
|
|
101
|
-
lines.push({
|
|
102
|
-
type: 'add',
|
|
103
|
-
text: after[column]!,
|
|
104
|
-
oldLine: null,
|
|
105
|
-
newLine: column + 1,
|
|
106
|
-
});
|
|
107
|
-
column += 1;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
while (row < rows) {
|
|
111
|
-
lines.push({
|
|
112
|
-
type: 'del',
|
|
113
|
-
text: before[row]!,
|
|
114
|
-
oldLine: row + 1,
|
|
115
|
-
newLine: null,
|
|
116
|
-
});
|
|
117
|
-
row += 1;
|
|
118
|
-
}
|
|
119
|
-
while (column < columns) {
|
|
120
|
-
lines.push({
|
|
121
|
-
type: 'add',
|
|
122
|
-
text: after[column]!,
|
|
123
|
-
oldLine: null,
|
|
124
|
-
newLine: column + 1,
|
|
125
|
-
});
|
|
126
|
-
column += 1;
|
|
127
|
-
}
|
|
128
|
-
return lines;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function groupHunks(lines: readonly DiffLine[]): readonly DiffHunk[] {
|
|
132
|
-
const changedIndexes = lines
|
|
133
|
-
.map((line, index) => (line.type === 'context' ? -1 : index))
|
|
134
|
-
.filter((index) => index >= 0);
|
|
135
|
-
if (changedIndexes.length === 0) return [];
|
|
136
|
-
|
|
137
|
-
const hunks: DiffHunk[] = [];
|
|
138
|
-
let start = Math.max(0, changedIndexes[0]! - CONTEXT_LINES);
|
|
139
|
-
let end = Math.min(lines.length - 1, changedIndexes[0]! + CONTEXT_LINES);
|
|
140
|
-
for (const index of changedIndexes.slice(1)) {
|
|
141
|
-
if (index - end <= CONTEXT_LINES * 2) {
|
|
142
|
-
end = Math.min(lines.length - 1, index + CONTEXT_LINES);
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
hunks.push(buildHunk(lines, start, end));
|
|
146
|
-
start = Math.max(0, index - CONTEXT_LINES);
|
|
147
|
-
end = Math.min(lines.length - 1, index + CONTEXT_LINES);
|
|
148
|
-
}
|
|
149
|
-
hunks.push(buildHunk(lines, start, end));
|
|
150
|
-
return hunks;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function buildHunk(
|
|
154
|
-
lines: readonly DiffLine[],
|
|
155
|
-
start: number,
|
|
156
|
-
end: number,
|
|
157
|
-
): DiffHunk {
|
|
158
|
-
const slice = lines.slice(start, end + 1);
|
|
159
|
-
const firstOld = slice.find((line) => line.oldLine !== null)?.oldLine ?? 0;
|
|
160
|
-
const firstNew = slice.find((line) => line.newLine !== null)?.newLine ?? 0;
|
|
161
|
-
const oldCount = slice.filter((line) => line.type !== 'add').length;
|
|
162
|
-
const newCount = slice.filter((line) => line.type !== 'del').length;
|
|
163
|
-
return {
|
|
164
|
-
header: `@@ -${firstOld},${oldCount} +${firstNew},${newCount} @@`,
|
|
165
|
-
lines: slice,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export async function diffFile(
|
|
170
|
-
beforePath: string,
|
|
171
|
-
afterPath: string,
|
|
172
|
-
path: string,
|
|
173
|
-
): Promise<FileDiff | null> {
|
|
174
|
-
const before = await readIfPresent(beforePath);
|
|
175
|
-
const after = await readIfPresent(afterPath);
|
|
176
|
-
if (before === null && after === null) return null;
|
|
177
|
-
if (before === after) return null;
|
|
178
|
-
|
|
179
|
-
const change: FileChange =
|
|
180
|
-
before === null ? 'created' : after === null ? 'deleted' : 'modified';
|
|
181
|
-
const beforeLines = before === null ? [] : before.split('\n');
|
|
182
|
-
const afterLines = after === null ? [] : after.split('\n');
|
|
183
|
-
const binary = [before, after].some(
|
|
184
|
-
(content) => content !== null && content.includes(String.fromCharCode(0)),
|
|
185
|
-
);
|
|
186
|
-
const truncated =
|
|
187
|
-
beforeLines.length + afterLines.length > MAX_DIFF_LINES || binary;
|
|
188
|
-
|
|
189
|
-
if (truncated) {
|
|
190
|
-
return {
|
|
191
|
-
path,
|
|
192
|
-
change,
|
|
193
|
-
additions: afterLines.length,
|
|
194
|
-
deletions: beforeLines.length,
|
|
195
|
-
binary,
|
|
196
|
-
truncated: true,
|
|
197
|
-
hunks: [],
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
const lines = lineDiff(beforeLines, afterLines);
|
|
201
|
-
return {
|
|
202
|
-
path,
|
|
203
|
-
change,
|
|
204
|
-
additions: lines.filter((line) => line.type === 'add').length,
|
|
205
|
-
deletions: lines.filter((line) => line.type === 'del').length,
|
|
206
|
-
binary: false,
|
|
207
|
-
truncated: false,
|
|
208
|
-
hunks: groupHunks(lines),
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export async function diffTrees(
|
|
213
|
-
basePath: string,
|
|
214
|
-
currentPath: string,
|
|
215
|
-
): Promise<readonly FileDiff[]> {
|
|
216
|
-
const paths = new Set([
|
|
217
|
-
...(await listTree(basePath)),
|
|
218
|
-
...(await listTree(currentPath)),
|
|
219
|
-
]);
|
|
220
|
-
const diffs: FileDiff[] = [];
|
|
221
|
-
for (const path of [...paths].sort()) {
|
|
222
|
-
const diff = await diffFile(
|
|
223
|
-
join(basePath, path),
|
|
224
|
-
join(currentPath, path),
|
|
225
|
-
path,
|
|
226
|
-
);
|
|
227
|
-
if (diff) diffs.push(diff);
|
|
228
|
-
}
|
|
229
|
-
return diffs;
|
|
230
|
-
}
|
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { access } from 'node:fs/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { inspectAutoReview } from './auto-review.ts';
|
|
5
|
-
import { checkDeclaredDependencies } from './dependencies.ts';
|
|
6
|
-
import {
|
|
7
|
-
modulePathOf,
|
|
8
|
-
type SandboxSession,
|
|
9
|
-
type SessionModule,
|
|
10
|
-
type SessionPaths,
|
|
11
|
-
} from './sessions.ts';
|
|
12
|
-
|
|
13
|
-
export type GateId =
|
|
14
|
-
| 'spec-schema'
|
|
15
|
-
| 'module-schema'
|
|
16
|
-
| 'dependencies'
|
|
17
|
-
| 'typecheck'
|
|
18
|
-
| 'tests'
|
|
19
|
-
| 'format'
|
|
20
|
-
| 'auto-review';
|
|
21
|
-
|
|
22
|
-
export interface GateResult {
|
|
23
|
-
readonly id: GateId;
|
|
24
|
-
/* The draft module directory a module-level gate ran in; absent for a gate
|
|
25
|
-
that checks the whole session workspace. */
|
|
26
|
-
readonly module?: string;
|
|
27
|
-
readonly status: 'passed' | 'failed' | 'skipped';
|
|
28
|
-
readonly durationMs: number;
|
|
29
|
-
readonly command: string;
|
|
30
|
-
readonly output: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface GateDefinition {
|
|
34
|
-
readonly id: GateId;
|
|
35
|
-
readonly summary: string;
|
|
36
|
-
/* Workspace gates run once per session; module gates run per draft module. */
|
|
37
|
-
readonly scope: 'workspace' | 'module';
|
|
38
|
-
command(context: GateContext): {
|
|
39
|
-
readonly command: string;
|
|
40
|
-
readonly args: readonly string[];
|
|
41
|
-
readonly cwd: string;
|
|
42
|
-
} | null;
|
|
43
|
-
/* A gate the sandbox answers itself, without spawning a process. */
|
|
44
|
-
inspect?(context: GateContext): Promise<{
|
|
45
|
-
readonly passed: boolean;
|
|
46
|
-
readonly output: string;
|
|
47
|
-
}>;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface GateContext {
|
|
51
|
-
readonly workspaceRoot: string;
|
|
52
|
-
readonly paths: SessionPaths;
|
|
53
|
-
readonly session: SandboxSession;
|
|
54
|
-
readonly module: SessionModule;
|
|
55
|
-
readonly modulePath: string;
|
|
56
|
-
readonly signal?: AbortSignal | undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/* Output keeps its head and its tail: the head names what failed (the test
|
|
60
|
-
list, the first compiler errors), the tail carries the summary. */
|
|
61
|
-
const HEAD_LIMIT = 10_000;
|
|
62
|
-
const TAIL_LIMIT = 6_000;
|
|
63
|
-
const GATE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
64
|
-
|
|
65
|
-
/* The only commands the sandbox may run. Agents never execute anything: they
|
|
66
|
-
request a gate by id and the orchestrator runs this fixed list. */
|
|
67
|
-
const GATE_DEFINITIONS: readonly GateDefinition[] = [
|
|
68
|
-
{
|
|
69
|
-
id: 'auto-review',
|
|
70
|
-
summary: 'Review evidence matches the current module contents.',
|
|
71
|
-
scope: 'module',
|
|
72
|
-
command: () => null,
|
|
73
|
-
inspect: (context) => inspectAutoReview(context.paths, context.module),
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: 'spec-schema',
|
|
77
|
-
summary: 'Validate the module specification against its schema.',
|
|
78
|
-
scope: 'workspace',
|
|
79
|
-
command: (context) => ({
|
|
80
|
-
command: 'pnpm',
|
|
81
|
-
args: [
|
|
82
|
-
'--dir',
|
|
83
|
-
context.workspaceRoot,
|
|
84
|
-
'--silent',
|
|
85
|
-
'flowdular',
|
|
86
|
-
'spec',
|
|
87
|
-
'validate',
|
|
88
|
-
'--all',
|
|
89
|
-
'--json',
|
|
90
|
-
'--root',
|
|
91
|
-
context.paths.workspace,
|
|
92
|
-
],
|
|
93
|
-
cwd: context.workspaceRoot,
|
|
94
|
-
}),
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: 'module-schema',
|
|
98
|
-
summary: 'Validate the module manifest and registry references.',
|
|
99
|
-
scope: 'workspace',
|
|
100
|
-
command: (context) => ({
|
|
101
|
-
command: 'pnpm',
|
|
102
|
-
args: [
|
|
103
|
-
'--dir',
|
|
104
|
-
context.workspaceRoot,
|
|
105
|
-
'--silent',
|
|
106
|
-
'flowdular',
|
|
107
|
-
'module',
|
|
108
|
-
'validate',
|
|
109
|
-
'--json',
|
|
110
|
-
'--root',
|
|
111
|
-
context.paths.workspace,
|
|
112
|
-
/* The other modules in the session are manifest-only copies for the
|
|
113
|
-
registry graph; file-level checks run against the drafts only. */
|
|
114
|
-
'--module',
|
|
115
|
-
context.session.modules.map((entry) => entry.id).join(','),
|
|
116
|
-
],
|
|
117
|
-
cwd: context.workspaceRoot,
|
|
118
|
-
}),
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
id: 'dependencies',
|
|
122
|
-
summary: 'Every imported package is declared by the module manifest.',
|
|
123
|
-
scope: 'module',
|
|
124
|
-
command: () => null,
|
|
125
|
-
inspect: async (context) => {
|
|
126
|
-
const report = await checkDeclaredDependencies(context.modulePath);
|
|
127
|
-
return {
|
|
128
|
-
passed: report.missing.length === 0,
|
|
129
|
-
output:
|
|
130
|
-
report.missing.length === 0
|
|
131
|
-
? `${report.imported.length} imported packages, all declared.`
|
|
132
|
-
: `Undeclared packages: ${report.missing.join(', ')}. Add them to package.json dependencies; the session installs what package.json declares and nothing else, and the ejected module would fail without them.`,
|
|
133
|
-
};
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
id: 'typecheck',
|
|
138
|
-
summary: 'Typecheck the module with the workspace compiler.',
|
|
139
|
-
scope: 'module',
|
|
140
|
-
command: (context) => ({
|
|
141
|
-
command: join(context.modulePath, 'node_modules', '.bin', 'tsrx-tsc'),
|
|
142
|
-
args: ['--noEmit', '-p', 'tsconfig.json'],
|
|
143
|
-
cwd: context.modulePath,
|
|
144
|
-
}),
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: 'tests',
|
|
148
|
-
summary: 'Run the module test suite.',
|
|
149
|
-
scope: 'module',
|
|
150
|
-
command: (context) => ({
|
|
151
|
-
command: join(context.modulePath, 'node_modules', '.bin', 'vitest'),
|
|
152
|
-
args: ['run', '--passWithNoTests=false'],
|
|
153
|
-
cwd: context.modulePath,
|
|
154
|
-
}),
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
id: 'format',
|
|
158
|
-
summary: 'Check formatting with the workspace Prettier configuration.',
|
|
159
|
-
scope: 'module',
|
|
160
|
-
command: (context) => ({
|
|
161
|
-
command: join(context.workspaceRoot, 'node_modules', '.bin', 'prettier'),
|
|
162
|
-
args: ['--check', '.'],
|
|
163
|
-
cwd: context.modulePath,
|
|
164
|
-
}),
|
|
165
|
-
},
|
|
166
|
-
];
|
|
167
|
-
|
|
168
|
-
export const GATE_IDS = GATE_DEFINITIONS.map((gate) => gate.id);
|
|
169
|
-
|
|
170
|
-
/* Formatting is mechanical, so the sandbox fixes it on request instead of
|
|
171
|
-
spending an agent turn on it. It is still the orchestrator running the
|
|
172
|
-
command, never the model. */
|
|
173
|
-
export async function formatDirectory(
|
|
174
|
-
workspaceRoot: string,
|
|
175
|
-
directory: string,
|
|
176
|
-
): Promise<GateResult> {
|
|
177
|
-
const command = join(workspaceRoot, 'node_modules', '.bin', 'prettier');
|
|
178
|
-
const startedAt = Date.now();
|
|
179
|
-
try {
|
|
180
|
-
await access(command);
|
|
181
|
-
} catch {
|
|
182
|
-
return {
|
|
183
|
-
id: 'format',
|
|
184
|
-
status: 'skipped',
|
|
185
|
-
durationMs: 0,
|
|
186
|
-
command,
|
|
187
|
-
output: 'Prettier is not installed in this workspace.',
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
/* An operator asked for this directly, so it is not tied to a turn and has
|
|
191
|
-
no turn signal to observe. */
|
|
192
|
-
const result = await runProcess(command, ['--write', '.'], directory);
|
|
193
|
-
return {
|
|
194
|
-
id: 'format',
|
|
195
|
-
status: result.code === 0 ? 'passed' : 'failed',
|
|
196
|
-
durationMs: Date.now() - startedAt,
|
|
197
|
-
command: `${command} --write .`,
|
|
198
|
-
output: result.output.trim(),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export async function formatSession(context: {
|
|
203
|
-
readonly workspaceRoot: string;
|
|
204
|
-
readonly paths: SessionPaths;
|
|
205
|
-
readonly session: SandboxSession;
|
|
206
|
-
}): Promise<readonly GateResult[]> {
|
|
207
|
-
const results: GateResult[] = [];
|
|
208
|
-
for (const module of context.session.modules) {
|
|
209
|
-
results.push({
|
|
210
|
-
...(await formatDirectory(
|
|
211
|
-
context.workspaceRoot,
|
|
212
|
-
modulePathOf(context.paths, module.directory),
|
|
213
|
-
)),
|
|
214
|
-
module: module.directory,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
return results;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function isGateId(value: string): value is GateId {
|
|
221
|
-
return (GATE_IDS as readonly string[]).includes(value);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function runProcess(
|
|
225
|
-
command: string,
|
|
226
|
-
args: readonly string[],
|
|
227
|
-
cwd: string,
|
|
228
|
-
signal?: AbortSignal | undefined,
|
|
229
|
-
): Promise<{ code: number | null; output: string }> {
|
|
230
|
-
return new Promise((resolvePromise) => {
|
|
231
|
-
if (signal?.aborted) {
|
|
232
|
-
resolvePromise({ code: null, output: 'The gate was stopped.' });
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
const child = spawn(command, [...args], {
|
|
236
|
-
cwd,
|
|
237
|
-
env: { ...process.env, CI: 'true', FORCE_COLOR: '0' },
|
|
238
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
239
|
-
});
|
|
240
|
-
let head = '';
|
|
241
|
-
let tail = '';
|
|
242
|
-
let omitted = 0;
|
|
243
|
-
const append = (chunk: string) => {
|
|
244
|
-
if (head.length < HEAD_LIMIT) {
|
|
245
|
-
const room = HEAD_LIMIT - head.length;
|
|
246
|
-
head += chunk.slice(0, room);
|
|
247
|
-
chunk = chunk.slice(room);
|
|
248
|
-
if (!chunk) return;
|
|
249
|
-
}
|
|
250
|
-
const kept = (tail + chunk).slice(-TAIL_LIMIT);
|
|
251
|
-
omitted += tail.length + chunk.length - kept.length;
|
|
252
|
-
tail = kept;
|
|
253
|
-
};
|
|
254
|
-
const output = () =>
|
|
255
|
-
omitted > 0
|
|
256
|
-
? `${head}\n[... ${omitted} characters omitted ...]\n${tail}`
|
|
257
|
-
: head + tail;
|
|
258
|
-
child.stdout.setEncoding('utf8');
|
|
259
|
-
child.stderr.setEncoding('utf8');
|
|
260
|
-
child.stdout.on('data', append);
|
|
261
|
-
child.stderr.on('data', append);
|
|
262
|
-
const timer = setTimeout(() => {
|
|
263
|
-
append('\nThe gate exceeded its time budget and was stopped.');
|
|
264
|
-
child.kill('SIGKILL');
|
|
265
|
-
}, GATE_TIMEOUT_MS);
|
|
266
|
-
timer.unref();
|
|
267
|
-
const stop = () => {
|
|
268
|
-
append('\nThe turn was stopped, so the gate was stopped with it.');
|
|
269
|
-
child.kill('SIGKILL');
|
|
270
|
-
};
|
|
271
|
-
signal?.addEventListener('abort', stop, { once: true });
|
|
272
|
-
const settle = (value: { code: number | null; output: string }) => {
|
|
273
|
-
clearTimeout(timer);
|
|
274
|
-
signal?.removeEventListener('abort', stop);
|
|
275
|
-
resolvePromise(value);
|
|
276
|
-
};
|
|
277
|
-
child.on('error', (error) => {
|
|
278
|
-
settle({ code: null, output: `${output()}\n${error.message}` });
|
|
279
|
-
});
|
|
280
|
-
child.on('close', (code) => {
|
|
281
|
-
settle({ code, output: output() });
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async function runGate(
|
|
287
|
-
definition: GateDefinition,
|
|
288
|
-
context: GateContext,
|
|
289
|
-
): Promise<GateResult> {
|
|
290
|
-
const id = definition.id;
|
|
291
|
-
const module =
|
|
292
|
-
definition.scope === 'module' ? { module: context.module.directory } : {};
|
|
293
|
-
const startedAt = Date.now();
|
|
294
|
-
if (definition.inspect) {
|
|
295
|
-
const result = await definition.inspect(context);
|
|
296
|
-
return {
|
|
297
|
-
id,
|
|
298
|
-
...module,
|
|
299
|
-
status: result.passed ? 'passed' : 'failed',
|
|
300
|
-
durationMs: Date.now() - startedAt,
|
|
301
|
-
command: definition.summary,
|
|
302
|
-
output: result.output,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
const invocation = definition.command(context);
|
|
306
|
-
if (!invocation) {
|
|
307
|
-
return {
|
|
308
|
-
id,
|
|
309
|
-
...module,
|
|
310
|
-
status: 'skipped',
|
|
311
|
-
durationMs: 0,
|
|
312
|
-
command: '',
|
|
313
|
-
output: 'This gate does not apply to the session.',
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
const printable = `${invocation.command} ${invocation.args.join(' ')}`;
|
|
317
|
-
if (invocation.command.includes('/')) {
|
|
318
|
-
try {
|
|
319
|
-
await access(invocation.command);
|
|
320
|
-
} catch {
|
|
321
|
-
return {
|
|
322
|
-
id,
|
|
323
|
-
...module,
|
|
324
|
-
status: 'skipped',
|
|
325
|
-
durationMs: 0,
|
|
326
|
-
command: printable,
|
|
327
|
-
output: `${invocation.command} is not installed in this session workspace. The module's package.json must declare it as a devDependency.`,
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
const result = await runProcess(
|
|
332
|
-
invocation.command,
|
|
333
|
-
invocation.args,
|
|
334
|
-
invocation.cwd,
|
|
335
|
-
context.signal,
|
|
336
|
-
);
|
|
337
|
-
return {
|
|
338
|
-
id,
|
|
339
|
-
...module,
|
|
340
|
-
status: result.code === 0 ? 'passed' : 'failed',
|
|
341
|
-
durationMs: Date.now() - startedAt,
|
|
342
|
-
command: printable,
|
|
343
|
-
output: result.output.trim(),
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export interface RunGatesInput {
|
|
348
|
-
readonly workspaceRoot: string;
|
|
349
|
-
readonly paths: SessionPaths;
|
|
350
|
-
readonly session: SandboxSession;
|
|
351
|
-
readonly gates: readonly GateId[];
|
|
352
|
-
/* The draft modules to gate; every module of the session by default. A turn
|
|
353
|
-
passes the ones that changed, so an untouched module is not re-checked. */
|
|
354
|
-
readonly modules?: readonly SessionModule[];
|
|
355
|
-
/* Stopping a turn must stop its gates. Without this a spawned gate outlives
|
|
356
|
-
the abort and the session stays busy until the gate's own budget expires. */
|
|
357
|
-
readonly signal?: AbortSignal | undefined;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/* Workspace gates run once; module gates run once per draft module, so a
|
|
361
|
-
session that touches several modules reports each of them. */
|
|
362
|
-
export async function runGates(
|
|
363
|
-
input: RunGatesInput,
|
|
364
|
-
): Promise<readonly GateResult[]> {
|
|
365
|
-
const results: GateResult[] = [];
|
|
366
|
-
const modules = input.modules ?? input.session.modules;
|
|
367
|
-
for (const id of input.gates) {
|
|
368
|
-
const definition = GATE_DEFINITIONS.find((gate) => gate.id === id)!;
|
|
369
|
-
const targets =
|
|
370
|
-
definition.scope === 'workspace' ? modules.slice(0, 1) : modules;
|
|
371
|
-
for (const module of targets) {
|
|
372
|
-
/* A stopped turn runs no further gates. The one already spawned is
|
|
373
|
-
killed through the same signal. */
|
|
374
|
-
if (input.signal?.aborted) return results;
|
|
375
|
-
results.push(
|
|
376
|
-
await runGate(definition, {
|
|
377
|
-
workspaceRoot: input.workspaceRoot,
|
|
378
|
-
paths: input.paths,
|
|
379
|
-
session: input.session,
|
|
380
|
-
module,
|
|
381
|
-
modulePath: modulePathOf(input.paths, module.directory),
|
|
382
|
-
signal: input.signal,
|
|
383
|
-
}),
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return results;
|
|
388
|
-
}
|