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.
Files changed (37) hide show
  1. package/README.md +19 -2
  2. package/dist/backend/adapters/claude-adapter.js +4 -1
  3. package/dist/backend/api/harness-routes.js +21 -4
  4. package/dist/backend/api/session-routes.js +5 -0
  5. package/dist/backend/api/translation-worker-routes.js +15 -2
  6. package/dist/backend/api/usage-analytics-routes.js +31 -0
  7. package/dist/backend/cli/install-vcm-harness.js +54 -5
  8. package/dist/backend/server.js +27 -5
  9. package/dist/backend/services/app-settings-service.js +26 -2
  10. package/dist/backend/services/architect-restart-service.js +136 -0
  11. package/dist/backend/services/claude-hook-service.js +134 -54
  12. package/dist/backend/services/gate-review-service.js +92 -27
  13. package/dist/backend/services/harness-service.js +54 -7
  14. package/dist/backend/services/message-service.js +6 -1
  15. package/dist/backend/services/runtime-coordinator-service.js +10 -10
  16. package/dist/backend/services/session-service.js +78 -25
  17. package/dist/backend/services/task-close-service.js +1 -0
  18. package/dist/backend/services/terminal-interrupt-service.js +4 -1
  19. package/dist/backend/services/turn-reconciler-service.js +1 -0
  20. package/dist/backend/services/usage-analytics-service.js +346 -0
  21. package/dist/backend/templates/handoff.js +4 -0
  22. package/dist/backend/templates/harness/architect-agent.js +18 -10
  23. package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +23 -0
  24. package/dist/backend/templates/harness/claude-root.js +1 -1
  25. package/dist/backend/templates/harness/gate-review.js +25 -3
  26. package/dist/backend/templates/harness/project-manager-agent.js +10 -2
  27. package/dist/backend/templates/harness/restart-architect-skill.js +75 -0
  28. package/dist/backend/templates/harness/tester-agent.js +15 -7
  29. package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +31 -5
  30. package/dist/backend/templates/harness/vcm-route-message-skill.js +3 -3
  31. package/dist/shared/types/app-settings.js +14 -0
  32. package/dist/shared/types/usage-analytics.js +1 -0
  33. package/dist/shared/validation/artifact-check.js +29 -3
  34. package/dist-frontend/assets/{index-BAE_pjXJ.js → index-CDkDHrWQ.js} +43 -43
  35. package/dist-frontend/assets/{index-CiEUp9Si.css → index-Ci7z8tW3.css} +1 -1
  36. package/dist-frontend/index.html +2 -2
  37. 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
- return deps.harnessService.startHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
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
- return deps.harnessService.restartHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
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
- return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
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
- return deps.sessionService.restartRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
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
- return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "translator", request.body);
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
- return deps.sessionService.restartRoleSession(project.repoRoot, taskSlug, "translator", request.body);
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
- return `---\nname: ${definition.agentName}\ndescription: ${frontmatter.description}\ntools: ${tools}${model}\n---\n\n# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
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) {
@@ -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 {