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.
@@ -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-CMuJhDdX.js"></script>
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` currently target Rust
105
- projects. Non-Rust repositories can still install the fixed harness, but
106
- generated context should be treated as unsupported until project-specific
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`:
@@ -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 crate-external Rust public APIs. It is a machine
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 is Rust-only. The fixed installer provides
370
- Rust generator tools as the default baseline, but it does not generate trusted
371
- context by itself. Non-Rust projects must use project-specific generators before
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 launch bootstrap as a visible temporary Claude Code terminal, not as
406
- an invisible background task:
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 Claude Code in the connected repository root without a role agent
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
- - log terminal output under `.ai/vcm/bootstrap/bootstrap.log`
414
- - persist session metadata under `.ai/vcm/bootstrap/session.json`
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 bootstrap terminal should be restartable
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [
@@ -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 find_cargo_root(project_root: Path) -> Path:
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
- raise SystemExit(f"Could not find Cargo.toml in {project_root} or its direct child directories.")
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 build_index(project_root: Path) -> dict:
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 cargo metadata.")
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.")