vibe-coding-master 0.3.29 → 0.3.30

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 (28) hide show
  1. package/README.md +9 -13
  2. package/dist/backend/api/project-routes.js +1 -1
  3. package/dist/backend/api/{codex-translation-routes.js → translation-worker-routes.js} +21 -21
  4. package/dist/backend/cli/install-vcm-harness.js +30 -25
  5. package/dist/backend/server.js +12 -22
  6. package/dist/backend/services/claude-hook-service.js +51 -1
  7. package/dist/backend/services/harness-service.js +49 -29
  8. package/dist/backend/services/session-service.js +56 -163
  9. package/dist/backend/services/translation-service.js +17 -17
  10. package/dist/backend/services/{codex-translation-service.js → translation-worker-service.js} +11 -11
  11. package/dist/backend/templates/harness/gate-review.js +2 -58
  12. package/dist/shared/constants.js +13 -10
  13. package/dist/shared/types/app-settings.js +1 -1
  14. package/dist/shared/types/session.js +2 -14
  15. package/dist-frontend/assets/{index-Cfum1Prr.css → index-BvUHLq4X.css} +1 -1
  16. package/dist-frontend/assets/{index-K34QFpWK.js → index-D0ezB4na.js} +36 -36
  17. package/dist-frontend/index.html +2 -2
  18. package/docs/{codex-translation-plan.md → claude-code-translation-plan.md} +151 -153
  19. package/docs/full-harness-baseline.md +3 -7
  20. package/docs/gateway-design.md +1 -1
  21. package/docs/product-design.md +2 -2
  22. package/docs/v0.4-custom-workflow-plan.md +0 -1
  23. package/docs/vcm-cc-best-practices.md +2 -5
  24. package/package.json +1 -1
  25. package/dist/backend/api/codex-hook-routes.js +0 -9
  26. package/dist/backend/codex-sandbox-mode.js +0 -28
  27. package/dist/backend/services/codex-hook-service.js +0 -71
  28. package/dist/shared/types/codex-hook.js +0 -1
package/README.md CHANGED
@@ -31,7 +31,7 @@ When Gate Review Gates are enabled for a task, or when a Gate Reviewer session a
31
31
  - Two-stage VCM harness setup: deterministic fixed install plus AI-assisted bootstrap.
32
32
  - VCM-managed root rules, role agents, repo-local VCM skills, Claude Code hooks, generated-context tools, and PR template.
33
33
  - Rust generated context for module indexing and crate-external public surface indexing.
34
- - Translation panel powered by the long-lived Codex Translator session.
34
+ - Translation panel powered by the long-lived Translator session.
35
35
  - Mobile Gateway through Tencent iLink Bot API / Weixin DM, for talking to PM and managing tasks from Weixin.
36
36
  - Durable task state, role session state, handoff artifacts, and message history.
37
37
 
@@ -139,10 +139,9 @@ Important container notes:
139
139
 
140
140
  - Install Claude Code inside the container, or make `claude` available in the container `PATH`.
141
141
  - Make sure Claude Code authentication works inside the container.
142
- - Make sure the container has network access to Claude services and any configured Codex model endpoints if translation is enabled.
142
+ - Make sure the container has network access to Claude services if translation is enabled.
143
143
  - VCM accepts normal Git repositories by checking `.git` directly. It also supports `.git` files that point to worktree gitdirs.
144
144
  - VCM uses per-command `git -c safe.directory=...` for Git metadata reads and does not require global `git config --global --add safe.directory`.
145
- - Set `VCM_SANDBOX=devcontainer` so VCM-managed Codex Translator sessions rely on the container boundary and do not start Codex's nested Linux sandbox.
146
145
  - Treat the container as the sandbox boundary, especially when using relaxed Claude Code permission modes.
147
146
 
148
147
  ## Basic Usage
@@ -210,7 +209,7 @@ VCM will not offer a separate `Create task worktree` button after a task exists,
210
209
 
211
210
  Because worktrees live under `.claude/worktrees/`, the connected repository must ignore both `.ai/vcm/` and `.claude/worktrees/`. Apply the VCM Harness before creating tasks so `.gitignore` contains the managed ignore block. The base repository must also be clean because the task branch/worktree is created from the connected repo's current `HEAD`.
212
211
 
213
- When a task is complete, VCM provides a red `Close Task` action. Closing a task shows a destructive confirmation, stops VCM-managed running role sessions for that task, then deletes the task worktree, deletes the task branch by default, removes the app-local task record, and removes task runtime metadata. VCM does not preflight running sessions or uncommitted changes before closing. Project-scoped Gate Reviewer and Codex Translator sessions are not task-owned and are not stopped by Close Task.
212
+ When a task is complete, VCM provides a red `Close Task` action. Closing a task shows a destructive confirmation, stops VCM-managed running role sessions for that task, then deletes the task worktree, deletes the task branch by default, removes the app-local task record, and removes task runtime metadata. VCM does not preflight running sessions or uncommitted changes before closing. Project-scoped Gate Reviewer and Translator sessions are not task-owned and are not stopped by Close Task.
214
213
 
215
214
  ## Sidebar UI
216
215
 
@@ -219,7 +218,7 @@ The left sidebar is intentionally compact and collapsible:
219
218
  - `Repository Path`: path input on one row; `Recent` and `Connect` on the next row.
220
219
  - `Connected Repository`: connected base repo path, branch, upstream/ahead-behind status, commit hash, working tree state, and a `Pull` button.
221
220
  - `Settings`: `Theme`, `Flow pause alert`, `Try alert`, `Messages`, and `Events`.
222
- - `Translation`: global conversation translation, auto-send, target language, output scope, file translation, bootstrap, memory update, session status, and Codex Translator session access.
221
+ - `Translation`: global conversation translation, auto-send, target language, output scope, file translation, bootstrap, memory update, session status, and Translator session access.
223
222
  - `Gate Review Gates`: global gate switches for architecture plan, validation adequacy, and final diff.
224
223
  - `Gateway`: Weixin iLink binding, Gateway on/off, Gateway translation, and QR login.
225
224
  - `VCM Harness`: fixed-install status, bootstrap completion checks, and the bootstrap terminal when one is running.
@@ -387,7 +386,7 @@ Translation settings are local and stored in:
387
386
 
388
387
  The same file stores recent repository paths and global translation preferences such as enablement, auto-send, and target language.
389
388
 
390
- VCM resolves `vcmDataDir` from `VCM_DATA_DIR`. If `VCM_DATA_DIR` is unset or empty, VCM uses `~/.vcm`. In Dev Containers, set `VCM_DATA_DIR=/workspace/.ai/vcm` and `VCM_SANDBOX=devcontainer` through `containerEnv` so VCM app state survives container rebuilds and VCM-managed Codex Translator sessions do not run a nested Codex sandbox.
389
+ VCM resolves `vcmDataDir` from `VCM_DATA_DIR`. If `VCM_DATA_DIR` is unset or empty, VCM uses `~/.vcm`. In Dev Containers, set `VCM_DATA_DIR=/workspace/.ai/vcm` through `containerEnv` so VCM app state survives container rebuilds.
391
390
 
392
391
  The sidebar `Settings` section also stores the UI theme preference in this file. The default is `system`, which follows the OS/browser color-scheme preference; users can cycle between `System`, `Light`, and `Dark`.
393
392
 
@@ -397,11 +396,11 @@ When Gateway is on, `Flow pause alert` is forced off because mobile notification
397
396
 
398
397
  Translation behavior:
399
398
 
400
- - Conversation translation is routed through the Codex Translator session and result files.
399
+ - Conversation translation is routed through the Translator session and result files.
401
400
  - Global translation controls live in the sidebar Translation section: enablement, auto-send, target language, output scope, bootstrap, memory update, file translation, session status, and `Open Session`.
402
401
  - File and conversation translation share `<baseRepoRoot>/.ai/vcm/translations/`; conversation result files are temporary runtime artifacts.
403
402
  - Claude Code output translation reads semantic Claude transcript JSONL files under `~/.claude/projects`, not raw PTY output.
404
- - Claude Code prose output waits 10 seconds before dispatch so adjacent output can be translated in one Codex batch.
403
+ - Claude Code prose output waits 10 seconds before dispatch so adjacent output can be translated in one Claude Code Translator batch.
405
404
  - VCM tails those transcript files in the backend. Closing the translation panel does not stop capture; the tailer stops only when the role session is stopped/restarted or the task is closed.
406
405
  - Translation events are cached under the task runtime repo at `.ai/vcm/translation/<task>/<role>/<session-id>.jsonl` and delivered to the frontend through HTTP polling.
407
406
  - The polling cursor is the next expected seq: `after=18` acknowledges seq `1..17` and returns seq `18+`; there is no snapshot mismatch error.
@@ -437,10 +436,7 @@ CLAUDE.md
437
436
  .claude/skills/vcm-harness-bootstrap/SKILL.md
438
437
  .claude/skills/vcm-gate-review/SKILL.md
439
438
  .claude/agents/gate-reviewer.md
440
- .ai/codex-translator/AGENTS.md
441
- .ai/codex-translator/config.toml
442
- .ai/codex-translator/.codex/config.toml
443
- .ai/codex-translator/.codex/hooks.json
439
+ .claude/agents/translator.md
444
440
  .ai/tools/request-gate-review
445
441
  .ai/tools/generate-module-index
446
442
  .ai/tools/generate-public-surface
@@ -683,5 +679,5 @@ See also:
683
679
  - `docs/vcm-cc-best-practices.md`
684
680
  - `docs/full-harness-baseline.md`
685
681
  - `docs/gate-review-gates.md`
686
- - `docs/codex-translation-plan.md`
682
+ - `docs/claude-code-translation-plan.md`
687
683
  - `docs/cc-best-practices.md` is archived and no longer maintained.
@@ -5,7 +5,7 @@ export function registerProjectRoutes(app, deps) {
5
5
  });
6
6
  app.post("/api/projects/connect", async (request) => {
7
7
  const project = await deps.projectService.connectProject(request.body);
8
- await deps.codexTranslationService?.cleanupStartupRuntime(project.repoRoot);
8
+ await deps.translationWorkerService?.cleanupStartupRuntime(project.repoRoot);
9
9
  return project;
10
10
  });
11
11
  app.get("/api/projects/current", async () => {
@@ -1,60 +1,60 @@
1
1
  import { VcmError } from "../errors.js";
2
- export function registerCodexTranslationRoutes(app, deps) {
3
- app.get("/api/translation/codex/state", async () => {
2
+ export function registerTranslationWorkerRoutes(app, deps) {
3
+ app.get("/api/translation/state", async () => {
4
4
  const project = await requireCurrentProject(deps.projectService);
5
- return deps.codexTranslationService.getState(project.repoRoot, {
5
+ return deps.translationWorkerService.getState(project.repoRoot, {
6
6
  visibility: "public"
7
7
  });
8
8
  });
9
- app.get("/api/translation/codex/session", async () => {
9
+ app.get("/api/translation/session", async () => {
10
10
  const project = await requireCurrentProject(deps.projectService);
11
11
  return (await deps.sessionService.getProjectTranslatorSession(project.repoRoot)) ?? null;
12
12
  });
13
- app.post("/api/translation/codex/session/ensure", async (request) => {
13
+ app.post("/api/translation/session/ensure", async (request) => {
14
14
  const project = await requireCurrentProject(deps.projectService);
15
15
  return deps.sessionService.ensureProjectTranslatorSession(project.repoRoot, request.body);
16
16
  });
17
- app.post("/api/translation/codex/session/start", async (request) => {
17
+ app.post("/api/translation/session/start", async (request) => {
18
18
  const project = await requireCurrentProject(deps.projectService);
19
19
  return deps.sessionService.startProjectTranslatorSession(project.repoRoot, request.body);
20
20
  });
21
- app.post("/api/translation/codex/session/resume", async (request) => {
21
+ app.post("/api/translation/session/resume", async (request) => {
22
22
  const project = await requireCurrentProject(deps.projectService);
23
23
  return deps.sessionService.resumeProjectTranslatorSession(project.repoRoot, request.body);
24
24
  });
25
- app.post("/api/translation/codex/session/restart", async (request) => {
25
+ app.post("/api/translation/session/restart", async (request) => {
26
26
  const project = await requireCurrentProject(deps.projectService);
27
27
  return deps.sessionService.restartProjectTranslatorSession(project.repoRoot, request.body);
28
28
  });
29
- app.post("/api/translation/codex/session/stop", async () => {
29
+ app.post("/api/translation/session/stop", async () => {
30
30
  const project = await requireCurrentProject(deps.projectService);
31
31
  return deps.sessionService.stopProjectTranslatorSession(project.repoRoot);
32
32
  });
33
- app.get("/api/translation/codex/source-files", async (request) => {
33
+ app.get("/api/translation/source-files", async (request) => {
34
34
  const project = await requireCurrentProject(deps.projectService);
35
- return deps.codexTranslationService.browseSourceFiles(project.repoRoot, {
35
+ return deps.translationWorkerService.browseSourceFiles(project.repoRoot, {
36
36
  path: request.query.path,
37
37
  query: request.query.query,
38
38
  limit: request.query.limit === undefined ? undefined : Number(request.query.limit)
39
39
  });
40
40
  });
41
- app.post("/api/translation/codex/files", async (request) => {
41
+ app.post("/api/translation/files", async (request) => {
42
42
  const project = await requireCurrentProject(deps.projectService);
43
- return deps.codexTranslationService.createFileJob(project.repoRoot, request.body);
43
+ return deps.translationWorkerService.createFileJob(project.repoRoot, request.body);
44
44
  });
45
- app.post("/api/translation/codex/bootstrap", async (request) => {
45
+ app.post("/api/translation/bootstrap", async (request) => {
46
46
  const project = await requireCurrentProject(deps.projectService);
47
- return deps.codexTranslationService.createBootstrapRun(project.repoRoot, request.body);
47
+ return deps.translationWorkerService.createBootstrapRun(project.repoRoot, request.body);
48
48
  });
49
- app.post("/api/translation/codex/memory-update", async (request) => {
49
+ app.post("/api/translation/memory-update", async (request) => {
50
50
  const project = await requireCurrentProject(deps.projectService);
51
- return deps.codexTranslationService.createMemoryUpdate(project.repoRoot, request.body);
51
+ return deps.translationWorkerService.createMemoryUpdate(project.repoRoot, request.body);
52
52
  });
53
- app.get("/api/translation/codex/files/:jobId", async (request) => {
53
+ app.get("/api/translation/files/:jobId", async (request) => {
54
54
  const project = await requireCurrentProject(deps.projectService);
55
- return deps.codexTranslationService.readFileJobOutput(project.repoRoot, request.params.jobId);
55
+ return deps.translationWorkerService.readFileJobOutput(project.repoRoot, request.params.jobId);
56
56
  });
57
- app.post("/api/translation/codex/files/:jobId/promote", async (request) => {
57
+ app.post("/api/translation/files/:jobId/promote", async (request) => {
58
58
  const project = await requireCurrentProject(deps.projectService);
59
59
  const targetPath = request.body?.targetPath?.trim();
60
60
  if (!targetPath) {
@@ -64,7 +64,7 @@ export function registerCodexTranslationRoutes(app, deps) {
64
64
  statusCode: 400
65
65
  });
66
66
  }
67
- return deps.codexTranslationService.promoteFileJob(project.repoRoot, request.params.jobId, targetPath);
67
+ return deps.translationWorkerService.promoteFileJob(project.repoRoot, request.params.jobId, targetPath);
68
68
  });
69
69
  }
70
70
  async function requireCurrentProject(projectService) {
@@ -6,7 +6,7 @@ import process from "node:process";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
8
8
  import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
9
- import { renderCodexCliConfigHarnessRules, renderCodexHooksHarnessRules, renderCodexTranslatorAgentsHarnessRules, renderCodexTranslatorConfigHarnessRules, renderGateReviewerAgentRules, renderRequestGateReviewTool, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
9
+ import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTranslatorAgentRules, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
10
10
  import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
11
11
  import { renderGitignoreHarnessRules } from "../templates/harness/gitignore.js";
12
12
  import { renderProjectManagerHarnessRules } from "../templates/harness/project-manager-agent.js";
@@ -22,6 +22,12 @@ const APP_ROOT = path.resolve(CLI_DIR, "../../..");
22
22
  const MANIFEST_PATH = ".ai/vcm-harness-manifest.json";
23
23
  const HTML_BLOCK_PATTERN = /<!-- VCM:BEGIN(?:\s+version=\d+)? -->[\s\S]*?<!-- VCM:END -->/m;
24
24
  const HASH_BLOCK_PATTERN = /# VCM:BEGIN(?:\s+version=\d+)?\n[\s\S]*?# VCM:END/m;
25
+ const LEGACY_CODEX_HARNESS_PATHS = [
26
+ ".ai/codex",
27
+ ".ai/codex-translator",
28
+ ".claude/skills/vcm-codex-review-gate",
29
+ ".ai/tools/request-codex-review"
30
+ ];
25
31
  const VCM_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'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}));});'"'"' | curl -fsS --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
26
32
  const VCM_STOP_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'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}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/stop" -H "content-type: application/json" --data-binary @- || true'`;
27
33
  const VCM_PERMISSION_REQUEST_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'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}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/permission-request" -H "content-type: application/json" --data-binary @- || true'`;
@@ -50,6 +56,9 @@ const AGENT_FRONTMATTER = {
50
56
  },
51
57
  "gate-reviewer": {
52
58
  description: "VCM independent gate review role for architecture plans, validation adequacy, and final diffs."
59
+ },
60
+ translator: {
61
+ description: "VCM project translation tool role for conversation translation, file translation, bootstrap, and memory updates."
53
62
  }
54
63
  };
55
64
  const MANAGED_FILES = [
@@ -117,11 +126,12 @@ const MANAGED_FILES = [
117
126
  content: renderGateReviewerAgentRules()
118
127
  },
119
128
  {
120
- path: ".ai/codex-translator/AGENTS.md",
121
- title: "VCM Codex Translator",
129
+ path: ".claude/agents/translator.md",
130
+ title: "Translator Agent",
131
+ agentName: "translator",
122
132
  commentStyle: "html",
123
- category: "codex-translator-agent",
124
- content: renderCodexTranslatorAgentsHarnessRules()
133
+ category: "agent-translator",
134
+ content: renderTranslatorAgentRules()
125
135
  }
126
136
  ];
127
137
  const DURABLE_DOC_TEMPLATES = [
@@ -181,24 +191,6 @@ const WHOLE_FILES = [
181
191
  mode: 0o644,
182
192
  content: renderSkillFile("VCM Gate Review Skill", "vcm-gate-review", "Use when project-manager reaches a Gate Review trigger or receives a VCM Gate Review callback.", renderVcmGateReviewSkillRules())
183
193
  },
184
- {
185
- path: ".ai/codex-translator/config.toml",
186
- category: "codex-translator-config",
187
- mode: 0o644,
188
- content: renderCodexTranslatorConfigHarnessRules()
189
- },
190
- {
191
- path: ".ai/codex-translator/.codex/config.toml",
192
- category: "codex-translator-hooks",
193
- mode: 0o644,
194
- content: renderCodexCliConfigHarnessRules()
195
- },
196
- {
197
- path: ".ai/codex-translator/.codex/hooks.json",
198
- category: "codex-translator-hooks",
199
- mode: 0o644,
200
- content: renderCodexHooksHarnessRules("codex-translator")
201
- },
202
194
  {
203
195
  path: ".ai/tools/request-gate-review",
204
196
  category: "runtime-tool",
@@ -271,6 +263,7 @@ async function main() {
271
263
  await installWholeFile({ projectRoot, file, dryRun, operations });
272
264
  }
273
265
  await removeLegacyFlatSkillFiles({ projectRoot, dryRun, operations });
266
+ await removeLegacyCodexHarnessPaths({ projectRoot, dryRun, operations });
274
267
  printReport({ projectRoot, dryRun, operations });
275
268
  }
276
269
  function parseArgs(argv) {
@@ -417,8 +410,6 @@ function fixedDirectories() {
417
410
  ".claude/skills/vcm-long-running-validation/",
418
411
  ".claude/skills/vcm-route-message/",
419
412
  ".claude/skills/vcm-gate-review/",
420
- ".ai/codex-translator/",
421
- ".ai/codex-translator/.codex/",
422
413
  ".ai/vcm/translations/",
423
414
  ".ai/vcm/gate-reviews/",
424
415
  ".ai/tools/",
@@ -632,6 +623,20 @@ async function removeLegacyFlatSkillFiles({ projectRoot, dryRun, operations }) {
632
623
  operations.push(done(legacy.path, "deleted legacy flat skill file"));
633
624
  }
634
625
  }
626
+ async function removeLegacyCodexHarnessPaths({ projectRoot, dryRun, operations }) {
627
+ for (const relativePath of LEGACY_CODEX_HARNESS_PATHS) {
628
+ const targetPath = resolveInside(projectRoot, relativePath);
629
+ if (!await pathExists(targetPath)) {
630
+ continue;
631
+ }
632
+ if (dryRun) {
633
+ operations.push(plan(relativePath, "delete legacy Codex harness path"));
634
+ continue;
635
+ }
636
+ await fs.rm(targetPath, { recursive: true, force: true });
637
+ operations.push(done(relativePath, "deleted legacy Codex harness path"));
638
+ }
639
+ }
635
640
  function stripSkillFrontmatter(content) {
636
641
  return content.replace(/^---\n[\s\S]*?\n---\n\n/, "");
637
642
  }
@@ -7,12 +7,11 @@ import { createClaudeAdapter } from "./adapters/claude-adapter.js";
7
7
  import { createCommandRunner } from "./adapters/command-runner.js";
8
8
  import { createCommandDispatcher } from "./services/command-dispatcher.js";
9
9
  import { createClaudeHookService } from "./services/claude-hook-service.js";
10
- import { createCodexHookService } from "./services/codex-hook-service.js";
11
10
  import { createGitAdapter } from "./adapters/git-adapter.js";
12
11
  import { createAppSettingsService } from "./services/app-settings-service.js";
13
12
  import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
14
13
  import { createGateReviewService } from "./services/gate-review-service.js";
15
- import { createCodexTranslationService } from "./services/codex-translation-service.js";
14
+ import { createTranslationWorkerService } from "./services/translation-worker-service.js";
16
15
  import { createHarnessService, createScriptFixedHarnessInstaller } from "./services/harness-service.js";
17
16
  import { createNodeFileSystemAdapter } from "./adapters/filesystem.js";
18
17
  import { createNodePtyTerminalRuntime } from "./runtime/node-pty-runtime.js";
@@ -22,7 +21,6 @@ import { createWeixinIlinkChannel } from "./gateway/channels/weixin-ilink-channe
22
21
  import { createGatewayAuditLog } from "./gateway/gateway-audit-log.js";
23
22
  import { createGatewayService } from "./gateway/gateway-service.js";
24
23
  import { createGatewaySettingsService } from "./gateway/gateway-settings-service.js";
25
- import { resolveCodexSandboxMode } from "./codex-sandbox-mode.js";
26
24
  import { createJobGuardService } from "./services/job-guard-service.js";
27
25
  import { createProjectService } from "./services/project-service.js";
28
26
  import { createSessionRegistry } from "./runtime/session-registry.js";
@@ -36,9 +34,8 @@ import { createDiagnosticsService } from "./services/diagnostics-service.js";
36
34
  import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
37
35
  import { registerArtifactRoutes } from "./api/artifact-routes.js";
38
36
  import { registerClaudeHookRoutes } from "./api/claude-hook-routes.js";
39
- import { registerCodexHookRoutes } from "./api/codex-hook-routes.js";
40
37
  import { registerGateReviewRoutes } from "./api/gate-review-routes.js";
41
- import { registerCodexTranslationRoutes } from "./api/codex-translation-routes.js";
38
+ import { registerTranslationWorkerRoutes } from "./api/translation-worker-routes.js";
42
39
  import { registerHarnessRoutes } from "./api/harness-routes.js";
43
40
  import { registerMessageRoutes } from "./api/message-routes.js";
44
41
  import { registerProjectRoutes } from "./api/project-routes.js";
@@ -66,19 +63,18 @@ export async function createServer(deps, options = {}) {
66
63
  registerDiagnosticsRoutes(app, { diagnosticsService: deps.diagnosticsService });
67
64
  registerAppSettingsRoutes(app, { appSettings: deps.appSettings });
68
65
  registerClaudeHookRoutes(app, { claudeHookService: deps.claudeHookService });
69
- registerCodexHookRoutes(app, { codexHookService: deps.codexHookService });
70
66
  registerGateReviewRoutes(app, {
71
67
  projectService: deps.projectService,
72
68
  gateReviewService: deps.gateReviewService
73
69
  });
74
- registerCodexTranslationRoutes(app, {
70
+ registerTranslationWorkerRoutes(app, {
75
71
  projectService: deps.projectService,
76
- codexTranslationService: deps.codexTranslationService,
72
+ translationWorkerService: deps.translationWorkerService,
77
73
  sessionService: deps.sessionService
78
74
  });
79
75
  registerProjectRoutes(app, {
80
76
  projectService: deps.projectService,
81
- codexTranslationService: deps.codexTranslationService
77
+ translationWorkerService: deps.translationWorkerService
82
78
  });
83
79
  registerHarnessRoutes(app, {
84
80
  projectService: deps.projectService,
@@ -142,7 +138,7 @@ export async function createServer(deps, options = {}) {
142
138
  }
143
139
  async function cleanupRecentTranslationRuntime(deps) {
144
140
  const repoRoots = await deps.projectService.getRecentRepositoryPaths();
145
- await Promise.all(repoRoots.map((repoRoot) => deps.codexTranslationService.cleanupStartupRuntime(repoRoot)));
141
+ await Promise.all(repoRoots.map((repoRoot) => deps.translationWorkerService.cleanupStartupRuntime(repoRoot)));
146
142
  }
147
143
  export async function startServer(options = {}) {
148
144
  const host = options.host ?? "127.0.0.1";
@@ -186,8 +182,7 @@ export function createDefaultServerDeps(options = {}) {
186
182
  artifactService,
187
183
  projectService,
188
184
  taskService,
189
- apiUrl: options.apiUrl,
190
- sandboxMode: resolveCodexSandboxMode(process.env)
185
+ apiUrl: options.apiUrl
191
186
  });
192
187
  const commandDispatcher = createCommandDispatcher({
193
188
  runtime,
@@ -223,7 +218,7 @@ export function createDefaultServerDeps(options = {}) {
223
218
  sessionService,
224
219
  roundService
225
220
  });
226
- const codexTranslationService = createCodexTranslationService({
221
+ const translationWorkerService = createTranslationWorkerService({
227
222
  fs,
228
223
  runtime,
229
224
  sessionService
@@ -234,7 +229,7 @@ export function createDefaultServerDeps(options = {}) {
234
229
  sessionRegistry: registry,
235
230
  transcripts,
236
231
  sessionService,
237
- codexTranslationService,
232
+ translationWorkerService,
238
233
  fs,
239
234
  projectService,
240
235
  appSettings
@@ -268,12 +263,8 @@ export function createDefaultServerDeps(options = {}) {
268
263
  appSettings,
269
264
  runtime,
270
265
  gatewayService,
271
- jobGuard: createJobGuardService()
272
- });
273
- const codexHookService = createCodexHookService({
274
- projectService,
275
- sessionService,
276
- codexTranslationService
266
+ jobGuard: createJobGuardService(),
267
+ translationWorkerService
277
268
  });
278
269
  const diagnosticsService = createDiagnosticsService({
279
270
  appRoot: getAppRoot(),
@@ -290,10 +281,9 @@ export function createDefaultServerDeps(options = {}) {
290
281
  harnessService,
291
282
  commandDispatcher,
292
283
  claudeHookService,
293
- codexHookService,
294
284
  messageService,
295
285
  gateReviewService,
296
- codexTranslationService,
286
+ translationWorkerService,
297
287
  roundService,
298
288
  statusService,
299
289
  translationService,
@@ -1,4 +1,4 @@
1
- import { isGateReviewerRoleName, isVcmRoleName } from "../../shared/constants.js";
1
+ import { isGateReviewerRoleName, isTranslatorToolRoleName, isVcmRoleName } from "../../shared/constants.js";
2
2
  import { VcmError } from "../errors.js";
3
3
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
4
4
  import { getTaskRuntimeRepoRoot } from "./task-service.js";
@@ -33,6 +33,36 @@ export function createClaudeHookService(deps) {
33
33
  taskRepoRoot
34
34
  };
35
35
  }
36
+ async function getTranslatorHookContext() {
37
+ const project = await deps.projectService.getCurrentProject();
38
+ if (!project) {
39
+ throw new VcmError({
40
+ code: "PROJECT_NOT_CONNECTED",
41
+ message: "Connect a repository before accepting Translator hooks.",
42
+ statusCode: 409
43
+ });
44
+ }
45
+ return { project };
46
+ }
47
+ async function processTranslatorHook(input) {
48
+ const eventName = parseHookEvent(input.event.hook_event_name);
49
+ const context = await getTranslatorHookContext();
50
+ const session = await deps.sessionService.recordProjectTranslatorHookEvent(context.project.repoRoot, {
51
+ eventName,
52
+ sessionId: stringOrUndefined(input.event.session_id),
53
+ transcriptPath: stringOrUndefined(input.event.transcript_path),
54
+ cwd: stringOrUndefined(input.event.cwd)
55
+ });
56
+ await deps.translationWorkerService?.handleTranslatorHook(context.project.repoRoot, eventName, input.taskSlug);
57
+ return {
58
+ ok: true,
59
+ eventName,
60
+ taskSlug: input.taskSlug,
61
+ role: input.role,
62
+ sessionUpdated: Boolean(session),
63
+ dispatchedCount: 0
64
+ };
65
+ }
36
66
  async function resolveHookTaskSlug(repoRoot, input) {
37
67
  if (!isGateReviewerRoleName(input.role)) {
38
68
  return input.taskSlug;
@@ -307,6 +337,20 @@ export function createClaudeHookService(deps) {
307
337
  }
308
338
  async function handlePermissionRequestHook(input) {
309
339
  if (!isVcmRoleName(input.role)) {
340
+ if (isTranslatorToolRoleName(input.role)) {
341
+ const preferences = await deps.appSettings.getPreferences();
342
+ if (preferences.permissionRequestMode !== "allowAll") {
343
+ return undefined;
344
+ }
345
+ return {
346
+ hookSpecificOutput: {
347
+ hookEventName: "PermissionRequest",
348
+ decision: {
349
+ behavior: "allow"
350
+ }
351
+ }
352
+ };
353
+ }
310
354
  throw new VcmError({
311
355
  code: "HOOK_ROLE_INVALID",
312
356
  message: `Unknown hook role: ${input.role}`,
@@ -337,6 +381,9 @@ export function createClaudeHookService(deps) {
337
381
  }
338
382
  return {
339
383
  async handleHook(input) {
384
+ if (isTranslatorToolRoleName(input.role)) {
385
+ return processTranslatorHook(input);
386
+ }
340
387
  const eventName = parseHookEvent(input.event.hook_event_name);
341
388
  if (eventName === "UserPromptSubmit") {
342
389
  return handleUserPromptSubmitHook(input);
@@ -352,6 +399,9 @@ export function createClaudeHookService(deps) {
352
399
  return processStopHook(input, { allowBlock: false });
353
400
  },
354
401
  handleStopHook(input) {
402
+ if (isTranslatorToolRoleName(input.role)) {
403
+ return processTranslatorHook(input);
404
+ }
355
405
  return processStopHook(input, { allowBlock: true });
356
406
  },
357
407
  handlePermissionRequestHook
@@ -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 { renderCodexCliConfigHarnessRules, renderCodexHooksHarnessRules, renderCodexTranslatorAgentsHarnessRules, renderCodexTranslatorConfigHarnessRules, renderGateReviewerAgentRules, renderRequestGateReviewTool, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
7
+ import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTranslatorAgentRules, 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";
@@ -25,6 +25,12 @@ export const VCM_HARNESS_VERSION = 1;
25
25
  const MANAGED_BLOCK_PATTERN = /<!-- VCM:BEGIN(?:\s+version=(\d+))? -->[\s\S]*?<!-- VCM:END -->/m;
26
26
  const HASH_MANAGED_BLOCK_PATTERN = /# VCM:BEGIN(?:\s+version=(\d+))?\n[\s\S]*?# VCM:END/m;
27
27
  const CLAUDE_SETTINGS_PATH = ".claude/settings.json";
28
+ const LEGACY_CODEX_HARNESS_PATHS = [
29
+ ".ai/codex",
30
+ ".ai/codex-translator",
31
+ ".claude/skills/vcm-codex-review-gate",
32
+ ".ai/tools/request-codex-review"
33
+ ];
28
34
  const VCM_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'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}));});'"'"' | curl -fsS --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
29
35
  const VCM_STOP_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'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}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/stop" -H "content-type: application/json" --data-binary @- || true'`;
30
36
  const VCM_PERMISSION_REQUEST_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'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}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/permission-request" -H "content-type: application/json" --data-binary @- || true'`;
@@ -107,31 +113,11 @@ const HARNESS_FILES = [
107
113
  renderRules: renderGateReviewerAgentRules
108
114
  },
109
115
  {
110
- kind: "codex-translator-agents",
111
- path: ".ai/codex-translator/AGENTS.md",
112
- title: "VCM Codex Translator",
113
- renderRules: renderCodexTranslatorAgentsHarnessRules
114
- },
115
- {
116
- kind: "codex-translator-config",
117
- path: ".ai/codex-translator/config.toml",
118
- title: "VCM Codex Translator Config",
119
- ownership: "raw-file",
120
- renderRules: renderCodexTranslatorConfigHarnessRules
121
- },
122
- {
123
- kind: "codex-translator-cli-config",
124
- path: ".ai/codex-translator/.codex/config.toml",
125
- title: "VCM Codex Translator CLI Config",
126
- ownership: "raw-file",
127
- renderRules: renderCodexCliConfigHarnessRules
128
- },
129
- {
130
- kind: "codex-translator-hooks",
131
- path: ".ai/codex-translator/.codex/hooks.json",
132
- title: "VCM Codex Translator Hooks",
133
- ownership: "raw-file",
134
- renderRules: () => renderCodexHooksHarnessRules("codex-translator")
116
+ kind: "agent-translator",
117
+ path: ".claude/agents/translator.md",
118
+ title: "Translator Agent",
119
+ frontmatter: renderAgentFrontmatter("translator", "VCM project translation tool role for conversation translation, file translation, bootstrap, and memory updates."),
120
+ renderRules: renderTranslatorAgentRules
135
121
  },
136
122
  {
137
123
  kind: "tool-request-gate-review",
@@ -175,7 +161,8 @@ export function createHarnessService(deps) {
175
161
  return {
176
162
  async getHarnessStatus(repoRoot) {
177
163
  const analyses = await analyzeHarnessFiles(deps.fs, repoRoot);
178
- return renderHarnessStatus(analyses);
164
+ const legacyChanges = await analyzeLegacyCodexHarnessPaths(deps.fs, repoRoot);
165
+ return renderHarnessStatus(analyses, legacyChanges);
179
166
  },
180
167
  async applyHarness(repoRoot) {
181
168
  if (deps.runFixedInstaller) {
@@ -192,6 +179,8 @@ export function createHarnessService(deps) {
192
179
  changedFiles.push(analysis.plannedChange);
193
180
  }
194
181
  }
182
+ const legacyChanges = await removeLegacyCodexHarnessPaths(deps.fs, repoRoot);
183
+ changedFiles.push(...legacyChanges);
195
184
  return {
196
185
  version: VCM_HARNESS_VERSION,
197
186
  changedFiles,
@@ -489,11 +478,42 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
489
478
  : currentContent.replace(managedBlockPattern, expectedBlock)
490
479
  };
491
480
  }
492
- function renderHarnessStatus(analyses) {
481
+ async function analyzeLegacyCodexHarnessPaths(fs, repoRoot) {
482
+ const changes = [];
483
+ for (const relativePath of LEGACY_CODEX_HARNESS_PATHS) {
484
+ if (!await fs.pathExists(resolveHarnessPath(repoRoot, relativePath))) {
485
+ continue;
486
+ }
487
+ changes.push({
488
+ path: relativePath,
489
+ action: "delete",
490
+ reason: "Legacy Codex harness path is obsolete; VCM now uses Claude Code Gate Reviewer and Translator roles."
491
+ });
492
+ }
493
+ return changes;
494
+ }
495
+ async function removeLegacyCodexHarnessPaths(fs, repoRoot) {
496
+ const changes = await analyzeLegacyCodexHarnessPaths(fs, repoRoot);
497
+ if (changes.length === 0) {
498
+ return [];
499
+ }
500
+ if (!fs.removePath) {
501
+ return [];
502
+ }
503
+ for (const change of changes) {
504
+ await fs.removePath(resolveHarnessPath(repoRoot, change.path), {
505
+ recursive: true,
506
+ force: true
507
+ });
508
+ }
509
+ return changes;
510
+ }
511
+ function renderHarnessStatus(analyses, legacyChanges = []) {
493
512
  const files = analyses.map((analysis) => analysis.status);
494
513
  const plannedChanges = analyses
495
514
  .map((analysis) => analysis.plannedChange)
496
- .filter((change) => Boolean(change));
515
+ .filter((change) => Boolean(change))
516
+ .concat(legacyChanges);
497
517
  // Derive `initialized`: the VCM harness is considered installed when at least one
498
518
  // VCM-exclusive marker is present (per analysis):
499
519
  // - status.hasManagedBlock === true (a managed block already lives in the file), OR