vibe-coding-master 0.9.1 → 0.9.3
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 +11 -5
- package/dist/backend/adapters/command-runner.js +1 -0
- package/dist/backend/adapters/filesystem.js +5 -2
- package/dist/backend/api/artifact-routes.js +0 -3
- package/dist/backend/api/task-routes.js +0 -1
- package/dist/backend/cli/install-vcm-harness.js +9 -2
- package/dist/backend/role-tool-policy.js +10 -1
- package/dist/backend/server.js +2 -0
- package/dist/backend/services/artifact-service.js +1 -16
- package/dist/backend/services/docs-sync-check.js +32 -0
- package/dist/backend/services/gate-review-service.js +10 -2
- package/dist/backend/services/harness-service.js +3 -2
- package/dist/backend/services/lsp-plugin.js +24 -5
- package/dist/backend/services/managed-artifact-validation.js +3 -1
- package/dist/backend/services/session-service.js +2 -2
- package/dist/backend/services/status-service.js +0 -1
- package/dist/backend/services/translation-worker-service.js +44 -6
- package/dist/backend/services/workflow-control-service.js +103 -165
- package/dist/backend/templates/handoff.js +2 -48
- package/dist/backend/templates/harness/architect-agent.js +43 -24
- package/dist/backend/templates/harness/architect-evidence-worker-agent.js +6 -3
- package/dist/backend/templates/harness/claude-root.js +9 -9
- package/dist/backend/templates/harness/coder-agent.js +0 -1
- package/dist/backend/templates/harness/gate-review.js +83 -13
- package/dist/backend/templates/harness/project-manager-agent.js +33 -27
- package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +13 -14
- package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-workflow-review-skill.js +2 -2
- package/dist/shared/types/gate-review.js +4 -0
- package/dist/shared/types/workflow.js +0 -1
- package/dist/shared/validation/artifact-check.js +1 -27
- package/dist/shared/validation/artifact-contract.js +0 -2
- package/dist/shared/validation/artifact-registry.js +1 -18
- package/package.json +1 -1
- package/scripts/claude-plugins/vcm-lsp-bridge/vcm-lsp-proxy.mjs +273 -0
- package/scripts/harness-tools/check-docs-sync +246 -0
- package/scripts/harness-tools/check-durable-docs +24 -0
- package/scripts/harness-tools/run-long-check +3 -3
- package/scripts/verify-package.mjs +2 -0
|
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
4
4
|
import { VcmError } from "../errors.js";
|
|
5
|
+
import { CODE_DIFF_DOCS_COVERAGE_ERROR_PREFIX } from "../../shared/types/gate-review.js";
|
|
5
6
|
import { WORKFLOW_EVIDENCE_ARTIFACTS, WORKFLOW_EVIDENCE_GATES, WORKFLOW_FLOWS } from "../../shared/types/workflow.js";
|
|
6
7
|
import { checkMarkdownArtifact, readArtifactSectionContent } from "../../shared/validation/artifact-check.js";
|
|
7
8
|
import { ARCHITECT_DEBUG_NORMAL_PLAN_DISPOSITION } from "../../shared/validation/artifact-contract.js";
|
|
@@ -147,7 +148,7 @@ export function createWorkflowControlService(deps) {
|
|
|
147
148
|
throw workflowError("WORKFLOW_FOLLOW_UP_APPROVAL_NOT_REQUIRED", "This workflow transition is already legal and must not consume a post-validation follow-up approval.", "Set User-Approved Follow-Up Approval Text to none.");
|
|
148
149
|
}
|
|
149
150
|
if (!followUpAllowed) {
|
|
150
|
-
throw workflowError("WORKFLOW_FOLLOW_UP_APPROVAL_INVALID", "User-approved follow-up work is legal only for a Tester dispatch after a fresh passing Test Report and successful validation-adequacy and code-diff
|
|
151
|
+
throw workflowError("WORKFLOW_FOLLOW_UP_APPROVAL_INVALID", "User-approved follow-up work is legal only for a Tester dispatch after a fresh passing Test Report and a successful validation-adequacy Gate (and, in a Debug or Diagnosis Branch, its code-diff Gate).", "Use the normal allowed transition, or ask the user only when optional Tester-owned work is proposed after validation is fully green.");
|
|
151
152
|
}
|
|
152
153
|
userApprovedFollowUp = createUserApprovedFollowUp(id(), current, candidate, effectiveFlow, baseHistoryHash, followUpApprovalText, now());
|
|
153
154
|
followUpApprovalId = userApprovedFollowUp.id;
|
|
@@ -401,7 +402,6 @@ export function createWorkflowControlService(deps) {
|
|
|
401
402
|
resolveUserInput,
|
|
402
403
|
runWhileNotAwaitingUser,
|
|
403
404
|
submitProgress,
|
|
404
|
-
assertDocsArtifactAllowed: (input, kind, role) => withLock(statePath(input), () => assertDocsArtifactAllowed(input, kind, role)),
|
|
405
405
|
assertRouteAuthorized: (input) => withLock(statePath(input), () => assertRouteAuthorized(input)),
|
|
406
406
|
claimDispatch,
|
|
407
407
|
releaseDispatch,
|
|
@@ -410,37 +410,6 @@ export function createWorkflowControlService(deps) {
|
|
|
410
410
|
confirmDispatch,
|
|
411
411
|
recoverTask
|
|
412
412
|
};
|
|
413
|
-
async function assertDocsArtifactAllowed(input, kind, role) {
|
|
414
|
-
const state = await getState(input);
|
|
415
|
-
failOnStateWarnings(state);
|
|
416
|
-
const progress = await readProgress(deps.fs, input);
|
|
417
|
-
const active = state.activeDispatch;
|
|
418
|
-
if (progress.status !== "active" || !active || active.targetRole !== role)
|
|
419
|
-
return;
|
|
420
|
-
let expected;
|
|
421
|
-
if (progress.flow === "docs-only") {
|
|
422
|
-
expected = "docs-update-report";
|
|
423
|
-
}
|
|
424
|
-
else if (role === "architect" && (progress.flow === "code-change"
|
|
425
|
-
|| progress.flow === "architect-debug" || progress.flow === "architecture-diagnosis")) {
|
|
426
|
-
const flow = progress.flow;
|
|
427
|
-
const run = resolveFlowRun(state.flowRun, progress);
|
|
428
|
-
const hadTester = progress.history.some((entry) => entry.sequence >= run.startedAtSequence
|
|
429
|
-
&& entry.sequence < active.sequence && entry.targetRole === "tester");
|
|
430
|
-
if (!run.activeBranch && hadTester) {
|
|
431
|
-
const source = run.resumedFromBranch ?? (flow === "code-change" ? "coder" : flow);
|
|
432
|
-
const docsTarget = `${flow}/architect`;
|
|
433
|
-
const allowed = await allowedAfterTester(deps.fs, input, state, flow, source, {
|
|
434
|
-
successTarget: docsTarget
|
|
435
|
-
});
|
|
436
|
-
if (allowed.includes(docsTarget))
|
|
437
|
-
expected = "docs-sync-report";
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
if (expected && expected !== kind) {
|
|
441
|
-
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.`);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
413
|
async function requestUserInput(input, question) {
|
|
445
414
|
return withLock(statePath(input), async () => {
|
|
446
415
|
const state = await getState(input);
|
|
@@ -730,15 +699,14 @@ async function canRouteUserApprovedFollowUp(fs, input, state, current, effective
|
|
|
730
699
|
return false;
|
|
731
700
|
}
|
|
732
701
|
const validation = await gateState(fs, input, "validation-adequacy");
|
|
702
|
+
if (!gatePassedForDispatch(state, effectiveFlow, "tester", "validation-adequacy", validation))
|
|
703
|
+
return false;
|
|
733
704
|
const codeDiff = await gateState(fs, input, "code-diff");
|
|
734
|
-
if (!gatePassedForDispatch(state, effectiveFlow, "tester", "
|
|
735
|
-
|| !gatePassedForDispatch(state, effectiveFlow, "tester", "code-diff", codeDiff))
|
|
705
|
+
if (!gatePassedForDispatch(state, effectiveFlow, "tester", "code-diff", codeDiff))
|
|
736
706
|
return false;
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
: `${effectiveFlow}/architect`;
|
|
741
|
-
return allowedTransitions.length === 1 && allowedTransitions[0] === expectedExit;
|
|
707
|
+
// Both Gates passed and no Final Acceptance follow-up is pending: the flow
|
|
708
|
+
// is waiting for Final Acceptance, the checkpoint the follow-up returns to.
|
|
709
|
+
return allowedTransitions.length === 0;
|
|
742
710
|
}
|
|
743
711
|
async function getAllowedTransitions(fs, input, state, current) {
|
|
744
712
|
if (current.status === "completed")
|
|
@@ -775,9 +743,6 @@ async function getAllowedTransitions(fs, input, state, current) {
|
|
|
775
743
|
return [];
|
|
776
744
|
return [];
|
|
777
745
|
}
|
|
778
|
-
const coderDocsCorrection = await allowedActiveCoderDocsCorrection(fs, input, state, flow);
|
|
779
|
-
if (coderDocsCorrection)
|
|
780
|
-
return coderDocsCorrection;
|
|
781
746
|
const segment = activeFlowSegment(current.history, flow, flowRun.startedAtSequence);
|
|
782
747
|
if (flow === "code-change") {
|
|
783
748
|
return allowedCodeChange(fs, input, state, segment, flowRun.resumedFromBranch);
|
|
@@ -799,20 +764,10 @@ function initialTransitions() {
|
|
|
799
764
|
async function allowedCodeChange(fs, input, state, segment, resumedFromBranch) {
|
|
800
765
|
const coderIndex = findLastIndex(segment, (entry) => entry.targetRole === "coder");
|
|
801
766
|
const testerIndex = findLastIndex(segment, (entry) => entry.targetRole === "tester");
|
|
802
|
-
if (
|
|
803
|
-
if (testerIndex < 0) {
|
|
804
|
-
return allowedPostImplementationArchitect(fs, input, state, "code-change");
|
|
805
|
-
}
|
|
806
|
-
return allowedAfterTester(fs, input, state, "code-change", resumedFromBranch, {
|
|
807
|
-
testerFailureFlow: "architect-debug",
|
|
808
|
-
implementationFailureFlow: "architect-debug",
|
|
809
|
-
successTarget: "code-change/architect"
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
if (coderIndex < 0) {
|
|
767
|
+
if (coderIndex < 0 && !resumedFromBranch) {
|
|
813
768
|
return (await allowedFreshArchitecturePlan(fs, input, state)) ?? ["code-change/architect"];
|
|
814
769
|
}
|
|
815
|
-
if (testerIndex < coderIndex) {
|
|
770
|
+
if (coderIndex >= 0 && testerIndex < coderIndex) {
|
|
816
771
|
const coder = await artifactState(fs, input, "coder-completion.md", "coder-completion");
|
|
817
772
|
if (!evidenceIsFresh(state, "code-change", "coder", "coder-completion.md", coder.hash)) {
|
|
818
773
|
return ["code-change/coder"];
|
|
@@ -821,14 +776,13 @@ async function allowedCodeChange(fs, input, state, segment, resumedFromBranch) {
|
|
|
821
776
|
return ["architect-debug/architect"];
|
|
822
777
|
return coder.value === "ready_for_review" ? ["code-change/tester"] : ["code-change/coder"];
|
|
823
778
|
}
|
|
779
|
+
// An Architect dispatch after validation (a code-diff docs correction or a
|
|
780
|
+
// Final Acceptance follow-up, including the first dispatch after a branch
|
|
781
|
+
// returns) is reviewed by an incremental code-diff Gate.
|
|
824
782
|
if (segment.some((entry, index) => index > testerIndex && entry.targetRole === "architect")) {
|
|
825
|
-
return allowedPostImplementationArchitect(fs, input, state, "code-change");
|
|
783
|
+
return allowedPostImplementationArchitect(fs, input, state, "code-change", "code-change");
|
|
826
784
|
}
|
|
827
|
-
return allowedAfterTester(fs, input, state, "code-change", "coder", {
|
|
828
|
-
testerFailureFlow: "architect-debug",
|
|
829
|
-
implementationFailureFlow: "architect-debug",
|
|
830
|
-
successTarget: "code-change/architect"
|
|
831
|
-
});
|
|
785
|
+
return allowedAfterTester(fs, input, state, "code-change", resumedFromBranch && coderIndex < 0 ? resumedFromBranch : "coder", { testerFailureFlow: "architect-debug", acceptanceFlow: "code-change" });
|
|
832
786
|
}
|
|
833
787
|
async function allowedFreshArchitecturePlan(fs, input, state, gateMustPostdate) {
|
|
834
788
|
const baseline = matchingEvidenceBaseline(state, "code-change", "architect");
|
|
@@ -850,19 +804,19 @@ async function allowedFreshArchitecturePlan(fs, input, state, gateMustPostdate)
|
|
|
850
804
|
async function allowedArchitectFix(fs, input, state, segment, source, resumedFromBranch, parentFlow) {
|
|
851
805
|
const architectIndex = findLastIndex(segment, (entry) => entry.targetRole === "architect");
|
|
852
806
|
const testerIndex = findLastIndex(segment, (entry) => entry.targetRole === "tester");
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
807
|
+
// A branch that passes its own code-diff Gate has returned to its parent, so
|
|
808
|
+
// Final Acceptance and its follow-ups belong to the parent flow.
|
|
809
|
+
const acceptanceFlow = parentFlow ?? source;
|
|
856
810
|
if (architectIndex < 0)
|
|
857
811
|
return [`${source}/architect`];
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
812
|
+
const artifactName = source === "architect-debug" ? "architect-debug.md" : "architecture-diagnosis.md";
|
|
813
|
+
const artifact = source === "architect-debug"
|
|
814
|
+
? await artifactState(fs, input, artifactName, "architect-debug")
|
|
815
|
+
: await artifactState(fs, input, artifactName, "architecture-diagnosis");
|
|
816
|
+
const artifactFresh = evidenceIsFresh(state, source, "architect", artifactName, artifact.hash);
|
|
817
|
+
if (testerIndex < 0 && !resumedFromBranch) {
|
|
818
|
+
if (!artifactFresh)
|
|
864
819
|
return [`${source}/architect`];
|
|
865
|
-
}
|
|
866
820
|
if (source === "architect-debug" && artifact.disposition === ARCHITECT_DEBUG_NORMAL_PLAN_DISPOSITION) {
|
|
867
821
|
return ["code-change/architect"];
|
|
868
822
|
}
|
|
@@ -875,73 +829,57 @@ async function allowedArchitectFix(fs, input, state, segment, source, resumedFro
|
|
|
875
829
|
return artifact.complete ? [`${source}/tester`] : [`${source}/architect`];
|
|
876
830
|
}
|
|
877
831
|
if (architectIndex > testerIndex) {
|
|
878
|
-
|
|
879
|
-
const artifact = source === "architect-debug"
|
|
880
|
-
? await artifactState(fs, input, artifactName, "architect-debug")
|
|
881
|
-
: await artifactState(fs, input, artifactName, "architecture-diagnosis");
|
|
882
|
-
if (evidenceIsFresh(state, source, "architect", artifactName, artifact.hash)
|
|
883
|
-
&& artifact.disposition === "user clarification required") {
|
|
832
|
+
if (artifactFresh && artifact.disposition === "user clarification required") {
|
|
884
833
|
return [`${source}/architect`];
|
|
885
834
|
}
|
|
886
|
-
|
|
835
|
+
// A new fix needs validation; any other post-validation Architect change
|
|
836
|
+
// is reviewed by an incremental code-diff Gate.
|
|
837
|
+
if (artifactFresh && artifact.complete)
|
|
887
838
|
return [`${source}/tester`];
|
|
888
|
-
|
|
889
|
-
const docs = await artifactState(fs, input, "docs-sync-report.md", "docs-sync-report");
|
|
890
|
-
return evidenceIsFresh(state, source, "architect", "docs-sync-report.md", docs.hash)
|
|
891
|
-
&& (docs.value === "synced" || docs.value === "unchanged")
|
|
892
|
-
? []
|
|
893
|
-
: [`${source}/architect`];
|
|
839
|
+
return allowedPostImplementationArchitect(fs, input, state, source, acceptanceFlow);
|
|
894
840
|
}
|
|
895
841
|
return allowedAfterTester(fs, input, state, source, source, {
|
|
896
842
|
testerFailureFlow: source === "architect-debug" ? "architecture-diagnosis" : undefined,
|
|
897
|
-
|
|
898
|
-
successTarget: parentFlow ? `${parentFlow}/architect` : `${source}/architect`
|
|
843
|
+
acceptanceFlow
|
|
899
844
|
});
|
|
900
845
|
}
|
|
901
|
-
async function allowedPostImplementationArchitect(fs, input, state, flow) {
|
|
902
|
-
const docs = await artifactState(fs, input, "docs-sync-report.md", "docs-sync-report");
|
|
903
|
-
const acceptance = await artifactState(fs, input, "final-acceptance.md", "final-acceptance");
|
|
846
|
+
async function allowedPostImplementationArchitect(fs, input, state, flow, acceptanceFlow) {
|
|
904
847
|
if (flow === "code-change") {
|
|
905
848
|
const planningTransition = await allowedFreshArchitecturePlan(fs, input, state, matchingEvidenceBaseline(state, "code-change", "architect")?.confirmedAt);
|
|
906
849
|
if (planningTransition !== undefined)
|
|
907
850
|
return planningTransition;
|
|
908
851
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
if (acceptance.value === "needs-architect-follow-up")
|
|
916
|
-
return [`${flow}/architect`];
|
|
917
|
-
return [];
|
|
918
|
-
}
|
|
919
|
-
if (docs.value === "blocked" && docs.correctionOwner && docs.correctionOwner !== "none") {
|
|
920
|
-
return [`${flow}/${docs.correctionOwner}`];
|
|
921
|
-
}
|
|
852
|
+
const codeDiff = await gateState(fs, input, "code-diff");
|
|
853
|
+
if (freshGateDecision(state, flow, "architect", "code-diff", codeDiff) === "request_changes") {
|
|
854
|
+
return codeDiffCorrectionTargets(codeDiff, flow);
|
|
855
|
+
}
|
|
856
|
+
if (gatePassedForDispatch(state, flow, "architect", "code-diff", codeDiff)) {
|
|
857
|
+
return finalAcceptanceFollowUpTargets(fs, input, state, flow, "architect", acceptanceFlow);
|
|
922
858
|
}
|
|
923
859
|
return [`${flow}/architect`];
|
|
924
860
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
const
|
|
930
|
-
if (
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
if (
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
if (
|
|
944
|
-
return ["
|
|
861
|
+
// Routes a code-diff Gate rejection: any implementation finding goes to the
|
|
862
|
+
// flow's Architect fix path, docs-only findings (alone or with test-only ones)
|
|
863
|
+
// to Architect first, and test-only findings to Tester.
|
|
864
|
+
function codeDiffCorrectionTargets(codeDiff, flow) {
|
|
865
|
+
const scopes = (codeDiff?.findings ?? []).map((finding) => finding.scope);
|
|
866
|
+
if (scopes.length === 0 || scopes.some((scope) => scope !== "docs-only" && scope !== "test-only")) {
|
|
867
|
+
return [flow === "code-change" ? "architect-debug/architect" : `${flow}/architect`];
|
|
868
|
+
}
|
|
869
|
+
return scopes.includes("docs-only") ? [`${flow}/architect`] : [`${flow}/tester`];
|
|
870
|
+
}
|
|
871
|
+
// After a passed code-diff Gate, PM runs Final Acceptance. Only a Final
|
|
872
|
+
// Acceptance produced after the latest dispatch can route a follow-up.
|
|
873
|
+
async function finalAcceptanceFollowUpTargets(fs, input, state, flow, targetRole, acceptanceFlow) {
|
|
874
|
+
const acceptance = await artifactState(fs, input, "final-acceptance.md", "final-acceptance");
|
|
875
|
+
if (!acceptance.complete
|
|
876
|
+
|| !evidenceProducedAfterActiveDispatch(state, flow, targetRole, "final-acceptance.md", acceptance.hash)) {
|
|
877
|
+
return [];
|
|
878
|
+
}
|
|
879
|
+
if (acceptance.value === "needs-coder-follow-up" && acceptanceFlow === "code-change")
|
|
880
|
+
return ["code-change/coder"];
|
|
881
|
+
if (acceptance.value === "needs-architect-follow-up")
|
|
882
|
+
return [`${acceptanceFlow}/architect`];
|
|
945
883
|
return [];
|
|
946
884
|
}
|
|
947
885
|
async function allowedAfterTester(fs, input, state, currentFlow, codeSource, options) {
|
|
@@ -962,10 +900,14 @@ async function allowedAfterTester(fs, input, state, currentFlow, codeSource, opt
|
|
|
962
900
|
return [];
|
|
963
901
|
const codeDiff = await gateState(fs, input, "code-diff");
|
|
964
902
|
if (freshGateDecision(state, currentFlow, "tester", "code-diff", codeDiff) === "request_changes") {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
903
|
+
return codeDiffCorrectionTargets(codeDiff, currentFlow);
|
|
904
|
+
}
|
|
905
|
+
// The docs coverage check refused to start code-diff: Architect updates the
|
|
906
|
+
// owning docs, then PM requests code-diff again.
|
|
907
|
+
if (codeDiff?.status === "failed"
|
|
908
|
+
&& codeDiff.error?.startsWith(CODE_DIFF_DOCS_COVERAGE_ERROR_PREFIX)
|
|
909
|
+
&& gateRecordIsFresh(state, currentFlow, "tester", "code-diff", codeDiff)) {
|
|
910
|
+
return [`${currentFlow}/architect`];
|
|
969
911
|
}
|
|
970
912
|
if (!gatePassedForDispatch(state, currentFlow, "tester", "code-diff", codeDiff))
|
|
971
913
|
return [];
|
|
@@ -973,7 +915,7 @@ async function allowedAfterTester(fs, input, state, currentFlow, codeSource, opt
|
|
|
973
915
|
const reviewedSources = codeDiff?.codeDiffSources ?? (codeDiff?.codeDiffSource ? [codeDiff.codeDiffSource] : []);
|
|
974
916
|
if (codeDiff?.status === "completed" && !reviewedSources.includes(gateCodeSource))
|
|
975
917
|
return [];
|
|
976
|
-
return
|
|
918
|
+
return finalAcceptanceFollowUpTargets(fs, input, state, currentFlow, "tester", options.acceptanceFlow);
|
|
977
919
|
}
|
|
978
920
|
async function artifactState(fs, input, fileName, kind) {
|
|
979
921
|
const relative = path.posix.join(input.handoffDir, fileName);
|
|
@@ -994,7 +936,7 @@ async function artifactState(fs, input, fileName, kind) {
|
|
|
994
936
|
value = readArtifactSectionContent(content, "Final Disposition")?.trim().toLowerCase();
|
|
995
937
|
if (kind === "test-report")
|
|
996
938
|
value = inline("Test Result");
|
|
997
|
-
if (kind === "docs-update-report" || kind === "
|
|
939
|
+
if (kind === "docs-update-report" || kind === "final-acceptance") {
|
|
998
940
|
value = readArtifactSectionContent(content, "Decision")?.trim().toLowerCase();
|
|
999
941
|
}
|
|
1000
942
|
const infrastructure = kind === "test-report"
|
|
@@ -1007,19 +949,13 @@ async function artifactState(fs, input, fileName, kind) {
|
|
|
1007
949
|
const disposition = kind === "architect-debug" || kind === "architecture-diagnosis"
|
|
1008
950
|
? readArtifactSectionContent(content, "Final Disposition")?.trim().toLowerCase()
|
|
1009
951
|
: undefined;
|
|
1010
|
-
const correctionOwner = kind === "docs-sync-report"
|
|
1011
|
-
? readArtifactSectionContent(content, "Correction Owner")?.trim().toLowerCase()
|
|
1012
|
-
: undefined;
|
|
1013
952
|
return {
|
|
1014
953
|
complete: check.status === "ok",
|
|
1015
954
|
hash: await artifactFingerprint(fs, absolute, content),
|
|
1016
955
|
value,
|
|
1017
956
|
infrastructure,
|
|
1018
957
|
hasUserApprovedCoverageGap,
|
|
1019
|
-
disposition
|
|
1020
|
-
correctionOwner: correctionOwner === "none" || asTargetRole(correctionOwner)
|
|
1021
|
-
? correctionOwner
|
|
1022
|
-
: undefined
|
|
958
|
+
disposition
|
|
1023
959
|
};
|
|
1024
960
|
}
|
|
1025
961
|
function hasSubstantiveArtifactSection(value) {
|
|
@@ -1213,7 +1149,7 @@ async function describeBlockedCheckpoint(fs, input, state, current) {
|
|
|
1213
1149
|
if (baseline.flow === "architecture-diagnosis" && test.value === "fail") {
|
|
1214
1150
|
return "Architecture Diagnosis stopped after the current Tester failure. Record the user's decision and use one exact Workflow Override if the user authorizes another Architect repair.";
|
|
1215
1151
|
}
|
|
1216
|
-
return "The latest Tester result is waiting for its current validation-adequacy or code-diff Gate result.";
|
|
1152
|
+
return "The latest Tester result is waiting for its current validation-adequacy or code-diff Gate result, or, after both Gates pass, for Final Acceptance.";
|
|
1217
1153
|
}
|
|
1218
1154
|
if (baseline.targetRole === "architect") {
|
|
1219
1155
|
return "The latest Architect dispatch has not produced the fresh workflow artifact required for its next step.";
|
|
@@ -1228,19 +1164,18 @@ async function validateCompletion(fs, input, state, candidate) {
|
|
|
1228
1164
|
throw workflowError("WORKFLOW_COMPLETION_INVALID", "An unstarted workflow cannot be completed.");
|
|
1229
1165
|
}
|
|
1230
1166
|
const flowRun = resolveFlowRun(state.flowRun, candidate);
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
if (candidate.flow === "
|
|
1235
|
-
await validateCompleteDeliveryEvidence(fs, input, state, candidate.flow);
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
if (candidate.flow === "architecture-diagnosis") {
|
|
1167
|
+
// A Debug or Diagnosis Branch returns to its parent once it passes its own
|
|
1168
|
+
// code-diff Gate, so the parent's Final Acceptance completes the run there.
|
|
1169
|
+
const inBranch = flowRun.activeBranch === candidate.flow;
|
|
1170
|
+
if (candidate.flow === "architecture-diagnosis" && !inBranch) {
|
|
1239
1171
|
const diagnosis = await artifactState(fs, input, "architecture-diagnosis.md", "architecture-diagnosis");
|
|
1240
1172
|
if (diagnosis.disposition === "analysis completed"
|
|
1241
1173
|
&& evidenceProducedAfterActiveDispatch(state, candidate.flow, "architect", "architecture-diagnosis.md", diagnosis.hash))
|
|
1242
1174
|
return;
|
|
1243
|
-
|
|
1175
|
+
}
|
|
1176
|
+
if (candidate.flow === "code-change" || candidate.flow === "architect-debug"
|
|
1177
|
+
|| candidate.flow === "architecture-diagnosis") {
|
|
1178
|
+
await validateCompleteDeliveryEvidence(fs, input, state, { ...candidate, status: "active" }, candidate.flow, flowRun);
|
|
1244
1179
|
return;
|
|
1245
1180
|
}
|
|
1246
1181
|
if (candidate.flow === "docs-only") {
|
|
@@ -1265,29 +1200,28 @@ async function validateCompletion(fs, input, state, candidate) {
|
|
|
1265
1200
|
}
|
|
1266
1201
|
}
|
|
1267
1202
|
}
|
|
1268
|
-
async function validateCompleteDeliveryEvidence(fs, input, state, flow) {
|
|
1203
|
+
async function validateCompleteDeliveryEvidence(fs, input, state, current, flow, flowRun) {
|
|
1269
1204
|
const baseline = state.activeDispatch;
|
|
1270
1205
|
if (!baseline || baseline.flow !== flow) {
|
|
1271
1206
|
throw workflowError("WORKFLOW_COMPLETION_INVALID", `The latest confirmed dispatch does not belong to the active ${flow} flow.`);
|
|
1272
1207
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
throw workflowError("WORKFLOW_COMPLETION_INVALID", `Docs Sync Report Decision must be synced or unchanged before completion; found ${docs.value ?? "missing"}.`);
|
|
1208
|
+
const validated = current.history.some((entry) => entry.sequence >= flowRun.startedAtSequence
|
|
1209
|
+
&& entry.sequence < baseline.sequence && entry.targetRole === "tester");
|
|
1210
|
+
if (baseline.targetRole !== "tester" && !(baseline.targetRole === "architect" && validated)) {
|
|
1211
|
+
throw workflowError("WORKFLOW_COMPLETION_INVALID", `The latest ${flow} dispatch is to ${baseline.targetRole}. Complete Tester validation and the validation-adequacy and code-diff Gates before completing the flow.`);
|
|
1212
|
+
}
|
|
1213
|
+
if (baseline.targetRole === "tester") {
|
|
1214
|
+
const test = await artifactState(fs, input, "test-report.md", "test-report");
|
|
1215
|
+
const validation = await gateState(fs, input, "validation-adequacy");
|
|
1216
|
+
if (!evidenceProducedAfterActiveDispatch(state, flow, "tester", "test-report.md", test.hash)
|
|
1217
|
+
|| !(test.value === "pass" || (test.value === "fail" && test.hasUserApprovedCoverageGap))
|
|
1218
|
+
|| !gatePassedForDispatch(state, flow, "tester", "validation-adequacy", validation)) {
|
|
1219
|
+
throw workflowError("WORKFLOW_COMPLETION_INVALID", `The latest Tester dispatch #${baseline.sequence} has no passing Test Report with a passed validation-adequacy Gate.`);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
const codeDiff = await gateState(fs, input, "code-diff");
|
|
1223
|
+
if (!gatePassedForDispatch(state, flow, baseline.targetRole, "code-diff", codeDiff)) {
|
|
1224
|
+
throw workflowError("WORKFLOW_COMPLETION_INVALID", `The code-diff Gate has not approved the work since the latest ${baseline.targetRole} dispatch #${baseline.sequence}.`, "Run .ai/tools/request-gate-review --gate code-diff for the current changes, then complete Final Acceptance.");
|
|
1291
1225
|
}
|
|
1292
1226
|
const acceptance = await artifactState(fs, input, "final-acceptance.md", "final-acceptance");
|
|
1293
1227
|
if (!acceptance.complete) {
|
|
@@ -1297,7 +1231,11 @@ async function validateCompleteDeliveryEvidence(fs, input, state, flow) {
|
|
|
1297
1231
|
throw workflowError("WORKFLOW_COMPLETION_INVALID", `Final Acceptance Decision must be accepted or accepted-with-known-risks; found ${acceptance.value ?? "missing"}.`);
|
|
1298
1232
|
}
|
|
1299
1233
|
if (baseline.artifactHashes["final-acceptance.md"] === acceptance.hash) {
|
|
1300
|
-
throw workflowError("WORKFLOW_COMPLETION_INVALID",
|
|
1234
|
+
throw workflowError("WORKFLOW_COMPLETION_INVALID", `Final Acceptance was not produced after the latest dispatch #${baseline.sequence}.`);
|
|
1235
|
+
}
|
|
1236
|
+
const pending = await getAllowedTransitions(fs, input, state, current);
|
|
1237
|
+
if (pending.length > 0) {
|
|
1238
|
+
throw workflowError("WORKFLOW_COMPLETION_INVALID", `The ${flow} flow still requires a dispatch before completion: ${pending.join(", ")}.`);
|
|
1301
1239
|
}
|
|
1302
1240
|
}
|
|
1303
1241
|
function evidenceProducedAfterActiveDispatch(state, flow, targetRole, artifact, currentHash) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ARCHITECT_DEBUG_DISPOSITIONS, ARCHITECT_DEBUG_STATUSES, ARCHITECTURE_BRIEF_STATUSES, ARCHITECTURE_DIAGNOSIS_DISPOSITIONS, ARCHITECTURE_EVIDENCE_STATUSES, ARCHITECTURE_PLAN_RESULTS, CODER_COMPLETION_DECISIONS, DOCS_REPORT_DECISIONS, DOCS_UPDATE_COMMIT_RULE,
|
|
1
|
+
import { ARCHITECT_DEBUG_DISPOSITIONS, ARCHITECT_DEBUG_STATUSES, ARCHITECTURE_BRIEF_STATUSES, ARCHITECTURE_DIAGNOSIS_DISPOSITIONS, ARCHITECTURE_EVIDENCE_STATUSES, ARCHITECTURE_PLAN_RESULTS, CODER_COMPLETION_DECISIONS, DOCS_REPORT_DECISIONS, DOCS_UPDATE_COMMIT_RULE, FINAL_ACCEPTANCE_DECISIONS, L3_ACTIONS, L3_REQUIRED_VALUES, PLANNING_PROGRESS_STATUSES, STRICT_NONE_VALUE, TEST_INFRASTRUCTURE_STATUSES, TEST_RESULTS, renderArtifactOptions } from "../../shared/validation/artifact-contract.js";
|
|
2
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. -->";
|
|
3
3
|
export function renderArchitectureBriefTemplate(taskSlug) {
|
|
4
4
|
return `# Architecture Brief: ${taskSlug}
|
|
@@ -483,52 +483,6 @@ TBD
|
|
|
483
483
|
${renderArtifactOptions(ARCHITECTURE_DIAGNOSIS_DISPOSITIONS)}
|
|
484
484
|
`;
|
|
485
485
|
}
|
|
486
|
-
export function renderDocsSyncReportTemplate(taskSlug) {
|
|
487
|
-
return `# Docs Sync Report: ${taskSlug}
|
|
488
|
-
|
|
489
|
-
${CURRENT_HANDOFF_NOTICE}
|
|
490
|
-
|
|
491
|
-
## Summary
|
|
492
|
-
|
|
493
|
-
TBD
|
|
494
|
-
|
|
495
|
-
## Architecture Drift Check
|
|
496
|
-
|
|
497
|
-
TBD
|
|
498
|
-
|
|
499
|
-
## Docs Updated
|
|
500
|
-
|
|
501
|
-
TBD
|
|
502
|
-
|
|
503
|
-
## Docs Reviewed And Left Unchanged
|
|
504
|
-
|
|
505
|
-
TBD
|
|
506
|
-
|
|
507
|
-
## Public Contract / Module Boundary Notes
|
|
508
|
-
|
|
509
|
-
TBD
|
|
510
|
-
|
|
511
|
-
## Remaining Documentation Risks
|
|
512
|
-
|
|
513
|
-
TBD
|
|
514
|
-
|
|
515
|
-
## Known Issues Disposition
|
|
516
|
-
|
|
517
|
-
TBD
|
|
518
|
-
|
|
519
|
-
## Correction Owner
|
|
520
|
-
|
|
521
|
-
${renderArtifactOptions(DOCS_SYNC_CORRECTION_OWNERS)}
|
|
522
|
-
|
|
523
|
-
## Correction Evidence
|
|
524
|
-
|
|
525
|
-
${STRICT_NONE_VALUE}
|
|
526
|
-
|
|
527
|
-
## Decision
|
|
528
|
-
|
|
529
|
-
${renderArtifactOptions(DOCS_REPORT_DECISIONS)}
|
|
530
|
-
`;
|
|
531
|
-
}
|
|
532
486
|
export function renderDocsUpdateReportTemplate(taskSlug, assignmentId = "docs-only") {
|
|
533
487
|
return `# Docs Update Report: ${taskSlug}
|
|
534
488
|
|
|
@@ -639,7 +593,7 @@ TBD
|
|
|
639
593
|
|
|
640
594
|
TBD
|
|
641
595
|
|
|
642
|
-
## Review And
|
|
596
|
+
## Review And Documentation
|
|
643
597
|
|
|
644
598
|
TBD
|
|
645
599
|
|