vibe-coding-master 0.0.13 → 0.0.15

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 (30) hide show
  1. package/README.md +36 -36
  2. package/dist/backend/api/claude-hook-routes.js +5 -0
  3. package/dist/backend/api/message-routes.js +8 -36
  4. package/dist/backend/server.js +12 -26
  5. package/dist/backend/services/artifact-service.js +27 -2
  6. package/dist/backend/services/claude-hook-service.js +67 -0
  7. package/dist/backend/services/harness-service.js +112 -0
  8. package/dist/backend/services/message-service.js +283 -175
  9. package/dist/backend/services/round-service.js +17 -127
  10. package/dist/backend/services/session-service.js +55 -2
  11. package/dist/backend/services/status-service.js +0 -76
  12. package/dist/backend/templates/handoff.js +3 -0
  13. package/dist/backend/templates/harness/architect-agent.js +5 -0
  14. package/dist/backend/templates/harness/claude-root.js +6 -2
  15. package/dist/backend/templates/harness/coder-agent.js +5 -0
  16. package/dist/backend/templates/harness/project-manager-agent.js +7 -3
  17. package/dist/backend/templates/harness/reviewer-agent.js +5 -0
  18. package/dist/backend/templates/message-envelope.js +7 -1
  19. package/dist/shared/types/claude-hook.js +1 -0
  20. package/dist-frontend/assets/{index-CyJrJge9.js → index-QhzvzTMZ.js} +43 -42
  21. package/dist-frontend/assets/index-jEkUTnIY.css +32 -0
  22. package/dist-frontend/index.html +2 -2
  23. package/docs/cc-best-practices.md +13 -4
  24. package/docs/product-design.md +74 -47
  25. package/docs/v1-architecture-design.md +53 -53
  26. package/docs/v1-implementation-plan.md +94 -61
  27. package/package.json +2 -3
  28. package/scripts/verify-package.mjs +0 -3
  29. package/dist/cli/vcmctl.js +0 -141
  30. package/dist-frontend/assets/index-N5DA0uE9.css +0 -32
@@ -1,117 +1,9 @@
1
1
  import { ROLE_NAMES } from "../../shared/constants.js";
2
- export function createRoundService(deps) {
2
+ export function createRoundService(deps = {}) {
3
3
  const now = deps.now ?? (() => new Date().toISOString());
4
- const debounceMs = deps.debounceMs ?? 1500;
5
- const trackers = new Map();
6
- function ensureTracker(session) {
7
- if (session.status !== "running") {
8
- stopSession(session.id);
9
- return;
10
- }
11
- if (trackers.has(session.id)) {
12
- return;
13
- }
14
- const tracker = {
15
- role: session.role,
16
- sessionId: session.id,
17
- taskSlug: session.taskSlug,
18
- status: "unknown",
19
- pendingToolUseIds: new Set(),
20
- unsubscribe: () => { }
21
- };
22
- tracker.unsubscribe = deps.transcripts.subscribeToRoleSession(session, (event) => {
23
- handleTranscriptEvent(tracker, event);
24
- }, {
25
- onError(error) {
26
- tracker.status = "unknown";
27
- tracker.reason = error.message;
28
- }
29
- });
30
- trackers.set(session.id, tracker);
31
- }
32
- function handleTranscriptEvent(tracker, event) {
33
- tracker.lastActivityAt = event.timestamp;
34
- clearIdleTimer(tracker);
35
- if (event.kind === "tool_use") {
36
- tracker.pendingToolUseIds.add(event.id);
37
- tracker.status = "using_tools";
38
- tracker.reason = undefined;
39
- return;
40
- }
41
- if (event.kind === "tool_result") {
42
- tracker.pendingToolUseIds.delete(event.toolResult.tool_use_id);
43
- tracker.status = tracker.pendingToolUseIds.size > 0 ? "using_tools" : "answering";
44
- tracker.reason = undefined;
45
- return;
46
- }
47
- if (event.kind === "question") {
48
- tracker.status = "waiting_user";
49
- tracker.reason = "Claude Code is asking for user input.";
50
- return;
51
- }
52
- if (event.kind === "agent" || event.kind === "todo") {
53
- tracker.status = "answering";
54
- tracker.reason = undefined;
55
- return;
56
- }
57
- if (event.kind === "thinking") {
58
- tracker.status = "answering";
59
- tracker.reason = undefined;
60
- return;
61
- }
62
- if (event.kind === "text") {
63
- if (event.stopReason === "max_tokens" || event.stopReason === "stop_sequence" || event.stopReason === "refusal") {
64
- tracker.status = "abnormal";
65
- tracker.reason = `Claude Code stopped with ${event.stopReason}.`;
66
- return;
67
- }
68
- tracker.status = "answering";
69
- tracker.reason = undefined;
70
- if (event.stopReason === "end_turn" && tracker.pendingToolUseIds.size === 0 && event.text.trim()) {
71
- scheduleIdle(tracker, event.timestamp);
72
- }
73
- }
74
- }
75
- function scheduleIdle(tracker, endedAt) {
76
- tracker.pendingAnswerEndedAt = endedAt;
77
- tracker.idleTimer = setTimeout(() => {
78
- if (tracker.pendingToolUseIds.size > 0 || tracker.pendingAnswerEndedAt !== endedAt) {
79
- return;
80
- }
81
- tracker.status = "idle";
82
- tracker.lastAnswerEndedAt = endedAt;
83
- tracker.reason = undefined;
84
- tracker.idleTimer = undefined;
85
- }, debounceMs);
86
- }
87
- function clearIdleTimer(tracker) {
88
- tracker.pendingAnswerEndedAt = undefined;
89
- if (tracker.idleTimer) {
90
- clearTimeout(tracker.idleTimer);
91
- tracker.idleTimer = undefined;
92
- }
93
- }
94
- function stopSession(sessionId) {
95
- const tracker = trackers.get(sessionId);
96
- if (!tracker) {
97
- return;
98
- }
99
- clearIdleTimer(tracker);
100
- tracker.unsubscribe();
101
- trackers.delete(sessionId);
102
- }
103
4
  return {
104
5
  getTaskRoundState(input) {
105
- const liveSessionIds = new Set(input.sessions.filter((session) => session.status === "running").map((session) => session.id));
106
- for (const session of input.sessions) {
107
- ensureTracker(session);
108
- }
109
- for (const [sessionId, tracker] of trackers) {
110
- if (tracker.taskSlug === input.taskSlug && !liveSessionIds.has(sessionId)) {
111
- stopSession(sessionId);
112
- }
113
- }
114
- const roleStates = ROLE_NAMES.map((role) => toRoleTurnState(role, input.sessions, trackers));
6
+ const roleStates = ROLE_NAMES.map((role) => toRoleTurnState(role, input.sessions));
115
7
  const latestDelivered = getLatestDeliveredMessage(input.messages);
116
8
  const queuedMessageCount = input.messages.filter((message) => message.status === "queued").length;
117
9
  const pendingMessageCount = input.messages.filter((message) => message.status === "pending_approval").length;
@@ -129,14 +21,8 @@ export function createRoundService(deps) {
129
21
  pendingMessageCount
130
22
  };
131
23
  },
132
- stopSession,
133
- stopTask(taskSlug) {
134
- for (const [sessionId, tracker] of trackers) {
135
- if (tracker.taskSlug === taskSlug) {
136
- stopSession(sessionId);
137
- }
138
- }
139
- }
24
+ stopSession() { },
25
+ stopTask() { }
140
26
  };
141
27
  }
142
28
  export function evaluateTaskRoundState(input) {
@@ -191,27 +77,31 @@ export function evaluateTaskRoundState(input) {
191
77
  updatedAt: input.updatedAt
192
78
  };
193
79
  }
194
- function toRoleTurnState(role, sessions, trackers) {
80
+ function toRoleTurnState(role, sessions) {
195
81
  const session = sessions.find((candidate) => candidate.role === role && candidate.status === "running");
196
- const tracker = session ? trackers.get(session.id) : undefined;
82
+ const activityStatus = session?.activityStatus ?? "idle";
197
83
  return {
198
84
  role,
199
85
  sessionId: session?.id,
200
- status: tracker?.status ?? "unknown",
201
- pendingToolUseCount: tracker?.pendingToolUseIds.size ?? 0,
202
- lastActivityAt: tracker?.lastActivityAt,
203
- lastAnswerEndedAt: tracker?.lastAnswerEndedAt,
204
- reason: tracker?.reason
86
+ status: session ? activityStatus === "running" ? "answering" : "idle" : "unknown",
87
+ pendingToolUseCount: 0,
88
+ lastActivityAt: session?.lastPromptSubmittedAt ?? session?.lastHookEventAt,
89
+ lastAnswerEndedAt: session?.lastStopAt,
90
+ reason: session
91
+ ? activityStatus === "running"
92
+ ? "Claude Code accepted a prompt and has not emitted Stop yet."
93
+ : "Claude Code emitted Stop or has not started a prompt in this process."
94
+ : undefined
205
95
  };
206
96
  }
207
97
  function getLatestDeliveredMessage(messages) {
208
98
  return messages
209
- .filter((message) => message.status === "delivered" || message.status === "staged")
99
+ .filter((message) => message.status === "submitted")
210
100
  .sort((left, right) => getMessageDeliveredAt(left).localeCompare(getMessageDeliveredAt(right)))
211
101
  .at(-1);
212
102
  }
213
103
  function getMessageDeliveredAt(message) {
214
- return message?.deliveredAt ?? message?.stagedAt ?? message?.createdAt ?? "";
104
+ return message?.submittedAt ?? message?.deliveredAt ?? message?.createdAt ?? "";
215
105
  }
216
106
  function getCompletedAtForDelivery(roleState, deliveredAt) {
217
107
  if (roleState.status !== "idle" || !roleState.lastAnswerEndedAt) {
@@ -41,9 +41,9 @@ export function createSessionService(deps) {
41
41
  cwd: taskRepoRoot,
42
42
  env: {
43
43
  VCM_API_URL: deps.apiUrl,
44
- VCM_CTL_COMMAND: deps.vcmctlCommand,
45
44
  VCM_TASK_SLUG: taskSlug,
46
- VCM_ROLE: role
45
+ VCM_ROLE: role,
46
+ VCM_SESSION_ID: claudeSessionId
47
47
  },
48
48
  cols: input.cols,
49
49
  rows: input.rows,
@@ -57,6 +57,7 @@ export function createSessionService(deps) {
57
57
  taskSlug,
58
58
  role,
59
59
  status: runtimeSession.status,
60
+ activityStatus: "idle",
60
61
  command: startCommand.display,
61
62
  permissionMode,
62
63
  cwd: taskRepoRoot,
@@ -99,6 +100,7 @@ export function createSessionService(deps) {
99
100
  const updated = {
100
101
  ...existing,
101
102
  status: "exited",
103
+ activityStatus: "idle",
102
104
  updatedAt: now()
103
105
  };
104
106
  deps.registry.upsert(updated);
@@ -139,6 +141,7 @@ export function createSessionService(deps) {
139
141
  return {
140
142
  ...record,
141
143
  status: runtimeSession.status,
144
+ activityStatus: record.activityStatus ?? "idle",
142
145
  pid: runtimeSession.pid,
143
146
  lastOutputAt: runtimeSession.lastOutputAt,
144
147
  exitCode: runtimeSession.exitCode
@@ -153,9 +156,59 @@ export function createSessionService(deps) {
153
156
  }
154
157
  }
155
158
  return sessions;
159
+ },
160
+ async recordClaudeHookEvent(repoRoot, input) {
161
+ const current = await this.getRoleSession(repoRoot, input.taskSlug, input.role);
162
+ if (!current || !matchesClaudeHookSession(current, input)) {
163
+ return undefined;
164
+ }
165
+ const timestamp = now();
166
+ const updated = {
167
+ ...current,
168
+ activityStatus: "idle",
169
+ lastHookEventAt: timestamp,
170
+ lastStopAt: timestamp,
171
+ updatedAt: timestamp
172
+ };
173
+ deps.registry.upsert(updated);
174
+ const config = await deps.projectService.loadConfig(repoRoot);
175
+ const task = await deps.taskService.loadTask(repoRoot, input.taskSlug);
176
+ await persistTaskSession(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
177
+ return updated;
178
+ },
179
+ async markRoleActivityRunning(repoRoot, taskSlug, role) {
180
+ const current = await this.getRoleSession(repoRoot, taskSlug, role);
181
+ if (!current) {
182
+ return undefined;
183
+ }
184
+ const timestamp = now();
185
+ const updated = {
186
+ ...current,
187
+ activityStatus: "running",
188
+ lastPromptSubmittedAt: timestamp,
189
+ lastHookEventAt: timestamp,
190
+ updatedAt: timestamp
191
+ };
192
+ deps.registry.upsert(updated);
193
+ const config = await deps.projectService.loadConfig(repoRoot);
194
+ const task = await deps.taskService.loadTask(repoRoot, taskSlug);
195
+ await persistTaskSession(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
196
+ return updated;
156
197
  }
157
198
  };
158
199
  }
200
+ function matchesClaudeHookSession(record, input) {
201
+ if (input.claudeSessionId && record.claudeSessionId === input.claudeSessionId) {
202
+ return true;
203
+ }
204
+ if (input.transcriptPath && record.transcriptPath === input.transcriptPath) {
205
+ return true;
206
+ }
207
+ if (!input.claudeSessionId && !input.transcriptPath) {
208
+ return true;
209
+ }
210
+ return false;
211
+ }
159
212
  function getRecoverableStatus(record) {
160
213
  if (!record.claudeSessionId) {
161
214
  return record.status === "running" ? "missing" : record.status;
@@ -16,84 +16,8 @@ export function createStatusService(deps) {
16
16
  task,
17
17
  sessions,
18
18
  artifacts,
19
- workflow: buildWorkflowReport(artifacts, sessions),
20
19
  warnings
21
20
  };
22
21
  }
23
22
  };
24
23
  }
25
- function buildWorkflowReport(artifacts, sessions) {
26
- const isComplete = (kind) => artifacts.checks.find((check) => check.kind === kind)?.status === "ok";
27
- const roleIsRunning = (role) => sessions.some((session) => session.role === role && session.status === "running");
28
- const architectureComplete = isComplete("architecture-plan");
29
- const implementationComplete = isComplete("implementation-log") && isComplete("validation-log");
30
- const reviewComplete = isComplete("review-report");
31
- const docsSyncComplete = isComplete("docs-sync-report");
32
- const steps = [
33
- {
34
- id: "architecture-plan",
35
- label: "Architecture",
36
- role: "architect",
37
- artifactPaths: [artifacts.paths.architecturePlanPath],
38
- status: architectureComplete ? "complete" : "ready",
39
- detail: architectureComplete
40
- ? "architecture-plan.md is ready."
41
- : roleIsRunning("architect")
42
- ? "Architect is running; produce architecture-plan.md before coder work."
43
- : "Start architect and produce architecture-plan.md before coder work."
44
- },
45
- {
46
- id: "implementation",
47
- label: "Implementation",
48
- role: "coder",
49
- artifactPaths: [artifacts.paths.implementationLogPath, artifacts.paths.validationLogPath],
50
- status: implementationComplete ? "complete" : architectureComplete ? "ready" : "blocked",
51
- detail: implementationComplete
52
- ? "implementation-log.md and validation-log.md are ready."
53
- : architectureComplete
54
- ? "Start coder, then update implementation-log.md and validation-log.md."
55
- : "Blocked until architecture-plan.md is complete."
56
- },
57
- {
58
- id: "review",
59
- label: "Review",
60
- role: "reviewer",
61
- artifactPaths: [artifacts.paths.reviewReportPath],
62
- status: reviewComplete ? "complete" : implementationComplete ? "ready" : "blocked",
63
- detail: reviewComplete
64
- ? "review-report.md is ready."
65
- : implementationComplete
66
- ? "Start reviewer for independent review and final test adequacy."
67
- : "Blocked until implementation-log.md and validation-log.md are complete."
68
- },
69
- {
70
- id: "docs-sync",
71
- label: "Docs Sync",
72
- role: "architect",
73
- artifactPaths: [artifacts.paths.docsSyncReportPath],
74
- status: docsSyncComplete ? "complete" : reviewComplete ? "ready" : "blocked",
75
- detail: docsSyncComplete
76
- ? "docs-sync-report.md is ready."
77
- : reviewComplete
78
- ? "Send architect a docs-sync / architecture drift check task."
79
- : "Blocked until review-report.md is complete."
80
- },
81
- {
82
- id: "final-acceptance",
83
- label: "PM Final",
84
- role: "project-manager",
85
- artifactPaths: [],
86
- status: docsSyncComplete ? "ready" : "blocked",
87
- detail: docsSyncComplete
88
- ? "Project Manager can prepare final acceptance, commit, and PR."
89
- : "Blocked until architect docs sync is complete."
90
- }
91
- ];
92
- const current = steps.find((step) => step.status !== "complete") ?? steps[steps.length - 1];
93
- return {
94
- currentStepId: current.id,
95
- nextAction: current.detail,
96
- blocked: current.status === "blocked",
97
- steps
98
- };
99
- }
@@ -106,3 +106,6 @@ TBD
106
106
  TBD
107
107
  `;
108
108
  }
109
+ export function renderMessageRouteTemplate() {
110
+ return "";
111
+ }
@@ -9,5 +9,10 @@ export function renderArchitectHarnessRules() {
9
9
  - Write docs-sync-report.md with docs changed, docs intentionally left unchanged, remaining documentation risks, and decision.
10
10
  - Stop and reply to project-manager if implementation drift changes architecture, public contracts, dependency direction, schema, auth, permission, payment, or design assumptions.
11
11
  - Reply to project-manager once per received VCM message when complete, blocked, or unclear; do not send fragmented progress updates unless project-manager explicitly requested them.
12
+ - Send replies by writing or updating .ai/vcm/handoffs/messages/architect-project-manager.md.
13
+ - If you need to send a VCM message, write at most one route file for project-manager in the current Claude Code turn, then end the turn.
14
+ - After writing the route file, end the turn immediately. Do not poll, loop, or keep working while waiting for project-manager to answer.
15
+ - Do not wait in a loop for another role to answer. VCM will deliver later replies in a new turn.
16
+ - Do not use Claude Code Task/Subagent for VCM role delegation; communicate through VCM route files only.
12
17
  `;
13
18
  }
@@ -6,10 +6,14 @@ export function renderRootClaudeHarnessRules() {
6
6
  - Canonical task handoffs live under .ai/vcm/handoffs/ inside the current task runtime repo.
7
7
  - Use only the current task's handoff directory for task-specific artifacts.
8
8
  - Do not create or write task handoffs outside .ai/vcm/handoffs/ for the current task.
9
- - Use vcmctl for role-to-role messaging instead of asking the user to copy prompts.
9
+ - Use route files under .ai/vcm/handoffs/messages/ for role-to-role messaging instead of asking the user to copy prompts.
10
+ - A role-to-role call is represented by exactly one file named <from-role>-<to-role>.md, such as project-manager-coder.md or coder-project-manager.md.
11
+ - If you need to revise a not-yet-delivered message to the same target, update that route file instead of creating another message.
10
12
  - Non-PM roles only reply to project-manager; they do not message other roles directly.
11
13
  - Role messaging is turn-based: do not send more than one active message to the same target role.
12
- - After sending a message to a role, wait for that role to reply with vcmctl reply or vcmctl result before sending another message to the same role.
14
+ - After writing a route file for another role, end the current Claude Code turn. Treat the file write as the final coordination action of this turn.
15
+ - Do not poll files, start shell loops, or keep the turn open waiting for another role's answer. VCM scans pending route files after your Stop hook and delivers later replies in a new turn.
16
+ - Do not use Claude Code Task/Subagent for VCM role delegation; VCM owns the four role sessions and the message queue.
13
17
  - If new information arrives while a role is still processing, update the relevant handoff artifact or wait; do not spam the target role's terminal.
14
18
  - High-risk decisions involving schema, auth, permissions, payment, billing, security, data deletion, or unclear user intent must stop for project-manager/user approval.
15
19
  - Required workflow gates: architect plan -> coder implementation/validation -> reviewer review -> architect docs sync -> project-manager final acceptance/commit/PR.
@@ -8,5 +8,10 @@ export function renderCoderHarnessRules() {
8
8
  - Do not change module boundaries, public contracts, dependency direction, or test strategy without project-manager/architect replan.
9
9
  - Stop and reply to project-manager when blocked, unclear, or when the plan no longer matches reality.
10
10
  - Reply to project-manager once per received VCM message when complete, blocked, or unclear; do not send fragmented progress updates unless project-manager explicitly requested them.
11
+ - Send replies by writing or updating .ai/vcm/handoffs/messages/coder-project-manager.md.
12
+ - If you need to send a VCM message, write at most one route file for project-manager in the current Claude Code turn, then end the turn.
13
+ - After writing the route file, end the turn immediately. Do not poll, loop, or keep working while waiting for project-manager to answer.
14
+ - Do not wait in a loop for another role to answer. VCM will deliver later replies in a new turn.
15
+ - Do not use Claude Code Task/Subagent for VCM role delegation; communicate through VCM route files only.
11
16
  `;
12
17
  }
@@ -3,10 +3,14 @@ export function renderProjectManagerHarnessRules() {
3
3
 
4
4
  - You are the user-facing orchestration hub for the VCM task.
5
5
  - Clarify the user's request, classify task risk, and choose the role route.
6
- - Use vcmctl send to assign work to architect, coder, or reviewer.
7
- - Send role work as durable instructions with artifact refs when possible.
6
+ - Assign work by writing or updating .ai/vcm/handoffs/messages/project-manager-architect.md, project-manager-coder.md, or project-manager-reviewer.md.
7
+ - Send role work as durable instructions with optional YAML frontmatter, for example type: task and artifact_refs: .ai/vcm/handoffs/architecture-plan.md.
8
8
  - Enforce per-role turn-taking: keep at most one in-flight message per target role.
9
- - Before sending another task, question, revise, or review-request to the same role, wait for that role's vcmctl reply or vcmctl result.
9
+ - Before sending another task, question, revise, or review-request to the same role, wait for that role's reply file to be delivered back by VCM.
10
+ - In one Claude Code turn, send at most one VCM message to any single target role.
11
+ - After writing a route file, end the turn immediately. Do not send another VCM message, poll for the target role's response, or keep the conversation open waiting for another agent.
12
+ - Continue orchestration only in a later turn after VCM delivers that role's result, blocked, question, or finding message.
13
+ - Do not use Claude Code Task/Subagent for VCM role delegation; VCM manages the four role sessions.
10
14
  - Use cancel only for urgent supersession; include what is superseded.
11
15
  - Track the workflow gates: architecture plan, implementation/validation, review, docs sync, final acceptance.
12
16
  - Request architect post-review docs sync after reviewer completes.
@@ -10,5 +10,10 @@ export function renderReviewerHarnessRules() {
10
10
  - Escalate architecture, public contract, design, or documentation drift issues to project-manager for architect follow-up.
11
11
  - Do not take over broad implementation and do not weaken tests to pass validation.
12
12
  - Reply to project-manager once per received VCM message when complete, blocked, or unclear; do not send fragmented progress updates unless project-manager explicitly requested them.
13
+ - Send replies by writing or updating .ai/vcm/handoffs/messages/reviewer-project-manager.md.
14
+ - If you need to send a VCM message, write at most one route file for project-manager in the current Claude Code turn, then end the turn.
15
+ - After writing the route file, end the turn immediately. Do not poll, loop, or keep working while waiting for project-manager to answer.
16
+ - Do not wait in a loop for another role to answer. VCM will deliver later replies in a new turn.
17
+ - Do not use Claude Code Task/Subagent for VCM role delegation; communicate through VCM route files only.
13
18
  `;
14
19
  }
@@ -2,6 +2,9 @@ export function renderMessageEnvelope(message) {
2
2
  const artifactRefs = message.artifactRefs.length > 0
3
3
  ? message.artifactRefs.map((artifact) => `- ${artifact}`).join("\n")
4
4
  : "- none";
5
+ const routeFileExample = message.toRole === "project-manager"
6
+ ? ".ai/vcm/handoffs/messages/project-manager-<target-role>.md"
7
+ : `.ai/vcm/handoffs/messages/${message.toRole}-project-manager.md`;
5
8
  return `
6
9
  [VCM MESSAGE]
7
10
  id: ${message.id}
@@ -17,7 +20,10 @@ ${artifactRefs}
17
20
 
18
21
  Instructions:
19
22
  - Read the message and execute only within this VCM task.
20
- - Reply to project-manager with vcmctl reply when complete, blocked, or unclear.
23
+ - If you need to send a VCM message after handling this, write or update .ai/vcm/handoffs/messages/<your-role>-<target-role>.md.
24
+ - Non-PM roles reply only to project-manager, for example ${routeFileExample}.
25
+ - After writing a route file, end this Claude Code turn immediately.
26
+ - Do not poll, loop, or wait for another role in this turn. VCM scans route files after your Stop hook and delivers later replies in a new turn.
21
27
  [/VCM MESSAGE]
22
28
  `;
23
29
  }
@@ -0,0 +1 @@
1
+ export {};