Maintained by: designer role
Last updated: 2026-05-14
This document defines the repository-level hooks artifact family in vstack. It complements:
docs/design/overview.md(global design baseline)docs/design/workflow.md(workflow progression model)docs/architecture/adr/027-repository-hooks-artifact-type.md(decision record)
Repository hooks are generated JSON artifacts installed at:
.github/hooks/<name>.json
They are managed by the same install/verify/status/uninstall manifest model used by other artifact families.
| Element | Path pattern |
|---|---|
| source template | src/vstack/_templates/hooks/<name>/hook.yaml |
| installed output | .github/hooks/<name>.json |
| manifest key | hooks |
| singular type | hook |
Hook artifacts render from YAML source templates into JSON payloads, so they differ from markdown artifact families:
| Property | Value |
|---|---|
add_frontmatter |
false |
auto_gen_footer |
false |
artifact_is_dir |
false |
template_filename |
hook.yaml |
Implication: manifest verification must use checksum ownership and skip markdown-footer metadata checks.
Hook templates follow the GitHub Copilot hooks envelope:
{
"version": 1,
"hooks": {
"preToolUse": [
{
"type": "command",
"bash": "...",
"powershell": "..."
}
]
}
}Hook artifacts intentionally carry two independent version concepts:
| Field | Location | Example | Purpose |
|---|---|---|---|
Payload version |
Generated .github/hooks/<name>.json |
1 |
Copilot hook envelope/contract version. |
Artifact version |
Source src/vstack/_templates/hooks/<name>/hook.yaml (top-level) |
20260514002 |
vstack template revision token used in manifest and footer metadata for ownership/drift checks. |
Design implication:
- Do not couple payload contract versioning to template revision versioning.
- A future Copilot contract bump (
version: 2) should be handled in hook generator render logic. - Routine template changes should continue to bump only the artifact revision token.
vstack ships seven default repository hooks:
| Hook name | Primary events | Intent |
|---|---|---|
agent-call-audit |
sessionStart, sessionEnd, preToolUse, postToolUse |
Actor and delegation visibility with per-session unknown counters, unknownSummary, and delegation start/end events |
session-audit |
userPromptSubmitted, sessionStart, sessionEnd, preToolUse, postToolUse |
Generic session, prompt, and tool telemetry without actor fields |
log-retention-cleanup |
sessionStart |
Prune dated log directories by retention policy |
pre-tool-safety-gate |
preToolUse, errorOccurred |
Deny destructive shell patterns + error logging |
post-edit-format |
postToolUse |
Edit-event logging + optional make format run |
post-edit-markdown-quality |
postToolUse |
vstack markdown/work-item formatting for docs and templates |
post-commit-security-scan |
postToolUse, sessionEnd |
Git-mutation checks + optional gitleaks run |
These defaults are safe in audit mode and can be upgraded to enforcement behavior. For vstack specifically, the markdown-quality hook gives the baseline hook set a direct payoff on ADRs, design docs, prompts, instructions, and other generated work items.
agent-call-audit keeps .vstack/logs/YYYYMMDD/hook-agent-call-unknown-events.tsv as a per-day sidecar.
- Why it exists: minimal-mode logging suppresses repeated unknown rows, so the sidecar accumulates unknown reasons per session instead of writing noisy duplicates.
- Operational value: it exposes parser quality and unknown-rate trends, and powers reliable
unknownSummaryemission atsessionEnd. - When it is not needed: verbose mode (raw payload logs), disabled/off hook logging, or sessions with no unknown actor/tool extraction outcomes.
--only hookis supported in all type-aware commands..vstack/config.yamlsupportsexclude.hookand per-name exclusions underhook..vstack/config.yamlalso supports ahooks:block for project-level defaults:
hooks:
enabled: true
mode: audit
log_level: minimal
log_retention_days: 7
log_dir: .vstack/logs
hooks:
pre-tool-safety-gate:
mode: enforce
log:
level: verbose
name: hook-security-alerts.log
retention_days: 14
post-edit-markdown-quality:
enabled: falsehooks.enabled: falsedisables the generated baseline hook family.hooks.modesets the generated default fallback forVSTACK_HOOKS_MODE.hooks.log_levelsets the generated default fallback forVSTACK_HOOKS_LOG_LEVEL(off,minimal,verbose).hooks.log_retention_dayssets the generated default fallback forVSTACK_HOOKS_LOG_RETENTION_DAYS.hooks.log_dirsets the generated default fallback forVSTACK_HOOK_LOG_DIR.hooks.hooks.<name>.enabled: falsedisables one named built-in hook.hooks.hooks.<name>.modeoverrides the default mode for one named hook.hooks.hooks.<name>.log.leveloverridesVSTACK_HOOKS_LOG_LEVELfor one hook.hooks.hooks.<name>.log.nameoverridesVSTACK_HOOK_LOG_NAMEfor one hook.hooks.hooks.<name>.log.retention_daysoverrides retention for one hook.
install/init: generate and track.github/hooks/*.jsonstatus: show managed/modified/missing state for hook filesverify:- source checks: required hook names and source template presence
- output checks: installed file presence + checksum ownership/drift
- metadata checks: skipped for hook artifacts
uninstall: remove tracked hook outputs according to checksum protection rules
Source of truth for built-in hooks is a single hook.yaml file per hook directory.
The generator validates YAML structure, then renders the GitHub Copilot JSON envelope.
That gives maintainers:
- readable multiline shell commands,
- descriptive metadata next to behavior,
- project-level mode overrides at generation time,
- the same installed
.github/hooks/*.jsoncontract expected by Copilot.
Migration is additive.
For existing repositories:
- run
vstack install(orvstack initin CI), - review generated
.github/hooks/*.json, - optionally disable specific defaults using
.vstack/config.yamlexclude.hook.
Per-agent frontmatter field hooks remains supported and is intentionally separate
from repository-level hooks artifacts.
Hook templates support two operational modes through VSTACK_HOOKS_MODE:
audit(default): record events and alerts without enforcing extra tool runsenforce: run optional actions such asmake formatandgitleakswhen conditions match
This keeps first-run installs non-disruptive while allowing stricter policy in CI or hardened repositories.
session-audit and agent-call-audit are intentionally bounded to timeoutSec: 5.
They do not depend on external tools and only perform local payload parsing and append-only log writes.
The shorter timeout reduces tail latency and keeps hook execution atomic and fast.
The internal logging contract keeps minimal mode compact and deterministic across both audit hooks:
| Hook | Event scope | Minimal schema baseline |
|---|---|---|
session-audit |
any event | timestamp, event, size_bytes, estimated_tokens, hook_execution_ms |
session-audit |
preToolUse, postToolUse |
base fields + tool_name, tool_call_id |
agent-call-audit |
any event | timestamp, event, size_bytes, estimated_tokens, hook_execution_ms |
agent-call-audit |
preToolUse, postToolUse |
base fields + session_id, actor_name, actor_type, tool_name, delegated_agent_name, model_used (no tool_call_id) |
session-audit and agent-call-audit |
verbose mode |
raw payload passthrough |
base fields means timestamp, event, size_bytes, estimated_tokens, and hook_execution_ms.
Repository hooks execute in the active Copilot hook runtime context.
- local workspace session: local machine runtime
- remote SSH/devcontainer session: remote runtime
All built-in hooks use cwd: "." and therefore run from the workspace root.
Built-in hooks follow a fail-safe dependency model:
auditmode must work without optional tools,enforcemode may call optional tools,- hooks must check tool availability before invoking external commands,
- missing optional tools should log a structured alert and continue.
This prevents install-time or first-run failures while supporting stricter policy in controlled environments.
- Keep default behavior non-destructive (
auditfirst). - Restrict deny-response behavior to explicit enforce paths.
- Use short timeouts and deterministic command paths.
- Never log secrets; log only event metadata and detection markers.
src/vstack/_templates/hooks/<name>/hook.yaml includes a metadata: section.
These fields are documentation-oriented, and selected values such as mode_default are also
used as generation-time defaults when the project does not override them.
Recommended fields:
| Field | Purpose |
|---|---|
description |
Human-readable explanation of hook behavior |
purpose |
Classification (audit, security, quality) |
security_level |
Expected risk posture (low, high) |
mode_default |
Declared default operation mode |
execution_context |
Runtime location expectation |
dependencies.required |
Must-exist tools for intended behavior |
dependencies.optional |
Optional tooling used in enforce mode |
When adding new built-in hooks:
- add template directory under
src/vstack/_templates/hooks/<name>/ - include
hook.yaml - add name to expected canonical hook list in CLI constants
- regenerate artifacts via
python -m vstack install - update this design doc and roadmap status if behavior changes