vibe-coding-master 0.7.37 → 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.
- package/README.md +4 -1
- package/dist/backend/cli/install-vcm-harness.js +29 -4
- package/dist/backend/services/harness-service.js +32 -8
- 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 +6 -6
- package/package.json +1 -1
- package/scripts/harness-tools/vcm-bash-guard +52 -17
package/README.md
CHANGED
|
@@ -471,7 +471,10 @@ VCM bundles the Claude Code LSP bridge and loads it for Architect, Coder, and
|
|
|
471
471
|
Reviewer sessions, including CCR launches. The project environment must still
|
|
472
472
|
provide the language server for each detected language: `rust-analyzer`,
|
|
473
473
|
`typescript-language-server`, `pyright-langserver`, `gopls`, `clangd`, or
|
|
474
|
-
`jdtls`.
|
|
474
|
+
`jdtls`. Architect, Coder, and Reviewer do not receive the Grep tool, and the
|
|
475
|
+
Harness guard rejects shell text-search commands for those roles so unresolved
|
|
476
|
+
semantic relationships cannot be hidden by textual fallback. Harness Studio
|
|
477
|
+
reports a missing executable or failed readiness probe
|
|
475
478
|
with the backend diagnostic.
|
|
476
479
|
|
|
477
480
|
Harness Engineer is task-scoped and runs from the active task worktree. The
|
|
@@ -53,6 +53,7 @@ const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
|
|
|
53
53
|
const VCM_AUTO_MEMORY_ENABLED = false;
|
|
54
54
|
const VCM_HOOK_DEFINITIONS = [
|
|
55
55
|
{ eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
56
|
+
{ eventName: "PreToolUse", matcher: "Grep", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
56
57
|
{ eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
57
58
|
{ eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
|
|
58
59
|
{ eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
@@ -65,18 +66,18 @@ const AGENT_FRONTMATTER = {
|
|
|
65
66
|
},
|
|
66
67
|
architect: {
|
|
67
68
|
description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.",
|
|
68
|
-
tools: "Read,
|
|
69
|
+
tools: "Read, Glob, Bash, Edit, Write, Agent, LSP"
|
|
69
70
|
},
|
|
70
71
|
coder: {
|
|
71
72
|
description: "VCM implementation role for scoped code changes and focused tests.",
|
|
72
|
-
tools: "Read,
|
|
73
|
+
tools: "Read, Glob, Bash, Edit, Write, Agent, LSP"
|
|
73
74
|
},
|
|
74
75
|
tester: {
|
|
75
76
|
description: "VCM testing role for validation, test adequacy, approved-scope validation, and risk findings."
|
|
76
77
|
},
|
|
77
78
|
reviewer: {
|
|
78
79
|
description: "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.",
|
|
79
|
-
tools: "Read,
|
|
80
|
+
tools: "Read, Glob, Bash, Write, LSP"
|
|
80
81
|
},
|
|
81
82
|
translator: {
|
|
82
83
|
description: "VCM task-scoped translation tool role for conversation translation, file translation, bootstrap, and memory updates."
|
|
@@ -99,6 +100,11 @@ const REQUIRED_AGENT_TOOLS = {
|
|
|
99
100
|
coder: ["Agent", "LSP"],
|
|
100
101
|
reviewer: ["LSP"]
|
|
101
102
|
};
|
|
103
|
+
const FORBIDDEN_AGENT_TOOLS = {
|
|
104
|
+
architect: ["Grep"],
|
|
105
|
+
coder: ["Grep"],
|
|
106
|
+
reviewer: ["Grep"]
|
|
107
|
+
};
|
|
102
108
|
const MANAGED_FILES = [
|
|
103
109
|
{
|
|
104
110
|
path: "CLAUDE.md",
|
|
@@ -266,7 +272,7 @@ const WHOLE_FILES = [
|
|
|
266
272
|
path: ".claude/skills/vcm-code-navigation/SKILL.md",
|
|
267
273
|
category: "skill",
|
|
268
274
|
mode: 0o644,
|
|
269
|
-
content: renderSkillFile("VCM Code Navigation Skill", "vcm-code-navigation", "Use when Architect or Reviewer must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths.", renderVcmCodeNavigationSkillRules())
|
|
275
|
+
content: renderSkillFile("VCM Code Navigation Skill", "vcm-code-navigation", "Use when Architect, Coder, or Reviewer must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths.", renderVcmCodeNavigationSkillRules())
|
|
270
276
|
},
|
|
271
277
|
{
|
|
272
278
|
path: ".claude/skills/vcm-final-acceptance/SKILL.md",
|
|
@@ -672,6 +678,9 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
|
|
|
672
678
|
for (const requiredTool of REQUIRED_AGENT_TOOLS[definition.agentName] ?? []) {
|
|
673
679
|
nextContent = ensureAgentTool(nextContent, requiredTool);
|
|
674
680
|
}
|
|
681
|
+
for (const forbiddenTool of FORBIDDEN_AGENT_TOOLS[definition.agentName] ?? []) {
|
|
682
|
+
nextContent = removeAgentTool(nextContent, forbiddenTool);
|
|
683
|
+
}
|
|
675
684
|
await writeIfChanged({
|
|
676
685
|
targetPath,
|
|
677
686
|
relativePath: definition.path,
|
|
@@ -734,6 +743,22 @@ function ensureAgentTool(content, requiredTool) {
|
|
|
734
743
|
const nextTools = [...tools, requiredTool].join(", ");
|
|
735
744
|
return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
|
|
736
745
|
}
|
|
746
|
+
function removeAgentTool(content, forbiddenTool) {
|
|
747
|
+
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
748
|
+
if (!frontmatterMatch) {
|
|
749
|
+
return content;
|
|
750
|
+
}
|
|
751
|
+
const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
|
|
752
|
+
if (!toolsMatch) {
|
|
753
|
+
return content;
|
|
754
|
+
}
|
|
755
|
+
const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
756
|
+
const nextTools = tools.filter((tool) => tool !== forbiddenTool);
|
|
757
|
+
if (nextTools.length === tools.length) {
|
|
758
|
+
return content;
|
|
759
|
+
}
|
|
760
|
+
return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools.join(", ")}`));
|
|
761
|
+
}
|
|
737
762
|
function migrateLegacyManagedFile(definition, currentContent, block) {
|
|
738
763
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
739
764
|
if (!legacyContent) {
|
|
@@ -53,6 +53,7 @@ const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
|
|
|
53
53
|
const VCM_AUTO_MEMORY_ENABLED = false;
|
|
54
54
|
const VCM_HOOK_DEFINITIONS = [
|
|
55
55
|
{ eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
56
|
+
{ eventName: "PreToolUse", matcher: "Grep", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
56
57
|
{ eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
57
58
|
{ eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
|
|
58
59
|
{ eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
@@ -116,7 +117,7 @@ const HARNESS_FILES = [
|
|
|
116
117
|
kind: "skill-vcm-code-navigation",
|
|
117
118
|
path: ".claude/skills/vcm-code-navigation/SKILL.md",
|
|
118
119
|
title: "VCM Code Navigation Skill",
|
|
119
|
-
frontmatter: renderSkillFrontmatter("vcm-code-navigation", "Use when Architect or Reviewer must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths."),
|
|
120
|
+
frontmatter: renderSkillFrontmatter("vcm-code-navigation", "Use when Architect, Coder, or Reviewer must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths."),
|
|
120
121
|
ownership: "whole-file",
|
|
121
122
|
renderRules: renderVcmCodeNavigationSkillRules
|
|
122
123
|
},
|
|
@@ -198,7 +199,8 @@ const HARNESS_FILES = [
|
|
|
198
199
|
title: "Reviewer Agent",
|
|
199
200
|
memoryBlock: true,
|
|
200
201
|
requiredTools: ["LSP"],
|
|
201
|
-
|
|
202
|
+
forbiddenTools: ["Grep"],
|
|
203
|
+
frontmatter: renderAgentFrontmatter("reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { tools: "Read, Glob, Bash, Write, LSP" }),
|
|
202
204
|
renderRules: renderReviewerAgentRules
|
|
203
205
|
},
|
|
204
206
|
{
|
|
@@ -272,8 +274,9 @@ const HARNESS_FILES = [
|
|
|
272
274
|
title: "Architect Agent",
|
|
273
275
|
memoryBlock: true,
|
|
274
276
|
requiredTools: ["Agent", "LSP"],
|
|
277
|
+
forbiddenTools: ["Grep"],
|
|
275
278
|
blankLineBeforeEnd: true,
|
|
276
|
-
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { tools: "Read,
|
|
279
|
+
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { tools: "Read, Glob, Bash, Edit, Write, Agent, LSP" }),
|
|
277
280
|
renderRules: renderArchitectHarnessRules
|
|
278
281
|
},
|
|
279
282
|
{
|
|
@@ -282,7 +285,8 @@ const HARNESS_FILES = [
|
|
|
282
285
|
title: "Coder Agent",
|
|
283
286
|
memoryBlock: true,
|
|
284
287
|
requiredTools: ["Agent", "LSP"],
|
|
285
|
-
|
|
288
|
+
forbiddenTools: ["Grep"],
|
|
289
|
+
frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { tools: "Read, Glob, Bash, Edit, Write, Agent, LSP" }),
|
|
286
290
|
renderRules: renderCoderHarnessRules
|
|
287
291
|
},
|
|
288
292
|
{
|
|
@@ -1220,7 +1224,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1220
1224
|
const migratedContent = migrateLegacyHarnessFile(definition, currentContent, expectedBlock);
|
|
1221
1225
|
if (migratedContent) {
|
|
1222
1226
|
const memoryUpdatedContent = definition.memoryBlock ? ensureVcmMemoryBlock(migratedContent) : migratedContent;
|
|
1223
|
-
const nextContent =
|
|
1227
|
+
const nextContent = normalizeAgentTools(memoryUpdatedContent, definition.requiredTools, definition.forbiddenTools);
|
|
1224
1228
|
return {
|
|
1225
1229
|
definition,
|
|
1226
1230
|
status: {
|
|
@@ -1239,7 +1243,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1239
1243
|
};
|
|
1240
1244
|
}
|
|
1241
1245
|
const insertedContent = `${currentContent.trimEnd()}\n\n${expectedBlock}\n`;
|
|
1242
|
-
const nextContent =
|
|
1246
|
+
const nextContent = normalizeAgentTools(insertedContent, definition.requiredTools, definition.forbiddenTools);
|
|
1243
1247
|
return {
|
|
1244
1248
|
definition,
|
|
1245
1249
|
status: {
|
|
@@ -1261,7 +1265,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1261
1265
|
const currentBlock = match[0];
|
|
1262
1266
|
const blockUpdatedContent = currentContent.replace(managedBlockPattern, expectedBlock);
|
|
1263
1267
|
const memoryUpdatedContent = definition.memoryBlock ? ensureVcmMemoryBlock(blockUpdatedContent) : blockUpdatedContent;
|
|
1264
|
-
const nextContent =
|
|
1268
|
+
const nextContent = normalizeAgentTools(memoryUpdatedContent, definition.requiredTools, definition.forbiddenTools);
|
|
1265
1269
|
const action = currentContent === nextContent ? "ok" : "update";
|
|
1266
1270
|
return {
|
|
1267
1271
|
definition,
|
|
@@ -1392,6 +1396,26 @@ function ensureAgentTool(content, requiredTool) {
|
|
|
1392
1396
|
function ensureAgentTools(content, requiredTools) {
|
|
1393
1397
|
return (requiredTools ?? []).reduce(ensureAgentTool, content);
|
|
1394
1398
|
}
|
|
1399
|
+
function removeAgentTool(content, forbiddenTool) {
|
|
1400
|
+
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
1401
|
+
if (!frontmatterMatch) {
|
|
1402
|
+
return content;
|
|
1403
|
+
}
|
|
1404
|
+
const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
|
|
1405
|
+
if (!toolsMatch) {
|
|
1406
|
+
return content;
|
|
1407
|
+
}
|
|
1408
|
+
const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
1409
|
+
const nextTools = tools.filter((tool) => tool !== forbiddenTool);
|
|
1410
|
+
if (nextTools.length === tools.length) {
|
|
1411
|
+
return content;
|
|
1412
|
+
}
|
|
1413
|
+
return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools.join(", ")}`));
|
|
1414
|
+
}
|
|
1415
|
+
function normalizeAgentTools(content, requiredTools, forbiddenTools) {
|
|
1416
|
+
const required = ensureAgentTools(content, requiredTools);
|
|
1417
|
+
return (forbiddenTools ?? []).reduce(removeAgentTool, required);
|
|
1418
|
+
}
|
|
1395
1419
|
function migrateLegacyHarnessFile(definition, currentContent, block) {
|
|
1396
1420
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
1397
1421
|
if (!legacyContent) {
|
|
@@ -1479,7 +1503,7 @@ function withVcmClaudeHooks(settings) {
|
|
|
1479
1503
|
? hooks[definition.eventName]
|
|
1480
1504
|
: [];
|
|
1481
1505
|
hooks[definition.eventName] = [
|
|
1482
|
-
...existingMatchers
|
|
1506
|
+
...existingMatchers,
|
|
1483
1507
|
{
|
|
1484
1508
|
...(definition.matcher ? { matcher: definition.matcher } : {}),
|
|
1485
1509
|
hooks: [
|
|
@@ -19,6 +19,13 @@ ${renderRoleMemoryRules("architect")}
|
|
|
19
19
|
- In Debug Mode and Architecture Diagnosis Mode, writing baseline unit tests for changed code and running required L0/L1 plus applicable L2/L3 checks are part of the implementation duty; tester still owns final validation.
|
|
20
20
|
- Do not make product priority or approval decisions; route those questions back to project-manager.
|
|
21
21
|
|
|
22
|
+
### Semantic Code Navigation
|
|
23
|
+
|
|
24
|
+
- Use \`vcm-code-navigation\` whenever work requires code definitions, implementations, references, callers, callees, or behavior paths. This applies in every Architect mode and in direct user communication.
|
|
25
|
+
- Use LSP for semantic relationships. Use Glob to locate files, Read to inspect complete code, and generated context, architecture documents, or runtime evidence for boundaries LSP does not model.
|
|
26
|
+
- Do not use the Grep tool or shell text-search commands such as \`grep\`, \`rg\`, or \`git grep\`.
|
|
27
|
+
- If LSP cannot resolve a required project-owned relationship, record it as unresolved. Do not replace semantic evidence with text matches.
|
|
28
|
+
|
|
22
29
|
### Work Persistence
|
|
23
30
|
|
|
24
31
|
- Treat Architect artifacts, not Session memory, as continuation state.
|
|
@@ -45,9 +52,8 @@ ${renderRoleMemoryRules("architect")}
|
|
|
45
52
|
### Planning Code Reading
|
|
46
53
|
|
|
47
54
|
- Do not plan from session memory, architecture docs, generated context, or code comments alone. Re-read current-worktree source and verify actual behavior from implementation.
|
|
48
|
-
- Use \`vcm-code-navigation\` for symbol definitions, implementations, references, call hierarchies, and bounded behavior paths. Start from generated indexes, use LSP semantic navigation
|
|
49
|
-
-
|
|
50
|
-
- If LSP is unavailable or cannot resolve a required relationship, record the limitation and exact fallback evidence in \`architecture-evidence.md\`; do not claim compiler-accurate completeness from text search alone.
|
|
55
|
+
- Use \`vcm-code-navigation\` for symbol definitions, implementations, references, call hierarchies, and bounded behavior paths. Start from generated indexes, use LSP semantic navigation, then read every resolved callable unit in full.
|
|
56
|
+
- If LSP cannot resolve a required project-owned relationship, record the limitation in \`architecture-evidence.md\` and leave it unresolved.
|
|
51
57
|
- Define the planning boundary as the affected feature or module and identify every existing or intended observable entry point for the behavior being changed.
|
|
52
58
|
- Read the complete implementation of each relevant existing entry point.
|
|
53
59
|
- Follow every project-owned call path the plan will change through cross-module calls, state reads and writes, persistence, side effects, completion and failure signals, and consumers.
|
|
@@ -202,7 +208,7 @@ The code-reading phase is complete only when:
|
|
|
202
208
|
- every indirect callback, event, hook, queue, route, and dynamic dispatch path has been resolved
|
|
203
209
|
- every relevant state reader and writer has been read
|
|
204
210
|
- every relevant cross-file surface caller and consumer has been read
|
|
205
|
-
- every semantic relationship records LSP,
|
|
211
|
+
- every semantic relationship records LSP, runtime, external-boundary, or generated-boundary evidence
|
|
206
212
|
- no unresolved project-owned symbol remains
|
|
207
213
|
|
|
208
214
|
Do not diagnose the root cause or choose a fix before the Code Reading Closure is complete.
|
|
@@ -12,6 +12,13 @@ ${renderRoleMemoryRules("coder")}
|
|
|
12
12
|
- Implement assigned file/function-level scaffold items; do not analyze, review, dispute, or redesign architecture, module boundaries, public contracts, dependency direction, durable docs strategy, validation strategy, or final test adequacy.
|
|
13
13
|
- Treat the architecture plan and scaffold as execution instructions, not review targets. Do not critique, reinterpret, or challenge them during Coder work.
|
|
14
14
|
|
|
15
|
+
### Semantic Code Navigation
|
|
16
|
+
|
|
17
|
+
- Use \`vcm-code-navigation\` whenever implementation requires code definitions, implementations, references, callers, callees, or behavior paths.
|
|
18
|
+
- Use LSP for semantic relationships. Use Glob to locate files and Read to inspect complete code.
|
|
19
|
+
- Do not use the Grep tool or shell text-search commands such as \`grep\`, \`rg\`, or \`git grep\`.
|
|
20
|
+
- If LSP cannot resolve a required project-owned relationship, record it as unresolved in the completion evidence. Do not replace semantic evidence with text matches.
|
|
21
|
+
|
|
15
22
|
### Shared Coding Standards
|
|
16
23
|
|
|
17
24
|
- Before editing production code or tests, read and follow \`docs/CODING_STANDARDS.md\`.
|
|
@@ -26,7 +33,7 @@ ${renderRoleMemoryRules("coder")}
|
|
|
26
33
|
- Do not stop before editing because of predicted architecture, design, contract, validation, or test failure; implement the assigned scaffold first.
|
|
27
34
|
- Use \`.ai/generated/module-index.json\` to locate approved module source and test files.
|
|
28
35
|
- Use \`.ai/generated/public-surface.json\` to avoid accidental public API drift.
|
|
29
|
-
-
|
|
36
|
+
- Use LSP definitions and references to locate assigned callable surfaces precisely.
|
|
30
37
|
|
|
31
38
|
### Implementation
|
|
32
39
|
|
|
@@ -15,20 +15,23 @@ Use only these decisions:
|
|
|
15
15
|
- \`approve\`: required gate evidence is present, current, internally consistent, sufficient for that gate, and has no gate-blocking finding.
|
|
16
16
|
- \`request_changes\`: evidence is missing, stale, contradictory, incomplete, insufficient, not reviewable, or unsafe.
|
|
17
17
|
|
|
18
|
+
## Semantic Code Navigation
|
|
19
|
+
|
|
20
|
+
Use \`vcm-code-navigation\` whenever a gate requires code definitions,
|
|
21
|
+
implementations, references, callers, callees, or behavior paths. Use LSP for
|
|
22
|
+
semantic relationships, Glob to locate files, Read to inspect complete code,
|
|
23
|
+
and generated context, architecture documents, or runtime evidence for
|
|
24
|
+
boundaries LSP does not model. Do not use the Grep tool or shell text-search
|
|
25
|
+
commands such as \`grep\`, \`rg\`, or \`git grep\`. If LSP cannot resolve
|
|
26
|
+
a required project-owned relationship, treat that evidence as unresolved and
|
|
27
|
+
return \`request_changes\`; do not replace semantic evidence with text matches.
|
|
28
|
+
|
|
18
29
|
Every Gate Review is a complete review of the current gate inputs. Review all
|
|
19
30
|
required evidence and rerun every required mechanical check before deciding.
|
|
20
31
|
Do not carry forward prior conclusions, closed checks, or partial verification.
|
|
21
32
|
Resolving prior findings does not replace the complete review. Return \`approve\`
|
|
22
33
|
or \`request_changes\` only after the review is complete.
|
|
23
34
|
|
|
24
|
-
Use \`vcm-code-navigation\` whenever a gate requires definitions, implementations,
|
|
25
|
-
references, call hierarchies, or a bounded behavior path. Use LSP semantic
|
|
26
|
-
navigation when available and read every resolved callable unit in full. Use
|
|
27
|
-
structural search when available and Grep for dynamic or textual edges and
|
|
28
|
-
explicit fallback discovery. Do not treat Grep as proof of a complete symbol,
|
|
29
|
-
caller, implementation, or reference set when LSP is available. If LSP cannot
|
|
30
|
-
resolve required evidence, record the limitation and exact fallback basis.
|
|
31
|
-
|
|
32
35
|
## Architecture Plan Gate
|
|
33
36
|
|
|
34
37
|
Format is necessary but not sufficient. Do not approve an architecture plan
|
|
@@ -469,7 +472,7 @@ If there are no findings, write:
|
|
|
469
472
|
None.
|
|
470
473
|
\`\`\`
|
|
471
474
|
|
|
472
|
-
Use Bash only for read-only inspection such as \`git diff\`, \`git status\`, \`git show\`, \`ls\`, \`
|
|
475
|
+
Use Bash only for read-only inspection such as \`git diff\`, \`git status\`, \`git show\`, \`ls\`, \`sed\`, or \`cat\`. Do not run shell text-search commands, tests, builds, formatters, generators, package managers, or commands that modify files.
|
|
473
476
|
|
|
474
477
|
Review only code, architecture, and documents; do not perform validation. Do not edit code, tests, durable docs, role files, route files, or handoff artifacts. Do not assign findings or remediation work to VCM roles, choose fixes, decide Replan, or decide whether user intervention is needed.
|
|
475
478
|
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
export function renderVcmCodeNavigationSkillRules() {
|
|
2
2
|
return `## Purpose
|
|
3
3
|
|
|
4
|
-
Use this skill when Architect or Reviewer must establish symbol definitions, implementations, references, callers, callees, or a bounded behavior path from current-worktree evidence.
|
|
4
|
+
Use this skill when Architect, Coder, or Reviewer must establish symbol definitions, implementations, references, callers, callees, or a bounded behavior path from current-worktree evidence.
|
|
5
5
|
|
|
6
6
|
## Navigation Order
|
|
7
7
|
|
|
8
8
|
1. Define the affected feature or module boundary and locate entry symbols with \`.ai/generated/module-index.json\` and \`.ai/generated/public-surface.json\` when available.
|
|
9
|
-
2.
|
|
9
|
+
2. Use LSP workspace or file symbols, definitions, implementations, references, and incoming or outgoing call hierarchy to resolve project-owned relationships.
|
|
10
10
|
3. Read the complete project-owned callable unit at every resolved location.
|
|
11
11
|
4. Expand one project-owned dependency hop at a time until the required behavior path has no unresolved symbol.
|
|
12
|
-
5. Use
|
|
12
|
+
5. Use Glob, generated context, architecture documents, and runtime evidence to locate dynamic registrations, configuration or string edges, macros, documentation, and external boundaries that LSP does not model.
|
|
13
13
|
|
|
14
14
|
## Evidence
|
|
15
15
|
|
|
16
|
-
- Record each resolved relationship and whether it came from LSP,
|
|
17
|
-
- Do not treat a Grep result as proof of a complete definition, caller, implementation, or reference set when LSP is available.
|
|
16
|
+
- Record each resolved relationship and whether it came from LSP, runtime evidence, an external boundary, or a generated boundary.
|
|
18
17
|
- When an empty LSP result contradicts a direct call in the code, another LSP result, or runtime evidence, treat the relationship as unresolved. Record the contradiction and use exact fallback evidence.
|
|
19
|
-
- If LSP is unavailable or cannot resolve a relationship, record that limitation and the
|
|
18
|
+
- If LSP is unavailable or cannot resolve a required project-owned relationship, record that limitation and leave the relationship unresolved. Do not substitute text search for semantic navigation.
|
|
19
|
+
- Do not use the Grep tool or shell text-search commands such as \`grep\`, \`rg\`, or \`git grep\`.
|
|
20
20
|
- Generated indexes and architecture docs locate likely code; reading current-worktree implementation establishes behavior.
|
|
21
21
|
|
|
22
22
|
## Context Boundary
|
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
|
}
|