vibe-coding-master 0.7.31 → 0.7.33

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
@@ -503,11 +503,14 @@ shared or current-role target and its supporting evidence. Adds and updates
503
503
  also state why the memory is necessary, the impact of omitting it, and whether
504
504
  the knowledge belongs in memory or a durable document. Harness Engineer first
505
505
  reviews every existing memory entry, recording its retention reason, removal
506
- impact, and durable-document disposition, then evaluates the proposals and
507
- records the resulting changes in the Retrospective report. VCM validates that
508
- report structure before applying the reviewed memory. Harness Engineer review
509
- and retrospective work remain tool role activity and do not participate in
510
- Round completion.
506
+ impact, and durable-document disposition, then evaluates every proposal item
507
+ independently. Each Add or Update decision records why the memory is necessary,
508
+ what happens if it is absent, whether it belongs in memory or a durable
509
+ 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.
511
514
 
512
515
  When Auto Memory is disabled, Review Task Harness does not collect proposals or
513
516
  ask Harness Engineer to update memory. When enabled, both automatic and manual
@@ -6,8 +6,8 @@ import { VcmError } from "../errors.js";
6
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
- import { validateMemoryProposal } from "./memory-proposal-validation.js";
10
- import { validateMemoryReviewReport } from "./memory-review-validation.js";
9
+ import { parseMemoryProposal, validateMemoryProposal } from "./memory-proposal-validation.js";
10
+ import { parseMemoryReviewReport, validateMemoryReviewOutput } from "./memory-review-validation.js";
11
11
  const MEMORY_FILE_DEFINITIONS = [
12
12
  { path: "CLAUDE.md", title: "Shared Memory" },
13
13
  { path: ".claude/agents/project-manager.md", title: "Project Manager Memory", role: "project-manager" },
@@ -251,6 +251,7 @@ export function createAutoMemoryService(deps) {
251
251
  statusCode: 409
252
252
  });
253
253
  }
254
+ const proposalCandidates = await readReviewCandidates(taskRepoRoot, state);
254
255
  const timestamp = now();
255
256
  state.reviewPromptDispatchedAt = timestamp;
256
257
  state.retrospectiveReportPath = retrospectiveReportPath;
@@ -263,7 +264,7 @@ export function createAutoMemoryService(deps) {
263
264
  roleDraftsPath: path.join(runRoot, "drafts"),
264
265
  currentMemoryPath: path.join(runRoot, "before"),
265
266
  reviewedMemoryPath: path.join(runRoot, "after"),
266
- proposalRoles: state.drafts.map((draft) => draft.role),
267
+ proposalCandidates,
267
268
  ...(planningCandidatePath
268
269
  ? { planningCandidatePath: resolveRepoPath(taskRepoRoot, planningCandidatePath) }
269
270
  : {})
@@ -360,11 +361,24 @@ export function createAutoMemoryService(deps) {
360
361
  await failReview(input.taskRepoRoot, state, "Task Harness Retrospective did not write the required retrospective report.");
361
362
  return true;
362
363
  }
363
- const report = await deps.fs.readText(state.retrospectiveReportPath);
364
- const currentMemorySnapshot = await readRunMemorySet(input.taskRepoRoot, state.runId, "before");
365
- const reportError = validateMemoryReviewReport(report, state.drafts.map((draft) => draft.role), hasSubstantiveMemory(currentMemorySnapshot));
366
- if (reportError) {
367
- await failReview(input.taskRepoRoot, state, `Task Harness Retrospective memory review report ${reportError}.`);
364
+ 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
+ }
379
+ }
380
+ catch (error) {
381
+ await failReview(input.taskRepoRoot, state, `Task Harness Retrospective memory review validation failed: ${errorMessage(error)}`);
368
382
  return true;
369
383
  }
370
384
  await applyReviewedMemory(input.taskRepoRoot, state);
@@ -525,6 +539,41 @@ export function createAutoMemoryService(deps) {
525
539
  ? relativePath
526
540
  : undefined;
527
541
  }
542
+ async function readReviewCandidates(taskRepoRoot, state) {
543
+ const candidates = [];
544
+ for (const draft of state.drafts) {
545
+ const draftPath = resolveRepoPath(taskRepoRoot, draft.path);
546
+ const parsed = parseMemoryProposal(await deps.fs.readText(draftPath));
547
+ if (!parsed.proposal) {
548
+ throw new Error(`${draft.role} memory draft ${parsed.error ?? "could not be parsed"}`);
549
+ }
550
+ candidates.push(...toReviewCandidates(draft.role, draft.role, parsed.proposal.items));
551
+ }
552
+ const planningCandidatePath = await findPlanningCandidateSnapshot(taskRepoRoot, state.runId);
553
+ if (planningCandidatePath) {
554
+ const parsed = parseMemoryProposal(await deps.fs.readText(resolveRepoPath(taskRepoRoot, planningCandidatePath)));
555
+ if (!parsed.proposal) {
556
+ throw new Error(`Architect planning-session memory candidate ${parsed.error ?? "could not be parsed"}`);
557
+ }
558
+ candidates.push(...toReviewCandidates("architect-planning", "architect", parsed.proposal.items));
559
+ }
560
+ return candidates;
561
+ }
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
+ }
528
577
  async function applyReviewedMemory(taskRepoRoot, state) {
529
578
  try {
530
579
  const before = await readRunMemorySet(taskRepoRoot, state.runId, "before");
@@ -756,6 +805,37 @@ export async function assertMemoryBlocksInstalled(fs, taskRepoRoot) {
756
805
  function currentDraft(state) {
757
806
  return state.drafts.find((draft) => draft.status !== "completed");
758
807
  }
808
+ function toReviewCandidates(source, currentRole, items) {
809
+ return items.map((item) => ({
810
+ id: `${source}:${item.operation}:${item.ordinal}`,
811
+ source,
812
+ operation: item.operation,
813
+ target: item.target === "shared" ? "shared" : currentRole,
814
+ ...(item.content ? { content: item.content } : {}),
815
+ ...(item.existing ? { existing: item.existing } : {})
816
+ }));
817
+ }
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
+ }
759
839
  function toActiveReview(state) {
760
840
  return {
761
841
  runId: state.runId,
@@ -260,9 +260,13 @@ export function createHarnessFeedbackService(deps) {
260
260
  "Before evaluating proposals, review every substantive entry in every current memory snapshot against current code, documentation, and final task evidence.",
261
261
  "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
262
  "Complete this full existing-memory review even when every proposal says no-change.",
263
- "Then evaluate every proposal, including its stated need, absence impact, and durable-document disposition.",
263
+ "Then evaluate every proposal item independently. Do not accept or reject a whole role draft as one decision.",
264
+ "For every Add or Update candidate, independently explain why the memory is necessary, what fails if it is absent, and why memory or a durable document is the correct destination.",
265
+ "Do not copy the proposer rationale as the review. Verify it against current code, durable documentation, and final task evidence.",
264
266
  "Keep only verified, durable, reusable project knowledge. Merge duplicates and keep role-specific knowledge in the matching role file.",
265
267
  "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
+ "For keep-in-memory use Durable doc disposition: memory. For keep-memory-reference use memory-reference. For move-to-durable-doc use durable-doc.",
266
270
  "Existing-memory Target must be shared or the exact role name. Decision must be retain, update, remove, or move-to-durable-doc.",
267
271
  "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.",
268
272
  "Do not keep full content in memory when durable-doc is correct. Use memory-reference only when an ongoing role needs the document pointer.",
@@ -272,8 +276,10 @@ export function createHarnessFeedbackService(deps) {
272
276
  "## Memory Review",
273
277
  "Existing memory reviewed: complete",
274
278
  "",
275
- "### Proposal Dispositions",
276
- ...memoryReview.proposalRoles.map((role) => `- ${role}: accepted|rejected|no-change`),
279
+ "### Proposal Decisions",
280
+ ...(memoryReview.proposalCandidates.length > 0
281
+ ? memoryReview.proposalCandidates.flatMap(renderMemoryProposalDecisionTemplate)
282
+ : ["none"]),
277
283
  "",
278
284
  "### Existing Memory Decisions",
279
285
  "#### Item 1",
@@ -299,6 +305,38 @@ export function createHarnessFeedbackService(deps) {
299
305
  "End your turn after writing the result."
300
306
  ].join("\n");
301
307
  }
308
+ function renderMemoryProposalDecisionTemplate(candidate) {
309
+ if (candidate.operation === "remove") {
310
+ return [
311
+ `#### Candidate ${candidate.id}`,
312
+ `Source: ${candidate.source}`,
313
+ "Operation: remove",
314
+ `Target: ${candidate.target}`,
315
+ `Existing: ${candidate.existing}`,
316
+ "Decision: remove|retain",
317
+ "Reason: <why the proposed removal should be applied or rejected>",
318
+ "Evidence checked: <current code, durable documentation, or final task evidence>",
319
+ ""
320
+ ];
321
+ }
322
+ return [
323
+ `#### Candidate ${candidate.id}`,
324
+ `Source: ${candidate.source}`,
325
+ `Operation: ${candidate.operation}`,
326
+ `Target: ${candidate.target}`,
327
+ `Candidate: ${candidate.content}`,
328
+ "Decision: keep-in-memory|keep-memory-reference|move-to-durable-doc|reject",
329
+ "Final target: shared|project-manager|architect|coder|tester|reviewer|harness-engineer|none",
330
+ "Why memory is necessary: <independent reason, or why it is not necessary>",
331
+ "Impact if absent: <specific impact, or why no durable impact exists>",
332
+ "Durable doc disposition: memory|durable-doc|memory-reference",
333
+ "Durable doc analysis: <why this destination is correct>",
334
+ "Durable doc path: <none or a project-relative durable doc path>",
335
+ "Evidence checked: <current code, durable documentation, or final task evidence>",
336
+ "Final content: <exact one-line reviewed-memory content or none>",
337
+ ""
338
+ ];
339
+ }
302
340
  function buildPendingFeedbackPrompt(repoRoot, feedbackPath) {
303
341
  return [
304
342
  "[VCM Harness Feedback]",
@@ -1,20 +1,23 @@
1
1
  const OPERATIONS = ["Add", "Update", "Remove"];
2
2
  export function validateMemoryProposal(content) {
3
+ return parseMemoryProposal(content).error;
4
+ }
5
+ export function parseMemoryProposal(content) {
3
6
  if (!/^# Memory Proposal\s*$/m.test(content)) {
4
- return "is missing the # Memory Proposal heading";
7
+ return { error: "is missing the # Memory Proposal heading" };
5
8
  }
6
9
  const decisionMatches = [...content.matchAll(/^Decision:[ \t]*(update|no-change)[ \t]*$/gm)];
7
10
  if (decisionMatches.length !== 1) {
8
- return "must contain exactly one Decision: update or Decision: no-change field";
11
+ return { error: "must contain exactly one Decision: update or Decision: no-change field" };
9
12
  }
10
13
  const levelTwoHeadings = [...content.matchAll(/^## ([^\r\n]+?)[ \t]*$/gm)];
11
14
  const sections = [...content.matchAll(/^## (Add|Update|Remove)[ \t]*$/gm)];
12
15
  if (levelTwoHeadings.length !== OPERATIONS.length
13
16
  || sections.length !== OPERATIONS.length
14
17
  || sections.some((section, index) => section[1] !== OPERATIONS[index])) {
15
- return "must contain only the Add, Update, and Remove sections, exactly once and in that order";
18
+ return { error: "must contain only the Add, Update, and Remove sections, exactly once and in that order" };
16
19
  }
17
- let itemCount = 0;
20
+ const items = [];
18
21
  for (let index = 0; index < sections.length; index += 1) {
19
22
  const section = sections[index];
20
23
  const operation = section[1];
@@ -22,30 +25,31 @@ export function validateMemoryProposal(content) {
22
25
  const bodyEnd = index + 1 < sections.length
23
26
  ? sections[index + 1].index ?? content.length
24
27
  : content.length;
25
- const result = validateOperationBody(operation, content.slice(bodyStart, bodyEnd));
26
- if (typeof result === "string") {
27
- return result;
28
+ const result = parseOperationBody(operation, content.slice(bodyStart, bodyEnd));
29
+ if (result.error) {
30
+ return { error: result.error };
28
31
  }
29
- itemCount += result;
32
+ items.push(...result.items);
30
33
  }
31
34
  const decision = decisionMatches[0][1];
32
- if (decision === "no-change" && itemCount !== 0) {
33
- return "uses Decision: no-change but contains a memory item";
35
+ if (decision === "no-change" && items.length !== 0) {
36
+ return { error: "uses Decision: no-change but contains a memory item" };
34
37
  }
35
- if (decision === "update" && itemCount === 0) {
36
- return "uses Decision: update without a structured memory item";
38
+ if (decision === "update" && items.length === 0) {
39
+ return { error: "uses Decision: update without a structured memory item" };
37
40
  }
38
- return undefined;
41
+ return { proposal: { decision, items } };
39
42
  }
40
- function validateOperationBody(operation, rawBody) {
43
+ function parseOperationBody(section, rawBody) {
41
44
  const body = rawBody.trim();
42
45
  if (body === "none") {
43
- return 0;
46
+ return { items: [] };
44
47
  }
45
48
  const itemHeadings = [...body.matchAll(/^### Item \d+\s*$/gm)];
46
49
  if (itemHeadings.length === 0 || body.slice(0, itemHeadings[0].index).trim()) {
47
- return `${operation} must contain none or one or more ### Item N blocks`;
50
+ return { items: [], error: `${section} must contain none or one or more ### Item N blocks` };
48
51
  }
52
+ const items = [];
49
53
  for (let index = 0; index < itemHeadings.length; index += 1) {
50
54
  const heading = itemHeadings[index];
51
55
  const itemStart = (heading.index ?? 0) + heading[0].length;
@@ -53,28 +57,69 @@ function validateOperationBody(operation, rawBody) {
53
57
  ? itemHeadings[index + 1].index ?? body.length
54
58
  : body.length;
55
59
  const item = body.slice(itemStart, itemEnd).trim();
56
- const expectedPattern = operation === "Add"
60
+ const expectedPattern = section === "Add"
57
61
  ? /^Target:[ \t]*(shared|current-role)[ \t]*\nContent:[ \t]*(\S.*)[ \t]*\nReason:[ \t]*(\S.*)[ \t]*\nImpact if absent:[ \t]*(\S.*)[ \t]*\nDurable doc disposition:[ \t]*(memory|durable-doc|memory-reference)[ \t]*\nDurable doc path:[ \t]*(\S.*)[ \t]*\nEvidence:[ \t]*(\S.*)[ \t]*$/
58
- : operation === "Update"
62
+ : section === "Update"
59
63
  ? /^Target:[ \t]*(shared|current-role)[ \t]*\nExisting:[ \t]*(\S.*)[ \t]*\nContent:[ \t]*(\S.*)[ \t]*\nReason:[ \t]*(\S.*)[ \t]*\nImpact if absent:[ \t]*(\S.*)[ \t]*\nDurable doc disposition:[ \t]*(memory|durable-doc|memory-reference)[ \t]*\nDurable doc path:[ \t]*(\S.*)[ \t]*\nEvidence:[ \t]*(\S.*)[ \t]*$/
60
64
  : /^Target:[ \t]*(shared|current-role)[ \t]*\nExisting:[ \t]*(\S.*)[ \t]*\nEvidence:[ \t]*(\S.*)[ \t]*$/;
61
- const fieldMatch = expectedPattern.exec(item);
62
- if (!fieldMatch) {
63
- const fields = operation === "Add"
65
+ const match = expectedPattern.exec(item);
66
+ if (!match) {
67
+ const fields = section === "Add"
64
68
  ? "Target, Content, Reason, Impact if absent, Durable doc disposition, Durable doc path, and Evidence"
65
- : operation === "Update"
69
+ : section === "Update"
66
70
  ? "Target, Existing, Content, Reason, Impact if absent, Durable doc disposition, Durable doc path, and Evidence"
67
71
  : "Target, Existing, and Evidence";
68
- return `${operation} ${heading[0].trim()} must contain one-line ${fields} fields in that order`;
72
+ return {
73
+ items: [],
74
+ error: `${section} ${heading[0].trim()} must contain one-line ${fields} fields in that order`
75
+ };
69
76
  }
70
- if (operation === "Add" || operation === "Update") {
71
- const disposition = fieldMatch[operation === "Add" ? 5 : 6];
72
- const durableDocPath = fieldMatch[operation === "Add" ? 6 : 7];
73
- if ((disposition === "memory" && durableDocPath !== "none")
74
- || (disposition !== "memory" && durableDocPath === "none")) {
75
- return `${operation} ${heading[0].trim()} must use Durable doc path: none only with Durable doc disposition: memory`;
76
- }
77
+ const parsed = parseProposalItem(section, index + 1, match);
78
+ if (parsed.durableDocDisposition
79
+ && ((parsed.durableDocDisposition === "memory" && parsed.durableDocPath !== "none")
80
+ || (parsed.durableDocDisposition !== "memory" && parsed.durableDocPath === "none"))) {
81
+ return {
82
+ items: [],
83
+ error: `${section} ${heading[0].trim()} must use Durable doc path: none only with Durable doc disposition: memory`
84
+ };
77
85
  }
86
+ items.push(parsed);
87
+ }
88
+ return { items };
89
+ }
90
+ function parseProposalItem(section, ordinal, match) {
91
+ if (section === "Add") {
92
+ return {
93
+ operation: "add",
94
+ ordinal,
95
+ target: match[1],
96
+ content: match[2],
97
+ reason: match[3],
98
+ impactIfAbsent: match[4],
99
+ durableDocDisposition: match[5],
100
+ durableDocPath: match[6],
101
+ evidence: match[7]
102
+ };
103
+ }
104
+ if (section === "Update") {
105
+ return {
106
+ operation: "update",
107
+ ordinal,
108
+ target: match[1],
109
+ existing: match[2],
110
+ content: match[3],
111
+ reason: match[4],
112
+ impactIfAbsent: match[5],
113
+ durableDocDisposition: match[6],
114
+ durableDocPath: match[7],
115
+ evidence: match[8]
116
+ };
78
117
  }
79
- return itemHeadings.length;
118
+ return {
119
+ operation: "remove",
120
+ ordinal,
121
+ target: match[1],
122
+ existing: match[2],
123
+ evidence: match[3]
124
+ };
80
125
  }
@@ -1,47 +1,222 @@
1
- export function validateMemoryReviewReport(content, proposalRoles, hasExistingMemory = false) {
1
+ export function validateMemoryReviewReport(content, candidates, hasExistingMemory = false) {
2
+ return parseMemoryReviewReport(content, candidates, hasExistingMemory).error;
3
+ }
4
+ export function parseMemoryReviewReport(content, candidates, hasExistingMemory = false) {
2
5
  const memoryReview = /^## Memory Review\s*$/m.exec(content);
3
6
  if (!memoryReview || memoryReview.index === undefined) {
4
- return "is missing the ## Memory Review section";
7
+ return { error: "is missing the ## Memory Review section" };
5
8
  }
6
9
  const sectionStart = memoryReview.index + memoryReview[0].length;
7
10
  const nextSection = /^## (?!#)/m.exec(content.slice(sectionStart));
8
11
  const section = content.slice(sectionStart, nextSection?.index === undefined ? content.length : sectionStart + nextSection.index);
9
12
  if (!/^Existing memory reviewed:[ \t]*complete[ \t]*$/m.test(section)) {
10
- return "must declare Existing memory reviewed: complete";
13
+ return { error: "must declare Existing memory reviewed: complete" };
11
14
  }
12
15
  if (!/^Reviewed memory set:[ \t]*complete[ \t]*$/m.test(section)) {
13
- return "must declare Reviewed memory set: complete";
16
+ return { error: "must declare Reviewed memory set: complete" };
14
17
  }
15
- const dispositions = extractReportSubsection(section, "Proposal Dispositions", "Existing Memory Decisions");
18
+ const dispositions = extractReportSubsection(section, "Proposal Decisions", "Existing Memory Decisions");
16
19
  if (dispositions === undefined) {
17
- return "is missing the Proposal Dispositions subsection";
20
+ return { error: "is missing the Proposal Decisions subsection" };
18
21
  }
19
- for (const role of proposalRoles) {
20
- const matches = dispositions.match(new RegExp(`^- ${escapeRegExp(role)}:[ \\t]*(accepted|rejected|no-change)[ \\t]*$`, "gm"));
21
- if (matches?.length !== 1) {
22
- return `must record exactly one accepted, rejected, or no-change disposition for ${role}`;
23
- }
22
+ const decisionsResult = parseProposalDecisions(dispositions, candidates);
23
+ if (decisionsResult.error) {
24
+ return decisionsResult;
24
25
  }
25
26
  const existingDecisions = extractReportSubsection(section, "Existing Memory Decisions", "Existing Memory Changes");
26
27
  if (existingDecisions === undefined) {
27
- return "is missing the Existing Memory Decisions subsection";
28
+ return { error: "is missing the Existing Memory Decisions subsection" };
28
29
  }
29
30
  const existingDecisionError = validateExistingMemoryDecisions(existingDecisions, hasExistingMemory);
30
31
  if (existingDecisionError) {
31
- return existingDecisionError;
32
+ return { error: existingDecisionError };
32
33
  }
33
34
  const existingChanges = extractReportSubsection(section, "Existing Memory Changes");
34
35
  if (existingChanges === undefined) {
35
- return "is missing the Existing Memory Changes subsection";
36
+ return { error: "is missing the Existing Memory Changes subsection" };
36
37
  }
37
38
  for (const field of ["retained", "updated", "removed"]) {
38
39
  const matches = existingChanges.match(new RegExp(`^- ${field}:[ \\t]*\\S.*$`, "gm"));
39
40
  if (matches?.length !== 1) {
40
- return `must record exactly one non-empty ${field} summary`;
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}`;
41
88
  }
42
89
  }
43
90
  return undefined;
44
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
+ }
45
220
  function validateExistingMemoryDecisions(content, hasExistingMemory) {
46
221
  const body = content.trim();
47
222
  if (body === "none") {
@@ -92,3 +267,20 @@ function extractReportSubsection(content, heading, nextHeading) {
92
267
  function escapeRegExp(value) {
93
268
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
94
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
+ }
@@ -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
77
  - \`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.
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.
@@ -117,6 +117,26 @@ of verifying only the cited instances. A claimed-complete enumeration with
117
117
  neither a recorded command nor a judgment-derived basis, or one that fails
118
118
  reconstruction, is unsupported by code evidence and is \`request_changes\`.
119
119
 
120
+ Run a backward-impact pass over the plan:
121
+
122
+ - For every existing code site named by the Module/File Plan or Scaffold
123
+ Manifest, inspect the complete callable unit or site and verify its current
124
+ comments, contracts, preconditions, configuration scope, lifecycle
125
+ assumptions, and safety assumptions against implementation and runtime
126
+ configuration.
127
+ - Identify which verified assumptions the plan preserves, updates, or
128
+ invalidates. Request changes when the plan omits a touched site, misstates an
129
+ assumption, or invalidates one without correcting the architecture, affected
130
+ surfaces, scaffold, and docs impact.
131
+ - When the plan newly handles one member of an existing persisted structure,
132
+ gate, invariant, or other semantic class, independently reconstruct the
133
+ complete directly related member set from its declaration, catalogue,
134
+ adjacent contract, or repository search. Verify that every member has an
135
+ evidence-backed plan disposition.
136
+ - Keep this pass bounded to plan-cited or scaffold-touched existing sites and
137
+ their directly related semantic classes. Do not expand it into an unrelated
138
+ whole-repository review.
139
+
120
140
  Request changes when the plan is structurally complete but architecturally
121
141
  under-specified, logically inconsistent, unsupported by code evidence, unsafe
122
142
  for boundary cases, conflicts with current project architecture, or leaves key
@@ -272,6 +292,18 @@ Verify that the Diagnosis evidence records applicable L2/L3 validation for the
272
292
  diagnosed failure path. Request changes when an applicable check was not run,
273
293
  did not pass, or does not exercise that failure path.
274
294
 
295
+ When a changed production or test hunk adds a file-local override,
296
+ normalization, or bypass of a shared default, constant, or documented contract,
297
+ search the current worktree for the same mechanism. If it already exists in at
298
+ least two other files, request changes unless the accepted architecture
299
+ explicitly fixes the owning behavior, confirms that local handling is intended
300
+ and corrects the owning documentation, or records the unresolved issue and
301
+ affected call sites through the Architect-owned known-issue flow. A documented
302
+ post-validation Architect docs sync satisfies the documentation timing; the
303
+ correct disposition must already be explicit. Extracting the workaround into a
304
+ helper is not an upstream disposition. Classify the finding as \`implementation\`
305
+ unless the owning behavior and contract are entirely test-only.
306
+
275
307
  Check every source for project coding-standard compliance, unnecessary
276
308
  duplication or abstraction, inconsistent error handling, unhandled fallible
277
309
  paths, debug/task-only artifacts, \`VCM:CODE\`, task-process comments or labels,
@@ -313,6 +345,8 @@ Use this findings structure:
313
345
  - End-To-End Flow:
314
346
  - Scope Fit:
315
347
  - Code Reality:
348
+ - Invalidated Assumptions:
349
+ - Existing-Class Completeness:
316
350
  - Ownership:
317
351
  - Data Flow:
318
352
  - Lifecycle:
@@ -77,14 +77,17 @@ You are not part of the task workflow round state.
77
77
  even when every proposal says \`no-change\`.
78
78
  - After reviewing existing memory, verify every role proposal against task
79
79
  evidence, including any Architect planning-session candidate assigned by VCM.
80
- Independently verify the stated need, absence impact, and durable-document
81
- disposition. Treat every candidate as a proposal rather than authority, merge
80
+ Review every proposal item separately; never accept or reject an entire role
81
+ draft as one decision. For every Add or Update candidate, independently state
82
+ why the memory is necessary, what fails if it is absent, and why memory or a
83
+ durable document is the correct destination. Do not copy the proposer rationale
84
+ as the review. Treat every candidate as a proposal rather than authority, merge
82
85
  duplicates, remove stale entries, and keep role-specific knowledge in the
83
86
  matching role memory output.
84
87
  - Do not keep the full content in memory when a durable document is the correct
85
88
  source. Use a short memory reference only when the role needs that document
86
89
  pointer across tasks.
87
- - Record every proposal disposition and the retained, updated, and removed
90
+ - Record every proposal decision and the retained, updated, and removed
88
91
  existing-memory decisions and summary in the exact Memory Review report block
89
92
  assigned by VCM.
90
93
  - Do not record task narrative, temporary state, unverified conclusions, or
@@ -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
 
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.7.31",
3
+ "version": "0.7.33",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [