vibe-coding-master 0.7.29 → 0.7.31
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/api/task-routes.js +4 -2
- package/dist/backend/server.js +2 -1
- package/dist/backend/services/architect-restart-service.js +71 -32
- package/dist/backend/services/gate-review-service.js +86 -8
- package/dist/backend/templates/handoff.js +21 -1
- package/dist/backend/templates/harness/architect-agent.js +3 -3
- package/dist/backend/templates/harness/claude-root.js +2 -2
- package/dist/backend/templates/harness/gate-review.js +98 -9
- package/dist/backend/templates/harness/project-manager-agent.js +30 -20
- package/dist/backend/templates/harness/restart-architect-skill.js +9 -5
- package/dist/backend/templates/harness/tester-agent.js +35 -4
- package/dist/shared/types/architect-restart.js +1 -0
- package/dist/shared/types/gate-review.js +4 -0
- package/dist/shared/validation/artifact-check.js +55 -1
- package/dist/shared/validation/artifact-contract.js +6 -0
- package/dist-frontend/assets/index-CguKU8Q5.css +32 -0
- package/dist-frontend/assets/{index-Bq1kZK1u.js → index-Cmr9W7-n.js} +22 -22
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/dist-frontend/assets/index-Ci7z8tW3.css +0 -32
|
@@ -70,7 +70,8 @@ export function registerTaskRoutes(app, deps) {
|
|
|
70
70
|
messages,
|
|
71
71
|
orchestration,
|
|
72
72
|
roundState,
|
|
73
|
-
workflowState
|
|
73
|
+
workflowState,
|
|
74
|
+
architectRestart: deps.architectRestartService.getState(project.repoRoot, taskSlug)
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
77
|
catch (error) {
|
|
@@ -84,7 +85,8 @@ export function registerTaskRoutes(app, deps) {
|
|
|
84
85
|
updatedAt: new Date().toISOString()
|
|
85
86
|
},
|
|
86
87
|
roundState: degradedRoundState(taskSlug),
|
|
87
|
-
workflowState: degradedWorkflowState(taskSlug)
|
|
88
|
+
workflowState: degradedWorkflowState(taskSlug),
|
|
89
|
+
architectRestart: deps.architectRestartService.getState(repoRoot, taskSlug)
|
|
88
90
|
};
|
|
89
91
|
}
|
|
90
92
|
throw error;
|
package/dist/backend/server.js
CHANGED
|
@@ -130,7 +130,8 @@ export async function createServer(deps, options = {}) {
|
|
|
130
130
|
messageService: deps.messageService,
|
|
131
131
|
taskLaunchService: deps.taskLaunchService,
|
|
132
132
|
roundService: deps.roundService,
|
|
133
|
-
taskWorkflowService: deps.taskWorkflowService
|
|
133
|
+
taskWorkflowService: deps.taskWorkflowService,
|
|
134
|
+
architectRestartService: deps.architectRestartService
|
|
134
135
|
});
|
|
135
136
|
registerSessionRoutes(app, {
|
|
136
137
|
projectService: deps.projectService,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
3
|
-
import { VcmError } from "../errors.js";
|
|
3
|
+
import { toVcmError, VcmError } from "../errors.js";
|
|
4
4
|
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
5
5
|
import { ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH } from "./memory-review-paths.js";
|
|
6
6
|
import { validateMemoryProposal } from "./memory-proposal-validation.js";
|
|
@@ -23,40 +23,41 @@ export function createArchitectRestartService(deps) {
|
|
|
23
23
|
async schedule(repoRoot, taskSlug) {
|
|
24
24
|
const session = await requireRunningArchitect(repoRoot, taskSlug);
|
|
25
25
|
await requireCompletePlan(repoRoot, taskSlug);
|
|
26
|
-
const memoryCandidatePath = (await deps.appSettings.getPreferences()).autoMemoryEnabled
|
|
27
|
-
? ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH
|
|
28
|
-
: undefined;
|
|
29
|
-
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
30
|
-
const candidatePath = resolveRepoPath(getTaskRuntimeRepoRoot(task), ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH);
|
|
31
|
-
if (deps.fs.removePath) {
|
|
32
|
-
await deps.fs.removePath(candidatePath, { force: true });
|
|
33
|
-
}
|
|
34
|
-
else if (await deps.fs.pathExists(candidatePath)) {
|
|
35
|
-
await deps.fs.writeText(candidatePath, "");
|
|
36
|
-
}
|
|
37
26
|
const key = taskKey(repoRoot, taskSlug);
|
|
38
27
|
const existing = pendingByTask.get(key);
|
|
39
28
|
if (existing?.sessionId === session.id) {
|
|
40
|
-
existing.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
if (existing.status === "blocked") {
|
|
30
|
+
existing.status = "pending";
|
|
31
|
+
existing.blocker = undefined;
|
|
32
|
+
await tryRestart(existing);
|
|
33
|
+
return {
|
|
34
|
+
taskSlug,
|
|
35
|
+
sessionId: session.id,
|
|
36
|
+
status: "scheduled",
|
|
37
|
+
...(existing.memoryCandidatePath
|
|
38
|
+
? { memoryCandidatePath: existing.memoryCandidatePath }
|
|
39
|
+
: {})
|
|
40
|
+
};
|
|
41
|
+
}
|
|
46
42
|
return {
|
|
47
43
|
taskSlug,
|
|
48
44
|
sessionId: session.id,
|
|
49
|
-
status: "
|
|
50
|
-
...(memoryCandidatePath
|
|
45
|
+
status: "already_scheduled",
|
|
46
|
+
...(existing.memoryCandidatePath
|
|
47
|
+
? { memoryCandidatePath: existing.memoryCandidatePath }
|
|
48
|
+
: {})
|
|
51
49
|
};
|
|
52
50
|
}
|
|
51
|
+
const memoryCandidatePath = (await deps.appSettings.getPreferences()).autoMemoryEnabled
|
|
52
|
+
? ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH
|
|
53
|
+
: undefined;
|
|
53
54
|
pendingByTask.set(key, {
|
|
54
55
|
repoRoot,
|
|
55
56
|
taskSlug,
|
|
56
57
|
sessionId: session.id,
|
|
57
58
|
stopped: false,
|
|
58
59
|
gateAccepted: false,
|
|
59
|
-
|
|
60
|
+
status: "pending",
|
|
60
61
|
memoryCandidatePath
|
|
61
62
|
});
|
|
62
63
|
return {
|
|
@@ -66,9 +67,24 @@ export function createArchitectRestartService(deps) {
|
|
|
66
67
|
...(memoryCandidatePath ? { memoryCandidatePath } : {})
|
|
67
68
|
};
|
|
68
69
|
},
|
|
70
|
+
getState(repoRoot, taskSlug) {
|
|
71
|
+
const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
|
|
72
|
+
if (!pending) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
taskSlug: pending.taskSlug,
|
|
77
|
+
sessionId: pending.sessionId,
|
|
78
|
+
status: pending.status,
|
|
79
|
+
...(pending.memoryCandidatePath
|
|
80
|
+
? { memoryCandidatePath: pending.memoryCandidatePath }
|
|
81
|
+
: {}),
|
|
82
|
+
...(pending.blocker ? { blocker: { ...pending.blocker } } : {})
|
|
83
|
+
};
|
|
84
|
+
},
|
|
69
85
|
async recordArchitectStop(repoRoot, taskSlug, sessionId) {
|
|
70
86
|
const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
|
|
71
|
-
if (!pending || pending.sessionId !== sessionId) {
|
|
87
|
+
if (!pending || pending.sessionId !== sessionId || pending.status === "blocked") {
|
|
72
88
|
return;
|
|
73
89
|
}
|
|
74
90
|
pending.stopped = true;
|
|
@@ -79,7 +95,7 @@ export function createArchitectRestartService(deps) {
|
|
|
79
95
|
return;
|
|
80
96
|
}
|
|
81
97
|
const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
|
|
82
|
-
if (!pending) {
|
|
98
|
+
if (!pending || pending.status === "blocked") {
|
|
83
99
|
return;
|
|
84
100
|
}
|
|
85
101
|
pending.deliveredMessageId = message.id;
|
|
@@ -90,7 +106,7 @@ export function createArchitectRestartService(deps) {
|
|
|
90
106
|
return;
|
|
91
107
|
}
|
|
92
108
|
const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
|
|
93
|
-
if (!pending) {
|
|
109
|
+
if (!pending || pending.status === "blocked") {
|
|
94
110
|
return;
|
|
95
111
|
}
|
|
96
112
|
pending.acceptedMessageId = message.id;
|
|
@@ -98,7 +114,7 @@ export function createArchitectRestartService(deps) {
|
|
|
98
114
|
},
|
|
99
115
|
async recordArchitectureGateDisposition(repoRoot, taskSlug, accepted) {
|
|
100
116
|
const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
|
|
101
|
-
if (!pending) {
|
|
117
|
+
if (!pending || pending.status === "blocked") {
|
|
102
118
|
return;
|
|
103
119
|
}
|
|
104
120
|
pending.gateAccepted = accepted;
|
|
@@ -131,7 +147,7 @@ export function createArchitectRestartService(deps) {
|
|
|
131
147
|
}
|
|
132
148
|
}
|
|
133
149
|
async function tryRestart(pending) {
|
|
134
|
-
if (pending.
|
|
150
|
+
if (pending.status !== "pending"
|
|
135
151
|
|| !pending.stopped
|
|
136
152
|
|| !pending.deliveredMessageId
|
|
137
153
|
|| pending.deliveredMessageId !== pending.acceptedMessageId
|
|
@@ -140,12 +156,26 @@ export function createArchitectRestartService(deps) {
|
|
|
140
156
|
}
|
|
141
157
|
const session = await deps.sessionService.getRoleSession(pending.repoRoot, pending.taskSlug, ARCHITECT_ROLE);
|
|
142
158
|
if (!session
|
|
143
|
-
|| session.id !== pending.sessionId
|
|
144
|
-
|
|
145
|
-
|
|
159
|
+
|| session.id !== pending.sessionId) {
|
|
160
|
+
blockPending(pending, new VcmError({
|
|
161
|
+
code: "ARCHITECT_RESTART_SESSION_UNAVAILABLE",
|
|
162
|
+
message: "Architect restart is blocked because the scheduled Architect session no longer exists.",
|
|
163
|
+
statusCode: 409
|
|
164
|
+
}));
|
|
146
165
|
return;
|
|
147
166
|
}
|
|
148
|
-
|
|
167
|
+
if (session.status !== "running") {
|
|
168
|
+
blockPending(pending, new VcmError({
|
|
169
|
+
code: "ARCHITECT_RESTART_SESSION_NOT_RUNNING",
|
|
170
|
+
message: "Architect restart is blocked because the scheduled Architect session is not running.",
|
|
171
|
+
statusCode: 409
|
|
172
|
+
}));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (session.activityStatus !== "idle") {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
pending.status = "executing";
|
|
149
179
|
try {
|
|
150
180
|
await requireCompletePlan(pending.repoRoot, pending.taskSlug);
|
|
151
181
|
await requirePlanningMemoryCandidate(pending);
|
|
@@ -157,10 +187,19 @@ export function createArchitectRestartService(deps) {
|
|
|
157
187
|
});
|
|
158
188
|
pendingByTask.delete(taskKey(pending.repoRoot, pending.taskSlug));
|
|
159
189
|
}
|
|
160
|
-
catch {
|
|
161
|
-
pending
|
|
190
|
+
catch (error) {
|
|
191
|
+
blockPending(pending, error);
|
|
162
192
|
}
|
|
163
193
|
}
|
|
194
|
+
function blockPending(pending, error) {
|
|
195
|
+
const normalized = toVcmError(error);
|
|
196
|
+
pending.status = "blocked";
|
|
197
|
+
pending.blocker = {
|
|
198
|
+
code: normalized.code,
|
|
199
|
+
message: normalized.message,
|
|
200
|
+
blockedAt: new Date().toISOString()
|
|
201
|
+
};
|
|
202
|
+
}
|
|
164
203
|
async function requirePlanningMemoryCandidate(pending) {
|
|
165
204
|
if (!pending.memoryCandidatePath) {
|
|
166
205
|
return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { CODE_DIFF_SOURCES, GATE_REVIEW_GATES } from "../../shared/types/gate-review.js";
|
|
3
|
+
import { CODE_DIFF_FINDING_SCOPES, CODE_DIFF_SOURCES, GATE_REVIEW_GATES } from "../../shared/types/gate-review.js";
|
|
4
4
|
import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js";
|
|
5
5
|
import { VcmError } from "../errors.js";
|
|
6
6
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
@@ -38,6 +38,7 @@ const VALIDATION_ANALYSIS_FIELDS = [
|
|
|
38
38
|
"Boundary And Failure Coverage",
|
|
39
39
|
"Public Contract Coverage",
|
|
40
40
|
"Test Integrity",
|
|
41
|
+
"Test Infrastructure",
|
|
41
42
|
"Skips And Gaps",
|
|
42
43
|
"User Approval And Gap Disposition",
|
|
43
44
|
"Validation Readiness"
|
|
@@ -68,7 +69,10 @@ const SOURCE_ARTIFACTS = {
|
|
|
68
69
|
".ai/vcm/handoffs/test-report.md",
|
|
69
70
|
"docs/TESTING.md"
|
|
70
71
|
],
|
|
71
|
-
"code-diff": [
|
|
72
|
+
"code-diff": [
|
|
73
|
+
".ai/vcm/handoffs/test-report.md",
|
|
74
|
+
".ai/vcm/gate-reviews/validation-adequacy-review.md"
|
|
75
|
+
]
|
|
72
76
|
};
|
|
73
77
|
const CODE_DIFF_SOURCE_ARTIFACTS = {
|
|
74
78
|
coder: [
|
|
@@ -88,6 +92,7 @@ const CORE_INPUT_ARTIFACTS = {
|
|
|
88
92
|
"validation-adequacy": ".ai/vcm/handoffs/test-report.md"
|
|
89
93
|
};
|
|
90
94
|
const VALID_SEVERITIES = new Set(["critical", "high", "medium", "low"]);
|
|
95
|
+
const VALID_CODE_DIFF_FINDING_SCOPES = new Set(CODE_DIFF_FINDING_SCOPES);
|
|
91
96
|
export function createGateReviewService(deps) {
|
|
92
97
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
93
98
|
const reportPollIntervalMs = deps.reportPollIntervalMs ?? DEFAULT_REPORT_POLL_INTERVAL_MS;
|
|
@@ -309,6 +314,39 @@ export function createGateReviewService(deps) {
|
|
|
309
314
|
};
|
|
310
315
|
}
|
|
311
316
|
}
|
|
317
|
+
if (gate === "code-diff") {
|
|
318
|
+
const prerequisiteError = await readCodeDiffPrerequisiteError(deps, context, index);
|
|
319
|
+
if (prerequisiteError) {
|
|
320
|
+
index = applyGateState(index, gate, {
|
|
321
|
+
status: "failed",
|
|
322
|
+
decision: undefined,
|
|
323
|
+
error: prerequisiteError,
|
|
324
|
+
exceptionReason: undefined,
|
|
325
|
+
requestId: undefined,
|
|
326
|
+
requestPath: undefined,
|
|
327
|
+
inputHash: undefined,
|
|
328
|
+
baseCommit: undefined,
|
|
329
|
+
headCommit: undefined,
|
|
330
|
+
commits: undefined,
|
|
331
|
+
changedFiles: undefined,
|
|
332
|
+
diffStat: undefined,
|
|
333
|
+
codeDiffSource,
|
|
334
|
+
codeDiffSources: codeDiffSource ? [codeDiffSource] : undefined,
|
|
335
|
+
requestedAt: undefined,
|
|
336
|
+
startedAt: undefined,
|
|
337
|
+
completedAt: now(),
|
|
338
|
+
callbackStatus: "not_sent",
|
|
339
|
+
callbackError: undefined
|
|
340
|
+
}, now(), true);
|
|
341
|
+
await saveIndex(deps.fs, context.taskRepoRoot, index);
|
|
342
|
+
return {
|
|
343
|
+
status: "failed_to_start",
|
|
344
|
+
gate,
|
|
345
|
+
record: index.gates[gate],
|
|
346
|
+
message: prerequisiteError
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
312
350
|
const codeDiffInput = gate === "code-diff"
|
|
313
351
|
? await resolveCodeDiffInput(deps, context, record)
|
|
314
352
|
: undefined;
|
|
@@ -1031,11 +1069,39 @@ async function readValidationReportError(fs, taskRepoRoot) {
|
|
|
1031
1069
|
const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
|
|
1032
1070
|
const content = await fs.pathExists(absolutePath) ? await fs.readText(absolutePath) : null;
|
|
1033
1071
|
const check = checkMarkdownArtifact("test-report", relativePath, content);
|
|
1034
|
-
if (check.status
|
|
1072
|
+
if (check.status !== "ok") {
|
|
1073
|
+
return `${relativePath} is incomplete and cannot start validation-adequacy review. `
|
|
1074
|
+
+ formatValidationArtifactFailure(check, content);
|
|
1075
|
+
}
|
|
1076
|
+
const infrastructureStatus = matchField(extractMarkdownSection(content ?? "", "Test Infrastructure") ?? "", "Status");
|
|
1077
|
+
if (infrastructureStatus === "repair-required") {
|
|
1078
|
+
return `${relativePath} cannot start validation-adequacy review while Test Infrastructure Status is repair-required. Route Tester repair first.`;
|
|
1079
|
+
}
|
|
1080
|
+
if (infrastructureStatus === "production-change-required") {
|
|
1081
|
+
return `${relativePath} cannot start validation-adequacy review while Test Infrastructure Status is production-change-required. Route the active flow's implementation-failure branch first.`;
|
|
1082
|
+
}
|
|
1083
|
+
return undefined;
|
|
1084
|
+
}
|
|
1085
|
+
async function readCodeDiffPrerequisiteError(deps, context, index) {
|
|
1086
|
+
const reportError = await readValidationReportError(deps.fs, context.taskRepoRoot);
|
|
1087
|
+
if (reportError) {
|
|
1088
|
+
return "code-diff requires completed Tester validation. " + reportError;
|
|
1089
|
+
}
|
|
1090
|
+
const validationGate = index.gates["validation-adequacy"];
|
|
1091
|
+
if (!validationGate.required) {
|
|
1092
|
+
return undefined;
|
|
1093
|
+
}
|
|
1094
|
+
if (validationGate.status === "skipped" || validationGate.status === "overridden") {
|
|
1035
1095
|
return undefined;
|
|
1036
1096
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1097
|
+
if (validationGate.status !== "completed" || validationGate.decision !== "approve") {
|
|
1098
|
+
return "code-diff requires the validation-adequacy Gate to complete successfully for the current Tester evidence.";
|
|
1099
|
+
}
|
|
1100
|
+
const currentValidationHash = await computeInputHash(deps, context.taskRepoRoot, "validation-adequacy");
|
|
1101
|
+
if (!validationGate.inputHash || validationGate.inputHash !== currentValidationHash) {
|
|
1102
|
+
return "code-diff requires a current validation-adequacy approval; code or test evidence changed after the recorded approval.";
|
|
1103
|
+
}
|
|
1104
|
+
return undefined;
|
|
1039
1105
|
}
|
|
1040
1106
|
async function readArchitectureEvidenceError(fs, taskRepoRoot) {
|
|
1041
1107
|
const relativePath = ".ai/vcm/handoffs/architecture-evidence.md";
|
|
@@ -1310,11 +1376,13 @@ function validateRequestChangeFindings(findings) {
|
|
|
1310
1376
|
}
|
|
1311
1377
|
}
|
|
1312
1378
|
function validateCodeDiffFindings(findings) {
|
|
1313
|
-
const incomplete = findings.find((finding) => !finding.file?.trim()
|
|
1379
|
+
const incomplete = findings.find((finding) => (!finding.file?.trim()
|
|
1380
|
+
|| !finding.location?.trim()
|
|
1381
|
+
|| !finding.scope));
|
|
1314
1382
|
if (incomplete) {
|
|
1315
1383
|
throw new VcmError({
|
|
1316
1384
|
code: "GATE_REVIEW_CODE_DIFF_FINDING_LOCATION_MISSING",
|
|
1317
|
-
message: `Code-diff finding ${incomplete.title} must contain File
|
|
1385
|
+
message: `Code-diff finding ${incomplete.title} must contain File, Line Or Symbol, and Finding Scope.`,
|
|
1318
1386
|
statusCode: 500
|
|
1319
1387
|
});
|
|
1320
1388
|
}
|
|
@@ -1408,6 +1476,7 @@ function extractFindings(content) {
|
|
|
1408
1476
|
file: matchField(block, "file"),
|
|
1409
1477
|
line: parsePositiveInteger(matchField(block, "line")),
|
|
1410
1478
|
location: matchField(block, "line or symbol"),
|
|
1479
|
+
scope: normalizeCodeDiffFindingScope(matchField(block, "finding scope")),
|
|
1411
1480
|
evidence: matchField(block, "evidence") ?? "",
|
|
1412
1481
|
expected: matchField(block, "expected") ?? "",
|
|
1413
1482
|
gap: matchField(block, "gap") ?? "",
|
|
@@ -1420,7 +1489,10 @@ function getSourceArtifacts(gate, codeDiffSources) {
|
|
|
1420
1489
|
if (gate !== "code-diff") {
|
|
1421
1490
|
return SOURCE_ARTIFACTS[gate];
|
|
1422
1491
|
}
|
|
1423
|
-
return [...new Set(
|
|
1492
|
+
return [...new Set([
|
|
1493
|
+
...SOURCE_ARTIFACTS["code-diff"],
|
|
1494
|
+
...(codeDiffSources ?? []).flatMap((source) => CODE_DIFF_SOURCE_ARTIFACTS[source])
|
|
1495
|
+
])];
|
|
1424
1496
|
}
|
|
1425
1497
|
function resolveCodeDiffSources(record, codeDiffInput, currentSource) {
|
|
1426
1498
|
const continuingRecordedRange = record.baseCommit === codeDiffInput.baseCommit
|
|
@@ -1467,6 +1539,12 @@ function normalizeSeverity(value) {
|
|
|
1467
1539
|
? normalized
|
|
1468
1540
|
: undefined;
|
|
1469
1541
|
}
|
|
1542
|
+
function normalizeCodeDiffFindingScope(value) {
|
|
1543
|
+
const normalized = typeof value === "string" ? value.toLowerCase() : "";
|
|
1544
|
+
return VALID_CODE_DIFF_FINDING_SCOPES.has(normalized)
|
|
1545
|
+
? normalized
|
|
1546
|
+
: undefined;
|
|
1547
|
+
}
|
|
1470
1548
|
function normalizeCallbackStatus(value) {
|
|
1471
1549
|
return value === "not_sent" || value === "sent" || value === "skipped" || value === "failed"
|
|
1472
1550
|
? value
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ARCHITECTURE_BRIEF_STATUSES, ARCHITECTURE_PLAN_RESULTS, DOCS_SYNC_DECISIONS, FINAL_ACCEPTANCE_DECISIONS, L3_ACTIONS, L3_REQUIRED_VALUES, STRICT_NONE_VALUE, TEST_RESULTS, renderArtifactOptions } from "../../shared/validation/artifact-contract.js";
|
|
1
|
+
import { ARCHITECTURE_BRIEF_STATUSES, ARCHITECTURE_PLAN_RESULTS, DOCS_SYNC_DECISIONS, FINAL_ACCEPTANCE_DECISIONS, L3_ACTIONS, L3_REQUIRED_VALUES, STRICT_NONE_VALUE, TEST_INFRASTRUCTURE_STATUSES, TEST_RESULTS, renderArtifactOptions } from "../../shared/validation/artifact-contract.js";
|
|
2
2
|
export function renderArchitectureBriefTemplate(taskSlug) {
|
|
3
3
|
return `# Architecture Brief: ${taskSlug}
|
|
4
4
|
|
|
@@ -201,6 +201,26 @@ TBD
|
|
|
201
201
|
|
|
202
202
|
TBD
|
|
203
203
|
|
|
204
|
+
## Test Infrastructure
|
|
205
|
+
|
|
206
|
+
Status: ${renderArtifactOptions(TEST_INFRASTRUCTURE_STATUSES)}
|
|
207
|
+
|
|
208
|
+
### Affected Files
|
|
209
|
+
|
|
210
|
+
${STRICT_NONE_VALUE}
|
|
211
|
+
|
|
212
|
+
### Boundary Evidence
|
|
213
|
+
|
|
214
|
+
${STRICT_NONE_VALUE}
|
|
215
|
+
|
|
216
|
+
### Defect-Class Sweep
|
|
217
|
+
|
|
218
|
+
${STRICT_NONE_VALUE}
|
|
219
|
+
|
|
220
|
+
### Repair Commit
|
|
221
|
+
|
|
222
|
+
${STRICT_NONE_VALUE}
|
|
223
|
+
|
|
204
224
|
## Failed Expectations
|
|
205
225
|
|
|
206
226
|
${STRICT_NONE_VALUE}
|
|
@@ -115,9 +115,9 @@ ${renderRoleMemoryRules("architect")}
|
|
|
115
115
|
|
|
116
116
|
#### Planning Completion
|
|
117
117
|
|
|
118
|
-
-
|
|
119
|
-
- If VCM returns a \`memoryCandidatePath\`,
|
|
120
|
-
-
|
|
118
|
+
- Before the first completed Architect-to-PM planning route, use the \`restart-architect\` skill after the complete plan, scaffold, reconciliation, L0 evidence, and commits are ready.
|
|
119
|
+
- If VCM returns a \`memoryCandidatePath\`, ensure one planning-session memory candidate exists at that path before routing. Use \`vcm-propose-memory\` only when the candidate is absent. Include only verified, durable, reusable project knowledge from planning; do not record task narrative, temporary state, unverified conclusions, or Harness rules.
|
|
120
|
+
- Gate revision rounds reuse the same pending restart and task-level candidate; do not recreate either one. Write the latest route message with both architecture artifacts and the plan, then end the turn. VCM restarts the session only after the Gate is accepted. Do not wait for or inspect the replacement session.
|
|
121
121
|
|
|
122
122
|
### Complete Task Planning
|
|
123
123
|
|
|
@@ -51,8 +51,8 @@ If a reusable harness problem is suspected, it is enough to record a concise fee
|
|
|
51
51
|
- All standard workflow routes among project-manager, architect, coder, and tester are PM-hub routes. Project-manager starts and advances every flow; architect, coder, and tester report blockers, failures, conflicts, incomplete work, and findings back to project-manager.
|
|
52
52
|
- Code changes use: \`project-manager -> architect interview -> architect planning -> coder -> tester -> architect docs sync -> project-manager final acceptance\`.
|
|
53
53
|
- Architect Debug Mode runs inside either Architect Debug Flow or Architect Debug Branch. Architecture Diagnosis Mode runs inside either Architecture Diagnosis Flow or Architecture Diagnosis Branch.
|
|
54
|
-
- Architect Debug Flow and an Architecture Diagnosis Flow that produces code changes
|
|
55
|
-
- Architect Debug Branch and Architecture Diagnosis Branch preserve the active parent flow and resume point, then return there after
|
|
54
|
+
- Code-Change Flow, Architect Debug Flow, and an Architecture Diagnosis Flow that produces code changes run tester validation, validation-adequacy Gate Review, and then code-diff Gate Review before architect docs sync and project-manager final acceptance. An analysis-only Architecture Diagnosis Flow completes from the diagnosis result.
|
|
55
|
+
- Architect Debug Branch and Architecture Diagnosis Branch preserve the active parent flow and resume point, then return there after tester validation, validation-adequacy Gate Review, and code-diff Gate Review complete. They do not run their own final acceptance.
|
|
56
56
|
- Docs-Only Flow uses: \`project-manager -> architect -> project-manager completion\`.
|
|
57
57
|
- Validation-Only Flow uses: \`project-manager -> tester -> validation-adequacy Gate Review -> project-manager completion\`.
|
|
58
58
|
- Communication-Only Flow uses: \`project-manager response or relay -> completion\`.
|
|
@@ -185,6 +185,14 @@ when they are relevant to the changed behavior. Check that tests were not
|
|
|
185
185
|
weakened, over-mocked, tied only to fixture values or implementation details,
|
|
186
186
|
or made green by bypassing the real behavior path.
|
|
187
187
|
|
|
188
|
+
Inspect the \`Test Infrastructure\` section of \`test-report.md\`. A report with
|
|
189
|
+
\`repair-required\` or \`production-change-required\` is not gate-ready. When
|
|
190
|
+
status is \`repaired\`, verify the affected files remain Tester-owned, the
|
|
191
|
+
boundary evidence excludes production or shared changes, the defect-class sweep
|
|
192
|
+
covers the affected test-infrastructure family, the repair commit exists in the
|
|
193
|
+
current range, and required clean-state validation was rerun. Request changes
|
|
194
|
+
for missing, contradictory, incomplete, weakened, or unverified repair evidence.
|
|
195
|
+
|
|
188
196
|
Do not approve only because \`Test Result: pass\` or all recorded commands are
|
|
189
197
|
green. Request changes when the report is incomplete or inconsistent with the
|
|
190
198
|
actual tests, validation level does not match risk, an important behavior has
|
|
@@ -207,10 +215,12 @@ convert the result to \`pass\` or independently accept the risk.
|
|
|
207
215
|
|
|
208
216
|
## Code Diff Gate
|
|
209
217
|
|
|
210
|
-
Read \`.claude/agents/coder.md
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
Read \`.claude/agents/coder.md\`, \`.claude/agents/tester.md\`,
|
|
219
|
+
\`.ai/vcm/handoffs/test-report.md\`, the current validation-adequacy Gate report,
|
|
220
|
+
and \`docs/CODING_STANDARDS.md\`; use the architect definition to understand
|
|
221
|
+
implementation responsibility boundaries. Code-diff runs only after Tester
|
|
222
|
+
validation and the current validation-adequacy disposition. Review every commit
|
|
223
|
+
in the range named by VCM and nothing outside that range.
|
|
214
224
|
|
|
215
225
|
Use every code source and evidence artifact named in the VCM prompt. A source
|
|
216
226
|
chain means the range contains the original implementation and later corrective
|
|
@@ -237,6 +247,13 @@ scaffold, and coder completion evidence. Verify that the complete planned
|
|
|
237
247
|
behavior is implemented without changing architect-owned boundaries or
|
|
238
248
|
contracts.
|
|
239
249
|
|
|
250
|
+
When the range contains Tester-authored changes recorded in \`test-report.md\`,
|
|
251
|
+
review those tests, fixtures, test-only helpers, and \`docs/TESTING.md\` against
|
|
252
|
+
the Tester role, the repair or coverage evidence, and
|
|
253
|
+
\`docs/CODING_STANDARDS.md\`. Do not reject a valid Tester-owned change merely
|
|
254
|
+
because it is not a Coder scaffold item. Verify that Tester changes remain
|
|
255
|
+
test-only, preserve real behavior paths, and are committed and validated.
|
|
256
|
+
|
|
240
257
|
For \`architect-debug\`, compare the commits with the current Architect route
|
|
241
258
|
command and \`.ai/vcm/handoffs/architect-debug.md\`. Verify that the confirmed
|
|
242
259
|
root cause is supported by the code, the implementation fixes that cause rather
|
|
@@ -262,6 +279,11 @@ and changes outside its governing evidence. Verify callable and public-surface
|
|
|
262
279
|
changes against their callers, exports, compatibility obligations, generated
|
|
263
280
|
context, and durable documentation.
|
|
264
281
|
|
|
282
|
+
Use the completed test report and validation-adequacy disposition as execution
|
|
283
|
+
evidence while independently deciding whether the implementation handles its
|
|
284
|
+
required behavior and boundary cases. Do not repeat the validation-adequacy
|
|
285
|
+
decision.
|
|
286
|
+
|
|
265
287
|
Inspect changed baseline tests for the changed callable units and applicable
|
|
266
288
|
branches. Request changes for weakened, deleted, skipped, fabricated, or
|
|
267
289
|
implementation-shaped tests, and for obvious missing baseline coverage required
|
|
@@ -312,6 +334,7 @@ Use this findings structure:
|
|
|
312
334
|
- Boundary And Failure Coverage:
|
|
313
335
|
- Public Contract Coverage:
|
|
314
336
|
- Test Integrity:
|
|
337
|
+
- Test Infrastructure:
|
|
315
338
|
- Skips And Gaps:
|
|
316
339
|
- User Approval And Gap Disposition:
|
|
317
340
|
- Validation Readiness:
|
|
@@ -337,6 +360,8 @@ Use this findings structure:
|
|
|
337
360
|
<!-- File and Line Or Symbol are required for code-diff findings. -->
|
|
338
361
|
- File:
|
|
339
362
|
- Line Or Symbol:
|
|
363
|
+
<!-- Finding Scope is required for code-diff findings. Use test-only only when correction needs no production, runtime, public-contract, dependency, generated-context, architecture, or shared-production change. -->
|
|
364
|
+
- Finding Scope: test-only|implementation
|
|
340
365
|
- Evidence:
|
|
341
366
|
- Expected:
|
|
342
367
|
- Gap:
|
|
@@ -375,6 +400,7 @@ If there are no findings, write:
|
|
|
375
400
|
- Boundary And Failure Coverage:
|
|
376
401
|
- Public Contract Coverage:
|
|
377
402
|
- Test Integrity:
|
|
403
|
+
- Test Infrastructure:
|
|
378
404
|
- Skips And Gaps:
|
|
379
405
|
- User Approval And Gap Disposition:
|
|
380
406
|
- Validation Readiness:
|
|
@@ -464,8 +490,8 @@ Use this skill at every project-manager Gate Review trigger point and whenever V
|
|
|
464
490
|
## Trigger Points
|
|
465
491
|
|
|
466
492
|
- \`architecture-plan\`: after the user confirms \`.ai/vcm/handoffs/architecture-brief.md\` and architect writes \`.ai/vcm/handoffs/architecture-plan.md\`, before coder dispatch.
|
|
467
|
-
- \`validation-adequacy\`: after tester writes a terminal \`Test Result: pass|fail\` that the active flow permits to reach the gate
|
|
468
|
-
- \`code-diff\`: after
|
|
493
|
+
- \`validation-adequacy\`: after tester writes a terminal \`Test Result: pass|fail\` that the active flow permits to reach the gate. Never request this gate for \`Test Result: incomplete\`.
|
|
494
|
+
- \`code-diff\`: after Tester completes and the current validation-adequacy Gate finishes successfully for a Coder implementation, Architect Debug fix, or Architecture Diagnosis fix. Identify the production-code source with \`--source coder\`, \`--source architect-debug\`, or \`--source architect-diagnosis\`. Validation-Only Flow does not request code-diff.
|
|
469
495
|
|
|
470
496
|
## Request
|
|
471
497
|
|
|
@@ -500,6 +526,7 @@ import argparse
|
|
|
500
526
|
import hashlib
|
|
501
527
|
import json
|
|
502
528
|
import os
|
|
529
|
+
import re
|
|
503
530
|
import subprocess
|
|
504
531
|
import sys
|
|
505
532
|
import urllib.error
|
|
@@ -528,7 +555,10 @@ SOURCE_ARTIFACTS = {
|
|
|
528
555
|
".ai/vcm/handoffs/test-report.md",
|
|
529
556
|
"docs/TESTING.md",
|
|
530
557
|
],
|
|
531
|
-
"code-diff": [
|
|
558
|
+
"code-diff": [
|
|
559
|
+
".ai/vcm/handoffs/test-report.md",
|
|
560
|
+
".ai/vcm/gate-reviews/validation-adequacy-review.md",
|
|
561
|
+
],
|
|
532
562
|
}
|
|
533
563
|
CODE_DIFF_SOURCE_ARTIFACTS = {
|
|
534
564
|
"coder": [
|
|
@@ -703,11 +733,14 @@ def code_diff_sources(gate_record: dict, source: str | None, code_diff: dict) ->
|
|
|
703
733
|
def source_artifacts(gate: str, sources: list[str] | None) -> list[str]:
|
|
704
734
|
if gate != "code-diff":
|
|
705
735
|
return SOURCE_ARTIFACTS[gate]
|
|
706
|
-
return list(dict.fromkeys(
|
|
736
|
+
return list(dict.fromkeys([
|
|
737
|
+
*SOURCE_ARTIFACTS["code-diff"],
|
|
738
|
+
*(
|
|
707
739
|
artifact
|
|
708
740
|
for source in (sources or [])
|
|
709
741
|
for artifact in CODE_DIFF_SOURCE_ARTIFACTS.get(source, [])
|
|
710
|
-
|
|
742
|
+
),
|
|
743
|
+
]))
|
|
711
744
|
|
|
712
745
|
|
|
713
746
|
def input_hash(root: Path, gate: str, sources: list[str] | None = None, gate_record=None) -> str:
|
|
@@ -787,6 +820,31 @@ def core_input_status(root: Path, gate: str) -> tuple[str, str] | None:
|
|
|
787
820
|
return (core_artifact, "ready")
|
|
788
821
|
|
|
789
822
|
|
|
823
|
+
def code_diff_prerequisite_error(root: Path, index: dict) -> str | None:
|
|
824
|
+
report_path = root / ".ai/vcm/handoffs/test-report.md"
|
|
825
|
+
try:
|
|
826
|
+
report = report_path.read_text()
|
|
827
|
+
except OSError:
|
|
828
|
+
return "code-diff requires completed Tester validation. .ai/vcm/handoffs/test-report.md is missing."
|
|
829
|
+
result = re.search(r"^\\s*Test Result\\s*:\\s*(pass|fail|incomplete)\\s*$", report, re.IGNORECASE | re.MULTILINE)
|
|
830
|
+
if result is None:
|
|
831
|
+
return "code-diff requires completed Tester validation. Test Result must be exactly pass or fail."
|
|
832
|
+
if result.group(1).lower() == "incomplete":
|
|
833
|
+
return "code-diff requires completed Tester validation. Test Result is incomplete."
|
|
834
|
+
|
|
835
|
+
validation = index.get("gates", {}).get("validation-adequacy", {})
|
|
836
|
+
if not isinstance(validation, dict) or not validation.get("required", False):
|
|
837
|
+
return None
|
|
838
|
+
if validation.get("status") in ("skipped", "overridden"):
|
|
839
|
+
return None
|
|
840
|
+
if validation.get("status") != "completed" or validation.get("decision") != "approve":
|
|
841
|
+
return "code-diff requires the validation-adequacy Gate to complete successfully for the current Tester evidence."
|
|
842
|
+
current_hash = input_hash(root, "validation-adequacy")
|
|
843
|
+
if not validation.get("inputHash") or validation.get("inputHash") != current_hash:
|
|
844
|
+
return "code-diff requires a current validation-adequacy approval; code or test evidence changed after the recorded approval."
|
|
845
|
+
return None
|
|
846
|
+
|
|
847
|
+
|
|
790
848
|
def request_id(gate: str) -> str:
|
|
791
849
|
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
792
850
|
return f"{stamp}-{gate}-{uuid.uuid4().hex[:8]}"
|
|
@@ -883,6 +941,37 @@ def local_request(gate: str, source: str | None) -> int:
|
|
|
883
941
|
print_result("not_required", gate=gate, message=f"{core_status[0]} is {core_status[1]}.")
|
|
884
942
|
return 0
|
|
885
943
|
|
|
944
|
+
if gate == "code-diff":
|
|
945
|
+
prerequisite_error = code_diff_prerequisite_error(root, index)
|
|
946
|
+
if prerequisite_error:
|
|
947
|
+
gate_record = index["gates"].setdefault(gate, {})
|
|
948
|
+
gate_record.update({
|
|
949
|
+
"required": True,
|
|
950
|
+
"status": "failed",
|
|
951
|
+
"decision": None,
|
|
952
|
+
"error": prerequisite_error,
|
|
953
|
+
"exceptionReason": None,
|
|
954
|
+
"requestId": None,
|
|
955
|
+
"requestPath": None,
|
|
956
|
+
"inputHash": None,
|
|
957
|
+
"baseCommit": None,
|
|
958
|
+
"headCommit": None,
|
|
959
|
+
"commits": None,
|
|
960
|
+
"changedFiles": None,
|
|
961
|
+
"diffStat": None,
|
|
962
|
+
"requestedAt": None,
|
|
963
|
+
"startedAt": None,
|
|
964
|
+
"completedAt": now_iso(),
|
|
965
|
+
"callbackStatus": "not_sent",
|
|
966
|
+
"callbackError": None,
|
|
967
|
+
"updatedAt": now_iso(),
|
|
968
|
+
})
|
|
969
|
+
if index.get("activeGate") == gate:
|
|
970
|
+
index["activeGate"] = None
|
|
971
|
+
write_json(index_path, index)
|
|
972
|
+
print_result("failed_to_start", gate=gate, reason=prerequisite_error)
|
|
973
|
+
return 2
|
|
974
|
+
|
|
886
975
|
gate_record = index["gates"].get(gate, {})
|
|
887
976
|
code_diff = {}
|
|
888
977
|
if gate == "code-diff":
|