vibe-coding-master 0.7.17 → 0.7.19
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 -2
- package/dist/backend/api/harness-routes.js +21 -4
- package/dist/backend/api/translation-worker-routes.js +15 -2
- package/dist/backend/cli/install-vcm-harness.js +3 -3
- package/dist/backend/server.js +2 -0
- package/dist/backend/services/app-settings-service.js +26 -2
- package/dist/backend/services/claude-hook-service.js +128 -54
- package/dist/backend/services/gate-review-service.js +52 -27
- package/dist/backend/services/harness-service.js +3 -3
- package/dist/backend/services/message-service.js +1 -1
- package/dist/backend/services/runtime-coordinator-service.js +10 -10
- package/dist/backend/services/session-service.js +44 -19
- package/dist/backend/services/terminal-interrupt-service.js +4 -1
- package/dist/backend/services/turn-reconciler-service.js +1 -0
- package/dist/backend/templates/handoff.js +4 -0
- package/dist/backend/templates/harness/gate-review.js +18 -1
- package/dist/backend/templates/harness/project-manager-agent.js +9 -1
- package/dist/backend/templates/harness/tester-agent.js +15 -7
- package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +1 -1
- package/dist/shared/types/app-settings.js +14 -0
- package/dist/shared/validation/artifact-check.js +29 -3
- package/dist-frontend/assets/index-CDkDHrWQ.js +97 -0
- package/dist-frontend/index.html +1 -1
- package/package.json +1 -1
- package/scripts/harness-tools/run-long-check +40 -1
- package/dist-frontend/assets/index-CStWyouh.js +0 -97
|
@@ -38,6 +38,7 @@ const VALIDATION_ANALYSIS_FIELDS = [
|
|
|
38
38
|
"Public Contract Coverage",
|
|
39
39
|
"Test Integrity",
|
|
40
40
|
"Skips And Gaps",
|
|
41
|
+
"User Approval And Gap Disposition",
|
|
41
42
|
"Validation Readiness"
|
|
42
43
|
];
|
|
43
44
|
const CODE_DIFF_ANALYSIS_FIELDS = [
|
|
@@ -61,6 +62,8 @@ const SOURCE_ARTIFACTS = {
|
|
|
61
62
|
],
|
|
62
63
|
"validation-adequacy": [
|
|
63
64
|
".ai/vcm/handoffs/architecture-plan.md",
|
|
65
|
+
".ai/vcm/handoffs/architect-debug.md",
|
|
66
|
+
".ai/vcm/handoffs/architecture-diagnosis.md",
|
|
64
67
|
".ai/vcm/handoffs/test-report.md",
|
|
65
68
|
"docs/TESTING.md"
|
|
66
69
|
],
|
|
@@ -278,6 +281,32 @@ export function createGateReviewService(deps) {
|
|
|
278
281
|
message: `${coreInput.path} is ${coreInput.status}.`
|
|
279
282
|
};
|
|
280
283
|
}
|
|
284
|
+
if (gate === "validation-adequacy") {
|
|
285
|
+
const validationReportError = await readValidationReportError(deps.fs, context.taskRepoRoot);
|
|
286
|
+
if (validationReportError) {
|
|
287
|
+
index = applyGateState(index, gate, {
|
|
288
|
+
status: "failed",
|
|
289
|
+
decision: undefined,
|
|
290
|
+
error: validationReportError,
|
|
291
|
+
exceptionReason: undefined,
|
|
292
|
+
requestId: undefined,
|
|
293
|
+
requestPath: undefined,
|
|
294
|
+
inputHash: undefined,
|
|
295
|
+
requestedAt: undefined,
|
|
296
|
+
startedAt: undefined,
|
|
297
|
+
completedAt: now(),
|
|
298
|
+
callbackStatus: "not_sent",
|
|
299
|
+
callbackError: undefined
|
|
300
|
+
}, now(), true);
|
|
301
|
+
await saveIndex(deps.fs, context.taskRepoRoot, index);
|
|
302
|
+
return {
|
|
303
|
+
status: "failed_to_start",
|
|
304
|
+
gate,
|
|
305
|
+
record: index.gates[gate],
|
|
306
|
+
message: validationReportError
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
281
310
|
const codeDiffInput = gate === "code-diff"
|
|
282
311
|
? await resolveCodeDiffInput(deps, context, record)
|
|
283
312
|
: undefined;
|
|
@@ -393,7 +422,6 @@ export function createGateReviewService(deps) {
|
|
|
393
422
|
return;
|
|
394
423
|
}
|
|
395
424
|
activeRuns.add(runKey);
|
|
396
|
-
let gateTurnStarted = false;
|
|
397
425
|
try {
|
|
398
426
|
const timestamp = now();
|
|
399
427
|
await updateGateRecord(context, gate, {
|
|
@@ -418,7 +446,7 @@ export function createGateReviewService(deps) {
|
|
|
418
446
|
}
|
|
419
447
|
const session = await ensureGateReviewerSession(context);
|
|
420
448
|
await submitTerminalInput(deps.runtime, session.id, prompt);
|
|
421
|
-
await deps.sessionService.markRoleActivityRunning(context.repoRoot, context.taskSlug, GATE_REVIEWER_ROLE);
|
|
449
|
+
await deps.sessionService.markRoleActivityRunning(context.repoRoot, context.taskSlug, GATE_REVIEWER_ROLE, session.id);
|
|
422
450
|
await deps.roundService.recordRoleTurnEvent({
|
|
423
451
|
repoRoot: context.repoRoot,
|
|
424
452
|
stateRepoRoot: context.taskRepoRoot,
|
|
@@ -427,14 +455,11 @@ export function createGateReviewService(deps) {
|
|
|
427
455
|
role: GATE_REVIEWER_ROLE,
|
|
428
456
|
eventName: "UserPromptSubmit"
|
|
429
457
|
});
|
|
430
|
-
gateTurnStarted = true;
|
|
431
458
|
const parsed = await waitForGateReport(deps.fs, context.taskRepoRoot, gate, requestId, now(), {
|
|
432
459
|
intervalMs: reportPollIntervalMs,
|
|
433
460
|
timeoutMs: reportTimeoutMs
|
|
434
461
|
});
|
|
435
462
|
const completedAt = now();
|
|
436
|
-
await recordGateReviewerTurnStop(context, gateTurnStarted);
|
|
437
|
-
gateTurnStarted = false;
|
|
438
463
|
await updateRequestStatus(deps.fs, context, requestId, "completed", {
|
|
439
464
|
completedAt,
|
|
440
465
|
decision: parsed.decision,
|
|
@@ -457,8 +482,6 @@ export function createGateReviewService(deps) {
|
|
|
457
482
|
catch (error) {
|
|
458
483
|
const timestamp = now();
|
|
459
484
|
const message = errorMessage(error);
|
|
460
|
-
await recordGateReviewerTurnStop(context, gateTurnStarted);
|
|
461
|
-
gateTurnStarted = false;
|
|
462
485
|
await updateRequestStatus(deps.fs, context, requestId, "failed", {
|
|
463
486
|
completedAt: timestamp,
|
|
464
487
|
error: message
|
|
@@ -478,20 +501,6 @@ export function createGateReviewService(deps) {
|
|
|
478
501
|
activeRuns.delete(runKey);
|
|
479
502
|
}
|
|
480
503
|
}
|
|
481
|
-
async function recordGateReviewerTurnStop(context, shouldRecord) {
|
|
482
|
-
if (!shouldRecord) {
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
await deps.sessionService.markRoleActivityIdle(context.repoRoot, context.taskSlug, GATE_REVIEWER_ROLE);
|
|
486
|
-
await deps.roundService.recordRoleTurnEvent({
|
|
487
|
-
repoRoot: context.repoRoot,
|
|
488
|
-
stateRepoRoot: context.taskRepoRoot,
|
|
489
|
-
stateRoot: context.stateRoot,
|
|
490
|
-
taskSlug: context.taskSlug,
|
|
491
|
-
role: GATE_REVIEWER_ROLE,
|
|
492
|
-
eventName: "Stop"
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
504
|
async function ensureGateReviewerSession(context) {
|
|
496
505
|
const existing = await deps.sessionService.getRoleSession(context.repoRoot, context.taskSlug, GATE_REVIEWER_ROLE);
|
|
497
506
|
if (existing?.status === "running" && deps.runtime.getSession(existing.id)) {
|
|
@@ -541,7 +550,7 @@ export function createGateReviewService(deps) {
|
|
|
541
550
|
});
|
|
542
551
|
try {
|
|
543
552
|
await submitTerminalInput(deps.runtime, session.id, prompt);
|
|
544
|
-
await deps.sessionService.markRoleActivityRunning(context.repoRoot, context.taskSlug, "project-manager");
|
|
553
|
+
await deps.sessionService.markRoleActivityRunning(context.repoRoot, context.taskSlug, "project-manager", session.id);
|
|
545
554
|
await deps.roundService.recordRoleTurnEvent({
|
|
546
555
|
repoRoot: context.repoRoot,
|
|
547
556
|
stateRepoRoot: context.taskRepoRoot,
|
|
@@ -986,6 +995,24 @@ async function readArchitectureBriefError(fs, taskRepoRoot) {
|
|
|
986
995
|
}
|
|
987
996
|
return undefined;
|
|
988
997
|
}
|
|
998
|
+
async function readValidationReportError(fs, taskRepoRoot) {
|
|
999
|
+
const relativePath = CORE_INPUT_ARTIFACTS["validation-adequacy"];
|
|
1000
|
+
if (!relativePath) {
|
|
1001
|
+
return undefined;
|
|
1002
|
+
}
|
|
1003
|
+
const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
|
|
1004
|
+
const content = await fs.pathExists(absolutePath) ? await fs.readText(absolutePath) : null;
|
|
1005
|
+
const check = checkMarkdownArtifact("test-report", relativePath, content);
|
|
1006
|
+
if (check.status === "ok") {
|
|
1007
|
+
return undefined;
|
|
1008
|
+
}
|
|
1009
|
+
const details = [
|
|
1010
|
+
check.missingHeadings.length > 0 ? `missing headings: ${check.missingHeadings.join(", ")}` : "",
|
|
1011
|
+
check.invalidFields.length > 0 ? check.invalidFields.join(" ") : "",
|
|
1012
|
+
check.hasPlaceholder ? "contains placeholders" : ""
|
|
1013
|
+
].filter(Boolean).join("; ");
|
|
1014
|
+
return `${relativePath} is incomplete and cannot start validation-adequacy review.${details ? ` ${details}` : ""}`;
|
|
1015
|
+
}
|
|
989
1016
|
async function readArchitectureEvidenceError(fs, taskRepoRoot) {
|
|
990
1017
|
const relativePath = ".ai/vcm/handoffs/architecture-evidence.md";
|
|
991
1018
|
const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
|
|
@@ -1214,16 +1241,14 @@ async function validateValidationApprovalInput(fs, taskRepoRoot) {
|
|
|
1214
1241
|
const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
|
|
1215
1242
|
const content = await fs.pathExists(absolutePath) ? await fs.readText(absolutePath) : null;
|
|
1216
1243
|
const check = checkMarkdownArtifact("test-report", relativePath, content);
|
|
1217
|
-
|
|
1218
|
-
if (check.status === "ok" && testResult === "pass") {
|
|
1244
|
+
if (check.status === "ok") {
|
|
1219
1245
|
return;
|
|
1220
1246
|
}
|
|
1221
1247
|
const details = [
|
|
1222
|
-
|
|
1248
|
+
`status=${check.status}`,
|
|
1223
1249
|
check.missingHeadings.length > 0 ? `missing headings: ${check.missingHeadings.join(", ")}` : "",
|
|
1224
1250
|
check.invalidFields.length > 0 ? check.invalidFields.join(" ") : "",
|
|
1225
|
-
check.hasPlaceholder ? "contains placeholders" : ""
|
|
1226
|
-
testResult !== "pass" ? "Test Result must be pass before approval." : ""
|
|
1251
|
+
check.hasPlaceholder ? "contains placeholders" : ""
|
|
1227
1252
|
].filter(Boolean).join("; ");
|
|
1228
1253
|
throw new VcmError({
|
|
1229
1254
|
code: "GATE_REVIEW_VALIDATION_INPUT_INCOMPLETE",
|
|
@@ -43,9 +43,9 @@ const LEGACY_CODEX_HARNESS_PATHS = [
|
|
|
43
43
|
".claude/skills/vcm-codex-review-gate",
|
|
44
44
|
".ai/tools/request-codex-review"
|
|
45
45
|
];
|
|
46
|
-
const VCM_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
|
|
47
|
-
const VCM_STOP_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --retry 2 --retry-delay 1 --retry-all-errors --connect-timeout 1 --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code/stop" -H "content-type: application/json" --data-binary @- || true'`;
|
|
48
|
-
const VCM_PERMISSION_REQUEST_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/permission-request" -H "content-type: application/json" --data-binary @- || true'`;
|
|
46
|
+
const VCM_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,runtimeSessionToken:process.env.VCM_RUNTIME_SESSION_TOKEN,event}));});'"'"' | curl -fsS --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
|
|
47
|
+
const VCM_STOP_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,runtimeSessionToken:process.env.VCM_RUNTIME_SESSION_TOKEN,event}));});'"'"' | curl -fsS --retry 2 --retry-delay 1 --retry-all-errors --connect-timeout 1 --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code/stop" -H "content-type: application/json" --data-binary @- || true'`;
|
|
48
|
+
const VCM_PERMISSION_REQUEST_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,runtimeSessionToken:process.env.VCM_RUNTIME_SESSION_TOKEN,event}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/permission-request" -H "content-type: application/json" --data-binary @- || true'`;
|
|
49
49
|
const VCM_BASH_GUARD_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ]; then exit 0; fi; guard=""; repo="$(git rev-parse --show-toplevel 2>/dev/null || true)"; if [ -n "$repo" ] && [ -f "$repo/.ai/tools/vcm-bash-guard" ]; then guard="$repo/.ai/tools/vcm-bash-guard"; else cwd="$(pwd -P 2>/dev/null || pwd)"; dir="$cwd"; while [ -n "$dir" ] && [ "$dir" != "/" ]; do if [ -f "$dir/.ai/tools/vcm-bash-guard" ]; then guard="$dir/.ai/tools/vcm-bash-guard"; break; fi; dir="$(dirname "$dir")"; done; if [ -z "$guard" ] && [ -n "\${CLAUDE_PROJECT_DIR:-}" ] && [ -f "\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-bash-guard" ]; then guard="\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-bash-guard"; fi; fi; [ -n "$guard" ] || exit 0; python3 "$guard" || exit 0'`;
|
|
50
50
|
const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
|
|
51
51
|
const VCM_AUTO_MEMORY_ENABLED = false;
|
|
@@ -118,7 +118,7 @@ export function createMessageService(deps) {
|
|
|
118
118
|
await submitTerminalInput(deps.runtime, session.id, renderMessageEnvelope(delivered), {
|
|
119
119
|
enterDelayMs: autoDispatchEnterDelayMs
|
|
120
120
|
});
|
|
121
|
-
await deps.sessionService.markRoleActivityRunning(input.repoRoot, input.taskSlug, routeFile.toRole);
|
|
121
|
+
await deps.sessionService.markRoleActivityRunning(input.repoRoot, input.taskSlug, routeFile.toRole, session.id);
|
|
122
122
|
if (routeFile.fromRole === PM_ROLE) {
|
|
123
123
|
await deps.taskWorkflowService?.recordPmDispatch({
|
|
124
124
|
taskRepoRoot: input.taskRepoRoot ?? input.repoRoot,
|
|
@@ -51,8 +51,8 @@ export function createRuntimeCoordinatorService(deps) {
|
|
|
51
51
|
.then((status) => status.initialized)
|
|
52
52
|
.catch(() => false);
|
|
53
53
|
await Promise.all([
|
|
54
|
-
reconcileHarnessEngineer(repoRoot, activeTask),
|
|
55
|
-
reconcileTranslator(repoRoot, activeTask, preferences.translationEnabled && harnessInitialized)
|
|
54
|
+
reconcileHarnessEngineer(repoRoot, activeTask, preferences.toolSessionDefaults["harness-engineer"]),
|
|
55
|
+
reconcileTranslator(repoRoot, activeTask, preferences.translationEnabled && harnessInitialized, preferences.toolSessionDefaults.translator)
|
|
56
56
|
]);
|
|
57
57
|
if (preferences.translationEnabled && harnessInitialized) {
|
|
58
58
|
await startConversationTranslationListeners(repoRoot, activeTask);
|
|
@@ -105,18 +105,18 @@ export function createRuntimeCoordinatorService(deps) {
|
|
|
105
105
|
}
|
|
106
106
|
return activeTasks[0] ?? null;
|
|
107
107
|
}
|
|
108
|
-
async function reconcileHarnessEngineer(repoRoot, task) {
|
|
108
|
+
async function reconcileHarnessEngineer(repoRoot, task, launchOptions) {
|
|
109
109
|
const existing = await deps.sessionService.getRoleSession(repoRoot, task.taskSlug, "harness-engineer");
|
|
110
110
|
if (!shouldAutoEnsureTaskToolSession(existing)) {
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
await ensureTaskToolRoleSession(repoRoot, task.taskSlug, "harness-engineer", {
|
|
114
|
-
permissionMode: existing?.permissionMode,
|
|
115
|
-
model: existing?.model,
|
|
116
|
-
effort: existing?.effort
|
|
114
|
+
permissionMode: existing?.permissionMode ?? launchOptions.permissionMode,
|
|
115
|
+
model: existing?.model ?? launchOptions.model,
|
|
116
|
+
effort: existing?.effort ?? launchOptions.effort
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
-
async function reconcileTranslator(repoRoot, task, enabled) {
|
|
119
|
+
async function reconcileTranslator(repoRoot, task, enabled, launchOptions) {
|
|
120
120
|
if (!enabled) {
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
@@ -125,9 +125,9 @@ export function createRuntimeCoordinatorService(deps) {
|
|
|
125
125
|
return;
|
|
126
126
|
}
|
|
127
127
|
await ensureTaskToolRoleSession(repoRoot, task.taskSlug, "translator", {
|
|
128
|
-
permissionMode: existing?.permissionMode,
|
|
129
|
-
model: existing?.model,
|
|
130
|
-
effort: existing?.effort
|
|
128
|
+
permissionMode: existing?.permissionMode ?? launchOptions.permissionMode,
|
|
129
|
+
model: existing?.model ?? launchOptions.model,
|
|
130
|
+
effort: existing?.effort ?? launchOptions.effort
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
function shouldAutoEnsureTaskToolSession(session) {
|
|
@@ -18,6 +18,7 @@ const PROJECT_TRANSLATOR_SCOPE = "__project__";
|
|
|
18
18
|
const PROJECT_HARNESS_ENGINEER_SCOPE = "__project_harness_engineer__";
|
|
19
19
|
const PROJECT_TOOL_CD_ENTER_DELAY_MS = 500;
|
|
20
20
|
const CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
|
|
21
|
+
const CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = "1";
|
|
21
22
|
// Project tool sessions launch a Claude Code TUI inside a PTY. The PTY reports
|
|
22
23
|
// "running" the instant it is spawned, which is earlier than the moment the TUI
|
|
23
24
|
// can actually accept pasted input. These bounds drive a quiescence-based
|
|
@@ -84,6 +85,7 @@ export function createSessionService(deps) {
|
|
|
84
85
|
...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride, input.appendSystemPrompt),
|
|
85
86
|
cwd: taskRepoRoot
|
|
86
87
|
};
|
|
88
|
+
const runtimeSessionToken = randomUUID();
|
|
87
89
|
const runtimeSession = await deps.runtime.createSession({
|
|
88
90
|
repoRoot,
|
|
89
91
|
taskSlug,
|
|
@@ -97,7 +99,8 @@ export function createSessionService(deps) {
|
|
|
97
99
|
VCM_TASK_REPO_ROOT: taskRepoRoot,
|
|
98
100
|
VCM_TASK_SLUG: taskSlug,
|
|
99
101
|
VCM_ROLE: role,
|
|
100
|
-
VCM_SESSION_ID: claudeSessionId || undefined
|
|
102
|
+
VCM_SESSION_ID: claudeSessionId || undefined,
|
|
103
|
+
VCM_RUNTIME_SESSION_TOKEN: runtimeSessionToken
|
|
101
104
|
}, modelEnvironment, buildUsageTelemetryEnvironment(deps.apiUrl, role, model)),
|
|
102
105
|
cols: input.cols,
|
|
103
106
|
rows: input.rows
|
|
@@ -106,6 +109,7 @@ export function createSessionService(deps) {
|
|
|
106
109
|
const harnessRevision = await readCurrentHarnessRevision(repoRoot);
|
|
107
110
|
const record = {
|
|
108
111
|
id: runtimeSession.id,
|
|
112
|
+
runtimeSessionToken,
|
|
109
113
|
claudeSessionId,
|
|
110
114
|
transcriptPath,
|
|
111
115
|
taskSlug,
|
|
@@ -208,6 +212,7 @@ export function createSessionService(deps) {
|
|
|
208
212
|
...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride),
|
|
209
213
|
cwd: launchCwd
|
|
210
214
|
};
|
|
215
|
+
const runtimeSessionToken = randomUUID();
|
|
211
216
|
const runtimeSession = await deps.runtime.createSession({
|
|
212
217
|
repoRoot,
|
|
213
218
|
taskSlug: PROJECT_TRANSLATOR_SCOPE,
|
|
@@ -224,7 +229,8 @@ export function createSessionService(deps) {
|
|
|
224
229
|
// active task; VCM_TASK_REPO_ROOT remains the active worktree.
|
|
225
230
|
VCM_TASK_SLUG: PROJECT_TRANSLATOR_SCOPE,
|
|
226
231
|
VCM_ROLE: TRANSLATOR_ROLE,
|
|
227
|
-
VCM_SESSION_ID: claudeSessionId || undefined
|
|
232
|
+
VCM_SESSION_ID: claudeSessionId || undefined,
|
|
233
|
+
VCM_RUNTIME_SESSION_TOKEN: runtimeSessionToken
|
|
228
234
|
}, modelEnvironment, buildUsageTelemetryEnvironment(deps.apiUrl, TRANSLATOR_ROLE, model)),
|
|
229
235
|
cols: input.cols,
|
|
230
236
|
rows: input.rows
|
|
@@ -233,6 +239,7 @@ export function createSessionService(deps) {
|
|
|
233
239
|
const harnessRevision = await readCurrentHarnessRevision(repoRoot);
|
|
234
240
|
const record = {
|
|
235
241
|
id: runtimeSession.id,
|
|
242
|
+
runtimeSessionToken,
|
|
236
243
|
claudeSessionId,
|
|
237
244
|
transcriptPath,
|
|
238
245
|
taskSlug: PROJECT_TRANSLATOR_SCOPE,
|
|
@@ -321,6 +328,7 @@ export function createSessionService(deps) {
|
|
|
321
328
|
...deps.claude.buildRoleStartCommand(HARNESS_ENGINEER_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride),
|
|
322
329
|
cwd: launchCwd
|
|
323
330
|
};
|
|
331
|
+
const runtimeSessionToken = randomUUID();
|
|
324
332
|
const runtimeSession = await deps.runtime.createSession({
|
|
325
333
|
repoRoot,
|
|
326
334
|
taskSlug: PROJECT_HARNESS_ENGINEER_SCOPE,
|
|
@@ -337,7 +345,8 @@ export function createSessionService(deps) {
|
|
|
337
345
|
// active task; VCM_TASK_REPO_ROOT remains the active worktree.
|
|
338
346
|
VCM_TASK_SLUG: PROJECT_HARNESS_ENGINEER_SCOPE,
|
|
339
347
|
VCM_ROLE: HARNESS_ENGINEER_ROLE,
|
|
340
|
-
VCM_SESSION_ID: claudeSessionId || undefined
|
|
348
|
+
VCM_SESSION_ID: claudeSessionId || undefined,
|
|
349
|
+
VCM_RUNTIME_SESSION_TOKEN: runtimeSessionToken
|
|
341
350
|
}, modelEnvironment, buildUsageTelemetryEnvironment(deps.apiUrl, HARNESS_ENGINEER_ROLE, model)),
|
|
342
351
|
cols: input.cols,
|
|
343
352
|
rows: input.rows
|
|
@@ -346,6 +355,7 @@ export function createSessionService(deps) {
|
|
|
346
355
|
const harnessRevision = await readCurrentHarnessRevision(repoRoot);
|
|
347
356
|
const record = {
|
|
348
357
|
id: runtimeSession.id,
|
|
358
|
+
runtimeSessionToken,
|
|
349
359
|
claudeSessionId,
|
|
350
360
|
transcriptPath,
|
|
351
361
|
taskSlug: PROJECT_HARNESS_ENGINEER_SCOPE,
|
|
@@ -516,6 +526,7 @@ export function createSessionService(deps) {
|
|
|
516
526
|
...deps.claude.buildRoleStartCommand(session.role, config.claudeCommand, permissionMode, session.claudeSessionId, true, model, effort, modelSettingsOverride),
|
|
517
527
|
cwd: launchCwd
|
|
518
528
|
};
|
|
529
|
+
const runtimeSessionToken = randomUUID();
|
|
519
530
|
const runtimeSession = await deps.runtime.createSession({
|
|
520
531
|
repoRoot,
|
|
521
532
|
taskSlug: normalizeProjectScopedRecordForPersistence(session).taskSlug,
|
|
@@ -529,7 +540,8 @@ export function createSessionService(deps) {
|
|
|
529
540
|
VCM_TASK_REPO_ROOT: targetCwd,
|
|
530
541
|
VCM_TASK_SLUG: normalizeProjectScopedRecordForPersistence(session).taskSlug,
|
|
531
542
|
VCM_ROLE: session.role,
|
|
532
|
-
VCM_SESSION_ID: session.claudeSessionId
|
|
543
|
+
VCM_SESSION_ID: session.claudeSessionId,
|
|
544
|
+
VCM_RUNTIME_SESSION_TOKEN: runtimeSessionToken
|
|
533
545
|
}, modelEnvironment, buildUsageTelemetryEnvironment(deps.apiUrl, session.role, model))
|
|
534
546
|
});
|
|
535
547
|
if ((await waitForSessionInputReady(runtimeSession.id)) === "exited") {
|
|
@@ -537,6 +549,7 @@ export function createSessionService(deps) {
|
|
|
537
549
|
return markProjectToolRuntimeUnavailable(repoRoot, {
|
|
538
550
|
...session,
|
|
539
551
|
id: runtimeSession.id,
|
|
552
|
+
runtimeSessionToken,
|
|
540
553
|
status: "crashed",
|
|
541
554
|
activityStatus: "idle",
|
|
542
555
|
command: startCommand.display,
|
|
@@ -554,6 +567,7 @@ export function createSessionService(deps) {
|
|
|
554
567
|
const resumed = {
|
|
555
568
|
...session,
|
|
556
569
|
id: runtimeSession.id,
|
|
570
|
+
runtimeSessionToken,
|
|
557
571
|
status: runtimeSession.status,
|
|
558
572
|
activityStatus: "idle",
|
|
559
573
|
command: startCommand.display,
|
|
@@ -680,9 +694,9 @@ export function createSessionService(deps) {
|
|
|
680
694
|
const view = toRoleSessionRecordView(record, deps.runtime);
|
|
681
695
|
return view ? withHarnessRevisionView(repoRoot, view) : undefined;
|
|
682
696
|
}
|
|
683
|
-
async function markTaskRoleActivityIdle(repoRoot, taskSlug, role) {
|
|
697
|
+
async function markTaskRoleActivityIdle(repoRoot, taskSlug, role, expectedSessionId) {
|
|
684
698
|
const current = await getTaskRoleSessionView(repoRoot, taskSlug, role);
|
|
685
|
-
if (!current) {
|
|
699
|
+
if (!current || (expectedSessionId && current.id !== expectedSessionId)) {
|
|
686
700
|
return undefined;
|
|
687
701
|
}
|
|
688
702
|
const timestamp = now();
|
|
@@ -795,7 +809,7 @@ export function createSessionService(deps) {
|
|
|
795
809
|
},
|
|
796
810
|
async recordProjectTranslatorHookEvent(repoRoot, input) {
|
|
797
811
|
const current = await this.getProjectTranslatorSession(repoRoot);
|
|
798
|
-
if (!current) {
|
|
812
|
+
if (!current || !matchesRoleHookSession(current, input)) {
|
|
799
813
|
return undefined;
|
|
800
814
|
}
|
|
801
815
|
const timestamp = now();
|
|
@@ -921,7 +935,7 @@ export function createSessionService(deps) {
|
|
|
921
935
|
},
|
|
922
936
|
async recordProjectHarnessEngineerHookEvent(repoRoot, input) {
|
|
923
937
|
const current = await this.getProjectHarnessEngineerSession(repoRoot);
|
|
924
|
-
if (!current) {
|
|
938
|
+
if (!current || !matchesRoleHookSession(current, input)) {
|
|
925
939
|
return undefined;
|
|
926
940
|
}
|
|
927
941
|
const timestamp = now();
|
|
@@ -1040,7 +1054,7 @@ export function createSessionService(deps) {
|
|
|
1040
1054
|
},
|
|
1041
1055
|
async recordRoleHookEvent(repoRoot, input) {
|
|
1042
1056
|
const current = await this.getRoleSession(repoRoot, input.taskSlug, input.role);
|
|
1043
|
-
if (!current ||
|
|
1057
|
+
if (!current || !matchesRoleHookSession(current, input)) {
|
|
1044
1058
|
return undefined;
|
|
1045
1059
|
}
|
|
1046
1060
|
const timestamp = now();
|
|
@@ -1072,7 +1086,9 @@ export function createSessionService(deps) {
|
|
|
1072
1086
|
eventName: input.eventName,
|
|
1073
1087
|
sessionId: input.claudeSessionId,
|
|
1074
1088
|
transcriptPath: input.transcriptPath,
|
|
1075
|
-
cwd: input.cwd
|
|
1089
|
+
cwd: input.cwd,
|
|
1090
|
+
runtimeSessionId: input.runtimeSessionId,
|
|
1091
|
+
runtimeSessionToken: input.runtimeSessionToken
|
|
1076
1092
|
});
|
|
1077
1093
|
},
|
|
1078
1094
|
async markTerminalSessionActivityIdle(repoRoot, sessionId) {
|
|
@@ -1095,11 +1111,11 @@ export function createSessionService(deps) {
|
|
|
1095
1111
|
? markProjectToolActivityIdle(repoRoot, current, persistHarnessEngineerSession)
|
|
1096
1112
|
: undefined;
|
|
1097
1113
|
}
|
|
1098
|
-
return markTaskRoleActivityIdle(repoRoot, taskSlug, role);
|
|
1114
|
+
return markTaskRoleActivityIdle(repoRoot, taskSlug, role, sessionId);
|
|
1099
1115
|
},
|
|
1100
|
-
async markRoleActivityRunning(repoRoot, taskSlug, role) {
|
|
1116
|
+
async markRoleActivityRunning(repoRoot, taskSlug, role, expectedSessionId) {
|
|
1101
1117
|
const current = await this.getRoleSession(repoRoot, taskSlug, role);
|
|
1102
|
-
if (!current) {
|
|
1118
|
+
if (!current || (expectedSessionId && current.id !== expectedSessionId)) {
|
|
1103
1119
|
return undefined;
|
|
1104
1120
|
}
|
|
1105
1121
|
const timestamp = now();
|
|
@@ -1155,7 +1171,18 @@ function toRoleSessionRecordView(record, runtime) {
|
|
|
1155
1171
|
exitCode: runtimeSession.exitCode
|
|
1156
1172
|
};
|
|
1157
1173
|
}
|
|
1158
|
-
function matchesRoleHookSession(record, input) {
|
|
1174
|
+
export function matchesRoleHookSession(record, input) {
|
|
1175
|
+
if (!record.claudeSessionId
|
|
1176
|
+
&& !record.transcriptPath
|
|
1177
|
+
&& input.eventName !== "UserPromptSubmit") {
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
if (input.runtimeSessionId) {
|
|
1181
|
+
return record.id === input.runtimeSessionId;
|
|
1182
|
+
}
|
|
1183
|
+
if (record.runtimeSessionToken) {
|
|
1184
|
+
return record.runtimeSessionToken === input.runtimeSessionToken;
|
|
1185
|
+
}
|
|
1159
1186
|
if (!record.claudeSessionId && !record.transcriptPath) {
|
|
1160
1187
|
return input.eventName === "UserPromptSubmit";
|
|
1161
1188
|
}
|
|
@@ -1165,9 +1192,6 @@ function matchesRoleHookSession(record, input) {
|
|
|
1165
1192
|
if (input.transcriptPath && record.transcriptPath === input.transcriptPath) {
|
|
1166
1193
|
return true;
|
|
1167
1194
|
}
|
|
1168
|
-
if (!input.sessionId && !input.transcriptPath) {
|
|
1169
|
-
return true;
|
|
1170
|
-
}
|
|
1171
1195
|
return false;
|
|
1172
1196
|
}
|
|
1173
1197
|
function nextHookSessionIdentity(current, input) {
|
|
@@ -1209,7 +1233,7 @@ function isSessionMissingError(error) {
|
|
|
1209
1233
|
error.code === "SESSION_MISSING";
|
|
1210
1234
|
}
|
|
1211
1235
|
function withoutRuntimeOnlySessionFields(session) {
|
|
1212
|
-
const { pid: _pid, ...persisted } = session;
|
|
1236
|
+
const { pid: _pid, runtimeSessionToken: _runtimeSessionToken, ...persisted } = session;
|
|
1213
1237
|
return persisted;
|
|
1214
1238
|
}
|
|
1215
1239
|
function defaultIsProcessAlive(pid) {
|
|
@@ -1542,7 +1566,8 @@ function withClaudeCodeRuntimeEnv(env, modelEnvironment = {}, telemetryEnvironme
|
|
|
1542
1566
|
...env,
|
|
1543
1567
|
...modelEnvironment,
|
|
1544
1568
|
...telemetryEnvironment,
|
|
1545
|
-
CLAUDE_CODE_DISABLE_AUTO_MEMORY
|
|
1569
|
+
CLAUDE_CODE_DISABLE_AUTO_MEMORY,
|
|
1570
|
+
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS
|
|
1546
1571
|
};
|
|
1547
1572
|
}
|
|
1548
1573
|
function buildUsageTelemetryEnvironment(apiUrl, role, model) {
|
|
@@ -11,7 +11,10 @@ export function createTerminalInterruptService(deps) {
|
|
|
11
11
|
if (!repoRoot) {
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
|
-
await deps.sessionService.markTerminalSessionActivityIdle(repoRoot, sessionId);
|
|
14
|
+
const session = await deps.sessionService.markTerminalSessionActivityIdle(repoRoot, sessionId);
|
|
15
|
+
if (!session) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
15
18
|
if (!isVcmRoleName(terminalSession.role)) {
|
|
16
19
|
return;
|
|
17
20
|
}
|
|
@@ -96,6 +96,7 @@ function buildReconciledHook(taskSlug, role, session, eventName, evidence) {
|
|
|
96
96
|
...(session?.claudeSessionId ? { session_id: session.claudeSessionId } : {}),
|
|
97
97
|
...(session?.transcriptPath ? { transcript_path: session.transcriptPath } : {}),
|
|
98
98
|
...(session?.cwd ? { cwd: session.cwd } : {}),
|
|
99
|
+
...(session?.id ? { vcm_runtime_session_id: session.id } : {}),
|
|
99
100
|
vcm_reconciled: true,
|
|
100
101
|
...evidence
|
|
101
102
|
}
|
|
@@ -110,6 +110,8 @@ production entry points needed to verify what those tests exercise. Read the
|
|
|
110
110
|
relevant architect/coder definitions and \`.ai/vcm/handoffs/architecture-plan.md\`
|
|
111
111
|
when the active flow produced an architecture plan. Read
|
|
112
112
|
\`.ai/generated/public-surface.json\` when public contracts changed.
|
|
113
|
+
When the report contains an approved Coverage Gap, also read the relevant
|
|
114
|
+
Architect Debug and Architecture Diagnosis evidence.
|
|
113
115
|
|
|
114
116
|
Reconstruct the accepted validation target, observable behavior, and risks
|
|
115
117
|
from the active flow evidence and current implementation. Treat Tester
|
|
@@ -138,7 +140,20 @@ actual tests, validation level does not match risk, an important behavior has
|
|
|
138
140
|
no concrete coverage mapping, a required check was skipped, required coverage
|
|
139
141
|
is unavailable, or a current-task coverage gap remains. A concrete risk-based
|
|
140
142
|
reason may show that integration or E2E coverage is unnecessary; unavailable
|
|
141
|
-
required coverage is not an approval reason.
|
|
143
|
+
required coverage without exact user approval is not an approval reason.
|
|
144
|
+
|
|
145
|
+
Treat every unresolved required-coverage item as gate-blocking unless
|
|
146
|
+
\`test-report.md\` contains the user's exact approval routed by project-manager.
|
|
147
|
+
Verify that Architect Debug and Architecture Diagnosis were completed before
|
|
148
|
+
user acceptance was requested, the approved gap exactly matches the final
|
|
149
|
+
Tester evidence, the affected behavior and remaining risk are stated
|
|
150
|
+
completely, and any new or changed \`Known Testing Gaps\` entry matches the
|
|
151
|
+
approved durable limitation. Project-manager, Architect, or Tester judgment is
|
|
152
|
+
not user authorization.
|
|
153
|
+
|
|
154
|
+
An approved gap keeps \`Test Result: fail\`. Gate approval means the validation
|
|
155
|
+
evidence and exact user exception are complete and consistent; it does not
|
|
156
|
+
convert the result to \`pass\` or independently accept the risk.
|
|
142
157
|
|
|
143
158
|
## Code Diff Gate
|
|
144
159
|
|
|
@@ -246,6 +261,7 @@ Use this findings structure:
|
|
|
246
261
|
- Public Contract Coverage:
|
|
247
262
|
- Test Integrity:
|
|
248
263
|
- Skips And Gaps:
|
|
264
|
+
- User Approval And Gap Disposition:
|
|
249
265
|
- Validation Readiness:
|
|
250
266
|
|
|
251
267
|
<!-- Include Code Diff Analysis only for code-diff gate. -->
|
|
@@ -306,6 +322,7 @@ If there are no findings, write:
|
|
|
306
322
|
- Public Contract Coverage:
|
|
307
323
|
- Test Integrity:
|
|
308
324
|
- Skips And Gaps:
|
|
325
|
+
- User Approval And Gap Disposition:
|
|
309
326
|
- Validation Readiness:
|
|
310
327
|
|
|
311
328
|
<!-- Include Code Diff Analysis only for code-diff gate. -->
|
|
@@ -180,7 +180,7 @@ Architecture Diagnosis Mode must run before another Debug Mode fix or Coder disp
|
|
|
180
180
|
#### Allowed Branches
|
|
181
181
|
|
|
182
182
|
- **Code-Diff Revision:** If the code-diff Gate returns \`request_changes\`, route the report to Architecture Diagnosis Mode and rerun \`code-diff --source architect-diagnosis\` after correction.
|
|
183
|
-
- **Tester Failure:** If Tester returns \`Test Result: fail\` for the Diagnosis implementation, pause and report to the user.
|
|
183
|
+
- **Tester Failure:** If Tester returns \`Test Result: fail\` for the Diagnosis implementation, pause and report to the user. If required validation remains unavailable, ask whether the user explicitly approves retaining that exact Coverage Gap.
|
|
184
184
|
|
|
185
185
|
#### Successful Exit
|
|
186
186
|
|
|
@@ -195,6 +195,14 @@ After Tester Failure, PM should summarize:
|
|
|
195
195
|
- what the Architect diagnosed
|
|
196
196
|
- what Tester still found wrong
|
|
197
197
|
|
|
198
|
+
If the user approves the exact gap, record the approval verbatim and route
|
|
199
|
+
Tester to add the approved \`Coverage Gaps\` entry and, when applicable, the
|
|
200
|
+
durable \`Known Testing Gaps\` entry. Then run the validation-adequacy Gate and
|
|
201
|
+
continue using the recorded user-approved exception.
|
|
202
|
+
|
|
203
|
+
Without explicit user approval, the gap remains blocking and the workflow
|
|
204
|
+
stays paused.
|
|
205
|
+
|
|
198
206
|
### Docs-Only Flow
|
|
199
207
|
|
|
200
208
|
Use Docs-Only Flow when the accepted task changes Architect-owned project documentation and does not require production-code, test-code, runtime-behavior, public-contract, dependency, or Harness changes.
|