vibe-coding-master 0.3.25 → 0.3.27
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 +7 -7
- package/dist/backend/api/codex-hook-routes.js +0 -7
- package/dist/backend/api/gate-review-routes.js +58 -0
- package/dist/backend/api/task-routes.js +2 -2
- package/dist/backend/cli/install-vcm-harness.js +17 -57
- package/dist/backend/server.js +7 -8
- package/dist/backend/services/app-settings-service.js +15 -15
- package/dist/backend/services/claude-hook-service.js +138 -28
- package/dist/backend/services/codex-hook-service.js +8 -50
- package/dist/backend/services/{codex-review-service.js → gate-review-service.js} +108 -108
- package/dist/backend/services/harness-service.js +17 -63
- package/dist/backend/services/session-service.js +275 -49
- package/dist/backend/services/task-service.js +2 -2
- package/dist/backend/templates/harness/claude-root.js +2 -2
- package/dist/backend/templates/harness/{codex-review.js → gate-review.js} +35 -278
- package/dist/backend/templates/harness/project-manager-agent.js +7 -7
- package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +5 -5
- package/dist/shared/constants.js +8 -5
- package/dist/shared/types/{codex-review.js → gate-review.js} +1 -1
- package/dist-frontend/assets/{index-CKWy15WL.js → index-DVy34Iwn.js} +31 -31
- package/dist-frontend/index.html +1 -1
- package/docs/codex-translation-plan.md +9 -9
- package/docs/gate-review-gates.md +133 -0
- package/docs/product-design.md +37 -40
- package/package.json +1 -1
- package/scripts/verify-package.mjs +4 -4
- package/dist/backend/api/codex-review-routes.js +0 -58
- package/docs/codex-review-gates.md +0 -593
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
|
|
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 runs Claude Code as a project-scoped long-lived review session, receives short gate prompts that include the current task and worktree path, writes reports under the task worktree, and stays outside normal PM role routing.
|
|
15
15
|
|
|
16
16
|
## Current Capabilities
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ When Codex Review Gates are enabled for a task, or when a Codex Reviewer session
|
|
|
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
|
|
24
|
+
- Optional Gate Reviewer terminal 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`
|
|
@@ -29,7 +29,7 @@ When Codex Review Gates are enabled for a task, or when a Codex Reviewer session
|
|
|
29
29
|
- PM-mediated role messaging through VCM-dispatched route files.
|
|
30
30
|
- Manual and automatic orchestration modes.
|
|
31
31
|
- Two-stage VCM harness setup: deterministic fixed install plus AI-assisted bootstrap.
|
|
32
|
-
- VCM-managed root rules,
|
|
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
34
|
- Translation panel powered by the long-lived Codex Translator session.
|
|
35
35
|
- Mobile Gateway through Tencent iLink Bot API / Weixin DM, for talking to PM and managing tasks from Weixin.
|
|
@@ -142,7 +142,7 @@ Important container notes:
|
|
|
142
142
|
- Make sure the container has network access to Claude services and any configured Codex model endpoints 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
|
|
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
146
|
- Treat the container as the sandbox boundary, especially when using relaxed Claude Code permission modes.
|
|
147
147
|
|
|
148
148
|
## Basic Usage
|
|
@@ -390,7 +390,7 @@ Translation settings are local and stored in:
|
|
|
390
390
|
|
|
391
391
|
The same file stores recent repository paths and global translation preferences such as enablement, auto-send, and target language.
|
|
392
392
|
|
|
393
|
-
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
|
|
393
|
+
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.
|
|
394
394
|
|
|
395
395
|
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`.
|
|
396
396
|
|
|
@@ -485,7 +485,7 @@ For `.gitignore`, VCM uses a gitignore-native managed block:
|
|
|
485
485
|
|
|
486
486
|
`.ai/vcm/` is the active VCM local control area, and `.claude/worktrees/` is the Claude-compatible task worktree area. VCM keeps the task index in app-local project state under `<vcmDataDir>/projects/`; each task runtime repo keeps its own session, message, orchestration, and translation state.
|
|
487
487
|
|
|
488
|
-
VCM also JSON-merges `.claude/settings.json` to install Claude Code `PreToolUse`, `UserPromptSubmit`, `Stop`, and `PermissionRequest` hooks plus a managed `env.BASH_DEFAULT_TIMEOUT_MS` so foreground watch windows fit inside the Bash tool timeout. The hooks post directly to the local VCM backend, so roles do not need a VCM CLI command to confirm delivery or report turn completion. The `Stop` hook forwards the backend response to Claude Code, which lets VCM block turn-end while a validation job is still running.
|
|
488
|
+
VCM also JSON-merges `.claude/settings.json` to install Claude Code `PreToolUse`, `UserPromptSubmit`, `Stop`, and `PermissionRequest` hooks plus a managed `env.BASH_DEFAULT_TIMEOUT_MS` so foreground watch windows fit inside the Bash tool timeout. The hooks post directly to the local VCM backend, so roles do not need a VCM CLI command to confirm delivery or report turn completion. The `Stop` hook forwards the backend response to Claude Code, which lets VCM block turn-end while a validation job is still running.
|
|
489
489
|
|
|
490
490
|
Bootstrap is AI-assisted. VCM starts a visible temporary Claude Code session in the connected repository and asks it to use the `vcm-harness-bootstrap` skill. Bootstrap fills project-specific content and generated context:
|
|
491
491
|
|
|
@@ -572,7 +572,7 @@ When it is on, VCM is in auto mode:
|
|
|
572
572
|
- When the target role later reaches `Stop`, VCM scans again and may deliver the next pending route file.
|
|
573
573
|
- If auto orchestration gets stuck after a manual copy/paste recovery, `Mark All Done` clears pending route files. It does not mutate message history.
|
|
574
574
|
|
|
575
|
-
VCM Harness injects Claude Code `UserPromptSubmit` and `Stop` hooks into `.claude/settings.json
|
|
575
|
+
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 is a Claude Code role, but VCM triggers and settles gate turns directly around the gate prompt and report callback. The terminal process status is still 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.
|
|
576
576
|
|
|
577
577
|
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.
|
|
578
578
|
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
export function registerCodexHookRoutes(app, deps) {
|
|
2
|
-
app.post("/api/hooks/codex-reviewer", async (request) => {
|
|
3
|
-
return deps.codexHookService.handleHook(request.body);
|
|
4
|
-
});
|
|
5
|
-
app.post("/api/hooks/codex-reviewer/stop", async (request) => {
|
|
6
|
-
await deps.codexHookService.handleStopHook(request.body);
|
|
7
|
-
return {};
|
|
8
|
-
});
|
|
9
2
|
app.post("/api/hooks/codex-translator", async (request) => {
|
|
10
3
|
return deps.codexHookService.handleHook(request.body);
|
|
11
4
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { VcmError } from "../errors.js";
|
|
2
|
+
import { isGateReviewGate } from "../services/gate-review-service.js";
|
|
3
|
+
export function registerGateReviewRoutes(app, deps) {
|
|
4
|
+
app.get("/api/tasks/:taskSlug/gate-review", async (request) => {
|
|
5
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
6
|
+
return deps.gateReviewService.getState(project.repoRoot, request.params.taskSlug);
|
|
7
|
+
});
|
|
8
|
+
app.put("/api/tasks/:taskSlug/gate-review/settings", async (request) => {
|
|
9
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
10
|
+
return deps.gateReviewService.updateSettings(project.repoRoot, request.params.taskSlug, request.body);
|
|
11
|
+
});
|
|
12
|
+
app.post("/api/tasks/:taskSlug/gate-review/:gate/request", async (request) => {
|
|
13
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
14
|
+
const gate = parseGate(request.params.gate);
|
|
15
|
+
return deps.gateReviewService.requestReviewGate(project.repoRoot, request.params.taskSlug, gate);
|
|
16
|
+
});
|
|
17
|
+
app.post("/api/tasks/:taskSlug/gate-review/:gate/retry", async (request) => {
|
|
18
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
19
|
+
const gate = parseGate(request.params.gate);
|
|
20
|
+
return deps.gateReviewService.retryReviewGate(project.repoRoot, request.params.taskSlug, gate);
|
|
21
|
+
});
|
|
22
|
+
app.post("/api/tasks/:taskSlug/gate-review/:gate/skip", async (request) => {
|
|
23
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
24
|
+
const gate = parseGate(request.params.gate);
|
|
25
|
+
return deps.gateReviewService.skipReviewGate(project.repoRoot, request.params.taskSlug, gate, request.body);
|
|
26
|
+
});
|
|
27
|
+
app.post("/api/tasks/:taskSlug/gate-review/:gate/override", async (request) => {
|
|
28
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
29
|
+
const gate = parseGate(request.params.gate);
|
|
30
|
+
return deps.gateReviewService.overrideReviewGate(project.repoRoot, request.params.taskSlug, gate, request.body);
|
|
31
|
+
});
|
|
32
|
+
app.get("/api/tasks/:taskSlug/gate-review/:gate/report", async (request) => {
|
|
33
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
34
|
+
const gate = parseGate(request.params.gate);
|
|
35
|
+
return deps.gateReviewService.readReport(project.repoRoot, request.params.taskSlug, gate);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function parseGate(gate) {
|
|
39
|
+
if (!isGateReviewGate(gate)) {
|
|
40
|
+
throw new VcmError({
|
|
41
|
+
code: "UNKNOWN_GATE_REVIEW_GATE",
|
|
42
|
+
message: `Unknown Gate review gate: ${gate}`,
|
|
43
|
+
statusCode: 400
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return gate;
|
|
47
|
+
}
|
|
48
|
+
async function requireCurrentProject(projectService) {
|
|
49
|
+
const project = await projectService.getCurrentProject();
|
|
50
|
+
if (!project) {
|
|
51
|
+
throw new VcmError({
|
|
52
|
+
code: "PROJECT_NOT_CONNECTED",
|
|
53
|
+
message: "Connect a repository first.",
|
|
54
|
+
statusCode: 409
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return project;
|
|
58
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
|
|
1
|
+
import { DISPATCHABLE_ROLES, isVcmRoleName } 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" && isVcmRoleName(session.role)) {
|
|
44
44
|
await deps.sessionService.stopRoleSession(repoRoot, taskSlug, session.role);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -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 { renderCodexCliConfigHarnessRules, renderCodexHooksHarnessRules, renderCodexTranslatorAgentsHarnessRules, renderCodexTranslatorConfigHarnessRules, renderGateReviewerAgentRules, renderRequestGateReviewTool, 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";
|
|
@@ -31,6 +31,8 @@ const VCM_HOOK_DEFINITIONS = [
|
|
|
31
31
|
{ eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
32
32
|
{ eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
33
33
|
{ eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
|
|
34
|
+
{ eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
35
|
+
{ eventName: "PostCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
34
36
|
{ eventName: "PermissionRequest", command: VCM_PERMISSION_REQUEST_HOOK_COMMAND, timeout: 5 }
|
|
35
37
|
];
|
|
36
38
|
const AGENT_FRONTMATTER = {
|
|
@@ -45,6 +47,9 @@ const AGENT_FRONTMATTER = {
|
|
|
45
47
|
},
|
|
46
48
|
reviewer: {
|
|
47
49
|
description: "VCM independent review role for acceptance, test adequacy, scope checks, and risk findings."
|
|
50
|
+
},
|
|
51
|
+
"gate-reviewer": {
|
|
52
|
+
description: "VCM independent gate review role for architecture plans, validation adequacy, and final diffs."
|
|
48
53
|
}
|
|
49
54
|
};
|
|
50
55
|
const MANAGED_FILES = [
|
|
@@ -104,11 +109,12 @@ const MANAGED_FILES = [
|
|
|
104
109
|
content: renderPullRequestTemplateHarnessRules()
|
|
105
110
|
},
|
|
106
111
|
{
|
|
107
|
-
path: ".
|
|
108
|
-
title: "
|
|
112
|
+
path: ".claude/agents/gate-reviewer.md",
|
|
113
|
+
title: "Gate Reviewer Agent",
|
|
114
|
+
agentName: "gate-reviewer",
|
|
109
115
|
commentStyle: "html",
|
|
110
|
-
category: "
|
|
111
|
-
content:
|
|
116
|
+
category: "gate-reviewer-agent",
|
|
117
|
+
content: renderGateReviewerAgentRules()
|
|
112
118
|
},
|
|
113
119
|
{
|
|
114
120
|
path: ".ai/codex-translator/AGENTS.md",
|
|
@@ -170,28 +176,10 @@ const WHOLE_FILES = [
|
|
|
170
176
|
content: renderSkillFile("VCM Route Message Skill", "vcm-route-message", "Use when a VCM role needs to hand off work, ask a question, report a result, report a blocker, or raise a finding to another VCM role.", renderVcmRouteMessageSkillRules())
|
|
171
177
|
},
|
|
172
178
|
{
|
|
173
|
-
path: ".claude/skills/vcm-
|
|
179
|
+
path: ".claude/skills/vcm-gate-review/SKILL.md",
|
|
174
180
|
category: "skill",
|
|
175
181
|
mode: 0o644,
|
|
176
|
-
content: renderSkillFile("VCM
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
path: ".ai/codex/config.toml",
|
|
180
|
-
category: "codex-review-config",
|
|
181
|
-
mode: 0o644,
|
|
182
|
-
content: renderCodexConfigHarnessRules()
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
path: ".ai/codex/.codex/config.toml",
|
|
186
|
-
category: "codex-review-hooks",
|
|
187
|
-
mode: 0o644,
|
|
188
|
-
content: renderCodexCliConfigHarnessRules()
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
path: ".ai/codex/.codex/hooks.json",
|
|
192
|
-
category: "codex-review-hooks",
|
|
193
|
-
mode: 0o644,
|
|
194
|
-
content: renderCodexHooksHarnessRules()
|
|
182
|
+
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())
|
|
195
183
|
},
|
|
196
184
|
{
|
|
197
185
|
path: ".ai/codex-translator/config.toml",
|
|
@@ -212,34 +200,10 @@ const WHOLE_FILES = [
|
|
|
212
200
|
content: renderCodexHooksHarnessRules("codex-translator")
|
|
213
201
|
},
|
|
214
202
|
{
|
|
215
|
-
path: ".ai/
|
|
216
|
-
category: "codex-review-prompt",
|
|
217
|
-
mode: 0o644,
|
|
218
|
-
content: renderCodexArchitecturePlanPrompt()
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
path: ".ai/codex/prompts/validation-adequacy-gate.md",
|
|
222
|
-
category: "codex-review-prompt",
|
|
223
|
-
mode: 0o644,
|
|
224
|
-
content: renderCodexValidationAdequacyPrompt()
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
path: ".ai/codex/prompts/final-diff-gate.md",
|
|
228
|
-
category: "codex-review-prompt",
|
|
229
|
-
mode: 0o644,
|
|
230
|
-
content: renderCodexFinalDiffPrompt()
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
path: ".ai/codex/schemas/codex-review-result.schema.json",
|
|
234
|
-
category: "codex-review-schema",
|
|
235
|
-
mode: 0o644,
|
|
236
|
-
content: renderCodexReviewResultSchema()
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
path: ".ai/tools/request-codex-review",
|
|
203
|
+
path: ".ai/tools/request-gate-review",
|
|
240
204
|
category: "runtime-tool",
|
|
241
205
|
mode: 0o755,
|
|
242
|
-
content:
|
|
206
|
+
content: renderRequestGateReviewTool()
|
|
243
207
|
},
|
|
244
208
|
{
|
|
245
209
|
path: ".ai/tools/run-long-check",
|
|
@@ -452,15 +416,11 @@ function fixedDirectories() {
|
|
|
452
416
|
".claude/skills/vcm-harness-bootstrap/",
|
|
453
417
|
".claude/skills/vcm-long-running-validation/",
|
|
454
418
|
".claude/skills/vcm-route-message/",
|
|
455
|
-
".claude/skills/vcm-
|
|
456
|
-
".ai/codex/",
|
|
457
|
-
".ai/codex/.codex/",
|
|
458
|
-
".ai/codex/prompts/",
|
|
459
|
-
".ai/codex/schemas/",
|
|
419
|
+
".claude/skills/vcm-gate-review/",
|
|
460
420
|
".ai/codex-translator/",
|
|
461
421
|
".ai/codex-translator/.codex/",
|
|
462
422
|
".ai/vcm/translations/",
|
|
463
|
-
".ai/vcm/
|
|
423
|
+
".ai/vcm/gate-reviews/",
|
|
464
424
|
".ai/tools/",
|
|
465
425
|
".ai/generated/"
|
|
466
426
|
];
|
package/dist/backend/server.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createCodexHookService } from "./services/codex-hook-service.js";
|
|
|
11
11
|
import { createGitAdapter } from "./adapters/git-adapter.js";
|
|
12
12
|
import { createAppSettingsService } from "./services/app-settings-service.js";
|
|
13
13
|
import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
|
|
14
|
-
import {
|
|
14
|
+
import { createGateReviewService } from "./services/gate-review-service.js";
|
|
15
15
|
import { createCodexTranslationService } from "./services/codex-translation-service.js";
|
|
16
16
|
import { createHarnessService, createScriptFixedHarnessInstaller } from "./services/harness-service.js";
|
|
17
17
|
import { createNodeFileSystemAdapter } from "./adapters/filesystem.js";
|
|
@@ -37,7 +37,7 @@ import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
|
|
|
37
37
|
import { registerArtifactRoutes } from "./api/artifact-routes.js";
|
|
38
38
|
import { registerClaudeHookRoutes } from "./api/claude-hook-routes.js";
|
|
39
39
|
import { registerCodexHookRoutes } from "./api/codex-hook-routes.js";
|
|
40
|
-
import {
|
|
40
|
+
import { registerGateReviewRoutes } from "./api/gate-review-routes.js";
|
|
41
41
|
import { registerCodexTranslationRoutes } from "./api/codex-translation-routes.js";
|
|
42
42
|
import { registerHarnessRoutes } from "./api/harness-routes.js";
|
|
43
43
|
import { registerMessageRoutes } from "./api/message-routes.js";
|
|
@@ -67,9 +67,9 @@ export async function createServer(deps, options = {}) {
|
|
|
67
67
|
registerAppSettingsRoutes(app, { appSettings: deps.appSettings });
|
|
68
68
|
registerClaudeHookRoutes(app, { claudeHookService: deps.claudeHookService });
|
|
69
69
|
registerCodexHookRoutes(app, { codexHookService: deps.codexHookService });
|
|
70
|
-
|
|
70
|
+
registerGateReviewRoutes(app, {
|
|
71
71
|
projectService: deps.projectService,
|
|
72
|
-
|
|
72
|
+
gateReviewService: deps.gateReviewService
|
|
73
73
|
});
|
|
74
74
|
registerCodexTranslationRoutes(app, {
|
|
75
75
|
projectService: deps.projectService,
|
|
@@ -213,7 +213,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
213
213
|
await taskService.updateTaskStatus(repoRoot, taskSlug, status);
|
|
214
214
|
}
|
|
215
215
|
});
|
|
216
|
-
const
|
|
216
|
+
const gateReviewService = createGateReviewService({
|
|
217
217
|
fs,
|
|
218
218
|
runner,
|
|
219
219
|
runtime,
|
|
@@ -266,14 +266,13 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
266
266
|
roundService,
|
|
267
267
|
translationService,
|
|
268
268
|
appSettings,
|
|
269
|
+
runtime,
|
|
269
270
|
gatewayService,
|
|
270
271
|
jobGuard: createJobGuardService()
|
|
271
272
|
});
|
|
272
273
|
const codexHookService = createCodexHookService({
|
|
273
274
|
projectService,
|
|
274
|
-
taskService,
|
|
275
275
|
sessionService,
|
|
276
|
-
roundService,
|
|
277
276
|
codexTranslationService
|
|
278
277
|
});
|
|
279
278
|
const diagnosticsService = createDiagnosticsService({
|
|
@@ -293,7 +292,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
293
292
|
claudeHookService,
|
|
294
293
|
codexHookService,
|
|
295
294
|
messageService,
|
|
296
|
-
|
|
295
|
+
gateReviewService,
|
|
297
296
|
codexTranslationService,
|
|
298
297
|
roundService,
|
|
299
298
|
statusService,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { VCM_ROLE_NAMES } from "../../shared/constants.js";
|
|
4
|
-
import {
|
|
4
|
+
import { GATE_REVIEW_GATES } from "../../shared/types/gate-review.js";
|
|
5
5
|
import { createDefaultLaunchTemplate, DEFAULT_TRANSLATION_OUTPUT_MODE, DEFAULT_TRANSLATION_TARGET_LANGUAGE, TRANSLATION_OUTPUT_MODE_OPTIONS, TRANSLATION_TARGET_LANGUAGE_OPTIONS } from "../../shared/types/app-settings.js";
|
|
6
6
|
import { CLAUDE_MODEL_OPTIONS, SESSION_EFFORT_OPTIONS } from "../../shared/types/session.js";
|
|
7
7
|
import { resolveVcmDataDir } from "../vcm-data-dir.js";
|
|
@@ -118,26 +118,26 @@ export function createAppSettingsService(deps) {
|
|
|
118
118
|
});
|
|
119
119
|
return config;
|
|
120
120
|
},
|
|
121
|
-
async
|
|
121
|
+
async getGateReviewSettings() {
|
|
122
122
|
const settings = await loadSettings();
|
|
123
|
-
const requiredGates =
|
|
123
|
+
const requiredGates = normalizeGateReviewGates(settings.gateReview?.requiredGates);
|
|
124
124
|
return {
|
|
125
125
|
enabled: requiredGates.length > 0,
|
|
126
126
|
requiredGates
|
|
127
127
|
};
|
|
128
128
|
},
|
|
129
|
-
async
|
|
129
|
+
async updateGateReviewSettings(_repoRoot, _taskSlug, requiredGates) {
|
|
130
130
|
const current = await loadSettings();
|
|
131
|
-
const normalizedRequiredGates =
|
|
131
|
+
const normalizedRequiredGates = normalizeGateReviewGates(requiredGates);
|
|
132
132
|
const timestamp = new Date().toISOString();
|
|
133
|
-
const
|
|
133
|
+
const nextGateReview = {
|
|
134
134
|
version: 1,
|
|
135
135
|
requiredGates: normalizedRequiredGates,
|
|
136
136
|
updatedAt: timestamp
|
|
137
137
|
};
|
|
138
138
|
await saveSettings({
|
|
139
139
|
...current,
|
|
140
|
-
|
|
140
|
+
gateReview: normalizeGateReviewSettingsState(nextGateReview)
|
|
141
141
|
});
|
|
142
142
|
return {
|
|
143
143
|
enabled: normalizedRequiredGates.length > 0,
|
|
@@ -187,28 +187,28 @@ function normalizeProjectIndexFile(input) {
|
|
|
187
187
|
projects
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
|
-
function
|
|
190
|
+
function normalizeGateReviewSettingsState(input) {
|
|
191
191
|
if (!isObject(input)) {
|
|
192
192
|
return undefined;
|
|
193
193
|
}
|
|
194
194
|
return {
|
|
195
195
|
version: 1,
|
|
196
|
-
requiredGates:
|
|
196
|
+
requiredGates: normalizeGateReviewGates(input.requiredGates),
|
|
197
197
|
updatedAt: typeof input.updatedAt === "string" ? input.updatedAt : new Date(0).toISOString()
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
|
-
function
|
|
200
|
+
function normalizeGateReviewGates(input) {
|
|
201
201
|
if (!Array.isArray(input)) {
|
|
202
202
|
return [];
|
|
203
203
|
}
|
|
204
204
|
const gates = [];
|
|
205
205
|
for (const value of input) {
|
|
206
|
-
if (!
|
|
206
|
+
if (!GATE_REVIEW_GATES.includes(value) || gates.includes(value)) {
|
|
207
207
|
continue;
|
|
208
208
|
}
|
|
209
209
|
gates.push(value);
|
|
210
210
|
}
|
|
211
|
-
return
|
|
211
|
+
return GATE_REVIEW_GATES.filter((gate) => gates.includes(gate));
|
|
212
212
|
}
|
|
213
213
|
function normalizeSettingsFile(input) {
|
|
214
214
|
const settings = {
|
|
@@ -216,9 +216,9 @@ function normalizeSettingsFile(input) {
|
|
|
216
216
|
preferences: normalizePreferences(input.preferences),
|
|
217
217
|
recentRepositoryPaths: normalizeRecentRepositoryPaths(input.recentRepositoryPaths)
|
|
218
218
|
};
|
|
219
|
-
const
|
|
220
|
-
if (
|
|
221
|
-
settings.
|
|
219
|
+
const gateReview = normalizeGateReviewSettingsState(input.gateReview);
|
|
220
|
+
if (gateReview) {
|
|
221
|
+
settings.gateReview = gateReview;
|
|
222
222
|
}
|
|
223
223
|
return settings;
|
|
224
224
|
}
|