vibe-coding-master 0.7.32 → 0.7.34
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 +15 -12
- package/dist/backend/adapters/git-adapter.js +21 -0
- package/dist/backend/services/auto-memory-service.js +115 -81
- package/dist/backend/services/gate-review-service.js +39 -5
- package/dist/backend/services/harness-feedback-service.js +6 -3
- package/dist/backend/templates/handoff.js +17 -0
- package/dist/backend/templates/harness/architect-agent.js +12 -3
- package/dist/backend/templates/harness/claude-root.js +8 -0
- package/dist/backend/templates/harness/coder-agent.js +1 -1
- package/dist/backend/templates/harness/gate-review.js +36 -0
- package/dist/backend/templates/harness/harness-engineer-agent.js +14 -9
- package/dist/backend/templates/harness/project-coding-standards.js +2 -0
- package/dist/backend/templates/harness/tester-agent.js +1 -1
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +7 -1
- package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +2 -0
- package/dist/backend/templates/harness/vcm-route-message-skill.js +1 -1
- package/package.json +1 -1
- package/dist/backend/services/memory-review-validation.js +0 -286
package/README.md
CHANGED
|
@@ -467,8 +467,9 @@ files.
|
|
|
467
467
|
Review Task Harness after Final Acceptance, Project Manager, Architect, Coder,
|
|
468
468
|
Tester, and an enabled Reviewer submit proposals in sequence through
|
|
469
469
|
`vcm-propose-memory`. Harness Engineer verifies and consolidates them before VCM
|
|
470
|
-
|
|
471
|
-
edit active memory directly
|
|
470
|
+
records the result as part of the same Task Harness Retrospective. Workflow roles
|
|
471
|
+
cannot edit active memory directly; Harness Engineer edits it only during the
|
|
472
|
+
assigned Auto Memory Retrospective.
|
|
472
473
|
|
|
473
474
|
When Auto Memory is enabled, the planning Architect writes a provisional memory
|
|
474
475
|
candidate before its post-planning Session restart. VCM snapshots that candidate
|
|
@@ -477,11 +478,12 @@ the completed implementation and tests, and Harness Engineer reviews it with all
|
|
|
477
478
|
final role proposals before anything becomes active memory.
|
|
478
479
|
|
|
479
480
|
Shared memory is stored in the root `CLAUDE.md` `<VCM-memory>` block. Role memory
|
|
480
|
-
is stored in the matching `.claude/agents/*.md` block.
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
481
|
+
is stored in the matching `.claude/agents/*.md` block. Harness Engineer changes
|
|
482
|
+
only those blocks and creates a dedicated commit in the active task worktree.
|
|
483
|
+
VCM verifies the mechanical commit boundary and records the after snapshot and
|
|
484
|
+
diff. Harness Studio shows current memory and task-local applied history. Memory
|
|
485
|
+
is applied before user review; while the task worktree remains available, the
|
|
486
|
+
user can edit current memory or revert a recorded change through another commit.
|
|
485
487
|
|
|
486
488
|
Post-task processing is ordered by the backend:
|
|
487
489
|
|
|
@@ -493,7 +495,8 @@ Final Acceptance
|
|
|
493
495
|
-> Task Harness Retrospective
|
|
494
496
|
-> Review pending Harness Feedback
|
|
495
497
|
-> Review Auto Memory proposals, when Auto Memory is enabled
|
|
496
|
-
|
|
498
|
+
-> Harness Engineer updates and commits memory, when Auto Memory is enabled
|
|
499
|
+
-> VCM records the committed memory result
|
|
497
500
|
```
|
|
498
501
|
|
|
499
502
|
Memory proposal prompts sent to Project Manager, Architect, Coder, Tester, and
|
|
@@ -507,10 +510,10 @@ impact, and durable-document disposition, then evaluates every proposal item
|
|
|
507
510
|
independently. Each Add or Update decision records why the memory is necessary,
|
|
508
511
|
what happens if it is absent, whether it belongs in memory or a durable
|
|
509
512
|
document, and the exact final memory content when retained. VCM validates that
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
completion.
|
|
513
|
+
the commit changes only assigned memory host files and only their
|
|
514
|
+
`<VCM-memory>` blocks; it does not parse or apply Harness Engineer's semantic
|
|
515
|
+
decisions. Harness Engineer review and retrospective work remain tool role
|
|
516
|
+
activity and do not participate in Round completion.
|
|
514
517
|
|
|
515
518
|
When Auto Memory is disabled, Review Task Harness does not collect proposals or
|
|
516
519
|
ask Harness Engineer to update memory. When enabled, both automatic and manual
|
|
@@ -157,6 +157,27 @@ export function createGitAdapter(runner) {
|
|
|
157
157
|
}
|
|
158
158
|
return result.stdout;
|
|
159
159
|
},
|
|
160
|
+
async getChangedPaths(repoRoot, baseRef, headRef = null) {
|
|
161
|
+
const result = await runGit(runner, repoRoot, [
|
|
162
|
+
"diff",
|
|
163
|
+
"--name-only",
|
|
164
|
+
"-z",
|
|
165
|
+
baseRef,
|
|
166
|
+
...(headRef ? [headRef] : [])
|
|
167
|
+
]);
|
|
168
|
+
if (result.exitCode !== 0) {
|
|
169
|
+
throw new VcmError({
|
|
170
|
+
code: "GIT_ERROR",
|
|
171
|
+
message: "Unable to read changed Git paths.",
|
|
172
|
+
statusCode: 400,
|
|
173
|
+
hint: result.stderr
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return result.stdout
|
|
177
|
+
.split("\0")
|
|
178
|
+
.map((changedPath) => changedPath.trim())
|
|
179
|
+
.filter(Boolean);
|
|
180
|
+
},
|
|
160
181
|
async getCommitList(repoRoot, range) {
|
|
161
182
|
const result = await runGit(runner, repoRoot, ["log", "--format=%H%x00%s%x00%cI%x1e", range]);
|
|
162
183
|
if (result.exitCode !== 0) {
|
|
@@ -7,7 +7,6 @@ import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
|
7
7
|
import { readVcmMemoryBlock, replaceVcmMemoryBlock } from "../templates/harness/memory-block.js";
|
|
8
8
|
import { ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH, architectPlanningCandidateSnapshotPath, MEMORY_REVIEW_RUNS_ROOT, MEMORY_REVIEW_STATE_PATH } from "./memory-review-paths.js";
|
|
9
9
|
import { parseMemoryProposal, validateMemoryProposal } from "./memory-proposal-validation.js";
|
|
10
|
-
import { parseMemoryReviewReport, validateMemoryReviewOutput } from "./memory-review-validation.js";
|
|
11
10
|
const MEMORY_FILE_DEFINITIONS = [
|
|
12
11
|
{ path: "CLAUDE.md", title: "Shared Memory" },
|
|
13
12
|
{ path: ".claude/agents/project-manager.md", title: "Project Manager Memory", role: "project-manager" },
|
|
@@ -97,6 +96,26 @@ export function createAutoMemoryService(deps) {
|
|
|
97
96
|
}
|
|
98
97
|
return memory;
|
|
99
98
|
}
|
|
99
|
+
async function writeRunMemoryHostSnapshot(taskRepoRoot, runId) {
|
|
100
|
+
for (const definition of MEMORY_FILE_DEFINITIONS) {
|
|
101
|
+
await deps.fs.writeText(resolveRepoPath(taskRepoRoot, memoryRunHostFilePath(runId, definition.path)), await deps.fs.readText(resolveRepoPath(taskRepoRoot, definition.path)));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function assertOnlyMemoryBlocksChanged(taskRepoRoot, runId, currentMemory) {
|
|
105
|
+
for (const definition of MEMORY_FILE_DEFINITIONS) {
|
|
106
|
+
const beforeHost = await deps.fs.readText(resolveRepoPath(taskRepoRoot, memoryRunHostFilePath(runId, definition.path)));
|
|
107
|
+
const currentHost = await deps.fs.readText(resolveRepoPath(taskRepoRoot, definition.path));
|
|
108
|
+
const expectedHost = replaceVcmMemoryBlock(beforeHost, currentMemory[definition.path]);
|
|
109
|
+
if (currentHost !== expectedHost) {
|
|
110
|
+
throw new VcmError({
|
|
111
|
+
code: "MEMORY_REVIEW_SCOPE_CHANGED",
|
|
112
|
+
message: `Harness Engineer changed content outside the VCM memory block: ${definition.path}`,
|
|
113
|
+
statusCode: 409,
|
|
114
|
+
hint: "Restore non-memory content, keep only the reviewed <VCM-memory> edit, and commit the correction."
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
100
119
|
async function getState(baseRepoRoot, taskRepoRoot) {
|
|
101
120
|
let [active, runs, memoryFiles] = await Promise.all([
|
|
102
121
|
loadActiveState(taskRepoRoot),
|
|
@@ -172,7 +191,6 @@ export function createAutoMemoryService(deps) {
|
|
|
172
191
|
};
|
|
173
192
|
const before = await readMemorySet(input.taskRepoRoot);
|
|
174
193
|
await writeRunMemorySet(input.taskRepoRoot, runId, "before", before);
|
|
175
|
-
await writeRunMemorySet(input.taskRepoRoot, runId, "after", before);
|
|
176
194
|
await snapshotArchitectPlanningCandidate(input.taskRepoRoot, runId);
|
|
177
195
|
await persistRun(input.taskRepoRoot, {
|
|
178
196
|
version: 1,
|
|
@@ -252,9 +270,31 @@ export function createAutoMemoryService(deps) {
|
|
|
252
270
|
});
|
|
253
271
|
}
|
|
254
272
|
const proposalCandidates = await readReviewCandidates(taskRepoRoot, state);
|
|
273
|
+
const currentMemory = await readMemorySet(taskRepoRoot);
|
|
274
|
+
const beforeMemory = await readRunMemorySet(taskRepoRoot, state.runId, "before");
|
|
275
|
+
if (!sameHashes(hashMemorySet(currentMemory), hashMemorySet(beforeMemory))) {
|
|
276
|
+
throw new VcmError({
|
|
277
|
+
code: "MEMORY_TARGET_CHANGED",
|
|
278
|
+
message: "VCM memory changed while role proposals were being collected.",
|
|
279
|
+
statusCode: 409,
|
|
280
|
+
hint: "Review the current memory, then retry Auto Memory."
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const memoryPaths = MEMORY_FILE_DEFINITIONS.map((definition) => definition.path);
|
|
284
|
+
const existingDiff = await deps.git.getDiff(taskRepoRoot, "HEAD", null, memoryPaths);
|
|
285
|
+
if (existingDiff.trim()) {
|
|
286
|
+
throw new VcmError({
|
|
287
|
+
code: "MEMORY_HOST_FILE_DIRTY",
|
|
288
|
+
message: "A file containing VCM memory has uncommitted changes before Harness Engineer review.",
|
|
289
|
+
statusCode: 409,
|
|
290
|
+
hint: "Commit or discard the existing host-file changes before starting Task Harness Retrospective."
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
await writeRunMemoryHostSnapshot(taskRepoRoot, state.runId);
|
|
255
294
|
const timestamp = now();
|
|
256
295
|
state.reviewPromptDispatchedAt = timestamp;
|
|
257
296
|
state.retrospectiveReportPath = retrospectiveReportPath;
|
|
297
|
+
state.reviewBaseCommit = await deps.git.getHeadCommit(taskRepoRoot);
|
|
258
298
|
state.updatedAt = timestamp;
|
|
259
299
|
await persistActiveState(taskRepoRoot, state);
|
|
260
300
|
const runRoot = resolveRepoPath(taskRepoRoot, `${MEMORY_REVIEW_RUNS_ROOT}/${state.runId}`);
|
|
@@ -263,7 +303,7 @@ export function createAutoMemoryService(deps) {
|
|
|
263
303
|
runId: state.runId,
|
|
264
304
|
roleDraftsPath: path.join(runRoot, "drafts"),
|
|
265
305
|
currentMemoryPath: path.join(runRoot, "before"),
|
|
266
|
-
|
|
306
|
+
activeMemoryPaths: memoryPaths.map((memoryPath) => resolveRepoPath(taskRepoRoot, memoryPath)),
|
|
267
307
|
proposalCandidates,
|
|
268
308
|
...(planningCandidatePath
|
|
269
309
|
? { planningCandidatePath: resolveRepoPath(taskRepoRoot, planningCandidatePath) }
|
|
@@ -277,6 +317,7 @@ export function createAutoMemoryService(deps) {
|
|
|
277
317
|
}
|
|
278
318
|
delete state.reviewPromptDispatchedAt;
|
|
279
319
|
delete state.retrospectiveReportPath;
|
|
320
|
+
delete state.reviewBaseCommit;
|
|
280
321
|
state.updatedAt = now();
|
|
281
322
|
await persistActiveState(taskRepoRoot, state);
|
|
282
323
|
}
|
|
@@ -362,26 +403,12 @@ export function createAutoMemoryService(deps) {
|
|
|
362
403
|
return true;
|
|
363
404
|
}
|
|
364
405
|
try {
|
|
365
|
-
|
|
366
|
-
const currentMemorySnapshot = await readRunMemorySet(input.taskRepoRoot, state.runId, "before");
|
|
367
|
-
const reviewedMemorySnapshot = await readRunMemorySet(input.taskRepoRoot, state.runId, "after");
|
|
368
|
-
const candidates = await readReviewCandidates(input.taskRepoRoot, state);
|
|
369
|
-
const reportResult = parseMemoryReviewReport(report, candidates, hasSubstantiveMemory(currentMemorySnapshot));
|
|
370
|
-
if (reportResult.error) {
|
|
371
|
-
await failReview(input.taskRepoRoot, state, `Task Harness Retrospective memory review report ${reportResult.error}.`);
|
|
372
|
-
return true;
|
|
373
|
-
}
|
|
374
|
-
const outputError = await validateReviewedMemoryOutput(input.taskRepoRoot, currentMemorySnapshot, reviewedMemorySnapshot, reportResult.decisions ?? []);
|
|
375
|
-
if (outputError) {
|
|
376
|
-
await failReview(input.taskRepoRoot, state, `Task Harness Retrospective reviewed memory ${outputError}.`);
|
|
377
|
-
return true;
|
|
378
|
-
}
|
|
406
|
+
await recordHarnessEngineerMemoryResult(input.taskRepoRoot, state);
|
|
379
407
|
}
|
|
380
408
|
catch (error) {
|
|
381
|
-
await failReview(input.taskRepoRoot, state, `
|
|
409
|
+
await failReview(input.taskRepoRoot, state, `Harness Engineer memory result could not be recorded: ${errorMessage(error)}`);
|
|
382
410
|
return true;
|
|
383
411
|
}
|
|
384
|
-
await applyReviewedMemory(input.taskRepoRoot, state);
|
|
385
412
|
return true;
|
|
386
413
|
}
|
|
387
414
|
return true;
|
|
@@ -559,44 +586,75 @@ export function createAutoMemoryService(deps) {
|
|
|
559
586
|
}
|
|
560
587
|
return candidates;
|
|
561
588
|
}
|
|
562
|
-
async function
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return await deps.fs.pathExists(resolveRepoPath(taskRepoRoot, durableDocPath));
|
|
570
|
-
}
|
|
571
|
-
catch {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
async function applyReviewedMemory(taskRepoRoot, state) {
|
|
578
|
-
try {
|
|
579
|
-
const before = await readRunMemorySet(taskRepoRoot, state.runId, "before");
|
|
580
|
-
const after = await readRunMemorySet(taskRepoRoot, state.runId, "after");
|
|
581
|
-
assertCompleteMemorySet(after);
|
|
582
|
-
await applyAndCommitMemorySet(taskRepoRoot, before, after, "chore: update VCM memory");
|
|
583
|
-
const timestamp = now();
|
|
584
|
-
const diff = renderMemoryDiff(before, after);
|
|
585
|
-
const run = await readRun(taskRepoRoot, state.runId);
|
|
586
|
-
await persistRun(taskRepoRoot, {
|
|
587
|
-
...run,
|
|
588
|
-
status: "applied",
|
|
589
|
-
updatedAt: timestamp,
|
|
590
|
-
appliedAt: timestamp,
|
|
591
|
-
afterHashes: hashMemorySet(after),
|
|
592
|
-
diff
|
|
589
|
+
async function recordHarnessEngineerMemoryResult(taskRepoRoot, state) {
|
|
590
|
+
if (!state.reviewBaseCommit) {
|
|
591
|
+
throw new VcmError({
|
|
592
|
+
code: "MEMORY_REVIEW_BASE_MISSING",
|
|
593
|
+
message: "Harness Engineer memory review has no recorded base commit.",
|
|
594
|
+
statusCode: 409,
|
|
595
|
+
hint: "Retry Auto Memory before running Task Harness Retrospective again."
|
|
593
596
|
});
|
|
594
|
-
await deps.fs.writeText(resolveRepoPath(taskRepoRoot, `${MEMORY_REVIEW_RUNS_ROOT}/${state.runId}/applied.patch`), diff);
|
|
595
|
-
await clearActiveState(taskRepoRoot);
|
|
596
597
|
}
|
|
597
|
-
|
|
598
|
-
|
|
598
|
+
const before = await readRunMemorySet(taskRepoRoot, state.runId, "before");
|
|
599
|
+
const after = await readMemorySet(taskRepoRoot);
|
|
600
|
+
await assertOnlyMemoryBlocksChanged(taskRepoRoot, state.runId, after);
|
|
601
|
+
const memoryPaths = MEMORY_FILE_DEFINITIONS.map((definition) => definition.path);
|
|
602
|
+
const uncommittedMemoryDiff = await deps.git.getDiff(taskRepoRoot, "HEAD", null, memoryPaths);
|
|
603
|
+
if (uncommittedMemoryDiff.trim()) {
|
|
604
|
+
throw new VcmError({
|
|
605
|
+
code: "MEMORY_REVIEW_NOT_COMMITTED",
|
|
606
|
+
message: "Harness Engineer left reviewed memory changes uncommitted.",
|
|
607
|
+
statusCode: 409,
|
|
608
|
+
hint: "Commit the reviewed <VCM-memory> changes before ending the retrospective turn."
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
const changedMemoryPaths = MEMORY_FILE_DEFINITIONS
|
|
612
|
+
.filter((definition) => before[definition.path] !== after[definition.path])
|
|
613
|
+
.map((definition) => definition.path);
|
|
614
|
+
const currentHead = await deps.git.getHeadCommit(taskRepoRoot);
|
|
615
|
+
const committedPaths = currentHead === state.reviewBaseCommit
|
|
616
|
+
? []
|
|
617
|
+
: await deps.git.getChangedPaths(taskRepoRoot, state.reviewBaseCommit, currentHead);
|
|
618
|
+
const unexpectedPaths = committedPaths.filter((changedPath) => !memoryPaths.includes(changedPath));
|
|
619
|
+
if (unexpectedPaths.length > 0) {
|
|
620
|
+
throw new VcmError({
|
|
621
|
+
code: "MEMORY_REVIEW_COMMIT_SCOPE_INVALID",
|
|
622
|
+
message: `Harness Engineer memory commit contains files outside managed memory hosts: ${unexpectedPaths.join(", ")}`,
|
|
623
|
+
statusCode: 409,
|
|
624
|
+
hint: "Move unrelated changes to a separate workflow and keep the memory review commit limited to managed memory files."
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
const missingCommittedPaths = changedMemoryPaths.filter((changedPath) => !committedPaths.includes(changedPath));
|
|
628
|
+
if (missingCommittedPaths.length > 0) {
|
|
629
|
+
throw new VcmError({
|
|
630
|
+
code: "MEMORY_REVIEW_COMMIT_MISSING",
|
|
631
|
+
message: `Harness Engineer did not commit reviewed memory files: ${missingCommittedPaths.join(", ")}`,
|
|
632
|
+
statusCode: 409,
|
|
633
|
+
hint: "Commit every changed memory host file before ending the retrospective turn."
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
if (changedMemoryPaths.length === 0 && committedPaths.length > 0) {
|
|
637
|
+
throw new VcmError({
|
|
638
|
+
code: "MEMORY_REVIEW_EMPTY_COMMIT_RANGE",
|
|
639
|
+
message: "Harness Engineer created memory-host commits but left no final memory change.",
|
|
640
|
+
statusCode: 409,
|
|
641
|
+
hint: "Remove the unnecessary memory commits or leave memory unchanged without committing."
|
|
642
|
+
});
|
|
599
643
|
}
|
|
644
|
+
await writeRunMemorySet(taskRepoRoot, state.runId, "after", after);
|
|
645
|
+
const timestamp = now();
|
|
646
|
+
const diff = renderMemoryDiff(before, after);
|
|
647
|
+
const run = await readRun(taskRepoRoot, state.runId);
|
|
648
|
+
await persistRun(taskRepoRoot, {
|
|
649
|
+
...run,
|
|
650
|
+
status: "applied",
|
|
651
|
+
updatedAt: timestamp,
|
|
652
|
+
appliedAt: timestamp,
|
|
653
|
+
afterHashes: hashMemorySet(after),
|
|
654
|
+
diff
|
|
655
|
+
});
|
|
656
|
+
await deps.fs.writeText(resolveRepoPath(taskRepoRoot, `${MEMORY_REVIEW_RUNS_ROOT}/${state.runId}/applied.patch`), diff);
|
|
657
|
+
await clearActiveState(taskRepoRoot);
|
|
600
658
|
}
|
|
601
659
|
async function createAppliedRun(taskRepoRoot, taskSlug, source, before, after) {
|
|
602
660
|
assertCompleteMemorySet(after);
|
|
@@ -815,27 +873,6 @@ function toReviewCandidates(source, currentRole, items) {
|
|
|
815
873
|
...(item.existing ? { existing: item.existing } : {})
|
|
816
874
|
}));
|
|
817
875
|
}
|
|
818
|
-
function memoryPathForTarget(target) {
|
|
819
|
-
if (target === "shared") {
|
|
820
|
-
return "CLAUDE.md";
|
|
821
|
-
}
|
|
822
|
-
const definition = MEMORY_FILE_DEFINITIONS.find((candidate) => "role" in candidate && candidate.role === target);
|
|
823
|
-
if (!definition) {
|
|
824
|
-
throw new Error(`Missing memory file definition for review target: ${target}`);
|
|
825
|
-
}
|
|
826
|
-
return definition.path;
|
|
827
|
-
}
|
|
828
|
-
function memorySetByTarget(memory) {
|
|
829
|
-
return {
|
|
830
|
-
shared: memory["CLAUDE.md"],
|
|
831
|
-
"project-manager": memory[memoryPathForTarget("project-manager")],
|
|
832
|
-
architect: memory[memoryPathForTarget("architect")],
|
|
833
|
-
coder: memory[memoryPathForTarget("coder")],
|
|
834
|
-
tester: memory[memoryPathForTarget("tester")],
|
|
835
|
-
reviewer: memory[memoryPathForTarget("reviewer")],
|
|
836
|
-
"harness-engineer": memory[memoryPathForTarget("harness-engineer")]
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
876
|
function toActiveReview(state) {
|
|
840
877
|
return {
|
|
841
878
|
runId: state.runId,
|
|
@@ -902,6 +939,9 @@ function requireSafeRunId(runId) {
|
|
|
902
939
|
function memoryRunFilePath(runId, snapshot, memoryPath) {
|
|
903
940
|
return `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/${snapshot}/${memoryPath}`;
|
|
904
941
|
}
|
|
942
|
+
function memoryRunHostFilePath(runId, memoryPath) {
|
|
943
|
+
return `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/host-before/${memoryPath}`;
|
|
944
|
+
}
|
|
905
945
|
function missingMemoryBlockError(filePath) {
|
|
906
946
|
return new VcmError({
|
|
907
947
|
code: "MEMORY_BLOCK_MISSING",
|
|
@@ -923,12 +963,6 @@ function hashMemorySet(memory) {
|
|
|
923
963
|
sha256(memory[definition.path] ?? "")
|
|
924
964
|
]));
|
|
925
965
|
}
|
|
926
|
-
function hasSubstantiveMemory(memory) {
|
|
927
|
-
return Object.values(memory).some((content) => {
|
|
928
|
-
const normalized = content.trim();
|
|
929
|
-
return Boolean(normalized && normalized !== "No accumulated project memory yet.");
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
966
|
function sameHashes(left, right) {
|
|
933
967
|
return MEMORY_FILE_DEFINITIONS.every((definition) => left[definition.path] === right[definition.path]);
|
|
934
968
|
}
|
|
@@ -401,6 +401,7 @@ export function createGateReviewService(deps) {
|
|
|
401
401
|
const requestPath = path.posix.join(REQUESTS_DIR, `${requestId}.json`);
|
|
402
402
|
const promptPath = path.posix.join(REQUESTS_DIR, `${requestId}.prompt.md`);
|
|
403
403
|
const requestReportPath = reportPathForRequest(requestId);
|
|
404
|
+
const inputSnapshots = await captureGateInputSnapshots(deps.fs, context.taskRepoRoot, requestId, getSourceArtifacts(gate, codeDiffSources));
|
|
404
405
|
const nextRecord = {
|
|
405
406
|
...record,
|
|
406
407
|
status: "running",
|
|
@@ -444,13 +445,14 @@ export function createGateReviewService(deps) {
|
|
|
444
445
|
codeDiffSource,
|
|
445
446
|
codeDiffSources,
|
|
446
447
|
codeDiff: codeDiffInput,
|
|
448
|
+
inputSnapshots,
|
|
447
449
|
reportPath: requestReportPath,
|
|
448
450
|
latestReportPath: nextRecord.reportPath,
|
|
449
451
|
promptPath: nextRecord.promptPath
|
|
450
452
|
});
|
|
451
453
|
await saveIndex(deps.fs, context.taskRepoRoot, index);
|
|
452
454
|
await notifyArchitecturePlanDisposition(context, gate, false);
|
|
453
|
-
void runGateReview(context, gate, requestId, codeDiffInput, codeDiffSources).catch(() => {
|
|
455
|
+
void runGateReview(context, gate, requestId, codeDiffInput, codeDiffSources, inputSnapshots).catch(() => {
|
|
454
456
|
// runGateReview records failures in the persisted gate state.
|
|
455
457
|
});
|
|
456
458
|
return {
|
|
@@ -460,7 +462,7 @@ export function createGateReviewService(deps) {
|
|
|
460
462
|
message: "Gate review started."
|
|
461
463
|
};
|
|
462
464
|
}
|
|
463
|
-
async function runGateReview(context, gate, requestId, codeDiffInput, codeDiffSources) {
|
|
465
|
+
async function runGateReview(context, gate, requestId, codeDiffInput, codeDiffSources, inputSnapshots = []) {
|
|
464
466
|
const runKey = `${context.taskRepoRoot}:${context.taskSlug}:${gate}`;
|
|
465
467
|
if (activeRuns.has(runKey)) {
|
|
466
468
|
return;
|
|
@@ -476,7 +478,7 @@ export function createGateReviewService(deps) {
|
|
|
476
478
|
await updateRequestStatus(deps.fs, context, requestId, "running", { startedAt: timestamp });
|
|
477
479
|
const reviewDir = resolveRepoPath(context.taskRepoRoot, GATE_REVIEW_DIR);
|
|
478
480
|
const agentPath = resolveRepoPath(context.repoRoot, REVIEWER_AGENT_PATH);
|
|
479
|
-
const prompt = buildGatePrompt(context, gate, requestId, codeDiffInput, codeDiffSources);
|
|
481
|
+
const prompt = buildGatePrompt(context, gate, requestId, codeDiffInput, codeDiffSources, inputSnapshots);
|
|
480
482
|
await deps.fs.ensureDir(reviewDir);
|
|
481
483
|
await deps.fs.ensureDir(resolveRepoPath(context.taskRepoRoot, REQUESTS_DIR));
|
|
482
484
|
await deps.fs.writeText(resolveRepoPath(context.taskRepoRoot, promptPathForRequest(requestId)), prompt);
|
|
@@ -1130,12 +1132,22 @@ function splitLines(value) {
|
|
|
1130
1132
|
.map((line) => line.trim())
|
|
1131
1133
|
.filter(Boolean);
|
|
1132
1134
|
}
|
|
1133
|
-
function buildGatePrompt(context, gate, requestId, codeDiffInput, codeDiffSources) {
|
|
1135
|
+
function buildGatePrompt(context, gate, requestId, codeDiffInput, codeDiffSources, inputSnapshots = []) {
|
|
1134
1136
|
const reportPath = reportPathForRequest(requestId);
|
|
1135
1137
|
const absoluteReportPath = resolveRepoPath(context.taskRepoRoot, reportPath);
|
|
1136
1138
|
const evidence = getSourceArtifacts(gate, codeDiffSources)
|
|
1137
1139
|
.map((relativePath) => `- ${relativePath}`)
|
|
1138
1140
|
.join("\n");
|
|
1141
|
+
const capturedEvidence = inputSnapshots.length > 0
|
|
1142
|
+
? `
|
|
1143
|
+
|
|
1144
|
+
Captured Task Evidence:
|
|
1145
|
+
${inputSnapshots.map((snapshot) => snapshot.status === "captured"
|
|
1146
|
+
? `- ${snapshot.sourcePath} -> ${snapshot.snapshotPath}`
|
|
1147
|
+
: `- ${snapshot.sourcePath} -> <missing at request time>`).join("\n")}
|
|
1148
|
+
|
|
1149
|
+
Use each captured snapshot as the immutable handoff or prior-Gate input for this request. Do not substitute a later rewritten live artifact.`
|
|
1150
|
+
: "";
|
|
1139
1151
|
const gitLine = gate === "architecture-plan"
|
|
1140
1152
|
? "\nDiff: inspect git status/diff in Worktree."
|
|
1141
1153
|
: "";
|
|
@@ -1174,7 +1186,7 @@ Request: ${requestId}
|
|
|
1174
1186
|
Report: ${absoluteReportPath}
|
|
1175
1187
|
|
|
1176
1188
|
Evidence:
|
|
1177
|
-
${evidence}${gitLine}${architectureContract}${validationContract}${codeDiffContract}${codeDiffSection}
|
|
1189
|
+
${evidence}${capturedEvidence}${gitLine}${architectureContract}${validationContract}${codeDiffContract}${codeDiffSection}
|
|
1178
1190
|
|
|
1179
1191
|
Write only Report. Start exactly:
|
|
1180
1192
|
Gate: ${gate}
|
|
@@ -1183,6 +1195,24 @@ Decision: approve|request_changes
|
|
|
1183
1195
|
Summary: <one or two sentences>
|
|
1184
1196
|
[/VCM GATE REVIEW]`;
|
|
1185
1197
|
}
|
|
1198
|
+
async function captureGateInputSnapshots(fs, taskRepoRoot, requestId, sourcePaths) {
|
|
1199
|
+
const snapshots = [];
|
|
1200
|
+
for (const sourcePath of new Set(sourcePaths.filter(isTaskEvidencePath))) {
|
|
1201
|
+
const absoluteSourcePath = resolveRepoPath(taskRepoRoot, sourcePath);
|
|
1202
|
+
if (!(await fs.pathExists(absoluteSourcePath))) {
|
|
1203
|
+
snapshots.push({ sourcePath, status: "missing" });
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
const snapshotPath = inputSnapshotPathForRequest(requestId, sourcePath);
|
|
1207
|
+
await fs.writeText(resolveRepoPath(taskRepoRoot, snapshotPath), await fs.readText(absoluteSourcePath));
|
|
1208
|
+
snapshots.push({ sourcePath, snapshotPath, status: "captured" });
|
|
1209
|
+
}
|
|
1210
|
+
return snapshots;
|
|
1211
|
+
}
|
|
1212
|
+
function isTaskEvidencePath(sourcePath) {
|
|
1213
|
+
return sourcePath.startsWith(".ai/vcm/handoffs/")
|
|
1214
|
+
|| sourcePath.startsWith(".ai/vcm/gate-reviews/");
|
|
1215
|
+
}
|
|
1186
1216
|
async function waitForGateReport(fs, taskRepoRoot, gate, requestId, timestamp, intervalMs) {
|
|
1187
1217
|
const reportPath = reportPathForRequest(requestId);
|
|
1188
1218
|
while (true) {
|
|
@@ -1427,6 +1457,10 @@ function reportPathForRequest(requestId) {
|
|
|
1427
1457
|
function promptPathForRequest(requestId) {
|
|
1428
1458
|
return path.posix.join(REQUESTS_DIR, `${requestId}.prompt.md`);
|
|
1429
1459
|
}
|
|
1460
|
+
function inputSnapshotPathForRequest(requestId, sourcePath) {
|
|
1461
|
+
const taskEvidencePath = sourcePath.replace(/^\.ai\/vcm\//, "");
|
|
1462
|
+
return path.posix.join(REQUESTS_DIR, `${requestId}.inputs`, taskEvidencePath);
|
|
1463
|
+
}
|
|
1430
1464
|
function promptPathForGate(gate) {
|
|
1431
1465
|
return path.posix.join(GATE_REVIEW_DIR, "prompts", `${gate}-gate.md`);
|
|
1432
1466
|
}
|
|
@@ -250,13 +250,14 @@ export function createHarnessFeedbackService(deps) {
|
|
|
250
250
|
"Auto Memory Review:",
|
|
251
251
|
`Role drafts: ${memoryReview.roleDraftsPath}`,
|
|
252
252
|
`Current memory snapshot: ${memoryReview.currentMemoryPath}`,
|
|
253
|
+
"Active memory files:",
|
|
254
|
+
...memoryReview.activeMemoryPaths.map((memoryPath) => `- ${memoryPath}`),
|
|
253
255
|
...(memoryReview.planningCandidatePath
|
|
254
256
|
? [`Architect planning-session candidate: ${memoryReview.planningCandidatePath}`]
|
|
255
257
|
: []),
|
|
256
|
-
`Write the complete reviewed memory set to: ${memoryReview.reviewedMemoryPath}`,
|
|
257
258
|
"",
|
|
258
259
|
"Review every memory candidate against final task evidence while performing this retrospective.",
|
|
259
|
-
"
|
|
260
|
+
"The snapshot files contain only the matching pre-review <VCM-memory> block content.",
|
|
260
261
|
"Before evaluating proposals, review every substantive entry in every current memory snapshot against current code, documentation, and final task evidence.",
|
|
261
262
|
"For each existing entry, decide retain, update, remove, or move-to-durable-doc. Record the decision reason, the impact of removing it, and whether memory or a durable document is the correct source.",
|
|
262
263
|
"Complete this full existing-memory review even when every proposal says no-change.",
|
|
@@ -265,12 +266,14 @@ export function createHarnessFeedbackService(deps) {
|
|
|
265
266
|
"Do not copy the proposer rationale as the review. Verify it against current code, durable documentation, and final task evidence.",
|
|
266
267
|
"Keep only verified, durable, reusable project knowledge. Merge duplicates and keep role-specific knowledge in the matching role file.",
|
|
267
268
|
"Do not record task narrative, temporary state, unverified conclusions, or Harness rules in memory.",
|
|
268
|
-
"Final content must be one exact line written to the selected
|
|
269
|
+
"Final content must be one exact line written to the selected active <VCM-memory> block. Use none when the decision does not keep memory.",
|
|
269
270
|
"For keep-in-memory use Durable doc disposition: memory. For keep-memory-reference use memory-reference. For move-to-durable-doc use durable-doc.",
|
|
270
271
|
"Existing-memory Target must be shared or the exact role name. Decision must be retain, update, remove, or move-to-durable-doc.",
|
|
271
272
|
"Durable doc disposition must be memory, durable-doc, or memory-reference. Use Durable doc path: none with memory and an actual path with the other dispositions.",
|
|
272
273
|
"Do not keep full content in memory when durable-doc is correct. Use memory-reference only when an ongoing role needs the document pointer.",
|
|
273
274
|
"Use none as the complete Existing Memory Decisions body only when no substantive existing memory entry exists.",
|
|
275
|
+
"Apply the reviewed result directly to the <VCM-memory> blocks in the listed active memory files. Do not change any content outside those blocks.",
|
|
276
|
+
"If memory changes, commit only the changed active memory files before ending the turn. Use commit message: chore: update VCM memory. If memory is unchanged, do not create a commit.",
|
|
274
277
|
"Use this exact block in the retrospective report and replace each option or placeholder with one allowed value or a concise summary:",
|
|
275
278
|
"",
|
|
276
279
|
"## Memory Review",
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { ARCHITECTURE_BRIEF_STATUSES, ARCHITECTURE_PLAN_RESULTS, DOCS_SYNC_DECISIONS, FINAL_ACCEPTANCE_DECISIONS, L3_ACTIONS, L3_REQUIRED_VALUES, STRICT_NONE_VALUE, TEST_INFRASTRUCTURE_STATUSES, TEST_RESULTS, renderArtifactOptions } from "../../shared/validation/artifact-contract.js";
|
|
2
|
+
const CURRENT_HANDOFF_NOTICE = "<!-- VCM current handoff: replace this file with one complete, self-contained snapshot of the current result. Restate all still-relevant evidence; do not refer to a prior revision, route message, Session, or transcript as evidence. -->";
|
|
2
3
|
export function renderArchitectureBriefTemplate(taskSlug) {
|
|
3
4
|
return `# Architecture Brief: ${taskSlug}
|
|
4
5
|
|
|
6
|
+
${CURRENT_HANDOFF_NOTICE}
|
|
7
|
+
|
|
5
8
|
Architecture Brief Status: ${renderArtifactOptions(ARCHITECTURE_BRIEF_STATUSES)}
|
|
6
9
|
|
|
7
10
|
## Accepted Outcome
|
|
@@ -28,6 +31,8 @@ TBD
|
|
|
28
31
|
export function renderArchitecturePlanTemplate(taskSlug) {
|
|
29
32
|
return `# Architecture Plan: ${taskSlug}
|
|
30
33
|
|
|
34
|
+
${CURRENT_HANDOFF_NOTICE}
|
|
35
|
+
|
|
31
36
|
Planning Result: ${renderArtifactOptions(ARCHITECTURE_PLAN_RESULTS)}
|
|
32
37
|
|
|
33
38
|
## Accepted Scope
|
|
@@ -135,6 +140,8 @@ TBD
|
|
|
135
140
|
export function renderKnownIssuesTemplate(taskSlug) {
|
|
136
141
|
return `# Known Issues: ${taskSlug}
|
|
137
142
|
|
|
143
|
+
${CURRENT_HANDOFF_NOTICE}
|
|
144
|
+
|
|
138
145
|
## Task Issues
|
|
139
146
|
|
|
140
147
|
No unresolved task issues recorded yet.
|
|
@@ -147,6 +154,8 @@ At task close, promote still-relevant confirmed issues to \`docs/known-issues.md
|
|
|
147
154
|
export function renderTestReportTemplate(taskSlug) {
|
|
148
155
|
return `# Test Report: ${taskSlug}
|
|
149
156
|
|
|
157
|
+
${CURRENT_HANDOFF_NOTICE}
|
|
158
|
+
|
|
150
159
|
Test Result: ${renderArtifactOptions(TEST_RESULTS)}
|
|
151
160
|
|
|
152
161
|
## Evidence Reviewed
|
|
@@ -249,6 +258,8 @@ ${STRICT_NONE_VALUE}
|
|
|
249
258
|
export function renderCoderCompletionTemplate(taskSlug) {
|
|
250
259
|
return `# Coder Completion: ${taskSlug}
|
|
251
260
|
|
|
261
|
+
${CURRENT_HANDOFF_NOTICE}
|
|
262
|
+
|
|
252
263
|
Decision: ready_for_review|incomplete|failed
|
|
253
264
|
|
|
254
265
|
## Scaffold Completion
|
|
@@ -293,6 +304,8 @@ TBD
|
|
|
293
304
|
export function renderArchitectDebugTemplate(taskSlug) {
|
|
294
305
|
return `# Architect Debug: ${taskSlug}
|
|
295
306
|
|
|
307
|
+
${CURRENT_HANDOFF_NOTICE}
|
|
308
|
+
|
|
296
309
|
Status: pending|completed
|
|
297
310
|
|
|
298
311
|
## PM-Routed Failure
|
|
@@ -342,6 +355,8 @@ TBD
|
|
|
342
355
|
export function renderDocsSyncReportTemplate(taskSlug) {
|
|
343
356
|
return `# Docs Sync Report: ${taskSlug}
|
|
344
357
|
|
|
358
|
+
${CURRENT_HANDOFF_NOTICE}
|
|
359
|
+
|
|
345
360
|
## Summary
|
|
346
361
|
|
|
347
362
|
TBD
|
|
@@ -378,6 +393,8 @@ ${renderArtifactOptions(DOCS_SYNC_DECISIONS)}
|
|
|
378
393
|
export function renderFinalAcceptanceTemplate(taskSlug) {
|
|
379
394
|
return `# Final Acceptance: ${taskSlug}
|
|
380
395
|
|
|
396
|
+
${CURRENT_HANDOFF_NOTICE}
|
|
397
|
+
|
|
381
398
|
## Decision
|
|
382
399
|
|
|
383
400
|
${renderArtifactOptions(FINAL_ACCEPTANCE_DECISIONS)}
|
|
@@ -46,6 +46,9 @@ ${renderRoleMemoryRules("architect")}
|
|
|
46
46
|
- Continue across module boundaries whenever the changed behavior path, state ownership, lifecycle, public contract, or failure path crosses them.
|
|
47
47
|
- Stop at standard-library, third-party, external-service, vendor, or generated-code boundaries and record the boundary contract, inputs, outputs, errors, and side effects relevant to the plan.
|
|
48
48
|
- For new behavior, read the existing integration points and caller or consumer paths it will join.
|
|
49
|
+
- When a plan would add a file-local override, normalization, or bypass of a shared default, constant, or documented contract, search the current worktree for the same mechanism. If it already exists in at least two other files, treat the new occurrence as evidence of an upstream ownership or contract problem.
|
|
50
|
+
- For every existing code site the plan or scaffold will change, inspect the complete callable unit or site and verify its current comments, contracts, preconditions, configuration scope, lifecycle assumptions, and safety assumptions against implementation and runtime configuration. Record the verified assumptions in \`architecture-evidence.md\`.
|
|
51
|
+
- When the plan newly handles one member of an existing persisted structure, gate, invariant, or other semantic class, reconstruct the complete directly related member set and record its completeness basis in \`architecture-evidence.md\`.
|
|
49
52
|
- Treat architecture docs, generated context, and comments as navigation evidence, not authority. Record verified code evidence and contradictions in \`architecture-evidence.md\`.
|
|
50
53
|
- Read tests only when needed to understand current behavior, not to assess test adequacy.
|
|
51
54
|
- Do not mark \`Architecture Evidence Status: complete\`, write Architecture Decision, or begin Code Scaffolding while a project-owned symbol remains unresolved on a behavior path the plan will change.
|
|
@@ -69,13 +72,16 @@ ${renderRoleMemoryRules("architect")}
|
|
|
69
72
|
|
|
70
73
|
#### Plan Document
|
|
71
74
|
|
|
72
|
-
- \`architecture-plan.md\` must start with \`Planning Result: complete|incomplete|user clarification required\` and use these sections: Accepted Scope, Current Code Reality, Architecture Decision, Module/File Plan, Public Surface Impact, Scaffold Manifest, Scaffold Build Evidence, Tester Coverage Hints, Docs Impact, Known Risks, and Coder Handoff Notes.
|
|
75
|
+
- \`architecture-plan.md\` must start with \`Planning Result: complete|incomplete|user clarification required\` and use these sections: Accepted Scope, Current Code Reality, Existing Assumptions And Class Coverage, Architecture Decision, Module/File Plan, Public Surface Impact, Scaffold Manifest, Scaffold Build Evidence, Tester Coverage Hints, Docs Impact, Known Risks, and Coder Handoff Notes.
|
|
73
76
|
- Use \`Planning Result: complete\` only when: the plan document is complete; the Scaffold Manifest ledger reconciles one to one against the committed markers; and \`Scaffold Build Evidence\` records a green compile/typecheck run at the current scaffold commit hash. Include the same Planning Result in the route message to project-manager; do not select the next route.
|
|
74
|
-
- \`architecture-plan.md\` is the current executable plan, not a changelog.
|
|
77
|
+
- \`architecture-plan.md\` is the complete, self-contained current executable plan, not a changelog. Each revision must restate every still-current decision, constraint, evidence reference, scaffold row, risk, and implementation instruction needed to execute and review the plan without a prior revision. Replace superseded decisions, obsolete scaffold rows, stale risks, and old implementation notes instead of appending history.
|
|
75
78
|
- \`Accepted Scope\`: state the PM-routed task scope and the confirmed brief's required user-visible outcome and decisions, plus any explicit non-scope that prevents accidental expansion.
|
|
76
79
|
- \`Current Code Reality\`: cite \`architecture-evidence.md\` and summarize only the verified facts that constrain the architecture decision. Do not duplicate the full evidence inventory. For any module whose build configuration the plan changes, the evidence artifact must quote its complete direct dependency list from the package manifest, never a summary or selection.
|
|
77
80
|
- Any enumeration the plan presents as complete over the codebase — call-site inventories, module or file lists, symbol sets — must either record the deterministic, repository-local command that generates it (run at the scaffold commit, the set transcribed from its output) or be explicitly marked as judgment-derived with the evidence basis for its completeness. A complete-claimed enumeration with neither is not evidence.
|
|
81
|
+
- \`Existing Assumptions And Class Coverage\`: include a \`Touched Site | Verified Assumption Or Contract | Evidence | Plan Effect | Disposition\` table and a \`Class Source | Completeness Basis | Member | Plan Disposition\` table. Cover every existing code site named by the Module/File Plan or Scaffold Manifest, including assumptions the plan preserves, updates, or invalidates. When the plan newly handles one member of an existing persisted structure, gate, invariant, or other semantic class, list every directly related member and its disposition. A bare \`None.\` is allowed only when the plan changes no existing code site and handles no member of an existing class.
|
|
78
82
|
- \`Architecture Decision\`: use the required Changed Behavior Flow, Ownership, Data Flow, Lifecycle, Boundaries, Invariants, Failure Model, and Decision Rationale subsections. Describe why the design fits verified current code.
|
|
83
|
+
- Correct every verified existing assumption that the plan invalidates through the Architecture Decision, affected callable surfaces, scaffold, and docs impact. Do not leave Coder to discover or reconcile the contradiction.
|
|
84
|
+
- When the repeated-workaround threshold is met, \`Architecture Decision\` must explicitly fix the owning behavior, confirm that local handling is intended and correct the owning documentation, or record the unresolved issue and affected call sites through Known Issues Sync. Extracting the workaround into a helper is not an upstream disposition.
|
|
79
85
|
- \`Module/File Plan\`: list each affected module, changed or created file, file responsibility, why it is in scope, expected change, dependency direction, user-visible behavior change, durable comment needs, and every non-private callable surface intended for use outside its file. For every ledger item that consumes or sources cross-module data, name the module and symbol that owns or produces the data, trace the source-to-consumer path, and identify every field, parameter, accessor, trait method, command field, dependency, or other cross-file surface required by that path.
|
|
80
86
|
- \`Public Surface Impact\`: state changed APIs, routes, commands, events, exports, storage formats, configuration, UI behavior, visibility changes, side effects, error boundaries, expected callers, or explicitly state none.
|
|
81
87
|
- \`Scaffold Manifest\`: an item ledger — one entry per implementation item. Use columns in the exact order \`ID | Action | File | ...\`; use an ID matching \`AA-1\` through \`AAAAAA-9999\`, an Action of exactly \`create\`, \`change\`, or \`delete\`, and a backticked repo-relative File path. An item is one created body or surface, one required change site — one contiguous edit region inside an existing body or surface — or one deletion of a body, site, or file. An item not in the ledger is not in the plan; coder must not implement it.
|
|
@@ -143,7 +149,7 @@ ${renderRoleMemoryRules("architect")}
|
|
|
143
149
|
- Before handing off an architect-completed Debug Mode fix, run the smallest relevant L0 fast checks for the touched files or changed modules: format, lint, typecheck, boundary, dependency, or project-defined equivalents. If a check cannot run, report the exact reason.
|
|
144
150
|
- If the Debug Mode fix changes module structure, source/test file lists, public APIs, routes, exports, re-exports, or other externally consumed surface, run \`.ai/tools/generate-module-index\` / \`.ai/tools/generate-public-surface\` or their \`--check\` mode as applicable.
|
|
145
151
|
- After an architect-completed Debug Mode fix, report the completed result and evidence path to project-manager. Do not select the next route.
|
|
146
|
-
- Before reporting a completed Debug Mode code fix, replace \`.ai/vcm/handoffs/architect-debug.md\` with current evidence. Set \`Status: completed\` and
|
|
152
|
+
- Before reporting a completed Debug Mode code fix, replace \`.ai/vcm/handoffs/architect-debug.md\` with complete, self-contained current evidence. Set \`Status: completed\` and restate the PM-routed failure, confirmed root cause, implementation, changed files and public-surface impact, baseline tests, diagnostic and L0/L1 validation, L2/L3 validation, generated-context status, remaining failure evidence, and final disposition needed to review the result without a prior revision. Remove superseded evidence instead of appending history.
|
|
147
153
|
- Final disposition must be one of: local fix completed, normal architecture plan required, or user clarification required.
|
|
148
154
|
- Report root cause, changed files, scope and public-surface impact, L0/L1 results, applicable L2/L3 results, baseline tests added or skipped with reason, generated-context regeneration or freshness check when applicable, final disposition, and the Debug completion evidence path when code was changed.
|
|
149
155
|
|
|
@@ -213,6 +219,8 @@ Small diff, minimum change, localized fix, or preserving the current implementat
|
|
|
213
219
|
9. \`Implementation And Validation\`
|
|
214
220
|
10. \`Final Disposition\`
|
|
215
221
|
|
|
222
|
+
Each rewritten \`architecture-diagnosis.md\` must be a complete, self-contained current diagnosis and implementation result. Restate all still-relevant code-reading closure, evidence, architecture findings, changes, validation, and remaining failure evidence; do not refer to a prior round or superseded diagnosis as evidence.
|
|
223
|
+
|
|
216
224
|
\`Implementation And Validation\` must use these subsections: \`Changed Files And Public Surface\`, \`Baseline Tests\`, \`Diagnostic And L0/L1 Validation\`, \`L2/L3 Validation\`, \`Generated Context\`, and \`Commit\`.
|
|
217
225
|
|
|
218
226
|
\`L2/L3 Validation\` must use this table:
|
|
@@ -301,6 +309,7 @@ Small diff, minimum change, localized fix, or preserving the current implementat
|
|
|
301
309
|
- Write \`.ai/vcm/handoffs/docs-sync-report.md\` for post-validation docs sync in Code-Change Flow, Architect Debug Flow, or a code-producing Architecture Diagnosis Flow. Do not write it for Docs-Only Flow or a Debug/Diagnosis Branch.
|
|
302
310
|
- In Docs-Only Flow, the Architect role result must record the decision, changed documents, evidence reviewed, checks performed, and commit.
|
|
303
311
|
- The report records decision, evidence reviewed, current-truth reconciliation, generated-context freshness, cross-document consistency, architecture docs, active plans, testing-doc consistency, known-issues disposition, durable-doc audit command and result, docs updated, docs left unchanged, remaining documentation risks, and handoff notes.
|
|
312
|
+
- Each rewritten \`docs-sync-report.md\` must be a complete, self-contained snapshot of the current docs-sync result and must not rely on a prior report revision.
|
|
304
313
|
- \`Decision\` must be \`synced\`, \`unchanged\`, or \`blocked\`.
|
|
305
314
|
|
|
306
315
|
### Background Jobs
|
|
@@ -66,6 +66,14 @@ If a reusable harness problem is suspected, it is enough to record a concise fee
|
|
|
66
66
|
- Runtime task records and handoffs under \`.ai/vcm/\` are temporary. Durable facts must move into code, tests, PR text, commit history, or long-term docs.
|
|
67
67
|
- Only architect writes \`.ai/vcm/handoffs/known-issues.md\`; other roles report unresolved findings back through their own handoff artifacts.
|
|
68
68
|
|
|
69
|
+
## VCM Current Handoff Contract
|
|
70
|
+
|
|
71
|
+
- A role-owned handoff under \`.ai/vcm/handoffs/\` is the complete current result for that artifact, not an append-only log or a pointer to an earlier revision.
|
|
72
|
+
- Whenever a handoff is rewritten, make the new revision self-contained: restate every still-relevant decision, evidence item, command, result, coverage mapping, finding, approval, and remaining action needed to interpret the current result without another round's artifact.
|
|
73
|
+
- Remove or replace superseded content. Do not use a prior round, prior report revision, consumed route message, role Session, or transcript as a substitute for evidence in the current handoff.
|
|
74
|
+
- A handoff may cite current code, durable docs, commits, preserved job output, or request-scoped Gate Review evidence that still exists at the cited path.
|
|
75
|
+
- Before routing an artifact reference, confirm the referenced handoff satisfies this contract.
|
|
76
|
+
|
|
69
77
|
## User Communication
|
|
70
78
|
|
|
71
79
|
- A message without a VCM marker is user communication.
|
|
@@ -70,7 +70,7 @@ ${renderRoleMemoryRules("coder")}
|
|
|
70
70
|
|
|
71
71
|
### Handoff
|
|
72
72
|
|
|
73
|
-
- Write \`.ai/vcm/handoffs/coder-completion.md\` before routing back to project-manager. This file is the current implementation completion evidence, not a log
|
|
73
|
+
- Write \`.ai/vcm/handoffs/coder-completion.md\` before routing back to project-manager. 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.
|
|
74
74
|
- \`coder-completion.md\` must include \`Decision: ready_for_review | incomplete | failed\`.
|
|
75
75
|
- \`coder-completion.md\` must report every Scaffold Manifest item disposition in the fixed Scaffold Completion table, plus changed files, private helpers added, manifest deviations as report-only facts, generated context status, baseline tests added or updated, L0/L1 commands and results, worker commits and integration status when workers were used, and compile/typecheck or L0/L1 failures.
|
|
76
76
|
- Use this structure:
|
|
@@ -8,6 +8,8 @@ ${renderRoleMemoryRules("reviewer")}
|
|
|
8
8
|
|
|
9
9
|
Review only the gate in the VCM prompt. Use the task and worktree paths named there. Project memory may orient you, but only current worktree evidence can decide the gate.
|
|
10
10
|
|
|
11
|
+
When the VCM prompt maps a task-evidence path to an immutable request snapshot, review that snapshot as the handoff or prior-Gate input for this request. Do not substitute a later rewritten live handoff. Continue to inspect current code, tests, durable docs, generated context, and the named commit range wherever the gate requires current-worktree evidence.
|
|
12
|
+
|
|
11
13
|
Use only these decisions:
|
|
12
14
|
|
|
13
15
|
- \`approve\`: required gate evidence is present, current, internally consistent, sufficient for that gate, and has no gate-blocking finding.
|
|
@@ -117,6 +119,26 @@ of verifying only the cited instances. A claimed-complete enumeration with
|
|
|
117
119
|
neither a recorded command nor a judgment-derived basis, or one that fails
|
|
118
120
|
reconstruction, is unsupported by code evidence and is \`request_changes\`.
|
|
119
121
|
|
|
122
|
+
Run a backward-impact pass over the plan:
|
|
123
|
+
|
|
124
|
+
- For every existing code site named by the Module/File Plan or Scaffold
|
|
125
|
+
Manifest, inspect the complete callable unit or site and verify its current
|
|
126
|
+
comments, contracts, preconditions, configuration scope, lifecycle
|
|
127
|
+
assumptions, and safety assumptions against implementation and runtime
|
|
128
|
+
configuration.
|
|
129
|
+
- Identify which verified assumptions the plan preserves, updates, or
|
|
130
|
+
invalidates. Request changes when the plan omits a touched site, misstates an
|
|
131
|
+
assumption, or invalidates one without correcting the architecture, affected
|
|
132
|
+
surfaces, scaffold, and docs impact.
|
|
133
|
+
- When the plan newly handles one member of an existing persisted structure,
|
|
134
|
+
gate, invariant, or other semantic class, independently reconstruct the
|
|
135
|
+
complete directly related member set from its declaration, catalogue,
|
|
136
|
+
adjacent contract, or repository search. Verify that every member has an
|
|
137
|
+
evidence-backed plan disposition.
|
|
138
|
+
- Keep this pass bounded to plan-cited or scaffold-touched existing sites and
|
|
139
|
+
their directly related semantic classes. Do not expand it into an unrelated
|
|
140
|
+
whole-repository review.
|
|
141
|
+
|
|
120
142
|
Request changes when the plan is structurally complete but architecturally
|
|
121
143
|
under-specified, logically inconsistent, unsupported by code evidence, unsafe
|
|
122
144
|
for boundary cases, conflicts with current project architecture, or leaves key
|
|
@@ -272,6 +294,18 @@ Verify that the Diagnosis evidence records applicable L2/L3 validation for the
|
|
|
272
294
|
diagnosed failure path. Request changes when an applicable check was not run,
|
|
273
295
|
did not pass, or does not exercise that failure path.
|
|
274
296
|
|
|
297
|
+
When a changed production or test hunk adds a file-local override,
|
|
298
|
+
normalization, or bypass of a shared default, constant, or documented contract,
|
|
299
|
+
search the current worktree for the same mechanism. If it already exists in at
|
|
300
|
+
least two other files, request changes unless the accepted architecture
|
|
301
|
+
explicitly fixes the owning behavior, confirms that local handling is intended
|
|
302
|
+
and corrects the owning documentation, or records the unresolved issue and
|
|
303
|
+
affected call sites through the Architect-owned known-issue flow. A documented
|
|
304
|
+
post-validation Architect docs sync satisfies the documentation timing; the
|
|
305
|
+
correct disposition must already be explicit. Extracting the workaround into a
|
|
306
|
+
helper is not an upstream disposition. Classify the finding as \`implementation\`
|
|
307
|
+
unless the owning behavior and contract are entirely test-only.
|
|
308
|
+
|
|
275
309
|
Check every source for project coding-standard compliance, unnecessary
|
|
276
310
|
duplication or abstraction, inconsistent error handling, unhandled fallible
|
|
277
311
|
paths, debug/task-only artifacts, \`VCM:CODE\`, task-process comments or labels,
|
|
@@ -313,6 +347,8 @@ Use this findings structure:
|
|
|
313
347
|
- End-To-End Flow:
|
|
314
348
|
- Scope Fit:
|
|
315
349
|
- Code Reality:
|
|
350
|
+
- Invalidated Assumptions:
|
|
351
|
+
- Existing-Class Completeness:
|
|
316
352
|
- Ownership:
|
|
317
353
|
- Data Flow:
|
|
318
354
|
- Lifecycle:
|
|
@@ -39,21 +39,22 @@ You are not part of the task workflow round state.
|
|
|
39
39
|
yourself.
|
|
40
40
|
- Retrospective Mode: analyze a completed task for reusable harness problems.
|
|
41
41
|
When the assigned prompt includes Auto Memory Review, also review the memory
|
|
42
|
-
proposals
|
|
42
|
+
proposals, update the active \`<VCM-memory>\` blocks, and commit those memory
|
|
43
|
+
changes yourself.
|
|
43
44
|
- VCM Feedback Mode: draft VCM product, installer, UI, or fixed-template issue
|
|
44
45
|
feedback. Do not submit without explicit in-session user authorization.
|
|
45
46
|
|
|
46
47
|
## Change Policy
|
|
47
48
|
|
|
48
|
-
- Apply edits only in Bootstrap Apply Mode, to
|
|
49
|
-
|
|
50
|
-
approved harness change.
|
|
49
|
+
- Apply edits only in Bootstrap Apply Mode, to active \`<VCM-memory>\` blocks
|
|
50
|
+
during an assigned Auto Memory Retrospective, or when VCM explicitly asks you
|
|
51
|
+
to apply an approved harness change.
|
|
51
52
|
- When applying edits, work only in the active task worktree named by VCM. Do not
|
|
52
53
|
edit the base repository root unless VCM explicitly says so.
|
|
53
54
|
- In Proposal Mode, do not edit files.
|
|
54
55
|
- In Retrospective Mode, write the assigned retrospective report and, only when
|
|
55
|
-
Auto Memory Review is included in the prompt, the assigned
|
|
56
|
-
|
|
56
|
+
Auto Memory Review is included in the prompt, directly update the assigned
|
|
57
|
+
active memory blocks. After every assigned pending feedback has a recorded
|
|
57
58
|
disposition, delete those processed feedback files.
|
|
58
59
|
- Commit every applied harness change yourself before ending your turn.
|
|
59
60
|
- Do not overwrite VCM fixed managed blocks.
|
|
@@ -92,9 +93,13 @@ You are not part of the task workflow round state.
|
|
|
92
93
|
assigned by VCM.
|
|
93
94
|
- Do not record task narrative, temporary state, unverified conclusions, or
|
|
94
95
|
Harness rules in memory.
|
|
95
|
-
- Edit only the
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
- Edit only the \`<VCM-memory>\` blocks in the active memory files assigned by
|
|
97
|
+
VCM. Do not change surrounding role definitions, project context, or managed
|
|
98
|
+
Harness blocks during Auto Memory Review.
|
|
99
|
+
- If the reviewed memory changes, commit only the changed active memory files
|
|
100
|
+
with commit message \`chore: update VCM memory\` before ending the turn. If
|
|
101
|
+
memory is unchanged, do not create a commit. VCM records the committed result,
|
|
102
|
+
diff, and review history; it does not apply or commit the memory for you.
|
|
98
103
|
|
|
99
104
|
## Task Harness Retrospective
|
|
100
105
|
|
|
@@ -18,6 +18,8 @@ Project-specific rules may be added outside the VCM managed block when they make
|
|
|
18
18
|
- Do not derive logic from visible test fixtures, fixed sample values, snapshot text, or special branches that only satisfy known tests.
|
|
19
19
|
- Coder and Coder Worker keep the diff inside the approved plan. In Debug Mode or Architecture Diagnosis Mode, Architect owns the technical change boundary after confirming the root cause.
|
|
20
20
|
- Preserve existing behavior unless the approved plan or a confirmed Debug/Diagnosis root cause changes it.
|
|
21
|
+
- Do not introduce the same file-local override, normalization, or bypass of a shared default, constant, or documented contract into a third file unless the accepted architecture explicitly fixes the owning behavior, confirms local handling is intended and corrects the owning documentation, or records the unresolved issue and affected call sites through the Architect-owned known-issue flow.
|
|
22
|
+
- Extracting the repeated local workaround into a helper is not an upstream disposition.
|
|
21
23
|
|
|
22
24
|
## Comments
|
|
23
25
|
|
|
@@ -179,7 +179,7 @@ L3 Required: yes|no
|
|
|
179
179
|
- When \`L3 Required: yes\`, include at least one complete flow-to-case mapping. \`Action\` must be \`run-existing\`, \`updated\`, or \`added\`.
|
|
180
180
|
- When \`L3 Required: no\`, use \`Not-Required Evidence\` to prove every condition in the L3 not-required rule.
|
|
181
181
|
- In every flow, if tests, fixtures, test-only helpers, or \`docs/TESTING.md\` changed, commit those changes before reporting a terminal result and record the changed files and commit in \`test-report.md\`. If no tracked files changed, record that no commit was required.
|
|
182
|
-
- \`test-report.md\` is the current validation evidence, not a log
|
|
182
|
+
- \`test-report.md\` is the complete, self-contained current validation evidence, not a log. Each revision must independently support its current \`Test Result\` by restating every still-relevant test or external check, coverage mapping, command and result, test-infrastructure fact, failed expectation, skipped check, gap, approval, blocking issue, and remaining validation needed to review that result without a prior revision. Remove superseded evidence, but never replace current evidence with “as recorded in the prior round”, a prior report reference, or a Session/transcript reference.
|
|
183
183
|
- In \`Coverage Mapping\`, map each accepted changed behavior or relevant risk to its validation level, actual test file and case or external evidence, exercised entry path and key assertions, result, and any remaining gap.
|
|
184
184
|
- In \`Validation Progress\`, record \`Completed Validation\` and \`Remaining Validation\`. A final \`pass\` report must set remaining validation to \`None\`.
|
|
185
185
|
- Use \`pass\` only when required validation completed and no blocking test failure, missing required coverage, unacceptable test weakness, or unresolved validation risk remains.
|
|
@@ -18,6 +18,8 @@ During an active Architect Interview, handle the user's answers and final confir
|
|
|
18
18
|
- If a fact can be established from the worktree or available tools, investigate it instead of asking the user.
|
|
19
19
|
- If code, docs, and the user's requested behavior conflict, state the concrete conflict and ask which user-visible behavior is intended.
|
|
20
20
|
- Maintain \`.ai/vcm/handoffs/architecture-evidence.md\` while reading. Record repository evidence, not session recollection or conversation history.
|
|
21
|
+
- For every existing code site the drafted mechanism may change, record the site's verified safety, configuration, lifecycle, state, data, and contract assumptions. Treat comments and docs as claims to verify against implementation and runtime configuration, not as authority or text to ignore.
|
|
22
|
+
- When the draft newly handles one member of an existing persisted structure, gate, invariant, or other semantic class, reconstruct and record the complete directly related member set and the evidence basis for its completeness.
|
|
21
23
|
|
|
22
24
|
## Feasibility Draft
|
|
23
25
|
|
|
@@ -89,6 +91,10 @@ Architecture Evidence Status: incomplete|complete
|
|
|
89
91
|
|
|
90
92
|
## Callers And Consumers
|
|
91
93
|
|
|
94
|
+
## Existing Assumptions
|
|
95
|
+
|
|
96
|
+
## Related Class Inventories
|
|
97
|
+
|
|
92
98
|
## External Boundaries
|
|
93
99
|
|
|
94
100
|
## Code And Docs Conflicts
|
|
@@ -96,7 +102,7 @@ Architecture Evidence Status: incomplete|complete
|
|
|
96
102
|
## Evidence Commands
|
|
97
103
|
\`\`\`
|
|
98
104
|
|
|
99
|
-
Identify inspected files and symbols, callers or consumers, state and side effects, verified behavior, and the worktree revision. Replace stale evidence instead of appending history.
|
|
105
|
+
Identify inspected files and symbols, callers or consumers, state and side effects, verified behavior, existing assumptions and their scope, related class members and their completeness basis, and the worktree revision. Replace stale evidence instead of appending history.
|
|
100
106
|
|
|
101
107
|
## Completion
|
|
102
108
|
|
|
@@ -123,6 +123,8 @@ accepted|accepted-with-known-risks|needs-coder-follow-up|needs-architect-follow-
|
|
|
123
123
|
## Final User Summary
|
|
124
124
|
\`\`\`
|
|
125
125
|
|
|
126
|
+
Each rewrite must be a complete, self-contained snapshot of the current acceptance decision and supporting evidence. Restate every still-relevant evidence result, file classification, validation result, Gate decision, docs-sync result, known-issues disposition, cleanup result, risk, and next action; do not rely on a prior acceptance revision, route message, Session, or transcript.
|
|
127
|
+
|
|
126
128
|
The final user summary should be concise and include files changed, validation, docs updates, open risks, and next action.
|
|
127
129
|
`;
|
|
128
130
|
}
|
|
@@ -44,7 +44,7 @@ If the same route file already contains a not-yet-delivered message, update that
|
|
|
44
44
|
|
|
45
45
|
## Message Format
|
|
46
46
|
|
|
47
|
-
Use the smallest body that is complete. Include artifact refs instead of copying long documents.
|
|
47
|
+
Use the smallest body that is complete. Include artifact refs instead of copying long documents. Reference a role handoff only after confirming its current revision is complete and self-contained; never use an artifact ref to stand in for evidence that exists only in a prior revision, consumed route message, Session, or transcript.
|
|
48
48
|
|
|
49
49
|
For simple user relay, use a lightweight body instead of the formal dispatch format.
|
|
50
50
|
|
package/package.json
CHANGED
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
export function validateMemoryReviewReport(content, candidates, hasExistingMemory = false) {
|
|
2
|
-
return parseMemoryReviewReport(content, candidates, hasExistingMemory).error;
|
|
3
|
-
}
|
|
4
|
-
export function parseMemoryReviewReport(content, candidates, hasExistingMemory = false) {
|
|
5
|
-
const memoryReview = /^## Memory Review\s*$/m.exec(content);
|
|
6
|
-
if (!memoryReview || memoryReview.index === undefined) {
|
|
7
|
-
return { error: "is missing the ## Memory Review section" };
|
|
8
|
-
}
|
|
9
|
-
const sectionStart = memoryReview.index + memoryReview[0].length;
|
|
10
|
-
const nextSection = /^## (?!#)/m.exec(content.slice(sectionStart));
|
|
11
|
-
const section = content.slice(sectionStart, nextSection?.index === undefined ? content.length : sectionStart + nextSection.index);
|
|
12
|
-
if (!/^Existing memory reviewed:[ \t]*complete[ \t]*$/m.test(section)) {
|
|
13
|
-
return { error: "must declare Existing memory reviewed: complete" };
|
|
14
|
-
}
|
|
15
|
-
if (!/^Reviewed memory set:[ \t]*complete[ \t]*$/m.test(section)) {
|
|
16
|
-
return { error: "must declare Reviewed memory set: complete" };
|
|
17
|
-
}
|
|
18
|
-
const dispositions = extractReportSubsection(section, "Proposal Decisions", "Existing Memory Decisions");
|
|
19
|
-
if (dispositions === undefined) {
|
|
20
|
-
return { error: "is missing the Proposal Decisions subsection" };
|
|
21
|
-
}
|
|
22
|
-
const decisionsResult = parseProposalDecisions(dispositions, candidates);
|
|
23
|
-
if (decisionsResult.error) {
|
|
24
|
-
return decisionsResult;
|
|
25
|
-
}
|
|
26
|
-
const existingDecisions = extractReportSubsection(section, "Existing Memory Decisions", "Existing Memory Changes");
|
|
27
|
-
if (existingDecisions === undefined) {
|
|
28
|
-
return { error: "is missing the Existing Memory Decisions subsection" };
|
|
29
|
-
}
|
|
30
|
-
const existingDecisionError = validateExistingMemoryDecisions(existingDecisions, hasExistingMemory);
|
|
31
|
-
if (existingDecisionError) {
|
|
32
|
-
return { error: existingDecisionError };
|
|
33
|
-
}
|
|
34
|
-
const existingChanges = extractReportSubsection(section, "Existing Memory Changes");
|
|
35
|
-
if (existingChanges === undefined) {
|
|
36
|
-
return { error: "is missing the Existing Memory Changes subsection" };
|
|
37
|
-
}
|
|
38
|
-
for (const field of ["retained", "updated", "removed"]) {
|
|
39
|
-
const matches = existingChanges.match(new RegExp(`^- ${field}:[ \\t]*\\S.*$`, "gm"));
|
|
40
|
-
if (matches?.length !== 1) {
|
|
41
|
-
return { error: `must record exactly one non-empty ${field} summary` };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return { decisions: decisionsResult.decisions };
|
|
45
|
-
}
|
|
46
|
-
export async function validateMemoryReviewOutput(input) {
|
|
47
|
-
const retainedProposalContent = new Set(input.decisions
|
|
48
|
-
.filter((decision) => (decision.decision === "keep-in-memory"
|
|
49
|
-
|| decision.decision === "keep-memory-reference"))
|
|
50
|
-
.map((decision) => decision.finalContent)
|
|
51
|
-
.filter((content) => Boolean(content && content !== "none")));
|
|
52
|
-
for (const decision of input.decisions) {
|
|
53
|
-
const originalBefore = memoryLines(input.before[decision.target]);
|
|
54
|
-
const originalAfter = memoryLines(input.after[decision.target]);
|
|
55
|
-
if (decision.operation === "remove") {
|
|
56
|
-
if (decision.decision === "remove" && originalAfter.has(decision.existing ?? "")) {
|
|
57
|
-
return `still contains ${decision.candidateId}, which the report decided to remove`;
|
|
58
|
-
}
|
|
59
|
-
if (decision.decision === "retain" && !originalAfter.has(decision.existing ?? "")) {
|
|
60
|
-
return `does not retain ${decision.candidateId} as required by the report`;
|
|
61
|
-
}
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
const keepsMemory = decision.decision === "keep-in-memory"
|
|
65
|
-
|| decision.decision === "keep-memory-reference";
|
|
66
|
-
if (keepsMemory) {
|
|
67
|
-
const finalTarget = decision.finalTarget;
|
|
68
|
-
if (!memoryLines(input.after[finalTarget]).has(decision.finalContent ?? "")) {
|
|
69
|
-
return `does not contain the exact Final content for ${decision.candidateId} in ${finalTarget}`;
|
|
70
|
-
}
|
|
71
|
-
if (decision.operation === "update"
|
|
72
|
-
&& decision.existing
|
|
73
|
-
&& decision.existing !== decision.finalContent
|
|
74
|
-
&& originalAfter.has(decision.existing)) {
|
|
75
|
-
return `still contains the superseded Existing value for ${decision.candidateId}`;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
else if (decision.candidate
|
|
79
|
-
&& !memorySetContains(input.before, decision.candidate)
|
|
80
|
-
&& memorySetContains(input.after, decision.candidate)
|
|
81
|
-
&& !retainedProposalContent.has(decision.candidate)) {
|
|
82
|
-
return `contains the rejected or durable-doc-only candidate ${decision.candidateId}`;
|
|
83
|
-
}
|
|
84
|
-
if (decision.durableDocDisposition === "memory-reference"
|
|
85
|
-
&& decision.durableDocPath
|
|
86
|
-
&& !(await input.durableDocExists(decision.durableDocPath))) {
|
|
87
|
-
return `references a missing durable document for ${decision.candidateId}: ${decision.durableDocPath}`;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
function parseProposalDecisions(content, candidates) {
|
|
93
|
-
const body = content.trim();
|
|
94
|
-
if (candidates.length === 0) {
|
|
95
|
-
return body === "none"
|
|
96
|
-
? { decisions: [] }
|
|
97
|
-
: { error: "Proposal Decisions must be none when no memory candidate exists" };
|
|
98
|
-
}
|
|
99
|
-
if (body === "none") {
|
|
100
|
-
return { error: "Proposal Decisions cannot be none while memory candidates exist" };
|
|
101
|
-
}
|
|
102
|
-
const headings = [...body.matchAll(/^#### Candidate ([A-Za-z0-9._:-]+)[ \t]*$/gm)];
|
|
103
|
-
if (headings.length === 0 || body.slice(0, headings[0].index).trim()) {
|
|
104
|
-
return { error: "Proposal Decisions must contain one #### Candidate <id> block per memory candidate" };
|
|
105
|
-
}
|
|
106
|
-
const expected = new Map(candidates.map((candidate) => [candidate.id, candidate]));
|
|
107
|
-
const seen = new Set();
|
|
108
|
-
const decisions = [];
|
|
109
|
-
for (let index = 0; index < headings.length; index += 1) {
|
|
110
|
-
const heading = headings[index];
|
|
111
|
-
const candidateId = heading[1];
|
|
112
|
-
if (seen.has(candidateId)) {
|
|
113
|
-
return { error: `contains duplicate proposal decision for ${candidateId}` };
|
|
114
|
-
}
|
|
115
|
-
const candidate = expected.get(candidateId);
|
|
116
|
-
if (!candidate) {
|
|
117
|
-
return { error: `contains an unexpected proposal decision for ${candidateId}` };
|
|
118
|
-
}
|
|
119
|
-
seen.add(candidateId);
|
|
120
|
-
const itemStart = (heading.index ?? 0) + heading[0].length;
|
|
121
|
-
const itemEnd = index + 1 < headings.length
|
|
122
|
-
? headings[index + 1].index ?? body.length
|
|
123
|
-
: body.length;
|
|
124
|
-
const result = parseProposalDecision(candidate, body.slice(itemStart, itemEnd).trim());
|
|
125
|
-
if (result.error) {
|
|
126
|
-
return { error: result.error };
|
|
127
|
-
}
|
|
128
|
-
decisions.push(result.decision);
|
|
129
|
-
}
|
|
130
|
-
const missing = candidates.find((candidate) => !seen.has(candidate.id));
|
|
131
|
-
if (missing) {
|
|
132
|
-
return { error: `is missing the proposal decision for ${missing.id}` };
|
|
133
|
-
}
|
|
134
|
-
return { decisions };
|
|
135
|
-
}
|
|
136
|
-
function parseProposalDecision(candidate, item) {
|
|
137
|
-
if (candidate.operation === "remove") {
|
|
138
|
-
const match = /^Source:[ \t]*(\S.*)[ \t]*\nOperation:[ \t]*remove[ \t]*\nTarget:[ \t]*(shared|project-manager|architect|coder|tester|reviewer|harness-engineer)[ \t]*\nExisting:[ \t]*(\S.*)[ \t]*\nDecision:[ \t]*(remove|retain)[ \t]*\nReason:[ \t]*(\S.*)[ \t]*\nEvidence checked:[ \t]*(\S.*)[ \t]*$/.exec(item);
|
|
139
|
-
if (!match) {
|
|
140
|
-
return {
|
|
141
|
-
error: `Proposal Decision ${candidate.id} must contain one-line Source, Operation, Target, Existing, Decision, Reason, and Evidence checked fields in that order`
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
if (match[1] !== candidate.source
|
|
145
|
-
|| match[2] !== candidate.target
|
|
146
|
-
|| match[3] !== candidate.existing) {
|
|
147
|
-
return { error: `Proposal Decision ${candidate.id} does not match its source proposal` };
|
|
148
|
-
}
|
|
149
|
-
if (isUnresolvedReviewText(match[5]) || isUnresolvedReviewText(match[6])) {
|
|
150
|
-
return { error: `Proposal Decision ${candidate.id} must replace every review placeholder with verified reasoning and evidence` };
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
decision: {
|
|
154
|
-
candidateId: candidate.id,
|
|
155
|
-
source: match[1],
|
|
156
|
-
operation: "remove",
|
|
157
|
-
target: match[2],
|
|
158
|
-
existing: match[3],
|
|
159
|
-
decision: match[4],
|
|
160
|
-
reason: match[5],
|
|
161
|
-
evidenceChecked: match[6]
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
const match = /^Source:[ \t]*(\S.*)[ \t]*\nOperation:[ \t]*(add|update)[ \t]*\nTarget:[ \t]*(shared|project-manager|architect|coder|tester|reviewer|harness-engineer)[ \t]*\nCandidate:[ \t]*(\S.*)[ \t]*\nDecision:[ \t]*(keep-in-memory|keep-memory-reference|move-to-durable-doc|reject)[ \t]*\nFinal target:[ \t]*(shared|project-manager|architect|coder|tester|reviewer|harness-engineer|none)[ \t]*\nWhy memory is necessary:[ \t]*(\S.*)[ \t]*\nImpact if absent:[ \t]*(\S.*)[ \t]*\nDurable doc disposition:[ \t]*(memory|durable-doc|memory-reference)[ \t]*\nDurable doc analysis:[ \t]*(\S.*)[ \t]*\nDurable doc path:[ \t]*(\S.*)[ \t]*\nEvidence checked:[ \t]*(\S.*)[ \t]*\nFinal content:[ \t]*(\S.*)[ \t]*$/.exec(item);
|
|
166
|
-
if (!match) {
|
|
167
|
-
return {
|
|
168
|
-
error: `Proposal Decision ${candidate.id} must contain one-line Source, Operation, Target, Candidate, Decision, Final target, Why memory is necessary, Impact if absent, Durable doc disposition, Durable doc analysis, Durable doc path, Evidence checked, and Final content fields in that order`
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
if (match[1] !== candidate.source
|
|
172
|
-
|| match[2] !== candidate.operation
|
|
173
|
-
|| match[3] !== candidate.target
|
|
174
|
-
|| match[4] !== candidate.content) {
|
|
175
|
-
return { error: `Proposal Decision ${candidate.id} does not match its source proposal` };
|
|
176
|
-
}
|
|
177
|
-
if ([match[7], match[8], match[10], match[12]].some(isUnresolvedReviewText)) {
|
|
178
|
-
return { error: `Proposal Decision ${candidate.id} must replace every review placeholder with independent analysis and verified evidence` };
|
|
179
|
-
}
|
|
180
|
-
const decision = match[5];
|
|
181
|
-
const finalTarget = match[6];
|
|
182
|
-
const durableDocDisposition = match[9];
|
|
183
|
-
const durableDocPath = match[11];
|
|
184
|
-
const finalContent = match[13];
|
|
185
|
-
const keepsMemory = decision === "keep-in-memory" || decision === "keep-memory-reference";
|
|
186
|
-
if (keepsMemory && (finalTarget === "none" || finalContent === "none")) {
|
|
187
|
-
return { error: `Proposal Decision ${candidate.id} must provide Final target and Final content when keeping memory` };
|
|
188
|
-
}
|
|
189
|
-
if (!keepsMemory && (finalTarget !== "none" || finalContent !== "none")) {
|
|
190
|
-
return { error: `Proposal Decision ${candidate.id} must use Final target: none and Final content: none when not keeping memory` };
|
|
191
|
-
}
|
|
192
|
-
if ((decision === "keep-in-memory" && durableDocDisposition !== "memory")
|
|
193
|
-
|| (decision === "keep-memory-reference" && durableDocDisposition !== "memory-reference")
|
|
194
|
-
|| (decision === "move-to-durable-doc" && durableDocDisposition !== "durable-doc")) {
|
|
195
|
-
return { error: `Proposal Decision ${candidate.id} has inconsistent Decision and Durable doc disposition values` };
|
|
196
|
-
}
|
|
197
|
-
if ((durableDocDisposition === "memory" && durableDocPath !== "none")
|
|
198
|
-
|| (durableDocDisposition !== "memory" && durableDocPath === "none")) {
|
|
199
|
-
return { error: `Proposal Decision ${candidate.id} must use Durable doc path: none only with Durable doc disposition: memory` };
|
|
200
|
-
}
|
|
201
|
-
return {
|
|
202
|
-
decision: {
|
|
203
|
-
candidateId: candidate.id,
|
|
204
|
-
source: match[1],
|
|
205
|
-
operation: match[2],
|
|
206
|
-
target: match[3],
|
|
207
|
-
candidate: match[4],
|
|
208
|
-
decision,
|
|
209
|
-
finalTarget,
|
|
210
|
-
whyMemoryIsNecessary: match[7],
|
|
211
|
-
impactIfAbsent: match[8],
|
|
212
|
-
durableDocDisposition,
|
|
213
|
-
durableDocAnalysis: match[10],
|
|
214
|
-
durableDocPath,
|
|
215
|
-
evidenceChecked: match[12],
|
|
216
|
-
finalContent
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
function validateExistingMemoryDecisions(content, hasExistingMemory) {
|
|
221
|
-
const body = content.trim();
|
|
222
|
-
if (body === "none") {
|
|
223
|
-
return hasExistingMemory
|
|
224
|
-
? "Existing Memory Decisions cannot be none while substantive existing memory is present"
|
|
225
|
-
: undefined;
|
|
226
|
-
}
|
|
227
|
-
const itemHeadings = [...body.matchAll(/^#### Item \d+[ \t]*$/gm)];
|
|
228
|
-
if (itemHeadings.length === 0 || body.slice(0, itemHeadings[0].index).trim()) {
|
|
229
|
-
return "Existing Memory Decisions must contain none or one or more #### Item N blocks";
|
|
230
|
-
}
|
|
231
|
-
for (let index = 0; index < itemHeadings.length; index += 1) {
|
|
232
|
-
const heading = itemHeadings[index];
|
|
233
|
-
const itemStart = (heading.index ?? 0) + heading[0].length;
|
|
234
|
-
const itemEnd = index + 1 < itemHeadings.length
|
|
235
|
-
? itemHeadings[index + 1].index ?? body.length
|
|
236
|
-
: body.length;
|
|
237
|
-
const item = body.slice(itemStart, itemEnd).trim();
|
|
238
|
-
const match = /^Target:[ \t]*(shared|project-manager|architect|coder|tester|reviewer|harness-engineer)[ \t]*\nExisting:[ \t]*(\S.*)[ \t]*\nDecision:[ \t]*(retain|update|remove|move-to-durable-doc)[ \t]*\nReason:[ \t]*(\S.*)[ \t]*\nImpact if removed:[ \t]*(\S.*)[ \t]*\nDurable doc disposition:[ \t]*(memory|durable-doc|memory-reference)[ \t]*\nDurable doc path:[ \t]*(\S.*)[ \t]*\nEvidence:[ \t]*(\S.*)[ \t]*$/.exec(item);
|
|
239
|
-
if (!match) {
|
|
240
|
-
return `Existing Memory Decisions ${heading[0].trim()} must contain one-line Target, Existing, Decision, Reason, Impact if removed, Durable doc disposition, Durable doc path, and Evidence fields in that order`;
|
|
241
|
-
}
|
|
242
|
-
const disposition = match[6];
|
|
243
|
-
const durableDocPath = match[7];
|
|
244
|
-
if ((disposition === "memory" && durableDocPath !== "none")
|
|
245
|
-
|| (disposition !== "memory" && durableDocPath === "none")) {
|
|
246
|
-
return `Existing Memory Decisions ${heading[0].trim()} must use Durable doc path: none only with Durable doc disposition: memory`;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return undefined;
|
|
250
|
-
}
|
|
251
|
-
function extractReportSubsection(content, heading, nextHeading) {
|
|
252
|
-
const start = new RegExp(`^### ${escapeRegExp(heading)}\\s*$`, "m").exec(content);
|
|
253
|
-
if (!start || start.index === undefined) {
|
|
254
|
-
return undefined;
|
|
255
|
-
}
|
|
256
|
-
const bodyStart = start.index + start[0].length;
|
|
257
|
-
if (nextHeading) {
|
|
258
|
-
const end = new RegExp(`^### ${escapeRegExp(nextHeading)}\\s*$`, "m")
|
|
259
|
-
.exec(content.slice(bodyStart));
|
|
260
|
-
return end?.index === undefined
|
|
261
|
-
? undefined
|
|
262
|
-
: content.slice(bodyStart, bodyStart + end.index);
|
|
263
|
-
}
|
|
264
|
-
const reviewedSet = /^Reviewed memory set:/m.exec(content.slice(bodyStart));
|
|
265
|
-
return content.slice(bodyStart, reviewedSet?.index === undefined ? content.length : bodyStart + reviewedSet.index);
|
|
266
|
-
}
|
|
267
|
-
function escapeRegExp(value) {
|
|
268
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
269
|
-
}
|
|
270
|
-
function memorySetContains(memory, value) {
|
|
271
|
-
return Object.values(memory).some((content) => memoryLines(content).has(value));
|
|
272
|
-
}
|
|
273
|
-
function memoryLines(content) {
|
|
274
|
-
return new Set(content
|
|
275
|
-
.split(/\r?\n/)
|
|
276
|
-
.map((line) => line.trim())
|
|
277
|
-
.filter(Boolean));
|
|
278
|
-
}
|
|
279
|
-
function isUnresolvedReviewText(value) {
|
|
280
|
-
const normalized = value.trim().toLowerCase();
|
|
281
|
-
return (/[<>]/.test(value)
|
|
282
|
-
|| normalized === "none"
|
|
283
|
-
|| normalized === "tbd"
|
|
284
|
-
|| normalized === "unknown"
|
|
285
|
-
|| normalized === "n/a");
|
|
286
|
-
}
|