vibe-coding-master 0.7.46 → 0.7.48

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.
@@ -1,13 +1,13 @@
1
1
  import path from "node:path";
2
- import { DISPATCHABLE_ROLES, VCM_ROLE_NAMES } from "../../shared/constants.js";
2
+ import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
3
3
  import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js";
4
- import { getArtifactDefinition, isArtifactKind } from "../../shared/validation/artifact-registry.js";
4
+ import { getManagedArtifactDefinition, isArtifactKind, isManagedArtifactKind } from "../../shared/validation/artifact-registry.js";
5
5
  import { VcmError } from "../errors.js";
6
6
  import { resolveRepoPath } from "../adapters/filesystem.js";
7
7
  import { renderArchitectureBriefTemplate, renderArchitectureDiagnosisTemplate, renderArchitectureEvidenceTemplate, renderArchitecturePlanTemplate, renderArchitectDebugTemplate, renderCoderCompletionTemplate, renderDocsUpdateReportTemplate, renderDocsSyncReportTemplate, renderFinalAcceptanceTemplate, renderKnownIssuesTemplate, renderPlanningProgressTemplate, renderMessageRouteTemplate, renderTestReportTemplate, renderWorkflowProgressTemplate } from "../templates/handoff.js";
8
8
  import { renderRoleCommandTemplate } from "../templates/role-command.js";
9
- import { validateMemoryProposal } from "./memory-proposal-validation.js";
10
9
  import { isMemoryProposalSubmissionPath } from "./memory-review-paths.js";
10
+ import { validateManagedArtifactContent } from "./managed-artifact-validation.js";
11
11
  const ARTIFACT_PATH_KEYS = [
12
12
  ["architecture-brief", "architectureBriefPath"],
13
13
  ["architecture-evidence", "architectureEvidencePath"],
@@ -191,21 +191,33 @@ export function createArtifactService(fs, deps = {}) {
191
191
  if (!normalized.trim()) {
192
192
  throw artifactRejected(input.kind, ["Artifact content is empty."]);
193
193
  }
194
+ if (!isManagedArtifactKind(input.kind)) {
195
+ throw new VcmError({
196
+ code: "ARTIFACT_KIND_INVALID",
197
+ message: `Unknown managed artifact kind: ${input.kind}`,
198
+ statusCode: 400
199
+ });
200
+ }
201
+ const definition = getManagedArtifactDefinition(input.kind);
202
+ const owners = Array.isArray(definition.owner) ? definition.owner : [definition.owner];
203
+ if (!owners.includes(input.role)) {
204
+ throw new VcmError({
205
+ code: "ARTIFACT_OWNER_MISMATCH",
206
+ message: `${input.kind} is owned by ${owners.join("|")}, not ${input.role}.`,
207
+ statusCode: 403
208
+ });
209
+ }
194
210
  if (isArtifactKind(input.kind)) {
195
- const definition = getArtifactDefinition(input.kind);
196
- const owners = Array.isArray(definition.owner) ? definition.owner : [definition.owner];
197
- if (!owners.includes(input.role)) {
198
- throw new VcmError({
199
- code: "ARTIFACT_OWNER_MISMATCH",
200
- message: `${input.kind} is owned by ${owners.join("|")}, not ${input.role}.`,
201
- statusCode: 403
202
- });
211
+ if (input.artifactPath?.trim()) {
212
+ throw artifactRejected(input.kind, ["--path is not allowed for fixed artifact kinds."]);
203
213
  }
204
- const artifactPath = path.posix.join(input.handoffDir, definition.fileName);
205
- const check = checkMarkdownArtifact(input.kind, artifactPath, normalized, { mode: input.mode });
206
- const errors = artifactCheckErrors(check, input.mode);
207
- if (errors.length > 0) {
208
- throw artifactRejected(input.kind, errors);
214
+ const artifactPath = path.posix.join(input.handoffDir, definition.fileName ?? "");
215
+ const validation = validateManagedArtifactContent(input.kind, normalized, {
216
+ path: artifactPath,
217
+ mode: input.mode
218
+ });
219
+ if (validation.errors.length > 0) {
220
+ throw artifactRejected(input.kind, validation.errors);
209
221
  }
210
222
  if (input.kind === "workflow-progress") {
211
223
  if (input.mode !== "final") {
@@ -238,7 +250,7 @@ export function createArtifactService(fs, deps = {}) {
238
250
  kind: input.kind,
239
251
  mode: input.mode,
240
252
  path: artifactPath,
241
- status: check.status
253
+ status: validation.status
242
254
  };
243
255
  }
244
256
  const dynamic = await validateDynamicArtifact(fs, input, normalized, deps.workflowControlService);
@@ -253,16 +265,6 @@ export function createArtifactService(fs, deps = {}) {
253
265
  }
254
266
  };
255
267
  }
256
- function artifactCheckErrors(check, mode) {
257
- const errors = [
258
- ...check.missingHeadings.map((heading) => `Missing required heading: ${heading}.`),
259
- ...check.invalidFields
260
- ];
261
- if (mode === "final" && check.hasPlaceholder) {
262
- errors.push("Final artifacts must not contain TBD, draft, or not-run placeholders.");
263
- }
264
- return errors;
265
- }
266
268
  function artifactRejected(kind, errors) {
267
269
  return new VcmError({
268
270
  code: "ARTIFACT_VALIDATION_FAILED",
@@ -272,16 +274,13 @@ function artifactRejected(kind, errors) {
272
274
  });
273
275
  }
274
276
  async function validateDynamicArtifact(fs, input, content, workflowControlService) {
275
- if (input.mode !== "final") {
276
- throw artifactRejected(input.kind, ["Dynamic artifacts must be submitted in final mode."]);
277
- }
278
277
  const artifactPath = normalizeRelativeArtifactPath(input.artifactPath);
279
278
  if (input.kind === "route-message") {
280
279
  const route = DEFAULT_MESSAGE_ROUTES.find(([fromRole, toRole]) => artifactPath === path.posix.join(input.handoffDir, "messages", `${fromRole}-${toRole}.md`));
281
280
  if (!route || route[0] !== input.role) {
282
281
  throw artifactRejected(input.kind, ["Route-message path must name the submitting role as the sender."]);
283
282
  }
284
- validateRouteMessage(content);
283
+ assertManagedArtifactContent(input.kind, content, artifactPath, input.mode);
285
284
  if (input.role === "project-manager") {
286
285
  if (!workflowControlService) {
287
286
  throw new VcmError({
@@ -302,40 +301,46 @@ async function validateDynamicArtifact(fs, input, content, workflowControlServic
302
301
  return { kind: input.kind, root: input.repoRoot, path: artifactPath };
303
302
  }
304
303
  if (input.kind === "coder-worker-report") {
305
- if (input.role !== "coder" || !/^\.ai\/vcm\/coder-workers\/reports\/[A-Za-z0-9._-]+\.md$/.test(artifactPath)) {
304
+ if (!/^\.ai\/vcm\/coder-workers\/reports\/[A-Za-z0-9._-]+\.md$/.test(artifactPath)) {
306
305
  throw artifactRejected(input.kind, ["Coder Worker reports must use the assigned .ai/vcm/coder-workers/reports/<worker-id>.md path."]);
307
306
  }
308
- validateCoderWorkerReport(content);
307
+ assertManagedArtifactContent(input.kind, content, artifactPath, input.mode);
309
308
  return { kind: input.kind, root: input.repoRoot, path: artifactPath };
310
309
  }
311
310
  if (input.kind === "gate-review-report") {
312
- if (input.role !== "reviewer" || !/^\.ai\/vcm\/gate-reviews\/requests\/[A-Za-z0-9._-]+\.report\.md$/.test(artifactPath)) {
311
+ if (!/^\.ai\/vcm\/gate-reviews\/requests\/[A-Za-z0-9._-]+\.report\.md$/.test(artifactPath)) {
313
312
  throw artifactRejected(input.kind, ["Reviewer must submit the assigned request report path."]);
314
313
  }
315
- await validateGateReviewReport(fs, input.repoRoot, artifactPath, content);
314
+ const requestId = path.posix.basename(artifactPath, ".report.md");
315
+ const requestPath = path.posix.join(".ai/vcm/gate-reviews/requests", `${requestId}.json`);
316
+ const absoluteRequestPath = resolveRepoPath(input.repoRoot, requestPath);
317
+ if (!(await fs.pathExists(absoluteRequestPath))) {
318
+ throw artifactRejected(input.kind, [`Gate request does not exist: ${requestPath}.`]);
319
+ }
320
+ const request = await fs.readJson(absoluteRequestPath);
321
+ if (request.reportPath !== artifactPath) {
322
+ throw artifactRejected(input.kind, [
323
+ `Report path must match the assigned request path ${request.reportPath ?? "<missing>"}.`
324
+ ]);
325
+ }
326
+ assertManagedArtifactContent(input.kind, content, artifactPath, input.mode, {
327
+ expectedGate: request.gate,
328
+ expectedRequestId: request.requestId ?? requestId
329
+ });
316
330
  return { kind: input.kind, root: input.repoRoot, path: artifactPath };
317
331
  }
318
332
  if (input.kind === "memory-proposal") {
319
- if (!VCM_ROLE_NAMES.includes(input.role)) {
320
- throw artifactRejected(input.kind, ["Only a VCM workflow role may submit a Memory Proposal."]);
321
- }
322
333
  if (!isMemoryProposalSubmissionPath(artifactPath, input.role)) {
323
334
  throw artifactRejected(input.kind, ["Memory proposal path is not assigned to the submitting role."]);
324
335
  }
325
- const memoryError = validateMemoryProposal(content);
326
- if (memoryError) {
327
- throw artifactRejected(input.kind, [`Memory proposal ${memoryError}.`]);
328
- }
336
+ assertManagedArtifactContent(input.kind, content, artifactPath, input.mode);
329
337
  return { kind: input.kind, root: input.repoRoot, path: artifactPath };
330
338
  }
331
339
  if (input.kind === "harness-feedback") {
332
- if (!VCM_ROLE_NAMES.includes(input.role)) {
333
- throw artifactRejected(input.kind, ["Only a VCM workflow role may submit Harness Feedback."]);
334
- }
335
340
  if (!/^\.ai\/vcm\/harness-feedback\/pending\/[A-Za-z0-9._-]+\.md$/.test(artifactPath)) {
336
341
  throw artifactRejected(input.kind, ["Harness Feedback path must be under .ai/vcm/harness-feedback/pending/. "]);
337
342
  }
338
- validateHarnessFeedback(content);
343
+ assertManagedArtifactContent(input.kind, content, artifactPath, input.mode);
339
344
  return { kind: input.kind, root: input.baseRepoRoot, path: artifactPath };
340
345
  }
341
346
  throw new VcmError({
@@ -344,89 +349,16 @@ async function validateDynamicArtifact(fs, input, content, workflowControlServic
344
349
  statusCode: 400
345
350
  });
346
351
  }
347
- function validateCoderWorkerReport(content) {
348
- const errors = [];
349
- if (!/^# Coder Worker Report:\s*\S.+$/m.test(content)) {
350
- errors.push("Coder Worker report requires '# Coder Worker Report: <worker-id>'.");
351
- }
352
- if (!/^Worker State:\s*completed\s*$/m.test(content)) {
353
- errors.push("Worker State must be completed.");
354
- }
355
- if (!/^Implementation Result:\s*(success|has_failed_items)\s*$/m.test(content)) {
356
- errors.push("Implementation Result must be success|has_failed_items.");
357
- }
358
- for (const heading of [
359
- "Assigned Scope", "Item Dispositions", "Files Changed", "Tests Added Or Updated",
360
- "L0/L1 Checks", "Commit", "Skipped Assigned Checks", "Objective Failures"
361
- ]) {
362
- if (!new RegExp(`^## ${escapeRegExp(heading)}\\s*$`, "m").test(content)) {
363
- errors.push(`Missing required section: ${heading}.`);
364
- }
365
- }
366
- if (errors.length > 0)
367
- throw artifactRejected("coder-worker-report", errors);
368
- }
369
- function validateRouteMessage(content) {
370
- const type = /^type:\s*(\S+)\s*$/m.exec(content)?.[1];
371
- const allowed = new Set(["task", "question", "revise", "cancel", "result", "blocked", "finding"]);
372
- if (!type || !allowed.has(type)) {
373
- throw artifactRejected("route-message", ["Frontmatter type must be one of task|question|revise|cancel|result|blocked|finding."]);
374
- }
375
- if (!content.startsWith("---\n") || !/\n---\n/.test(content) || !content.split(/\n---\n/, 2)[1]?.trim()) {
376
- throw artifactRejected("route-message", ["Route message requires frontmatter and a non-empty body."]);
377
- }
378
- }
379
- async function validateGateReviewReport(fs, repoRoot, artifactPath, content) {
380
- const requestId = path.posix.basename(artifactPath, ".report.md");
381
- const requestPath = path.posix.join(".ai/vcm/gate-reviews/requests", `${requestId}.json`);
382
- const absoluteRequestPath = resolveRepoPath(repoRoot, requestPath);
383
- if (!(await fs.pathExists(absoluteRequestPath))) {
384
- throw artifactRejected("gate-review-report", [`Gate request does not exist: ${requestPath}.`]);
385
- }
386
- const request = await fs.readJson(absoluteRequestPath);
387
- const fields = Object.fromEntries(["Gate", "Request", "Decision", "Summary"].map((field) => [
388
- field,
389
- new RegExp(`^${field}:\\s*(.+?)\\s*$`, "mi").exec(content)?.[1]?.trim()
390
- ]));
391
- const errors = [];
392
- if (fields.Gate !== request.gate)
393
- errors.push(`Gate must be ${request.gate ?? "the requested gate"}.`);
394
- if (fields.Request !== request.requestId)
395
- errors.push(`Request must be ${request.requestId ?? requestId}.`);
396
- if (fields.Decision !== "approve" && fields.Decision !== "request_changes")
397
- errors.push("Decision must be approve|request_changes.");
398
- if (!fields.Summary)
399
- errors.push("Summary is required.");
400
- const requiredSection = request.gate === "architecture-plan"
401
- ? "Architecture Analysis"
402
- : request.gate === "validation-adequacy"
403
- ? "Validation Analysis"
404
- : "Code Diff Analysis";
405
- if (!new RegExp(`^## ${escapeRegExp(requiredSection)}\\s*$`, "m").test(content)) {
406
- errors.push(`Missing required section: ${requiredSection}.`);
407
- }
408
- if (!/^## Findings\s*$/m.test(content))
409
- errors.push("Missing required section: Findings.");
410
- if (errors.length > 0)
411
- throw artifactRejected("gate-review-report", errors);
412
- }
413
- function validateHarnessFeedback(content) {
414
- const errors = [];
415
- if (!/^#\s+\S.+$/m.test(content))
416
- errors.push("Harness Feedback requires a title.");
417
- for (const field of [
418
- "Reporter role", "Task slug", "Summary", "Observed problem", "Expected behavior",
419
- "Evidence", "Suspected harness area", "Impact", "Urgency"
420
- ]) {
421
- if (!new RegExp(`^- ${escapeRegExp(field)}:\\s*\\S`, "m").test(content)) {
422
- errors.push(`Harness Feedback is missing ${field}.`);
423
- }
424
- }
425
- if (!/^- Urgency:\s*(low|medium|high)\s*$/m.test(content)) {
426
- errors.push("Urgency must be low|medium|high.");
352
+ function assertManagedArtifactContent(kind, content, artifactPath, mode, expected = {}) {
353
+ const validation = validateManagedArtifactContent(kind, content, {
354
+ path: artifactPath,
355
+ mode,
356
+ expectedGate: expected.expectedGate,
357
+ expectedRequestId: expected.expectedRequestId
358
+ });
359
+ if (validation.errors.length > 0) {
360
+ throw artifactRejected(kind, validation.errors);
427
361
  }
428
- if (errors.length > 0)
429
- throw artifactRejected("harness-feedback", errors);
430
362
  }
431
363
  function normalizeRelativeArtifactPath(value) {
432
364
  const normalized = value?.replaceAll("\\", "/").replace(/^\.\//, "").trim();
@@ -445,9 +377,6 @@ async function writeAtomic(fs, absolutePath, content) {
445
377
  }
446
378
  await fs.writeText(absolutePath, content);
447
379
  }
448
- function escapeRegExp(value) {
449
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
450
- }
451
380
  function getLegacyRoleCommandPath(roleCommandsDir, role) {
452
381
  return path.posix.join(roleCommandsDir, `${role}-command.md`);
453
382
  }
@@ -235,6 +235,9 @@ export function createClaudeHookService(deps) {
235
235
  if (!session) {
236
236
  return completedHookResult(input, eventName);
237
237
  }
238
+ if (input.role === "architect") {
239
+ await deps.architectRestartService?.recordReplacementPromptSubmitted(context.project.repoRoot, context.taskSlug, session.id);
240
+ }
238
241
  const boundToTask = await isHookSessionBoundToTask(context, input.role);
239
242
  if (boundToTask) {
240
243
  deps.jobGuard?.notePromptSubmitted({
@@ -6,6 +6,7 @@ import { VcmError } from "../errors.js";
6
6
  import { resolveRepoPath } from "../adapters/filesystem.js";
7
7
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
8
8
  import { getTaskRuntimeRepoRoot } from "./task-service.js";
9
+ import { parseGateReviewReportArtifact } from "./managed-artifact-validation.js";
9
10
  const REVIEWER_AGENT_PATH = ".claude/agents/reviewer.md";
10
11
  const GATE_REVIEW_DIR = ".ai/vcm/gate-reviews";
11
12
  const REQUESTS_DIR = ".ai/vcm/gate-reviews/requests";
@@ -15,49 +16,6 @@ const REVIEWER_ROLE = "reviewer";
15
16
  const DEFAULT_REPORT_POLL_INTERVAL_MS = 1000;
16
17
  const activeRuns = new Map();
17
18
  const gateStateLocks = new Map();
18
- const ARCHITECTURE_ANALYSIS_FIELDS = [
19
- "Evidence Read",
20
- "Architecture Brief Fit",
21
- "End-To-End Flow",
22
- "Scope Fit",
23
- "Code Reality",
24
- "Ownership",
25
- "Data Flow",
26
- "Lifecycle",
27
- "Invariants",
28
- "Boundaries And Public Surface",
29
- "Failure Model",
30
- "Coder Readiness"
31
- ];
32
- const VALIDATION_ANALYSIS_FIELDS = [
33
- "Evidence Read",
34
- "Changed Behavior And Risk",
35
- "Coverage Mapping",
36
- "Baseline Coverage",
37
- "L2 Integration Coverage",
38
- "L3 Trigger Assessment",
39
- "L3 End-To-End Coverage",
40
- "Boundary And Failure Coverage",
41
- "Public Contract Coverage",
42
- "Test Integrity",
43
- "Test Infrastructure",
44
- "Skips And Gaps",
45
- "User Approval And Gap Disposition",
46
- "Validation Readiness"
47
- ];
48
- const CODE_DIFF_ANALYSIS_FIELDS = [
49
- "Commit Range And Sources",
50
- "Evidence Read",
51
- "Changed Files And Symbols",
52
- "Changed Behavior",
53
- "Source Evidence Fit",
54
- "Callers And Public Surface",
55
- "State Lifecycle And Failure Paths",
56
- "Coding Standards",
57
- "Baseline Test Integrity",
58
- "Generated Context And Durable Docs",
59
- "Code Readiness"
60
- ];
61
19
  const SOURCE_ARTIFACTS = {
62
20
  "architecture-plan": [
63
21
  ".ai/vcm/handoffs/architecture-brief.md",
@@ -1531,113 +1489,61 @@ async function parseGateReport(fs, taskRepoRoot, gate, requestId, timestamp, rep
1531
1489
  });
1532
1490
  }
1533
1491
  const content = await fs.readText(absolutePath);
1534
- const parsedGate = matchField(content, "Gate");
1535
- if (parsedGate && parsedGate !== gate) {
1536
- throw new VcmError({
1537
- code: "GATE_REVIEW_REPORT_GATE_MISMATCH",
1538
- message: `Gate review report gate is ${parsedGate}, expected ${gate}.`,
1539
- statusCode: 500
1540
- });
1541
- }
1542
- const parsedRequest = matchField(content, "Request");
1543
- if (requestId && parsedRequest !== requestId) {
1544
- throw new VcmError({
1545
- code: "GATE_REVIEW_REPORT_STALE",
1546
- message: `Gate review report request is ${parsedRequest ?? "missing"}, expected ${requestId}.`,
1547
- statusCode: 500
1548
- });
1549
- }
1550
- const decision = normalizeDecision(matchField(content, "Decision"));
1551
- if (!decision) {
1492
+ const validation = parseGateReviewReportArtifact(content, {
1493
+ expectedGate: gate,
1494
+ expectedRequestId: requestId
1495
+ });
1496
+ if (!validation.parsed || validation.errors.length > 0) {
1552
1497
  throw new VcmError({
1553
- code: "GATE_REVIEW_DECISION_MISSING",
1554
- message: `Gate review report must contain Decision: approve or Decision: request_changes.`,
1498
+ code: gateReportErrorCode(validation.errors),
1499
+ message: `Gate review report is invalid: ${validation.errors.join(" ")}`,
1555
1500
  statusCode: 500
1556
1501
  });
1557
1502
  }
1558
- const findings = extractFindings(content);
1559
- if (gate === "architecture-plan") {
1560
- validateArchitectureAnalysis(content);
1561
- }
1562
1503
  if (gate === "validation-adequacy") {
1563
- validateValidationAnalysis(content);
1564
- if (decision === "approve") {
1504
+ if (validation.parsed.decision === "approve") {
1565
1505
  await validateValidationApprovalInput(fs, taskRepoRoot);
1566
1506
  }
1567
1507
  }
1568
- if (gate === "code-diff") {
1569
- validateCodeDiffAnalysis(content);
1570
- }
1571
- if (decision === "request_changes") {
1572
- validateRequestChangeFindings(findings);
1573
- if (gate === "code-diff") {
1574
- validateCodeDiffFindings(findings);
1575
- }
1576
- }
1577
1508
  return {
1578
1509
  gate,
1579
- requestId: parsedRequest,
1580
- decision,
1581
- summary: extractSummary(content),
1582
- findings,
1510
+ requestId: validation.parsed.requestId,
1511
+ decision: validation.parsed.decision,
1512
+ summary: validation.parsed.summary,
1513
+ findings: validation.parsed.findings,
1583
1514
  reportPath,
1584
1515
  content,
1585
1516
  parsedAt: timestamp
1586
1517
  };
1587
1518
  }
1588
- function validateArchitectureAnalysis(content) {
1589
- const section = extractMarkdownSection(content, "Architecture Analysis");
1590
- if (!section) {
1591
- throw new VcmError({
1592
- code: "GATE_REVIEW_ARCHITECTURE_ANALYSIS_MISSING",
1593
- message: "Architecture-plan review must contain a non-empty Architecture Analysis section.",
1594
- statusCode: 500
1595
- });
1596
- }
1597
- const missingFields = ARCHITECTURE_ANALYSIS_FIELDS.filter((field) => !matchField(section, field));
1598
- if (missingFields.length > 0) {
1599
- throw new VcmError({
1600
- code: "GATE_REVIEW_ARCHITECTURE_ANALYSIS_INCOMPLETE",
1601
- message: `Architecture Analysis is missing required evidence: ${missingFields.join(", ")}.`,
1602
- statusCode: 500
1603
- });
1604
- }
1605
- }
1606
- function validateValidationAnalysis(content) {
1607
- const section = extractMarkdownSection(content, "Validation Analysis");
1608
- if (!section) {
1609
- throw new VcmError({
1610
- code: "GATE_REVIEW_VALIDATION_ANALYSIS_MISSING",
1611
- message: "Validation-adequacy review must contain a non-empty Validation Analysis section.",
1612
- statusCode: 500
1613
- });
1614
- }
1615
- const missingFields = VALIDATION_ANALYSIS_FIELDS.filter((field) => !matchField(section, field));
1616
- if (missingFields.length > 0) {
1617
- throw new VcmError({
1618
- code: "GATE_REVIEW_VALIDATION_ANALYSIS_INCOMPLETE",
1619
- message: `Validation Analysis is missing required evidence: ${missingFields.join(", ")}.`,
1620
- statusCode: 500
1621
- });
1622
- }
1623
- }
1624
- function validateCodeDiffAnalysis(content) {
1625
- const section = extractMarkdownSection(content, "Code Diff Analysis");
1626
- if (!section) {
1627
- throw new VcmError({
1628
- code: "GATE_REVIEW_CODE_DIFF_ANALYSIS_MISSING",
1629
- message: "Code-diff review must contain a non-empty Code Diff Analysis section.",
1630
- statusCode: 500
1631
- });
1632
- }
1633
- const missingFields = CODE_DIFF_ANALYSIS_FIELDS.filter((field) => !matchField(section, field));
1634
- if (missingFields.length > 0) {
1635
- throw new VcmError({
1636
- code: "GATE_REVIEW_CODE_DIFF_ANALYSIS_INCOMPLETE",
1637
- message: `Code Diff Analysis is missing required evidence: ${missingFields.join(", ")}.`,
1638
- statusCode: 500
1639
- });
1640
- }
1519
+ function gateReportErrorCode(errors) {
1520
+ const message = errors.join(" ");
1521
+ if (/Architecture Analysis must appear exactly once/.test(message))
1522
+ return "GATE_REVIEW_ARCHITECTURE_ANALYSIS_MISSING";
1523
+ if (/Architecture Analysis field/.test(message))
1524
+ return "GATE_REVIEW_ARCHITECTURE_ANALYSIS_INCOMPLETE";
1525
+ if (/Validation Analysis must appear exactly once/.test(message))
1526
+ return "GATE_REVIEW_VALIDATION_ANALYSIS_MISSING";
1527
+ if (/Validation Analysis field/.test(message))
1528
+ return "GATE_REVIEW_VALIDATION_ANALYSIS_INCOMPLETE";
1529
+ if (/Code Diff Analysis must appear exactly once/.test(message))
1530
+ return "GATE_REVIEW_CODE_DIFF_ANALYSIS_MISSING";
1531
+ if (/Code Diff Analysis field/.test(message))
1532
+ return "GATE_REVIEW_CODE_DIFF_ANALYSIS_INCOMPLETE";
1533
+ if (/Finding Scope|field File|field Line Or Symbol/.test(message)) {
1534
+ return "GATE_REVIEW_CODE_DIFF_FINDING_LOCATION_MISSING";
1535
+ }
1536
+ if (/field Evidence|field Expected|field Gap|field Risk/.test(message))
1537
+ return "GATE_REVIEW_FINDING_INCOMPLETE";
1538
+ if (/requires at least one structured finding/.test(message))
1539
+ return "GATE_REVIEW_FINDINGS_MISSING";
1540
+ if (/Decision must/.test(message))
1541
+ return "GATE_REVIEW_DECISION_MISSING";
1542
+ if (/Gate must/.test(message))
1543
+ return "GATE_REVIEW_REPORT_GATE_MISMATCH";
1544
+ if (/Request must/.test(message))
1545
+ return "GATE_REVIEW_REPORT_STALE";
1546
+ return "GATE_REVIEW_REPORT_INVALID";
1641
1547
  }
1642
1548
  async function validateValidationApprovalInput(fs, taskRepoRoot) {
1643
1549
  const relativePath = CORE_INPUT_ARTIFACTS["validation-adequacy"];
@@ -1679,38 +1585,6 @@ function formatArtifactCheckFailure(check) {
1679
1585
  function renderFoundValue(value) {
1680
1586
  return value && value.trim().length > 0 ? JSON.stringify(value.trim()) : "<missing>";
1681
1587
  }
1682
- function validateRequestChangeFindings(findings) {
1683
- if (findings.length === 0) {
1684
- throw new VcmError({
1685
- code: "GATE_REVIEW_FINDINGS_MISSING",
1686
- message: "A request_changes decision must contain at least one structured finding.",
1687
- statusCode: 500
1688
- });
1689
- }
1690
- const incomplete = findings.find((finding) => (!finding.evidence.trim()
1691
- || !finding.expected.trim()
1692
- || !finding.gap.trim()
1693
- || !finding.risk.trim()));
1694
- if (incomplete) {
1695
- throw new VcmError({
1696
- code: "GATE_REVIEW_FINDING_INCOMPLETE",
1697
- message: `Finding ${incomplete.title} must contain Evidence, Expected, Gap, and Risk.`,
1698
- statusCode: 500
1699
- });
1700
- }
1701
- }
1702
- function validateCodeDiffFindings(findings) {
1703
- const incomplete = findings.find((finding) => (!finding.file?.trim()
1704
- || !finding.location?.trim()
1705
- || !finding.scope));
1706
- if (incomplete) {
1707
- throw new VcmError({
1708
- code: "GATE_REVIEW_CODE_DIFF_FINDING_LOCATION_MISSING",
1709
- message: `Code-diff finding ${incomplete.title} must contain File, Line Or Symbol, and Finding Scope.`,
1710
- statusCode: 500
1711
- });
1712
- }
1713
- }
1714
1588
  function extractMarkdownSection(content, heading) {
1715
1589
  const match = new RegExp(`^##\\s+${escapeRegex(heading)}\\s*$`, "im").exec(content);
1716
1590
  if (!match || match.index === undefined) {
@@ -1780,39 +1654,6 @@ function matchField(content, field) {
1780
1654
  const match = content.match(new RegExp(`^\\s*(?:[-*]\\s*)?${escapeRegex(field)}\\s*:\\s*(.+?)\\s*$`, "mi"));
1781
1655
  return match?.[1]?.trim();
1782
1656
  }
1783
- function extractSummary(content) {
1784
- const field = matchField(content, "Summary");
1785
- if (field) {
1786
- return field;
1787
- }
1788
- const section = content.match(/^##\s+Summary\s*\n([\s\S]*?)(?=\n##\s+|\s*$)/im);
1789
- return section?.[1]?.trim() || undefined;
1790
- }
1791
- function extractFindings(content) {
1792
- const findings = [];
1793
- const blocks = content.split(/\n(?=#{2,4}\s+|-+\s*severity\s*:|severity\s*:)/i);
1794
- for (const block of blocks) {
1795
- const heading = block.match(/^#{2,4}\s+(critical|high|medium|low)\s*:\s*(.+)$/im);
1796
- const severity = normalizeSeverity(matchField(block, "severity") ?? heading?.[1]);
1797
- const title = matchField(block, "title") ?? heading?.[2]?.trim() ?? block.match(/^#{2,4}\s+(.+)$/m)?.[1]?.trim();
1798
- if (!severity || !title) {
1799
- continue;
1800
- }
1801
- findings.push({
1802
- severity,
1803
- title,
1804
- file: matchField(block, "file"),
1805
- line: parsePositiveInteger(matchField(block, "line")),
1806
- location: matchField(block, "line or symbol"),
1807
- scope: normalizeCodeDiffFindingScope(matchField(block, "finding scope")),
1808
- evidence: matchField(block, "evidence") ?? "",
1809
- expected: matchField(block, "expected") ?? "",
1810
- gap: matchField(block, "gap") ?? "",
1811
- risk: matchField(block, "risk") ?? ""
1812
- });
1813
- }
1814
- return findings;
1815
- }
1816
1657
  function getSourceArtifacts(gate, codeDiffSources) {
1817
1658
  if (gate !== "code-diff") {
1818
1659
  return SOURCE_ARTIFACTS[gate];
@@ -1928,13 +1769,6 @@ function isFinding(value) {
1928
1769
  function isString(value) {
1929
1770
  return typeof value === "string";
1930
1771
  }
1931
- function parsePositiveInteger(value) {
1932
- if (!value) {
1933
- return undefined;
1934
- }
1935
- const parsed = Number.parseInt(value, 10);
1936
- return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
1937
- }
1938
1772
  function assertExceptionReason(reason) {
1939
1773
  if (!reason?.trim()) {
1940
1774
  throw new VcmError({
@@ -1998,16 +1832,7 @@ function errorMessage(error) {
1998
1832
  return "Unknown Gate review error.";
1999
1833
  }
2000
1834
  function isPendingReportError(error) {
2001
- return error instanceof VcmError && [
2002
- "GATE_REVIEW_DECISION_MISSING",
2003
- "GATE_REVIEW_ARCHITECTURE_ANALYSIS_MISSING",
2004
- "GATE_REVIEW_ARCHITECTURE_ANALYSIS_INCOMPLETE",
2005
- "GATE_REVIEW_FINDINGS_MISSING",
2006
- "GATE_REVIEW_FINDING_INCOMPLETE",
2007
- "GATE_REVIEW_REPORT_GATE_MISMATCH",
2008
- "GATE_REVIEW_REPORT_MISSING",
2009
- "GATE_REVIEW_REPORT_STALE"
2010
- ].includes(error.code);
1835
+ return error instanceof VcmError && error.code === "GATE_REVIEW_REPORT_MISSING";
2011
1836
  }
2012
1837
  function delay(ms) {
2013
1838
  if (ms <= 0) {