vibe-coding-master 0.7.5 → 0.7.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.
Files changed (55) hide show
  1. package/README.md +19 -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 +50 -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 +19 -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-BO2AuF-q.js +97 -0
  49. package/dist-frontend/assets/index-C2etsYlK.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
@@ -253,8 +253,10 @@ Automatic mode:
253
253
  - the UI switches to the target role before dispatch
254
254
  - Claude Code hooks confirm whether the prompt was accepted
255
255
 
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.
256
+ If the flow stops, VCM always shows a blocking pause alert. `Pause alert sound`
257
+ only controls the looping sound. Enabling Gateway turns that preference off once;
258
+ it can be turned back on afterward. A new Gateway command closes an open pause
259
+ alert after its instruction is successfully submitted to PM.
258
260
 
259
261
  ## Gate Review Gates
260
262
 
@@ -280,8 +282,9 @@ findings back to the responsible role.
280
282
 
281
283
  Conversation translation is controlled from the sidebar `Translation` section.
282
284
 
283
- VCM uses the project-scoped Translator role and Claude transcript JSONL files,
284
- not raw terminal text.
285
+ VCM uses a task-scoped Translator role and Claude transcript JSONL files, not
286
+ raw terminal text. Translation memory and completed file translations remain
287
+ project-level durable data.
285
288
 
286
289
  Common controls:
287
290
 
@@ -385,8 +388,9 @@ Use it to:
385
388
  - merge task harness commits back to the connected repository branch when
386
389
  appropriate
387
390
 
388
- Harness Engineer is project-scoped and resumable. When it performs task work,
389
- VCM runs it from the active task worktree.
391
+ Harness Engineer is task-scoped and runs from the active task worktree. A new
392
+ task receives its own Harness Engineer session. Durable memory is versioned with
393
+ the project harness files.
390
394
 
391
395
  ### Auto Memory
392
396
 
@@ -396,10 +400,12 @@ Tester, and an enabled Gate Reviewer submit proposals in sequence through
396
400
  `vcm-propose-memory`. Harness Engineer verifies and consolidates them before VCM
397
401
  applies the result. Roles cannot edit active memory directly.
398
402
 
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.
403
+ Shared memory is stored in the root `CLAUDE.md` `<VCM-memory>` block. Role memory
404
+ is stored in the matching `.claude/agents/*.md` block. VCM changes only block
405
+ contents and creates a dedicated commit in the active task worktree. Harness
406
+ Studio shows current memory and task-local applied history. Memory is applied
407
+ before user review; while the task worktree remains available, the user can edit
408
+ current memory or revert a recorded change through another commit.
403
409
 
404
410
  Post-task processing is ordered by the backend:
405
411
 
@@ -420,12 +426,9 @@ can continue.
420
426
 
421
427
  `Close Task` is destructive.
422
428
 
423
- It stops task-owned VCM role sessions and removes task-owned worktree/branch
424
- state. Commit or preserve anything important before closing.
425
-
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.
429
+ It stops every running session owned by the task, including Translator and
430
+ Harness Engineer, then removes task-owned worktree/branch state. Commit or
431
+ preserve anything important before closing.
429
432
 
430
433
  ## Troubleshooting
431
434
 
@@ -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,55 +76,66 @@ 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);
@@ -183,6 +194,18 @@ export function registerHarnessRoutes(app, deps) {
183
194
  return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
184
195
  });
185
196
  }
197
+ function requireTaskSlug(value, roleLabel) {
198
+ const taskSlug = value?.trim();
199
+ if (!taskSlug) {
200
+ throw new VcmError({
201
+ code: "TOOL_SESSION_TASK_REQUIRED",
202
+ message: `${roleLabel} requires an active task.`,
203
+ statusCode: 409,
204
+ hint: "Create or select a task before using this session."
205
+ });
206
+ }
207
+ return taskSlug;
208
+ }
186
209
  function degradedHarnessStatus(error) {
187
210
  return {
188
211
  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) {
@@ -11,18 +11,21 @@ import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTransl
11
11
  import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
12
12
  import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
13
13
  import { renderGitignoreHarnessRules } from "../templates/harness/gitignore.js";
14
+ import { ensureVcmMemoryBlock } from "../templates/harness/memory-block.js";
14
15
  import { renderLegacyProjectCodingStandardsTemplate, renderProjectCodingStandardsProjectSection, renderProjectCodingStandardsRules } from "../templates/harness/project-coding-standards.js";
15
16
  import { renderProjectGlossaryTemplate } from "../templates/harness/project-glossary.js";
16
17
  import { renderLegacyProjectKnownIssuesTemplate, renderProjectKnownIssuesRules, renderProjectKnownIssuesSection } from "../templates/harness/project-known-issues.js";
17
18
  import { renderProjectManagerHarnessRules } from "../templates/harness/project-manager-agent.js";
18
19
  import { renderPullRequestTemplateHarnessRules } from "../templates/harness/pull-request-template.js";
19
20
  import { renderTesterHarnessRules } from "../templates/harness/tester-agent.js";
21
+ import { renderVcmArchitectureInterviewSkillRules } from "../templates/harness/vcm-architecture-interview-skill.js";
20
22
  import { renderVcmFinalAcceptanceSkillRules } from "../templates/harness/vcm-final-acceptance-skill.js";
21
23
  import { renderVcmHarnessBootstrapSkillRules } from "../templates/harness/vcm-harness-bootstrap-skill.js";
22
24
  import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/vcm-long-running-validation-skill.js";
23
25
  import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propose-memory-skill.js";
24
26
  import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
25
27
  import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
28
+ import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
26
29
  import { readVcmPackageVersion } from "../app-version.js";
27
30
  const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
28
31
  const APP_ROOT = path.resolve(CLI_DIR, "../../..");
@@ -69,10 +72,10 @@ const AGENT_FRONTMATTER = {
69
72
  tools: "Read, Grep, Glob, Bash, Write"
70
73
  },
71
74
  translator: {
72
- description: "VCM project translation tool role for conversation translation, file translation, bootstrap, and memory updates."
75
+ description: "VCM task-scoped translation tool role for conversation translation, file translation, bootstrap, and memory updates."
73
76
  },
74
77
  "harness-engineer": {
75
- description: "VCM project-scoped harness maintenance role for harness diagnosis, diff proposals, and VCM issue drafts."
78
+ description: "VCM task-scoped harness maintenance role for harness diagnosis, diff proposals, and VCM issue drafts."
76
79
  },
77
80
  "vcm-coder-worker": {
78
81
  description: "Bounded VCM implementation worker for assigned modules, files, and VCM:CODE markers from Coder.",
@@ -86,6 +89,7 @@ const MANAGED_FILES = [
86
89
  commentStyle: "html",
87
90
  category: "root-rules",
88
91
  blankLineBeforeEnd: true,
92
+ memoryBlock: true,
89
93
  content: renderRootClaudeHarnessRules()
90
94
  },
91
95
  {
@@ -119,6 +123,7 @@ const MANAGED_FILES = [
119
123
  agentName: "project-manager",
120
124
  commentStyle: "html",
121
125
  category: "core-agent",
126
+ memoryBlock: true,
122
127
  content: renderProjectManagerHarnessRules()
123
128
  },
124
129
  {
@@ -128,6 +133,7 @@ const MANAGED_FILES = [
128
133
  commentStyle: "html",
129
134
  category: "core-agent",
130
135
  blankLineBeforeEnd: true,
136
+ memoryBlock: true,
131
137
  content: renderArchitectHarnessRules()
132
138
  },
133
139
  {
@@ -136,6 +142,7 @@ const MANAGED_FILES = [
136
142
  agentName: "coder",
137
143
  commentStyle: "html",
138
144
  category: "core-agent",
145
+ memoryBlock: true,
139
146
  content: renderCoderHarnessRules()
140
147
  },
141
148
  {
@@ -144,6 +151,7 @@ const MANAGED_FILES = [
144
151
  agentName: "tester",
145
152
  commentStyle: "html",
146
153
  category: "core-agent",
154
+ memoryBlock: true,
147
155
  content: renderTesterHarnessRules()
148
156
  },
149
157
  {
@@ -159,6 +167,7 @@ const MANAGED_FILES = [
159
167
  agentName: "gate-reviewer",
160
168
  commentStyle: "html",
161
169
  category: "gate-reviewer-agent",
170
+ memoryBlock: true,
162
171
  content: renderGateReviewerAgentRules()
163
172
  },
164
173
  {
@@ -175,6 +184,7 @@ const MANAGED_FILES = [
175
184
  agentName: "harness-engineer",
176
185
  commentStyle: "html",
177
186
  category: "agent-harness-engineer",
187
+ memoryBlock: true,
178
188
  content: renderHarnessEngineerHarnessRules()
179
189
  },
180
190
  {
@@ -201,6 +211,12 @@ const DURABLE_DOC_TEMPLATES = [
201
211
  },
202
212
  ];
203
213
  const WHOLE_FILES = [
214
+ {
215
+ path: ".ai/tools/check-durable-docs",
216
+ category: "durable-docs-tool",
217
+ mode: 0o755,
218
+ templatePath: "scripts/harness-tools/check-durable-docs"
219
+ },
204
220
  {
205
221
  path: ".ai/tools/generate-module-index",
206
222
  category: "generated-context-tool",
@@ -213,6 +229,12 @@ const WHOLE_FILES = [
213
229
  mode: 0o755,
214
230
  templatePath: "scripts/harness-tools/generate-public-surface"
215
231
  },
232
+ {
233
+ path: ".claude/skills/vcm-architecture-interview/SKILL.md",
234
+ category: "skill",
235
+ mode: 0o644,
236
+ content: renderSkillFile("VCM Architecture Interview Skill", "vcm-architecture-interview", "Use when Architect must confirm user-owned behavior and contract decisions before architecture planning.", renderVcmArchitectureInterviewSkillRules())
237
+ },
216
238
  {
217
239
  path: ".claude/skills/vcm-final-acceptance/SKILL.md",
218
240
  category: "skill",
@@ -237,6 +259,12 @@ const WHOLE_FILES = [
237
259
  mode: 0o644,
238
260
  content: renderSkillFile("VCM Route Message Skill", "vcm-route-message", "Use when project-manager dispatches a VCM role or when a VCM role reports a question, result, blocker, or finding back to project-manager.", renderVcmRouteMessageSkillRules())
239
261
  },
262
+ {
263
+ path: ".claude/skills/vcm-task-state/SKILL.md",
264
+ category: "skill",
265
+ mode: 0o644,
266
+ content: renderSkillFile("VCM Task State Skill", "vcm-task-state", "Use only as project-manager to declare the current task workflow checkpoint to VCM.", renderVcmTaskStateSkillRules())
267
+ },
240
268
  {
241
269
  path: ".claude/skills/vcm-gate-review/SKILL.md",
242
270
  category: "skill",
@@ -261,6 +289,12 @@ const WHOLE_FILES = [
261
289
  mode: 0o755,
262
290
  content: renderRequestGateReviewTool()
263
291
  },
292
+ {
293
+ path: ".ai/tools/update-task-state",
294
+ category: "runtime-tool",
295
+ mode: 0o755,
296
+ content: renderUpdateTaskStateTool()
297
+ },
264
298
  {
265
299
  path: ".ai/tools/run-long-check",
266
300
  category: "runtime-tool",
@@ -481,6 +515,7 @@ function fixedDirectories() {
481
515
  ".claude/skills/vcm-harness-bootstrap/",
482
516
  ".claude/skills/vcm-long-running-validation/",
483
517
  ".claude/skills/vcm-route-message/",
518
+ ".claude/skills/vcm-task-state/",
484
519
  ".claude/skills/vcm-gate-review/",
485
520
  ".claude/skills/vcm-report-harness-issue/",
486
521
  ".claude/skills/vcm-propose-memory/",
@@ -541,6 +576,9 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
541
576
  ?? `${currentContent.trimEnd()}\n\n${block}\n`;
542
577
  }
543
578
  }
579
+ if (definition.memoryBlock) {
580
+ nextContent = ensureVcmMemoryBlock(nextContent);
581
+ }
544
582
  await writeIfChanged({
545
583
  targetPath,
546
584
  relativePath: definition.path,