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.
- package/README.md +12 -0
- package/dist/backend/adapters/claude-adapter.js +4 -1
- package/dist/backend/api/session-routes.js +5 -0
- package/dist/backend/api/usage-analytics-routes.js +31 -0
- package/dist/backend/cli/install-vcm-harness.js +51 -2
- package/dist/backend/server.js +25 -5
- package/dist/backend/services/architect-restart-service.js +136 -0
- package/dist/backend/services/claude-hook-service.js +6 -0
- package/dist/backend/services/gate-review-service.js +40 -0
- package/dist/backend/services/harness-service.js +51 -4
- package/dist/backend/services/message-service.js +5 -0
- package/dist/backend/services/session-service.js +34 -6
- package/dist/backend/services/task-close-service.js +1 -0
- package/dist/backend/services/usage-analytics-service.js +346 -0
- package/dist/backend/templates/harness/architect-agent.js +18 -10
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +23 -0
- package/dist/backend/templates/harness/claude-root.js +1 -1
- package/dist/backend/templates/harness/gate-review.js +7 -2
- package/dist/backend/templates/harness/project-manager-agent.js +1 -1
- package/dist/backend/templates/harness/restart-architect-skill.js +75 -0
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +31 -5
- package/dist/backend/templates/harness/vcm-route-message-skill.js +3 -3
- package/dist/shared/types/usage-analytics.js +1 -0
- package/dist-frontend/assets/index-CStWyouh.js +97 -0
- package/dist-frontend/assets/{index-CiEUp9Si.css → index-Ci7z8tW3.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/dist-frontend/assets/index-BAE_pjXJ.js +0 -97
|
@@ -130,6 +130,11 @@ export function createMessageService(deps) {
|
|
|
130
130
|
}).catch(() => undefined);
|
|
131
131
|
}
|
|
132
132
|
scheduleDispatchConfirmation(input, delivered, session.id);
|
|
133
|
+
await deps.onRouteDelivered?.({
|
|
134
|
+
repoRoot: input.repoRoot,
|
|
135
|
+
taskSlug: input.taskSlug,
|
|
136
|
+
message: delivered
|
|
137
|
+
});
|
|
133
138
|
return {
|
|
134
139
|
message: delivered,
|
|
135
140
|
delivered: true,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { ROLE_NAMES, isDispatchableRole } from "../../shared/constants.js";
|
|
3
4
|
import { CCR_GPT_SESSION_MODEL, isCcrSessionModel } from "../../shared/types/session.js";
|
|
@@ -80,7 +81,7 @@ export function createSessionService(deps) {
|
|
|
80
81
|
? claudeTranscriptPath(taskRepoRoot, resumeClaudeSessionId, persisted?.claudeConfigDir)
|
|
81
82
|
: undefined;
|
|
82
83
|
const startCommand = {
|
|
83
|
-
...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride),
|
|
84
|
+
...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride, input.appendSystemPrompt),
|
|
84
85
|
cwd: taskRepoRoot
|
|
85
86
|
};
|
|
86
87
|
const runtimeSession = await deps.runtime.createSession({
|
|
@@ -97,7 +98,7 @@ export function createSessionService(deps) {
|
|
|
97
98
|
VCM_TASK_SLUG: taskSlug,
|
|
98
99
|
VCM_ROLE: role,
|
|
99
100
|
VCM_SESSION_ID: claudeSessionId || undefined
|
|
100
|
-
}, modelEnvironment),
|
|
101
|
+
}, modelEnvironment, buildUsageTelemetryEnvironment(deps.apiUrl, role, model)),
|
|
101
102
|
cols: input.cols,
|
|
102
103
|
rows: input.rows
|
|
103
104
|
});
|
|
@@ -224,7 +225,7 @@ export function createSessionService(deps) {
|
|
|
224
225
|
VCM_TASK_SLUG: PROJECT_TRANSLATOR_SCOPE,
|
|
225
226
|
VCM_ROLE: TRANSLATOR_ROLE,
|
|
226
227
|
VCM_SESSION_ID: claudeSessionId || undefined
|
|
227
|
-
}, modelEnvironment),
|
|
228
|
+
}, modelEnvironment, buildUsageTelemetryEnvironment(deps.apiUrl, TRANSLATOR_ROLE, model)),
|
|
228
229
|
cols: input.cols,
|
|
229
230
|
rows: input.rows
|
|
230
231
|
});
|
|
@@ -337,7 +338,7 @@ export function createSessionService(deps) {
|
|
|
337
338
|
VCM_TASK_SLUG: PROJECT_HARNESS_ENGINEER_SCOPE,
|
|
338
339
|
VCM_ROLE: HARNESS_ENGINEER_ROLE,
|
|
339
340
|
VCM_SESSION_ID: claudeSessionId || undefined
|
|
340
|
-
}, modelEnvironment),
|
|
341
|
+
}, modelEnvironment, buildUsageTelemetryEnvironment(deps.apiUrl, HARNESS_ENGINEER_ROLE, model)),
|
|
341
342
|
cols: input.cols,
|
|
342
343
|
rows: input.rows
|
|
343
344
|
});
|
|
@@ -529,7 +530,7 @@ export function createSessionService(deps) {
|
|
|
529
530
|
VCM_TASK_SLUG: normalizeProjectScopedRecordForPersistence(session).taskSlug,
|
|
530
531
|
VCM_ROLE: session.role,
|
|
531
532
|
VCM_SESSION_ID: session.claudeSessionId
|
|
532
|
-
}, modelEnvironment)
|
|
533
|
+
}, modelEnvironment, buildUsageTelemetryEnvironment(deps.apiUrl, session.role, model))
|
|
533
534
|
});
|
|
534
535
|
if ((await waitForSessionInputReady(runtimeSession.id)) === "exited") {
|
|
535
536
|
deps.registry.remove(runtimeSession.id);
|
|
@@ -1536,13 +1537,40 @@ function formatClaudeCdCommand(targetCwd) {
|
|
|
1536
1537
|
function isExitedStatus(status) {
|
|
1537
1538
|
return status === "exited" || status === "crashed" || status === "missing";
|
|
1538
1539
|
}
|
|
1539
|
-
function withClaudeCodeRuntimeEnv(env, modelEnvironment = {}) {
|
|
1540
|
+
function withClaudeCodeRuntimeEnv(env, modelEnvironment = {}, telemetryEnvironment = {}) {
|
|
1540
1541
|
return {
|
|
1541
1542
|
...env,
|
|
1542
1543
|
...modelEnvironment,
|
|
1544
|
+
...telemetryEnvironment,
|
|
1543
1545
|
CLAUDE_CODE_DISABLE_AUTO_MEMORY
|
|
1544
1546
|
};
|
|
1545
1547
|
}
|
|
1548
|
+
function buildUsageTelemetryEnvironment(apiUrl, role, model) {
|
|
1549
|
+
const disabled = {
|
|
1550
|
+
CLAUDE_CODE_ENABLE_TELEMETRY: undefined,
|
|
1551
|
+
OTEL_LOGS_EXPORTER: "none",
|
|
1552
|
+
OTEL_METRICS_EXPORTER: "none",
|
|
1553
|
+
OTEL_TRACES_EXPORTER: "none",
|
|
1554
|
+
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: undefined,
|
|
1555
|
+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: undefined,
|
|
1556
|
+
OTEL_RESOURCE_ATTRIBUTES: undefined,
|
|
1557
|
+
OTEL_LOG_USER_PROMPTS: "0",
|
|
1558
|
+
OTEL_LOG_ASSISTANT_RESPONSES: "0",
|
|
1559
|
+
OTEL_LOG_TOOL_DETAILS: "0",
|
|
1560
|
+
OTEL_LOG_RAW_API_BODIES: "0"
|
|
1561
|
+
};
|
|
1562
|
+
if (!apiUrl || isCcrSessionModel(model)) {
|
|
1563
|
+
return disabled;
|
|
1564
|
+
}
|
|
1565
|
+
return {
|
|
1566
|
+
...disabled,
|
|
1567
|
+
CLAUDE_CODE_ENABLE_TELEMETRY: "1",
|
|
1568
|
+
OTEL_LOGS_EXPORTER: "otlp",
|
|
1569
|
+
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: "http/json",
|
|
1570
|
+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: `${apiUrl.replace(/\/+$/, "")}/api/telemetry/v1/logs`,
|
|
1571
|
+
OTEL_RESOURCE_ATTRIBUTES: `vcm.role=${role},vcm.launch_id=${randomUUID()}`
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1546
1574
|
function delay(ms) {
|
|
1547
1575
|
if (ms <= 0) {
|
|
1548
1576
|
return Promise.resolve();
|
|
@@ -5,6 +5,7 @@ export function createTaskCloseService(deps) {
|
|
|
5
5
|
async closeTask(repoRoot, taskSlug) {
|
|
6
6
|
const task = await deps.taskService.markTaskCleaned(repoRoot, taskSlug);
|
|
7
7
|
const warnings = [];
|
|
8
|
+
deps.architectRestartService?.clear(repoRoot, taskSlug);
|
|
8
9
|
await stopTaskRoleSessions(repoRoot, taskSlug, warnings);
|
|
9
10
|
await bestEffort("Unable to stop task translation runtime", () => deps.translationService.stopTask(getTaskRuntimeRepoRoot(task), taskSlug, { clearCache: true }), warnings);
|
|
10
11
|
await bestEffort("Unable to clear task round runtime", () => deps.roundService.stopTask(taskSlug), warnings);
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ROLE_NAMES, isRoleName } from "../../shared/constants.js";
|
|
3
|
+
const USAGE_FILE = path.join("telemetry", "usage.json");
|
|
4
|
+
export function createUsageAnalyticsService(deps) {
|
|
5
|
+
const now = deps.now ?? (() => new Date().toISOString());
|
|
6
|
+
const writeQueues = new Map();
|
|
7
|
+
return {
|
|
8
|
+
async ingest(taskRepoRoot, stateRoot, payload) {
|
|
9
|
+
const events = extractApiRequestEvents(payload);
|
|
10
|
+
if (events.length === 0) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const usagePath = getUsagePath(taskRepoRoot, stateRoot);
|
|
14
|
+
await enqueueWrite(writeQueues, usagePath, async () => {
|
|
15
|
+
const state = await loadState(deps.fs, usagePath);
|
|
16
|
+
const seen = new Set(state.seenEvents);
|
|
17
|
+
let changed = false;
|
|
18
|
+
for (const event of events) {
|
|
19
|
+
if (seen.has(event.eventKey)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
seen.add(event.eventKey);
|
|
23
|
+
state.seenEvents.push(event.eventKey);
|
|
24
|
+
addEvent(state, event);
|
|
25
|
+
changed = true;
|
|
26
|
+
}
|
|
27
|
+
if (!changed) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
state.updatedAt = now();
|
|
31
|
+
await deps.fs.writeJsonAtomic(usagePath, state);
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
async getReport(taskRepoRoot, stateRoot, taskSlug) {
|
|
35
|
+
const usagePath = getUsagePath(taskRepoRoot, stateRoot);
|
|
36
|
+
await writeQueues.get(usagePath);
|
|
37
|
+
if (!(await deps.fs.pathExists(usagePath))) {
|
|
38
|
+
return emptyReport(taskSlug);
|
|
39
|
+
}
|
|
40
|
+
const state = normalizeStoredState(await deps.fs.readJson(usagePath));
|
|
41
|
+
return toReport(taskSlug, state);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function extractApiRequestEvents(payload) {
|
|
46
|
+
if (!isObject(payload) || !Array.isArray(payload.resourceLogs)) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const events = [];
|
|
50
|
+
for (const resourceLog of payload.resourceLogs) {
|
|
51
|
+
if (!isObject(resourceLog)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const resourceAttributes = readAttributes(isObject(resourceLog.resource) ? resourceLog.resource.attributes : undefined);
|
|
55
|
+
if (!Array.isArray(resourceLog.scopeLogs)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (const scopeLog of resourceLog.scopeLogs) {
|
|
59
|
+
if (!isObject(scopeLog) || !Array.isArray(scopeLog.logRecords)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
for (const logRecord of scopeLog.logRecords) {
|
|
63
|
+
if (!isObject(logRecord)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const attributes = {
|
|
67
|
+
...resourceAttributes,
|
|
68
|
+
...readAttributes(logRecord.attributes)
|
|
69
|
+
};
|
|
70
|
+
if (readString(attributes["event.name"]) !== "api_request") {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const roleValue = readString(attributes["vcm.role"]);
|
|
74
|
+
const launchId = readString(attributes["vcm.launch_id"]);
|
|
75
|
+
if (!roleValue || !isRoleName(roleValue) || !launchId) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const model = normalizeModel(readString(attributes.model));
|
|
79
|
+
if (isCcrModel(model)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const sequence = readInteger(attributes["event.sequence"]);
|
|
83
|
+
const sessionId = readString(attributes["session.id"]) || launchId;
|
|
84
|
+
const requestId = readString(attributes.request_id);
|
|
85
|
+
const timestamp = readString(logRecord.timeUnixNano) || readString(logRecord.observedTimeUnixNano);
|
|
86
|
+
const fallbackIdentity = [sessionId, requestId, timestamp, model].join(":");
|
|
87
|
+
const eventKey = `${launchId}:${sequence ?? fallbackIdentity}`;
|
|
88
|
+
const costUsdMicros = readNonNegativeInteger(attributes.cost_usd_micros)
|
|
89
|
+
?? Math.round((readNonNegativeNumber(attributes.cost_usd) ?? 0) * 1_000_000);
|
|
90
|
+
events.push({
|
|
91
|
+
eventKey,
|
|
92
|
+
role: roleValue,
|
|
93
|
+
model,
|
|
94
|
+
sessionId,
|
|
95
|
+
inputTokens: readNonNegativeInteger(attributes.input_tokens) ?? 0,
|
|
96
|
+
outputTokens: readNonNegativeInteger(attributes.output_tokens) ?? 0,
|
|
97
|
+
cacheReadTokens: readNonNegativeInteger(attributes.cache_read_tokens) ?? 0,
|
|
98
|
+
cacheCreationTokens: readNonNegativeInteger(attributes.cache_creation_tokens) ?? 0,
|
|
99
|
+
costUsdMicros
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return events;
|
|
105
|
+
}
|
|
106
|
+
function addEvent(state, event) {
|
|
107
|
+
addTotals(state.totals, event);
|
|
108
|
+
state.byRole[event.role] ??= emptyStoredTotals();
|
|
109
|
+
addTotals(state.byRole[event.role], event);
|
|
110
|
+
state.byModel[event.model] ??= emptyStoredTotals();
|
|
111
|
+
addTotals(state.byModel[event.model], event);
|
|
112
|
+
addUnique(state.sessionsByRole, event.role, event.sessionId);
|
|
113
|
+
addUnique(state.sessionsByModel, event.model, event.sessionId);
|
|
114
|
+
}
|
|
115
|
+
function addTotals(target, event) {
|
|
116
|
+
target.inputTokens += event.inputTokens;
|
|
117
|
+
target.outputTokens += event.outputTokens;
|
|
118
|
+
target.cacheReadTokens += event.cacheReadTokens;
|
|
119
|
+
target.cacheCreationTokens += event.cacheCreationTokens;
|
|
120
|
+
target.costUsdMicros += event.costUsdMicros;
|
|
121
|
+
target.requestCount += 1;
|
|
122
|
+
}
|
|
123
|
+
function addUnique(target, key, value) {
|
|
124
|
+
const values = target[key] ?? [];
|
|
125
|
+
if (!values.includes(value)) {
|
|
126
|
+
values.push(value);
|
|
127
|
+
}
|
|
128
|
+
target[key] = values;
|
|
129
|
+
}
|
|
130
|
+
async function loadState(fs, usagePath) {
|
|
131
|
+
if (!(await fs.pathExists(usagePath))) {
|
|
132
|
+
return emptyStoredState();
|
|
133
|
+
}
|
|
134
|
+
return normalizeStoredState(await fs.readJson(usagePath));
|
|
135
|
+
}
|
|
136
|
+
function normalizeStoredState(input) {
|
|
137
|
+
return {
|
|
138
|
+
version: 1,
|
|
139
|
+
updatedAt: typeof input.updatedAt === "string" ? input.updatedAt : new Date(0).toISOString(),
|
|
140
|
+
seenEvents: Array.isArray(input.seenEvents) ? input.seenEvents.filter((value) => typeof value === "string") : [],
|
|
141
|
+
sessionsByRole: normalizeRoleStringLists(input.sessionsByRole),
|
|
142
|
+
sessionsByModel: normalizeStringLists(input.sessionsByModel),
|
|
143
|
+
totals: normalizeStoredTotals(input.totals),
|
|
144
|
+
byRole: normalizeRoleTotals(input.byRole),
|
|
145
|
+
byModel: normalizeModelTotals(input.byModel)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function normalizeRoleStringLists(input) {
|
|
149
|
+
if (!isObject(input)) {
|
|
150
|
+
return {};
|
|
151
|
+
}
|
|
152
|
+
const result = {};
|
|
153
|
+
for (const role of ROLE_NAMES) {
|
|
154
|
+
const values = input[role];
|
|
155
|
+
if (Array.isArray(values)) {
|
|
156
|
+
result[role] = values.filter((value) => typeof value === "string");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
function normalizeStringLists(input) {
|
|
162
|
+
const result = {};
|
|
163
|
+
if (!isObject(input)) {
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
for (const [key, values] of Object.entries(input)) {
|
|
167
|
+
if (isSafeGroupKey(key) && Array.isArray(values)) {
|
|
168
|
+
result[key] = values.filter((value) => typeof value === "string");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
function normalizeRoleTotals(input) {
|
|
174
|
+
if (!isObject(input)) {
|
|
175
|
+
return {};
|
|
176
|
+
}
|
|
177
|
+
const result = {};
|
|
178
|
+
for (const role of ROLE_NAMES) {
|
|
179
|
+
if (isObject(input[role])) {
|
|
180
|
+
result[role] = normalizeStoredTotals(input[role]);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
function normalizeModelTotals(input) {
|
|
186
|
+
const result = {};
|
|
187
|
+
if (!isObject(input)) {
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
for (const [key, totals] of Object.entries(input)) {
|
|
191
|
+
if (isSafeGroupKey(key) && isObject(totals)) {
|
|
192
|
+
result[key] = normalizeStoredTotals(totals);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
function normalizeStoredTotals(input) {
|
|
198
|
+
const value = isObject(input) ? input : {};
|
|
199
|
+
return {
|
|
200
|
+
inputTokens: readNonNegativeInteger(value.inputTokens) ?? 0,
|
|
201
|
+
outputTokens: readNonNegativeInteger(value.outputTokens) ?? 0,
|
|
202
|
+
cacheReadTokens: readNonNegativeInteger(value.cacheReadTokens) ?? 0,
|
|
203
|
+
cacheCreationTokens: readNonNegativeInteger(value.cacheCreationTokens) ?? 0,
|
|
204
|
+
costUsdMicros: readNonNegativeInteger(value.costUsdMicros) ?? 0,
|
|
205
|
+
requestCount: readNonNegativeInteger(value.requestCount) ?? 0
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function toReport(taskSlug, state) {
|
|
209
|
+
const allSessions = new Set(Object.values(state.sessionsByRole).flatMap((values) => values ?? []));
|
|
210
|
+
const byRole = ROLE_NAMES.map((role) => ({
|
|
211
|
+
role,
|
|
212
|
+
...toPublicTotals(state.byRole[role] ?? emptyStoredTotals(), state.sessionsByRole[role]?.length ?? 0)
|
|
213
|
+
}));
|
|
214
|
+
const byModel = Object.keys(state.byModel)
|
|
215
|
+
.sort((left, right) => left.localeCompare(right))
|
|
216
|
+
.map((model) => ({
|
|
217
|
+
model,
|
|
218
|
+
...toPublicTotals(state.byModel[model], state.sessionsByModel[model]?.length ?? 0)
|
|
219
|
+
}));
|
|
220
|
+
return {
|
|
221
|
+
version: 1,
|
|
222
|
+
taskSlug,
|
|
223
|
+
updatedAt: state.updatedAt,
|
|
224
|
+
totals: toPublicTotals(state.totals, allSessions.size),
|
|
225
|
+
byRole,
|
|
226
|
+
byModel
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function emptyReport(taskSlug) {
|
|
230
|
+
return {
|
|
231
|
+
version: 1,
|
|
232
|
+
taskSlug,
|
|
233
|
+
updatedAt: null,
|
|
234
|
+
totals: toPublicTotals(emptyStoredTotals(), 0),
|
|
235
|
+
byRole: ROLE_NAMES.map((role) => ({ role, ...toPublicTotals(emptyStoredTotals(), 0) })),
|
|
236
|
+
byModel: []
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function toPublicTotals(totals, sessionCount) {
|
|
240
|
+
return {
|
|
241
|
+
inputTokens: totals.inputTokens,
|
|
242
|
+
outputTokens: totals.outputTokens,
|
|
243
|
+
cacheReadTokens: totals.cacheReadTokens,
|
|
244
|
+
cacheCreationTokens: totals.cacheCreationTokens,
|
|
245
|
+
costUsd: totals.costUsdMicros / 1_000_000,
|
|
246
|
+
requestCount: totals.requestCount,
|
|
247
|
+
sessionCount
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function emptyStoredState() {
|
|
251
|
+
return {
|
|
252
|
+
version: 1,
|
|
253
|
+
updatedAt: new Date(0).toISOString(),
|
|
254
|
+
seenEvents: [],
|
|
255
|
+
sessionsByRole: {},
|
|
256
|
+
sessionsByModel: {},
|
|
257
|
+
totals: emptyStoredTotals(),
|
|
258
|
+
byRole: {},
|
|
259
|
+
byModel: {}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function emptyStoredTotals() {
|
|
263
|
+
return {
|
|
264
|
+
inputTokens: 0,
|
|
265
|
+
outputTokens: 0,
|
|
266
|
+
cacheReadTokens: 0,
|
|
267
|
+
cacheCreationTokens: 0,
|
|
268
|
+
costUsdMicros: 0,
|
|
269
|
+
requestCount: 0
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function getUsagePath(taskRepoRoot, stateRoot) {
|
|
273
|
+
return path.join(taskRepoRoot, stateRoot, USAGE_FILE);
|
|
274
|
+
}
|
|
275
|
+
async function enqueueWrite(queues, key, operation) {
|
|
276
|
+
const previous = queues.get(key) ?? Promise.resolve();
|
|
277
|
+
const current = previous.catch(() => undefined).then(operation);
|
|
278
|
+
queues.set(key, current);
|
|
279
|
+
try {
|
|
280
|
+
await current;
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
if (queues.get(key) === current) {
|
|
284
|
+
queues.delete(key);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function readAttributes(input) {
|
|
289
|
+
const result = {};
|
|
290
|
+
if (!Array.isArray(input)) {
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
for (const entry of input) {
|
|
294
|
+
if (!isObject(entry) || typeof entry.key !== "string") {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
result[entry.key] = readAnyValue(entry.value);
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
function readAnyValue(input) {
|
|
302
|
+
if (!isObject(input)) {
|
|
303
|
+
return input;
|
|
304
|
+
}
|
|
305
|
+
for (const key of ["stringValue", "intValue", "doubleValue", "boolValue"]) {
|
|
306
|
+
if (key in input) {
|
|
307
|
+
return input[key];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
function readString(input) {
|
|
313
|
+
if (typeof input === "string") {
|
|
314
|
+
return input.trim() || undefined;
|
|
315
|
+
}
|
|
316
|
+
if (typeof input === "number" || typeof input === "bigint") {
|
|
317
|
+
return String(input);
|
|
318
|
+
}
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
function readInteger(input) {
|
|
322
|
+
const value = typeof input === "number" ? input : typeof input === "string" ? Number(input) : Number.NaN;
|
|
323
|
+
return Number.isSafeInteger(value) ? value : undefined;
|
|
324
|
+
}
|
|
325
|
+
function readNonNegativeInteger(input) {
|
|
326
|
+
const value = readInteger(input);
|
|
327
|
+
return value !== undefined && value >= 0 ? value : undefined;
|
|
328
|
+
}
|
|
329
|
+
function readNonNegativeNumber(input) {
|
|
330
|
+
const value = typeof input === "number" ? input : typeof input === "string" ? Number(input) : Number.NaN;
|
|
331
|
+
return Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
332
|
+
}
|
|
333
|
+
function normalizeModel(input) {
|
|
334
|
+
const model = input?.slice(0, 200).trim();
|
|
335
|
+
return model && isSafeGroupKey(model) ? model : "unknown";
|
|
336
|
+
}
|
|
337
|
+
function isCcrModel(model) {
|
|
338
|
+
const normalized = model.toLowerCase();
|
|
339
|
+
return normalized.startsWith("gpt-") || normalized.startsWith("ccr:") || normalized.includes("codex api/");
|
|
340
|
+
}
|
|
341
|
+
function isSafeGroupKey(value) {
|
|
342
|
+
return value !== "__proto__" && value !== "prototype" && value !== "constructor";
|
|
343
|
+
}
|
|
344
|
+
function isObject(input) {
|
|
345
|
+
return typeof input === "object" && input !== null && !Array.isArray(input);
|
|
346
|
+
}
|
|
@@ -8,7 +8,7 @@ ${renderRoleMemoryRules("architect")}
|
|
|
8
8
|
### Role Scope
|
|
9
9
|
|
|
10
10
|
- Own technical analysis, architecture planning, module boundaries, file-level responsibilities, cross-file callable surfaces, public contracts, verifiable behavior, implementation boundaries within the accepted scope, behavior/contract proof points, risks, and architect-owned replan decisions.
|
|
11
|
-
- Own \`.ai/vcm/handoffs/architecture-brief.md\` during Architect Interview and preserve
|
|
11
|
+
- Own \`.ai/vcm/handoffs/architecture-brief.md\` and \`.ai/vcm/handoffs/architecture-evidence.md\` during Architect Interview and preserve them as planning inputs.
|
|
12
12
|
- Define every changed or created file's purpose, logic boundary, collaboration points, and non-private callable surface.
|
|
13
13
|
- Own \`.ai/vcm/handoffs/known-issues.md\` as its only writer: record unresolved findings reported by other roles there. Own \`docs/known-issues.md\` promotion and durable issue updates.
|
|
14
14
|
- Own architecture docs sync across \`docs/ARCHITECTURE.md\` and affected \`<module>/ARCHITECTURE.md\` files.
|
|
@@ -21,15 +21,15 @@ ${renderRoleMemoryRules("architect")}
|
|
|
21
21
|
|
|
22
22
|
### Architecture Interview
|
|
23
23
|
|
|
24
|
-
- Before the first Architecture Planning step of Code-Change Flow, use \`vcm-architecture-interview\` and complete \`.ai/vcm/handoffs/architecture-brief.md\`
|
|
24
|
+
- Before the first Architecture Planning step of Code-Change Flow, use \`vcm-architecture-interview\` and complete \`.ai/vcm/handoffs/architecture-brief.md\` and \`.ai/vcm/handoffs/architecture-evidence.md\`.
|
|
25
25
|
- Read project evidence before asking questions. Ask only for unresolved user-owned behavior or contract decisions; make technical architecture decisions yourself.
|
|
26
26
|
- Continue the formal interview directly with the user until the brief is explicitly confirmed. Do not report each answer to project-manager.
|
|
27
27
|
- Do not write or revise \`architecture-plan.md\`, create scaffold, or implement code during Architect Interview.
|
|
28
|
-
- After confirmation, report
|
|
28
|
+
- After confirmation and evidence completion, report both artifacts to project-manager and stop. Project-manager must route Architect planning separately.
|
|
29
29
|
|
|
30
30
|
### Planning Inputs
|
|
31
31
|
|
|
32
|
-
- Read the role message, confirmed \`.ai/vcm/handoffs/architecture-brief.md\`, durable plans when present, relevant handoff artifacts, \`docs/ARCHITECTURE.md\`, affected \`<module>/ARCHITECTURE.md\` files when present, and affected project docs before planning.
|
|
32
|
+
- Read the role message, confirmed \`.ai/vcm/handoffs/architecture-brief.md\`, complete \`.ai/vcm/handoffs/architecture-evidence.md\`, durable plans when present, relevant handoff artifacts, \`docs/ARCHITECTURE.md\`, affected \`<module>/ARCHITECTURE.md\` files when present, and affected project docs before planning.
|
|
33
33
|
- Read \`.ai/generated/module-index.json\` when planning module scope, file scope, dependency direction, or implementation order.
|
|
34
34
|
- Read \`.ai/generated/public-surface.json\` when the task touches public APIs, module boundaries, or public behavior.
|
|
35
35
|
- If durable docs conflict with the requested plan or code reality, report the conflict to project-manager and identify whether user approval is required.
|
|
@@ -45,13 +45,13 @@ ${renderRoleMemoryRules("architect")}
|
|
|
45
45
|
- Continue across module boundaries whenever the changed behavior path, state ownership, lifecycle, public contract, or failure path crosses them.
|
|
46
46
|
- Stop at standard-library, third-party, external-service, vendor, or generated-code boundaries and record the boundary contract, inputs, outputs, errors, and side effects relevant to the plan.
|
|
47
47
|
- For new behavior, read the existing integration points and caller or consumer paths it will join.
|
|
48
|
-
- Treat architecture docs, generated context, and comments as navigation evidence, not authority. Record
|
|
48
|
+
- Treat architecture docs, generated context, and comments as navigation evidence, not authority. Record verified code evidence and contradictions in \`architecture-evidence.md\`.
|
|
49
49
|
- Read tests only when needed to understand current behavior, not to assess test adequacy.
|
|
50
|
-
- Do not write Architecture Decision or begin Code Scaffolding while a project-owned symbol remains unresolved on a behavior path the plan will change.
|
|
50
|
+
- Do not mark \`Architecture Evidence Status: complete\`, write Architecture Decision, or begin Code Scaffolding while a project-owned symbol remains unresolved on a behavior path the plan will change.
|
|
51
51
|
|
|
52
52
|
### Architecture Plan
|
|
53
53
|
|
|
54
|
-
- Do not begin Architecture Decision, Code Scaffolding, or a complete architecture plan unless \`architecture-brief.md\` has \`Architecture Brief Status: confirmed\`.
|
|
54
|
+
- Do not begin Architecture Decision, Code Scaffolding, or a complete architecture plan unless \`architecture-brief.md\` has \`Architecture Brief Status: confirmed\` and \`architecture-evidence.md\` has \`Architecture Evidence Status: complete\`.
|
|
55
55
|
- Treat the confirmed brief as the user-owned behavior and contract input. Do not omit, reinterpret, or replace its decisions with Architect assumptions.
|
|
56
56
|
- Before coder work starts, write \`.ai/vcm/handoffs/architecture-plan.md\`, choose the minimum necessary code scaffolding, and include a Scaffold Manifest for task-specific context and coder guidance.
|
|
57
57
|
- The architecture-plan handoff is not complete until every \`create\`, \`change\`, and \`delete\` ledger item, every new or changed non-private callable surface, contract comments, and all \`VCM:CODE\` placeholders have been scaffolded and committed, and the scaffolded workspace passes the project's compile/typecheck L0 check.
|
|
@@ -59,8 +59,8 @@ ${renderRoleMemoryRules("architect")}
|
|
|
59
59
|
|
|
60
60
|
#### Planning Work Plan
|
|
61
61
|
|
|
62
|
-
- When starting architecture planning
|
|
63
|
-
- After \`
|
|
62
|
+
- When starting architecture planning, first write an \`Architecture Evidence Verification\` row to \`.ai/vcm/handoffs/planning-progress.md\`, with the evidence artifact, verified worktree revision, done criterion, and status. Complete it by checking that the evidence covers the accepted feature boundary and still matches the current worktree.
|
|
63
|
+
- After \`Architecture Evidence Verification\` is complete, add the remaining planning steps: one head step for cross-module work (architecture decision, boundaries, ownership, invariants, build-configuration proofs), one middle step per affected module from the module index in dependency order — split a module into per-file steps when it exceeds one round — and one tail step for cross-module wiring, whole-plan ledger reconciliation, and final build evidence. A small task degrades to head, one middle step, and tail.
|
|
64
64
|
- Bind every step to repository facts and machine checks: scope is module or file paths from the module index; deliverable is plan sections or ledger ID ranges; done criterion is a tool output or recorded check result — never a self-assessment.
|
|
65
65
|
- Update \`planning-progress.md\` at the end of every planning round: mark completed steps with their evidence and leave remaining steps unchanged. Do not shrink, merge, or drop a remaining step without recording the change and its reason.
|
|
66
66
|
- If the round ends before all steps are done, report \`Planning Result: incomplete\` with the progress record; project-manager routes continuation. Never compress remaining enumeration or scaffolding into summary rows to reach \`Planning Result: complete\` within the current round — an honest \`incomplete\` with recorded progress is the required outcome.
|
|
@@ -72,7 +72,7 @@ ${renderRoleMemoryRules("architect")}
|
|
|
72
72
|
- Use \`Planning Result: complete\` only when: the plan document is complete; the Scaffold Manifest ledger reconciles one to one against the committed markers; and \`Scaffold Build Evidence\` records a green compile/typecheck run at the current scaffold commit hash. Include the same Planning Result in the route message to project-manager; do not select the next route.
|
|
73
73
|
- \`architecture-plan.md\` is the current executable plan, not a changelog. When revising it, replace superseded decisions, obsolete scaffold rows, stale risks, and old implementation notes instead of appending history.
|
|
74
74
|
- \`Accepted Scope\`: state the PM-routed task scope and the confirmed brief's required user-visible outcome and decisions, plus any explicit non-scope that prevents accidental expansion.
|
|
75
|
-
- \`Current Code Reality\`:
|
|
75
|
+
- \`Current Code Reality\`: cite \`architecture-evidence.md\` and summarize only the verified facts that constrain the architecture decision. Do not duplicate the full evidence inventory. For any module whose build configuration the plan changes, the evidence artifact must quote its complete direct dependency list from the package manifest, never a summary or selection.
|
|
76
76
|
- Any enumeration the plan presents as complete over the codebase — call-site inventories, module or file lists, symbol sets — must either record the deterministic, repository-local command that generates it (run at the scaffold commit, the set transcribed from its output) or be explicitly marked as judgment-derived with the evidence basis for its completeness. A complete-claimed enumeration with neither is not evidence.
|
|
77
77
|
- \`Architecture Decision\`: use the required Changed Behavior Flow, Ownership, Data Flow, Lifecycle, Boundaries, Invariants, Failure Model, and Decision Rationale subsections. Describe why the design fits verified current code.
|
|
78
78
|
- \`Module/File Plan\`: list each affected module, changed or created file, file responsibility, why it is in scope, expected change, dependency direction, user-visible behavior change, durable comment needs, and every non-private callable surface intended for use outside its file.
|
|
@@ -94,6 +94,9 @@ ${renderRoleMemoryRules("architect")}
|
|
|
94
94
|
|
|
95
95
|
#### Code Scaffolding
|
|
96
96
|
|
|
97
|
+
- Use the Agent tool to invoke \`vcm-architect-scaffold-worker\` in the foreground after the plan and Scaffold Manifest are complete. Give it the exact plan path and require it to return before this Architect turn continues.
|
|
98
|
+
- Use one scaffold worker. Do not run it in the background or end the Architect turn while it is active.
|
|
99
|
+
- Review the worker commit, actual diff, callable surfaces, marker placement, ledger reconciliation, and L0 results yourself. Architect owns every final scaffold claim and must correct any worker error before marking planning complete.
|
|
97
100
|
- Create or update only the minimum module/file scaffolding needed to make boundaries, callable surfaces, and placeholders unambiguous. Minimum limits depth (no business implementation), never breadth: every \`create\`, \`change\`, and \`delete\` item must be scaffolded.
|
|
98
101
|
- When a required configuration, package manifest, or build-definition change cannot safely contain a \`VCM:CODE\` marker, complete and commit it directly as Architect-owned scaffold work. Record it in the Module/File Plan and Scaffold Build Evidence. Do not add it to the Scaffold Manifest.
|
|
99
102
|
- When the plan introduces a new cross-module call path or seam — a module invoking surfaces it does not invoke today — scaffold one wired exemplar that materializes the full path shape: the imports, interface implementations, and conditional-compilation gating the intended body needs, with placeholder bodies only. Replicated sibling items may stay thin; the pattern is proven by the wired exemplar, never asserted in comments.
|
|
@@ -108,6 +111,11 @@ ${renderRoleMemoryRules("architect")}
|
|
|
108
111
|
- Architect scaffolding may include modules, files, signatures, type shapes, durable comments, and placeholder bodies, but not real business implementation beyond minimal scaffold code.
|
|
109
112
|
- Coder may add private implementation helpers, but must not add or change cross-file callable surface without architect replan.
|
|
110
113
|
|
|
114
|
+
#### Planning Completion
|
|
115
|
+
|
|
116
|
+
- After the complete plan, scaffold, reconciliation, L0 evidence, and commits are ready, use the \`restart-architect\` skill before writing the completed Architect-to-PM route message.
|
|
117
|
+
- After VCM reports the restart is scheduled, write the route message with both architecture artifacts and the plan, then end the turn. Do not wait for or inspect the replacement session.
|
|
118
|
+
|
|
111
119
|
### Complete Task Planning
|
|
112
120
|
|
|
113
121
|
- Plan the full accepted task scope routed by PM.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function renderArchitectScaffoldWorkerHarnessRules() {
|
|
2
|
+
return `
|
|
3
|
+
## VCM Architect Scaffold Worker Rules
|
|
4
|
+
|
|
5
|
+
You are \`vcm-architect-scaffold-worker\`, a foreground subagent invoked by Architect after the architecture plan and Scaffold Manifest are complete.
|
|
6
|
+
|
|
7
|
+
### Scope
|
|
8
|
+
|
|
9
|
+
- Execute only the scaffold work assigned by Architect from the current \`.ai/vcm/handoffs/architecture-plan.md\`.
|
|
10
|
+
- Create the declared files, callable surfaces, contract comments, placeholder bodies, configuration changes, and one \`VCM:CODE <ID>\` marker for every Scaffold Manifest item.
|
|
11
|
+
- Do not change architecture decisions, accepted scope, ledger items, public contracts, or implementation boundaries.
|
|
12
|
+
- Do not implement business logic beyond the minimum compilable scaffold.
|
|
13
|
+
- Follow \`docs/CODING_STANDARDS.md\` for every code or test edit.
|
|
14
|
+
|
|
15
|
+
### Validation And Commit
|
|
16
|
+
|
|
17
|
+
- Run \`.ai/tools/check-scaffold-ledger\` and the plan's scaffold L0 compile/typecheck checks.
|
|
18
|
+
- Commit only the scaffold changes after the ledger reconciles and required checks pass.
|
|
19
|
+
- Return the commit hash, changed files, ledger result, and exact check results to Architect.
|
|
20
|
+
- If the assigned scaffold cannot be completed, return the concrete failure evidence without changing the plan.
|
|
21
|
+
|
|
22
|
+
Architect reviews the worker's commit and remains responsible for the final scaffold, plan, and evidence.`;
|
|
23
|
+
}
|
|
@@ -84,7 +84,7 @@ If a reusable harness problem is suspected, it is enough to record a concise fee
|
|
|
84
84
|
## VCM Worktree Policy
|
|
85
85
|
|
|
86
86
|
- Use one branch, one worktree, one handoff directory, and one PR or final patch per VCM-managed task.
|
|
87
|
-
- VCM workflow role handoffs run sequentially in the same task worktree. Coder-managed workers may run concurrently within the Coder turn.
|
|
87
|
+
- VCM workflow role handoffs run sequentially in the same task worktree. Coder-managed workers may run concurrently within the Coder turn; Architect may run one foreground scaffold worker inside its planning turn.
|
|
88
88
|
- If \`git status\` shows uncommitted changes, commit them before handing off to another role.
|
|
89
89
|
`;
|
|
90
90
|
}
|
|
@@ -63,7 +63,8 @@ automates it). Run this on every review round, including revision rounds:
|
|
|
63
63
|
|
|
64
64
|
For \`architecture-plan\`, reconstruct the proposed architecture and look for
|
|
65
65
|
design flaws before checking formatting. Read the confirmed
|
|
66
|
-
\`.ai/vcm/handoffs/architecture-brief.md\`, \`.ai/vcm/handoffs/architecture-
|
|
66
|
+
\`.ai/vcm/handoffs/architecture-brief.md\`, \`.ai/vcm/handoffs/architecture-evidence.md\`,
|
|
67
|
+
\`.ai/vcm/handoffs/architecture-plan.md\`,
|
|
67
68
|
\`.claude/agents/architect.md\`, root \`CLAUDE.md\`, \`docs/ARCHITECTURE.md\`,
|
|
68
69
|
affected module \`ARCHITECTURE.md\` files, \`.ai/generated/module-index.json\`,
|
|
69
70
|
\`.ai/generated/public-surface.json\` when public surface may change, and the
|
|
@@ -446,7 +447,11 @@ REPORTS = {
|
|
|
446
447
|
"code-diff": ".ai/vcm/gate-reviews/code-diff-review.md",
|
|
447
448
|
}
|
|
448
449
|
SOURCE_ARTIFACTS = {
|
|
449
|
-
"architecture-plan": [
|
|
450
|
+
"architecture-plan": [
|
|
451
|
+
".ai/vcm/handoffs/architecture-brief.md",
|
|
452
|
+
".ai/vcm/handoffs/architecture-evidence.md",
|
|
453
|
+
".ai/vcm/handoffs/architecture-plan.md",
|
|
454
|
+
],
|
|
450
455
|
"validation-adequacy": [
|
|
451
456
|
".ai/vcm/handoffs/architecture-plan.md",
|
|
452
457
|
".ai/vcm/handoffs/test-report.md",
|
|
@@ -88,7 +88,7 @@ PM may leave this path only through the allowed branches below.
|
|
|
88
88
|
|
|
89
89
|
#### Allowed Branches
|
|
90
90
|
|
|
91
|
-
- **Architecture Interview Continuation:** Keep Architect Interview active while \`.ai/vcm/handoffs/architecture-brief.md\` is \`interviewing\`. After the user
|
|
91
|
+
- **Architecture Interview Continuation:** Keep Architect Interview active while \`.ai/vcm/handoffs/architecture-brief.md\` is \`interviewing\` or \`.ai/vcm/handoffs/architecture-evidence.md\` is incomplete. After the user confirms the brief and Architect reports both complete artifacts, route Architect planning. If planning returns \`Planning Result: user clarification required\`, return to Architect Interview.
|
|
92
92
|
- **Architecture Plan Revision:** If Architect planning is incomplete, route Architect again to continue the recorded planning work plan; multi-round planning against \`.ai/vcm/handoffs/planning-progress.md\` is the normal path for large plans, and PM must not press for completion within one round or accept summary-row compression in place of remaining steps. If the architecture-plan Gate returns \`request_changes\`, route the complete report to Architect, then rerun the full architecture-plan Gate after the plan and scaffold are revised.
|
|
93
93
|
- **Coder Continuation:** If Coder returns \`Decision: incomplete\`, lacks the required completion artifact, or has not completed implementation and L0/L1 validation, route Coder again — this is the only route for an in-progress sweep. Problems recorded inside an incomplete report are sweep state, not routable failures; PM routes problems onward only from a post-sweep \`failed\` report carrying the consolidated per-item disposition.
|
|
94
94
|
- **Coder Failure Debug:** If Coder returns \`Decision: failed\` with compile, typecheck, or L0/L1 failure evidence after implementation, suspend the main flow and enter Architect Debug Branch.
|