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 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
- applies the result as part of the same Task Harness Retrospective. Roles cannot
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. VCM changes only block
481
- contents and creates a dedicated commit in the active task worktree. Harness
482
- Studio shows current memory and task-local applied history. Memory is applied
483
- before user review; while the task worktree remains available, the user can edit
484
- current memory or revert a recorded change through another commit.
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
- -> Apply reviewed memory, when Auto Memory is enabled
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
- every candidate has one matching decision and that the reviewed memory files
511
- implement those decisions before applying them. Harness Engineer review and
512
- retrospective work remain tool role activity and do not participate in Round
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
- reviewedMemoryPath: path.join(runRoot, "after"),
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
- const report = await deps.fs.readText(state.retrospectiveReportPath);
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, `Task Harness Retrospective memory review validation failed: ${errorMessage(error)}`);
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 validateReviewedMemoryOutput(taskRepoRoot, before, after, decisions) {
563
- return validateMemoryReviewOutput({
564
- before: memorySetByTarget(before),
565
- after: memorySetByTarget(after),
566
- decisions,
567
- durableDocExists: async (durableDocPath) => {
568
- try {
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
- catch (error) {
598
- await failReview(taskRepoRoot, state, `Harness Engineer memory result could not be applied: ${errorMessage(error)}`);
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
- "Each snapshot file contains only the matching <VCM-memory> block content. Edit every existing reviewed-memory file in place.",
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 reviewed-memory file. Use none when the decision does not keep memory.",
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. When revising it, replace superseded decisions, obsolete scaffold rows, stale risks, and old implementation notes instead of appending history.
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 record 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. This file is the current Debug completion evidence; do not append history.
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; replace stale content instead of appending history.
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 and write only the reviewed-memory output files assigned by VCM.
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 assigned reviewed-memory output
49
- files during Retrospective Mode, or when VCM explicitly asks you to apply an
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 reviewed-memory
56
- output files. After every assigned pending feedback has a recorded
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 review output paths assigned by VCM. Do not edit active
96
- \`<VCM-memory>\` blocks directly. VCM applies the reviewed output, records the
97
- diff, and commits the changed host files.
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; when rewriting it, carry forward still-unresolved findings or explicitly mark them resolved instead of dropping them.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.7.32",
3
+ "version": "0.7.34",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [
@@ -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
- }