vibe-coding-master 0.7.42 → 0.7.44

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 (50) hide show
  1. package/README.md +49 -17
  2. package/dist/backend/api/artifact-routes.js +3 -0
  3. package/dist/backend/api/harness-routes.js +16 -0
  4. package/dist/backend/api/task-routes.js +32 -2
  5. package/dist/backend/api/workflow-control-routes.js +7 -26
  6. package/dist/backend/cli/install-vcm-harness.js +61 -6
  7. package/dist/backend/role-tool-policy.js +1 -1
  8. package/dist/backend/server.js +14 -5
  9. package/dist/backend/services/artifact-service.js +10 -32
  10. package/dist/backend/services/auto-memory-service.js +642 -14
  11. package/dist/backend/services/claude-hook-service.js +183 -6
  12. package/dist/backend/services/gate-review-service.js +173 -74
  13. package/dist/backend/services/harness-feedback-service.js +180 -80
  14. package/dist/backend/services/harness-service.js +65 -9
  15. package/dist/backend/services/memory-review-paths.js +13 -0
  16. package/dist/backend/services/role-stall-detector-service.js +322 -0
  17. package/dist/backend/services/round-service.js +25 -0
  18. package/dist/backend/services/runtime-coordinator-service.js +12 -1
  19. package/dist/backend/services/session-service.js +70 -3
  20. package/dist/backend/services/status-service.js +1 -0
  21. package/dist/backend/services/translation-worker-service.js +19 -4
  22. package/dist/backend/services/workflow-control-service.js +524 -204
  23. package/dist/backend/templates/handoff.js +46 -5
  24. package/dist/backend/templates/harness/architect-agent.js +12 -6
  25. package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +1 -1
  26. package/dist/backend/templates/harness/check-scaffold-ledger.js +234 -10
  27. package/dist/backend/templates/harness/claude-root.js +3 -2
  28. package/dist/backend/templates/harness/coder-agent.js +12 -5
  29. package/dist/backend/templates/harness/gate-review.js +150 -57
  30. package/dist/backend/templates/harness/harness-engineer-agent.js +38 -13
  31. package/dist/backend/templates/harness/project-manager-agent.js +18 -12
  32. package/dist/backend/templates/harness/resolve-durable-doc-assignment.js +60 -0
  33. package/dist/backend/templates/harness/tester-agent.js +13 -0
  34. package/dist/backend/templates/harness/vcm-ask-user-skill.js +82 -0
  35. package/dist/backend/templates/harness/vcm-code-navigation-skill.js +7 -6
  36. package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -2
  37. package/dist/backend/templates/harness/vcm-workflow-review-skill.js +7 -9
  38. package/dist/shared/types/role-stall.js +1 -0
  39. package/dist/shared/types/workflow.js +15 -0
  40. package/dist/shared/validation/artifact-check.js +3 -3
  41. package/dist/shared/validation/artifact-contract.js +1 -1
  42. package/dist/shared/validation/artifact-registry.js +17 -1
  43. package/dist-frontend/assets/{index-C_XHGNBD.css → index-B0d4Z6ny.css} +1 -1
  44. package/dist-frontend/assets/index-BvCmrFlN.js +97 -0
  45. package/dist-frontend/index.html +2 -2
  46. package/package.json +1 -1
  47. package/scripts/claude-plugins/vcm-lsp-bridge/.claude-plugin/plugin.json +21 -6
  48. package/scripts/harness-tools/vcm-artifact +1 -2
  49. package/scripts/harness-tools/vcm-bash-guard +204 -14
  50. package/dist-frontend/assets/index-Bocc2DWF.js +0 -97
package/README.md CHANGED
@@ -190,6 +190,12 @@ restart, but it does not infer transitions or choose the next role. Current
190
190
  artifacts, Gate Review state, Round/Turn state, and the role rules remain
191
191
  authoritative.
192
192
 
193
+ Whenever Project Manager asks the user a question, it first uses
194
+ `vcm-ask-user`. VCM records the wait and cancels any pending role dispatch in
195
+ one state update. Stop Hook then ends the PM turn without route scanning. Only
196
+ a new direct user message clears the wait, and the next role dispatch requires
197
+ a fresh Workflow Review.
198
+
193
199
  Typical flow:
194
200
 
195
201
  ```text
@@ -329,7 +335,8 @@ strict `workflow-progress.md` transition through `vcm-workflow-review`. VCM
329
335
  checks the confirmed dispatch history and current task artifacts, then grants
330
336
  one matching route. The target role's `UserPromptSubmit` consumes that approval
331
337
  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.
338
+ only by the user's exact one-time authorization recorded in the next Workflow
339
+ Progress submission.
333
340
 
334
341
  If the flow stops, VCM always shows a blocking pause alert. `Pause alert sound`
335
342
  only controls the looping sound. Enabling Gateway turns that preference off once;
@@ -474,14 +481,22 @@ Use it to:
474
481
  - merge task harness commits back to the connected repository branch when
475
482
  appropriate
476
483
 
477
- VCM bundles the Claude Code LSP bridge and loads it for Architect, Coder, and
478
- Reviewer sessions, including CCR launches. The project environment must still
484
+ VCM bundles the Claude Code LSP bridge and loads it only for Architect sessions,
485
+ including CCR launches. The project environment must still
479
486
  provide the language server for each detected language: `rust-analyzer`,
480
487
  `typescript-language-server`, `pyright-langserver`, `gopls`, `clangd`, or
481
- `jdtls`. Their Agent definitions preload `vcm-code-navigation`. Architect,
482
- Coder, and Reviewer use LSP for semantic relationships. Harness Studio reports
488
+ `jdtls`. Architect preloads `vcm-code-navigation` and uses LSP for semantic
489
+ relationships. Accessors, backing fields, trait items, implementations, wrappers,
490
+ and aliases are queried as separate symbols; a bounded source-text fallback may
491
+ only locate candidates after the correct LSP query is demonstrably partial.
492
+ Other roles use generated context and ordinary source reads.
493
+ Harness Studio reports
483
494
  whether the server executable and plugin can run; the role Session performs the
484
- real workspace warm-up and semantic query retries.
495
+ real workspace indexing and semantic query retries. VCM uses Claude Code progress
496
+ hooks to detect a model response, tool call, subagent, or compaction that may be
497
+ stalled. Detection only opens a warning; it does not interrupt the Session or
498
+ change the Round. The user can ignore that warning or explicitly stop and resume
499
+ the same Claude Session from the warning dialog.
485
500
 
486
501
  Harness Engineer is task-scoped and runs from the active task worktree. The
487
502
  backend automatically starts a fresh Harness Engineer for each active task or
@@ -493,10 +508,11 @@ files.
493
508
  `Auto memory` is the switch for the entire automated memory workflow. During
494
509
  Review Task Harness after Final Acceptance, Project Manager, Architect, Coder,
495
510
  Tester, and an enabled Reviewer submit proposals in sequence through
496
- `vcm-propose-memory`. Harness Engineer verifies and consolidates them before VCM
497
- records the result as part of the same Task Harness Retrospective. Workflow roles
498
- cannot edit active memory directly; Harness Engineer edits it only during the
499
- assigned Auto Memory Retrospective.
511
+ `vcm-propose-memory`. Harness Engineer verifies and consolidates them, commits
512
+ the reviewed memory, and writes the assigned machine-readable review result as
513
+ part of the same Task Harness Retrospective. Workflow roles cannot edit active
514
+ memory directly; Harness Engineer edits it only during the assigned Auto Memory
515
+ Retrospective.
500
516
 
501
517
  When Auto Memory is enabled, the planning Architect writes a provisional memory
502
518
  candidate before its post-planning Session restart. VCM snapshots that candidate
@@ -523,7 +539,10 @@ Final Acceptance
523
539
  -> Review pending Harness Feedback
524
540
  -> Review Auto Memory proposals, when Auto Memory is enabled
525
541
  -> Harness Engineer updates and commits memory, when Auto Memory is enabled
526
- -> VCM records the committed memory result
542
+ -> Harness Engineer writes review-result.json, when Auto Memory is enabled
543
+ -> VCM validates and records the committed memory result
544
+ -> VCM dispatches durable-document assignments one at a time
545
+ -> Complete Task Harness Retrospective after every assignment completes
527
546
  ```
528
547
 
529
548
  Memory proposal prompts sent to Project Manager, Architect, Coder, Tester, and
@@ -537,16 +556,29 @@ impact, and durable-document disposition, then evaluates every proposal item
537
556
  independently. Each Add or Update decision records why the memory is necessary,
538
557
  what happens if it is absent, whether it belongs in memory or a durable
539
558
  document, and the exact final memory content when retained. VCM validates that
540
- the commit changes only assigned memory host files and only their
541
- `<VCM-memory>` blocks; it does not parse or apply Harness Engineer's semantic
542
- decisions. Harness Engineer review and retrospective work remain tool role
543
- activity and do not participate in Round completion.
559
+ every existing entry and proposal has one structurally valid decision, every
560
+ move decision has one durable-document assignment, moved content has already
561
+ been removed from memory, and the commit changes only assigned memory host
562
+ files and only their `<VCM-memory>` blocks. VCM does not decide whether the
563
+ Harness Engineer's semantic judgment is correct. Harness Engineer review and
564
+ retrospective work remain tool role activity and do not participate in Round
565
+ completion.
566
+
567
+ For `move-to-durable-doc`, memory is removed in the Harness Engineer commit
568
+ before documentation work starts. VCM assigns architecture and known-issues
569
+ documents to Architect and testing documentation to Tester. For another target,
570
+ PM selects Architect, Coder, or Tester and asks the user only when ownership
571
+ cannot be determined. These assignments are backend-dispatched work rather than
572
+ a Docs-Only Flow. Each owner updates and commits the document, submits the exact
573
+ assignment ID in `docs-update-report.md`, and finishes before the next assignment
574
+ starts. Harness Studio shows assignment status and allows failed work to be
575
+ retried.
544
576
 
545
577
  When Auto Memory is disabled, Review Task Harness does not collect proposals or
546
578
  ask Harness Engineer to update memory. When enabled, both automatic and manual
547
579
  review requests collect proposals before starting the retrospective. A failed
548
- proposal collection or combined retrospective memory review must be retried
549
- from Harness Studio.
580
+ proposal collection, combined retrospective memory review, or durable-document
581
+ assignment must be retried from Harness Studio.
550
582
 
551
583
  ## Closing a Task
552
584
 
@@ -144,6 +144,9 @@ function artifactNameToPath(paths, artifactName) {
144
144
  if (artifactName === "test-report.md") {
145
145
  return paths.testReportPath;
146
146
  }
147
+ if (artifactName === "docs-update-report.md") {
148
+ return paths.docsUpdateReportPath;
149
+ }
147
150
  if (artifactName === "docs-sync-report.md") {
148
151
  return paths.docsSyncReportPath;
149
152
  }
@@ -208,6 +208,22 @@ export function registerHarnessRoutes(app, deps) {
208
208
  const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
209
209
  return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
210
210
  });
211
+ app.post("/api/projects/harness/memory/assignments/retry", async (request) => {
212
+ const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
213
+ return deps.autoMemoryService.retryDurableDocAssignment(project.repoRoot, task.worktreePath, request.body?.assignmentId ?? "");
214
+ });
215
+ app.post("/api/projects/harness/memory/assignments/owner", async (request) => {
216
+ const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
217
+ const owner = request.body?.owner;
218
+ if (owner !== "architect" && owner !== "coder" && owner !== "tester") {
219
+ throw new VcmError({
220
+ code: "DURABLE_DOC_OWNER_INVALID",
221
+ message: "Durable-document owner must be architect, coder, or tester.",
222
+ statusCode: 400
223
+ });
224
+ }
225
+ return deps.autoMemoryService.resolveDurableDocAssignmentOwner(project.repoRoot, task.worktreePath, request.body?.assignmentId ?? "", owner);
226
+ });
211
227
  }
212
228
  async function persistHarnessEngineerDefaults(appSettings, session) {
213
229
  const defaults = createDefaultToolSessionDefaults()["harness-engineer"];
@@ -71,7 +71,8 @@ export function registerTaskRoutes(app, deps) {
71
71
  orchestration,
72
72
  roundState,
73
73
  workflowState,
74
- architectRestart: deps.architectRestartService.getState(project.repoRoot, taskSlug)
74
+ architectRestart: deps.architectRestartService.getState(project.repoRoot, taskSlug),
75
+ roleStallWarning: deps.roleStallDetector.getWarning(project.repoRoot, taskSlug)
75
76
  };
76
77
  }
77
78
  catch (error) {
@@ -86,12 +87,30 @@ export function registerTaskRoutes(app, deps) {
86
87
  },
87
88
  roundState: degradedRoundState(taskSlug),
88
89
  workflowState: degradedWorkflowState(taskSlug),
89
- architectRestart: deps.architectRestartService.getState(repoRoot, taskSlug)
90
+ architectRestart: deps.architectRestartService.getState(repoRoot, taskSlug),
91
+ roleStallWarning: deps.roleStallDetector.getWarning(repoRoot, taskSlug)
90
92
  };
91
93
  }
92
94
  throw error;
93
95
  }
94
96
  });
97
+ app.post("/api/tasks/:taskSlug/role-stall/ignore", async (request) => {
98
+ const project = await requireCurrentProject(deps.projectService);
99
+ return deps.roleStallDetector.ignoreWarning(project.repoRoot, request.params.taskSlug, requireWarningId(request.body?.warningId));
100
+ });
101
+ app.post("/api/tasks/:taskSlug/role-stall/recover", async (request) => {
102
+ const project = await requireCurrentProject(deps.projectService);
103
+ const config = await deps.projectService.loadConfig(project.repoRoot);
104
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
105
+ const taskRepoRoot = getTaskRuntimeRepoRoot(task);
106
+ return deps.roleStallDetector.recoverWarning({
107
+ repoRoot: project.repoRoot,
108
+ taskRepoRoot,
109
+ stateRoot: config.stateRoot,
110
+ taskSlug: task.taskSlug,
111
+ warningId: requireWarningId(request.body?.warningId)
112
+ });
113
+ });
95
114
  app.post("/api/tasks/:taskSlug/workflow-state", async (request) => {
96
115
  const project = await requireCurrentProject(deps.projectService);
97
116
  const config = await deps.projectService.loadConfig(project.repoRoot);
@@ -117,6 +136,16 @@ export function registerTaskRoutes(app, deps) {
117
136
  return deps.taskCloseService.closeTask(project.repoRoot, request.params.taskSlug);
118
137
  });
119
138
  }
139
+ function requireWarningId(value) {
140
+ if (typeof value === "string" && value.trim()) {
141
+ return value.trim();
142
+ }
143
+ throw new VcmError({
144
+ code: "ROLE_STALL_WARNING_ID_REQUIRED",
145
+ message: "A role stall warning id is required.",
146
+ statusCode: 400
147
+ });
148
+ }
120
149
  async function requireCurrentProject(projectService) {
121
150
  const project = await projectService.getCurrentProject();
122
151
  if (!project) {
@@ -206,6 +235,7 @@ function degradedArtifactSummary(handoffDir) {
206
235
  architectDebugPath: `${handoffDir}/architect-debug.md`,
207
236
  architectureDiagnosisPath: `${handoffDir}/architecture-diagnosis.md`,
208
237
  testReportPath: `${handoffDir}/test-report.md`,
238
+ docsUpdateReportPath: `${handoffDir}/docs-update-report.md`,
209
239
  docsSyncReportPath: `${handoffDir}/docs-sync-report.md`,
210
240
  workflowProgressPath: `${handoffDir}/workflow-progress.md`,
211
241
  finalAcceptancePath: `${handoffDir}/final-acceptance.md`
@@ -1,30 +1,21 @@
1
1
  import { VcmError } from "../errors.js";
2
- import { submitTerminalInput } from "../runtime/terminal-submit.js";
3
2
  import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
4
3
  export function registerWorkflowControlRoutes(app, deps) {
5
4
  app.get("/api/tasks/:taskSlug/workflow-control", async (request) => {
6
5
  const context = await getContext(deps, request.params.taskSlug);
7
6
  return deps.workflowControlService.getState(context);
8
7
  });
9
- app.post("/api/tasks/:taskSlug/workflow-overrides/:overrideId/approve", async (request) => {
10
- const authorizationText = request.body?.authorizationText?.trim();
11
- if (!authorizationText) {
8
+ app.post("/api/tasks/:taskSlug/ask-user", async (request) => {
9
+ const context = await getContext(deps, request.params.taskSlug);
10
+ const question = typeof request.body?.question === "string" ? request.body.question.trim() : "";
11
+ if (!question) {
12
12
  throw new VcmError({
13
- code: "WORKFLOW_OVERRIDE_AUTHORIZATION_REQUIRED",
14
- message: "Enter the exact workflow exception that the user authorizes.",
13
+ code: "WORKFLOW_USER_QUESTION_REQUIRED",
14
+ message: "A non-empty user question is required.",
15
15
  statusCode: 400
16
16
  });
17
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;
18
+ return deps.workflowControlService.requestUserInput(context, question);
28
19
  });
29
20
  }
30
21
  async function getContext(deps, taskSlug) {
@@ -44,13 +35,3 @@ async function getContext(deps, taskSlug) {
44
35
  taskSlug: task.taskSlug
45
36
  };
46
37
  }
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,10 +28,12 @@ import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/v
28
28
  import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propose-memory-skill.js";
29
29
  import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
30
30
  import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
31
+ import { renderAskUserTool, renderVcmAskUserSkillRules } from "../templates/harness/vcm-ask-user-skill.js";
31
32
  import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
32
33
  import { renderVcmWorkflowReviewSkillRules } from "../templates/harness/vcm-workflow-review-skill.js";
33
34
  import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
34
35
  import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
36
+ import { renderResolveDurableDocAssignmentTool } from "../templates/harness/resolve-durable-doc-assignment.js";
35
37
  import { readVcmPackageVersion } from "../app-version.js";
36
38
  const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
37
39
  const APP_ROOT = path.resolve(CLI_DIR, "../../..");
@@ -57,8 +59,15 @@ const VCM_HOOK_DEFINITIONS = [
57
59
  { eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
58
60
  { eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
59
61
  { eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
62
+ { eventName: "PreToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
63
+ { eventName: "PostToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
64
+ { eventName: "PostToolUseFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
65
+ { eventName: "PostToolBatch", command: VCM_HOOK_COMMAND, timeout: 5 },
66
+ { eventName: "SubagentStart", command: VCM_HOOK_COMMAND, timeout: 5 },
67
+ { eventName: "SubagentStop", command: VCM_HOOK_COMMAND, timeout: 5 },
60
68
  { eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
61
69
  { eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
70
+ { eventName: "PreCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
62
71
  { eventName: "PostCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
63
72
  { eventName: "PermissionRequest", command: VCM_PERMISSION_REQUEST_HOOK_COMMAND, timeout: 5 }
64
73
  ];
@@ -74,8 +83,7 @@ const AGENT_FRONTMATTER = {
74
83
  },
75
84
  coder: {
76
85
  description: "VCM implementation role for scoped code changes and focused tests.",
77
- disallowedTools: CODE_ROLE_DISALLOWED_TOOLS.join(", "),
78
- skills: ["vcm-code-navigation"]
86
+ disallowedTools: CODE_ROLE_DISALLOWED_TOOLS.join(", ")
79
87
  },
80
88
  tester: {
81
89
  description: "VCM testing role for validation, test adequacy, approved-scope validation, and risk findings.",
@@ -83,8 +91,7 @@ const AGENT_FRONTMATTER = {
83
91
  },
84
92
  reviewer: {
85
93
  description: "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.",
86
- disallowedTools: REVIEWER_DISALLOWED_TOOLS.join(", "),
87
- skills: ["vcm-code-navigation"]
94
+ disallowedTools: REVIEWER_DISALLOWED_TOOLS.join(", ")
88
95
  },
89
96
  translator: {
90
97
  description: "VCM task-scoped translation tool role for conversation translation, file translation, bootstrap, and memory updates."
@@ -114,7 +121,9 @@ const REQUIRED_AGENT_TOOLS = {
114
121
  "harness-engineer": ["Skill"]
115
122
  };
116
123
  const REQUIRED_AGENT_SKILLS = {
117
- architect: ["vcm-code-navigation"],
124
+ architect: ["vcm-code-navigation"]
125
+ };
126
+ const REMOVED_AGENT_SKILLS = {
118
127
  coder: ["vcm-code-navigation"],
119
128
  reviewer: ["vcm-code-navigation"]
120
129
  };
@@ -285,7 +294,7 @@ const WHOLE_FILES = [
285
294
  path: ".claude/skills/vcm-code-navigation/SKILL.md",
286
295
  category: "skill",
287
296
  mode: 0o644,
288
- content: renderSkillFile("VCM Code Navigation Skill", "vcm-code-navigation", "Use when Architect, Coder, or Reviewer must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths.", renderVcmCodeNavigationSkillRules())
297
+ content: renderSkillFile("VCM Code Navigation Skill", "vcm-code-navigation", "Use when Architect must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths.", renderVcmCodeNavigationSkillRules())
289
298
  },
290
299
  {
291
300
  path: ".claude/skills/vcm-final-acceptance/SKILL.md",
@@ -311,6 +320,12 @@ const WHOLE_FILES = [
311
320
  mode: 0o644,
312
321
  content: renderSkillFile("VCM Route Message Skill", "vcm-route-message", "Use when project-manager dispatches a VCM role or when a VCM role reports a question, result, blocker, or finding back to project-manager.", renderVcmRouteMessageSkillRules())
313
322
  },
323
+ {
324
+ path: ".claude/skills/vcm-ask-user/SKILL.md",
325
+ category: "skill",
326
+ mode: 0o644,
327
+ content: renderSkillFile("VCM Ask User Skill", "vcm-ask-user", "Use whenever project-manager asks the user a question and must pause the workflow.", renderVcmAskUserSkillRules())
328
+ },
314
329
  {
315
330
  path: ".claude/skills/vcm-task-state/SKILL.md",
316
331
  category: "skill",
@@ -353,6 +368,12 @@ const WHOLE_FILES = [
353
368
  mode: 0o755,
354
369
  content: renderRequestGateReviewTool()
355
370
  },
371
+ {
372
+ path: ".ai/tools/vcm-ask-user",
373
+ category: "runtime-tool",
374
+ mode: 0o755,
375
+ content: renderAskUserTool()
376
+ },
356
377
  {
357
378
  path: ".ai/tools/update-task-state",
358
379
  category: "runtime-tool",
@@ -371,6 +392,12 @@ const WHOLE_FILES = [
371
392
  mode: 0o755,
372
393
  content: renderRequestArchitectRestartTool()
373
394
  },
395
+ {
396
+ path: ".ai/tools/resolve-durable-doc-assignment",
397
+ category: "runtime-tool",
398
+ mode: 0o755,
399
+ content: renderResolveDurableDocAssignmentTool()
400
+ },
374
401
  {
375
402
  path: ".ai/tools/run-long-check",
376
403
  category: "runtime-tool",
@@ -635,6 +662,7 @@ function fixedDirectories() {
635
662
  ".claude/skills/vcm-harness-bootstrap/",
636
663
  ".claude/skills/vcm-long-running-validation/",
637
664
  ".claude/skills/vcm-route-message/",
665
+ ".claude/skills/vcm-ask-user/",
638
666
  ".claude/skills/vcm-task-state/",
639
667
  ".claude/skills/vcm-workflow-review/",
640
668
  ".claude/skills/vcm-gate-review/",
@@ -711,6 +739,9 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
711
739
  for (const requiredSkill of REQUIRED_AGENT_SKILLS[definition.agentName] ?? []) {
712
740
  nextContent = ensureAgentSkill(nextContent, requiredSkill);
713
741
  }
742
+ for (const removedSkill of REMOVED_AGENT_SKILLS[definition.agentName] ?? []) {
743
+ nextContent = removeAgentSkill(nextContent, removedSkill);
744
+ }
714
745
  await writeIfChanged({
715
746
  targetPath,
716
747
  relativePath: definition.path,
@@ -829,6 +860,30 @@ function ensureAgentSkill(content, requiredSkill) {
829
860
  const nextSkills = `${skillsMatch[0].trimEnd()}\n - ${requiredSkill}`;
830
861
  return content.replace(frontmatter, frontmatter.replace(skillsMatch[0], nextSkills));
831
862
  }
863
+ function removeAgentSkill(content, removedSkill) {
864
+ const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
865
+ if (!frontmatterMatch) {
866
+ return content;
867
+ }
868
+ const frontmatter = frontmatterMatch[0];
869
+ const skillsMatch = frontmatter.match(/^skills:[ \t]*(?:\r?\n((?:\s+-\s+[^\r\n]+\r?\n?)*))?/m);
870
+ if (!skillsMatch) {
871
+ return content;
872
+ }
873
+ const listedSkills = (skillsMatch[1] ?? "")
874
+ .split(/\r?\n/)
875
+ .map((line) => line.match(/^\s+-\s+(.+)$/)?.[1]?.trim())
876
+ .filter(Boolean);
877
+ const remainingSkills = listedSkills.filter((skill) => skill !== removedSkill);
878
+ if (remainingSkills.length === listedSkills.length) {
879
+ return content;
880
+ }
881
+ const lineEnding = skillsMatch[0].includes("\r\n") ? "\r\n" : "\n";
882
+ const replacement = remainingSkills.length > 0
883
+ ? `skills:${lineEnding}${remainingSkills.map((skill) => ` - ${skill}`).join(lineEnding)}${lineEnding}`
884
+ : "";
885
+ return content.replace(frontmatter, frontmatter.replace(skillsMatch[0], replacement));
886
+ }
832
887
  function migrateLegacyManagedFile(definition, currentContent, block) {
833
888
  const legacyContent = definition.legacyWholeFile?.trimEnd();
834
889
  if (!legacyContent) {
@@ -44,7 +44,7 @@ export const REVIEWER_RUNTIME_DISALLOWED_TOOLS = [
44
44
  "Edit",
45
45
  ...NON_VCM_ROLE_RUNTIME_TOOLS
46
46
  ];
47
- const LSP_ROLE_NAMES = new Set(["architect", "coder", "reviewer"]);
47
+ const LSP_ROLE_NAMES = new Set(["architect"]);
48
48
  export function roleUsesLsp(role) {
49
49
  return LSP_ROLE_NAMES.has(role);
50
50
  }
@@ -13,6 +13,7 @@ import { createAppSettingsService } from "./services/app-settings-service.js";
13
13
  import { createCcrIntegrationService } from "./services/ccr-integration-service.js";
14
14
  import { createAutoMemoryService } from "./services/auto-memory-service.js";
15
15
  import { createArchitectRestartService } from "./services/architect-restart-service.js";
16
+ import { createRoleStallDetectorService } from "./services/role-stall-detector-service.js";
16
17
  import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
17
18
  import { createGateReviewService } from "./services/gate-review-service.js";
18
19
  import { createHarnessFeedbackService } from "./services/harness-feedback-service.js";
@@ -134,7 +135,8 @@ export async function createServer(deps, options = {}) {
134
135
  taskLaunchService: deps.taskLaunchService,
135
136
  roundService: deps.roundService,
136
137
  taskWorkflowService: deps.taskWorkflowService,
137
- architectRestartService: deps.architectRestartService
138
+ architectRestartService: deps.architectRestartService,
139
+ roleStallDetector: deps.roleStallDetector
138
140
  });
139
141
  registerSessionRoutes(app, {
140
142
  projectService: deps.projectService,
@@ -154,9 +156,7 @@ export async function createServer(deps, options = {}) {
154
156
  registerWorkflowControlRoutes(app, {
155
157
  projectService: deps.projectService,
156
158
  taskService: deps.taskService,
157
- sessionService: deps.sessionService,
158
- workflowControlService: deps.workflowControlService,
159
- runtime: deps.runtime
159
+ workflowControlService: deps.workflowControlService
160
160
  });
161
161
  }
162
162
  registerMessageRoutes(app, {
@@ -332,6 +332,7 @@ export function createDefaultServerDeps(options = {}) {
332
332
  appSettings,
333
333
  sessionService,
334
334
  roundService,
335
+ workflowControlService,
335
336
  onArchitecturePlanDisposition: ({ repoRoot, taskSlug, accepted }) => architectRestartService.recordArchitectureGateDisposition(repoRoot, taskSlug, accepted)
336
337
  });
337
338
  const translationWorkerService = createTranslationWorkerService({
@@ -340,6 +341,10 @@ export function createDefaultServerDeps(options = {}) {
340
341
  sessionService
341
342
  });
342
343
  const transcripts = createClaudeTranscriptService();
344
+ const roleStallDetector = createRoleStallDetectorService({
345
+ sessionService,
346
+ roundService
347
+ });
343
348
  const translationService = createTranslationService({
344
349
  runtime,
345
350
  sessionRegistry: registry,
@@ -411,7 +416,9 @@ export function createDefaultServerDeps(options = {}) {
411
416
  gatewayService,
412
417
  jobGuard: createJobGuardService(),
413
418
  translationWorkerService,
414
- architectRestartService
419
+ architectRestartService,
420
+ roleStallDetector,
421
+ workflowControlService
415
422
  });
416
423
  const runtimeCoordinator = createRuntimeCoordinatorService({
417
424
  appSettings,
@@ -423,6 +430,7 @@ export function createDefaultServerDeps(options = {}) {
423
430
  harnessFeedbackService,
424
431
  autoMemoryService,
425
432
  roundService,
433
+ roleStallDetector,
426
434
  gatewayService,
427
435
  async getStateRoot(repoRoot) {
428
436
  return (await projectService.loadConfig(repoRoot)).stateRoot;
@@ -463,6 +471,7 @@ export function createDefaultServerDeps(options = {}) {
463
471
  autoMemoryService,
464
472
  commandDispatcher,
465
473
  claudeHookService,
474
+ roleStallDetector,
466
475
  messageService,
467
476
  taskLaunchService,
468
477
  gateReviewService,
@@ -4,9 +4,10 @@ import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js
4
4
  import { getArtifactDefinition, isArtifactKind } from "../../shared/validation/artifact-registry.js";
5
5
  import { VcmError } from "../errors.js";
6
6
  import { resolveRepoPath } from "../adapters/filesystem.js";
7
- import { renderArchitectureBriefTemplate, renderArchitectureDiagnosisTemplate, renderArchitectureEvidenceTemplate, renderArchitecturePlanTemplate, renderArchitectDebugTemplate, renderCoderCompletionTemplate, renderDocsSyncReportTemplate, renderFinalAcceptanceTemplate, renderKnownIssuesTemplate, renderPlanningProgressTemplate, renderMessageRouteTemplate, renderTestReportTemplate, renderWorkflowProgressTemplate } from "../templates/handoff.js";
7
+ import { renderArchitectureBriefTemplate, renderArchitectureDiagnosisTemplate, renderArchitectureEvidenceTemplate, renderArchitecturePlanTemplate, renderArchitectDebugTemplate, renderCoderCompletionTemplate, renderDocsUpdateReportTemplate, renderDocsSyncReportTemplate, renderFinalAcceptanceTemplate, renderKnownIssuesTemplate, renderPlanningProgressTemplate, renderMessageRouteTemplate, renderTestReportTemplate, renderWorkflowProgressTemplate } from "../templates/handoff.js";
8
8
  import { renderRoleCommandTemplate } from "../templates/role-command.js";
9
9
  import { validateMemoryProposal } from "./memory-proposal-validation.js";
10
+ import { isMemoryProposalSubmissionPath } from "./memory-review-paths.js";
10
11
  const ARTIFACT_PATH_KEYS = [
11
12
  ["architecture-brief", "architectureBriefPath"],
12
13
  ["architecture-evidence", "architectureEvidencePath"],
@@ -17,6 +18,7 @@ const ARTIFACT_PATH_KEYS = [
17
18
  ["architect-debug", "architectDebugPath"],
18
19
  ["architecture-diagnosis", "architectureDiagnosisPath"],
19
20
  ["test-report", "testReportPath"],
21
+ ["docs-update-report", "docsUpdateReportPath"],
20
22
  ["docs-sync-report", "docsSyncReportPath"],
21
23
  ["workflow-progress", "workflowProgressPath"],
22
24
  ["final-acceptance", "finalAcceptancePath"]
@@ -54,6 +56,7 @@ export function createArtifactService(fs, deps = {}) {
54
56
  architectDebugPath: path.posix.join(handoffDir, "architect-debug.md"),
55
57
  architectureDiagnosisPath: path.posix.join(handoffDir, "architecture-diagnosis.md"),
56
58
  testReportPath: path.posix.join(handoffDir, "test-report.md"),
59
+ docsUpdateReportPath: path.posix.join(handoffDir, "docs-update-report.md"),
57
60
  docsSyncReportPath: path.posix.join(handoffDir, "docs-sync-report.md"),
58
61
  workflowProgressPath: path.posix.join(handoffDir, "workflow-progress.md"),
59
62
  finalAcceptancePath: path.posix.join(handoffDir, "final-acceptance.md")
@@ -84,6 +87,7 @@ export function createArtifactService(fs, deps = {}) {
84
87
  [paths.architectDebugPath, renderArchitectDebugTemplate(input.taskSlug)],
85
88
  [paths.architectureDiagnosisPath, renderArchitectureDiagnosisTemplate(input.taskSlug)],
86
89
  [paths.testReportPath, renderTestReportTemplate(input.taskSlug)],
90
+ [paths.docsUpdateReportPath, renderDocsUpdateReportTemplate(input.taskSlug)],
87
91
  [paths.docsSyncReportPath, renderDocsSyncReportTemplate(input.taskSlug)],
88
92
  [paths.workflowProgressPath, renderWorkflowProgressTemplate(input.taskSlug)],
89
93
  [paths.finalAcceptancePath, renderFinalAcceptanceTemplate(input.taskSlug)],
@@ -189,10 +193,11 @@ export function createArtifactService(fs, deps = {}) {
189
193
  }
190
194
  if (isArtifactKind(input.kind)) {
191
195
  const definition = getArtifactDefinition(input.kind);
192
- if (definition.owner !== input.role) {
196
+ const owners = Array.isArray(definition.owner) ? definition.owner : [definition.owner];
197
+ if (!owners.includes(input.role)) {
193
198
  throw new VcmError({
194
199
  code: "ARTIFACT_OWNER_MISMATCH",
195
- message: `${input.kind} is owned by ${definition.owner}, not ${input.role}.`,
200
+ message: `${input.kind} is owned by ${owners.join("|")}, not ${input.role}.`,
196
201
  statusCode: 403
197
202
  });
198
203
  }
@@ -314,8 +319,8 @@ async function validateDynamicArtifact(fs, input, content, workflowControlServic
314
319
  if (!VCM_ROLE_NAMES.includes(input.role)) {
315
320
  throw artifactRejected(input.kind, ["Only a VCM workflow role may submit a Memory Proposal."]);
316
321
  }
317
- if (!/^\.ai\/vcm\/memory-review\/(?:runs\/[A-Za-z0-9._-]+\/drafts|candidates)\/[A-Za-z0-9._-]+\.md$/.test(artifactPath)) {
318
- throw artifactRejected(input.kind, ["Memory proposal path is outside the VCM memory-review draft locations."]);
322
+ if (!isMemoryProposalSubmissionPath(artifactPath, input.role)) {
323
+ throw artifactRejected(input.kind, ["Memory proposal path is not assigned to the submitting role."]);
319
324
  }
320
325
  const memoryError = validateMemoryProposal(content);
321
326
  if (memoryError) {
@@ -333,39 +338,12 @@ async function validateDynamicArtifact(fs, input, content, workflowControlServic
333
338
  validateHarnessFeedback(content);
334
339
  return { kind: input.kind, root: input.baseRepoRoot, path: artifactPath };
335
340
  }
336
- if (input.kind === "retrospective-report") {
337
- if (input.role !== "harness-engineer") {
338
- throw artifactRejected(input.kind, ["Only Harness Engineer may submit a retrospective report."]);
339
- }
340
- if (!/^\.ai\/vcm\/harness-feedback\/task-retrospectives\/[A-Za-z0-9._-]+\.md$/.test(artifactPath)) {
341
- throw artifactRejected(input.kind, ["Retrospective report path must be under .ai/vcm/harness-feedback/task-retrospectives/."]);
342
- }
343
- validateRetrospectiveReport(content);
344
- return { kind: input.kind, root: input.baseRepoRoot, path: artifactPath };
345
- }
346
341
  throw new VcmError({
347
342
  code: "ARTIFACT_KIND_INVALID",
348
343
  message: `Unknown managed artifact kind: ${input.kind}`,
349
344
  statusCode: 400
350
345
  });
351
346
  }
352
- function validateRetrospectiveReport(content) {
353
- const errors = getRetrospectiveReportErrors(content);
354
- if (errors.length > 0)
355
- throw artifactRejected("retrospective-report", errors);
356
- }
357
- export function getRetrospectiveReportErrors(content) {
358
- const errors = [];
359
- if (!/^# Task Harness Retrospective(?::\s*.+)?\s*$/m.test(content)) {
360
- errors.push("Retrospective report requires '# Task Harness Retrospective: <task>'.");
361
- }
362
- for (const heading of ["Findings", "Feedback Dispositions", "Recommended Harness Changes", "VCM Issue Drafts"]) {
363
- if (!new RegExp(`^## ${escapeRegExp(heading)}\\s*$`, "m").test(content)) {
364
- errors.push(`Missing required section: ${heading}.`);
365
- }
366
- }
367
- return errors;
368
- }
369
347
  function validateCoderWorkerReport(content) {
370
348
  const errors = [];
371
349
  if (!/^# Coder Worker Report:\s*\S.+$/m.test(content)) {