vibe-coding-master 0.0.17 → 0.2.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/README.md +57 -28
- package/dist/backend/api/artifact-routes.js +5 -5
- package/dist/backend/api/harness-routes.js +8 -0
- package/dist/backend/server.js +8 -2
- package/dist/backend/services/artifact-service.js +12 -12
- package/dist/backend/services/harness-service.js +579 -5
- package/dist/backend/services/project-service.js +4 -1
- package/dist/backend/services/session-service.js +1 -3
- package/dist/backend/services/task-service.js +16 -17
- package/dist/backend/templates/handoff.js +64 -26
- package/dist/backend/templates/harness/architect-agent.js +42 -12
- package/dist/backend/templates/harness/claude-root.js +42 -18
- package/dist/backend/templates/harness/coder-agent.js +15 -11
- package/dist/backend/templates/harness/known-issues-doc.js +22 -0
- package/dist/backend/templates/harness/project-manager-agent.js +66 -16
- package/dist/backend/templates/harness/pull-request-template.js +29 -0
- package/dist/backend/templates/harness/reviewer-agent.js +40 -12
- package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +105 -0
- package/dist/backend/templates/harness/vcm-harness-bootstrap-skill.js +78 -0
- package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +50 -0
- package/dist/backend/templates/harness/vcm-route-message-skill.js +86 -0
- package/dist/backend/templates/message-envelope.js +1 -0
- package/dist/backend/templates/role-command.js +7 -1
- package/dist/shared/validation/artifact-check.js +14 -9
- package/dist-frontend/assets/index-CrY5Ryps.js +90 -0
- package/dist-frontend/assets/index-CvvtrrCN.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/docs/cc-best-practices.md +433 -192
- package/docs/full-harness-baseline.md +254 -0
- package/docs/product-design.md +9 -9
- package/docs/v0.2-implementation-plan.md +379 -0
- package/docs/vcm-cc-best-practices.md +449 -0
- package/package.json +3 -1
- package/scripts/harness-tools/generate-module-index +298 -0
- package/scripts/harness-tools/generate-public-surface +692 -0
- package/scripts/install-vcm-harness.mjs +1607 -0
- package/scripts/uninstall-vcm-harness.mjs +490 -0
- package/scripts/verify-package.mjs +4 -0
- package/dist-frontend/assets/index-D40qaonx.css +0 -32
- package/dist-frontend/assets/index-DK2F4LFT.js +0 -90
- package/docs/v1-architecture-design.md +0 -1014
- package/docs/v1-implementation-plan.md +0 -1379
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
|
|
7
|
+
const MANIFEST_PATH = ".ai/vcm-harness-manifest.json";
|
|
8
|
+
const HTML_BLOCK_PATTERN = /<!-- VCM:BEGIN(?:\s+version=\d+)? -->[\s\S]*?<!-- VCM:END -->/m;
|
|
9
|
+
const HASH_BLOCK_PATTERN = /# VCM:BEGIN(?:\s+version=\d+)?\n[\s\S]*?# VCM:END/m;
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const args = parseArgs(process.argv.slice(2));
|
|
13
|
+
if (args.help) {
|
|
14
|
+
printUsage();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!args.projectRoot) {
|
|
19
|
+
fail("Missing project root.");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const projectRoot = path.resolve(args.projectRoot);
|
|
23
|
+
const manifestPath = args.manifest
|
|
24
|
+
? resolveInside(projectRoot, args.manifest)
|
|
25
|
+
: path.join(projectRoot, MANIFEST_PATH);
|
|
26
|
+
const manifest = await readManifest(manifestPath);
|
|
27
|
+
const dryRun = args.dryRun;
|
|
28
|
+
const operations = [];
|
|
29
|
+
const warnings = [];
|
|
30
|
+
const plannedDeletes = new Set();
|
|
31
|
+
|
|
32
|
+
validateManifest(manifest, manifestPath);
|
|
33
|
+
|
|
34
|
+
for (const entry of manifest.entries) {
|
|
35
|
+
await processEntry({ projectRoot, entry, dryRun, operations, warnings, plannedDeletes });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const runtimeRoot of manifest.runtimeRoots ?? []) {
|
|
39
|
+
await deleteRuntimeRoot({ projectRoot, runtimeRoot, dryRun, operations, warnings, plannedDeletes });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await removeManifestDirectories({ projectRoot, manifest, dryRun, operations, warnings, plannedDeletes });
|
|
43
|
+
|
|
44
|
+
printReport({ projectRoot, manifestPath, dryRun, operations, warnings });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseArgs(argv) {
|
|
48
|
+
const args = {
|
|
49
|
+
dryRun: false,
|
|
50
|
+
help: false,
|
|
51
|
+
manifest: undefined,
|
|
52
|
+
projectRoot: undefined
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
56
|
+
const arg = argv[index];
|
|
57
|
+
if (arg === "--help" || arg === "-h") {
|
|
58
|
+
args.help = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (arg === "--dry-run") {
|
|
62
|
+
args.dryRun = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (arg === "--manifest") {
|
|
66
|
+
const value = argv[index + 1];
|
|
67
|
+
if (!value) {
|
|
68
|
+
fail("--manifest requires a relative path inside the project root.");
|
|
69
|
+
}
|
|
70
|
+
args.manifest = value;
|
|
71
|
+
index += 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (arg.startsWith("--")) {
|
|
75
|
+
fail(`Unknown option: ${arg}`);
|
|
76
|
+
}
|
|
77
|
+
if (args.projectRoot) {
|
|
78
|
+
fail(`Unexpected argument: ${arg}`);
|
|
79
|
+
}
|
|
80
|
+
args.projectRoot = arg;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return args;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function printUsage() {
|
|
87
|
+
console.log(`Usage:
|
|
88
|
+
node scripts/uninstall-vcm-harness.mjs <project-root>
|
|
89
|
+
node scripts/uninstall-vcm-harness.mjs <project-root> --manifest <relative-path>
|
|
90
|
+
node scripts/uninstall-vcm-harness.mjs <project-root> --dry-run
|
|
91
|
+
|
|
92
|
+
Deletes VCM-owned harness changes by default. Pass --dry-run to preview.
|
|
93
|
+
The script reads .ai/vcm-harness-manifest.json from the target project and removes
|
|
94
|
+
only VCM-owned managed blocks, VCM-owned whole files, generated artifacts,
|
|
95
|
+
VCM Claude settings hooks, runtime roots, and empty VCM-created directories.`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function readManifest(manifestPath) {
|
|
99
|
+
const content = await fs.readFile(manifestPath, "utf8").catch((error) => {
|
|
100
|
+
if (error.code === "ENOENT") {
|
|
101
|
+
fail(`Manifest not found: ${manifestPath}`);
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(content);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
fail(`Manifest is not valid JSON: ${manifestPath}\n${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function validateManifest(manifest, manifestPath) {
|
|
113
|
+
if (!isPlainObject(manifest)) {
|
|
114
|
+
fail(`Manifest must be a JSON object: ${manifestPath}`);
|
|
115
|
+
}
|
|
116
|
+
if (manifest.manager !== "vcm") {
|
|
117
|
+
fail(`Manifest manager must be "vcm": ${manifestPath}`);
|
|
118
|
+
}
|
|
119
|
+
if (!Array.isArray(manifest.entries)) {
|
|
120
|
+
fail(`Manifest entries must be an array: ${manifestPath}`);
|
|
121
|
+
}
|
|
122
|
+
if (manifest.runtimeRoots !== undefined && !Array.isArray(manifest.runtimeRoots)) {
|
|
123
|
+
fail(`Manifest runtimeRoots must be an array when present: ${manifestPath}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function processEntry(context) {
|
|
128
|
+
const { entry } = context;
|
|
129
|
+
if (!isPlainObject(entry) || typeof entry.path !== "string") {
|
|
130
|
+
context.warnings.push("Skipped malformed manifest entry.");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const uninstallAction = entry.uninstall?.action;
|
|
135
|
+
|
|
136
|
+
if (entry.entryType === "directory") {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (entry.ownership === "managed-block" || uninstallAction === "remove-managed-block") {
|
|
141
|
+
await removeManagedBlock(context);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (entry.ownership === "json-merge" || uninstallAction === "remove-owned-json-keys") {
|
|
146
|
+
await removeOwnedJson(context);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
entry.ownership === "whole-file" ||
|
|
152
|
+
entry.ownership === "derived-artifact" ||
|
|
153
|
+
uninstallAction === "delete-file-if-unchanged" ||
|
|
154
|
+
uninstallAction === "delete-derived-artifact"
|
|
155
|
+
) {
|
|
156
|
+
await deleteFile(context);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
context.warnings.push(`No uninstall handler for ${entry.path}.`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function removeManagedBlock({ projectRoot, entry, dryRun, operations, warnings, plannedDeletes }) {
|
|
164
|
+
const absolutePath = resolveInside(projectRoot, entry.path);
|
|
165
|
+
const content = await readOptionalText(absolutePath);
|
|
166
|
+
if (content === undefined) {
|
|
167
|
+
operations.push(skip(entry.path, "missing"));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const pattern = entry.marker?.type === "hash-comment" ? HASH_BLOCK_PATTERN : HTML_BLOCK_PATTERN;
|
|
172
|
+
if (!pattern.test(content)) {
|
|
173
|
+
warnings.push(`Managed block not found: ${entry.path}`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const nextContent = normalizeAfterBlockRemoval(content.replace(pattern, ""));
|
|
178
|
+
if (shouldDeleteManagedBlockStub(entry, nextContent)) {
|
|
179
|
+
await removeFileAndEmptyParents({ projectRoot, absolutePath, relativePath: entry.path, dryRun, operations, plannedDeletes });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (dryRun) {
|
|
184
|
+
operations.push(plan(entry.path, "remove managed block"));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await fs.writeFile(absolutePath, nextContent, "utf8");
|
|
189
|
+
operations.push(done(entry.path, "removed managed block"));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function normalizeAfterBlockRemoval(content) {
|
|
193
|
+
const trimmed = content.trim();
|
|
194
|
+
return trimmed ? `${trimmed}\n` : "";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function shouldDeleteManagedBlockStub(entry, content) {
|
|
198
|
+
const trimmed = content.trim();
|
|
199
|
+
if (!trimmed) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (entry.category === "core-agent") {
|
|
204
|
+
return /^---\n[\s\S]*?\n---\n\n# .+ Agent$/.test(trimmed);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (entry.category === "pull-request-template") {
|
|
208
|
+
return trimmed === "# Pull Request Template";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function removeOwnedJson({ projectRoot, entry, dryRun, operations, warnings, plannedDeletes }) {
|
|
215
|
+
const absolutePath = resolveInside(projectRoot, entry.path);
|
|
216
|
+
const content = await readOptionalText(absolutePath);
|
|
217
|
+
if (content === undefined) {
|
|
218
|
+
operations.push(skip(entry.path, "missing"));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let value;
|
|
223
|
+
try {
|
|
224
|
+
value = JSON.parse(content);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
warnings.push(`Skipped invalid JSON ${entry.path}: ${error.message}`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!isPlainObject(value)) {
|
|
231
|
+
warnings.push(`Skipped non-object JSON file: ${entry.path}`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const nextValue = removeVcmHookMatchers(value, entry.jsonOwnership?.hookMatchers ?? ["VCM"]);
|
|
236
|
+
if (deepEqual(value, nextValue)) {
|
|
237
|
+
operations.push(skip(entry.path, "no VCM-owned JSON values found"));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (dryRun) {
|
|
242
|
+
operations.push(plan(entry.path, "remove VCM-owned JSON values"));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
await fs.writeFile(absolutePath, `${JSON.stringify(nextValue, null, 2)}\n`, "utf8");
|
|
247
|
+
operations.push(done(entry.path, "removed VCM-owned JSON values"));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function removeVcmHookMatchers(settings, hookMatchers) {
|
|
251
|
+
const nextSettings = structuredClone(settings);
|
|
252
|
+
if (!isPlainObject(nextSettings.hooks)) {
|
|
253
|
+
return nextSettings;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const hooks = { ...nextSettings.hooks };
|
|
257
|
+
for (const [eventName, eventMatchers] of Object.entries(hooks)) {
|
|
258
|
+
if (!Array.isArray(eventMatchers)) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const remaining = eventMatchers.filter((matcher) => !isOwnedHookMatcher(matcher, hookMatchers));
|
|
262
|
+
if (remaining.length > 0) {
|
|
263
|
+
hooks[eventName] = remaining;
|
|
264
|
+
} else {
|
|
265
|
+
delete hooks[eventName];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (Object.keys(hooks).length > 0) {
|
|
270
|
+
nextSettings.hooks = hooks;
|
|
271
|
+
} else {
|
|
272
|
+
delete nextSettings.hooks;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return nextSettings;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function isOwnedHookMatcher(matcher, hookMatchers) {
|
|
279
|
+
if (!isPlainObject(matcher) || !Array.isArray(matcher.hooks)) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
return matcher.hooks.some((hook) => {
|
|
283
|
+
if (!isPlainObject(hook)) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
const command = typeof hook.command === "string" ? hook.command : "";
|
|
287
|
+
return hookMatchers.some((marker) => command.includes(marker)) ||
|
|
288
|
+
command.includes("/api/hooks/claude-code") ||
|
|
289
|
+
command.includes("hook-event");
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function deleteFile({ projectRoot, entry, dryRun, operations, plannedDeletes }) {
|
|
294
|
+
const absolutePath = resolveInside(projectRoot, entry.path);
|
|
295
|
+
await removeFileAndEmptyParents({ projectRoot, absolutePath, relativePath: entry.path, dryRun, operations, plannedDeletes });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function deleteRuntimeRoot({ projectRoot, runtimeRoot, dryRun, operations, warnings, plannedDeletes }) {
|
|
299
|
+
if (typeof runtimeRoot !== "string") {
|
|
300
|
+
warnings.push("Skipped malformed runtime root.");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const absolutePath = resolveInside(projectRoot, runtimeRoot);
|
|
304
|
+
const exists = await pathExists(absolutePath);
|
|
305
|
+
if (!exists) {
|
|
306
|
+
operations.push(skip(runtimeRoot, "missing"));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (dryRun) {
|
|
311
|
+
operations.push(plan(runtimeRoot, "delete runtime root"));
|
|
312
|
+
plannedDeletes.add(toRelative(projectRoot, absolutePath));
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
await fs.rm(absolutePath, { recursive: true, force: true });
|
|
317
|
+
operations.push(done(runtimeRoot, "deleted runtime root"));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function removeManifestDirectories({ projectRoot, manifest, dryRun, operations, warnings, plannedDeletes }) {
|
|
321
|
+
const directories = manifest.entries
|
|
322
|
+
.filter((entry) => entry.entryType === "directory" && entry.ownership === "vcm-created")
|
|
323
|
+
.map((entry) => entry.path)
|
|
324
|
+
.sort((left, right) => right.length - left.length);
|
|
325
|
+
|
|
326
|
+
for (const directory of directories) {
|
|
327
|
+
const absolutePath = resolveInside(projectRoot, directory);
|
|
328
|
+
const stat = await fs.stat(absolutePath).catch((error) => {
|
|
329
|
+
if (error.code === "ENOENT") {
|
|
330
|
+
operations.push(skip(directory, "missing"));
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
throw error;
|
|
334
|
+
});
|
|
335
|
+
if (!stat) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (!stat.isDirectory()) {
|
|
339
|
+
warnings.push(`Manifest directory is not a directory: ${directory}`);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const children = await fs.readdir(absolutePath);
|
|
343
|
+
if (dryRun && children.every((child) => child === ".gitkeep" || plannedDeletes.has(toRelative(projectRoot, path.join(absolutePath, child))))) {
|
|
344
|
+
if (children.includes(".gitkeep")) {
|
|
345
|
+
operations.push(plan(path.posix.join(directory.replace(/\/$/, ""), ".gitkeep"), "delete VCM directory placeholder"));
|
|
346
|
+
}
|
|
347
|
+
operations.push(plan(directory, "delete empty VCM-created directory"));
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (children.length === 1 && children[0] === ".gitkeep") {
|
|
351
|
+
const keepPath = path.join(absolutePath, ".gitkeep");
|
|
352
|
+
if (dryRun) {
|
|
353
|
+
operations.push(plan(path.posix.join(directory.replace(/\/$/, ""), ".gitkeep"), "delete VCM directory placeholder"));
|
|
354
|
+
operations.push(plan(directory, "delete empty VCM-created directory"));
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
await fs.rm(keepPath, { force: true });
|
|
358
|
+
operations.push(done(path.posix.join(directory.replace(/\/$/, ""), ".gitkeep"), "deleted VCM directory placeholder"));
|
|
359
|
+
await fs.rmdir(absolutePath);
|
|
360
|
+
operations.push(done(directory, "deleted empty VCM-created directory"));
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (children.length > 0) {
|
|
364
|
+
operations.push(skip(directory, "not empty"));
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (dryRun) {
|
|
368
|
+
operations.push(plan(directory, "delete empty VCM-created directory"));
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
await fs.rmdir(absolutePath);
|
|
372
|
+
operations.push(done(directory, "deleted empty VCM-created directory"));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function removeFileAndEmptyParents({ projectRoot, absolutePath, relativePath, dryRun, operations, plannedDeletes }) {
|
|
377
|
+
const stat = await fs.stat(absolutePath).catch((error) => {
|
|
378
|
+
if (error.code === "ENOENT") {
|
|
379
|
+
operations.push(skip(relativePath, "missing"));
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
throw error;
|
|
383
|
+
});
|
|
384
|
+
if (!stat) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (!stat.isFile()) {
|
|
388
|
+
operations.push(skip(relativePath, "not a file"));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (dryRun) {
|
|
393
|
+
operations.push(plan(relativePath, "delete file"));
|
|
394
|
+
plannedDeletes?.add(toRelative(projectRoot, absolutePath));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
await fs.rm(absolutePath, { force: true });
|
|
399
|
+
operations.push(done(relativePath, "deleted file"));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function resolveInside(root, relativePath) {
|
|
403
|
+
if (path.isAbsolute(relativePath)) {
|
|
404
|
+
fail(`Manifest path must be relative: ${relativePath}`);
|
|
405
|
+
}
|
|
406
|
+
const normalized = path.normalize(relativePath);
|
|
407
|
+
if (normalized === ".." || normalized.startsWith(`..${path.sep}`)) {
|
|
408
|
+
fail(`Manifest path escapes the project root: ${relativePath}`);
|
|
409
|
+
}
|
|
410
|
+
const resolved = path.resolve(root, normalized);
|
|
411
|
+
if (!isInside(root, resolved) && resolved !== root) {
|
|
412
|
+
fail(`Manifest path escapes the project root: ${relativePath}`);
|
|
413
|
+
}
|
|
414
|
+
return resolved;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function isInside(root, candidate) {
|
|
418
|
+
const relative = path.relative(root, candidate);
|
|
419
|
+
return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function toRelative(root, candidate) {
|
|
423
|
+
return path.relative(root, candidate).split(path.sep).join("/");
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function readOptionalText(absolutePath) {
|
|
427
|
+
return fs.readFile(absolutePath, "utf8").catch((error) => {
|
|
428
|
+
if (error.code === "ENOENT") {
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
throw error;
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function pathExists(absolutePath) {
|
|
436
|
+
return fs.stat(absolutePath).then(
|
|
437
|
+
() => true,
|
|
438
|
+
(error) => {
|
|
439
|
+
if (error.code === "ENOENT") {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function plan(pathName, action) {
|
|
448
|
+
return { status: "plan", path: pathName, action };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function done(pathName, action) {
|
|
452
|
+
return { status: "done", path: pathName, action };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function skip(pathName, reason) {
|
|
456
|
+
return { status: "skip", path: pathName, action: reason };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function printReport({ projectRoot, manifestPath, dryRun, operations, warnings }) {
|
|
460
|
+
console.log(`${dryRun ? "Dry-run" : "Applied"} VCM harness uninstall`);
|
|
461
|
+
console.log(`Project: ${projectRoot}`);
|
|
462
|
+
console.log(`Manifest: ${manifestPath}`);
|
|
463
|
+
|
|
464
|
+
for (const operation of operations) {
|
|
465
|
+
console.log(`${operation.status.toUpperCase()} ${operation.path} - ${operation.action}`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
for (const warning of warnings) {
|
|
469
|
+
console.warn(`WARN ${warning}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (dryRun) {
|
|
473
|
+
console.log("No files changed. Re-run without --dry-run to apply.");
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function isPlainObject(value) {
|
|
478
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function deepEqual(left, right) {
|
|
482
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function fail(message) {
|
|
486
|
+
console.error(`VCM harness uninstall failed: ${message}`);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
await main();
|
|
@@ -6,6 +6,10 @@ const requiredFiles = [
|
|
|
6
6
|
"README.md",
|
|
7
7
|
"package.json",
|
|
8
8
|
"scripts/fix-node-pty-spawn-helper.mjs",
|
|
9
|
+
"scripts/harness-tools/generate-module-index",
|
|
10
|
+
"scripts/harness-tools/generate-public-surface",
|
|
11
|
+
"scripts/install-vcm-harness.mjs",
|
|
12
|
+
"scripts/uninstall-vcm-harness.mjs",
|
|
9
13
|
"dist/main.js",
|
|
10
14
|
"dist/backend/server.js",
|
|
11
15
|
"dist/backend/api/harness-routes.js",
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
3
|
-
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
4
|
-
* https://github.com/chjj/term.js
|
|
5
|
-
* @license MIT
|
|
6
|
-
*
|
|
7
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
-
* in the Software without restriction, including without limitation the rights
|
|
10
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
-
* furnished to do so, subject to the following conditions:
|
|
13
|
-
*
|
|
14
|
-
* The above copyright notice and this permission notice shall be included in
|
|
15
|
-
* all copies or substantial portions of the Software.
|
|
16
|
-
*
|
|
17
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
-
* THE SOFTWARE.
|
|
24
|
-
*
|
|
25
|
-
* Originally forked from (with the author's permission):
|
|
26
|
-
* Fabrice Bellard's javascript vt100 for jslinux:
|
|
27
|
-
* http://bellard.org/jslinux/
|
|
28
|
-
* Copyright (c) 2011 Fabrice Bellard
|
|
29
|
-
* The original design remains. The terminal itself
|
|
30
|
-
* has been extended to include xterm CSI codes, among
|
|
31
|
-
* other features.
|
|
32
|
-
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{color-scheme:light;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;background:#f5f2ea;color:#1c2024;line-height:1.5}html,body,#root{height:100%}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:#f5f2ea}button,input,select,textarea{font:inherit}button{border:1px solid #9ba6ad;background:#f8f7f2;color:#1d252b;border-radius:6px;min-height:34px;padding:6px 10px;cursor:pointer}button:hover:not(:disabled){background:#eef4f2;border-color:#607d74}button:disabled{cursor:not-allowed;opacity:.55}.danger-button{border-color:#b84a45;background:#fff1ee;color:#8d211d;font-weight:750}.danger-button:hover:not(:disabled){border-color:#8d211d;background:#ffe2dc}input,select,textarea:not(.xterm-helper-textarea){width:100%;border:1px solid #b9b0a1;border-radius:6px;background:#fffdf8;color:#202326;padding:8px 10px}textarea:not(.xterm-helper-textarea){min-height:240px;resize:vertical;font-family:Menlo,Monaco,Consolas,monospace;font-size:12px}h1,h2,p{margin-top:0}h1{margin-bottom:4px;font-size:26px;line-height:1.15}h2{margin-bottom:10px;font-size:14px;letter-spacing:0}.app-shell{display:grid;grid-template-columns:minmax(280px,320px) minmax(0,1fr);height:100vh;min-height:0;overflow:hidden}.app-shell.is-sidebar-collapsed{grid-template-columns:46px minmax(0,1fr)}.app-sidebar{position:relative;min-width:0;border-right:1px solid #d3c9b8;background:#fbfaf6;padding:14px;overflow:auto}.app-shell.is-sidebar-collapsed .app-sidebar{overflow:hidden;padding:8px}.sidebar-toggle{position:absolute;top:10px;right:10px;z-index:2;display:grid;place-items:center;width:28px;min-height:28px;padding:0;background:#fffdf8}.sidebar-toggle:before{width:8px;height:8px;border-color:currentColor;border-style:solid;border-width:0 2px 2px 0;content:"";transform:translate(2px) rotate(135deg)}.app-shell.is-sidebar-collapsed .sidebar-toggle{left:9px;right:auto}.app-shell.is-sidebar-collapsed .sidebar-toggle:before{transform:translate(-2px) rotate(-45deg)}.sidebar-content{min-width:0}.app-shell.is-sidebar-collapsed .sidebar-content{width:0;opacity:0;pointer-events:none}.app-main{min-width:0;height:100%;padding:14px 16px;overflow:auto}.brand-header{display:flex;gap:12px;align-items:baseline;margin-bottom:10px;padding-right:34px}.brand-header strong{font-size:18px}.brand-header span,.muted{color:#667071;font-size:13px}.sidebar-section{margin-bottom:8px;border:1px solid #e0d6c7;border-radius:8px;background:#fffdfa;overflow:hidden}.sidebar-section-toggle{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;width:100%;min-height:34px;border:0;border-radius:0;background:transparent;color:#1c2024;font-size:13px;font-weight:750;text-align:left}.sidebar-section-toggle:hover{background:#f5f1e8}.sidebar-section-toggle[aria-expanded=true]{border-bottom:1px solid #ece5d9}.sidebar-section-chevron{width:8px;height:8px;border-color:currentColor;border-style:solid;border-width:0 2px 2px 0;transform:rotate(45deg)}.sidebar-section-toggle[aria-expanded=true] .sidebar-section-chevron{transform:rotate(-135deg)}.sidebar-section-content{padding:8px}.repo-connect,.project-summary,.harness-panel,.task-create{margin:0}.inline-form{display:grid;grid-template-columns:minmax(0,1fr);gap:8px}.inline-form.has-recent-paths{grid-template-columns:minmax(0,1fr) auto}.inline-form>input{grid-column:1 / -1}.inline-form>button{justify-self:end}.inline-form.has-recent-paths>button{justify-self:auto}.repo-recent-select{min-width:0;max-width:none}.project-summary dl{display:grid;gap:8px;margin:0}.project-summary div{min-width:0}.project-summary dt{color:#6c6255;font-size:12px}.project-summary dd{margin:0;overflow-wrap:anywhere;font-size:13px}.warnings,.error-banner{border:1px solid #c87b54;background:#fff4ed;color:#6f3218;border-radius:6px;padding:10px 12px}.round-notice{position:fixed;right:20px;bottom:20px;z-index:30;display:grid;gap:2px;min-width:220px;max-width:min(360px,calc(100vw - 40px));border:1px solid #2f6f73;border-radius:8px;background:#e8f4f2;color:#123f43;box-shadow:0 14px 32px #1f242b29;padding:10px 12px}.round-notice strong{font-size:13px}.round-notice span{font-size:12px}.warnings{margin:12px 0 0;padding-left:26px;font-size:13px}.harness-panel{display:grid;gap:8px}.harness-panel-header{display:flex;justify-content:space-between;gap:8px;align-items:center}.harness-panel-header h2,.harness-panel-header p,.harness-result p{margin-bottom:0}.harness-actions{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}.harness-file-list{display:grid;gap:6px;margin:0;padding:0;list-style:none}.harness-file-list li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;border:1px solid #ece5d9;border-radius:6px;padding:6px 8px;background:#fffdfa}.harness-file-list span{overflow:hidden;font-size:12px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.harness-changes,.harness-result{border:1px solid #e0d6c7;border-radius:6px;padding:8px;background:#f8f7f2;font-size:12px}.harness-changes h3{margin:0 0 4px;font-size:12px}.harness-changes ul,.harness-result ul{margin:0;padding-left:18px}.task-create form{display:grid;gap:8px}.task-create-preview{display:grid;gap:2px;border:1px solid #e0d6c7;border-radius:6px;background:#f8f7f2;padding:6px 8px}.task-create-option{display:flex;align-items:center;gap:8px;color:#1f242b;font-size:13px;font-weight:700}.task-create-option input{width:16px;height:16px;margin:0}.task-create-preview span{color:#255f3d;font-size:12px;font-weight:700}.task-create-preview small{overflow:hidden;color:#687273;font-size:11px;text-overflow:ellipsis;white-space:nowrap}.task-nav{display:grid;gap:8px}.task-nav-item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;text-align:left}.task-nav-item.is-active,.role-tab.is-active{border-color:#2f6f73;background:#e8f1ef}.sidebar-settings{display:grid;gap:8px}.sidebar-settings button{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;width:100%;text-align:left}.sidebar-settings .settings-toggle.is-active{border-color:#2f6f73;background:#e8f1ef}.sidebar-settings .theme-mode-toggle span:last-child{font-weight:750}.workspace-header{display:grid;grid-template-columns:minmax(120px,max-content) minmax(420px,1fr) auto;gap:10px;align-items:center;margin-bottom:6px}.workspace-title-line{display:flex;min-width:0}.workspace-title-line h1{margin-bottom:0;font-size:18px;line-height:1.15}.workspace-header-actions{display:flex;gap:8px;align-items:center;justify-content:flex-end;min-width:max-content}.role-tabs{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-bottom:8px;min-width:0}.workspace-header .role-tabs{margin-bottom:0}.role-tab{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px;align-items:center;min-height:30px;padding:4px 8px;text-align:left}.workspace-grid{display:grid;grid-template-columns:minmax(0,1fr);flex:1;gap:10px;align-items:stretch;min-height:0}.workspace-main{min-width:0;min-height:0;display:flex;flex-direction:column;gap:8px}.role-console-stack{min-width:0;min-height:0;flex:1;display:flex;flex-direction:column}.role-console-panel{min-width:0;min-height:0;flex:1;display:none}.role-console-panel.is-active{display:flex;flex-direction:column}.session-console,.message-panel,.event-log,.empty-workspace{border:1px solid #d6d0c6;border-radius:8px;background:#fffdf8;padding:10px}.task-workspace{display:flex;flex-direction:column;gap:8px;height:100%;min-height:0}.session-console{display:grid;grid-template-rows:auto minmax(0,1fr);gap:8px;flex:1;height:100%;min-height:0}.session-console-top{display:flex;gap:10px;align-items:center;justify-content:space-between}.session-console-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:flex-end}.session-controls{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:space-between;margin:0 0 8px}.permission-mode-field{display:grid;grid-template-columns:auto minmax(180px,260px);gap:8px;align-items:center;width:fit-content;max-width:100%}.permission-mode-field span{color:#5f6a6c;font-size:13px;font-weight:650}.permission-mode-field small{display:block;color:#7b8587;font-size:11px;font-weight:500;line-height:1.2}.permission-mode-field select{width:100%;min-height:30px;border:1px solid #b9b0a1;border-radius:6px;background:#fffdf8;color:#202326;padding:4px 8px}.session-toolbar{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}.session-toolbar button{min-height:30px;padding:4px 9px}.translation-toggle{display:inline-flex;align-items:center;justify-content:center;min-height:28px;border:1px solid #b5bec4;border-radius:6px;background:#fffefa;color:#4f5558;font-size:12px;font-weight:650;padding:3px 10px;white-space:nowrap}.translation-toggle.is-active{border-color:#2f7e84;background:#e8f4f2;color:#145e64}.translation-settings-grid input[type=checkbox]{width:auto}.session-console-body{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;min-height:0;height:100%}.session-console-body.has-translation{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px;align-items:stretch}.terminal-pane,.translation-pane{display:grid;min-width:0;min-height:0;height:100%}.terminal-frame,.terminal-empty{width:100%;height:100%;min-height:0;border-radius:6px;overflow:hidden;background:#111316}.terminal-empty{display:grid;place-items:center;align-content:center;gap:8px;color:#d6d0c6;border:1px solid #292d31}.translation-panel{display:grid;grid-template-rows:auto minmax(0,1fr) auto;gap:8px;height:100%;min-height:0;border:1px solid #292d31;border-radius:6px;background:#0d1117;color:#d6deeb;padding:8px;min-width:0;width:100%;overflow:hidden;font-family:Menlo,Monaco,Consolas,monospace}.translation-panel-header{display:grid;gap:3px}.translation-panel-titlebar,.translation-panel-actions,.translation-status-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;justify-content:space-between}.translation-panel-header h2,.translation-panel-header p{margin-bottom:0}.translation-panel-titlebar h2{font-size:16px}.translation-panel-header p,.translation-composer span{color:#8b949e;font-size:12px}.translation-panel-actions button{border-color:#3a4149;background:#161b22;color:#d6deeb;min-height:26px;padding:2px 8px;font-size:12px}.translation-panel-actions button:hover:not(:disabled),.translation-composer-actions button:hover:not(:disabled){border-color:#58a6ff;background:#1f2937}.translation-panel-actions .auto-send-toggle.is-active{border-color:#56d364;background:#12261a;color:#d6deeb}.translation-panel-actions{justify-content:flex-end}.translation-status-row{flex-wrap:nowrap}.translation-status-row p:last-child{flex:0 0 auto;text-align:right}.translation-entry-list{display:grid;align-content:start;gap:8px;min-height:0;min-width:0;overflow-x:hidden;overflow-y:auto;scrollbar-color:#4b5563 #0d1117}.translation-entry{border:0;border-radius:0;background:transparent;min-width:0;max-width:100%;padding:0}.translation-entry.is-user-input{border-top:4px solid #3a4149;margin-top:14px;padding-top:14px}.translation-entry.is-user-input:first-child{margin-top:0}.translation-entry pre{box-sizing:border-box;margin:0;max-height:none;max-width:100%;min-width:0;overflow:visible;white-space:pre-wrap;overflow-wrap:anywhere;font-family:Menlo,Monaco,Consolas,monospace;font-size:12px;line-height:1.45;color:#d6deeb}.translation-markdown{max-width:100%;min-width:0;color:#d6deeb;font-size:12px;line-height:1.55;overflow-wrap:anywhere}.translation-markdown>:first-child{margin-top:0}.translation-markdown>:last-child{margin-bottom:0}.translation-markdown p,.translation-markdown ul,.translation-markdown ol,.translation-markdown blockquote,.translation-markdown pre,.translation-markdown table{margin:0 0 8px}.translation-markdown h1,.translation-markdown h2,.translation-markdown h3,.translation-markdown h4,.translation-markdown h5,.translation-markdown h6{margin:10px 0 6px;color:#f0f6fc;font-weight:700;line-height:1.25}.translation-markdown h1{font-size:17px}.translation-markdown h2{font-size:15px}.translation-markdown h3,.translation-markdown h4,.translation-markdown h5,.translation-markdown h6{font-size:13px}.translation-markdown ul,.translation-markdown ol{padding-left:22px}.translation-markdown li{margin:2px 0}.translation-markdown .task-list-item{list-style:none}.translation-markdown input[type=checkbox]{width:13px;height:13px;margin:0 6px 0 0;accent-color:#56d364}.translation-markdown blockquote{border-left:3px solid #3a4149;color:#b7c0ca;padding-left:10px}.translation-markdown a{color:#79c0ff}.translation-markdown code{border-radius:4px;background:#161b22;color:#f0f6fc;padding:1px 4px;font-family:Menlo,Monaco,Consolas,monospace;font-size:.95em}.translation-markdown pre{overflow-x:auto;border:1px solid #292d31;border-radius:6px;background:#111316;padding:8px;white-space:pre}.translation-markdown pre code{background:transparent;padding:0}.translation-markdown table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse}.translation-markdown img{max-width:100%;border-radius:4px}.translation-markdown th,.translation-markdown td{border:1px solid #30363d;padding:4px 6px;text-align:left;vertical-align:top}.translation-markdown hr{border:0;border-top:1px solid #292d31;margin:10px 0}.translation-entry.is-tool-output pre{display:block;overflow:hidden;color:#7d8590;text-overflow:ellipsis;white-space:nowrap;width:100%}.translation-entry-note{margin:2px 0 0;color:#6e7681;font-size:10px;line-height:1.35}.translation-entry-note.is-error{color:#a3715f}.translation-composer{display:grid;gap:6px;border-top:1px solid #292d31;padding-top:8px}.translation-composer-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:stretch}.translation-composer textarea{width:100%;min-height:38px;max-height:88px;border-color:#3a4149;background:#0d1117;color:#d6deeb;font-family:inherit;font-size:12px;line-height:1.35;resize:vertical}.translation-composer textarea::placeholder{color:#7d8590}.translation-composer textarea::selection{background:#8b949e59;color:#fff}.translation-composer textarea:focus,.translation-composer textarea:focus-visible{border-color:#4b5563;outline:none;box-shadow:none}.translation-composer-actions{display:grid;align-content:start;gap:6px;min-width:104px}.translation-composer-actions button{border-color:#3a4149;background:#161b22;color:#d6deeb;width:100%;min-height:38px;padding:4px 9px;font-size:12px}.translation-panel .muted{color:#8b949e}.translation-panel .error-banner{border-color:#da7b72;background:#2d1518;color:#ffdcd7}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:20;display:grid;place-items:center;background:#181c1f61;padding:18px}.translation-settings-modal{display:grid;gap:12px;width:min(980px,100%);max-height:min(760px,92vh);overflow:auto;border:1px solid #d6d0c6;border-radius:8px;background:#fffdf8;padding:14px}.message-modal,.event-modal{display:grid;grid-template-rows:auto minmax(0,1fr);gap:12px;width:min(980px,100%);max-height:min(760px,92vh);overflow:hidden;border:1px solid #d6d0c6;border-radius:8px;background:#fffdf8;padding:14px}.translation-settings-modal header,.message-modal header,.event-modal header,.translation-settings-modal footer{display:flex;gap:8px;align-items:center;justify-content:space-between}.translation-prompt-settings{display:grid;gap:10px;border-top:1px solid #ece5d9;padding-top:12px}.translation-prompt-settings header{display:flex;gap:10px;align-items:end;justify-content:space-between}.translation-prompt-settings h3,.translation-prompt-settings p{margin-bottom:0}.translation-prompt-settings h3{font-size:13px}.translation-prompt-settings textarea{min-height:120px;max-height:260px;font-family:Menlo,Monaco,Consolas,monospace;font-size:12px}.translation-prompt-stack{display:grid;gap:10px}.translation-prompt-stack label{display:grid;gap:4px}.translation-prompt-stack span{color:#4f5558;font-size:12px;font-weight:650}.translation-settings-modal h2,.message-modal h2,.message-modal p,.event-modal h2,.event-modal p,.translation-settings-modal p{margin-bottom:0}.translation-settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.translation-settings-grid label{display:grid;gap:4px}.translation-settings-grid span{color:#4f5558;font-size:12px;font-weight:650}.translation-settings-grid select,.translation-prompt-settings select{min-height:34px;border:1px solid #b9b0a1;border-radius:6px;background:#fffdf8;color:#202326;padding:6px 10px}.translation-test-result{border-radius:6px;padding:8px;font-size:13px}.translation-test-result.is-ok{border:1px solid #6ea77e;background:#e6f3e9;color:#245334}.translation-test-result.is-error{border:1px solid #c46e5f;background:#fae9e6;color:#6f2b21}.message-panel{display:grid;gap:8px}.message-modal .message-panel,.event-modal .event-log{min-height:0;overflow:auto;border:0;background:transparent;padding:0}.message-panel-header{display:flex;justify-content:space-between;gap:12px;align-items:center}.message-panel-header h2,.message-panel-header p{margin-bottom:0}.message-controls,.message-mode-toggle,.modal-actions,.message-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center}.message-mode-toggle{color:#4f5558;font-size:13px;font-weight:650}.message-mode-toggle input{width:auto}.message-list{display:grid;gap:8px;margin:0;padding:0;list-style:none}.message-item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:start;border:1px solid #ece5d9;border-radius:6px;padding:8px;background:#fffdfa}.message-meta{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:4px}.message-meta time,.message-meta span:not(.status-badge),.message-reason,.message-path{color:#667071;font-size:12px}.message-sequence{min-width:32px;color:#1f242b;font-weight:800}.message-actions button{min-height:30px;padding:4px 10px}.message-item p{display:-webkit-box;margin-bottom:4px;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2}.message-reason,.message-path{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.event-log ol{margin:0;padding-left:22px;font-size:13px}.event-log{max-height:92px;overflow:auto}.event-log h2{margin-bottom:4px;font-size:13px}.event-log p{margin-bottom:0}.event-log li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-badge{display:inline-flex;align-items:center;justify-content:center;min-width:66px;min-height:20px;border-radius:999px;padding:2px 8px;border:1px solid #c7c1b8;background:#f2eee7;color:#4f5558;font-size:11px;white-space:nowrap}.status-running,.status-ok{border-color:#6ea77e;background:#e6f3e9;color:#245334}.status-idle{border-color:#9aa3a8;background:#edf0f1;color:#465056}.status-blocked,.status-crashed,.status-missing,.status-empty{border-color:#c46e5f;background:#fae9e6;color:#6f2b21}.status-waiting,.status-starting,.status-incomplete{border-color:#c4a34e;background:#f7efcf;color:#604e16}.status-exited,.status-done,.status-resumable{border-color:#7b98b8;background:#e9f0f8;color:#2e4e70}.status-queued,.status-translating,.status-ready,.status-create,.status-insert,.status-update{border-color:#c4a34e;background:#f7efcf;color:#604e16}.status-rejected,.status-failed,.status-delivery_failed,.status-response_ready_missing_result,.status-cancelled,.status-blocked{border-color:#c46e5f;background:#fae9e6;color:#6f2b21}.status-pending{border-color:#c7c1b8;background:#f2eee7;color:#4f5558}.status-translated,.status-preserved{border-color:#7b98b8;background:#e9f0f8;color:#2e4e70}.empty-workspace{max-width:680px}@media(max-width:980px){.app-shell,.workspace-grid{grid-template-columns:1fr}.app-sidebar{border-right:0;border-bottom:1px solid #d3c9b8}.role-tabs{grid-template-columns:repeat(2,minmax(0,1fr))}.session-console-body.has-translation,.translation-settings-grid{grid-template-columns:1fr}}@media(max-width:560px){.app-main,.app-sidebar{padding:12px}.workspace-header,.inline-form,.inline-form.has-recent-paths{grid-template-columns:1fr;display:grid}.role-tabs{grid-template-columns:1fr}.terminal-frame,.terminal-empty{min-height:300px;height:54vh}.permission-mode-field{grid-template-columns:1fr;width:100%}}:root[data-theme=dark]{color-scheme:dark;background:#0d1117;color:#e6edf3}:root[data-theme=dark] body,:root[data-theme=dark] .app-main{background:#0d1117;color:#e6edf3}:root[data-theme=dark] button{border-color:#3d4652;background:#161b22;color:#e6edf3}:root[data-theme=dark] button:hover:not(:disabled){border-color:#58a6ff;background:#1f2937}:root[data-theme=dark] input,:root[data-theme=dark] select,:root[data-theme=dark] textarea:not(.xterm-helper-textarea){border-color:#3d4652;background:#0d1117;color:#e6edf3}:root[data-theme=dark] input::placeholder,:root[data-theme=dark] textarea::placeholder{color:#7d8590}:root[data-theme=dark] .danger-button{border-color:#da7b72;background:#2d1518;color:#ffdcd7}:root[data-theme=dark] .danger-button:hover:not(:disabled){border-color:#ffa198;background:#3d1f23}:root[data-theme=dark] .app-sidebar{border-color:#30363d;background:#0f141b}:root[data-theme=dark] .sidebar-toggle{background:#161b22}:root[data-theme=dark] .brand-header span,:root[data-theme=dark] .muted,:root[data-theme=dark] .message-meta time,:root[data-theme=dark] .message-meta span:not(.status-badge),:root[data-theme=dark] .message-reason,:root[data-theme=dark] .message-path{color:#8b949e}:root[data-theme=dark] .sidebar-section,:root[data-theme=dark] .session-console,:root[data-theme=dark] .message-panel,:root[data-theme=dark] .event-log,:root[data-theme=dark] .empty-workspace,:root[data-theme=dark] .translation-settings-modal,:root[data-theme=dark] .message-modal,:root[data-theme=dark] .event-modal{border-color:#30363d;background:#11161d}:root[data-theme=dark] .sidebar-section-toggle{color:#e6edf3}:root[data-theme=dark] .sidebar-section-toggle:hover{background:#161b22}:root[data-theme=dark] .sidebar-section-toggle[aria-expanded=true],:root[data-theme=dark] .translation-prompt-settings{border-color:#30363d}:root[data-theme=dark] .project-summary dt,:root[data-theme=dark] .permission-mode-field span,:root[data-theme=dark] .translation-settings-grid span,:root[data-theme=dark] .translation-prompt-stack span,:root[data-theme=dark] .message-mode-toggle{color:#b7c0ca}:root[data-theme=dark] .permission-mode-field small,:root[data-theme=dark] .task-create-preview small{color:#8b949e}:root[data-theme=dark] .warnings,:root[data-theme=dark] .error-banner{border-color:#da7b72;background:#2d1518;color:#ffdcd7}:root[data-theme=dark] .round-notice{border-color:#56d4dd;background:#10262b;color:#d6fbff;box-shadow:0 14px 32px #0104097a}:root[data-theme=dark] .harness-file-list li,:root[data-theme=dark] .harness-changes,:root[data-theme=dark] .harness-result,:root[data-theme=dark] .task-create-preview,:root[data-theme=dark] .message-item{border-color:#30363d;background:#0d1117}:root[data-theme=dark] .task-create-option{color:#e6edf3}:root[data-theme=dark] .task-create-preview span{color:#7ee787}:root[data-theme=dark] .message-sequence{color:#f0f6fc}:root[data-theme=dark] .task-nav-item.is-active,:root[data-theme=dark] .role-tab.is-active,:root[data-theme=dark] .sidebar-settings .settings-toggle.is-active,:root[data-theme=dark] .translation-toggle.is-active{border-color:#56d4dd;background:#10262b;color:#d6fbff}:root[data-theme=dark] .translation-toggle{border-color:#3d4652;background:#161b22;color:#b7c0ca}:root[data-theme=dark] .permission-mode-field select,:root[data-theme=dark] .translation-settings-grid select,:root[data-theme=dark] .translation-prompt-settings select{border-color:#3d4652;background:#0d1117;color:#e6edf3}:root[data-theme=dark] .terminal-empty{border-color:#30363d}:root[data-theme=dark] .modal-backdrop{background:#010409b8}:root[data-theme=dark] .message-modal .message-panel,:root[data-theme=dark] .event-modal .event-log{background:transparent}:root[data-theme=dark] .status-badge{border-color:#3d4652;background:#161b22;color:#d6deeb}:root[data-theme=dark] .translation-test-result.is-ok,:root[data-theme=dark] .status-running,:root[data-theme=dark] .status-ok{border-color:#3fb950;background:#12261a;color:#aff5b4}:root[data-theme=dark] .status-idle{border-color:#69717d;background:#20262f;color:#d6deeb}:root[data-theme=dark] .translation-test-result.is-error,:root[data-theme=dark] .status-blocked,:root[data-theme=dark] .status-crashed,:root[data-theme=dark] .status-missing,:root[data-theme=dark] .status-empty,:root[data-theme=dark] .status-rejected,:root[data-theme=dark] .status-failed,:root[data-theme=dark] .status-delivery_failed,:root[data-theme=dark] .status-response_ready_missing_result,:root[data-theme=dark] .status-cancelled{border-color:#f85149;background:#2d1518;color:#ffdcd7}:root[data-theme=dark] .status-waiting,:root[data-theme=dark] .status-starting,:root[data-theme=dark] .status-incomplete,:root[data-theme=dark] .status-queued,:root[data-theme=dark] .status-translating,:root[data-theme=dark] .status-ready,:root[data-theme=dark] .status-create,:root[data-theme=dark] .status-insert,:root[data-theme=dark] .status-update{border-color:#d29922;background:#2d2208;color:#f8e3a1}:root[data-theme=dark] .status-exited,:root[data-theme=dark] .status-done,:root[data-theme=dark] .status-resumable,:root[data-theme=dark] .status-translated,:root[data-theme=dark] .status-preserved{border-color:#388bfd;background:#10223a;color:#c9e2ff}:root[data-theme=dark] .status-pending{border-color:#3d4652;background:#161b22;color:#d6deeb}
|