vibe-coding-master 0.7.16 → 0.7.18
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 +19 -2
- package/dist/backend/adapters/claude-adapter.js +4 -1
- package/dist/backend/api/harness-routes.js +21 -4
- package/dist/backend/api/session-routes.js +5 -0
- package/dist/backend/api/translation-worker-routes.js +15 -2
- package/dist/backend/api/usage-analytics-routes.js +31 -0
- package/dist/backend/cli/install-vcm-harness.js +54 -5
- package/dist/backend/server.js +27 -5
- package/dist/backend/services/app-settings-service.js +26 -2
- package/dist/backend/services/architect-restart-service.js +136 -0
- package/dist/backend/services/claude-hook-service.js +134 -54
- package/dist/backend/services/gate-review-service.js +92 -27
- package/dist/backend/services/harness-service.js +54 -7
- package/dist/backend/services/message-service.js +6 -1
- package/dist/backend/services/runtime-coordinator-service.js +10 -10
- package/dist/backend/services/session-service.js +78 -25
- package/dist/backend/services/task-close-service.js +1 -0
- package/dist/backend/services/terminal-interrupt-service.js +4 -1
- package/dist/backend/services/turn-reconciler-service.js +1 -0
- package/dist/backend/services/usage-analytics-service.js +346 -0
- package/dist/backend/templates/handoff.js +4 -0
- package/dist/backend/templates/harness/architect-agent.js +18 -10
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +23 -0
- package/dist/backend/templates/harness/claude-root.js +1 -1
- package/dist/backend/templates/harness/gate-review.js +25 -3
- package/dist/backend/templates/harness/project-manager-agent.js +10 -2
- package/dist/backend/templates/harness/restart-architect-skill.js +75 -0
- package/dist/backend/templates/harness/tester-agent.js +15 -7
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +31 -5
- package/dist/backend/templates/harness/vcm-route-message-skill.js +3 -3
- package/dist/shared/types/app-settings.js +14 -0
- package/dist/shared/types/usage-analytics.js +1 -0
- package/dist/shared/validation/artifact-check.js +29 -3
- package/dist-frontend/assets/{index-BAE_pjXJ.js → index-CDkDHrWQ.js} +43 -43
- package/dist-frontend/assets/{index-CiEUp9Si.css → index-Ci7z8tW3.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
|
@@ -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 = [
|
|
@@ -56,10 +57,13 @@ const CODE_DIFF_ANALYSIS_FIELDS = [
|
|
|
56
57
|
const SOURCE_ARTIFACTS = {
|
|
57
58
|
"architecture-plan": [
|
|
58
59
|
".ai/vcm/handoffs/architecture-brief.md",
|
|
60
|
+
".ai/vcm/handoffs/architecture-evidence.md",
|
|
59
61
|
".ai/vcm/handoffs/architecture-plan.md"
|
|
60
62
|
],
|
|
61
63
|
"validation-adequacy": [
|
|
62
64
|
".ai/vcm/handoffs/architecture-plan.md",
|
|
65
|
+
".ai/vcm/handoffs/architect-debug.md",
|
|
66
|
+
".ai/vcm/handoffs/architecture-diagnosis.md",
|
|
63
67
|
".ai/vcm/handoffs/test-report.md",
|
|
64
68
|
"docs/TESTING.md"
|
|
65
69
|
],
|
|
@@ -228,6 +232,30 @@ export function createGateReviewService(deps) {
|
|
|
228
232
|
message: architectureBriefError
|
|
229
233
|
};
|
|
230
234
|
}
|
|
235
|
+
const architectureEvidenceError = await readArchitectureEvidenceError(deps.fs, context.taskRepoRoot);
|
|
236
|
+
if (architectureEvidenceError) {
|
|
237
|
+
index = applyGateState(index, gate, {
|
|
238
|
+
status: "failed",
|
|
239
|
+
decision: undefined,
|
|
240
|
+
error: architectureEvidenceError,
|
|
241
|
+
exceptionReason: undefined,
|
|
242
|
+
requestId: undefined,
|
|
243
|
+
requestPath: undefined,
|
|
244
|
+
inputHash: undefined,
|
|
245
|
+
requestedAt: undefined,
|
|
246
|
+
startedAt: undefined,
|
|
247
|
+
completedAt: now(),
|
|
248
|
+
callbackStatus: "not_sent",
|
|
249
|
+
callbackError: undefined
|
|
250
|
+
}, now(), true);
|
|
251
|
+
await saveIndex(deps.fs, context.taskRepoRoot, index);
|
|
252
|
+
return {
|
|
253
|
+
status: "failed_to_start",
|
|
254
|
+
gate,
|
|
255
|
+
record: index.gates[gate],
|
|
256
|
+
message: architectureEvidenceError
|
|
257
|
+
};
|
|
258
|
+
}
|
|
231
259
|
}
|
|
232
260
|
const coreInput = await readCoreInputArtifact(deps.fs, context.taskRepoRoot, gate);
|
|
233
261
|
if (coreInput && coreInput.status !== "ready") {
|
|
@@ -253,6 +281,32 @@ export function createGateReviewService(deps) {
|
|
|
253
281
|
message: `${coreInput.path} is ${coreInput.status}.`
|
|
254
282
|
};
|
|
255
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
|
+
}
|
|
256
310
|
const codeDiffInput = gate === "code-diff"
|
|
257
311
|
? await resolveCodeDiffInput(deps, context, record)
|
|
258
312
|
: undefined;
|
|
@@ -368,7 +422,6 @@ export function createGateReviewService(deps) {
|
|
|
368
422
|
return;
|
|
369
423
|
}
|
|
370
424
|
activeRuns.add(runKey);
|
|
371
|
-
let gateTurnStarted = false;
|
|
372
425
|
try {
|
|
373
426
|
const timestamp = now();
|
|
374
427
|
await updateGateRecord(context, gate, {
|
|
@@ -393,7 +446,7 @@ export function createGateReviewService(deps) {
|
|
|
393
446
|
}
|
|
394
447
|
const session = await ensureGateReviewerSession(context);
|
|
395
448
|
await submitTerminalInput(deps.runtime, session.id, prompt);
|
|
396
|
-
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);
|
|
397
450
|
await deps.roundService.recordRoleTurnEvent({
|
|
398
451
|
repoRoot: context.repoRoot,
|
|
399
452
|
stateRepoRoot: context.taskRepoRoot,
|
|
@@ -402,14 +455,11 @@ export function createGateReviewService(deps) {
|
|
|
402
455
|
role: GATE_REVIEWER_ROLE,
|
|
403
456
|
eventName: "UserPromptSubmit"
|
|
404
457
|
});
|
|
405
|
-
gateTurnStarted = true;
|
|
406
458
|
const parsed = await waitForGateReport(deps.fs, context.taskRepoRoot, gate, requestId, now(), {
|
|
407
459
|
intervalMs: reportPollIntervalMs,
|
|
408
460
|
timeoutMs: reportTimeoutMs
|
|
409
461
|
});
|
|
410
462
|
const completedAt = now();
|
|
411
|
-
await recordGateReviewerTurnStop(context, gateTurnStarted);
|
|
412
|
-
gateTurnStarted = false;
|
|
413
463
|
await updateRequestStatus(deps.fs, context, requestId, "completed", {
|
|
414
464
|
completedAt,
|
|
415
465
|
decision: parsed.decision,
|
|
@@ -432,8 +482,6 @@ export function createGateReviewService(deps) {
|
|
|
432
482
|
catch (error) {
|
|
433
483
|
const timestamp = now();
|
|
434
484
|
const message = errorMessage(error);
|
|
435
|
-
await recordGateReviewerTurnStop(context, gateTurnStarted);
|
|
436
|
-
gateTurnStarted = false;
|
|
437
485
|
await updateRequestStatus(deps.fs, context, requestId, "failed", {
|
|
438
486
|
completedAt: timestamp,
|
|
439
487
|
error: message
|
|
@@ -453,20 +501,6 @@ export function createGateReviewService(deps) {
|
|
|
453
501
|
activeRuns.delete(runKey);
|
|
454
502
|
}
|
|
455
503
|
}
|
|
456
|
-
async function recordGateReviewerTurnStop(context, shouldRecord) {
|
|
457
|
-
if (!shouldRecord) {
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
await deps.sessionService.markRoleActivityIdle(context.repoRoot, context.taskSlug, GATE_REVIEWER_ROLE);
|
|
461
|
-
await deps.roundService.recordRoleTurnEvent({
|
|
462
|
-
repoRoot: context.repoRoot,
|
|
463
|
-
stateRepoRoot: context.taskRepoRoot,
|
|
464
|
-
stateRoot: context.stateRoot,
|
|
465
|
-
taskSlug: context.taskSlug,
|
|
466
|
-
role: GATE_REVIEWER_ROLE,
|
|
467
|
-
eventName: "Stop"
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
504
|
async function ensureGateReviewerSession(context) {
|
|
471
505
|
const existing = await deps.sessionService.getRoleSession(context.repoRoot, context.taskSlug, GATE_REVIEWER_ROLE);
|
|
472
506
|
if (existing?.status === "running" && deps.runtime.getSession(existing.id)) {
|
|
@@ -516,7 +550,7 @@ export function createGateReviewService(deps) {
|
|
|
516
550
|
});
|
|
517
551
|
try {
|
|
518
552
|
await submitTerminalInput(deps.runtime, session.id, prompt);
|
|
519
|
-
await deps.sessionService.markRoleActivityRunning(context.repoRoot, context.taskSlug, "project-manager");
|
|
553
|
+
await deps.sessionService.markRoleActivityRunning(context.repoRoot, context.taskSlug, "project-manager", session.id);
|
|
520
554
|
await deps.roundService.recordRoleTurnEvent({
|
|
521
555
|
repoRoot: context.repoRoot,
|
|
522
556
|
stateRepoRoot: context.taskRepoRoot,
|
|
@@ -961,6 +995,39 @@ async function readArchitectureBriefError(fs, taskRepoRoot) {
|
|
|
961
995
|
}
|
|
962
996
|
return undefined;
|
|
963
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
|
+
}
|
|
1016
|
+
async function readArchitectureEvidenceError(fs, taskRepoRoot) {
|
|
1017
|
+
const relativePath = ".ai/vcm/handoffs/architecture-evidence.md";
|
|
1018
|
+
const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
|
|
1019
|
+
if (!await fs.pathExists(absolutePath)) {
|
|
1020
|
+
return `${relativePath} is missing. Complete architecture evidence before requesting architecture-plan review.`;
|
|
1021
|
+
}
|
|
1022
|
+
const content = await fs.readText(absolutePath);
|
|
1023
|
+
if (content.trim().length === 0) {
|
|
1024
|
+
return `${relativePath} is empty. Complete architecture evidence before requesting architecture-plan review.`;
|
|
1025
|
+
}
|
|
1026
|
+
if (!/^\s*Architecture Evidence Status\s*:\s*complete\s*$/im.test(content)) {
|
|
1027
|
+
return `${relativePath} is incomplete. Finish current-worktree evidence before requesting architecture-plan review.`;
|
|
1028
|
+
}
|
|
1029
|
+
return undefined;
|
|
1030
|
+
}
|
|
964
1031
|
async function commandStdout(runner, cwd, args) {
|
|
965
1032
|
const result = await runner.run("git", args, { cwd });
|
|
966
1033
|
return result.exitCode === 0 ? result.stdout : "";
|
|
@@ -1174,16 +1241,14 @@ async function validateValidationApprovalInput(fs, taskRepoRoot) {
|
|
|
1174
1241
|
const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
|
|
1175
1242
|
const content = await fs.pathExists(absolutePath) ? await fs.readText(absolutePath) : null;
|
|
1176
1243
|
const check = checkMarkdownArtifact("test-report", relativePath, content);
|
|
1177
|
-
|
|
1178
|
-
if (check.status === "ok" && testResult === "pass") {
|
|
1244
|
+
if (check.status === "ok") {
|
|
1179
1245
|
return;
|
|
1180
1246
|
}
|
|
1181
1247
|
const details = [
|
|
1182
|
-
|
|
1248
|
+
`status=${check.status}`,
|
|
1183
1249
|
check.missingHeadings.length > 0 ? `missing headings: ${check.missingHeadings.join(", ")}` : "",
|
|
1184
1250
|
check.invalidFields.length > 0 ? check.invalidFields.join(" ") : "",
|
|
1185
|
-
check.hasPlaceholder ? "contains placeholders" : ""
|
|
1186
|
-
testResult !== "pass" ? "Test Result must be pass before approval." : ""
|
|
1251
|
+
check.hasPlaceholder ? "contains placeholders" : ""
|
|
1187
1252
|
].filter(Boolean).join("; ");
|
|
1188
1253
|
throw new VcmError({
|
|
1189
1254
|
code: "GATE_REVIEW_VALIDATION_INPUT_INCOMPLETE",
|
|
@@ -4,6 +4,7 @@ import { promisify } from "node:util";
|
|
|
4
4
|
import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
|
|
5
5
|
import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
|
|
6
6
|
import { renderCoderWorkerHarnessRules } from "../templates/harness/coder-worker-agent.js";
|
|
7
|
+
import { renderArchitectScaffoldWorkerHarnessRules } from "../templates/harness/architect-scaffold-worker-agent.js";
|
|
7
8
|
import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTranslatorAgentRules, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
|
|
8
9
|
import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
|
|
9
10
|
import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
|
|
@@ -24,6 +25,7 @@ import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-
|
|
|
24
25
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
25
26
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
26
27
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
28
|
+
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
27
29
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
28
30
|
import { VcmError } from "../errors.js";
|
|
29
31
|
import { bumpHarnessRevision, readHarnessRevisionState } from "./harness-revision.js";
|
|
@@ -41,9 +43,9 @@ const LEGACY_CODEX_HARNESS_PATHS = [
|
|
|
41
43
|
".claude/skills/vcm-codex-review-gate",
|
|
42
44
|
".ai/tools/request-codex-review"
|
|
43
45
|
];
|
|
44
|
-
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'`;
|
|
45
|
-
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'`;
|
|
46
|
-
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'`;
|
|
47
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'`;
|
|
48
50
|
const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
|
|
49
51
|
const VCM_AUTO_MEMORY_ENABLED = false;
|
|
@@ -172,6 +174,14 @@ const HARNESS_FILES = [
|
|
|
172
174
|
ownership: "whole-file",
|
|
173
175
|
renderRules: renderVcmProposeMemorySkillRules
|
|
174
176
|
},
|
|
177
|
+
{
|
|
178
|
+
kind: "skill-restart-architect",
|
|
179
|
+
path: ".claude/skills/restart-architect/SKILL.md",
|
|
180
|
+
title: "Restart Architect Skill",
|
|
181
|
+
frontmatter: renderSkillFrontmatter("restart-architect", "Use after Architect completes and commits architecture planning and scaffold work."),
|
|
182
|
+
ownership: "whole-file",
|
|
183
|
+
renderRules: renderRestartArchitectSkillRules
|
|
184
|
+
},
|
|
175
185
|
{
|
|
176
186
|
kind: "agent-gate-reviewer",
|
|
177
187
|
path: ".claude/agents/gate-reviewer.md",
|
|
@@ -202,6 +212,13 @@ const HARNESS_FILES = [
|
|
|
202
212
|
frontmatter: renderAgentFrontmatter("vcm-coder-worker", "Bounded VCM implementation worker for assigned modules, files, and VCM:CODE markers from Coder.", { model: "inherit" }),
|
|
203
213
|
renderRules: renderCoderWorkerHarnessRules
|
|
204
214
|
},
|
|
215
|
+
{
|
|
216
|
+
kind: "agent-architect-scaffold-worker",
|
|
217
|
+
path: ".claude/agents/vcm-architect-scaffold-worker.md",
|
|
218
|
+
title: "VCM Architect Scaffold Worker Agent",
|
|
219
|
+
frontmatter: renderAgentFrontmatter("vcm-architect-scaffold-worker", "Foreground Architect worker for exact scaffold execution and scaffold validation.", { model: "opus", effort: "xhigh" }),
|
|
220
|
+
renderRules: renderArchitectScaffoldWorkerHarnessRules
|
|
221
|
+
},
|
|
205
222
|
{
|
|
206
223
|
kind: "tool-request-gate-review",
|
|
207
224
|
path: ".ai/tools/request-gate-review",
|
|
@@ -223,6 +240,13 @@ const HARNESS_FILES = [
|
|
|
223
240
|
ownership: "raw-file",
|
|
224
241
|
renderRules: renderCheckScaffoldLedgerTool
|
|
225
242
|
},
|
|
243
|
+
{
|
|
244
|
+
kind: "tool-request-architect-restart",
|
|
245
|
+
path: ".ai/tools/request-architect-restart",
|
|
246
|
+
title: "Request Architect Restart Tool",
|
|
247
|
+
ownership: "raw-file",
|
|
248
|
+
renderRules: renderRequestArchitectRestartTool
|
|
249
|
+
},
|
|
226
250
|
{
|
|
227
251
|
kind: "agent-project-manager",
|
|
228
252
|
path: ".claude/agents/project-manager.md",
|
|
@@ -237,7 +261,7 @@ const HARNESS_FILES = [
|
|
|
237
261
|
title: "Architect Agent",
|
|
238
262
|
memoryBlock: true,
|
|
239
263
|
blankLineBeforeEnd: true,
|
|
240
|
-
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync."),
|
|
264
|
+
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent" }),
|
|
241
265
|
renderRules: renderArchitectHarnessRules
|
|
242
266
|
},
|
|
243
267
|
{
|
|
@@ -1192,6 +1216,9 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1192
1216
|
};
|
|
1193
1217
|
}
|
|
1194
1218
|
const insertedContent = `${currentContent.trimEnd()}\n\n${expectedBlock}\n`;
|
|
1219
|
+
const nextContent = definition.kind === "agent-architect"
|
|
1220
|
+
? ensureAgentTool(insertedContent, "Agent")
|
|
1221
|
+
: insertedContent;
|
|
1195
1222
|
return {
|
|
1196
1223
|
definition,
|
|
1197
1224
|
status: {
|
|
@@ -1206,13 +1233,16 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1206
1233
|
action: "insert",
|
|
1207
1234
|
reason: "File exists but does not contain VCM managed rules."
|
|
1208
1235
|
},
|
|
1209
|
-
nextContent: definition.memoryBlock ? ensureVcmMemoryBlock(
|
|
1236
|
+
nextContent: definition.memoryBlock ? ensureVcmMemoryBlock(nextContent) : nextContent
|
|
1210
1237
|
};
|
|
1211
1238
|
}
|
|
1212
1239
|
const managedVersion = match[1] ? Number(match[1]) : undefined;
|
|
1213
1240
|
const currentBlock = match[0];
|
|
1214
1241
|
const blockUpdatedContent = currentContent.replace(managedBlockPattern, expectedBlock);
|
|
1215
|
-
const
|
|
1242
|
+
const memoryUpdatedContent = definition.memoryBlock ? ensureVcmMemoryBlock(blockUpdatedContent) : blockUpdatedContent;
|
|
1243
|
+
const nextContent = definition.kind === "agent-architect"
|
|
1244
|
+
? ensureAgentTool(memoryUpdatedContent, "Agent")
|
|
1245
|
+
: memoryUpdatedContent;
|
|
1216
1246
|
const action = currentContent === nextContent ? "ok" : "update";
|
|
1217
1247
|
return {
|
|
1218
1248
|
definition,
|
|
@@ -1323,6 +1353,22 @@ function renderNewHarnessFile(definition, block, contentAfterBlock = definition.
|
|
|
1323
1353
|
const suffix = contentAfterBlock?.trim();
|
|
1324
1354
|
return `${frontmatter}# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
|
|
1325
1355
|
}
|
|
1356
|
+
function ensureAgentTool(content, requiredTool) {
|
|
1357
|
+
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
1358
|
+
if (!frontmatterMatch) {
|
|
1359
|
+
return content;
|
|
1360
|
+
}
|
|
1361
|
+
const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
|
|
1362
|
+
if (!toolsMatch) {
|
|
1363
|
+
return content.replace(/^(---\r?\n[\s\S]*?)(\r?\n---)/, `$1\ntools: ${requiredTool}$2`);
|
|
1364
|
+
}
|
|
1365
|
+
const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
1366
|
+
if (tools.includes(requiredTool)) {
|
|
1367
|
+
return content;
|
|
1368
|
+
}
|
|
1369
|
+
const nextTools = [...tools, requiredTool].join(", ");
|
|
1370
|
+
return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
|
|
1371
|
+
}
|
|
1326
1372
|
function migrateLegacyHarnessFile(definition, currentContent, block) {
|
|
1327
1373
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
1328
1374
|
if (!legacyContent) {
|
|
@@ -1454,7 +1500,8 @@ function isPlainObject(value) {
|
|
|
1454
1500
|
function renderAgentFrontmatter(name, description, options = {}) {
|
|
1455
1501
|
const tools = options.tools ?? "Read, Grep, Glob, Bash, Edit, Write";
|
|
1456
1502
|
const model = options.model ? `\nmodel: ${options.model}` : "";
|
|
1457
|
-
|
|
1503
|
+
const effort = options.effort ? `\neffort: ${options.effort}` : "";
|
|
1504
|
+
return `---\nname: ${name}\ndescription: ${description}\ntools: ${tools}${model}${effort}\n---`;
|
|
1458
1505
|
}
|
|
1459
1506
|
function renderSkillFrontmatter(name, description) {
|
|
1460
1507
|
return `---\nname: ${name}\ndescription: ${description}\n---`;
|
|
@@ -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,
|
|
@@ -130,6 +130,11 @@ export function createMessageService(deps) {
|
|
|
130
130
|
}).catch(() => undefined);
|
|
131
131
|
}
|
|
132
132
|
scheduleDispatchConfirmation(input, delivered, session.id);
|
|
133
|
+
await deps.onRouteDelivered?.({
|
|
134
|
+
repoRoot: input.repoRoot,
|
|
135
|
+
taskSlug: input.taskSlug,
|
|
136
|
+
message: delivered
|
|
137
|
+
});
|
|
133
138
|
return {
|
|
134
139
|
message: delivered,
|
|
135
140
|
delivered: true,
|
|
@@ -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) {
|