vibe-coding-master 0.3.21 → 0.3.23

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.
@@ -12,13 +12,9 @@ const CODEX_REVIEW_DIR = ".ai/vcm/codex-reviews";
12
12
  const CODEX_CONFIG_PATH = ".ai/codex/config.toml";
13
13
  const CODEX_TRANSLATOR_DIR = ".ai/codex-translator";
14
14
  const CODEX_TRANSLATION_DIR = ".ai/vcm/translations";
15
- const CODEX_TRANSLATOR_SESSION_PATH = ".ai/vcm/translations/runtime/session.json";
16
- const LEGACY_CODEX_TRANSLATOR_SESSION_PATH = ".ai/vcm/translations/session.json";
17
- const CODEX_TRANSLATOR_LOG_PATHS = [
18
- ".ai/vcm/translations/runtime/codex-translator.log",
19
- ".ai/vcm/translations/codex-translator.log"
20
- ];
15
+ const CODEX_TRANSLATOR_SESSION_PATH = ".ai/vcm/translations/session.json";
21
16
  const CODEX_TRANSLATOR_CONFIG_PATH = ".ai/codex-translator/config.toml";
17
+ const PROJECT_TRANSLATOR_SCOPE = "__project__";
22
18
  export function createSessionService(deps) {
23
19
  const now = deps.now ?? (() => new Date().toISOString());
24
20
  async function launchRoleSession(repoRoot, taskSlug, role, input, launchMode) {
@@ -42,9 +38,6 @@ export function createSessionService(deps) {
42
38
  const effort = isCodexRole
43
39
  ? normalizeCodexEffort(input.effort ?? persisted?.effort)
44
40
  : normalizeClaudeEffort(input.effort ?? persisted?.effort);
45
- if (isTranslator) {
46
- await cleanupCodexTranslatorLogs(deps.fs, repoRoot);
47
- }
48
41
  const claudeSessionId = launchMode === "resume"
49
42
  ? persisted?.claudeSessionId
50
43
  : randomUUID();
@@ -112,14 +105,167 @@ export function createSessionService(deps) {
112
105
  await persistRoleSessionRecord(deps.fs, repoRoot, taskRepoRoot, config.stateRoot, record);
113
106
  return record;
114
107
  }
108
+ async function launchProjectTranslatorSession(repoRoot, input, launchMode) {
109
+ const live = toRoleSessionRecordView(getRegisteredProjectTranslatorSession(deps.registry, deps.runtime), deps.runtime);
110
+ if (live && live.status === "running") {
111
+ return live;
112
+ }
113
+ const persisted = await loadPersistedCodexTranslatorSession(deps.fs, repoRoot);
114
+ const model = normalizeCodexModel(input.model ?? persisted?.model);
115
+ const effort = normalizeCodexEffort(input.effort ?? persisted?.effort);
116
+ const claudeSessionId = launchMode === "resume"
117
+ ? persisted?.claudeSessionId
118
+ : randomUUID();
119
+ if (!claudeSessionId) {
120
+ throw new VcmError({
121
+ code: "CODEX_TRANSLATOR_SESSION_MISSING",
122
+ message: "Codex Translator does not have a session id to resume.",
123
+ statusCode: 409,
124
+ hint: "Start the translator once before using Resume."
125
+ });
126
+ }
127
+ const startCommand = await buildCodexStartCommand(deps.fs, repoRoot, repoRoot, CODEX_TRANSLATOR_ROLE, launchMode, model, effort, deps.sandboxMode, launchMode === "resume" && hasCapturedCodexSession(persisted) ? claudeSessionId : undefined);
128
+ const runtimeSession = await deps.runtime.createSession({
129
+ taskSlug: PROJECT_TRANSLATOR_SCOPE,
130
+ role: CODEX_TRANSLATOR_ROLE,
131
+ command: startCommand.command,
132
+ args: startCommand.args,
133
+ cwd: startCommand.cwd,
134
+ env: {
135
+ VCM_API_URL: deps.apiUrl,
136
+ VCM_TASK_REPO_ROOT: repoRoot,
137
+ VCM_TASK_SLUG: PROJECT_TRANSLATOR_SCOPE,
138
+ VCM_ROLE: CODEX_TRANSLATOR_ROLE,
139
+ VCM_SESSION_ID: claudeSessionId
140
+ },
141
+ cols: input.cols,
142
+ rows: input.rows
143
+ });
144
+ const timestamp = now();
145
+ const record = {
146
+ id: runtimeSession.id,
147
+ claudeSessionId,
148
+ taskSlug: PROJECT_TRANSLATOR_SCOPE,
149
+ role: CODEX_TRANSLATOR_ROLE,
150
+ status: runtimeSession.status,
151
+ activityStatus: "idle",
152
+ command: startCommand.display,
153
+ permissionMode: "default",
154
+ model,
155
+ effort,
156
+ cwd: startCommand.cwd,
157
+ terminalBackend: "node-pty",
158
+ pid: runtimeSession.pid,
159
+ startedAt: runtimeSession.startedAt,
160
+ updatedAt: timestamp,
161
+ lastOutputAt: runtimeSession.lastOutputAt,
162
+ exitCode: runtimeSession.exitCode
163
+ };
164
+ deps.registry.upsert(record);
165
+ await persistCodexTranslatorSession(deps.fs, repoRoot, record);
166
+ return record;
167
+ }
115
168
  return {
169
+ startProjectTranslatorSession(repoRoot, input = {}) {
170
+ return launchProjectTranslatorSession(repoRoot, input, "fresh");
171
+ },
172
+ resumeProjectTranslatorSession(repoRoot, input = {}) {
173
+ return launchProjectTranslatorSession(repoRoot, input, "resume");
174
+ },
175
+ async stopProjectTranslatorSession(repoRoot) {
176
+ const existing = await this.getProjectTranslatorSession(repoRoot);
177
+ if (!existing) {
178
+ throw new VcmError({
179
+ code: "SESSION_MISSING",
180
+ message: "Codex Translator session has not been started.",
181
+ statusCode: 404
182
+ });
183
+ }
184
+ if (deps.runtime.getSession(existing.id)) {
185
+ await deps.runtime.stop(existing.id);
186
+ }
187
+ const updated = {
188
+ ...existing,
189
+ status: "exited",
190
+ activityStatus: "idle",
191
+ updatedAt: now()
192
+ };
193
+ deps.registry.upsert(updated);
194
+ await persistCodexTranslatorSession(deps.fs, repoRoot, updated);
195
+ return updated;
196
+ },
197
+ async restartProjectTranslatorSession(repoRoot, input = {}) {
198
+ const existing = await this.getProjectTranslatorSession(repoRoot);
199
+ if (!existing) {
200
+ return launchProjectTranslatorSession(repoRoot, input, "fresh");
201
+ }
202
+ if (deps.runtime.getSession(existing.id)) {
203
+ await deps.runtime.stop(existing.id);
204
+ }
205
+ deps.registry.remove(existing.id);
206
+ return launchProjectTranslatorSession(repoRoot, input, "fresh");
207
+ },
208
+ async getProjectTranslatorSession(repoRoot) {
209
+ const record = getRegisteredProjectTranslatorSession(deps.registry, deps.runtime)
210
+ ?? await loadPersistedCodexTranslatorSession(deps.fs, repoRoot);
211
+ return toRoleSessionRecordView(record, deps.runtime);
212
+ },
213
+ async ensureProjectTranslatorSession(repoRoot, input = {}) {
214
+ const existing = await this.getProjectTranslatorSession(repoRoot);
215
+ if (existing?.status === "running") {
216
+ return existing;
217
+ }
218
+ if (existing?.claudeSessionId) {
219
+ return this.resumeProjectTranslatorSession(repoRoot, {
220
+ model: input.model ?? existing.model,
221
+ effort: input.effort ?? existing.effort,
222
+ cols: input.cols,
223
+ rows: input.rows
224
+ });
225
+ }
226
+ return this.startProjectTranslatorSession(repoRoot, input);
227
+ },
228
+ async recordProjectTranslatorHookEvent(repoRoot, input) {
229
+ const current = await this.getProjectTranslatorSession(repoRoot);
230
+ if (!current) {
231
+ return undefined;
232
+ }
233
+ const timestamp = now();
234
+ const isStop = input.eventName === "Stop";
235
+ const updated = {
236
+ ...current,
237
+ claudeSessionId: input.sessionId ?? current.claudeSessionId,
238
+ transcriptPath: input.transcriptPath ?? current.transcriptPath,
239
+ cwd: input.cwd ?? current.cwd,
240
+ activityStatus: isStop ? "idle" : "running",
241
+ lastHookEventAt: timestamp,
242
+ lastTurnEndedAt: isStop ? timestamp : current.lastTurnEndedAt,
243
+ lastTurnStartedAt: isStop ? current.lastTurnStartedAt : timestamp,
244
+ updatedAt: timestamp
245
+ };
246
+ deps.registry.upsert(updated);
247
+ await persistCodexTranslatorSession(deps.fs, repoRoot, updated);
248
+ return updated;
249
+ },
116
250
  startRoleSession(repoRoot, taskSlug, role, input = {}) {
251
+ if (role === CODEX_TRANSLATOR_ROLE) {
252
+ void taskSlug;
253
+ return this.startProjectTranslatorSession(repoRoot, input);
254
+ }
117
255
  return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
118
256
  },
119
257
  resumeRoleSession(repoRoot, taskSlug, role, input = {}) {
258
+ if (role === CODEX_TRANSLATOR_ROLE) {
259
+ void taskSlug;
260
+ return this.resumeProjectTranslatorSession(repoRoot, input);
261
+ }
120
262
  return launchRoleSession(repoRoot, taskSlug, role, input, "resume");
121
263
  },
122
264
  async stopRoleSession(repoRoot, taskSlug, role) {
265
+ if (role === CODEX_TRANSLATOR_ROLE) {
266
+ void taskSlug;
267
+ return this.stopProjectTranslatorSession(repoRoot);
268
+ }
123
269
  const existing = await this.getRoleSession(repoRoot, taskSlug, role);
124
270
  if (!existing) {
125
271
  throw new VcmError({
@@ -144,6 +290,10 @@ export function createSessionService(deps) {
144
290
  return updated;
145
291
  },
146
292
  async restartRoleSession(repoRoot, taskSlug, role, input = {}) {
293
+ if (role === CODEX_TRANSLATOR_ROLE) {
294
+ void taskSlug;
295
+ return this.restartProjectTranslatorSession(repoRoot, input);
296
+ }
147
297
  const existing = await this.getRoleSession(repoRoot, taskSlug, role);
148
298
  if (!existing) {
149
299
  return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
@@ -155,6 +305,10 @@ export function createSessionService(deps) {
155
305
  return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
156
306
  },
157
307
  async getRoleSession(repoRoot, taskSlug, role) {
308
+ if (role === CODEX_TRANSLATOR_ROLE) {
309
+ void taskSlug;
310
+ return this.getProjectTranslatorSession(repoRoot);
311
+ }
158
312
  const config = await deps.projectService.loadConfig(repoRoot);
159
313
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
160
314
  const taskRepoRoot = getTaskRuntimeRepoRoot(task);
@@ -171,12 +325,9 @@ export function createSessionService(deps) {
171
325
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
172
326
  const taskRepoRoot = getTaskRuntimeRepoRoot(task);
173
327
  const persistedTaskSession = await loadPersistedTaskSessionRecord(deps.fs, taskRepoRoot, config.stateRoot, taskSlug);
174
- for (const role of ROLE_NAMES) {
175
- const record = role === CODEX_TRANSLATOR_ROLE
176
- ? getRegisteredRoleSession(deps.registry, deps.runtime, taskSlug, role)
177
- ?? await loadPersistedCodexTranslatorSession(deps.fs, repoRoot, taskSlug)
178
- : deps.registry.getByRole(taskSlug, role)
179
- ?? normalizePersistedRoleRecord(persistedTaskSession?.roles[role]?.record);
328
+ for (const role of ROLE_NAMES.filter((candidate) => candidate !== CODEX_TRANSLATOR_ROLE)) {
329
+ const record = deps.registry.getByRole(taskSlug, role)
330
+ ?? normalizePersistedRoleRecord(persistedTaskSession?.roles[role]?.record);
180
331
  const session = toRoleSessionRecordView(record, deps.runtime);
181
332
  if (session) {
182
333
  sessions.push(session);
@@ -185,6 +336,15 @@ export function createSessionService(deps) {
185
336
  return sessions;
186
337
  },
187
338
  async recordRoleHookEvent(repoRoot, input) {
339
+ if (input.role === CODEX_TRANSLATOR_ROLE) {
340
+ void input.taskSlug;
341
+ return this.recordProjectTranslatorHookEvent(repoRoot, {
342
+ eventName: input.eventName,
343
+ sessionId: input.sessionId,
344
+ transcriptPath: input.transcriptPath,
345
+ cwd: input.cwd
346
+ });
347
+ }
188
348
  const current = await this.getRoleSession(repoRoot, input.taskSlug, input.role);
189
349
  if (!current || (!input.allowSessionMismatch && !matchesRoleHookSession(current, input))) {
190
350
  return undefined;
@@ -393,6 +553,11 @@ function getRegisteredRoleSession(registry, runtime, taskSlug, role) {
393
553
  ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
394
554
  return scopeProjectRoleSession(scoped, taskSlug);
395
555
  }
556
+ function getRegisteredProjectTranslatorSession(registry, runtime) {
557
+ const candidates = registry.list().filter((session) => session.role === CODEX_TRANSLATOR_ROLE);
558
+ const live = candidates.find((session) => runtime.getSession(session.id)?.status === "running");
559
+ return live ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
560
+ }
396
561
  function compareSessionUpdatedAtDesc(left, right) {
397
562
  return (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
398
563
  }
@@ -407,38 +572,25 @@ function scopeProjectRoleSession(record, taskSlug) {
407
572
  }
408
573
  async function loadPersistedRoleRecordForRole(fs, baseRepoRoot, taskRepoRoot, stateRoot, taskSlug, role) {
409
574
  if (role === CODEX_TRANSLATOR_ROLE) {
410
- return loadPersistedCodexTranslatorSession(fs, baseRepoRoot, taskSlug);
575
+ void taskSlug;
576
+ return loadPersistedCodexTranslatorSession(fs, baseRepoRoot);
411
577
  }
412
578
  return loadPersistedRoleRecord(fs, taskRepoRoot, stateRoot, taskSlug, role);
413
579
  }
414
- async function loadPersistedCodexTranslatorSession(fs, repoRoot, taskSlug) {
415
- let sessionPath = resolveRepoPath(repoRoot, CODEX_TRANSLATOR_SESSION_PATH);
580
+ async function loadPersistedCodexTranslatorSession(fs, repoRoot) {
581
+ const sessionPath = resolveRepoPath(repoRoot, CODEX_TRANSLATOR_SESSION_PATH);
416
582
  if (!(await fs.pathExists(sessionPath))) {
417
- const legacySessionPath = resolveRepoPath(repoRoot, LEGACY_CODEX_TRANSLATOR_SESSION_PATH);
418
- if (!(await fs.pathExists(legacySessionPath))) {
419
- return undefined;
420
- }
421
- sessionPath = legacySessionPath;
583
+ return undefined;
422
584
  }
423
585
  const payload = await fs.readJson(sessionPath);
424
586
  const record = normalizePersistedRoleRecord(isProjectRoleSessionFile(payload) ? payload.record : payload);
425
587
  if (!record) {
426
588
  return undefined;
427
589
  }
428
- if (sessionPath.endsWith(LEGACY_CODEX_TRANSLATOR_SESSION_PATH)) {
429
- await persistCodexTranslatorSession(fs, repoRoot, {
430
- ...record,
431
- taskSlug
432
- });
433
- await fs.removePath?.(sessionPath, { force: true });
434
- }
435
- return scopeProjectRoleSession(record, taskSlug);
436
- }
437
- async function cleanupCodexTranslatorLogs(fs, repoRoot) {
438
- if (!fs.removePath) {
439
- return;
440
- }
441
- await Promise.all(CODEX_TRANSLATOR_LOG_PATHS.map((relativePath) => fs.removePath?.(resolveRepoPath(repoRoot, relativePath), { force: true })));
590
+ return {
591
+ ...record,
592
+ taskSlug: PROJECT_TRANSLATOR_SCOPE
593
+ };
442
594
  }
443
595
  async function loadPersistedRoleRecord(fs, repoRoot, stateRoot, taskSlug, role) {
444
596
  const current = await loadPersistedTaskSessionRecord(fs, repoRoot, stateRoot, taskSlug);
@@ -511,7 +663,7 @@ async function persistCodexTranslatorSession(fs, repoRoot, session) {
511
663
  updatedAt: session.updatedAt,
512
664
  record: {
513
665
  ...session,
514
- taskSlug: session.taskSlug
666
+ taskSlug: PROJECT_TRANSLATOR_SCOPE
515
667
  }
516
668
  });
517
669
  }
@@ -54,7 +54,9 @@ function degradedArtifactSummary(handoffDir) {
54
54
  logsDir,
55
55
  messagesDir,
56
56
  roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
57
- roleLogPaths: Object.fromEntries(ROLE_NAMES.map((role) => [role, `${logsDir}/${role}.log`])),
57
+ roleLogPaths: Object.fromEntries(ROLE_NAMES
58
+ .filter((role) => role !== "codex-translator")
59
+ .map((role) => [role, `${logsDir}/${role}.log`])),
58
60
  messageRoutePaths: {},
59
61
  architecturePlanPath: `${handoffDir}/architecture-plan.md`,
60
62
  knownIssuesPath: `${handoffDir}/known-issues.md`,
@@ -157,6 +157,8 @@ export function createTranslationService(deps) {
157
157
  }
158
158
  function startTranscriptTail(roleSession) {
159
159
  const state = getState(roleSession.id);
160
+ state.taskSlug = roleSession.taskSlug;
161
+ state.role = roleSession.role;
160
162
  if (state.unsubscribeTranscript) {
161
163
  return;
162
164
  }
@@ -183,7 +185,7 @@ export function createTranslationService(deps) {
183
185
  const config = await loadConfig();
184
186
  let displayed = false;
185
187
  if (event.kind === "text") {
186
- const shouldTranslate = config.outputMode === "all" || event.stopReason === "end_turn";
188
+ const shouldTranslate = shouldTranslateTextTranscriptEvent(state, event, config);
187
189
  displayed = shouldTranslate
188
190
  ? processClaudeOutputText(sessionId, event.text, config, event.id, {
189
191
  flushImmediately: event.stopReason === "end_turn"
@@ -206,6 +208,18 @@ export function createTranslationService(deps) {
206
208
  state.seenTranscriptIds.add(event.id);
207
209
  }
208
210
  }
211
+ function shouldTranslateTextTranscriptEvent(state, event, config) {
212
+ if (config.outputMode === "all") {
213
+ return true;
214
+ }
215
+ if (event.stopReason !== "end_turn") {
216
+ return false;
217
+ }
218
+ if (config.outputMode === "pm-final-only") {
219
+ return state.role === "project-manager";
220
+ }
221
+ return true;
222
+ }
209
223
  function processClaudeOutputText(sessionId, rawText, config, entryId, options = {}) {
210
224
  return startClaudeOutputTranslation(sessionId, rawText, config, {
211
225
  entryId,
@@ -890,8 +904,6 @@ export function createTranslationService(deps) {
890
904
  });
891
905
  }
892
906
  return deps.codexTranslationService.createConversationJob(input.repoRoot, {
893
- taskSlug: input.taskSlug,
894
- role: input.role,
895
907
  direction: input.direction,
896
908
  sourceText: input.text,
897
909
  sourceLanguage: input.sourceLanguage,
@@ -921,7 +933,6 @@ export function createTranslationService(deps) {
921
933
  }
922
934
  try {
923
935
  return await deps.codexTranslationService.validateConversationResult(repoRoot, {
924
- taskSlug: job.taskSlug,
925
936
  resultPath: job.resultPath,
926
937
  sourceHash: job.sourceHash,
927
938
  targetLanguage: job.targetLanguage
@@ -15,9 +15,9 @@ Task-specific context and coder guidance go here, not in source-code comments.
15
15
  Source-code comments should only describe durable behavior, contracts, invariants,
16
16
  error boundaries, or non-obvious logic that should remain useful after this task.
17
17
 
18
- | File | Action | Task Context | Durable Code Comment Needed | Coder Work | Allowed Freedom | VCM:CODE | Proof Point | Replan Trigger |
19
- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
20
- | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
18
+ | ID | File | Action | Task Context | Durable Code Comment Needed | Coder Work | Allowed Freedom | VCM:CODE | Proof Point | Replan Trigger |
19
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
20
+ | SCF-001 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
21
21
 
22
22
  ## Implementation Plan
23
23
 
@@ -27,7 +27,8 @@ export function renderArchitectHarnessRules() {
27
27
 
28
28
  - Define the expected implementation scope: affected modules, changed or created files, each file's responsibility, why it is in scope, and user-visible behavior changes.
29
29
  - Define every non-private callable surface intended for use outside its file: visibility, signature shape, responsibility, expected callers, behavior contract, side effects, and error boundaries.
30
- - Include a \`Scaffold Manifest\` for task-specific file context: file action, why the file is in scope, coder work, allowed implementation freedom, expected \`VCM:CODE\` placeholders, durable code comment needs, proof points, and Replan triggers.
30
+ - Include a \`Scaffold Manifest\` for task-specific file context: stable row ID, file action, why the file is in scope, coder work, allowed implementation freedom, expected \`VCM:CODE\` placeholders, durable code comment needs, proof points, and Replan triggers.
31
+ - Give each Scaffold Manifest row a stable ID such as \`SCF-001\`; use that ID in any related \`VCM:CODE\` marker so coder can report completion by ID.
31
32
  - Put task context, phase notes, handoff instructions, temporary rationale, and coder guidance in the \`Scaffold Manifest\`, not in source-code comments.
32
33
  - Cover architecture docs impact, known risks, and Replan triggers.
33
34
  - For docs impact, state whether changes belong in \`docs/ARCHITECTURE.md\`, affected \`<module>/ARCHITECTURE.md\`, \`.ai/generated/public-surface.json\`, or no durable architecture doc.
@@ -41,7 +42,7 @@ export function renderArchitectHarnessRules() {
41
42
  - Define every new or changed non-private callable surface directly in code with its signature shape and contract comment.
42
43
  - When changing an existing non-private callable surface, update its signature and contract comment in code before coder work starts; leave \`VCM:CODE\` only where implementation must change.
43
44
  - Non-private callable surface includes any function, method, type, trait, enum, constant, re-export, or similar symbol that another file can call or depend on.
44
- - Mark incomplete implementation bodies with \`VCM:CODE\`; coder must implement them and remove the markers before handoff.
45
+ - Mark incomplete implementation bodies with \`VCM:CODE <Scaffold Manifest ID>\`; coder must implement them and remove the markers before handoff.
45
46
  - Architect scaffolding may include modules, files, signatures, type shapes, durable comments, and placeholder bodies, but not real business implementation beyond minimal scaffold code.
46
47
  - Coder may add private implementation helpers, but must not add or change cross-file callable surface without architect replan.
47
48
 
@@ -10,7 +10,7 @@ export function renderCoderHarnessRules() {
10
10
  ### Coder Implementation Discipline
11
11
 
12
12
  - Implement the architect-defined scaffold exactly; do not change file responsibilities, callable-surface signatures, or architect-defined contract intent unless the architecture plan explicitly allows it.
13
- - Implement every \`VCM:CODE\` placeholder and remove all \`VCM:CODE\` markers before handoff.
13
+ - Implement every \`VCM:CODE\` placeholder, track completion by Scaffold Manifest ID when present, and remove all \`VCM:CODE\` markers before handoff.
14
14
  - Do not fake completion: no hardcoded success, disabled logic, swallowed errors, test-only shortcuts, or silent fallback that hides failure.
15
15
  - Keep the diff inside approved scope: no unrelated rewrites, drive-by refactors, renamed symbols, moved files, or formatting churn.
16
16
  - Preserve existing behavior unless the architecture plan explicitly changes it; keep existing call sites and shared code paths working.
@@ -40,6 +40,11 @@ export function renderCoderHarnessRules() {
40
40
  - Do not weaken, delete, or skip tests to make validation pass.
41
41
  - Record confirmed out-of-scope issues found during implementation in \`.ai/vcm/handoffs/known-issues.md\`.
42
42
 
43
+ ### Handoff
44
+
45
+ - In the route message back to project-manager, include a \`Scaffold Completion\` section when the architecture plan contains a Scaffold Manifest.
46
+ - The \`Scaffold Completion\` section must report completed Scaffold Manifest IDs or \`VCM:CODE\` IDs, remaining markers if any, private helpers added, manifest deviations, and whether Replan is needed.
47
+
43
48
  ### Generated Context
44
49
 
45
50
  - Regenerate \`.ai/generated/module-index.json\` with \`.ai/tools/generate-module-index\` after module, manifest, source-file, or test-file changes.
@@ -12,8 +12,9 @@ export const TRANSLATION_TARGET_LANGUAGE_OPTIONS = [
12
12
  { value: "es", label: "Spanish" }
13
13
  ];
14
14
  export const TRANSLATION_OUTPUT_MODE_OPTIONS = [
15
- { value: "final-only", label: "Final summary" },
16
- { value: "all", label: "All output" }
15
+ { value: "pm-final-only", label: "PM final reply" },
16
+ { value: "final-only", label: "Each role final reply" },
17
+ { value: "all", label: "All replies" }
17
18
  ];
18
19
  export function createDefaultLaunchTemplate() {
19
20
  const roles = {};