vibe-coding-master 0.3.28 → 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.
- package/README.md +14 -18
- package/dist/backend/api/project-routes.js +1 -1
- package/dist/backend/api/task-routes.js +2 -2
- package/dist/backend/api/{codex-translation-routes.js → translation-worker-routes.js} +21 -21
- package/dist/backend/cli/install-vcm-harness.js +30 -25
- package/dist/backend/gateway/gateway-service.js +24 -5
- package/dist/backend/server.js +12 -22
- package/dist/backend/services/claude-hook-service.js +93 -26
- package/dist/backend/services/harness-service.js +49 -29
- package/dist/backend/services/message-service.js +9 -6
- package/dist/backend/services/project-service.js +2 -2
- package/dist/backend/services/session-service.js +99 -172
- package/dist/backend/services/translation-service.js +17 -17
- package/dist/backend/services/{codex-translation-service.js → translation-worker-service.js} +11 -11
- package/dist/backend/templates/harness/gate-review.js +2 -58
- package/dist/shared/constants.js +19 -12
- package/dist/shared/types/app-settings.js +1 -1
- package/dist/shared/types/session.js +2 -14
- package/dist-frontend/assets/{index-Cfum1Prr.css → index-BvUHLq4X.css} +1 -1
- package/dist-frontend/assets/{index-x9I-bGUt.js → index-D0ezB4na.js} +36 -36
- package/dist-frontend/index.html +2 -2
- package/docs/{codex-translation-plan.md → claude-code-translation-plan.md} +151 -153
- package/docs/full-harness-baseline.md +3 -7
- package/docs/gate-review-gates.md +13 -5
- package/docs/gateway-design.md +1 -1
- package/docs/product-design.md +12 -11
- package/docs/v0.4-custom-workflow-plan.md +0 -1
- package/docs/vcm-cc-best-practices.md +2 -5
- package/package.json +1 -1
- package/dist/backend/api/codex-hook-routes.js +0 -9
- package/dist/backend/codex-sandbox-mode.js +0 -28
- package/dist/backend/services/codex-hook-service.js +0 -71
- package/dist/shared/types/codex-hook.js +0 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ VCM is designed for long-running coding work where one Claude Code conversation
|
|
|
11
11
|
|
|
12
12
|
Each role runs as a real Claude Code process inside an embedded terminal. The GUI lets the user start, stop, resume, restart, switch, observe, and manually intervene in those sessions without juggling separate terminal windows.
|
|
13
13
|
|
|
14
|
-
When Gate Review Gates are enabled for a task, or when a Gate Reviewer session already exists, the workspace can also show a fifth `Gate Reviewer` terminal role. It
|
|
14
|
+
When Gate Review Gates are enabled for a task, or when a Gate Reviewer session already exists, the workspace can also show a fifth `Gate Reviewer` terminal role. It is an optional VCM flow role powered by Claude Code. Its session is project-scoped and reusable across tasks, but each gate turn is bound to the current task/worktree, writes reports under the task worktree, and stays outside normal PM route-file dispatch.
|
|
15
15
|
|
|
16
16
|
## Current Capabilities
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ When Gate Review Gates are enabled for a task, or when a Gate Reviewer session a
|
|
|
21
21
|
- Connected repository status for the base repo, including branch, upstream status, commit hash, dirty state, and fast-forward-only pull.
|
|
22
22
|
- Embedded Claude Code terminals powered by `node-pty` and `xterm.js`.
|
|
23
23
|
- One Claude Code session per role, with role tabs in the task header.
|
|
24
|
-
- Optional Gate Reviewer
|
|
24
|
+
- Optional Gate Reviewer VCM flow role when any Gate Review Gate is enabled.
|
|
25
25
|
- Role session recovery through persisted Claude session ids and `claude --resume`.
|
|
26
26
|
- Permission mode selection before start, resume, or restart:
|
|
27
27
|
- `default`
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
@@ -350,14 +349,14 @@ Typical mobile flow:
|
|
|
350
349
|
- `/pull-current`: runs the same fast-forward-only connected repository pull as the desktop `Pull` button.
|
|
351
350
|
- `/tasks`: lists tasks for the selected project.
|
|
352
351
|
- `/use-task <index-or-task-slug>`: selects the Gateway's current task context.
|
|
353
|
-
- `/create-task <task-slug> [title]`: creates a worktree-backed task and starts the four role sessions using the saved launch template.
|
|
352
|
+
- `/create-task <task-slug> [title]`: creates a worktree-backed task and starts the four core role sessions using the saved launch template. If any Gate Review Gate is enabled, Gateway also resumes or starts Gate Reviewer from the saved template.
|
|
354
353
|
- `/close-task`: starts a destructive close confirmation for the current task.
|
|
355
354
|
- `/close-task confirm <task-slug>`: closes the task through VCM cleanup after exact slug confirmation.
|
|
356
355
|
- `/translate on` and `/translate off`: changes Gateway translation for mobile messages.
|
|
357
356
|
|
|
358
357
|
`/pull-current` only pulls the connected base repository. It does not pull task worktrees, stash local changes, merge divergent branches, or run arbitrary shell commands.
|
|
359
358
|
|
|
360
|
-
`/create-task` uses the saved launch template from the desktop settings. The template controls permission mode, model, effort, and auto orchestration for the four core role sessions. Gateway translation uses the global Gateway translation setting, not the launch template.
|
|
359
|
+
`/create-task` uses the saved launch template from the desktop settings. The template controls permission mode, model, effort, and auto orchestration for the four core role sessions plus the optional Gate Reviewer. Gateway translation uses the global Gateway translation setting, not the launch template.
|
|
361
360
|
|
|
362
361
|
`/close-task` is destructive. It stops VCM-managed role sessions and removes task-owned worktree/branch state according to the same cleanup behavior as the desktop `Close Task` action.
|
|
363
362
|
|
|
@@ -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`
|
|
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
|
|
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
|
|
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
|
-
.
|
|
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
|
|
@@ -574,7 +570,7 @@ When it is on, VCM is in auto mode:
|
|
|
574
570
|
- When the target role later reaches `Stop`, VCM scans again and may deliver the next pending route file.
|
|
575
571
|
- If auto orchestration gets stuck after a manual copy/paste recovery, `Mark All Done` clears pending route files. It does not mutate message history.
|
|
576
572
|
|
|
577
|
-
VCM Harness injects Claude Code `UserPromptSubmit` and `Stop` hooks into `.claude/settings.json`. Role tabs become `running` when Claude Code accepts a prompt and `idle` after `Stop`; VCM also marks a role `running` immediately after it writes a message to that embedded terminal. Gate Reviewer
|
|
573
|
+
VCM Harness injects Claude Code `UserPromptSubmit` and `Stop` hooks into `.claude/settings.json`. Role tabs become `running` when Claude Code accepts a prompt and `idle` after `Stop`; VCM also marks a role `running` immediately after it writes a message to that embedded terminal. Gate Reviewer uses the same Claude hook/Round/translation path as other VCM flow roles, while VCM still owns gate report polling and the PM callback. The terminal process status is tracked separately. Claude role rules require a role to end its turn after writing or updating a message route file, rather than polling, looping, or waiting for another role inside the same Claude Code turn.
|
|
578
574
|
|
|
579
575
|
The implementation keeps only the active manual/auto orchestration mode. It does not expose pause/resume, stage/approve/reject, or a separate agent-facing message CLI.
|
|
580
576
|
|
|
@@ -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/
|
|
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.
|
|
8
|
+
await deps.translationWorkerService?.cleanupStartupRuntime(project.repoRoot);
|
|
9
9
|
return project;
|
|
10
10
|
});
|
|
11
11
|
app.get("/api/projects/current", async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CORE_VCM_ROLE_NAMES, DISPATCHABLE_ROLES } from "../../shared/constants.js";
|
|
2
2
|
import { isOpenFileLimitError, VcmError } from "../errors.js";
|
|
3
3
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
4
4
|
export function registerTaskRoutes(app, deps) {
|
|
@@ -40,7 +40,7 @@ export function registerTaskRoutes(app, deps) {
|
|
|
40
40
|
async function stopRunningRoleSessions(deps, repoRoot, taskSlug) {
|
|
41
41
|
const sessions = await deps.sessionService.listRoleSessions(repoRoot, taskSlug);
|
|
42
42
|
for (const session of sessions) {
|
|
43
|
-
if (session.status === "running" &&
|
|
43
|
+
if (session.status === "running" && CORE_VCM_ROLE_NAMES.some((role) => role === session.role)) {
|
|
44
44
|
await deps.sessionService.stopRoleSession(repoRoot, taskSlug, session.role);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -1,60 +1,60 @@
|
|
|
1
1
|
import { VcmError } from "../errors.js";
|
|
2
|
-
export function
|
|
3
|
-
app.get("/api/translation/
|
|
2
|
+
export function registerTranslationWorkerRoutes(app, deps) {
|
|
3
|
+
app.get("/api/translation/state", async () => {
|
|
4
4
|
const project = await requireCurrentProject(deps.projectService);
|
|
5
|
-
return deps.
|
|
5
|
+
return deps.translationWorkerService.getState(project.repoRoot, {
|
|
6
6
|
visibility: "public"
|
|
7
7
|
});
|
|
8
8
|
});
|
|
9
|
-
app.get("/api/translation/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
33
|
+
app.get("/api/translation/source-files", async (request) => {
|
|
34
34
|
const project = await requireCurrentProject(deps.projectService);
|
|
35
|
-
return deps.
|
|
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/
|
|
41
|
+
app.post("/api/translation/files", async (request) => {
|
|
42
42
|
const project = await requireCurrentProject(deps.projectService);
|
|
43
|
-
return deps.
|
|
43
|
+
return deps.translationWorkerService.createFileJob(project.repoRoot, request.body);
|
|
44
44
|
});
|
|
45
|
-
app.post("/api/translation/
|
|
45
|
+
app.post("/api/translation/bootstrap", async (request) => {
|
|
46
46
|
const project = await requireCurrentProject(deps.projectService);
|
|
47
|
-
return deps.
|
|
47
|
+
return deps.translationWorkerService.createBootstrapRun(project.repoRoot, request.body);
|
|
48
48
|
});
|
|
49
|
-
app.post("/api/translation/
|
|
49
|
+
app.post("/api/translation/memory-update", async (request) => {
|
|
50
50
|
const project = await requireCurrentProject(deps.projectService);
|
|
51
|
-
return deps.
|
|
51
|
+
return deps.translationWorkerService.createMemoryUpdate(project.repoRoot, request.body);
|
|
52
52
|
});
|
|
53
|
-
app.get("/api/translation/
|
|
53
|
+
app.get("/api/translation/files/:jobId", async (request) => {
|
|
54
54
|
const project = await requireCurrentProject(deps.projectService);
|
|
55
|
-
return deps.
|
|
55
|
+
return deps.translationWorkerService.readFileJobOutput(project.repoRoot, request.params.jobId);
|
|
56
56
|
});
|
|
57
|
-
app.post("/api/translation/
|
|
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.
|
|
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 {
|
|
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: ".
|
|
121
|
-
title: "
|
|
129
|
+
path: ".claude/agents/translator.md",
|
|
130
|
+
title: "Translator Agent",
|
|
131
|
+
agentName: "translator",
|
|
122
132
|
commentStyle: "html",
|
|
123
|
-
category: "
|
|
124
|
-
content:
|
|
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
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { CORE_VCM_ROLE_DEFINITIONS, CORE_VCM_ROLE_NAMES, GATE_REVIEWER_ROLE_DEFINITION } from "../../shared/constants.js";
|
|
3
3
|
import { VcmError } from "../errors.js";
|
|
4
4
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
5
5
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
@@ -407,17 +407,36 @@ export function createGatewayService(deps) {
|
|
|
407
407
|
taskSlug: task.taskSlug,
|
|
408
408
|
mode: template.autoOrchestration ? "auto" : "manual"
|
|
409
409
|
});
|
|
410
|
+
const gateReviewSettings = await deps.appSettings.getGateReviewSettings(project.repoRoot, task.taskSlug);
|
|
411
|
+
const roleDefinitions = [
|
|
412
|
+
...CORE_VCM_ROLE_DEFINITIONS,
|
|
413
|
+
...(gateReviewSettings.enabled ? [GATE_REVIEWER_ROLE_DEFINITION] : [])
|
|
414
|
+
];
|
|
410
415
|
const startedRoles = [];
|
|
411
|
-
for (const definition of
|
|
416
|
+
for (const definition of roleDefinitions) {
|
|
412
417
|
const roleTemplate = template.roles[definition.name];
|
|
413
418
|
try {
|
|
414
|
-
|
|
419
|
+
const sessionInput = {
|
|
415
420
|
cols: 100,
|
|
416
421
|
rows: 28,
|
|
417
422
|
permissionMode: roleTemplate.permissionMode,
|
|
418
423
|
model: roleTemplate.model,
|
|
419
424
|
effort: roleTemplate.effort
|
|
420
|
-
}
|
|
425
|
+
};
|
|
426
|
+
const existing = await deps.sessionService.getRoleSession(project.repoRoot, task.taskSlug, definition.name);
|
|
427
|
+
if (existing?.status === "running") {
|
|
428
|
+
if (definition.name === "gate-reviewer") {
|
|
429
|
+
await deps.sessionService.resumeRoleSession(project.repoRoot, task.taskSlug, definition.name, sessionInput);
|
|
430
|
+
}
|
|
431
|
+
startedRoles.push(definition.name);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (existing?.claudeSessionId) {
|
|
435
|
+
await deps.sessionService.resumeRoleSession(project.repoRoot, task.taskSlug, definition.name, sessionInput);
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
await deps.sessionService.startRoleSession(project.repoRoot, task.taskSlug, definition.name, sessionInput);
|
|
439
|
+
}
|
|
421
440
|
startedRoles.push(definition.name);
|
|
422
441
|
}
|
|
423
442
|
catch (error) {
|
|
@@ -536,7 +555,7 @@ export function createGatewayService(deps) {
|
|
|
536
555
|
async function stopRunningRoleSessions(repoRoot, taskSlug) {
|
|
537
556
|
const sessions = await deps.sessionService.listRoleSessions(repoRoot, taskSlug);
|
|
538
557
|
for (const session of sessions) {
|
|
539
|
-
if (session.status === "running") {
|
|
558
|
+
if (session.status === "running" && CORE_VCM_ROLE_NAMES.some((role) => role === session.role)) {
|
|
540
559
|
await deps.sessionService.stopRoleSession(repoRoot, taskSlug, session.role);
|
|
541
560
|
}
|
|
542
561
|
}
|
package/dist/backend/server.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
-
|
|
70
|
+
registerTranslationWorkerRoutes(app, {
|
|
75
71
|
projectService: deps.projectService,
|
|
76
|
-
|
|
72
|
+
translationWorkerService: deps.translationWorkerService,
|
|
77
73
|
sessionService: deps.sessionService
|
|
78
74
|
});
|
|
79
75
|
registerProjectRoutes(app, {
|
|
80
76
|
projectService: deps.projectService,
|
|
81
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
286
|
+
translationWorkerService,
|
|
297
287
|
roundService,
|
|
298
288
|
statusService,
|
|
299
289
|
translationService,
|