vibe-coding-master 0.0.5 → 0.0.7

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 (42) hide show
  1. package/README.md +168 -63
  2. package/dist/backend/adapters/translation-provider.js +145 -0
  3. package/dist/backend/api/artifact-routes.js +3 -0
  4. package/dist/backend/api/harness-routes.js +22 -0
  5. package/dist/backend/api/project-routes.js +3 -8
  6. package/dist/backend/api/translation-routes.js +70 -0
  7. package/dist/backend/runtime/node-pty-runtime.js +20 -18
  8. package/dist/backend/server.js +31 -1
  9. package/dist/backend/services/app-settings-service.js +128 -0
  10. package/dist/backend/services/artifact-service.js +7 -4
  11. package/dist/backend/services/claude-transcript-service.js +509 -0
  12. package/dist/backend/services/harness-service.js +178 -0
  13. package/dist/backend/services/project-service.js +4 -0
  14. package/dist/backend/services/session-service.js +7 -5
  15. package/dist/backend/services/status-service.js +76 -0
  16. package/dist/backend/services/translation-prompts.js +173 -0
  17. package/dist/backend/services/translation-queue.js +39 -0
  18. package/dist/backend/services/translation-service.js +546 -0
  19. package/dist/backend/templates/handoff.js +32 -0
  20. package/dist/backend/templates/harness/architect-agent.js +12 -0
  21. package/dist/backend/templates/harness/claude-root.js +14 -0
  22. package/dist/backend/templates/harness/coder-agent.js +11 -0
  23. package/dist/backend/templates/harness/project-manager-agent.js +14 -0
  24. package/dist/backend/templates/harness/reviewer-agent.js +13 -0
  25. package/dist/backend/ws/translation-ws.js +35 -0
  26. package/dist/shared/types/harness.js +1 -0
  27. package/dist/shared/types/translation.js +5 -0
  28. package/dist/shared/validation/artifact-check.js +15 -1
  29. package/dist/shared/validation/language-detect.js +46 -0
  30. package/dist-frontend/assets/index-BNASqKEK.css +32 -0
  31. package/dist-frontend/assets/index-Bp49_End.js +58 -0
  32. package/dist-frontend/index.html +2 -2
  33. package/docs/cc-best-practices.md +93 -36
  34. package/docs/product-design.md +313 -1408
  35. package/docs/v1-architecture-design.md +500 -1153
  36. package/docs/v1-implementation-plan.md +783 -1604
  37. package/package.json +3 -1
  38. package/scripts/verify-package.mjs +121 -0
  39. package/dist/backend/templates/role-messaging-context.js +0 -44
  40. package/dist-frontend/assets/index-Bah6k-Ix.css +0 -32
  41. package/dist-frontend/assets/index-EMaQuIB6.js +0 -58
  42. package/docs/v1-message-bus-orchestration-design.md +0 -534
@@ -0,0 +1,546 @@
1
+ import { TRANSLATION_PROMPT_KEYS } from "../../shared/types/translation.js";
2
+ import { TranslationProviderError } from "../adapters/translation-provider.js";
3
+ import { VcmError } from "../errors.js";
4
+ import { buildTranslationPrompt, getTranslationPromptPreviews, parseTranslationWarning } from "./translation-prompts.js";
5
+ import { createTranslationQueueRegistry } from "./translation-queue.js";
6
+ const DEFAULT_SETTINGS = {
7
+ version: 1,
8
+ enabled: false,
9
+ providerType: "openai-compatible",
10
+ baseUrl: "https://api.openai.com/v1",
11
+ model: "gpt-4o-mini",
12
+ sourceLanguage: "auto",
13
+ targetLanguage: "zh-CN",
14
+ workingLanguage: "en",
15
+ inputMode: "review-before-send",
16
+ translateOutput: true,
17
+ translateUserInput: true,
18
+ contextEnabled: true,
19
+ preserveTechnicalTokens: true,
20
+ skipCjkText: true,
21
+ redactSecrets: true,
22
+ requestTimeoutMs: 15000,
23
+ temperature: 0.1
24
+ };
25
+ const TRANSCRIPT_REPLAY_GRACE_MS = 5000;
26
+ export function createTranslationService(deps) {
27
+ const now = deps.now ?? (() => new Date().toISOString());
28
+ const id = deps.id ?? (() => `tr_${Date.now()}_${Math.random().toString(16).slice(2)}`);
29
+ const queues = createTranslationQueueRegistry();
30
+ const sessionStates = new Map();
31
+ let cachedConfig = null;
32
+ async function loadConfig() {
33
+ if (cachedConfig) {
34
+ return cachedConfig;
35
+ }
36
+ const storedConfig = await deps.appSettings.getTranslationConfig();
37
+ if (!storedConfig) {
38
+ cachedConfig = { settings: DEFAULT_SETTINGS, secrets: {} };
39
+ return cachedConfig;
40
+ }
41
+ const rawSettings = storedConfig.settings ?? {};
42
+ const apiKey = storedConfig.secrets?.apiKey ?? rawSettings.apiKey;
43
+ cachedConfig = {
44
+ settings: normalizeSettings(rawSettings),
45
+ secrets: {
46
+ ...(storedConfig.secrets ?? {}),
47
+ ...(apiKey !== undefined ? { apiKey } : {})
48
+ }
49
+ };
50
+ return cachedConfig;
51
+ }
52
+ async function saveConfig(config) {
53
+ cachedConfig = config;
54
+ await deps.appSettings.updateTranslationConfig(config);
55
+ }
56
+ function getState(sessionId) {
57
+ let state = sessionStates.get(sessionId);
58
+ if (!state) {
59
+ state = {
60
+ listeners: new Set(),
61
+ seenTranscriptIds: new Set(),
62
+ entries: []
63
+ };
64
+ sessionStates.set(sessionId, state);
65
+ }
66
+ return state;
67
+ }
68
+ function emit(sessionId, message) {
69
+ const state = getState(sessionId);
70
+ for (const listener of state.listeners) {
71
+ listener(message);
72
+ }
73
+ }
74
+ async function handleTranscriptEvent(sessionId, event) {
75
+ const state = getState(sessionId);
76
+ if (state.seenTranscriptIds.has(event.id)) {
77
+ return;
78
+ }
79
+ state.seenTranscriptIds.add(event.id);
80
+ if (event.kind === "text") {
81
+ state.lastAssistantText = event.text;
82
+ }
83
+ const { settings } = await loadConfig();
84
+ if (!settings.enabled || !settings.translateOutput) {
85
+ return;
86
+ }
87
+ if (event.kind === "text") {
88
+ await processClaudeOutputText(sessionId, event.text, event.id);
89
+ return;
90
+ }
91
+ if (event.kind === "question" || event.kind === "todo" || event.kind === "agent") {
92
+ await processClaudeOutputText(sessionId, formatStructuredTranscriptEvent(event), event.id);
93
+ return;
94
+ }
95
+ if (event.kind === "tool_use" || event.kind === "tool_result") {
96
+ await pushPreservedTranscriptEntry(sessionId, event.id, formatRawTranscriptEvent(event));
97
+ }
98
+ }
99
+ async function processClaudeOutputText(sessionId, rawText, entryId) {
100
+ const session = deps.runtime.getSession(sessionId);
101
+ const roleSession = deps.sessionRegistry.get(sessionId);
102
+ if (!session && !roleSession) {
103
+ return;
104
+ }
105
+ const { settings, secrets } = await loadConfig();
106
+ if (!rawText.trim()) {
107
+ return;
108
+ }
109
+ const text = rawText;
110
+ const baseEntry = {
111
+ ...createEntry({
112
+ taskSlug: roleSession?.taskSlug ?? session.taskSlug,
113
+ role: roleSession?.role ?? session.role,
114
+ direction: "cc-output-to-user",
115
+ sourceKind: "prose",
116
+ sourceText: text,
117
+ settings,
118
+ status: "translating",
119
+ contextUsed: false,
120
+ id: entryId
121
+ }),
122
+ translationStartedAt: now()
123
+ };
124
+ pushEntry(sessionId, baseEntry);
125
+ const queue = queues.getQueue(sessionId);
126
+ await queue.enqueue(async () => {
127
+ emit(sessionId, { type: "translation-status", status: "translating" });
128
+ try {
129
+ const prompt = buildTranslationPrompt({
130
+ direction: "cc-output-to-user",
131
+ text,
132
+ sourceKind: "prose",
133
+ settings
134
+ });
135
+ const result = await deps.provider.translate({
136
+ settings,
137
+ secrets,
138
+ systemPrompt: prompt.systemPrompt,
139
+ userPrompt: prompt.userPrompt
140
+ });
141
+ const completed = {
142
+ ...baseEntry,
143
+ status: "translated",
144
+ translatedText: result.text,
145
+ completedAt: now(),
146
+ tokenUsage: result.tokenUsage
147
+ };
148
+ replaceEntry(sessionId, completed);
149
+ getState(sessionId).lastAssistantText = text;
150
+ emit(sessionId, { type: "translation-status", status: "ready" });
151
+ }
152
+ catch (error) {
153
+ const failed = {
154
+ ...baseEntry,
155
+ status: "failed",
156
+ error: error instanceof Error ? error.message : "Translation failed.",
157
+ completedAt: now()
158
+ };
159
+ replaceEntry(sessionId, failed);
160
+ emit(sessionId, { type: "translation-status", status: "failed" });
161
+ }
162
+ });
163
+ }
164
+ function pushEntry(sessionId, entry) {
165
+ getState(sessionId).entries.push(entry);
166
+ emit(sessionId, { type: "translation-entry", entry });
167
+ }
168
+ async function pushPreservedTranscriptEntry(sessionId, entryId, sourceText) {
169
+ const session = deps.runtime.getSession(sessionId);
170
+ const roleSession = deps.sessionRegistry.get(sessionId);
171
+ if (!session && !roleSession) {
172
+ return;
173
+ }
174
+ const { settings } = await loadConfig();
175
+ const queue = queues.getQueue(sessionId);
176
+ await queue.enqueue(async () => {
177
+ const entry = createEntry({
178
+ taskSlug: roleSession?.taskSlug ?? session.taskSlug,
179
+ role: roleSession?.role ?? session.role,
180
+ direction: "cc-output-to-user",
181
+ sourceKind: "tool-output",
182
+ sourceText,
183
+ settings,
184
+ status: "preserved",
185
+ contextUsed: false,
186
+ id: entryId,
187
+ translatedText: sourceText,
188
+ completedAt: now()
189
+ });
190
+ pushEntry(sessionId, entry);
191
+ });
192
+ }
193
+ function replaceEntry(sessionId, entry) {
194
+ const state = getState(sessionId);
195
+ state.entries = state.entries.map((current) => current.id === entry.id ? entry : current);
196
+ emit(sessionId, { type: "translation-entry", entry });
197
+ }
198
+ function createEntry(input) {
199
+ return {
200
+ id: input.id ?? id(),
201
+ taskSlug: input.taskSlug,
202
+ role: input.role,
203
+ direction: input.direction,
204
+ sourceKind: input.sourceKind,
205
+ sourceLanguage: input.direction === "user-input-to-english" ? input.settings.sourceLanguage : "en",
206
+ targetLanguage: input.direction === "user-input-to-english" ? "en" : input.settings.targetLanguage,
207
+ sourceText: input.sourceText,
208
+ translatedText: input.translatedText ?? "",
209
+ status: input.status,
210
+ contextUsed: input.contextUsed,
211
+ createdAt: now(),
212
+ completedAt: input.completedAt,
213
+ provider: input.settings.providerType,
214
+ model: input.settings.model
215
+ };
216
+ }
217
+ return {
218
+ async getSettings() {
219
+ const { settings, secrets } = await loadConfig();
220
+ return exposeSettings(settings, secrets);
221
+ },
222
+ async updateSettings(input, secrets) {
223
+ const current = await loadConfig();
224
+ const next = {
225
+ settings: normalizeSettings({ ...current.settings, ...input }),
226
+ secrets: {
227
+ ...current.secrets,
228
+ ...(secrets?.apiKey !== undefined ? { apiKey: secrets.apiKey } : {})
229
+ }
230
+ };
231
+ await saveConfig(next);
232
+ return exposeSettings(next.settings, next.secrets);
233
+ },
234
+ async getPromptPreviews() {
235
+ const { settings } = await loadConfig();
236
+ return getTranslationPromptPreviews(settings);
237
+ },
238
+ async testProvider() {
239
+ const { settings, secrets } = await loadConfig();
240
+ return deps.provider.testConnection(settings, secrets);
241
+ },
242
+ async translateUserInput(input) {
243
+ const { settings, secrets } = await loadConfig();
244
+ if (!settings.enabled || !settings.translateUserInput) {
245
+ throw new VcmError({
246
+ code: "TRANSLATION_DISABLED",
247
+ message: "Translation input is disabled.",
248
+ statusCode: 409
249
+ });
250
+ }
251
+ if (!input.text.trim()) {
252
+ throw new VcmError({
253
+ code: "TRANSLATION_INPUT_EMPTY",
254
+ message: "Translation input cannot be empty.",
255
+ statusCode: 400
256
+ });
257
+ }
258
+ const roleSession = await deps.sessionService.getRoleSession(input.repoRoot, input.taskSlug, input.role);
259
+ const sessionState = roleSession ? getState(roleSession.id) : undefined;
260
+ const contextText = settings.contextEnabled && input.useContext !== false
261
+ ? sessionState?.lastAssistantText
262
+ : undefined;
263
+ const prompt = buildTranslationPrompt({
264
+ direction: "user-input-to-english",
265
+ text: input.text,
266
+ contextText,
267
+ settings
268
+ });
269
+ const entry = {
270
+ ...createEntry({
271
+ taskSlug: input.taskSlug,
272
+ role: input.role,
273
+ direction: "user-input-to-english",
274
+ sourceKind: "prose",
275
+ sourceText: input.text,
276
+ settings,
277
+ status: "translating",
278
+ contextUsed: Boolean(contextText)
279
+ }),
280
+ translationStartedAt: now()
281
+ };
282
+ if (roleSession) {
283
+ pushEntry(roleSession.id, entry);
284
+ }
285
+ try {
286
+ const result = await deps.provider.translate({
287
+ settings,
288
+ secrets,
289
+ systemPrompt: prompt.systemPrompt,
290
+ userPrompt: prompt.userPrompt
291
+ });
292
+ const parsed = prompt.parseWarning ? parseTranslationWarning(result.text) : { text: result.text };
293
+ const completed = {
294
+ ...entry,
295
+ status: "translated",
296
+ translatedText: parsed.text,
297
+ warning: parsed.warning,
298
+ completedAt: now(),
299
+ tokenUsage: result.tokenUsage
300
+ };
301
+ if (roleSession) {
302
+ replaceEntry(roleSession.id, completed);
303
+ }
304
+ const mode = input.mode ?? settings.inputMode;
305
+ const shouldSend = input.send === true && mode === "auto-send" && !parsed.warning;
306
+ if (shouldSend) {
307
+ await writeToCurrentRole(input.repoRoot, input.taskSlug, input.role, parsed.text);
308
+ }
309
+ return {
310
+ translation: completed,
311
+ englishPreview: parsed.text,
312
+ contextUsed: Boolean(contextText),
313
+ requiresReview: mode === "review-before-send" || Boolean(parsed.warning),
314
+ sent: shouldSend
315
+ };
316
+ }
317
+ catch (error) {
318
+ const failed = {
319
+ ...entry,
320
+ status: "failed",
321
+ error: normalizeTranslationError(error),
322
+ completedAt: now()
323
+ };
324
+ if (roleSession) {
325
+ replaceEntry(roleSession.id, failed);
326
+ }
327
+ throw new VcmError({
328
+ code: "TRANSLATION_FAILED",
329
+ message: failed.error ?? "Translation failed.",
330
+ statusCode: 502
331
+ });
332
+ }
333
+ },
334
+ async sendTranslatedInput(input) {
335
+ await writeToCurrentRole(input.repoRoot, input.taskSlug, input.role, input.englishText);
336
+ },
337
+ subscribeToSession(sessionId, listener) {
338
+ const session = deps.runtime.getSession(sessionId);
339
+ const roleSession = deps.sessionRegistry.get(sessionId);
340
+ if (!session && !roleSession) {
341
+ throw new VcmError({
342
+ code: "SESSION_MISSING",
343
+ message: `Terminal session does not exist: ${sessionId}`,
344
+ statusCode: 404
345
+ });
346
+ }
347
+ const state = getState(sessionId);
348
+ state.listeners.add(listener);
349
+ for (const entry of state.entries) {
350
+ listener({ type: "translation-entry", entry });
351
+ }
352
+ if (!state.unsubscribeTranscript) {
353
+ if (!roleSession) {
354
+ listener({
355
+ type: "translation-error",
356
+ message: "Claude transcript watcher is unavailable for this session."
357
+ });
358
+ }
359
+ else {
360
+ const replaySince = getTranscriptReplaySince(roleSession);
361
+ state.unsubscribeTranscript = deps.transcripts.subscribeToRoleSession(roleSession, (event) => {
362
+ void handleTranscriptEvent(sessionId, event).catch((error) => {
363
+ emit(sessionId, {
364
+ type: "translation-error",
365
+ message: error instanceof Error ? error.message : "Translation failed."
366
+ });
367
+ });
368
+ }, {
369
+ onError(error) {
370
+ emit(sessionId, {
371
+ type: "translation-error",
372
+ message: error.message
373
+ });
374
+ },
375
+ replaySince
376
+ });
377
+ }
378
+ }
379
+ void loadConfig().then(({ settings }) => {
380
+ listener({ type: "translation-status", status: settings.enabled ? "ready" : "paused" });
381
+ });
382
+ return () => {
383
+ state.listeners.delete(listener);
384
+ if (state.listeners.size === 0 && state.unsubscribeTranscript) {
385
+ state.unsubscribeTranscript();
386
+ state.unsubscribeTranscript = undefined;
387
+ }
388
+ };
389
+ },
390
+ clearSession(sessionId) {
391
+ const state = getState(sessionId);
392
+ state.entries = [];
393
+ queues.clearQueue(sessionId);
394
+ },
395
+ async retryTranslation(sessionId, translationId) {
396
+ const state = getState(sessionId);
397
+ const original = state.entries.find((entry) => entry.id === translationId);
398
+ if (!original) {
399
+ throw new VcmError({
400
+ code: "TRANSLATION_ENTRY_MISSING",
401
+ message: `Translation entry not found: ${translationId}`,
402
+ statusCode: 404
403
+ });
404
+ }
405
+ if (original.direction !== "cc-output-to-user") {
406
+ throw new VcmError({
407
+ code: "TRANSLATION_RETRY_UNSUPPORTED",
408
+ message: "Only Claude Code output translation entries can be retried.",
409
+ statusCode: 400
410
+ });
411
+ }
412
+ await processClaudeOutputText(sessionId, original.sourceText);
413
+ return state.entries[state.entries.length - 1] ?? original;
414
+ }
415
+ };
416
+ async function writeToCurrentRole(repoRoot, taskSlug, role, text) {
417
+ const record = await deps.sessionService.getRoleSession(repoRoot, taskSlug, role);
418
+ if (!record || record.status !== "running") {
419
+ throw new VcmError({
420
+ code: "SESSION_NOT_RUNNING",
421
+ message: `${role} session is not running.`,
422
+ statusCode: 409
423
+ });
424
+ }
425
+ deps.runtime.write(record.id, formatTerminalSubmit(text));
426
+ }
427
+ }
428
+ export function formatTerminalSubmit(text) {
429
+ return `${text.replace(/[\r\n]+$/g, "")}\r`;
430
+ }
431
+ function getTranscriptReplaySince(roleSession) {
432
+ const rawTimestamp = roleSession.startedAt ?? roleSession.updatedAt;
433
+ const timestampMs = Date.parse(rawTimestamp);
434
+ if (!Number.isFinite(timestampMs)) {
435
+ return undefined;
436
+ }
437
+ return new Date(Math.max(0, timestampMs - TRANSCRIPT_REPLAY_GRACE_MS)).toISOString();
438
+ }
439
+ function formatStructuredTranscriptEvent(event) {
440
+ if (event.kind === "question") {
441
+ return event.question.questions.map((question, index) => {
442
+ const title = question.header ? `${question.header}: ${question.question}` : question.question;
443
+ const options = question.options.map((option) => {
444
+ const preview = option.preview ? `\n Preview: ${option.preview}` : "";
445
+ return `- ${option.label}: ${option.description}${preview}`;
446
+ });
447
+ return [`AskUserQuestion ${index + 1}`, title, `Multi-select: ${question.multiSelect ? "yes" : "no"}`, "Options:", ...options]
448
+ .filter(Boolean)
449
+ .join("\n");
450
+ }).join("\n\n");
451
+ }
452
+ if (event.kind === "todo") {
453
+ return [
454
+ "TodoWrite plan",
455
+ ...event.todo.todos.map((todo) => {
456
+ const text = todo.status === "in_progress" && todo.activeForm ? todo.activeForm : todo.content;
457
+ return `- [${todo.status}] ${text}`;
458
+ })
459
+ ].join("\n");
460
+ }
461
+ return [
462
+ `Agent dispatch${event.agent.subagent_type ? `: ${event.agent.subagent_type}` : ""}`,
463
+ event.agent.description ? `Description: ${event.agent.description}` : "",
464
+ event.agent.prompt ? `Prompt:\n${event.agent.prompt}` : ""
465
+ ].filter(Boolean).join("\n");
466
+ }
467
+ function formatRawTranscriptEvent(event) {
468
+ if (event.kind === "tool_use") {
469
+ return `● ${event.toolUse.name}(${formatUnknown(event.toolUse.input)})`;
470
+ }
471
+ const errorPrefix = event.toolResult.isError ? "[error] " : "";
472
+ return `⎿ ${errorPrefix}${formatUnknown(event.toolResult.content)}`;
473
+ }
474
+ function formatUnknown(value) {
475
+ if (typeof value === "string") {
476
+ return value;
477
+ }
478
+ if (value === undefined) {
479
+ return "";
480
+ }
481
+ try {
482
+ return JSON.stringify(value);
483
+ }
484
+ catch {
485
+ return String(value);
486
+ }
487
+ }
488
+ function normalizeSettings(input) {
489
+ const { apiKey: _apiKey, ...settings } = input;
490
+ return {
491
+ ...DEFAULT_SETTINGS,
492
+ ...settings,
493
+ version: 1,
494
+ providerType: "openai-compatible",
495
+ workingLanguage: "en",
496
+ requestTimeoutMs: clampNumber(input.requestTimeoutMs, 3000, 120000, DEFAULT_SETTINGS.requestTimeoutMs),
497
+ temperature: clampNumber(input.temperature, 0, 1, DEFAULT_SETTINGS.temperature),
498
+ prompts: normalizePromptMap(input.prompts)
499
+ };
500
+ }
501
+ function exposeSettings(settings, secrets) {
502
+ return {
503
+ ...settings,
504
+ apiKey: secrets.apiKey ?? ""
505
+ };
506
+ }
507
+ function normalizePromptMap(input) {
508
+ if (!input || typeof input !== "object") {
509
+ return undefined;
510
+ }
511
+ const prompts = {};
512
+ for (const [key, value] of Object.entries(input)) {
513
+ const normalizedKey = normalizePromptKey(key);
514
+ if (normalizedKey && typeof value === "string" && value.trim()) {
515
+ prompts[normalizedKey] = value;
516
+ }
517
+ }
518
+ return Object.keys(prompts).length > 0 ? prompts : undefined;
519
+ }
520
+ function normalizePromptKey(key) {
521
+ if (TRANSLATION_PROMPT_KEYS.includes(key)) {
522
+ return key;
523
+ }
524
+ if (key === "user-input-to-english") {
525
+ return "zh-to-en";
526
+ }
527
+ if (key === "user-input-to-english-with-context") {
528
+ return "zh-to-en-with-context";
529
+ }
530
+ if (key === "cc-output-to-user") {
531
+ return "en-to-zh";
532
+ }
533
+ return undefined;
534
+ }
535
+ function clampNumber(value, min, max, fallback) {
536
+ if (typeof value !== "number" || !Number.isFinite(value)) {
537
+ return fallback;
538
+ }
539
+ return Math.min(max, Math.max(min, value));
540
+ }
541
+ function normalizeTranslationError(error) {
542
+ if (error instanceof TranslationProviderError) {
543
+ return `${error.code}: ${error.message}`;
544
+ }
545
+ return error instanceof Error ? error.message : "Translation failed.";
546
+ }
@@ -74,3 +74,35 @@ TBD
74
74
  TBD
75
75
  `;
76
76
  }
77
+ export function renderDocsSyncReportTemplate(taskSlug) {
78
+ return `# Docs Sync Report: ${taskSlug}
79
+
80
+ ## Summary
81
+
82
+ TBD
83
+
84
+ ## Architecture Drift Check
85
+
86
+ TBD
87
+
88
+ ## Docs Updated
89
+
90
+ TBD
91
+
92
+ ## Docs Reviewed And Left Unchanged
93
+
94
+ TBD
95
+
96
+ ## Public Contract / Module Boundary Notes
97
+
98
+ TBD
99
+
100
+ ## Remaining Documentation Risks
101
+
102
+ TBD
103
+
104
+ ## Decision
105
+
106
+ TBD
107
+ `;
108
+ }
@@ -0,0 +1,12 @@
1
+ export function renderArchitectHarnessRules() {
2
+ return `## VCM Architect Rules
3
+
4
+ - Own architecture planning, module boundaries, file responsibilities, public contracts, test contracts, risk, phases, and stop conditions.
5
+ - Write architecture-plan.md under the current task handoff directory before coder work starts.
6
+ - Do not implement production code.
7
+ - After reviewer completes, perform docs sync and architecture drift checks when requested by project-manager.
8
+ - Update stale architecture/module/testing/security/dependency docs when the final code made them stale.
9
+ - Write docs-sync-report.md with docs changed, docs intentionally left unchanged, remaining documentation risks, and decision.
10
+ - Stop and reply to project-manager if implementation drift changes architecture, public contracts, dependency direction, schema, auth, permission, payment, or design assumptions.
11
+ `;
12
+ }
@@ -0,0 +1,14 @@
1
+ export function renderRootClaudeHarnessRules() {
2
+ return `## VCM Shared Rules
3
+
4
+ - This repository uses VibeCodingMaster for multi-session Claude Code work.
5
+ - User-facing work starts with the project-manager role.
6
+ - Canonical task handoffs live under .ai/handoffs/<task-slug>/.
7
+ - Use only the current task's handoff directory for task-specific artifacts.
8
+ - Do not create or write .ai/handoffs/<other-task>/ for the current task.
9
+ - Use vcmctl for role-to-role messaging instead of asking the user to copy prompts.
10
+ - Non-PM roles only reply to project-manager; they do not message other roles directly.
11
+ - High-risk decisions involving schema, auth, permissions, payment, billing, security, data deletion, or unclear user intent must stop for project-manager/user approval.
12
+ - Required workflow gates: architect plan -> coder implementation/validation -> reviewer review -> architect docs sync -> project-manager final acceptance/commit/PR.
13
+ `;
14
+ }
@@ -0,0 +1,11 @@
1
+ export function renderCoderHarnessRules() {
2
+ return `## VCM Coder Rules
3
+
4
+ - Implement only the approved task scope and architecture plan.
5
+ - Read the task spec, architecture-plan.md, relevant module docs, and role command before editing.
6
+ - Add or update direct unit, contract, or regression tests for changed behavior.
7
+ - Maintain implementation-log.md and validation-log.md under the current task handoff directory.
8
+ - Do not change module boundaries, public contracts, dependency direction, or test strategy without project-manager/architect replan.
9
+ - Stop and reply to project-manager when blocked, unclear, or when the plan no longer matches reality.
10
+ `;
11
+ }
@@ -0,0 +1,14 @@
1
+ export function renderProjectManagerHarnessRules() {
2
+ return `## VCM Project Manager Rules
3
+
4
+ - You are the user-facing orchestration hub for the VCM task.
5
+ - Clarify the user's request, classify task risk, and choose the role route.
6
+ - Use vcmctl send to assign work to architect, coder, or reviewer.
7
+ - Send role work as durable instructions with artifact refs when possible.
8
+ - Track the workflow gates: architecture plan, implementation/validation, review, docs sync, final acceptance.
9
+ - Request architect post-review docs sync after reviewer completes.
10
+ - Prepare final acceptance, commit, and PR only after reviewer and docs-sync gates pass or an explicit exception is approved.
11
+ - Do not implement non-trivial production code directly.
12
+ - Stop and ask the user for high-risk decisions or unclear requirements.
13
+ `;
14
+ }
@@ -0,0 +1,13 @@
1
+ export function renderReviewerHarnessRules() {
2
+ return `## VCM Reviewer Rules
3
+
4
+ - Own independent acceptance review and final test adequacy.
5
+ - Read task spec, architecture-plan.md, implementation-log.md, validation-log.md, and git diff.
6
+ - Verify scope, role compliance, architecture compliance, public contract compliance, validation evidence, docs gaps, and risk.
7
+ - Add or strengthen missing tests only when the fix is small, local, low-risk, and review-scoped.
8
+ - Write review-report.md under the current task handoff directory.
9
+ - Escalate larger implementation issues to project-manager for coder follow-up.
10
+ - Escalate architecture, public contract, design, or documentation drift issues to project-manager for architect follow-up.
11
+ - Do not take over broad implementation and do not weaken tests to pass validation.
12
+ `;
13
+ }
@@ -0,0 +1,35 @@
1
+ import { WebSocketServer } from "ws";
2
+ import { toVcmError } from "../errors.js";
3
+ export function registerTranslationWs(app, deps) {
4
+ const wss = new WebSocketServer({ noServer: true });
5
+ app.server.on("upgrade", (request, socket, head) => {
6
+ const url = new URL(request.url ?? "/", "http://localhost");
7
+ const match = /^\/ws\/translation\/([^/]+)$/.exec(url.pathname);
8
+ if (!match) {
9
+ return;
10
+ }
11
+ wss.handleUpgrade(request, socket, head, (ws) => {
12
+ bindTranslationSocket(ws, decodeURIComponent(match[1] ?? ""), deps.translationService);
13
+ });
14
+ });
15
+ }
16
+ function bindTranslationSocket(ws, sessionId, translationService) {
17
+ let unsubscribe = () => { };
18
+ try {
19
+ unsubscribe = translationService.subscribeToSession(sessionId, (message) => send(ws, message));
20
+ }
21
+ catch (error) {
22
+ const vcmError = toVcmError(error);
23
+ send(ws, { type: "translation-error", message: vcmError.message });
24
+ ws.close();
25
+ return;
26
+ }
27
+ ws.on("close", () => {
28
+ unsubscribe();
29
+ });
30
+ }
31
+ function send(ws, message) {
32
+ if (ws.readyState === ws.OPEN) {
33
+ ws.send(JSON.stringify(message));
34
+ }
35
+ }
@@ -0,0 +1 @@
1
+ export {};