vibe-coding-master 0.4.0 → 0.4.2
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 +3 -3
- package/dist/backend/cli/install-vcm-harness.js +1 -1
- package/dist/backend/server.js +8 -8
- package/dist/backend/services/claude-hook-service.js +5 -0
- package/dist/backend/services/harness-service.js +119 -174
- package/dist/backend/services/message-service.js +0 -32
- package/dist/backend/templates/harness/architect-agent.js +1 -1
- package/dist/backend/templates/harness/claude-root.js +1 -1
- package/dist/backend/templates/harness/coder-agent.js +1 -1
- package/dist/backend/templates/harness/vcm-harness-bootstrap-skill.js +1 -1
- package/dist-frontend/assets/{index-CMuJhDdX.js → index-uYhgIwNK.js} +22 -22
- package/dist-frontend/index.html +1 -1
- package/docs/full-harness-baseline.md +5 -5
- package/docs/product-design.md +1 -1
- package/docs/vcm-cc-best-practices.md +12 -18
- package/package.json +1 -1
- package/scripts/harness-tools/__pycache__/generate-module-indexcpython-314.pyc +0 -0
- package/scripts/harness-tools/__pycache__/generate-public-surfacecpython-314.pyc +0 -0
- package/scripts/harness-tools/generate-module-index +225 -5
- package/scripts/harness-tools/generate-public-surface +388 -3
package/dist-frontend/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>VibeCodingMaster</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-uYhgIwNK.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-kJDZAzjD.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -101,10 +101,10 @@ Directory roots created by the installer:
|
|
|
101
101
|
.ai/tools/vcm-bash-guard
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
`generate-module-index` and `generate-public-surface`
|
|
105
|
-
projects
|
|
106
|
-
|
|
107
|
-
generators exist.
|
|
104
|
+
`generate-module-index` and `generate-public-surface` support Rust/Cargo
|
|
105
|
+
projects and npm workspace TypeScript/JavaScript projects. Other repository
|
|
106
|
+
shapes can still install the fixed harness, but generated context should be
|
|
107
|
+
treated as unsupported until project-specific generators exist.
|
|
108
108
|
|
|
109
109
|
## Runtime State
|
|
110
110
|
|
|
@@ -130,9 +130,9 @@ Project-scoped runtime state lives in the base repository:
|
|
|
130
130
|
|
|
131
131
|
```text
|
|
132
132
|
<baseRepoRoot>/.ai/vcm/gate-reviewer/session.json
|
|
133
|
+
<baseRepoRoot>/.ai/vcm/harness-engineer/session.json
|
|
133
134
|
<baseRepoRoot>/.ai/vcm/translations/
|
|
134
135
|
<baseRepoRoot>/.ai/vcm/bootstrap/session.json
|
|
135
|
-
<baseRepoRoot>/.ai/vcm/bootstrap/bootstrap.log
|
|
136
136
|
```
|
|
137
137
|
|
|
138
138
|
App-local state lives under `vcmDataDir`:
|
package/docs/product-design.md
CHANGED
|
@@ -825,9 +825,9 @@ Project-scoped local files:
|
|
|
825
825
|
|
|
826
826
|
```text
|
|
827
827
|
.ai/vcm/gate-reviewer/session.json
|
|
828
|
+
.ai/vcm/harness-engineer/session.json
|
|
828
829
|
.ai/vcm/translations/
|
|
829
830
|
.ai/vcm/bootstrap/session.json
|
|
830
|
-
.ai/vcm/bootstrap/bootstrap.log
|
|
831
831
|
```
|
|
832
832
|
|
|
833
833
|
External Claude transcripts:
|
|
@@ -171,8 +171,8 @@ Current runtime files and directories:
|
|
|
171
171
|
.ai/vcm/handoffs/known-issues.md
|
|
172
172
|
.ai/vcm/gate-reviews/
|
|
173
173
|
.ai/vcm/jobs/<job-id>/
|
|
174
|
+
.ai/vcm/harness-engineer/session.json
|
|
174
175
|
.ai/vcm/bootstrap/session.json
|
|
175
|
-
.ai/vcm/bootstrap/bootstrap.log
|
|
176
176
|
```
|
|
177
177
|
|
|
178
178
|
App-local VCM task records live outside the connected repository:
|
|
@@ -363,13 +363,12 @@ The current example has two generated artifacts:
|
|
|
363
363
|
`module-index.json` helps agents find layers, modules, manifests, module docs,
|
|
364
364
|
source files, test files, and workspace dependencies.
|
|
365
365
|
|
|
366
|
-
`public-surface.json` indexes
|
|
367
|
-
index, not an architecture document.
|
|
366
|
+
`public-surface.json` indexes project public APIs, routes, and externally
|
|
367
|
+
consumed surfaces. It is a machine index, not an architecture document.
|
|
368
368
|
|
|
369
|
-
Current generated-context support
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
`.ai/generated/*` is considered reliable.
|
|
369
|
+
Current generated-context support covers Rust/Cargo projects and npm workspace
|
|
370
|
+
TypeScript/JavaScript projects. Other repository shapes must use
|
|
371
|
+
project-specific generators before `.ai/generated/*` is considered reliable.
|
|
373
372
|
|
|
374
373
|
There is no `test-map.json`. Rust unit tests live with source where appropriate;
|
|
375
374
|
integration tests use Cargo's normal test layout. Test files are discoverable
|
|
@@ -402,22 +401,17 @@ during bootstrap.
|
|
|
402
401
|
Important claims should be marked as verified, inferred, unknown, or needing
|
|
403
402
|
human confirmation.
|
|
404
403
|
|
|
405
|
-
VCM should
|
|
406
|
-
|
|
404
|
+
VCM should run bootstrap through the project-scoped `harness-engineer` session,
|
|
405
|
+
not through a separate temporary terminal or invisible background task:
|
|
407
406
|
|
|
408
407
|
- run the deterministic fixed installer first
|
|
409
|
-
- start
|
|
410
|
-
- set `VCM_TASK_REPO_ROOT`, `VCM_HARNESS_BOOTSTRAP=1`, `VCM_SESSION_ID`, and
|
|
411
|
-
`VCM_API_URL`
|
|
408
|
+
- start or resume the `harness-engineer` role in the connected repository root
|
|
412
409
|
- send a prompt that explicitly requires using `vcm-harness-bootstrap`
|
|
413
|
-
-
|
|
414
|
-
-
|
|
415
|
-
- mark bootstrap complete only when project context, generated context,
|
|
416
|
-
project architecture docs, module architecture docs, and testing docs are
|
|
417
|
-
present and non-empty
|
|
410
|
+
- persist the bootstrap run marker under `.ai/vcm/bootstrap/session.json`
|
|
411
|
+
- mark the bootstrap run complete when the `harness-engineer` Stop hook arrives
|
|
418
412
|
|
|
419
413
|
The UI should expose both stages: fixed install status and bootstrap completion
|
|
420
|
-
status. A failed or disconnected
|
|
414
|
+
status. A failed or disconnected Harness Engineer session should be restartable
|
|
421
415
|
without treating project-owned durable docs as VCM-owned harness files.
|
|
422
416
|
|
|
423
417
|
## 12. Final Acceptance
|
package/package.json
CHANGED
|
@@ -12,6 +12,12 @@ except ModuleNotFoundError:
|
|
|
12
12
|
tomllib = None
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
NODE_SOURCE_EXTENSIONS = (".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs")
|
|
16
|
+
NODE_EXCLUDED_DIRS = {"node_modules", "dist", "build", "coverage", ".next", ".turbo", ".vite"}
|
|
17
|
+
NODE_DEPENDENCY_TABLES = ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies")
|
|
18
|
+
NODE_TEST_MARKERS = (".test.", ".spec.")
|
|
19
|
+
|
|
20
|
+
|
|
15
21
|
def relative_path(path: Path, root: Path) -> str:
|
|
16
22
|
try:
|
|
17
23
|
rel = path.absolute().relative_to(root.absolute())
|
|
@@ -20,7 +26,7 @@ def relative_path(path: Path, root: Path) -> str:
|
|
|
20
26
|
return "." if str(rel) == "." else rel.as_posix()
|
|
21
27
|
|
|
22
28
|
|
|
23
|
-
def
|
|
29
|
+
def maybe_find_cargo_root(project_root: Path) -> Path | None:
|
|
24
30
|
if (project_root / "Cargo.toml").is_file():
|
|
25
31
|
return project_root
|
|
26
32
|
|
|
@@ -28,7 +34,18 @@ def find_cargo_root(project_root: Path) -> Path:
|
|
|
28
34
|
if child.is_dir() and (child / "Cargo.toml").is_file():
|
|
29
35
|
return child
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def find_cargo_root(project_root: Path) -> Path:
|
|
41
|
+
cargo_root = maybe_find_cargo_root(project_root)
|
|
42
|
+
if cargo_root is None:
|
|
43
|
+
raise SystemExit(f"Could not find Cargo.toml in {project_root} or its direct child directories.")
|
|
44
|
+
return cargo_root
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def load_json(path: Path) -> dict:
|
|
48
|
+
return json.loads(path.read_text())
|
|
32
49
|
|
|
33
50
|
|
|
34
51
|
def load_toml(path: Path) -> dict:
|
|
@@ -149,6 +166,196 @@ def rust_files_under(module_dir: Path, project_root: Path, child: str) -> list[s
|
|
|
149
166
|
)
|
|
150
167
|
|
|
151
168
|
|
|
169
|
+
def maybe_find_node_root(project_root: Path) -> Path | None:
|
|
170
|
+
if (project_root / "package.json").is_file():
|
|
171
|
+
return project_root
|
|
172
|
+
|
|
173
|
+
for child in sorted(project_root.iterdir(), key=lambda path: path.name):
|
|
174
|
+
if child.is_dir() and (child / "package.json").is_file():
|
|
175
|
+
return child
|
|
176
|
+
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def workspace_patterns(package_data: dict) -> list[str]:
|
|
181
|
+
workspaces = package_data.get("workspaces")
|
|
182
|
+
if isinstance(workspaces, list):
|
|
183
|
+
return [pattern for pattern in workspaces if isinstance(pattern, str)]
|
|
184
|
+
if isinstance(workspaces, dict):
|
|
185
|
+
packages = workspaces.get("packages", [])
|
|
186
|
+
if isinstance(packages, list):
|
|
187
|
+
return [pattern for pattern in packages if isinstance(pattern, str)]
|
|
188
|
+
return []
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def node_workspace_manifests(node_root: Path) -> list[Path]:
|
|
192
|
+
root_manifest = node_root / "package.json"
|
|
193
|
+
root_data = load_json(root_manifest)
|
|
194
|
+
patterns = workspace_patterns(root_data)
|
|
195
|
+
|
|
196
|
+
if not patterns:
|
|
197
|
+
return [root_manifest]
|
|
198
|
+
|
|
199
|
+
manifests: list[Path] = []
|
|
200
|
+
seen: set[Path] = set()
|
|
201
|
+
|
|
202
|
+
for pattern in patterns:
|
|
203
|
+
if pattern.startswith("!") or pattern.startswith("/") or ".." in Path(pattern).parts:
|
|
204
|
+
continue
|
|
205
|
+
for candidate in sorted(node_root.glob(pattern), key=lambda path: path.as_posix()):
|
|
206
|
+
manifest = candidate / "package.json"
|
|
207
|
+
if not candidate.is_dir() or not manifest.is_file() or manifest in seen:
|
|
208
|
+
continue
|
|
209
|
+
if any(part in NODE_EXCLUDED_DIRS for part in relative_path(candidate, node_root).split("/")):
|
|
210
|
+
continue
|
|
211
|
+
manifests.append(manifest)
|
|
212
|
+
seen.add(manifest)
|
|
213
|
+
|
|
214
|
+
if root_data.get("name") and (root_data.get("main") or root_data.get("exports") or (node_root / "src").is_dir()):
|
|
215
|
+
manifests.insert(0, root_manifest)
|
|
216
|
+
|
|
217
|
+
return manifests
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def node_dependency_names(manifest: Path) -> set[str]:
|
|
221
|
+
data = load_json(manifest)
|
|
222
|
+
names: set[str] = set()
|
|
223
|
+
for table_name in NODE_DEPENDENCY_TABLES:
|
|
224
|
+
table = data.get(table_name, {})
|
|
225
|
+
if isinstance(table, dict):
|
|
226
|
+
names.update(name for name in table.keys() if isinstance(name, str))
|
|
227
|
+
return names
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def node_source_files_under(module_dir: Path, project_root: Path) -> tuple[list[str], list[str]]:
|
|
231
|
+
source_root = module_dir / "src"
|
|
232
|
+
if not source_root.is_dir():
|
|
233
|
+
return ([], [])
|
|
234
|
+
|
|
235
|
+
source_files: list[str] = []
|
|
236
|
+
test_files: list[str] = []
|
|
237
|
+
|
|
238
|
+
for path in sorted(source_root.rglob("*"), key=lambda item: item.as_posix()):
|
|
239
|
+
if not path.is_file():
|
|
240
|
+
continue
|
|
241
|
+
if path.suffix not in NODE_SOURCE_EXTENSIONS or path.name.endswith(".d.ts"):
|
|
242
|
+
continue
|
|
243
|
+
if any(part in NODE_EXCLUDED_DIRS for part in path.relative_to(module_dir).parts):
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
rel = relative_path(path, project_root)
|
|
247
|
+
if any(marker in path.name for marker in NODE_TEST_MARKERS):
|
|
248
|
+
test_files.append(rel)
|
|
249
|
+
else:
|
|
250
|
+
source_files.append(rel)
|
|
251
|
+
|
|
252
|
+
tests_root = module_dir / "tests"
|
|
253
|
+
if tests_root.is_dir():
|
|
254
|
+
for path in sorted(tests_root.rglob("*"), key=lambda item: item.as_posix()):
|
|
255
|
+
if (
|
|
256
|
+
path.is_file()
|
|
257
|
+
and path.suffix in NODE_SOURCE_EXTENSIONS
|
|
258
|
+
and not path.name.endswith(".d.ts")
|
|
259
|
+
and not any(part in NODE_EXCLUDED_DIRS for part in path.relative_to(module_dir).parts)
|
|
260
|
+
):
|
|
261
|
+
test_files.append(relative_path(path, project_root))
|
|
262
|
+
|
|
263
|
+
return (source_files, sorted(set(test_files)))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def infer_node_layer(module_path: str, node_root: Path, project_root: Path) -> tuple[str, str]:
|
|
267
|
+
node_root_rel = relative_path(node_root, project_root)
|
|
268
|
+
if module_path == node_root_rel:
|
|
269
|
+
return ("root", node_root_rel)
|
|
270
|
+
|
|
271
|
+
relative_to_node_root = relative_path(project_root / module_path, node_root)
|
|
272
|
+
first = relative_to_node_root.split("/", 1)[0]
|
|
273
|
+
return (first, relative_path(node_root / first, project_root))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def node_package_records(node_root: Path) -> list[dict]:
|
|
277
|
+
manifests = node_workspace_manifests(node_root)
|
|
278
|
+
records: list[dict] = []
|
|
279
|
+
names_by_manifest: dict[Path, str] = {}
|
|
280
|
+
|
|
281
|
+
for manifest in manifests:
|
|
282
|
+
data = load_json(manifest)
|
|
283
|
+
name = data.get("name")
|
|
284
|
+
if isinstance(name, str) and name:
|
|
285
|
+
names_by_manifest[manifest] = name
|
|
286
|
+
|
|
287
|
+
workspace_names = set(names_by_manifest.values())
|
|
288
|
+
|
|
289
|
+
for manifest, name in names_by_manifest.items():
|
|
290
|
+
dependency_names = node_dependency_names(manifest) & workspace_names
|
|
291
|
+
records.append(
|
|
292
|
+
{
|
|
293
|
+
"name": name,
|
|
294
|
+
"manifest": manifest,
|
|
295
|
+
"workspaceDependencies": sorted(dependency_names),
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
return records
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def build_node_index(project_root: Path, node_root: Path) -> dict:
|
|
303
|
+
records = node_package_records(node_root)
|
|
304
|
+
has_workspaces = bool(workspace_patterns(load_json(node_root / "package.json")))
|
|
305
|
+
layers: list[dict] = []
|
|
306
|
+
layer_by_name: dict[str, dict] = {}
|
|
307
|
+
|
|
308
|
+
for record in records:
|
|
309
|
+
manifest_path = record["manifest"]
|
|
310
|
+
module_dir = manifest_path.parent
|
|
311
|
+
module_path = relative_path(module_dir, project_root)
|
|
312
|
+
manifest_rel = relative_path(manifest_path, project_root)
|
|
313
|
+
layer_name, layer_path = infer_node_layer(module_path, node_root, project_root)
|
|
314
|
+
source_files, test_files = node_source_files_under(module_dir, project_root)
|
|
315
|
+
|
|
316
|
+
if layer_name not in layer_by_name:
|
|
317
|
+
layer = {
|
|
318
|
+
"name": layer_name,
|
|
319
|
+
"path": layer_path,
|
|
320
|
+
"modules": [],
|
|
321
|
+
}
|
|
322
|
+
layer_by_name[layer_name] = layer
|
|
323
|
+
layers.append(layer)
|
|
324
|
+
|
|
325
|
+
layer_by_name[layer_name]["modules"].append(
|
|
326
|
+
{
|
|
327
|
+
"name": record["name"],
|
|
328
|
+
"path": module_path,
|
|
329
|
+
"manifest": manifest_rel,
|
|
330
|
+
"architectureDoc": f"{module_path}/ARCHITECTURE.md"
|
|
331
|
+
if module_path != "."
|
|
332
|
+
else "ARCHITECTURE.md",
|
|
333
|
+
"workspaceDependencies": record["workspaceDependencies"],
|
|
334
|
+
"language": "typescript",
|
|
335
|
+
"files": {
|
|
336
|
+
"source": source_files,
|
|
337
|
+
"tests": test_files,
|
|
338
|
+
},
|
|
339
|
+
}
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
"schemaVersion": 1,
|
|
344
|
+
"kind": "module-index",
|
|
345
|
+
"generatedBy": ".ai/tools/generate-module-index",
|
|
346
|
+
"workspace": {
|
|
347
|
+
"type": "npm-workspaces" if has_workspaces else "npm-package",
|
|
348
|
+
"root": relative_path(node_root, project_root),
|
|
349
|
+
"manifest": relative_path(node_root / "package.json", project_root),
|
|
350
|
+
},
|
|
351
|
+
"layerInference": {
|
|
352
|
+
"method": "path-first-segment-from-workspace-root",
|
|
353
|
+
"rootPackageLayer": "root",
|
|
354
|
+
},
|
|
355
|
+
"layers": layers,
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
152
359
|
def package_records_from_metadata(project_root: Path, cargo_root: Path, metadata: dict) -> list[dict]:
|
|
153
360
|
workspace_root = Path(metadata["workspace_root"])
|
|
154
361
|
workspace_member_ids = set(metadata["workspace_members"])
|
|
@@ -201,8 +408,7 @@ def package_records_from_manifests(cargo_root: Path) -> list[dict]:
|
|
|
201
408
|
return records
|
|
202
409
|
|
|
203
410
|
|
|
204
|
-
def
|
|
205
|
-
cargo_root = find_cargo_root(project_root)
|
|
411
|
+
def build_cargo_index(project_root: Path, cargo_root: Path) -> dict:
|
|
206
412
|
metadata = load_cargo_metadata(cargo_root)
|
|
207
413
|
records = (
|
|
208
414
|
package_records_from_metadata(project_root, cargo_root, metadata)
|
|
@@ -262,8 +468,22 @@ def build_index(project_root: Path) -> dict:
|
|
|
262
468
|
}
|
|
263
469
|
|
|
264
470
|
|
|
471
|
+
def build_index(project_root: Path) -> dict:
|
|
472
|
+
cargo_root = maybe_find_cargo_root(project_root)
|
|
473
|
+
if cargo_root is not None:
|
|
474
|
+
return build_cargo_index(project_root, cargo_root)
|
|
475
|
+
|
|
476
|
+
node_root = maybe_find_node_root(project_root)
|
|
477
|
+
if node_root is not None:
|
|
478
|
+
return build_node_index(project_root, node_root)
|
|
479
|
+
|
|
480
|
+
raise SystemExit(
|
|
481
|
+
f"Could not find Cargo.toml or package.json in {project_root} or its direct child directories."
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
|
|
265
485
|
def main() -> int:
|
|
266
|
-
parser = argparse.ArgumentParser(description="Generate .ai/generated/module-index.json from
|
|
486
|
+
parser = argparse.ArgumentParser(description="Generate .ai/generated/module-index.json from Cargo or npm workspace metadata.")
|
|
267
487
|
parser.add_argument("--check", action="store_true", help="Fail if the generated module index differs from the current file.")
|
|
268
488
|
parser.add_argument("--print", action="store_true", help="Print generated JSON instead of writing it.")
|
|
269
489
|
parser.add_argument("--output", default=".ai/generated/module-index.json", help="Output path relative to the project root.")
|