vibe-coding-master 0.7.4 → 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.
- package/dist/backend/services/artifact-service.js +2 -1
- package/dist/backend/services/gate-review-service.js +291 -31
- package/dist/backend/templates/handoff.js +89 -1
- package/dist/backend/templates/harness/architect-agent.js +20 -5
- package/dist/backend/templates/harness/coder-agent.js +5 -7
- package/dist/backend/templates/harness/coder-worker-agent.js +3 -3
- package/dist/backend/templates/harness/gate-review.js +276 -62
- package/dist/backend/templates/harness/project-manager-agent.js +7 -3
- package/dist/backend/templates/harness/tester-agent.js +4 -3
- package/dist/shared/validation/artifact-check.js +21 -1
- package/package.json +1 -1
|
@@ -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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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("
|
|
797
|
-
digest.update(
|
|
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
|
-
|
|
811
|
-
digest.update(
|
|
812
|
-
digest.update(await commandStdout(deps.runner, taskRepoRoot, ["
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
1266
|
+
function getSourceArtifacts(gate, codeDiffSources) {
|
|
1030
1267
|
if (gate !== "code-diff") {
|
|
1031
1268
|
return SOURCE_ARTIFACTS[gate];
|
|
1032
1269
|
}
|
|
1033
|
-
return
|
|
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
|
}
|