vibe-coding-master 0.7.47 → 0.7.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/backend/api/session-routes.js +41 -1
- package/dist/backend/server.js +17 -3
- package/dist/backend/services/architect-restart-service.js +251 -78
- package/dist/backend/services/artifact-service.js +60 -131
- package/dist/backend/services/claude-hook-service.js +6 -0
- package/dist/backend/services/gate-review-service.js +42 -217
- package/dist/backend/services/harness-feedback-service.js +13 -24
- package/dist/backend/services/managed-artifact-validation.js +403 -0
- package/dist/backend/services/message-service.js +13 -64
- package/dist/backend/services/role-context-restart-service.js +240 -0
- package/dist/backend/services/runtime-recovery-service.js +2 -0
- package/dist/backend/services/session-service.js +39 -2
- package/dist/backend/services/task-close-service.js +6 -1
- package/dist/backend/templates/harness/coder-agent.js +1 -0
- package/dist/backend/templates/harness/gate-review.js +13 -98
- package/dist/backend/templates/harness/tester-agent.js +1 -0
- package/dist/backend/templates/harness/vcm-report-harness-issue-skill.js +1 -1
- package/dist/shared/validation/artifact-registry.js +68 -0
- package/dist-frontend/assets/{index-nAV6toi8.js → index-DLsIPTvK.js} +34 -34
- package/dist-frontend/index.html +1 -1
- package/package.json +1 -1
- package/scripts/harness-tools/vcm-artifact +1 -34
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { VCM_ROLE_NAMES } from "../../shared/constants.js";
|
|
3
|
+
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
4
|
+
import { toVcmError, VcmError } from "../errors.js";
|
|
5
|
+
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
6
|
+
const STATE_DIR = "restart-with-context";
|
|
7
|
+
export function createRoleContextRestartService(deps) {
|
|
8
|
+
const pending = new Map();
|
|
9
|
+
const operations = new Map();
|
|
10
|
+
const now = deps.now ?? (() => new Date().toISOString());
|
|
11
|
+
return {
|
|
12
|
+
async restart(repoRoot, taskSlug, role, input = {}) {
|
|
13
|
+
return withLock(restartKey(repoRoot, taskSlug, role), async () => {
|
|
14
|
+
const current = await deps.sessionService.getRoleSession(repoRoot, taskSlug, role);
|
|
15
|
+
if (!current) {
|
|
16
|
+
throw new VcmError({
|
|
17
|
+
code: "SESSION_MISSING",
|
|
18
|
+
message: `${role} session has not been started.`,
|
|
19
|
+
statusCode: 404
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const state = {
|
|
23
|
+
version: 1,
|
|
24
|
+
taskSlug,
|
|
25
|
+
role,
|
|
26
|
+
sourceSessionId: current.id,
|
|
27
|
+
status: "launching",
|
|
28
|
+
permissionMode: input.permissionMode ?? current.permissionMode,
|
|
29
|
+
model: input.model ?? current.model,
|
|
30
|
+
effort: input.effort ?? current.effort,
|
|
31
|
+
cols: input.cols,
|
|
32
|
+
rows: input.rows,
|
|
33
|
+
updatedAt: now()
|
|
34
|
+
};
|
|
35
|
+
await persist(repoRoot, state);
|
|
36
|
+
return launch(repoRoot, state);
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
async recoverTask(repoRoot, taskSlug) {
|
|
40
|
+
for (const role of VCM_ROLE_NAMES) {
|
|
41
|
+
await withLock(restartKey(repoRoot, taskSlug, role), async () => {
|
|
42
|
+
const state = await load(repoRoot, taskSlug, role);
|
|
43
|
+
if (!state) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const session = await deps.sessionService.getRoleSession(repoRoot, taskSlug, role);
|
|
47
|
+
if (state.replacementSessionId
|
|
48
|
+
&& session?.id === state.replacementSessionId
|
|
49
|
+
&& session.claudeSessionId) {
|
|
50
|
+
await remove(repoRoot, state);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
state.status = "launching";
|
|
54
|
+
state.error = undefined;
|
|
55
|
+
state.updatedAt = now();
|
|
56
|
+
await persist(repoRoot, state);
|
|
57
|
+
await launch(repoRoot, state);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
async recordPromptSubmitted(repoRoot, taskSlug, role, sessionId) {
|
|
62
|
+
await withLock(restartKey(repoRoot, taskSlug, role), async () => {
|
|
63
|
+
const state = pending.get(restartKey(repoRoot, taskSlug, role))
|
|
64
|
+
?? await load(repoRoot, taskSlug, role);
|
|
65
|
+
if (!state
|
|
66
|
+
|| state.status !== "awaiting_prompt_confirmation"
|
|
67
|
+
|| state.replacementSessionId !== sessionId) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
await remove(repoRoot, state);
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
async clearRole(repoRoot, taskSlug, role) {
|
|
74
|
+
await withLock(restartKey(repoRoot, taskSlug, role), async () => {
|
|
75
|
+
pending.delete(restartKey(repoRoot, taskSlug, role));
|
|
76
|
+
const target = await statePath(repoRoot, taskSlug, role);
|
|
77
|
+
if (await deps.fs.pathExists(target)) {
|
|
78
|
+
await deps.fs.removePath?.(target, { force: true });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
async clear(repoRoot, taskSlug) {
|
|
83
|
+
for (const role of VCM_ROLE_NAMES) {
|
|
84
|
+
pending.delete(restartKey(repoRoot, taskSlug, role));
|
|
85
|
+
}
|
|
86
|
+
const target = await stateDirectory(repoRoot, taskSlug);
|
|
87
|
+
if (await deps.fs.pathExists(target)) {
|
|
88
|
+
await deps.fs.removePath?.(target, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
async function launch(repoRoot, state) {
|
|
93
|
+
const prompts = await buildPrompts(repoRoot, state.taskSlug, state.role);
|
|
94
|
+
try {
|
|
95
|
+
const replacement = await deps.sessionService.restartRoleSessionForContext(repoRoot, state.taskSlug, state.role, {
|
|
96
|
+
permissionMode: state.permissionMode,
|
|
97
|
+
model: state.model,
|
|
98
|
+
effort: state.effort,
|
|
99
|
+
cols: state.cols,
|
|
100
|
+
rows: state.rows,
|
|
101
|
+
appendSystemPrompt: prompts.system
|
|
102
|
+
});
|
|
103
|
+
state.replacementSessionId = replacement.id;
|
|
104
|
+
state.status = "awaiting_prompt_confirmation";
|
|
105
|
+
state.updatedAt = now();
|
|
106
|
+
await persist(repoRoot, state);
|
|
107
|
+
await deps.sessionService.submitRolePrompt(repoRoot, state.taskSlug, state.role, replacement.id, prompts.user);
|
|
108
|
+
return replacement;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const normalized = toVcmError(error);
|
|
112
|
+
state.status = "blocked";
|
|
113
|
+
state.error = {
|
|
114
|
+
code: normalized.code,
|
|
115
|
+
message: normalized.message
|
|
116
|
+
};
|
|
117
|
+
state.updatedAt = now();
|
|
118
|
+
await persist(repoRoot, state);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function buildPrompts(repoRoot, taskSlug, role) {
|
|
123
|
+
const [config, task] = await Promise.all([
|
|
124
|
+
deps.projectService.loadConfig(repoRoot),
|
|
125
|
+
deps.taskService.loadTask(repoRoot, taskSlug)
|
|
126
|
+
]);
|
|
127
|
+
const handoffDir = task.handoffDir;
|
|
128
|
+
const stateRoot = config.stateRoot;
|
|
129
|
+
const files = roleContextFiles(role, handoffDir, stateRoot);
|
|
130
|
+
return {
|
|
131
|
+
system: [
|
|
132
|
+
`This fresh ${role} session continues the current VCM task after Restart With Context.`,
|
|
133
|
+
"",
|
|
134
|
+
"Before continuing, read the existing files or directories that apply:",
|
|
135
|
+
...files.map((file) => `- ${file}`),
|
|
136
|
+
"- the current worktree and Git state",
|
|
137
|
+
"",
|
|
138
|
+
"Treat the current artifacts and worktree as the source of truth. Continue the accepted assignment from the recorded current state. Do not repeat completed work or rely on the previous Session transcript."
|
|
139
|
+
].join("\n"),
|
|
140
|
+
user: `[VCM RESTART WITH CONTEXT]\nRestore the current ${role} work from the listed task artifacts and continue the accepted assignment.`
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function persist(repoRoot, state) {
|
|
144
|
+
await deps.fs.writeJsonAtomic(await statePath(repoRoot, state.taskSlug, state.role), state);
|
|
145
|
+
pending.set(restartKey(repoRoot, state.taskSlug, state.role), state);
|
|
146
|
+
}
|
|
147
|
+
async function load(repoRoot, taskSlug, role) {
|
|
148
|
+
const target = await statePath(repoRoot, taskSlug, role);
|
|
149
|
+
if (!(await deps.fs.pathExists(target))) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const state = await deps.fs.readJson(target);
|
|
153
|
+
if (state.version !== 1
|
|
154
|
+
|| state.taskSlug !== taskSlug
|
|
155
|
+
|| state.role !== role
|
|
156
|
+
|| !["launching", "awaiting_prompt_confirmation", "blocked"].includes(state.status)) {
|
|
157
|
+
throw new VcmError({
|
|
158
|
+
code: "ROLE_CONTEXT_RESTART_STATE_INVALID",
|
|
159
|
+
message: `Restart With Context state is invalid for ${role} in task ${taskSlug}.`,
|
|
160
|
+
statusCode: 500
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
pending.set(restartKey(repoRoot, taskSlug, role), state);
|
|
164
|
+
return state;
|
|
165
|
+
}
|
|
166
|
+
async function remove(repoRoot, state) {
|
|
167
|
+
pending.delete(restartKey(repoRoot, state.taskSlug, state.role));
|
|
168
|
+
const target = await statePath(repoRoot, state.taskSlug, state.role);
|
|
169
|
+
if (await deps.fs.pathExists(target)) {
|
|
170
|
+
await deps.fs.removePath?.(target, { force: true });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function stateDirectory(repoRoot, taskSlug) {
|
|
174
|
+
const [config, task] = await Promise.all([
|
|
175
|
+
deps.projectService.loadConfig(repoRoot),
|
|
176
|
+
deps.taskService.loadTask(repoRoot, taskSlug)
|
|
177
|
+
]);
|
|
178
|
+
return resolveRepoPath(getTaskRuntimeRepoRoot(task), path.posix.join(config.stateRoot, STATE_DIR));
|
|
179
|
+
}
|
|
180
|
+
async function statePath(repoRoot, taskSlug, role) {
|
|
181
|
+
return path.join(await stateDirectory(repoRoot, taskSlug), `${role}.json`);
|
|
182
|
+
}
|
|
183
|
+
async function withLock(key, operation) {
|
|
184
|
+
const previous = operations.get(key) ?? Promise.resolve();
|
|
185
|
+
const current = previous.catch(() => undefined).then(operation);
|
|
186
|
+
operations.set(key, current);
|
|
187
|
+
try {
|
|
188
|
+
return await current;
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
if (operations.get(key) === current) {
|
|
192
|
+
operations.delete(key);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function roleContextFiles(role, handoffDir, stateRoot) {
|
|
198
|
+
switch (role) {
|
|
199
|
+
case "project-manager":
|
|
200
|
+
return [
|
|
201
|
+
`${handoffDir}/workflow-progress.md`,
|
|
202
|
+
`${stateRoot}/workflow/state.json`,
|
|
203
|
+
`${stateRoot}/workflow-control.json`,
|
|
204
|
+
`${handoffDir}/messages/`
|
|
205
|
+
];
|
|
206
|
+
case "architect":
|
|
207
|
+
return [
|
|
208
|
+
`${handoffDir}/role-commands/architect.md`,
|
|
209
|
+
`${handoffDir}/architecture-brief.md`,
|
|
210
|
+
`${handoffDir}/architecture-evidence.md`,
|
|
211
|
+
`${handoffDir}/planning-progress.md`,
|
|
212
|
+
`${handoffDir}/architecture-plan.md`,
|
|
213
|
+
`${handoffDir}/architect-debug.md`,
|
|
214
|
+
`${handoffDir}/architecture-diagnosis.md`,
|
|
215
|
+
".ai/vcm/gate-reviews/index.json"
|
|
216
|
+
];
|
|
217
|
+
case "coder":
|
|
218
|
+
return [
|
|
219
|
+
`${handoffDir}/role-commands/coder.md`,
|
|
220
|
+
`${handoffDir}/architecture-plan.md`,
|
|
221
|
+
`${handoffDir}/coder-completion.md`,
|
|
222
|
+
".ai/vcm/coder-workers/"
|
|
223
|
+
];
|
|
224
|
+
case "tester":
|
|
225
|
+
return [
|
|
226
|
+
`${handoffDir}/role-commands/tester.md`,
|
|
227
|
+
`${handoffDir}/architecture-plan.md`,
|
|
228
|
+
`${handoffDir}/coder-completion.md`,
|
|
229
|
+
`${handoffDir}/test-report.md`
|
|
230
|
+
];
|
|
231
|
+
case "reviewer":
|
|
232
|
+
return [
|
|
233
|
+
".ai/vcm/gate-reviews/index.json",
|
|
234
|
+
".ai/vcm/gate-reviews/requests/"
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function restartKey(repoRoot, taskSlug, role) {
|
|
239
|
+
return `${repoRoot}\0${taskSlug}\0${role}`;
|
|
240
|
+
}
|
|
@@ -35,6 +35,8 @@ export function createRuntimeRecoveryService(deps) {
|
|
|
35
35
|
await recoverMessages(taskRepoRoot, config.stateRoot, task.taskSlug, recoveredAt, context);
|
|
36
36
|
await recoverGateReview(taskRepoRoot, recoveredAt, context);
|
|
37
37
|
await cleanupCoderWorkers(taskRepoRoot, context);
|
|
38
|
+
await deps.architectRestartService?.recoverTask(repoRoot, task.taskSlug);
|
|
39
|
+
await deps.roleContextRestartService?.recoverTask(repoRoot, task.taskSlug);
|
|
38
40
|
if ((roundRecovered || task.status === "running") && !hasLiveTaskSession(task.taskSlug)) {
|
|
39
41
|
await deps.taskService.updateTaskStatus(repoRoot, task.taskSlug, "stopped");
|
|
40
42
|
}
|
|
@@ -45,7 +45,7 @@ export function createSessionService(deps) {
|
|
|
45
45
|
harnessOutdated: sessionRevision < currentRevision
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
-
async function launchRoleSession(repoRoot, taskSlug, role, input, launchMode) {
|
|
48
|
+
async function launchRoleSession(repoRoot, taskSlug, role, input, launchMode, options = {}) {
|
|
49
49
|
const config = await deps.projectService.loadConfig(repoRoot);
|
|
50
50
|
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
51
51
|
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
@@ -145,7 +145,7 @@ export function createSessionService(deps) {
|
|
|
145
145
|
};
|
|
146
146
|
deps.registry.upsert(record);
|
|
147
147
|
await persistRoleSessionRecord(deps.fs, repoRoot, taskRepoRoot, config.stateRoot, record);
|
|
148
|
-
if (role === "project-manager") {
|
|
148
|
+
if (role === "project-manager" && options.restoreProjectManagerContext !== false) {
|
|
149
149
|
await restoreProjectManagerWorkflowContext(record, taskRepoRoot, config.stateRoot);
|
|
150
150
|
}
|
|
151
151
|
return withHarnessRevisionView(taskRepoRoot, record);
|
|
@@ -1110,6 +1110,43 @@ export function createSessionService(deps) {
|
|
|
1110
1110
|
await clearPersistedRoleSessionRecord(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, taskSlug, role, now());
|
|
1111
1111
|
return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
|
|
1112
1112
|
},
|
|
1113
|
+
async restartRoleSessionForContext(repoRoot, taskSlug, role, input = {}) {
|
|
1114
|
+
const existing = await this.getRoleSession(repoRoot, taskSlug, role);
|
|
1115
|
+
if (!existing) {
|
|
1116
|
+
return launchRoleSession(repoRoot, taskSlug, role, input, "fresh", {
|
|
1117
|
+
restoreProjectManagerContext: false
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
await getModelLaunchEnvironment(normalizeClaudeModel(input.model ?? existing.model));
|
|
1121
|
+
if (deps.runtime.getSession(existing.id)) {
|
|
1122
|
+
await deps.runtime.stop(existing.id);
|
|
1123
|
+
}
|
|
1124
|
+
deps.registry.remove(existing.id);
|
|
1125
|
+
const config = await deps.projectService.loadConfig(repoRoot);
|
|
1126
|
+
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
1127
|
+
await clearPersistedRoleSessionRecord(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, taskSlug, role, now());
|
|
1128
|
+
return launchRoleSession(repoRoot, taskSlug, role, input, "fresh", {
|
|
1129
|
+
restoreProjectManagerContext: false
|
|
1130
|
+
});
|
|
1131
|
+
},
|
|
1132
|
+
async submitRolePrompt(repoRoot, taskSlug, role, expectedSessionId, prompt) {
|
|
1133
|
+
const session = await this.getRoleSession(repoRoot, taskSlug, role);
|
|
1134
|
+
if (!session || session.id !== expectedSessionId || session.status !== "running") {
|
|
1135
|
+
throw new VcmError({
|
|
1136
|
+
code: "ROLE_SESSION_NOT_RUNNING",
|
|
1137
|
+
message: `${role} replacement session is not running.`,
|
|
1138
|
+
statusCode: 409
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
if ((await waitForSessionInputReady(session.id)) === "exited") {
|
|
1142
|
+
throw new VcmError({
|
|
1143
|
+
code: "ROLE_SESSION_START_FAILED",
|
|
1144
|
+
message: `${role} replacement session exited before it could accept the recovery prompt.`,
|
|
1145
|
+
statusCode: 409
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
await submitTerminalInput(deps.runtime, session.id, prompt);
|
|
1149
|
+
},
|
|
1113
1150
|
async getRoleSession(repoRoot, taskSlug, role) {
|
|
1114
1151
|
const config = await deps.projectService.loadConfig(repoRoot);
|
|
1115
1152
|
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
@@ -5,7 +5,12 @@ export function createTaskCloseService(deps) {
|
|
|
5
5
|
async closeTask(repoRoot, taskSlug) {
|
|
6
6
|
const task = await deps.taskService.markTaskCleaned(repoRoot, taskSlug);
|
|
7
7
|
const warnings = [];
|
|
8
|
-
deps.architectRestartService
|
|
8
|
+
if (deps.architectRestartService) {
|
|
9
|
+
await bestEffort("Unable to clear Architect restart state", () => deps.architectRestartService.clear(repoRoot, taskSlug), warnings);
|
|
10
|
+
}
|
|
11
|
+
if (deps.roleContextRestartService) {
|
|
12
|
+
await bestEffort("Unable to clear Restart With Context state", () => deps.roleContextRestartService.clear(repoRoot, taskSlug), warnings);
|
|
13
|
+
}
|
|
9
14
|
await stopTaskRoleSessions(repoRoot, taskSlug, warnings);
|
|
10
15
|
await bestEffort("Unable to stop task translation runtime", () => deps.translationService.stopTask(getTaskRuntimeRepoRoot(task), taskSlug, { clearCache: true }), warnings);
|
|
11
16
|
await bestEffort("Unable to clear task round runtime", () => deps.roundService.stopTask(taskSlug), warnings);
|
|
@@ -85,6 +85,7 @@ ${renderRoleMemoryRules("coder")}
|
|
|
85
85
|
|
|
86
86
|
- In Docs-Only Flow, commit the documentation changes and submit \`.ai/vcm/handoffs/docs-update-report.md\` through \`vcm-artifact\` with the decision, changed and reviewed documents, evidence, checks, commit, and remaining documentation issues. Do not submit \`coder-completion.md\` for Docs-Only work.
|
|
87
87
|
- Submit \`.ai/vcm/handoffs/coder-completion.md\` before routing back to project-manager: write a candidate outside \`.ai/vcm\`, then run \`.ai/tools/vcm-artifact coder-completion --file <candidate> --mode draft|final\`. This file is the complete, self-contained current implementation completion evidence, not a log. Each revision must restate every Scaffold Manifest disposition, changed file, helper, deviation, generated-context result, baseline-test change, L0/L1 command and result, worker result, commit, and objective failure still needed to review the current implementation without a prior revision. Replace stale content instead of appending history.
|
|
88
|
+
- Maintain an incomplete \`coder-completion.md\` draft while implementation is in progress. Replace it after each completed module or worker result and after compile, L0, or L1 results so it always states completed work, remaining scaffold items, current validation, and current commits. The final submission replaces this draft.
|
|
88
89
|
- After committing the actual implementation state and before submitting a final \`coder-completion.md\`, run \`.ai/tools/check-scaffold-ledger --mode completion --completion <candidate>\`; submit the candidate only after it passes. An incomplete draft does not use completion mode.
|
|
89
90
|
- \`coder-completion.md\` must include \`Decision: ready_for_review | incomplete | failed\`.
|
|
90
91
|
- \`coder-completion.md\` must report every Scaffold Manifest item disposition in the fixed Scaffold Completion table, plus changed files, private helpers added, manifest deviations as report-only facts, generated context status, baseline tests added or updated, L0/L1 commands and results, worker commits and integration status when workers were used, and compile/typecheck or L0/L1 failures.
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { renderRoleMemoryRules } from "./role-memory.js";
|
|
2
|
+
import { GATE_ANALYSIS_FIELDS, GATE_ANALYSIS_HEADINGS } from "../../services/managed-artifact-validation.js";
|
|
3
|
+
function renderGateAnalysisSections() {
|
|
4
|
+
return Object.keys(GATE_ANALYSIS_HEADINGS)
|
|
5
|
+
.map((gate) => [
|
|
6
|
+
`<!-- Include ${GATE_ANALYSIS_HEADINGS[gate]} only for ${gate} gate. -->`,
|
|
7
|
+
`## ${GATE_ANALYSIS_HEADINGS[gate]}`,
|
|
8
|
+
"",
|
|
9
|
+
...GATE_ANALYSIS_FIELDS[gate].map((field) => `- ${field}:`)
|
|
10
|
+
].join("\n"))
|
|
11
|
+
.join("\n\n");
|
|
12
|
+
}
|
|
2
13
|
export function renderReviewerAgentRules() {
|
|
3
14
|
return `## Role
|
|
4
15
|
|
|
@@ -362,56 +373,7 @@ Summary: <one or two sentences>
|
|
|
362
373
|
Use this findings structure:
|
|
363
374
|
|
|
364
375
|
\`\`\`md
|
|
365
|
-
|
|
366
|
-
## Architecture Analysis
|
|
367
|
-
|
|
368
|
-
- Evidence Read:
|
|
369
|
-
- Architecture Brief Fit:
|
|
370
|
-
- End-To-End Flow:
|
|
371
|
-
- Scope Fit:
|
|
372
|
-
- Code Reality:
|
|
373
|
-
- Invalidated Assumptions:
|
|
374
|
-
- Existing-Class Completeness:
|
|
375
|
-
- Ownership:
|
|
376
|
-
- Data Flow:
|
|
377
|
-
- Lifecycle:
|
|
378
|
-
- Invariants:
|
|
379
|
-
- Boundaries And Public Surface:
|
|
380
|
-
- Failure Model:
|
|
381
|
-
- Coder Readiness:
|
|
382
|
-
|
|
383
|
-
<!-- Include Validation Analysis only for validation-adequacy gate. -->
|
|
384
|
-
## Validation Analysis
|
|
385
|
-
|
|
386
|
-
- Evidence Read:
|
|
387
|
-
- Changed Behavior And Risk:
|
|
388
|
-
- Coverage Mapping:
|
|
389
|
-
- Baseline Coverage:
|
|
390
|
-
- L2 Integration Coverage:
|
|
391
|
-
- L3 Trigger Assessment:
|
|
392
|
-
- L3 End-To-End Coverage:
|
|
393
|
-
- Boundary And Failure Coverage:
|
|
394
|
-
- Public Contract Coverage:
|
|
395
|
-
- Test Integrity:
|
|
396
|
-
- Test Infrastructure:
|
|
397
|
-
- Skips And Gaps:
|
|
398
|
-
- User Approval And Gap Disposition:
|
|
399
|
-
- Validation Readiness:
|
|
400
|
-
|
|
401
|
-
<!-- Include Code Diff Analysis only for code-diff gate. -->
|
|
402
|
-
## Code Diff Analysis
|
|
403
|
-
|
|
404
|
-
- Commit Range And Sources:
|
|
405
|
-
- Evidence Read:
|
|
406
|
-
- Changed Files And Symbols:
|
|
407
|
-
- Changed Behavior:
|
|
408
|
-
- Source Evidence Fit:
|
|
409
|
-
- Callers And Public Surface:
|
|
410
|
-
- State Lifecycle And Failure Paths:
|
|
411
|
-
- Coding Standards:
|
|
412
|
-
- Baseline Test Integrity:
|
|
413
|
-
- Generated Context And Durable Docs:
|
|
414
|
-
- Code Readiness:
|
|
376
|
+
${renderGateAnalysisSections()}
|
|
415
377
|
|
|
416
378
|
## Findings
|
|
417
379
|
|
|
@@ -430,54 +392,7 @@ Use this findings structure:
|
|
|
430
392
|
If there are no findings, write:
|
|
431
393
|
|
|
432
394
|
\`\`\`md
|
|
433
|
-
|
|
434
|
-
## Architecture Analysis
|
|
435
|
-
|
|
436
|
-
- Evidence Read:
|
|
437
|
-
- Architecture Brief Fit:
|
|
438
|
-
- End-To-End Flow:
|
|
439
|
-
- Scope Fit:
|
|
440
|
-
- Code Reality:
|
|
441
|
-
- Ownership:
|
|
442
|
-
- Data Flow:
|
|
443
|
-
- Lifecycle:
|
|
444
|
-
- Invariants:
|
|
445
|
-
- Boundaries And Public Surface:
|
|
446
|
-
- Failure Model:
|
|
447
|
-
- Coder Readiness:
|
|
448
|
-
|
|
449
|
-
<!-- Include Validation Analysis only for validation-adequacy gate. -->
|
|
450
|
-
## Validation Analysis
|
|
451
|
-
|
|
452
|
-
- Evidence Read:
|
|
453
|
-
- Changed Behavior And Risk:
|
|
454
|
-
- Coverage Mapping:
|
|
455
|
-
- Baseline Coverage:
|
|
456
|
-
- L2 Integration Coverage:
|
|
457
|
-
- L3 Trigger Assessment:
|
|
458
|
-
- L3 End-To-End Coverage:
|
|
459
|
-
- Boundary And Failure Coverage:
|
|
460
|
-
- Public Contract Coverage:
|
|
461
|
-
- Test Integrity:
|
|
462
|
-
- Test Infrastructure:
|
|
463
|
-
- Skips And Gaps:
|
|
464
|
-
- User Approval And Gap Disposition:
|
|
465
|
-
- Validation Readiness:
|
|
466
|
-
|
|
467
|
-
<!-- Include Code Diff Analysis only for code-diff gate. -->
|
|
468
|
-
## Code Diff Analysis
|
|
469
|
-
|
|
470
|
-
- Commit Range And Sources:
|
|
471
|
-
- Evidence Read:
|
|
472
|
-
- Changed Files And Symbols:
|
|
473
|
-
- Changed Behavior:
|
|
474
|
-
- Source Evidence Fit:
|
|
475
|
-
- Callers And Public Surface:
|
|
476
|
-
- State Lifecycle And Failure Paths:
|
|
477
|
-
- Coding Standards:
|
|
478
|
-
- Baseline Test Integrity:
|
|
479
|
-
- Generated Context And Durable Docs:
|
|
480
|
-
- Code Readiness:
|
|
395
|
+
${renderGateAnalysisSections()}
|
|
481
396
|
|
|
482
397
|
## Findings
|
|
483
398
|
|
|
@@ -151,6 +151,7 @@ Coverage Gap.
|
|
|
151
151
|
### Outputs
|
|
152
152
|
|
|
153
153
|
- Write \`.ai/vcm/handoffs/test-report.md\` with \`Test Result: pass|fail|incomplete\`, evidence reviewed, tests added or updated, coverage mapping, validation progress, commands run or checked, validation results, test-infrastructure status and evidence, failed expectations, reproduction steps, skipped checks with reasons, coverage gaps, blocking validation issues, and user approval evidence.
|
|
154
|
+
- Maintain an incomplete \`test-report.md\` draft while validation is in progress. Replace it after each completed validation level or long-running check and after changing tests or fixtures so it always states completed validation, remaining validation, current results, and current commits. The final submission replaces this draft.
|
|
154
155
|
- \`test-report.md\` must include this test-infrastructure section:
|
|
155
156
|
|
|
156
157
|
\`\`\`md
|
|
@@ -38,7 +38,7 @@ Use this structure:
|
|
|
38
38
|
- Evidence: <file paths, command names, logs, or repeated failure pattern>
|
|
39
39
|
- Suspected harness area: <skill, role definition, tool, routing, validation, bootstrap, or managed instruction>
|
|
40
40
|
- Impact: <who is affected and how>
|
|
41
|
-
- Urgency: low
|
|
41
|
+
- Urgency: low|medium|high
|
|
42
42
|
\`\`\`
|
|
43
43
|
|
|
44
44
|
## Constraints
|
|
@@ -214,7 +214,62 @@ export const ARTIFACT_DEFINITIONS = [
|
|
|
214
214
|
]
|
|
215
215
|
}
|
|
216
216
|
];
|
|
217
|
+
export const DYNAMIC_ARTIFACT_DEFINITIONS = [
|
|
218
|
+
{
|
|
219
|
+
kind: "route-message",
|
|
220
|
+
storage: "dynamic",
|
|
221
|
+
owner: ["project-manager", "architect", "coder", "tester"],
|
|
222
|
+
requiredHeadings: [],
|
|
223
|
+
allowedModes: ["final"]
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
kind: "coder-worker-report",
|
|
227
|
+
storage: "dynamic",
|
|
228
|
+
owner: "coder",
|
|
229
|
+
requiredHeadings: [
|
|
230
|
+
"Assigned Scope",
|
|
231
|
+
"Item Dispositions",
|
|
232
|
+
"Files Changed",
|
|
233
|
+
"Tests Added Or Updated",
|
|
234
|
+
"L0/L1 Checks",
|
|
235
|
+
"Commit",
|
|
236
|
+
"Skipped Assigned Checks",
|
|
237
|
+
"Objective Failures"
|
|
238
|
+
],
|
|
239
|
+
allowedModes: ["final"]
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
kind: "gate-review-report",
|
|
243
|
+
storage: "dynamic",
|
|
244
|
+
owner: "reviewer",
|
|
245
|
+
requiredHeadings: ["Findings"],
|
|
246
|
+
allowedModes: ["final"]
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
kind: "memory-proposal",
|
|
250
|
+
storage: "dynamic",
|
|
251
|
+
owner: ["project-manager", "architect", "coder", "tester", "reviewer"],
|
|
252
|
+
requiredHeadings: ["Add", "Update", "Remove"],
|
|
253
|
+
allowedModes: ["final"]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
kind: "harness-feedback",
|
|
257
|
+
storage: "dynamic",
|
|
258
|
+
owner: ["project-manager", "architect", "coder", "tester", "reviewer"],
|
|
259
|
+
requiredHeadings: [],
|
|
260
|
+
allowedModes: ["final"]
|
|
261
|
+
}
|
|
262
|
+
];
|
|
263
|
+
export const MANAGED_ARTIFACT_DEFINITIONS = [
|
|
264
|
+
...ARTIFACT_DEFINITIONS.map((definition) => ({
|
|
265
|
+
...definition,
|
|
266
|
+
storage: "handoff",
|
|
267
|
+
allowedModes: definition.kind === "workflow-progress" ? ["final"] : ["draft", "final"]
|
|
268
|
+
})),
|
|
269
|
+
...DYNAMIC_ARTIFACT_DEFINITIONS
|
|
270
|
+
];
|
|
217
271
|
const DEFINITION_BY_KIND = new Map(ARTIFACT_DEFINITIONS.map((definition) => [definition.kind, definition]));
|
|
272
|
+
const MANAGED_DEFINITION_BY_KIND = new Map(MANAGED_ARTIFACT_DEFINITIONS.map((definition) => [definition.kind, definition]));
|
|
218
273
|
export function getArtifactDefinition(kind) {
|
|
219
274
|
const definition = DEFINITION_BY_KIND.get(kind);
|
|
220
275
|
if (!definition) {
|
|
@@ -225,3 +280,16 @@ export function getArtifactDefinition(kind) {
|
|
|
225
280
|
export function isArtifactKind(value) {
|
|
226
281
|
return DEFINITION_BY_KIND.has(value);
|
|
227
282
|
}
|
|
283
|
+
export function getManagedArtifactDefinition(kind) {
|
|
284
|
+
const definition = MANAGED_DEFINITION_BY_KIND.get(kind);
|
|
285
|
+
if (!definition) {
|
|
286
|
+
throw new Error(`Unknown managed artifact kind: ${kind}`);
|
|
287
|
+
}
|
|
288
|
+
return definition;
|
|
289
|
+
}
|
|
290
|
+
export function isDynamicArtifactKind(value) {
|
|
291
|
+
return DYNAMIC_ARTIFACT_DEFINITIONS.some((definition) => definition.kind === value);
|
|
292
|
+
}
|
|
293
|
+
export function isManagedArtifactKind(value) {
|
|
294
|
+
return MANAGED_DEFINITION_BY_KIND.has(value);
|
|
295
|
+
}
|