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.
- package/README.md +36 -36
- package/dist/backend/api/claude-hook-routes.js +5 -0
- package/dist/backend/api/message-routes.js +8 -36
- package/dist/backend/server.js +12 -26
- package/dist/backend/services/artifact-service.js +27 -2
- package/dist/backend/services/claude-hook-service.js +67 -0
- package/dist/backend/services/harness-service.js +112 -0
- package/dist/backend/services/message-service.js +283 -175
- package/dist/backend/services/round-service.js +17 -127
- package/dist/backend/services/session-service.js +55 -2
- package/dist/backend/services/status-service.js +0 -76
- package/dist/backend/templates/handoff.js +3 -0
- package/dist/backend/templates/harness/architect-agent.js +5 -0
- package/dist/backend/templates/harness/claude-root.js +6 -2
- package/dist/backend/templates/harness/coder-agent.js +5 -0
- package/dist/backend/templates/harness/project-manager-agent.js +7 -3
- package/dist/backend/templates/harness/reviewer-agent.js +5 -0
- package/dist/backend/templates/message-envelope.js +7 -1
- package/dist/shared/types/claude-hook.js +1 -0
- package/dist-frontend/assets/{index-CyJrJge9.js → index-QhzvzTMZ.js} +43 -42
- package/dist-frontend/assets/index-jEkUTnIY.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/docs/cc-best-practices.md +13 -4
- package/docs/product-design.md +74 -47
- package/docs/v1-architecture-design.md +53 -53
- package/docs/v1-implementation-plan.md +94 -61
- package/package.json +2 -3
- package/scripts/verify-package.mjs +0 -3
- package/dist/cli/vcmctl.js +0 -141
- 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
|
|
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(
|
|
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
|
|
80
|
+
function toRoleTurnState(role, sessions) {
|
|
195
81
|
const session = sessions.find((candidate) => candidate.role === role && candidate.status === "running");
|
|
196
|
-
const
|
|
82
|
+
const activityStatus = session?.activityStatus ?? "idle";
|
|
197
83
|
return {
|
|
198
84
|
role,
|
|
199
85
|
sessionId: session?.id,
|
|
200
|
-
status:
|
|
201
|
-
pendingToolUseCount:
|
|
202
|
-
lastActivityAt:
|
|
203
|
-
lastAnswerEndedAt:
|
|
204
|
-
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 === "
|
|
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?.
|
|
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
|
-
}
|
|
@@ -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
|
|
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
|
|
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
|
-
-
|
|
7
|
-
- Send role work as durable instructions with
|
|
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
|
|
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
|
-
-
|
|
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 {};
|