-
Notifications
You must be signed in to change notification settings - Fork 3
Shared Modules
VCP's TypeScript modules live in plugins/vcp/lib/. They provide shared logic used by both hooks and skills (via CLI entrypoints).
plugins/vcp/lib/
├── global-config.ts # Global config (~/.vcp/config.json) load/save/resolve
├── global-config.test.ts # 42 tests
├── vcp-context-core.ts # Core extraction logic (shared by hooks + CLI entrypoints)
├── vcp-context-core.test.ts # 40 tests
├── vcp-logger.ts # Shared diagnostic logging (hooks + lib modules)
├── vcp-logger.test.ts # 9 tests
├── resolve-config.ts # CLI entrypoint for scanning skills
└── generate-context.ts # CLI entrypoint for /vcp-context skill
The module for loading, saving, and resolving global VCP configuration (~/.vcp/config.json).
| Type | Description |
|---|---|
VcpGlobalConfig |
Parsed ~/.vcp/config.json: standards_url, pluginRoot, debug?, defaults?
|
| Constant | Value |
|---|---|
DEFAULT_MANIFEST_URL |
"https://raw.githubusercontent.com/Z-M-Huang/vcp/main/standards/manifest.json" |
-
globalConfigPath(): string— Returnspath.join(os.homedir(), ".vcp", "config.json") -
loadGlobalConfig(): Promise<VcpGlobalConfig | null>— Reads~/.vcp/config.json. Returns parsed config ornullon any error. Never throws. -
saveGlobalConfig(config: VcpGlobalConfig): Promise<void>— Writes config. Creates~/.vcp/directory if needed. -
ensureGlobalConfig(projectConfig): Promise<VcpGlobalConfig | null>— Returns existing global config, or auto-creates one from project config'spluginRoot+DEFAULT_MANIFEST_URL. Returnsnullif no config exists and project config lackspluginRoot, or if the write fails. Security: Always usesDEFAULT_MANIFEST_URL— never promotes project config'sstandards_urlto global config (project config is untrusted). Only used in the skills path (resolve-config.ts), never in hooks. -
validateStandardsUrl(url): string | null— Validates a URL is safe to fetch. Rejects non-HTTPS, localhost, private/link-local IPs, IPv6 ULA, IPv4-mapped IPv6 loopback/private, trailing-dot localhost, and cloud metadata endpoints. Returnsnullif valid, error message string if not. -
resolveStandardsUrl(globalConfig, projectConfig): string | null— Resolution: project → global → null. Validates the resolved URL viavalidateStandardsUrl(). -
resolvePluginRoot(globalConfig, projectConfig): string | null— Resolution: project → global → null -
mergeIgnoreArrays(globalIgnore, projectIgnore): string[]— Deduplicated union of both arrays -
applyGlobalDefaults(globalConfig, projectConfig): VcpConfig— Applies global defaults. severity: project wins. scopes/compliance: project wins. ignore: union.
The central module that all context generation and config resolution flows through.
| Type | Description |
|---|---|
VcpConfig |
Parsed .vcp/config.json with fields: version, scopes, compliance, ignore?, frameworks?, exclude?, severity?, pluginRoot?
|
ParsedIgnores |
Categorized ignore entries: { standards: Set, rules: Set, cwes: Set }
|
StandardEntry |
Single entry from manifest: { id, url, scope, severity, tags, applies }
|
Manifest |
Parsed manifest.json: { version, repository, scopes, standards: StandardEntry[] }
|
ScopedRules |
Map of scope to array of { standardId, severity, rules: Array<{number, title}> }
|
Reads .vcp/config.json from the project root using Bun.file(). Returns parsed config or null if the file doesn't exist or can't be parsed.
Categorizes ignore entries into three sets:
-
Standards: Entries without
/rule-and not matchingCWE-\d+(e.g.,"core-architecture") -
Rules: Entries containing
/rule-(e.g.,"core-security/rule-3") -
CWEs: Entries matching
CWE-\d+(e.g.,"CWE-798")
Fetches standards/manifest.json from the provided URL. Detects manifest version: v1 manifests construct full URLs from standards_base_url + path for backward compatibility; v2 manifests use full HTTPS URLs directly. Each scope manifest URL is validated via validateStandardsUrl() before fetching — unsafe URLs cause the fetch to throw. Throws on any fetch failure (including individual scope manifests). No caching.
Filters the manifest's standards array by:
-
applies === "always"(core standards, always included) -
appliesmatches an active scope fromconfig.scopes -
appliesmatches"compliance:{framework}"where framework is inconfig.compliance
Then removes any standard whose id appears in the parsed ignore list's standards set.
Without config: returns only core standards (applies === "always").
Fetches each standard's markdown content from entry.url in parallel via Promise.all. Each URL is validated via validateStandardsUrl() before fetching — unsafe URLs are skipped. Emits a console.warn to stderr listing any dropped standards (with reasons). Returns a map of standard ID to markdown content.
For each standard:
- Finds the
## Rulessection in the markdown - Extracts numbered rules using the regex
/^(\d+)\.\s+\*\*(.+?)\*\*/gm - Filters out rules matching the parsed ignore list's
rulesset - Groups results by scope with severity annotation
Formats the extracted rules as a markdown block:
- Grouped by scope (Core first, then alphabetical)
- Within each scope, ordered by severity (critical, high, medium, low)
- Prefixed with
## VCP Standards Context - Suffixed with
> Run /vcp-audit for deep analysis. - Applies token budget truncation if output exceeds limits
Token budget truncation algorithm:
- Build the full output
- If under budget, return as-is
- If over budget, greedily include standards from highest severity first
- Ensure at least one standard always survives
Builds a ### Standard References section listing URLs to full standard documents. Only includes entries that exist in the fetched Map (successfully fetched) AND pass validateStandardsUrl() re-validation (defense-in-depth against duplicate-ID URL mismatch). Output is appended after the budget-controlled rule summaries by generateContext().
Orchestrator function — the single entry point for hook wrappers:
-
loadGlobalConfig()→ global config or null -
loadConfig()→ project config or null -
applyGlobalDefaults()if global config exists -
resolveStandardsUrl()to determine manifest URL fetchManifest(url)resolveApplicableStandards()fetchStandards()extractRuleSummaries()-
formatContext()— budget-controlled rule summaries -
buildReferenceSection()— appends standard URLs for on-demand loading
Wrapped in try/catch — returns FALLBACK_MESSAGE on any error.
| Constant | Value |
|---|---|
FALLBACK_MESSAGE |
"VCP active but not fully initialized. Run /vcp-init to configure, then /vcp-audit before committing." |
SEVERITY_ORDER |
{ critical: 0, high: 1, medium: 2, low: 3 } |
CLI entrypoint that scanning skills call via Bash to resolve project configuration.
bun resolve-config.ts [project-root]- Reads
~/.vcp/config.json(global config) - If global config is missing, auto-creates it via
ensureGlobalConfig()(usesDEFAULT_MANIFEST_URL+ project'spluginRoot) - Reads
.vcp/config.jsonfrom the project root - Merges ignore arrays from both configs
- Fetches the standards manifest
- Resolves applicable standards (with ignore filtering)
- Outputs JSON to stdout
| Code | Meaning |
|---|---|
| 0 | Success — JSON output on stdout |
| 1 | Failure — error message on stderr |
{
"standardsUrl": "https://raw.githubusercontent.com/.../standards/manifest.json",
"applicableStandards": [
{
"id": "core-security",
"url": "https://raw.githubusercontent.com/.../standards/core-security.md",
"scope": "core",
"severity": "critical",
"tags": ["security", "owasp", "cwe"]
}
],
"ignoredRules": ["core-security/rule-3"],
"severity": "medium",
"exclude": ["node_modules/**", ".git/**", "dist/**"]
}Before resolve-config.ts, each of the 4 scanning skills had ~25 lines of duplicated SKILL.md prose for fetching the manifest, reading .vcp/config.json, resolving scopes, and applying ignores. This CLI centralizes that logic in TypeScript so skills only need to call it via Bash and parse the JSON output.
CLI entrypoint for the /vcp-context skill.
bun generate-context.ts [project-root]Calls generateContext(projectRoot) and outputs the result to stdout. Always exits 0.
Skills can't import TypeScript modules directly — they execute via AI instructions, not code. This CLI provides a Bash-callable bridge between the skill's SKILL.md instructions and the shared TypeScript module.
Shared diagnostic logging utility used by all hooks and available to lib modules.
Appends a timestamped log entry to .vcp/vcp.log in the project root. Only writes when debug is true — otherwise returns immediately (no-op).
Parameters:
| Parameter | Type | Description |
|---|---|---|
projectRoot |
string |
Absolute path to the project root |
entry.source |
string |
Hook or module name (e.g., "security-gate", "stop-reminder") |
entry.event |
string |
Hook event (e.g., "PreToolUse", "SessionStart", "Stop") |
entry.decision |
"allow" | "block" | "warn" | "info" | "error" |
What the hook decided |
entry.details |
string? |
Optional — additional context (CWE IDs, char counts, etc.) |
debug |
boolean |
Whether to write the log entry. Defaults to false. Hooks read this from globalConfig?.debug ?? false. |
Behavior:
- If
debugisfalse(or omitted), returns immediately — no file I/O - Validates
projectRootis non-empty and an absolute path; silently returns if not - Appends to
.vcp/vcp.log(creates the file if it doesn't exist) - Never throws — logging failures are silently caught to avoid breaking hook execution
Log format:
ISO_TIMESTAMP [event] source: decision — details
Example:
2026-02-16T10:31:05.789Z [PreToolUse] security-gate: block — CWE-798
All 4 hooks import vcpLog and loadGlobalConfig, read the debug flag from global config, and pass it to every vcpLog call. Logging only occurs when the user has set debug: true in ~/.vcp/config.json:
| Hook | Events Logged |
|---|---|
security-context.ts |
info with character count and full injected context between --- BEGIN CONTEXT --- / --- END CONTEXT --- markers |
security-gate.ts |
allow (no findings / empty content), block (CWE IDs), warn (suppressed CWEs) |
test-quality-warning.ts |
warn (issue count + file path), allow (no issues) |
stop-reminder.ts |
info (reminder shown) |
| Aspect | Hooks | Skills |
|---|---|---|
| Execution | Bun runs TypeScript directly | AI interprets SKILL.md instructions |
| Access to modules |
import from relative paths |
Call CLI via Bash |
| Config access |
CLAUDE_PROJECT_DIR env var |
Read .vcp/config.json with pluginRoot
|
| Plugin path |
CLAUDE_PLUGIN_ROOT (template var in hooks.json) |
pluginRoot from .vcp/config.json
|
Both environments need the same logic (config loading, manifest fetch, standard resolution, ignore filtering). The shared module provides it once, with two access patterns:
-
Hooks: Direct
import - Skills: CLI entrypoint called via Bash
| Category | Tests | What's Tested |
|---|---|---|
parseIgnoreList |
3 | Categorization into standards/rules/cwes, empty input |
resolveApplicableStandards |
8 | Core-only, scope matching, compliance matching, no-config fallback, ignore filtering |
extractRuleSummaries |
8 | Basic extraction, scope grouping, no-rules handling, rule-level ignore, multi-standard |
formatContext |
6 | Output structure, scope grouping, severity ordering, token budget truncation, core-before-non-core ordering |
flattenV2Manifest |
6 | Multi-scope flattening, applies/scope field preservation, all entry fields, empty input, integration with resolveApplicableStandards |
buildReferenceSection |
3 | Only fetched standards included, empty fetched map, unsafe URLs excluded even if fetched |
loadConfig auto-migration |
3 | .vcp/config.json loading, .vcp.json → .vcp/config.json migration, missing config |
| Integration: security-context.ts | 3 | Hook exit codes, output format, no-config behavior |
| Category | Tests | What's Tested |
|---|---|---|
| File creation | 1 | Creates .vcp/vcp.log in project root (debug=true) |
| Append behavior | 1 | Multiple entries appended correctly (debug=true) |
| Format validation | 1 | ISO timestamp, event, source, decision, details pattern |
| Error handling | 1 | Nonexistent directory does not throw |
| Guard: empty root | 1 | Empty projectRoot silently no-ops |
| Guard: relative root | 1 | Relative path silently no-ops |
| Optional details | 1 | Omits — separator when details undefined |
| Debug: false | 1 | No .vcp/vcp.log file created when debug=false |
| Debug: default | 1 | No .vcp/vcp.log file created when debug omitted (defaults to false) |
| Category | Tests | What's Tested |
|---|---|---|
globalConfigPath |
2 | Path ends in .vcp/config.json, starts with homedir |
loadGlobalConfig |
1 | Never throws (returns null or config) |
validateStandardsUrl |
16 | HTTPS enforcement, localhost, private IPs, link-local, cloud metadata, IPv6 ULA, IPv4-mapped IPv6, trailing-dot localhost, enterprise URLs |
resolveStandardsUrl |
7 | Project wins, global fallback, null when both null, invalid URL rejection, edge cases |
resolvePluginRoot |
3 | Project wins, global fallback, null when both null |
mergeIgnoreArrays |
3 | Union, deduplication, empty arrays |
applyGlobalDefaults |
7 | Severity override, ignore merge, scopes no-merge, compliance no-merge, null global, no mutation |
ensureGlobalConfig |
3 | Returns existing config, null without pluginRoot, always uses DEFAULT_MANIFEST_URL (never promotes project URL) |
DEFAULT_MANIFEST_URL |
1 | URL format validation |
VCP Wiki
Guides
- First-Time Setup Guide
- How Configuration Works
- Configuration Recipes
- Web Portal Guide
- Daily VCP Workflow
- Troubleshooting
VCP Plugin
- Configuration
- Skills Reference
- Three‐Layer Enforcement Model
- Hooks Reference
- Security Gate Patterns
- Shared Modules
Dev Buddy Plugin
- Dev Buddy Quick Start
- Dev Buddy Configuration
- Stage Skills Guide
- AI Provider Presets
- System Prompts Reference
- Chatroom
MCP Doc Plugin
Standards
Project
VCP Wiki (中文)
指南
VCP 插件
Dev Buddy 插件
MCP Doc 插件
标准
项目