vibe-coding-master 0.7.16 → 0.7.18
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 +19 -2
- package/dist/backend/adapters/claude-adapter.js +4 -1
- package/dist/backend/api/harness-routes.js +21 -4
- package/dist/backend/api/session-routes.js +5 -0
- package/dist/backend/api/translation-worker-routes.js +15 -2
- package/dist/backend/api/usage-analytics-routes.js +31 -0
- package/dist/backend/cli/install-vcm-harness.js +54 -5
- package/dist/backend/server.js +27 -5
- package/dist/backend/services/app-settings-service.js +26 -2
- package/dist/backend/services/architect-restart-service.js +136 -0
- package/dist/backend/services/claude-hook-service.js +134 -54
- package/dist/backend/services/gate-review-service.js +92 -27
- package/dist/backend/services/harness-service.js +54 -7
- package/dist/backend/services/message-service.js +6 -1
- package/dist/backend/services/runtime-coordinator-service.js +10 -10
- package/dist/backend/services/session-service.js +78 -25
- package/dist/backend/services/task-close-service.js +1 -0
- package/dist/backend/services/terminal-interrupt-service.js +4 -1
- package/dist/backend/services/turn-reconciler-service.js +1 -0
- package/dist/backend/services/usage-analytics-service.js +346 -0
- package/dist/backend/templates/handoff.js +4 -0
- package/dist/backend/templates/harness/architect-agent.js +18 -10
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +23 -0
- package/dist/backend/templates/harness/claude-root.js +1 -1
- package/dist/backend/templates/harness/gate-review.js +25 -3
- package/dist/backend/templates/harness/project-manager-agent.js +10 -2
- package/dist/backend/templates/harness/restart-architect-skill.js +75 -0
- package/dist/backend/templates/harness/tester-agent.js +15 -7
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +31 -5
- package/dist/backend/templates/harness/vcm-route-message-skill.js +3 -3
- package/dist/shared/types/app-settings.js +14 -0
- package/dist/shared/types/usage-analytics.js +1 -0
- package/dist/shared/validation/artifact-check.js +29 -3
- package/dist-frontend/assets/{index-BAE_pjXJ.js → index-CDkDHrWQ.js} +43 -43
- package/dist-frontend/assets/{index-CiEUp9Si.css → index-Ci7z8tW3.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -228,6 +228,18 @@ such as a Dev Container or VM.
|
|
|
228
228
|
Model and effort can be selected before start/resume/restart. Changes affect the
|
|
229
229
|
next launched process, not a currently running Claude Code process.
|
|
230
230
|
|
|
231
|
+
## Usage Analytics
|
|
232
|
+
|
|
233
|
+
Open `Usage Analytics` in the sidebar `Task` section to inspect native Claude
|
|
234
|
+
Code usage for the active task. The report shows task totals and breakdowns by
|
|
235
|
+
role and model for input, output, cache-read, and cache-creation tokens plus
|
|
236
|
+
estimated USD cost. It combines every restart and resumed Claude session for
|
|
237
|
+
all seven roles. CCR/GPT usage is excluded.
|
|
238
|
+
|
|
239
|
+
VCM retains only aggregate task data in
|
|
240
|
+
`<task-worktree>/.ai/vcm/telemetry/usage.json`. The file is temporary runtime
|
|
241
|
+
state and is removed with the task worktree when the task is closed.
|
|
242
|
+
|
|
231
243
|
### GPT Through Claude Code Router
|
|
232
244
|
|
|
233
245
|
VCM can launch its normal Claude Code sessions with `GPT-5.6 Sol (CCR)` through
|
|
@@ -270,7 +282,7 @@ not fall back to another model.
|
|
|
270
282
|
|
|
271
283
|
## Launch Template
|
|
272
284
|
|
|
273
|
-
The launch template stores per-role defaults:
|
|
285
|
+
The global launch template stores per-role defaults for the five workflow roles:
|
|
274
286
|
|
|
275
287
|
- permission mode
|
|
276
288
|
- model
|
|
@@ -281,7 +293,12 @@ One-click start launches the four core roles. If any Gate Review Gate is enabled
|
|
|
281
293
|
it also launches Gate Reviewer.
|
|
282
294
|
|
|
283
295
|
Translator and Harness Engineer are tool roles. They are controlled from their
|
|
284
|
-
own panels, not from the main role tab bar.
|
|
296
|
+
own panels, not from the main role tab bar or the one-click launch template.
|
|
297
|
+
Their permission, model, and effort are stored as independent tool Session
|
|
298
|
+
defaults only after an explicit Start or Restart succeeds. Selector changes,
|
|
299
|
+
Resume, and backend automatic startup do not write these defaults. Without a
|
|
300
|
+
saved value, both tools use `bypassPermissions`, the default model, and medium
|
|
301
|
+
effort. An existing task Session keeps its own recorded values.
|
|
285
302
|
|
|
286
303
|
## Orchestration
|
|
287
304
|
|
|
@@ -18,7 +18,7 @@ export function createClaudeAdapter(runner) {
|
|
|
18
18
|
}
|
|
19
19
|
return result.stdout.trim();
|
|
20
20
|
},
|
|
21
|
-
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride) {
|
|
21
|
+
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride, appendSystemPrompt) {
|
|
22
22
|
const args = ["--agent", role];
|
|
23
23
|
const sessionSettings = { ...settingsOverride };
|
|
24
24
|
if (claudeSessionId) {
|
|
@@ -39,6 +39,9 @@ export function createClaudeAdapter(runner) {
|
|
|
39
39
|
if (permissionMode !== "default") {
|
|
40
40
|
args.push("--permission-mode", permissionMode);
|
|
41
41
|
}
|
|
42
|
+
if (appendSystemPrompt) {
|
|
43
|
+
args.push("--append-system-prompt", appendSystemPrompt);
|
|
44
|
+
}
|
|
42
45
|
return {
|
|
43
46
|
command,
|
|
44
47
|
args,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isOpenFileLimitError, VcmError } from "../errors.js";
|
|
2
|
+
import { createDefaultToolSessionDefaults } from "../../shared/types/app-settings.js";
|
|
2
3
|
export function registerHarnessRoutes(app, deps) {
|
|
3
4
|
app.get("/api/projects/harness", async (request) => {
|
|
4
5
|
const { task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
|
|
@@ -68,13 +69,17 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
68
69
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
69
70
|
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
70
71
|
await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
|
|
71
|
-
|
|
72
|
+
const result = await deps.harnessService.startHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
|
|
73
|
+
await persistHarnessEngineerDefaults(deps.appSettings, result.session);
|
|
74
|
+
return result;
|
|
72
75
|
});
|
|
73
76
|
app.post("/api/projects/harness/bootstrap/restart", async (request) => {
|
|
74
77
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
75
78
|
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
76
79
|
await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
|
|
77
|
-
|
|
80
|
+
const result = await deps.harnessService.restartHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
|
|
81
|
+
await persistHarnessEngineerDefaults(deps.appSettings, result.session);
|
|
82
|
+
return result;
|
|
78
83
|
});
|
|
79
84
|
app.post("/api/projects/harness/bootstrap/stop", async (request) => {
|
|
80
85
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
@@ -111,7 +116,9 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
111
116
|
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
|
|
112
117
|
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
113
118
|
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
114
|
-
|
|
119
|
+
const session = await deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
|
|
120
|
+
await persistHarnessEngineerDefaults(deps.appSettings, session);
|
|
121
|
+
return session;
|
|
115
122
|
});
|
|
116
123
|
app.post("/api/projects/harness/engineer/session/resume", async (request) => {
|
|
117
124
|
const project = await requireCurrentProject(deps.projectService);
|
|
@@ -125,7 +132,9 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
125
132
|
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
|
|
126
133
|
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
127
134
|
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
128
|
-
|
|
135
|
+
const session = await deps.sessionService.restartRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
|
|
136
|
+
await persistHarnessEngineerDefaults(deps.appSettings, session);
|
|
137
|
+
return session;
|
|
129
138
|
});
|
|
130
139
|
app.post("/api/projects/harness/engineer/session/stop", async (request) => {
|
|
131
140
|
const project = await requireCurrentProject(deps.projectService);
|
|
@@ -201,6 +210,14 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
201
210
|
return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
|
|
202
211
|
});
|
|
203
212
|
}
|
|
213
|
+
async function persistHarnessEngineerDefaults(appSettings, session) {
|
|
214
|
+
const defaults = createDefaultToolSessionDefaults()["harness-engineer"];
|
|
215
|
+
await appSettings.updateToolSessionDefaults("harness-engineer", {
|
|
216
|
+
permissionMode: session.permissionMode ?? defaults.permissionMode,
|
|
217
|
+
model: session.model ?? defaults.model,
|
|
218
|
+
effort: session.effort ?? defaults.effort
|
|
219
|
+
});
|
|
220
|
+
}
|
|
204
221
|
function requireTaskSlug(value, roleLabel) {
|
|
205
222
|
const taskSlug = value?.trim();
|
|
206
223
|
if (!taskSlug) {
|
|
@@ -10,6 +10,11 @@ export function registerSessionRoutes(app, deps) {
|
|
|
10
10
|
const role = parseRole(request.params.role);
|
|
11
11
|
return deps.sessionService.startRoleSession(project.repoRoot, request.params.taskSlug, role, request.body);
|
|
12
12
|
});
|
|
13
|
+
app.post("/api/tasks/:taskSlug/sessions/architect/restart-after-planning", async (request, reply) => {
|
|
14
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
15
|
+
const result = await deps.architectRestartService.schedule(project.repoRoot, request.params.taskSlug);
|
|
16
|
+
return reply.code(202).send(result);
|
|
17
|
+
});
|
|
13
18
|
app.post("/api/tasks/:taskSlug/sessions/:role/stop", async (request) => {
|
|
14
19
|
const project = await requireCurrentProject(deps.projectService);
|
|
15
20
|
const role = parseRole(request.params.role);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { VcmError } from "../errors.js";
|
|
2
|
+
import { createDefaultToolSessionDefaults } from "../../shared/types/app-settings.js";
|
|
2
3
|
export function registerTranslationWorkerRoutes(app, deps) {
|
|
3
4
|
app.get("/api/translation/state", async () => {
|
|
4
5
|
const project = await requireCurrentProject(deps.projectService);
|
|
@@ -29,7 +30,9 @@ export function registerTranslationWorkerRoutes(app, deps) {
|
|
|
29
30
|
app.post("/api/translation/session/start", async (request) => {
|
|
30
31
|
const project = await requireCurrentProject(deps.projectService);
|
|
31
32
|
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
|
|
32
|
-
|
|
33
|
+
const session = await deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "translator", request.body);
|
|
34
|
+
await persistToolSessionDefaults(deps.appSettings, "translator", session);
|
|
35
|
+
return session;
|
|
33
36
|
});
|
|
34
37
|
app.post("/api/translation/session/resume", async (request) => {
|
|
35
38
|
const project = await requireCurrentProject(deps.projectService);
|
|
@@ -44,7 +47,9 @@ export function registerTranslationWorkerRoutes(app, deps) {
|
|
|
44
47
|
if (existing) {
|
|
45
48
|
await deps.translationService.stopSession(existing.id, { clearCache: true });
|
|
46
49
|
}
|
|
47
|
-
|
|
50
|
+
const session = await deps.sessionService.restartRoleSession(project.repoRoot, taskSlug, "translator", request.body);
|
|
51
|
+
await persistToolSessionDefaults(deps.appSettings, "translator", session);
|
|
52
|
+
return session;
|
|
48
53
|
});
|
|
49
54
|
app.post("/api/translation/session/stop", async (request) => {
|
|
50
55
|
const project = await requireCurrentProject(deps.projectService);
|
|
@@ -90,6 +95,14 @@ export function registerTranslationWorkerRoutes(app, deps) {
|
|
|
90
95
|
return deps.translationWorkerService.promoteFileJob(project.repoRoot, request.params.jobId, targetPath);
|
|
91
96
|
});
|
|
92
97
|
}
|
|
98
|
+
async function persistToolSessionDefaults(appSettings, role, session) {
|
|
99
|
+
const defaults = createDefaultToolSessionDefaults().translator;
|
|
100
|
+
await appSettings.updateToolSessionDefaults(role, {
|
|
101
|
+
permissionMode: session.permissionMode,
|
|
102
|
+
model: session.model ?? defaults.model,
|
|
103
|
+
effort: session.effort ?? defaults.effort
|
|
104
|
+
});
|
|
105
|
+
}
|
|
93
106
|
function requireTaskSlug(value, roleLabel) {
|
|
94
107
|
const taskSlug = value?.trim();
|
|
95
108
|
if (!taskSlug) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { VcmError } from "../errors.js";
|
|
2
|
+
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
3
|
+
export function registerUsageAnalyticsRoutes(app, deps) {
|
|
4
|
+
app.post("/api/telemetry/v1/logs", async (request) => {
|
|
5
|
+
const project = await deps.projectService.getCurrentProject();
|
|
6
|
+
if (!project) {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
const tasks = await deps.taskService.listTasks(project.repoRoot);
|
|
10
|
+
const activeTask = tasks.find((task) => task.cleanupStatus !== "cleaned");
|
|
11
|
+
if (!activeTask) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
15
|
+
await deps.usageAnalyticsService.ingest(getTaskRuntimeRepoRoot(activeTask), config.stateRoot, request.body);
|
|
16
|
+
return {};
|
|
17
|
+
});
|
|
18
|
+
app.get("/api/tasks/:taskSlug/usage-analytics", async (request) => {
|
|
19
|
+
const project = await deps.projectService.getCurrentProject();
|
|
20
|
+
if (!project) {
|
|
21
|
+
throw new VcmError({
|
|
22
|
+
code: "PROJECT_NOT_CONNECTED",
|
|
23
|
+
message: "Connect a repository before loading task usage analytics.",
|
|
24
|
+
statusCode: 409
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
28
|
+
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
29
|
+
return deps.usageAnalyticsService.getReport(getTaskRuntimeRepoRoot(task), config.stateRoot, task.taskSlug);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -7,6 +7,7 @@ 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
9
|
import { renderCoderWorkerHarnessRules } from "../templates/harness/coder-worker-agent.js";
|
|
10
|
+
import { renderArchitectScaffoldWorkerHarnessRules } from "../templates/harness/architect-scaffold-worker-agent.js";
|
|
10
11
|
import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTranslatorAgentRules, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
|
|
11
12
|
import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
|
|
12
13
|
import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
|
|
@@ -27,6 +28,7 @@ import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-
|
|
|
27
28
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
28
29
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
29
30
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
31
|
+
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
30
32
|
import { readVcmPackageVersion } from "../app-version.js";
|
|
31
33
|
const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
32
34
|
const APP_ROOT = path.resolve(CLI_DIR, "../../..");
|
|
@@ -40,9 +42,9 @@ const LEGACY_CODEX_HARNESS_PATHS = [
|
|
|
40
42
|
".claude/skills/vcm-codex-review-gate",
|
|
41
43
|
".ai/tools/request-codex-review"
|
|
42
44
|
];
|
|
43
|
-
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'`;
|
|
44
|
-
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 --retry 2 --retry-delay 1 --retry-all-errors --connect-timeout 1 --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code/stop" -H "content-type: application/json" --data-binary @- || true'`;
|
|
45
|
-
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'`;
|
|
45
|
+
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,runtimeSessionToken:process.env.VCM_RUNTIME_SESSION_TOKEN,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'`;
|
|
46
|
+
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,runtimeSessionToken:process.env.VCM_RUNTIME_SESSION_TOKEN,event}));});'"'"' | curl -fsS --retry 2 --retry-delay 1 --retry-all-errors --connect-timeout 1 --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code/stop" -H "content-type: application/json" --data-binary @- || true'`;
|
|
47
|
+
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,runtimeSessionToken:process.env.VCM_RUNTIME_SESSION_TOKEN,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'`;
|
|
46
48
|
const VCM_BASH_GUARD_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ]; then exit 0; fi; guard=""; repo="$(git rev-parse --show-toplevel 2>/dev/null || true)"; if [ -n "$repo" ] && [ -f "$repo/.ai/tools/vcm-bash-guard" ]; then guard="$repo/.ai/tools/vcm-bash-guard"; else cwd="$(pwd -P 2>/dev/null || pwd)"; dir="$cwd"; while [ -n "$dir" ] && [ "$dir" != "/" ]; do if [ -f "$dir/.ai/tools/vcm-bash-guard" ]; then guard="$dir/.ai/tools/vcm-bash-guard"; break; fi; dir="$(dirname "$dir")"; done; if [ -z "$guard" ] && [ -n "\${CLAUDE_PROJECT_DIR:-}" ] && [ -f "\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-bash-guard" ]; then guard="\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-bash-guard"; fi; fi; [ -n "$guard" ] || exit 0; python3 "$guard" || exit 0'`;
|
|
47
49
|
const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
|
|
48
50
|
const VCM_AUTO_MEMORY_ENABLED = false;
|
|
@@ -59,7 +61,8 @@ const AGENT_FRONTMATTER = {
|
|
|
59
61
|
description: "User-facing VCM orchestration role for task clarification, role routing, handoffs, acceptance, and PR preparation."
|
|
60
62
|
},
|
|
61
63
|
architect: {
|
|
62
|
-
description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync."
|
|
64
|
+
description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.",
|
|
65
|
+
tools: "Read, Grep, Glob, Bash, Edit, Write, Agent"
|
|
63
66
|
},
|
|
64
67
|
coder: {
|
|
65
68
|
description: "VCM implementation role for scoped code changes and focused tests.",
|
|
@@ -81,6 +84,11 @@ const AGENT_FRONTMATTER = {
|
|
|
81
84
|
"vcm-coder-worker": {
|
|
82
85
|
description: "Bounded VCM implementation worker for assigned modules, files, and VCM:CODE markers from Coder.",
|
|
83
86
|
model: "inherit"
|
|
87
|
+
},
|
|
88
|
+
"vcm-architect-scaffold-worker": {
|
|
89
|
+
description: "Foreground Architect worker for exact scaffold execution and scaffold validation.",
|
|
90
|
+
model: "opus",
|
|
91
|
+
effort: "xhigh"
|
|
84
92
|
}
|
|
85
93
|
};
|
|
86
94
|
const MANAGED_FILES = [
|
|
@@ -195,6 +203,14 @@ const MANAGED_FILES = [
|
|
|
195
203
|
commentStyle: "html",
|
|
196
204
|
category: "agent-coder-worker",
|
|
197
205
|
content: renderCoderWorkerHarnessRules()
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
path: ".claude/agents/vcm-architect-scaffold-worker.md",
|
|
209
|
+
title: "VCM Architect Scaffold Worker Agent",
|
|
210
|
+
agentName: "vcm-architect-scaffold-worker",
|
|
211
|
+
commentStyle: "html",
|
|
212
|
+
category: "agent-architect-scaffold-worker",
|
|
213
|
+
content: renderArchitectScaffoldWorkerHarnessRules()
|
|
198
214
|
}
|
|
199
215
|
];
|
|
200
216
|
const DURABLE_DOC_TEMPLATES = [
|
|
@@ -284,6 +300,12 @@ const WHOLE_FILES = [
|
|
|
284
300
|
mode: 0o644,
|
|
285
301
|
content: renderSkillFile("VCM Propose Memory Skill", "vcm-propose-memory", "Use only when VCM requests a role memory proposal during Task Harness Review.", renderVcmProposeMemorySkillRules())
|
|
286
302
|
},
|
|
303
|
+
{
|
|
304
|
+
path: ".claude/skills/restart-architect/SKILL.md",
|
|
305
|
+
category: "skill",
|
|
306
|
+
mode: 0o644,
|
|
307
|
+
content: renderSkillFile("Restart Architect Skill", "restart-architect", "Use after Architect completes and commits architecture planning and scaffold work.", renderRestartArchitectSkillRules())
|
|
308
|
+
},
|
|
287
309
|
{
|
|
288
310
|
path: ".ai/tools/request-gate-review",
|
|
289
311
|
category: "runtime-tool",
|
|
@@ -302,6 +324,12 @@ const WHOLE_FILES = [
|
|
|
302
324
|
mode: 0o755,
|
|
303
325
|
content: renderCheckScaffoldLedgerTool()
|
|
304
326
|
},
|
|
327
|
+
{
|
|
328
|
+
path: ".ai/tools/request-architect-restart",
|
|
329
|
+
category: "runtime-tool",
|
|
330
|
+
mode: 0o755,
|
|
331
|
+
content: renderRequestArchitectRestartTool()
|
|
332
|
+
},
|
|
305
333
|
{
|
|
306
334
|
path: ".ai/tools/run-long-check",
|
|
307
335
|
category: "runtime-tool",
|
|
@@ -526,6 +554,7 @@ function fixedDirectories() {
|
|
|
526
554
|
".claude/skills/vcm-gate-review/",
|
|
527
555
|
".claude/skills/vcm-report-harness-issue/",
|
|
528
556
|
".claude/skills/vcm-propose-memory/",
|
|
557
|
+
".claude/skills/restart-architect/",
|
|
529
558
|
".ai/vcm/translations/",
|
|
530
559
|
".ai/vcm/gate-reviews/",
|
|
531
560
|
".ai/tools/",
|
|
@@ -586,6 +615,9 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
|
|
|
586
615
|
if (definition.memoryBlock) {
|
|
587
616
|
nextContent = ensureVcmMemoryBlock(nextContent);
|
|
588
617
|
}
|
|
618
|
+
if (definition.agentName === "architect") {
|
|
619
|
+
nextContent = ensureAgentTool(nextContent, "Agent");
|
|
620
|
+
}
|
|
589
621
|
await writeIfChanged({
|
|
590
622
|
targetPath,
|
|
591
623
|
relativePath: definition.path,
|
|
@@ -627,10 +659,27 @@ function renderNewManagedFile(definition, block) {
|
|
|
627
659
|
const frontmatter = AGENT_FRONTMATTER[definition.agentName];
|
|
628
660
|
const tools = frontmatter.tools ?? "Read, Grep, Glob, Bash, Edit, Write";
|
|
629
661
|
const model = frontmatter.model ? `\nmodel: ${frontmatter.model}` : "";
|
|
630
|
-
|
|
662
|
+
const effort = frontmatter.effort ? `\neffort: ${frontmatter.effort}` : "";
|
|
663
|
+
return `---\nname: ${definition.agentName}\ndescription: ${frontmatter.description}\ntools: ${tools}${model}${effort}\n---\n\n# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
|
|
631
664
|
}
|
|
632
665
|
return `# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
|
|
633
666
|
}
|
|
667
|
+
function ensureAgentTool(content, requiredTool) {
|
|
668
|
+
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
669
|
+
if (!frontmatterMatch) {
|
|
670
|
+
return content;
|
|
671
|
+
}
|
|
672
|
+
const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
|
|
673
|
+
if (!toolsMatch) {
|
|
674
|
+
return content.replace(/^(---\r?\n[\s\S]*?)(\r?\n---)/, `$1\ntools: ${requiredTool}$2`);
|
|
675
|
+
}
|
|
676
|
+
const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
677
|
+
if (tools.includes(requiredTool)) {
|
|
678
|
+
return content;
|
|
679
|
+
}
|
|
680
|
+
const nextTools = [...tools, requiredTool].join(", ");
|
|
681
|
+
return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
|
|
682
|
+
}
|
|
634
683
|
function migrateLegacyManagedFile(definition, currentContent, block) {
|
|
635
684
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
636
685
|
if (!legacyContent) {
|
package/dist/backend/server.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createGitAdapter } from "./adapters/git-adapter.js";
|
|
|
12
12
|
import { createAppSettingsService } from "./services/app-settings-service.js";
|
|
13
13
|
import { createCcrIntegrationService } from "./services/ccr-integration-service.js";
|
|
14
14
|
import { createAutoMemoryService } from "./services/auto-memory-service.js";
|
|
15
|
+
import { createArchitectRestartService } from "./services/architect-restart-service.js";
|
|
15
16
|
import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
|
|
16
17
|
import { createGateReviewService } from "./services/gate-review-service.js";
|
|
17
18
|
import { createHarnessFeedbackService } from "./services/harness-feedback-service.js";
|
|
@@ -42,6 +43,7 @@ import { createTaskWorkflowService } from "./services/task-workflow-service.js";
|
|
|
42
43
|
import { createTaskLaunchService } from "./services/task-launch-service.js";
|
|
43
44
|
import { createTerminalInterruptService } from "./services/terminal-interrupt-service.js";
|
|
44
45
|
import { createTranslationService } from "./services/translation-service.js";
|
|
46
|
+
import { createUsageAnalyticsService } from "./services/usage-analytics-service.js";
|
|
45
47
|
import { createTurnReconcilerService } from "./services/turn-reconciler-service.js";
|
|
46
48
|
import { createDiagnosticsService } from "./services/diagnostics-service.js";
|
|
47
49
|
import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
|
|
@@ -57,6 +59,7 @@ import { registerRuntimeStateRoutes } from "./api/runtime-state-routes.js";
|
|
|
57
59
|
import { registerSessionRoutes } from "./api/session-routes.js";
|
|
58
60
|
import { registerTaskRoutes } from "./api/task-routes.js";
|
|
59
61
|
import { registerTranslationRoutes } from "./api/translation-routes.js";
|
|
62
|
+
import { registerUsageAnalyticsRoutes } from "./api/usage-analytics-routes.js";
|
|
60
63
|
import { registerTerminalWs } from "./ws/terminal-ws.js";
|
|
61
64
|
import { toVcmError } from "./errors.js";
|
|
62
65
|
import { readVcmPackageVersion } from "./app-version.js";
|
|
@@ -90,6 +93,7 @@ export async function createServer(deps, options = {}) {
|
|
|
90
93
|
gateReviewService: deps.gateReviewService
|
|
91
94
|
});
|
|
92
95
|
registerTranslationWorkerRoutes(app, {
|
|
96
|
+
appSettings: deps.appSettings,
|
|
93
97
|
projectService: deps.projectService,
|
|
94
98
|
translationWorkerService: deps.translationWorkerService,
|
|
95
99
|
sessionService: deps.sessionService,
|
|
@@ -100,6 +104,7 @@ export async function createServer(deps, options = {}) {
|
|
|
100
104
|
runtimeRecoveryService: deps.runtimeRecoveryService
|
|
101
105
|
});
|
|
102
106
|
registerHarnessRoutes(app, {
|
|
107
|
+
appSettings: deps.appSettings,
|
|
103
108
|
projectService: deps.projectService,
|
|
104
109
|
harnessService: deps.harnessService,
|
|
105
110
|
harnessFeedbackService: deps.harnessFeedbackService,
|
|
@@ -132,7 +137,8 @@ export async function createServer(deps, options = {}) {
|
|
|
132
137
|
sessionService: deps.sessionService,
|
|
133
138
|
commandDispatcher: deps.commandDispatcher,
|
|
134
139
|
translationService: deps.translationService,
|
|
135
|
-
roundService: deps.roundService
|
|
140
|
+
roundService: deps.roundService,
|
|
141
|
+
architectRestartService: deps.architectRestartService
|
|
136
142
|
});
|
|
137
143
|
registerArtifactRoutes(app, {
|
|
138
144
|
projectService: deps.projectService,
|
|
@@ -155,6 +161,11 @@ export async function createServer(deps, options = {}) {
|
|
|
155
161
|
sessionService: deps.sessionService,
|
|
156
162
|
translationService: deps.translationService
|
|
157
163
|
});
|
|
164
|
+
registerUsageAnalyticsRoutes(app, {
|
|
165
|
+
projectService: deps.projectService,
|
|
166
|
+
taskService: deps.taskService,
|
|
167
|
+
usageAnalyticsService: deps.usageAnalyticsService
|
|
168
|
+
});
|
|
158
169
|
registerGatewayRoutes(app, { gatewayService: deps.gatewayService });
|
|
159
170
|
registerTerminalWs(app, {
|
|
160
171
|
runtime: deps.runtime,
|
|
@@ -264,12 +275,18 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
264
275
|
sessionService,
|
|
265
276
|
artifactService
|
|
266
277
|
});
|
|
278
|
+
const architectRestartService = createArchitectRestartService({
|
|
279
|
+
fs,
|
|
280
|
+
taskService,
|
|
281
|
+
sessionService
|
|
282
|
+
});
|
|
267
283
|
const messageService = createMessageService({
|
|
268
284
|
fs,
|
|
269
285
|
runtime,
|
|
270
286
|
sessionService,
|
|
271
287
|
taskService,
|
|
272
|
-
taskWorkflowService
|
|
288
|
+
taskWorkflowService,
|
|
289
|
+
onRouteDelivered: ({ repoRoot, taskSlug, message }) => architectRestartService.recordRouteDelivered(repoRoot, taskSlug, message)
|
|
273
290
|
});
|
|
274
291
|
const taskLaunchService = createTaskLaunchService({
|
|
275
292
|
projectService,
|
|
@@ -312,6 +329,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
312
329
|
roundService,
|
|
313
330
|
appSettings
|
|
314
331
|
});
|
|
332
|
+
const usageAnalyticsService = createUsageAnalyticsService({ fs });
|
|
315
333
|
const gatewayChannels = createGatewayChannelRegistry([
|
|
316
334
|
createWeixinIlinkChannel(),
|
|
317
335
|
createLarkChannel()
|
|
@@ -331,7 +349,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
331
349
|
translationService,
|
|
332
350
|
roundService,
|
|
333
351
|
projectService,
|
|
334
|
-
taskWorkflowService
|
|
352
|
+
taskWorkflowService,
|
|
353
|
+
architectRestartService
|
|
335
354
|
});
|
|
336
355
|
const gatewayService = createGatewayService({
|
|
337
356
|
fs,
|
|
@@ -368,7 +387,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
368
387
|
autoMemoryService,
|
|
369
388
|
gatewayService,
|
|
370
389
|
jobGuard: createJobGuardService(),
|
|
371
|
-
translationWorkerService
|
|
390
|
+
translationWorkerService,
|
|
391
|
+
architectRestartService
|
|
372
392
|
});
|
|
373
393
|
const turnReconciler = createTurnReconcilerService({
|
|
374
394
|
sessionService,
|
|
@@ -412,6 +432,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
412
432
|
taskService,
|
|
413
433
|
taskCloseService,
|
|
414
434
|
taskWorkflowService,
|
|
435
|
+
architectRestartService,
|
|
415
436
|
sessionService,
|
|
416
437
|
artifactService,
|
|
417
438
|
harnessService,
|
|
@@ -431,7 +452,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
431
452
|
runtimeRecoveryService,
|
|
432
453
|
terminalInterruptService,
|
|
433
454
|
runtime,
|
|
434
|
-
diagnosticsService
|
|
455
|
+
diagnosticsService,
|
|
456
|
+
usageAnalyticsService
|
|
435
457
|
};
|
|
436
458
|
}
|
|
437
459
|
export function getDefaultStaticDir() {
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { VCM_ROLE_NAMES } from "../../shared/constants.js";
|
|
4
4
|
import { GATE_REVIEW_GATES } from "../../shared/types/gate-review.js";
|
|
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";
|
|
5
|
+
import { createDefaultLaunchTemplate, createDefaultToolSessionDefaults, 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, CCR_GPT_SESSION_MODEL, SESSION_EFFORT_OPTIONS } from "../../shared/types/session.js";
|
|
7
7
|
import { resolveVcmDataDir } from "../vcm-data-dir.js";
|
|
8
8
|
const MAX_RECENT_REPOSITORIES = 5;
|
|
@@ -73,6 +73,21 @@ export function createAppSettingsService(deps) {
|
|
|
73
73
|
});
|
|
74
74
|
return preferences;
|
|
75
75
|
},
|
|
76
|
+
async updateToolSessionDefaults(role, input) {
|
|
77
|
+
const current = await loadSettings();
|
|
78
|
+
const preferences = normalizePreferences({
|
|
79
|
+
...current.preferences,
|
|
80
|
+
toolSessionDefaults: {
|
|
81
|
+
...current.preferences.toolSessionDefaults,
|
|
82
|
+
[role]: input
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
await saveSettings({
|
|
86
|
+
...current,
|
|
87
|
+
preferences
|
|
88
|
+
});
|
|
89
|
+
return preferences.toolSessionDefaults[role];
|
|
90
|
+
},
|
|
76
91
|
async getRecentRepositoryPaths() {
|
|
77
92
|
return (await loadSettings()).recentRepositoryPaths;
|
|
78
93
|
},
|
|
@@ -274,7 +289,8 @@ function normalizePreferences(input) {
|
|
|
274
289
|
translationAutoSendEnabled: candidate.translationAutoSendEnabled === true,
|
|
275
290
|
translationTargetLanguage: normalizeTranslationTargetLanguage(candidate.translationTargetLanguage),
|
|
276
291
|
translationOutputMode: normalizeTranslationOutputMode(candidate.translationOutputMode),
|
|
277
|
-
launchTemplate: normalizeLaunchTemplate(candidate.launchTemplate)
|
|
292
|
+
launchTemplate: normalizeLaunchTemplate(candidate.launchTemplate),
|
|
293
|
+
toolSessionDefaults: normalizeToolSessionDefaults(candidate.toolSessionDefaults)
|
|
278
294
|
};
|
|
279
295
|
}
|
|
280
296
|
function normalizeThemeMode(input) {
|
|
@@ -313,6 +329,14 @@ function normalizeLaunchTemplate(input) {
|
|
|
313
329
|
autoOrchestration: input.autoOrchestration !== false
|
|
314
330
|
};
|
|
315
331
|
}
|
|
332
|
+
function normalizeToolSessionDefaults(input) {
|
|
333
|
+
const defaults = createDefaultToolSessionDefaults();
|
|
334
|
+
const candidate = isObject(input) ? input : {};
|
|
335
|
+
return {
|
|
336
|
+
translator: normalizeRoleLaunchTemplateEntry(candidate.translator, defaults.translator),
|
|
337
|
+
"harness-engineer": normalizeRoleLaunchTemplateEntry(candidate["harness-engineer"], defaults["harness-engineer"])
|
|
338
|
+
};
|
|
339
|
+
}
|
|
316
340
|
function normalizeRoleLaunchTemplateEntry(input, fallback) {
|
|
317
341
|
const candidate = isObject(input) ? input : {};
|
|
318
342
|
return {
|