vibe-coding-master 0.6.22 → 0.6.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -333,7 +333,7 @@ Gateway can:
333
333
  - create a task
334
334
  - close a task with confirmation
335
335
  - send plain text to Project Manager
336
- - push PM replies back to the active chat
336
+ - push the PM Round-final reply and Round status back to the active chat
337
337
  - translate mobile messages when Gateway translation is enabled
338
338
 
339
339
  Gateway does not expose the embedded terminal and does not send directly to
@@ -361,6 +361,12 @@ Common commands:
361
361
  Gateway credentials and audit logs are stored in app-local state, not connected
362
362
  repositories.
363
363
 
364
+ When Gateway starts, VCM enables conversation translation, auto-send, and the
365
+ `Round final reply` scope. On a normal Round end, Gateway sends the PM original
366
+ reply first, then reuses the matching translation already produced for the
367
+ translation panel. `/retry` explicitly creates a new translation only when the
368
+ previous Gateway translation failed or was unavailable.
369
+
364
370
  ## Harness Studio
365
371
 
366
372
  Harness Studio is the UI for VCM harness maintenance.
@@ -371,8 +377,10 @@ Use it to:
371
377
  - run bootstrap
372
378
  - open Harness Engineer
373
379
  - review harness files
380
+ - view and edit shared or role-specific VCM memory
381
+ - review and revert memory changes recorded in the active task worktree
374
382
  - copy file paths for discussion
375
- - review task harness after a task completes
383
+ - review task harness after post-task memory processing completes
376
384
  - inspect commit diffs for harness changes
377
385
  - merge task harness commits back to the connected repository branch when
378
386
  appropriate
@@ -380,6 +388,32 @@ Use it to:
380
388
  Harness Engineer is project-scoped and resumable. When it performs task work,
381
389
  VCM runs it from the active task worktree.
382
390
 
391
+ ### Auto Memory
392
+
393
+ Enable `Auto memory` in the `VCM Harness` sidebar group to collect durable role
394
+ knowledge after a complete task passes Final Acceptance. Project Manager,
395
+ Architect, Coder, Tester, and an enabled Gate Reviewer submit memory drafts in
396
+ sequence. Harness Engineer verifies and consolidates them before VCM applies
397
+ the result.
398
+
399
+ Canonical memory is stored under the base repository's `.ai/vcm/memory/`.
400
+ Harness Studio shows current memory and task-local applied history. Memory is
401
+ applied before user review; while the task worktree remains available, the user
402
+ can edit current memory or revert a recorded change.
403
+
404
+ Post-task processing is ordered by the backend:
405
+
406
+ ```text
407
+ Final Acceptance
408
+ -> Auto Memory, when enabled
409
+ -> Task Harness Retrospective
410
+ ```
411
+
412
+ Auto Memory adds no retrospective prerequisite when it is disabled. When it is
413
+ enabled, both automatic and manual retrospective requests wait until memory has
414
+ been applied for the current Final Acceptance. A failed memory review must be
415
+ retried from Harness Studio before retrospective can run.
416
+
383
417
  ## Closing a Task
384
418
 
385
419
  `Close Task` is destructive.
@@ -480,5 +514,5 @@ npm start
480
514
  - `docs/ARCHITECTURE.md`: repository architecture
481
515
  - `docs/TESTING.md`: validation strategy
482
516
  - `docs/vcm-cc-best-practices.md`: current VCM Claude Code harness practice
483
- - `docs/v0.5-custom-workflow-plan.md`: future custom workflow plan
517
+ - `docs/v0.5-custom-workflow-plan.md`: deferred custom workflow proposal
484
518
  - `docs/cc-best-practices.md`: archived generic Claude Code harness notes
@@ -47,6 +47,14 @@ export function createNodeFileSystemAdapter() {
47
47
  await fs.writeFile(targetPath, content, "utf8");
48
48
  });
49
49
  },
50
+ async writeTextAtomic(targetPath, content) {
51
+ await runFileOperation(async () => {
52
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
53
+ const tempPath = `${targetPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
54
+ await fs.writeFile(tempPath, content, "utf8");
55
+ await fs.rename(tempPath, targetPath);
56
+ });
57
+ },
50
58
  async appendText(targetPath, content) {
51
59
  await runFileOperation(async () => {
52
60
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
@@ -66,11 +66,13 @@ export function registerHarnessRoutes(app, deps) {
66
66
  });
67
67
  app.post("/api/projects/harness/bootstrap/start", async (request) => {
68
68
  const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
69
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
69
70
  await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
70
71
  return deps.harnessService.startHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
71
72
  });
72
73
  app.post("/api/projects/harness/bootstrap/restart", async (request) => {
73
74
  const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
75
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
74
76
  await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
75
77
  return deps.harnessService.restartHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
76
78
  });
@@ -80,6 +82,7 @@ export function registerHarnessRoutes(app, deps) {
80
82
  });
81
83
  app.post("/api/projects/harness/bootstrap/run", async (request) => {
82
84
  const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
85
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
83
86
  await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
84
87
  return deps.harnessService.runHarnessBootstrap(project.repoRoot, task.worktreePath);
85
88
  });
@@ -93,14 +96,26 @@ export function registerHarnessRoutes(app, deps) {
93
96
  });
94
97
  app.post("/api/projects/harness/engineer/session/start", async (request) => {
95
98
  const project = await requireCurrentProject(deps.projectService);
99
+ if (request.body?.taskSlug) {
100
+ const task = await deps.taskService.loadTask(project.repoRoot, request.body.taskSlug);
101
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
102
+ }
96
103
  return deps.sessionService.startProjectHarnessEngineerSession(project.repoRoot, request.body);
97
104
  });
98
105
  app.post("/api/projects/harness/engineer/session/resume", async (request) => {
99
106
  const project = await requireCurrentProject(deps.projectService);
107
+ if (request.body?.taskSlug) {
108
+ const task = await deps.taskService.loadTask(project.repoRoot, request.body.taskSlug);
109
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
110
+ }
100
111
  return deps.sessionService.resumeProjectHarnessEngineerSession(project.repoRoot, request.body);
101
112
  });
102
113
  app.post("/api/projects/harness/engineer/session/restart", async (request) => {
103
114
  const project = await requireCurrentProject(deps.projectService);
115
+ if (request.body?.taskSlug) {
116
+ const task = await deps.taskService.loadTask(project.repoRoot, request.body.taskSlug);
117
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
118
+ }
104
119
  return deps.sessionService.restartProjectHarnessEngineerSession(project.repoRoot, request.body);
105
120
  });
106
121
  app.post("/api/projects/harness/engineer/session/stop", async () => {
@@ -127,6 +142,10 @@ export function registerHarnessRoutes(app, deps) {
127
142
  });
128
143
  }
129
144
  const taskSlug = await normalizeOptionalTaskSlug(deps, project.repoRoot, request.body?.taskSlug);
145
+ if (taskSlug && (action === "approve" || action === "comment")) {
146
+ const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
147
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
148
+ }
130
149
  return deps.harnessFeedbackService.decide(project.repoRoot, {
131
150
  action,
132
151
  taskSlug,
@@ -135,6 +154,14 @@ export function registerHarnessRoutes(app, deps) {
135
154
  });
136
155
  app.post("/api/projects/harness/task-retrospective", async (request) => {
137
156
  const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
157
+ await deps.autoMemoryService.assertTaskRetrospectiveReady({
158
+ baseRepoRoot: project.repoRoot,
159
+ taskRepoRoot: task.worktreePath,
160
+ taskSlug: task.taskSlug,
161
+ handoffDir: task.handoffDir,
162
+ roundReady: true
163
+ });
164
+ await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
138
165
  const trigger = request.body?.trigger === "auto" ? "auto" : "manual";
139
166
  return deps.harnessFeedbackService.startTaskRetrospective(project.repoRoot, {
140
167
  taskSlug: task.taskSlug,
@@ -143,6 +170,33 @@ export function registerHarnessRoutes(app, deps) {
143
170
  trigger
144
171
  });
145
172
  });
173
+ app.get("/api/projects/harness/memory", async (request) => {
174
+ const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
175
+ return deps.autoMemoryService.getState(project.repoRoot, task.worktreePath);
176
+ });
177
+ app.get("/api/projects/harness/memory/file", async (request) => {
178
+ const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
179
+ return deps.autoMemoryService.getFile(project.repoRoot, task.worktreePath, request.query.path ?? "");
180
+ });
181
+ app.put("/api/projects/harness/memory/file", async (request) => {
182
+ const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug ?? request.body?.taskSlug);
183
+ if (typeof request.body?.content !== "string") {
184
+ throw new VcmError({
185
+ code: "MEMORY_FILE_CONTENT_INVALID",
186
+ message: "Memory file content must be a string.",
187
+ statusCode: 400
188
+ });
189
+ }
190
+ return deps.autoMemoryService.updateFile(project.repoRoot, task.worktreePath, task.taskSlug, request.query.path ?? "", request.body.content);
191
+ });
192
+ app.post("/api/projects/harness/memory/revert", async (request) => {
193
+ const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
194
+ return deps.autoMemoryService.revertRun(project.repoRoot, task.worktreePath, request.body?.runId ?? "");
195
+ });
196
+ app.post("/api/projects/harness/memory/retry", async (request) => {
197
+ const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
198
+ return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
199
+ });
146
200
  }
147
201
  function degradedHarnessStatus(error) {
148
202
  return {
@@ -18,14 +18,16 @@ export function registerRuntimeStateRoutes(app, deps) {
18
18
  harnessStatus: null,
19
19
  harnessBootstrapStatus: null,
20
20
  harnessFeedbackState,
21
+ autoMemoryState: null,
21
22
  gatewayStatus: coordinated.gatewayStatus
22
23
  };
23
24
  }
24
25
  try {
25
26
  const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
26
- const [harnessStatus, harnessBootstrapStatus] = await Promise.all([
27
+ const [harnessStatus, harnessBootstrapStatus, autoMemoryState] = await Promise.all([
27
28
  withOpenFileLimitFallback(() => deps.harnessService.getHarnessStatus(task.worktreePath), (error) => degradedHarnessStatus(error)),
28
- withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath), (error) => degradedBootstrapStatus(error))
29
+ withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath), (error) => degradedBootstrapStatus(error)),
30
+ deps.autoMemoryService.getState(project.repoRoot, task.worktreePath)
29
31
  ]);
30
32
  return {
31
33
  translatorSession,
@@ -34,6 +36,7 @@ export function registerRuntimeStateRoutes(app, deps) {
34
36
  harnessStatus,
35
37
  harnessBootstrapStatus,
36
38
  harnessFeedbackState,
39
+ autoMemoryState,
37
40
  gatewayStatus: coordinated.gatewayStatus
38
41
  };
39
42
  }
@@ -46,6 +49,7 @@ export function registerRuntimeStateRoutes(app, deps) {
46
49
  harnessStatus: degradedHarnessStatus(error),
47
50
  harnessBootstrapStatus: degradedBootstrapStatus(error),
48
51
  harnessFeedbackState,
52
+ autoMemoryState: null,
49
53
  gatewayStatus: coordinated.gatewayStatus
50
54
  };
51
55
  }
@@ -9,6 +9,7 @@ import { createCommandDispatcher } from "./services/command-dispatcher.js";
9
9
  import { createClaudeHookService } from "./services/claude-hook-service.js";
10
10
  import { createGitAdapter } from "./adapters/git-adapter.js";
11
11
  import { createAppSettingsService } from "./services/app-settings-service.js";
12
+ import { createAutoMemoryService } from "./services/auto-memory-service.js";
12
13
  import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
13
14
  import { createGateReviewService } from "./services/gate-review-service.js";
14
15
  import { createHarnessFeedbackService } from "./services/harness-feedback-service.js";
@@ -94,6 +95,7 @@ export async function createServer(deps, options = {}) {
94
95
  projectService: deps.projectService,
95
96
  harnessService: deps.harnessService,
96
97
  harnessFeedbackService: deps.harnessFeedbackService,
98
+ autoMemoryService: deps.autoMemoryService,
97
99
  sessionService: deps.sessionService,
98
100
  taskService: deps.taskService
99
101
  });
@@ -104,6 +106,7 @@ export async function createServer(deps, options = {}) {
104
106
  translationWorkerService: deps.translationWorkerService,
105
107
  harnessService: deps.harnessService,
106
108
  harnessFeedbackService: deps.harnessFeedbackService,
109
+ autoMemoryService: deps.autoMemoryService,
107
110
  runtimeCoordinator: deps.runtimeCoordinator
108
111
  });
109
112
  registerTaskRoutes(app, {
@@ -222,6 +225,16 @@ export function createDefaultServerDeps(options = {}) {
222
225
  runtime,
223
226
  sessionService
224
227
  });
228
+ const autoMemoryService = createAutoMemoryService({
229
+ fs,
230
+ runtime,
231
+ sessionService,
232
+ appSettings,
233
+ async isHarnessEngineerAvailable(repoRoot) {
234
+ const state = await harnessFeedbackService.getState(repoRoot);
235
+ return state.status === "idle";
236
+ }
237
+ });
225
238
  const commandDispatcher = createCommandDispatcher({
226
239
  runtime,
227
240
  sessionService,
@@ -314,6 +327,7 @@ export function createDefaultServerDeps(options = {}) {
314
327
  translationService,
315
328
  harnessService,
316
329
  harnessFeedbackService,
330
+ autoMemoryService,
317
331
  roundService,
318
332
  gatewayService,
319
333
  async getStateRoot(repoRoot) {
@@ -338,6 +352,7 @@ export function createDefaultServerDeps(options = {}) {
338
352
  runtime,
339
353
  harnessService,
340
354
  harnessFeedbackService,
355
+ autoMemoryService,
341
356
  gatewayService,
342
357
  jobGuard: createJobGuardService(),
343
358
  translationWorkerService
@@ -363,6 +378,7 @@ export function createDefaultServerDeps(options = {}) {
363
378
  artifactService,
364
379
  harnessService,
365
380
  harnessFeedbackService,
381
+ autoMemoryService,
366
382
  commandDispatcher,
367
383
  claudeHookService,
368
384
  messageService,
@@ -242,6 +242,7 @@ function normalizePreferences(input) {
242
242
  roleRetryEnabled: candidate.roleRetryEnabled !== false,
243
243
  permissionRequestMode: normalizePermissionRequestMode(candidate.permissionRequestMode),
244
244
  autoTaskHarnessReviewEnabled: candidate.autoTaskHarnessReviewEnabled === true,
245
+ autoMemoryEnabled: candidate.autoMemoryEnabled === true,
245
246
  translationEnabled: candidate.translationEnabled === true,
246
247
  translationAutoSendEnabled: candidate.translationAutoSendEnabled === true,
247
248
  translationTargetLanguage: normalizeTranslationTargetLanguage(candidate.translationTargetLanguage),