vibe-coding-master 0.7.1 → 0.7.3

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.
@@ -131,27 +131,6 @@ export function registerHarnessRoutes(app, deps) {
131
131
  const taskSlug = await normalizeOptionalTaskSlug(deps, project.repoRoot, request.query.taskSlug);
132
132
  return deps.harnessFeedbackService.getState(project.repoRoot, taskSlug);
133
133
  });
134
- app.post("/api/projects/harness/feedback/decision", async (request) => {
135
- const project = await requireCurrentProject(deps.projectService);
136
- const action = request.body?.action;
137
- if (action !== "approve" && action !== "reject" && action !== "comment" && action !== "cancel") {
138
- throw new VcmError({
139
- code: "HARNESS_FEEDBACK_DECISION_INVALID",
140
- message: "Harness feedback decision action is invalid.",
141
- statusCode: 400
142
- });
143
- }
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
- }
149
- return deps.harnessFeedbackService.decide(project.repoRoot, {
150
- action,
151
- taskSlug,
152
- comment: typeof request.body?.comment === "string" ? request.body.comment : undefined
153
- });
154
- });
155
134
  app.post("/api/projects/harness/task-retrospective", async (request) => {
156
135
  const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
157
136
  const trigger = request.body?.trigger === "auto" ? "auto" : "manual";
@@ -233,9 +233,8 @@ export function createDefaultServerDeps(options = {}) {
233
233
  runtime,
234
234
  sessionService,
235
235
  appSettings,
236
- async isHarnessEngineerAvailable(repoRoot) {
237
- const state = await harnessFeedbackService.getState(repoRoot);
238
- return state.status === "idle";
236
+ async isHarnessEngineerAvailable() {
237
+ return true;
239
238
  }
240
239
  });
241
240
  const commandDispatcher = createCommandDispatcher({
@@ -347,7 +346,6 @@ export function createDefaultServerDeps(options = {}) {
347
346
  appSettings,
348
347
  runtime,
349
348
  harnessService,
350
- harnessFeedbackService,
351
349
  autoMemoryService,
352
350
  gatewayService,
353
351
  jobGuard: createJobGuardService(),
@@ -126,7 +126,6 @@ export function createClaudeHookService(deps) {
126
126
  sessionId: session?.id,
127
127
  claudeSessionId: stringOrUndefined(input.event.session_id)
128
128
  });
129
- await deps.harnessFeedbackService?.recordHarnessEngineerHook(context.project.repoRoot, eventName);
130
129
  return {
131
130
  ok: true,
132
131
  eventName,
@@ -1,22 +1,28 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { checkMarkdownArtifact, readArtifactSectionValue } from "../../shared/validation/artifact-check.js";
4
- import { resolveRepoPath, toRepoRelativePath } from "../adapters/filesystem.js";
4
+ import { resolveRepoPath } from "../adapters/filesystem.js";
5
5
  import { VcmError } from "../errors.js";
6
6
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
7
7
  const FEEDBACK_ROOT = ".ai/vcm/harness-feedback";
8
8
  const PENDING_DIR = `${FEEDBACK_ROOT}/pending`;
9
- const ACTIVE_DIR = `${FEEDBACK_ROOT}/active`;
10
- const COMPLETED_DIR = `${FEEDBACK_ROOT}/completed`;
11
9
  const TASK_RETROSPECTIVE_DIR = `${FEEDBACK_ROOT}/task-retrospectives`;
12
- const STATE_PATH = `${FEEDBACK_ROOT}/state.json`;
10
+ const LEGACY_STATE_PATH = `${FEEDBACK_ROOT}/state.json`;
13
11
  export function createHarnessFeedbackService(deps) {
14
12
  const now = deps.now ?? (() => new Date().toISOString());
15
- async function getState(repoRoot, activeTaskSlug) {
16
- await maybeDispatchNext(repoRoot, activeTaskSlug);
17
- return buildStateReport(repoRoot);
13
+ async function getState(repoRoot, _activeTaskSlug) {
14
+ await cleanupLegacyState(repoRoot);
15
+ const pending = await listPendingFeedback(repoRoot);
16
+ return {
17
+ version: 1,
18
+ status: pending.length > 0 ? "queued" : "idle",
19
+ queuedCount: pending.length,
20
+ pending,
21
+ warnings: []
22
+ };
18
23
  }
19
24
  async function startTaskRetrospective(repoRoot, input) {
25
+ await cleanupLegacyState(repoRoot);
20
26
  const taskSlug = input.taskSlug.trim();
21
27
  if (!taskSlug) {
22
28
  throw new VcmError({
@@ -25,22 +31,13 @@ export function createHarnessFeedbackService(deps) {
25
31
  statusCode: 409
26
32
  });
27
33
  }
28
- const state = await loadStoredState(repoRoot);
29
- if (state) {
30
- throw new VcmError({
31
- code: "HARNESS_FEEDBACK_ACTIVE",
32
- message: "Harness feedback is already active.",
33
- statusCode: 409,
34
- hint: "Review, approve, comment, or reject the current Harness feedback before starting Task Harness Retrospective."
35
- });
36
- }
37
34
  const existingMarker = await loadTaskRetrospectiveMarker(repoRoot, taskSlug);
38
35
  if (existingMarker) {
39
36
  throw new VcmError({
40
37
  code: "TASK_HARNESS_RETROSPECTIVE_EXISTS",
41
38
  message: `Task Harness Retrospective has already been triggered for task: ${taskSlug}`,
42
39
  statusCode: 409,
43
- hint: "Review the existing Harness feedback item instead of starting another retrospective for the same task."
40
+ hint: "Review the existing retrospective result instead of starting another retrospective for the same task."
44
41
  });
45
42
  }
46
43
  const finalAcceptancePath = path.posix.join(input.handoffDir, "final-acceptance.md");
@@ -62,204 +59,39 @@ export function createHarnessFeedbackService(deps) {
62
59
  }
63
60
  const session = await ensureIdleHarnessEngineer(repoRoot, taskSlug);
64
61
  const timestamp = now();
65
- const finalAcceptanceHash = `sha256:${sha256(finalAcceptanceContent)}`;
66
- const id = sanitizeFeedbackId(`${timestamp}-task-retrospective-${taskSlug}`);
67
- const feedbackPath = `${ACTIVE_DIR}/${id}/feedback.md`;
68
- const analysisPath = `${ACTIVE_DIR}/${id}/analysis.md`;
69
- const applyReportPath = `${ACTIVE_DIR}/${id}/apply-report.md`;
70
- const active = {
71
- id,
72
- title: `Task Harness Retrospective: ${taskSlug}`,
73
- path: feedbackPath,
74
- source: "task-retrospective",
62
+ const analysisPath = `${TASK_RETROSPECTIVE_DIR}/${sanitizeFeedbackId(taskSlug)}.md`;
63
+ await persistTaskRetrospectiveMarker(repoRoot, {
64
+ version: 1,
75
65
  taskSlug,
76
- summary: "Review the completed task workflow for reusable harness problems.",
77
66
  trigger: input.trigger,
78
- finalAcceptanceHash,
79
- feedbackPath,
67
+ status: "triggered",
80
68
  analysisPath,
81
- applyReportPath,
82
- startedAt: timestamp,
83
- updatedAt: timestamp,
84
- lastPromptAt: timestamp
85
- };
86
- const nextState = {
87
- version: 1,
88
- status: "analyzing",
89
- active
90
- };
91
- await deps.fs.ensureDir(resolveRepoPath(repoRoot, path.posix.dirname(feedbackPath)));
92
- await deps.fs.writeText(resolveRepoPath(repoRoot, feedbackPath), renderTaskRetrospectiveFeedback(active, finalAcceptancePath));
93
- await persistStoredState(repoRoot, nextState);
94
- await persistTaskRetrospectiveMarker(repoRoot, active, "analyzing");
95
- await submitTerminalInput(deps.runtime, session.id, buildTaskRetrospectivePrompt(repoRoot, active));
96
- return buildStateReport(repoRoot);
97
- }
98
- async function decide(repoRoot, input) {
99
- const state = await loadStoredState(repoRoot);
100
- if (state && input.action === "cancel") {
101
- await completeActive(repoRoot, state, "canceled", input.comment);
102
- await clearStoredState(repoRoot);
103
- return getState(repoRoot, input.taskSlug);
104
- }
105
- if (!state || state.status !== "awaiting_user_approval") {
106
- throw new VcmError({
107
- code: "HARNESS_FEEDBACK_NOT_AWAITING_APPROVAL",
108
- message: "There is no Harness feedback waiting for user approval.",
109
- statusCode: 409
110
- });
111
- }
112
- if (input.action === "reject") {
113
- await completeActive(repoRoot, state, "rejected", input.comment);
114
- await clearStoredState(repoRoot);
115
- return getState(repoRoot, input.taskSlug);
116
- }
117
- const taskSlug = input.taskSlug?.trim();
118
- if (!taskSlug) {
119
- throw new VcmError({
120
- code: "HARNESS_FEEDBACK_TASK_REQUIRED",
121
- message: "Select an active task before asking Harness Engineer to continue feedback work.",
122
- statusCode: 409
123
- });
124
- }
125
- if (input.action === "comment") {
126
- const session = await ensureIdleHarnessEngineer(repoRoot, taskSlug);
127
- const timestamp = now();
128
- const nextState = {
129
- ...state,
130
- status: "analyzing",
131
- active: {
132
- ...state.active,
133
- updatedAt: timestamp,
134
- lastPromptAt: timestamp
135
- }
136
- };
137
- await persistStoredState(repoRoot, nextState);
138
- await persistTaskRetrospectiveMarker(repoRoot, nextState.active, "analyzing");
139
- await submitTerminalInput(deps.runtime, session.id, buildFeedbackCommentPrompt(repoRoot, nextState.active, input.comment ?? ""));
140
- return buildStateReport(repoRoot);
141
- }
142
- const session = await ensureIdleHarnessEngineer(repoRoot, taskSlug);
143
- const timestamp = now();
144
- const nextState = {
145
- ...state,
146
- status: "applying",
147
- active: {
148
- ...state.active,
149
- updatedAt: timestamp,
150
- lastPromptAt: timestamp
151
- }
152
- };
153
- await persistStoredState(repoRoot, nextState);
154
- await persistTaskRetrospectiveMarker(repoRoot, nextState.active, "applying");
155
- await submitTerminalInput(deps.runtime, session.id, buildFeedbackApplyPrompt(repoRoot, nextState.active, input.comment ?? ""));
156
- return buildStateReport(repoRoot);
157
- }
158
- async function recordHarnessEngineerHook(repoRoot, eventName) {
159
- if (eventName !== "Stop" && eventName !== "StopFailure") {
160
- return;
161
- }
162
- const state = await loadStoredState(repoRoot);
163
- if (!state) {
164
- return;
165
- }
166
- const timestamp = now();
167
- if (state.status === "analyzing") {
168
- const nextState = {
169
- ...state,
170
- status: "awaiting_user_approval",
171
- active: {
172
- ...state.active,
173
- updatedAt: timestamp
174
- }
175
- };
176
- await persistStoredState(repoRoot, nextState);
177
- await persistTaskRetrospectiveMarker(repoRoot, nextState.active, "awaiting_user_approval", timestamp);
178
- return;
179
- }
180
- if (state.status === "applying") {
181
- await completeActive(repoRoot, {
182
- ...state,
183
- active: {
184
- ...state.active,
185
- updatedAt: timestamp
186
- }
187
- }, "applied");
188
- await clearStoredState(repoRoot);
189
- }
190
- }
191
- async function assertHarnessEngineerAvailable(repoRoot) {
192
- const state = await loadStoredState(repoRoot);
193
- if (!state) {
194
- return;
195
- }
196
- throw new VcmError({
197
- code: "HARNESS_ENGINEER_FEEDBACK_ACTIVE",
198
- message: "Harness Engineer is reserved for an active Harness feedback item.",
199
- statusCode: 409,
200
- hint: state.status === "awaiting_user_approval"
201
- ? "Review, approve, comment, or reject the current Harness feedback before starting another Harness Engineer task."
202
- : "Wait for the current Harness feedback turn to finish before starting another Harness Engineer task."
69
+ finalAcceptanceHash: `sha256:${sha256(finalAcceptanceContent)}`,
70
+ createdAt: timestamp,
71
+ updatedAt: timestamp
203
72
  });
73
+ await submitTerminalInput(deps.runtime, session.id, buildTaskRetrospectivePrompt(repoRoot, analysisPath));
74
+ return getState(repoRoot);
204
75
  }
205
- async function maybeDispatchNext(repoRoot, activeTaskSlug) {
206
- const state = await loadStoredState(repoRoot);
207
- if (state) {
208
- return;
209
- }
210
- const pending = await listPendingFeedback(repoRoot);
211
- const next = pending[0];
212
- const taskSlug = activeTaskSlug?.trim();
213
- if (!next || !taskSlug) {
214
- return;
215
- }
216
- const session = await getIdleHarnessEngineer(repoRoot, taskSlug);
217
- if (!session) {
218
- return;
219
- }
220
- const timestamp = now();
221
- const analysisPath = `${ACTIVE_DIR}/${next.id}/analysis.md`;
222
- const applyReportPath = `${ACTIVE_DIR}/${next.id}/apply-report.md`;
223
- const active = {
224
- ...next,
225
- feedbackPath: next.path,
226
- source: next.source ?? "role-feedback",
227
- analysisPath,
228
- applyReportPath,
229
- startedAt: timestamp,
230
- updatedAt: timestamp,
231
- lastPromptAt: timestamp
232
- };
233
- const nextState = {
234
- version: 1,
235
- status: "analyzing",
236
- active
237
- };
238
- await persistStoredState(repoRoot, nextState);
239
- await deps.fs.ensureDir(resolveRepoPath(repoRoot, path.posix.dirname(analysisPath)));
240
- await submitTerminalInput(deps.runtime, session.id, await buildFeedbackAnalysisPrompt(repoRoot, active));
76
+ async function assertHarnessEngineerAvailable(_repoRoot) {
77
+ return undefined;
241
78
  }
242
- async function getIdleHarnessEngineer(repoRoot, taskSlug) {
79
+ async function ensureIdleHarnessEngineer(repoRoot, taskSlug) {
243
80
  const existing = await deps.sessionService.getProjectHarnessEngineerSession(repoRoot);
244
81
  if (existing?.status === "running" && existing.activityStatus === "running") {
245
- return undefined;
82
+ throw new VcmError({
83
+ code: "HARNESS_ENGINEER_BUSY",
84
+ message: "Harness Engineer is busy or unavailable.",
85
+ statusCode: 409,
86
+ hint: "Wait for the current Harness Engineer turn to finish, then retry."
87
+ });
246
88
  }
247
89
  const session = await deps.sessionService.ensureProjectHarnessEngineerSession(repoRoot, {
248
90
  taskSlug,
249
91
  cols: 120,
250
92
  rows: 32
251
93
  });
252
- if (session.status !== "running" || session.activityStatus === "running") {
253
- return undefined;
254
- }
255
- if (!deps.runtime.getSession(session.id)) {
256
- return undefined;
257
- }
258
- return session;
259
- }
260
- async function ensureIdleHarnessEngineer(repoRoot, taskSlug) {
261
- const session = await getIdleHarnessEngineer(repoRoot, taskSlug);
262
- if (!session) {
94
+ if (session.status !== "running" || session.activityStatus === "running" || !deps.runtime.getSession(session.id)) {
263
95
  throw new VcmError({
264
96
  code: "HARNESS_ENGINEER_BUSY",
265
97
  message: "Harness Engineer is busy or unavailable.",
@@ -269,54 +101,6 @@ export function createHarnessFeedbackService(deps) {
269
101
  }
270
102
  return session;
271
103
  }
272
- async function buildStateReport(repoRoot) {
273
- const [state, pending] = await Promise.all([
274
- loadStoredState(repoRoot),
275
- listPendingFeedback(repoRoot)
276
- ]);
277
- if (!state) {
278
- return {
279
- version: 1,
280
- status: pending.length > 0 ? "queued" : "idle",
281
- queuedCount: pending.length,
282
- pending,
283
- warnings: []
284
- };
285
- }
286
- const active = await readActiveItem(repoRoot, state);
287
- return {
288
- version: 1,
289
- status: state.status,
290
- queuedCount: Math.max(0, pending.length - (pending.some((item) => item.id === state.active.id) ? 1 : 0)),
291
- pending: pending.filter((item) => item.id !== state.active.id),
292
- active,
293
- warnings: []
294
- };
295
- }
296
- async function readActiveItem(repoRoot, state) {
297
- const feedbackContent = await readOptionalText(repoRoot, state.active.feedbackPath) ?? "";
298
- const analysisContent = await readOptionalText(repoRoot, state.active.analysisPath);
299
- const applyReportContent = await readOptionalText(repoRoot, state.active.applyReportPath);
300
- return {
301
- id: state.active.id,
302
- title: state.active.title,
303
- path: state.active.path,
304
- source: state.active.source,
305
- reporterRole: state.active.reporterRole,
306
- taskSlug: state.active.taskSlug,
307
- summary: state.active.summary,
308
- status: state.status,
309
- startedAt: state.active.startedAt,
310
- updatedAt: state.active.updatedAt,
311
- trigger: state.active.trigger,
312
- finalAcceptanceHash: state.active.finalAcceptanceHash,
313
- feedbackContent,
314
- analysisPath: state.active.analysisPath,
315
- analysisContent,
316
- applyReportPath: state.active.applyReportPath,
317
- applyReportContent
318
- };
319
- }
320
104
  async function listPendingFeedback(repoRoot) {
321
105
  const pendingDir = resolveRepoPath(repoRoot, PENDING_DIR);
322
106
  if (!(await deps.fs.pathExists(pendingDir))) {
@@ -350,135 +134,37 @@ export function createHarnessFeedbackService(deps) {
350
134
  summary: metadata.summary
351
135
  };
352
136
  }
353
- async function buildFeedbackAnalysisPrompt(repoRoot, active) {
354
- const feedback = await readOptionalText(repoRoot, active.feedbackPath) ?? "";
355
- return [
356
- "[VCM Harness Feedback Analysis]",
357
- "",
358
- "Analyze this harness feedback. Do not edit files yet.",
359
- "",
360
- `Base repository root: ${repoRoot}`,
361
- `Feedback file: ${resolveRepoPath(repoRoot, active.feedbackPath)}`,
362
- `Result path: ${resolveRepoPath(repoRoot, active.analysisPath)}`,
363
- "",
364
- "Rules:",
365
- "- Decide whether the reported issue is a real reusable harness problem.",
366
- "- Inspect relevant harness files before judging.",
367
- "- If the issue is not real or does not need a harness change, say so in the result file.",
368
- "- If it should be fixed, write a short proposal with affected files, proposed diff shape, risks, validation, and whether a VCM GitHub issue is needed.",
369
- "- Do not edit harness files or product source during this analysis turn.",
370
- "- End your turn after writing the result file.",
371
- "",
372
- "<HARNESS_FEEDBACK>",
373
- feedback.trimEnd(),
374
- "</HARNESS_FEEDBACK>"
375
- ].join("\n");
376
- }
377
- function buildTaskRetrospectivePrompt(repoRoot, active) {
137
+ function buildTaskRetrospectivePrompt(repoRoot, analysisPath) {
378
138
  return [
379
139
  "[VCM Task Harness Retrospective]",
380
140
  "",
381
141
  "Review the completed task from the current active task worktree.",
382
142
  "",
383
- `Write the analysis to Result Path: ${resolveRepoPath(repoRoot, active.analysisPath)}`,
143
+ `Write the analysis to Result Path: ${resolveRepoPath(repoRoot, analysisPath)}`,
384
144
  "End your turn after writing the result."
385
145
  ].join("\n");
386
146
  }
387
- function buildFeedbackCommentPrompt(repoRoot, active, comment) {
388
- return [
389
- "[VCM Harness Feedback Revision]",
390
- "",
391
- "The user reviewed your harness feedback analysis and added comments.",
392
- "",
393
- `Feedback file: ${resolveRepoPath(repoRoot, active.feedbackPath)}`,
394
- `Current analysis path: ${resolveRepoPath(repoRoot, active.analysisPath)}`,
395
- `Rewrite the analysis result at: ${resolveRepoPath(repoRoot, active.analysisPath)}`,
396
- "",
397
- "Rules:",
398
- "- Do not edit harness files yet.",
399
- "- Address the user's comments and keep the proposal concise.",
400
- "- End your turn after updating the analysis result file.",
401
- "",
402
- "<USER_COMMENT>",
403
- comment.trim(),
404
- "</USER_COMMENT>"
405
- ].join("\n");
406
- }
407
- function buildFeedbackApplyPrompt(repoRoot, active, comment) {
408
- return [
409
- "[VCM Harness Feedback Approved]",
410
- "",
411
- "The user approved this harness improvement. Apply only the approved harness changes.",
412
- "",
413
- `Base repository root: ${repoRoot}`,
414
- `Feedback file: ${resolveRepoPath(repoRoot, active.feedbackPath)}`,
415
- `Approved analysis path: ${resolveRepoPath(repoRoot, active.analysisPath)}`,
416
- `Write completion report to: ${resolveRepoPath(repoRoot, active.applyReportPath)}`,
417
- "",
418
- "Rules:",
419
- "- Work in the active task worktree.",
420
- "- Edit only harness files and project harness docs that are necessary for the approved change.",
421
- "- Do not edit product source code.",
422
- "- Do not overwrite VCM fixed managed blocks; draft an issue instead if a fixed template is wrong.",
423
- "- Stage the harness changes and create a commit yourself.",
424
- "- Write the completion report with files changed, commit id if available, validation run, and any follow-up.",
425
- "- End your turn after the report is written.",
426
- ...(comment.trim()
427
- ? ["", "<USER_APPROVAL_COMMENT>", comment.trim(), "</USER_APPROVAL_COMMENT>"]
428
- : [])
429
- ].join("\n");
430
- }
431
- async function completeActive(repoRoot, state, outcome, comment = "") {
432
- const completedDir = `${COMPLETED_DIR}/${state.active.id}`;
433
- await deps.fs.ensureDir(resolveRepoPath(repoRoot, completedDir));
434
- const feedbackContent = await readOptionalText(repoRoot, state.active.feedbackPath);
435
- const analysisContent = await readOptionalText(repoRoot, state.active.analysisPath);
436
- const applyReportContent = await readOptionalText(repoRoot, state.active.applyReportPath);
437
- if (feedbackContent !== undefined) {
438
- await deps.fs.writeText(resolveRepoPath(repoRoot, `${completedDir}/feedback.md`), feedbackContent);
439
- }
440
- if (analysisContent !== undefined) {
441
- await deps.fs.writeText(resolveRepoPath(repoRoot, `${completedDir}/analysis.md`), analysisContent);
442
- }
443
- if (applyReportContent !== undefined) {
444
- await deps.fs.writeText(resolveRepoPath(repoRoot, `${completedDir}/apply-report.md`), applyReportContent);
445
- }
446
- await deps.fs.writeJsonAtomic(resolveRepoPath(repoRoot, `${completedDir}/decision.json`), {
447
- version: 1,
448
- id: state.active.id,
449
- title: state.active.title,
450
- outcome,
451
- comment,
452
- completedAt: now()
453
- });
454
- await persistTaskRetrospectiveMarker(repoRoot, state.active, outcome === "applied" ? "completed" : outcome);
455
- await deps.fs.removePath?.(resolveRepoPath(repoRoot, state.active.feedbackPath), { force: true });
456
- await deps.fs.removePath?.(resolveRepoPath(repoRoot, path.posix.dirname(state.active.analysisPath)), { recursive: true, force: true });
457
- }
458
- async function loadStoredState(repoRoot) {
459
- const statePath = resolveRepoPath(repoRoot, STATE_PATH);
460
- if (!(await deps.fs.pathExists(statePath))) {
461
- return undefined;
462
- }
463
- const state = await deps.fs.readJson(statePath);
464
- if (state?.version !== 1 || !state.active?.id || !state.status) {
147
+ async function loadTaskRetrospectiveMarker(repoRoot, taskSlug) {
148
+ const markerPath = resolveRepoPath(repoRoot, getTaskRetrospectiveMarkerPath(taskSlug));
149
+ if (!(await deps.fs.pathExists(markerPath))) {
465
150
  return undefined;
466
151
  }
467
- state.active.source = state.active.source ?? "role-feedback";
468
- return state;
152
+ return deps.fs.readJson(markerPath);
469
153
  }
470
- async function persistStoredState(repoRoot, state) {
471
- await deps.fs.writeJsonAtomic(resolveRepoPath(repoRoot, STATE_PATH), state);
154
+ async function persistTaskRetrospectiveMarker(repoRoot, marker) {
155
+ const markerPath = resolveRepoPath(repoRoot, getTaskRetrospectiveMarkerPath(String(marker.taskSlug ?? "")));
156
+ await deps.fs.ensureDir(path.dirname(markerPath));
157
+ await deps.fs.writeJsonAtomic(markerPath, marker);
472
158
  }
473
- async function clearStoredState(repoRoot) {
474
- await deps.fs.removePath?.(resolveRepoPath(repoRoot, STATE_PATH), { force: true });
159
+ async function cleanupLegacyState(repoRoot) {
160
+ const statePath = resolveRepoPath(repoRoot, LEGACY_STATE_PATH);
161
+ if (await deps.fs.pathExists(statePath)) {
162
+ await deps.fs.removePath?.(statePath, { force: true });
163
+ }
475
164
  }
476
165
  async function readOptionalText(repoRoot, relativePath) {
477
166
  const absolutePath = resolveRepoPath(repoRoot, relativePath);
478
- if (!(await deps.fs.pathExists(absolutePath))) {
479
- return undefined;
480
- }
481
- return deps.fs.readText(absolutePath);
167
+ return readAbsoluteOptionalText(absolutePath);
482
168
  }
483
169
  async function readAbsoluteOptionalText(absolutePath) {
484
170
  if (!(await deps.fs.pathExists(absolutePath))) {
@@ -486,34 +172,9 @@ export function createHarnessFeedbackService(deps) {
486
172
  }
487
173
  return deps.fs.readText(absolutePath);
488
174
  }
489
- async function loadTaskRetrospectiveMarker(repoRoot, taskSlug) {
490
- const markerPath = resolveRepoPath(repoRoot, getTaskRetrospectiveMarkerPath(taskSlug));
491
- if (!(await deps.fs.pathExists(markerPath))) {
492
- return undefined;
493
- }
494
- return deps.fs.readJson(markerPath);
495
- }
496
- async function persistTaskRetrospectiveMarker(repoRoot, active, status, timestamp = now()) {
497
- if (active.source !== "task-retrospective" || !active.taskSlug) {
498
- return;
499
- }
500
- await deps.fs.writeJsonAtomic(resolveRepoPath(repoRoot, getTaskRetrospectiveMarkerPath(active.taskSlug)), {
501
- version: 1,
502
- taskSlug: active.taskSlug,
503
- activeId: active.id,
504
- trigger: active.trigger ?? "manual",
505
- status,
506
- finalAcceptanceHash: active.finalAcceptanceHash,
507
- createdAt: active.startedAt,
508
- updatedAt: timestamp,
509
- ...(status === "completed" || status === "rejected" ? { completedAt: timestamp } : {})
510
- });
511
- }
512
175
  return {
513
176
  getState,
514
177
  startTaskRetrospective,
515
- decide,
516
- recordHarnessEngineerHook,
517
178
  assertHarnessEngineerAvailable
518
179
  };
519
180
  }
@@ -535,19 +196,6 @@ function firstHeading(content) {
535
196
  function compactLine(value) {
536
197
  return value.replace(/\s+/g, " ").trim().slice(0, 160);
537
198
  }
538
- function renderTaskRetrospectiveFeedback(active, finalAcceptancePath) {
539
- return [
540
- `# ${active.title}`,
541
- "",
542
- `Source: ${active.source}`,
543
- `Task slug: ${active.taskSlug ?? ""}`,
544
- `Trigger: ${active.trigger ?? "manual"}`,
545
- `Final acceptance: ${finalAcceptancePath}`,
546
- `Final acceptance hash: ${active.finalAcceptanceHash ?? ""}`,
547
- "",
548
- "Summary: Review the completed task workflow for reusable harness problems."
549
- ].join("\n");
550
- }
551
199
  function getTaskRetrospectiveMarkerPath(taskSlug) {
552
200
  return `${TASK_RETROSPECTIVE_DIR}/${sanitizeFeedbackId(taskSlug)}.json`;
553
201
  }
@@ -558,6 +206,3 @@ function sanitizeFeedbackId(value) {
558
206
  function sha256(content) {
559
207
  return createHash("sha256").update(content).digest("hex");
560
208
  }
561
- export function getHarnessFeedbackRelativePath(repoRoot, absolutePath) {
562
- return toRepoRelativePath(repoRoot, absolutePath);
563
- }
@@ -6,7 +6,6 @@ const HARNESS_ENGINEER_SESSION_PATH = ".ai/vcm/harness-engineer/session.json";
6
6
  const BOOTSTRAP_SESSION_PATH = ".ai/vcm/bootstrap/session.json";
7
7
  const HARNESS_FEEDBACK_STATE_PATH = ".ai/vcm/harness-feedback/state.json";
8
8
  const CODER_WORKERS_RUNTIME_DIR = ".ai/vcm/coder-workers";
9
- const RECOVERABLE_FEEDBACK_STATES = new Set(["analyzing", "applying"]);
10
9
  export function createRuntimeRecoveryService(deps) {
11
10
  const now = deps.now ?? (() => new Date().toISOString());
12
11
  return {
@@ -20,7 +19,7 @@ export function createRuntimeRecoveryService(deps) {
20
19
  const config = await deps.projectService.loadConfig(repoRoot);
21
20
  await runStep(context, "recover project tool sessions", () => recoverProjectToolSessions(repoRoot, recoveredAt, context));
22
21
  await runStep(context, "recover harness bootstrap", () => recoverHarnessBootstrap(repoRoot, recoveredAt, context));
23
- await runStep(context, "recover harness feedback", () => recoverHarnessFeedback(repoRoot, recoveredAt, context));
22
+ await runStep(context, "cleanup legacy harness feedback state", () => cleanupLegacyHarnessFeedback(repoRoot, context));
24
23
  const tasks = await deps.taskService.listTasks(repoRoot);
25
24
  for (const task of tasks.filter((candidate) => candidate.cleanupStatus === "cleaned")) {
26
25
  await runStep(context, `retry cleaned task ${task.taskSlug}`, async () => {
@@ -258,39 +257,13 @@ export function createRuntimeRecoveryService(deps) {
258
257
  await deps.fs.removePath?.(absolutePath, { force: true });
259
258
  context.changedPaths.add(BOOTSTRAP_SESSION_PATH);
260
259
  }
261
- async function recoverHarnessFeedback(repoRoot, timestamp, context) {
260
+ async function cleanupLegacyHarnessFeedback(repoRoot, context) {
262
261
  const absolutePath = path.join(repoRoot, HARNESS_FEEDBACK_STATE_PATH);
263
- const state = await readJsonIfExists(absolutePath);
264
- if (!state?.status || !RECOVERABLE_FEEDBACK_STATES.has(state.status)) {
262
+ if (!(await deps.fs.pathExists(absolutePath))) {
265
263
  return;
266
264
  }
267
- const previousStatus = state.status;
268
- const next = {
269
- ...state,
270
- status: "awaiting_user_approval",
271
- updatedAt: timestamp,
272
- active: state.active
273
- ? {
274
- ...state.active,
275
- updatedAt: timestamp
276
- }
277
- : state.active
278
- };
279
- await deps.fs.writeJsonAtomic(absolutePath, next);
265
+ await deps.fs.removePath?.(absolutePath, { force: true });
280
266
  context.changedPaths.add(HARNESS_FEEDBACK_STATE_PATH);
281
- const analysisPath = state.active?.analysisPath;
282
- if (analysisPath) {
283
- const notePath = path.join(repoRoot, analysisPath);
284
- if (!(await deps.fs.pathExists(notePath))) {
285
- await deps.fs.writeText(notePath, [
286
- "# Harness Feedback Recovery",
287
- "",
288
- `VCM restarted while this harness feedback item was ${previousStatus}.`,
289
- "Review the current repository diff and either comment, reject, cancel, or approve explicitly."
290
- ].join("\n"));
291
- context.changedPaths.add(analysisPath);
292
- }
293
- }
294
267
  }
295
268
  function hasLiveTaskSession(taskSlug) {
296
269
  return deps.runtime.listSessions(taskSlug).some((session) => session.status === "running");