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 +14 -4
- package/dist/backend/server.js +1 -1
- package/dist/backend/services/artifact-service.js +15 -6
- package/dist/backend/services/auto-memory-service.js +114 -50
- package/dist/backend/services/claude-hook-service.js +1 -0
- package/dist/backend/services/harness-feedback-service.js +14 -1
- package/dist/backend/services/memory-review-paths.js +3 -0
- package/dist/backend/templates/harness/architect-agent.js +2 -1
- package/dist/backend/templates/harness/coder-agent.js +2 -1
- package/dist/backend/templates/harness/tester-agent.js +2 -2
- package/dist-frontend/assets/{index-c2YwYw6n.js → index-CqZm9xCA.js} +31 -31
- package/dist-frontend/assets/{index-BIFZPhDN.css → index-xgiQeUD5.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/harness-tools/vcm-artifact +1 -1
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
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
package/dist/backend/server.js
CHANGED
|
@@ -246,7 +246,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
246
246
|
const workflowControlService = createWorkflowControlService({ fs });
|
|
247
247
|
const artifactService = createArtifactService(fs, {
|
|
248
248
|
workflowControlService,
|
|
249
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
546
|
-
delete
|
|
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
|
-
|
|
665
|
-
if (!assignment
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (session.activityStatus === "running") {
|
|
702
|
+
while (assignment) {
|
|
703
|
+
try {
|
|
704
|
+
await dispatchDurableDocAssignment(baseRepoRoot, taskRepoRoot, state, assignment);
|
|
685
705
|
return;
|
|
686
706
|
}
|
|
687
|
-
|
|
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
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
1333
|
-
|
|
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?.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|