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 +22 -16
- package/dist/backend/api/claude-hook-routes.js +3 -0
- package/dist/backend/api/message-routes.js +4 -4
- package/dist/backend/api/round-routes.js +4 -2
- package/dist/backend/services/claude-hook-service.js +110 -48
- package/dist/backend/services/harness-service.js +4 -4
- package/dist/backend/services/message-service.js +110 -117
- package/dist/backend/services/round-service.js +14 -52
- package/dist/backend/services/session-service.js +4 -2
- package/dist/backend/templates/harness/project-manager-agent.js +1 -0
- package/dist-frontend/assets/index-D40qaonx.css +32 -0
- package/dist-frontend/assets/{index-QhzvzTMZ.js → index-DK2F4LFT.js} +45 -44
- package/dist-frontend/index.html +2 -2
- package/docs/cc-best-practices.md +1 -0
- package/docs/product-design.md +29 -25
- package/docs/v1-architecture-design.md +27 -20
- package/docs/v1-implementation-plan.md +28 -24
- package/package.json +1 -1
- package/dist-frontend/assets/index-jEkUTnIY.css +0 -32
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
312
|
-
- `Mark All Done`
|
|
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
|
|
322
|
-
-
|
|
323
|
-
-
|
|
324
|
-
- VCM
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
55
|
-
if (value ===
|
|
56
|
-
return
|
|
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
|
|
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
|
|
297
|
+
return typeof hook.command === "string" && hook.command.includes("/api/hooks/claude-code");
|
|
298
298
|
});
|
|
299
299
|
}
|
|
300
300
|
function isPlainObject(value) {
|