vibe-coding-master 0.7.37 → 0.7.39
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 +7 -3
- package/dist/backend/adapters/claude-adapter.js +6 -0
- package/dist/backend/cli/install-vcm-harness.js +64 -5
- package/dist/backend/services/code-intelligence-service.js +5 -5
- package/dist/backend/services/harness-service.js +63 -9
- package/dist/backend/templates/harness/architect-agent.js +10 -4
- package/dist/backend/templates/harness/coder-agent.js +8 -1
- package/dist/backend/templates/harness/gate-review.js +12 -9
- package/dist/backend/templates/harness/vcm-code-navigation-skill.js +10 -8
- package/dist-frontend/assets/{index-VillnFRo.js → index-DkM0mnQD.js} +1 -1
- package/dist-frontend/index.html +1 -1
- package/package.json +1 -1
- package/scripts/harness-tools/vcm-bash-guard +52 -17
package/dist-frontend/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DkM0mnQD.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-CXSOe-NN.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""VCM PreToolUse guard for supervised
|
|
2
|
+
"""VCM PreToolUse guard for supervised tools inside VCM role sessions.
|
|
3
3
|
|
|
4
|
-
Reads the Claude Code PreToolUse hook payload on stdin.
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
|
186
|
+
"VCM denied this tool call (" + "; ".join(reasons) + "). " + " ".join(hints)
|
|
152
187
|
),
|
|
153
188
|
}
|
|
154
189
|
}
|