vibe-coding-master 0.8.5 → 0.8.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.
package/README.md CHANGED
@@ -589,10 +589,20 @@ before documentation work starts. VCM assigns architecture and known-issues
589
589
  documents to Architect and testing documentation to Tester. For another target,
590
590
  PM selects Architect, Coder, or Tester and asks the user only when ownership
591
591
  cannot be determined. These assignments are backend-dispatched work rather than
592
- a Docs-Only Flow. Each owner updates and commits the document, submits the exact
593
- assignment ID in `docs-update-report.md`, and finishes before the next assignment
594
- starts. Harness Studio shows assignment status and allows failed work to be
595
- retried.
592
+ a Docs-Only Flow. Each owner submits `docs-update-report` through `vcm-artifact`
593
+ with the exact assigned `--path` and Assignment ID. Reports are isolated under
594
+ `.ai/vcm/memory-review/runs/<run-id>/assignments/<assignment-id>/report.md`.
595
+ The reported commit must modify the target document within the assignment's
596
+ original commit range; unrelated later commits do not invalidate it. Existing
597
+ correct documentation can be reported as `unchanged` without a new commit.
598
+
599
+ Assignments run one at a time. A failed assignment does not block later items.
600
+ After the remaining items finish, unresolved failures are reported in Harness
601
+ Studio with their causes and individual Retry buttons. Retry preserves earlier
602
+ committed work and the original baseline, including after a backend restart.
603
+ Migration content and review snapshots remain in the task-local run directory;
604
+ failures do not automatically restore removed memory. Turning Auto Memory off
605
+ does not discard or cancel already-applied document migrations.
596
606
 
597
607
  When Auto Memory is disabled, Review Task Harness does not collect proposals or
598
608
  ask Harness Engineer to update memory. When enabled, both automatic and manual
@@ -246,7 +246,7 @@ export function createDefaultServerDeps(options = {}) {
246
246
  const workflowControlService = createWorkflowControlService({ fs });
247
247
  const artifactService = createArtifactService(fs, {
248
248
  workflowControlService,
249
- isRoleMemoryTurn: (taskRepoRoot, role) => autoMemoryService.isRoleMemoryTurn(taskRepoRoot, role)
249
+ getDurableDocAssignment: (taskRepoRoot, role) => autoMemoryService.getDurableDocAssignment(taskRepoRoot, role)
250
250
  });
251
251
  const projectService = createProjectService({ fs, git, appSettings });
252
252
  const taskService = createTaskService({ fs, git, artifactService, projectService });
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
3
- import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js";
3
+ import { checkMarkdownArtifact, readArtifactSectionValue } from "../../shared/validation/artifact-check.js";
4
4
  import { getManagedArtifactDefinition, isArtifactKind, isManagedArtifactKind } from "../../shared/validation/artifact-registry.js";
5
5
  import { VcmError } from "../errors.js";
6
6
  import { resolveRepoPath } from "../adapters/filesystem.js";
@@ -208,10 +208,21 @@ export function createArtifactService(fs, deps = {}) {
208
208
  });
209
209
  }
210
210
  if (isArtifactKind(input.kind)) {
211
- if (input.artifactPath?.trim()) {
211
+ const assignment = input.kind === "docs-update-report"
212
+ ? await deps.getDurableDocAssignment?.(input.repoRoot, input.role)
213
+ : undefined;
214
+ if (assignment) {
215
+ if (input.artifactPath !== assignment.reportPath) {
216
+ throw artifactRejected(input.kind, [`Use the assigned --path ${assignment.reportPath}.`]);
217
+ }
218
+ if (readArtifactSectionValue(normalized, "Assignment ID")?.trim() !== assignment.id) {
219
+ throw artifactRejected(input.kind, [`Assignment ID must be exactly ${assignment.id}.`]);
220
+ }
221
+ }
222
+ else if (input.artifactPath?.trim()) {
212
223
  throw artifactRejected(input.kind, ["--path is not allowed for fixed artifact kinds."]);
213
224
  }
214
- const artifactPath = path.posix.join(input.handoffDir, definition.fileName ?? "");
225
+ const artifactPath = assignment?.reportPath ?? path.posix.join(input.handoffDir, definition.fileName ?? "");
215
226
  const validation = validateManagedArtifactContent(input.kind, normalized, {
216
227
  path: artifactPath,
217
228
  mode: input.mode
@@ -222,9 +233,7 @@ export function createArtifactService(fs, deps = {}) {
222
233
  if ((input.kind === "docs-update-report" || input.kind === "docs-sync-report")
223
234
  && deps.workflowControlService) {
224
235
  // Memory-owned documentation work is independent of PM's active dispatch.
225
- const memoryUpdate = input.kind === "docs-update-report"
226
- && await deps.isRoleMemoryTurn?.(input.repoRoot, input.role);
227
- if (!memoryUpdate) {
236
+ if (!assignment) {
228
237
  await deps.workflowControlService.assertDocsArtifactAllowed({
229
238
  taskRepoRoot: input.repoRoot,
230
239
  stateRoot: input.stateRoot ?? ".ai/vcm",
@@ -5,7 +5,7 @@ import { resolveRepoPath } from "../adapters/filesystem.js";
5
5
  import { VcmError } from "../errors.js";
6
6
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
7
7
  import { readVcmMemoryHostFrame, readVcmMemoryBlock, replaceVcmMemoryBlock } from "../templates/harness/memory-block.js";
8
- import { ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH, architectPlanningCandidateSnapshotPath, memoryReviewRoleDraftPath, MEMORY_REVIEW_RUNS_ROOT, MEMORY_REVIEW_STATE_PATH } from "./memory-review-paths.js";
8
+ import { ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH, architectPlanningCandidateSnapshotPath, durableDocAssignmentReportPath, memoryReviewRoleDraftPath, MEMORY_REVIEW_RUNS_ROOT, MEMORY_REVIEW_STATE_PATH } from "./memory-review-paths.js";
9
9
  import { parseMemoryProposal, validateMemoryProposal } from "./memory-proposal-validation.js";
10
10
  const MEMORY_FILE_DEFINITIONS = [
11
11
  { path: "CLAUDE.md", title: "Shared Memory" },
@@ -18,6 +18,20 @@ const MEMORY_FILE_DEFINITIONS = [
18
18
  ];
19
19
  export function createAutoMemoryService(deps) {
20
20
  const now = deps.now ?? (() => new Date().toISOString());
21
+ const taskLocks = new Map();
22
+ async function withTaskLock(taskRepoRoot, operation) {
23
+ const previous = taskLocks.get(taskRepoRoot) ?? Promise.resolve();
24
+ const current = previous.catch(() => undefined).then(operation);
25
+ taskLocks.set(taskRepoRoot, current);
26
+ try {
27
+ return await current;
28
+ }
29
+ finally {
30
+ if (taskLocks.get(taskRepoRoot) === current) {
31
+ taskLocks.delete(taskRepoRoot);
32
+ }
33
+ }
34
+ }
21
35
  async function readMemorySet(repoRoot) {
22
36
  const memory = {};
23
37
  for (const definition of MEMORY_FILE_DEFINITIONS) {
@@ -151,7 +165,7 @@ export function createAutoMemoryService(deps) {
151
165
  else if (active?.status === "reviewing" && active.reviewPromptDispatchedAt) {
152
166
  return getState(input.baseRepoRoot, input.taskRepoRoot);
153
167
  }
154
- else if (active) {
168
+ else if (active && !preserveDisabledReview(active)) {
155
169
  await discardActiveReview(input.taskRepoRoot, active);
156
170
  }
157
171
  return getState(input.baseRepoRoot, input.taskRepoRoot);
@@ -361,6 +375,11 @@ export function createAutoMemoryService(deps) {
361
375
  }
362
376
  return assignment.status === "running" && assignment.owner === role;
363
377
  }
378
+ async function getDurableDocAssignment(taskRepoRoot, role) {
379
+ const state = await loadActiveState(taskRepoRoot);
380
+ const assignment = state?.status === "documenting" ? currentDurableDocAssignment(state) : undefined;
381
+ return assignment?.status === "running" && assignment.owner === role ? assignment : undefined;
382
+ }
364
383
  async function handleRoleHook(input) {
365
384
  const state = await loadActiveState(input.taskRepoRoot);
366
385
  if (!(await deps.appSettings.getPreferences()).autoMemoryEnabled && !preserveDisabledReview(state)) {
@@ -522,12 +541,19 @@ export function createAutoMemoryService(deps) {
522
541
  statusCode: 409
523
542
  });
524
543
  }
544
+ if (state.assignments.length > 0) {
545
+ throw new VcmError({
546
+ code: "DURABLE_DOC_ASSIGNMENTS_FAILED",
547
+ message: "Memory was already applied. Retry the failed durable-document assignments individually.",
548
+ statusCode: 409
549
+ });
550
+ }
525
551
  await clearActiveState(taskRepoRoot);
526
552
  return getState(baseRepoRoot, taskRepoRoot);
527
553
  }
528
554
  async function retryDurableDocAssignment(baseRepoRoot, taskRepoRoot, assignmentId) {
529
555
  const state = await loadActiveState(taskRepoRoot);
530
- const assignment = state?.status === "documenting"
556
+ const assignment = state && (state.status === "documenting" || state.status === "failed")
531
557
  ? state.assignments.find((candidate) => candidate.id === assignmentId)
532
558
  : undefined;
533
559
  if (!state || !assignment || assignment.status !== "failed") {
@@ -539,11 +565,10 @@ export function createAutoMemoryService(deps) {
539
565
  }
540
566
  assignment.status = assignment.owner ? "pending" : "waiting-owner";
541
567
  assignment.updatedAt = now();
542
- delete assignment.error;
543
568
  delete assignment.requestedOwner;
544
569
  delete assignment.dispatchedAt;
545
- delete assignment.baseCommit;
546
- delete assignment.reportHashBefore;
570
+ state.status = "documenting";
571
+ delete state.error;
547
572
  await persistMemoryAssignmentState(taskRepoRoot, state);
548
573
  await dispatchCurrentDurableDocAssignment(baseRepoRoot, taskRepoRoot, state);
549
574
  return getState(baseRepoRoot, taskRepoRoot);
@@ -620,6 +645,7 @@ export function createAutoMemoryService(deps) {
620
645
  assignment.error = `${input.role} durable-document assignment turn failed.`;
621
646
  assignment.updatedAt = now();
622
647
  await persistMemoryAssignmentState(input.taskRepoRoot, state);
648
+ await dispatchCurrentDurableDocAssignment(input.baseRepoRoot, input.taskRepoRoot, state);
623
649
  return true;
624
650
  }
625
651
  if (input.eventName !== "Stop") {
@@ -644,13 +670,6 @@ export function createAutoMemoryService(deps) {
644
670
  assignment.updatedAt = assignment.completedAt;
645
671
  delete assignment.error;
646
672
  await persistMemoryAssignmentState(input.taskRepoRoot, state);
647
- const next = currentDurableDocAssignment(state);
648
- if (next) {
649
- await dispatchCurrentDurableDocAssignment(input.baseRepoRoot, input.taskRepoRoot, state);
650
- }
651
- else {
652
- await clearActiveState(input.taskRepoRoot);
653
- }
654
673
  }
655
674
  catch (error) {
656
675
  assignment.status = "failed";
@@ -658,11 +677,13 @@ export function createAutoMemoryService(deps) {
658
677
  assignment.updatedAt = now();
659
678
  await persistMemoryAssignmentState(input.taskRepoRoot, state);
660
679
  }
680
+ await dispatchCurrentDurableDocAssignment(input.baseRepoRoot, input.taskRepoRoot, state);
661
681
  return true;
662
682
  }
663
683
  async function dispatchCurrentDurableDocAssignment(baseRepoRoot, taskRepoRoot, state) {
664
- const assignment = currentDurableDocAssignment(state);
665
- if (!assignment || assignment.status === "failed") {
684
+ let assignment = currentDurableDocAssignment(state);
685
+ if (!assignment) {
686
+ await finishDurableDocAssignments(taskRepoRoot, state);
666
687
  return;
667
688
  }
668
689
  if (assignment.status === "running" || assignment.status === "resolving-owner") {
@@ -678,28 +699,50 @@ export function createAutoMemoryService(deps) {
678
699
  assignment.updatedAt = now();
679
700
  await persistMemoryAssignmentState(taskRepoRoot, state);
680
701
  }
681
- const role = assignment.owner ?? "project-manager";
682
- try {
683
- const session = await ensureWorkflowRoleSession(baseRepoRoot, state.taskSlug, role);
684
- if (session.activityStatus === "running") {
702
+ while (assignment) {
703
+ try {
704
+ await dispatchDurableDocAssignment(baseRepoRoot, taskRepoRoot, state, assignment);
685
705
  return;
686
706
  }
687
- assignment.status = assignment.owner ? "running" : "resolving-owner";
707
+ catch (error) {
708
+ assignment.status = "failed";
709
+ assignment.error = `Unable to dispatch durable-document assignment: ${errorMessage(error)}`;
710
+ assignment.updatedAt = now();
711
+ await persistMemoryAssignmentState(taskRepoRoot, state);
712
+ assignment = currentDurableDocAssignment(state);
713
+ }
714
+ }
715
+ await finishDurableDocAssignments(taskRepoRoot, state);
716
+ }
717
+ async function dispatchDurableDocAssignment(baseRepoRoot, taskRepoRoot, state, assignment) {
718
+ const session = await ensureWorkflowRoleSession(baseRepoRoot, state.taskSlug, assignment.owner ?? "project-manager");
719
+ if (session.activityStatus === "running") {
720
+ return;
721
+ }
722
+ assignment.status = assignment.owner ? "running" : "resolving-owner";
723
+ assignment.reportPath = durableDocAssignmentReportPath(assignment.runId, assignment.id);
724
+ if (!assignment.baseCommit) {
688
725
  assignment.baseCommit = await deps.git.getHeadCommit(taskRepoRoot);
689
726
  assignment.reportHashBefore = await hashOptionalFile(taskRepoRoot, assignment.reportPath);
690
- assignment.dispatchedAt = now();
691
- assignment.updatedAt = assignment.dispatchedAt;
692
- await persistMemoryAssignmentState(taskRepoRoot, state);
693
- await submitTerminalInput(deps.runtime, session.id, assignment.owner
694
- ? buildDurableDocAssignmentPrompt(taskRepoRoot, assignment)
695
- : buildDurableDocOwnerPrompt(taskRepoRoot, assignment));
696
727
  }
697
- catch (error) {
698
- assignment.status = "failed";
699
- assignment.error = `Unable to dispatch durable-document assignment: ${errorMessage(error)}`;
700
- assignment.updatedAt = now();
728
+ assignment.dispatchedAt = now();
729
+ assignment.updatedAt = assignment.dispatchedAt;
730
+ await persistMemoryAssignmentState(taskRepoRoot, state);
731
+ await submitTerminalInput(deps.runtime, session.id, assignment.owner
732
+ ? buildDurableDocAssignmentPrompt(taskRepoRoot, assignment)
733
+ : buildDurableDocOwnerPrompt(taskRepoRoot, assignment));
734
+ }
735
+ async function finishDurableDocAssignments(taskRepoRoot, state) {
736
+ const failed = state.assignments.filter((assignment) => assignment.status === "failed");
737
+ if (failed.length > 0) {
738
+ state.status = "failed";
739
+ state.error = `Memory was applied, but ${failed.length} durable-document assignment(s) failed: ${failed.map((assignment) => assignment.targetPath).join(", ")}. Retry the failed assignments; their content is retained in the memory review run.`;
701
740
  await persistMemoryAssignmentState(taskRepoRoot, state);
741
+ return;
702
742
  }
743
+ delete state.error;
744
+ await persistMemoryAssignmentState(taskRepoRoot, state);
745
+ await clearActiveState(taskRepoRoot);
703
746
  }
704
747
  async function validateDurableDocAssignmentCompletion(taskRepoRoot, assignment) {
705
748
  const reportAbsolutePath = resolveRepoPath(taskRepoRoot, assignment.reportPath);
@@ -727,22 +770,36 @@ export function createAutoMemoryService(deps) {
727
770
  if (decision !== "synced" && decision !== "unchanged") {
728
771
  throw new Error("Decision must be synced or unchanged");
729
772
  }
773
+ if (!(await deps.fs.pathExists(resolveRepoPath(taskRepoRoot, assignment.targetPath)))) {
774
+ throw new Error(`target document is missing: ${assignment.targetPath}`);
775
+ }
730
776
  const currentHead = await deps.git.getHeadCommit(taskRepoRoot);
731
777
  if (decision === "synced") {
732
- if (!assignment.baseCommit || currentHead === assignment.baseCommit) {
733
- throw new Error("documentation changes were not committed");
734
- }
735
- const changedPaths = await deps.git.getChangedPaths(taskRepoRoot, assignment.baseCommit, currentHead);
736
- if (!changedPaths.includes(assignment.targetPath)) {
737
- throw new Error(`documentation commit does not include ${assignment.targetPath}`);
738
- }
739
- const reportedCommit = readArtifactSectionValue(reportContent, "Commit")?.trim();
740
- if (!reportedCommit || reportedCommit === "TBD" || !currentHead.startsWith(reportedCommit)) {
741
- throw new Error(`Commit must identify the current documentation commit ${currentHead}`);
742
- }
778
+ return validateDurableDocCommit(taskRepoRoot, assignment, reportContent, currentHead);
743
779
  }
744
780
  return currentHead;
745
781
  }
782
+ async function validateDurableDocCommit(taskRepoRoot, assignment, reportContent, currentHead) {
783
+ if (!assignment.baseCommit || currentHead === assignment.baseCommit) {
784
+ throw new Error("documentation changes were not committed");
785
+ }
786
+ const changedPaths = await deps.git.getChangedPaths(taskRepoRoot, assignment.baseCommit, currentHead);
787
+ if (!changedPaths.includes(assignment.targetPath)) {
788
+ throw new Error(`documentation commit does not include ${assignment.targetPath}`);
789
+ }
790
+ const reportedCommit = readArtifactSectionValue(reportContent, "Commit")?.trim();
791
+ const commits = await deps.git.getCommitList(taskRepoRoot, `${assignment.baseCommit}..${currentHead}`);
792
+ const matches = reportedCommit ? commits.filter((commit) => commit.sha.startsWith(reportedCommit)) : [];
793
+ if (matches.length !== 1) {
794
+ throw new Error(`Commit must uniquely identify a documentation commit in ${assignment.baseCommit}..${currentHead}; received ${reportedCommit || "empty"}`);
795
+ }
796
+ const commit = matches[0].sha;
797
+ const commitPaths = await deps.git.getChangedPaths(taskRepoRoot, `${commit}^`, commit);
798
+ if (!commitPaths.includes(assignment.targetPath)) {
799
+ throw new Error(`reported commit ${commit} does not modify ${assignment.targetPath}`);
800
+ }
801
+ return commit;
802
+ }
746
803
  async function hashOptionalFile(taskRepoRoot, relativePath) {
747
804
  const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
748
805
  return await deps.fs.pathExists(absolutePath)
@@ -756,6 +813,7 @@ export function createAutoMemoryService(deps) {
756
813
  await persistRun(taskRepoRoot, {
757
814
  ...run,
758
815
  assignments: state.assignments,
816
+ error: state.error,
759
817
  updatedAt: state.updatedAt
760
818
  });
761
819
  }
@@ -897,7 +955,7 @@ export function createAutoMemoryService(deps) {
897
955
  evidence: assignment.evidence,
898
956
  ...(owner ? { owner } : {}),
899
957
  status: owner ? "pending" : "waiting-owner",
900
- reportPath: ".ai/vcm/handoffs/docs-update-report.md",
958
+ reportPath: durableDocAssignmentReportPath(state.runId, `${state.runId}-doc-${index + 1}`),
901
959
  createdAt: timestamp,
902
960
  updatedAt: timestamp
903
961
  };
@@ -1317,20 +1375,21 @@ export function createAutoMemoryService(deps) {
1317
1375
  return `sha256:${sha256(content)}`;
1318
1376
  }
1319
1377
  return {
1320
- reconcileTask,
1378
+ reconcileTask: (input) => withTaskLock(input.taskRepoRoot, () => reconcileTask(input)),
1321
1379
  getState,
1322
1380
  getTaskRetrospectiveReadiness,
1323
1381
  getFile,
1324
1382
  updateFile,
1325
1383
  revertRun,
1326
1384
  retryFailedReview,
1327
- retryDurableDocAssignment,
1328
- resolveDurableDocAssignmentOwner,
1385
+ retryDurableDocAssignment: (base, task, id) => withTaskLock(task, () => retryDurableDocAssignment(base, task, id)),
1386
+ resolveDurableDocAssignmentOwner: (base, task, id, owner) => withTaskLock(task, () => resolveDurableDocAssignmentOwner(base, task, id, owner)),
1329
1387
  prepareTaskRetrospectiveReview,
1330
1388
  cancelTaskRetrospectiveReview,
1331
1389
  isRoleMemoryTurn,
1332
- handleRoleHook,
1333
- handleHarnessEngineerHook,
1390
+ getDurableDocAssignment,
1391
+ handleRoleHook: (input) => withTaskLock(input.taskRepoRoot, () => handleRoleHook(input)),
1392
+ handleHarnessEngineerHook: (input) => withTaskLock(input.taskRepoRoot, () => handleHarnessEngineerHook(input)),
1334
1393
  assertHarnessEngineerAvailable
1335
1394
  };
1336
1395
  }
@@ -1350,11 +1409,12 @@ function currentDraft(state) {
1350
1409
  return state.drafts.find((draft) => draft.status !== "completed");
1351
1410
  }
1352
1411
  function preserveDisabledReview(state) {
1353
- return state?.status === "documenting"
1412
+ return Boolean(state?.assignments.length)
1354
1413
  || (state?.status === "reviewing" && Boolean(state.reviewPromptDispatchedAt));
1355
1414
  }
1356
1415
  function currentDurableDocAssignment(state) {
1357
- return state.assignments.find((assignment) => assignment.status !== "completed");
1416
+ return state.assignments.find((assignment) => assignment.status === "running" || assignment.status === "resolving-owner")
1417
+ ?? state.assignments.find((assignment) => assignment.status === "pending" || assignment.status === "waiting-owner");
1358
1418
  }
1359
1419
  function toReviewCandidates(source, currentRole, items) {
1360
1420
  return items.map((item) => ({
@@ -1435,7 +1495,11 @@ function buildDurableDocAssignmentPrompt(taskRepoRoot, assignment) {
1435
1495
  ...assignment.evidence.map((item) => `- ${item}`),
1436
1496
  "",
1437
1497
  "Verify the content, update the target and any directly related durable documentation, run applicable documentation checks, and commit the documentation changes.",
1438
- "Submit .ai/vcm/handoffs/docs-update-report.md through vcm-artifact. Set its Assignment ID to the exact ID above and record the final commit.",
1498
+ `Assigned report path: ${assignment.reportPath}`,
1499
+ `Submit with .ai/tools/vcm-artifact docs-update-report --file <candidate> --path ${assignment.reportPath} --mode final`,
1500
+ "Set Assignment ID to the exact ID above.",
1501
+ "For synced, record the commit that changed the target document; it need not be HEAD. If the document already preserves the content, use unchanged with evidence; do not create a redundant commit.",
1502
+ ...(assignment.error ? [`Previous failure: ${assignment.error}`] : []),
1439
1503
  "End the turn after VCM accepts the report."
1440
1504
  ].join("\n");
1441
1505
  }
@@ -160,6 +160,7 @@ export function createClaudeHookService(deps) {
160
160
  taskSlug: activeTask.taskSlug,
161
161
  eventName,
162
162
  memoryReviewStatus: memoryState?.status ?? "idle",
163
+ memoryReviewHasAssignments: Boolean(memoryState?.active?.assignments.length),
163
164
  memoryReviewError: memoryState?.active?.error
164
165
  })
165
166
  : false;
@@ -164,6 +164,7 @@ export function createHarnessFeedbackService(deps) {
164
164
  await persistTaskRetrospectiveMarker(repoRoot, {
165
165
  ...processedMarker,
166
166
  status: "failed",
167
+ durableDocFailure: input.memoryReviewHasAssignments,
167
168
  failedAt: timestamp,
168
169
  updatedAt: timestamp,
169
170
  error: input.memoryReviewError
@@ -185,17 +186,26 @@ export function createHarnessFeedbackService(deps) {
185
186
  }
186
187
  async function completeWaitingTaskRetrospective(repoRoot, taskSlug, memoryStatus) {
187
188
  const marker = await loadTaskRetrospectiveMarker(repoRoot, taskSlug);
188
- if (!marker || marker.status !== "waiting-docs") {
189
+ if (!marker || (marker.status !== "waiting-docs" && !(marker.status === "failed" && marker.durableDocFailure))) {
189
190
  return false;
190
191
  }
191
192
  if (memoryStatus === "documenting") {
193
+ if (marker.status === "failed") {
194
+ await persistTaskRetrospectiveMarker(repoRoot, {
195
+ ...marker, status: "waiting-docs", updatedAt: now(), error: undefined, failedAt: undefined
196
+ });
197
+ }
192
198
  return true;
193
199
  }
194
200
  if (memoryStatus === "failed") {
201
+ if (marker.status === "failed") {
202
+ return true;
203
+ }
195
204
  const timestamp = now();
196
205
  await persistTaskRetrospectiveMarker(repoRoot, {
197
206
  ...marker,
198
207
  status: "failed",
208
+ durableDocFailure: true,
199
209
  failedAt: timestamp,
200
210
  updatedAt: timestamp,
201
211
  error: "Task Harness Retrospective durable-document assignment failed."
@@ -214,6 +224,9 @@ export function createHarnessFeedbackService(deps) {
214
224
  await persistTaskRetrospectiveMarker(repoRoot, {
215
225
  ...processedMarker,
216
226
  status: "completed",
227
+ durableDocFailure: undefined,
228
+ error: undefined,
229
+ failedAt: undefined,
217
230
  completedAt: timestamp,
218
231
  updatedAt: timestamp
219
232
  });
@@ -6,6 +6,9 @@ const MEMORY_REVIEW_ROLE_DRAFT_PATTERN = new RegExp(`^${escapeRegExp(MEMORY_REVI
6
6
  export function memoryReviewRoleDraftPath(runId, role) {
7
7
  return `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/drafts/${role}.md`;
8
8
  }
9
+ export function durableDocAssignmentReportPath(runId, assignmentId) {
10
+ return `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/assignments/${assignmentId}/report.md`;
11
+ }
9
12
  export function isMemoryProposalSubmissionPath(artifactPath, role) {
10
13
  if (artifactPath === ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH) {
11
14
  return role === "architect";
@@ -301,7 +301,8 @@ Each rewritten \`architecture-diagnosis.md\` must be a complete, self-contained
301
301
  - For a \`[VCM Durable Documentation Assignment]\`, update the assigned durable
302
302
  document and directly related documentation, run applicable documentation
303
303
  checks, commit the result, and submit \`docs-update-report.md\` with the exact
304
- assigned Assignment ID. This backend assignment does not start Docs-Only Flow.
304
+ assigned Assignment ID. Use \`vcm-artifact docs-update-report --path <assigned-report-path>\`;
305
+ do not use the shared Docs-Only report path. This backend assignment does not start Docs-Only Flow.
305
306
 
306
307
  - In Docs-Only Flow, verify claims against current code and durable docs, update the PM-assigned project documents and related documentation needed for consistency, run applicable documentation checks, commit the changes, and submit \`.ai/vcm/handoffs/docs-update-report.md\` through \`vcm-artifact\` before reporting to PM.
307
308
  - In Code-Change Flow, Architect Debug Flow, and a code-producing Architecture Diagnosis Flow, perform post-validation docs sync only when project-manager requests it after tester completes.
@@ -82,7 +82,8 @@ ${renderRoleMemoryRules("coder")}
82
82
  - For a \`[VCM Durable Documentation Assignment]\`, verify and update the assigned
83
83
  durable document and directly related documentation, run applicable checks,
84
84
  commit the result, and submit \`docs-update-report.md\` with the exact assigned
85
- Assignment ID. This backend assignment does not start Docs-Only Flow.
85
+ Assignment ID. Use \`vcm-artifact docs-update-report --path <assigned-report-path>\`;
86
+ do not use the shared Docs-Only report path. This backend assignment does not start Docs-Only Flow.
86
87
 
87
88
  - In Docs-Only Flow, commit the documentation changes and submit \`.ai/vcm/handoffs/docs-update-report.md\` through \`vcm-artifact\` with the decision, changed and reviewed documents, evidence, checks, commit, and remaining documentation issues. Do not submit \`coder-completion.md\` for Docs-Only work.
88
89
  - Submit \`.ai/vcm/handoffs/coder-completion.md\` before routing back to project-manager: write a candidate outside \`.ai/vcm\`, then run \`.ai/tools/vcm-artifact coder-completion --file <candidate> --mode draft|final\`. This file is the complete, self-contained current implementation completion evidence, not a log. Each revision must restate every Scaffold Manifest disposition, changed file, helper, deviation, generated-context result, baseline-test change, L0/L1 command and result, worker result, commit, and objective failure still needed to review the current implementation without a prior revision. Replace stale content instead of appending history.
@@ -42,8 +42,8 @@ ${renderRoleMemoryRules("tester")}
42
42
  - For a \`[VCM Durable Documentation Assignment]\`, verify and update the assigned
43
43
  durable document and directly related documentation, run applicable
44
44
  documentation checks, commit the result, and submit \`docs-update-report.md\`
45
- with the exact assigned Assignment ID. This backend assignment does not start
46
- Docs-Only Flow.
45
+ with the exact assigned Assignment ID. Use \`vcm-artifact docs-update-report --path <assigned-report-path>\`;
46
+ do not use the shared Docs-Only report path. This backend assignment does not start Docs-Only Flow.
47
47
 
48
48
  - Verify the assigned documentation against current project evidence, update the required documentation, run applicable documentation checks, and commit the changes.
49
49
  - Submit \`.ai/vcm/handoffs/docs-update-report.md\` through \`vcm-artifact\` with the decision, changed and reviewed documents, evidence, checks, commit, and remaining documentation issues.