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.
- package/README.md +9 -13
- package/dist/backend/api/project-routes.js +1 -1
- 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/server.js +12 -22
- package/dist/backend/services/claude-hook-service.js +51 -1
- package/dist/backend/services/harness-service.js +49 -29
- package/dist/backend/services/session-service.js +56 -163
- 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 +13 -10
- 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-K34QFpWK.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/gateway-design.md +1 -1
- package/docs/product-design.md +2 -2
- 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
|
@@ -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.
|
|
@@ -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
|
|
@@ -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,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
|
}
|
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,
|
|
@@ -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 {
|
|
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: "
|
|
111
|
-
path: ".
|
|
112
|
-
title: "
|
|
113
|
-
|
|
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
|
-
|
|
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
|
|
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
|