vibe-coding-master 0.7.36 → 0.7.38

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.
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>VibeCodingMaster</title>
7
- <script type="module" crossorigin src="/assets/index-DhLRhgSV.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-DN9kwoOt.css">
7
+ <script type="module" crossorigin src="/assets/index-VillnFRo.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-CXSOe-NN.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.7.36",
3
+ "version": "0.7.38",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "vcm-lsp-bridge",
3
+ "description": "VCM-managed Claude Code LSP bridge for supported project languages",
4
+ "version": "1.0.0",
5
+ "author": {
6
+ "name": "VibeCodingMaster"
7
+ },
8
+ "lspServers": {
9
+ "rust-analyzer": {
10
+ "command": "rust-analyzer",
11
+ "extensionToLanguage": {
12
+ ".rs": "rust"
13
+ }
14
+ },
15
+ "typescript": {
16
+ "command": "typescript-language-server",
17
+ "args": [
18
+ "--stdio"
19
+ ],
20
+ "extensionToLanguage": {
21
+ ".ts": "typescript",
22
+ ".tsx": "typescriptreact",
23
+ ".js": "javascript",
24
+ ".jsx": "javascriptreact",
25
+ ".mts": "typescript",
26
+ ".cts": "typescript",
27
+ ".mjs": "javascript",
28
+ ".cjs": "javascript"
29
+ }
30
+ },
31
+ "pyright": {
32
+ "command": "pyright-langserver",
33
+ "args": [
34
+ "--stdio"
35
+ ],
36
+ "extensionToLanguage": {
37
+ ".py": "python",
38
+ ".pyi": "python"
39
+ }
40
+ },
41
+ "gopls": {
42
+ "command": "gopls",
43
+ "extensionToLanguage": {
44
+ ".go": "go"
45
+ }
46
+ },
47
+ "clangd": {
48
+ "command": "clangd",
49
+ "args": [
50
+ "--background-index"
51
+ ],
52
+ "extensionToLanguage": {
53
+ ".c": "c",
54
+ ".h": "c",
55
+ ".cpp": "cpp",
56
+ ".cc": "cpp",
57
+ ".cxx": "cpp",
58
+ ".hpp": "cpp",
59
+ ".hxx": "cpp",
60
+ ".C": "cpp",
61
+ ".H": "cpp"
62
+ }
63
+ },
64
+ "jdtls": {
65
+ "command": "jdtls",
66
+ "extensionToLanguage": {
67
+ ".java": "java"
68
+ },
69
+ "startupTimeout": 120000
70
+ }
71
+ }
72
+ }
@@ -1,11 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- """VCM PreToolUse guard for supervised Bash inside VCM role sessions.
2
+ """VCM PreToolUse guard for supervised tools inside VCM role sessions.
3
3
 
4
- Reads the Claude Code PreToolUse hook payload on stdin. When the Bash tool
5
- call would start background work (run_in_background, nohup, setsid, disown,
6
- or a lone '&'), or composes a supervised long-job tool with another shell
7
- command, it prints a deny decision that redirects the role to the
8
- vcm-long-running-validation skill. Anything else is allowed by staying silent.
4
+ Reads the Claude Code PreToolUse hook payload on stdin. It enforces supervised
5
+ Bash execution for every VCM role and prevents LSP-enabled roles from replacing
6
+ semantic code navigation with the Grep tool or shell text-search commands.
9
7
 
10
8
  Quoted payloads of `sh -c` / `bash -lc` style invocations are executable
11
9
  shell code, so they are scanned recursively. `.ai/tools/run-long-check` is the
@@ -13,6 +11,7 @@ only sanctioned detached worker; the command it runs must still stay in the
13
11
  supervised foreground process group.
14
12
  """
15
13
  import json
14
+ import os
16
15
  import re
17
16
  import sys
18
17
 
@@ -22,6 +21,11 @@ SKILL_HINT = (
22
21
  "`.ai/tools/watch-job <job-id>` in the same turn, repeating watch-job "
23
22
  "until it reports a terminal result."
24
23
  )
24
+ CODE_NAV_HINT = (
25
+ "Use LSP, Glob, Read, generated context, architecture documents, or runtime "
26
+ "evidence; do not substitute text search."
27
+ )
28
+ LSP_ROLES = {"architect", "coder", "reviewer"}
25
29
 
26
30
  MAX_NESTED_SHELL_DEPTH = 3
27
31
  QUOTED = re.compile(r"'([^']*)'|\"([^\"]*)\"")
@@ -40,6 +44,20 @@ RUN_LONG_CHECK_SHELL_STRING = re.compile(
40
44
  r"(?:-\w+\s+)*-\w*c\w*(?:\s|$)"
41
45
  )
42
46
  COMMAND_SUBSTITUTION = re.compile(r"\$\(([^()]*)\)|`([^`]*)`")
47
+ TEXT_SEARCH_COMMAND = re.compile(
48
+ r"(?:^|[;&|()\n`])\s*"
49
+ r"(?:(?:[A-Za-z_][A-Za-z0-9_]*=[^\s;&|()]+)\s+)*"
50
+ r"(?:env(?:\s+(?:-[^\s]+|[A-Za-z_][A-Za-z0-9_]*=[^\s]+))*\s+)?"
51
+ r"(?:command\s+|sudo(?:\s+-[^\s]+)*\s+)?"
52
+ r"(?:[^\s;&|()]*/)?(?:grep|egrep|fgrep|rg|ripgrep)(?=\s|$|[;&|()])"
53
+ )
54
+ GIT_GREP_COMMAND = re.compile(
55
+ r"(?:^|[;&|()\n`])\s*(?:git)(?:\s+-[^\s]+)*\s+grep(?=\s|$|[;&|()])"
56
+ )
57
+ WRAPPED_TEXT_SEARCH_COMMAND = re.compile(
58
+ r"(?:^|\s)(?:xargs(?:\s+-[^\s]+)*|-exec|-execdir)\s+"
59
+ r"(?:[^\s;&|()]*/)?(?:grep|egrep|fgrep|rg|ripgrep)(?=\s|$|[;&|()])"
60
+ )
43
61
 
44
62
 
45
63
  def strip_quoted(command: str) -> str:
@@ -83,7 +101,7 @@ def protected_tool_invoked(command_without_quotes: str) -> bool:
83
101
  return bool(PROTECTED_TOOL_INVOCATION.search(command_without_quotes))
84
102
 
85
103
 
86
- def scan_shell_command(command: str, depth: int = 0) -> list[str]:
104
+ def scan_shell_command(command: str, depth: int = 0, forbid_text_search: bool = False) -> list[str]:
87
105
  reasons = []
88
106
  stripped = strip_escaped_characters(strip_quoted(command))
89
107
  if re.search(r"(?:^|[\s;&|(])(?:nohup|setsid)(?:\s|$)", stripped):
@@ -92,6 +110,12 @@ def scan_shell_command(command: str, depth: int = 0) -> list[str]:
92
110
  reasons.append("disown is forbidden")
93
111
  if unquoted_ampersand(stripped):
94
112
  reasons.append("'&' background execution is forbidden")
113
+ if forbid_text_search and (
114
+ TEXT_SEARCH_COMMAND.search(stripped)
115
+ or GIT_GREP_COMMAND.search(stripped)
116
+ or WRAPPED_TEXT_SEARCH_COMMAND.search(stripped)
117
+ ):
118
+ reasons.append("text-search commands are forbidden for this LSP-enabled role")
95
119
 
96
120
  if protected_tool_invoked(stripped):
97
121
  if SHELL_CONTROL_OPERATOR.search(stripped):
@@ -104,17 +128,17 @@ def scan_shell_command(command: str, depth: int = 0) -> list[str]:
104
128
  for segment in quoted_segments(command):
105
129
  if protected_tool_invoked(strip_escaped_characters(strip_quoted(segment))):
106
130
  reasons.append("run-long-check and watch-job must not be invoked through a shell command string")
107
- reasons.extend(scan_shell_command(segment, depth + 1))
131
+ reasons.extend(scan_shell_command(segment, depth + 1, forbid_text_search))
108
132
  if depth < MAX_NESTED_SHELL_DEPTH:
109
133
  for substitution in double_quoted_command_substitutions(command):
110
134
  nested_stripped = strip_escaped_characters(strip_quoted(substitution))
111
135
  if protected_tool_invoked(nested_stripped):
112
136
  reasons.append("run-long-check and watch-job must not be invoked through command substitution")
113
- reasons.extend(scan_shell_command(substitution, depth + 1))
137
+ reasons.extend(scan_shell_command(substitution, depth + 1, forbid_text_search))
114
138
  return reasons
115
139
 
116
140
 
117
- def guard_reasons(tool_input: dict) -> list[str]:
141
+ def guard_reasons(tool_input: dict, forbid_text_search: bool = False) -> list[str]:
118
142
  reasons = []
119
143
  if tool_input.get("run_in_background"):
120
144
  reasons.append("Bash run_in_background is forbidden")
@@ -122,7 +146,7 @@ def guard_reasons(tool_input: dict) -> list[str]:
122
146
  command = tool_input.get("command")
123
147
  command = command if isinstance(command, str) else ""
124
148
 
125
- reasons.extend(scan_shell_command(command))
149
+ reasons.extend(scan_shell_command(command, forbid_text_search=forbid_text_search))
126
150
  return list(dict.fromkeys(reasons))
127
151
 
128
152
 
@@ -132,15 +156,26 @@ def main() -> int:
132
156
  payload = json.loads(raw) if raw.strip() else {}
133
157
  except ValueError:
134
158
  return 0
135
- if payload.get("tool_name") != "Bash":
159
+ tool_name = payload.get("tool_name")
160
+ role = os.environ.get("VCM_ROLE", "").strip()
161
+ forbid_text_search = role in LSP_ROLES
162
+ if tool_name == "Grep" and forbid_text_search:
163
+ reasons = ["the Grep tool is forbidden for this LSP-enabled role"]
164
+ elif tool_name == "Bash":
165
+ tool_input = payload.get("tool_input")
166
+ tool_input = tool_input if isinstance(tool_input, dict) else {}
167
+ reasons = guard_reasons(tool_input, forbid_text_search=forbid_text_search)
168
+ else:
136
169
  return 0
137
-
138
- tool_input = payload.get("tool_input")
139
- tool_input = tool_input if isinstance(tool_input, dict) else {}
140
- reasons = guard_reasons(tool_input)
141
170
  if not reasons:
142
171
  return 0
143
172
 
173
+ hints = []
174
+ if any("text-search" in reason or "Grep tool" in reason for reason in reasons):
175
+ hints.append(CODE_NAV_HINT)
176
+ if any("text-search" not in reason and "Grep tool" not in reason for reason in reasons):
177
+ hints.append(SKILL_HINT)
178
+
144
179
  print(
145
180
  json.dumps(
146
181
  {
@@ -148,7 +183,7 @@ def main() -> int:
148
183
  "hookEventName": "PreToolUse",
149
184
  "permissionDecision": "deny",
150
185
  "permissionDecisionReason": (
151
- "VCM denied this Bash call (" + "; ".join(reasons) + "). " + SKILL_HINT
186
+ "VCM denied this tool call (" + "; ".join(reasons) + "). " + " ".join(hints)
152
187
  ),
153
188
  }
154
189
  }
@@ -6,6 +6,7 @@ const requiredFiles = [
6
6
  "README.md",
7
7
  "package.json",
8
8
  "scripts/fix-node-pty-spawn-helper.mjs",
9
+ "scripts/claude-plugins/vcm-lsp-bridge/.claude-plugin/plugin.json",
9
10
  "scripts/harness-tools/check-durable-docs",
10
11
  "scripts/harness-tools/generate-module-index",
11
12
  "scripts/harness-tools/generate-public-surface",