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
@@ -80,7 +80,7 @@ export function createClaudeHookService(deps) {
80
80
  return { project };
81
81
  }
82
82
  async function processTranslatorHook(input) {
83
- const eventName = parseHookEvent(input.event.hook_event_name);
83
+ const eventName = parseBusinessHookEvent(input.event.hook_event_name);
84
84
  const context = await getTranslatorHookContext();
85
85
  const session = input.taskSlug === "__project__"
86
86
  ? await deps.sessionService.recordProjectTranslatorHookEvent(context.project.repoRoot, {
@@ -114,7 +114,7 @@ export function createClaudeHookService(deps) {
114
114
  };
115
115
  }
116
116
  async function processHarnessEngineerHook(input) {
117
- const eventName = parseHookEvent(input.event.hook_event_name);
117
+ const eventName = parseBusinessHookEvent(input.event.hook_event_name);
118
118
  const context = await getProjectToolHookContext("Harness Engineer");
119
119
  const projectScoped = input.taskSlug === "__project_harness_engineer__";
120
120
  const session = projectScoped
@@ -158,7 +158,7 @@ export function createClaudeHookService(deps) {
158
158
  ? await deps.harnessFeedbackService?.handleTaskRetrospectiveHook(context.project.repoRoot, {
159
159
  taskSlug: activeTask.taskSlug,
160
160
  eventName,
161
- memoryReviewSucceeded: memoryState?.status !== "failed"
161
+ memoryReviewStatus: memoryState?.status ?? "idle"
162
162
  })
163
163
  : false;
164
164
  if (memoryHandled || retrospectiveHandled) {
@@ -212,6 +212,16 @@ export function createClaudeHookService(deps) {
212
212
  if (memoryResult) {
213
213
  return memoryResult;
214
214
  }
215
+ const prompt = stringOrUndefined(input.event.prompt);
216
+ if (input.role === "project-manager"
217
+ && prompt
218
+ && isDirectUserPrompt(prompt)
219
+ && deps.workflowControlService) {
220
+ const workflowState = await deps.workflowControlService.getState(createWorkflowControlContext(context));
221
+ if (workflowState.awaitingUser) {
222
+ await deps.workflowControlService.resolveUserInput(createWorkflowControlContext(context));
223
+ }
224
+ }
215
225
  const session = await deps.sessionService.recordClaudeHookEvent(context.project.repoRoot, {
216
226
  taskSlug: context.taskSlug,
217
227
  role: input.role,
@@ -240,6 +250,12 @@ export function createClaudeHookService(deps) {
240
250
  role: input.role,
241
251
  eventName
242
252
  });
253
+ await deps.roleStallDetector?.recordHook({
254
+ ...createStallContext(context),
255
+ role: input.role,
256
+ eventName,
257
+ event: input.event
258
+ });
243
259
  }
244
260
  if (session) {
245
261
  await deps.translationService.recordConversationBoundary({
@@ -260,7 +276,7 @@ export function createClaudeHookService(deps) {
260
276
  handoffDir: context.task.handoffDir,
261
277
  taskSlug: context.taskSlug,
262
278
  role: input.role,
263
- prompt: stringOrUndefined(input.event.prompt)
279
+ prompt
264
280
  });
265
281
  if (submitted) {
266
282
  await deps.architectRestartService?.recordRouteAccepted(context.project.repoRoot, context.taskSlug, submitted);
@@ -292,6 +308,7 @@ export function createClaudeHookService(deps) {
292
308
  return completedHookResult(input, eventName);
293
309
  }
294
310
  await clearStopFailureRecoveryState(context, input.role);
311
+ let pmAwaitingUser = false;
295
312
  if (options.allowBlock && deps.jobGuard) {
296
313
  const verdict = await deps.jobGuard.evaluateStop({
297
314
  repoRoot: context.project.repoRoot,
@@ -313,6 +330,42 @@ export function createClaudeHookService(deps) {
313
330
  };
314
331
  }
315
332
  }
333
+ if (input.role === "project-manager" && deps.workflowControlService) {
334
+ const workflowState = await deps.workflowControlService.getState(createWorkflowControlContext(context));
335
+ if (workflowState.awaitingUser) {
336
+ pmAwaitingUser = true;
337
+ }
338
+ else {
339
+ const lastAssistantMessage = stringOrUndefined(input.event.last_assistant_message);
340
+ if (options.allowBlock && lastAssistantMessage && asksUserQuestion(lastAssistantMessage)) {
341
+ return {
342
+ ok: true,
343
+ eventName,
344
+ taskSlug: context.taskSlug,
345
+ role: input.role,
346
+ sessionUpdated: false,
347
+ dispatchedCount: 0,
348
+ stopDecision: {
349
+ behavior: "block",
350
+ reason: "You asked the user a question without registering the wait. Use vcm-ask-user with the exact question, then ask that question and stop. Do not advance or route the workflow."
351
+ }
352
+ };
353
+ }
354
+ }
355
+ }
356
+ await deps.roleStallDetector?.recordHook({
357
+ ...createStallContext(context),
358
+ role: input.role,
359
+ eventName,
360
+ event: input.event
361
+ });
362
+ if (pmAwaitingUser) {
363
+ return recordTurnEnd(input, context, eventName, {
364
+ dispatchRouteFiles: false,
365
+ notifyGateway: true,
366
+ settleGuard: false
367
+ });
368
+ }
316
369
  return recordTurnEnd(input, context, eventName, {
317
370
  dispatchRouteFiles: !isReviewerRoleName(input.role),
318
371
  notifyGateway: true,
@@ -357,6 +410,12 @@ export function createClaudeHookService(deps) {
357
410
  if (memoryResult) {
358
411
  return memoryResult;
359
412
  }
413
+ await deps.roleStallDetector?.recordHook({
414
+ ...createStallContext(context),
415
+ role: input.role,
416
+ eventName,
417
+ event: input.event
418
+ });
360
419
  const routeDispatchInput = createRouteDispatchInput(input, context);
361
420
  const pending = await deps.messageService.listPendingRouteFiles(routeDispatchInput);
362
421
  const hasCompletionEvidence = pending.some((routeFile) => routeFile.fromRole === input.role);
@@ -430,6 +489,14 @@ export function createClaudeHookService(deps) {
430
489
  runtimeSessionId: stringOrUndefined(input.event.vcm_runtime_session_id),
431
490
  runtimeSessionToken: input.runtimeSessionToken
432
491
  });
492
+ if (session) {
493
+ await deps.roleStallDetector?.recordHook({
494
+ ...createStallContext(context),
495
+ role: input.role,
496
+ eventName,
497
+ event: input.event
498
+ });
499
+ }
433
500
  return {
434
501
  ok: true,
435
502
  eventName,
@@ -528,6 +595,17 @@ export function createClaudeHookService(deps) {
528
595
  if (!deps.autoMemoryService || !(await deps.autoMemoryService.isRoleMemoryTurn(context.taskRepoRoot, input.role))) {
529
596
  return undefined;
530
597
  }
598
+ const prompt = stringOrUndefined(input.event.prompt);
599
+ if (eventName === "UserPromptSubmit"
600
+ && input.role === "project-manager"
601
+ && prompt
602
+ && isDirectUserPrompt(prompt)
603
+ && deps.workflowControlService) {
604
+ const workflowState = await deps.workflowControlService.getState(createWorkflowControlContext(context));
605
+ if (workflowState.awaitingUser) {
606
+ await deps.workflowControlService.resolveUserInput(createWorkflowControlContext(context));
607
+ }
608
+ }
531
609
  const session = await deps.sessionService.recordClaudeHookEvent(context.project.repoRoot, {
532
610
  taskSlug: context.taskSlug,
533
611
  role: input.role,
@@ -552,6 +630,14 @@ export function createClaudeHookService(deps) {
552
630
  eventName
553
631
  });
554
632
  }
633
+ if (boundToTask) {
634
+ await deps.roleStallDetector?.recordHook({
635
+ ...createStallContext(context),
636
+ role: input.role,
637
+ eventName,
638
+ event: input.event
639
+ });
640
+ }
555
641
  await deps.autoMemoryService.handleRoleHook({
556
642
  baseRepoRoot: context.project.repoRoot,
557
643
  taskRepoRoot: context.taskRepoRoot,
@@ -559,6 +645,10 @@ export function createClaudeHookService(deps) {
559
645
  role: input.role,
560
646
  eventName
561
647
  });
648
+ if (eventName === "Stop") {
649
+ const memoryState = await deps.autoMemoryService.getState(context.project.repoRoot, context.taskRepoRoot);
650
+ await deps.harnessFeedbackService?.completeWaitingTaskRetrospective(context.project.repoRoot, context.taskSlug, memoryState.status);
651
+ }
562
652
  return {
563
653
  ok: true,
564
654
  eventName,
@@ -579,6 +669,14 @@ export function createClaudeHookService(deps) {
579
669
  ...(stoppedRole ? { stoppedRole } : {})
580
670
  };
581
671
  }
672
+ function createWorkflowControlContext(context) {
673
+ return {
674
+ taskRepoRoot: context.taskRepoRoot,
675
+ stateRoot: context.config.stateRoot,
676
+ handoffDir: context.task.handoffDir,
677
+ taskSlug: context.taskSlug
678
+ };
679
+ }
582
680
  async function isCurrentRoleHook(context, input, eventName) {
583
681
  const current = await deps.sessionService.getRoleSession(context.project.repoRoot, context.taskSlug, input.role);
584
682
  return Boolean(current && matchesRoleHookSession(current, {
@@ -764,6 +862,31 @@ export function createClaudeHookService(deps) {
764
862
  taskSlug: context.taskSlug
765
863
  };
766
864
  }
865
+ function createStallContext(context) {
866
+ return {
867
+ repoRoot: context.project.repoRoot,
868
+ taskRepoRoot: context.taskRepoRoot,
869
+ stateRoot: context.config.stateRoot,
870
+ taskSlug: context.taskSlug
871
+ };
872
+ }
873
+ async function processProgressHook(input, eventName) {
874
+ const role = input.role;
875
+ if (!isVcmRoleName(role)) {
876
+ return completedHookResult(input, eventName);
877
+ }
878
+ const context = await getHookContext(input);
879
+ if (!(await isCurrentRoleHook(context, input, eventName))) {
880
+ return completedHookResult(input, eventName);
881
+ }
882
+ await deps.roleStallDetector?.recordHook({
883
+ ...createStallContext(context),
884
+ role,
885
+ eventName,
886
+ event: input.event
887
+ });
888
+ return completedHookResult(input, eventName);
889
+ }
767
890
  function renderStopFailureRecoveryPrompt() {
768
891
  return [
769
892
  "[VCM Recovery]",
@@ -806,6 +929,17 @@ export function createClaudeHookService(deps) {
806
929
  });
807
930
  }
808
931
  const preferences = await deps.appSettings.getPreferences();
932
+ if (deps.roleStallDetector) {
933
+ const context = await getHookContext(input);
934
+ if (await isCurrentRoleHook(context, input, "PermissionRequest")) {
935
+ await deps.roleStallDetector.recordHook({
936
+ ...createStallContext(context),
937
+ role: input.role,
938
+ eventName: "PermissionRequest",
939
+ event: input.event
940
+ });
941
+ }
942
+ }
809
943
  if (preferences.permissionRequestMode !== "allowAll") {
810
944
  return undefined;
811
945
  }
@@ -821,13 +955,16 @@ export function createClaudeHookService(deps) {
821
955
  return {
822
956
  async handleHook(input) {
823
957
  return withRoleHookLock(input, async () => {
958
+ const eventName = parseHookEvent(input.event.hook_event_name);
959
+ if (isProgressHook(eventName)) {
960
+ return processProgressHook(input, eventName);
961
+ }
824
962
  if (isTranslatorToolRoleName(input.role)) {
825
963
  return processTranslatorHook(input);
826
964
  }
827
965
  if (isHarnessEngineerToolRoleName(input.role)) {
828
966
  return processHarnessEngineerHook(input);
829
967
  }
830
- const eventName = parseHookEvent(input.event.hook_event_name);
831
968
  if (eventName === "UserPromptSubmit") {
832
969
  return handleUserPromptSubmitHook(input);
833
970
  }
@@ -860,6 +997,13 @@ function parseHookEvent(value) {
860
997
  if (value === "UserPromptSubmit"
861
998
  || value === "Stop"
862
999
  || value === "StopFailure"
1000
+ || value === "PreToolUse"
1001
+ || value === "PostToolUse"
1002
+ || value === "PostToolUseFailure"
1003
+ || value === "PostToolBatch"
1004
+ || value === "SubagentStart"
1005
+ || value === "SubagentStop"
1006
+ || value === "PreCompact"
863
1007
  || value === "PostCompact") {
864
1008
  return value;
865
1009
  }
@@ -867,9 +1011,28 @@ function parseHookEvent(value) {
867
1011
  code: "HOOK_EVENT_UNSUPPORTED",
868
1012
  message: `Unsupported Claude Code hook event: ${String(value)}`,
869
1013
  statusCode: 400,
870
- hint: "VCM accepts UserPromptSubmit, Stop, StopFailure, and PostCompact hooks only."
1014
+ hint: "Use a Claude Code event installed by the VCM Harness."
871
1015
  });
872
1016
  }
1017
+ function isProgressHook(eventName) {
1018
+ return eventName === "PreToolUse"
1019
+ || eventName === "PostToolUse"
1020
+ || eventName === "PostToolUseFailure"
1021
+ || eventName === "PostToolBatch"
1022
+ || eventName === "SubagentStart"
1023
+ || eventName === "SubagentStop"
1024
+ || eventName === "PreCompact";
1025
+ }
1026
+ function parseBusinessHookEvent(value) {
1027
+ const eventName = parseHookEvent(value);
1028
+ if (eventName === "UserPromptSubmit"
1029
+ || eventName === "Stop"
1030
+ || eventName === "StopFailure"
1031
+ || eventName === "PostCompact") {
1032
+ return eventName;
1033
+ }
1034
+ throwUnsupportedEvent(eventName);
1035
+ }
873
1036
  function throwUnsupportedEvent(eventName) {
874
1037
  throw new VcmError({
875
1038
  code: "HOOK_EVENT_UNSUPPORTED",
@@ -918,3 +1081,17 @@ function normalizeDiagnosticString(value, maxLength) {
918
1081
  function stringOrUndefined(value) {
919
1082
  return typeof value === "string" ? value : undefined;
920
1083
  }
1084
+ function isDirectUserPrompt(prompt) {
1085
+ const normalized = prompt.trim();
1086
+ return normalized.length > 0 && !/^\[VCM(?:\s|\])/i.test(normalized);
1087
+ }
1088
+ function asksUserQuestion(message) {
1089
+ const withoutCodeBlocks = message.replace(/```[\s\S]*?```/g, "");
1090
+ const lines = withoutCodeBlocks
1091
+ .split(/\r?\n/)
1092
+ .map((line) => line.trim())
1093
+ .filter(Boolean);
1094
+ return lines.some((line) => /[??]["')\]}】”’]*$/.test(line)
1095
+ || /^(?:please\s+(?:answer|choose|confirm|decide|provide|select|tell)|(?:can|could|do|does|is|are|should|will|would)\s+you\b)/i.test(line)
1096
+ || /^(?:请(?:回答|选择|确认|决定|提供|告知)|你(?:是否|能否|要不要|可否)|是否需要你|需要你(?:选择|确认|决定|提供|告知))/.test(line));
1097
+ }