vibe-coding-master 0.7.3 → 0.7.5

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.
@@ -3,7 +3,7 @@ import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
3
3
  import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js";
4
4
  import { VcmError } from "../errors.js";
5
5
  import { resolveRepoPath } from "../adapters/filesystem.js";
6
- import { renderArchitecturePlanTemplate, renderCoderCompletionTemplate, renderDocsSyncReportTemplate, renderFinalAcceptanceTemplate, renderKnownIssuesTemplate, renderMessageRouteTemplate, renderTestReportTemplate } from "../templates/handoff.js";
6
+ import { renderArchitecturePlanTemplate, renderArchitectDebugTemplate, renderCoderCompletionTemplate, renderDocsSyncReportTemplate, renderFinalAcceptanceTemplate, renderKnownIssuesTemplate, renderMessageRouteTemplate, renderTestReportTemplate } from "../templates/handoff.js";
7
7
  import { renderRoleCommandTemplate } from "../templates/role-command.js";
8
8
  const ARTIFACT_PATH_KEYS = [
9
9
  ["architecture-plan", "architecturePlanPath"],
@@ -62,6 +62,7 @@ export function createArtifactService(fs) {
62
62
  [paths.architecturePlanPath, renderArchitecturePlanTemplate(input.taskSlug)],
63
63
  [paths.knownIssuesPath, renderKnownIssuesTemplate(input.taskSlug)],
64
64
  [path.posix.join(paths.handoffDir, "coder-completion.md"), renderCoderCompletionTemplate(input.taskSlug)],
65
+ [path.posix.join(paths.handoffDir, "architect-debug.md"), renderArchitectDebugTemplate(input.taskSlug)],
65
66
  [paths.testReportPath, renderTestReportTemplate(input.taskSlug)],
66
67
  [paths.docsSyncReportPath, renderDocsSyncReportTemplate(input.taskSlug)],
67
68
  [paths.finalAcceptancePath, renderFinalAcceptanceTemplate(input.taskSlug)],
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { CODE_DIFF_SOURCES, GATE_REVIEW_GATES } from "../../shared/types/gate-review.js";
4
+ import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js";
4
5
  import { VcmError } from "../errors.js";
5
6
  import { resolveRepoPath } from "../adapters/filesystem.js";
6
7
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
@@ -13,13 +14,52 @@ const GATE_REVIEWER_ROLE = "gate-reviewer";
13
14
  const DEFAULT_REPORT_POLL_INTERVAL_MS = 1000;
14
15
  const DEFAULT_REPORT_TIMEOUT_MS = 30 * 60 * 1000;
15
16
  const activeRuns = new Set();
17
+ const ARCHITECTURE_ANALYSIS_FIELDS = [
18
+ "Evidence Read",
19
+ "End-To-End Flow",
20
+ "Scope Fit",
21
+ "Code Reality",
22
+ "Ownership",
23
+ "Data Flow",
24
+ "Lifecycle",
25
+ "Invariants",
26
+ "Boundaries And Public Surface",
27
+ "Failure Model",
28
+ "Coder Readiness"
29
+ ];
30
+ const VALIDATION_ANALYSIS_FIELDS = [
31
+ "Evidence Read",
32
+ "Changed Behavior And Risk",
33
+ "Coverage Mapping",
34
+ "Baseline Coverage",
35
+ "Integration And E2E Coverage",
36
+ "Boundary And Failure Coverage",
37
+ "Public Contract Coverage",
38
+ "Test Integrity",
39
+ "Skips And Gaps",
40
+ "Validation Readiness"
41
+ ];
42
+ const CODE_DIFF_ANALYSIS_FIELDS = [
43
+ "Commit Range And Sources",
44
+ "Evidence Read",
45
+ "Changed Files And Symbols",
46
+ "Changed Behavior",
47
+ "Source Evidence Fit",
48
+ "Callers And Public Surface",
49
+ "State Lifecycle And Failure Paths",
50
+ "Coding Standards",
51
+ "Baseline Test Integrity",
52
+ "Generated Context And Durable Docs",
53
+ "Code Readiness"
54
+ ];
16
55
  const SOURCE_ARTIFACTS = {
17
56
  "architecture-plan": [
18
57
  ".ai/vcm/handoffs/architecture-plan.md"
19
58
  ],
20
59
  "validation-adequacy": [
21
60
  ".ai/vcm/handoffs/architecture-plan.md",
22
- ".ai/vcm/handoffs/test-report.md"
61
+ ".ai/vcm/handoffs/test-report.md",
62
+ "docs/TESTING.md"
23
63
  ],
24
64
  "code-diff": []
25
65
  };
@@ -29,7 +69,8 @@ const CODE_DIFF_SOURCE_ARTIFACTS = {
29
69
  ".ai/vcm/handoffs/coder-completion.md"
30
70
  ],
31
71
  "architect-debug": [
32
- ".ai/vcm/handoffs/role-commands/architect.md"
72
+ ".ai/vcm/handoffs/role-commands/architect.md",
73
+ ".ai/vcm/handoffs/architect-debug.md"
33
74
  ],
34
75
  "architect-diagnosis": [
35
76
  ".ai/vcm/handoffs/architecture-diagnosis.md"
@@ -103,6 +144,7 @@ export function createGateReviewService(deps) {
103
144
  decision: undefined,
104
145
  error: message,
105
146
  codeDiffSource: undefined,
147
+ codeDiffSources: undefined,
106
148
  requestId: undefined,
107
149
  requestPath: undefined,
108
150
  inputHash: undefined,
@@ -143,6 +185,7 @@ export function createGateReviewService(deps) {
143
185
  changedFiles: undefined,
144
186
  diffStat: undefined,
145
187
  codeDiffSource,
188
+ codeDiffSources: codeDiffSource ? [codeDiffSource] : undefined,
146
189
  requestedAt: undefined,
147
190
  startedAt: undefined,
148
191
  completedAt: now(),
@@ -200,6 +243,7 @@ export function createGateReviewService(deps) {
200
243
  changedFiles: undefined,
201
244
  diffStat: undefined,
202
245
  codeDiffSource,
246
+ codeDiffSources: codeDiffSource ? [codeDiffSource] : undefined,
203
247
  requestedAt: undefined,
204
248
  startedAt: undefined,
205
249
  completedAt: undefined,
@@ -214,7 +258,10 @@ export function createGateReviewService(deps) {
214
258
  message: "No new commits to review."
215
259
  };
216
260
  }
217
- const inputHash = await computeInputHash(deps, context.taskRepoRoot, gate, codeDiffInput, codeDiffSource);
261
+ const codeDiffSources = gate === "code-diff" && codeDiffInput && codeDiffSource
262
+ ? resolveCodeDiffSources(record, codeDiffInput, codeDiffSource)
263
+ : undefined;
264
+ const inputHash = await computeInputHash(deps, context.taskRepoRoot, gate, codeDiffInput, codeDiffSources);
218
265
  if (!options.force
219
266
  && record.status === "completed"
220
267
  && record.decision === "approve"
@@ -246,6 +293,7 @@ export function createGateReviewService(deps) {
246
293
  changedFiles: codeDiffInput?.changedFiles,
247
294
  diffStat: codeDiffInput?.diffStat,
248
295
  codeDiffSource,
296
+ codeDiffSources,
249
297
  requestedAt: timestamp,
250
298
  startedAt: undefined,
251
299
  completedAt: undefined,
@@ -270,12 +318,13 @@ export function createGateReviewService(deps) {
270
318
  requestedAt: timestamp,
271
319
  inputHash,
272
320
  codeDiffSource,
321
+ codeDiffSources,
273
322
  codeDiff: codeDiffInput,
274
323
  reportPath: nextRecord.reportPath,
275
324
  promptPath: nextRecord.promptPath
276
325
  });
277
326
  await saveIndex(deps.fs, context.taskRepoRoot, index);
278
- void runGateReview(context, gate, requestId, codeDiffInput, codeDiffSource).catch(() => {
327
+ void runGateReview(context, gate, requestId, codeDiffInput, codeDiffSources).catch(() => {
279
328
  // runGateReview records failures in the persisted gate state.
280
329
  });
281
330
  return {
@@ -285,7 +334,7 @@ export function createGateReviewService(deps) {
285
334
  message: "Gate review started."
286
335
  };
287
336
  }
288
- async function runGateReview(context, gate, requestId, codeDiffInput, codeDiffSource) {
337
+ async function runGateReview(context, gate, requestId, codeDiffInput, codeDiffSources) {
289
338
  const runKey = `${context.taskRepoRoot}:${context.taskSlug}:${gate}`;
290
339
  if (activeRuns.has(runKey)) {
291
340
  return;
@@ -302,7 +351,7 @@ export function createGateReviewService(deps) {
302
351
  await updateRequestStatus(deps.fs, context, requestId, "running", { startedAt: timestamp });
303
352
  const reviewDir = resolveRepoPath(context.taskRepoRoot, GATE_REVIEW_DIR);
304
353
  const agentPath = resolveRepoPath(context.repoRoot, GATE_REVIEW_AGENT_PATH);
305
- const prompt = buildGatePrompt(context, gate, requestId, codeDiffInput, codeDiffSource);
354
+ const prompt = buildGatePrompt(context, gate, requestId, codeDiffInput, codeDiffSources);
306
355
  await deps.fs.ensureDir(reviewDir);
307
356
  await deps.fs.ensureDir(resolveRepoPath(context.taskRepoRoot, REQUESTS_DIR));
308
357
  await deps.fs.writeText(resolveRepoPath(context.taskRepoRoot, promptPathForRequest(requestId)), prompt);
@@ -333,6 +382,12 @@ export function createGateReviewService(deps) {
333
382
  const completedAt = now();
334
383
  await recordGateReviewerTurnStop(context, gateTurnStarted);
335
384
  gateTurnStarted = false;
385
+ await updateRequestStatus(deps.fs, context, requestId, "completed", {
386
+ completedAt,
387
+ decision: parsed.decision,
388
+ reportPath: parsed.reportPath
389
+ });
390
+ activeRuns.delete(runKey);
336
391
  await updateGateRecord(context, gate, {
337
392
  status: "completed",
338
393
  decision: parsed.decision,
@@ -344,11 +399,6 @@ export function createGateReviewService(deps) {
344
399
  callbackError: undefined,
345
400
  updatedAt: completedAt
346
401
  }, { clearActiveGate: true });
347
- await updateRequestStatus(deps.fs, context, requestId, "completed", {
348
- completedAt,
349
- decision: parsed.decision,
350
- reportPath: parsed.reportPath
351
- });
352
402
  await callbackProjectManager(context, gate, "completed", parsed.decision, parsed.reportPath);
353
403
  }
354
404
  catch (error) {
@@ -356,6 +406,11 @@ export function createGateReviewService(deps) {
356
406
  const message = errorMessage(error);
357
407
  await recordGateReviewerTurnStop(context, gateTurnStarted);
358
408
  gateTurnStarted = false;
409
+ await updateRequestStatus(deps.fs, context, requestId, "failed", {
410
+ completedAt: timestamp,
411
+ error: message
412
+ });
413
+ activeRuns.delete(runKey);
359
414
  await updateGateRecord(context, gate, {
360
415
  status: "failed",
361
416
  error: message,
@@ -364,10 +419,6 @@ export function createGateReviewService(deps) {
364
419
  callbackError: undefined,
365
420
  updatedAt: timestamp
366
421
  }, { clearActiveGate: true });
367
- await updateRequestStatus(deps.fs, context, requestId, "failed", {
368
- completedAt: timestamp,
369
- error: message
370
- });
371
422
  await callbackProjectManager(context, gate, "failed", undefined, reportPathForGate(gate), message);
372
423
  }
373
424
  finally {
@@ -598,6 +649,7 @@ function normalizeIndex(raw, config, timestamp) {
598
649
  changedFiles: Array.isArray(existing?.changedFiles) ? existing.changedFiles.filter(isString) : undefined,
599
650
  diffStat: typeof existing?.diffStat === "string" ? existing.diffStat : undefined,
600
651
  codeDiffSource: isCodeDiffSource(existing?.codeDiffSource) ? existing.codeDiffSource : undefined,
652
+ codeDiffSources: normalizeCodeDiffSources(existing?.codeDiffSources, existing?.codeDiffSource),
601
653
  summary: typeof existing?.summary === "string" ? existing.summary : undefined,
602
654
  findings: Array.isArray(existing?.findings) ? existing.findings.filter(isFinding) : undefined,
603
655
  error: typeof existing?.error === "string" ? existing.error : undefined,
@@ -766,23 +818,25 @@ async function isAncestor(runner, cwd, ancestor, descendant) {
766
818
  const result = await runner.run("git", ["merge-base", "--is-ancestor", ancestor, descendant], { cwd });
767
819
  return result.exitCode === 0;
768
820
  }
769
- async function computeInputHash(deps, taskRepoRoot, gate, codeDiffInput, codeDiffSource) {
821
+ async function computeInputHash(deps, taskRepoRoot, gate, codeDiffInput, codeDiffSources) {
770
822
  const digest = createHash("sha256");
771
823
  const coreArtifact = CORE_INPUT_ARTIFACTS[gate];
772
824
  if (coreArtifact) {
773
825
  digest.update(coreArtifact);
774
826
  digest.update(await deps.fs.readText(resolveRepoPath(taskRepoRoot, coreArtifact)));
775
- return digest.digest("hex");
776
827
  }
777
828
  const common = [
778
829
  "CLAUDE.md",
830
+ ".claude/agents/architect.md",
831
+ ".claude/agents/coder.md",
779
832
  ".claude/agents/gate-reviewer.md",
833
+ ".claude/agents/tester.md",
780
834
  ".claude/skills/vcm-gate-review/SKILL.md",
781
835
  ".ai/tools/request-gate-review",
782
836
  "docs/CODING_STANDARDS.md"
783
837
  ];
784
- const sourceArtifacts = getSourceArtifacts(gate, codeDiffSource);
785
- for (const relativePath of [...common, ...sourceArtifacts]) {
838
+ const sourceArtifacts = getSourceArtifacts(gate, codeDiffSources);
839
+ for (const relativePath of new Set([...common, ...sourceArtifacts].filter((item) => item !== coreArtifact))) {
786
840
  digest.update(relativePath);
787
841
  const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
788
842
  if (await deps.fs.pathExists(absolutePath)) {
@@ -793,8 +847,8 @@ async function computeInputHash(deps, taskRepoRoot, gate, codeDiffInput, codeDif
793
847
  }
794
848
  }
795
849
  if (gate === "code-diff" && codeDiffInput) {
796
- digest.update("codeDiffSource");
797
- digest.update(codeDiffSource ?? "<missing>");
850
+ digest.update("codeDiffSources");
851
+ digest.update(codeDiffSources?.join("\n") ?? "<missing>");
798
852
  digest.update("baseCommit");
799
853
  digest.update(codeDiffInput.baseCommit);
800
854
  digest.update("headCommit");
@@ -807,9 +861,44 @@ async function computeInputHash(deps, taskRepoRoot, gate, codeDiffInput, codeDif
807
861
  digest.update(codeDiffInput.diffHash);
808
862
  }
809
863
  if (gate === "architecture-plan") {
810
- digest.update(await commandStdout(deps.runner, taskRepoRoot, ["status", "--porcelain=v1"]));
811
- digest.update(await commandStdout(deps.runner, taskRepoRoot, ["diff", "--binary"]));
812
- digest.update(await commandStdout(deps.runner, taskRepoRoot, ["diff", "--cached", "--binary"]));
864
+ const evidencePathspec = ["--", ".", ":(exclude).ai/vcm/**"];
865
+ digest.update("head");
866
+ digest.update(await commandStdout(deps.runner, taskRepoRoot, ["rev-parse", "HEAD"]));
867
+ digest.update("workingDiff");
868
+ digest.update(await commandStdout(deps.runner, taskRepoRoot, ["diff", "--binary", ...evidencePathspec]));
869
+ digest.update("stagedDiff");
870
+ digest.update(await commandStdout(deps.runner, taskRepoRoot, ["diff", "--cached", "--binary", ...evidencePathspec]));
871
+ const untracked = splitLines(await commandStdout(deps.runner, taskRepoRoot, [
872
+ "ls-files",
873
+ "--others",
874
+ "--exclude-standard",
875
+ ...evidencePathspec
876
+ ]));
877
+ for (const relativePath of untracked) {
878
+ digest.update("untracked");
879
+ digest.update(relativePath);
880
+ digest.update(await commandStdout(deps.runner, taskRepoRoot, ["hash-object", "--", relativePath]));
881
+ }
882
+ }
883
+ if (gate === "validation-adequacy") {
884
+ const evidencePathspec = ["--", ".", ":(exclude).ai/vcm/**", ":(exclude)docs/**"];
885
+ digest.update("trackedEvidence");
886
+ digest.update(await commandStdout(deps.runner, taskRepoRoot, ["ls-files", "-s", ...evidencePathspec]));
887
+ digest.update("workingEvidence");
888
+ digest.update(await commandStdout(deps.runner, taskRepoRoot, ["diff", "--binary", ...evidencePathspec]));
889
+ digest.update("stagedEvidence");
890
+ digest.update(await commandStdout(deps.runner, taskRepoRoot, ["diff", "--cached", "--binary", ...evidencePathspec]));
891
+ const untracked = splitLines(await commandStdout(deps.runner, taskRepoRoot, [
892
+ "ls-files",
893
+ "--others",
894
+ "--exclude-standard",
895
+ ...evidencePathspec
896
+ ]));
897
+ for (const relativePath of untracked) {
898
+ digest.update("untrackedEvidence");
899
+ digest.update(relativePath);
900
+ digest.update(await commandStdout(deps.runner, taskRepoRoot, ["hash-object", "--", relativePath]));
901
+ }
813
902
  }
814
903
  return digest.digest("hex");
815
904
  }
@@ -838,21 +927,30 @@ function splitLines(value) {
838
927
  .map((line) => line.trim())
839
928
  .filter(Boolean);
840
929
  }
841
- function buildGatePrompt(context, gate, requestId, codeDiffInput, codeDiffSource) {
930
+ function buildGatePrompt(context, gate, requestId, codeDiffInput, codeDiffSources) {
842
931
  const reportPath = reportPathForGate(gate);
843
932
  const absoluteReportPath = resolveRepoPath(context.taskRepoRoot, reportPath);
844
- const evidence = getSourceArtifacts(gate, codeDiffSource)
933
+ const evidence = getSourceArtifacts(gate, codeDiffSources)
845
934
  .map((relativePath) => `- ${relativePath}`)
846
935
  .join("\n");
847
936
  const gitLine = gate === "architecture-plan"
848
937
  ? "\nDiff: inspect git status/diff in Worktree."
849
938
  : "";
939
+ const architectureContract = gate === "architecture-plan"
940
+ ? "\n\nComplete every Architecture Analysis field required by the Gate Reviewer role with concrete current-worktree evidence before deciding."
941
+ : "";
942
+ const validationContract = gate === "validation-adequacy"
943
+ ? "\n\nComplete every Validation Analysis field required by the Gate Reviewer role with concrete current-worktree production and test evidence before deciding."
944
+ : "";
945
+ const codeDiffContract = gate === "code-diff"
946
+ ? "\n\nComplete every Code Diff Analysis field required by the Gate Reviewer role with concrete evidence from the named commit range before deciding."
947
+ : "";
850
948
  const codeDiffSection = gate === "code-diff" && codeDiffInput
851
949
  ? `
852
950
 
853
951
  Code Diff Input:
854
952
  This code-diff gate reviews the new commits from one PM route flow, not the whole task and not one terminal turn.
855
- Code source: ${codeDiffSource}
953
+ Code sources: ${codeDiffSources?.join(" -> ") ?? "<missing>"}
856
954
  Base commit: ${codeDiffInput.baseCommit}
857
955
  Head commit: ${codeDiffInput.headCommit}
858
956
  Commits:
@@ -873,7 +971,7 @@ Request: ${requestId}
873
971
  Report: ${absoluteReportPath}
874
972
 
875
973
  Evidence:
876
- ${evidence}${gitLine}${codeDiffSection}
974
+ ${evidence}${gitLine}${architectureContract}${validationContract}${codeDiffContract}${codeDiffSection}
877
975
 
878
976
  Write only Report. Start exactly:
879
977
  Gate: ${gate}
@@ -940,17 +1038,155 @@ async function parseGateReport(fs, taskRepoRoot, gate, requestId, timestamp) {
940
1038
  statusCode: 500
941
1039
  });
942
1040
  }
1041
+ const findings = extractFindings(content);
1042
+ if (gate === "architecture-plan") {
1043
+ validateArchitectureAnalysis(content);
1044
+ }
1045
+ if (gate === "validation-adequacy") {
1046
+ validateValidationAnalysis(content);
1047
+ if (decision === "approve") {
1048
+ await validateValidationApprovalInput(fs, taskRepoRoot);
1049
+ }
1050
+ }
1051
+ if (gate === "code-diff") {
1052
+ validateCodeDiffAnalysis(content);
1053
+ }
1054
+ if (decision === "request_changes") {
1055
+ validateRequestChangeFindings(findings);
1056
+ if (gate === "code-diff") {
1057
+ validateCodeDiffFindings(findings);
1058
+ }
1059
+ }
943
1060
  return {
944
1061
  gate,
945
1062
  requestId: parsedRequest,
946
1063
  decision,
947
1064
  summary: extractSummary(content),
948
- findings: extractFindings(content),
1065
+ findings,
949
1066
  reportPath,
950
1067
  content,
951
1068
  parsedAt: timestamp
952
1069
  };
953
1070
  }
1071
+ function validateArchitectureAnalysis(content) {
1072
+ const section = extractMarkdownSection(content, "Architecture Analysis");
1073
+ if (!section) {
1074
+ throw new VcmError({
1075
+ code: "GATE_REVIEW_ARCHITECTURE_ANALYSIS_MISSING",
1076
+ message: "Architecture-plan review must contain a non-empty Architecture Analysis section.",
1077
+ statusCode: 500
1078
+ });
1079
+ }
1080
+ const missingFields = ARCHITECTURE_ANALYSIS_FIELDS.filter((field) => !matchField(section, field));
1081
+ if (missingFields.length > 0) {
1082
+ throw new VcmError({
1083
+ code: "GATE_REVIEW_ARCHITECTURE_ANALYSIS_INCOMPLETE",
1084
+ message: `Architecture Analysis is missing required evidence: ${missingFields.join(", ")}.`,
1085
+ statusCode: 500
1086
+ });
1087
+ }
1088
+ }
1089
+ function validateValidationAnalysis(content) {
1090
+ const section = extractMarkdownSection(content, "Validation Analysis");
1091
+ if (!section) {
1092
+ throw new VcmError({
1093
+ code: "GATE_REVIEW_VALIDATION_ANALYSIS_MISSING",
1094
+ message: "Validation-adequacy review must contain a non-empty Validation Analysis section.",
1095
+ statusCode: 500
1096
+ });
1097
+ }
1098
+ const missingFields = VALIDATION_ANALYSIS_FIELDS.filter((field) => !matchField(section, field));
1099
+ if (missingFields.length > 0) {
1100
+ throw new VcmError({
1101
+ code: "GATE_REVIEW_VALIDATION_ANALYSIS_INCOMPLETE",
1102
+ message: `Validation Analysis is missing required evidence: ${missingFields.join(", ")}.`,
1103
+ statusCode: 500
1104
+ });
1105
+ }
1106
+ }
1107
+ function validateCodeDiffAnalysis(content) {
1108
+ const section = extractMarkdownSection(content, "Code Diff Analysis");
1109
+ if (!section) {
1110
+ throw new VcmError({
1111
+ code: "GATE_REVIEW_CODE_DIFF_ANALYSIS_MISSING",
1112
+ message: "Code-diff review must contain a non-empty Code Diff Analysis section.",
1113
+ statusCode: 500
1114
+ });
1115
+ }
1116
+ const missingFields = CODE_DIFF_ANALYSIS_FIELDS.filter((field) => !matchField(section, field));
1117
+ if (missingFields.length > 0) {
1118
+ throw new VcmError({
1119
+ code: "GATE_REVIEW_CODE_DIFF_ANALYSIS_INCOMPLETE",
1120
+ message: `Code Diff Analysis is missing required evidence: ${missingFields.join(", ")}.`,
1121
+ statusCode: 500
1122
+ });
1123
+ }
1124
+ }
1125
+ async function validateValidationApprovalInput(fs, taskRepoRoot) {
1126
+ const relativePath = CORE_INPUT_ARTIFACTS["validation-adequacy"];
1127
+ if (!relativePath) {
1128
+ return;
1129
+ }
1130
+ const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
1131
+ const content = await fs.pathExists(absolutePath) ? await fs.readText(absolutePath) : null;
1132
+ const check = checkMarkdownArtifact("test-report", relativePath, content);
1133
+ const testResult = content ? matchField(content, "Test Result")?.toLowerCase() : undefined;
1134
+ if (check.status === "ok" && testResult === "pass") {
1135
+ return;
1136
+ }
1137
+ const details = [
1138
+ check.status !== "ok" ? `status=${check.status}` : "",
1139
+ check.missingHeadings.length > 0 ? `missing headings: ${check.missingHeadings.join(", ")}` : "",
1140
+ check.invalidFields.length > 0 ? check.invalidFields.join(" ") : "",
1141
+ check.hasPlaceholder ? "contains placeholders" : "",
1142
+ testResult !== "pass" ? "Test Result must be pass before approval." : ""
1143
+ ].filter(Boolean).join("; ");
1144
+ throw new VcmError({
1145
+ code: "GATE_REVIEW_VALIDATION_INPUT_INCOMPLETE",
1146
+ message: `Validation-adequacy cannot approve incomplete Tester evidence in ${relativePath}. ${details}`,
1147
+ statusCode: 500
1148
+ });
1149
+ }
1150
+ function validateRequestChangeFindings(findings) {
1151
+ if (findings.length === 0) {
1152
+ throw new VcmError({
1153
+ code: "GATE_REVIEW_FINDINGS_MISSING",
1154
+ message: "A request_changes decision must contain at least one structured finding.",
1155
+ statusCode: 500
1156
+ });
1157
+ }
1158
+ const incomplete = findings.find((finding) => (!finding.evidence.trim()
1159
+ || !finding.expected.trim()
1160
+ || !finding.gap.trim()
1161
+ || !finding.risk.trim()));
1162
+ if (incomplete) {
1163
+ throw new VcmError({
1164
+ code: "GATE_REVIEW_FINDING_INCOMPLETE",
1165
+ message: `Finding ${incomplete.title} must contain Evidence, Expected, Gap, and Risk.`,
1166
+ statusCode: 500
1167
+ });
1168
+ }
1169
+ }
1170
+ function validateCodeDiffFindings(findings) {
1171
+ const incomplete = findings.find((finding) => !finding.file?.trim() || !finding.location?.trim());
1172
+ if (incomplete) {
1173
+ throw new VcmError({
1174
+ code: "GATE_REVIEW_CODE_DIFF_FINDING_LOCATION_MISSING",
1175
+ message: `Code-diff finding ${incomplete.title} must contain File and Line Or Symbol.`,
1176
+ statusCode: 500
1177
+ });
1178
+ }
1179
+ }
1180
+ function extractMarkdownSection(content, heading) {
1181
+ const match = new RegExp(`^##\\s+${escapeRegex(heading)}\\s*$`, "im").exec(content);
1182
+ if (!match || match.index === undefined) {
1183
+ return undefined;
1184
+ }
1185
+ const remainder = content.slice(match.index + match[0].length);
1186
+ const nextHeading = remainder.search(/^##\s+/m);
1187
+ const section = (nextHeading >= 0 ? remainder.slice(0, nextHeading) : remainder).trim();
1188
+ return section || undefined;
1189
+ }
954
1190
  async function updateRequestStatus(fs, context, requestId, status, patch) {
955
1191
  const requestPath = resolveRepoPath(context.taskRepoRoot, path.posix.join(REQUESTS_DIR, `${requestId}.json`));
956
1192
  const current = await readJsonOrNull(fs, requestPath) ?? {
@@ -1018,6 +1254,7 @@ function extractFindings(content) {
1018
1254
  title,
1019
1255
  file: matchField(block, "file"),
1020
1256
  line: parsePositiveInteger(matchField(block, "line")),
1257
+ location: matchField(block, "line or symbol"),
1021
1258
  evidence: matchField(block, "evidence") ?? "",
1022
1259
  expected: matchField(block, "expected") ?? "",
1023
1260
  gap: matchField(block, "gap") ?? "",
@@ -1026,11 +1263,30 @@ function extractFindings(content) {
1026
1263
  }
1027
1264
  return findings;
1028
1265
  }
1029
- function getSourceArtifacts(gate, codeDiffSource) {
1266
+ function getSourceArtifacts(gate, codeDiffSources) {
1030
1267
  if (gate !== "code-diff") {
1031
1268
  return SOURCE_ARTIFACTS[gate];
1032
1269
  }
1033
- return codeDiffSource ? CODE_DIFF_SOURCE_ARTIFACTS[codeDiffSource] : [];
1270
+ return [...new Set((codeDiffSources ?? []).flatMap((source) => CODE_DIFF_SOURCE_ARTIFACTS[source]))];
1271
+ }
1272
+ function resolveCodeDiffSources(record, codeDiffInput, currentSource) {
1273
+ const continuingRecordedRange = record.baseCommit === codeDiffInput.baseCommit
1274
+ && ((record.status === "completed" && record.decision === "request_changes")
1275
+ || record.status === "failed");
1276
+ if (!continuingRecordedRange) {
1277
+ return [currentSource];
1278
+ }
1279
+ return [...new Set([
1280
+ ...(normalizeCodeDiffSources(record.codeDiffSources, record.codeDiffSource) ?? []),
1281
+ currentSource
1282
+ ])];
1283
+ }
1284
+ function normalizeCodeDiffSources(sources, source) {
1285
+ const normalized = Array.isArray(sources) ? sources.filter(isCodeDiffSource) : [];
1286
+ if (normalized.length === 0 && isCodeDiffSource(source)) {
1287
+ normalized.push(source);
1288
+ }
1289
+ return normalized.length > 0 ? [...new Set(normalized)] : undefined;
1034
1290
  }
1035
1291
  export function isCodeDiffSource(value) {
1036
1292
  return typeof value === "string" && CODE_DIFF_SOURCES.includes(value);
@@ -1119,6 +1375,10 @@ function errorMessage(error) {
1119
1375
  function isPendingReportError(error) {
1120
1376
  return error instanceof VcmError && [
1121
1377
  "GATE_REVIEW_DECISION_MISSING",
1378
+ "GATE_REVIEW_ARCHITECTURE_ANALYSIS_MISSING",
1379
+ "GATE_REVIEW_ARCHITECTURE_ANALYSIS_INCOMPLETE",
1380
+ "GATE_REVIEW_FINDINGS_MISSING",
1381
+ "GATE_REVIEW_FINDING_INCOMPLETE",
1122
1382
  "GATE_REVIEW_REPORT_GATE_MISMATCH",
1123
1383
  "GATE_REVIEW_REPORT_MISSING",
1124
1384
  "GATE_REVIEW_REPORT_STALE"
@@ -7,10 +7,56 @@ TBD
7
7
 
8
8
  ## Current Code Reality
9
9
 
10
+ ### Planning Boundary
11
+
12
+ TBD
13
+
14
+ ### Code Reading Evidence
15
+
16
+ | File / Symbol | Called By | Calls / Consumers | State / Side Effects | Verified Behavior |
17
+ | --- | --- | --- | --- | --- |
18
+ | TBD | TBD | TBD | TBD | TBD |
19
+
20
+ ### Existing Behavior Trace
21
+
22
+ TBD
23
+
24
+ ### Code / Docs Conflicts
25
+
10
26
  TBD
11
27
 
12
28
  ## Architecture Decision
13
29
 
30
+ ### Changed Behavior Flow
31
+
32
+ TBD
33
+
34
+ ### Ownership
35
+
36
+ TBD
37
+
38
+ ### Data Flow
39
+
40
+ TBD
41
+
42
+ ### Lifecycle
43
+
44
+ TBD
45
+
46
+ ### Boundaries
47
+
48
+ TBD
49
+
50
+ ### Invariants
51
+
52
+ TBD
53
+
54
+ ### Failure Model
55
+
56
+ TBD
57
+
58
+ ### Decision Rationale
59
+
14
60
  TBD
15
61
 
16
62
  ## Module/File Plan
@@ -27,7 +73,7 @@ Task-specific context and coder guidance go here, not in source-code comments.
27
73
  Source-code comments should only describe durable behavior, contracts, invariants,
28
74
  error boundaries, or non-obvious logic that should remain useful after this task.
29
75
 
30
- | ID | File / Action | Why In Scope | Coder Work | Allowed Freedom | Expected VCM:CODE | Durable Comment Needs | Behavior / Contract Proof Points |
76
+ | ID | File / Action | Current Evidence / Why In Scope | Coder Work | Allowed Freedom | Expected VCM:CODE | Durable Comment Needs | Behavior / Contract Proof Points |
31
77
  | --- | --- | --- | --- | --- | --- | --- | --- |
32
78
  | SCF-001 | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
33
79
 
@@ -73,6 +119,10 @@ TBD
73
119
 
74
120
  TBD
75
121
 
122
+ ## Coverage Mapping
123
+
124
+ TBD
125
+
76
126
  ## Commands Run Or Checked
77
127
 
78
128
  TBD
@@ -145,6 +195,44 @@ TBD
145
195
 
146
196
  ## Objective Failures
147
197
 
198
+ TBD
199
+ `;
200
+ }
201
+ export function renderArchitectDebugTemplate(taskSlug) {
202
+ return `# Architect Debug: ${taskSlug}
203
+
204
+ Status: pending|completed
205
+
206
+ ## PM-Routed Failure
207
+
208
+ TBD
209
+
210
+ ## Confirmed Root Cause
211
+
212
+ TBD
213
+
214
+ ## Implementation
215
+
216
+ TBD
217
+
218
+ ## Changed Files And Public Surface
219
+
220
+ TBD
221
+
222
+ ## Baseline Tests
223
+
224
+ TBD
225
+
226
+ ## Diagnostic And L0-L3 Validation
227
+
228
+ TBD
229
+
230
+ ## Generated Context
231
+
232
+ TBD
233
+
234
+ ## Remaining Failure Evidence
235
+
148
236
  TBD
149
237
  `;
150
238
  }