vibe-coding-master 0.7.4 → 0.7.6

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 (57) hide show
  1. package/README.md +19 -16
  2. package/dist/backend/adapters/git-adapter.js +15 -0
  3. package/dist/backend/api/artifact-routes.js +3 -0
  4. package/dist/backend/api/harness-routes.js +50 -27
  5. package/dist/backend/api/runtime-state-routes.js +7 -3
  6. package/dist/backend/api/task-routes.js +36 -4
  7. package/dist/backend/api/translation-routes.js +11 -2
  8. package/dist/backend/api/translation-worker-routes.js +37 -9
  9. package/dist/backend/cli/install-vcm-harness.js +40 -2
  10. package/dist/backend/gateway/gateway-service.js +34 -17
  11. package/dist/backend/server.js +12 -3
  12. package/dist/backend/services/artifact-service.js +5 -1
  13. package/dist/backend/services/auto-memory-service.js +156 -81
  14. package/dist/backend/services/claude-hook-service.js +50 -35
  15. package/dist/backend/services/command-dispatcher.js +1 -1
  16. package/dist/backend/services/gate-review-service.js +335 -31
  17. package/dist/backend/services/harness-feedback-service.js +19 -8
  18. package/dist/backend/services/harness-service.js +112 -34
  19. package/dist/backend/services/message-service.js +39 -2
  20. package/dist/backend/services/round-service.js +10 -121
  21. package/dist/backend/services/runtime-coordinator-service.js +18 -10
  22. package/dist/backend/services/runtime-recovery-service.js +1 -2
  23. package/dist/backend/services/session-service.js +36 -98
  24. package/dist/backend/services/status-service.js +1 -0
  25. package/dist/backend/services/task-close-service.js +12 -27
  26. package/dist/backend/services/task-workflow-service.js +228 -0
  27. package/dist/backend/services/translation-worker-service.js +14 -7
  28. package/dist/backend/templates/handoff.js +128 -1
  29. package/dist/backend/templates/harness/architect-agent.js +85 -22
  30. package/dist/backend/templates/harness/claude-root.js +25 -29
  31. package/dist/backend/templates/harness/coder-agent.js +5 -7
  32. package/dist/backend/templates/harness/coder-worker-agent.js +3 -3
  33. package/dist/backend/templates/harness/gate-review.js +292 -65
  34. package/dist/backend/templates/harness/harness-engineer-agent.js +8 -8
  35. package/dist/backend/templates/harness/memory-block.js +69 -0
  36. package/dist/backend/templates/harness/project-known-issues.js +1 -0
  37. package/dist/backend/templates/harness/project-manager-agent.js +217 -75
  38. package/dist/backend/templates/harness/role-memory.js +9 -12
  39. package/dist/backend/templates/harness/tester-agent.js +8 -4
  40. package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +82 -0
  41. package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +4 -3
  42. package/dist/backend/templates/harness/vcm-harness-bootstrap-skill.js +14 -3
  43. package/dist/backend/templates/harness/vcm-propose-memory-skill.js +2 -2
  44. package/dist/backend/templates/harness/vcm-route-message-skill.js +5 -0
  45. package/dist/backend/templates/harness/vcm-task-state-skill.js +110 -0
  46. package/dist/backend/templates/message-envelope.js +1 -1
  47. package/dist/shared/constants.js +0 -10
  48. package/dist/shared/types/workflow.js +1 -0
  49. package/dist/shared/validation/artifact-check.js +41 -1
  50. package/dist-frontend/assets/index-BO2AuF-q.js +97 -0
  51. package/dist-frontend/assets/index-C2etsYlK.css +32 -0
  52. package/dist-frontend/index.html +2 -2
  53. package/package.json +1 -1
  54. package/scripts/harness-tools/check-durable-docs +298 -0
  55. package/scripts/verify-package.mjs +1 -0
  56. package/dist-frontend/assets/index-DCb-S6Ls.css +0 -32
  57. package/dist-frontend/assets/index-NTlycxx9.js +0 -97
@@ -4,25 +4,30 @@ import { checkMarkdownArtifact, readArtifactSectionValue } from "../../shared/va
4
4
  import { resolveRepoPath } from "../adapters/filesystem.js";
5
5
  import { VcmError } from "../errors.js";
6
6
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
7
- const MEMORY_ROOT = ".ai/vcm/memory";
7
+ import { readVcmMemoryBlock, replaceVcmMemoryBlock } from "../templates/harness/memory-block.js";
8
8
  const MEMORY_REVIEW_ROOT = ".ai/vcm/memory-review";
9
9
  const MEMORY_REVIEW_RUNS_ROOT = `${MEMORY_REVIEW_ROOT}/runs`;
10
10
  const MEMORY_REVIEW_STATE_PATH = `${MEMORY_REVIEW_ROOT}/state.json`;
11
11
  const MEMORY_FILE_DEFINITIONS = [
12
- { path: `${MEMORY_ROOT}/shared.md`, title: "Shared Memory" },
13
- { path: `${MEMORY_ROOT}/roles/project-manager.md`, title: "Project Manager Memory", role: "project-manager" },
14
- { path: `${MEMORY_ROOT}/roles/architect.md`, title: "Architect Memory", role: "architect" },
15
- { path: `${MEMORY_ROOT}/roles/coder.md`, title: "Coder Memory", role: "coder" },
16
- { path: `${MEMORY_ROOT}/roles/tester.md`, title: "Tester Memory", role: "tester" },
17
- { path: `${MEMORY_ROOT}/roles/gate-reviewer.md`, title: "Gate Reviewer Memory", role: "gate-reviewer" },
18
- { path: `${MEMORY_ROOT}/roles/harness-engineer.md`, title: "Harness Engineer Memory", role: "harness-engineer" }
12
+ { path: "CLAUDE.md", title: "Shared Memory" },
13
+ { path: ".claude/agents/project-manager.md", title: "Project Manager Memory", role: "project-manager" },
14
+ { path: ".claude/agents/architect.md", title: "Architect Memory", role: "architect" },
15
+ { path: ".claude/agents/coder.md", title: "Coder Memory", role: "coder" },
16
+ { path: ".claude/agents/tester.md", title: "Tester Memory", role: "tester" },
17
+ { path: ".claude/agents/gate-reviewer.md", title: "Gate Reviewer Memory", role: "gate-reviewer" },
18
+ { path: ".claude/agents/harness-engineer.md", title: "Harness Engineer Memory", role: "harness-engineer" }
19
19
  ];
20
20
  export function createAutoMemoryService(deps) {
21
21
  const now = deps.now ?? (() => new Date().toISOString());
22
22
  async function readMemorySet(repoRoot) {
23
23
  const memory = {};
24
24
  for (const definition of MEMORY_FILE_DEFINITIONS) {
25
- memory[definition.path] = await deps.fs.readText(resolveRepoPath(repoRoot, definition.path));
25
+ const hostContent = await deps.fs.readText(resolveRepoPath(repoRoot, definition.path));
26
+ const blockContent = readVcmMemoryBlock(hostContent);
27
+ if (blockContent === undefined) {
28
+ throw missingMemoryBlockError(definition.path);
29
+ }
30
+ memory[definition.path] = blockContent;
26
31
  }
27
32
  return memory;
28
33
  }
@@ -30,7 +35,8 @@ export function createAutoMemoryService(deps) {
30
35
  assertCompleteMemorySet(memory);
31
36
  for (const definition of MEMORY_FILE_DEFINITIONS) {
32
37
  const targetPath = resolveRepoPath(repoRoot, definition.path);
33
- const content = ensureTrailingNewline(memory[definition.path]);
38
+ const hostContent = await deps.fs.readText(targetPath);
39
+ const content = replaceVcmMemoryBlock(hostContent, memory[definition.path]);
34
40
  if (deps.fs.writeTextAtomic) {
35
41
  await deps.fs.writeTextAtomic(targetPath, content);
36
42
  }
@@ -39,9 +45,40 @@ export function createAutoMemoryService(deps) {
39
45
  }
40
46
  }
41
47
  }
42
- async function applyMemorySet(baseRepoRoot, taskRepoRoot, memory) {
43
- await writeMemorySet(baseRepoRoot, memory);
44
- await writeMemorySet(taskRepoRoot, memory);
48
+ async function applyAndCommitMemorySet(taskRepoRoot, before, after, commitMessage) {
49
+ assertCompleteMemorySet(after);
50
+ const changedPaths = MEMORY_FILE_DEFINITIONS
51
+ .filter((definition) => before[definition.path] !== after[definition.path])
52
+ .map((definition) => definition.path);
53
+ if (changedPaths.length === 0) {
54
+ return;
55
+ }
56
+ const current = await readMemorySet(taskRepoRoot);
57
+ if (!sameHashes(hashMemorySet(current), hashMemorySet(before))) {
58
+ throw new VcmError({
59
+ code: "MEMORY_TARGET_CHANGED",
60
+ message: "VCM memory changed while the review was in progress.",
61
+ statusCode: 409,
62
+ hint: "Review the current memory, then retry the memory review."
63
+ });
64
+ }
65
+ const existingDiff = await deps.git.getDiff(taskRepoRoot, "HEAD", null, changedPaths);
66
+ if (existingDiff.trim()) {
67
+ throw new VcmError({
68
+ code: "MEMORY_HOST_FILE_DIRTY",
69
+ message: "A file containing VCM memory already has uncommitted changes.",
70
+ statusCode: 409,
71
+ hint: "Commit or discard the existing host-file changes before applying memory."
72
+ });
73
+ }
74
+ await writeMemorySet(taskRepoRoot, after);
75
+ try {
76
+ await deps.git.commitPaths(taskRepoRoot, commitMessage, changedPaths);
77
+ }
78
+ catch (error) {
79
+ await writeMemorySet(taskRepoRoot, before);
80
+ throw error;
81
+ }
45
82
  }
46
83
  async function writeRunMemorySet(taskRepoRoot, runId, snapshot, memory) {
47
84
  assertCompleteMemorySet(memory);
@@ -61,8 +98,7 @@ export function createAutoMemoryService(deps) {
61
98
  return memory;
62
99
  }
63
100
  async function getState(baseRepoRoot, taskRepoRoot) {
64
- await ensureTaskMemorySnapshot(deps.fs, baseRepoRoot, taskRepoRoot);
65
- let [active, runs, files] = await Promise.all([
101
+ let [active, runs, memoryFiles] = await Promise.all([
66
102
  loadActiveState(taskRepoRoot),
67
103
  listRuns(taskRepoRoot),
68
104
  listMemoryFiles(taskRepoRoot)
@@ -75,14 +111,13 @@ export function createAutoMemoryService(deps) {
75
111
  return {
76
112
  version: 1,
77
113
  status: active?.status ?? "idle",
78
- files,
114
+ files: memoryFiles.files,
79
115
  runs,
80
116
  ...(active ? { active: toActiveReview(active) } : {}),
81
- warnings: []
117
+ warnings: memoryFiles.warnings
82
118
  };
83
119
  }
84
120
  async function reconcileTask(input) {
85
- await ensureTaskMemorySnapshot(deps.fs, input.baseRepoRoot, input.taskRepoRoot);
86
121
  const active = await loadActiveState(input.taskRepoRoot);
87
122
  const preferences = await deps.appSettings.getPreferences();
88
123
  if (!preferences.autoMemoryEnabled) {
@@ -91,6 +126,7 @@ export function createAutoMemoryService(deps) {
91
126
  }
92
127
  return getState(input.baseRepoRoot, input.taskRepoRoot);
93
128
  }
129
+ await assertMemoryBlocksInstalled(deps.fs, input.taskRepoRoot);
94
130
  if (active?.status === "collecting") {
95
131
  await dispatchCurrentDraft(input.baseRepoRoot, input.taskRepoRoot, active);
96
132
  return getState(input.baseRepoRoot, input.taskRepoRoot);
@@ -135,7 +171,7 @@ export function createAutoMemoryService(deps) {
135
171
  updatedAt: timestamp,
136
172
  drafts
137
173
  };
138
- const before = await readMemorySet(input.baseRepoRoot);
174
+ const before = await readMemorySet(input.taskRepoRoot);
139
175
  await writeRunMemorySet(input.taskRepoRoot, runId, "before", before);
140
176
  await writeRunMemorySet(input.taskRepoRoot, runId, "after", before);
141
177
  await persistRun(input.taskRepoRoot, {
@@ -188,7 +224,7 @@ export function createAutoMemoryService(deps) {
188
224
  async function isRoleMemoryTurn(taskRepoRoot, role) {
189
225
  const state = await loadActiveState(taskRepoRoot);
190
226
  const draft = state?.status === "collecting" ? currentDraft(state) : undefined;
191
- return draft?.role === role && draft.status === "running";
227
+ return draft?.role === role && draft.status === "dispatched";
192
228
  }
193
229
  async function handleRoleHook(input) {
194
230
  const state = await loadActiveState(input.taskRepoRoot);
@@ -204,11 +240,6 @@ export function createAutoMemoryService(deps) {
204
240
  return false;
205
241
  }
206
242
  if (input.eventName === "UserPromptSubmit" || input.eventName === "PostCompact") {
207
- if (draft.status !== "running") {
208
- draft.status = "running";
209
- state.updatedAt = now();
210
- await persistActiveState(input.taskRepoRoot, state);
211
- }
212
243
  return true;
213
244
  }
214
245
  if (input.eventName === "StopFailure") {
@@ -265,15 +296,19 @@ export function createAutoMemoryService(deps) {
265
296
  return true;
266
297
  }
267
298
  if (input.eventName === "Stop") {
268
- await applyReviewedMemory(input.baseRepoRoot, input.taskRepoRoot, state);
299
+ await applyReviewedMemory(input.taskRepoRoot, state);
269
300
  return true;
270
301
  }
271
302
  return true;
272
303
  }
273
- async function getFile(baseRepoRoot, taskRepoRoot, filePath) {
274
- await ensureTaskMemorySnapshot(deps.fs, baseRepoRoot, taskRepoRoot);
304
+ async function getFile(_baseRepoRoot, taskRepoRoot, filePath) {
305
+ await assertMemoryBlocksInstalled(deps.fs, taskRepoRoot);
275
306
  const definition = requireMemoryFileDefinition(filePath);
276
- const content = await deps.fs.readText(resolveRepoPath(taskRepoRoot, definition.path));
307
+ const hostContent = await deps.fs.readText(resolveRepoPath(taskRepoRoot, definition.path));
308
+ const content = readVcmMemoryBlock(hostContent);
309
+ if (content === undefined) {
310
+ throw missingMemoryBlockError(definition.path);
311
+ }
277
312
  return {
278
313
  path: definition.path,
279
314
  title: definition.title,
@@ -286,13 +321,13 @@ export function createAutoMemoryService(deps) {
286
321
  async function updateFile(baseRepoRoot, taskRepoRoot, taskSlug, filePath, content) {
287
322
  await assertNoActiveReview(taskRepoRoot);
288
323
  const definition = requireMemoryFileDefinition(filePath);
289
- const before = await readMemorySet(baseRepoRoot);
324
+ const before = await readMemorySet(taskRepoRoot);
290
325
  const normalizedContent = ensureTrailingNewline(content);
291
326
  if (before[definition.path] === normalizedContent) {
292
327
  return getState(baseRepoRoot, taskRepoRoot);
293
328
  }
294
329
  const after = { ...before, [definition.path]: normalizedContent };
295
- await createAppliedRun(baseRepoRoot, taskRepoRoot, taskSlug, "user", before, after);
330
+ await createAppliedRun(taskRepoRoot, taskSlug, "user", before, after);
296
331
  return getState(baseRepoRoot, taskRepoRoot);
297
332
  }
298
333
  async function revertRun(baseRepoRoot, taskRepoRoot, runId) {
@@ -305,7 +340,7 @@ export function createAutoMemoryService(deps) {
305
340
  statusCode: 409
306
341
  });
307
342
  }
308
- const current = await readMemorySet(baseRepoRoot);
343
+ const current = await readMemorySet(taskRepoRoot);
309
344
  if (!sameHashes(hashMemorySet(current), run.afterHashes)) {
310
345
  throw new VcmError({
311
346
  code: "MEMORY_RUN_CHANGED",
@@ -315,8 +350,7 @@ export function createAutoMemoryService(deps) {
315
350
  });
316
351
  }
317
352
  const before = await readRunMemorySet(taskRepoRoot, run.runId, "before");
318
- await writeMemorySet(baseRepoRoot, before);
319
- await writeMemorySet(taskRepoRoot, before);
353
+ await applyAndCommitMemorySet(taskRepoRoot, current, before, "chore: revert VCM memory");
320
354
  const timestamp = now();
321
355
  await persistRun(taskRepoRoot, {
322
356
  ...run,
@@ -382,7 +416,7 @@ export function createAutoMemoryService(deps) {
382
416
  if (session.activityStatus === "running") {
383
417
  return;
384
418
  }
385
- draft.status = "running";
419
+ draft.status = "dispatched";
386
420
  state.updatedAt = now();
387
421
  await persistActiveState(taskRepoRoot, state);
388
422
  await submitTerminalInput(deps.runtime, session.id, buildRoleDraftPrompt(taskRepoRoot, state, draft));
@@ -399,15 +433,16 @@ export function createAutoMemoryService(deps) {
399
433
  return;
400
434
  }
401
435
  try {
402
- const existing = await deps.sessionService.getProjectHarnessEngineerSession(baseRepoRoot);
436
+ const existing = await deps.sessionService.getRoleSession(baseRepoRoot, state.taskSlug, "harness-engineer");
403
437
  if (existing?.activityStatus === "running") {
404
438
  return;
405
439
  }
406
- const session = await deps.sessionService.ensureProjectHarnessEngineerSession(baseRepoRoot, {
407
- taskSlug: state.taskSlug,
408
- cols: 120,
409
- rows: 32
410
- });
440
+ const input = { cols: 120, rows: 32 };
441
+ const session = existing?.status === "running"
442
+ ? existing
443
+ : existing?.claudeSessionId
444
+ ? await deps.sessionService.resumeRoleSession(baseRepoRoot, state.taskSlug, "harness-engineer", input)
445
+ : await deps.sessionService.startRoleSession(baseRepoRoot, state.taskSlug, "harness-engineer", input);
411
446
  if (session.status !== "running" || session.activityStatus === "running" || !deps.runtime.getSession(session.id)) {
412
447
  return;
413
448
  }
@@ -432,12 +467,12 @@ export function createAutoMemoryService(deps) {
432
467
  }
433
468
  return deps.sessionService.startRoleSession(baseRepoRoot, taskSlug, role, options);
434
469
  }
435
- async function applyReviewedMemory(baseRepoRoot, taskRepoRoot, state) {
470
+ async function applyReviewedMemory(taskRepoRoot, state) {
436
471
  try {
437
472
  const before = await readRunMemorySet(taskRepoRoot, state.runId, "before");
438
473
  const after = await readRunMemorySet(taskRepoRoot, state.runId, "after");
439
474
  assertCompleteMemorySet(after);
440
- await applyMemorySet(baseRepoRoot, taskRepoRoot, after);
475
+ await applyAndCommitMemorySet(taskRepoRoot, before, after, "chore: update VCM memory");
441
476
  const timestamp = now();
442
477
  const diff = renderMemoryDiff(before, after);
443
478
  const run = await readRun(taskRepoRoot, state.runId);
@@ -456,28 +491,34 @@ export function createAutoMemoryService(deps) {
456
491
  await failReview(taskRepoRoot, state, `Harness Engineer memory result could not be applied: ${errorMessage(error)}`);
457
492
  }
458
493
  }
459
- async function createAppliedRun(baseRepoRoot, taskRepoRoot, taskSlug, source, before, after) {
494
+ async function createAppliedRun(taskRepoRoot, taskSlug, source, before, after) {
460
495
  assertCompleteMemorySet(after);
461
496
  const timestamp = now();
462
497
  const runId = createRunId(timestamp, source);
463
498
  await writeRunMemorySet(taskRepoRoot, runId, "before", before);
464
499
  await writeRunMemorySet(taskRepoRoot, runId, "after", after);
465
- await applyMemorySet(baseRepoRoot, taskRepoRoot, after);
466
- const diff = renderMemoryDiff(before, after);
467
- await persistRun(taskRepoRoot, {
468
- version: 1,
469
- runId,
470
- taskSlug,
471
- source,
472
- status: "applied",
473
- createdAt: timestamp,
474
- updatedAt: timestamp,
475
- appliedAt: timestamp,
476
- beforeHashes: hashMemorySet(before),
477
- afterHashes: hashMemorySet(after),
478
- diff
479
- });
480
- await deps.fs.writeText(resolveRepoPath(taskRepoRoot, `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/applied.patch`), diff);
500
+ try {
501
+ await applyAndCommitMemorySet(taskRepoRoot, before, after, "chore: update VCM memory");
502
+ const diff = renderMemoryDiff(before, after);
503
+ await persistRun(taskRepoRoot, {
504
+ version: 1,
505
+ runId,
506
+ taskSlug,
507
+ source,
508
+ status: "applied",
509
+ createdAt: timestamp,
510
+ updatedAt: timestamp,
511
+ appliedAt: timestamp,
512
+ beforeHashes: hashMemorySet(before),
513
+ afterHashes: hashMemorySet(after),
514
+ diff
515
+ });
516
+ await deps.fs.writeText(resolveRepoPath(taskRepoRoot, `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/applied.patch`), diff);
517
+ }
518
+ catch (error) {
519
+ await deps.fs.removePath?.(resolveRepoPath(taskRepoRoot, `${MEMORY_REVIEW_RUNS_ROOT}/${runId}`), { recursive: true, force: true });
520
+ throw error;
521
+ }
481
522
  }
482
523
  async function failReview(taskRepoRoot, state, message) {
483
524
  const timestamp = now();
@@ -531,8 +572,21 @@ export function createAutoMemoryService(deps) {
531
572
  }
532
573
  async function listMemoryFiles(taskRepoRoot) {
533
574
  const files = [];
575
+ const warnings = [];
534
576
  for (const definition of MEMORY_FILE_DEFINITIONS) {
535
- const content = await deps.fs.readText(resolveRepoPath(taskRepoRoot, definition.path));
577
+ let content = "";
578
+ const hostPath = resolveRepoPath(taskRepoRoot, definition.path);
579
+ try {
580
+ if (await deps.fs.pathExists(hostPath)) {
581
+ content = readVcmMemoryBlock(await deps.fs.readText(hostPath)) ?? "";
582
+ }
583
+ }
584
+ catch (error) {
585
+ warnings.push(`${definition.path}: ${errorMessage(error)}`);
586
+ }
587
+ if (!content) {
588
+ warnings.push(`${definition.path}: VCM memory block is missing. Refresh the VCM Harness.`);
589
+ }
536
590
  files.push({
537
591
  path: definition.path,
538
592
  title: definition.title,
@@ -540,7 +594,7 @@ export function createAutoMemoryService(deps) {
540
594
  sizeBytes: Buffer.byteLength(content, "utf8")
541
595
  });
542
596
  }
543
- return files;
597
+ return { files, warnings: [...new Set(warnings)] };
544
598
  }
545
599
  async function hasCompletedRunForFinalAcceptance(taskRepoRoot, finalAcceptanceHash) {
546
600
  return Boolean(await findCompletedRunForFinalAcceptance(taskRepoRoot, finalAcceptanceHash));
@@ -554,8 +608,22 @@ export function createAutoMemoryService(deps) {
554
608
  if (!(await deps.fs.pathExists(statePath))) {
555
609
  return undefined;
556
610
  }
557
- const state = await deps.fs.readJson(statePath);
558
- return state?.version === 1 && state.runId ? state : undefined;
611
+ const stored = await deps.fs.readJson(statePath);
612
+ if (stored?.version !== 1 || !stored.runId) {
613
+ return undefined;
614
+ }
615
+ const migrated = stored.drafts.some((draft) => draft.status === "running");
616
+ const state = {
617
+ ...stored,
618
+ drafts: stored.drafts.map((draft) => ({
619
+ ...draft,
620
+ status: draft.status === "running" ? "dispatched" : draft.status
621
+ }))
622
+ };
623
+ if (migrated) {
624
+ await persistActiveState(taskRepoRoot, state);
625
+ }
626
+ return state;
559
627
  }
560
628
  async function persistActiveState(taskRepoRoot, state) {
561
629
  await deps.fs.writeJsonAtomic(resolveRepoPath(taskRepoRoot, MEMORY_REVIEW_STATE_PATH), state);
@@ -600,9 +668,6 @@ export function createAutoMemoryService(deps) {
600
668
  return `sha256:${sha256(content)}`;
601
669
  }
602
670
  return {
603
- ensureTaskSnapshot(baseRepoRoot, taskRepoRoot) {
604
- return ensureTaskMemorySnapshot(deps.fs, baseRepoRoot, taskRepoRoot);
605
- },
606
671
  reconcileTask,
607
672
  getState,
608
673
  getTaskRetrospectiveReadiness,
@@ -616,21 +681,18 @@ export function createAutoMemoryService(deps) {
616
681
  assertHarnessEngineerAvailable
617
682
  };
618
683
  }
619
- export async function ensureTaskMemorySnapshot(fs, baseRepoRoot, taskRepoRoot) {
684
+ export async function assertMemoryBlocksInstalled(fs, taskRepoRoot) {
620
685
  for (const definition of MEMORY_FILE_DEFINITIONS) {
621
- const canonicalPath = resolveRepoPath(baseRepoRoot, definition.path);
622
- if (!(await fs.pathExists(canonicalPath))) {
623
- await fs.writeText(canonicalPath, renderDefaultMemoryFile(definition.title));
686
+ const hostPath = resolveRepoPath(taskRepoRoot, definition.path);
687
+ if (!(await fs.pathExists(hostPath))) {
688
+ throw missingMemoryBlockError(definition.path);
624
689
  }
625
- const snapshotPath = resolveRepoPath(taskRepoRoot, definition.path);
626
- if (!(await fs.pathExists(snapshotPath))) {
627
- await fs.writeText(snapshotPath, await fs.readText(canonicalPath));
690
+ const content = await fs.readText(hostPath);
691
+ if (readVcmMemoryBlock(content) === undefined) {
692
+ throw missingMemoryBlockError(definition.path);
628
693
  }
629
694
  }
630
695
  }
631
- function renderDefaultMemoryFile(title) {
632
- return `# ${title}\n\nNo accumulated project memory yet.\n`;
633
- }
634
696
  function currentDraft(state) {
635
697
  return state.drafts.find((draft) => draft.status !== "completed");
636
698
  }
@@ -649,13 +711,17 @@ function toActiveReview(state) {
649
711
  };
650
712
  }
651
713
  function buildRoleDraftPrompt(taskRepoRoot, state, draft) {
714
+ const roleDefinition = MEMORY_FILE_DEFINITIONS.find((definition) => "role" in definition && definition.role === draft.role);
715
+ if (!roleDefinition) {
716
+ throw new Error(`Missing memory definition for role: ${draft.role}`);
717
+ }
652
718
  return [
653
719
  "[VCM Task Harness Review: Memory Proposal]",
654
720
  "",
655
721
  "Use the vcm-propose-memory skill to submit the assigned proposal.",
656
722
  `Task worktree: ${taskRepoRoot}`,
657
- `Current shared memory: ${resolveRepoPath(taskRepoRoot, `${MEMORY_ROOT}/shared.md`)}`,
658
- `Current role memory: ${resolveRepoPath(taskRepoRoot, `${MEMORY_ROOT}/roles/${draft.role}.md`)}`,
723
+ `Current shared memory block: ${resolveRepoPath(taskRepoRoot, "CLAUDE.md")}`,
724
+ `Current role memory block: ${resolveRepoPath(taskRepoRoot, roleDefinition.path)}`,
659
725
  `Write the draft to: ${resolveRepoPath(taskRepoRoot, draft.path)}`,
660
726
  "",
661
727
  "End the turn after writing the draft."
@@ -672,9 +738,10 @@ function buildHarnessReviewPrompt(taskRepoRoot, state) {
672
738
  `Current memory snapshot: ${path.join(runRoot, "before")}`,
673
739
  `Write the complete reviewed memory set to: ${path.join(runRoot, "after")}`,
674
740
  "",
741
+ "Each snapshot file contains only the matching <VCM-memory> block content, not the full host file.",
675
742
  "Keep only verified, durable, reusable project knowledge. Merge duplicates, remove stale entries, and keep role-specific knowledge in the matching role file.",
676
743
  "Do not record task narrative, temporary state, unverified conclusions, or Harness rules.",
677
- "Do not edit product code, harness files, the canonical base-repository memory, or review metadata.",
744
+ "Do not edit product code, active harness files, active memory blocks, or review metadata.",
678
745
  "All existing files already exist in the after directory. Edit those files in place and end the turn when review is complete."
679
746
  ].join("\n");
680
747
  }
@@ -702,7 +769,15 @@ function requireSafeRunId(runId) {
702
769
  return normalized;
703
770
  }
704
771
  function memoryRunFilePath(runId, snapshot, memoryPath) {
705
- return `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/${snapshot}/${memoryPath.slice(`${MEMORY_ROOT}/`.length)}`;
772
+ return `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/${snapshot}/${memoryPath}`;
773
+ }
774
+ function missingMemoryBlockError(filePath) {
775
+ return new VcmError({
776
+ code: "MEMORY_BLOCK_MISSING",
777
+ message: `VCM memory block is missing from ${filePath}.`,
778
+ statusCode: 409,
779
+ hint: "Refresh the VCM Harness in the active task worktree before using Auto Memory."
780
+ });
706
781
  }
707
782
  function assertCompleteMemorySet(memory) {
708
783
  for (const definition of MEMORY_FILE_DEFINITIONS) {
@@ -1,6 +1,6 @@
1
- import { isGateReviewerRoleName, isHarnessEngineerToolRoleName, isTranslatorToolRoleName, isUserFacingRole, isVcmRoleName } from "../../shared/constants.js";
1
+ import { isGateReviewerRoleName, isHarnessEngineerToolRoleName, isTranslatorToolRoleName, isVcmRoleName } from "../../shared/constants.js";
2
2
  import { VcmError } from "../errors.js";
3
- import { readLatestRoleTurnReply, readTranscriptTurnEvidence } from "./claude-transcript-reply.js";
3
+ import { readTranscriptTurnEvidence } from "./claude-transcript-reply.js";
4
4
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
5
5
  import { getTaskRuntimeRepoRoot } from "./task-service.js";
6
6
  const MAX_ROLE_RETRY_ATTEMPTS = 20;
@@ -74,12 +74,22 @@ export function createClaudeHookService(deps) {
74
74
  async function processTranslatorHook(input) {
75
75
  const eventName = parseHookEvent(input.event.hook_event_name);
76
76
  const context = await getTranslatorHookContext();
77
- const session = await deps.sessionService.recordProjectTranslatorHookEvent(context.project.repoRoot, {
78
- eventName,
79
- sessionId: stringOrUndefined(input.event.session_id),
80
- transcriptPath: stringOrUndefined(input.event.transcript_path),
81
- cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
82
- });
77
+ const session = input.taskSlug === "__project__"
78
+ ? await deps.sessionService.recordProjectTranslatorHookEvent(context.project.repoRoot, {
79
+ eventName,
80
+ sessionId: stringOrUndefined(input.event.session_id),
81
+ transcriptPath: stringOrUndefined(input.event.transcript_path),
82
+ cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
83
+ })
84
+ : await deps.sessionService.recordRoleHookEvent(context.project.repoRoot, {
85
+ taskSlug: input.taskSlug,
86
+ role: input.role,
87
+ eventName,
88
+ sessionId: stringOrUndefined(input.event.session_id),
89
+ transcriptPath: stringOrUndefined(input.event.transcript_path),
90
+ cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd),
91
+ allowSessionMismatch: true
92
+ });
83
93
  await deps.translationWorkerService?.handleTranslatorHook(context.project.repoRoot, eventName, input.taskSlug);
84
94
  return {
85
95
  ok: true,
@@ -93,15 +103,26 @@ export function createClaudeHookService(deps) {
93
103
  async function processHarnessEngineerHook(input) {
94
104
  const eventName = parseHookEvent(input.event.hook_event_name);
95
105
  const context = await getProjectToolHookContext("Harness Engineer");
96
- const session = await deps.sessionService.recordProjectHarnessEngineerHookEvent(context.project.repoRoot, {
97
- eventName,
98
- sessionId: stringOrUndefined(input.event.session_id),
99
- transcriptPath: stringOrUndefined(input.event.transcript_path),
100
- cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
101
- });
106
+ const projectScoped = input.taskSlug === "__project_harness_engineer__";
107
+ const session = projectScoped
108
+ ? await deps.sessionService.recordProjectHarnessEngineerHookEvent(context.project.repoRoot, {
109
+ eventName,
110
+ sessionId: stringOrUndefined(input.event.session_id),
111
+ transcriptPath: stringOrUndefined(input.event.transcript_path),
112
+ cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
113
+ })
114
+ : await deps.sessionService.recordRoleHookEvent(context.project.repoRoot, {
115
+ taskSlug: input.taskSlug,
116
+ role: input.role,
117
+ eventName,
118
+ sessionId: stringOrUndefined(input.event.session_id),
119
+ transcriptPath: stringOrUndefined(input.event.transcript_path),
120
+ cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd),
121
+ allowSessionMismatch: true
122
+ });
102
123
  const activeTask = deps.autoMemoryService
103
124
  ? (await deps.taskService.listTasks(context.project.repoRoot))
104
- .find((task) => task.cleanupStatus !== "cleaned")
125
+ .find((task) => task.cleanupStatus !== "cleaned" && (projectScoped || task.taskSlug === input.taskSlug))
105
126
  : undefined;
106
127
  const memoryHandled = activeTask
107
128
  ? await deps.autoMemoryService?.handleHarnessEngineerHook({
@@ -221,13 +242,13 @@ export function createClaudeHookService(deps) {
221
242
  throwUnsupportedEvent(eventName);
222
243
  }
223
244
  const context = await getHookContext(input);
224
- if (await isDuplicateCompletedStop(context, input)) {
225
- return completedHookResult(input, eventName);
226
- }
227
245
  const memoryResult = await processAutoMemoryRoleHook(input, context, eventName);
228
246
  if (memoryResult) {
229
247
  return memoryResult;
230
248
  }
249
+ if (await isDuplicateCompletedStop(context, input)) {
250
+ return completedHookResult(input, eventName);
251
+ }
231
252
  await clearStopFailureRecoveryState(context, input.role);
232
253
  if (options.allowBlock && deps.jobGuard) {
233
254
  const verdict = await deps.jobGuard.evaluateStop({
@@ -381,7 +402,6 @@ export function createClaudeHookService(deps) {
381
402
  cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
382
403
  });
383
404
  if (boundToTask) {
384
- const userFacingReply = await captureUserFacingReply(eventName, input.role, session);
385
405
  await deps.roundService.recordClaudeHookEvent({
386
406
  repoRoot: context.project.repoRoot,
387
407
  stateRepoRoot: context.taskRepoRoot,
@@ -389,7 +409,6 @@ export function createClaudeHookService(deps) {
389
409
  taskSlug: context.taskSlug,
390
410
  role: input.role,
391
411
  eventName,
392
- ...(userFacingReply ? { userFacingReply } : {}),
393
412
  ...(options.settleGuard
394
413
  ? {
395
414
  settleGuard: async () => {
@@ -459,6 +478,17 @@ export function createClaudeHookService(deps) {
459
478
  transcriptPath: stringOrUndefined(input.event.transcript_path),
460
479
  cwd: stringOrUndefined(input.event.cwd) ?? stringOrUndefined(input.event.new_cwd)
461
480
  });
481
+ const boundToTask = await isHookSessionBoundToTask(context, input.role);
482
+ if (boundToTask && eventName !== "PostCompact") {
483
+ await deps.roundService.recordClaudeHookEvent({
484
+ repoRoot: context.project.repoRoot,
485
+ stateRepoRoot: context.taskRepoRoot,
486
+ stateRoot: context.config.stateRoot,
487
+ taskSlug: context.taskSlug,
488
+ role: input.role,
489
+ eventName
490
+ });
491
+ }
462
492
  await deps.autoMemoryService.handleRoleHook({
463
493
  baseRepoRoot: context.project.repoRoot,
464
494
  taskRepoRoot: context.taskRepoRoot,
@@ -755,21 +785,6 @@ function parseHookEvent(value) {
755
785
  hint: "VCM accepts UserPromptSubmit, Stop, StopFailure, and PostCompact hooks only."
756
786
  });
757
787
  }
758
- // On a user-facing role's Stop, best-effort capture its last user-facing turn
759
- // text to seed the await-user pause message. Capture failure is non-fatal and
760
- // must never block turn-end, so a missing/failed read just omits the field.
761
- async function captureUserFacingReply(eventName, role, session) {
762
- if (eventName !== "Stop" || !isUserFacingRole(role) || !session) {
763
- return undefined;
764
- }
765
- try {
766
- const reply = await readLatestRoleTurnReply(session);
767
- return reply ? { text: reply.text, truncated: reply.truncated } : undefined;
768
- }
769
- catch {
770
- return undefined;
771
- }
772
- }
773
788
  function throwUnsupportedEvent(eventName) {
774
789
  throw new VcmError({
775
790
  code: "HOOK_EVENT_UNSUPPORTED",
@@ -26,7 +26,7 @@ export function createCommandDispatcher(deps) {
26
26
  hint: `Start the ${input.role} session before sending a role command.`
27
27
  });
28
28
  }
29
- const instruction = `Please read and execute the role command at: ${commandPath}`;
29
+ const instruction = `[VCM ROLE COMMAND]\nRead and execute the role command at: ${commandPath}\n[/VCM ROLE COMMAND]`;
30
30
  await submitTerminalInput(deps.runtime, session.id, instruction);
31
31
  return {
32
32
  taskSlug: input.taskSlug,