vibe-coding-master 0.3.26 → 0.3.28

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 (31) hide show
  1. package/README.md +33 -29
  2. package/dist/backend/api/codex-hook-routes.js +0 -7
  3. package/dist/backend/api/gate-review-routes.js +58 -0
  4. package/dist/backend/api/task-routes.js +2 -2
  5. package/dist/backend/cli/install-vcm-harness.js +15 -57
  6. package/dist/backend/server.js +6 -8
  7. package/dist/backend/services/app-settings-service.js +15 -15
  8. package/dist/backend/services/codex-hook-service.js +8 -50
  9. package/dist/backend/services/{codex-review-service.js → gate-review-service.js} +108 -108
  10. package/dist/backend/services/harness-service.js +15 -63
  11. package/dist/backend/services/session-service.js +257 -41
  12. package/dist/backend/services/task-service.js +2 -2
  13. package/dist/backend/templates/harness/claude-root.js +2 -2
  14. package/dist/backend/templates/harness/{codex-review.js → gate-review.js} +35 -278
  15. package/dist/backend/templates/harness/project-manager-agent.js +7 -7
  16. package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +5 -5
  17. package/dist/shared/constants.js +8 -5
  18. package/dist/shared/types/{codex-review.js → gate-review.js} +1 -1
  19. package/dist-frontend/assets/{index-CKWy15WL.js → index-x9I-bGUt.js} +37 -37
  20. package/dist-frontend/index.html +1 -1
  21. package/docs/codex-translation-plan.md +83 -136
  22. package/docs/full-harness-baseline.md +113 -207
  23. package/docs/gate-review-gates.md +133 -0
  24. package/docs/gateway-design.md +6 -8
  25. package/docs/product-design.md +66 -38
  26. package/docs/v0.2-implementation-plan.md +4 -0
  27. package/docs/vcm-cc-best-practices.md +36 -33
  28. package/package.json +1 -1
  29. package/scripts/verify-package.mjs +4 -4
  30. package/dist/backend/api/codex-review-routes.js +0 -58
  31. package/docs/codex-review-gates.md +0 -593
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
6
6
  import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
7
- import { renderCodexAgentsHarnessRules, renderCodexArchitecturePlanPrompt, renderCodexCliConfigHarnessRules, renderCodexConfigHarnessRules, renderCodexFinalDiffPrompt, renderCodexHooksHarnessRules, renderCodexReviewResultSchema, renderCodexTranslatorAgentsHarnessRules, renderCodexTranslatorConfigHarnessRules, renderCodexValidationAdequacyPrompt, renderRequestCodexReviewTool, renderVcmCodexReviewGateSkillRules } from "../templates/harness/codex-review.js";
7
+ import { renderCodexCliConfigHarnessRules, renderCodexHooksHarnessRules, renderCodexTranslatorAgentsHarnessRules, renderCodexTranslatorConfigHarnessRules, renderGateReviewerAgentRules, renderRequestGateReviewTool, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
8
8
  import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
9
9
  import { renderGitignoreHarnessRules } from "../templates/harness/gitignore.js";
10
10
  import { renderProjectManagerHarnessRules } from "../templates/harness/project-manager-agent.js";
@@ -92,39 +92,19 @@ const HARNESS_FILES = [
92
92
  renderRules: renderVcmLongRunningValidationSkillRules
93
93
  },
94
94
  {
95
- kind: "skill-vcm-codex-review-gate",
96
- path: ".claude/skills/vcm-codex-review-gate/SKILL.md",
97
- title: "VCM Codex Review Gate Skill",
98
- frontmatter: renderSkillFrontmatter("vcm-codex-review-gate", "Use when project-manager reaches a Codex Review Gate or receives a VCM Codex Review callback."),
95
+ kind: "skill-vcm-gate-review",
96
+ path: ".claude/skills/vcm-gate-review/SKILL.md",
97
+ title: "VCM Gate Review Skill",
98
+ frontmatter: renderSkillFrontmatter("vcm-gate-review", "Use when project-manager reaches a Gate Review trigger or receives a VCM Gate Review callback."),
99
99
  ownership: "whole-file",
100
- renderRules: renderVcmCodexReviewGateSkillRules
100
+ renderRules: renderVcmGateReviewSkillRules
101
101
  },
102
102
  {
103
- kind: "codex-agents",
104
- path: ".ai/codex/AGENTS.md",
105
- title: "VCM Codex Reviewer",
106
- renderRules: renderCodexAgentsHarnessRules
107
- },
108
- {
109
- kind: "codex-config",
110
- path: ".ai/codex/config.toml",
111
- title: "VCM Codex Config",
112
- ownership: "raw-file",
113
- renderRules: renderCodexConfigHarnessRules
114
- },
115
- {
116
- kind: "codex-cli-config",
117
- path: ".ai/codex/.codex/config.toml",
118
- title: "VCM Codex CLI Config",
119
- ownership: "raw-file",
120
- renderRules: renderCodexCliConfigHarnessRules
121
- },
122
- {
123
- kind: "codex-hooks",
124
- path: ".ai/codex/.codex/hooks.json",
125
- title: "VCM Codex Hooks",
126
- ownership: "raw-file",
127
- renderRules: renderCodexHooksHarnessRules
103
+ kind: "agent-gate-reviewer",
104
+ path: ".claude/agents/gate-reviewer.md",
105
+ title: "Gate Reviewer Agent",
106
+ frontmatter: renderAgentFrontmatter("gate-reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and final diffs."),
107
+ renderRules: renderGateReviewerAgentRules
128
108
  },
129
109
  {
130
110
  kind: "codex-translator-agents",
@@ -154,39 +134,11 @@ const HARNESS_FILES = [
154
134
  renderRules: () => renderCodexHooksHarnessRules("codex-translator")
155
135
  },
156
136
  {
157
- kind: "codex-prompt-architecture-plan",
158
- path: ".ai/codex/prompts/architecture-plan-gate.md",
159
- title: "Codex Architecture Plan Gate Prompt",
160
- ownership: "raw-file",
161
- renderRules: renderCodexArchitecturePlanPrompt
162
- },
163
- {
164
- kind: "codex-prompt-validation-adequacy",
165
- path: ".ai/codex/prompts/validation-adequacy-gate.md",
166
- title: "Codex Validation Adequacy Gate Prompt",
167
- ownership: "raw-file",
168
- renderRules: renderCodexValidationAdequacyPrompt
169
- },
170
- {
171
- kind: "codex-prompt-final-diff",
172
- path: ".ai/codex/prompts/final-diff-gate.md",
173
- title: "Codex Final Diff Gate Prompt",
174
- ownership: "raw-file",
175
- renderRules: renderCodexFinalDiffPrompt
176
- },
177
- {
178
- kind: "codex-review-schema",
179
- path: ".ai/codex/schemas/codex-review-result.schema.json",
180
- title: "Codex Review Result Schema",
181
- ownership: "raw-file",
182
- renderRules: renderCodexReviewResultSchema
183
- },
184
- {
185
- kind: "tool-request-codex-review",
186
- path: ".ai/tools/request-codex-review",
187
- title: "Request Codex Review Tool",
137
+ kind: "tool-request-gate-review",
138
+ path: ".ai/tools/request-gate-review",
139
+ title: "Request Gate Review Tool",
188
140
  ownership: "raw-file",
189
- renderRules: renderRequestCodexReviewTool
141
+ renderRules: renderRequestGateReviewTool
190
142
  },
191
143
  {
192
144
  kind: "agent-project-manager",
@@ -1,20 +1,19 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
- import { ROLE_NAMES, isCodexRoleName, isDispatchableRole } from "../../shared/constants.js";
3
+ import { VCM_ROLE_NAMES, isCodexRoleName, isDispatchableRole } from "../../shared/constants.js";
4
4
  import { VcmError } from "../errors.js";
5
5
  import { resolveRepoPath } from "../adapters/filesystem.js";
6
6
  import { claudeTranscriptPath } from "./claude-transcript-service.js";
7
7
  import { getTaskRuntimeRepoRoot } from "./task-service.js";
8
- const CODEX_REVIEWER_ROLE = "codex-reviewer";
8
+ const GATE_REVIEWER_ROLE = "gate-reviewer";
9
9
  const CODEX_TRANSLATOR_ROLE = "codex-translator";
10
- const CODEX_DIR = ".ai/codex";
11
- const CODEX_REVIEW_DIR = ".ai/vcm/codex-reviews";
12
- const CODEX_CONFIG_PATH = ".ai/codex/config.toml";
13
10
  const CODEX_TRANSLATOR_DIR = ".ai/codex-translator";
14
11
  const CODEX_TRANSLATION_DIR = ".ai/vcm/translations";
15
12
  const CODEX_TRANSLATOR_SESSION_PATH = ".ai/vcm/translations/session.json";
16
13
  const CODEX_TRANSLATOR_CONFIG_PATH = ".ai/codex-translator/config.toml";
14
+ const GATE_REVIEWER_SESSION_PATH = ".ai/vcm/gate-reviewer/session.json";
17
15
  const PROJECT_TRANSLATOR_SCOPE = "__project__";
16
+ const PROJECT_GATE_REVIEWER_SCOPE = "__project_gate_reviewer__";
18
17
  export function createSessionService(deps) {
19
18
  const now = deps.now ?? (() => new Date().toISOString());
20
19
  async function launchRoleSession(repoRoot, taskSlug, role, input, launchMode) {
@@ -102,6 +101,75 @@ export function createSessionService(deps) {
102
101
  await persistRoleSessionRecord(deps.fs, repoRoot, taskRepoRoot, config.stateRoot, record);
103
102
  return record;
104
103
  }
104
+ async function launchProjectGateReviewerSession(repoRoot, input, launchMode) {
105
+ const live = toRoleSessionRecordView(getRegisteredProjectGateReviewerSession(deps.registry, deps.runtime), deps.runtime);
106
+ if (live && live.status === "running") {
107
+ return live;
108
+ }
109
+ const config = await deps.projectService.loadConfig(repoRoot);
110
+ const persisted = await loadPersistedProjectGateReviewerSession(deps.fs, repoRoot);
111
+ const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
112
+ const model = normalizeClaudeModel(input.model ?? persisted?.model);
113
+ const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort);
114
+ const claudeSessionId = launchMode === "resume"
115
+ ? persisted?.claudeSessionId
116
+ : randomUUID();
117
+ if (!claudeSessionId) {
118
+ throw new VcmError({
119
+ code: "GATE_REVIEWER_SESSION_MISSING",
120
+ message: "Gate Reviewer does not have a session id to resume.",
121
+ statusCode: 409,
122
+ hint: "Start Gate Reviewer once before using Resume."
123
+ });
124
+ }
125
+ const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
126
+ ? persisted.transcriptPath
127
+ : claudeTranscriptPath(repoRoot, claudeSessionId);
128
+ const startCommand = {
129
+ ...deps.claude.buildRoleStartCommand(GATE_REVIEWER_ROLE, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
130
+ cwd: repoRoot
131
+ };
132
+ const runtimeSession = await deps.runtime.createSession({
133
+ taskSlug: PROJECT_GATE_REVIEWER_SCOPE,
134
+ role: GATE_REVIEWER_ROLE,
135
+ command: startCommand.command,
136
+ args: startCommand.args,
137
+ cwd: startCommand.cwd,
138
+ env: {
139
+ VCM_API_URL: deps.apiUrl,
140
+ VCM_TASK_REPO_ROOT: repoRoot,
141
+ VCM_TASK_SLUG: PROJECT_GATE_REVIEWER_SCOPE,
142
+ VCM_ROLE: GATE_REVIEWER_ROLE,
143
+ VCM_SESSION_ID: claudeSessionId
144
+ },
145
+ cols: input.cols,
146
+ rows: input.rows
147
+ });
148
+ const timestamp = now();
149
+ const record = {
150
+ id: runtimeSession.id,
151
+ claudeSessionId,
152
+ transcriptPath,
153
+ taskSlug: PROJECT_GATE_REVIEWER_SCOPE,
154
+ role: GATE_REVIEWER_ROLE,
155
+ status: runtimeSession.status,
156
+ activityStatus: "idle",
157
+ command: startCommand.display,
158
+ permissionMode,
159
+ model,
160
+ effort,
161
+ cwd: startCommand.cwd,
162
+ terminalBackend: "node-pty",
163
+ pid: runtimeSession.pid,
164
+ startedAt: runtimeSession.startedAt,
165
+ updatedAt: timestamp,
166
+ lastOutputAt: runtimeSession.lastOutputAt,
167
+ exitCode: runtimeSession.exitCode
168
+ };
169
+ deps.registry.upsert(record);
170
+ await persistProjectGateReviewerSession(deps.fs, repoRoot, record);
171
+ return record;
172
+ }
105
173
  async function launchProjectTranslatorSession(repoRoot, input, launchMode) {
106
174
  const live = toRoleSessionRecordView(getRegisteredProjectTranslatorSession(deps.registry, deps.runtime), deps.runtime);
107
175
  if (live && live.status === "running") {
@@ -163,6 +231,67 @@ export function createSessionService(deps) {
163
231
  return record;
164
232
  }
165
233
  return {
234
+ startProjectGateReviewerSession(repoRoot, input = {}) {
235
+ return launchProjectGateReviewerSession(repoRoot, input, "fresh");
236
+ },
237
+ resumeProjectGateReviewerSession(repoRoot, input = {}) {
238
+ return launchProjectGateReviewerSession(repoRoot, input, "resume");
239
+ },
240
+ async stopProjectGateReviewerSession(repoRoot) {
241
+ const existing = await this.getProjectGateReviewerSession(repoRoot);
242
+ if (!existing) {
243
+ throw new VcmError({
244
+ code: "SESSION_MISSING",
245
+ message: "Gate Reviewer session has not been started.",
246
+ statusCode: 404
247
+ });
248
+ }
249
+ if (deps.runtime.getSession(existing.id)) {
250
+ await deps.runtime.stop(existing.id);
251
+ }
252
+ const updated = {
253
+ ...existing,
254
+ taskSlug: PROJECT_GATE_REVIEWER_SCOPE,
255
+ status: "exited",
256
+ activityStatus: "idle",
257
+ updatedAt: now()
258
+ };
259
+ deps.registry.upsert(updated);
260
+ await persistProjectGateReviewerSession(deps.fs, repoRoot, updated);
261
+ return updated;
262
+ },
263
+ async restartProjectGateReviewerSession(repoRoot, input = {}) {
264
+ const existing = await this.getProjectGateReviewerSession(repoRoot);
265
+ if (!existing) {
266
+ return launchProjectGateReviewerSession(repoRoot, input, "fresh");
267
+ }
268
+ if (deps.runtime.getSession(existing.id)) {
269
+ await deps.runtime.stop(existing.id);
270
+ }
271
+ deps.registry.remove(existing.id);
272
+ return launchProjectGateReviewerSession(repoRoot, input, "fresh");
273
+ },
274
+ async getProjectGateReviewerSession(repoRoot) {
275
+ const record = getRegisteredProjectGateReviewerSession(deps.registry, deps.runtime)
276
+ ?? await loadPersistedProjectGateReviewerSession(deps.fs, repoRoot);
277
+ return toRoleSessionRecordView(record, deps.runtime);
278
+ },
279
+ async ensureProjectGateReviewerSession(repoRoot, input = {}) {
280
+ const existing = await this.getProjectGateReviewerSession(repoRoot);
281
+ if (existing?.status === "running") {
282
+ return existing;
283
+ }
284
+ if (existing?.claudeSessionId) {
285
+ return this.resumeProjectGateReviewerSession(repoRoot, {
286
+ permissionMode: input.permissionMode ?? existing.permissionMode,
287
+ model: input.model ?? existing.model,
288
+ effort: input.effort ?? existing.effort,
289
+ cols: input.cols,
290
+ rows: input.rows
291
+ });
292
+ }
293
+ return this.startProjectGateReviewerSession(repoRoot, input);
294
+ },
166
295
  startProjectTranslatorSession(repoRoot, input = {}) {
167
296
  return launchProjectTranslatorSession(repoRoot, input, "fresh");
168
297
  },
@@ -247,6 +376,10 @@ export function createSessionService(deps) {
247
376
  return updated;
248
377
  },
249
378
  startRoleSession(repoRoot, taskSlug, role, input = {}) {
379
+ if (role === GATE_REVIEWER_ROLE) {
380
+ return this.startProjectGateReviewerSession(repoRoot, input)
381
+ .then((session) => scopeProjectRoleSession(session, taskSlug));
382
+ }
250
383
  if (role === CODEX_TRANSLATOR_ROLE) {
251
384
  void taskSlug;
252
385
  return this.startProjectTranslatorSession(repoRoot, input);
@@ -254,6 +387,10 @@ export function createSessionService(deps) {
254
387
  return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
255
388
  },
256
389
  resumeRoleSession(repoRoot, taskSlug, role, input = {}) {
390
+ if (role === GATE_REVIEWER_ROLE) {
391
+ return this.resumeProjectGateReviewerSession(repoRoot, input)
392
+ .then((session) => scopeProjectRoleSession(session, taskSlug));
393
+ }
257
394
  if (role === CODEX_TRANSLATOR_ROLE) {
258
395
  void taskSlug;
259
396
  return this.resumeProjectTranslatorSession(repoRoot, input);
@@ -261,6 +398,9 @@ export function createSessionService(deps) {
261
398
  return launchRoleSession(repoRoot, taskSlug, role, input, "resume");
262
399
  },
263
400
  async stopRoleSession(repoRoot, taskSlug, role) {
401
+ if (role === GATE_REVIEWER_ROLE) {
402
+ return scopeProjectRoleSession(await this.stopProjectGateReviewerSession(repoRoot), taskSlug);
403
+ }
264
404
  if (role === CODEX_TRANSLATOR_ROLE) {
265
405
  void taskSlug;
266
406
  return this.stopProjectTranslatorSession(repoRoot);
@@ -289,6 +429,9 @@ export function createSessionService(deps) {
289
429
  return updated;
290
430
  },
291
431
  async restartRoleSession(repoRoot, taskSlug, role, input = {}) {
432
+ if (role === GATE_REVIEWER_ROLE) {
433
+ return scopeProjectRoleSession(await this.restartProjectGateReviewerSession(repoRoot, input), taskSlug);
434
+ }
292
435
  if (role === CODEX_TRANSLATOR_ROLE) {
293
436
  void taskSlug;
294
437
  return this.restartProjectTranslatorSession(repoRoot, input);
@@ -304,6 +447,9 @@ export function createSessionService(deps) {
304
447
  return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
305
448
  },
306
449
  async getRoleSession(repoRoot, taskSlug, role) {
450
+ if (role === GATE_REVIEWER_ROLE) {
451
+ return scopeProjectRoleSession(await this.getProjectGateReviewerSession(repoRoot), taskSlug);
452
+ }
307
453
  if (role === CODEX_TRANSLATOR_ROLE) {
308
454
  void taskSlug;
309
455
  return this.getProjectTranslatorSession(repoRoot);
@@ -324,7 +470,7 @@ export function createSessionService(deps) {
324
470
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
325
471
  const taskRepoRoot = getTaskRuntimeRepoRoot(task);
326
472
  const persistedTaskSession = await loadPersistedTaskSessionRecord(deps.fs, taskRepoRoot, config.stateRoot, taskSlug);
327
- for (const role of ROLE_NAMES.filter((candidate) => candidate !== CODEX_TRANSLATOR_ROLE)) {
473
+ for (const role of VCM_ROLE_NAMES) {
328
474
  const record = deps.registry.getByRole(taskSlug, role)
329
475
  ?? normalizePersistedRoleRecord(persistedTaskSession?.roles[role]?.record);
330
476
  const session = toRoleSessionRecordView(record, deps.runtime);
@@ -332,9 +478,38 @@ export function createSessionService(deps) {
332
478
  sessions.push(session);
333
479
  }
334
480
  }
481
+ const gateReviewerSession = scopeProjectRoleSession(await this.getProjectGateReviewerSession(repoRoot), taskSlug);
482
+ if (gateReviewerSession) {
483
+ sessions.push(gateReviewerSession);
484
+ }
335
485
  return sessions;
336
486
  },
337
487
  async recordRoleHookEvent(repoRoot, input) {
488
+ if (input.role === GATE_REVIEWER_ROLE) {
489
+ const current = await this.getProjectGateReviewerSession(repoRoot);
490
+ if (!current || (!input.allowSessionMismatch && !matchesRoleHookSession(current, input))) {
491
+ return undefined;
492
+ }
493
+ const timestamp = now();
494
+ const isTurnEnd = isTurnEndHook(input.eventName);
495
+ const isCompact = isCompactHook(input.eventName);
496
+ const updated = {
497
+ ...current,
498
+ taskSlug: PROJECT_GATE_REVIEWER_SCOPE,
499
+ claudeSessionId: input.sessionId ?? current.claudeSessionId,
500
+ transcriptPath: input.transcriptPath ?? current.transcriptPath,
501
+ cwd: input.cwd ?? current.cwd,
502
+ activityStatus: isTurnEnd ? "idle" : isCompact ? current.activityStatus : "running",
503
+ lastHookEventAt: timestamp,
504
+ lastTurnEndedAt: isTurnEnd ? timestamp : current.lastTurnEndedAt,
505
+ lastTurnStartedAt: isTurnEnd || isCompact ? current.lastTurnStartedAt : timestamp,
506
+ lastCompactAt: isCompact ? timestamp : current.lastCompactAt,
507
+ updatedAt: timestamp
508
+ };
509
+ deps.registry.upsert(updated);
510
+ await persistProjectGateReviewerSession(deps.fs, repoRoot, updated);
511
+ return scopeProjectRoleSession(updated, input.taskSlug);
512
+ }
338
513
  if (input.role === CODEX_TRANSLATOR_ROLE) {
339
514
  void input.taskSlug;
340
515
  return this.recordProjectTranslatorHookEvent(repoRoot, {
@@ -392,6 +567,15 @@ export function createSessionService(deps) {
392
567
  lastHookEventAt: timestamp,
393
568
  updatedAt: timestamp
394
569
  };
570
+ if (role === GATE_REVIEWER_ROLE) {
571
+ const persisted = {
572
+ ...updated,
573
+ taskSlug: PROJECT_GATE_REVIEWER_SCOPE
574
+ };
575
+ deps.registry.upsert(persisted);
576
+ await persistProjectGateReviewerSession(deps.fs, repoRoot, persisted);
577
+ return updated;
578
+ }
395
579
  deps.registry.upsert(updated);
396
580
  const config = await deps.projectService.loadConfig(repoRoot);
397
581
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
@@ -410,6 +594,15 @@ export function createSessionService(deps) {
410
594
  lastTurnEndedAt: timestamp,
411
595
  updatedAt: timestamp
412
596
  };
597
+ if (role === GATE_REVIEWER_ROLE) {
598
+ const persisted = {
599
+ ...updated,
600
+ taskSlug: PROJECT_GATE_REVIEWER_SCOPE
601
+ };
602
+ deps.registry.upsert(persisted);
603
+ await persistProjectGateReviewerSession(deps.fs, repoRoot, persisted);
604
+ return updated;
605
+ }
413
606
  deps.registry.upsert(updated);
414
607
  const config = await deps.projectService.loadConfig(repoRoot);
415
608
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
@@ -440,20 +633,27 @@ function toRoleSessionRecordView(record, runtime) {
440
633
  exitCode: runtimeSession.exitCode
441
634
  };
442
635
  }
443
- async function buildCodexStartCommand(fs, baseRepoRoot, taskRepoRoot, role, launchMode, selectedModel, selectedEffort, sandboxMode, resumeSessionId) {
636
+ async function buildCodexStartCommand(fs, baseRepoRoot, _taskRepoRoot, role, launchMode, selectedModel, selectedEffort, sandboxMode, resumeSessionId) {
444
637
  const isTranslator = role === CODEX_TRANSLATOR_ROLE;
445
- const codexDir = resolveRepoPath(isTranslator ? baseRepoRoot : taskRepoRoot, isTranslator ? CODEX_TRANSLATOR_DIR : CODEX_DIR);
446
- const outputDir = resolveRepoPath(isTranslator ? baseRepoRoot : taskRepoRoot, isTranslator ? CODEX_TRANSLATION_DIR : CODEX_REVIEW_DIR);
638
+ if (!isTranslator) {
639
+ throw new VcmError({
640
+ code: "CODEX_ROLE_UNSUPPORTED",
641
+ message: `${role} is not a Codex role.`,
642
+ statusCode: 400
643
+ });
644
+ }
645
+ const codexDir = resolveRepoPath(baseRepoRoot, CODEX_TRANSLATOR_DIR);
646
+ const outputDir = resolveRepoPath(baseRepoRoot, CODEX_TRANSLATION_DIR);
447
647
  if (!(await fs.pathExists(codexDir))) {
448
648
  throw new VcmError({
449
- code: isTranslator ? "CODEX_TRANSLATOR_CONFIG_MISSING" : "CODEX_REVIEW_CONFIG_MISSING",
450
- message: `${isTranslator ? CODEX_TRANSLATOR_DIR : CODEX_DIR} does not exist.`,
649
+ code: "CODEX_TRANSLATOR_CONFIG_MISSING",
650
+ message: `${CODEX_TRANSLATOR_DIR} does not exist.`,
451
651
  statusCode: 409,
452
- hint: `Apply the VCM harness before starting ${isTranslator ? "Codex Translator" : "Codex Reviewer"}.`
652
+ hint: "Apply the VCM harness before starting Codex Translator."
453
653
  });
454
654
  }
455
655
  await fs.ensureDir(outputDir);
456
- const config = await loadCodexSessionConfig(fs, isTranslator ? baseRepoRoot : taskRepoRoot, isTranslator ? CODEX_TRANSLATOR_CONFIG_PATH : CODEX_CONFIG_PATH);
656
+ const config = await loadCodexSessionConfig(fs, baseRepoRoot, CODEX_TRANSLATOR_CONFIG_PATH);
457
657
  const args = launchMode === "resume"
458
658
  ? resumeSessionId ? ["resume", resumeSessionId] : ["resume", "--last"]
459
659
  : [];
@@ -462,12 +662,7 @@ async function buildCodexStartCommand(fs, baseRepoRoot, taskRepoRoot, role, laun
462
662
  args.push("--dangerously-bypass-approvals-and-sandbox");
463
663
  }
464
664
  else {
465
- if (isTranslator) {
466
- args.push("--add-dir", baseRepoRoot);
467
- }
468
- else {
469
- args.push("--add-dir", outputDir);
470
- }
665
+ args.push("--add-dir", baseRepoRoot);
471
666
  args.push("--sandbox", "workspace-write", "--ask-for-approval", "never");
472
667
  }
473
668
  args.push("--dangerously-bypass-hook-trust", "--search");
@@ -480,7 +675,7 @@ async function buildCodexStartCommand(fs, baseRepoRoot, taskRepoRoot, role, laun
480
675
  return {
481
676
  command: config.command,
482
677
  args,
483
- cwd: isTranslator ? baseRepoRoot : taskRepoRoot,
678
+ cwd: baseRepoRoot,
484
679
  display: [config.command, ...args].map(formatDisplayArg).join(" ")
485
680
  };
486
681
  }
@@ -508,9 +703,8 @@ async function loadCodexSessionConfig(fs, repoRoot, configRelativePath) {
508
703
  };
509
704
  }
510
705
  const content = await fs.readText(configPath);
511
- const reviewSection = extractTomlSection(content, "vcm.codex_review");
512
706
  return {
513
- command: parseTomlString(content, "command") ?? parseTomlString(reviewSection, "command") ?? "codex"
707
+ command: parseTomlString(content, "command") ?? "codex"
514
708
  };
515
709
  }
516
710
  function matchesRoleHookSession(record, input) {
@@ -550,7 +744,7 @@ function getHandoffArtifactPath(paths, role) {
550
744
  return undefined;
551
745
  }
552
746
  function getRegisteredRoleSession(registry, runtime, taskSlug, role) {
553
- if (role !== CODEX_TRANSLATOR_ROLE) {
747
+ if (role !== CODEX_TRANSLATOR_ROLE && role !== GATE_REVIEWER_ROLE) {
554
748
  return registry.getByRole(taskSlug, role);
555
749
  }
556
750
  const candidates = registry.list().filter((session) => session.role === role);
@@ -560,6 +754,11 @@ function getRegisteredRoleSession(registry, runtime, taskSlug, role) {
560
754
  ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
561
755
  return scopeProjectRoleSession(scoped, taskSlug);
562
756
  }
757
+ function getRegisteredProjectGateReviewerSession(registry, runtime) {
758
+ const candidates = registry.list().filter((session) => session.role === GATE_REVIEWER_ROLE);
759
+ const live = candidates.find((session) => runtime.getSession(session.id)?.status === "running");
760
+ return live ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
761
+ }
563
762
  function getRegisteredProjectTranslatorSession(registry, runtime) {
564
763
  const candidates = registry.list().filter((session) => session.role === CODEX_TRANSLATOR_ROLE);
565
764
  const live = candidates.find((session) => runtime.getSession(session.id)?.status === "running");
@@ -569,7 +768,7 @@ function compareSessionUpdatedAtDesc(left, right) {
569
768
  return (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
570
769
  }
571
770
  function scopeProjectRoleSession(record, taskSlug) {
572
- if (!record || record.role !== CODEX_TRANSLATOR_ROLE) {
771
+ if (!record || (record.role !== CODEX_TRANSLATOR_ROLE && record.role !== GATE_REVIEWER_ROLE)) {
573
772
  return record;
574
773
  }
575
774
  return {
@@ -578,12 +777,31 @@ function scopeProjectRoleSession(record, taskSlug) {
578
777
  };
579
778
  }
580
779
  async function loadPersistedRoleRecordForRole(fs, baseRepoRoot, taskRepoRoot, stateRoot, taskSlug, role) {
780
+ if (role === GATE_REVIEWER_ROLE) {
781
+ void taskSlug;
782
+ return loadPersistedProjectGateReviewerSession(fs, baseRepoRoot);
783
+ }
581
784
  if (role === CODEX_TRANSLATOR_ROLE) {
582
785
  void taskSlug;
583
786
  return loadPersistedCodexTranslatorSession(fs, baseRepoRoot);
584
787
  }
585
788
  return loadPersistedRoleRecord(fs, taskRepoRoot, stateRoot, taskSlug, role);
586
789
  }
790
+ async function loadPersistedProjectGateReviewerSession(fs, repoRoot) {
791
+ const sessionPath = resolveRepoPath(repoRoot, GATE_REVIEWER_SESSION_PATH);
792
+ if (!(await fs.pathExists(sessionPath))) {
793
+ return undefined;
794
+ }
795
+ const payload = await fs.readJson(sessionPath);
796
+ const record = normalizePersistedRoleRecord(isProjectRoleSessionFile(payload) ? payload.record : payload);
797
+ if (!record) {
798
+ return undefined;
799
+ }
800
+ return {
801
+ ...record,
802
+ taskSlug: PROJECT_GATE_REVIEWER_SCOPE
803
+ };
804
+ }
587
805
  async function loadPersistedCodexTranslatorSession(fs, repoRoot) {
588
806
  const sessionPath = resolveRepoPath(repoRoot, CODEX_TRANSLATOR_SESSION_PATH);
589
807
  if (!(await fs.pathExists(sessionPath))) {
@@ -657,12 +875,27 @@ async function persistTaskSession(fs, repoRoot, stateRoot, session) {
657
875
  });
658
876
  }
659
877
  async function persistRoleSessionRecord(fs, baseRepoRoot, taskRepoRoot, stateRoot, session) {
878
+ if (session.role === GATE_REVIEWER_ROLE) {
879
+ await persistProjectGateReviewerSession(fs, baseRepoRoot, session);
880
+ return;
881
+ }
660
882
  if (session.role === CODEX_TRANSLATOR_ROLE) {
661
883
  await persistCodexTranslatorSession(fs, baseRepoRoot, session);
662
884
  return;
663
885
  }
664
886
  await persistTaskSession(fs, taskRepoRoot, stateRoot, session);
665
887
  }
888
+ async function persistProjectGateReviewerSession(fs, repoRoot, session) {
889
+ await fs.writeJsonAtomic(resolveRepoPath(repoRoot, GATE_REVIEWER_SESSION_PATH), {
890
+ version: 1,
891
+ role: session.role,
892
+ updatedAt: session.updatedAt,
893
+ record: {
894
+ ...session,
895
+ taskSlug: PROJECT_GATE_REVIEWER_SCOPE
896
+ }
897
+ });
898
+ }
666
899
  async function persistCodexTranslatorSession(fs, repoRoot, session) {
667
900
  await fs.writeJsonAtomic(resolveRepoPath(repoRoot, CODEX_TRANSLATOR_SESSION_PATH), {
668
901
  version: 1,
@@ -736,23 +969,6 @@ function normalizeCodexEffort(value) {
736
969
  }
737
970
  return "default";
738
971
  }
739
- function extractTomlSection(content, sectionName) {
740
- const lines = content.split(/\r?\n/);
741
- const header = `[${sectionName}]`;
742
- const start = lines.findIndex((line) => line.trim() === header);
743
- if (start < 0) {
744
- return "";
745
- }
746
- const section = [];
747
- for (let index = start + 1; index < lines.length; index += 1) {
748
- const line = lines[index];
749
- if (/^\s*\[[^\]]+\]\s*$/.test(line)) {
750
- break;
751
- }
752
- section.push(line);
753
- }
754
- return section.join("\n");
755
- }
756
972
  function parseTomlString(content, key) {
757
973
  const pattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, "m");
758
974
  return pattern.exec(content)?.[1];
@@ -179,7 +179,7 @@ async function ensureTaskRuntimeStateDirs(fs, taskRepoRoot, stateRoot) {
179
179
  await fs.ensureDir(path.join(taskRepoRoot, stateRoot, "messages"));
180
180
  await fs.ensureDir(path.join(taskRepoRoot, stateRoot, "orchestration"));
181
181
  await fs.ensureDir(path.join(taskRepoRoot, stateRoot, "translation"));
182
- await fs.ensureDir(path.join(taskRepoRoot, stateRoot, "codex-reviews"));
182
+ await fs.ensureDir(path.join(taskRepoRoot, stateRoot, "gate-reviews"));
183
183
  }
184
184
  function getTaskStatePaths(taskStoreRoot, taskRepoRoot, stateRoot, handoffRoot, taskSlug) {
185
185
  return [
@@ -188,7 +188,7 @@ function getTaskStatePaths(taskStoreRoot, taskRepoRoot, stateRoot, handoffRoot,
188
188
  path.join(taskRepoRoot, stateRoot, "messages", `${taskSlug}.jsonl`),
189
189
  path.join(taskRepoRoot, stateRoot, "orchestration", `${taskSlug}.json`),
190
190
  path.join(taskRepoRoot, stateRoot, "translation", taskSlug),
191
- path.join(taskRepoRoot, stateRoot, "codex-reviews"),
191
+ path.join(taskRepoRoot, stateRoot, "gate-reviews"),
192
192
  path.join(taskRepoRoot, handoffRoot)
193
193
  ];
194
194
  }
@@ -5,7 +5,7 @@ export function renderRootClaudeHarnessRules() {
5
5
  - Read module-local \`CLAUDE.md\` before editing a subdirectory if one exists.
6
6
  - Use \`vcm-route-message\` whenever a VCM role hands off work, asks another role a question, reports a result, reports a blocker, or raises a finding. Follow its write-then-stop rule.
7
7
  - Use \`vcm-long-running-validation\` for long-running validation. Follow the background job limits below.
8
- - Project-manager uses \`vcm-codex-review-gate\` at enabled Codex Review Gate trigger points and on VCM Codex review callbacks.
8
+ - Project-manager uses \`vcm-gate-review\` at enabled Gate Review trigger points and on VCM Gate Review callbacks.
9
9
 
10
10
  ## VCM Background Jobs
11
11
 
@@ -32,7 +32,7 @@ export function renderRootClaudeHarnessRules() {
32
32
  - Test-only or validation-only work may use: \`project-manager -> reviewer -> project-manager final acceptance\`.
33
33
  - If a docs/test/validation-only task reveals required code, architecture, public contract, dependency, durable-doc, or test-strategy changes, route back through the full code-change flow.
34
34
  - Keep role outputs under \`.ai/vcm/handoffs/\`.
35
- - Codex Review Gate reports live under \`.ai/vcm/codex-reviews/\` and are VCM-managed task evidence.
35
+ - Gate Review Gate reports live under \`.ai/vcm/gate-reviews/\` and are VCM-managed task evidence.
36
36
  - Runtime task records and handoffs under \`.ai/vcm/\` are temporary. Durable facts must move into code, tests, PR text, commit history, or long-term docs.
37
37
  - Record current-task unresolved findings in \`.ai/vcm/handoffs/known-issues.md\`.
38
38