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
@@ -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 its confirmed user decisions during planning.
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\` with the user.
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 the confirmed brief to project-manager and stop. Project-manager must route Architect planning separately.
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 contradictions with implementation in Current Code Reality.
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 for a confirmed brief, first write a \`Current Code Reality / Scope Discovery\` row to \`.ai/vcm/handoffs/planning-progress.md\`, with its scope, deliverable, done criterion, and status. Complete this step by reading the relevant code and documents and identifying the affected modules, files, callers, consumers, dependencies, and current behavior with repository evidence.
63
- - After \`Current Code Reality / Scope Discovery\` 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.
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\`: use the required Planning Boundary, Code Reading Evidence, Existing Behavior Trace, and Code / Docs Conflicts subsections. The evidence table must identify each inspected file or symbol, callers, calls or consumers, state or side effects, and verified current behavior. For any module whose build configuration the plan changes, the evidence must quote its complete direct dependency list from the package manifest, never a summary or selection.
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-plan.md\`,
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": [".ai/vcm/handoffs/architecture-plan.md"],
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 explicitly confirms the brief and Architect reports it to PM, route Architect planning. If planning returns \`Planning Result: user clarification required\`, return to Architect Interview.
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.