vibe-coding-master 0.7.5 → 0.7.7

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.
Files changed (55) hide show
  1. package/README.md +40 -16
  2. package/dist/backend/adapters/git-adapter.js +15 -0
  3. package/dist/backend/api/artifact-routes.js +3 -0
  4. package/dist/backend/api/harness-routes.js +57 -27
  5. package/dist/backend/api/runtime-state-routes.js +7 -3
  6. package/dist/backend/api/task-routes.js +36 -4
  7. package/dist/backend/api/translation-routes.js +11 -2
  8. package/dist/backend/api/translation-worker-routes.js +37 -9
  9. package/dist/backend/cli/install-vcm-harness.js +40 -2
  10. package/dist/backend/gateway/gateway-service.js +34 -17
  11. package/dist/backend/server.js +12 -3
  12. package/dist/backend/services/artifact-service.js +4 -1
  13. package/dist/backend/services/auto-memory-service.js +156 -81
  14. package/dist/backend/services/claude-hook-service.js +50 -35
  15. package/dist/backend/services/command-dispatcher.js +1 -1
  16. package/dist/backend/services/gate-review-service.js +44 -0
  17. package/dist/backend/services/harness-feedback-service.js +47 -8
  18. package/dist/backend/services/harness-service.js +112 -34
  19. package/dist/backend/services/message-service.js +39 -2
  20. package/dist/backend/services/round-service.js +10 -121
  21. package/dist/backend/services/runtime-coordinator-service.js +18 -10
  22. package/dist/backend/services/runtime-recovery-service.js +1 -2
  23. package/dist/backend/services/session-service.js +36 -98
  24. package/dist/backend/services/status-service.js +1 -0
  25. package/dist/backend/services/task-close-service.js +12 -27
  26. package/dist/backend/services/task-workflow-service.js +228 -0
  27. package/dist/backend/services/translation-worker-service.js +14 -7
  28. package/dist/backend/templates/handoff.js +40 -1
  29. package/dist/backend/templates/harness/architect-agent.js +67 -19
  30. package/dist/backend/templates/harness/claude-root.js +25 -29
  31. package/dist/backend/templates/harness/gate-review.js +26 -13
  32. package/dist/backend/templates/harness/harness-engineer-agent.js +8 -8
  33. package/dist/backend/templates/harness/memory-block.js +69 -0
  34. package/dist/backend/templates/harness/project-known-issues.js +1 -0
  35. package/dist/backend/templates/harness/project-manager-agent.js +211 -73
  36. package/dist/backend/templates/harness/role-memory.js +9 -12
  37. package/dist/backend/templates/harness/tester-agent.js +4 -1
  38. package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +82 -0
  39. package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +4 -3
  40. package/dist/backend/templates/harness/vcm-harness-bootstrap-skill.js +14 -3
  41. package/dist/backend/templates/harness/vcm-propose-memory-skill.js +2 -2
  42. package/dist/backend/templates/harness/vcm-route-message-skill.js +5 -0
  43. package/dist/backend/templates/harness/vcm-task-state-skill.js +110 -0
  44. package/dist/backend/templates/message-envelope.js +1 -1
  45. package/dist/shared/constants.js +0 -10
  46. package/dist/shared/types/workflow.js +1 -0
  47. package/dist/shared/validation/artifact-check.js +20 -0
  48. package/dist-frontend/assets/index-42EpETgd.js +97 -0
  49. package/dist-frontend/assets/index-D65x2x0F.css +32 -0
  50. package/dist-frontend/index.html +2 -2
  51. package/package.json +1 -1
  52. package/scripts/harness-tools/check-durable-docs +298 -0
  53. package/scripts/verify-package.mjs +1 -0
  54. package/dist-frontend/assets/index-DCb-S6Ls.css +0 -32
  55. package/dist-frontend/assets/index-NTlycxx9.js +0 -97
package/README.md CHANGED
@@ -181,6 +181,12 @@ By default:
181
181
  Roles for the same task share the same task worktree. VCM does not create one
182
182
  worktree per role.
183
183
 
184
+ Project Manager may record an advisory task checkpoint under
185
+ `.ai/vcm/workflow/state.json`. VCM restores and displays this context after a
186
+ restart, but it does not infer transitions or choose the next role. Current
187
+ artifacts, Gate Review state, Round/Turn state, and the role rules remain
188
+ authoritative.
189
+
184
190
  Typical flow:
185
191
 
186
192
  ```text
@@ -253,8 +259,10 @@ Automatic mode:
253
259
  - the UI switches to the target role before dispatch
254
260
  - Claude Code hooks confirm whether the prompt was accepted
255
261
 
256
- If the flow stops, VCM shows a pause alert. The `Pause alert sound` setting only
257
- controls the sound; the stopped-flow state is still shown in the UI.
262
+ If the flow stops, VCM always shows a blocking pause alert. `Pause alert sound`
263
+ only controls the looping sound. Enabling Gateway turns that preference off once;
264
+ it can be turned back on afterward. A new Gateway command closes an open pause
265
+ alert after its instruction is successfully submitted to PM.
258
266
 
259
267
  ## Gate Review Gates
260
268
 
@@ -280,8 +288,9 @@ findings back to the responsible role.
280
288
 
281
289
  Conversation translation is controlled from the sidebar `Translation` section.
282
290
 
283
- VCM uses the project-scoped Translator role and Claude transcript JSONL files,
284
- not raw terminal text.
291
+ VCM uses a task-scoped Translator role and Claude transcript JSONL files, not
292
+ raw terminal text. Translation memory and completed file translations remain
293
+ project-level durable data.
285
294
 
286
295
  Common controls:
287
296
 
@@ -385,8 +394,9 @@ Use it to:
385
394
  - merge task harness commits back to the connected repository branch when
386
395
  appropriate
387
396
 
388
- Harness Engineer is project-scoped and resumable. When it performs task work,
389
- VCM runs it from the active task worktree.
397
+ Harness Engineer is task-scoped and runs from the active task worktree. A new
398
+ task receives its own Harness Engineer session. Durable memory is versioned with
399
+ the project harness files.
390
400
 
391
401
  ### Auto Memory
392
402
 
@@ -396,20 +406,28 @@ Tester, and an enabled Gate Reviewer submit proposals in sequence through
396
406
  `vcm-propose-memory`. Harness Engineer verifies and consolidates them before VCM
397
407
  applies the result. Roles cannot edit active memory directly.
398
408
 
399
- Canonical memory is stored under the base repository's `.ai/vcm/memory/`.
400
- Harness Studio shows current memory and task-local applied history. Memory is
401
- applied before user review; while the task worktree remains available, the user
402
- can edit current memory or revert a recorded change.
409
+ Shared memory is stored in the root `CLAUDE.md` `<VCM-memory>` block. Role memory
410
+ is stored in the matching `.claude/agents/*.md` block. VCM changes only block
411
+ contents and creates a dedicated commit in the active task worktree. Harness
412
+ Studio shows current memory and task-local applied history. Memory is applied
413
+ before user review; while the task worktree remains available, the user can edit
414
+ current memory or revert a recorded change through another commit.
403
415
 
404
416
  Post-task processing is ordered by the backend:
405
417
 
406
418
  ```text
407
419
  Final Acceptance
408
420
  -> Review Task Harness
409
- -> Memory proposals and Harness Engineer review, when Auto Memory is enabled
421
+ -> Workflow-role memory proposals, when Auto Memory is enabled
422
+ -> Harness Engineer memory review, when Auto Memory is enabled
410
423
  -> Task Harness Retrospective
411
424
  ```
412
425
 
426
+ Memory proposal prompts sent to Project Manager, Architect, Coder, Tester, and
427
+ an enabled Gate Reviewer use their normal task sessions and participate in
428
+ Round/Turn tracking. Harness Engineer review and retrospective work remain tool
429
+ role activity and do not participate in Round completion.
430
+
413
431
  When Auto Memory is disabled, Review Task Harness does not collect proposals or
414
432
  ask Harness Engineer to update memory. When enabled, both automatic and manual
415
433
  review requests complete the memory phase before retrospective analysis. A
@@ -420,12 +438,14 @@ can continue.
420
438
 
421
439
  `Close Task` is destructive.
422
440
 
423
- It stops task-owned VCM role sessions and removes task-owned worktree/branch
424
- state. Commit or preserve anything important before closing.
441
+ It stops every running session owned by the task, including Translator and
442
+ Harness Engineer, then removes task-owned worktree/branch state. Commit or
443
+ preserve anything important before closing.
425
444
 
426
- Project-scoped tool sessions such as Translator and Harness Engineer are not
427
- ordinary task deliverables. VCM may move them to a safe cwd when task context
428
- changes.
445
+ Uncommitted changes, unmerged commits, and cleanup failures are reported as
446
+ warnings; they do not block logical task closure. VCM may discard the task
447
+ worktree and task branch even when they contain commits that are not present on
448
+ the connected repository branch.
429
449
 
430
450
  ## Troubleshooting
431
451
 
@@ -514,7 +534,11 @@ npm start
514
534
  ## Documentation
515
535
 
516
536
  - `docs/ARCHITECTURE.md`: repository architecture
537
+ - `docs/CODING_STANDARDS.md`: shared implementation and test standards
538
+ - `docs/GLOSSARY.md`: allowed durable abbreviations
517
539
  - `docs/TESTING.md`: validation strategy
540
+ - `docs/known-issues.md`: current unresolved durable issues
541
+ - `src/backend/gateway/ARCHITECTURE.md`: mobile gateway sub-area architecture
518
542
  - `docs/vcm-cc-best-practices.md`: current VCM Claude Code harness practice
519
543
  - `docs/v0.5-custom-workflow-plan.md`: deferred custom workflow proposal
520
544
  - `docs/cc-best-practices.md`: archived generic Claude Code harness notes
@@ -293,6 +293,21 @@ export function createGitAdapter(runner) {
293
293
  }
294
294
  return this.getHeadCommit(repoRoot);
295
295
  },
296
+ async commitPaths(repoRoot, message, paths) {
297
+ if (paths.length === 0) {
298
+ return this.getHeadCommit(repoRoot);
299
+ }
300
+ const result = await runGit(runner, repoRoot, ["commit", "--only", "-m", message, "--", ...paths]);
301
+ if (result.exitCode !== 0) {
302
+ throw new VcmError({
303
+ code: "GIT_COMMIT_FAILED",
304
+ message: "Unable to commit VCM memory changes.",
305
+ statusCode: 409,
306
+ hint: result.stderr || result.stdout
307
+ });
308
+ }
309
+ return this.getHeadCommit(repoRoot);
310
+ },
296
311
  async createWorktree(input) {
297
312
  const result = await runGit(runner, input.repoRoot, [
298
313
  "worktree",
@@ -64,6 +64,9 @@ function parseDispatchableRole(role) {
64
64
  return role;
65
65
  }
66
66
  function artifactNameToPath(paths, artifactName) {
67
+ if (artifactName === "architecture-brief.md") {
68
+ return paths.architectureBriefPath;
69
+ }
67
70
  if (artifactName === "architecture-plan.md") {
68
71
  return paths.architecturePlanPath;
69
72
  }
@@ -55,7 +55,7 @@ export function registerHarnessRoutes(app, deps) {
55
55
  app.get("/api/projects/harness/bootstrap", async (request) => {
56
56
  const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
57
57
  try {
58
- return await deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath);
58
+ return await deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath, task.taskSlug);
59
59
  }
60
60
  catch (error) {
61
61
  if (isOpenFileLimitError(error)) {
@@ -76,61 +76,79 @@ export function registerHarnessRoutes(app, deps) {
76
76
  await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
77
77
  return deps.harnessService.restartHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
78
78
  });
79
- app.post("/api/projects/harness/bootstrap/stop", async () => {
80
- const project = await requireCurrentProject(deps.projectService);
81
- return deps.harnessService.stopHarnessBootstrap(project.repoRoot);
79
+ app.post("/api/projects/harness/bootstrap/stop", async (request) => {
80
+ const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
81
+ return deps.harnessService.stopHarnessBootstrap(project.repoRoot, task.worktreePath, task.taskSlug);
82
82
  });
83
83
  app.post("/api/projects/harness/bootstrap/run", async (request) => {
84
84
  const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
85
85
  await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
86
86
  await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
87
- return deps.harnessService.runHarnessBootstrap(project.repoRoot, task.worktreePath);
87
+ return deps.harnessService.runHarnessBootstrap(project.repoRoot, task.worktreePath, task.taskSlug);
88
88
  });
89
- app.get("/api/projects/harness/engineer/session", async () => {
89
+ app.get("/api/projects/harness/engineer/session", async (request) => {
90
90
  const project = await requireCurrentProject(deps.projectService);
91
- return (await deps.sessionService.getProjectHarnessEngineerSession(project.repoRoot)) ?? null;
91
+ const taskSlug = request.query.taskSlug?.trim();
92
+ if (!taskSlug) {
93
+ return null;
94
+ }
95
+ return (await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "harness-engineer")) ?? null;
92
96
  });
93
97
  app.post("/api/projects/harness/engineer/session/ensure", async (request) => {
94
98
  const project = await requireCurrentProject(deps.projectService);
95
- return deps.sessionService.ensureProjectHarnessEngineerSession(project.repoRoot, request.body);
99
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
100
+ const existing = await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "harness-engineer");
101
+ if (existing?.status === "running") {
102
+ return existing;
103
+ }
104
+ if (existing?.claudeSessionId) {
105
+ return deps.sessionService.resumeRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
106
+ }
107
+ return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
96
108
  });
97
109
  app.post("/api/projects/harness/engineer/session/start", async (request) => {
98
110
  const project = await requireCurrentProject(deps.projectService);
99
- if (request.body?.taskSlug) {
100
- const task = await deps.taskService.loadTask(project.repoRoot, request.body.taskSlug);
101
- await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
102
- }
103
- return deps.sessionService.startProjectHarnessEngineerSession(project.repoRoot, request.body);
111
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
112
+ const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
113
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
114
+ return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
104
115
  });
105
116
  app.post("/api/projects/harness/engineer/session/resume", async (request) => {
106
117
  const project = await requireCurrentProject(deps.projectService);
107
- if (request.body?.taskSlug) {
108
- const task = await deps.taskService.loadTask(project.repoRoot, request.body.taskSlug);
109
- await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
110
- }
111
- return deps.sessionService.resumeProjectHarnessEngineerSession(project.repoRoot, request.body);
118
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
119
+ const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
120
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
121
+ return deps.sessionService.resumeRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
112
122
  });
113
123
  app.post("/api/projects/harness/engineer/session/restart", async (request) => {
114
124
  const project = await requireCurrentProject(deps.projectService);
115
- if (request.body?.taskSlug) {
116
- const task = await deps.taskService.loadTask(project.repoRoot, request.body.taskSlug);
117
- await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
118
- }
119
- return deps.sessionService.restartProjectHarnessEngineerSession(project.repoRoot, request.body);
125
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
126
+ const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
127
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
128
+ return deps.sessionService.restartRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
120
129
  });
121
- app.post("/api/projects/harness/engineer/session/stop", async () => {
130
+ app.post("/api/projects/harness/engineer/session/stop", async (request) => {
122
131
  const project = await requireCurrentProject(deps.projectService);
123
- return deps.sessionService.stopProjectHarnessEngineerSession(project.repoRoot);
132
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
133
+ return deps.sessionService.stopRoleSession(project.repoRoot, taskSlug, "harness-engineer");
124
134
  });
125
- app.post("/api/projects/harness/engineer/session/notify-harness", async () => {
135
+ app.post("/api/projects/harness/engineer/session/notify-harness", async (request) => {
126
136
  const project = await requireCurrentProject(deps.projectService);
127
- return deps.sessionService.notifyProjectHarnessEngineerHarnessUpdated(project.repoRoot);
137
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
138
+ return deps.sessionService.notifyRoleHarnessUpdated(project.repoRoot, taskSlug, "harness-engineer");
128
139
  });
129
140
  app.get("/api/projects/harness/feedback", async (request) => {
130
141
  const project = await requireCurrentProject(deps.projectService);
131
142
  const taskSlug = await normalizeOptionalTaskSlug(deps, project.repoRoot, request.query.taskSlug);
132
143
  return deps.harnessFeedbackService.getState(project.repoRoot, taskSlug);
133
144
  });
145
+ app.post("/api/projects/harness/feedback/send", async (request) => {
146
+ const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
147
+ return deps.harnessFeedbackService.sendPendingFeedback(project.repoRoot, {
148
+ taskSlug: task.taskSlug,
149
+ feedbackPath: request.body?.feedbackPath ?? ""
150
+ });
151
+ });
134
152
  app.post("/api/projects/harness/task-retrospective", async (request) => {
135
153
  const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
136
154
  const trigger = request.body?.trigger === "auto" ? "auto" : "manual";
@@ -183,6 +201,18 @@ export function registerHarnessRoutes(app, deps) {
183
201
  return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
184
202
  });
185
203
  }
204
+ function requireTaskSlug(value, roleLabel) {
205
+ const taskSlug = value?.trim();
206
+ if (!taskSlug) {
207
+ throw new VcmError({
208
+ code: "TOOL_SESSION_TASK_REQUIRED",
209
+ message: `${roleLabel} requires an active task.`,
210
+ statusCode: 409,
211
+ hint: "Create or select a task before using this session."
212
+ });
213
+ }
214
+ return taskSlug;
215
+ }
186
216
  function degradedHarnessStatus(error) {
187
217
  return {
188
218
  version: 1,
@@ -5,9 +5,13 @@ export function registerRuntimeStateRoutes(app, deps) {
5
5
  const taskSlug = request.query.taskSlug?.trim();
6
6
  const coordinated = await deps.runtimeCoordinator.reconcileProject(project.repoRoot, { taskSlug });
7
7
  const [translatorSession, translationState, harnessEngineerSession, harnessFeedbackState] = await Promise.all([
8
- deps.sessionService.getProjectTranslatorSession(project.repoRoot).then((session) => session ?? null),
8
+ taskSlug
9
+ ? deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "translator").then((session) => session ?? null)
10
+ : Promise.resolve(null),
9
11
  deps.translationWorkerService.getState(project.repoRoot, { visibility: "public" }),
10
- deps.sessionService.getProjectHarnessEngineerSession(project.repoRoot).then((session) => session ?? null),
12
+ taskSlug
13
+ ? deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "harness-engineer").then((session) => session ?? null)
14
+ : Promise.resolve(null),
11
15
  deps.harnessFeedbackService.getState(project.repoRoot, taskSlug || undefined)
12
16
  ]);
13
17
  if (!taskSlug) {
@@ -26,7 +30,7 @@ export function registerRuntimeStateRoutes(app, deps) {
26
30
  const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
27
31
  const [harnessStatus, harnessBootstrapStatus, autoMemoryState] = await Promise.all([
28
32
  withOpenFileLimitFallback(() => deps.harnessService.getHarnessStatus(task.worktreePath), (error) => degradedHarnessStatus(error)),
29
- withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath), (error) => degradedBootstrapStatus(error)),
33
+ withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath, task.taskSlug), (error) => degradedBootstrapStatus(error)),
30
34
  deps.autoMemoryService.getState(project.repoRoot, task.worktreePath)
31
35
  ]);
32
36
  return {
@@ -45,7 +45,7 @@ export function registerTaskRoutes(app, deps) {
45
45
  handoffDir: task.handoffDir,
46
46
  taskSlug
47
47
  };
48
- const [taskStatus, messages, orchestration, roundState] = await Promise.all([
48
+ const [taskStatus, messages, orchestration, roundState, workflowState] = await Promise.all([
49
49
  withOpenFileLimitFallback(() => deps.statusService.getTaskStatus(project.repoRoot, taskSlug), (error) => degradedTaskStatus(project.repoRoot, taskSlug, error)),
50
50
  withOpenFileLimitFallback(() => deps.messageService.listMessages(context), () => []),
51
51
  withOpenFileLimitFallback(() => deps.messageService.getOrchestrationState(context), () => ({
@@ -58,13 +58,19 @@ export function registerTaskRoutes(app, deps) {
58
58
  stateRepoRoot: taskRepoRoot,
59
59
  stateRoot: config.stateRoot,
60
60
  taskSlug
61
- }), () => degradedRoundState(taskSlug))
61
+ }), () => degradedRoundState(taskSlug)),
62
+ deps.taskWorkflowService?.getState({
63
+ taskRepoRoot,
64
+ stateRoot: config.stateRoot,
65
+ taskSlug
66
+ }) ?? Promise.resolve(degradedWorkflowState(taskSlug))
62
67
  ]);
63
68
  return {
64
69
  taskStatus,
65
70
  messages,
66
71
  orchestration,
67
- roundState
72
+ roundState,
73
+ workflowState
68
74
  };
69
75
  }
70
76
  catch (error) {
@@ -77,12 +83,26 @@ export function registerTaskRoutes(app, deps) {
77
83
  mode: "auto",
78
84
  updatedAt: new Date().toISOString()
79
85
  },
80
- roundState: degradedRoundState(taskSlug)
86
+ roundState: degradedRoundState(taskSlug),
87
+ workflowState: degradedWorkflowState(taskSlug)
81
88
  };
82
89
  }
83
90
  throw error;
84
91
  }
85
92
  });
93
+ app.post("/api/tasks/:taskSlug/workflow-state", async (request) => {
94
+ const project = await requireCurrentProject(deps.projectService);
95
+ const config = await deps.projectService.loadConfig(project.repoRoot);
96
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
97
+ if (!deps.taskWorkflowService) {
98
+ return degradedWorkflowState(task.taskSlug);
99
+ }
100
+ return deps.taskWorkflowService.declare({
101
+ taskRepoRoot: getTaskRuntimeRepoRoot(task),
102
+ stateRoot: config.stateRoot,
103
+ taskSlug: task.taskSlug
104
+ }, request.body ?? {});
105
+ });
86
106
  app.post("/api/tasks/:taskSlug/one-click-start", async (request) => {
87
107
  const project = await requireCurrentProject(deps.projectService);
88
108
  return deps.taskLaunchService.startTaskRoleSessions(project.repoRoot, {
@@ -143,6 +163,17 @@ function degradedRoundState(taskSlug) {
143
163
  updatedAt: new Date().toISOString()
144
164
  };
145
165
  }
166
+ function degradedWorkflowState(taskSlug) {
167
+ return {
168
+ version: 1,
169
+ taskSlug,
170
+ revision: 0,
171
+ declared: null,
172
+ lastDispatch: null,
173
+ warnings: ["Task workflow state is temporarily unavailable."],
174
+ updatedAt: new Date().toISOString()
175
+ };
176
+ }
146
177
  async function withOpenFileLimitFallback(run, fallback) {
147
178
  try {
148
179
  return await run();
@@ -164,6 +195,7 @@ function degradedArtifactSummary(handoffDir) {
164
195
  messagesDir,
165
196
  roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
166
197
  messageRoutePaths: {},
198
+ architectureBriefPath: `${handoffDir}/architecture-brief.md`,
167
199
  architecturePlanPath: `${handoffDir}/architecture-plan.md`,
168
200
  knownIssuesPath: `${handoffDir}/known-issues.md`,
169
201
  testReportPath: `${handoffDir}/test-report.md`,
@@ -86,9 +86,18 @@ export function registerTranslationRoutes(app, deps) {
86
86
  await deps.translationService.stopSession(request.params.sessionId);
87
87
  return { ok: true };
88
88
  });
89
- app.post("/api/projects/translation/session/notify-harness", async () => {
89
+ app.post("/api/projects/translation/session/notify-harness", async (request) => {
90
90
  const project = await requireCurrentProject(deps.projectService);
91
- return deps.sessionService.notifyProjectTranslatorHarnessUpdated(project.repoRoot);
91
+ const taskSlug = request.body?.taskSlug?.trim();
92
+ if (!taskSlug) {
93
+ throw new VcmError({
94
+ code: "TOOL_SESSION_TASK_REQUIRED",
95
+ message: "Translator requires an active task.",
96
+ statusCode: 409,
97
+ hint: "Create or select a task before notifying Translator."
98
+ });
99
+ }
100
+ return deps.sessionService.notifyRoleHarnessUpdated(project.repoRoot, taskSlug, "translator");
92
101
  });
93
102
  app.post("/api/translation/sessions/:sessionId/retry/:translationId", async (request) => {
94
103
  await requireCurrentProject(deps.projectService);
@@ -6,33 +6,49 @@ export function registerTranslationWorkerRoutes(app, deps) {
6
6
  visibility: "public"
7
7
  });
8
8
  });
9
- app.get("/api/translation/session", async () => {
9
+ app.get("/api/translation/session", async (request) => {
10
10
  const project = await requireCurrentProject(deps.projectService);
11
- return (await deps.sessionService.getProjectTranslatorSession(project.repoRoot)) ?? null;
11
+ const taskSlug = request.query.taskSlug?.trim();
12
+ if (!taskSlug) {
13
+ return null;
14
+ }
15
+ return (await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "translator")) ?? null;
12
16
  });
13
17
  app.post("/api/translation/session/ensure", async (request) => {
14
18
  const project = await requireCurrentProject(deps.projectService);
15
- return deps.sessionService.ensureProjectTranslatorSession(project.repoRoot, request.body);
19
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
20
+ const existing = await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "translator");
21
+ if (existing?.status === "running") {
22
+ return existing;
23
+ }
24
+ if (existing?.claudeSessionId) {
25
+ return deps.sessionService.resumeRoleSession(project.repoRoot, taskSlug, "translator", request.body);
26
+ }
27
+ return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "translator", request.body);
16
28
  });
17
29
  app.post("/api/translation/session/start", async (request) => {
18
30
  const project = await requireCurrentProject(deps.projectService);
19
- return deps.sessionService.startProjectTranslatorSession(project.repoRoot, request.body);
31
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
32
+ return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "translator", request.body);
20
33
  });
21
34
  app.post("/api/translation/session/resume", async (request) => {
22
35
  const project = await requireCurrentProject(deps.projectService);
23
- return deps.sessionService.resumeProjectTranslatorSession(project.repoRoot, request.body);
36
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
37
+ return deps.sessionService.resumeRoleSession(project.repoRoot, taskSlug, "translator", request.body);
24
38
  });
25
39
  app.post("/api/translation/session/restart", async (request) => {
26
40
  const project = await requireCurrentProject(deps.projectService);
27
- const existing = await deps.sessionService.getProjectTranslatorSession(project.repoRoot);
41
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
42
+ const existing = await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "translator");
28
43
  if (existing) {
29
44
  await deps.translationService.stopSession(existing.id, { clearCache: true });
30
45
  }
31
- return deps.sessionService.restartProjectTranslatorSession(project.repoRoot, request.body);
46
+ return deps.sessionService.restartRoleSession(project.repoRoot, taskSlug, "translator", request.body);
32
47
  });
33
- app.post("/api/translation/session/stop", async () => {
48
+ app.post("/api/translation/session/stop", async (request) => {
34
49
  const project = await requireCurrentProject(deps.projectService);
35
- const session = await deps.sessionService.stopProjectTranslatorSession(project.repoRoot);
50
+ const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
51
+ const session = await deps.sessionService.stopRoleSession(project.repoRoot, taskSlug, "translator");
36
52
  await deps.translationService.stopSession(session.id);
37
53
  return session;
38
54
  });
@@ -73,6 +89,18 @@ export function registerTranslationWorkerRoutes(app, deps) {
73
89
  return deps.translationWorkerService.promoteFileJob(project.repoRoot, request.params.jobId, targetPath);
74
90
  });
75
91
  }
92
+ function requireTaskSlug(value, roleLabel) {
93
+ const taskSlug = value?.trim();
94
+ if (!taskSlug) {
95
+ throw new VcmError({
96
+ code: "TOOL_SESSION_TASK_REQUIRED",
97
+ message: `${roleLabel} requires an active task.`,
98
+ statusCode: 409,
99
+ hint: "Create or select a task before using this session."
100
+ });
101
+ }
102
+ return taskSlug;
103
+ }
76
104
  async function requireCurrentProject(projectService) {
77
105
  const project = await projectService.getCurrentProject();
78
106
  if (!project) {