vibe-coding-master 0.0.15 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -194,7 +194,7 @@ All sidebar sections are collapsed by default. When no task is selected, `Reposi
194
194
 
195
195
  ## Translation
196
196
 
197
- The role toolbar has an `Auto orchestration` on/off button immediately to the left of the `Translate` button. The `Translate` button opens a translation panel beside the embedded terminal. The terminal and translation panel split the available width evenly.
197
+ The task header has a global `Translate` button next to `Close Task`. It opens a translation panel beside the embedded terminal for the role consoles and keeps the same on/off setting while switching roles. The terminal and translation panel split the available width evenly.
198
198
 
199
199
  Translation settings are local and stored in:
200
200
 
@@ -206,7 +206,7 @@ The same file stores recent repository paths. The translation API key is stored
206
206
 
207
207
  The sidebar `Settings` section also stores the UI theme preference in this file. The default is `system`, which follows the OS/browser color-scheme preference; users can cycle between `System`, `Light`, and `Dark`.
208
208
 
209
- The same sidebar also has a `Round alert` toggle. It is on by default and controls the in-app prompt plus short sound that fires when VCM detects that the current full conversation round is complete. The `Try alert` button triggers the same local prompt and sound for testing.
209
+ The same sidebar also has a `Round alert` toggle. It is on by default and controls the in-app prompt plus a soft two-note completion chime that fires when VCM detects that the current full conversation round is complete. The `Try alert` button triggers the same local prompt and sound for testing.
210
210
 
211
211
  Translation behavior:
212
212
 
@@ -258,7 +258,7 @@ For `.gitignore`, VCM uses a gitignore-native managed block:
258
258
 
259
259
  `.ai/vcm/` is the active VCM local control area, and `.claude/worktrees/` is the Claude-compatible task worktree area. The base repo keeps the task index; each task runtime repo keeps its own session, message, orchestration, and translation state.
260
260
 
261
- VCM also JSON-merges `.claude/settings.json` to install a Claude Code `Stop` hook. The hook posts directly to the local VCM backend, so roles do not need a VCM CLI command to report turn completion.
261
+ VCM also JSON-merges `.claude/settings.json` to install Claude Code `UserPromptSubmit` and `Stop` hooks. The hooks post directly to the local VCM backend, so roles do not need a VCM CLI command to confirm delivery or report turn completion.
262
262
 
263
263
  After applying harness changes, VCM reports the exact files changed and reminds the user to review and commit them before starting long-running work.
264
264
 
@@ -276,7 +276,10 @@ Claude Code role
276
276
  -> ends the Claude Code turn
277
277
  -> Stop hook calls VCM backend directly
278
278
  -> VCM scans pending route files
279
- -> VCM validates, snapshots, and dispatches pending messages
279
+ -> VCM validates and dispatches at most one pending route file
280
+ -> VCM records dispatchingAt and the GUI switches to the target role
281
+ -> VCM snapshots only actually delivered message history
282
+ -> UserPromptSubmit hook records acceptedAt and clears the matching route file
280
283
  ```
281
284
 
282
285
  Examples:
@@ -301,15 +304,16 @@ Each directed role route has exactly one message file. If a role changes its min
301
304
 
302
305
  ## Orchestration Modes
303
306
 
304
- VCM has a task-level `Auto orchestration` switch in the active role toolbar, immediately to the left of `Translate`.
307
+ VCM has a task-level `Auto orchestration` switch in the active role toolbar.
305
308
 
306
309
  When it is off, VCM is in manual mode:
307
310
 
308
311
  - Roles may write pending route files under `.ai/vcm/handoffs/messages/`.
309
312
  - Messages appear in the `Messages` modal.
310
313
  - The user can inspect them.
311
- - The current GUI shows sequence, timestamp, status, body preview, path, `Copy`, and `Mark All Done`.
312
- - `Mark All Done` marks open message records as `acknowledged` after the user manually copied or handled stuck queued/in-flight messages.
314
+ - The current GUI shows newest message history first with stable increasing sequence numbers, timestamp, body preview, path, `Copy`, `Mark All Done`, and `Delete All`.
315
+ - `Mark All Done` clears non-empty pending route files after the user manually copied or handled stuck handoff content.
316
+ - `Delete All` removes message history from the Messages modal without touching pending route files.
313
317
  - The user decides what to do next by copying or manually acting on the message.
314
318
  - VCM does not write to the target terminal or press Enter for the user.
315
319
 
@@ -318,25 +322,27 @@ When it is on, VCM is in auto mode:
318
322
  - Backend policy still applies.
319
323
  - Roles write pending route files under `.ai/vcm/handoffs/messages/`.
320
324
  - On Claude Code `Stop`, VCM scans pending route files.
321
- - If the target role session is running, idle, and has no active message, VCM writes a `[VCM MESSAGE]` envelope to the target terminal and submits it.
322
- - After successful terminal submission, VCM snapshots the body in message history and clears the source route file.
323
- - When the GUI observes a newly submitted auto message, it switches the active role tab to that message's target role so the user can watch the next step.
324
- - VCM enforces per-role turn-taking: each target role can have at most one in-flight `submitted` message.
325
+ - If the target role session is running and idle, VCM writes a `[VCM MESSAGE]` envelope to the target terminal and submits it.
326
+ - Just before terminal submission, VCM records `dispatchingAt`, waits briefly for the GUI to switch tabs, then writes to the embedded terminal.
327
+ - After successful terminal write, VCM snapshots the delivered body as message history.
328
+ - Claude Code `UserPromptSubmit` confirms that the prompt was accepted; VCM stores `acceptedAt` and clears the source route file if it still contains the same message.
329
+ - When the GUI observes a newly dispatching auto message, it switches the active role tab to that message's target role before the message is submitted.
330
+ - VCM enforces sequential turn-taking from hook state: a role that has accepted a prompt is busy until its `Stop` hook fires.
325
331
  - Additional pending files to a busy target role remain non-empty and are not written to that terminal.
326
332
  - When the target role later reaches `Stop`, VCM scans again and may deliver the next pending route file.
327
- - If auto orchestration gets stuck after a manual copy/paste recovery, `Mark All Done` clears open `pending_approval`, `queued`, `submitted`, and failed message records to `acknowledged`, and clears pending route files.
333
+ - If auto orchestration gets stuck after a manual copy/paste recovery, `Mark All Done` clears pending route files. It does not mutate message history.
328
334
 
329
- VCM Harness injects a Claude Code `Stop` hook into `.claude/settings.json`. Role tabs become `running` when VCM submits user input or a message to that embedded terminal, and `idle` after `Stop`; the terminal process status is still tracked separately. The injected role rules require a role to end its turn after writing or updating a message route file, rather than polling, looping, or waiting for another role inside the same Claude Code turn.
335
+ VCM Harness injects Claude Code `UserPromptSubmit` and `Stop` hooks into `.claude/settings.json`. Role tabs become `running` when Claude Code accepts a prompt and `idle` after `Stop`; VCM also marks a role `running` immediately after it writes a message to that embedded terminal. The terminal process status is still tracked separately. The injected role rules require a role to end its turn after writing or updating a message route file, rather than polling, looping, or waiting for another role inside the same Claude Code turn.
330
336
 
331
337
  The implementation keeps only the active manual/auto orchestration mode. It does not expose pause/resume, stage/approve/reject, or a separate agent-facing message CLI.
332
338
 
333
339
  ## Round Completion Alerts
334
340
 
335
- VCM detects conversation completion from hook-driven role activity state, not PTY silence. VCM terminal submission marks a role `running`, and `Stop` marks that role `idle` with a stop timestamp.
341
+ VCM detects conversation completion from hook-driven role activity state, not PTY silence or message history. `UserPromptSubmit` marks a role `running`, and `Stop` marks that role `idle` with a stop timestamp.
336
342
 
337
- For role chains, VCM waits for the latest active message's target role to reach hook `Stop`. For example, if PM sends work to Coder and Coder sends a result back to PM, the round is not complete when Coder finishes; it is complete only after PM reaches `Stop` for the final response. If no VCM role message is involved, the latest direct role `Stop` can complete the round.
343
+ For role chains, VCM waits for the final role to reach hook `Stop`. For example, if PM sends work to Coder and Coder sends a result back to PM, the round is not complete when Coder finishes; it is complete only after PM reaches `Stop` for the final response. Pending route files block completion because more dispatch work is waiting; message history does not define completion.
338
344
 
339
- When `Round alert` is enabled, the frontend polls the task round state, deduplicates each completion id, shows a small `Round complete` prompt, and plays a short local sound.
345
+ When `Round alert` is enabled, the frontend polls the task round state, deduplicates each completion id, shows a small `Round complete` prompt, and plays the local completion chime.
340
346
 
341
347
  ## Resume Behavior
342
348
 
@@ -1,4 +1,7 @@
1
1
  export function registerClaudeHookRoutes(app, deps) {
2
+ app.post("/api/hooks/claude-code", async (request) => {
3
+ return deps.claudeHookService.handleHook(request.body);
4
+ });
2
5
  app.post("/api/hooks/claude-code/stop", async (request) => {
3
6
  return deps.claudeHookService.handleStopHook(request.body);
4
7
  });
@@ -9,10 +9,6 @@ export function registerMessageRoutes(app, deps) {
9
9
  const context = await getRouteContext(deps, request.params.taskSlug);
10
10
  return deps.messageService.listPendingRouteFiles(context);
11
11
  });
12
- app.post("/api/tasks/:taskSlug/messages/scan", async (request) => {
13
- const context = await getRouteContext(deps, request.params.taskSlug);
14
- return deps.messageService.scanAndDispatchPendingRouteFiles(context);
15
- });
16
12
  app.post("/api/tasks/:taskSlug/messages/mark-all-done", async (request) => {
17
13
  const context = await getRouteContext(deps, request.params.taskSlug);
18
14
  return deps.messageService.markAllDone({
@@ -20,6 +16,10 @@ export function registerMessageRoutes(app, deps) {
20
16
  clearRouteFiles: true
21
17
  });
22
18
  });
19
+ app.delete("/api/tasks/:taskSlug/messages/history", async (request) => {
20
+ const context = await getRouteContext(deps, request.params.taskSlug);
21
+ return deps.messageService.deleteMessageHistory(context);
22
+ });
23
23
  app.get("/api/tasks/:taskSlug/orchestration", async (request) => {
24
24
  const context = await getRouteContext(deps, request.params.taskSlug);
25
25
  return deps.messageService.getOrchestrationState(context);
@@ -7,16 +7,18 @@ export function registerRoundRoutes(app, deps) {
7
7
  const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
8
8
  const taskRepoRoot = getTaskRuntimeRepoRoot(task);
9
9
  const sessions = await deps.sessionService.listRoleSessions(project.repoRoot, request.params.taskSlug);
10
- const messages = await deps.messageService.listMessages({
10
+ const pendingRoutes = await deps.messageService.listPendingRouteFiles({
11
11
  repoRoot: project.repoRoot,
12
+ taskRepoRoot,
12
13
  stateRepoRoot: taskRepoRoot,
13
14
  stateRoot: config.stateRoot,
15
+ handoffDir: task.handoffDir,
14
16
  taskSlug: request.params.taskSlug
15
17
  });
16
18
  return deps.roundService.getTaskRoundState({
17
19
  taskSlug: request.params.taskSlug,
18
20
  sessions,
19
- messages
21
+ pendingRouteCount: pendingRoutes.length
20
22
  });
21
23
  });
22
24
  }
@@ -2,64 +2,126 @@ import { isRoleName } from "../../shared/constants.js";
2
2
  import { VcmError } from "../errors.js";
3
3
  import { getTaskRuntimeRepoRoot } from "./task-service.js";
4
4
  export function createClaudeHookService(deps) {
5
- return {
6
- async handleStopHook(input) {
7
- if (!isRoleName(input.role)) {
8
- throw new VcmError({
9
- code: "HOOK_ROLE_INVALID",
10
- message: `Unknown hook role: ${input.role}`,
11
- statusCode: 400
12
- });
13
- }
14
- const eventName = parseStopEvent(input.event.hook_event_name);
15
- const project = await deps.projectService.getCurrentProject();
16
- if (!project) {
17
- throw new VcmError({
18
- code: "PROJECT_NOT_CONNECTED",
19
- message: "Connect a repository before accepting Claude Code hooks.",
20
- statusCode: 409
21
- });
22
- }
23
- const session = await deps.sessionService.recordClaudeHookEvent(project.repoRoot, {
24
- taskSlug: input.taskSlug,
25
- role: input.role,
26
- eventName,
27
- claudeSessionId: stringOrUndefined(input.event.session_id),
28
- transcriptPath: stringOrUndefined(input.event.transcript_path),
29
- cwd: stringOrUndefined(input.event.cwd)
5
+ async function getHookContext(input) {
6
+ if (!isRoleName(input.role)) {
7
+ throw new VcmError({
8
+ code: "HOOK_ROLE_INVALID",
9
+ message: `Unknown hook role: ${input.role}`,
10
+ statusCode: 400
30
11
  });
31
- const config = await deps.projectService.loadConfig(project.repoRoot);
32
- const task = await deps.taskService.loadTask(project.repoRoot, input.taskSlug);
33
- const taskRepoRoot = getTaskRuntimeRepoRoot(task);
34
- const dispatched = await deps.messageService.scanAndDispatchPendingRouteFiles({
35
- repoRoot: project.repoRoot,
36
- taskRepoRoot,
37
- stateRepoRoot: taskRepoRoot,
38
- stateRoot: config.stateRoot,
39
- handoffDir: task.handoffDir,
40
- taskSlug: input.taskSlug,
41
- stoppedRole: input.role
12
+ }
13
+ const project = await deps.projectService.getCurrentProject();
14
+ if (!project) {
15
+ throw new VcmError({
16
+ code: "PROJECT_NOT_CONNECTED",
17
+ message: "Connect a repository before accepting Claude Code hooks.",
18
+ statusCode: 409
42
19
  });
43
- return {
44
- ok: true,
45
- eventName,
46
- taskSlug: input.taskSlug,
47
- role: input.role,
48
- sessionUpdated: Boolean(session),
49
- dispatchedCount: dispatched.filter((result) => result.delivered).length
50
- };
51
20
  }
21
+ const config = await deps.projectService.loadConfig(project.repoRoot);
22
+ const task = await deps.taskService.loadTask(project.repoRoot, input.taskSlug);
23
+ const taskRepoRoot = getTaskRuntimeRepoRoot(task);
24
+ return {
25
+ project,
26
+ config,
27
+ task,
28
+ taskRepoRoot
29
+ };
30
+ }
31
+ async function handleUserPromptSubmitHook(input) {
32
+ const eventName = parseHookEvent(input.event.hook_event_name);
33
+ if (eventName !== "UserPromptSubmit") {
34
+ throwUnsupportedEvent(eventName);
35
+ }
36
+ const context = await getHookContext(input);
37
+ const session = await deps.sessionService.recordClaudeHookEvent(context.project.repoRoot, {
38
+ taskSlug: input.taskSlug,
39
+ role: input.role,
40
+ eventName,
41
+ claudeSessionId: stringOrUndefined(input.event.session_id),
42
+ transcriptPath: stringOrUndefined(input.event.transcript_path),
43
+ cwd: stringOrUndefined(input.event.cwd)
44
+ });
45
+ const submitted = await deps.messageService.confirmPromptSubmitted({
46
+ repoRoot: context.project.repoRoot,
47
+ taskRepoRoot: context.taskRepoRoot,
48
+ stateRepoRoot: context.taskRepoRoot,
49
+ stateRoot: context.config.stateRoot,
50
+ handoffDir: context.task.handoffDir,
51
+ taskSlug: input.taskSlug,
52
+ role: input.role,
53
+ prompt: stringOrUndefined(input.event.prompt)
54
+ });
55
+ return {
56
+ ok: true,
57
+ eventName,
58
+ taskSlug: input.taskSlug,
59
+ role: input.role,
60
+ sessionUpdated: Boolean(session),
61
+ dispatchedCount: 0,
62
+ acceptedMessageId: submitted?.id
63
+ };
64
+ }
65
+ async function handleStopHook(input) {
66
+ const eventName = parseHookEvent(input.event.hook_event_name ?? "Stop");
67
+ if (eventName !== "Stop") {
68
+ throwUnsupportedEvent(eventName);
69
+ }
70
+ const context = await getHookContext(input);
71
+ const session = await deps.sessionService.recordClaudeHookEvent(context.project.repoRoot, {
72
+ taskSlug: input.taskSlug,
73
+ role: input.role,
74
+ eventName,
75
+ claudeSessionId: stringOrUndefined(input.event.session_id),
76
+ transcriptPath: stringOrUndefined(input.event.transcript_path),
77
+ cwd: stringOrUndefined(input.event.cwd)
78
+ });
79
+ const dispatched = await deps.messageService.scanAndDispatchPendingRouteFiles({
80
+ repoRoot: context.project.repoRoot,
81
+ taskRepoRoot: context.taskRepoRoot,
82
+ stateRepoRoot: context.taskRepoRoot,
83
+ stateRoot: context.config.stateRoot,
84
+ handoffDir: context.task.handoffDir,
85
+ taskSlug: input.taskSlug,
86
+ stoppedRole: input.role
87
+ });
88
+ return {
89
+ ok: true,
90
+ eventName,
91
+ taskSlug: input.taskSlug,
92
+ role: input.role,
93
+ sessionUpdated: Boolean(session),
94
+ dispatchedCount: dispatched.filter((result) => result.delivered).length
95
+ };
96
+ }
97
+ return {
98
+ async handleHook(input) {
99
+ const eventName = parseHookEvent(input.event.hook_event_name);
100
+ if (eventName === "UserPromptSubmit") {
101
+ return handleUserPromptSubmitHook(input);
102
+ }
103
+ return handleStopHook(input);
104
+ },
105
+ handleStopHook
52
106
  };
53
107
  }
54
- function parseStopEvent(value) {
55
- if (value === undefined || value === "Stop") {
56
- return "Stop";
108
+ function parseHookEvent(value) {
109
+ if (value === "UserPromptSubmit" || value === "Stop") {
110
+ return value;
57
111
  }
58
112
  throw new VcmError({
59
113
  code: "HOOK_EVENT_UNSUPPORTED",
60
114
  message: `Unsupported Claude Code hook event: ${String(value)}`,
61
115
  statusCode: 400,
62
- hint: "VCM accepts Stop hooks only."
116
+ hint: "VCM accepts UserPromptSubmit and Stop hooks only."
117
+ });
118
+ }
119
+ function throwUnsupportedEvent(eventName) {
120
+ throw new VcmError({
121
+ code: "HOOK_EVENT_UNSUPPORTED",
122
+ message: `Unsupported Claude Code hook event: ${eventName}`,
123
+ statusCode: 400,
124
+ hint: "Use the matching VCM hook endpoint for this event."
63
125
  });
64
126
  }
65
127
  function stringOrUndefined(value) {
@@ -9,8 +9,8 @@ export const VCM_HARNESS_VERSION = 1;
9
9
  const MANAGED_BLOCK_PATTERN = /<!-- VCM:BEGIN(?:\s+version=(\d+))? -->[\s\S]*?<!-- VCM:END -->/m;
10
10
  const HASH_MANAGED_BLOCK_PATTERN = /# VCM:BEGIN(?:\s+version=(\d+))?\n[\s\S]*?# VCM:END/m;
11
11
  const CLAUDE_SETTINGS_PATH = ".claude/settings.json";
12
- 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/stop" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
13
- const VCM_HOOK_EVENTS = ["Stop"];
12
+ 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'`;
13
+ const VCM_HOOK_EVENTS = ["UserPromptSubmit", "Stop"];
14
14
  const HARNESS_FILES = [
15
15
  {
16
16
  kind: "root-claude",
@@ -227,7 +227,7 @@ async function analyzeClaudeSettingsFile(fs, repoRoot) {
227
227
  path: CLAUDE_SETTINGS_PATH,
228
228
  action,
229
229
  reason: exists
230
- ? "Claude Code hook settings do not contain the VCM Stop hook bridge."
230
+ ? "Claude Code hook settings do not contain the VCM UserPromptSubmit/Stop hook bridge."
231
231
  : "Claude Code hook settings are missing; VCM will create them."
232
232
  },
233
233
  nextContent: action === "ok" ? undefined : nextContent
@@ -294,7 +294,7 @@ function isVcmHookMatcher(value) {
294
294
  if (!isPlainObject(hook)) {
295
295
  return false;
296
296
  }
297
- return typeof hook.command === "string" && hook.command.includes("/api/hooks/claude-code/stop");
297
+ return typeof hook.command === "string" && hook.command.includes("/api/hooks/claude-code");
298
298
  });
299
299
  }
300
300
  function isPlainObject(value) {