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.
Files changed (37) hide show
  1. package/README.md +19 -2
  2. package/dist/backend/adapters/claude-adapter.js +4 -1
  3. package/dist/backend/api/harness-routes.js +21 -4
  4. package/dist/backend/api/session-routes.js +5 -0
  5. package/dist/backend/api/translation-worker-routes.js +15 -2
  6. package/dist/backend/api/usage-analytics-routes.js +31 -0
  7. package/dist/backend/cli/install-vcm-harness.js +54 -5
  8. package/dist/backend/server.js +27 -5
  9. package/dist/backend/services/app-settings-service.js +26 -2
  10. package/dist/backend/services/architect-restart-service.js +136 -0
  11. package/dist/backend/services/claude-hook-service.js +134 -54
  12. package/dist/backend/services/gate-review-service.js +92 -27
  13. package/dist/backend/services/harness-service.js +54 -7
  14. package/dist/backend/services/message-service.js +6 -1
  15. package/dist/backend/services/runtime-coordinator-service.js +10 -10
  16. package/dist/backend/services/session-service.js +78 -25
  17. package/dist/backend/services/task-close-service.js +1 -0
  18. package/dist/backend/services/terminal-interrupt-service.js +4 -1
  19. package/dist/backend/services/turn-reconciler-service.js +1 -0
  20. package/dist/backend/services/usage-analytics-service.js +346 -0
  21. package/dist/backend/templates/handoff.js +4 -0
  22. package/dist/backend/templates/harness/architect-agent.js +18 -10
  23. package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +23 -0
  24. package/dist/backend/templates/harness/claude-root.js +1 -1
  25. package/dist/backend/templates/harness/gate-review.js +25 -3
  26. package/dist/backend/templates/harness/project-manager-agent.js +10 -2
  27. package/dist/backend/templates/harness/restart-architect-skill.js +75 -0
  28. package/dist/backend/templates/harness/tester-agent.js +15 -7
  29. package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +31 -5
  30. package/dist/backend/templates/harness/vcm-route-message-skill.js +3 -3
  31. package/dist/shared/types/app-settings.js +14 -0
  32. package/dist/shared/types/usage-analytics.js +1 -0
  33. package/dist/shared/validation/artifact-check.js +29 -3
  34. package/dist-frontend/assets/{index-BAE_pjXJ.js → index-CDkDHrWQ.js} +43 -43
  35. package/dist-frontend/assets/{index-CiEUp9Si.css → index-Ci7z8tW3.css} +1 -1
  36. package/dist-frontend/index.html +2 -2
  37. 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
- const testResult = content ? matchField(content, "Test Result")?.toLowerCase() : undefined;
1178
- if (check.status === "ok" && testResult === "pass") {
1244
+ if (check.status === "ok") {
1179
1245
  return;
1180
1246
  }
1181
1247
  const details = [
1182
- check.status !== "ok" ? `status=${check.status}` : "",
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(insertedContent) : insertedContent
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 nextContent = definition.memoryBlock ? ensureVcmMemoryBlock(blockUpdatedContent) : blockUpdatedContent;
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
- return `---\nname: ${name}\ndescription: ${description}\ntools: ${tools}${model}\n---`;
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) {