vibe-coding-master 0.3.26 → 0.3.28
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 +33 -29
- 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 +15 -57
- package/dist/backend/server.js +6 -8
- package/dist/backend/services/app-settings-service.js +15 -15
- 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 +15 -63
- package/dist/backend/services/session-service.js +257 -41
- 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-x9I-bGUt.js} +37 -37
- package/dist-frontend/index.html +1 -1
- package/docs/codex-translation-plan.md +83 -136
- package/docs/full-harness-baseline.md +113 -207
- package/docs/gate-review-gates.md +133 -0
- package/docs/gateway-design.md +6 -8
- package/docs/product-design.md +66 -38
- package/docs/v0.2-implementation-plan.md +4 -0
- package/docs/vcm-cc-best-practices.md +36 -33
- 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,11 +29,11 @@ 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.
|
|
36
|
-
- Durable task state, session state,
|
|
36
|
+
- Durable task state, role session state, handoff artifacts, and message history.
|
|
37
37
|
|
|
38
38
|
## Requirements
|
|
39
39
|
|
|
@@ -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
|
|
@@ -193,14 +193,12 @@ In this terminology, `Session` is a VCM statistics term. It is different from a
|
|
|
193
193
|
|
|
194
194
|
## Task Worktree Management
|
|
195
195
|
|
|
196
|
-
VCM uses task-level worktree management
|
|
196
|
+
VCM uses task-level worktree management for every task:
|
|
197
197
|
|
|
198
198
|
```text
|
|
199
199
|
one task = one branch + one git worktree + one handoff directory + one role-session set
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
-
The `Create worktree and branch` option is selected by default when creating a task:
|
|
203
|
-
|
|
204
202
|
- task name: `<task>`
|
|
205
203
|
- branch: `feature/<task>`
|
|
206
204
|
- worktree path: `.claude/worktrees/<task>` inside the connected base repository
|
|
@@ -208,13 +206,11 @@ The `Create worktree and branch` option is selected by default when creating a t
|
|
|
208
206
|
|
|
209
207
|
VCM will not create worktrees per role. `project-manager`, `architect`, `coder`, and `reviewer` for the same task share the same task worktree.
|
|
210
208
|
|
|
211
|
-
The user can turn this option off. In that mode, VCM creates app-local task metadata, creates the handoff structure in the connected repository, records the current branch, and starts role sessions from the connected repository path.
|
|
212
|
-
|
|
213
209
|
VCM will not offer a separate `Create task worktree` button after a task exists, and a task should not be switched to another branch/worktree mode after creation.
|
|
214
210
|
|
|
215
211
|
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`.
|
|
216
212
|
|
|
217
|
-
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.
|
|
213
|
+
When a task is complete, VCM provides a red `Close Task` action. Closing a task shows a destructive confirmation, stops VCM-managed running role sessions for that task, then deletes the task worktree, deletes the task branch by default, removes the app-local task record, and removes task runtime metadata. VCM does not preflight running sessions or uncommitted changes before closing. Project-scoped Gate Reviewer and Codex Translator sessions are not task-owned and are not stopped by Close Task.
|
|
218
214
|
|
|
219
215
|
## Sidebar UI
|
|
220
216
|
|
|
@@ -223,6 +219,8 @@ The left sidebar is intentionally compact and collapsible:
|
|
|
223
219
|
- `Repository Path`: path input on one row; `Recent` and `Connect` on the next row.
|
|
224
220
|
- `Connected Repository`: connected base repo path, branch, upstream/ahead-behind status, commit hash, working tree state, and a `Pull` button.
|
|
225
221
|
- `Settings`: `Theme`, `Flow pause alert`, `Try alert`, `Messages`, and `Events`.
|
|
222
|
+
- `Translation`: global conversation translation, auto-send, target language, output scope, file translation, bootstrap, memory update, session status, and Codex Translator session access.
|
|
223
|
+
- `Gate Review Gates`: global gate switches for architecture plan, validation adequacy, and final diff.
|
|
226
224
|
- `Gateway`: Weixin iLink binding, Gateway on/off, Gateway translation, and QR login.
|
|
227
225
|
- `VCM Harness`: fixed-install status, bootstrap completion checks, and the bootstrap terminal when one is running.
|
|
228
226
|
- `New Task`: one `task name` input.
|
|
@@ -233,9 +231,8 @@ All sidebar sections are collapsed by default. When no task is selected, `Reposi
|
|
|
233
231
|
Opening `Connected Repository` refreshes the base repo status through the
|
|
234
232
|
backend. VCM does not poll it continuously. The `Pull` button runs
|
|
235
233
|
`git pull --ff-only` against the connected base repo only. It is disabled when
|
|
236
|
-
the base repo has uncommitted changes
|
|
237
|
-
|
|
238
|
-
not stash, merge, or mutate task worktrees.
|
|
234
|
+
the base repo has uncommitted changes or when the current branch has no
|
|
235
|
+
upstream. It does not stash, merge, or mutate task worktrees.
|
|
239
236
|
|
|
240
237
|
When VCM is connected to an active task, the bottom of the sidebar shows a task status dock. It stays outside the collapsible groups and shows the active VCM Session title, task status, start time, total elapsed time, total Round count, and role active runtime. The dock also shows the Current Round while it is running, or the Last Round after a flow pause, including start time, total elapsed time, role active runtime, Turn count, and Round status.
|
|
241
238
|
|
|
@@ -360,7 +357,7 @@ Typical mobile flow:
|
|
|
360
357
|
|
|
361
358
|
`/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.
|
|
362
359
|
|
|
363
|
-
`/create-task` uses the saved launch template from the desktop settings. The template controls permission mode, model, effort, auto orchestration
|
|
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.
|
|
364
361
|
|
|
365
362
|
`/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.
|
|
366
363
|
|
|
@@ -378,7 +375,7 @@ Typical mobile flow:
|
|
|
378
375
|
|
|
379
376
|
## Translation
|
|
380
377
|
|
|
381
|
-
|
|
378
|
+
Translation is controlled from the sidebar `Translation` group. When conversation translation is enabled, each running core VCM role console shows a translation panel beside the embedded terminal. The terminal and translation panel split the available width evenly.
|
|
382
379
|
|
|
383
380
|
The task header does not include a manual `Refresh` button. Task status, role status, messages, orchestration state, and flow pause state refresh automatically. The remaining `Refresh` button lives only in the sidebar `VCM Harness` section and is for rechecking harness files.
|
|
384
381
|
|
|
@@ -390,7 +387,7 @@ Translation settings are local and stored in:
|
|
|
390
387
|
|
|
391
388
|
The same file stores recent repository paths and global translation preferences such as enablement, auto-send, and target language.
|
|
392
389
|
|
|
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
|
|
390
|
+
VCM resolves `vcmDataDir` from `VCM_DATA_DIR`. If `VCM_DATA_DIR` is unset or empty, VCM uses `~/.vcm`. In Dev Containers, set `VCM_DATA_DIR=/workspace/.ai/vcm` and `VCM_SANDBOX=devcontainer` through `containerEnv` so VCM app state survives container rebuilds and VCM-managed Codex Translator sessions do not run a nested Codex sandbox.
|
|
394
391
|
|
|
395
392
|
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
393
|
|
|
@@ -401,7 +398,7 @@ When Gateway is on, `Flow pause alert` is forced off because mobile notification
|
|
|
401
398
|
Translation behavior:
|
|
402
399
|
|
|
403
400
|
- Conversation translation is routed through the Codex Translator session and result files.
|
|
404
|
-
- Global translation controls live in the sidebar Translation section: enablement, auto-send, target language, bootstrap, memory update, and
|
|
401
|
+
- 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`.
|
|
405
402
|
- File and conversation translation share `<baseRepoRoot>/.ai/vcm/translations/`; conversation result files are temporary runtime artifacts.
|
|
406
403
|
- Claude Code output translation reads semantic Claude transcript JSONL files under `~/.claude/projects`, not raw PTY output.
|
|
407
404
|
- Claude Code prose output waits 10 seconds before dispatch so adjacent output can be translated in one Codex batch.
|
|
@@ -415,10 +412,10 @@ Translation behavior:
|
|
|
415
412
|
- Assistant prose renders Markdown in the panel, including headings, lists, code fences, tables, and links.
|
|
416
413
|
- Tool calls and tool results are preserved as dim one-line rows such as `● Bash({"command":"npm test"})`.
|
|
417
414
|
- User input uses one textarea. Press `Enter` to translate or send the current English draft; press `Shift+Enter` for a newline.
|
|
418
|
-
- After user input is translated, the
|
|
415
|
+
- After user input is translated, the English draft is appended after the original source text in the same textarea.
|
|
419
416
|
- `Send English` writes the current English draft to the active embedded terminal and submits it.
|
|
420
417
|
- Automatic terminal submission uses bracketed paste first, then sends Enter separately for Claude Code TUI reliability.
|
|
421
|
-
- The
|
|
418
|
+
- The sidebar `Auto-send` toggle sends the translated draft automatically when translation succeeds without warnings.
|
|
422
419
|
|
|
423
420
|
## Project Harness
|
|
424
421
|
|
|
@@ -438,7 +435,13 @@ CLAUDE.md
|
|
|
438
435
|
.claude/skills/vcm-final-acceptance/SKILL.md
|
|
439
436
|
.claude/skills/vcm-long-running-validation/SKILL.md
|
|
440
437
|
.claude/skills/vcm-harness-bootstrap/SKILL.md
|
|
441
|
-
.
|
|
438
|
+
.claude/skills/vcm-gate-review/SKILL.md
|
|
439
|
+
.claude/agents/gate-reviewer.md
|
|
440
|
+
.ai/codex-translator/AGENTS.md
|
|
441
|
+
.ai/codex-translator/config.toml
|
|
442
|
+
.ai/codex-translator/.codex/config.toml
|
|
443
|
+
.ai/codex-translator/.codex/hooks.json
|
|
444
|
+
.ai/tools/request-gate-review
|
|
442
445
|
.ai/tools/generate-module-index
|
|
443
446
|
.ai/tools/generate-public-surface
|
|
444
447
|
.ai/tools/run-long-check
|
|
@@ -485,7 +488,7 @@ For `.gitignore`, VCM uses a gitignore-native managed block:
|
|
|
485
488
|
|
|
486
489
|
`.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
490
|
|
|
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.
|
|
491
|
+
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
492
|
|
|
490
493
|
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
494
|
|
|
@@ -536,7 +539,6 @@ Runtime message and handoff files:
|
|
|
536
539
|
.ai/vcm/messages/<task>.jsonl # under the task runtime repo
|
|
537
540
|
.ai/vcm/orchestration/<task>.json # under the task runtime repo
|
|
538
541
|
.ai/vcm/handoffs/messages/<from-role>-<to-role>.md
|
|
539
|
-
.ai/vcm/handoffs/logs/
|
|
540
542
|
```
|
|
541
543
|
|
|
542
544
|
Each directed role route has exactly one message file. Route messages are the only dynamic task-dispatch files. If a role changes its mind during one turn, it edits the same route file instead of creating another message. A blank file means no pending message; a non-empty file means pending work for VCM to submit.
|
|
@@ -572,7 +574,7 @@ When it is on, VCM is in auto mode:
|
|
|
572
574
|
- When the target role later reaches `Stop`, VCM scans again and may deliver the next pending route file.
|
|
573
575
|
- 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
576
|
|
|
575
|
-
VCM Harness injects Claude Code `UserPromptSubmit` and `Stop` hooks into `.claude/settings.json
|
|
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 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
578
|
|
|
577
579
|
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
580
|
|
|
@@ -603,7 +605,7 @@ Session buttons behave as follows:
|
|
|
603
605
|
- `Restart`: stops the current process if needed, creates a new UUID, and starts a fresh Claude session.
|
|
604
606
|
- `Stop`: stops the embedded terminal process and leaves the persisted Claude session id resumable.
|
|
605
607
|
|
|
606
|
-
|
|
608
|
+
Ordinary VCM role sessions do not persist raw terminal logs under handoff state. Claude Code transcript JSONL files under `~/.claude/projects/` are the semantic record used for output translation and recovery.
|
|
607
609
|
|
|
608
610
|
## Local Project Files
|
|
609
611
|
|
|
@@ -622,7 +624,9 @@ For a connected repository, VCM uses:
|
|
|
622
624
|
<taskRepoRoot>/.ai/vcm/handoffs/review-report.md
|
|
623
625
|
<taskRepoRoot>/.ai/vcm/handoffs/docs-sync-report.md
|
|
624
626
|
<taskRepoRoot>/.ai/vcm/handoffs/messages/<from-role>-<to-role>.md
|
|
625
|
-
<taskRepoRoot>/.ai/vcm/
|
|
627
|
+
<taskRepoRoot>/.ai/vcm/gate-reviews/
|
|
628
|
+
<baseRepoRoot>/.ai/vcm/gate-reviewer/session.json
|
|
629
|
+
<baseRepoRoot>/.ai/vcm/translations/
|
|
626
630
|
```
|
|
627
631
|
|
|
628
632
|
The project config is stored under `vcmDataDir` so it is durable local app state. `vcmDataDir` is `VCM_DATA_DIR` when set, otherwise `~/.vcm`. For Dev Containers, prefer:
|
|
@@ -635,9 +639,7 @@ The project config is stored under `vcmDataDir` so it is durable local app state
|
|
|
635
639
|
}
|
|
636
640
|
```
|
|
637
641
|
|
|
638
|
-
For
|
|
639
|
-
|
|
640
|
-
Because handoffs are scoped to `taskRepoRoot` without an extra task-name directory, VCM allows only one active inline task per connected repository. Use the default worktree mode for parallel tasks.
|
|
642
|
+
For every task, `taskRepoRoot` is `<baseRepoRoot>/.claude/worktrees/<task>`.
|
|
641
643
|
|
|
642
644
|
## Packaging
|
|
643
645
|
|
|
@@ -680,4 +682,6 @@ See also:
|
|
|
680
682
|
- `docs/v0.2-implementation-plan.md`
|
|
681
683
|
- `docs/vcm-cc-best-practices.md`
|
|
682
684
|
- `docs/full-harness-baseline.md`
|
|
685
|
+
- `docs/gate-review-gates.md`
|
|
686
|
+
- `docs/codex-translation-plan.md`
|
|
683
687
|
- `docs/cc-best-practices.md` is archived and no longer maintained.
|
|
@@ -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";
|
|
@@ -47,6 +47,9 @@ const AGENT_FRONTMATTER = {
|
|
|
47
47
|
},
|
|
48
48
|
reviewer: {
|
|
49
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."
|
|
50
53
|
}
|
|
51
54
|
};
|
|
52
55
|
const MANAGED_FILES = [
|
|
@@ -106,11 +109,12 @@ const MANAGED_FILES = [
|
|
|
106
109
|
content: renderPullRequestTemplateHarnessRules()
|
|
107
110
|
},
|
|
108
111
|
{
|
|
109
|
-
path: ".
|
|
110
|
-
title: "
|
|
112
|
+
path: ".claude/agents/gate-reviewer.md",
|
|
113
|
+
title: "Gate Reviewer Agent",
|
|
114
|
+
agentName: "gate-reviewer",
|
|
111
115
|
commentStyle: "html",
|
|
112
|
-
category: "
|
|
113
|
-
content:
|
|
116
|
+
category: "gate-reviewer-agent",
|
|
117
|
+
content: renderGateReviewerAgentRules()
|
|
114
118
|
},
|
|
115
119
|
{
|
|
116
120
|
path: ".ai/codex-translator/AGENTS.md",
|
|
@@ -172,28 +176,10 @@ const WHOLE_FILES = [
|
|
|
172
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())
|
|
173
177
|
},
|
|
174
178
|
{
|
|
175
|
-
path: ".claude/skills/vcm-
|
|
179
|
+
path: ".claude/skills/vcm-gate-review/SKILL.md",
|
|
176
180
|
category: "skill",
|
|
177
181
|
mode: 0o644,
|
|
178
|
-
content: renderSkillFile("VCM
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
path: ".ai/codex/config.toml",
|
|
182
|
-
category: "codex-review-config",
|
|
183
|
-
mode: 0o644,
|
|
184
|
-
content: renderCodexConfigHarnessRules()
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
path: ".ai/codex/.codex/config.toml",
|
|
188
|
-
category: "codex-review-hooks",
|
|
189
|
-
mode: 0o644,
|
|
190
|
-
content: renderCodexCliConfigHarnessRules()
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
path: ".ai/codex/.codex/hooks.json",
|
|
194
|
-
category: "codex-review-hooks",
|
|
195
|
-
mode: 0o644,
|
|
196
|
-
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())
|
|
197
183
|
},
|
|
198
184
|
{
|
|
199
185
|
path: ".ai/codex-translator/config.toml",
|
|
@@ -214,34 +200,10 @@ const WHOLE_FILES = [
|
|
|
214
200
|
content: renderCodexHooksHarnessRules("codex-translator")
|
|
215
201
|
},
|
|
216
202
|
{
|
|
217
|
-
path: ".ai/
|
|
218
|
-
category: "codex-review-prompt",
|
|
219
|
-
mode: 0o644,
|
|
220
|
-
content: renderCodexArchitecturePlanPrompt()
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
path: ".ai/codex/prompts/validation-adequacy-gate.md",
|
|
224
|
-
category: "codex-review-prompt",
|
|
225
|
-
mode: 0o644,
|
|
226
|
-
content: renderCodexValidationAdequacyPrompt()
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
path: ".ai/codex/prompts/final-diff-gate.md",
|
|
230
|
-
category: "codex-review-prompt",
|
|
231
|
-
mode: 0o644,
|
|
232
|
-
content: renderCodexFinalDiffPrompt()
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
path: ".ai/codex/schemas/codex-review-result.schema.json",
|
|
236
|
-
category: "codex-review-schema",
|
|
237
|
-
mode: 0o644,
|
|
238
|
-
content: renderCodexReviewResultSchema()
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
path: ".ai/tools/request-codex-review",
|
|
203
|
+
path: ".ai/tools/request-gate-review",
|
|
242
204
|
category: "runtime-tool",
|
|
243
205
|
mode: 0o755,
|
|
244
|
-
content:
|
|
206
|
+
content: renderRequestGateReviewTool()
|
|
245
207
|
},
|
|
246
208
|
{
|
|
247
209
|
path: ".ai/tools/run-long-check",
|
|
@@ -454,15 +416,11 @@ function fixedDirectories() {
|
|
|
454
416
|
".claude/skills/vcm-harness-bootstrap/",
|
|
455
417
|
".claude/skills/vcm-long-running-validation/",
|
|
456
418
|
".claude/skills/vcm-route-message/",
|
|
457
|
-
".claude/skills/vcm-
|
|
458
|
-
".ai/codex/",
|
|
459
|
-
".ai/codex/.codex/",
|
|
460
|
-
".ai/codex/prompts/",
|
|
461
|
-
".ai/codex/schemas/",
|
|
419
|
+
".claude/skills/vcm-gate-review/",
|
|
462
420
|
".ai/codex-translator/",
|
|
463
421
|
".ai/codex-translator/.codex/",
|
|
464
422
|
".ai/vcm/translations/",
|
|
465
|
-
".ai/vcm/
|
|
423
|
+
".ai/vcm/gate-reviews/",
|
|
466
424
|
".ai/tools/",
|
|
467
425
|
".ai/generated/"
|
|
468
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,
|
|
@@ -272,9 +272,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
272
272
|
});
|
|
273
273
|
const codexHookService = createCodexHookService({
|
|
274
274
|
projectService,
|
|
275
|
-
taskService,
|
|
276
275
|
sessionService,
|
|
277
|
-
roundService,
|
|
278
276
|
codexTranslationService
|
|
279
277
|
});
|
|
280
278
|
const diagnosticsService = createDiagnosticsService({
|
|
@@ -294,7 +292,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
294
292
|
claudeHookService,
|
|
295
293
|
codexHookService,
|
|
296
294
|
messageService,
|
|
297
|
-
|
|
295
|
+
gateReviewService,
|
|
298
296
|
codexTranslationService,
|
|
299
297
|
roundService,
|
|
300
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
|
}
|