vibe-coding-master 0.4.18 → 0.4.20

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.
@@ -258,6 +258,9 @@ export function createHarnessService(deps) {
258
258
  async getRepositoryDiff(repoRoot, input = {}) {
259
259
  return getRepositoryDiffReport(requireRepositoryDiffGit(deps.git), repoRoot, input, now());
260
260
  },
261
+ async mergeRepositoryDiffToCurrentBranch(baseRepoRoot, input) {
262
+ return mergeRepositoryDiffToCurrentBranch(requireRepositoryMergeGit(deps.git), baseRepoRoot, input, now());
263
+ },
261
264
  async getBootstrapStatus(repoRoot, targetRepoRoot = repoRoot) {
262
265
  return getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now);
263
266
  },
@@ -454,6 +457,7 @@ function requireRepositoryDiffGit(git) {
454
457
  if (!git?.getCommitDiff ||
455
458
  !git.getCommitInfo ||
456
459
  !git.getCommitList ||
460
+ !git.getCurrentBranch ||
457
461
  !git.getHeadCommit ||
458
462
  !git.getMergeBase) {
459
463
  throw new VcmError({
@@ -464,7 +468,22 @@ function requireRepositoryDiffGit(git) {
464
468
  }
465
469
  return git;
466
470
  }
471
+ function requireRepositoryMergeGit(git) {
472
+ if (!git?.branchExists ||
473
+ !git.getCurrentBranch ||
474
+ !git.getHeadCommit ||
475
+ !git.mergeBranchFastForward) {
476
+ throw new VcmError({
477
+ code: "HARNESS_GIT_UNAVAILABLE",
478
+ message: "Git-backed repository merge is not available in this VCM runtime.",
479
+ statusCode: 501
480
+ });
481
+ }
482
+ return git;
483
+ }
467
484
  async function getRepositoryDiffReport(git, repoRoot, input, generatedAt) {
485
+ const sourceBranch = await git.getCurrentBranch(repoRoot);
486
+ const targetBranch = input.baseRepoRoot ? await git.getCurrentBranch(input.baseRepoRoot) : sourceBranch;
468
487
  const baseRef = input.baseRepoRoot
469
488
  ? await git.getHeadCommit(input.baseRepoRoot)
470
489
  : "HEAD^";
@@ -476,6 +495,8 @@ async function getRepositoryDiffReport(git, repoRoot, input, generatedAt) {
476
495
  return {
477
496
  version: 1,
478
497
  repoRoot,
498
+ sourceBranch,
499
+ targetBranch,
479
500
  generatedAt,
480
501
  commits,
481
502
  summary: createEmptyRepositoryDiffSummary(),
@@ -497,6 +518,8 @@ async function getRepositoryDiffReport(git, repoRoot, input, generatedAt) {
497
518
  return {
498
519
  version: 1,
499
520
  repoRoot,
521
+ sourceBranch,
522
+ targetBranch,
500
523
  generatedAt,
501
524
  commits,
502
525
  commit: {
@@ -522,6 +545,71 @@ async function getRepositoryDiffReport(git, repoRoot, input, generatedAt) {
522
545
  warnings
523
546
  };
524
547
  }
548
+ async function mergeRepositoryDiffToCurrentBranch(git, baseRepoRoot, input, mergedAt) {
549
+ const taskBranch = input.taskBranch.trim();
550
+ if (!taskBranch) {
551
+ throw new VcmError({
552
+ code: "HARNESS_MERGE_SOURCE_BRANCH_MISSING",
553
+ message: "Task branch is missing.",
554
+ statusCode: 400
555
+ });
556
+ }
557
+ if (!(await git.branchExists(baseRepoRoot, taskBranch))) {
558
+ throw new VcmError({
559
+ code: "HARNESS_MERGE_SOURCE_BRANCH_MISSING",
560
+ message: `Task branch does not exist locally: ${taskBranch}`,
561
+ statusCode: 409,
562
+ hint: "Create or restore the task branch before merging it into the connected repository branch."
563
+ });
564
+ }
565
+ const targetBranch = await git.getCurrentBranch(baseRepoRoot);
566
+ if (targetBranch === "detached") {
567
+ throw new VcmError({
568
+ code: "HARNESS_MERGE_BASE_DETACHED",
569
+ message: "The connected repository is in detached HEAD state.",
570
+ statusCode: 409,
571
+ hint: "Checkout the intended target branch in the connected repository before merging the task branch."
572
+ });
573
+ }
574
+ if (taskBranch === targetBranch) {
575
+ throw new VcmError({
576
+ code: "HARNESS_MERGE_BRANCH_INVALID",
577
+ message: "Task branch is already the connected repository branch.",
578
+ statusCode: 409,
579
+ hint: `Source and target are both ${targetBranch}.`
580
+ });
581
+ }
582
+ await assertVisibleWorktreeClean(git, input.taskRepoRoot, "The active task worktree", "HARNESS_MERGE_TASK_DIRTY");
583
+ await assertVisibleWorktreeClean(git, baseRepoRoot, "The connected repository", "HARNESS_MERGE_BASE_DIRTY");
584
+ const beforeSha = await git.getHeadCommit(baseRepoRoot);
585
+ const mergeResult = await git.mergeBranchFastForward(baseRepoRoot, taskBranch);
586
+ const afterSha = await git.getHeadCommit(baseRepoRoot);
587
+ return {
588
+ version: 1,
589
+ baseRepoRoot,
590
+ taskRepoRoot: input.taskRepoRoot,
591
+ sourceBranch: taskBranch,
592
+ targetBranch,
593
+ beforeSha,
594
+ afterSha,
595
+ changed: beforeSha !== afterSha,
596
+ stdout: mergeResult.stdout,
597
+ stderr: mergeResult.stderr,
598
+ mergedAt
599
+ };
600
+ }
601
+ async function assertVisibleWorktreeClean(git, repoRoot, label, code) {
602
+ const entries = await getVisibleGitStatusEntries(git, repoRoot);
603
+ if (entries.length === 0) {
604
+ return;
605
+ }
606
+ throw new VcmError({
607
+ code,
608
+ message: `${label} has uncommitted Git-visible changes.`,
609
+ statusCode: 409,
610
+ hint: `Commit or revert these files before merging to main: ${entries.map((entry) => entry.path).slice(0, 12).join(", ")}`
611
+ });
612
+ }
525
613
  function toRepositoryDiffCommit(commit) {
526
614
  return {
527
615
  sha: commit.sha,
@@ -241,6 +241,36 @@ export function createRoundService(deps) {
241
241
  recordClaudeHookEvent(input) {
242
242
  return recordRoleTurnEvent(input);
243
243
  },
244
+ async setRoleRecovery(input) {
245
+ return withTaskLock(input, async () => {
246
+ const timestamp = now();
247
+ const state = await load(input);
248
+ const next = {
249
+ ...state,
250
+ roleRecovery: normalizeRoleRecovery(input.recovery),
251
+ updatedAt: timestamp
252
+ };
253
+ await save(input, next);
254
+ await updateSessionStatus(input, "running");
255
+ return toSessionRoundState(next, timestamp);
256
+ });
257
+ },
258
+ async clearRoleRecovery(input) {
259
+ return withTaskLock(input, async () => {
260
+ const timestamp = now();
261
+ const state = await load(input);
262
+ if (!state.roleRecovery || (input.role && state.roleRecovery.role !== input.role)) {
263
+ return toSessionRoundState(state, timestamp);
264
+ }
265
+ const next = {
266
+ ...state,
267
+ roleRecovery: undefined,
268
+ updatedAt: timestamp
269
+ };
270
+ await save(input, next);
271
+ return toSessionRoundState(next, timestamp);
272
+ });
273
+ },
244
274
  stopSession() { },
245
275
  stopTask(taskSlug) {
246
276
  clearSettleTimersForTask(taskSlug);
@@ -352,6 +382,7 @@ function toSessionRoundState(state, updatedAt) {
352
382
  totalCompletedTurnCount: state.totalCompletedTurnCount,
353
383
  totalCcActiveMs: state.totalCcActiveMs,
354
384
  currentRoundCcActiveMs: 0,
385
+ roleRecovery: state.roleRecovery,
355
386
  roles: [],
356
387
  updatedAt
357
388
  };
@@ -379,6 +410,7 @@ function toSessionRoundState(state, updatedAt) {
379
410
  totalCompletedTurnCount: state.totalCompletedTurnCount,
380
411
  totalCcActiveMs: state.totalCcActiveMs + activeDurationMs,
381
412
  currentRoundCcActiveMs,
413
+ roleRecovery: state.roleRecovery,
382
414
  roles: current.roles,
383
415
  updatedAt
384
416
  };
@@ -394,9 +426,35 @@ function normalizeRoundFile(input, taskSlug, updatedAt) {
394
426
  totalTurnCount: normalizeNumber(input.totalTurnCount ?? legacy.totalPromptSubmitCount),
395
427
  totalCompletedTurnCount: normalizeNumber(input.totalCompletedTurnCount ?? legacy.totalStopCount),
396
428
  totalCcActiveMs: normalizeNumber(input.totalCcActiveMs),
429
+ roleRecovery: normalizeRoleRecovery(input.roleRecovery),
397
430
  updatedAt: typeof input.updatedAt === "string" ? input.updatedAt : updatedAt
398
431
  };
399
432
  }
433
+ function normalizeRoleRecovery(input) {
434
+ if (!input || typeof input !== "object") {
435
+ return undefined;
436
+ }
437
+ const record = input;
438
+ if (typeof record.role !== "string" ||
439
+ (record.status !== "waiting" && record.status !== "retrying" && record.status !== "failed") ||
440
+ typeof record.lastFailureAt !== "string") {
441
+ return undefined;
442
+ }
443
+ return {
444
+ role: record.role,
445
+ status: record.status,
446
+ attempt: normalizeNumber(record.attempt),
447
+ maxAttempts: normalizeNumber(record.maxAttempts),
448
+ lastFailureAt: record.lastFailureAt,
449
+ error: typeof record.error === "string" ? record.error : undefined,
450
+ errorDetails: typeof record.errorDetails === "string" ? record.errorDetails : undefined,
451
+ lastAssistantMessage: typeof record.lastAssistantMessage === "string" ? record.lastAssistantMessage : undefined,
452
+ retryable: typeof record.retryable === "boolean" ? record.retryable : undefined,
453
+ nextRetryAt: typeof record.nextRetryAt === "string" ? record.nextRetryAt : undefined,
454
+ lastRetryAt: typeof record.lastRetryAt === "string" ? record.lastRetryAt : undefined,
455
+ failedAt: typeof record.failedAt === "string" ? record.failedAt : undefined
456
+ };
457
+ }
400
458
  function normalizeRound(input) {
401
459
  if (!input || typeof input.id !== "string") {
402
460
  return undefined;