From claude-vb6-skills
Applies general Visual Basic 6 conventions when editing .bas, .cls, .frm, .frx, .vbp, .vbg, .ctl, or .dob files, or any code identified as VB6 (not VB.NET, not VBA). Covers case-preservation in existing code (VB6 is case-insensitive but diff tools are not), Option Explicit requirement, Hungarian notation with scope prefix plus type prefix (mstr/mint/mcur for module-level in .bas; m_str/m_int/m_cur for module-level in .cls; gstr/gint for global; str/int/cur for parameters and locals), file header blocks for .cls modules with 80-hyphen delimiters, comment-per-field convention, Property Get/Let/Set patterns without public fields, ByVal/ByRef explicit declarations, Long over Integer for performance and overflow safety, preservation of Windows-1252 encoding and CRLF line endings, and never editing .frx files manually due to binary offset corruption. Activates for any Visual Basic 6 development task, code review, refactoring, or new file creation in a VB6 project.
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-vb6-skills:vb6-guidelinesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
VB6 is not VB.NET. It is not C#. It is not VBA. The rules below reflect what the
VB6 is not VB.NET. It is not C#. It is not VBA. The rules below reflect what the VB6 compiler (1998) requires and what the COM ecosystem expects.
VB6 is case-insensitive at the compiler level. Diff tools (Git, GitHub, GitLab,
Bitbucket, code review tools) are case-sensitive. Changing the case of an
identifier in existing code pollutes pull requests with ghost changes, hides the
real diff, and breaks git blame.
Absolute rule: when editing existing code, the case of every identifier stays exactly as it is. No exceptions.
This applies to:
m_StrName stays m_StrName even if the current
convention is m_strName)Public Sub ReadFile is not "normalized" to
ReadFile if it is already ReadFile)If/Then/End If — preserve as written; the IDE may have
normalized different files differently)As String, As Long)vbCrLf vs vbCRLF)When you may use the "correct" case:
When you may not, even when it looks obvious:
The cost of polluting the diff outweighs the value of uniformity. Always.
VB6 allows implicit typing for VB3/4 backward compatibility. In new code, this is guaranteed technical debt.
Option Explicit at the top of every module (.bas, .cls,
.frm)Dim i As Long, never Dim iLong over Integer — VB6 Integer is 16 bits, overflows at
32,768Variant except when interacting with APIs that return Variant
(e.g., ADO GetRows)Function: Public Function X() As String,
not Public Function X()The convention is two-part: a scope prefix followed by a type prefix, both in lowercase in new code.
| Scope | Prefix | Example |
|---|---|---|
Module-level in .bas | m | Private mobjCnn As Object |
Module-level in .cls | m_ | Private m_strName As String |
Global public in .bas | g | Public gstrServer As String |
| Parameter / local | (none) | Dim strValue As String |
| VB6 Type | Prefix |
|---|---|
String | str |
Integer | int |
Long | lng |
Currency | cur |
Boolean | bln |
Double | dbl |
Date | dtm |
Byte | byt |
Object (generic) | obj |
Variant | var |
Recordset (ADO) | rs |
Dictionary | dic |
Collection | col |
Canonical examples:
Private mobjCnn As Object ' module-level Object Connection
Private mrsCustomers As Object ' module-level Recordset
Private mdicConfig As New Dictionary
Private m_strName As String ' in .cls
Public gstrServer As String
Public Sub DoWork(ByVal strInput As String, ByVal lngCount As Long)
Dim intIndex As Integer
End Sub
Acronyms keep uppercase within the identifier: m_strNSU, m_strCNPJ,
m_strXML, m_strCPF. Not m_strNsu.
Project-wide "infrastructure" globals widely known across the codebase may omit
the type prefix (e.g., gParameters, gAgent). For new code, the type prefix
is recommended.
See section 1. If a variable already exists as m_StrName or mObjCnn, it
stays exactly that way in any edit.
Every new .cls starts with a delimited header block:
'--------------------------------------------------------------------------------
' Component : clsCustomer
' Project : SampleApp
'
' Description: Domain object representing a customer record.
' Mirrors the structure of the Customer table.
' <reference URL when applicable>
' Modified :
'--------------------------------------------------------------------------------
Option Explicit
Delimiters are 80 hyphens. Indentation inside the block uses spaces, not tabs.
Each private field gets a one-line comment above it:
' Variable to hold 'Name' property value
Private m_strName As String
' Variable to hold 'Active' property value
Private m_blnActive As Boolean
Yes, it is repetitive. It is the convention. New classes follow it.
Class modules expose state through properties, not public fields. Public fields break binary compatibility in ActiveX DLLs, prevent validation, and break IntelliSense for late-bound consumers.
Private m_strName As String
Private m_objConnection As Object
Public Property Get Name() As String
Name = m_strName
End Property
Public Property Let Name(ByVal strValue As String)
m_strName = strValue
End Property
Public Property Set Connection(ByVal objValue As Object)
Set m_objConnection = objValue
End Property
Property Get readsProperty Let writes a value type (String, Long, Boolean, etc.)Property Set writes an object referenceParameter naming in setters follows the type prefix: strValue, lngValue,
blnValue, objValue.
Setters do not validate, do not transform, do not log:
Public Property Let Name(ByVal strValue As String)
m_strName = strValue
End Property
When validation or transformation is needed, it goes in a dedicated method
(LoadByID, Validate, Normalize), not in the property setter.
Store raw state, expose human-readable form as a Get-only derived property:
' Stored as raw code
Public Property Get Status() As String
Status = m_strStatus
End Property
Public Property Let Status(ByVal strValue As String)
m_strStatus = strValue
End Property
' Derived for display
Public Property Get StatusDescription() As String
Select Case Val(m_strStatus)
Case 1: StatusDescription = "1-Authorized"
Case 2: StatusDescription = "2-Denied"
Case 3: StatusDescription = "3-Cancelled"
End Select
End Property
Do not put Select Case inside the Let.
Trivial CRUD properties do not need a header. Properties that carry domain rules (valid values, code tables, references to external specifications) get a header block:
'--------------------------------------------------------------------------------
' Project : SampleApp
' Procedure : Status
' Description: Document status. Valid values:
' 1 - Authorized
' 2 - Denied
' 3 - Cancelled
' Created by : author
' Date-Time : DD/MM/YYYY-HH:MM:SS
'
' Parameters : strValue (String) - status code
'--------------------------------------------------------------------------------
Criterion: if a reader would need to open external documentation to understand the property, it gets a header.
A lighter format is acceptable for utility functions (where Project is
irrelevant because the utility is reused across projects):
'---------------------------------------------------------------------------------------
' Procedure : NormalizePhone
' Author : author
' Date : DD/MM/YYYY
' Purpose : Strips non-digit characters from a phone number string.
'---------------------------------------------------------------------------------------
TypeClasses that belong to a hierarchy identify themselves via
Public Property Get Type() As String returning a constant string
(e.g., "CUSTOMER", "INVOICE"). When creating a new class in the same group,
include the discriminator.
In VB6, parameters default to ByRef, unlike most modern languages.
ByVal or ByRef — never rely
on the defaultByVal for primitives that do not need to be modified (Long, String,
Boolean)ByRef only when the function actually needs to modify the caller's
variableString by ByRef accidentally lets the function mutate the
caller's variable.frx files are binary. They store images, icons, and long strings referenced
by their companion .frm. Any manual edit corrupts the offset table and breaks
the form.
Reformatting .frm files is equally dangerous — the declarative section at the
top has strict ordering requirements; the IDE depends on it.
Comments and string literals follow the team's working language. Identifiers remain unaccented for IDE compatibility. New code matches the language already established in the file or module.
VB6 codebases typically wrap MsgBox in custom dialog forms (for theming,
logging integration, consistent button layouts). When such wrappers exist in
the project (commonly named MsgBoxCritical, MsgBoxInfo, MsgBoxWarning,
MsgBoxQuestion), new code uses them — not raw MsgBox.
Option Explicit at the top of the moduleDim and every Function/Sub has an explicit typeByVal / ByRef explicit on every parameterVariant unless interacting with a Variant-returning APILong instead of Integer for loop counters and quantitiesm_<type> in classes or m<type> in
modules, lowercase type prefixg<type>, lowercase.frx untouchedMsgBox direct call when wrapper functions existnpx claudepluginhub alexcassol/claude-vb6-skills --plugin claude-vb6-skillsProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.