vibe-coding-master 0.4.10 → 0.4.12

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.
@@ -32,7 +32,7 @@ export function registerTaskRoutes(app, deps) {
32
32
  const project = await requireCurrentProject(deps.projectService);
33
33
  const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
34
34
  await stopRunningRoleSessions(deps, project.repoRoot, request.params.taskSlug);
35
- await stopProjectToolSessions(deps, project.repoRoot);
35
+ await moveProjectToolSessionsToSafeCwd(deps, project.repoRoot);
36
36
  await deps.translationService.stopTask(getTaskRuntimeRepoRoot(task), request.params.taskSlug, { clearCache: true });
37
37
  deps.roundService.stopTask(request.params.taskSlug);
38
38
  return deps.taskService.cleanupTask(project.repoRoot, request.params.taskSlug, request.body ?? {});
@@ -46,10 +46,10 @@ async function stopRunningRoleSessions(deps, repoRoot, taskSlug) {
46
46
  }
47
47
  }
48
48
  }
49
- async function stopProjectToolSessions(deps, repoRoot) {
49
+ async function moveProjectToolSessionsToSafeCwd(deps, repoRoot) {
50
50
  await Promise.all([
51
- ignoreMissingSession(deps.sessionService.stopProjectTranslatorSession(repoRoot)),
52
- ignoreMissingSession(deps.sessionService.stopProjectHarnessEngineerSession(repoRoot))
51
+ ignoreMissingSession(deps.sessionService.moveProjectTranslatorSessionToSafeCwd(repoRoot)),
52
+ ignoreMissingSession(deps.sessionService.moveProjectHarnessEngineerSessionToSafeCwd(repoRoot))
53
53
  ]);
54
54
  }
55
55
  async function ignoreMissingSession(operation) {
@@ -526,7 +526,7 @@ export function createGatewayService(deps) {
526
526
  }
527
527
  const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
528
528
  await stopRunningRoleSessions(project.repoRoot, taskSlug);
529
- await stopProjectToolSessions(project.repoRoot);
529
+ await moveProjectToolSessionsToSafeCwd(project.repoRoot);
530
530
  await deps.translationService.stopTask(getTaskRuntimeRepoRoot(task), taskSlug, { clearCache: true });
531
531
  deps.roundService.stopTask(taskSlug);
532
532
  const result = await deps.taskService.cleanupTask(project.repoRoot, taskSlug, {
@@ -561,10 +561,10 @@ export function createGatewayService(deps) {
561
561
  }
562
562
  }
563
563
  }
564
- async function stopProjectToolSessions(repoRoot) {
564
+ async function moveProjectToolSessionsToSafeCwd(repoRoot) {
565
565
  await Promise.all([
566
- ignoreMissingSession(deps.sessionService.stopProjectTranslatorSession(repoRoot)),
567
- ignoreMissingSession(deps.sessionService.stopProjectHarnessEngineerSession(repoRoot))
566
+ ignoreMissingSession(deps.sessionService.moveProjectTranslatorSessionToSafeCwd(repoRoot)),
567
+ ignoreMissingSession(deps.sessionService.moveProjectHarnessEngineerSessionToSafeCwd(repoRoot))
568
568
  ]);
569
569
  }
570
570
  async function ignoreMissingSession(operation) {
@@ -62,7 +62,7 @@ export function createClaudeHookService(deps) {
62
62
  eventName,
63
63
  sessionId: stringOrUndefined(input.event.session_id),
64
64
  transcriptPath: stringOrUndefined(input.event.transcript_path),
65
- cwd: stringOrUndefined(input.event.cwd)
65
+ cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
66
66
  });
67
67
  await deps.translationWorkerService?.handleTranslatorHook(context.project.repoRoot, eventName, input.taskSlug);
68
68
  return {
@@ -81,7 +81,7 @@ export function createClaudeHookService(deps) {
81
81
  eventName,
82
82
  sessionId: stringOrUndefined(input.event.session_id),
83
83
  transcriptPath: stringOrUndefined(input.event.transcript_path),
84
- cwd: stringOrUndefined(input.event.cwd)
84
+ cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
85
85
  });
86
86
  await deps.harnessService?.recordHarnessBootstrapHook(context.project.repoRoot, {
87
87
  eventName,
@@ -129,7 +129,7 @@ export function createClaudeHookService(deps) {
129
129
  eventName,
130
130
  claudeSessionId: stringOrUndefined(input.event.session_id),
131
131
  transcriptPath: stringOrUndefined(input.event.transcript_path),
132
- cwd: stringOrUndefined(input.event.cwd)
132
+ cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
133
133
  });
134
134
  await deps.roundService.recordClaudeHookEvent({
135
135
  repoRoot: context.project.repoRoot,
@@ -250,7 +250,7 @@ export function createClaudeHookService(deps) {
250
250
  eventName,
251
251
  claudeSessionId: stringOrUndefined(input.event.session_id),
252
252
  transcriptPath: stringOrUndefined(input.event.transcript_path),
253
- cwd: stringOrUndefined(input.event.cwd)
253
+ cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
254
254
  });
255
255
  return {
256
256
  ok: true,
@@ -270,7 +270,7 @@ export function createClaudeHookService(deps) {
270
270
  eventName,
271
271
  claudeSessionId: stringOrUndefined(input.event.session_id),
272
272
  transcriptPath: stringOrUndefined(input.event.transcript_path),
273
- cwd: stringOrUndefined(input.event.cwd)
273
+ cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
274
274
  });
275
275
  await deps.roundService.recordClaudeHookEvent({
276
276
  repoRoot: context.project.repoRoot,
@@ -448,7 +448,10 @@ export function createClaudeHookService(deps) {
448
448
  };
449
449
  }
450
450
  function parseHookEvent(value) {
451
- if (value === "UserPromptSubmit" || value === "Stop" || value === "StopFailure" || value === "PostCompact") {
451
+ if (value === "UserPromptSubmit"
452
+ || value === "Stop"
453
+ || value === "StopFailure"
454
+ || value === "PostCompact") {
452
455
  return value;
453
456
  }
454
457
  throw new VcmError({
@@ -209,7 +209,7 @@ export function createSessionService(deps) {
209
209
  const taskContext = await resolveProjectToolTaskContext(repoRoot, input, "Translator");
210
210
  const live = toRoleSessionRecordView(getRegisteredProjectTranslatorSession(deps.registry, deps.runtime), deps.runtime);
211
211
  if (live && live.status === "running") {
212
- return withHarnessRevisionView(repoRoot, live);
212
+ return withHarnessRevisionView(repoRoot, await migrateRunningProjectToolSessionCwd(repoRoot, live, taskContext.taskRepoRoot));
213
213
  }
214
214
  const config = await deps.projectService.loadConfig(repoRoot);
215
215
  const persisted = await loadPersistedTranslatorSession(deps.fs, repoRoot);
@@ -228,10 +228,15 @@ export function createSessionService(deps) {
228
228
  });
229
229
  }
230
230
  await deps.fs.ensureDir(resolveRepoPath(repoRoot, TRANSLATION_DIR));
231
- const transcriptPath = claudeTranscriptPath(taskContext.taskRepoRoot, claudeSessionId);
231
+ const launchCwd = launchMode === "resume"
232
+ ? persisted?.cwd ?? taskContext.taskRepoRoot
233
+ : taskContext.taskRepoRoot;
234
+ const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
235
+ ? persisted.transcriptPath
236
+ : claudeTranscriptPath(launchCwd, claudeSessionId);
232
237
  const startCommand = {
233
238
  ...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
234
- cwd: taskContext.taskRepoRoot
239
+ cwd: launchCwd
235
240
  };
236
241
  const runtimeSession = await deps.runtime.createSession({
237
242
  taskSlug: PROJECT_TRANSLATOR_SCOPE,
@@ -274,13 +279,13 @@ export function createSessionService(deps) {
274
279
  };
275
280
  deps.registry.upsert(record);
276
281
  await persistTranslatorSession(deps.fs, repoRoot, record);
277
- return withHarnessRevisionView(repoRoot, record);
282
+ return withHarnessRevisionView(repoRoot, await migrateRunningProjectToolSessionCwd(repoRoot, record, taskContext.taskRepoRoot));
278
283
  }
279
284
  async function launchProjectHarnessEngineerSession(repoRoot, input, launchMode) {
280
285
  const taskContext = await resolveProjectToolTaskContext(repoRoot, input, "Harness Engineer");
281
286
  const live = toRoleSessionRecordView(getRegisteredProjectHarnessEngineerSession(deps.registry, deps.runtime), deps.runtime);
282
287
  if (live && live.status === "running") {
283
- return withHarnessRevisionView(repoRoot, live);
288
+ return withHarnessRevisionView(repoRoot, await migrateRunningProjectToolSessionCwd(repoRoot, live, taskContext.taskRepoRoot));
284
289
  }
285
290
  const config = await deps.projectService.loadConfig(repoRoot);
286
291
  const persisted = await loadPersistedHarnessEngineerSession(deps.fs, repoRoot);
@@ -299,10 +304,15 @@ export function createSessionService(deps) {
299
304
  });
300
305
  }
301
306
  await deps.fs.ensureDir(resolveRepoPath(repoRoot, HARNESS_ENGINEER_DIR));
302
- const transcriptPath = claudeTranscriptPath(taskContext.taskRepoRoot, claudeSessionId);
307
+ const launchCwd = launchMode === "resume"
308
+ ? persisted?.cwd ?? taskContext.taskRepoRoot
309
+ : taskContext.taskRepoRoot;
310
+ const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
311
+ ? persisted.transcriptPath
312
+ : claudeTranscriptPath(launchCwd, claudeSessionId);
303
313
  const startCommand = {
304
314
  ...deps.claude.buildRoleStartCommand(HARNESS_ENGINEER_ROLE, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
305
- cwd: taskContext.taskRepoRoot
315
+ cwd: launchCwd
306
316
  };
307
317
  const runtimeSession = await deps.runtime.createSession({
308
318
  taskSlug: PROJECT_HARNESS_ENGINEER_SCOPE,
@@ -345,7 +355,7 @@ export function createSessionService(deps) {
345
355
  };
346
356
  deps.registry.upsert(record);
347
357
  await persistHarnessEngineerSession(deps.fs, repoRoot, record);
348
- return withHarnessRevisionView(repoRoot, record);
358
+ return withHarnessRevisionView(repoRoot, await migrateRunningProjectToolSessionCwd(repoRoot, record, taskContext.taskRepoRoot));
349
359
  }
350
360
  async function resolveProjectToolTaskContext(repoRoot, input, roleLabel) {
351
361
  const taskSlug = input.taskSlug?.trim();
@@ -363,6 +373,100 @@ export function createSessionService(deps) {
363
373
  taskRepoRoot: getTaskRuntimeRepoRoot(task)
364
374
  };
365
375
  }
376
+ async function migrateRunningProjectToolSessionCwd(repoRoot, session, targetCwd) {
377
+ if (session.role !== TRANSLATOR_ROLE
378
+ && session.role !== HARNESS_ENGINEER_ROLE
379
+ && session.role !== GATE_REVIEWER_ROLE) {
380
+ return session;
381
+ }
382
+ if (samePath(session.cwd, targetCwd)) {
383
+ return session;
384
+ }
385
+ const runtimeSession = deps.runtime.getSession(session.id);
386
+ if (!runtimeSession || runtimeSession.status !== "running") {
387
+ return session;
388
+ }
389
+ assertSafeCwdTarget(targetCwd);
390
+ const timestamp = now();
391
+ await submitTerminalInput(deps.runtime, session.id, `/cd ${targetCwd}`);
392
+ const updated = {
393
+ ...session,
394
+ cwd: targetCwd,
395
+ previousCwd: session.cwd,
396
+ transcriptPath: claudeTranscriptPath(targetCwd, session.claudeSessionId),
397
+ updatedAt: timestamp
398
+ };
399
+ deps.registry.upsert(normalizeProjectScopedRecordForPersistence(updated));
400
+ await persistProjectScopedToolSession(repoRoot, updated);
401
+ return updated;
402
+ }
403
+ async function resumeProjectToolSessionAtCwd(repoRoot, session, targetCwd) {
404
+ const live = toRoleSessionRecordView(session.role === TRANSLATOR_ROLE
405
+ ? getRegisteredProjectTranslatorSession(deps.registry, deps.runtime)
406
+ : session.role === HARNESS_ENGINEER_ROLE
407
+ ? getRegisteredProjectHarnessEngineerSession(deps.registry, deps.runtime)
408
+ : getRegisteredProjectGateReviewerSession(deps.registry, deps.runtime), deps.runtime);
409
+ if (live?.status === "running") {
410
+ return migrateRunningProjectToolSessionCwd(repoRoot, live, targetCwd);
411
+ }
412
+ const config = await deps.projectService.loadConfig(repoRoot);
413
+ const permissionMode = normalizeClaudePermissionMode(session.permissionMode);
414
+ const model = normalizeClaudeModel(session.model);
415
+ const effort = normalizeClaudeEffort(session.effort);
416
+ const launchCwd = session.cwd || targetCwd;
417
+ const startCommand = {
418
+ ...deps.claude.buildRoleStartCommand(session.role, config.claudeCommand, permissionMode, session.claudeSessionId, true, model, effort),
419
+ cwd: launchCwd
420
+ };
421
+ const runtimeSession = await deps.runtime.createSession({
422
+ taskSlug: normalizeProjectScopedRecordForPersistence(session).taskSlug,
423
+ role: session.role,
424
+ command: startCommand.command,
425
+ args: startCommand.args,
426
+ cwd: startCommand.cwd,
427
+ env: {
428
+ VCM_API_URL: deps.apiUrl,
429
+ VCM_TASK_REPO_ROOT: targetCwd,
430
+ VCM_TASK_SLUG: normalizeProjectScopedRecordForPersistence(session).taskSlug,
431
+ VCM_ROLE: session.role,
432
+ VCM_SESSION_ID: session.claudeSessionId
433
+ }
434
+ });
435
+ const timestamp = now();
436
+ const resumed = {
437
+ ...session,
438
+ id: runtimeSession.id,
439
+ status: runtimeSession.status,
440
+ activityStatus: "idle",
441
+ command: startCommand.display,
442
+ permissionMode,
443
+ model,
444
+ effort,
445
+ cwd: launchCwd,
446
+ pid: runtimeSession.pid,
447
+ startedAt: runtimeSession.startedAt,
448
+ updatedAt: timestamp,
449
+ lastOutputAt: runtimeSession.lastOutputAt,
450
+ exitCode: runtimeSession.exitCode,
451
+ transcriptPath: session.transcriptPath ?? claudeTranscriptPath(launchCwd, session.claudeSessionId)
452
+ };
453
+ deps.registry.upsert(normalizeProjectScopedRecordForPersistence(resumed));
454
+ await persistProjectScopedToolSession(repoRoot, resumed);
455
+ return migrateRunningProjectToolSessionCwd(repoRoot, resumed, targetCwd);
456
+ }
457
+ async function persistProjectScopedToolSession(repoRoot, session) {
458
+ if (session.role === GATE_REVIEWER_ROLE) {
459
+ await persistProjectGateReviewerSession(deps.fs, repoRoot, session);
460
+ return;
461
+ }
462
+ if (session.role === TRANSLATOR_ROLE) {
463
+ await persistTranslatorSession(deps.fs, repoRoot, session);
464
+ return;
465
+ }
466
+ if (session.role === HARNESS_ENGINEER_ROLE) {
467
+ await persistHarnessEngineerSession(deps.fs, repoRoot, session);
468
+ }
469
+ }
366
470
  async function notifyHarnessUpdatedForSession(repoRoot, session) {
367
471
  const runtimeSession = deps.runtime.getSession(session.id);
368
472
  if (!runtimeSession || runtimeSession.status !== "running") {
@@ -505,6 +609,23 @@ export function createSessionService(deps) {
505
609
  await persistTranslatorSession(deps.fs, repoRoot, updated);
506
610
  return updated;
507
611
  },
612
+ async moveProjectTranslatorSessionToSafeCwd(repoRoot) {
613
+ const existing = await this.getProjectTranslatorSession(repoRoot);
614
+ if (!existing) {
615
+ throw new VcmError({
616
+ code: "SESSION_MISSING",
617
+ message: "Translator session has not been started.",
618
+ statusCode: 404
619
+ });
620
+ }
621
+ if (samePath(existing.cwd, repoRoot)) {
622
+ return existing;
623
+ }
624
+ return withHarnessRevisionView(repoRoot, await resumeProjectToolSessionAtCwd(repoRoot, {
625
+ ...existing,
626
+ taskSlug: PROJECT_TRANSLATOR_SCOPE
627
+ }, repoRoot));
628
+ },
508
629
  async restartProjectTranslatorSession(repoRoot, input = {}) {
509
630
  const existing = await this.getProjectTranslatorSession(repoRoot);
510
631
  if (!existing) {
@@ -525,6 +646,16 @@ export function createSessionService(deps) {
525
646
  async ensureProjectTranslatorSession(repoRoot, input = {}) {
526
647
  const existing = await this.getProjectTranslatorSession(repoRoot);
527
648
  if (existing?.status === "running") {
649
+ if (input.taskSlug) {
650
+ return this.resumeProjectTranslatorSession(repoRoot, {
651
+ taskSlug: input.taskSlug,
652
+ permissionMode: input.permissionMode ?? existing.permissionMode,
653
+ model: input.model ?? existing.model,
654
+ effort: input.effort ?? existing.effort,
655
+ cols: input.cols,
656
+ rows: input.rows
657
+ });
658
+ }
528
659
  return existing;
529
660
  }
530
661
  if (existing?.claudeSessionId) {
@@ -602,6 +733,23 @@ export function createSessionService(deps) {
602
733
  await persistHarnessEngineerSession(deps.fs, repoRoot, updated);
603
734
  return updated;
604
735
  },
736
+ async moveProjectHarnessEngineerSessionToSafeCwd(repoRoot) {
737
+ const existing = await this.getProjectHarnessEngineerSession(repoRoot);
738
+ if (!existing) {
739
+ throw new VcmError({
740
+ code: "SESSION_MISSING",
741
+ message: "Harness Engineer session has not been started.",
742
+ statusCode: 404
743
+ });
744
+ }
745
+ if (samePath(existing.cwd, repoRoot)) {
746
+ return existing;
747
+ }
748
+ return withHarnessRevisionView(repoRoot, await resumeProjectToolSessionAtCwd(repoRoot, {
749
+ ...existing,
750
+ taskSlug: PROJECT_HARNESS_ENGINEER_SCOPE
751
+ }, repoRoot));
752
+ },
605
753
  async restartProjectHarnessEngineerSession(repoRoot, input = {}) {
606
754
  const existing = await this.getProjectHarnessEngineerSession(repoRoot);
607
755
  if (!existing) {
@@ -622,6 +770,16 @@ export function createSessionService(deps) {
622
770
  async ensureProjectHarnessEngineerSession(repoRoot, input = {}) {
623
771
  const existing = await this.getProjectHarnessEngineerSession(repoRoot);
624
772
  if (existing?.status === "running") {
773
+ if (input.taskSlug) {
774
+ return this.resumeProjectHarnessEngineerSession(repoRoot, {
775
+ taskSlug: input.taskSlug,
776
+ permissionMode: input.permissionMode ?? existing.permissionMode,
777
+ model: input.model ?? existing.model,
778
+ effort: input.effort ?? existing.effort,
779
+ cols: input.cols,
780
+ rows: input.rows
781
+ });
782
+ }
625
783
  return existing;
626
784
  }
627
785
  if (existing?.claudeSessionId) {
@@ -1003,6 +1161,18 @@ function getRecoverableStatus(record) {
1003
1161
  }
1004
1162
  return "resumable";
1005
1163
  }
1164
+ function samePath(left, right) {
1165
+ return path.resolve(left) === path.resolve(right);
1166
+ }
1167
+ function assertSafeCwdTarget(targetCwd) {
1168
+ if (!targetCwd.trim() || /[\r\n]/.test(targetCwd)) {
1169
+ throw new VcmError({
1170
+ code: "SESSION_CWD_INVALID",
1171
+ message: "Session cwd target is invalid.",
1172
+ statusCode: 400
1173
+ });
1174
+ }
1175
+ }
1006
1176
  function getHandoffArtifactPath(paths, role) {
1007
1177
  if (role === "architect") {
1008
1178
  return paths.architecturePlanPath;