vibe-coding-master 0.0.16 → 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.
Files changed (47) hide show
  1. package/README.md +74 -41
  2. package/dist/backend/api/artifact-routes.js +5 -5
  3. package/dist/backend/api/harness-routes.js +8 -0
  4. package/dist/backend/api/message-routes.js +4 -4
  5. package/dist/backend/api/round-routes.js +4 -2
  6. package/dist/backend/server.js +8 -2
  7. package/dist/backend/services/artifact-service.js +12 -12
  8. package/dist/backend/services/claude-hook-service.js +1 -1
  9. package/dist/backend/services/harness-service.js +579 -5
  10. package/dist/backend/services/message-service.js +71 -137
  11. package/dist/backend/services/project-service.js +4 -1
  12. package/dist/backend/services/round-service.js +14 -52
  13. package/dist/backend/services/session-service.js +1 -3
  14. package/dist/backend/services/task-service.js +16 -17
  15. package/dist/backend/templates/handoff.js +64 -26
  16. package/dist/backend/templates/harness/architect-agent.js +42 -12
  17. package/dist/backend/templates/harness/claude-root.js +42 -18
  18. package/dist/backend/templates/harness/coder-agent.js +15 -11
  19. package/dist/backend/templates/harness/known-issues-doc.js +22 -0
  20. package/dist/backend/templates/harness/project-manager-agent.js +66 -15
  21. package/dist/backend/templates/harness/pull-request-template.js +29 -0
  22. package/dist/backend/templates/harness/reviewer-agent.js +40 -12
  23. package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +105 -0
  24. package/dist/backend/templates/harness/vcm-harness-bootstrap-skill.js +78 -0
  25. package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +50 -0
  26. package/dist/backend/templates/harness/vcm-route-message-skill.js +86 -0
  27. package/dist/backend/templates/message-envelope.js +1 -0
  28. package/dist/backend/templates/role-command.js +7 -1
  29. package/dist/shared/validation/artifact-check.js +14 -9
  30. package/dist-frontend/assets/index-CrY5Ryps.js +90 -0
  31. package/dist-frontend/assets/index-CvvtrrCN.css +32 -0
  32. package/dist-frontend/index.html +2 -2
  33. package/docs/cc-best-practices.md +434 -192
  34. package/docs/full-harness-baseline.md +254 -0
  35. package/docs/product-design.md +31 -28
  36. package/docs/v0.2-implementation-plan.md +379 -0
  37. package/docs/vcm-cc-best-practices.md +449 -0
  38. package/package.json +3 -1
  39. package/scripts/harness-tools/generate-module-index +298 -0
  40. package/scripts/harness-tools/generate-public-surface +692 -0
  41. package/scripts/install-vcm-harness.mjs +1607 -0
  42. package/scripts/uninstall-vcm-harness.mjs +490 -0
  43. package/scripts/verify-package.mjs +4 -0
  44. package/dist-frontend/assets/index-CvtyKEfS.js +0 -89
  45. package/dist-frontend/assets/index-jEkUTnIY.css +0 -32
  46. package/docs/v1-architecture-design.md +0 -1009
  47. package/docs/v1-implementation-plan.md +0 -1376
@@ -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",