vibe-coding-master 0.3.23 → 0.3.24

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.
@@ -81,6 +81,18 @@ export function createGitAdapter(runner) {
81
81
  }
82
82
  return result.stdout;
83
83
  },
84
+ async getStagedStatus(repoRoot) {
85
+ const result = await runGit(runner, repoRoot, ["diff", "--cached", "--name-status"]);
86
+ if (result.exitCode !== 0) {
87
+ throw new VcmError({
88
+ code: "GIT_ERROR",
89
+ message: "Unable to read staged Git changes.",
90
+ statusCode: 400,
91
+ hint: result.stderr
92
+ });
93
+ }
94
+ return result.stdout;
95
+ },
84
96
  async isIgnored(repoRoot, repoRelativePath) {
85
97
  const result = await runGit(runner, repoRoot, ["check-ignore", "-q", "--", repoRelativePath]);
86
98
  if (result.exitCode === 0) {
@@ -111,6 +123,47 @@ export function createGitAdapter(runner) {
111
123
  hint: result.stderr
112
124
  });
113
125
  },
126
+ async addPaths(repoRoot, paths) {
127
+ if (paths.length === 0) {
128
+ return;
129
+ }
130
+ const result = await runGit(runner, repoRoot, ["add", "--", ...paths]);
131
+ if (result.exitCode !== 0) {
132
+ throw new VcmError({
133
+ code: "GIT_ADD_FAILED",
134
+ message: "Unable to stage harness changes.",
135
+ statusCode: 409,
136
+ hint: result.stderr || result.stdout
137
+ });
138
+ }
139
+ },
140
+ async commit(repoRoot, message) {
141
+ const result = await runGit(runner, repoRoot, ["commit", "-m", message]);
142
+ if (result.exitCode !== 0) {
143
+ throw new VcmError({
144
+ code: "GIT_COMMIT_FAILED",
145
+ message: "Unable to commit harness changes.",
146
+ statusCode: 409,
147
+ hint: result.stderr || result.stdout
148
+ });
149
+ }
150
+ return this.getHeadCommit(repoRoot);
151
+ },
152
+ async rebase(repoRoot, upstream) {
153
+ const result = await runGit(runner, repoRoot, ["rebase", upstream]);
154
+ if (result.exitCode !== 0) {
155
+ throw new VcmError({
156
+ code: "GIT_REBASE_FAILED",
157
+ message: "Unable to rebase task branch onto the harness commit.",
158
+ statusCode: 409,
159
+ hint: result.stderr || result.stdout
160
+ });
161
+ }
162
+ return {
163
+ stdout: result.stdout,
164
+ stderr: result.stderr
165
+ };
166
+ },
114
167
  async createWorktree(input) {
115
168
  const result = await runGit(runner, input.repoRoot, [
116
169
  "worktree",
@@ -1,4 +1,4 @@
1
- import { isDispatchableRole, isRoleName } from "../../shared/constants.js";
1
+ import { isDispatchableRole } from "../../shared/constants.js";
2
2
  import { VcmError } from "../errors.js";
3
3
  import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
4
4
  export function registerArtifactRoutes(app, deps) {
@@ -52,33 +52,6 @@ export function registerArtifactRoutes(app, deps) {
52
52
  });
53
53
  return { ok: true };
54
54
  });
55
- app.get("/api/tasks/:taskSlug/logs/:role", async (request) => {
56
- const project = await requireCurrentProject(deps.projectService);
57
- if (!isRoleName(request.params.role)) {
58
- throw new VcmError({
59
- code: "UNKNOWN_ROLE",
60
- message: `Unknown role: ${request.params.role}`,
61
- statusCode: 400
62
- });
63
- }
64
- const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
65
- const taskRepoRoot = getTaskRuntimeRepoRoot(task);
66
- const paths = deps.artifactService.getHandoffPaths(taskRepoRoot, task.handoffDir);
67
- const artifactPath = paths.roleLogPaths[request.params.role];
68
- if (!artifactPath) {
69
- return {
70
- role: request.params.role,
71
- content: ""
72
- };
73
- }
74
- return {
75
- role: request.params.role,
76
- content: await deps.artifactService.readArtifact({
77
- repoRoot: taskRepoRoot,
78
- artifactPath
79
- })
80
- };
81
- });
82
55
  }
83
56
  function parseDispatchableRole(role) {
84
57
  if (!isDispatchableRole(role)) {
@@ -16,6 +16,16 @@ export function registerHarnessRoutes(app, deps) {
16
16
  const project = await requireCurrentProject(deps.projectService);
17
17
  return deps.harnessService.applyHarness(project.repoRoot);
18
18
  });
19
+ app.post("/api/projects/harness/tasks/:taskSlug/commit-and-rebase", async (request) => {
20
+ const project = await requireCurrentProject(deps.projectService);
21
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
22
+ return deps.harnessService.commitAndRebaseTask(project.repoRoot, {
23
+ taskSlug: task.taskSlug,
24
+ branch: task.branch,
25
+ worktreePath: task.worktreePath,
26
+ changedFiles: request.body?.changedFiles ?? []
27
+ });
28
+ });
19
29
  app.get("/api/projects/harness/bootstrap", async () => {
20
30
  const project = await requireCurrentProject(deps.projectService);
21
31
  try {
@@ -1,4 +1,4 @@
1
- import { DISPATCHABLE_ROLES, ROLE_NAMES } from "../../shared/constants.js";
1
+ import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
2
2
  import { isOpenFileLimitError, VcmError } from "../errors.js";
3
3
  import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
4
4
  export function registerTaskRoutes(app, deps) {
@@ -66,6 +66,7 @@ function degradedTaskStatus(repoRoot, taskSlug, error) {
66
66
  createdAt: timestamp,
67
67
  updatedAt: timestamp,
68
68
  repoRoot,
69
+ worktreePath: `${repoRoot}/.claude/worktrees/${taskSlug}`,
69
70
  branch: "unknown",
70
71
  handoffDir,
71
72
  status: "stopped"
@@ -79,18 +80,13 @@ function degradedTaskStatus(repoRoot, taskSlug, error) {
79
80
  }
80
81
  function degradedArtifactSummary(handoffDir) {
81
82
  const roleCommandsDir = `${handoffDir}/role-commands`;
82
- const logsDir = `${handoffDir}/logs`;
83
83
  const messagesDir = `${handoffDir}/messages`;
84
84
  return {
85
85
  paths: {
86
86
  handoffDir,
87
87
  roleCommandsDir,
88
- logsDir,
89
88
  messagesDir,
90
89
  roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
91
- roleLogPaths: Object.fromEntries(ROLE_NAMES
92
- .filter((role) => role !== "codex-translator")
93
- .map((role) => [role, `${logsDir}/${role}.log`])),
94
90
  messageRoutePaths: {},
95
91
  architecturePlanPath: `${handoffDir}/architecture-plan.md`,
96
92
  knownIssuesPath: `${handoffDir}/known-issues.md`,
@@ -352,17 +352,6 @@ export function createGatewayService(deps) {
352
352
  }
353
353
  async function pullCurrent() {
354
354
  const project = await ensureProject();
355
- const settings = await syncDesktopContext(await deps.settings.loadSettings());
356
- if (settings.currentTaskSlug) {
357
- const task = await deps.taskService.loadTask(project.repoRoot, settings.currentTaskSlug);
358
- if (!task.worktreePath && task.cleanupStatus !== "cleaned") {
359
- throw new VcmError({
360
- code: "GATEWAY_PULL_BLOCKED_BY_INLINE_TASK",
361
- message: `Inline task "${task.taskSlug}" uses the base repository.`,
362
- statusCode: 409
363
- });
364
- }
365
- }
366
355
  const pulled = await deps.projectService.pullCurrentProject();
367
356
  return [
368
357
  "Connected repository updated.",
@@ -377,7 +366,7 @@ export function createGatewayService(deps) {
377
366
  if (tasks.length === 0) {
378
367
  return "No tasks. Use /create-task <task-slug> [title] to create one.";
379
368
  }
380
- return tasks.map((task, index) => `${index + 1}. ${task.taskSlug} [${task.status}] ${task.worktreePath ? "worktree" : "inline"}`).join("\n");
369
+ return tasks.map((task, index) => `${index + 1}. ${task.taskSlug} [${task.status}] ${task.branch}`).join("\n");
381
370
  }
382
371
  async function useTask(selector) {
383
372
  const project = await ensureProject();
@@ -405,8 +394,7 @@ export function createGatewayService(deps) {
405
394
  const project = await ensureProject();
406
395
  const task = await deps.taskService.createTask(project.repoRoot, {
407
396
  taskSlug,
408
- title,
409
- createWorktree: true
397
+ title
410
398
  });
411
399
  const config = await deps.projectService.loadConfig(project.repoRoot);
412
400
  const taskRepoRoot = getTaskRuntimeRepoRoot(task);
@@ -460,7 +448,7 @@ export function createGatewayService(deps) {
460
448
  return [
461
449
  `Task created and initialized: ${task.taskSlug}`,
462
450
  `branch: ${task.branch}`,
463
- `worktree: ${task.worktreePath ?? task.repoRoot}`,
451
+ `worktree: ${task.worktreePath}`,
464
452
  `orchestration: ${template.autoOrchestration ? "auto" : "manual"}`,
465
453
  `translation: ${preferences.translationEnabled ? "on" : "off"}`,
466
454
  `sessions: ${startedRoles.join(", ")}`
@@ -523,7 +511,6 @@ export function createGatewayService(deps) {
523
511
  deps.roundService.stopTask(taskSlug);
524
512
  const result = await deps.taskService.cleanupTask(project.repoRoot, taskSlug, {
525
513
  force: true,
526
- deleteBranch: Boolean(task.worktreePath),
527
514
  forceDeleteBranch: true
528
515
  });
529
516
  clearFailedTranslation(project.repoRoot, taskSlug);
@@ -82,7 +82,8 @@ export async function createServer(deps, options = {}) {
82
82
  });
83
83
  registerHarnessRoutes(app, {
84
84
  projectService: deps.projectService,
85
- harnessService: deps.harnessService
85
+ harnessService: deps.harnessService,
86
+ taskService: deps.taskService
86
87
  });
87
88
  registerTaskRoutes(app, {
88
89
  projectService: deps.projectService,
@@ -170,6 +171,7 @@ export function createDefaultServerDeps(options = {}) {
170
171
  const projectService = createProjectService({ fs, git, appSettings });
171
172
  const harnessService = createHarnessService({
172
173
  fs,
174
+ git,
173
175
  runtime,
174
176
  projectService,
175
177
  apiUrl: options.apiUrl,
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { DISPATCHABLE_ROLES, ROLE_NAMES } from "../../shared/constants.js";
2
+ import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
3
3
  import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js";
4
4
  import { VcmError } from "../errors.js";
5
5
  import { resolveRepoPath } from "../adapters/filesystem.js";
@@ -25,25 +25,16 @@ export function createArtifactService(fs) {
25
25
  return {
26
26
  getHandoffPaths(_repoRoot, handoffDir) {
27
27
  const roleCommandsDir = path.posix.join(handoffDir, "role-commands");
28
- const logsDir = path.posix.join(handoffDir, "logs");
29
28
  const messagesDir = path.posix.join(handoffDir, "messages");
30
29
  return {
31
30
  handoffDir,
32
31
  roleCommandsDir,
33
- logsDir,
34
32
  messagesDir,
35
33
  roleCommandPaths: {
36
34
  architect: path.posix.join(roleCommandsDir, "architect.md"),
37
35
  coder: path.posix.join(roleCommandsDir, "coder.md"),
38
36
  reviewer: path.posix.join(roleCommandsDir, "reviewer.md")
39
37
  },
40
- roleLogPaths: {
41
- "project-manager": path.posix.join(logsDir, "project-manager.log"),
42
- architect: path.posix.join(logsDir, "architect.log"),
43
- coder: path.posix.join(logsDir, "coder.log"),
44
- reviewer: path.posix.join(logsDir, "reviewer.log"),
45
- "codex-reviewer": path.posix.join(logsDir, "codex-reviewer.log")
46
- },
47
38
  messageRoutePaths: getDefaultMessageRoutePaths(messagesDir),
48
39
  architecturePlanPath: path.posix.join(handoffDir, "architecture-plan.md"),
49
40
  knownIssuesPath: path.posix.join(handoffDir, "known-issues.md"),
@@ -59,7 +50,6 @@ export function createArtifactService(fs) {
59
50
  const paths = this.getHandoffPaths(input.repoRoot, input.handoffDir);
60
51
  await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.handoffDir));
61
52
  await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.roleCommandsDir));
62
- await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.logsDir));
63
53
  await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.messagesDir));
64
54
  return paths;
65
55
  },
@@ -168,25 +158,6 @@ export function createArtifactService(fs) {
168
158
  async saveRoleCommand(input) {
169
159
  const paths = this.getHandoffPaths(input.repoRoot, input.handoffDir);
170
160
  await fs.writeText(resolveRepoPath(input.repoRoot, paths.roleCommandPaths[input.role]), input.content);
171
- },
172
- async appendRoleLog(input) {
173
- if (!ROLE_NAMES.includes(input.role)) {
174
- throw new VcmError({
175
- code: "UNKNOWN_ROLE",
176
- message: `Unknown role: ${input.role}`,
177
- statusCode: 400
178
- });
179
- }
180
- const paths = this.getHandoffPaths(input.repoRoot, input.handoffDir);
181
- const roleLogPath = paths.roleLogPaths[input.role];
182
- if (!roleLogPath) {
183
- throw new VcmError({
184
- code: "ROLE_LOG_UNAVAILABLE",
185
- message: `${input.role} does not write a handoff log.`,
186
- statusCode: 409
187
- });
188
- }
189
- await fs.appendText(resolveRepoPath(input.repoRoot, roleLogPath), input.content);
190
161
  }
191
162
  };
192
163
  }
@@ -246,6 +246,73 @@ export function createHarnessService(deps) {
246
246
  : "VCM Harness updated. Review these files and commit the harness changes before starting long-running work."
247
247
  };
248
248
  },
249
+ async commitAndRebaseTask(repoRoot, input) {
250
+ if (!deps.git) {
251
+ throw new VcmError({
252
+ code: "HARNESS_GIT_UNAVAILABLE",
253
+ message: "Git-backed harness sync is not available in this VCM runtime.",
254
+ statusCode: 501
255
+ });
256
+ }
257
+ const changedFiles = dedupeHarnessChanges(input.changedFiles);
258
+ const changedPaths = changedFiles.map((change) => change.path);
259
+ const taskBranch = await deps.git.getCurrentBranch(input.worktreePath);
260
+ if (taskBranch !== input.branch) {
261
+ throw new VcmError({
262
+ code: "HARNESS_TASK_BRANCH_MISMATCH",
263
+ message: "The selected task worktree is not on its recorded task branch.",
264
+ statusCode: 409,
265
+ hint: `Expected ${input.branch}, found ${taskBranch}.`
266
+ });
267
+ }
268
+ const taskStatus = await deps.git.getStatusPorcelain(input.worktreePath);
269
+ if (taskStatus.trim()) {
270
+ throw new VcmError({
271
+ code: "HARNESS_TASK_DIRTY",
272
+ message: "The selected task worktree has uncommitted changes.",
273
+ statusCode: 409,
274
+ hint: "Commit or clean the task worktree before rebasing it onto the harness commit."
275
+ });
276
+ }
277
+ const preExistingStaged = await deps.git.getStagedStatus(repoRoot);
278
+ if (preExistingStaged.trim()) {
279
+ throw new VcmError({
280
+ code: "HARNESS_BASE_STAGED_CHANGES",
281
+ message: "The connected repository already has staged changes.",
282
+ statusCode: 409,
283
+ hint: "Commit, unstage, or clean existing staged changes before using Commit & rebase task."
284
+ });
285
+ }
286
+ const baseBranch = await deps.git.getCurrentBranch(repoRoot);
287
+ const baseCommitBefore = await deps.git.getHeadCommit(repoRoot);
288
+ let harnessCommit;
289
+ let committed = false;
290
+ if (changedPaths.length > 0) {
291
+ await deps.git.addPaths(repoRoot, changedPaths);
292
+ const stagedHarnessChanges = await deps.git.getStagedStatus(repoRoot);
293
+ if (stagedHarnessChanges.trim()) {
294
+ harnessCommit = await deps.git.commit(repoRoot, "chore: update VCM harness");
295
+ committed = true;
296
+ }
297
+ }
298
+ const baseCommitAfter = await deps.git.getHeadCommit(repoRoot);
299
+ await deps.git.rebase(input.worktreePath, baseCommitAfter);
300
+ return {
301
+ taskSlug: input.taskSlug,
302
+ branch: input.branch,
303
+ worktreePath: input.worktreePath,
304
+ baseBranch,
305
+ baseCommitBefore,
306
+ baseCommitAfter,
307
+ harnessCommit,
308
+ committed,
309
+ rebased: true,
310
+ changedFiles,
311
+ message: committed
312
+ ? `Committed VCM harness update ${shortCommit(baseCommitAfter)} on ${baseBranch} and rebased ${input.branch}.`
313
+ : `No new harness commit was needed; rebased ${input.branch} onto ${shortCommit(baseCommitAfter)}.`
314
+ };
315
+ },
249
316
  async getBootstrapStatus(repoRoot) {
250
317
  return getHarnessBootstrapStatus(deps, repoRoot, now);
251
318
  },
@@ -320,6 +387,45 @@ export function createHarnessService(deps) {
320
387
  }
321
388
  };
322
389
  }
390
+ function dedupeHarnessChanges(changedFiles) {
391
+ const seen = new Set();
392
+ const changes = [];
393
+ for (const change of changedFiles) {
394
+ const normalizedPath = normalizeHarnessGitPath(change.path);
395
+ if (seen.has(normalizedPath)) {
396
+ continue;
397
+ }
398
+ seen.add(normalizedPath);
399
+ changes.push({
400
+ ...change,
401
+ path: normalizedPath
402
+ });
403
+ }
404
+ return changes;
405
+ }
406
+ function normalizeHarnessGitPath(value) {
407
+ if (!value || value.includes("\0") || path.posix.isAbsolute(value)) {
408
+ throw new VcmError({
409
+ code: "HARNESS_CHANGED_PATH_INVALID",
410
+ message: "Harness changed file path is invalid.",
411
+ statusCode: 400,
412
+ hint: value
413
+ });
414
+ }
415
+ const normalized = path.posix.normalize(value).replace(/^\.\//, "");
416
+ if (normalized === "." || normalized.startsWith("../")) {
417
+ throw new VcmError({
418
+ code: "HARNESS_CHANGED_PATH_INVALID",
419
+ message: "Harness changed file path must stay inside the repository.",
420
+ statusCode: 400,
421
+ hint: value
422
+ });
423
+ }
424
+ return normalized;
425
+ }
426
+ function shortCommit(commit) {
427
+ return commit.slice(0, 7);
428
+ }
323
429
  async function analyzeHarnessFiles(fs, repoRoot) {
324
430
  const analyses = [];
325
431
  for (const definition of HARNESS_FILES) {
@@ -30,7 +30,6 @@ export function createSessionService(deps) {
30
30
  const isCodexRole = isCodexRoleName(role);
31
31
  const isTranslator = role === CODEX_TRANSLATOR_ROLE;
32
32
  const sessionRepoRoot = isTranslator ? repoRoot : taskRepoRoot;
33
- const sessionLogPath = isTranslator ? undefined : paths.roleLogPaths[role];
34
33
  const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
35
34
  const model = isCodexRole
36
35
  ? normalizeCodexModel(input.model ?? persisted?.model)
@@ -72,8 +71,7 @@ export function createSessionService(deps) {
72
71
  VCM_SESSION_ID: claudeSessionId
73
72
  },
74
73
  cols: input.cols,
75
- rows: input.rows,
76
- ...(sessionLogPath ? { logPath: resolveRepoPath(sessionRepoRoot, sessionLogPath) } : {})
74
+ rows: input.rows
77
75
  });
78
76
  const timestamp = now();
79
77
  const record = {
@@ -91,7 +89,6 @@ export function createSessionService(deps) {
91
89
  cwd: startCommand.cwd,
92
90
  terminalBackend: "node-pty",
93
91
  pid: runtimeSession.pid,
94
- ...(sessionLogPath ? { logPath: sessionLogPath } : {}),
95
92
  roleCommandPath: isDispatchableRole(role)
96
93
  ? paths.roleCommandPaths[role]
97
94
  : undefined,
@@ -1,4 +1,4 @@
1
- import { DISPATCHABLE_ROLES, ROLE_NAMES } from "../../shared/constants.js";
1
+ import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
2
2
  import { isOpenFileLimitError } from "../errors.js";
3
3
  import { getTaskRuntimeRepoRoot } from "./task-service.js";
4
4
  export function createStatusService(deps) {
@@ -45,18 +45,13 @@ export function createStatusService(deps) {
45
45
  }
46
46
  function degradedArtifactSummary(handoffDir) {
47
47
  const roleCommandsDir = `${handoffDir}/role-commands`;
48
- const logsDir = `${handoffDir}/logs`;
49
48
  const messagesDir = `${handoffDir}/messages`;
50
49
  return {
51
50
  paths: {
52
51
  handoffDir,
53
52
  roleCommandsDir,
54
- logsDir,
55
53
  messagesDir,
56
54
  roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
57
- roleLogPaths: Object.fromEntries(ROLE_NAMES
58
- .filter((role) => role !== "codex-translator")
59
- .map((role) => [role, `${logsDir}/${role}.log`])),
60
55
  messageRoutePaths: {},
61
56
  architecturePlanPath: `${handoffDir}/architecture-plan.md`,
62
57
  knownIssuesPath: `${handoffDir}/known-issues.md`,