vibe-coding-master 0.3.0 → 0.3.1

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 (35) hide show
  1. package/README.md +9 -6
  2. package/dist/backend/adapters/claude-adapter.js +4 -1
  3. package/dist/backend/api/codex-hook-routes.js +9 -0
  4. package/dist/backend/api/codex-review-routes.js +4 -0
  5. package/dist/backend/api/translation-routes.js +2 -2
  6. package/dist/backend/cli/install-vcm-harness.js +14 -1
  7. package/dist/backend/gateway/gateway-service.js +4 -3
  8. package/dist/backend/server.js +10 -0
  9. package/dist/backend/services/app-settings-service.js +12 -4
  10. package/dist/backend/services/artifact-service.js +2 -1
  11. package/dist/backend/services/claude-hook-service.js +3 -3
  12. package/dist/backend/services/codex-hook-service.js +87 -0
  13. package/dist/backend/services/codex-review-service.js +144 -59
  14. package/dist/backend/services/harness-service.js +28 -1
  15. package/dist/backend/services/message-service.js +8 -7
  16. package/dist/backend/services/project-service.js +2 -2
  17. package/dist/backend/services/round-service.js +29 -25
  18. package/dist/backend/services/session-service.js +141 -12
  19. package/dist/backend/templates/harness/codex-review.js +37 -6
  20. package/dist/shared/constants.js +15 -1
  21. package/dist/shared/types/app-settings.js +4 -3
  22. package/dist/shared/types/codex-hook.js +1 -0
  23. package/dist/shared/types/session.js +44 -0
  24. package/dist-frontend/assets/index-BavJjWQY.js +92 -0
  25. package/dist-frontend/assets/index-CR1EOe-w.css +32 -0
  26. package/dist-frontend/index.html +2 -2
  27. package/docs/ARCHITECTURE.md +1 -0
  28. package/docs/TESTING.md +82 -0
  29. package/docs/codex-review-gates.md +83 -45
  30. package/docs/gateway-design.md +5 -4
  31. package/docs/known-issues.md +1 -0
  32. package/docs/product-design.md +26 -10
  33. package/package.json +1 -1
  34. package/dist-frontend/assets/index-C9l94uxA.js +0 -92
  35. package/dist-frontend/assets/index-D6jWo6Jd.css +0 -32
@@ -5,6 +5,10 @@ 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";
9
+ const CODEX_DIR = ".ai/codex";
10
+ const CODEX_REVIEW_DIR = ".ai/vcm/codex-reviews";
11
+ const CODEX_CONFIG_PATH = ".ai/codex/config.toml";
8
12
  export function createSessionService(deps) {
9
13
  const now = deps.now ?? (() => new Date().toISOString());
10
14
  async function launchRoleSession(repoRoot, taskSlug, role, input, launchMode) {
@@ -17,29 +21,38 @@ export function createSessionService(deps) {
17
21
  const taskRepoRoot = getTaskRuntimeRepoRoot(task);
18
22
  const paths = deps.artifactService.getHandoffPaths(taskRepoRoot, task.handoffDir);
19
23
  const persisted = await loadPersistedRoleRecord(deps.fs, taskRepoRoot, config.stateRoot, taskSlug, role);
24
+ const isCodexReviewer = role === CODEX_REVIEWER_ROLE;
20
25
  const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
21
- const model = normalizeClaudeModel(input.model ?? persisted?.model);
26
+ const model = isCodexReviewer
27
+ ? normalizeCodexModel(input.model ?? persisted?.model)
28
+ : normalizeClaudeModel(input.model ?? persisted?.model);
29
+ const effort = normalizeSessionEffort(input.effort ?? persisted?.effort);
22
30
  const claudeSessionId = launchMode === "resume"
23
31
  ? persisted?.claudeSessionId
24
32
  : randomUUID();
25
33
  if (!claudeSessionId) {
26
34
  throw new VcmError({
27
35
  code: "CLAUDE_SESSION_MISSING",
28
- message: `${role} does not have a Claude session id to resume.`,
36
+ message: `${role} does not have a session id to resume.`,
29
37
  statusCode: 409,
30
38
  hint: "Start the role once before using Resume."
31
39
  });
32
40
  }
33
41
  const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
34
42
  ? persisted.transcriptPath
35
- : claudeTranscriptPath(taskRepoRoot, claudeSessionId);
36
- const startCommand = deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model);
43
+ : isCodexReviewer ? undefined : claudeTranscriptPath(taskRepoRoot, claudeSessionId);
44
+ const startCommand = isCodexReviewer
45
+ ? await buildCodexReviewerStartCommand(deps.fs, taskRepoRoot, launchMode, model, effort)
46
+ : {
47
+ ...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
48
+ cwd: taskRepoRoot
49
+ };
37
50
  const runtimeSession = await deps.runtime.createSession({
38
51
  taskSlug,
39
52
  role,
40
53
  command: startCommand.command,
41
54
  args: startCommand.args,
42
- cwd: taskRepoRoot,
55
+ cwd: startCommand.cwd,
43
56
  env: {
44
57
  VCM_API_URL: deps.apiUrl,
45
58
  VCM_TASK_REPO_ROOT: taskRepoRoot,
@@ -63,7 +76,8 @@ export function createSessionService(deps) {
63
76
  command: startCommand.display,
64
77
  permissionMode,
65
78
  model,
66
- cwd: taskRepoRoot,
79
+ effort,
80
+ cwd: startCommand.cwd,
67
81
  terminalBackend: "node-pty",
68
82
  pid: runtimeSession.pid,
69
83
  logPath: paths.roleLogPaths[role],
@@ -159,15 +173,18 @@ export function createSessionService(deps) {
159
173
  }
160
174
  return sessions;
161
175
  },
162
- async recordClaudeHookEvent(repoRoot, input) {
176
+ async recordRoleHookEvent(repoRoot, input) {
163
177
  const current = await this.getRoleSession(repoRoot, input.taskSlug, input.role);
164
- if (!current || !matchesClaudeHookSession(current, input)) {
178
+ if (!current || (!input.allowSessionMismatch && !matchesRoleHookSession(current, input))) {
165
179
  return undefined;
166
180
  }
167
181
  const timestamp = now();
168
182
  const isStop = input.eventName === "Stop";
169
183
  const updated = {
170
184
  ...current,
185
+ claudeSessionId: input.sessionId ?? current.claudeSessionId,
186
+ transcriptPath: input.transcriptPath ?? current.transcriptPath,
187
+ cwd: input.cwd ?? current.cwd,
171
188
  activityStatus: isStop ? "idle" : "running",
172
189
  lastHookEventAt: timestamp,
173
190
  lastTurnEndedAt: isStop ? timestamp : current.lastTurnEndedAt,
@@ -180,6 +197,16 @@ export function createSessionService(deps) {
180
197
  await persistTaskSession(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
181
198
  return updated;
182
199
  },
200
+ recordClaudeHookEvent(repoRoot, input) {
201
+ return this.recordRoleHookEvent(repoRoot, {
202
+ taskSlug: input.taskSlug,
203
+ role: input.role,
204
+ eventName: input.eventName,
205
+ sessionId: input.claudeSessionId,
206
+ transcriptPath: input.transcriptPath,
207
+ cwd: input.cwd
208
+ });
209
+ },
183
210
  async markRoleActivityRunning(repoRoot, taskSlug, role) {
184
211
  const current = await this.getRoleSession(repoRoot, taskSlug, role);
185
212
  if (!current) {
@@ -201,14 +228,67 @@ export function createSessionService(deps) {
201
228
  }
202
229
  };
203
230
  }
204
- function matchesClaudeHookSession(record, input) {
205
- if (input.claudeSessionId && record.claudeSessionId === input.claudeSessionId) {
231
+ async function buildCodexReviewerStartCommand(fs, taskRepoRoot, launchMode, selectedModel, selectedEffort) {
232
+ const codexDir = resolveRepoPath(taskRepoRoot, CODEX_DIR);
233
+ const reviewDir = resolveRepoPath(taskRepoRoot, CODEX_REVIEW_DIR);
234
+ if (!(await fs.pathExists(codexDir))) {
235
+ throw new VcmError({
236
+ code: "CODEX_REVIEW_CONFIG_MISSING",
237
+ message: `${CODEX_DIR} does not exist.`,
238
+ statusCode: 409,
239
+ hint: "Apply the VCM harness before starting Codex Reviewer."
240
+ });
241
+ }
242
+ await fs.ensureDir(reviewDir);
243
+ const config = await loadCodexSessionConfig(fs, taskRepoRoot);
244
+ const model = selectedModel === "default"
245
+ ? config.model
246
+ : selectedModel;
247
+ const modelReasoningEffort = selectedEffort === "default"
248
+ ? config.modelReasoningEffort
249
+ : selectedEffort;
250
+ const args = launchMode === "resume"
251
+ ? ["resume", "--last"]
252
+ : [];
253
+ args.push("--cd", codexDir, "--add-dir", reviewDir, "--sandbox", "workspace-write", "--ask-for-approval", "never", "--dangerously-bypass-hook-trust");
254
+ if (model && model !== "default") {
255
+ args.push("--model", model);
256
+ }
257
+ if (modelReasoningEffort && modelReasoningEffort !== "default") {
258
+ args.push("--config", `model_reasoning_effort="${modelReasoningEffort}"`);
259
+ }
260
+ return {
261
+ command: config.command,
262
+ args,
263
+ cwd: taskRepoRoot,
264
+ display: [config.command, ...args].map(formatDisplayArg).join(" ")
265
+ };
266
+ }
267
+ async function loadCodexSessionConfig(fs, taskRepoRoot) {
268
+ const configPath = resolveRepoPath(taskRepoRoot, CODEX_CONFIG_PATH);
269
+ if (!(await fs.pathExists(configPath))) {
270
+ return {
271
+ command: "codex",
272
+ model: "gpt-5.5",
273
+ modelReasoningEffort: "xhigh"
274
+ };
275
+ }
276
+ const content = await fs.readText(configPath);
277
+ const reviewSection = extractTomlSection(content, "vcm.codex_review");
278
+ return {
279
+ command: parseTomlString(reviewSection, "command") ?? "codex",
280
+ model: parseTomlString(content, "model") ?? "gpt-5.5",
281
+ modelReasoningEffort: parseTomlString(content, "model_reasoning_effort") ?? "xhigh"
282
+ };
283
+ }
284
+ function matchesRoleHookSession(record, input) {
285
+ if (input.sessionId && record.claudeSessionId === input.sessionId) {
206
286
  return true;
207
287
  }
208
288
  if (input.transcriptPath && record.transcriptPath === input.transcriptPath) {
209
289
  return true;
210
290
  }
211
- if (!input.claudeSessionId && !input.transcriptPath) {
291
+ if (!input.sessionId && !input.transcriptPath) {
212
292
  return true;
213
293
  }
214
294
  return false;
@@ -249,7 +329,10 @@ async function loadPersistedRoleRecord(fs, repoRoot, stateRoot, taskSlug, role)
249
329
  ? legacy.lastStopAt
250
330
  : undefined),
251
331
  permissionMode: normalizeClaudePermissionMode(record.permissionMode),
252
- model: normalizeClaudeModel(record.model)
332
+ model: record.role === CODEX_REVIEWER_ROLE
333
+ ? normalizeCodexModel(record.model)
334
+ : normalizeClaudeModel(record.model),
335
+ effort: normalizeSessionEffort(record.effort)
253
336
  }
254
337
  : undefined;
255
338
  }
@@ -311,3 +394,49 @@ function normalizeClaudeModel(value) {
311
394
  }
312
395
  return "default";
313
396
  }
397
+ function normalizeCodexModel(value) {
398
+ if (value === "default" || value === "gpt-5.5") {
399
+ return value;
400
+ }
401
+ return "gpt-5.5";
402
+ }
403
+ function normalizeSessionEffort(value) {
404
+ if (value === "low"
405
+ || value === "medium"
406
+ || value === "high"
407
+ || value === "xhigh"
408
+ || value === "max") {
409
+ return value;
410
+ }
411
+ return "default";
412
+ }
413
+ function extractTomlSection(content, sectionName) {
414
+ const lines = content.split(/\r?\n/);
415
+ const header = `[${sectionName}]`;
416
+ const start = lines.findIndex((line) => line.trim() === header);
417
+ if (start < 0) {
418
+ return "";
419
+ }
420
+ const section = [];
421
+ for (let index = start + 1; index < lines.length; index += 1) {
422
+ const line = lines[index];
423
+ if (/^\s*\[[^\]]+\]\s*$/.test(line)) {
424
+ break;
425
+ }
426
+ section.push(line);
427
+ }
428
+ return section.join("\n");
429
+ }
430
+ function parseTomlString(content, key) {
431
+ const pattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, "m");
432
+ return pattern.exec(content)?.[1];
433
+ }
434
+ function escapeRegExp(value) {
435
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
436
+ }
437
+ function formatDisplayArg(value) {
438
+ if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) {
439
+ return value;
440
+ }
441
+ return `'${value.replaceAll("'", "'\\''")}'`;
442
+ }
@@ -96,12 +96,8 @@ approval_policy = "never"
96
96
  default_permissions = "vcm_codex_reviewer"
97
97
 
98
98
  [vcm.codex_review]
99
- enabled = true
100
- required_gates = [
101
- "architecture-plan",
102
- "validation-adequacy",
103
- "final-diff",
104
- ]
99
+ enabled = false
100
+ required_gates = []
105
101
 
106
102
  [permissions.vcm_codex_reviewer.workspace_roots]
107
103
  "../.." = true
@@ -118,6 +114,41 @@ required_gates = [
118
114
  [permissions.vcm_codex_reviewer.network]
119
115
  enabled = false`;
120
116
  }
117
+ export function renderCodexCliConfigHarnessRules() {
118
+ return `[features]
119
+ hooks = true`;
120
+ }
121
+ export function renderCodexHooksHarnessRules() {
122
+ const eventScript = "let s=\"\";process.stdin.setEncoding(\"utf8\");process.stdin.on(\"data\",d=>s+=d);process.stdin.on(\"end\",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});";
123
+ const userPromptCommand = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'${eventScript}'"'"' | curl -fsS --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/codex-reviewer" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
124
+ const stopCommand = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then printf "{}"; exit 0; fi; node -e '"'"'${eventScript}'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/codex-reviewer/stop" -H "content-type: application/json" --data-binary @- || printf "{}"'`;
125
+ return JSON.stringify({
126
+ hooks: {
127
+ UserPromptSubmit: [
128
+ {
129
+ hooks: [
130
+ {
131
+ type: "command",
132
+ command: userPromptCommand,
133
+ timeout: 5
134
+ }
135
+ ]
136
+ }
137
+ ],
138
+ Stop: [
139
+ {
140
+ hooks: [
141
+ {
142
+ type: "command",
143
+ command: stopCommand,
144
+ timeout: 10
145
+ }
146
+ ]
147
+ }
148
+ ]
149
+ }
150
+ }, null, 2);
151
+ }
121
152
  export function renderCodexArchitecturePlanPrompt() {
122
153
  return `# Codex Gate: architecture-plan
123
154
 
@@ -1,6 +1,6 @@
1
1
  export const DEFAULT_BACKEND_PORT = 4173;
2
2
  export const DEFAULT_FRONTEND_PORT = 5173;
3
- export const ROLE_DEFINITIONS = [
3
+ export const VCM_ROLE_DEFINITIONS = [
4
4
  {
5
5
  name: "project-manager",
6
6
  label: "Project Manager",
@@ -26,6 +26,17 @@ export const ROLE_DEFINITIONS = [
26
26
  dispatchable: true
27
27
  }
28
28
  ];
29
+ export const CODEX_REVIEWER_ROLE_DEFINITION = {
30
+ name: "codex-reviewer",
31
+ label: "Codex Reviewer",
32
+ commandAgent: "codex-reviewer",
33
+ dispatchable: false
34
+ };
35
+ export const ROLE_DEFINITIONS = [
36
+ ...VCM_ROLE_DEFINITIONS,
37
+ CODEX_REVIEWER_ROLE_DEFINITION
38
+ ];
39
+ export const VCM_ROLE_NAMES = VCM_ROLE_DEFINITIONS.map((role) => role.name);
29
40
  export const ROLE_NAMES = ROLE_DEFINITIONS.map((role) => role.name);
30
41
  export const DISPATCHABLE_ROLES = ROLE_DEFINITIONS
31
42
  .filter((role) => role.dispatchable)
@@ -33,6 +44,9 @@ export const DISPATCHABLE_ROLES = ROLE_DEFINITIONS
33
44
  export function isRoleName(value) {
34
45
  return ROLE_NAMES.includes(value);
35
46
  }
47
+ export function isVcmRoleName(value) {
48
+ return VCM_ROLE_NAMES.includes(value);
49
+ }
36
50
  export function isDispatchableRole(value) {
37
51
  return DISPATCHABLE_ROLES.includes(value);
38
52
  }
@@ -1,12 +1,13 @@
1
- import { ROLE_NAMES } from "../constants.js";
1
+ import { VCM_ROLE_NAMES } from "../constants.js";
2
2
  export const THEME_MODES = ["system", "light", "dark"];
3
3
  export const PERMISSION_REQUEST_MODES = ["off", "allowAll"];
4
4
  export function createDefaultLaunchTemplate() {
5
5
  const roles = {};
6
- for (const role of ROLE_NAMES) {
6
+ for (const role of VCM_ROLE_NAMES) {
7
7
  roles[role] = {
8
8
  permissionMode: "default",
9
- model: "default"
9
+ model: "default",
10
+ effort: "default"
10
11
  };
11
12
  }
12
13
  return {
@@ -0,0 +1 @@
1
+ export {};
@@ -35,3 +35,47 @@ export const CLAUDE_MODEL_OPTIONS = [
35
35
  description: "Opus 4.8 + 1M context"
36
36
  }
37
37
  ];
38
+ export const CODEX_MODEL_OPTIONS = [
39
+ {
40
+ value: "gpt-5.5",
41
+ label: "GPT-5.5",
42
+ description: "Strong Codex reviewer default"
43
+ },
44
+ {
45
+ value: "default",
46
+ label: "Default",
47
+ description: "Codex account default"
48
+ }
49
+ ];
50
+ export const SESSION_EFFORT_OPTIONS = [
51
+ {
52
+ value: "default",
53
+ label: "Default",
54
+ description: "CLI or project default"
55
+ },
56
+ {
57
+ value: "low",
58
+ label: "Low",
59
+ description: "Fastest reasoning"
60
+ },
61
+ {
62
+ value: "medium",
63
+ label: "Medium",
64
+ description: "Balanced reasoning"
65
+ },
66
+ {
67
+ value: "high",
68
+ label: "High",
69
+ description: "Deeper reasoning"
70
+ },
71
+ {
72
+ value: "xhigh",
73
+ label: "XHigh",
74
+ description: "Extra high reasoning"
75
+ },
76
+ {
77
+ value: "max",
78
+ label: "Max",
79
+ description: "Maximum reasoning"
80
+ }
81
+ ];