vibe-coding-master 0.8.2 → 0.8.4

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.
@@ -122,11 +122,11 @@ export function createWorkflowControlService(deps) {
122
122
  const violatedRule = candidate.proposal.violatedRule?.trim();
123
123
  if (!authorizationText && !violatedRule) {
124
124
  throw workflowError("WORKFLOW_TRANSITION_DENIED", verdict.reason, verdict.allowedTransitions.length > 0
125
- ? `Allowed next dispatches: ${verdict.allowedTransitions.join(", ")}. Recheck the flow, or ask the user directly for an exact one-time authorization.`
125
+ ? `Allowed next dispatches: ${verdict.allowedTransitions.join(", ")}. Recheck the flow, reuse an existing direct user instruction that explicitly authorizes this exact transition, or ask the user only when no such authorization exists.`
126
126
  : verdict.blockedHint);
127
127
  }
128
128
  if (!authorizationText || violatedRule !== verdict.reason) {
129
- throw workflowError("WORKFLOW_USER_AUTHORIZATION_INVALID", `User authorization must include the exact authorization text and exact violated rule: ${verdict.reason}`, "Ask the user directly, then copy the user's authorization verbatim into Authorization Text and the rejection reason verbatim into Violated Rule.");
129
+ throw workflowError("WORKFLOW_USER_AUTHORIZATION_INVALID", `User authorization must include the exact authorization text and exact violated rule: ${verdict.reason}`, "Copy an existing direct user instruction that explicitly authorizes this exact transition into Authorization Text. Ask the user only when no such authorization exists. Copy the rejection reason verbatim into Violated Rule.");
130
130
  }
131
131
  userAuthorization = createUserAuthorization(id(), current, candidate, effectiveFlow, baseHistoryHash, verdict.reason, authorizationText, now());
132
132
  overrideAuthorizationId = userAuthorization.id;
@@ -318,6 +318,7 @@ export function createWorkflowControlService(deps) {
318
318
  requestUserInput,
319
319
  resolveUserInput,
320
320
  submitProgress,
321
+ assertDocsArtifactAllowed,
321
322
  assertRouteAuthorized,
322
323
  claimDispatch,
323
324
  releaseDispatch,
@@ -325,6 +326,37 @@ export function createWorkflowControlService(deps) {
325
326
  confirmDispatch,
326
327
  recoverTask
327
328
  };
329
+ async function assertDocsArtifactAllowed(input, kind, role) {
330
+ const state = await getState(input);
331
+ failOnStateWarnings(state);
332
+ const progress = await readProgress(deps.fs, input);
333
+ const active = state.activeDispatch;
334
+ if (progress.status !== "active" || !active || active.targetRole !== role)
335
+ return;
336
+ let expected;
337
+ if (progress.flow === "docs-only") {
338
+ expected = "docs-update-report";
339
+ }
340
+ else if (role === "architect" && (progress.flow === "code-change"
341
+ || progress.flow === "architect-debug" || progress.flow === "architecture-diagnosis")) {
342
+ const flow = progress.flow;
343
+ const run = resolveFlowRun(state.flowRun, progress);
344
+ const hadTester = progress.history.some((entry) => entry.sequence >= run.startedAtSequence
345
+ && entry.sequence < active.sequence && entry.targetRole === "tester");
346
+ if (!run.activeBranch && hadTester) {
347
+ const source = run.resumedFromBranch ?? (flow === "code-change" ? "coder" : flow);
348
+ const docsTarget = `${flow}/architect`;
349
+ const allowed = await allowedAfterTester(deps.fs, input, state, flow, source, {
350
+ successTarget: docsTarget
351
+ });
352
+ if (allowed.includes(docsTarget))
353
+ expected = "docs-sync-report";
354
+ }
355
+ }
356
+ if (expected && expected !== kind) {
357
+ throw workflowError("WORKFLOW_DOCS_ARTIFACT_MISMATCH", `${kind} was not written: the active ${progress.flow} dispatch #${active.sequence} requires ${path.posix.join(input.handoffDir, `${expected}.md`)}.`, `Submit --kind ${expected} through .ai/tools/vcm-artifact. The two documentation reports are not interchangeable.`);
358
+ }
359
+ }
328
360
  async function requestUserInput(input, question) {
329
361
  return withLock(statePath(input), async () => {
330
362
  const state = await getState(input);
@@ -667,7 +699,7 @@ async function allowedCodeChange(fs, input, state, segment, resumedFromBranch) {
667
699
  const testerIndex = findLastIndex(segment, (entry) => entry.targetRole === "tester");
668
700
  if (resumedFromBranch && coderIndex < 0) {
669
701
  if (testerIndex < 0) {
670
- return allowedPostImplementationArchitect(fs, input, state, "code-change", segment);
702
+ return allowedPostImplementationArchitect(fs, input, state, "code-change");
671
703
  }
672
704
  return allowedAfterTester(fs, input, state, "code-change", resumedFromBranch, {
673
705
  testerFailureFlow: "architect-debug",
@@ -676,17 +708,7 @@ async function allowedCodeChange(fs, input, state, segment, resumedFromBranch) {
676
708
  });
677
709
  }
678
710
  if (coderIndex < 0) {
679
- const plan = await artifactState(fs, input, "architecture-plan.md", "architecture-plan");
680
- if (!evidenceIsFresh(state, "code-change", "architect", "architecture-plan.md", plan.hash)
681
- || plan.value !== "complete")
682
- return ["code-change/architect"];
683
- const gate = await gateState(fs, input, "architecture-plan");
684
- if (freshGateDecision(state, "code-change", "architect", "architecture-plan", gate) === "request_changes") {
685
- return ["code-change/architect"];
686
- }
687
- return gatePassedForDispatch(state, "code-change", "architect", "architecture-plan", gate)
688
- ? ["code-change/coder"]
689
- : [];
711
+ return (await allowedFreshArchitecturePlan(fs, input, state)) ?? ["code-change/architect"];
690
712
  }
691
713
  if (testerIndex < coderIndex) {
692
714
  const coder = await artifactState(fs, input, "coder-completion.md", "coder-completion");
@@ -697,9 +719,8 @@ async function allowedCodeChange(fs, input, state, segment, resumedFromBranch) {
697
719
  return ["architect-debug/architect"];
698
720
  return coder.value === "ready_for_review" ? ["code-change/tester"] : ["code-change/coder"];
699
721
  }
700
- const architectsAfterTester = segment.filter((entry, index) => index > testerIndex && entry.targetRole === "architect");
701
- if (architectsAfterTester.length > 0) {
702
- return allowedPostImplementationArchitect(fs, input, state, "code-change", architectsAfterTester);
722
+ if (segment.some((entry, index) => index > testerIndex && entry.targetRole === "architect")) {
723
+ return allowedPostImplementationArchitect(fs, input, state, "code-change");
703
724
  }
704
725
  return allowedAfterTester(fs, input, state, "code-change", "coder", {
705
726
  testerFailureFlow: "architect-debug",
@@ -707,13 +728,15 @@ async function allowedCodeChange(fs, input, state, segment, resumedFromBranch) {
707
728
  successTarget: "code-change/architect"
708
729
  });
709
730
  }
710
- async function allowedArchitectureFollowup(fs, input, state, followupDispatchedAt) {
731
+ async function allowedFreshArchitecturePlan(fs, input, state, gateMustPostdate) {
732
+ const baseline = matchingEvidenceBaseline(state, "code-change", "architect");
711
733
  const plan = await artifactState(fs, input, "architecture-plan.md", "architecture-plan");
712
- if (!evidenceIsFresh(state, "code-change", "architect", "architecture-plan.md", plan.hash)
713
- || plan.value !== "complete")
734
+ if (!baseline || baseline.artifactHashes["architecture-plan.md"] === plan.hash)
735
+ return undefined;
736
+ if (plan.value !== "complete")
714
737
  return ["code-change/architect"];
715
738
  const gate = await gateState(fs, input, "architecture-plan");
716
- if (!gate || !followupDispatchedAt || gate.updatedAt <= followupDispatchedAt)
739
+ if (!gate || (gateMustPostdate && gate.updatedAt <= gateMustPostdate))
717
740
  return [];
718
741
  if (freshGateDecision(state, "code-change", "architect", "architecture-plan", gate) === "request_changes") {
719
742
  return ["code-change/architect"];
@@ -726,7 +749,7 @@ async function allowedArchitectFix(fs, input, state, segment, source, resumedFro
726
749
  const architectIndex = findLastIndex(segment, (entry) => entry.targetRole === "architect");
727
750
  const testerIndex = findLastIndex(segment, (entry) => entry.targetRole === "tester");
728
751
  if (resumedFromBranch && architectIndex >= 0 && testerIndex < 0) {
729
- return allowedPostImplementationArchitect(fs, input, state, source, segment);
752
+ return allowedPostImplementationArchitect(fs, input, state, source);
730
753
  }
731
754
  if (architectIndex < 0)
732
755
  return [`${source}/architect`];
@@ -773,11 +796,13 @@ async function allowedArchitectFix(fs, input, state, segment, source, resumedFro
773
796
  successTarget: parentFlow ? `${parentFlow}/architect` : `${source}/architect`
774
797
  });
775
798
  }
776
- async function allowedPostImplementationArchitect(fs, input, state, flow, architectEntries) {
799
+ async function allowedPostImplementationArchitect(fs, input, state, flow) {
777
800
  const docs = await artifactState(fs, input, "docs-sync-report.md", "docs-sync-report");
778
801
  const acceptance = await artifactState(fs, input, "final-acceptance.md", "final-acceptance");
779
- if (flow === "code-change" && architectEntries.length > 1 && acceptance.value === "needs-architect-follow-up") {
780
- return allowedArchitectureFollowup(fs, input, state, architectEntries[1]?.confirmedAt);
802
+ if (flow === "code-change") {
803
+ const planningTransition = await allowedFreshArchitecturePlan(fs, input, state, matchingEvidenceBaseline(state, "code-change", "architect")?.confirmedAt);
804
+ if (planningTransition !== undefined)
805
+ return planningTransition;
781
806
  }
782
807
  if (evidenceIsFresh(state, flow, "architect", "docs-sync-report.md", docs.hash) && docs.complete) {
783
808
  if (docs.value === "synced" || docs.value === "unchanged") {
@@ -824,13 +849,13 @@ async function allowedAfterTester(fs, input, state, currentFlow, codeSource, opt
824
849
  }
825
850
  if (test.value === "incomplete" || test.infrastructure === "repair-required")
826
851
  return [`${currentFlow}/tester`];
827
- if (test.value === "fail" && test.infrastructure !== "repair-required") {
828
- return options.testerFailureFlow ? [`${options.testerFailureFlow}/architect`] : [];
829
- }
830
852
  const validation = await gateState(fs, input, "validation-adequacy");
831
853
  if (freshGateDecision(state, currentFlow, "tester", "validation-adequacy", validation) === "request_changes") {
832
854
  return [`${currentFlow}/tester`];
833
855
  }
856
+ if (test.value === "fail" && !test.hasUserApprovedCoverageGap) {
857
+ return options.testerFailureFlow ? [`${options.testerFailureFlow}/architect`] : [];
858
+ }
834
859
  if (!gatePassedForDispatch(state, currentFlow, "tester", "validation-adequacy", validation))
835
860
  return [];
836
861
  const codeDiff = await gateState(fs, input, "code-diff");
@@ -873,6 +898,10 @@ async function artifactState(fs, input, fileName, kind) {
873
898
  const infrastructure = kind === "test-report"
874
899
  ? /^Status:\s*(.+?)\s*$/mi.exec(readArtifactSectionContent(content, "Test Infrastructure") ?? "")?.[1]?.trim().toLowerCase()
875
900
  : undefined;
901
+ const hasUserApprovedCoverageGap = kind === "test-report"
902
+ && check.status === "ok"
903
+ && hasSubstantiveArtifactSection(readArtifactSectionContent(content, "Coverage Gaps"))
904
+ && hasSubstantiveArtifactSection(readArtifactSectionContent(content, "User Approval Evidence"));
876
905
  const disposition = kind === "architect-debug" || kind === "architecture-diagnosis"
877
906
  ? readArtifactSectionContent(content, "Final Disposition")?.trim().toLowerCase()
878
907
  : undefined;
@@ -884,12 +913,17 @@ async function artifactState(fs, input, fileName, kind) {
884
913
  hash: await artifactFingerprint(fs, absolute, content),
885
914
  value,
886
915
  infrastructure,
916
+ hasUserApprovedCoverageGap,
887
917
  disposition,
888
918
  correctionOwner: correctionOwner === "none" || asTargetRole(correctionOwner)
889
919
  ? correctionOwner
890
920
  : undefined
891
921
  };
892
922
  }
923
+ function hasSubstantiveArtifactSection(value) {
924
+ const normalized = value?.trim();
925
+ return Boolean(normalized && !/^(none|tbd)\.?$/i.test(normalized));
926
+ }
893
927
  async function gateState(fs, input, gate) {
894
928
  const target = resolveRepoPath(input.taskRepoRoot, path.posix.join(".ai/vcm/gate-reviews", "index.json"));
895
929
  if (!(await fs.pathExists(target)))
@@ -1138,11 +1172,17 @@ async function validateCompleteDeliveryEvidence(fs, input, state, flow) {
1138
1172
  throw workflowError("WORKFLOW_COMPLETION_INVALID", `The latest ${flow} dispatch is to ${baseline.targetRole}. Complete every required downstream validation and Gate step, then route Architect for docs sync before completing the flow.`);
1139
1173
  }
1140
1174
  const docs = await artifactState(fs, input, "docs-sync-report.md", "docs-sync-report");
1141
- if (!docs.complete) {
1142
- throw workflowError("WORKFLOW_COMPLETION_INVALID", "Docs Sync Report is missing or malformed for this complete delivery flow.");
1143
- }
1144
- if (baseline.artifactHashes["docs-sync-report.md"] === docs.hash) {
1145
- throw workflowError("WORKFLOW_COMPLETION_INVALID", "Docs Sync Report was not produced after the final Architect dispatch.");
1175
+ if (!docs.complete || baseline.artifactHashes["docs-sync-report.md"] === docs.hash) {
1176
+ const update = await artifactState(fs, input, "docs-update-report.md", "docs-update-report");
1177
+ const wrongReport = update.hash !== MISSING_EVIDENCE_HASH
1178
+ && baseline.artifactHashes["docs-update-report.md"] !== update.hash;
1179
+ const reason = !docs.complete
1180
+ ? "Docs Sync Report is missing or malformed for this complete delivery flow."
1181
+ : "Docs Sync Report was not produced after the final Architect dispatch.";
1182
+ throw workflowError("WORKFLOW_COMPLETION_INVALID", `${reason} Required file: ${path.posix.join(input.handoffDir, "docs-sync-report.md")}, produced after dispatch #${baseline.sequence} (${baseline.confirmedAt}).`
1183
+ + (wrongReport
1184
+ ? ` ${path.posix.join(input.handoffDir, "docs-update-report.md")} was updated after this dispatch, but cannot replace docs-sync-report.md.`
1185
+ : ""), "Ask Architect to submit --kind docs-sync-report through .ai/tools/vcm-artifact with Decision: synced or unchanged and Correction Owner: none. Report blocked with its correction owner when synchronization is incomplete.");
1146
1186
  }
1147
1187
  if (docs.value !== "synced" && docs.value !== "unchanged") {
1148
1188
  throw workflowError("WORKFLOW_COMPLETION_INVALID", `Docs Sync Report Decision must be synced or unchanged before completion; found ${docs.value ?? "missing"}.`);
@@ -18,6 +18,10 @@ ${renderRoleMemoryRules("project-manager")}
18
18
 
19
19
  ### User Communication
20
20
 
21
+ - Assistant text emitted before a tool call or role dispatch while the current Round continues is not user communication. Do not use intermediate text to deliver status, findings, decisions, questions, or required actions to the user.
22
+ - The user is informed only by PM's Round Final Reply when the Round pauses for user input or reaches a terminal result.
23
+ - The Round Final Reply must be self-contained. Restate every result, blocker, decision, risk, and required user action needed to understand the current state. Never rely on earlier PM text in the same Round.
24
+ - When asking the user a question, register it through \`vcm-ask-user\`, make the complete question and its necessary context the final reply, and end the turn.
21
25
  - Explain task status, blockers, role results, and decisions in user-facing language.
22
26
  - Prefer plain logic over code-level detail: describe what changed, why it matters, what risk remains, and what decision is needed.
23
27
  - Exclude irrelevant implementation detail, but retain the technical facts needed to explain the cause, evidence, impact, and unresolved state. Plain language means translating technical detail, not deleting it.
@@ -77,7 +81,7 @@ PM owns task flow selection. Every user request that asks VCM to perform deliver
77
81
 
78
82
  - After PM selects a flow, only its defined main path and explicitly allowed branches may be used.
79
83
  - The active flow may change only through a transition explicitly defined by that flow or an explicit user instruction.
80
- - Only the user may authorize any other flow change. Apply that authorization only to the exact change the user confirmed.
84
+ - Only the user may authorize any other flow change. A direct user instruction that explicitly authorizes the exact change is the authorization. Reuse that instruction verbatim and do not ask the user to confirm it again.
81
85
  - A role request, role result, tool suggestion, requested next action, workload, task size, context size, difficulty, predicted risk, or PM discretion must not create an unlisted branch or change the active flow.
82
86
  - If a role requests an unlisted branch or role handoff, keep the current flow active and return the incomplete work to the current responsible role.
83
87
  - An incomplete, unrecognized, or non-standard role result returns to the same role for a valid result.
@@ -90,6 +94,8 @@ The main flow is:
90
94
 
91
95
  \`Architect Interview and planning -> architecture-plan Gate -> Coder implementation -> Tester validation -> validation-adequacy Gate -> code-diff Gate -> Architect docs sync -> Final Acceptance -> completed\`
92
96
 
97
+ Require \`.ai/vcm/handoffs/docs-sync-report.md\` from the current Architect docs-sync dispatch. \`docs-update-report.md\` cannot replace it.
98
+
93
99
  PM may leave this path only through the allowed branches below.
94
100
 
95
101
  #### Allowed Branches
@@ -171,7 +177,7 @@ The shared path is:
171
177
 
172
178
  #### Successful Exit
173
179
 
174
- - For Architect Debug Flow, code-diff approval continues to \`Architect docs sync -> Final Acceptance\`.
180
+ - For Architect Debug Flow, code-diff approval continues to \`Architect docs sync -> Final Acceptance\`. Require \`.ai/vcm/handoffs/docs-sync-report.md\` from this docs-sync dispatch with \`Decision: synced\` or \`unchanged\` and \`Correction Owner: none\`; handle \`blocked\` through Docs Sync Correction.
175
181
  - For Architect Debug Branch, code-diff approval returns to the recorded parent-flow resume point after the parent flow's validation and code-diff milestones. The branch does not run its own docs sync or Final Acceptance.
176
182
 
177
183
  Architect Debug Flow or Branch never routes implementation to Coder. Architect executes Architect Debug Mode; PM owns whether the current context is a Flow or Branch and where it continues afterward.
@@ -207,7 +213,7 @@ Architecture Diagnosis Mode must run before another Debug Mode fix or Coder disp
207
213
 
208
214
  - An analysis-only Architecture Diagnosis Flow completes from the diagnosis result.
209
215
  - An analysis-only Architecture Diagnosis Branch returns to the recorded parent-flow resume point.
210
- - A code-producing Architecture Diagnosis Flow continues after code-diff approval to \`Architect docs sync -> Final Acceptance\`.
216
+ - A code-producing Architecture Diagnosis Flow continues after code-diff approval to \`Architect docs sync -> Final Acceptance\`. Require \`.ai/vcm/handoffs/docs-sync-report.md\` from this docs-sync dispatch with \`Decision: synced\` or \`unchanged\` and \`Correction Owner: none\`; handle \`blocked\` through Docs Sync Correction.
211
217
  - A code-producing Architecture Diagnosis Branch returns after code-diff approval to the recorded parent-flow resume point after the parent flow's validation and code-diff milestones. It does not run its own docs sync or Final Acceptance.
212
218
 
213
219
  After Tester Failure, PM should summarize:
@@ -48,7 +48,13 @@ If VCM rejects the transition, remain in the current PM turn and choose a legal
48
48
 
49
49
  ## User Authorization
50
50
 
51
- Only the user's explicit instruction may authorize a rejected transition. Use \`vcm-ask-user\` with the exact authorization question, ask it, and wait. After the user authorizes the exact exception, resubmit the unchanged transition with:
51
+ Only the user's explicit instruction may authorize a rejected transition.
52
+
53
+ If the current user instruction already explicitly authorizes the exact rejected transition, reuse that instruction verbatim as \`Authorization Text\`. Do not ask the user to confirm it again.
54
+
55
+ If no such instruction exists, use \`vcm-ask-user\` with the exact authorization question, ask it, and wait.
56
+
57
+ Resubmit the unchanged transition with:
52
58
 
53
59
  \`\`\`text
54
60
  Authorization Text: <user's exact authorization>