From 1dbc71f35b3ed390fed2bfcca286ce8490eaa81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A1udia?= Date: Thu, 17 Sep 2026 11:31:09 +0100 Subject: [PATCH] Add Verity quality gate and project memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces Verity to this repo: a standard, a knowledge graph, and the agent-facing instructions that make both load-bearing. - `.verity/standard.yaml` — the shared quality standard - `.verity/memory/` — knowledge graph (17 nodes + index + schema), committed so the guidance is shared rather than machine-local - `.claude/skills/verity-*` — 8 skills (analyze, feedback, insights, learn, memory, reflect, setup, status) - `.claude/settings.json` — hooks wiring the gate into the session lifecycle - `CLAUDE.md` — project memory, accepted-risk waiver rules, post-task reflection, and housekeeping-turn declarations - `.gitignore` — ignores machine-local `.verity/` state, keeping the standard and knowledge graph tracked Co-Authored-By: Claude Opus 5 --- .claude/settings.json | 56 ++ .claude/skills/verity-analyze/SKILL.md | 221 ++++++ .claude/skills/verity-feedback/SKILL.md | 39 + .claude/skills/verity-insights/SKILL.md | 111 +++ .claude/skills/verity-learn/SKILL.md | 63 ++ .claude/skills/verity-memory/SKILL.md | 113 +++ .claude/skills/verity-reflect/SKILL.md | 83 +++ .claude/skills/verity-setup/SKILL.md | 673 ++++++++++++++++++ .../verity-setup/patterns-reference.yaml | 614 ++++++++++++++++ .../verity-setup/standard-template.yaml | 153 ++++ .../skills/verity-setup/validate-patterns.mjs | 345 +++++++++ .claude/skills/verity-status/SKILL.md | 47 ++ .gitignore | 8 + .verity/memory/SCHEMA.md | 48 ++ .../memory/domain/n001-project-overview.md | 23 + .verity/memory/domain/n002-project-purpose.md | 18 + .../domain/n006-pick-the-workflow-first.md | 34 + ...n007-the-loop-investigate-change-verify.md | 24 + .../memory/domain/n008-when-rules-conflict.md | 23 + .verity/memory/domain/n009-hard-rules.md | 27 + .../n010-mechanics-that-fail-silently.md | 26 + .verity/memory/domain/n011-reference.md | 26 + .verity/memory/domain/n012-project-memory.md | 24 + .../domain/n014-post-task-reflection.md | 38 + .../memory/domain/n015-housekeeping-turns.md | 40 ++ .verity/memory/index.md | 3 + .verity/memory/integrations/n003-mkdocs.md | 18 + .../integrations/n004-mkdocs-material.md | 18 + .verity/memory/integrations/n005-jinja2.md | 18 + .../n013-quality-gate-accepted-risks.md | 26 + .verity/standard.yaml | 184 +++++ CLAUDE.md | 73 ++ VERITY.md | 36 + 33 files changed, 3253 insertions(+) create mode 100644 .claude/settings.json create mode 100644 .claude/skills/verity-analyze/SKILL.md create mode 100644 .claude/skills/verity-feedback/SKILL.md create mode 100644 .claude/skills/verity-insights/SKILL.md create mode 100644 .claude/skills/verity-learn/SKILL.md create mode 100644 .claude/skills/verity-memory/SKILL.md create mode 100644 .claude/skills/verity-reflect/SKILL.md create mode 100644 .claude/skills/verity-setup/SKILL.md create mode 100644 .claude/skills/verity-setup/patterns-reference.yaml create mode 100644 .claude/skills/verity-setup/standard-template.yaml create mode 100644 .claude/skills/verity-setup/validate-patterns.mjs create mode 100644 .claude/skills/verity-status/SKILL.md create mode 100644 .verity/memory/SCHEMA.md create mode 100644 .verity/memory/domain/n001-project-overview.md create mode 100644 .verity/memory/domain/n002-project-purpose.md create mode 100644 .verity/memory/domain/n006-pick-the-workflow-first.md create mode 100644 .verity/memory/domain/n007-the-loop-investigate-change-verify.md create mode 100644 .verity/memory/domain/n008-when-rules-conflict.md create mode 100644 .verity/memory/domain/n009-hard-rules.md create mode 100644 .verity/memory/domain/n010-mechanics-that-fail-silently.md create mode 100644 .verity/memory/domain/n011-reference.md create mode 100644 .verity/memory/domain/n012-project-memory.md create mode 100644 .verity/memory/domain/n014-post-task-reflection.md create mode 100644 .verity/memory/domain/n015-housekeeping-turns.md create mode 100644 .verity/memory/index.md create mode 100644 .verity/memory/integrations/n003-mkdocs.md create mode 100644 .verity/memory/integrations/n004-mkdocs-material.md create mode 100644 .verity/memory/integrations/n005-jinja2.md create mode 100644 .verity/memory/quality/n013-quality-gate-accepted-risks.md create mode 100644 .verity/standard.yaml create mode 100644 VERITY.md diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..995456d2b3 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,56 @@ +{ + "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "verity intent capture" + } + ] + } + ], + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "verity baseline capture" + } + ] + } + ], + "PostCompact": [ + { + "hooks": [ + { + "type": "command", + "command": "verity compact" + } + ] + } + ], + "SessionEnd": [ + { + "hooks": [ + { + "type": "command", + "command": "verity session end" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "verity analyze", + "timeout": 120, + "statusMessage": "Running Verity analysis" + } + ] + } + ] + } +} diff --git a/.claude/skills/verity-analyze/SKILL.md b/.claude/skills/verity-analyze/SKILL.md new file mode 100644 index 0000000000..ccb897fcf0 --- /dev/null +++ b/.claude/skills/verity-analyze/SKILL.md @@ -0,0 +1,221 @@ +# /verity-analyze — Run Verity analysis on demand + +You are running an on-demand Verity analysis. This is like a "second opinion" — the user (or you) can invoke it at any point to get a quality and security review with rich context. + +**Usage:** +- `/verity-analyze` — Curate files, write intent, select specs autonomously (deep review) +- `/verity-analyze src/api/routes.ts src/db.ts` — Analyze specific files (quick check) +- `/verity-analyze src/api/` — Analyze a directory and related files +- `/verity-analyze --full` — Sample the full codebase (20 most recently modified files) +- `/verity-analyze --intent "Implementing OAuth2 PKCE flow per spec/AUTH.md"` — User provides intent + +--- + +## Step 1: Check configuration + +1. Verify `.verity/standard.yaml` exists. If not: "Run `/verity-setup` first." +2. Verify `verity` CLI is available: `which verity`. If not: "Re-run the Verity installer: `curl -fsSL https://raw.githubusercontent.com/codacy/verity/main/install.sh | bash`" +3. Run `verity auth verify` to check the token is valid. + - **Valid**: proceed with the full deep review. + - **Fails / not authenticated**: don't stop. Tell the user Verity is in + **local-only mode**, so the review will run static analysis and show findings + but won't perform the deep (LLM) review or upload anything. To unlock the deep + review they can authenticate with `verity login` (one login covers every + repository they can write to). + Continue — `verity review` degrades to a local static-only report on its own. + +--- + +## Step 2: Determine files to analyze + +### Quick mode (specific files or `--full`) + +**Specific files** (paths or directories provided): +- Use the provided file paths directly. If a directory is given, include all analyzable files under it. +- Verify each file exists. + +**`--full` flag** (full codebase sample): +```bash +FILES=$(git ls-files | grep -E '\.(ts|tsx|js|jsx|mjs|cjs|py|go|java|kt|rb|rs|c|cpp|h|hpp|cs|php)$') +``` +If more than 20 files, select the 20 most recently modified: +```bash +echo "$FILES" | xargs ls -t | head -20 +``` + +In quick mode, skip Steps 3 and 4 — go straight to Step 5. + +### Deep mode (no arguments, or directory hints) + +When no specific files are given, curate a comprehensive review scope: + +#### 2a. Start with changed files + +```bash +CHANGED=$(git diff --name-only HEAD 2>/dev/null; git diff --name-only --cached 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null) +CHANGED_CODE=$(echo "$CHANGED" | sort -u | grep -E '\.(ts|tsx|js|jsx|mjs|cjs|py|go|java|kt|rb|rs|c|cpp|h|hpp|cs|php)$') +``` + +Also check if the agent just committed (clean tree but recent HEAD): +```bash +# If working tree is clean, check what the last commit changed +git diff --name-only HEAD~1..HEAD 2>/dev/null +``` + +If the user provided directory hints (e.g., `/verity-analyze src/api/`), also include all files under those directories. + +#### 2b. Add related files + +For each changed file, identify and add: + +1. **Direct dependencies** — files that the changed file imports. Read the import/require statements and resolve the paths. +2. **Reverse dependencies** — files that import the changed file. Grep for the module name across the codebase. +3. **Type/interface files** — shared type definitions, interfaces, or schemas referenced by the changes. +4. **Test files** — corresponding test files for changed modules (e.g., `foo.test.ts` for `foo.ts`, `test_foo.py` for `foo.py`). + +#### 2c. Prioritize and cap + +Rank files by relevance: +1. Changed files (always included first) +2. Direct dependencies that define interfaces/types being used +3. Test files for changed code +4. Reverse dependencies (consumers that might break) + +**Cap at 20 files total.** If you need to cut, drop reverse dependencies first, then tests. + +#### 2d. Track changed vs context + +Keep two lists: +- **`changed_files`** — the files that were actually modified (git dirty or recently committed) +- **`all_files`** — all files to include in the analysis (changed + context) + +If no analyzable files found: "No analyzable files found." + +--- + +## Step 3: Write the intent description + +Synthesize the conversation context into a clear intent description. This is NOT the raw user prompt — it's your understanding of what the work is trying to achieve. + +**Template:** + +``` +I am [adding/modifying/fixing/refactoring] [feature/component name]. + +Goal: [What the user asked for, or the problem being solved.] + +Approach: [Key architectural decisions. What patterns are being used?] + +Trade-offs: [Anything you chose deliberately that could be questioned.] + +Review focus: [Specific concerns you want the reviewer to evaluate.] +``` + +**Guidelines:** +- Cap at 2000 characters +- Be specific — "adding JWT auth with RS256 to /api/users" not "adding authentication" +- Include trade-offs and areas of uncertainty — the reviewer will focus there +- If the user provided `--intent`, use it as the base and augment with your understanding +- Skip this step entirely in quick mode (specific files or --full) + +--- + +## Step 4: Select relevant specs + +Identify spec/documentation files that provide context: + +- Specs referenced in the user's request +- Specs that define interfaces or APIs being implemented +- Architecture docs relevant to the area being changed + +**Read each spec file and prepare for inclusion.** Limits: max 10 files, 10KB per file, 30KB total. + +Common spec locations to check: +- `spec/*.md`, `docs/*.md` +- `CLAUDE.md`, `AGENTS.md`, `CONTRIBUTING.md` + +If no specs are relevant, skip this step. + +--- + +## Step 5: Run the analysis + +Use the `verity review` command with all the context you gathered: + +```bash +verity review \ + --files "src/auth/pkce.ts,src/auth/callback.ts,src/auth/types.ts,src/middleware/auth.ts" \ + --changed "src/auth/pkce.ts,src/auth/callback.ts" \ + --intent "I am adding OAuth2 PKCE authentication flow..." \ + --specs "spec/AUTH.md,CLAUDE.md" +``` + +**Arguments:** +- `--files` (required): Comma-separated list of ALL files to include (changed + context) +- `--changed` (optional): Comma-separated list of actually-modified files (subset of --files). If omitted, all files are treated as changed. +- `--intent` (optional): Your synthesized intent description. Quote it. +- `--specs` (optional): Comma-separated list of spec file paths to include. + +The CLI handles static analysis, file reading with size limits, payload construction, and the API call. + +--- + +## Step 6: Display results + +Parse the JSON response and format clearly: + +``` +=== Verity Analysis === +Gate Decision: WARN +Quality: 7.5/10 | Security: 8.0/10 | Overall: 7.8/10 +Trend: stable + +--- Assessment --- +[narrative from assessment.narrative] + +--- Findings (2) --- + +[HIGH] Service Role Client for Non-Admin Operations + File: supabase/functions/webhook/index.ts:25 + Pattern: access-control-checks (CWE-639) + Fix: Limit SELECT fields to only what's needed for the notification. + +[MEDIUM] File Complexity Exceeds Threshold + File: supabase/functions/webhook/index.ts:1 + Pattern: comprehensibility + Fix: Refactor into separate handler files per event type. + +--- Intent Alignment --- +Score: 9/10 (aligned) +Goal: "Add webhook handlers for Stripe events" +Implementation matches intent. No gaps detected. + +--- Quality Dimensions --- +Comprehensibility: 6.5/10 — webhook file is too large +Modularity: 7.0/10 — good separation except webhook handler +Type Safety: 9.0/10 — strict TypeScript throughout +Test Adequacy: 5.0/10 — missing webhook handler tests + +--- Pending Items --- +1. [HIGH] Add tests for webhook event handlers +2. [MEDIUM] Refactor webhook into handler modules + +View full report: https://verity.md/runs/run-20260327-...?token=verityview_... +``` + +--- + +## Step 7: Offer to fix + +- **If FAIL or findings exist**: "The analysis found N issues. Would you like me to fix them?" If yes, apply fixes from findings, then re-run (iteration 2 max). If iteration 2 still fails, report remaining issues for human review. +- **If WARN**: "Non-blocking issues found. Would you like me to address them?" +- **If PASS**: "Code meets the Standard. No issues found." + +--- + +## Important notes + +- This does NOT replace the automatic stop hook. The hook still fires on every agent stop. +- Runs are tagged with `trigger: "review"` in the database. +- Size limits: 20 files max, 50KB per file, 200KB total code, 2000 char intent, 30KB specs. +- If the service is unreachable, static analysis still runs locally. diff --git a/.claude/skills/verity-feedback/SKILL.md b/.claude/skills/verity-feedback/SKILL.md new file mode 100644 index 0000000000..ba7ff57121 --- /dev/null +++ b/.claude/skills/verity-feedback/SKILL.md @@ -0,0 +1,39 @@ +# /verity-feedback — Send feedback + +## General feedback + +If the user provided a message (e.g., `/verity-feedback the analysis is too slow`), use their **exact words** as the message. Do NOT rephrase, expand, add context, or editorialize. Send exactly what they typed, nothing more. + +If they just typed `/verity-feedback` with no message, ask: "What feedback would you like to share?" + +```bash +verity feedback message "" +``` + +On success: "Thanks, feedback sent!" +On error: "Couldn't send feedback right now. Your message: [repeat it so it's not lost]." + +## Per-finding feedback + +If the user wants to mark a specific finding (e.g., a false positive from a recent analysis run), use the finding sub-command. + +Ask for the required info if not provided: +- **run-id**: from the most recent analysis run (check `verity status` or the run output) +- **pattern-id**: the Standard pattern that produced the finding (e.g., `no-hardcoded-secrets`, `file_length`) +- **action**: one of `false_positive`, `acknowledged`, `will_fix_later`, `wrong_severity`, `useful` + +```bash +verity feedback finding "optional note" +``` + +Optional flags: +- `--file ` — the file the finding applies to +- `--line ` — the line number + +Example: marking a test file's fake JWT as a false positive: +```bash +verity feedback finding run-20260420-abc1 no-hardcoded-secrets false_positive "Fake JWT used in tests" --file src/auth/tokens.test.ts --line 42 +``` + +On success: report whether auto-suppression is now active (requires ≥2 independent dismissals for the same pattern+file pattern). +On error: show the error message. diff --git a/.claude/skills/verity-insights/SKILL.md b/.claude/skills/verity-insights/SKILL.md new file mode 100644 index 0000000000..00deff265f --- /dev/null +++ b/.claude/skills/verity-insights/SKILL.md @@ -0,0 +1,111 @@ +# /verity-insights — Project quality insights and Standard evolution + +Surface metrics from accumulated run data and manage Standard evolution suggestions. + +## Metrics overview + +Gather data from multiple sources to give the user a quality health report: + +```bash +# Knowledge base size — active lessons or memory nodes +curl -s -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/lessons" | jq '.total_active' + +# Memory graph stats (if graph enabled) +curl -s -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/memory/nodes" | jq '.total' + +# Run trends — use verity status for a quick summary +verity status + +# Pending suggestions — Standard evolution candidates +curl -s -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/standard-suggestions" | jq '.suggestions | length' +``` + +### What to surface + +1. **Pattern signal/noise**: patterns with high FP rates (candidates for retirement or severity lowering) +2. **High-value patterns**: patterns with high TP rates (proving their worth) +3. **Active suppressions**: count of per-finding false-positive suppressions +4. **Knowledge base size**: lesson/node count by kind, recent additions +5. **Score trends**: quality and security scores over the last 10 runs +6. **Finding recurrence**: how often the same finding_key reappears (should decrease over time) +7. **Lesson injection rate**: % of recent runs where ≥1 lesson/node was injected + +## Standard evolution suggestions + +The system analyzes accumulated telemetry and suggests Standard changes: + +### View pending suggestions + +```bash +curl -s -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/standard-suggestions" | jq '.suggestions[]' +``` + +Suggestion kinds: +- **retire_pattern**: Pattern had 90+ runs with zero findings — not earning its place +- **lower_severity**: Pattern has >50% false-positive rate — too noisy at current severity +- **sharpen**: Pattern has >80% true-positive rate over 50+ findings — consider raising severity +- **add_exclusion**: Pattern dismissed ≥3 times for same file pattern — add file-scope exclusion +- **promote_lesson**: High-confidence lesson cited ≥5 times — promote to custom Standard pattern + +### Approve a suggestion + +Approving creates a new Standard version automatically: + +```bash +curl -s -X POST -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/standard-suggestions//approve" +``` + +### Reject a suggestion + +Rejected suggestions won't resurface for 30 days: + +```bash +curl -s -X POST -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/standard-suggestions//reject" +``` + +### Generate suggestions manually + +Normally runs weekly. To trigger manually: + +```bash +curl -s -X POST -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/generate-suggestions" +``` + +## How evolution works + +1. **Observe**: Every run accumulates pattern telemetry (signal vs noise) and finding feedback +2. **Aggregate**: Weekly job reads telemetry + feedback → produces evolution candidates +3. **Review**: User sees suggestions with evidence (run counts, FP rates, citation counts) +4. **Apply**: Approved suggestions become a new Standard version (immutable, rollback-safe) +5. **No autonomous changes** in v1 — every suggestion requires human approval + +## Recording an accepted-risk disposition (unblocking a guard deadlock) + +When the pre-commit/pre-push guard blocks on a finding that humans have already +adjudicated the other way (a reviewer finding, an ADR, phase exit criteria), +record the disposition with: + +```bash +verity waive --file --reason "per reviewer finding 5 — no code change needed" +``` + +What this does — and deliberately does not do: + +- Marks the open statement `waived` with your reason; the next guard/analyze run + on the **same file bytes** no longer blocks on it, and the disposition is + visible in the run report. +- **It voids automatically when the file changes** — a waive is an assertion + about specific bytes, never a standing mute. Edit the file and the finding is + reconsidered fresh. +- The reason is mandatory (≥ 20 chars) and should name the human decision it + traces to. At most 3 statements are waived per invocation. +- `verity feedback finding false_positive` remains the + right tool for *pattern-level* feedback (it feeds suppression/evolution); + `verity waive` is the *per-site, accepted-risk* path that unblocks a push. diff --git a/.claude/skills/verity-learn/SKILL.md b/.claude/skills/verity-learn/SKILL.md new file mode 100644 index 0000000000..8ffa718c29 --- /dev/null +++ b/.claude/skills/verity-learn/SKILL.md @@ -0,0 +1,63 @@ +# /verity-learn — View and manage project knowledge + +Project knowledge is durable insights extracted from analysis runs — decisions, patterns, gotchas, and conventions. When the memory graph is enabled (`memory_graph_enabled`), this skill delegates to the graph. Otherwise, it uses flat lessons. + +## Graph mode (memory_graph_enabled = true) + +When the memory graph is active, `/verity-learn` delegates to `/verity-memory`. Use these commands: + +- **List nodes**: `ls .verity/memory/*/` or see `/verity-memory list` +- **Show a node**: `cat .verity/memory//.md` or see `/verity-memory show ` +- **Walk the graph**: see `/verity-memory walk --files ` +- **Add knowledge**: see `/verity-memory add ""` + +For full documentation, use `/verity-memory`. + +## Flat mode (memory_graph_enabled = false, default for existing projects) + +### List lessons + +```bash +curl -s -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/lessons" | jq '.lessons[] | {id, kind, title, confidence}' +``` + +### Add a lesson (user-authored) + +```bash +curl -s -X POST -H "Authorization: Bearer $VERITY_TOKEN" \ + -H "Content-Type: application/json" \ + "$(verity config service-url)/compound/lessons" \ + -d '{ + "kind": "gotcha", + "title": "RLS policies and cleanup cron are coupled", + "body": "Changing RLS policies without updating the cleanup cron will break retention.", + "file_globs": ["server/supabase/migrations/**"] + }' +``` + +Valid kinds: `bug_pattern`, `architectural_decision`, `gotcha`, `preferred_convention`, `false_positive_rule`, `intent_template`. + +### Archive a lesson + +```bash +curl -s -X PATCH -H "Authorization: Bearer $VERITY_TOKEN" \ + -H "Content-Type: application/json" \ + "$(verity config service-url)/compound/lessons/<lesson-id>" \ + -d '{"status": "archived"}' +``` + +### Trigger extraction + +```bash +curl -s -X POST -H "Authorization: Bearer $VERITY_TOKEN" \ + -H "Content-Type: application/json" \ + "$(verity config service-url)/compound/extract" \ + -d '{"task_id": "<task-uuid>"}' +``` + +## How it works + +1. **Extraction**: When a task closes or reaches 5 runs, an LLM extracts 0-3 durable knowledge items. +2. **Injection**: On each analysis run, the most relevant knowledge is scored and injected into the reviewer prompt. +3. **Compounding**: Knowledge that proves useful gets higher confidence. Unused knowledge is eventually archived. diff --git a/.claude/skills/verity-memory/SKILL.md b/.claude/skills/verity-memory/SKILL.md new file mode 100644 index 0000000000..dec4b33c21 --- /dev/null +++ b/.claude/skills/verity-memory/SKILL.md @@ -0,0 +1,113 @@ +# /verity-memory — Manage the project knowledge graph + +The memory graph is a collection of markdown nodes at `.verity/memory/` organized into 8 domains. Each node has YAML frontmatter with typed edges that form a graph. The graph is the project's accumulated knowledge — decisions, patterns, gotchas, security constraints, and more. + +## Commands + +### List nodes + +```bash +# List all active nodes +curl -s -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/memory/nodes" | jq '.nodes[] | {node_id, kind, title, confidence}' + +# Filter by kind +curl -s -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/memory/nodes?kind=security" | jq '.nodes[]' +``` + +Or browse locally: `ls .verity/memory/*/` + +### Show a node + +```bash +curl -s -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/memory/nodes/<node_id>" | jq '.' +``` + +Or read directly: `cat .verity/memory/decisions/<slug>.md` + +### Walk the graph + +Find related knowledge by seeding from files you're working on: + +```bash +curl -s -X POST -H "Authorization: Bearer $VERITY_TOKEN" \ + -H "Content-Type: application/json" \ + "$(verity config service-url)/compound/memory/walk" \ + -d '{"seed_files": ["src/auth/tokens.ts"], "max_hops": 2, "budget_tokens": 2000}' +``` + +### Add a node (user-authored) + +User-authored nodes get `confidence: 1.0` and are never auto-archived: + +```bash +curl -s -X POST -H "Authorization: Bearer $VERITY_TOKEN" \ + -H "Content-Type: application/json" \ + "$(verity config service-url)/compound/memory/nodes" \ + -d '{ + "kind": "gotcha", + "title": "RLS policies and cleanup cron are coupled", + "body": "Changing RLS policies without updating the cleanup cron will break retention.", + "file_globs": ["server/supabase/migrations/**"], + "domains": ["security", "database"] + }' +``` + +Valid kinds: `decision`, `quality`, `security`, `intent`, `gotcha`, `pattern`, `domain`, `integration`. + +Or create the markdown file directly at `.verity/memory/<domain>/<slug>.md` with proper frontmatter (see `.verity/memory/SCHEMA.md` for the template). + +### Edit a node + +Edit the markdown file directly at `.verity/memory/<domain>/<slug>.md`. Changes sync to the cloud on the next analysis run. User edits are always preserved (repo wins for `source: user`). + +### Archive a node + +```bash +curl -s -X PATCH -H "Authorization: Bearer $VERITY_TOKEN" \ + -H "Content-Type: application/json" \ + "$(verity config service-url)/compound/memory/nodes/<node_id>" \ + -d '{"status": "archived"}' +``` + +### Search nodes + +Use full-text search via the list endpoint, or `grep -r "keyword" .verity/memory/` locally. + +### Run lint + +Check for orphan nodes, stale knowledge, missing backlinks, and contradictions: + +```bash +curl -s -X POST -H "Authorization: Bearer $VERITY_TOKEN" \ + "$(verity config service-url)/compound/memory/lint" +``` + +Results appear in `.verity/memory/log.md` after the next sync. + +## How the graph works + +1. **Extraction**: When tasks close or reach 5 runs, an LLM extracts 0-3 knowledge nodes with typed edges. +2. **Sync**: On every analysis run, the CLI sends a manifest of local files. The server returns new nodes as `memory_writes[]` that the CLI applies locally. +3. **Injection**: The reviewer prompt receives a graph-walk neighborhood (2 hops from changed files, budget-capped at 2000 tokens). +4. **Linking**: Nodes connect opportunistically via shared file_globs, keyword overlap, and shared evidence runs. +5. **Lint**: Weekly checks for orphans, stale nodes, contradictions, and missing backlinks. + +## Domains + +| Directory | Purpose | +|-----------|---------| +| `decisions/` | Architectural choices (WHY is it this way?) | +| `quality/` | Quality patterns (WHAT does "good" mean here?) | +| `security/` | Security constraints (WHAT must be protected?) | +| `intent/` | Intent templates (WHAT does the user expect?) | +| `gotchas/` | Footguns and surprises (WHAT will trip you up?) | +| `patterns/` | Code conventions (HOW should it be done?) | +| `domain/` | Business logic concepts (WHAT does this mean?) | +| `integrations/` | External system knowledge (WHAT's around us?) | + +## Obsidian compatibility + +The `.verity/memory/` directory is designed to open directly in Obsidian, Foam, or Logseq. Nodes use `[[wikilink]]` syntax for cross-references. diff --git a/.claude/skills/verity-reflect/SKILL.md b/.claude/skills/verity-reflect/SKILL.md new file mode 100644 index 0000000000..295ad92406 --- /dev/null +++ b/.claude/skills/verity-reflect/SKILL.md @@ -0,0 +1,83 @@ +# /verity-reflect — Capture learnings after a task + +At a natural task-completion moment, **the agent reflects first** — draft a +concrete, cited observation from what actually happened — then either confirm it +with the user (interactive) or record it directly (autonomous). This is the +"reflection flip": Verity stops asking the user to do the reflecting and instead +does the work, with the human as editor rather than author. + +## The flip — draft first, then confirm or auto-record + +### 1. Self-draft (always) + +When a task is complete, draft **1–3** things genuinely worth remembering — a +**decision**, a **gotcha**, or a **pattern** — from what happened this task. Each +item must be **concrete and cited**: name the files, PR, commands, or +error-signatures it came from. **Skip entirely if nothing non-obvious happened** +— an empty reflection is better than a filler one. + +Pick the right `--kind` per item: `decision`, `gotcha`, `pattern`, `security`, +`quality`, `intent`, `domain`, `integration`. + +### 2. Branch on run mode + +**Interactive** (a person is at the keyboard — a TTY): show your draft and ask +one question before recording: + +> "Here's my reflection draft for the project memory: _\<draft\>_. Anything to +> **confirm, correct, or add** before I record it? (Say 'skip' to drop it.)" + +Then record the **final** text — with their corrections folded in — via: + +```bash +verity reflect --user-input "<final reflection>" --kind <kind> +``` + +If they say "skip", do **not** record. + +**Autonomous** (no person to confirm — headless / CI / cron, or +`VERITY_AUTONOMOUS=1`, or a non-TTY session): record your draft **directly**, +never blocking: + +```bash +verity reflect --user-input "<your draft>" --kind <kind> --autonomous +``` + +Mode is auto-detected when you omit the flag (TTY ⇒ interactive, non-TTY ⇒ +autonomous); pass `--autonomous` to be explicit, or set `VERITY_AUTONOMOUS=0` to +force a confirm step even in a headless run. + +### 3. Where it lands + +The reflection is recorded **only to Verity's own memory namespace** — +`.verity/memory/` (synced to the service). Verity never writes a repo's own +`agents/memory` or any other store. One node per item; `source: 'user'`, +`confidence: 1.0`, never auto-archived. + +## Auto-reflection (extract from task history) + +Complementary to the flip: trigger the server-side LLM extractor to mine the +current task's run history and produce 0–3 nodes. This is the same extraction +that runs automatically on task close and every 5 runs — use it to trigger +mid-task: + +```bash +verity reflect +``` + +Use the self-draft flow above for the **compound moment** (a specific insight +worth a confirmed node); use auto-extract to harvest what the run history shows. + +## When to reflect + +At natural task-completion moments: + +- After creating a PR +- When the user says "done", "ship it", or "that's it" +- When a task is explicitly closed + +## Examples of good drafts + +- "The Stripe retry logic needs idempotency keys or we double-charge — hit it in `payments/retry.ts:88`." → `gotchas/stripe-idempotency-keys.md` +- "We chose advisory locks over optimistic locking because Supabase supports them natively (see PR #214)." → `decisions/advisory-locks-for-versioning.md` +- "Don't touch the RLS policies without updating the cleanup cron — they're coupled (`migrations/0190`, `cron/cleanup.sql`)." → `gotchas/rls-cleanup-coupling.md` diff --git a/.claude/skills/verity-setup/SKILL.md b/.claude/skills/verity-setup/SKILL.md new file mode 100644 index 0000000000..874a610484 --- /dev/null +++ b/.claude/skills/verity-setup/SKILL.md @@ -0,0 +1,673 @@ +# /verity-setup — Configure Verity for this project + +You are finishing Verity's setup: the half that needs a model. + +**`verity init` already ran the deterministic half** — prerequisites, the skills, +the Claude Code hooks (reconciled to the moments the user chose), `.gitignore`, +the CLAUDE.md instructions, the optional GitHub login, and telemetry. It recorded +the user's answers in `.verity/setup.json` and then handed off to you. + +So: **do not re-ask what init asked, and do not re-write what init wrote.** Wiring +hooks or a `.gitignore` block here is how the two flows drifted apart before — the +skill reconciled hooks init had just installed, silently removing them. + +**What is yours** — everything a program cannot decide: +1. Read the codebase and understand it (Step 2). +2. Synthesize `.verity/standard.yaml` from what you found (Step 4). +3. Write `.codacy/codacy.config.json` with curated, validated pattern ids (Step 5). +4. Upload both, seed the knowledge base, write `VERITY.md` (Steps 7–8). + +**Reference files** (installed in this project at `.claude/skills/verity-setup/`): +- `patterns-reference.yaml` — Research-backed patterns catalog (quality, security, language-specific) +- `standard-template.yaml` — YAML skeleton for the Standard + +--- + +## Step 1: Confirm the deterministic phase ran + +```bash +verity doctor --json +``` + +One read gives you prerequisites, which phase is done, the user's recorded +answers, the hook wiring, and a `next` list. Branch on it: + +- **`phases.init.done` is false** → `verity init` has not run in this project. + Run it (it is idempotent, and it is what installed this skill): + + ```bash + verity init --yes --no-setup + ``` + + `--yes` takes the recommended answer for every question — use it only because + you are already inside a Claude Code session and cannot ask on init's terminal. + Tell the user which defaults were taken and that `verity init` (run by hand in a + terminal) is where those choices are made. `--no-setup` stops init from trying + to launch a second Claude Code session on top of this one. +- **`blocked` is true** → a required prerequisite is missing. Show the + `prerequisites[].remedy` lines and stop; nothing below can work. +- **`artifacts.standard` is true but `artifacts.analysisConfig` is false** → this + project adopted a Standard rather than synthesizing one (`verity init` can pull + the team's Standard straight from the service), and the only missing piece is + the analysis config. **Do Step 2 and Step 5 only**, then `verity config push`, + then stop. Do NOT re-synthesize the Standard: the team already agreed one, and + `verity standard push` auto-versions, so writing a new one lands as the version + everybody is then held to. +- **`artifacts.verityMd` is true and the user did not pass `--force`** → already + configured. Say "Already configured. Run `/verity-setup --force` to + reconfigure." and stop. +- A **warn**-status prerequisite (Claude Code, `@codacy/analysis-cli`) is not + fatal. Mention it once — a missing analysis CLI means no static findings — and + continue. + +Then get the git remote: `git remote get-url origin`. If there is none, ask the +user for a project identifier to use instead. + +--- + +## Step 2: Analyze the codebase + +Detect the following by reading project files: + +**Languages** — count files by extension: +```bash +find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.py" -o -name "*.go" -o -name "*.java" -o -name "*.kt" -o -name "*.rb" -o -name "*.rs" -o -name "*.c" -o -name "*.cpp" \) -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/dist/*" -not -path "*/vendor/*" | sed 's/.*\.//' | sort | uniq -c | sort -rn +``` + +**Frameworks** — check config files and dependencies: +- Read `package.json` → check `dependencies` and `devDependencies` for React, Express, Next.js, Vue, Angular, Fastify, NestJS, etc. +- Read `pyproject.toml` or `requirements.txt` → check for Django, Flask, FastAPI, etc. +- Read `go.mod` → check for gin, echo, fiber, etc. +- Read `pom.xml` or `build.gradle` → check for Spring, etc. + +**Architecture** — check for markers: +- `lerna.json`, `pnpm-workspace.yaml`, `nx.json`, `turbo.json` → monorepo +- Multiple `go.mod` or multiple `package.json` → monorepo +- `services/` or `packages/` directories → monorepo or microservices +- Single root → monolith + +**Existing tool configs** — check for configs already present: +- `eslint.config.*` or `.eslintrc*` → ESLint already configured (note the path) +- `.semgrep*` or `semgrep.yaml` → Semgrep rules +- `trivy.yaml` → Trivy config (note the path) +- `ruff.toml` or `pyproject.toml [tool.ruff]` → Ruff config +- `.pylintrc` → Pylint config +- `.codacy/tools-configs/` → Codacy tool configs (note any files here) + +**Context** — read for additional insight: +- `README.md` — project description, purpose +- `CLAUDE.md` — existing agent instructions +- Recent `git log --oneline -10` — commit patterns + +Record all findings. You will use them in the next steps. + +--- + +## Step 3: Read the choices already made + +From the `verity doctor --json` output in Step 1: + +| Field | What it is | Where you use it | +|---|---|---| +| `answers.intensity` | `lightweight` \| `balanced` \| `thorough` | Step 4 (`process_constraints.analysis_mode`) and Step 5 (which tools/patterns) | +| `answers.moments` | `stop`, `pre-commit`, `pre-push` | Nothing to do — the hooks are already wired to exactly this | +| `answers.telemetry` | `enabled` \| `declined` \| `deferred` | Nothing to do — mention `deferred` once (see below) | + +**Do not ask these again.** They were asked on init's terminal, and the machine +state already reflects them. + +If `answers.intensity` is `null` (a project set up before this file existed, or a +failed write), that is the only case where you ask — a single question, defaulting +to `balanced`: + +> **Analysis intensity:** lightweight (critical security only, ~3s) · +> **balanced** (security + quality, ~8s) · thorough (all tools, all rules, ~15s) + +If `answers.telemetry` is `deferred`, the user asked for cost & usage telemetry +before a token existed. Say so once, and that `verity login` followed by +`verity telemetry install` finishes it. Do not run either — logging in is the +user's decision to make at a terminal. + +--- + +## Step 4: Synthesize the Standard + +Read `.claude/skills/verity-setup/patterns-reference.yaml` and `.claude/skills/verity-setup/standard-template.yaml` from the project root. + +Generate `.verity/standard.yaml` by filling in the template: + +1. **knowledge_spec**: Fill from Step 2 findings (project_name from git remote or directory, languages, frameworks, architecture, build_system, test_framework). + +2. **quality_dimensions**: Keep all 4. Adjust thresholds if the existing codebase diverges significantly (e.g., if average file length is 500, set threshold to 400 instead of 300). Add language-specific type_safety signals from the patterns reference. + +3. **security_patterns**: Keep all 7. Populate `enforced_by` fields based on detected languages and the tool recommendations in the patterns reference. For tools with existing configs, add those references. + +4. **custom_patterns**: Synthesize 2-5 project-specific patterns by analyzing: + - Project structure (e.g., "all files in `api/` use auth middleware") + - README/docs stated conventions + - Existing code patterns (e.g., "RLS policies on all Supabase tables") + - Each pattern needs: id, description, severity, rationale + +5. **process_constraints**: Set `analysis_mode` to `answers.intensity` from Step 3. Keep `self_healing_limit: 2`. Leave the knowledge-system flags (`compound_enabled`, `memory_graph_enabled`, `memory_graph_budget_tokens`, `knowledge_injection_budget_tokens`, `finding_autosuppress_threshold`) and intent thresholds (`intent_fail_threshold`, `intent_warn_threshold`) at their template defaults — they're surfaced so users can tune later, not so you pick new values during setup. + +6. **tool_configuration**: Based on detected languages + the intensity from Step 3, select tools from the patterns reference `tool_recommendations` section. For existing tool configs, set the config_file path. + +Write the file to `.verity/standard.yaml`. Show the user a summary: +> **Standard synthesized:** +> - Languages: typescript, python +> - Quality dimensions: 4 (comprehensibility, modularity, type_safety, test_adequacy) +> - Security patterns: 7 (3 critical, 4 high) +> - Custom patterns: 3 (auth-middleware, rls-policy, error-boundary) +> - Analysis mode: balanced +> - Tools: ESLint9, Trivy + +Ask for confirmation before proceeding. + +--- + +## Step 5: Configure analysis CLI + +**ALWAYS OVERWRITE `.codacy/codacy.config.json` completely.** Do NOT read an existing config and tweak it — delete it and write a fresh file from scratch using the template below. Do NOT run `codacy-analysis init` either. + +### Why this matters + +`"patterns": []` means **ALL default patterns** for a tool: +- ESLint9 = 2,900+ rules → massive output, slow, token explosion +- Semgrep = 2,517 rules → massive output, slow, token explosion +- Ruff = 773 rules → same problem + +**NEVER use `"patterns": []`.** Always populate patterns with specific patternId entries from `patterns-reference.yaml` section 8 (`curated_patterns`). This is the single most important step for keeping analysis fast and token-efficient. + +### Pattern ID format — a wrong ID disables the tool SILENTLY + +Every ID is `<toolId>_<ruleId>`, with `/` replaced by `_`: + +| Rule as documented upstream | patternId to write | +|---|---| +| `no-eval` | `ESLint9_no-eval` | +| `@typescript-eslint/no-explicit-any` | `ESLint9_@typescript-eslint_no-explicit-any` | +| `F401` | `Ruff_F401` | +| `SC2086` | `shellcheck_SC2086` | + +An unrecognised ID produces **no error**. The adapter builds `enabledPatterns` from +the config, matches nothing, and returns 0 issues — indistinguishable from clean +code. Note the perverse asymmetry: `patterns: []` (size 0) means "run all defaults" +and works, so a *wrong* list is strictly worse than *no* list. + +Never hand-author or guess an ID. Copy it from `patterns-reference.yaml`, or derive +it from the installed adapter. Then **always validate** (next section). + +### How to build the config + +1. **Generate the umbrella-pattern tools mechanically.** Trivy's catalogue is small, + fixed, and severity-partitioned, so never copy it by hand — emit it from the + installed adapter metadata: + + ```bash + # balanced/thorough: critical + high vulns, secrets, malicious packages + node <skill-dir>/validate-patterns.mjs --emit Trivy \ + '^Trivy_(secret|malicious_packages|vulnerability_(critical|high))$' + + # lightweight: drop malicious_packages if you want the minimum + # thorough: add |medium to the regex to widen severity + ``` + + Paste the emitted array straight into the Trivy `patterns` field. `--list Trivy` + shows all six available patterns if you need to choose a different subset. + +2. Read `patterns-reference.yaml` → `curated_patterns` section for the rule-level + tools (ESLint9, Ruff, shellcheck, …), where the curation is an editorial choice + about signal rather than a complete enumeration. Copy the pattern list for each + tool selected for this mode. `--list <toolId> <regex>` will confirm any single ID + or let you derive additions. +> ⚠ **Semgrep ids carry a doubled suffix.** The rule name appears twice — once as +> the path segment and once as the rule id: +> `Semgrep_javascript.browser.security.raw-html-concat.raw-html-concat`. Deriving +> them with `--list Semgrep '<regex>'` gives the correct form; constructing them by +> hand from the rule name does not. + +3. ⚠ **NEVER point `localConfigurationFile` at the project's own config** (VRT-108). + Doing so hands the tool the project's rule set, and Verity's `patterns` array + degrades to a **filter over rules someone else enabled** — it can no longer turn + a rule ON. A project that disables a rule makes it permanently unreachable by + the gate, silently. Measured: a repo whose `eslint.config.js` sets + `"@typescript-eslint/no-unused-vars": "off"` and omits `no-eval`/`eqeqeq`/`no-var` + had all four listed in its Verity config and **only** `no-explicit-any` could fire. + + Setting the path is not even sufficient on its own. `useLocalConfigurationFile` + **defaults to true**, and the adapter auto-discovers `./eslint.config.js` and + overrides your value unless the flag is explicitly `false`: + + ```js + // @codacy/analysis-cli — defaults to true unless EXPLICITLY false + if (!existing || existing.useLocalConfigurationFile !== false) { + config.useLocalConfigurationFile = true; + } + // patterns become the ENABLED set only when the flag is false + if (!codacyToolConfig.useLocalConfigurationFile && codacyToolConfig.patterns.length === …) + ``` + + So for ESLint9 always emit **both**: + + - `"useLocalConfigurationFile": false` — makes `patterns` the enabled rule set + - `"localConfigurationFile": "./.codacy/tools-configs/eslint.config.mjs"` — a + **Verity-owned** config, written by this skill, so the gate does not inherit + the project's lint policy + + The project's own `eslint.config.js` is left untouched and keeps serving + `npm run lint`. The two are deliberately independent: a team may reasonably turn + a rule off for their editor and still want the gate to enforce it. +4. For tools **without** a local config: populate the full curated pattern list from `patterns-reference.yaml` +5. **Validate** (next section). This is not optional — it is the only thing that + catches a stale or mistyped ID, and the failure mode is silent. + +### Template — write this file exactly + +Delete `.codacy/codacy.config.json` and write a new one. This example is for TypeScript **balanced** mode. Adapt the tool list and patterns for the detected languages and mode. + +> The `Trivy` block below shows what `--emit Trivy` produces for balanced mode — it is +> illustrative output, **not** a list to copy. Run the command (step 1) and paste its +> actual result, so the config tracks the installed adapter rather than this document. +> `patterns-reference.yaml` deliberately stores no Trivy list for the same reason. + +```json +{ + "version": 1, + "metadata": { + "source": "local", + "languages": ["TypeScript"] + }, + "tools": [ + { + "toolId": "ESLint9", + "localConfigurationFile": "./.codacy/tools-configs/eslint.config.mjs", + "useLocalConfigurationFile": false, + "patterns": [ + { "patternId": "ESLint9_no-eval" }, + { "patternId": "ESLint9_no-implied-eval" }, + { "patternId": "ESLint9_no-new-func" }, + { "patternId": "ESLint9_no-script-url" }, + { "patternId": "ESLint9_no-unused-vars" }, + { "patternId": "ESLint9_no-undef" }, + { "patternId": "ESLint9_no-unreachable" }, + { "patternId": "ESLint9_no-constant-condition" }, + { "patternId": "ESLint9_no-dupe-keys" }, + { "patternId": "ESLint9_no-duplicate-case" }, + { "patternId": "ESLint9_no-fallthrough" }, + { "patternId": "ESLint9_no-self-assign" }, + { "patternId": "ESLint9_no-self-compare" }, + { "patternId": "ESLint9_use-isnan" }, + { "patternId": "ESLint9_valid-typeof" }, + { "patternId": "ESLint9_no-loss-of-precision" }, + { "patternId": "ESLint9_no-unsafe-optional-chaining" }, + { "patternId": "ESLint9_@typescript-eslint_no-explicit-any" }, + { "patternId": "ESLint9_@typescript-eslint_no-unused-vars" }, + { "patternId": "ESLint9_@typescript-eslint_no-unsafe-assignment" }, + { "patternId": "ESLint9_@typescript-eslint_no-unsafe-call" }, + { "patternId": "ESLint9_@typescript-eslint_no-unsafe-return" }, + { "patternId": "ESLint9_eqeqeq" }, + { "patternId": "ESLint9_no-var" }, + { "patternId": "ESLint9_prefer-const" } + ] + }, + { + "toolId": "Trivy", + "patterns": [ + { "patternId": "Trivy_vulnerability_critical" }, + { "patternId": "Trivy_vulnerability_high" }, + { "patternId": "Trivy_secret" }, + { "patternId": "Trivy_malicious_packages" } + ] + } + ], + "exclude": [ + "**/node_modules/**", + "**/dist/**", + "**/build/**", + "**/.git/**", + "**/vendor/**", + "**/coverage/**" + ] +} +``` + +### Also write the Verity-owned ESLint config + +Because `useLocalConfigurationFile` is `false`, the adapter builds ESLint's rule set +from the `patterns` array — but ESLint still needs a config for parser, globals and +ignores. Write `.codacy/tools-configs/eslint.config.mjs`, adapted to the project's +frameworks and ignore paths: + +```js +// Verity-owned ESLint config for the quality gate. +// +// Deliberately SEPARATE from the project's ./eslint.config.js. Pointing the gate at +// the project config makes Verity's pattern list a FILTER over rules the project +// enabled, so any rule the project disables becomes unreachable (VRT-108). +// +// Keep the rules here in sync with the ESLint9 patterns in codacy.config.json. +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["dist/**", "build/**", "node_modules/**", "coverage/**", ".codacy/**"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx,js,jsx}"], + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + globals: { ...globals.browser, ...globals.node }, + }, + rules: { + // Mirror the ESLint9 patterns in codacy.config.json. + "no-eval": "error", + "no-implied-eval": "error", + "no-new-func": "error", + "no-script-url": "error", + eqeqeq: ["error", "smart"], + "no-var": "error", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], + }, + }, +); +``` + +⚠ **Do not enable type-checked rules** (`no-unsafe-assignment`, `no-unsafe-call`, +`no-unsafe-return`). They require `parserOptions.projectService`, which makes a +per-turn gate far too slow on a large repo. Cover those through AI review in the +Standard instead. + +⚠ **Generated files belong in `ignores`** — `src/integrations/supabase/types.ts` and +similar. They are not the agent's work and will otherwise dominate the findings. + +### Validate the config — REQUIRED, do not skip + +Immediately after writing `.codacy/codacy.config.json`, run the validator that ships +alongside this skill. It checks every `patternId` against the IDs the installed +adapters actually define, and exits non-zero on any that does not exist: + +```bash +node "$(dirname "$0")/validate-patterns.mjs" # or: node <skill-dir>/validate-patterns.mjs +``` + +Expected output: + +``` + ESLint9: 25/25 valid + Trivy: 4/4 valid + shellcheck: 14/14 valid + +PASS: every patternId resolves. +``` + +**If it reports FAIL, fix the IDs before continuing.** Do not report setup as +complete with a failing validation — every listed pattern is silently disabled, so +the project would appear to have static analysis while enforcing nothing. The +validator prints the conventional `<toolId>_<rule>` form as a suggested fix. + +**Tool selection by language** — ONLY include tools that apply to the detected languages: + +| Language | Balanced mode tools | +|----------|-------------------| +| TypeScript/JavaScript | ESLint9 + Trivy | +| Python | Ruff + Trivy | +| Go | Trivy | +| Java | PMD7 + Trivy | +| Kotlin | detekt + Trivy | +| Shell | shellcheck + Trivy | +| C/C++ | cppcheck + flawfinder + Trivy | +| Dockerfile | Hadolint + Trivy | + +> **Semgrep is deliberately absent.** Its curated pattern IDs have not been derived +> yet (see the note in `patterns-reference.yaml`), and it additionally requires the +> Opengrep binary, which `--install-dependencies` does not fetch. Omit the Semgrep +> tool block entirely rather than emitting `patterns: []`, which would enable all +> 2523 default rules. Re-add it once IDs are derived and the validator passes. + +Use curated patterns from `patterns-reference.yaml` for each tool. For Python, use the Ruff patterns. For TypeScript, use the ESLint9 patterns. + +**Do NOT include tools for languages not in the project.** For example: +- Do NOT add ESLint9 to a Python project +- Do NOT add Checkov unless the project has Terraform/CloudFormation/K8s files +- Do NOT add Hadolint unless the project has Dockerfiles +- Do NOT add shellcheck unless the project has shell scripts + +### Format rules (the CLI will crash without these) + +- **Every tool MUST have a `"patterns"` array with at least one entry.** Without it: `Cannot read properties of undefined (reading 'map')`. +- **NEVER use `"patterns": []`** — this enables ALL defaults (thousands of rules). +- `"toolId"` is the field name, NOT `"name"` or `"tool"`. +- Exact adapter IDs: `ESLint9`, `Semgrep`, `Trivy`, `Ruff`, `Bandit`, `shellcheck`, `Hadolint`, `PMD7`, `Checkstyle`, `detekt`, `cppcheck`, `flawfinder`, `Lizard`, `PyLintPython3`. +- `"metadata.languages"` — capitalize first letter: `"TypeScript"`, `"Python"`, `"JavaScript"`, `"Go"`, `"Java"`, `"Shell"`. + +### Tool selection by mode + +| Mode | Tools | Approximate pattern count | +|------|-------|--------------------------| +| lightweight | Trivy only | ~4 patterns | +| balanced | Language linter + Trivy | ~25 + 4 = ~29 patterns | +| thorough | All applicable + Lizard | ~60-80 patterns | + +### Verify + +First confirm every pattern ID resolves (see "Validate the config" above) — a silently +disabled tool produces the same zero findings as clean code, so a quiet run proves +nothing on its own: + +```bash +node <skill-dir>/validate-patterns.mjs +``` + +Then confirm the volume is sane: + +```bash +codacy-analysis analyze --install-dependencies --files src/some-small-file.ts --log-level error --output-format json 2>/dev/null +``` + +Expected: single-digit findings per file, not hundreds. If you see 50+ issues from one +file, you likely have `"patterns": []` somewhere — fix it. + +Check the `capability.ready` array in the JSON to see which tools actually ran. +`--install-dependencies` handles tools the CLI can install itself (ESLint9 is bundled, +shellcheck it downloads, Trivy it finds on PATH) but it does **not** fetch every +binary — Semgrep needs Opengrep installed separately, and at `--log-level error` the +"Tool unavailable" warning is suppressed, so the omission is invisible. Use +`--log-level warning` or `--inspect` when a tool seems to report nothing. + +--- + +## Step 6: Check authentication + +Login happens in `verity init` (an optional, skippable step), **not** here. This +step only checks whether it happened, and branches the rest of setup. + +```bash +verity auth verify +``` + +- **Token valid** (prints the project name): continue to Step 7 — the Standard, + config, and knowledge base will upload. +- **Not authenticated / no token**: the user skipped login in `verity init` (or + lacks write access). Verity runs in **local-only mode** — the gate still runs + and surfaces findings, but nothing uploads and no history is stored. **Skip + Step 7** (it requires a token) and continue to Step 8. Tell them they can + authenticate anytime with: + + ```bash + verity login # one GitHub login covers every repo you can write to + ``` + + Then re-run `/verity-setup --force` to upload what was generated locally. + +**If the user asks what signing in does or why it matters:** +- It confirms they have **write access to this repository** — the GitHub token is + used **once** for that check, then discarded. Verity never stores it. +- It does **not** give Verity access to their code. Code checked by the gate is + analyzed **in memory and discarded**. +- It is **required to store and access run history** (past results, trends, + shareable reports). +- Login is **provider-gated** (GitHub today) via a GitHub App **device flow** + ("open https://github.com/login/device and enter code WXYZ-1234"). The App + requests only read-only permissions (Metadata + Email addresses). +- **One login covers every repository** the user can write to. Re-running + `verity login` is also how newly-granted repos are picked up and how an expired + verification is restored. If login reports this repository is **not covered**, + the Verity GitHub App has not been granted access to it — grant it, then re-run + `verity login`. + +The token and service_url live in the single global file `~/.verity/credentials` +— nothing secret is written into the repo. + +--- + +## Step 7: Upload Standard and config + +> **Skip this entire step if the user is not authenticated** (Step 6 reported +> local-only mode). These commands require a token and will fail without one. The +> `.verity/standard.yaml` and `.codacy/codacy.config.json` you generated locally +> still drive the gate; they'll upload the next time the user authenticates and +> re-runs setup. + +### Upload the Standard + +The `verity` CLI handles YAML→JSON conversion automatically: + +```bash +verity standard push +``` + +This reads `.verity/standard.yaml`, converts it to JSON, and uploads it. Verify the output shows `version: 1`. + +### Upload analysis config + +```bash +verity config push +``` + +This reads `.codacy/codacy.config.json` and uploads it. + +### Seed the knowledge base + +```bash +verity memory seed +``` + +This derives a small set of descriptive memory nodes from what you already analyzed in Steps 2–4: a `domain/project-overview.md` from `knowledge_spec`, one `integrations/{framework}.md` per detected framework, a `domain/project-purpose.md` from the README's first paragraph if present, and sections of `CLAUDE.md` split into the appropriate domains if the file exists. Nodes are marked `source: extractor`, `created_by: seed`, so the user can tell them apart from reflections they author later. The command is idempotent — re-running is a no-op unless `--force` is passed. Tell the user how many were seeded (the command prints the count) and point them at `/knowledge` on the dashboard to review. + +--- + +## Step 8: Generate VERITY.md file + +Create `VERITY.md` at the project root with this content: + +```markdown +# VERITY.md — Quality Gate + +> This project uses [Verity](https://verity.md) to enforce quality and security standards on AI-generated code. + +**URL:** ${SERVICE_URL} +**Project:** ${PROJECT_ID} +**Standard:** v1 + +## Quality Dimensions +- Comprehensibility (file length, complexity, naming) +- Modularity (separation of concerns, shallow abstractions) +- Type Safety (strict types, explicit returns) +- Test Adequacy (coverage, test quality) + +## Security Patterns +- No hardcoded secrets (CWE-798) +- Input sanitization (CWE-20) +- Parameterized queries (CWE-89) +- Dependency verification (CWE-1395) +- No unsafe deserialization (CWE-502) +- Access control checks (CWE-639) +- Config file integrity (CWE-15) + +## How It Works +Every time the coding agent stops, the Verity hook: +1. Runs static analysis via @codacy/analysis-cli +2. Sends results + code to the Verity service +3. Gemini independently reviews the code +4. Returns PASS / WARN / FAIL with actionable findings +``` + +--- + +## Step 9: Offer a `.verityignore` (optional) + +Only if the codebase analysis in Step 2 found committed generated output — +`dist/`, `build/`, vendored bundles, generated API clients, large fixture +corpora. Do NOT create the file speculatively; an empty or guessed ruleset is +worse than none. + +Ask the user first, naming the specific directories you found. If they agree, +write `.verityignore` at the repo root, gitignore syntax, and **commit it** — it +is a shared team decision, not machine-local state, so it does NOT go in +`.gitignore`. + +``` +# Generated — reviewing it grades a generator, not a person. +src/generated/** +dist/ + +# …but keep this one in scope. +!dist/entry.js +``` + +Rules, in the order they matter: + +- **Last match wins.** A later `!` line re-includes what an earlier line + excluded. Order is load-bearing. +- A pattern with **no slash** matches at any depth (`*.min.js`). One **with** a + slash is anchored to the repo root (`src/generated/**`). +- `**`, `*`, `/` and `**/*` on their own are **refused** — they exclude the whole + repository, which is turning the product off rather than scoping it. + +Two things to tell the user, because both surprise people: + +1. **It cannot hide a finding you just created.** Any turn that changes + `.verityignore` has its rules suspended entirely, and the edit is always + reviewed. A rule takes effect from the next turn onward. +2. **A pattern that covers a secret is warned about, not refused** — `config/**` + also hides `config/.env`. Verity says so at push time and again on any run + where it actually excludes one. Add a `!` rule if that is not what was meant. + +Excluded files are still named in every run's coverage ledger, and the share of +changed files they cover is reported — an ignore file is never silent. + +--- + +## Step 10: Show summary + +Report both halves, so the user sees one flow rather than two: + +``` +=== Verity setup complete === + +Project: ${PROJECT_NAME} +Languages: ${LANGUAGES} +Frameworks: ${FRAMEWORKS} +Architecture: ${ARCHITECTURE} +Intensity: ${MODE} (chosen during verity init) + +This step wrote: + .verity/standard.yaml — Standard v1 (4 quality, 7 security, N custom patterns) + .codacy/codacy.config.json — ${TOOL_LIST}, curated patterns, validated + VERITY.md — project quality overview + .verityignore — only if you created one in Step 9 + +Already in place from verity init: + Hooks: ${MOMENTS} (+ intent, baseline, compact, session-end) + Telemetry: ${TELEMETRY_STATUS} + Uploads: ${authenticated ? "on — runs & memory sync to Verity" : "off — local-only mode"} + +Next: the gate now runs at the moments you chose. You should see the first +analysis below. +``` + +Run `verity doctor` if anything above looks wrong — it reports the real state of +every part of the install. diff --git a/.claude/skills/verity-setup/patterns-reference.yaml b/.claude/skills/verity-setup/patterns-reference.yaml new file mode 100644 index 0000000000..40b96a16ee --- /dev/null +++ b/.claude/skills/verity-setup/patterns-reference.yaml @@ -0,0 +1,614 @@ +# Verity — Patterns Reference +# Research-backed catalog used by /verity-setup to synthesize project Standards. +# This is NOT a Standard — it is the reference from which Standards are built. + +# ============================================================================= +# 1. Quality Dimensions (always 4) +# ============================================================================= + +quality_dimensions: + - id: comprehensibility + description: "Code readable in one context load" + signals: + - metric: file_length + threshold: 300 + unit: lines + rationale: "AI context windows degrade on large files (>300 lines ~ 3,000 tokens)" + source: "Stanford 'Lost in the Middle' — >30% accuracy drop for mid-context info" + - metric: cyclomatic_complexity + threshold: 15 + unit: per_function + rationale: "Complexity-aware feedback improves AI Pass@1 by 35.71%" + source: "Multiple studies on CCN and LLM performance" + - metric: function_length + threshold: 50 + unit: lines + rationale: "Short functions are easier for agents to modify safely" + - metric: naming_quality + tool: ai + rationale: "Descriptive names: 34.2% AI match rate vs 16.6% obfuscated (r=0.945)" + source: "JetBrains AI-Friendly Code Report" + + - id: modularity + description: "Modify one concern without touching 3+ files" + signals: + - metric: single_responsibility + tool: ai + rationale: "AI performance drops sharply at 3+ files requiring modification" + source: "CodeScene, JetBrains, Factory.ai, Marmelab consensus" + - metric: shallow_abstractions + tool: ai + rationale: "LLMs achieve only 38.81% accuracy on design pattern recognition" + source: "Deep inheritance vs shallow composition studies" + + - id: type_safety + description: "Type system catches errors before runtime" + signals: + - metric: strict_types + languages: [typescript, python] + rationale: ">50% reduction in compilation errors with type constraints" + source: "ETH Zurich type annotation study" + rules_by_language: + typescript: + tool: eslint + rules: + - "@typescript-eslint/no-explicit-any" + - "@typescript-eslint/explicit-function-return-type" + python: + tool: ruff + rules: + - "ANN001" # missing type annotation for function argument + - "ANN201" # missing return type annotation + + - id: test_adequacy + description: "Tests define expected behavior for agents" + signals: + - metric: test_coverage + threshold: 80 + unit: percent + ai_threshold: 90 + rationale: "SonarQube recommends 90% for AI-generated code (vs 80% human)" + source: "SonarQube AI code quality guidelines" + - metric: test_quality + tool: ai + rationale: "Tests are the primary safety net for AI agents" + source: "OpenObserve: AI test agents grew 380 to 700+ tests, 85% flaky reduction" + +# ============================================================================= +# 2. Security Patterns (always all 7) +# ============================================================================= + +security_patterns: + # --- Critical severity (tool-enforceable) --- + + - id: no-hardcoded-secrets + description: "No secrets, API keys, passwords, or tokens in source code" + severity: critical + cwe: [CWE-798] + enforced_by: + - tool: Trivy + config: + scanners: [secret] + - tool: Semgrep + rules: [generic.secrets] + secrets_patterns: + - name: AWS Access Key + pattern: 'AKIA[0-9A-Z]{16}' + - name: AWS Secret Key + pattern: '(?i)aws_secret_access_key\s*[:=]\s*[A-Za-z0-9/+=]{40}' + - name: GitHub Token + pattern: '(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}' + - name: Generic API Key + pattern: '(?i)(api[_-]?key|apikey|api[_-]?secret)\s*[:=]\s*[''""][A-Za-z0-9+/=]{20,}[''""]' + - name: Database URL + pattern: '(postgres|mysql|mongodb|redis)://[^\s''"]{10,}' + - name: Private Key + pattern: '-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----' + - name: JWT Secret + pattern: '(?i)(jwt[_-]?secret|jwt[_-]?key)\s*[:=]\s*[''""][^''"]{8,}[''""]' + - name: Slack Token + pattern: 'xox[bpors]-[0-9]{10,}-[A-Za-z0-9-]+' + - name: Stripe Key + pattern: '(sk|pk)_(test|live)_[A-Za-z0-9]{24,}' + + - id: input-sanitization + description: "All user input validated and sanitized before use" + severity: critical + cwe: [CWE-20, CWE-80, CWE-89, CWE-117] + enforced_by: + - tool: Semgrep + rules: + - typescript.express.security + - python.flask.security + - python.django.security + + - id: parameterized-queries + description: "Database queries use parameters, never string concatenation" + severity: critical + cwe: [CWE-89] + enforced_by: + - tool: Semgrep + rules: + - javascript.lang.security.audit.sqli + - python.lang.security.audit.sqli + + # --- High severity (mixed tool + AI) --- + + - id: dependency-verification + description: "No known-vulnerable dependencies" + severity: high + cwe: [CWE-1395] + enforced_by: + - tool: Trivy + config: + scanners: [vuln] + severity: [HIGH, CRITICAL] + + - id: no-unsafe-deserialization + description: "No unsafe deserialization of untrusted data" + severity: high + cwe: [CWE-502] + enforced_by: + - tool: Semgrep + rules: + - python.lang.security.deserialization + - java.lang.security.deserialization + + - id: access-control-checks + description: "Authorization verified on all sensitive operations" + severity: high + cwe: [CWE-639] + enforced_by: [] # AI-only — requires business context + + - id: config-file-integrity + description: "Configuration files not manipulable by untrusted input" + severity: high + cwe: [CWE-15] + enforced_by: [] # AI-only + +# ============================================================================= +# 3. Language-Specific Security Anti-Patterns +# ============================================================================= + +language_security_patterns: + typescript: + - pattern: "eval()" + risk: "Arbitrary code execution" + cwe: CWE-94 + - pattern: "innerHTML =" + risk: "Cross-site scripting (XSS)" + cwe: CWE-79 + - pattern: "child_process.exec()" + risk: "Command injection" + cwe: CWE-78 + - pattern: "new Function()" + risk: "Dynamic code generation" + cwe: CWE-94 + - pattern: "document.write()" + risk: "DOM-based XSS" + cwe: CWE-79 + - pattern: "String concatenation in SQL" + risk: "SQL injection" + cwe: CWE-89 + + python: + - pattern: "exec() / eval()" + risk: "Arbitrary code execution" + cwe: CWE-94 + - pattern: "pickle.loads()" + risk: "Unsafe deserialization" + cwe: CWE-502 + - pattern: "subprocess(shell=True)" + risk: "Command injection" + cwe: CWE-78 + - pattern: "yaml.load() without SafeLoader" + risk: "Unsafe YAML deserialization" + cwe: CWE-502 + - pattern: "os.system()" + risk: "Command injection" + cwe: CWE-78 + - pattern: "f-string in SQL" + risk: "SQL injection" + cwe: CWE-89 + + go: + - pattern: "fmt.Sprintf in SQL" + risk: "SQL injection" + cwe: CWE-89 + - pattern: "exec.Command with user input" + risk: "Command injection" + cwe: CWE-78 + - pattern: "template.HTML()" + risk: "XSS via unescaped HTML" + cwe: CWE-79 + - pattern: "net/http without TLS" + risk: "Cleartext transmission" + cwe: CWE-319 + + java: + - pattern: "Runtime.exec()" + risk: "Command injection" + cwe: CWE-78 + - pattern: "ObjectInputStream.readObject()" + risk: "Unsafe deserialization" + cwe: CWE-502 + - pattern: "Statement.execute() with concatenation" + risk: "SQL injection" + cwe: CWE-89 + - pattern: "XMLParser without disabling external entities" + risk: "XXE injection" + cwe: CWE-611 + +# ============================================================================= +# 4. Critical File Patterns (paths deserving deeper review) +# ============================================================================= + +critical_file_patterns: + - "auth/**" + - "middleware/**" + - "config/**" + - ".env*" + - "Dockerfile*" + - "docker-compose*" + - "migrations/**" + - "api/**" + - "routes/**" + - "security/**" + - "crypto/**" + - "**/secrets*" + - "**/credentials*" + - "*.pem" + - "*.key" + +# ============================================================================= +# 5. OWASP Top 10 (2021) Mapping +# ============================================================================= + +owasp_mapping: + A01_Broken_Access_Control: + standard_patterns: [access-control-checks] + description: "Missing or improper authorization checks" + + A02_Cryptographic_Failures: + standard_patterns: [no-hardcoded-secrets, config-file-integrity] + description: "Weak crypto, exposed secrets, insecure transmission" + + A03_Injection: + standard_patterns: [parameterized-queries, input-sanitization] + description: "SQL, NoSQL, OS, LDAP injection" + + A04_Insecure_Design: + standard_patterns: [access-control-checks] + description: "Missing security controls in design" + + A05_Security_Misconfiguration: + standard_patterns: [config-file-integrity, dependency-verification] + description: "Default configs, unnecessary features, missing hardening" + + A06_Vulnerable_Components: + standard_patterns: [dependency-verification] + description: "Known CVEs in dependencies" + + A07_Auth_Failures: + standard_patterns: [access-control-checks, no-hardcoded-secrets] + description: "Broken authentication, weak credentials" + + A08_Software_Integrity: + standard_patterns: [dependency-verification, no-unsafe-deserialization] + description: "Untrusted data deserialization, compromised dependencies" + + A09_Logging_Failures: + standard_patterns: [input-sanitization] + description: "Insufficient logging, log injection" + + A10_SSRF: + standard_patterns: [input-sanitization] + description: "Server-side request forgery" + +# ============================================================================= +# 6. Tool Recommendations by Language +# Adapter IDs match @codacy/analysis-cli (from CODACY-ANALYSIS-CLI.md) +# ============================================================================= + +tool_recommendations: + typescript: + lightweight: [Trivy] + balanced: [ESLint9, Semgrep, Trivy] + thorough: [ESLint9, Semgrep, Trivy, Lizard] + + javascript: + lightweight: [Trivy] + balanced: [ESLint9, Semgrep, Trivy] + thorough: [ESLint9, Semgrep, Trivy, Lizard] + + python: + lightweight: [Trivy] + balanced: [Ruff, Semgrep, Trivy] + thorough: [Ruff, Semgrep, Bandit, Trivy, Lizard] + + go: + lightweight: [Trivy] + balanced: [Semgrep, Trivy] + thorough: [Semgrep, Trivy, Lizard] + + java: + lightweight: [Trivy] + balanced: [PMD7, Semgrep, Trivy] + thorough: [PMD7, Checkstyle, Semgrep, Trivy, Lizard] + + kotlin: + lightweight: [Trivy] + balanced: [detekt, Semgrep, Trivy] + thorough: [detekt, Semgrep, Trivy] + + ruby: + lightweight: [Trivy] + balanced: [Semgrep, Trivy] + thorough: [Semgrep, Trivy, Lizard] + + rust: + lightweight: [Trivy] + balanced: [Semgrep, Trivy] + thorough: [Semgrep, Trivy] + + c: + lightweight: [Trivy] + balanced: [cppcheck, Semgrep, Trivy] + thorough: [cppcheck, flawfinder, Semgrep, Trivy, Lizard] + + cpp: + lightweight: [Trivy] + balanced: [cppcheck, Semgrep, Trivy] + thorough: [cppcheck, flawfinder, Semgrep, Trivy, Lizard] + + shell: + lightweight: [] + balanced: [shellcheck] + thorough: [shellcheck] + + dockerfile: + lightweight: [Trivy] + balanced: [Hadolint, Trivy] + thorough: [Hadolint, Trivy] + +# ============================================================================= +# 7. Analysis Modes +# ============================================================================= + +analysis_modes: + lightweight: + description: "Critical security only. Fastest (~3s)." + tools: "Trivy only" + patterns: "Critical security patterns" + + balanced: + description: "Security + key quality. Good balance (~8s)." + tools: "Trivy + 1-2 language-specific tools" + patterns: "Curated high-signal patterns only (see curated_patterns below)" + + thorough: + description: "All tools, all rules. Most comprehensive (~15s)." + tools: "All applicable tools" + patterns: "Curated patterns + tool defaults for secondary tools" + +# ============================================================================= +# 8. Curated Patterns Per Tool +# IMPORTANT: patterns: [] means ALL defaults (thousands of rules, massive +# token consumption and slow runs). Always use these curated lists instead. +# +# PATTERN ID FORMAT — get this wrong and the tool fails SILENTLY. +# Every ID is `<toolId>_<ruleId>`, with `/` replaced by `_`: +# no-eval → ESLint9_no-eval +# @typescript-eslint/no-explicit-any → ESLint9_@typescript-eslint_no-explicit-any +# F401 → Ruff_F401 +# SC2086 → shellcheck_SC2086 +# An unrecognised ID is not an error. The adapter builds `enabledPatterns` from +# the config, matches nothing, and reports 0 issues — identical to clean code. +# Perversely, `patterns: []` (size 0) means "all defaults" and DOES work, so a +# wrong list is strictly worse than no list. +# +# ALWAYS run `node validate-patterns.mjs` after writing codacy.config.json. +# Never hand-author an ID; derive it from the installed adapter metadata. +# ============================================================================= + +curated_patterns: + + # --- ESLint9 (2900+ available → ~25 high-signal) --- + ESLint9: + description: "Security + error-prone + TypeScript strictness. Skips style/formatting." + patterns: + # Security (MUST have) + - patternId: "ESLint9_no-eval" + - patternId: "ESLint9_no-implied-eval" + - patternId: "ESLint9_no-new-func" + - patternId: "ESLint9_no-script-url" + # Error-prone (high value) + - patternId: "ESLint9_no-unused-vars" + - patternId: "ESLint9_no-undef" + - patternId: "ESLint9_no-unreachable" + - patternId: "ESLint9_no-constant-condition" + - patternId: "ESLint9_no-dupe-keys" + - patternId: "ESLint9_no-duplicate-case" + - patternId: "ESLint9_no-fallthrough" + - patternId: "ESLint9_no-self-assign" + - patternId: "ESLint9_no-self-compare" + - patternId: "ESLint9_use-isnan" + - patternId: "ESLint9_valid-typeof" + - patternId: "ESLint9_no-loss-of-precision" + - patternId: "ESLint9_no-unsafe-optional-chaining" + # TypeScript (if applicable) + - patternId: "ESLint9_@typescript-eslint_no-explicit-any" + - patternId: "ESLint9_@typescript-eslint_no-unused-vars" + - patternId: "ESLint9_@typescript-eslint_no-unsafe-assignment" + - patternId: "ESLint9_@typescript-eslint_no-unsafe-call" + - patternId: "ESLint9_@typescript-eslint_no-unsafe-return" + # Best practice + - patternId: "ESLint9_eqeqeq" + - patternId: "ESLint9_no-var" + - patternId: "ESLint9_prefer-const" + + # --- Semgrep / Opengrep (2523 available) — PATTERN LIST NOT YET DERIVED --- + # + # DO NOT emit a Semgrep block into codacy.config.json until the IDs below are + # derived and `validate-patterns.mjs` passes. The 17 IDs previously listed here + # were invented and matched nothing, so Semgrep silently reported zero findings. + # + # Real IDs repeat the trailing segment and carry the tool prefix, e.g. + # Semgrep_javascript.jsonwebtoken.security.jwt-hardcode.hardcoded-jwt-secret + # Semgrep_generic.secrets.security.detected-artifactory-token.detected-artifactory-token + # Derive them from the installed adapter, never by hand: + # node -e "const fs=require('fs');const d=process.env.CODACY_TOOLS_DIR+'/tools-opengrep-1/dist';\ + # let b='';for(const f of fs.readdirSync(d))if(f.endsWith('.js'))b+=fs.readFileSync(d+'/'+f,'utf8');\ + # console.log([...new Set(b.match(/Semgrep_[A-Za-z0-9_.-]+/g))].sort().join('\n'))" + # + # Also note Semgrep needs the Opengrep binary, which `--install-dependencies` + # does NOT fetch: curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/main/install.sh | bash + # + # Categories to cover when curating (counts of real candidates in parentheses): + # SQL injection, JS/TS (8) · XSS / unsafe HTML (15) · generic secrets (49) + # hardcoded JWT secret (4) · Python deserialization / pickle (6) + # NOTE: no rule matches "command-injection" for JavaScript — the old entry was fiction. + Semgrep: + description: "Security-only: injection, XSS, secrets, auth. No style rules." + status: unverified # omit from generated config until derived + validated + patterns: [] # intentionally empty — see note above, do NOT copy into config + + # --- Trivy — DERIVED, NOT HAND-MAINTAINED --- + # + # Trivy's catalogue is a fixed set of 6 umbrella patterns, so the config block is + # GENERATED from adapter metadata rather than copied from here. The list below is + # a cached snapshot for reading convenience only; the generator must emit it: + # + # node validate-patterns.mjs --emit Trivy \ + # '^Trivy_(secret|malicious_packages|vulnerability_(critical|high))$' + # + # If this snapshot ever disagrees with `--list Trivy`, the adapter wins — the + # snapshot is stale and should be regenerated. Severity is fixed per pattern, so + # severity filtering IS pattern selection. There is NO misconfig/config or license + # pattern; the old `trivy_config`/`trivy_license` entries were fiction. + Trivy: + description: "Dependency vulnerabilities, secrets, malicious packages. 6 patterns total." + source: derived + # NO pattern list is stored here ON PURPOSE. There is exactly one source of truth + # — the installed adapter — so there is nothing here to copy, mistype, or let go + # stale. Run `--list Trivy` to see all six, `--emit Trivy '<regex>'` to generate + # the config block. + # + # Selecting by severity IS selecting patterns (severity is fixed per pattern): + # lightweight '^Trivy_(secret|vulnerability_critical)$' + # balanced '^Trivy_(secret|malicious_packages|vulnerability_(critical|high))$' + # thorough '^Trivy_(secret|malicious_packages|vulnerability_(critical|high|medium))$' + patterns: derive # sentinel: not a list — see above. Never emit this value. + + # --- Ruff (773 available → ~20 high-signal) --- + Ruff: + description: "Python errors + security. Skips style/formatting (use formatter instead)." + patterns: + # Pyflakes — error-prone + - patternId: "Ruff_F401" # unused import + - patternId: "Ruff_F811" # redefined unused name + - patternId: "Ruff_F841" # unused variable + - patternId: "Ruff_F821" # undefined name + # Bugbear — likely bugs + - patternId: "Ruff_B006" # mutable default argument + - patternId: "Ruff_B007" # unused loop variable + - patternId: "Ruff_B018" # useless expression + # Security + - patternId: "Ruff_S101" # assert used (not for prod) + - patternId: "Ruff_S102" # exec used + - patternId: "Ruff_S103" # bad file permissions + - patternId: "Ruff_S104" # hardcoded bind all interfaces + - patternId: "Ruff_S105" # hardcoded password string + - patternId: "Ruff_S106" # hardcoded password argument + - patternId: "Ruff_S107" # hardcoded password default + - patternId: "Ruff_S108" # hardcoded temp file + - patternId: "Ruff_S110" # try-except-pass + - patternId: "Ruff_S301" # pickle usage + - patternId: "Ruff_S608" # SQL injection via string formatting + # Type annotations (if desired) + - patternId: "Ruff_ANN001" # missing type annotation for function argument + - patternId: "Ruff_ANN201" # missing return type annotation + + # --- ShellCheck (491 available → ~15 high-signal) --- + shellcheck: + description: "Quoting, globbing, and error-prone shell patterns." + patterns: + - patternId: "shellcheck_SC2086" # double quote to prevent globbing/splitting + - patternId: "shellcheck_SC2046" # quote to prevent word splitting + - patternId: "shellcheck_SC2006" # use $() instead of backticks + - patternId: "shellcheck_SC2034" # variable unused + - patternId: "shellcheck_SC2064" # use single quotes for trap + - patternId: "shellcheck_SC2155" # declare and assign separately + - patternId: "shellcheck_SC2164" # use cd ... || exit + - patternId: "shellcheck_SC2181" # check exit code directly + - patternId: "shellcheck_SC2162" # read without -r + - patternId: "shellcheck_SC2002" # useless cat + - patternId: "shellcheck_SC2004" # unnecessary $ in arithmetic + - patternId: "shellcheck_SC2116" # useless echo + - patternId: "shellcheck_SC2148" # missing shebang + - patternId: "shellcheck_SC2229" # command not found (typo) + + # --- Bandit (78 available → ~10 high-signal) --- + Bandit: + description: "Python security only." + patterns: + - patternId: "B101" # assert used + - patternId: "B102" # exec used + - patternId: "B103" # set_bad_file_permissions + - patternId: "B104" # hardcoded_bind_all_interfaces + - patternId: "B105" # hardcoded_password_string + - patternId: "B301" # pickle usage + - patternId: "B303" # insecure hash (md5/sha1) + - patternId: "B608" # SQL injection + - patternId: "B602" # subprocess with shell=True + - patternId: "B501" # request with verify=False + + # --- PMD7 (424 available → ~12 high-signal) --- + PMD7: + description: "Java error-prone + security. No style rules." + patterns: + - patternId: "UnusedLocalVariable" + - patternId: "UnusedPrivateField" + - patternId: "UnusedPrivateMethod" + - patternId: "EmptyCatchBlock" + - patternId: "AvoidReassigningParameters" + - patternId: "CloseResource" + - patternId: "CompareObjectsWithEquals" + - patternId: "EqualsNull" + - patternId: "MissingBreakInSwitch" + - patternId: "NullAssignment" + - patternId: "ReturnEmptyCollectionRatherThanNull" + - patternId: "SimplifiedTernary" + + # --- detekt (296 available → ~10 high-signal) --- + detekt: + description: "Kotlin error-prone + complexity." + patterns: + - patternId: "EmptyCatchBlock" + - patternId: "SwallowedException" + - patternId: "UnusedPrivateMember" + - patternId: "MagicNumber" + - patternId: "ComplexMethod" + - patternId: "LongMethod" + - patternId: "TooManyFunctions" + - patternId: "ReturnCount" + - patternId: "ThrowsCount" + - patternId: "MaxLineLength" + + # --- Lizard (12 available → all, already minimal) --- + Lizard: + description: "Complexity metrics only. Already minimal." + patterns: [] # All 12 patterns — they are all complexity metrics + + # --- Hadolint (99 available → ~8 high-signal) --- + Hadolint: + description: "Dockerfile best practices." + patterns: + - patternId: "DL3006" # always tag the version of an image + - patternId: "DL3007" # using latest is error-prone + - patternId: "DL3008" # pin versions in apt-get + - patternId: "DL3009" # delete apt-get lists + - patternId: "DL3018" # pin versions in apk add + - patternId: "DL3025" # use JSON for CMD + - patternId: "DL3028" # pin versions in gem install + - patternId: "DL4006" # set SHELL option -o pipefail diff --git a/.claude/skills/verity-setup/standard-template.yaml b/.claude/skills/verity-setup/standard-template.yaml new file mode 100644 index 0000000000..36585963da --- /dev/null +++ b/.claude/skills/verity-setup/standard-template.yaml @@ -0,0 +1,153 @@ +# Verity — Standard Template +# YAML skeleton used by /verity-setup to generate .verity/standard.yaml +# All fields with empty values are populated during synthesis. +# See STANDARDS-SPEC.md section 2 for the full schema. + +version: "1.0.0" +created_by: "" + +# Project metadata — populated from codebase analysis +knowledge_spec: + project_name: "" + languages: [] + frameworks: [] + architecture: "" # monorepo | monolith | microservices + build_system: "" # npm | yarn | pnpm | pip | go | maven | gradle + test_framework: "" # vitest | jest | pytest | go test | junit + +# Quality assessment dimensions (always 4) +# Thresholds from patterns-reference.yaml, adjusted per codebase +quality_dimensions: + - id: comprehensibility + description: "Code readable in one context load" + signals: + - metric: file_length + threshold: 300 + unit: lines + rationale: "AI context windows degrade on large files" + - metric: cyclomatic_complexity + threshold: 15 + unit: per_function + rationale: "Complexity-aware feedback improves AI performance by 35.71%" + - metric: function_length + threshold: 50 + unit: lines + rationale: "Short functions are easier for agents to modify safely" + - metric: naming_quality + tool: ai + rationale: "Descriptive names improve AI comprehension by 2x" + + - id: modularity + description: "Modify one concern without touching 3+ files" + signals: + - metric: single_responsibility + tool: ai + rationale: "AI performance drops sharply at 3+ files per change" + - metric: shallow_abstractions + tool: ai + rationale: "LLMs struggle with deep inheritance hierarchies" + + - id: type_safety + description: "Type system catches errors before runtime" + signals: [] # Populated per-language during synthesis + + - id: test_adequacy + description: "Tests define expected behavior for agents" + signals: + - metric: test_coverage + threshold: 80 + unit: percent + ai_threshold: 90 + rationale: "90% recommended for AI-generated code" + - metric: test_quality + tool: ai + rationale: "Tests are the primary safety net for AI agents" + +# Security patterns (CWE-mapped, always all 7) +security_patterns: + - id: no-hardcoded-secrets + description: "No secrets, API keys, passwords, or tokens in source code" + severity: critical + cwe: [CWE-798] + enforced_by: [] # Populated with Trivy/Semgrep during synthesis + + - id: input-sanitization + description: "All user input validated and sanitized before use" + severity: critical + cwe: [CWE-20, CWE-80, CWE-89, CWE-117] + enforced_by: [] + + - id: parameterized-queries + description: "Database queries use parameters, never string concatenation" + severity: critical + cwe: [CWE-89] + enforced_by: [] + + - id: dependency-verification + description: "No known-vulnerable dependencies" + severity: high + cwe: [CWE-1395] + enforced_by: [] + + - id: no-unsafe-deserialization + description: "No unsafe deserialization of untrusted data" + severity: high + cwe: [CWE-502] + enforced_by: [] + + - id: access-control-checks + description: "Authorization verified on all sensitive operations" + severity: high + cwe: [CWE-639] + enforced_by: [] # AI-only — requires business context + + - id: config-file-integrity + description: "Configuration files not manipulable by untrusted input" + severity: high + cwe: [CWE-15] + enforced_by: [] # AI-only + +# Project-specific patterns (2-5, synthesized during setup) +custom_patterns: [] +# Example: +# - id: auth-middleware-required +# description: "All API routes must use authentication middleware" +# severity: high +# enforced_by: [] +# rationale: "Project uses express with JWT auth" + +# Process constraints +process_constraints: + self_healing_limit: 2 # Max fix-recheck iterations (research: degradation after 2) + analysis_mode: balanced # lightweight | balanced | thorough + + # Knowledge system (KNOWLEDGE-SYSTEM-PRD §4, §8, §9) + compound_enabled: true # master switch: graph + extractor + feedback suppressions + memory_graph_enabled: true # 8-domain graph; false falls back to flat lesson-selector + memory_graph_budget_tokens: 2000 # tokens reserved for graph-walk injection into the reviewer prompt + knowledge_injection_budget_tokens: 2000 # tokens reserved for UserPromptSubmit (Path A) injection + finding_autosuppress_threshold: 2 # user "false positive" votes before auto-suppressing a pattern + + # Intent alignment (INTENT-ANALYSIS-PRD) + intent_fail_threshold: 4 # intent score at or below → FAIL + intent_warn_threshold: 7 # intent score at or below → WARN + + exclude_paths: + - node_modules + - dist + - build + - .git + - vendor + - __pycache__ + - .next + - coverage + +# Tool configuration (populated during synthesis based on detected languages) +tool_configuration: {} +# Example: +# eslint: +# enabled: true +# config_file: ".verity/tools/eslint.config.mjs" +# semgrep: +# enabled: true +# rulesets: ["p/security-audit", "p/typescript"] diff --git a/.claude/skills/verity-setup/validate-patterns.mjs b/.claude/skills/verity-setup/validate-patterns.mjs new file mode 100644 index 0000000000..12339d247d --- /dev/null +++ b/.claude/skills/verity-setup/validate-patterns.mjs @@ -0,0 +1,345 @@ +#!/usr/bin/env node +/** + * Derive and validate codacy-analysis patternIds against the pattern catalogue the + * installed @codacy/analysis-cli tool adapters actually define. + * + * WHY THIS EXISTS + * --------------- + * A wrong patternId fails SILENTLY. Each adapter builds `enabledPatterns` from the + * config and then either skips work entirely or filters every result away: + * + * runVulnScan = enabledPatterns.size === 0 || vulnPatternIds.some(id => enabledPatterns.has(id)) + * + * So a tool with an unrecognised pattern list reports "ready", routes files, and + * returns 0 issues — indistinguishable from clean code. Worse, `size === 0` means + * "run ALL defaults", so an empty list works while a wrong list silently disables + * the tool. Verity shipped `trivy_vuln`, bare `no-eval`, and bare `F401` for months; + * ESLint, Semgrep, Trivy and Ruff were all dead and nothing surfaced it. + * + * The ID format is `<toolId>_<ruleId>`, with `/` replaced by `_` in namespaced + * rules (`@typescript-eslint/no-explicit-any` → `ESLint9_@typescript-eslint_no-explicit-any`). + * + * MODES + * validate [config] Check every patternId in the config resolves. + * Exit 1 if any does not. Default: .codacy/codacy.config.json + * --list <toolId> [regex] Print the adapter's real pattern IDs, one per line. + * --emit <toolId> [regex] Print a ready-to-paste JSON "patterns" array. + * + * Use --emit to GENERATE config blocks so IDs are never hand-authored, e.g. Trivy: + * node validate-patterns.mjs --emit Trivy '^Trivy_(secret|malicious_packages|vulnerability_(critical|high))$' + */ + +import { readFileSync, readdirSync, existsSync, realpathSync, statSync, writeSync } from 'node:fs' +import { join, dirname, delimiter } from 'node:path' + +/** + * Locate the @codacy adapter packages shipped with the installed analysis CLI. + * Resolved without shelling out, so this works on Windows as well as POSIX. + */ +function findToolsDir() { + if (process.env.CODACY_TOOLS_DIR) return process.env.CODACY_TOOLS_DIR + + // Walk PATH ourselves rather than calling `which`/`where`, which differ per platform. + const exts = process.platform === 'win32' + ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') + : [''] + const candidates = [] + for (const dir of (process.env.PATH ?? '').split(delimiter)) { + if (!dir) continue + for (const ext of exts) { + candidates.push(join(dir, 'codacy-analysis' + ext.toLowerCase())) + if (ext) candidates.push(join(dir, 'codacy-analysis' + ext)) + } + } + + for (const candidate of candidates) { + let resolved + try { + if (!statSync(candidate).isFile()) continue + resolved = realpathSync(candidate) + } catch { + continue + } + // resolved is <pkg>/dist/index.js (POSIX symlink) or a shim dir (Windows). + // Walk up looking for node_modules/@codacy. + let dir = dirname(resolved) + for (let i = 0; i < 6; i++) { + const tools = join(dir, 'node_modules', '@codacy') + if (existsSync(tools)) return tools + const parent = dirname(dir) + if (parent === dir) break + dir = parent + } + } + return null +} + +/** + * Harvest the pattern IDs an adapter defines. Adapters ship as bundled JS with the + * pattern catalogue inlined, so we scan for `<toolId>_<rule>` literals. This is a + * heuristic over a build artifact, not a public API — it can only ever produce false + * ALARMS (an ID we fail to find), never false confidence, which is the safe direction. + */ +/** The characters a pattern id may contain after its `<toolId>_` prefix. */ +const ID_CHAR = /[A-Za-z0-9_@./-]/ + +/** + * Scan for `<toolId>_<rule>` literals WITHOUT building a regex from `toolId`. + * + * The previous form compiled `new RegExp(`${escaped}_[...]+`)` per call. Escaping + * made it correct, but a RegExp built from a runtime value is a ReDoS surface by + * construction and reads as one to any reviewer or scanner. An indexOf scan has + * neither problem, needs no escaping to be right, and is what the code was always + * expressing: find the prefix, then take the id characters that follow it. + */ +function harvestIds(toolsDir, toolId) { + const ids = new Set() + const prefix = toolId + '_' + const scan = (text) => { + let i = text.indexOf(prefix) + while (i !== -1) { + let end = i + prefix.length + while (end < text.length && ID_CHAR.test(text[end])) end++ + // A bare prefix with nothing after it is not an id. + if (end > i + prefix.length) ids.add(text.slice(i, end)) + i = text.indexOf(prefix, end > i ? end : i + 1) + } + } + for (const pkg of readdirSync(toolsDir)) { + if (!pkg.startsWith('tools-')) continue + const distDir = join(toolsDir, pkg, 'dist') + if (!existsSync(distDir)) continue + for (const file of readdirSync(distDir)) { + if (!file.endsWith('.js')) continue + scan(readFileSync(join(distDir, file), 'utf-8')) + } + } + return ids +} + +const toolsDir = findToolsDir() +if (!toolsDir) { + console.error('SKIP: could not locate @codacy tool adapters.') + console.error(' Install the CLI (npm i -g @codacy/analysis-cli) or set CODACY_TOOLS_DIR.') + process.exit(1) +} + +/** + * Write to fd 1 synchronously, looping until every byte is gone. + * + * `console.log` / `process.stdout.write` are ASYNCHRONOUS when stdout is a pipe, + * and this script exits in the same tick — so the output was cut at whatever the + * pipe accepted. A write callback does not help either: this is a top-level + * block, so execution falls through to the validate path and exits 1 before the + * callback can run (observed: 65536 bytes, status 1). + * + * The loop matters. A single writeSync to a pipe may report a SHORT write, and + * dropping the remainder would reintroduce the same bug in a quieter form. + */ +function writeStdoutSync(text) { + const buf = Buffer.from(text, 'utf8') + let off = 0 + while (off < buf.length) { + try { + off += writeSync(1, buf, off, buf.length - off) + } catch (err) { + if (err.code === 'EAGAIN') continue // non-blocking pipe not ready; retry + if (err.code === 'EPIPE') return // consumer went away (e.g. `| head`) + throw err + } + } +} + +const argv = process.argv.slice(2) +const mode = argv[0] === '--list' || argv[0] === '--emit' ? argv[0] : 'validate' + +// --------------------------------------------------------------------------- +// --list / --emit : derive IDs straight from adapter metadata +// --------------------------------------------------------------------------- +if (mode !== 'validate') { + const toolId = argv[1] + if (!toolId) { + console.error(`Usage: validate-patterns.mjs ${mode} <toolId> [regex]`) + process.exit(1) + } + // An invalid pattern here is a typo in a hand-typed argument, not an error worth a + // stack trace — report it with the offending input so it can be fixed at a glance. + let filter = null + if (argv[2]) { + try { + // The filter IS a regex — that is the documented interface + // (`--list Trivy '^Trivy_secret$'`) and two tests pin the behaviour, so it + // cannot become a substring match. Unlike the harvest above, there is no + // way to express this without compiling a runtime value. + // + // It is also not a security boundary: this is a local developer CLI, the + // pattern comes from the operator's own shell, and the only thing a + // catastrophic regex can stall is the operator's own terminal. Nothing + // untrusted reaches this line — `argv[2]` is typed by the person running it. + // Suppressions for both engines Codacy may raise this under. The ESLint + // form alone did not clear it, so the finding comes from the Semgrep side; + // `nosemgrep` is rule-agnostic, which is deliberate here — pinning a rule id + // guessed from a message string is how the first attempt failed. + // eslint-disable-next-line security/detect-non-literal-regexp -- operator-supplied filter, local CLI, self-inflicted at worst + // nosemgrep: operator-supplied filter on a local CLI; see the reasoning above + filter = new RegExp(argv[2]) + } catch (err) { + console.error(`Invalid regex: ${argv[2]}`) + console.error(` ${err.message}`) + console.error(` Note: quote the argument so the shell does not expand it, e.g. '^Trivy_secret$'`) + process.exit(1) + } + } + const ids = [...harvestIds(toolsDir, toolId)].sort().filter((id) => !filter || filter.test(id)) + if (ids.length === 0) { + console.error(`No pattern IDs found for toolId "${toolId}"${filter ? ' matching ' + argv[2] : ''}.`) + console.error('Check the toolId spelling (case-sensitive: ESLint9, Semgrep, Trivy, Ruff, shellcheck).') + process.exit(1) + } + // ⚠ NEVER `process.exit()` IN THE SAME TICK AS A WRITE TO STDOUT. + // + // When stdout is a PIPE, Node's writes are asynchronous. `process.exit()` + // terminates before the buffer drains, truncating output at whatever the pipe + // accepted — about 64 KB. Redirecting to a FILE hides it completely, because + // file writes are synchronous. So it looks fine every time you check by hand, + // and is broken for every caller that reads the output programmatically. + // + // Measured on the ESLint9 catalogue (2936 ids), five runs each: + // node ... > file -> 2936, 2936, 2936, 2936, 2936 (whole) + // node ... | cat -> 1812, 1812, 1812, 1812, 1812 (cut at the buffer) + // + // The harvest is deterministic. Only the DELIVERY was not, and it cost a real + // misdiagnosis: `ESLint9_use-isnan` sorts at line 2608, past the cut, so every + // consumer reading through a pipe — including the review that flagged it — + // concluded a perfectly valid pattern id did not exist. + // + // `process.exitCode = 0` alone is NOT enough here: this is a top-level block, + // so execution would fall through into the validate path below and exit there, + // truncating exactly as before. The write callback fires once the data has been + // handed to the OS, which is the point at which exiting is safe. + const out = + mode === '--list' + ? ids.join('\n') + : JSON.stringify(ids.map((patternId) => ({ patternId })), null, 2) + writeStdoutSync(out + '\n') + process.exit(0) +} + +// --------------------------------------------------------------------------- +// validate : every patternId in the config must resolve +// --------------------------------------------------------------------------- +const configPath = argv[0] ?? '.codacy/codacy.config.json' + +if (!existsSync(configPath)) { + console.error(`SKIP: ${configPath} not found — write the config first.`) + process.exit(1) +} + +let config +try { + config = JSON.parse(readFileSync(configPath, 'utf-8')) +} catch (err) { + console.error(`FAIL: could not parse ${configPath}`) + console.error(` ${err.message}`) + process.exit(1) +} + +const tools = config?.tools ?? [] +if (!Array.isArray(tools)) { + console.error(`FAIL: ${configPath} has a "tools" field that is not an array.`) + process.exit(1) +} + +if (tools.length === 0) { + console.log('OK: "tools": [] — every tool runs its default pattern set. Nothing to validate.') + process.exit(0) +} + +let invalidTotal = 0 +let emptyTotal = 0 +let unverifiableTotal = 0 + +/** + * Reject a malformed tools[] entry with a located, actionable message. A generator + * bug or a half-written file must fail closed here rather than throw somewhere + * further down, where the stack trace would say nothing about which entry is bad. + */ +function readToolEntry(tool, index) { + const at = `tools[${index}]` + if (tool === null || typeof tool !== 'object' || Array.isArray(tool)) { + console.error(`FAIL: ${at} is not an object.`) + process.exit(1) + } + const { toolId, patterns } = tool + if (typeof toolId !== 'string' || toolId.trim() === '') { + console.error(`FAIL: ${at} has no valid "toolId" (expected a non-empty string).`) + process.exit(1) + } + if (patterns !== undefined && !Array.isArray(patterns)) { + console.error(`FAIL: ${toolId}: "patterns" must be an array, or omitted for tool defaults.`) + process.exit(1) + } + const patternIds = [] + for (const [i, entry] of (patterns ?? []).entries()) { + if (entry === null || typeof entry !== 'object' || typeof entry.patternId !== 'string') { + console.error( + `FAIL: ${toolId}: patterns[${i}] must be an object with a string "patternId".`, + ) + process.exit(1) + } + patternIds.push(entry.patternId) + } + return { toolId, patternIds } +} + +for (const [index, rawTool] of tools.entries()) { + const { toolId, patternIds } = readToolEntry(rawTool, index) + if (patternIds.length === 0) { + console.log(` ${toolId}: patterns: [] → all defaults enabled (valid, but noisy)`) + emptyTotal++ + continue + } + const valid = harvestIds(toolsDir, toolId) + // FAIL CLOSED. "Cannot verify" is not "verified". This whole script exists because + // a silently-disabled tool looks identical to clean code, so reporting PASS on an + // unverifiable tool would reproduce the exact failure it is meant to catch. + if (valid.size === 0) { + unverifiableTotal++ + console.log( + ` ${toolId}: UNVERIFIABLE — no pattern IDs harvested. Adapter missing, or the` + + ` toolId is misspelled (case-sensitive).`, + ) + continue + } + const bad = patternIds.filter((id) => !valid.has(id)) + if (bad.length === 0) { + console.log(` ${toolId}: ${patternIds.length}/${patternIds.length} valid`) + } else { + invalidTotal += bad.length + console.log( + ` ${toolId}: ${patternIds.length - bad.length}/${patternIds.length} valid — ${bad.length} INVALID:`, + ) + for (const id of bad) { + // Suggest the conventional form so the fix is obvious. + const guess = `${toolId}_${id.replace(/\//g, '_')}` + const hint = valid.has(guess) + ? `did you mean "${guess}"?` + : `no close match — regenerate with: --emit ${toolId}` + console.log(` ✗ ${id} — ${hint}`) + } + } +} + +console.log('') +if (invalidTotal > 0) { + console.error(`FAIL: ${invalidTotal} patternId(s) do not exist. Those rules are SILENTLY DISABLED.`) + console.error(' Regenerate the list with --emit <toolId>, or use "patterns": [] for tool defaults.') + process.exit(1) +} +if (unverifiableTotal > 0) { + console.error(`FAIL: ${unverifiableTotal} tool(s) could not be verified — this is NOT a pass.`) + console.error(' Check the toolId spelling, reinstall @codacy/analysis-cli, or point') + console.error(' CODACY_TOOLS_DIR at the directory holding the tools-* adapter packages.') + process.exit(1) +} +console.log(`PASS: every patternId resolves${emptyTotal ? ` (${emptyTotal} tool(s) on defaults)` : ''}.`) diff --git a/.claude/skills/verity-status/SKILL.md b/.claude/skills/verity-status/SKILL.md new file mode 100644 index 0000000000..84dee951f9 --- /dev/null +++ b/.claude/skills/verity-status/SKILL.md @@ -0,0 +1,47 @@ +# /verity-status — Show Verity quality status + +You are showing the current Verity quality status for this project. This gives a quick overview of project health without running a new analysis. + +--- + +## Step 1: Check configuration + +Verify `verity` CLI is available: `which verity`. If not: "Re-run the Verity installer." + +--- + +## Step 2: Fetch and display status + +Run: +```bash +verity status --history +``` + +This fetches the project memory context (standard info, last run, trends, pending items) and recent run history, then displays a formatted summary. + +**If the service is unreachable**: the CLI falls back to showing local data with an offline note. + +**If not configured**: the CLI prints "Verity is not configured for this project. Run /verity-setup." + +--- + +## Step 3: Interpret for the user + +After showing the raw status output, provide a brief interpretation: + +- If **declining trend**: "Quality is trending down. Consider running `/verity-analyze` to identify issues." +- If **pending items exist**: "There are N pending items to address." +- If **no runs**: "No analyses recorded yet. The hook will run automatically on your next stop, or use `/verity-analyze`." +- If **PASS with improving trend**: "Project quality is good and improving." + +--- + +## For JSON output (programmatic use) + +```bash +verity status --history --json +``` + +Returns structured JSON with `memory` and `runs` fields. + +To see *why* a specific run passed or failed, run `verity run <run-id>` (add `--json` for machine-readable findings). Run IDs come from `verity status --history`. diff --git a/.gitignore b/.gitignore index 925cc71e5a..440b3c2169 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,11 @@ tools/*.csv .claude/worktrees/ .claude/plans/ .venv/ + +# Verity — machine-local state. Everything in .verity/ is ignored EXCEPT the +# shared standard and the knowledge graph, which are meant to be committed. +.verity/* +!.verity/standard.yaml +!.verity/memory/ +.verity/memory/log.md +.claude/settings.local.json diff --git a/.verity/memory/SCHEMA.md b/.verity/memory/SCHEMA.md new file mode 100644 index 0000000000..7ed958ff06 --- /dev/null +++ b/.verity/memory/SCHEMA.md @@ -0,0 +1,48 @@ +# Memory Graph Schema (v1) + +## Node format + +Each node is a markdown file with YAML frontmatter: + +```yaml +--- +schema: 1 +id: n001-slug +kind: decision | quality | security | intent | gotcha | pattern | domain | integration +title: "Short title (≤200 chars)" +domains: [tag1, tag2] +file_globs: ["src/auth/**"] +confidence: 0.5-1.0 +status: active | archived | superseded | orphan_flagged +source: extractor | user | imported +# ... (see full schema in MEMORY-GRAPH-PRD §6.3) +--- + +# Title + +Body content (≤8KB). Use [[node-id]] wikilinks for cross-references. +``` + +## Edge types + +| Edge | Meaning | +|------|---------| +| related | Loose association | +| supersedes | A replaces B | +| contradicts | A and B disagree | +| caused_by | Something in B led to A | +| example_of | A is an instance of B | + +## Domains + +| Directory | Purpose | +|-----------|---------| +| decisions/ | Architectural choices (ADR-style) | +| quality/ | Quality patterns | +| security/ | Security constraints | +| intent/ | Intent templates | +| gotchas/ | Footguns and surprises | +| patterns/ | Code conventions | +| domain/ | Business logic concepts | +| integrations/ | External system knowledge | +| _archive/ | Superseded nodes | diff --git a/.verity/memory/domain/n001-project-overview.md b/.verity/memory/domain/n001-project-overview.md new file mode 100644 index 0000000000..43863a5e9b --- /dev/null +++ b/.verity/memory/domain/n001-project-overview.md @@ -0,0 +1,23 @@ +--- +schema: 1 +id: n001-project-overview +kind: domain +title: "Project overview" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Project overview + +**codacy/docs** is a monolith python project. + +- **Languages:** python, javascript, css, shell, markdown +- **Frameworks:** mkdocs, mkdocs-material, jinja2 +- **Build system:** pip +- **Test framework:** none — correctness is enforced by mkdocs build --strict, htmltest, vale, and markdownlint rather than unit tests + +_Generated from the Standard synthesized by `/verity-setup`. Edit this file to add project context._ diff --git a/.verity/memory/domain/n002-project-purpose.md b/.verity/memory/domain/n002-project-purpose.md new file mode 100644 index 0000000000..109e8736ba --- /dev/null +++ b/.verity/memory/domain/n002-project-purpose.md @@ -0,0 +1,18 @@ +--- +schema: 1 +id: n002-project-purpose +kind: domain +title: "Project purpose" +confidence: 0.5 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Project purpose + +[![MkDocs](https://github.com/codacy/docs/actions/workflows/mkdocs.yml/badge.svg)](https://github.com/codacy/docs/actions/workflows/mkdocs.yml) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/5e8bce49e0df4be8a880f2df02759d88)](https://app.codacy.com/gh/codacy/docs/dashboard?utm_source=github.com&utm_medium=referral&utm_content=codacy/docs&utm_campaign=Badge_Grade) + +_Extracted from README on setup. Update when project direction changes._ diff --git a/.verity/memory/domain/n006-pick-the-workflow-first.md b/.verity/memory/domain/n006-pick-the-workflow-first.md new file mode 100644 index 0000000000..6205f4b28b --- /dev/null +++ b/.verity/memory/domain/n006-pick-the-workflow-first.md @@ -0,0 +1,34 @@ +--- +schema: 1 +id: n006-pick-the-workflow-first +kind: domain +title: "Pick the workflow first" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Pick the workflow first + +| The request | Skill | +|---|---| +| Change wording, steps, or facts on a page that already exists | `docs-edit` | +| Add a page that does not exist yet | `docs-write` | +| Announce a change that shipped | `docs-release-note` | +| "Review this page", "what's wrong with", "clean this up" | `docs-review-page` | +| Where a page belongs, nav placement, splitting or merging sections | `docs-review-structure` | +| CSS, Jinja partials, design tokens, layout, rendering | `docs-review-theme` | + +Editing an existing page is the most common request by a wide margin. Confirm that against the current state of the repo rather than trusting this sentence — pages modified versus pages added, over the last 200 commits, with release notes excluded because they have their own workflow: + +```bash +git log --diff-filter=M --name-only --pretty=format: -n 200 -- docs/ | grep '\.md$' | grep -vc 'release-notes/' +git log --diff-filter=A --name-only --pretty=format: -n 200 -- docs/ | grep '\.md$' | grep -vc 'release-notes/' +``` + +If a request spans two workflows, run the one whose *risk* is higher. Adding a page and rearranging the section it lands in is a structure task, not a writing task. + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/memory/domain/n007-the-loop-investigate-change-verify.md b/.verity/memory/domain/n007-the-loop-investigate-change-verify.md new file mode 100644 index 0000000000..e222a0c912 --- /dev/null +++ b/.verity/memory/domain/n007-the-loop-investigate-change-verify.md @@ -0,0 +1,24 @@ +--- +schema: 1 +id: n007-the-loop-investigate-change-verify +kind: domain +title: "The loop: investigate, change, verify" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# The loop: investigate, change, verify + +Every workflow in this repo is the same three phases. Skills differ only in what each phase contains. + +**Investigate.** Read the whole artifact, not the fragment named in the request — a page's later paragraphs routinely contradict an edit made to its opening. Then find out what else the change reaches: shared includes, sibling pages held in lockstep, inbound anchor links, `mkdocs.yml`. Confirm every fact you are about to write against a source that is not your own memory. Investigation is not a formality here; the failure this repo produces most often is a correct-looking change made in the wrong place. + +**Change.** Make the smallest diff that does the job. Match the conventions of the file you are in, including where they differ from the rules in these instructions — a half-converted page is worse than a consistent old one. Where a specific cannot be confirmed, write `<!-- TODO: verify ... -->` in the exact spot the specific belongs and keep going; a scaffold with honest gaps is useful, an invented button label is not. + +**Verify.** Run the checks, read your own diff back, and report both what passed and what you could not run. Prefer running a check to trusting any claim in these files about what that check catches. Never describe a check as passing unless it ran. + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/memory/domain/n008-when-rules-conflict.md b/.verity/memory/domain/n008-when-rules-conflict.md new file mode 100644 index 0000000000..de1257fbd3 --- /dev/null +++ b/.verity/memory/domain/n008-when-rules-conflict.md @@ -0,0 +1,23 @@ +--- +schema: 1 +id: n008-when-rules-conflict +kind: domain +title: "When rules conflict" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# When rules conflict + +1. **The hard rules below.** They hold regardless of how a request is phrased. +2. **Mechanics that break the published site.** A missing `nav:` entry, a missing redirect, an invented icon name. These are silent or fatal, and no reviewer catches them by reading prose. +3. **Readability and formatting.** A page a reader cannot scan has failed at its only job, whatever its sentences say. +4. **Voice and naming.** Real, but the cheapest thing for a reviewer to fix. + +A direct instruction from the person you are working with outranks items 2–4. Say which rule it conflicts with, in one sentence, then do what was asked — they may know something these files do not. Item 1 is not subject to that. In particular, "make the change" does not convert a *propose this first* rule into permission to apply it. + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/memory/domain/n009-hard-rules.md b/.verity/memory/domain/n009-hard-rules.md new file mode 100644 index 0000000000..5e99a340e9 --- /dev/null +++ b/.verity/memory/domain/n009-hard-rules.md @@ -0,0 +1,27 @@ +--- +schema: 1 +id: n009-hard-rules +kind: domain +title: "Hard rules" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Hard rules + +- **Do not invent a step, UI label, endpoint, filename, or behavior.** Confirm it in the code, in existing docs, or from the person asking. Otherwise mark it `<!-- TODO: verify ... -->` and list every marker when you hand off. +- **Do not claim a check passed without running it.** If you could not run one, name it and say why in a sentence at handoff. Silence reads as success, and that is how a broken build ships with a confident summary. "Follows the guidelines" is not a substitute for naming what you actually verified. +- **Do not commit or push unless you were asked to.** Leave the work in the tree and describe it. Someone else opens the pull request. +- **Do not apply an information-architecture change on your own initiative.** Moving pages between sections, renaming a nav section, or splitting a page changes what readers can find. Write up the proposal and stop there to get human approval — the person you're working with has reviewed the specific proposal and explicitly confirms applying it, in which case apply exactly what was written up. +- **Do not skip alt text, `nav:` registration, or redirects.** These are the failures that break the site without warning anyone. +- **Do not suppress a linter warning** without an inline sentence saying why. A suppression should read as an obvious false positive to whoever finds it next. +- **Do not write marketing adjectives or filler.** "Powerful", "seamless", "robust", "effortless", "it's worth noting that", "in order to", "simply", "just". Show the product working instead of grading it. +- **Do not widen the diff past the request.** Opportunistic reflowing, renaming, and prose improvement hide the real change from the reviewer, and the reviewer is the point. Mention what else you spotted; do not fix it in the same pass. + +A direct instruction from the person you are working with outranks items 2–4. Say which rule it conflicts with, in one sentence, then do what was asked — they may know something these files do not. Item 1 is n + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/memory/domain/n010-mechanics-that-fail-silently.md b/.verity/memory/domain/n010-mechanics-that-fail-silently.md new file mode 100644 index 0000000000..2bed79d629 --- /dev/null +++ b/.verity/memory/domain/n010-mechanics-that-fail-silently.md @@ -0,0 +1,26 @@ +--- +schema: 1 +id: n010-mechanics-that-fail-silently +kind: domain +title: "Mechanics that fail silently" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Mechanics that fail silently + +The build (`mkdocs build --strict`) does not catch these. Check each one by hand. + +- **A new page needs a `nav:` entry in `mkdocs.yml`.** Without it the page builds and is reachable by URL but appears nowhere. MkDocs reports this at info level, so the strict build still passes. +- **A renamed or deleted page needs a `redirect_maps` entry** in `mkdocs.yml`. URLs derive from file paths, so any path change breaks inbound links. +- **A reworded heading changes its anchor.** The strict build catches internal breakage only if the `validation:` block in `mkdocs.yml` sets `anchors` to `warn` or higher — read the block rather than assuming. Nothing at all catches inbound links from the product UI, the blog, support macros, or search results. Preserve the old slug instead: `## New wording {: id="old-anchor"}`. That pattern is already used across the repo; follow it rather than adding a redirect. +- **A new top-level `nav:` section needs an `extra.sidebar_icons` entry, and the icon set is closed.** The value is used as a template path, so a name that does not exist crashes the build with a Jinja `TemplateNotFound` — not a warning you can defer. Pick a name that exists: `ls theme/assets/vendor/ionicons/svg/`. If nothing fits, say so and stop; vendoring an SVG is a separate change. +- **A release note needs three edits, not one.** See `docs-release-note`. +- **A Self-hosted-specific claim needs a version.** Check it against `extra.codacy_self_hosted_version` in `mkdocs.yml` and do not present a version-gated behavior as current. +- **Filenames are kebab-case and match the page title.** Release notes are the exception and carry a channel-and-date prefix instead. + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/memory/domain/n011-reference.md b/.verity/memory/domain/n011-reference.md new file mode 100644 index 0000000000..2777f300e6 --- /dev/null +++ b/.verity/memory/domain/n011-reference.md @@ -0,0 +1,26 @@ +--- +schema: 1 +id: n011-reference +kind: domain +title: "Reference" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Reference + +Shared across every workflow. Read the relevant one rather than re-deriving it. + +- [`.claude/reference/formatting.md`](.claude/reference/formatting.md) — markdown and structural rules: admonitions, tables, headings, links, images, nesting. The design contract for page bodies. +- [`.claude/reference/voice.md`](.claude/reference/voice.md) — tone, with before/after pairs, plus canonical product names. +- [`.claude/reference/repo-map.md`](.claude/reference/repo-map.md) — how the site is assembled, what each check does and does not catch, and the commands that answer questions about the current state of the repo. +- [`.claude/reference/information-architecture.md`](.claude/reference/information-architecture.md) — durable classification decisions (developer tools vs. the rest of the product, Codacy AI vs. developer tools, where borderline features belong) so structure questions aren't re-derived each time. +- [`CONTRIBUTING.md`](CONTRIBUTING.md) — local build, preview, and release mechanics for humans. + +<!-- verity-memory:start --> + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/memory/domain/n012-project-memory.md b/.verity/memory/domain/n012-project-memory.md new file mode 100644 index 0000000000..30efda7f4c --- /dev/null +++ b/.verity/memory/domain/n012-project-memory.md @@ -0,0 +1,24 @@ +--- +schema: 1 +id: n012-project-memory +kind: domain +title: "Project Memory" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Project Memory + +This project has a knowledge graph maintained at `.verity/memory/`. Before starting +non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns +that may apply to the change you are about to make. Open specific node files via +the Read tool when the title or scope suggests relevance. + +The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are +superseded — ignore them unless investigating history. + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/memory/domain/n014-post-task-reflection.md b/.verity/memory/domain/n014-post-task-reflection.md new file mode 100644 index 0000000000..c9ee87cc69 --- /dev/null +++ b/.verity/memory/domain/n014-post-task-reflection.md @@ -0,0 +1,38 @@ +--- +schema: 1 +id: n014-post-task-reflection +kind: domain +title: "Post-task reflection" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Post-task reflection + +When a task is complete (you've created a PR, the user says "done" or "ship it", +or the work is clearly finished), **draft the reflection yourself first** — 1–3 +concrete things worth remembering (a decision, a gotcha, or a pattern), each +cited with the files / PR / commands / error-signatures it came from. Skip +entirely if nothing non-obvious happened. + +Then, depending on how the session is running: + +- **Interactive** (a person is at the keyboard): show your draft and ask one + question before recording — "Here's my reflection draft for the project + memory: <draft>. Anything to confirm, correct, or add before I record it? + (Say 'skip' to drop it.)" Record the final text (with their corrections) via + `verity reflect --user-input "<final reflection>" --kind <kind>`. If they say + "skip", do not record. +- **Autonomous** (headless / CI / cron, or `VERITY_AUTONOMOUS=1`): record your + draft directly, never blocking — `verity reflect --user-input "<your draft>" + --kind <kind> --autonomous`. + +> Durable, hand-curated guidance goes in the preserve region below (it survives +> regeneration) or anywhere OUTSIDE these markers. Everything else between the +> markers is tool-owned and overwritten on each run. + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/memory/domain/n015-housekeeping-turns.md b/.verity/memory/domain/n015-housekeeping-turns.md new file mode 100644 index 0000000000..14c5bbbbf0 --- /dev/null +++ b/.verity/memory/domain/n015-housekeeping-turns.md @@ -0,0 +1,40 @@ +--- +schema: 1 +id: n015-housekeeping-turns +kind: domain +title: "Housekeeping Turns" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Housekeeping Turns + +When a turn will be pure housekeeping — pulling, installing dependencies, +rebasing, a formatting sweep you are not authoring — declare it BEFORE doing it: + +```bash +verity ignore --turn --agent --reason "pulling latest before starting" +``` + +This skips the review for that turn, which saves the turn Verity would +otherwise spend saying it had nothing to say. Use `--for 30m` instead of +`--turn` when a single piece of housekeeping spans several turns. + +**It is a claim about the turn, not a way to silence review.** The declaration +is checked against what the turn actually did: if anything is authored — by you, +by a subagent, or by a shell command that can write files — it voids, the review +runs anyway, and the broken declaration is reported. So declare housekeeping you +are about to do, never work you have already done, and never as a way to get past +a finding. Declarations are budgeted per session and every one is recorded with +its reason. + +<!-- verity-memory:preserve --> +<!-- Add binding, hand-curated guidance here; it survives Verity regeneration. --> +<!-- /verity-memory:preserve --> +<!-- verity-memory:end --> + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/memory/index.md b/.verity/memory/index.md new file mode 100644 index 0000000000..579b50e0dd --- /dev/null +++ b/.verity/memory/index.md @@ -0,0 +1,3 @@ +# Project Memory Index + +No nodes yet. Run an analysis to start building the knowledge graph. diff --git a/.verity/memory/integrations/n003-mkdocs.md b/.verity/memory/integrations/n003-mkdocs.md new file mode 100644 index 0000000000..0c43928acf --- /dev/null +++ b/.verity/memory/integrations/n003-mkdocs.md @@ -0,0 +1,18 @@ +--- +schema: 1 +id: n003-mkdocs +kind: integration +title: "mkdocs" +confidence: 0.5 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# mkdocs + +This project uses **mkdocs**. + +Capture gotchas and integration-specific decisions here via `/verity-reflect` as they come up during work. diff --git a/.verity/memory/integrations/n004-mkdocs-material.md b/.verity/memory/integrations/n004-mkdocs-material.md new file mode 100644 index 0000000000..f3dda9be8b --- /dev/null +++ b/.verity/memory/integrations/n004-mkdocs-material.md @@ -0,0 +1,18 @@ +--- +schema: 1 +id: n004-mkdocs-material +kind: integration +title: "mkdocs-material" +confidence: 0.5 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# mkdocs-material + +This project uses **mkdocs-material**. + +Capture gotchas and integration-specific decisions here via `/verity-reflect` as they come up during work. diff --git a/.verity/memory/integrations/n005-jinja2.md b/.verity/memory/integrations/n005-jinja2.md new file mode 100644 index 0000000000..56383a42d9 --- /dev/null +++ b/.verity/memory/integrations/n005-jinja2.md @@ -0,0 +1,18 @@ +--- +schema: 1 +id: n005-jinja2 +kind: integration +title: "jinja2" +confidence: 0.5 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# jinja2 + +This project uses **jinja2**. + +Capture gotchas and integration-specific decisions here via `/verity-reflect` as they come up during work. diff --git a/.verity/memory/quality/n013-quality-gate-accepted-risks.md b/.verity/memory/quality/n013-quality-gate-accepted-risks.md new file mode 100644 index 0000000000..9cfa622291 --- /dev/null +++ b/.verity/memory/quality/n013-quality-gate-accepted-risks.md @@ -0,0 +1,26 @@ +--- +schema: 1 +id: n013-quality-gate-accepted-risks +kind: quality +title: "Quality gate: accepted risks" +confidence: 0.6 +status: active +source: extractor +created_by: seed +created_at: 2026-09-14T10:26:36.300Z +updated_at: 2026-09-14T10:26:36.300Z +--- + +# Quality gate: accepted risks + +When the Verity pre-commit/pre-push gate FAILs, fix the findings — that is the +default. Use `verity waive <pattern-id> --file <path> --reason "…"` ONLY to relay +a risk a human has explicitly accepted: a named code-review finding, an ADR, or +the user saying so in this conversation. The --reason must cite that source. + +Never waive on your own judgment, to get past a block, or pre-emptively. A waive +binds to the file's current bytes and voids automatically when the file changes, +and every waive is recorded in the run ledger. For a pattern-level false positive +use `verity feedback finding <run-id> <pattern-id> false_positive` instead. + +_Seeded from CLAUDE.md. Edit or archive if outdated._ diff --git a/.verity/standard.yaml b/.verity/standard.yaml new file mode 100644 index 0000000000..e0b00e253f --- /dev/null +++ b/.verity/standard.yaml @@ -0,0 +1,184 @@ +# Verity — Standard +# Generated by /verity-setup. See STANDARDS-SPEC.md for the full schema. + +version: "1.0.0" +created_by: "claudia@codacy.com" + +knowledge_spec: + project_name: "codacy/docs" + languages: [python, javascript, css, shell, markdown] + frameworks: [mkdocs, mkdocs-material, jinja2] + architecture: "monolith" # single MkDocs site; two git submodules (theme, chart) are vendored, not owned code + build_system: "pip" + test_framework: "none — correctness is enforced by mkdocs build --strict, htmltest, vale, and markdownlint rather than unit tests" + +quality_dimensions: + - id: comprehensibility + description: "Code readable in one context load" + signals: + - metric: file_length + threshold: 300 + unit: lines + rationale: "AI context windows degrade on large files" + - metric: cyclomatic_complexity + threshold: 15 + unit: per_function + rationale: "Complexity-aware feedback improves AI performance by 35.71%" + - metric: function_length + threshold: 50 + unit: lines + rationale: "Short functions are easier for agents to modify safely" + - metric: naming_quality + tool: ai + rationale: "Descriptive names improve AI comprehension by 2x" + + - id: modularity + description: "Modify one concern without touching 3+ files" + signals: + - metric: single_responsibility + tool: ai + rationale: "AI performance drops sharply at 3+ files per change" + - metric: shallow_abstractions + tool: ai + rationale: "LLMs struggle with deep inheritance hierarchies" + + - id: type_safety + description: "Type system catches errors before runtime" + signals: + - metric: naming_and_docstring_clarity + tool: ai + languages: [python] + rationale: "Project's Python surface (theme/hooks, tools/) is untyped; AI review substitutes for a type checker until annotations are adopted" + + - id: test_adequacy + description: "Content and tooling changes are verifiable before merge" + signals: + - metric: build_validation + tool: ai + rationale: "No unit test suite exists; mkdocs build --strict, htmltest, vale, and markdownlint are the project's actual safety net and should be treated as the coverage signal" + - metric: test_quality + tool: ai + rationale: "Any new Python/JS helper script should carry at least a smoke check; flag scripts with none" + +security_patterns: + - id: no-hardcoded-secrets + description: "No secrets, API keys, passwords, or tokens in source code" + severity: critical + cwe: [CWE-798] + enforced_by: + - tool: Trivy + config: + scanners: [secret] + + - id: input-sanitization + description: "All user input validated and sanitized before use" + severity: critical + cwe: [CWE-20, CWE-80, CWE-89, CWE-117] + enforced_by: [] # AI-only — project has no user-facing request handling; applies mainly to theme/hooks/image_metadata.py and JS in docs/assets/javascripts + + - id: parameterized-queries + description: "Database queries use parameters, never string concatenation" + severity: critical + cwe: [CWE-89] + enforced_by: [] # not applicable — no database access in this codebase; kept per Standard schema + + - id: dependency-verification + description: "No known-vulnerable dependencies" + severity: high + cwe: [CWE-1395] + enforced_by: + - tool: Trivy + config: + scanners: [vuln] + severity: [HIGH, CRITICAL] + + - id: no-unsafe-deserialization + description: "No unsafe deserialization of untrusted data" + severity: high + cwe: [CWE-502] + enforced_by: + - tool: Ruff + rules: [S301] + + - id: access-control-checks + description: "Authorization verified on all sensitive operations" + severity: high + cwe: [CWE-639] + enforced_by: [] # AI-only — requires business context + + - id: config-file-integrity + description: "Configuration files not manipulable by untrusted input" + severity: high + cwe: [CWE-15] + enforced_by: [] # AI-only + +custom_patterns: + - id: nav-and-redirect-registration + description: "A new page has a mkdocs.yml nav: entry; a renamed/deleted page has a redirect_maps entry" + severity: high + enforced_by: [] # AI-only — mkdocs build --strict does not catch either omission + rationale: "docs/CLAUDE.md: missing nav is silent (page builds but is unreachable); missing redirect breaks inbound links. Both are info-level or unchecked by the strict build." + + - id: no-invented-specifics + description: "Steps, UI labels, endpoints, filenames, and behaviors are confirmed against code/existing docs, or marked <!-- TODO: verify ... -->" + severity: high + enforced_by: [] # AI-only + rationale: "docs/CLAUDE.md hard rule — an invented button label or endpoint is worse than an honest gap." + + - id: no-marketing-filler + description: "Avoid marketing adjectives and filler ('powerful', 'seamless', 'robust', 'effortless', 'it's worth noting that', 'in order to', 'simply', 'just')" + severity: medium + enforced_by: [] # AI-only — voice/tone check + rationale: "docs/CLAUDE.md hard rule and .claude/reference/voice.md — show the product working instead of grading it." + + - id: self-hosted-version-gating + description: "A Self-hosted-specific claim states the version and is checked against extra.codacy_self_hosted_version in mkdocs.yml" + severity: medium + enforced_by: [] # AI-only + rationale: "docs/CLAUDE.md — prevents presenting a version-gated behavior as universally current." + + - id: alt-text-and-icon-safety + description: "Images carry alt text; a new top-level nav: section's sidebar_icons entry names an SVG that actually exists under theme/assets/vendor/ionicons/svg/" + severity: high + enforced_by: [] # AI-only + rationale: "docs/CLAUDE.md — an invented icon name crashes the build with a Jinja TemplateNotFound, not a warning." + +process_constraints: + self_healing_limit: 2 + analysis_mode: balanced + + compound_enabled: true + memory_graph_enabled: true + memory_graph_budget_tokens: 2000 + knowledge_injection_budget_tokens: 2000 + finding_autosuppress_threshold: 2 + + intent_fail_threshold: 4 + intent_warn_threshold: 7 + + exclude_paths: + - node_modules + - dist + - build + - .git + - vendor + - __pycache__ + - .next + - coverage + - site + - .venv + - .cache + - submodules + - .claude/worktrees + - .codacy + +tool_configuration: + eslint: + enabled: true + config_file: ".codacy/tools-configs/eslint.config.mjs" + ruff: + enabled: true + shellcheck: + enabled: true + trivy: + enabled: true diff --git a/CLAUDE.md b/CLAUDE.md index ec2c794fc0..1d6c87a44d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,3 +75,76 @@ Shared across every workflow. Read the relevant one rather than re-deriving it. - [`.claude/reference/repo-map.md`](.claude/reference/repo-map.md) — how the site is assembled, what each check does and does not catch, and the commands that answer questions about the current state of the repo. - [`.claude/reference/information-architecture.md`](.claude/reference/information-architecture.md) — durable classification decisions (developer tools vs. the rest of the product, Codacy AI vs. developer tools, where borderline features belong) so structure questions aren't re-derived each time. - [`CONTRIBUTING.md`](CONTRIBUTING.md) — local build, preview, and release mechanics for humans. + +<!-- verity-memory:start --> +## Project Memory + +This project has a knowledge graph maintained at `.verity/memory/`. Before starting +non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns +that may apply to the change you are about to make. Open specific node files via +the Read tool when the title or scope suggests relevance. + +The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are +superseded — ignore them unless investigating history. + +## Quality gate: accepted risks + +When the Verity pre-commit/pre-push gate FAILs, fix the findings — that is the +default. Use `verity waive <pattern-id> --file <path> --reason "…"` ONLY to relay +a risk a human has explicitly accepted: a named code-review finding, an ADR, or +the user saying so in this conversation. The --reason must cite that source. + +Never waive on your own judgment, to get past a block, or pre-emptively. A waive +binds to the file's current bytes and voids automatically when the file changes, +and every waive is recorded in the run ledger. For a pattern-level false positive +use `verity feedback finding <run-id> <pattern-id> false_positive` instead. + +## Post-task reflection + +When a task is complete (you've created a PR, the user says "done" or "ship it", +or the work is clearly finished), **draft the reflection yourself first** — 1–3 +concrete things worth remembering (a decision, a gotcha, or a pattern), each +cited with the files / PR / commands / error-signatures it came from. Skip +entirely if nothing non-obvious happened. + +Then, depending on how the session is running: + +- **Interactive** (a person is at the keyboard): show your draft and ask one + question before recording — "Here's my reflection draft for the project + memory: <draft>. Anything to confirm, correct, or add before I record it? + (Say 'skip' to drop it.)" Record the final text (with their corrections) via + `verity reflect --user-input "<final reflection>" --kind <kind>`. If they say + "skip", do not record. +- **Autonomous** (headless / CI / cron, or `VERITY_AUTONOMOUS=1`): record your + draft directly, never blocking — `verity reflect --user-input "<your draft>" + --kind <kind> --autonomous`. + +> Durable, hand-curated guidance goes in the preserve region below (it survives +> regeneration) or anywhere OUTSIDE these markers. Everything else between the +> markers is tool-owned and overwritten on each run. + +## Housekeeping Turns + +When a turn will be pure housekeeping — pulling, installing dependencies, +rebasing, a formatting sweep you are not authoring — declare it BEFORE doing it: + +```bash +verity ignore --turn --agent --reason "pulling latest before starting" +``` + +This skips the review for that turn, which saves the turn Verity would +otherwise spend saying it had nothing to say. Use `--for 30m` instead of +`--turn` when a single piece of housekeeping spans several turns. + +**It is a claim about the turn, not a way to silence review.** The declaration +is checked against what the turn actually did: if anything is authored — by you, +by a subagent, or by a shell command that can write files — it voids, the review +runs anyway, and the broken declaration is reported. So declare housekeeping you +are about to do, never work you have already done, and never as a way to get past +a finding. Declarations are budgeted per session and every one is recorded with +its reason. + +<!-- verity-memory:preserve --> +<!-- Add binding, hand-curated guidance here; it survives Verity regeneration. --> +<!-- /verity-memory:preserve --> +<!-- verity-memory:end --> diff --git a/VERITY.md b/VERITY.md new file mode 100644 index 0000000000..75714d0710 --- /dev/null +++ b/VERITY.md @@ -0,0 +1,36 @@ +# VERITY.md — Quality Gate + +> This project uses [Verity](https://verity.md) to enforce quality and security standards on AI-generated code. + +**Project:** docs +**Standard:** v1 + +## Quality Dimensions +- Comprehensibility (file length, complexity, naming) +- Modularity (separation of concerns, shallow abstractions) +- Type Safety (this repo has no type checker; AI review substitutes for one on `theme/hooks/*.py` and `tools/*.py`) +- Test Adequacy (no unit test suite; `mkdocs build --strict`, `htmltest`, `vale`, and `markdownlint` are the actual safety net) + +## Security Patterns +- No hardcoded secrets (CWE-798) +- Input sanitization (CWE-20) +- Parameterized queries (CWE-89) +- Dependency verification (CWE-1395) +- No unsafe deserialization (CWE-502) +- Access control checks (CWE-639) +- Config file integrity (CWE-15) + +## Project-Specific Patterns +Synthesized from `docs/CLAUDE.md`'s hard rules and silent-failure mechanics: +- New/renamed pages register `nav:` and `redirect_maps` entries in `mkdocs.yml` +- No invented steps, UI labels, endpoints, or behaviors — mark unconfirmed specifics with `<!-- TODO: verify ... -->` +- No marketing adjectives or filler language +- Self-hosted-specific claims are checked against `extra.codacy_self_hosted_version` +- Images carry alt text; new nav icon names exist under `theme/assets/vendor/ionicons/svg/` + +## How It Works +Every time the coding agent stops, the Verity hook: +1. Runs static analysis via @codacy/analysis-cli (ESLint9, Ruff, shellcheck, Trivy) +2. Sends results + code to the Verity service +3. Gemini independently reviews the code +4. Returns PASS / WARN / FAIL with actionable findings