vibe-coding-master 0.7.48 → 0.7.49

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.
package/README.md CHANGED
@@ -221,8 +221,15 @@ Controls:
221
221
  - `Start`: start a new Claude Code role session
222
222
  - `Resume`: resume a saved Claude Code session
223
223
  - `Restart`: stop current process and start fresh
224
+ - `Restart With Context`: restart a VCM workflow role and continue from its current task artifacts
224
225
  - `Stop`: stop the embedded terminal process
225
226
 
227
+ `Restart With Context` is available only for Project Manager, Architect, Coder,
228
+ Tester, and Reviewer. It reuses their existing handoffs, workflow state, Gate
229
+ requests, worktree, and Git state; it does not create a separate context file.
230
+ Translator and Harness Engineer are tool Agents and do not use this workflow-role
231
+ recovery path.
232
+
226
233
  Permission modes:
227
234
 
228
235
  ```text
@@ -1,4 +1,4 @@
1
- import { isDispatchableRole, isRoleName } from "../../shared/constants.js";
1
+ import { isDispatchableRole, isRoleName, isVcmRoleName } from "../../shared/constants.js";
2
2
  import { VcmError } from "../errors.js";
3
3
  export function registerSessionRoutes(app, deps) {
4
4
  app.get("/api/tasks/:taskSlug/sessions", async (request) => {
@@ -8,6 +8,9 @@ export function registerSessionRoutes(app, deps) {
8
8
  app.post("/api/tasks/:taskSlug/sessions/:role/start", async (request) => {
9
9
  const project = await requireCurrentProject(deps.projectService);
10
10
  const role = parseRole(request.params.role);
11
+ if (isVcmRoleName(role)) {
12
+ await deps.roleContextRestartService.clearRole(project.repoRoot, request.params.taskSlug, role);
13
+ }
11
14
  return deps.sessionService.startRoleSession(project.repoRoot, request.params.taskSlug, role, request.body);
12
15
  });
13
16
  app.post("/api/tasks/:taskSlug/sessions/architect/restart-after-planning", async (request, reply) => {
@@ -18,14 +21,38 @@ export function registerSessionRoutes(app, deps) {
18
21
  app.post("/api/tasks/:taskSlug/sessions/:role/stop", async (request) => {
19
22
  const project = await requireCurrentProject(deps.projectService);
20
23
  const role = parseRole(request.params.role);
24
+ if (isVcmRoleName(role)) {
25
+ await deps.roleContextRestartService.clearRole(project.repoRoot, request.params.taskSlug, role);
26
+ }
21
27
  const session = await deps.sessionService.stopRoleSession(project.repoRoot, request.params.taskSlug, role);
22
28
  await deps.translationService.stopSession(session.id);
23
29
  deps.roundService.stopSession(session.id);
24
30
  return session;
25
31
  });
32
+ app.post("/api/tasks/:taskSlug/sessions/:role/restart-with-context", async (request) => {
33
+ const project = await requireCurrentProject(deps.projectService);
34
+ const role = parseVcmRole(request.params.role);
35
+ if (role === "architect" && deps.architectRestartService.getState(project.repoRoot, request.params.taskSlug)) {
36
+ throw new VcmError({
37
+ code: "ARCHITECT_RESTART_CONFLICT",
38
+ message: "Architect already has a pending post-planning restart.",
39
+ statusCode: 409
40
+ });
41
+ }
42
+ const existing = await deps.sessionService.getRoleSession(project.repoRoot, request.params.taskSlug, role);
43
+ await deps.sessionService.assertModelLaunchReady(request.body?.model ?? existing?.model);
44
+ if (existing) {
45
+ await deps.translationService.stopSession(existing.id, { clearCache: true });
46
+ deps.roundService.stopSession(existing.id);
47
+ }
48
+ return deps.roleContextRestartService.restart(project.repoRoot, request.params.taskSlug, role, request.body);
49
+ });
26
50
  app.post("/api/tasks/:taskSlug/sessions/:role/restart", async (request) => {
27
51
  const project = await requireCurrentProject(deps.projectService);
28
52
  const role = parseRole(request.params.role);
53
+ if (isVcmRoleName(role)) {
54
+ await deps.roleContextRestartService.clearRole(project.repoRoot, request.params.taskSlug, role);
55
+ }
29
56
  const existing = await deps.sessionService.getRoleSession(project.repoRoot, request.params.taskSlug, role);
30
57
  await deps.sessionService.assertModelLaunchReady(request.body?.model ?? existing?.model);
31
58
  if (existing) {
@@ -37,6 +64,9 @@ export function registerSessionRoutes(app, deps) {
37
64
  app.post("/api/tasks/:taskSlug/sessions/:role/resume", async (request) => {
38
65
  const project = await requireCurrentProject(deps.projectService);
39
66
  const role = parseRole(request.params.role);
67
+ if (isVcmRoleName(role)) {
68
+ await deps.roleContextRestartService.clearRole(project.repoRoot, request.params.taskSlug, role);
69
+ }
40
70
  return deps.sessionService.resumeRoleSession(project.repoRoot, request.params.taskSlug, role, request.body);
41
71
  });
42
72
  app.post("/api/tasks/:taskSlug/sessions/:role/notify-harness", async (request) => {
@@ -70,6 +100,16 @@ function parseRole(role) {
70
100
  }
71
101
  return role;
72
102
  }
103
+ function parseVcmRole(role) {
104
+ if (!isVcmRoleName(role)) {
105
+ throw new VcmError({
106
+ code: "ROLE_CONTEXT_RESTART_UNSUPPORTED",
107
+ message: `Restart With Context is available only for VCM workflow roles: ${role}`,
108
+ statusCode: 400
109
+ });
110
+ }
111
+ return role;
112
+ }
73
113
  async function requireCurrentProject(projectService) {
74
114
  const project = await projectService.getCurrentProject();
75
115
  if (!project) {
@@ -13,6 +13,7 @@ import { createAppSettingsService } from "./services/app-settings-service.js";
13
13
  import { createCodexBridgeIntegrationService } from "./services/codex-bridge-integration-service.js";
14
14
  import { createAutoMemoryService } from "./services/auto-memory-service.js";
15
15
  import { createArchitectRestartService } from "./services/architect-restart-service.js";
16
+ import { createRoleContextRestartService } from "./services/role-context-restart-service.js";
16
17
  import { createRoleStallDetectorService } from "./services/role-stall-detector-service.js";
17
18
  import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
18
19
  import { createGateReviewService } from "./services/gate-review-service.js";
@@ -144,7 +145,8 @@ export async function createServer(deps, options = {}) {
144
145
  commandDispatcher: deps.commandDispatcher,
145
146
  translationService: deps.translationService,
146
147
  roundService: deps.roundService,
147
- architectRestartService: deps.architectRestartService
148
+ architectRestartService: deps.architectRestartService,
149
+ roleContextRestartService: deps.roleContextRestartService
148
150
  });
149
151
  registerArtifactRoutes(app, {
150
152
  projectService: deps.projectService,
@@ -301,6 +303,12 @@ export function createDefaultServerDeps(options = {}) {
301
303
  sessionService,
302
304
  appSettings
303
305
  });
306
+ const roleContextRestartService = createRoleContextRestartService({
307
+ fs,
308
+ projectService,
309
+ taskService,
310
+ sessionService
311
+ });
304
312
  const messageService = createMessageService({
305
313
  fs,
306
314
  runtime,
@@ -378,7 +386,8 @@ export function createDefaultServerDeps(options = {}) {
378
386
  roundService,
379
387
  projectService,
380
388
  taskWorkflowService,
381
- architectRestartService
389
+ architectRestartService,
390
+ roleContextRestartService
382
391
  });
383
392
  const gatewayService = createGatewayService({
384
393
  fs,
@@ -401,7 +410,8 @@ export function createDefaultServerDeps(options = {}) {
401
410
  projectService,
402
411
  taskService,
403
412
  translationWorkerService,
404
- architectRestartService
413
+ architectRestartService,
414
+ roleContextRestartService
405
415
  });
406
416
  const claudeHookService = createClaudeHookService({
407
417
  projectService,
@@ -419,6 +429,7 @@ export function createDefaultServerDeps(options = {}) {
419
429
  jobGuard: createJobGuardService(),
420
430
  translationWorkerService,
421
431
  architectRestartService,
432
+ roleContextRestartService,
422
433
  roleStallDetector,
423
434
  workflowControlService
424
435
  });
@@ -466,6 +477,7 @@ export function createDefaultServerDeps(options = {}) {
466
477
  taskCloseService,
467
478
  taskWorkflowService,
468
479
  architectRestartService,
480
+ roleContextRestartService,
469
481
  sessionService,
470
482
  artifactService,
471
483
  harnessService,
@@ -238,6 +238,9 @@ export function createClaudeHookService(deps) {
238
238
  if (input.role === "architect") {
239
239
  await deps.architectRestartService?.recordReplacementPromptSubmitted(context.project.repoRoot, context.taskSlug, session.id);
240
240
  }
241
+ if (isVcmRoleName(input.role)) {
242
+ await deps.roleContextRestartService?.recordPromptSubmitted(context.project.repoRoot, context.taskSlug, input.role, session.id);
243
+ }
241
244
  const boundToTask = await isHookSessionBoundToTask(context, input.role);
242
245
  if (boundToTask) {
243
246
  deps.jobGuard?.notePromptSubmitted({
@@ -0,0 +1,240 @@
1
+ import path from "node:path";
2
+ import { VCM_ROLE_NAMES } from "../../shared/constants.js";
3
+ import { resolveRepoPath } from "../adapters/filesystem.js";
4
+ import { toVcmError, VcmError } from "../errors.js";
5
+ import { getTaskRuntimeRepoRoot } from "./task-service.js";
6
+ const STATE_DIR = "restart-with-context";
7
+ export function createRoleContextRestartService(deps) {
8
+ const pending = new Map();
9
+ const operations = new Map();
10
+ const now = deps.now ?? (() => new Date().toISOString());
11
+ return {
12
+ async restart(repoRoot, taskSlug, role, input = {}) {
13
+ return withLock(restartKey(repoRoot, taskSlug, role), async () => {
14
+ const current = await deps.sessionService.getRoleSession(repoRoot, taskSlug, role);
15
+ if (!current) {
16
+ throw new VcmError({
17
+ code: "SESSION_MISSING",
18
+ message: `${role} session has not been started.`,
19
+ statusCode: 404
20
+ });
21
+ }
22
+ const state = {
23
+ version: 1,
24
+ taskSlug,
25
+ role,
26
+ sourceSessionId: current.id,
27
+ status: "launching",
28
+ permissionMode: input.permissionMode ?? current.permissionMode,
29
+ model: input.model ?? current.model,
30
+ effort: input.effort ?? current.effort,
31
+ cols: input.cols,
32
+ rows: input.rows,
33
+ updatedAt: now()
34
+ };
35
+ await persist(repoRoot, state);
36
+ return launch(repoRoot, state);
37
+ });
38
+ },
39
+ async recoverTask(repoRoot, taskSlug) {
40
+ for (const role of VCM_ROLE_NAMES) {
41
+ await withLock(restartKey(repoRoot, taskSlug, role), async () => {
42
+ const state = await load(repoRoot, taskSlug, role);
43
+ if (!state) {
44
+ return;
45
+ }
46
+ const session = await deps.sessionService.getRoleSession(repoRoot, taskSlug, role);
47
+ if (state.replacementSessionId
48
+ && session?.id === state.replacementSessionId
49
+ && session.claudeSessionId) {
50
+ await remove(repoRoot, state);
51
+ return;
52
+ }
53
+ state.status = "launching";
54
+ state.error = undefined;
55
+ state.updatedAt = now();
56
+ await persist(repoRoot, state);
57
+ await launch(repoRoot, state);
58
+ });
59
+ }
60
+ },
61
+ async recordPromptSubmitted(repoRoot, taskSlug, role, sessionId) {
62
+ await withLock(restartKey(repoRoot, taskSlug, role), async () => {
63
+ const state = pending.get(restartKey(repoRoot, taskSlug, role))
64
+ ?? await load(repoRoot, taskSlug, role);
65
+ if (!state
66
+ || state.status !== "awaiting_prompt_confirmation"
67
+ || state.replacementSessionId !== sessionId) {
68
+ return;
69
+ }
70
+ await remove(repoRoot, state);
71
+ });
72
+ },
73
+ async clearRole(repoRoot, taskSlug, role) {
74
+ await withLock(restartKey(repoRoot, taskSlug, role), async () => {
75
+ pending.delete(restartKey(repoRoot, taskSlug, role));
76
+ const target = await statePath(repoRoot, taskSlug, role);
77
+ if (await deps.fs.pathExists(target)) {
78
+ await deps.fs.removePath?.(target, { force: true });
79
+ }
80
+ });
81
+ },
82
+ async clear(repoRoot, taskSlug) {
83
+ for (const role of VCM_ROLE_NAMES) {
84
+ pending.delete(restartKey(repoRoot, taskSlug, role));
85
+ }
86
+ const target = await stateDirectory(repoRoot, taskSlug);
87
+ if (await deps.fs.pathExists(target)) {
88
+ await deps.fs.removePath?.(target, { recursive: true, force: true });
89
+ }
90
+ }
91
+ };
92
+ async function launch(repoRoot, state) {
93
+ const prompts = await buildPrompts(repoRoot, state.taskSlug, state.role);
94
+ try {
95
+ const replacement = await deps.sessionService.restartRoleSessionForContext(repoRoot, state.taskSlug, state.role, {
96
+ permissionMode: state.permissionMode,
97
+ model: state.model,
98
+ effort: state.effort,
99
+ cols: state.cols,
100
+ rows: state.rows,
101
+ appendSystemPrompt: prompts.system
102
+ });
103
+ state.replacementSessionId = replacement.id;
104
+ state.status = "awaiting_prompt_confirmation";
105
+ state.updatedAt = now();
106
+ await persist(repoRoot, state);
107
+ await deps.sessionService.submitRolePrompt(repoRoot, state.taskSlug, state.role, replacement.id, prompts.user);
108
+ return replacement;
109
+ }
110
+ catch (error) {
111
+ const normalized = toVcmError(error);
112
+ state.status = "blocked";
113
+ state.error = {
114
+ code: normalized.code,
115
+ message: normalized.message
116
+ };
117
+ state.updatedAt = now();
118
+ await persist(repoRoot, state);
119
+ throw error;
120
+ }
121
+ }
122
+ async function buildPrompts(repoRoot, taskSlug, role) {
123
+ const [config, task] = await Promise.all([
124
+ deps.projectService.loadConfig(repoRoot),
125
+ deps.taskService.loadTask(repoRoot, taskSlug)
126
+ ]);
127
+ const handoffDir = task.handoffDir;
128
+ const stateRoot = config.stateRoot;
129
+ const files = roleContextFiles(role, handoffDir, stateRoot);
130
+ return {
131
+ system: [
132
+ `This fresh ${role} session continues the current VCM task after Restart With Context.`,
133
+ "",
134
+ "Before continuing, read the existing files or directories that apply:",
135
+ ...files.map((file) => `- ${file}`),
136
+ "- the current worktree and Git state",
137
+ "",
138
+ "Treat the current artifacts and worktree as the source of truth. Continue the accepted assignment from the recorded current state. Do not repeat completed work or rely on the previous Session transcript."
139
+ ].join("\n"),
140
+ user: `[VCM RESTART WITH CONTEXT]\nRestore the current ${role} work from the listed task artifacts and continue the accepted assignment.`
141
+ };
142
+ }
143
+ async function persist(repoRoot, state) {
144
+ await deps.fs.writeJsonAtomic(await statePath(repoRoot, state.taskSlug, state.role), state);
145
+ pending.set(restartKey(repoRoot, state.taskSlug, state.role), state);
146
+ }
147
+ async function load(repoRoot, taskSlug, role) {
148
+ const target = await statePath(repoRoot, taskSlug, role);
149
+ if (!(await deps.fs.pathExists(target))) {
150
+ return undefined;
151
+ }
152
+ const state = await deps.fs.readJson(target);
153
+ if (state.version !== 1
154
+ || state.taskSlug !== taskSlug
155
+ || state.role !== role
156
+ || !["launching", "awaiting_prompt_confirmation", "blocked"].includes(state.status)) {
157
+ throw new VcmError({
158
+ code: "ROLE_CONTEXT_RESTART_STATE_INVALID",
159
+ message: `Restart With Context state is invalid for ${role} in task ${taskSlug}.`,
160
+ statusCode: 500
161
+ });
162
+ }
163
+ pending.set(restartKey(repoRoot, taskSlug, role), state);
164
+ return state;
165
+ }
166
+ async function remove(repoRoot, state) {
167
+ pending.delete(restartKey(repoRoot, state.taskSlug, state.role));
168
+ const target = await statePath(repoRoot, state.taskSlug, state.role);
169
+ if (await deps.fs.pathExists(target)) {
170
+ await deps.fs.removePath?.(target, { force: true });
171
+ }
172
+ }
173
+ async function stateDirectory(repoRoot, taskSlug) {
174
+ const [config, task] = await Promise.all([
175
+ deps.projectService.loadConfig(repoRoot),
176
+ deps.taskService.loadTask(repoRoot, taskSlug)
177
+ ]);
178
+ return resolveRepoPath(getTaskRuntimeRepoRoot(task), path.posix.join(config.stateRoot, STATE_DIR));
179
+ }
180
+ async function statePath(repoRoot, taskSlug, role) {
181
+ return path.join(await stateDirectory(repoRoot, taskSlug), `${role}.json`);
182
+ }
183
+ async function withLock(key, operation) {
184
+ const previous = operations.get(key) ?? Promise.resolve();
185
+ const current = previous.catch(() => undefined).then(operation);
186
+ operations.set(key, current);
187
+ try {
188
+ return await current;
189
+ }
190
+ finally {
191
+ if (operations.get(key) === current) {
192
+ operations.delete(key);
193
+ }
194
+ }
195
+ }
196
+ }
197
+ function roleContextFiles(role, handoffDir, stateRoot) {
198
+ switch (role) {
199
+ case "project-manager":
200
+ return [
201
+ `${handoffDir}/workflow-progress.md`,
202
+ `${stateRoot}/workflow/state.json`,
203
+ `${stateRoot}/workflow-control.json`,
204
+ `${handoffDir}/messages/`
205
+ ];
206
+ case "architect":
207
+ return [
208
+ `${handoffDir}/role-commands/architect.md`,
209
+ `${handoffDir}/architecture-brief.md`,
210
+ `${handoffDir}/architecture-evidence.md`,
211
+ `${handoffDir}/planning-progress.md`,
212
+ `${handoffDir}/architecture-plan.md`,
213
+ `${handoffDir}/architect-debug.md`,
214
+ `${handoffDir}/architecture-diagnosis.md`,
215
+ ".ai/vcm/gate-reviews/index.json"
216
+ ];
217
+ case "coder":
218
+ return [
219
+ `${handoffDir}/role-commands/coder.md`,
220
+ `${handoffDir}/architecture-plan.md`,
221
+ `${handoffDir}/coder-completion.md`,
222
+ ".ai/vcm/coder-workers/"
223
+ ];
224
+ case "tester":
225
+ return [
226
+ `${handoffDir}/role-commands/tester.md`,
227
+ `${handoffDir}/architecture-plan.md`,
228
+ `${handoffDir}/coder-completion.md`,
229
+ `${handoffDir}/test-report.md`
230
+ ];
231
+ case "reviewer":
232
+ return [
233
+ ".ai/vcm/gate-reviews/index.json",
234
+ ".ai/vcm/gate-reviews/requests/"
235
+ ];
236
+ }
237
+ }
238
+ function restartKey(repoRoot, taskSlug, role) {
239
+ return `${repoRoot}\0${taskSlug}\0${role}`;
240
+ }
@@ -36,6 +36,7 @@ export function createRuntimeRecoveryService(deps) {
36
36
  await recoverGateReview(taskRepoRoot, recoveredAt, context);
37
37
  await cleanupCoderWorkers(taskRepoRoot, context);
38
38
  await deps.architectRestartService?.recoverTask(repoRoot, task.taskSlug);
39
+ await deps.roleContextRestartService?.recoverTask(repoRoot, task.taskSlug);
39
40
  if ((roundRecovered || task.status === "running") && !hasLiveTaskSession(task.taskSlug)) {
40
41
  await deps.taskService.updateTaskStatus(repoRoot, task.taskSlug, "stopped");
41
42
  }
@@ -45,7 +45,7 @@ export function createSessionService(deps) {
45
45
  harnessOutdated: sessionRevision < currentRevision
46
46
  };
47
47
  }
48
- async function launchRoleSession(repoRoot, taskSlug, role, input, launchMode) {
48
+ async function launchRoleSession(repoRoot, taskSlug, role, input, launchMode, options = {}) {
49
49
  const config = await deps.projectService.loadConfig(repoRoot);
50
50
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
51
51
  const taskRepoRoot = getTaskRuntimeRepoRoot(task);
@@ -145,7 +145,7 @@ export function createSessionService(deps) {
145
145
  };
146
146
  deps.registry.upsert(record);
147
147
  await persistRoleSessionRecord(deps.fs, repoRoot, taskRepoRoot, config.stateRoot, record);
148
- if (role === "project-manager") {
148
+ if (role === "project-manager" && options.restoreProjectManagerContext !== false) {
149
149
  await restoreProjectManagerWorkflowContext(record, taskRepoRoot, config.stateRoot);
150
150
  }
151
151
  return withHarnessRevisionView(taskRepoRoot, record);
@@ -1110,6 +1110,43 @@ export function createSessionService(deps) {
1110
1110
  await clearPersistedRoleSessionRecord(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, taskSlug, role, now());
1111
1111
  return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
1112
1112
  },
1113
+ async restartRoleSessionForContext(repoRoot, taskSlug, role, input = {}) {
1114
+ const existing = await this.getRoleSession(repoRoot, taskSlug, role);
1115
+ if (!existing) {
1116
+ return launchRoleSession(repoRoot, taskSlug, role, input, "fresh", {
1117
+ restoreProjectManagerContext: false
1118
+ });
1119
+ }
1120
+ await getModelLaunchEnvironment(normalizeClaudeModel(input.model ?? existing.model));
1121
+ if (deps.runtime.getSession(existing.id)) {
1122
+ await deps.runtime.stop(existing.id);
1123
+ }
1124
+ deps.registry.remove(existing.id);
1125
+ const config = await deps.projectService.loadConfig(repoRoot);
1126
+ const task = await deps.taskService.loadTask(repoRoot, taskSlug);
1127
+ await clearPersistedRoleSessionRecord(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, taskSlug, role, now());
1128
+ return launchRoleSession(repoRoot, taskSlug, role, input, "fresh", {
1129
+ restoreProjectManagerContext: false
1130
+ });
1131
+ },
1132
+ async submitRolePrompt(repoRoot, taskSlug, role, expectedSessionId, prompt) {
1133
+ const session = await this.getRoleSession(repoRoot, taskSlug, role);
1134
+ if (!session || session.id !== expectedSessionId || session.status !== "running") {
1135
+ throw new VcmError({
1136
+ code: "ROLE_SESSION_NOT_RUNNING",
1137
+ message: `${role} replacement session is not running.`,
1138
+ statusCode: 409
1139
+ });
1140
+ }
1141
+ if ((await waitForSessionInputReady(session.id)) === "exited") {
1142
+ throw new VcmError({
1143
+ code: "ROLE_SESSION_START_FAILED",
1144
+ message: `${role} replacement session exited before it could accept the recovery prompt.`,
1145
+ statusCode: 409
1146
+ });
1147
+ }
1148
+ await submitTerminalInput(deps.runtime, session.id, prompt);
1149
+ },
1113
1150
  async getRoleSession(repoRoot, taskSlug, role) {
1114
1151
  const config = await deps.projectService.loadConfig(repoRoot);
1115
1152
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
@@ -8,6 +8,9 @@ export function createTaskCloseService(deps) {
8
8
  if (deps.architectRestartService) {
9
9
  await bestEffort("Unable to clear Architect restart state", () => deps.architectRestartService.clear(repoRoot, taskSlug), warnings);
10
10
  }
11
+ if (deps.roleContextRestartService) {
12
+ await bestEffort("Unable to clear Restart With Context state", () => deps.roleContextRestartService.clear(repoRoot, taskSlug), warnings);
13
+ }
11
14
  await stopTaskRoleSessions(repoRoot, taskSlug, warnings);
12
15
  await bestEffort("Unable to stop task translation runtime", () => deps.translationService.stopTask(getTaskRuntimeRepoRoot(task), taskSlug, { clearCache: true }), warnings);
13
16
  await bestEffort("Unable to clear task round runtime", () => deps.roundService.stopTask(taskSlug), warnings);
@@ -85,6 +85,7 @@ ${renderRoleMemoryRules("coder")}
85
85
 
86
86
  - In Docs-Only Flow, commit the documentation changes and submit \`.ai/vcm/handoffs/docs-update-report.md\` through \`vcm-artifact\` with the decision, changed and reviewed documents, evidence, checks, commit, and remaining documentation issues. Do not submit \`coder-completion.md\` for Docs-Only work.
87
87
  - Submit \`.ai/vcm/handoffs/coder-completion.md\` before routing back to project-manager: write a candidate outside \`.ai/vcm\`, then run \`.ai/tools/vcm-artifact coder-completion --file <candidate> --mode draft|final\`. This file is the complete, self-contained current implementation completion evidence, not a log. Each revision must restate every Scaffold Manifest disposition, changed file, helper, deviation, generated-context result, baseline-test change, L0/L1 command and result, worker result, commit, and objective failure still needed to review the current implementation without a prior revision. Replace stale content instead of appending history.
88
+ - Maintain an incomplete \`coder-completion.md\` draft while implementation is in progress. Replace it after each completed module or worker result and after compile, L0, or L1 results so it always states completed work, remaining scaffold items, current validation, and current commits. The final submission replaces this draft.
88
89
  - After committing the actual implementation state and before submitting a final \`coder-completion.md\`, run \`.ai/tools/check-scaffold-ledger --mode completion --completion <candidate>\`; submit the candidate only after it passes. An incomplete draft does not use completion mode.
89
90
  - \`coder-completion.md\` must include \`Decision: ready_for_review | incomplete | failed\`.
90
91
  - \`coder-completion.md\` must report every Scaffold Manifest item disposition in the fixed Scaffold Completion table, plus changed files, private helpers added, manifest deviations as report-only facts, generated context status, baseline tests added or updated, L0/L1 commands and results, worker commits and integration status when workers were used, and compile/typecheck or L0/L1 failures.
@@ -151,6 +151,7 @@ Coverage Gap.
151
151
  ### Outputs
152
152
 
153
153
  - Write \`.ai/vcm/handoffs/test-report.md\` with \`Test Result: pass|fail|incomplete\`, evidence reviewed, tests added or updated, coverage mapping, validation progress, commands run or checked, validation results, test-infrastructure status and evidence, failed expectations, reproduction steps, skipped checks with reasons, coverage gaps, blocking validation issues, and user approval evidence.
154
+ - Maintain an incomplete \`test-report.md\` draft while validation is in progress. Replace it after each completed validation level or long-running check and after changing tests or fixtures so it always states completed validation, remaining validation, current results, and current commits. The final submission replaces this draft.
154
155
  - \`test-report.md\` must include this test-infrastructure section:
155
156
 
156
157
  \`\`\`md