vibe-coding-master 0.7.16 → 0.7.17

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 (28) hide show
  1. package/README.md +12 -0
  2. package/dist/backend/adapters/claude-adapter.js +4 -1
  3. package/dist/backend/api/session-routes.js +5 -0
  4. package/dist/backend/api/usage-analytics-routes.js +31 -0
  5. package/dist/backend/cli/install-vcm-harness.js +51 -2
  6. package/dist/backend/server.js +25 -5
  7. package/dist/backend/services/architect-restart-service.js +136 -0
  8. package/dist/backend/services/claude-hook-service.js +6 -0
  9. package/dist/backend/services/gate-review-service.js +40 -0
  10. package/dist/backend/services/harness-service.js +51 -4
  11. package/dist/backend/services/message-service.js +5 -0
  12. package/dist/backend/services/session-service.js +34 -6
  13. package/dist/backend/services/task-close-service.js +1 -0
  14. package/dist/backend/services/usage-analytics-service.js +346 -0
  15. package/dist/backend/templates/harness/architect-agent.js +18 -10
  16. package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +23 -0
  17. package/dist/backend/templates/harness/claude-root.js +1 -1
  18. package/dist/backend/templates/harness/gate-review.js +7 -2
  19. package/dist/backend/templates/harness/project-manager-agent.js +1 -1
  20. package/dist/backend/templates/harness/restart-architect-skill.js +75 -0
  21. package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +31 -5
  22. package/dist/backend/templates/harness/vcm-route-message-skill.js +3 -3
  23. package/dist/shared/types/usage-analytics.js +1 -0
  24. package/dist-frontend/assets/index-CStWyouh.js +97 -0
  25. package/dist-frontend/assets/{index-CiEUp9Si.css → index-Ci7z8tW3.css} +1 -1
  26. package/dist-frontend/index.html +2 -2
  27. package/package.json +1 -1
  28. package/dist-frontend/assets/index-BAE_pjXJ.js +0 -97
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
@@ -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,
@@ -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);
@@ -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, "../../..");
@@ -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";
@@ -132,7 +135,8 @@ export async function createServer(deps, options = {}) {
132
135
  sessionService: deps.sessionService,
133
136
  commandDispatcher: deps.commandDispatcher,
134
137
  translationService: deps.translationService,
135
- roundService: deps.roundService
138
+ roundService: deps.roundService,
139
+ architectRestartService: deps.architectRestartService
136
140
  });
137
141
  registerArtifactRoutes(app, {
138
142
  projectService: deps.projectService,
@@ -155,6 +159,11 @@ export async function createServer(deps, options = {}) {
155
159
  sessionService: deps.sessionService,
156
160
  translationService: deps.translationService
157
161
  });
162
+ registerUsageAnalyticsRoutes(app, {
163
+ projectService: deps.projectService,
164
+ taskService: deps.taskService,
165
+ usageAnalyticsService: deps.usageAnalyticsService
166
+ });
158
167
  registerGatewayRoutes(app, { gatewayService: deps.gatewayService });
159
168
  registerTerminalWs(app, {
160
169
  runtime: deps.runtime,
@@ -264,12 +273,18 @@ export function createDefaultServerDeps(options = {}) {
264
273
  sessionService,
265
274
  artifactService
266
275
  });
276
+ const architectRestartService = createArchitectRestartService({
277
+ fs,
278
+ taskService,
279
+ sessionService
280
+ });
267
281
  const messageService = createMessageService({
268
282
  fs,
269
283
  runtime,
270
284
  sessionService,
271
285
  taskService,
272
- taskWorkflowService
286
+ taskWorkflowService,
287
+ onRouteDelivered: ({ repoRoot, taskSlug, message }) => architectRestartService.recordRouteDelivered(repoRoot, taskSlug, message)
273
288
  });
274
289
  const taskLaunchService = createTaskLaunchService({
275
290
  projectService,
@@ -312,6 +327,7 @@ export function createDefaultServerDeps(options = {}) {
312
327
  roundService,
313
328
  appSettings
314
329
  });
330
+ const usageAnalyticsService = createUsageAnalyticsService({ fs });
315
331
  const gatewayChannels = createGatewayChannelRegistry([
316
332
  createWeixinIlinkChannel(),
317
333
  createLarkChannel()
@@ -331,7 +347,8 @@ export function createDefaultServerDeps(options = {}) {
331
347
  translationService,
332
348
  roundService,
333
349
  projectService,
334
- taskWorkflowService
350
+ taskWorkflowService,
351
+ architectRestartService
335
352
  });
336
353
  const gatewayService = createGatewayService({
337
354
  fs,
@@ -368,7 +385,8 @@ export function createDefaultServerDeps(options = {}) {
368
385
  autoMemoryService,
369
386
  gatewayService,
370
387
  jobGuard: createJobGuardService(),
371
- translationWorkerService
388
+ translationWorkerService,
389
+ architectRestartService
372
390
  });
373
391
  const turnReconciler = createTurnReconcilerService({
374
392
  sessionService,
@@ -412,6 +430,7 @@ export function createDefaultServerDeps(options = {}) {
412
430
  taskService,
413
431
  taskCloseService,
414
432
  taskWorkflowService,
433
+ architectRestartService,
415
434
  sessionService,
416
435
  artifactService,
417
436
  harnessService,
@@ -431,7 +450,8 @@ export function createDefaultServerDeps(options = {}) {
431
450
  runtimeRecoveryService,
432
451
  terminalInterruptService,
433
452
  runtime,
434
- diagnosticsService
453
+ diagnosticsService,
454
+ usageAnalyticsService
435
455
  };
436
456
  }
437
457
  export function getDefaultStaticDir() {
@@ -0,0 +1,136 @@
1
+ import path from "node:path";
2
+ import { resolveRepoPath } from "../adapters/filesystem.js";
3
+ import { VcmError } from "../errors.js";
4
+ import { getTaskRuntimeRepoRoot } from "./task-service.js";
5
+ const ARCHITECT_ROLE = "architect";
6
+ const PM_ROLE = "project-manager";
7
+ const COMPLETE_PLAN_PATTERN = /^Planning Result:\s*complete\s*$/im;
8
+ export const ARCHITECT_RESTORE_PROMPT = `This Architect session continues the current task after completed architecture planning.
9
+
10
+ Before performing any assigned work, read:
11
+ - .ai/vcm/handoffs/architecture-brief.md
12
+ - .ai/vcm/handoffs/architecture-evidence.md
13
+ - .ai/vcm/handoffs/architecture-plan.md
14
+ - the current scaffold commit and worktree state
15
+ - the latest Gate Review report when present
16
+
17
+ Treat the current artifacts and worktree as the source of truth. Do not repeat the completed interview or planning work unless current evidence contradicts them.`;
18
+ export function createArchitectRestartService(deps) {
19
+ const pendingByTask = new Map();
20
+ return {
21
+ async schedule(repoRoot, taskSlug) {
22
+ const session = await requireRunningArchitect(repoRoot, taskSlug);
23
+ await requireCompletePlan(repoRoot, taskSlug);
24
+ const key = taskKey(repoRoot, taskSlug);
25
+ const existing = pendingByTask.get(key);
26
+ if (existing?.sessionId === session.id) {
27
+ return { taskSlug, sessionId: session.id, status: "scheduled" };
28
+ }
29
+ pendingByTask.set(key, {
30
+ repoRoot,
31
+ taskSlug,
32
+ sessionId: session.id,
33
+ stopped: false,
34
+ executing: false
35
+ });
36
+ return { taskSlug, sessionId: session.id, status: "scheduled" };
37
+ },
38
+ async recordArchitectStop(repoRoot, taskSlug, sessionId) {
39
+ const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
40
+ if (!pending || pending.sessionId !== sessionId) {
41
+ return;
42
+ }
43
+ pending.stopped = true;
44
+ await tryRestart(pending);
45
+ },
46
+ async recordRouteDelivered(repoRoot, taskSlug, message) {
47
+ if (!isArchitectToPm(message)) {
48
+ return;
49
+ }
50
+ const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
51
+ if (!pending) {
52
+ return;
53
+ }
54
+ pending.deliveredMessageId = message.id;
55
+ await tryRestart(pending);
56
+ },
57
+ async recordRouteAccepted(repoRoot, taskSlug, message) {
58
+ if (!isArchitectToPm(message)) {
59
+ return;
60
+ }
61
+ const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
62
+ if (!pending) {
63
+ return;
64
+ }
65
+ pending.acceptedMessageId = message.id;
66
+ await tryRestart(pending);
67
+ },
68
+ clear(repoRoot, taskSlug) {
69
+ pendingByTask.delete(taskKey(repoRoot, taskSlug));
70
+ }
71
+ };
72
+ async function requireRunningArchitect(repoRoot, taskSlug) {
73
+ const session = await deps.sessionService.getRoleSession(repoRoot, taskSlug, ARCHITECT_ROLE);
74
+ if (!session || session.status !== "running") {
75
+ throw new VcmError({
76
+ code: "ARCHITECT_SESSION_NOT_RUNNING",
77
+ message: "Architect session is not running.",
78
+ statusCode: 409
79
+ });
80
+ }
81
+ return session;
82
+ }
83
+ async function requireCompletePlan(repoRoot, taskSlug) {
84
+ const task = await deps.taskService.loadTask(repoRoot, taskSlug);
85
+ const planPath = resolveRepoPath(getTaskRuntimeRepoRoot(task), path.posix.join(task.handoffDir, "architecture-plan.md"));
86
+ if (!(await deps.fs.pathExists(planPath))) {
87
+ throw incompletePlanError("architecture-plan.md does not exist.");
88
+ }
89
+ const content = await deps.fs.readText(planPath);
90
+ if (!COMPLETE_PLAN_PATTERN.test(content)) {
91
+ throw incompletePlanError("architecture-plan.md is not marked complete.");
92
+ }
93
+ }
94
+ async function tryRestart(pending) {
95
+ if (pending.executing
96
+ || !pending.stopped
97
+ || !pending.deliveredMessageId
98
+ || pending.deliveredMessageId !== pending.acceptedMessageId) {
99
+ return;
100
+ }
101
+ const session = await deps.sessionService.getRoleSession(pending.repoRoot, pending.taskSlug, ARCHITECT_ROLE);
102
+ if (!session
103
+ || session.id !== pending.sessionId
104
+ || session.status !== "running"
105
+ || session.activityStatus !== "idle") {
106
+ return;
107
+ }
108
+ pending.executing = true;
109
+ try {
110
+ await requireCompletePlan(pending.repoRoot, pending.taskSlug);
111
+ await deps.sessionService.restartRoleSession(pending.repoRoot, pending.taskSlug, ARCHITECT_ROLE, {
112
+ permissionMode: session.permissionMode,
113
+ model: session.model,
114
+ effort: session.effort,
115
+ appendSystemPrompt: ARCHITECT_RESTORE_PROMPT
116
+ });
117
+ pendingByTask.delete(taskKey(pending.repoRoot, pending.taskSlug));
118
+ }
119
+ catch {
120
+ pending.executing = false;
121
+ }
122
+ }
123
+ }
124
+ function isArchitectToPm(message) {
125
+ return message.fromRole === ARCHITECT_ROLE && message.toRole === PM_ROLE;
126
+ }
127
+ function taskKey(repoRoot, taskSlug) {
128
+ return `${repoRoot}\0${taskSlug}`;
129
+ }
130
+ function incompletePlanError(reason) {
131
+ return new VcmError({
132
+ code: "ARCHITECT_PLAN_INCOMPLETE",
133
+ message: `Architect restart cannot be scheduled. ${reason}`,
134
+ statusCode: 409
135
+ });
136
+ }
@@ -227,6 +227,9 @@ export function createClaudeHookService(deps) {
227
227
  role: input.role,
228
228
  prompt: stringOrUndefined(input.event.prompt)
229
229
  });
230
+ if (submitted) {
231
+ await deps.architectRestartService?.recordRouteAccepted(context.project.repoRoot, context.taskSlug, submitted);
232
+ }
230
233
  return {
231
234
  ok: true,
232
235
  eventName,
@@ -437,6 +440,9 @@ export function createClaudeHookService(deps) {
437
440
  occurredAt: session.lastTurnEndedAt ?? session.updatedAt
438
441
  });
439
442
  }
443
+ if (eventName === "Stop" && input.role === "architect" && session) {
444
+ await deps.architectRestartService?.recordArchitectStop(context.project.repoRoot, context.taskSlug, session.id);
445
+ }
440
446
  if (options.notifyGateway && session && input.role === "project-manager") {
441
447
  void deps.gatewayService?.handlePmStop({
442
448
  repoRoot: context.project.repoRoot,
@@ -56,6 +56,7 @@ const CODE_DIFF_ANALYSIS_FIELDS = [
56
56
  const SOURCE_ARTIFACTS = {
57
57
  "architecture-plan": [
58
58
  ".ai/vcm/handoffs/architecture-brief.md",
59
+ ".ai/vcm/handoffs/architecture-evidence.md",
59
60
  ".ai/vcm/handoffs/architecture-plan.md"
60
61
  ],
61
62
  "validation-adequacy": [
@@ -228,6 +229,30 @@ export function createGateReviewService(deps) {
228
229
  message: architectureBriefError
229
230
  };
230
231
  }
232
+ const architectureEvidenceError = await readArchitectureEvidenceError(deps.fs, context.taskRepoRoot);
233
+ if (architectureEvidenceError) {
234
+ index = applyGateState(index, gate, {
235
+ status: "failed",
236
+ decision: undefined,
237
+ error: architectureEvidenceError,
238
+ exceptionReason: undefined,
239
+ requestId: undefined,
240
+ requestPath: undefined,
241
+ inputHash: undefined,
242
+ requestedAt: undefined,
243
+ startedAt: undefined,
244
+ completedAt: now(),
245
+ callbackStatus: "not_sent",
246
+ callbackError: undefined
247
+ }, now(), true);
248
+ await saveIndex(deps.fs, context.taskRepoRoot, index);
249
+ return {
250
+ status: "failed_to_start",
251
+ gate,
252
+ record: index.gates[gate],
253
+ message: architectureEvidenceError
254
+ };
255
+ }
231
256
  }
232
257
  const coreInput = await readCoreInputArtifact(deps.fs, context.taskRepoRoot, gate);
233
258
  if (coreInput && coreInput.status !== "ready") {
@@ -961,6 +986,21 @@ async function readArchitectureBriefError(fs, taskRepoRoot) {
961
986
  }
962
987
  return undefined;
963
988
  }
989
+ async function readArchitectureEvidenceError(fs, taskRepoRoot) {
990
+ const relativePath = ".ai/vcm/handoffs/architecture-evidence.md";
991
+ const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
992
+ if (!await fs.pathExists(absolutePath)) {
993
+ return `${relativePath} is missing. Complete architecture evidence before requesting architecture-plan review.`;
994
+ }
995
+ const content = await fs.readText(absolutePath);
996
+ if (content.trim().length === 0) {
997
+ return `${relativePath} is empty. Complete architecture evidence before requesting architecture-plan review.`;
998
+ }
999
+ if (!/^\s*Architecture Evidence Status\s*:\s*complete\s*$/im.test(content)) {
1000
+ return `${relativePath} is incomplete. Finish current-worktree evidence before requesting architecture-plan review.`;
1001
+ }
1002
+ return undefined;
1003
+ }
964
1004
  async function commandStdout(runner, cwd, args) {
965
1005
  const result = await runner.run("git", args, { cwd });
966
1006
  return result.exitCode === 0 ? result.stdout : "";
@@ -4,6 +4,7 @@ import { promisify } from "node:util";
4
4
  import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
5
5
  import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
6
6
  import { renderCoderWorkerHarnessRules } from "../templates/harness/coder-worker-agent.js";
7
+ import { renderArchitectScaffoldWorkerHarnessRules } from "../templates/harness/architect-scaffold-worker-agent.js";
7
8
  import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTranslatorAgentRules, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
8
9
  import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
9
10
  import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
@@ -24,6 +25,7 @@ import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-
24
25
  import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
25
26
  import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
26
27
  import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
28
+ import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
27
29
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
28
30
  import { VcmError } from "../errors.js";
29
31
  import { bumpHarnessRevision, readHarnessRevisionState } from "./harness-revision.js";
@@ -172,6 +174,14 @@ const HARNESS_FILES = [
172
174
  ownership: "whole-file",
173
175
  renderRules: renderVcmProposeMemorySkillRules
174
176
  },
177
+ {
178
+ kind: "skill-restart-architect",
179
+ path: ".claude/skills/restart-architect/SKILL.md",
180
+ title: "Restart Architect Skill",
181
+ frontmatter: renderSkillFrontmatter("restart-architect", "Use after Architect completes and commits architecture planning and scaffold work."),
182
+ ownership: "whole-file",
183
+ renderRules: renderRestartArchitectSkillRules
184
+ },
175
185
  {
176
186
  kind: "agent-gate-reviewer",
177
187
  path: ".claude/agents/gate-reviewer.md",
@@ -202,6 +212,13 @@ const HARNESS_FILES = [
202
212
  frontmatter: renderAgentFrontmatter("vcm-coder-worker", "Bounded VCM implementation worker for assigned modules, files, and VCM:CODE markers from Coder.", { model: "inherit" }),
203
213
  renderRules: renderCoderWorkerHarnessRules
204
214
  },
215
+ {
216
+ kind: "agent-architect-scaffold-worker",
217
+ path: ".claude/agents/vcm-architect-scaffold-worker.md",
218
+ title: "VCM Architect Scaffold Worker Agent",
219
+ frontmatter: renderAgentFrontmatter("vcm-architect-scaffold-worker", "Foreground Architect worker for exact scaffold execution and scaffold validation.", { model: "opus", effort: "xhigh" }),
220
+ renderRules: renderArchitectScaffoldWorkerHarnessRules
221
+ },
205
222
  {
206
223
  kind: "tool-request-gate-review",
207
224
  path: ".ai/tools/request-gate-review",
@@ -223,6 +240,13 @@ const HARNESS_FILES = [
223
240
  ownership: "raw-file",
224
241
  renderRules: renderCheckScaffoldLedgerTool
225
242
  },
243
+ {
244
+ kind: "tool-request-architect-restart",
245
+ path: ".ai/tools/request-architect-restart",
246
+ title: "Request Architect Restart Tool",
247
+ ownership: "raw-file",
248
+ renderRules: renderRequestArchitectRestartTool
249
+ },
226
250
  {
227
251
  kind: "agent-project-manager",
228
252
  path: ".claude/agents/project-manager.md",
@@ -237,7 +261,7 @@ const HARNESS_FILES = [
237
261
  title: "Architect Agent",
238
262
  memoryBlock: true,
239
263
  blankLineBeforeEnd: true,
240
- frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync."),
264
+ frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent" }),
241
265
  renderRules: renderArchitectHarnessRules
242
266
  },
243
267
  {
@@ -1192,6 +1216,9 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
1192
1216
  };
1193
1217
  }
1194
1218
  const insertedContent = `${currentContent.trimEnd()}\n\n${expectedBlock}\n`;
1219
+ const nextContent = definition.kind === "agent-architect"
1220
+ ? ensureAgentTool(insertedContent, "Agent")
1221
+ : insertedContent;
1195
1222
  return {
1196
1223
  definition,
1197
1224
  status: {
@@ -1206,13 +1233,16 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
1206
1233
  action: "insert",
1207
1234
  reason: "File exists but does not contain VCM managed rules."
1208
1235
  },
1209
- nextContent: definition.memoryBlock ? ensureVcmMemoryBlock(insertedContent) : insertedContent
1236
+ nextContent: definition.memoryBlock ? ensureVcmMemoryBlock(nextContent) : nextContent
1210
1237
  };
1211
1238
  }
1212
1239
  const managedVersion = match[1] ? Number(match[1]) : undefined;
1213
1240
  const currentBlock = match[0];
1214
1241
  const blockUpdatedContent = currentContent.replace(managedBlockPattern, expectedBlock);
1215
- const nextContent = definition.memoryBlock ? ensureVcmMemoryBlock(blockUpdatedContent) : blockUpdatedContent;
1242
+ const memoryUpdatedContent = definition.memoryBlock ? ensureVcmMemoryBlock(blockUpdatedContent) : blockUpdatedContent;
1243
+ const nextContent = definition.kind === "agent-architect"
1244
+ ? ensureAgentTool(memoryUpdatedContent, "Agent")
1245
+ : memoryUpdatedContent;
1216
1246
  const action = currentContent === nextContent ? "ok" : "update";
1217
1247
  return {
1218
1248
  definition,
@@ -1323,6 +1353,22 @@ function renderNewHarnessFile(definition, block, contentAfterBlock = definition.
1323
1353
  const suffix = contentAfterBlock?.trim();
1324
1354
  return `${frontmatter}# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
1325
1355
  }
1356
+ function ensureAgentTool(content, requiredTool) {
1357
+ const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
1358
+ if (!frontmatterMatch) {
1359
+ return content;
1360
+ }
1361
+ const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
1362
+ if (!toolsMatch) {
1363
+ return content.replace(/^(---\r?\n[\s\S]*?)(\r?\n---)/, `$1\ntools: ${requiredTool}$2`);
1364
+ }
1365
+ const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
1366
+ if (tools.includes(requiredTool)) {
1367
+ return content;
1368
+ }
1369
+ const nextTools = [...tools, requiredTool].join(", ");
1370
+ return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
1371
+ }
1326
1372
  function migrateLegacyHarnessFile(definition, currentContent, block) {
1327
1373
  const legacyContent = definition.legacyWholeFile?.trimEnd();
1328
1374
  if (!legacyContent) {
@@ -1454,7 +1500,8 @@ function isPlainObject(value) {
1454
1500
  function renderAgentFrontmatter(name, description, options = {}) {
1455
1501
  const tools = options.tools ?? "Read, Grep, Glob, Bash, Edit, Write";
1456
1502
  const model = options.model ? `\nmodel: ${options.model}` : "";
1457
- return `---\nname: ${name}\ndescription: ${description}\ntools: ${tools}${model}\n---`;
1503
+ const effort = options.effort ? `\neffort: ${options.effort}` : "";
1504
+ return `---\nname: ${name}\ndescription: ${description}\ntools: ${tools}${model}${effort}\n---`;
1458
1505
  }
1459
1506
  function renderSkillFrontmatter(name, description) {
1460
1507
  return `---\nname: ${name}\ndescription: ${description}\n---`;