vibe-coding-master 0.7.39 → 0.7.40

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 (48) hide show
  1. package/README.md +11 -6
  2. package/dist/backend/adapters/claude-adapter.js +1 -6
  3. package/dist/backend/api/artifact-routes.js +72 -1
  4. package/dist/backend/api/runtime-state-routes.js +17 -5
  5. package/dist/backend/api/task-routes.js +6 -0
  6. package/dist/backend/api/workflow-control-routes.js +56 -0
  7. package/dist/backend/cli/install-vcm-harness.js +21 -31
  8. package/dist/backend/server.js +21 -5
  9. package/dist/backend/services/artifact-service.js +309 -5
  10. package/dist/backend/services/auto-memory-service.js +2 -2
  11. package/dist/backend/services/gate-review-service.js +81 -33
  12. package/dist/backend/services/harness-feedback-service.js +11 -4
  13. package/dist/backend/services/harness-service.js +17 -31
  14. package/dist/backend/services/message-service.js +105 -37
  15. package/dist/backend/services/status-service.js +6 -0
  16. package/dist/backend/services/workflow-control-service.js +854 -0
  17. package/dist/backend/templates/handoff.js +158 -3
  18. package/dist/backend/templates/harness/architect-agent.js +2 -2
  19. package/dist/backend/templates/harness/check-scaffold-ledger.js +105 -39
  20. package/dist/backend/templates/harness/claude-root.js +9 -1
  21. package/dist/backend/templates/harness/coder-agent.js +1 -2
  22. package/dist/backend/templates/harness/coder-worker-agent.js +1 -1
  23. package/dist/backend/templates/harness/gate-review.js +3 -4
  24. package/dist/backend/templates/harness/harness-engineer-agent.js +18 -1
  25. package/dist/backend/templates/harness/project-manager-agent.js +3 -2
  26. package/dist/backend/templates/harness/tester-agent.js +1 -0
  27. package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +5 -0
  28. package/dist/backend/templates/harness/vcm-code-navigation-skill.js +1 -2
  29. package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +2 -2
  30. package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +4 -2
  31. package/dist/backend/templates/harness/vcm-propose-memory-skill.js +4 -2
  32. package/dist/backend/templates/harness/vcm-report-harness-issue-skill.js +2 -1
  33. package/dist/backend/templates/harness/vcm-route-message-skill.js +4 -7
  34. package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -13
  35. package/dist/backend/templates/harness/vcm-workflow-review-skill.js +62 -0
  36. package/dist/backend/templates/message-envelope.js +4 -4
  37. package/dist/shared/types/workflow.js +7 -1
  38. package/dist/shared/validation/artifact-check.js +99 -95
  39. package/dist/shared/validation/artifact-contract.js +9 -0
  40. package/dist/shared/validation/artifact-registry.js +211 -0
  41. package/dist-frontend/assets/{index-DkM0mnQD.js → index-Bocc2DWF.js} +34 -34
  42. package/dist-frontend/assets/{index-CXSOe-NN.css → index-C_XHGNBD.css} +1 -1
  43. package/dist-frontend/index.html +2 -2
  44. package/package.json +1 -1
  45. package/scripts/harness-tools/run-long-check +38 -8
  46. package/scripts/harness-tools/vcm-artifact +148 -0
  47. package/scripts/harness-tools/vcm-bash-guard +42 -47
  48. package/scripts/harness-tools/watch-job +58 -4
package/README.md CHANGED
@@ -324,6 +324,13 @@ Automatic mode:
324
324
  - the UI switches to the target role before dispatch
325
325
  - Claude Code hooks confirm whether the prompt was accepted
326
326
 
327
+ Before Project Manager can dispatch Architect, Coder, or Tester, it submits a
328
+ strict `workflow-progress.md` transition through `vcm-workflow-review`. VCM
329
+ checks the confirmed dispatch history and current task artifacts, then grants
330
+ one matching route. The target role's `UserPromptSubmit` consumes that approval
331
+ and appends the confirmed transition. A rejected transition can be bypassed
332
+ only by an exact one-time user authorization recorded through the VCM dialog.
333
+
327
334
  If the flow stops, VCM always shows a blocking pause alert. `Pause alert sound`
328
335
  only controls the looping sound. Enabling Gateway turns that preference off once;
329
336
  it can be turned back on afterward. A new Gateway command closes an open pause
@@ -471,12 +478,10 @@ VCM bundles the Claude Code LSP bridge and loads it for Architect, Coder, and
471
478
  Reviewer sessions, including CCR launches. The project environment must still
472
479
  provide the language server for each detected language: `rust-analyzer`,
473
480
  `typescript-language-server`, `pyright-langserver`, `gopls`, `clangd`, or
474
- `jdtls`. Their Agent definitions preload `vcm-code-navigation`, and their
475
- launches explicitly enable Glob while leaving Grep unavailable. The Harness
476
- guard also rejects shell text-search commands for those roles so unresolved
477
- semantic relationships cannot be hidden by textual fallback. Harness Studio
478
- reports whether the server executable and plugin can run; the role Session
479
- performs the real workspace warm-up and semantic query retries.
481
+ `jdtls`. Their Agent definitions preload `vcm-code-navigation`. Architect,
482
+ Coder, and Reviewer use LSP for semantic relationships. Harness Studio reports
483
+ whether the server executable and plugin can run; the role Session performs the
484
+ real workspace warm-up and semantic query retries.
480
485
 
481
486
  Harness Engineer is task-scoped and runs from the active task worktree. The
482
487
  backend automatically starts a fresh Harness Engineer for each active task or
@@ -20,7 +20,7 @@ export function createClaudeAdapter(runner) {
20
20
  },
21
21
  buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride, appendSystemPrompt, pluginDirs = []) {
22
22
  const args = pluginDirs.flatMap((pluginDir) => ["--plugin-dir", pluginDir]);
23
- args.push("--allowedTools", roleSearchToolOptIns(role).join(","));
23
+ args.push("--allowedTools", "Glob,Grep");
24
24
  args.push("--agent", role);
25
25
  const sessionSettings = { ...settingsOverride };
26
26
  if (claudeSessionId) {
@@ -52,11 +52,6 @@ export function createClaudeAdapter(runner) {
52
52
  }
53
53
  };
54
54
  }
55
- function roleSearchToolOptIns(role) {
56
- return role === "architect" || role === "coder" || role === "reviewer"
57
- ? ["Glob"]
58
- : ["Glob", "Grep"];
59
- }
60
55
  function formatDisplayArg(value) {
61
56
  if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) {
62
57
  return value;
@@ -1,4 +1,4 @@
1
- import { isDispatchableRole } from "../../shared/constants.js";
1
+ import { isDispatchableRole, isRoleName } from "../../shared/constants.js";
2
2
  import { VcmError } from "../errors.js";
3
3
  import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
4
4
  export function registerArtifactRoutes(app, deps) {
@@ -52,6 +52,59 @@ export function registerArtifactRoutes(app, deps) {
52
52
  });
53
53
  return { ok: true };
54
54
  });
55
+ app.post("/api/tasks/:taskSlug/artifacts/submit", async (request) => {
56
+ const project = await requireCurrentProject(deps.projectService);
57
+ const config = await deps.projectService.loadConfig(project.repoRoot);
58
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
59
+ const body = request.body;
60
+ if (!body || !isRoleName(body.role)) {
61
+ throw new VcmError({
62
+ code: "ARTIFACT_ROLE_INVALID",
63
+ message: `Unknown artifact role: ${body?.role ?? "missing"}`,
64
+ statusCode: 400
65
+ });
66
+ }
67
+ if (body.mode !== "draft" && body.mode !== "final") {
68
+ throw new VcmError({
69
+ code: "ARTIFACT_MODE_INVALID",
70
+ message: "Artifact mode must be draft or final.",
71
+ statusCode: 400
72
+ });
73
+ }
74
+ if (typeof body.content !== "string") {
75
+ throw new VcmError({
76
+ code: "ARTIFACT_CONTENT_INVALID",
77
+ message: "Artifact content must be text.",
78
+ statusCode: 400
79
+ });
80
+ }
81
+ const session = body.role === "translator"
82
+ ? await deps.sessionService.getProjectTranslatorSession(project.repoRoot)
83
+ : body.role === "harness-engineer"
84
+ ? await deps.sessionService.getRoleSession(project.repoRoot, task.taskSlug, body.role)
85
+ ?? await deps.sessionService.getProjectHarnessEngineerSession(project.repoRoot)
86
+ : await deps.sessionService.getRoleSession(project.repoRoot, task.taskSlug, body.role);
87
+ if (!session || !session.runtimeSessionToken || session.runtimeSessionToken !== body.runtimeSessionToken) {
88
+ throw new VcmError({
89
+ code: "ARTIFACT_SESSION_INVALID",
90
+ message: `${body.role} does not have the active VCM Session that owns this artifact submission.`,
91
+ statusCode: 409,
92
+ hint: "Submit from the active role terminal with .ai/tools/vcm-artifact."
93
+ });
94
+ }
95
+ return deps.artifactService.submitArtifact({
96
+ repoRoot: getTaskRuntimeRepoRoot(task),
97
+ baseRepoRoot: project.repoRoot,
98
+ stateRoot: config.stateRoot,
99
+ handoffDir: task.handoffDir,
100
+ taskSlug: task.taskSlug,
101
+ kind: body.kind,
102
+ mode: body.mode,
103
+ role: body.role,
104
+ content: body.content,
105
+ artifactPath: body.path
106
+ });
107
+ });
55
108
  }
56
109
  function parseDispatchableRole(role) {
57
110
  if (!isDispatchableRole(role)) {
@@ -67,18 +120,36 @@ function artifactNameToPath(paths, artifactName) {
67
120
  if (artifactName === "architecture-brief.md") {
68
121
  return paths.architectureBriefPath;
69
122
  }
123
+ if (artifactName === "architecture-evidence.md") {
124
+ return paths.architectureEvidencePath;
125
+ }
126
+ if (artifactName === "planning-progress.md") {
127
+ return paths.planningProgressPath;
128
+ }
70
129
  if (artifactName === "architecture-plan.md") {
71
130
  return paths.architecturePlanPath;
72
131
  }
73
132
  if (artifactName === "known-issues.md") {
74
133
  return paths.knownIssuesPath;
75
134
  }
135
+ if (artifactName === "coder-completion.md") {
136
+ return paths.coderCompletionPath;
137
+ }
138
+ if (artifactName === "architect-debug.md") {
139
+ return paths.architectDebugPath;
140
+ }
141
+ if (artifactName === "architecture-diagnosis.md") {
142
+ return paths.architectureDiagnosisPath;
143
+ }
76
144
  if (artifactName === "test-report.md") {
77
145
  return paths.testReportPath;
78
146
  }
79
147
  if (artifactName === "docs-sync-report.md") {
80
148
  return paths.docsSyncReportPath;
81
149
  }
150
+ if (artifactName === "workflow-progress.md") {
151
+ return paths.workflowProgressPath;
152
+ }
82
153
  if (artifactName === "final-acceptance.md") {
83
154
  return paths.finalAcceptancePath;
84
155
  }
@@ -23,15 +23,25 @@ export function registerRuntimeStateRoutes(app, deps) {
23
23
  harnessBootstrapStatus: null,
24
24
  harnessFeedbackState,
25
25
  autoMemoryState: null,
26
- gatewayStatus: coordinated.gatewayStatus
26
+ gatewayStatus: coordinated.gatewayStatus,
27
+ workflowControlState: null
27
28
  };
28
29
  }
29
30
  try {
30
31
  const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
31
- const [harnessStatus, harnessBootstrapStatus, autoMemoryState] = await Promise.all([
32
+ const config = deps.workflowControlService
33
+ ? await deps.projectService.loadConfig(project.repoRoot)
34
+ : null;
35
+ const [harnessStatus, harnessBootstrapStatus, autoMemoryState, workflowControlState] = await Promise.all([
32
36
  withOpenFileLimitFallback(() => deps.harnessService.getHarnessStatus(task.worktreePath), (error) => degradedHarnessStatus(error)),
33
37
  withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath, task.taskSlug), (error) => degradedBootstrapStatus(error)),
34
- deps.autoMemoryService.getState(project.repoRoot, task.worktreePath)
38
+ deps.autoMemoryService.getState(project.repoRoot, task.worktreePath),
39
+ deps.workflowControlService && config ? deps.workflowControlService.getState({
40
+ taskRepoRoot: task.worktreePath,
41
+ stateRoot: config.stateRoot,
42
+ handoffDir: task.handoffDir,
43
+ taskSlug: task.taskSlug
44
+ }) : Promise.resolve(null)
35
45
  ]);
36
46
  return {
37
47
  translatorSession,
@@ -41,7 +51,8 @@ export function registerRuntimeStateRoutes(app, deps) {
41
51
  harnessBootstrapStatus,
42
52
  harnessFeedbackState,
43
53
  autoMemoryState,
44
- gatewayStatus: coordinated.gatewayStatus
54
+ gatewayStatus: coordinated.gatewayStatus,
55
+ workflowControlState
45
56
  };
46
57
  }
47
58
  catch (error) {
@@ -54,7 +65,8 @@ export function registerRuntimeStateRoutes(app, deps) {
54
65
  harnessBootstrapStatus: degradedBootstrapStatus(error),
55
66
  harnessFeedbackState,
56
67
  autoMemoryState: null,
57
- gatewayStatus: coordinated.gatewayStatus
68
+ gatewayStatus: coordinated.gatewayStatus,
69
+ workflowControlState: null
58
70
  };
59
71
  }
60
72
  throw error;
@@ -198,10 +198,16 @@ function degradedArtifactSummary(handoffDir) {
198
198
  roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
199
199
  messageRoutePaths: {},
200
200
  architectureBriefPath: `${handoffDir}/architecture-brief.md`,
201
+ architectureEvidencePath: `${handoffDir}/architecture-evidence.md`,
202
+ planningProgressPath: `${handoffDir}/planning-progress.md`,
201
203
  architecturePlanPath: `${handoffDir}/architecture-plan.md`,
202
204
  knownIssuesPath: `${handoffDir}/known-issues.md`,
205
+ coderCompletionPath: `${handoffDir}/coder-completion.md`,
206
+ architectDebugPath: `${handoffDir}/architect-debug.md`,
207
+ architectureDiagnosisPath: `${handoffDir}/architecture-diagnosis.md`,
203
208
  testReportPath: `${handoffDir}/test-report.md`,
204
209
  docsSyncReportPath: `${handoffDir}/docs-sync-report.md`,
210
+ workflowProgressPath: `${handoffDir}/workflow-progress.md`,
205
211
  finalAcceptancePath: `${handoffDir}/final-acceptance.md`
206
212
  },
207
213
  checks: []
@@ -0,0 +1,56 @@
1
+ import { VcmError } from "../errors.js";
2
+ import { submitTerminalInput } from "../runtime/terminal-submit.js";
3
+ import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
4
+ export function registerWorkflowControlRoutes(app, deps) {
5
+ app.get("/api/tasks/:taskSlug/workflow-control", async (request) => {
6
+ const context = await getContext(deps, request.params.taskSlug);
7
+ return deps.workflowControlService.getState(context);
8
+ });
9
+ app.post("/api/tasks/:taskSlug/workflow-overrides/:overrideId/approve", async (request) => {
10
+ const authorizationText = request.body?.authorizationText?.trim();
11
+ if (!authorizationText) {
12
+ throw new VcmError({
13
+ code: "WORKFLOW_OVERRIDE_AUTHORIZATION_REQUIRED",
14
+ message: "Enter the exact workflow exception that the user authorizes.",
15
+ statusCode: 400
16
+ });
17
+ }
18
+ const context = await getContext(deps, request.params.taskSlug);
19
+ const state = await deps.workflowControlService.approveOverride(context, request.params.overrideId, authorizationText);
20
+ await notifyProjectManager(deps, context, request.params.overrideId, "approved", authorizationText);
21
+ return state;
22
+ });
23
+ app.post("/api/tasks/:taskSlug/workflow-overrides/:overrideId/reject", async (request) => {
24
+ const context = await getContext(deps, request.params.taskSlug);
25
+ const state = await deps.workflowControlService.rejectOverride(context, request.params.overrideId);
26
+ await notifyProjectManager(deps, context, request.params.overrideId, "rejected");
27
+ return state;
28
+ });
29
+ }
30
+ async function getContext(deps, taskSlug) {
31
+ const project = await deps.projectService.getCurrentProject();
32
+ if (!project) {
33
+ throw new VcmError({ code: "PROJECT_NOT_CONNECTED", message: "Connect a repository first.", statusCode: 409 });
34
+ }
35
+ const [config, task] = await Promise.all([
36
+ deps.projectService.loadConfig(project.repoRoot),
37
+ deps.taskService.loadTask(project.repoRoot, taskSlug)
38
+ ]);
39
+ return {
40
+ repoRoot: project.repoRoot,
41
+ taskRepoRoot: getTaskRuntimeRepoRoot(task),
42
+ stateRoot: config.stateRoot,
43
+ handoffDir: task.handoffDir,
44
+ taskSlug: task.taskSlug
45
+ };
46
+ }
47
+ async function notifyProjectManager(deps, context, overrideId, decision, authorizationText) {
48
+ const session = await deps.sessionService.getRoleSession(context.repoRoot, context.taskSlug, "project-manager");
49
+ if (!session || session.status !== "running" || session.activityStatus === "running")
50
+ return;
51
+ const prompt = decision === "approved"
52
+ ? `[VCM Workflow Override Decision]\nDecision: approved\nAuthorization ID: ${overrideId}\nAuthorization Text: ${authorizationText}\n\nResubmit workflow-progress.md with this Authorization ID and exact Authorization Text.`
53
+ : `[VCM Workflow Override Decision]\nDecision: rejected\nAuthorization ID: ${overrideId}\n\nKeep the current workflow and choose a legal next dispatch.`;
54
+ await submitTerminalInput(deps.runtime, session.id, prompt);
55
+ await deps.sessionService.markRoleActivityRunning(context.repoRoot, context.taskSlug, "project-manager", session.id);
56
+ }
@@ -28,6 +28,7 @@ import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propo
28
28
  import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
29
29
  import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
30
30
  import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
31
+ import { renderVcmWorkflowReviewSkillRules } from "../templates/harness/vcm-workflow-review-skill.js";
31
32
  import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
32
33
  import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
33
34
  import { readVcmPackageVersion } from "../app-version.js";
@@ -53,7 +54,7 @@ const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
53
54
  const VCM_AUTO_MEMORY_ENABLED = false;
54
55
  const VCM_HOOK_DEFINITIONS = [
55
56
  { eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
56
- { eventName: "PreToolUse", matcher: "Grep", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
57
+ { eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
57
58
  { eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
58
59
  { eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
59
60
  { eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
@@ -66,12 +67,12 @@ const AGENT_FRONTMATTER = {
66
67
  },
67
68
  architect: {
68
69
  description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.",
69
- tools: "Read, Glob, Bash, Edit, Write, Agent, LSP",
70
+ tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP",
70
71
  skills: ["vcm-code-navigation"]
71
72
  },
72
73
  coder: {
73
74
  description: "VCM implementation role for scoped code changes and focused tests.",
74
- tools: "Read, Glob, Bash, Edit, Write, Agent, LSP",
75
+ tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP",
75
76
  skills: ["vcm-code-navigation"]
76
77
  },
77
78
  tester: {
@@ -79,7 +80,7 @@ const AGENT_FRONTMATTER = {
79
80
  },
80
81
  reviewer: {
81
82
  description: "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.",
82
- tools: "Read, Glob, Bash, Write, LSP",
83
+ tools: "Read, Grep, Glob, Bash, Write, LSP",
83
84
  skills: ["vcm-code-navigation"]
84
85
  },
85
86
  translator: {
@@ -99,14 +100,9 @@ const AGENT_FRONTMATTER = {
99
100
  }
100
101
  };
101
102
  const REQUIRED_AGENT_TOOLS = {
102
- architect: ["Agent", "LSP"],
103
- coder: ["Agent", "LSP"],
104
- reviewer: ["LSP"]
105
- };
106
- const FORBIDDEN_AGENT_TOOLS = {
107
- architect: ["Grep"],
108
- coder: ["Grep"],
109
- reviewer: ["Grep"]
103
+ architect: ["Grep", "Agent", "LSP"],
104
+ coder: ["Grep", "Agent", "LSP"],
105
+ reviewer: ["Grep", "LSP"]
110
106
  };
111
107
  const REQUIRED_AGENT_SKILLS = {
112
108
  architect: ["vcm-code-navigation"],
@@ -312,6 +308,12 @@ const WHOLE_FILES = [
312
308
  mode: 0o644,
313
309
  content: renderSkillFile("VCM Task State Skill", "vcm-task-state", "Use only as project-manager to declare the current task workflow checkpoint to VCM.", renderVcmTaskStateSkillRules())
314
310
  },
311
+ {
312
+ path: ".claude/skills/vcm-workflow-review/SKILL.md",
313
+ category: "skill",
314
+ mode: 0o644,
315
+ content: renderSkillFile("VCM Workflow Review Skill", "vcm-workflow-review", "Use before every project-manager dispatch to Architect, Coder, or Tester so VCM can approve the workflow transition.", renderVcmWorkflowReviewSkillRules())
316
+ },
315
317
  {
316
318
  path: ".claude/skills/vcm-gate-review/SKILL.md",
317
319
  category: "skill",
@@ -377,6 +379,12 @@ const WHOLE_FILES = [
377
379
  category: "runtime-tool",
378
380
  mode: 0o755,
379
381
  templatePath: "scripts/harness-tools/vcm-bash-guard"
382
+ },
383
+ {
384
+ path: ".ai/tools/vcm-artifact",
385
+ category: "runtime-tool",
386
+ mode: 0o755,
387
+ templatePath: "scripts/harness-tools/vcm-artifact"
380
388
  }
381
389
  ];
382
390
  const LEGACY_FLAT_SKILL_FILES = [
@@ -619,6 +627,7 @@ function fixedDirectories() {
619
627
  ".claude/skills/vcm-long-running-validation/",
620
628
  ".claude/skills/vcm-route-message/",
621
629
  ".claude/skills/vcm-task-state/",
630
+ ".claude/skills/vcm-workflow-review/",
622
631
  ".claude/skills/vcm-gate-review/",
623
632
  ".claude/skills/vcm-report-harness-issue/",
624
633
  ".claude/skills/vcm-propose-memory/",
@@ -686,9 +695,6 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
686
695
  for (const requiredTool of REQUIRED_AGENT_TOOLS[definition.agentName] ?? []) {
687
696
  nextContent = ensureAgentTool(nextContent, requiredTool);
688
697
  }
689
- for (const forbiddenTool of FORBIDDEN_AGENT_TOOLS[definition.agentName] ?? []) {
690
- nextContent = removeAgentTool(nextContent, forbiddenTool);
691
- }
692
698
  for (const requiredSkill of REQUIRED_AGENT_SKILLS[definition.agentName] ?? []) {
693
699
  nextContent = ensureAgentSkill(nextContent, requiredSkill);
694
700
  }
@@ -757,22 +763,6 @@ function ensureAgentTool(content, requiredTool) {
757
763
  const nextTools = [...tools, requiredTool].join(", ");
758
764
  return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
759
765
  }
760
- function removeAgentTool(content, forbiddenTool) {
761
- const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
762
- if (!frontmatterMatch) {
763
- return content;
764
- }
765
- const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
766
- if (!toolsMatch) {
767
- return content;
768
- }
769
- const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
770
- const nextTools = tools.filter((tool) => tool !== forbiddenTool);
771
- if (nextTools.length === tools.length) {
772
- return content;
773
- }
774
- return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools.join(", ")}`));
775
- }
776
766
  function ensureAgentSkill(content, requiredSkill) {
777
767
  const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
778
768
  if (!frontmatterMatch) {
@@ -45,6 +45,7 @@ import { createTerminalInterruptService } from "./services/terminal-interrupt-se
45
45
  import { createTerminalProcessExitService } from "./services/terminal-process-exit-service.js";
46
46
  import { createTranslationService } from "./services/translation-service.js";
47
47
  import { createUsageAnalyticsService } from "./services/usage-analytics-service.js";
48
+ import { createWorkflowControlService } from "./services/workflow-control-service.js";
48
49
  import { createDiagnosticsService } from "./services/diagnostics-service.js";
49
50
  import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
50
51
  import { registerArtifactRoutes } from "./api/artifact-routes.js";
@@ -60,6 +61,7 @@ import { registerSessionRoutes } from "./api/session-routes.js";
60
61
  import { registerTaskRoutes } from "./api/task-routes.js";
61
62
  import { registerTranslationRoutes } from "./api/translation-routes.js";
62
63
  import { registerUsageAnalyticsRoutes } from "./api/usage-analytics-routes.js";
64
+ import { registerWorkflowControlRoutes } from "./api/workflow-control-routes.js";
63
65
  import { registerTerminalWs } from "./ws/terminal-ws.js";
64
66
  import { toVcmError } from "./errors.js";
65
67
  import { readVcmPackageVersion } from "./app-version.js";
@@ -120,7 +122,8 @@ export async function createServer(deps, options = {}) {
120
122
  harnessService: deps.harnessService,
121
123
  harnessFeedbackService: deps.harnessFeedbackService,
122
124
  autoMemoryService: deps.autoMemoryService,
123
- runtimeCoordinator: deps.runtimeCoordinator
125
+ runtimeCoordinator: deps.runtimeCoordinator,
126
+ workflowControlService: deps.workflowControlService
124
127
  });
125
128
  registerTaskRoutes(app, {
126
129
  projectService: deps.projectService,
@@ -144,8 +147,18 @@ export async function createServer(deps, options = {}) {
144
147
  registerArtifactRoutes(app, {
145
148
  projectService: deps.projectService,
146
149
  taskService: deps.taskService,
147
- artifactService: deps.artifactService
148
- });
150
+ artifactService: deps.artifactService,
151
+ sessionService: deps.sessionService
152
+ });
153
+ if (deps.workflowControlService) {
154
+ registerWorkflowControlRoutes(app, {
155
+ projectService: deps.projectService,
156
+ taskService: deps.taskService,
157
+ sessionService: deps.sessionService,
158
+ workflowControlService: deps.workflowControlService,
159
+ runtime: deps.runtime
160
+ });
161
+ }
149
162
  registerMessageRoutes(app, {
150
163
  projectService: deps.projectService,
151
164
  taskService: deps.taskService,
@@ -228,7 +241,8 @@ export function createDefaultServerDeps(options = {}) {
228
241
  });
229
242
  const runtime = createNodePtyTerminalRuntime({ fs });
230
243
  const registry = createSessionRegistry();
231
- const artifactService = createArtifactService(fs);
244
+ const workflowControlService = createWorkflowControlService({ fs });
245
+ const artifactService = createArtifactService(fs, { workflowControlService });
232
246
  const projectService = createProjectService({ fs, git, appSettings });
233
247
  const taskService = createTaskService({ fs, git, artifactService, projectService });
234
248
  const taskWorkflowService = createTaskWorkflowService({ fs });
@@ -292,6 +306,7 @@ export function createDefaultServerDeps(options = {}) {
292
306
  sessionService,
293
307
  taskService,
294
308
  taskWorkflowService,
309
+ workflowControlService,
295
310
  onRouteDelivered: ({ repoRoot, taskSlug, message }) => architectRestartService.recordRouteDelivered(repoRoot, taskSlug, message)
296
311
  });
297
312
  const taskLaunchService = createTaskLaunchService({
@@ -462,7 +477,8 @@ export function createDefaultServerDeps(options = {}) {
462
477
  terminalProcessExitService,
463
478
  runtime,
464
479
  diagnosticsService,
465
- usageAnalyticsService
480
+ usageAnalyticsService,
481
+ workflowControlService
466
482
  };
467
483
  }
468
484
  export function getDefaultStaticDir() {