vibe-coding-master 0.4.4 → 0.4.6

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.
@@ -135,6 +135,42 @@ export function createGitAdapter(runner) {
135
135
  }
136
136
  return result.stdout;
137
137
  },
138
+ async getCommitList(repoRoot, range) {
139
+ const result = await runGit(runner, repoRoot, ["log", "--format=%H%x00%s%x00%cI%x1e", range]);
140
+ if (result.exitCode !== 0) {
141
+ throw new VcmError({
142
+ code: "GIT_ERROR",
143
+ message: "Unable to read Git commit list.",
144
+ statusCode: 400,
145
+ hint: result.stderr
146
+ });
147
+ }
148
+ return result.stdout
149
+ .split("\x1e")
150
+ .map((record) => record.trim())
151
+ .filter(Boolean)
152
+ .map((record) => {
153
+ const [sha = "", subject = "", committedAt = ""] = record.split("\0");
154
+ return {
155
+ sha: sha.trim(),
156
+ subject: subject.trim(),
157
+ committedAt: committedAt.trim() || undefined
158
+ };
159
+ })
160
+ .filter((commit) => commit.sha.length > 0);
161
+ },
162
+ async getMergeBase(repoRoot, leftRef, rightRef) {
163
+ const result = await runGit(runner, repoRoot, ["merge-base", leftRef, rightRef]);
164
+ if (result.exitCode !== 0) {
165
+ throw new VcmError({
166
+ code: "GIT_ERROR",
167
+ message: "Unable to find Git merge base.",
168
+ statusCode: 400,
169
+ hint: result.stderr
170
+ });
171
+ }
172
+ return result.stdout.trim();
173
+ },
138
174
  async isIgnored(repoRoot, repoRelativePath) {
139
175
  const result = await runGit(runner, repoRoot, ["check-ignore", "-q", "--", repoRelativePath]);
140
176
  if (result.exitCode === 0) {
@@ -32,9 +32,11 @@ export function registerHarnessRoutes(app, deps) {
32
32
  return deps.harnessService.updateHarnessFileContent(task.worktreePath, request.query.path ?? "", request.body.content);
33
33
  });
34
34
  app.get("/api/projects/harness/repository-diff", async (request) => {
35
- const { task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
36
- const scope = request.query.scope === "all" ? "all" : "harness";
37
- return deps.harnessService.getRepositoryDiff(task.worktreePath, scope);
35
+ const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
36
+ return deps.harnessService.getRepositoryDiff(task.worktreePath, {
37
+ baseRepoRoot: project.repoRoot,
38
+ commitSha: request.query.commit
39
+ });
38
40
  });
39
41
  app.get("/api/projects/harness/bootstrap", async (request) => {
40
42
  const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
@@ -246,15 +246,8 @@ export function createHarnessService(deps) {
246
246
  : formatHarnessApplyMessage(committed.harnessCommit, true)
247
247
  };
248
248
  },
249
- async getRepositoryDiff(repoRoot, scope = "harness") {
250
- if (!deps.git) {
251
- throw new VcmError({
252
- code: "HARNESS_GIT_UNAVAILABLE",
253
- message: "Git-backed repository diff is not available in this VCM runtime.",
254
- statusCode: 501
255
- });
256
- }
257
- return getRepositoryDiffReport(deps.git, repoRoot, scope, now());
249
+ async getRepositoryDiff(repoRoot, input = {}) {
250
+ return getRepositoryDiffReport(requireRepositoryDiffGit(deps.git), repoRoot, input, now());
258
251
  },
259
252
  async getBootstrapStatus(repoRoot, targetRepoRoot = repoRoot) {
260
253
  return getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now);
@@ -345,7 +338,6 @@ export function createHarnessService(deps) {
345
338
  const timestamp = now();
346
339
  if (input.eventName === "Stop" && state.targetRepoRoot) {
347
340
  await bumpHarnessRevision(deps.fs, state.targetRepoRoot, timestamp);
348
- await commitHarnessVisibleChanges(deps.git, state.targetRepoRoot, "chore(vcm-harness): bootstrap project context");
349
341
  }
350
342
  await persistHarnessBootstrapRunState(deps.fs, repoRoot, {
351
343
  ...state,
@@ -449,12 +441,42 @@ function shortCommit(commit) {
449
441
  }
450
442
  const REPOSITORY_DIFF_MAX_FILES = 250;
451
443
  const REPOSITORY_DIFF_MAX_DIFF_CHARS = 180_000;
452
- async function getRepositoryDiffReport(git, repoRoot, scope, generatedAt) {
453
- const commitInfo = await git.getCommitInfo(repoRoot, "HEAD");
454
- const rawDiff = await git.getCommitDiff(repoRoot, "HEAD");
455
- const allFiles = parseCommitDiffFiles(rawDiff)
456
- .filter((file) => scope === "all" || file.category !== "product_code");
444
+ function requireRepositoryDiffGit(git) {
445
+ if (!git?.getCommitDiff ||
446
+ !git.getCommitInfo ||
447
+ !git.getCommitList ||
448
+ !git.getHeadCommit ||
449
+ !git.getMergeBase) {
450
+ throw new VcmError({
451
+ code: "HARNESS_GIT_UNAVAILABLE",
452
+ message: "Git-backed repository diff is not available in this VCM runtime.",
453
+ statusCode: 501
454
+ });
455
+ }
456
+ return git;
457
+ }
458
+ async function getRepositoryDiffReport(git, repoRoot, input, generatedAt) {
459
+ const baseRef = input.baseRepoRoot
460
+ ? await git.getHeadCommit(input.baseRepoRoot)
461
+ : "HEAD^";
462
+ const mergeBase = await git.getMergeBase(repoRoot, baseRef, "HEAD");
463
+ const commits = (await git.getCommitList(repoRoot, `${mergeBase}..HEAD`)).map(toRepositoryDiffCommit);
457
464
  const warnings = [];
465
+ const selectedCommit = selectRepositoryDiffCommit(commits, input.commitSha);
466
+ if (!selectedCommit) {
467
+ return {
468
+ version: 1,
469
+ repoRoot,
470
+ generatedAt,
471
+ commits,
472
+ summary: createEmptyRepositoryDiffSummary(),
473
+ files: [],
474
+ warnings
475
+ };
476
+ }
477
+ const commitInfo = await git.getCommitInfo(repoRoot, selectedCommit.sha);
478
+ const rawDiff = await git.getCommitDiff(repoRoot, selectedCommit.sha);
479
+ const allFiles = parseCommitDiffFiles(rawDiff);
458
480
  const files = allFiles.slice(0, REPOSITORY_DIFF_MAX_FILES);
459
481
  if (allFiles.length > files.length) {
460
482
  warnings.push(`Diff file list is truncated to ${REPOSITORY_DIFF_MAX_FILES} files.`);
@@ -466,8 +488,8 @@ async function getRepositoryDiffReport(git, repoRoot, scope, generatedAt) {
466
488
  return {
467
489
  version: 1,
468
490
  repoRoot,
469
- scope,
470
491
  generatedAt,
492
+ commits,
471
493
  commit: {
472
494
  sha: commitInfo.sha,
473
495
  shortSha: commitInfo.sha.slice(0, 12),
@@ -491,6 +513,36 @@ async function getRepositoryDiffReport(git, repoRoot, scope, generatedAt) {
491
513
  warnings
492
514
  };
493
515
  }
516
+ function toRepositoryDiffCommit(commit) {
517
+ return {
518
+ sha: commit.sha,
519
+ shortSha: commit.sha.slice(0, 12),
520
+ subject: commit.subject,
521
+ committedAt: commit.committedAt
522
+ };
523
+ }
524
+ function selectRepositoryDiffCommit(commits, requestedSha) {
525
+ const normalizedSha = requestedSha?.trim();
526
+ if (normalizedSha) {
527
+ return commits.find((commit) => commit.sha === normalizedSha || commit.shortSha === normalizedSha);
528
+ }
529
+ return commits[0];
530
+ }
531
+ function createEmptyRepositoryDiffSummary() {
532
+ return {
533
+ totalFiles: 0,
534
+ committedFiles: 0,
535
+ stagedFiles: 0,
536
+ unstagedFiles: 0,
537
+ untrackedFiles: 0,
538
+ additions: 0,
539
+ deletions: 0,
540
+ harnessFiles: 0,
541
+ productCodeFiles: 0,
542
+ truncatedFiles: 0,
543
+ binaryFiles: 0
544
+ };
545
+ }
494
546
  function parseCommitDiffFiles(rawDiff) {
495
547
  const chunks = rawDiff.split(/(?=^diff --git )/m).filter((chunk) => chunk.startsWith("diff --git "));
496
548
  return chunks.map(parseCommitDiffFile);
@@ -1435,15 +1487,18 @@ Required work:
1435
1487
  - Fill target docs/ARCHITECTURE.md with project-level module overview, responsibilities, relationships, dependency direction, project-wide constraints, and links to module-level architecture docs.
1436
1488
  - Create or update target module-level ARCHITECTURE.md files for clear module boundaries listed by module-index.json.
1437
1489
  - Fill target docs/TESTING.md with project-native validation levels, commands, validation selection rules, final-validation cleanup, test layout, integration/E2E case lists, generated-context freshness checks, and known testing gaps.
1490
+ - Review git status and git diff in the target task worktree.
1491
+ - Stage only allowed bootstrap harness changes and create a commit in the target task worktree.
1438
1492
 
1439
1493
  Boundaries:
1440
1494
  - Do not write to the base repository root.
1441
1495
  - Do not edit product source, product tests, package manifests, lockfiles, deployment config, secrets, or VCM managed blocks.
1442
1496
  - Preserve user-authored content.
1443
1497
  - Do not create new validation wrapper tools.
1498
+ - VCM will not create the bootstrap commit for you.
1444
1499
 
1445
1500
  Final response:
1446
- Summarize files reviewed, files updated, generated artifacts, verified claims, inferred claims, unknowns, confirmation-needed items, and suggested validation commands.`;
1501
+ Summarize files reviewed, files updated, generated artifacts, commit hash, final git status, verified claims, inferred claims, unknowns, confirmation-needed items, and suggested validation commands.`;
1447
1502
  }
1448
1503
  export function createScriptFixedHarnessInstaller(scriptPath = path.resolve(process.cwd(), "scripts/install-vcm-harness.mjs")) {
1449
1504
  return async (repoRoot) => {
@@ -206,6 +206,7 @@ export function createSessionService(deps) {
206
206
  return updated;
207
207
  }
208
208
  async function launchProjectTranslatorSession(repoRoot, input, launchMode) {
209
+ const taskContext = await resolveProjectToolTaskContext(repoRoot, input, "Translator");
209
210
  const live = toRoleSessionRecordView(getRegisteredProjectTranslatorSession(deps.registry, deps.runtime), deps.runtime);
210
211
  if (live && live.status === "running") {
211
212
  return withHarnessRevisionView(repoRoot, live);
@@ -227,12 +228,10 @@ export function createSessionService(deps) {
227
228
  });
228
229
  }
229
230
  await deps.fs.ensureDir(resolveRepoPath(repoRoot, TRANSLATION_DIR));
230
- const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
231
- ? persisted.transcriptPath
232
- : claudeTranscriptPath(repoRoot, claudeSessionId);
231
+ const transcriptPath = claudeTranscriptPath(taskContext.taskRepoRoot, claudeSessionId);
233
232
  const startCommand = {
234
233
  ...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
235
- cwd: repoRoot
234
+ cwd: taskContext.taskRepoRoot
236
235
  };
237
236
  const runtimeSession = await deps.runtime.createSession({
238
237
  taskSlug: PROJECT_TRANSLATOR_SCOPE,
@@ -242,8 +241,8 @@ export function createSessionService(deps) {
242
241
  cwd: startCommand.cwd,
243
242
  env: {
244
243
  VCM_API_URL: deps.apiUrl,
245
- VCM_TASK_REPO_ROOT: repoRoot,
246
- VCM_TASK_SLUG: PROJECT_TRANSLATOR_SCOPE,
244
+ VCM_TASK_REPO_ROOT: taskContext.taskRepoRoot,
245
+ VCM_TASK_SLUG: taskContext.taskSlug,
247
246
  VCM_ROLE: TRANSLATOR_ROLE,
248
247
  VCM_SESSION_ID: claudeSessionId
249
248
  },
@@ -278,6 +277,7 @@ export function createSessionService(deps) {
278
277
  return withHarnessRevisionView(repoRoot, record);
279
278
  }
280
279
  async function launchProjectHarnessEngineerSession(repoRoot, input, launchMode) {
280
+ const taskContext = await resolveProjectToolTaskContext(repoRoot, input, "Harness Engineer");
281
281
  const live = toRoleSessionRecordView(getRegisteredProjectHarnessEngineerSession(deps.registry, deps.runtime), deps.runtime);
282
282
  if (live && live.status === "running") {
283
283
  return withHarnessRevisionView(repoRoot, live);
@@ -299,12 +299,10 @@ export function createSessionService(deps) {
299
299
  });
300
300
  }
301
301
  await deps.fs.ensureDir(resolveRepoPath(repoRoot, HARNESS_ENGINEER_DIR));
302
- const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
303
- ? persisted.transcriptPath
304
- : claudeTranscriptPath(repoRoot, claudeSessionId);
302
+ const transcriptPath = claudeTranscriptPath(taskContext.taskRepoRoot, claudeSessionId);
305
303
  const startCommand = {
306
304
  ...deps.claude.buildRoleStartCommand(HARNESS_ENGINEER_ROLE, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
307
- cwd: repoRoot
305
+ cwd: taskContext.taskRepoRoot
308
306
  };
309
307
  const runtimeSession = await deps.runtime.createSession({
310
308
  taskSlug: PROJECT_HARNESS_ENGINEER_SCOPE,
@@ -314,8 +312,8 @@ export function createSessionService(deps) {
314
312
  cwd: startCommand.cwd,
315
313
  env: {
316
314
  VCM_API_URL: deps.apiUrl,
317
- VCM_TASK_REPO_ROOT: repoRoot,
318
- VCM_TASK_SLUG: PROJECT_HARNESS_ENGINEER_SCOPE,
315
+ VCM_TASK_REPO_ROOT: taskContext.taskRepoRoot,
316
+ VCM_TASK_SLUG: taskContext.taskSlug,
319
317
  VCM_ROLE: HARNESS_ENGINEER_ROLE,
320
318
  VCM_SESSION_ID: claudeSessionId
321
319
  },
@@ -349,6 +347,22 @@ export function createSessionService(deps) {
349
347
  await persistHarnessEngineerSession(deps.fs, repoRoot, record);
350
348
  return withHarnessRevisionView(repoRoot, record);
351
349
  }
350
+ async function resolveProjectToolTaskContext(repoRoot, input, roleLabel) {
351
+ const taskSlug = input.taskSlug?.trim();
352
+ if (!taskSlug) {
353
+ throw new VcmError({
354
+ code: "PROJECT_TOOL_TASK_REQUIRED",
355
+ message: `${roleLabel} requires an active task worktree.`,
356
+ statusCode: 409,
357
+ hint: "Create or select a task, then start or resume this project tool session."
358
+ });
359
+ }
360
+ const task = await deps.taskService.loadTask(repoRoot, taskSlug);
361
+ return {
362
+ taskSlug: task.taskSlug,
363
+ taskRepoRoot: getTaskRuntimeRepoRoot(task)
364
+ };
365
+ }
352
366
  async function notifyHarnessUpdatedForSession(repoRoot, session) {
353
367
  const runtimeSession = deps.runtime.getSession(session.id);
354
368
  if (!runtimeSession || runtimeSession.status !== "running") {
@@ -515,6 +529,7 @@ export function createSessionService(deps) {
515
529
  }
516
530
  if (existing?.claudeSessionId) {
517
531
  return this.resumeProjectTranslatorSession(repoRoot, {
532
+ taskSlug: input.taskSlug,
518
533
  permissionMode: input.permissionMode ?? existing.permissionMode,
519
534
  model: input.model ?? existing.model,
520
535
  effort: input.effort ?? existing.effort,
@@ -611,6 +626,7 @@ export function createSessionService(deps) {
611
626
  }
612
627
  if (existing?.claudeSessionId) {
613
628
  return this.resumeProjectHarnessEngineerSession(repoRoot, {
629
+ taskSlug: input.taskSlug,
614
630
  permissionMode: input.permissionMode ?? existing.permissionMode,
615
631
  model: input.model ?? existing.model,
616
632
  effort: input.effort ?? existing.effort,
@@ -661,12 +677,10 @@ export function createSessionService(deps) {
661
677
  .then((session) => scopeProjectRoleSession(session, taskSlug));
662
678
  }
663
679
  if (role === TRANSLATOR_ROLE) {
664
- void taskSlug;
665
- return this.startProjectTranslatorSession(repoRoot, input);
680
+ return this.startProjectTranslatorSession(repoRoot, { ...input, taskSlug });
666
681
  }
667
682
  if (role === HARNESS_ENGINEER_ROLE) {
668
- void taskSlug;
669
- return this.startProjectHarnessEngineerSession(repoRoot, input);
683
+ return this.startProjectHarnessEngineerSession(repoRoot, { ...input, taskSlug });
670
684
  }
671
685
  return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
672
686
  },
@@ -676,12 +690,10 @@ export function createSessionService(deps) {
676
690
  .then((session) => scopeProjectRoleSession(session, taskSlug));
677
691
  }
678
692
  if (role === TRANSLATOR_ROLE) {
679
- void taskSlug;
680
- return this.resumeProjectTranslatorSession(repoRoot, input);
693
+ return this.resumeProjectTranslatorSession(repoRoot, { ...input, taskSlug });
681
694
  }
682
695
  if (role === HARNESS_ENGINEER_ROLE) {
683
- void taskSlug;
684
- return this.resumeProjectHarnessEngineerSession(repoRoot, input);
696
+ return this.resumeProjectHarnessEngineerSession(repoRoot, { ...input, taskSlug });
685
697
  }
686
698
  return launchRoleSession(repoRoot, taskSlug, role, input, "resume");
687
699
  },
@@ -732,12 +744,10 @@ export function createSessionService(deps) {
732
744
  return scopeProjectRoleSession(await launchProjectGateReviewerSession(repoRoot, input, "fresh", taskSlug), taskSlug);
733
745
  }
734
746
  if (role === TRANSLATOR_ROLE) {
735
- void taskSlug;
736
- return this.restartProjectTranslatorSession(repoRoot, input);
747
+ return this.restartProjectTranslatorSession(repoRoot, { ...input, taskSlug });
737
748
  }
738
749
  if (role === HARNESS_ENGINEER_ROLE) {
739
- void taskSlug;
740
- return this.restartProjectHarnessEngineerSession(repoRoot, input);
750
+ return this.restartProjectHarnessEngineerSession(repoRoot, { ...input, taskSlug });
741
751
  }
742
752
  const existing = await this.getRoleSession(repoRoot, taskSlug, role);
743
753
  if (!existing) {
@@ -904,6 +904,7 @@ export function createTranslationService(deps) {
904
904
  });
905
905
  }
906
906
  return deps.translationWorkerService.createConversationJob(input.repoRoot, {
907
+ taskSlug: input.taskSlug,
907
908
  direction: input.direction,
908
909
  sourceText: input.text,
909
910
  sourceLanguage: input.sourceLanguage,
@@ -207,7 +207,7 @@ export function createTranslationWorkerService(deps) {
207
207
  queue.updatedAt = next.updatedAt;
208
208
  await saveQueue(repoRoot, queue);
209
209
  }
210
- const session = await ensureTranslatorSession(repoRoot, next.targetLanguage);
210
+ const session = await ensureTranslatorSession(repoRoot, next.targetLanguage, next.taskSlug);
211
211
  await submitTerminalInput(deps.runtime, session.id, batch?.prompt ?? await buildQueuePrompt(repoRoot, next));
212
212
  const dispatchedAt = now();
213
213
  for (const item of batch?.items ?? [next]) {
@@ -255,7 +255,7 @@ export function createTranslationWorkerService(deps) {
255
255
  await saveQueue(repoRoot, queue);
256
256
  return { items: candidates, prompt };
257
257
  }
258
- async function ensureTranslatorSession(repoRoot, targetLanguage) {
258
+ async function ensureTranslatorSession(repoRoot, targetLanguage, taskSlug) {
259
259
  void targetLanguage;
260
260
  if (!deps.sessionService) {
261
261
  throw new VcmError({
@@ -265,6 +265,7 @@ export function createTranslationWorkerService(deps) {
265
265
  });
266
266
  }
267
267
  return deps.sessionService.ensureProjectTranslatorSession(repoRoot, {
268
+ taskSlug,
268
269
  model: "default",
269
270
  effort: "medium"
270
271
  });
@@ -873,6 +874,7 @@ export function createTranslationWorkerService(deps) {
873
874
  },
874
875
  async createFileJob(repoRoot, input) {
875
876
  await ensureLayout(repoRoot);
877
+ const taskSlug = requireTranslationTaskSlug(input.taskSlug);
876
878
  const sourcePath = normalizeRepoRelative(input.sourcePath);
877
879
  const absoluteSourcePath = resolveRepoPath(repoRoot, sourcePath);
878
880
  assertInsideRepo(repoRoot, absoluteSourcePath);
@@ -925,6 +927,7 @@ export function createTranslationWorkerService(deps) {
925
927
  type: input.force ? "force-retranslate" : "file",
926
928
  status: "queued",
927
929
  targetLanguage,
930
+ taskSlug,
928
931
  jobId,
929
932
  requestPath: job.requestPath,
930
933
  expectedResultPath: job.resultPath,
@@ -997,6 +1000,7 @@ export function createTranslationWorkerService(deps) {
997
1000
  },
998
1001
  async createBootstrapRun(repoRoot, input) {
999
1002
  await ensureLayout(repoRoot);
1003
+ const taskSlug = requireTranslationTaskSlug(input.taskSlug);
1000
1004
  const targetLanguage = input.targetLanguage.trim() || "zh-CN";
1001
1005
  const candidatePaths = (input.candidatePaths?.length
1002
1006
  ? input.candidatePaths
@@ -1020,6 +1024,7 @@ export function createTranslationWorkerService(deps) {
1020
1024
  type: "bootstrap",
1021
1025
  status: "queued",
1022
1026
  targetLanguage,
1027
+ taskSlug,
1023
1028
  jobId: runId,
1024
1029
  requestPath: run.requestPath,
1025
1030
  expectedResultPath: run.reportPath,
@@ -1054,6 +1059,7 @@ export function createTranslationWorkerService(deps) {
1054
1059
  },
1055
1060
  async createMemoryUpdate(repoRoot, input) {
1056
1061
  await ensureLayout(repoRoot);
1062
+ const taskSlug = requireTranslationTaskSlug(input.taskSlug);
1057
1063
  const targetLanguage = input.targetLanguage.trim() || "zh-CN";
1058
1064
  const timestamp = now();
1059
1065
  const runId = `memory-update-${Date.now()}-${createId().slice(0, 8)}`;
@@ -1065,6 +1071,7 @@ export function createTranslationWorkerService(deps) {
1065
1071
  type: "memory-update",
1066
1072
  status: "queued",
1067
1073
  targetLanguage,
1074
+ taskSlug,
1068
1075
  jobId: runId,
1069
1076
  requestPath
1070
1077
  });
@@ -1130,6 +1137,7 @@ export function createTranslationWorkerService(deps) {
1130
1137
  },
1131
1138
  async createConversationJob(repoRoot, input) {
1132
1139
  await ensureLayout(repoRoot);
1140
+ const taskSlug = requireTranslationTaskSlug(input.taskSlug);
1133
1141
  const sourceText = input.sourceText.trimEnd();
1134
1142
  if (!sourceText.trim()) {
1135
1143
  throw new VcmError({
@@ -1159,6 +1167,7 @@ export function createTranslationWorkerService(deps) {
1159
1167
  type: "conversation",
1160
1168
  status: "queued",
1161
1169
  targetLanguage,
1170
+ taskSlug,
1162
1171
  jobId,
1163
1172
  requestPath: job.requestPath,
1164
1173
  expectedResultPath: job.resultPath
@@ -1287,10 +1296,27 @@ export function createTranslationWorkerService(deps) {
1287
1296
  }
1288
1297
  },
1289
1298
  ensureTranslatorSession(repoRoot) {
1290
- return ensureTranslatorSession(repoRoot, "zh-CN");
1299
+ void repoRoot;
1300
+ throw new VcmError({
1301
+ code: "TRANSLATION_TASK_REQUIRED",
1302
+ message: "Translator requires an active task worktree.",
1303
+ statusCode: 409
1304
+ });
1291
1305
  }
1292
1306
  };
1293
1307
  }
1308
+ function requireTranslationTaskSlug(value) {
1309
+ const taskSlug = value?.trim();
1310
+ if (!taskSlug) {
1311
+ throw new VcmError({
1312
+ code: "TRANSLATION_TASK_REQUIRED",
1313
+ message: "Translation requires an active task worktree.",
1314
+ statusCode: 409,
1315
+ hint: "Create or select a task before starting translation."
1316
+ });
1317
+ }
1318
+ return taskSlug;
1319
+ }
1294
1320
  async function listBrowserEntries(repoRoot, fs, currentPath, limit) {
1295
1321
  const directoryPath = resolveRepoPath(repoRoot, currentPath);
1296
1322
  const names = (await fs.readDir(directoryPath)).filter((name) => !isIgnoredBrowserName(name));
@@ -1496,6 +1522,7 @@ function isQueueItem(value) {
1496
1522
  typeof candidate.type === "string" &&
1497
1523
  typeof candidate.status === "string" &&
1498
1524
  typeof candidate.targetLanguage === "string" &&
1525
+ typeof candidate.taskSlug === "string" &&
1499
1526
  typeof candidate.requestPath === "string";
1500
1527
  }
1501
1528
  function isFileJob(value) {
@@ -27,6 +27,7 @@ You are not part of the task workflow round state.
27
27
 
28
28
  - Propose harness changes as reviewable diffs.
29
29
  - Do not silently apply edits.
30
+ - During a VCM-managed bootstrap run, apply permitted bootstrap edits directly in the active task worktree and commit them yourself.
30
31
  - Do not overwrite VCM fixed managed blocks.
31
32
  - Keep project-specific customization outside VCM managed blocks.
32
33
  - If a fixed managed block appears wrong, draft a VCM issue instead of editing
@@ -34,6 +35,7 @@ You are not part of the task workflow round state.
34
35
  - Include affected files, impacted roles, session restart/reminder impact, and
35
36
  validation recommendations with every proposal.
36
37
  - Do not edit production source code as part of harness maintenance.
38
+ - VCM does not create Harness Engineer commits after your turn.
37
39
 
38
40
  ## VCM Feedback
39
41
 
@@ -19,7 +19,9 @@ This skill is an operating procedure. It does not replace the deterministic VCM
19
19
  3. Fill project context: add or update non-managed project facts in \`CLAUDE.md\` above the VCM managed block.
20
20
  4. Fill durable docs: update \`docs/ARCHITECTURE.md\`, module-level \`ARCHITECTURE.md\` files, and \`docs/TESTING.md\` with detailed project-specific content.
21
21
  5. Preserve user-authored content and VCM managed blocks.
22
- 6. Report evidence, unknowns, confirmation-needed areas, generation failures, and recommended deterministic VCM actions.
22
+ 6. Review \`git status\` and \`git diff\`.
23
+ 7. Stage only allowed bootstrap harness changes and create a commit in the active task worktree.
24
+ 8. Report evidence, commit hash, final git status, unknowns, confirmation-needed areas, generation failures, and recommended deterministic VCM actions.
23
25
 
24
26
  ## Typical Outputs
25
27
 
@@ -64,12 +66,20 @@ This skill is an operating procedure. It does not replace the deterministic VCM
64
66
  - Keep historical investigation details, superseded failures, temporary diagnostics, and per-task validation logs out of \`docs/TESTING.md\`.
65
67
  - Keep reviewer ownership of validation strategy and testing documentation clear.
66
68
 
69
+ ### Commit
70
+
71
+ - Create the bootstrap commit yourself after the allowed files are updated.
72
+ - Do not include product source, product tests, package manifests, lockfiles, deployment config, secrets, or VCM managed-block changes in the commit.
73
+ - VCM will not commit bootstrap changes after your turn.
74
+
67
75
  ## Final Summary
68
76
 
69
77
  Include:
70
78
 
71
79
  - files reviewed
72
80
  - files drafted or updated
81
+ - commit hash
82
+ - final git status
73
83
  - verified claims
74
84
  - inferred claims
75
85
  - unknowns