diff --git a/commands/test-way.md b/commands/test-way.md index e3bd8bf..945860b 100644 --- a/commands/test-way.md +++ b/commands/test-way.md @@ -97,7 +97,7 @@ Use the `way-match suggest` command: Output is section-delimited (GAPS, COVERAGE, UNUSED, VOCABULARY). Parse and display in a readable format: ``` -=== Vocabulary Analysis: softwaredev/security === +=== Vocabulary Analysis: softwaredev/code/security === Gaps (body terms not in vocabulary, freq >= 2): parameterized freq=3 diff --git a/docs/governance.md b/docs/governance.md index 422570b..b729d73 100644 --- a/docs/governance.md +++ b/docs/governance.md @@ -157,9 +157,9 @@ In an enterprise, policy documents and way implementations typically live in sep ``` compliance-repo/ your-claude-config/ ├── docs/architecture/ ├── hooks/ways/ -│ ├── ADR-150.md │ ├── softwaredev/commits/way.md +│ ├── ADR-150.md │ ├── softwaredev/delivery/commits/way.md │ └── ADR-200.md │ │ (provenance: → ADR-150) -├── audit-ledger.json │ └── softwaredev/security/way.md +├── audit-ledger.json │ └── softwaredev/code/security/way.md └── controls.xlsx └── governance/ ├── policies/ └── provenance-manifest.json diff --git a/docs/hooks-and-ways.md b/docs/hooks-and-ways.md index 31f7e3d..9986be0 100644 --- a/docs/hooks-and-ways.md +++ b/docs/hooks-and-ways.md @@ -368,9 +368,9 @@ macro: append # macro output after static content ``` Macros generate dynamic content. Examples: -- `softwaredev/adr/macro.sh` - Tri-state detection: no tooling, tooling available, tooling installed -- `softwaredev/quality/macro.sh` - Scans for long files in the project, outputs priority list -- `softwaredev/github/macro.sh` - Detects solo vs team project, adjusts PR guidance +- `softwaredev/architecture/adr/macro.sh` - Tri-state detection: no tooling, tooling available, tooling installed +- `softwaredev/code/quality/macro.sh` - Scans for long files in the project, outputs priority list +- `softwaredev/delivery/github/macro.sh` - Detects solo vs team project, adjusts PR guidance **Security**: Project-local macros only run if the project is listed in `~/.claude/trusted-project-macros`. @@ -385,11 +385,11 @@ flowchart TD classDef marker fill:#00695C,stroke:#004D40,color:#fff classDef result fill:#2E7D32,stroke:#1B5E20,color:#fff - T["Trigger fires for softwaredev/github"] --> PL + T["Trigger fires for softwaredev/delivery/github"] --> PL - PL{"$PROJECT/.claude/ways/
softwaredev/github/way.md
exists?"} + PL{"$PROJECT/.claude/ways/
softwaredev/delivery/github/way.md
exists?"} PL -->|yes| USE_P["Use project-local way"]:::project - PL -->|no| GL{"~/.claude/hooks/ways/
softwaredev/github/way.md
exists?"}:::global + PL -->|no| GL{"~/.claude/hooks/ways/
softwaredev/delivery/github/way.md
exists?"}:::global GL -->|yes| USE_G["Use global way"]:::global GL -->|no| SKIP["No output"] diff --git a/docs/hooks-and-ways/extending.md b/docs/hooks-and-ways/extending.md index 719d09d..dddce60 100644 --- a/docs/hooks-and-ways/extending.md +++ b/docs/hooks-and-ways/extending.md @@ -101,7 +101,7 @@ These are discovered alongside global ways and follow the same matching rules. A project-local way with the same domain/name path as a global way takes precedence. They share a single marker, so only the project-local version fires. -Example: If a project has `.claude/ways/softwaredev/testing/way.md`, it replaces `~/.claude/hooks/ways/softwaredev/testing/way.md` for that project. +Example: If a project has `.claude/ways/softwaredev/code/testing/way.md`, it replaces `~/.claude/hooks/ways/softwaredev/code/testing/way.md` for that project. ### Macros in project-local ways diff --git a/docs/hooks-and-ways/provenance.md b/docs/hooks-and-ways/provenance.md index b6427f8..a17f544 100644 --- a/docs/hooks-and-ways/provenance.md +++ b/docs/hooks-and-ways/provenance.md @@ -131,9 +131,9 @@ In an enterprise, policy documents and way implementations typically live in sep ``` compliance-repo/ your-claude-config/ ├── docs/architecture/ ├── hooks/ways/ -│ ├── ADR-150.md │ ├── softwaredev/commits/way.md +│ ├── ADR-150.md │ ├── softwaredev/delivery/commits/way.md │ └── ADR-200.md │ │ (provenance: → ADR-150) -├── audit-ledger.json │ └── softwaredev/security/way.md +├── audit-ledger.json │ └── softwaredev/code/security/way.md └── controls.xlsx └── provenance-manifest.json ``` diff --git a/docs/hooks-and-ways/stats.md b/docs/hooks-and-ways/stats.md index 868128b..ee4364c 100644 --- a/docs/hooks-and-ways/stats.md +++ b/docs/hooks-and-ways/stats.md @@ -7,7 +7,7 @@ You can't manage what you can't see. The ways system logs every firing event and Every time a way fires, `log-event.sh` appends a line to `~/.claude/stats/events.jsonl`: ```json -{"ts":"2026-02-05T19:00:34Z","event":"way_fired","way":"softwaredev/commits","domain":"softwaredev","trigger":"bash","scope":"agent","project":"/home/you/myproject","session":"abc-123"} +{"ts":"2026-02-05T19:00:34Z","event":"way_fired","way":"softwaredev/delivery/commits","domain":"softwaredev","trigger":"bash","scope":"agent","project":"/home/you/myproject","session":"abc-123"} ``` Session start events are also logged. For teammates, the team name is included: @@ -39,8 +39,8 @@ Top ways: meta/todos 96 ████████████████████ meta/memory 96 ████████████████████ meta/knowledge 30 ██████ - softwaredev/commits 19 ███ - softwaredev/design 18 ███ + softwaredev/delivery/commits 19 ███ + softwaredev/architecture/design 18 ███ By scope: agent 319 diff --git a/docs/scripts/adr b/docs/scripts/adr index a9375ad..d819977 120000 --- a/docs/scripts/adr +++ b/docs/scripts/adr @@ -1 +1 @@ -../../hooks/ways/softwaredev/adr/adr-tool \ No newline at end of file +../../hooks/ways/softwaredev/architecture/adr/adr-tool \ No newline at end of file diff --git a/hooks/ways/check-bash-pre.sh b/hooks/ways/check-bash-pre.sh index f1669e8..6ef14c0 100755 --- a/hooks/ways/check-bash-pre.sh +++ b/hooks/ways/check-bash-pre.sh @@ -9,7 +9,7 @@ # │ keywords match │ # └─────────────────┘ # -# Ways are nested: domain/wayname/way.md (e.g., softwaredev/github/way.md) +# Ways are nested: domain/wayname/way.md (e.g., softwaredev/delivery/github/way.md) # Multiple ways can match a single command - CONTEXT accumulates # all matching way outputs. Markers prevent duplicate content. # Output is returned as additionalContext JSON for Claude to see. @@ -33,7 +33,7 @@ scan_ways() { # Find all way.md files recursively while IFS= read -r -d '' wayfile; do - # Extract way path relative to ways dir (e.g., "softwaredev/github") + # Extract way path relative to ways dir (e.g., "softwaredev/delivery/github") waypath="${wayfile#$dir/}" waypath="${waypath%/way.md}" diff --git a/hooks/ways/check-file-pre.sh b/hooks/ways/check-file-pre.sh index a714fd9..e6ffc30 100755 --- a/hooks/ways/check-file-pre.sh +++ b/hooks/ways/check-file-pre.sh @@ -8,7 +8,7 @@ # └───────────────────────┘ │ if files match │ └──────────────┘ # └─────────────────┘ # -# Ways are nested: domain/wayname/way.md (e.g., softwaredev/github/way.md) +# Ways are nested: domain/wayname/way.md (e.g., softwaredev/delivery/github/way.md) # Multiple ways can match a single file path - CONTEXT accumulates # all matching way outputs. Markers prevent duplicate content. # Output is returned as additionalContext JSON for Claude to see. @@ -31,7 +31,7 @@ scan_ways() { # Find all way.md files recursively while IFS= read -r -d '' wayfile; do - # Extract way path relative to ways dir (e.g., "softwaredev/github") + # Extract way path relative to ways dir (e.g., "softwaredev/delivery/github") waypath="${wayfile#$dir/}" waypath="${waypath%/way.md}" diff --git a/hooks/ways/check-prompt.sh b/hooks/ways/check-prompt.sh index 59a9101..10a88e0 100755 --- a/hooks/ways/check-prompt.sh +++ b/hooks/ways/check-prompt.sh @@ -9,7 +9,7 @@ # │ semantic match │ # └─────────────────┘ # -# Ways are nested: domain/wayname/way.md (e.g., softwaredev/github/way.md) +# Ways are nested: domain/wayname/way.md (e.g., softwaredev/delivery/github/way.md) # Matching is ADDITIVE: pattern (regex/keyword) and semantic are OR'd. # Semantic matching degrades: BM25 binary → gzip NCD → skip. # Project-local ways are scanned first (and take precedence). @@ -45,7 +45,7 @@ scan_ways() { # Find all way.md files recursively while IFS= read -r -d '' wayfile; do - # Extract way path relative to ways dir (e.g., "softwaredev/github") + # Extract way path relative to ways dir (e.g., "softwaredev/delivery/github") waypath="${wayfile#$dir/}" waypath="${waypath%/way.md}" diff --git a/hooks/ways/log-event.sh b/hooks/ways/log-event.sh index 0fd73b2..cc5edb1 100755 --- a/hooks/ways/log-event.sh +++ b/hooks/ways/log-event.sh @@ -1,7 +1,7 @@ #!/bin/bash # Log a ways event to ~/.claude/stats/events.jsonl # Usage: log-event.sh key=value key=value ... -# Example: log-event.sh event=way_fired way=softwaredev/github trigger=prompt +# Example: log-event.sh event=way_fired way=softwaredev/delivery/github trigger=prompt # # All values are safely JSON-encoded via jq. Event log is append-only JSONL. diff --git a/hooks/ways/macro.sh b/hooks/ways/macro.sh index ee375ab..018285e 100755 --- a/hooks/ways/macro.sh +++ b/hooks/ways/macro.sh @@ -12,7 +12,7 @@ CURRENT_DOMAIN="" # Find all way.md files, sorted by path while IFS= read -r wayfile; do - # Extract relative path (e.g., "softwaredev/github") + # Extract relative path (e.g., "softwaredev/delivery/github") relpath="${wayfile#$WAYS_DIR/}" relpath="${relpath%/way.md}" diff --git a/hooks/ways/show-way.sh b/hooks/ways/show-way.sh index 06d4206..8174582 100755 --- a/hooks/ways/show-way.sh +++ b/hooks/ways/show-way.sh @@ -2,7 +2,7 @@ # Show a "way" once per session (strips frontmatter, runs macro if configured) # Usage: show-way.sh # -# Way paths can be nested: "softwaredev/github", "awsops/iam", etc. +# Way paths can be nested: "softwaredev/delivery/github", "awsops/iam", etc. # Looks for: {way-path}/way.md and optionally {way-path}/macro.sh # # STATE MACHINE: @@ -34,7 +34,7 @@ TEAM=$(detect_team "$SESSION_ID") # Check if domain is disabled via ~/.claude/ways.json # Example: { "disabled": ["itops", "softwaredev"] } WAYS_CONFIG="${HOME}/.claude/ways.json" -DOMAIN="${WAY%%/*}" # First path component (e.g., "softwaredev" from "softwaredev/github") +DOMAIN="${WAY%%/*}" # First path component (e.g., "softwaredev" from "softwaredev/delivery/github") if [[ -f "$WAYS_CONFIG" ]]; then if jq -e --arg d "$DOMAIN" '.disabled | index($d) != null' "$WAYS_CONFIG" >/dev/null 2>&1; then exit 0 diff --git a/hooks/ways/softwaredev/adr/adr-tool b/hooks/ways/softwaredev/adr/adr-tool deleted file mode 100755 index 0ea8f23..0000000 --- a/hooks/ways/softwaredev/adr/adr-tool +++ /dev/null @@ -1,755 +0,0 @@ -#!/usr/bin/env python3 -""" -ADR - Architecture Decision Record CLI Tool - -A librarian for managing Architecture Decision Records. - -Usage: - adr list [--domain DOMAIN] [--status STATUS] [--group] - adr view # View an ADR (aliases: v, show) - adr new - adr lint [--check] [paths...] - adr index [-y] - adr domains - adr config - -Configuration is loaded from docs/architecture/adr.yaml -""" - -import argparse -import re -import subprocess -import sys -from datetime import date -from pathlib import Path -from dataclasses import dataclass, field -from typing import Optional - -# Check for PyYAML -try: - import yaml -except ImportError: - print("Error: PyYAML is required. Install with: pip install pyyaml", file=sys.stderr) - print(" Or system-wide: sudo apt install python3-yaml", file=sys.stderr) - sys.exit(1) - -# ============================================================================ -# Configuration -# ============================================================================ - -def get_config_path() -> Path: - """Get path to adr.yaml config file.""" - return Path(__file__).parent.parent / 'architecture' / 'adr.yaml' - -def load_config() -> dict: - """Load configuration from adr.yaml.""" - config_path = get_config_path() - - if not config_path.exists(): - print(f"Error: Config not found: {config_path}", file=sys.stderr) - print("Run from project root or create docs/architecture/adr.yaml", file=sys.stderr) - sys.exit(1) - - try: - with open(config_path) as f: - config = yaml.safe_load(f) - except yaml.YAMLError as e: - print(f"Error: Invalid YAML in config: {e}", file=sys.stderr) - sys.exit(1) - - # Validate required fields - if 'domains' not in config: - print("Error: Config missing 'domains' section", file=sys.stderr) - sys.exit(1) - - # Convert range lists to tuples for easier use - for domain, cfg in config.get('domains', {}).items(): - if 'range' in cfg and isinstance(cfg['range'], list): - cfg['range'] = tuple(cfg['range']) - - if 'legacy' in config and 'range' in config['legacy']: - if isinstance(config['legacy']['range'], list): - config['legacy']['range'] = tuple(config['legacy']['range']) - - return config - -# Global config (loaded on first access) -_config = None - -def get_config() -> dict: - """Get cached config.""" - global _config - if _config is None: - _config = load_config() - return _config - -def get_domains() -> dict: - """Get domain configuration.""" - return get_config().get('domains', {}) - -def get_statuses() -> set: - """Get valid statuses.""" - return set(get_config().get('statuses', ['Draft', 'Proposed', 'Accepted', 'Superseded', 'Deprecated'])) - -def relative_path(path: Path, base: Path = None) -> Path: - """Get path relative to base, or return absolute if not possible.""" - if base is None: - base = Path.cwd() - try: - return path.relative_to(base) - except ValueError: - return path - -def get_defaults() -> dict: - """Get default values for new ADRs.""" - return get_config().get('defaults', {'deciders': [], 'status': 'Draft'}) - -def get_legacy_range() -> tuple: - """Get legacy ADR number range.""" - legacy = get_config().get('legacy', {}) - return legacy.get('range', (1, 99)) - -def _detect_git_user() -> Optional[str]: - """Detect current git user (GitHub username or git config name).""" - # Try GitHub username from gh CLI - try: - result = subprocess.run( - ['gh', 'api', 'user', '--jq', '.login'], - capture_output=True, text=True, timeout=5) - if result.returncode == 0 and result.stdout.strip(): - return result.stdout.strip() - except (FileNotFoundError, subprocess.TimeoutExpired): - pass - # Fall back to git config user.name - try: - result = subprocess.run( - ['git', 'config', 'user.name'], - capture_output=True, text=True, timeout=5) - if result.returncode == 0 and result.stdout.strip(): - return result.stdout.strip() - except (FileNotFoundError, subprocess.TimeoutExpired): - pass - return None - -# ============================================================================ -# Data Classes -# ============================================================================ - -@dataclass -class ADRInfo: - path: Path - number: Optional[str] = None - title: Optional[str] = None - status: Optional[str] = None - date: Optional[str] = None - deciders: list = field(default_factory=list) - related: list = field(default_factory=list) - domain: Optional[str] = None - issues: list = field(default_factory=list) - -@dataclass -class Issue: - message: str - severity: str = 'warning' - -# ============================================================================ -# Parsing -# ============================================================================ - -TITLE_PATTERN = re.compile(r'^# ADR-(\d+(?:\.\d+)?): (.+)$') - -def parse_adr(path: Path) -> ADRInfo: - """Parse an ADR file and extract metadata.""" - info = ADRInfo(path=path) - - try: - content = path.read_text() - except Exception as e: - info.issues.append(Issue(f"Cannot read: {e}", 'error')) - return info - - lines = content.split('\n') - - # Parse YAML frontmatter - has_frontmatter = lines and lines[0].strip() == '---' - if has_frontmatter: - end_idx = None - for i, line in enumerate(lines[1:], 1): - if line.strip() == '---': - end_idx = i - break - - if end_idx: - yaml_content = '\n'.join(lines[1:end_idx]) - try: - data = yaml.safe_load(yaml_content) or {} - info.status = data.get('status') - info.date = str(data.get('date', '')) if data.get('date') else None - deciders = data.get('deciders', []) - info.deciders = deciders if isinstance(deciders, list) else [deciders] - info.related = data.get('related', []) - except yaml.YAMLError as e: - info.issues.append(Issue(f"YAML error: {e}", 'error')) - else: - info.issues.append(Issue("Opening --- found but no closing --- for frontmatter", 'error')) - else: - # Check for inline metadata (pre-YAML pattern) - inline_meta = any(re.match(r'^(Status|Date|Deciders):\s', line) for line in lines[:15]) - if inline_meta: - info.issues.append(Issue( - "No YAML frontmatter — found inline metadata (Status:/Date:/Deciders:). " - "Convert to YAML frontmatter: wrap in --- delimiters, use lowercase keys", - 'error')) - else: - info.issues.append(Issue("No YAML frontmatter found", 'error')) - - # Find title - for line in lines: - match = TITLE_PATTERN.match(line) - if match: - info.number = match.group(1) - info.title = match.group(2) - break - - # Determine domain from folder (preferred) or number range (fallback) - folder_name = path.parent.name - for domain, config in get_domains().items(): - folders = config['folder'] - if isinstance(folders, str): - folders = [folders] - if folder_name in folders: - info.domain = domain - break - else: - # Fallback: determine from number range - if info.number: - try: - base_num = int(info.number.split('.')[0]) - for domain, config in get_domains().items(): - if config['range'][0] <= base_num <= config['range'][1]: - info.domain = domain - break - except ValueError: - pass - - # Validation - if not info.number: - info.issues.append(Issue("Missing ADR number in title", 'error')) - # Skip field-level checks if no frontmatter — root cause already reported - if has_frontmatter: - valid_statuses = get_statuses() - if not info.status: - info.issues.append(Issue("Missing status in frontmatter", 'error')) - elif info.status not in valid_statuses: - info.issues.append(Issue(f"Invalid status: {info.status} (valid: {', '.join(sorted(valid_statuses))})", 'warning')) - if not info.date: - info.issues.append(Issue("Missing date in frontmatter", 'error')) - if not info.deciders: - info.issues.append(Issue("Missing deciders in frontmatter", 'warning')) - - return info - -def find_adrs() -> list[Path]: - """Find all ADR files.""" - docs_root = Path(__file__).parent.parent - arch_dir = docs_root / 'architecture' - return sorted(arch_dir.rglob("ADR-*.md")) - -def get_all_adrs() -> list[ADRInfo]: - """Parse all ADR files.""" - return [parse_adr(p) for p in find_adrs()] - -# ============================================================================ -# Commands -# ============================================================================ - -def cmd_list(args): - """List all ADRs.""" - adrs = get_all_adrs() - domains = get_domains() - - # Filter by domain - if args.domain: - adrs = [a for a in adrs if a.domain == args.domain] - - # Filter by status - if args.status: - adrs = [a for a in adrs if a.status and a.status.lower() == args.status.lower()] - - # Sort by number - def sort_key(adr): - if not adr.number: - return (9999, 0) - parts = adr.number.split('.') - return (int(parts[0]), int(parts[1]) if len(parts) > 1 else 0) - - adrs.sort(key=sort_key) - - project = get_config().get('project_name', 'Project') - print(f"\n{project} — Architecture Decision Records ({len(adrs)} total)") - print("=" * 55) - - def status_icon(status): - return { - 'Draft': '📝', - 'Proposed': '💡', - 'Accepted': '✅', - 'Superseded': '📦', - 'Deprecated': '🗑️' - }.get(status, '❓') - - def print_adr(adr): - print(f" {status_icon(adr.status)} ADR-{adr.number or '???':8} {adr.title or '(no title)'}") - - if args.group: - # Group by domain - show domains in config order, then legacy - for domain_key, domain_info in domains.items(): - domain_adrs = [a for a in adrs if a.domain == domain_key] - if not domain_adrs: - continue - print(f"\n## {domain_info.get('name', domain_key)} ({domain_key})") - print("-" * 50) - for adr in domain_adrs: - print_adr(adr) - - # Legacy (no domain) - legacy_adrs = [a for a in adrs if not a.domain] - if legacy_adrs: - legacy_label = get_config().get('legacy', {}).get('label', 'Legacy') - print(f"\n## {legacy_label}") - print("-" * 50) - for adr in legacy_adrs: - print_adr(adr) - else: - # Flat list, sorted by number - for adr in adrs: - print_adr(adr) - - print(f"\nTotal: {len(adrs)} ADRs") - return 0 - - -def cmd_view(args): - """View an ADR using the configured viewer.""" - import shutil - - # Normalize the ADR reference (accept "38", "038", "ADR-038", "ADR-38", "51.2") - ref = args.adr.upper().replace('ADR-', '').lstrip('0') or '0' - - # Helper to extract number from filename (e.g., ADR-051.2-foo.md -> 51.2) - def filename_number(path): - match = re.match(r'ADR-(\d+(?:\.\d+)?)', path.name, re.IGNORECASE) - return match.group(1).lstrip('0') if match else None - - # Find the ADR by title number or filename number - adrs = get_all_adrs() - matches = [a for a in adrs if - (a.number and a.number.lstrip('0') == ref) or - filename_number(a.path) == ref] - - if not matches: - print(f"Error: ADR not found: {args.adr}", file=sys.stderr) - print(f"Use `adr list` to see available ADRs.", file=sys.stderr) - return 1 - - if len(matches) > 1: - print(f"Multiple ADRs match '{args.adr}':") - for adr in matches: - print(f" ADR-{adr.number}: {adr.title}") - return 1 - - adr = matches[0] - - # Get viewer command from config - viewer_cmd = get_config().get('viewer', 'cat {file}') - - # Check if viewer command exists - viewer_bin = viewer_cmd.split()[0] - if not shutil.which(viewer_bin): - print(f"Warning: Viewer '{viewer_bin}' not found, using cat", file=sys.stderr) - viewer_cmd = 'cat {file}' - - # Build and run command - cmd = viewer_cmd.replace('{file}', str(adr.path)) - return subprocess.run(cmd, shell=True).returncode - - -def cmd_new(args): - """Create a new ADR.""" - domain = args.domain.lower() - domains = get_domains() - defaults = get_defaults() - - if domain not in domains: - print(f"Error: Unknown domain '{domain}'", file=sys.stderr) - print(f"Valid domains: {', '.join(domains.keys())}", file=sys.stderr) - return 1 - - config = domains[domain] - - # Find next available number in range - adrs = get_all_adrs() - used_numbers = set() - for adr in adrs: - if adr.number: - try: - used_numbers.add(int(adr.number.split('.')[0])) - except ValueError: - pass - - next_num = None - for n in range(config['range'][0], config['range'][1] + 1): - if n not in used_numbers: - next_num = n - break - - if next_num is None: - print(f"Error: No available numbers in {domain} range ({config['range'][0]}-{config['range'][1]})", file=sys.stderr) - return 1 - - # Generate slug from title - slug = re.sub(r'[^a-z0-9]+', '-', args.title.lower()).strip('-') - - # Create file path (use first folder if multiple) - docs_root = Path(__file__).parent.parent - folders = config['folder'] - primary_folder = folders[0] if isinstance(folders, list) else folders - folder = docs_root / 'architecture' / primary_folder - filename = f"ADR-{next_num:03d}-{slug}.md" - filepath = folder / filename - - if filepath.exists(): - print(f"Error: File already exists: {filepath}", file=sys.stderr) - return 1 - - # Generate content - today = date.today().isoformat() - default_status = defaults.get('status', 'Draft') - default_deciders = defaults.get('deciders', []) - - # Auto-detect current user if no deciders configured - if not default_deciders: - git_user = _detect_git_user() - if git_user: - default_deciders = [git_user] - - deciders_yaml = '\n'.join(f' - {d}' for d in default_deciders) if default_deciders else ' - # add deciders' - - content = f'''--- -status: {default_status} -date: {today} -deciders: -{deciders_yaml} -related: [] ---- - -# ADR-{next_num:03d}: {args.title} - -## Context - -[What is the issue that we're seeing that is motivating this decision or change?] - -## Decision - -[What is the change that we're proposing and/or doing?] - -## Consequences - -### Positive - -- [What becomes easier?] - -### Negative - -- [What becomes harder?] - -### Neutral - -- [What other changes does this enable or require?] - -## Alternatives Considered - -- [What other options were evaluated?] -- [Why were they rejected?] -''' - - # Write file - folder.mkdir(parents=True, exist_ok=True) - filepath.write_text(content) - - print(f"Created: {relative_path(filepath)}") - print(f" Domain: {config['name']} ({domain})") - print(f" Number: ADR-{next_num:03d}") - return 0 - -def cmd_lint(args): - """Lint ADR files for issues.""" - if args.paths: - paths = [Path(p) for p in args.paths] - adrs = [parse_adr(p) for p in paths] - else: - adrs = get_all_adrs() - - total_errors = 0 - total_warnings = 0 - - # Status summary - status_counts = {} - for adr in adrs: - status = adr.status or 'Unknown' - status_counts[status] = status_counts.get(status, 0) + 1 - - print(f"\nScanned: {len(adrs)} ADRs") - print(f"\nStatus distribution:") - for status, count in sorted(status_counts.items()): - print(f" {status}: {count}") - - # Issues - files_with_issues = [adr for adr in adrs if adr.issues] - - if files_with_issues: - print(f"\n{'─'*60}") - print(f"Issues found in {len(files_with_issues)} files:") - print(f"{'─'*60}") - - for adr in files_with_issues: - print(f"\n{relative_path(adr.path)}") - - for issue in adr.issues: - icon = '❌' if issue.severity == 'error' else '⚠️' - print(f" {icon} {issue.message}") - - if issue.severity == 'error': - total_errors += 1 - else: - total_warnings += 1 - - print(f"\n{'═'*60}") - print(f"Summary: {total_errors} errors, {total_warnings} warnings") - print(f"{'═'*60}\n") - - if args.check and total_errors > 0: - return 1 - return 0 - -def cmd_index(args): - """Generate/update the ADR index file.""" - adrs = get_all_adrs() - domains = get_domains() - legacy_range = get_legacy_range() - - # Sort by number - def sort_key(adr): - if not adr.number: - return (9999, 0) - parts = adr.number.split('.') - return (int(parts[0]), int(parts[1]) if len(parts) > 1 else 0) - - adrs.sort(key=sort_key) - - lines = [ - "# Architecture Decision Records", - "", - f"This directory contains Architecture Decision Records (ADRs) for {get_config().get('project_name', 'this project')}.", - "Each ADR documents a significant architectural decision, its context, and consequences.", - "", - "## ADR Format", - "", - "All ADRs follow a consistent format:", - "- **Status:** Draft / Proposed / Accepted / Deprecated / Superseded", - "- **Date:** When the decision was made", - "- **Deciders:** Who made the decision", - "- **Context:** The problem or situation requiring a decision", - "- **Decision:** The architectural choice made", - "- **Consequences:** Benefits, drawbacks, and other impacts", - "", - f"_This index is auto-generated by `adr index`. Configuration: [`adr.yaml`](./adr.yaml)_", - "", - ] - - # By domain - for domain, config in domains.items(): - domain_adrs = [a for a in adrs if a.domain == domain] - if not domain_adrs: - continue - - lines.append(f"## {config['name']}") - lines.append(f"_{config['description']}_") - lines.append("") - lines.append("| ADR | Title | Status |") - lines.append("|-----|-------|--------|") - - for adr in domain_adrs: - # Use actual folder from path, not config (supports multiple folders) - folder = adr.path.parent.name - filename = adr.path.name - link = f"[ADR-{adr.number}](./{folder}/{filename})" - lines.append(f"| {link} | {adr.title or '?'} | {adr.status or '?'} |") - - lines.append("") - - # Legacy (pre-domain numbering) - legacy_label = get_config().get('legacy', {}).get('label', 'Legacy') - uncategorized = [a for a in adrs if not a.domain] - if uncategorized: - lines.append(f"## {legacy_label}") - lines.append("") - lines.append("| ADR | Title | Status |") - lines.append("|-----|-------|--------|") - - for adr in uncategorized: - rel_path = adr.path.relative_to(adr.path.parent.parent) - link = f"[ADR-{adr.number}](./{rel_path})" - lines.append(f"| {link} | {adr.title or '?'} | {adr.status or '?'} |") - - lines.append("") - - content = '\n'.join(lines) - - # Check against existing index - docs_root = Path(__file__).parent.parent - index_path = docs_root / 'architecture' / 'INDEX.md' - - if index_path.exists(): - existing = index_path.read_text() - if existing == content: - print(f"Index is up to date: {relative_path(index_path)}") - print(f" {len(adrs)} ADRs across {len(domains)} domains") - return 0 - - # Show what changed - existing_lines = existing.splitlines() - new_lines = content.splitlines() - - added = len([l for l in new_lines if l not in existing_lines]) - removed = len([l for l in existing_lines if l not in new_lines]) - - print(f"Index needs updating: {relative_path(index_path)}") - print(f" {len(adrs)} ADRs across {len(domains)} domains") - print(f" Changes: +{added} -{removed} lines") - - if not args.yes: - try: - response = input("\nUpdate index? [y/N]: ") - if response.lower() != 'y': - print("Skipped.") - return 0 - except (KeyboardInterrupt, EOFError): - print("\nSkipped.") - return 0 - - index_path.write_text(content) - print(f"Updated: {relative_path(index_path)}") - return 0 - -def cmd_domains(args): - """List available domains.""" - domains = get_domains() - - project = get_config().get('project_name', 'Project') - print(f"\n{project} — ADR Domain Number Series") - print("=" * 60) - print(f"(from {relative_path(get_config_path())})") - - for domain, config in domains.items(): - r = config['range'] - folders = config['folder'] - if isinstance(folders, list): - folder_str = ', '.join(f"{f}/" for f in folders) - else: - folder_str = f"{folders}/" - print(f"\n {domain:8} ({r[0]:3}-{r[1]:3}) {config['name']}") - print(f" {config['description']}") - print(f" Folder: {folder_str}") - - # Show legacy range - legacy = get_config().get('legacy', {}) - if legacy: - r = legacy.get('range', (1, 99)) - print(f"\n {'legacy':8} ({r[0]:3}-{r[1]:3}) {legacy.get('label', 'Legacy')}") - - print() - return 0 - -def cmd_config(args): - """Show current configuration.""" - config_path = get_config_path() - - print(f"\nConfig file: {config_path}") - print("-" * 60) - - try: - print(config_path.read_text()) - except Exception as e: - print(f"Error reading config: {e}", file=sys.stderr) - return 1 - - return 0 - -# ============================================================================ -# Main -# ============================================================================ - -def main(): - parser = argparse.ArgumentParser( - description='ADR - Architecture Decision Record CLI Tool', - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=__doc__ - ) - subparsers = parser.add_subparsers(dest='command', help='Command') - - # list - p_list = subparsers.add_parser('list', aliases=['ls'], help='List ADRs') - p_list.add_argument('--domain', '-d', help='Filter by domain') - p_list.add_argument('--status', '-s', help='Filter by status') - p_list.add_argument('--group', '-g', action='store_true', - help='Group by domain') - - # view - p_view = subparsers.add_parser('view', aliases=['v', 'show'], help='View an ADR') - p_view.add_argument('adr', help='ADR number (e.g., 38, 038, ADR-038)') - - # new - p_new = subparsers.add_parser('new', help='Create new ADR') - p_new.add_argument('domain', help='Domain (see `adr domains` for list)') - p_new.add_argument('title', help='ADR title') - - # lint - p_lint = subparsers.add_parser('lint', help='Lint ADR files') - p_lint.add_argument('paths', nargs='*', help='Specific files to lint') - p_lint.add_argument('--check', action='store_true', help='Exit 1 if errors (CI mode)') - - # index - index_parser = subparsers.add_parser('index', help='Generate ADR index') - index_parser.add_argument('-y', '--yes', action='store_true', - help='Update without prompting') - - # domains - subparsers.add_parser('domains', help='List domain number series') - - # config - subparsers.add_parser('config', help='Show configuration') - - args = parser.parse_args() - - if not args.command: - parser.print_help() - return 0 - - commands = { - 'list': cmd_list, - 'ls': cmd_list, - 'view': cmd_view, - 'v': cmd_view, - 'show': cmd_view, - 'new': cmd_new, - 'lint': cmd_lint, - 'index': cmd_index, - 'domains': cmd_domains, - 'config': cmd_config, - } - - return commands[args.command](args) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/hooks/ways/softwaredev/adr-context/macro.sh b/hooks/ways/softwaredev/architecture/adr-context/macro.sh similarity index 100% rename from hooks/ways/softwaredev/adr-context/macro.sh rename to hooks/ways/softwaredev/architecture/adr-context/macro.sh diff --git a/hooks/ways/softwaredev/adr-context/way.md b/hooks/ways/softwaredev/architecture/adr-context/way.md similarity index 100% rename from hooks/ways/softwaredev/adr-context/way.md rename to hooks/ways/softwaredev/architecture/adr-context/way.md diff --git a/hooks/ways/softwaredev/adr/adr.yaml.template b/hooks/ways/softwaredev/architecture/adr/adr.yaml.template similarity index 100% rename from hooks/ways/softwaredev/adr/adr.yaml.template rename to hooks/ways/softwaredev/architecture/adr/adr.yaml.template diff --git a/hooks/ways/softwaredev/adr/macro.sh b/hooks/ways/softwaredev/architecture/adr/macro.sh similarity index 87% rename from hooks/ways/softwaredev/adr/macro.sh rename to hooks/ways/softwaredev/architecture/adr/macro.sh index 9cb99f2..d379257 100755 --- a/hooks/ways/softwaredev/adr/macro.sh +++ b/hooks/ways/softwaredev/architecture/adr/macro.sh @@ -40,7 +40,7 @@ if [[ -n "$ADR_SCRIPT" ]]; then echo "**Always use \`$ADR_SCRIPT new\` to create ADRs** — it handles numbering, domain routing, and templates." # Check if project script differs from universal template - UNIVERSAL="${HOME}/.claude/hooks/ways/softwaredev/adr/adr-tool" + UNIVERSAL="${HOME}/.claude/hooks/ways/softwaredev/architecture/adr/adr-tool" if [[ -f "$UNIVERSAL" ]] && ! diff -q "$PROJECT_DIR/$ADR_SCRIPT" "$UNIVERSAL" &>/dev/null; then echo "" echo "_Note: Project script differs from the universal template. This is expected for customized setups._" @@ -58,6 +58,6 @@ echo "- Template generation with frontmatter" echo "- Linting and validation" echo "- Index generation" echo "" -echo "To install: \`mkdir -p docs/scripts && cp ~/.claude/hooks/ways/softwaredev/adr/adr-tool docs/scripts/adr && chmod +x docs/scripts/adr && mkdir -p docs/architecture && cp ~/.claude/hooks/ways/softwaredev/adr/adr.yaml.template docs/architecture/adr.yaml\`" +echo "To install: \`mkdir -p docs/scripts && cp ~/.claude/hooks/ways/softwaredev/architecture/adr/adr-tool docs/scripts/adr && chmod +x docs/scripts/adr && mkdir -p docs/architecture && cp ~/.claude/hooks/ways/softwaredev/architecture/adr/adr.yaml.template docs/architecture/adr.yaml\`" echo "" echo "To decline permanently: \`mkdir -p .claude && touch .claude/no-adr-tooling\`" diff --git a/hooks/ways/softwaredev/adr/migration/way.md b/hooks/ways/softwaredev/architecture/adr/migration/way.md similarity index 95% rename from hooks/ways/softwaredev/adr/migration/way.md rename to hooks/ways/softwaredev/architecture/adr/migration/way.md index c38e2d6..ba2c86b 100644 --- a/hooks/ways/softwaredev/adr/migration/way.md +++ b/hooks/ways/softwaredev/architecture/adr/migration/way.md @@ -42,13 +42,13 @@ mkdir -p docs/architecture docs/scripts 2. Create `docs/architecture/adr.yaml` from the template: ```bash -cp hooks/ways/softwaredev/adr/adr.yaml.template docs/architecture/adr.yaml +cp hooks/ways/softwaredev/architecture/adr/adr.yaml.template docs/architecture/adr.yaml # Edit: set project_name, define domains for your project ``` 3. Symlink or copy the ADR tool: ```bash -ln -s ../../hooks/ways/softwaredev/adr/adr-tool docs/scripts/adr +ln -s ../../hooks/ways/softwaredev/architecture/adr/adr-tool docs/scripts/adr chmod +x docs/scripts/adr ``` @@ -162,7 +162,7 @@ viewer: cat {file} # Command for `adr view` ({file} is placeholder - Don't overlap ranges — the tool assigns the next available number within a domain's range - `folder` can be a string or list (for domains spanning multiple directories) -**A template is available** at `hooks/ways/softwaredev/adr/adr.yaml.template`. +**A template is available** at `hooks/ways/softwaredev/architecture/adr/adr.yaml.template`. ## Validation diff --git a/hooks/ways/softwaredev/adr/way.md b/hooks/ways/softwaredev/architecture/adr/way.md similarity index 99% rename from hooks/ways/softwaredev/adr/way.md rename to hooks/ways/softwaredev/architecture/adr/way.md index 3090c0b..0c1ab14 100644 --- a/hooks/ways/softwaredev/adr/way.md +++ b/hooks/ways/softwaredev/architecture/adr/way.md @@ -53,7 +53,7 @@ provenance: ``` docs/ -├── scripts/adr # CLI tool (symlink to hooks/ways/softwaredev/adr/adr-tool) +├── scripts/adr # CLI tool (symlink to hooks/ways/softwaredev/architecture/adr/adr-tool) └── architecture/ ├── adr.yaml # Domain config: number ranges, statuses, defaults ├── INDEX.md # Auto-generated index (adr index -y) diff --git a/hooks/ways/softwaredev/design/way.md b/hooks/ways/softwaredev/architecture/design/way.md similarity index 100% rename from hooks/ways/softwaredev/design/way.md rename to hooks/ways/softwaredev/architecture/design/way.md diff --git a/hooks/ways/softwaredev/errors/way.md b/hooks/ways/softwaredev/code/errors/way.md similarity index 92% rename from hooks/ways/softwaredev/errors/way.md rename to hooks/ways/softwaredev/code/errors/way.md index 62fab70..4577d74 100644 --- a/hooks/ways/softwaredev/errors/way.md +++ b/hooks/ways/softwaredev/code/errors/way.md @@ -1,4 +1,7 @@ --- +description: error handling patterns, exception management, try-catch boundaries, error wrapping and propagation +vocabulary: exception handling catch throw boundary wrap rethrow fallback graceful recovery propagate unhandled +threshold: 2.0 pattern: error.?handl|exception|try.?catch|throw|catch scope: agent, subagent provenance: diff --git a/hooks/ways/softwaredev/performance/way.md b/hooks/ways/softwaredev/code/performance/way.md similarity index 84% rename from hooks/ways/softwaredev/performance/way.md rename to hooks/ways/softwaredev/code/performance/way.md index 351a4cc..c09eb65 100644 --- a/hooks/ways/softwaredev/performance/way.md +++ b/hooks/ways/softwaredev/code/performance/way.md @@ -1,4 +1,7 @@ --- +description: performance optimization, profiling, benchmarking, latency reduction, memory efficiency +vocabulary: optimize profile benchmark latency throughput memory cache bottleneck flamegraph allocation heap +threshold: 2.0 pattern: slow|optimi|latency|profile|performance|speed.?up|benchmark|bottleneck|throughput|memory.?leak scope: agent, subagent --- diff --git a/hooks/ways/softwaredev/quality/macro.sh b/hooks/ways/softwaredev/code/quality/macro.sh similarity index 100% rename from hooks/ways/softwaredev/quality/macro.sh rename to hooks/ways/softwaredev/code/quality/macro.sh diff --git a/hooks/ways/softwaredev/quality/way.md b/hooks/ways/softwaredev/code/quality/way.md similarity index 90% rename from hooks/ways/softwaredev/quality/way.md rename to hooks/ways/softwaredev/code/quality/way.md index e341556..d913b0f 100644 --- a/hooks/ways/softwaredev/quality/way.md +++ b/hooks/ways/softwaredev/code/quality/way.md @@ -1,4 +1,7 @@ --- +description: code quality, refactoring, SOLID principles, code review standards, technical debt, maintainability +vocabulary: refactor quality solid principle decompose extract method responsibility coupling cohesion maintainability readability +threshold: 2.0 pattern: solid.?principle|refactor|code.?review|code.?quality|clean.?up|simplify|decompos|extract.?method|tech.?debt macro: append scan_exclude: \.md$|\.lock$|\.min\.(js|css)$|\.generated\.|\.bundle\.|vendor/|node_modules/|dist/|build/|__pycache__/ diff --git a/hooks/ways/softwaredev/security/way.md b/hooks/ways/softwaredev/code/security/way.md similarity index 100% rename from hooks/ways/softwaredev/security/way.md rename to hooks/ways/softwaredev/code/security/way.md diff --git a/hooks/ways/softwaredev/testing/way.md b/hooks/ways/softwaredev/code/testing/way.md similarity index 100% rename from hooks/ways/softwaredev/testing/way.md rename to hooks/ways/softwaredev/code/testing/way.md diff --git a/hooks/ways/softwaredev/commits/way.md b/hooks/ways/softwaredev/delivery/commits/way.md similarity index 90% rename from hooks/ways/softwaredev/commits/way.md rename to hooks/ways/softwaredev/delivery/commits/way.md index 8ea634e..0c70e49 100644 --- a/hooks/ways/softwaredev/commits/way.md +++ b/hooks/ways/softwaredev/delivery/commits/way.md @@ -1,4 +1,7 @@ --- +description: git commit messages, branch naming, conventional commits, atomic changes +vocabulary: commit message branch conventional feat fix refactor scope atomic squash amend stash rebase cherry +threshold: 2.0 pattern: commit|push.*(remote|origin|upstream) commands: git\ commit scope: agent, subagent diff --git a/hooks/ways/softwaredev/github/macro.sh b/hooks/ways/softwaredev/delivery/github/macro.sh similarity index 100% rename from hooks/ways/softwaredev/github/macro.sh rename to hooks/ways/softwaredev/delivery/github/macro.sh diff --git a/hooks/ways/softwaredev/github/way.md b/hooks/ways/softwaredev/delivery/github/way.md similarity index 94% rename from hooks/ways/softwaredev/github/way.md rename to hooks/ways/softwaredev/delivery/github/way.md index 0d37fae..c3e7086 100644 --- a/hooks/ways/softwaredev/github/way.md +++ b/hooks/ways/softwaredev/delivery/github/way.md @@ -1,4 +1,7 @@ --- +description: GitHub pull requests, issues, code review, CI checks, repository management +vocabulary: pr pullrequest issue review checks ci label milestone fork repository upstream draft +threshold: 2.0 pattern: github|\ issue|pull.?request|\ pr\ |\ pr$|review.?(pr|comment)|merge.?request commands: ^gh\ |^gh$ macro: prepend diff --git a/hooks/ways/softwaredev/migrations/way.md b/hooks/ways/softwaredev/delivery/migrations/way.md similarity index 84% rename from hooks/ways/softwaredev/migrations/way.md rename to hooks/ways/softwaredev/delivery/migrations/way.md index 17f8da0..7dfc493 100644 --- a/hooks/ways/softwaredev/migrations/way.md +++ b/hooks/ways/softwaredev/delivery/migrations/way.md @@ -1,4 +1,7 @@ --- +description: database migrations, schema changes, table alterations, rollback procedures +vocabulary: migration schema alter table column index rollback seed ddl prisma alembic knex flyway +threshold: 2.0 pattern: migrat|schema|database.?change|alter.?table|alembic|prisma.?migrate|knex.?migrate|flyway|liquibase scope: agent, subagent --- diff --git a/hooks/ways/softwaredev/patches/way.md b/hooks/ways/softwaredev/delivery/patches/way.md similarity index 90% rename from hooks/ways/softwaredev/patches/way.md rename to hooks/ways/softwaredev/delivery/patches/way.md index 1573193..80e770b 100644 --- a/hooks/ways/softwaredev/patches/way.md +++ b/hooks/ways/softwaredev/delivery/patches/way.md @@ -1,4 +1,7 @@ --- +description: creating and applying patch files, git diff generation, patch series management +vocabulary: patch diff apply hunk unified series format-patch +threshold: 2.0 pattern: patch|\.diff|apply.*change files: \.(patch|diff)$ commands: git\ apply|git\ diff.*\> diff --git a/hooks/ways/softwaredev/release/way.md b/hooks/ways/softwaredev/delivery/release/way.md similarity index 92% rename from hooks/ways/softwaredev/release/way.md rename to hooks/ways/softwaredev/delivery/release/way.md index 565d9c0..78a18ec 100644 --- a/hooks/ways/softwaredev/release/way.md +++ b/hooks/ways/softwaredev/delivery/release/way.md @@ -1,4 +1,7 @@ --- +description: software releases, changelog generation, version bumping, semantic versioning, tagging +vocabulary: release changelog version bump semver tag publish ship major minor breaking +threshold: 2.0 pattern: release|changelog|tag|version.?bump|bump.?version|npm.?publish|cargo.?publish scope: agent, subagent provenance: diff --git a/hooks/ways/softwaredev/api/way.md b/hooks/ways/softwaredev/docs/api/way.md similarity index 100% rename from hooks/ways/softwaredev/api/way.md rename to hooks/ways/softwaredev/docs/api/way.md diff --git a/hooks/ways/softwaredev/docs/way.md b/hooks/ways/softwaredev/docs/way.md index 96e14f2..7650b5e 100644 --- a/hooks/ways/softwaredev/docs/way.md +++ b/hooks/ways/softwaredev/docs/way.md @@ -1,4 +1,7 @@ --- +description: README authoring, docstrings, technical prose, Mermaid diagrams, project guides +vocabulary: readme docstring technical writing mermaid diagram flowchart sequence onboarding +threshold: 2.0 pattern: readme|documentation|docs|document.*project|explain.*repo|docstring|mermaid|diagram files: README\.md$|docs/.*\.md$ scope: agent, subagent diff --git a/hooks/ways/softwaredev/config/way.md b/hooks/ways/softwaredev/environment/config/way.md similarity index 100% rename from hooks/ways/softwaredev/config/way.md rename to hooks/ways/softwaredev/environment/config/way.md diff --git a/hooks/ways/softwaredev/debugging/way.md b/hooks/ways/softwaredev/environment/debugging/way.md similarity index 100% rename from hooks/ways/softwaredev/debugging/way.md rename to hooks/ways/softwaredev/environment/debugging/way.md diff --git a/hooks/ways/softwaredev/deps/way.md b/hooks/ways/softwaredev/environment/deps/way.md similarity index 90% rename from hooks/ways/softwaredev/deps/way.md rename to hooks/ways/softwaredev/environment/deps/way.md index 4641615..52e1859 100644 --- a/hooks/ways/softwaredev/deps/way.md +++ b/hooks/ways/softwaredev/environment/deps/way.md @@ -1,4 +1,7 @@ --- +description: dependency management, package installation, library evaluation, security auditing of third-party code +vocabulary: dependency package library install upgrade outdated audit vulnerability license bundle npm pip cargo +threshold: 2.0 pattern: dependenc|package|library|npm.?install|pip.?install|upgrade.*version commands: npm\ install|yarn\ add|pip\ install|cargo\ add|go\ get scope: agent, subagent diff --git a/hooks/ways/softwaredev/ssh/way.md b/hooks/ways/softwaredev/environment/ssh/way.md similarity index 95% rename from hooks/ways/softwaredev/ssh/way.md rename to hooks/ways/softwaredev/environment/ssh/way.md index 8a16286..f3ee37b 100644 --- a/hooks/ways/softwaredev/ssh/way.md +++ b/hooks/ways/softwaredev/environment/ssh/way.md @@ -1,4 +1,7 @@ --- +description: SSH remote access, key management, secure file transfer, non-interactive authentication +vocabulary: ssh remote key agent scp rsync bastion jumphost tunnel forwarding batchmode noninteractive +threshold: 2.0 pattern: ssh|remote.?server|remote.?host|sshpass commands: ^ssh\ |^scp\ |^rsync.*:|\bsshpass\b scope: agent, subagent diff --git a/tests/README.md b/tests/README.md index 5672238..3b4cf8d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -13,7 +13,7 @@ Three layers, from fast/automated to slow/interactive. See [way-match/results.md ### 1. Fixture Tests (BM25 vs NCD scorer comparison) -Runs 32 test prompts against a fixed 7-way corpus. Compares BM25 binary against gzip NCD baseline. Reports TP/FP/TN/FN for each scorer. +Runs 54 test prompts against a fixed 18-way corpus (all softwaredev ways with BM25 semantic matching). Compares BM25 binary against gzip NCD baseline. Reports TP/FP/TN/FN for each scorer. ```bash tests/way-match/run-tests.sh fixture --verbose @@ -25,7 +25,7 @@ Options: `--bm25-only`, `--ncd-only`, `--verbose` **What it covers**: Scorer accuracy, false positive rate, head-to-head comparison. Tests direct vocabulary matches, synonym/paraphrase variants, and negative controls. -**Current baseline**: BM25 26/32, NCD 24/32, 0 FP for both. +**Current baseline**: BM25 48/54, 0 FP. ### 2. Integration Tests (real way files) @@ -43,7 +43,7 @@ bash tools/way-match/test-integration.sh ### 3. Activation Test (live agent + subagent) -Interactive test protocol that verifies the full hook pipeline in a running Claude Code session. Tests regex matching, BM25 semantic matching, negative controls, and subagent injection. +Interactive test protocol that verifies the full hook pipeline in a running Claude Code session. Tests regex matching, BM25 semantic matching (established and newly-added vocabularies), co-activation of related ways, negative controls, and subagent injection. **To run**: Start a fresh session from `~/.claude/` and type: @@ -51,19 +51,21 @@ Interactive test protocol that verifies the full hook pipeline in a running Clau read and run the activation test at tests/way-activation-test.md ``` -Claude reads the test file (avoiding prompt-hook contamination), then walks you through 7 steps: +Claude reads the test file (avoiding prompt-hook contamination), then walks you through 9 steps: | Step | Who | Tests | |------|-----|-------| | 1 | Claude | Session baseline (no premature domain activation) | -| 2 | User types prompt | Regex pattern matching (commits way) | -| 3 | User types prompt | BM25 semantic matching (security way) | -| 4 | User types prompt | Negative control (no false positives) | -| 5 | Claude | Subagent injection (Testing Way via SubagentStart) | -| 6 | Claude | Subagent negative (no injection on irrelevant prompt) | -| 7 | Claude | Summary table | +| 2 | User types prompt | Regex pattern matching (delivery/commits) | +| 3 | User types prompt | BM25 semantic matching, established way (code/security) | +| 4 | User types prompt | BM25 semantic matching, newly-semantic way (code/performance) | +| 5 | User types prompt | Co-activation of multiple related ways (delivery/migrations + others) | +| 6 | User types prompt | Negative control (no false positives) | +| 7 | Claude | Subagent injection (Testing Way via SubagentStart) | +| 8 | Claude | Subagent negative (no fresh injection; parent context OK) | +| 9 | Claude | Summary table | -Takes about 3 minutes. **Current baseline**: 6/6 PASS. +Takes about 5 minutes. **Current baseline**: 8/8 PASS (steps 1-8). ### Ad-Hoc Vocabulary Testing @@ -108,6 +110,8 @@ bash governance/governance.sh --lint # full governance lint | Changed a way's vocabulary or threshold | Integration tests + `/test-way` | | Changed hook scripts (check-*.sh, inject-*.sh, match-way.sh) | Activation test | | Added a new way | Integration tests + `/test-way` + activation test | +| Restructured way directories | All three test layers + symlink/path verification | +| Added semantic matching to a way | Fixture tests + integration tests + activation test (step 4) | | Renamed or moved documentation files | Doc-graph | | Changed provenance metadata in way frontmatter | Governance verification | | Changed policy source documents | Governance verification | diff --git a/tests/way-activation-test.md b/tests/way-activation-test.md index 9d28383..574ffe6 100644 --- a/tests/way-activation-test.md +++ b/tests/way-activation-test.md @@ -35,21 +35,41 @@ After reading this file, begin with Step 1. > **CLAUDE**: After the user sends that message, check if you received new domain-specific content in a system-reminder. Look for guidance about message conventions, branch naming, or attribution rules. Report what fired. -**Expected**: The commits way should fire (regex pattern: `commit|push.*(remote|origin|upstream)`). You should see guidance about conventional commit format and branch naming. +**Expected**: The commits way (`delivery/commits`) should fire (regex pattern: `commit|push.*(remote|origin|upstream)`). You should see guidance about conventional commit format and branch naming. --- -### Step 3 — Semantic trigger (BM25) +### Step 3 — Semantic trigger (BM25, established way) > **USER**: Type exactly: `how should I hash passwords with bcrypt for our login system?` > **CLAUDE**: Check if you received new domain-specific content. Look for guidance about vulnerability categories, credential handling, input validation, or defensive defaults. Report what fired. -**Expected**: The security way should fire via BM25 semantic matching (vocabulary includes bcrypt, hash, password, authentication, login). You should see detection rules and security defaults. +**Expected**: The security way (`code/security`) should fire via BM25 semantic matching (vocabulary includes bcrypt, hash, password, authentication, login). You should see detection rules and security defaults. --- -### Step 4 — Negative test (no false positive) +### Step 4 — Semantic trigger (BM25, newly-semantic way) + +> **USER**: Type exactly: `profile the rendering loop to find the bottleneck and reduce latency` + +> **CLAUDE**: Check if you received new domain-specific content. Look for guidance about profiling tools, algorithmic analysis, benchmarking, or measurement approaches. Report what fired. + +**Expected**: The performance way (`code/performance`) should fire via BM25 semantic matching. This way previously only had regex triggers — the vocabulary (optimize, profile, benchmark, latency, bottleneck, etc.) was added during the taxonomy restructure. You should see guidance about static analysis for algorithmic issues and generating before/after measurements. + +--- + +### Step 5 — Co-activation test (multiple related ways) + +> **USER**: Type exactly: `create a migration to alter the users table and add an index on the email column` + +> **CLAUDE**: Check how many domain-specific ways were injected. List each one by name/heading. Report which ways fired and whether they provide complementary guidance. + +**Expected**: The migrations way (`delivery/migrations`) should fire — the prompt contains vocabulary terms (migration, alter, table, column, index). Other ways MAY also co-activate if they share relevant terms (e.g., design via "schema" concepts). Co-activation of related ways is expected and correct — each adds a different lens. Report all ways that fired. + +--- + +### Step 6 — Negative test (no false positive) > **USER**: Type exactly: `what's the weather like today?` @@ -59,7 +79,7 @@ After reading this file, begin with Step 1. --- -### Step 5 — Subagent injection (the critical path) +### Step 7 — Subagent injection (the critical path) > **CLAUDE**: Spawn a diagnostic subagent with this exact configuration: > - Use the Task tool with subagent_type: `general-purpose` @@ -78,7 +98,7 @@ If the subagent sees NO injected content beyond the base configuration, the inje --- -### Step 6 — Subagent negative test +### Step 8 — Subagent negative test > **CLAUDE**: Spawn another diagnostic subagent: > - Use the Task tool with subagent_type: `general-purpose` @@ -87,21 +107,28 @@ If the subagent sees NO injected content beyond the base configuration, the inje > > Report the subagent's findings. -**Expected**: The subagent should NOT receive domain-specific procedural content. The background phrase has no relevance to any hook vocabulary. Only base configuration content should appear. If domain-specific content (about code, operations, or tooling) appears, that is a false positive. +**Expected**: The SubagentStart **injection pipeline** should NOT fire — no stash is created because "what time is it in Tokyo" has zero overlap with any way vocabulary. However, `general-purpose` subagents inherit the parent conversation context, so they will see ways that fired earlier in the session (e.g., Performance Way from Step 4). This is context inheritance, not injection. + +**How to evaluate**: The subagent should report seeing parent-context content (expected) but should NOT report a SubagentStart system-reminder block with new domain-specific content beyond what already appeared in the parent session. Compare against Step 7 — that subagent should have received a *fresh* Testing Way block via SubagentStart injection. This subagent should have no such fresh injection. --- -### Step 7 — Summary +### Step 9 — Summary > **CLAUDE**: Compile a summary table: > -> | Step | Test | Expected | Result | -> |------|------|----------|--------| -> | 1 | Session baseline | No domain-specific hooks | ? | -> | 2 | Regex keyword match | Commits way fires | ? | -> | 3 | BM25 semantic match | Security way fires | ? | -> | 4 | Negative (no match) | Nothing fires | ? | -> | 5 | Subagent injection | Testing Way received | ? | -> | 6 | Subagent negative | No domain content received | ? | +> | Step | Test | Cluster | Expected | Result | +> |------|------|---------|----------|--------| +> | 1 | Session baseline | — | No domain-specific hooks | ? | +> | 2 | Regex keyword match | delivery | Commits way fires | ? | +> | 3 | BM25 semantic (established) | code | Security way fires | ? | +> | 4 | BM25 semantic (new vocabulary) | code | Performance way fires | ? | +> | 5 | Co-activation | delivery+architecture | Migrations fires, others may join | ? | +> | 6 | Negative (no match) | — | Nothing fires | ? | +> | 7 | Subagent injection | code | Testing Way received | ? | +> | 8 | Subagent negative | — | No fresh injection (parent context OK) | ? | > -> Report the final pass/fail count and any observations. +> Report the final pass/fail count and any observations about: +> - Whether the taxonomy restructure affected hook delivery +> - Whether newly-semantic ways activate correctly +> - Whether co-activation produced useful complementary context diff --git a/tools/way-match/test-fixtures.jsonl b/tools/way-match/test-fixtures.jsonl index a2e35bf..b0fac6f 100644 --- a/tools/way-match/test-fixtures.jsonl +++ b/tools/way-match/test-fixtures.jsonl @@ -1,29 +1,51 @@ -{"prompt": "add unit tests for the auth module", "expected": "softwaredev-testing", "match": true, "category": "direct"} -{"prompt": "write pytest fixtures for the database layer", "expected": "softwaredev-testing", "match": true, "category": "direct"} -{"prompt": "increase test coverage to 80%", "expected": "softwaredev-testing", "match": true, "category": "direct"} -{"prompt": "mock the payment gateway in tests", "expected": "softwaredev-testing", "match": true, "category": "direct"} -{"prompt": "how should I structure the REST API endpoints", "expected": "softwaredev-api", "match": true, "category": "direct"} -{"prompt": "add pagination to the users endpoint", "expected": "softwaredev-api", "match": true, "category": "direct"} -{"prompt": "what HTTP status code for a deleted resource", "expected": "softwaredev-api", "match": true, "category": "direct"} -{"prompt": "debug this segfault in the parser", "expected": "softwaredev-debugging", "match": true, "category": "direct"} -{"prompt": "investigate why the build is broken", "expected": "softwaredev-debugging", "match": true, "category": "direct"} -{"prompt": "add a breakpoint and step through the logic", "expected": "softwaredev-debugging", "match": true, "category": "direct"} -{"prompt": "fix the XSS vulnerability in the comment form", "expected": "softwaredev-security", "match": true, "category": "direct"} -{"prompt": "rotate the database credentials", "expected": "softwaredev-security", "match": true, "category": "direct"} -{"prompt": "sanitize user input before SQL queries", "expected": "softwaredev-security", "match": true, "category": "direct"} -{"prompt": "design the database schema for orders", "expected": "softwaredev-design", "match": true, "category": "direct"} -{"prompt": "which architecture pattern for the notification service", "expected": "softwaredev-design", "match": true, "category": "direct"} -{"prompt": "set up environment variables for staging", "expected": "softwaredev-config", "match": true, "category": "direct"} -{"prompt": "manage the dotenv files across environments", "expected": "softwaredev-config", "match": true, "category": "direct"} -{"prompt": "plan how to implement the search feature", "expected": "softwaredev-adr-context", "match": true, "category": "direct"} -{"prompt": "why was the caching layer built this way", "expected": "softwaredev-adr-context", "match": true, "category": "direct"} -{"prompt": "speed up the SQL queries", "expected": "softwaredev-design", "match": true, "category": "synonym", "note": "NCD likely misses: no shared bytes with design description"} -{"prompt": "harden the login endpoints against brute force", "expected": "softwaredev-security", "match": true, "category": "synonym", "note": "NCD likely misses: harden not in vocabulary"} -{"prompt": "the app crashes when you submit the form", "expected": "softwaredev-debugging", "match": true, "category": "synonym", "note": "NCD likely misses: crash/submit not in debugging vocabulary"} -{"prompt": "verify the service handles bad input gracefully", "expected": "softwaredev-testing", "match": true, "category": "synonym", "note": "NCD likely misses: verify/gracefully not in testing vocabulary"} -{"prompt": "set up the connection string for postgres", "expected": "softwaredev-config", "match": true, "category": "synonym", "note": "NCD likely misses: connection string is config concept but not in vocabulary"} -{"prompt": "how do the microservices talk to each other", "expected": "softwaredev-design", "match": true, "category": "synonym", "note": "NCD likely misses: microservices/talk are design concepts but informal"} -{"prompt": "make the API respond with proper error codes", "expected": "softwaredev-api", "match": true, "category": "synonym", "note": "NCD may match via api but error codes is the real signal"} +{"prompt": "add unit tests for the auth module", "expected": "softwaredev-code-testing", "match": true, "category": "direct"} +{"prompt": "write pytest fixtures for the database layer", "expected": "softwaredev-code-testing", "match": true, "category": "direct"} +{"prompt": "increase test coverage to 80%", "expected": "softwaredev-code-testing", "match": true, "category": "direct"} +{"prompt": "mock the payment gateway in tests", "expected": "softwaredev-code-testing", "match": true, "category": "direct"} +{"prompt": "how should I structure the REST API endpoints", "expected": "softwaredev-docs-api", "match": true, "category": "direct"} +{"prompt": "add pagination to the users endpoint", "expected": "softwaredev-docs-api", "match": true, "category": "direct"} +{"prompt": "what HTTP status code for a deleted resource", "expected": "softwaredev-docs-api", "match": true, "category": "direct"} +{"prompt": "debug this segfault in the parser", "expected": "softwaredev-environment-debugging", "match": true, "category": "direct"} +{"prompt": "investigate why the build is broken", "expected": "softwaredev-environment-debugging", "match": true, "category": "direct"} +{"prompt": "add a breakpoint and step through the logic", "expected": "softwaredev-environment-debugging", "match": true, "category": "direct"} +{"prompt": "fix the XSS vulnerability in the comment form", "expected": "softwaredev-code-security", "match": true, "category": "direct"} +{"prompt": "rotate the database credentials", "expected": "softwaredev-code-security", "match": true, "category": "direct"} +{"prompt": "sanitize user input before SQL queries", "expected": "softwaredev-code-security", "match": true, "category": "direct"} +{"prompt": "design the database schema for orders", "expected": "softwaredev-architecture-design", "match": true, "category": "direct"} +{"prompt": "which architecture pattern for the notification service", "expected": "softwaredev-architecture-design", "match": true, "category": "direct"} +{"prompt": "set up environment variables for staging", "expected": "softwaredev-environment-config", "match": true, "category": "direct"} +{"prompt": "manage the dotenv files across environments", "expected": "softwaredev-environment-config", "match": true, "category": "direct"} +{"prompt": "plan how to implement the search feature", "expected": "softwaredev-architecture-adr-context", "match": true, "category": "direct"} +{"prompt": "why was the caching layer built this way", "expected": "softwaredev-architecture-adr-context", "match": true, "category": "direct"} +{"prompt": "write a conventional commit message for this change", "expected": "softwaredev-delivery-commits", "match": true, "category": "direct"} +{"prompt": "should I squash these commits before merging", "expected": "softwaredev-delivery-commits", "match": true, "category": "direct"} +{"prompt": "create a pull request for the auth feature", "expected": "softwaredev-delivery-github", "match": true, "category": "direct"} +{"prompt": "check the CI status on this PR", "expected": "softwaredev-delivery-github", "match": true, "category": "direct"} +{"prompt": "generate a patch from my local changes", "expected": "softwaredev-delivery-patches", "match": true, "category": "direct"} +{"prompt": "apply this diff to the codebase", "expected": "softwaredev-delivery-patches", "match": true, "category": "direct"} +{"prompt": "bump the version and generate a changelog", "expected": "softwaredev-delivery-release", "match": true, "category": "direct"} +{"prompt": "tag a new release with semver", "expected": "softwaredev-delivery-release", "match": true, "category": "direct"} +{"prompt": "create a database migration to add a column", "expected": "softwaredev-delivery-migrations", "match": true, "category": "direct"} +{"prompt": "write the rollback for this schema change", "expected": "softwaredev-delivery-migrations", "match": true, "category": "direct"} +{"prompt": "add proper exception handling to this function", "expected": "softwaredev-code-errors", "match": true, "category": "direct"} +{"prompt": "should I catch this error at the boundary or propagate it", "expected": "softwaredev-code-errors", "match": true, "category": "direct"} +{"prompt": "refactor this class to follow single responsibility", "expected": "softwaredev-code-quality", "match": true, "category": "direct"} +{"prompt": "decompose this large function into smaller methods", "expected": "softwaredev-code-quality", "match": true, "category": "direct"} +{"prompt": "profile this function to find the bottleneck", "expected": "softwaredev-code-performance", "match": true, "category": "direct"} +{"prompt": "benchmark the memory allocation in this loop", "expected": "softwaredev-code-performance", "match": true, "category": "direct"} +{"prompt": "is this npm package well maintained before I install it", "expected": "softwaredev-environment-deps", "match": true, "category": "direct"} +{"prompt": "audit our dependencies for vulnerabilities", "expected": "softwaredev-environment-deps", "match": true, "category": "direct"} +{"prompt": "set up SSH key-based access to the server", "expected": "softwaredev-environment-ssh", "match": true, "category": "direct"} +{"prompt": "transfer files to the remote host with rsync", "expected": "softwaredev-environment-ssh", "match": true, "category": "direct"} +{"prompt": "write a README for this project", "expected": "softwaredev-docs", "match": true, "category": "direct"} +{"prompt": "add a mermaid diagram to the documentation", "expected": "softwaredev-docs", "match": true, "category": "direct"} +{"prompt": "speed up the SQL queries", "expected": "softwaredev-architecture-design", "match": true, "category": "synonym", "note": "NCD likely misses: no shared bytes with design description"} +{"prompt": "harden the login endpoints against brute force", "expected": "softwaredev-code-security", "match": true, "category": "synonym", "note": "NCD likely misses: harden not in vocabulary"} +{"prompt": "the app crashes when you submit the form", "expected": "softwaredev-environment-debugging", "match": true, "category": "synonym", "note": "NCD likely misses: crash/submit not in debugging vocabulary"} +{"prompt": "verify the service handles bad input gracefully", "expected": "softwaredev-code-testing", "match": true, "category": "synonym", "note": "NCD likely misses: verify/gracefully not in testing vocabulary"} +{"prompt": "set up the connection string for postgres", "expected": "softwaredev-environment-config", "match": true, "category": "synonym", "note": "NCD likely misses: connection string is config concept but not in vocabulary"} +{"prompt": "how do the microservices talk to each other", "expected": "softwaredev-architecture-design", "match": true, "category": "synonym", "note": "NCD likely misses: microservices/talk are design concepts but informal"} +{"prompt": "make the API respond with proper error codes", "expected": "softwaredev-docs-api", "match": true, "category": "synonym", "note": "NCD may match via api but error codes is the real signal"} {"prompt": "what's the weather today", "expected": null, "match": false, "category": "negative"} {"prompt": "hello", "expected": null, "match": false, "category": "negative"} {"prompt": "tell me a joke about programmers", "expected": null, "match": false, "category": "negative"} diff --git a/tools/way-match/test-harness.sh b/tools/way-match/test-harness.sh index d95baf0..2f51da5 100755 --- a/tools/way-match/test-harness.sh +++ b/tools/way-match/test-harness.sh @@ -16,35 +16,79 @@ BM25_BINARY="$SCRIPT_DIR/../../bin/way-match" # Way corpus: id|description|vocabulary|threshold declare -A WAY_DESC WAY_VOCAB WAY_THRESH -WAY_DESC[softwaredev-testing]="writing unit tests, test coverage, mocking dependencies, test-driven development" -WAY_VOCAB[softwaredev-testing]="unittest coverage mock tdd assertion jest pytest rspec testcase spec fixture describe expect verify" -WAY_THRESH[softwaredev-testing]="2.0" +WAY_DESC[softwaredev-code-testing]="writing unit tests, test coverage, mocking dependencies, test-driven development" +WAY_VOCAB[softwaredev-code-testing]="unittest coverage mock tdd assertion jest pytest rspec testcase spec fixture describe expect verify" +WAY_THRESH[softwaredev-code-testing]="2.0" -WAY_DESC[softwaredev-api]="designing REST APIs, HTTP endpoints, API versioning, request response structure" -WAY_VOCAB[softwaredev-api]="endpoint api rest route http status pagination versioning graphql request response header payload crud webhook" -WAY_THRESH[softwaredev-api]="2.0" +WAY_DESC[softwaredev-docs-api]="designing REST APIs, HTTP endpoints, API versioning, request response structure" +WAY_VOCAB[softwaredev-docs-api]="endpoint api rest route http status pagination versioning graphql request response header payload crud webhook" +WAY_THRESH[softwaredev-docs-api]="2.0" -WAY_DESC[softwaredev-debugging]="debugging code issues, troubleshooting errors, investigating broken behavior, fixing bugs" -WAY_VOCAB[softwaredev-debugging]="debug breakpoint stacktrace investigate troubleshoot regression bisect crash error fail bug log trace exception segfault hang timeout" -WAY_THRESH[softwaredev-debugging]="2.0" +WAY_DESC[softwaredev-environment-debugging]="debugging code issues, troubleshooting errors, investigating broken behavior, fixing bugs" +WAY_VOCAB[softwaredev-environment-debugging]="debug breakpoint stacktrace investigate troubleshoot regression bisect crash error fail bug log trace exception segfault hang timeout" +WAY_THRESH[softwaredev-environment-debugging]="2.0" -WAY_DESC[softwaredev-security]="application security, authentication, secrets management, input validation, vulnerability prevention" -WAY_VOCAB[softwaredev-security]="authentication secrets password credentials owasp injection xss sql sanitize vulnerability bcrypt hash encrypt token cert ssl tls csrf cors rotate login expose" -WAY_THRESH[softwaredev-security]="2.0" +WAY_DESC[softwaredev-code-security]="application security, authentication, secrets management, input validation, vulnerability prevention" +WAY_VOCAB[softwaredev-code-security]="authentication secrets password credentials owasp injection xss sql sanitize vulnerability bcrypt hash encrypt token cert ssl tls csrf cors rotate login expose" +WAY_THRESH[softwaredev-code-security]="2.0" -WAY_DESC[softwaredev-design]="software system design architecture patterns database schema component modeling" -WAY_VOCAB[softwaredev-design]="architecture pattern database schema modeling interface component modules factory observer strategy monolith microservice domain layer coupling cohesion abstraction singleton" -WAY_THRESH[softwaredev-design]="2.0" +WAY_DESC[softwaredev-architecture-design]="software system design architecture patterns database schema component modeling" +WAY_VOCAB[softwaredev-architecture-design]="architecture pattern database schema modeling interface component modules factory observer strategy monolith microservice domain layer coupling cohesion abstraction singleton" +WAY_THRESH[softwaredev-architecture-design]="2.0" -WAY_DESC[softwaredev-config]="application configuration, environment variables, dotenv files, config file management" -WAY_VOCAB[softwaredev-config]="dotenv environment configuration envvar config.json config.yaml connection port host url setting variable" -WAY_THRESH[softwaredev-config]="2.0" +WAY_DESC[softwaredev-environment-config]="application configuration, environment variables, dotenv files, config file management" +WAY_VOCAB[softwaredev-environment-config]="dotenv environment configuration envvar config.json config.yaml connection port host url setting variable" +WAY_THRESH[softwaredev-environment-config]="2.0" -WAY_DESC[softwaredev-adr-context]="planning how to implement a feature, deciding an approach, understanding existing project decisions, starting work on an item, investigating why something was built a certain way" -WAY_VOCAB[softwaredev-adr-context]="plan approach debate implement build work pick understand investigate why how decision context tradeoff evaluate option consider scope" -WAY_THRESH[softwaredev-adr-context]="2.0" +WAY_DESC[softwaredev-architecture-adr-context]="planning how to implement a feature, deciding an approach, understanding existing project decisions, starting work on an item, investigating why something was built a certain way" +WAY_VOCAB[softwaredev-architecture-adr-context]="plan approach debate implement build work pick understand investigate why how decision context tradeoff evaluate option consider scope" +WAY_THRESH[softwaredev-architecture-adr-context]="2.0" -WAY_IDS=(softwaredev-testing softwaredev-api softwaredev-debugging softwaredev-security softwaredev-design softwaredev-config softwaredev-adr-context) +WAY_DESC[softwaredev-delivery-commits]="git commit messages, branch naming, conventional commits, atomic changes" +WAY_VOCAB[softwaredev-delivery-commits]="commit message branch conventional feat fix refactor scope atomic squash amend stash rebase cherry" +WAY_THRESH[softwaredev-delivery-commits]="2.0" + +WAY_DESC[softwaredev-delivery-github]="GitHub pull requests, issues, code review, CI checks, repository management" +WAY_VOCAB[softwaredev-delivery-github]="pr pullrequest issue review checks ci label milestone fork repository upstream draft" +WAY_THRESH[softwaredev-delivery-github]="2.0" + +WAY_DESC[softwaredev-delivery-patches]="creating and applying patch files, git diff generation, patch series management" +WAY_VOCAB[softwaredev-delivery-patches]="patch diff apply hunk unified series format-patch" +WAY_THRESH[softwaredev-delivery-patches]="2.0" + +WAY_DESC[softwaredev-delivery-release]="software releases, changelog generation, version bumping, semantic versioning, tagging" +WAY_VOCAB[softwaredev-delivery-release]="release changelog version bump semver tag publish ship major minor breaking" +WAY_THRESH[softwaredev-delivery-release]="2.0" + +WAY_DESC[softwaredev-delivery-migrations]="database migrations, schema changes, table alterations, rollback procedures" +WAY_VOCAB[softwaredev-delivery-migrations]="migration schema alter table column index rollback seed ddl prisma alembic knex flyway" +WAY_THRESH[softwaredev-delivery-migrations]="2.0" + +WAY_DESC[softwaredev-code-errors]="error handling patterns, exception management, try-catch boundaries, error wrapping and propagation" +WAY_VOCAB[softwaredev-code-errors]="exception handling catch throw boundary wrap rethrow fallback graceful recovery propagate unhandled" +WAY_THRESH[softwaredev-code-errors]="2.0" + +WAY_DESC[softwaredev-code-quality]="code quality, refactoring, SOLID principles, code review standards, technical debt, maintainability" +WAY_VOCAB[softwaredev-code-quality]="refactor quality solid principle decompose extract method responsibility coupling cohesion maintainability readability" +WAY_THRESH[softwaredev-code-quality]="2.0" + +WAY_DESC[softwaredev-code-performance]="performance optimization, profiling, benchmarking, latency reduction, memory efficiency" +WAY_VOCAB[softwaredev-code-performance]="optimize profile benchmark latency throughput memory cache bottleneck flamegraph allocation heap" +WAY_THRESH[softwaredev-code-performance]="2.0" + +WAY_DESC[softwaredev-environment-deps]="dependency management, package installation, library evaluation, security auditing of third-party code" +WAY_VOCAB[softwaredev-environment-deps]="dependency package library install upgrade outdated audit vulnerability license bundle npm pip cargo" +WAY_THRESH[softwaredev-environment-deps]="2.0" + +WAY_DESC[softwaredev-environment-ssh]="SSH remote access, key management, secure file transfer, non-interactive authentication" +WAY_VOCAB[softwaredev-environment-ssh]="ssh remote key agent scp rsync bastion jumphost tunnel forwarding batchmode noninteractive" +WAY_THRESH[softwaredev-environment-ssh]="2.0" + +WAY_DESC[softwaredev-docs]="README authoring, docstrings, technical prose, Mermaid diagrams, project guides" +WAY_VOCAB[softwaredev-docs]="readme docstring technical writing mermaid diagram flowchart sequence onboarding" +WAY_THRESH[softwaredev-docs]="2.0" + +WAY_IDS=(softwaredev-code-testing softwaredev-docs-api softwaredev-environment-debugging softwaredev-code-security softwaredev-architecture-design softwaredev-environment-config softwaredev-architecture-adr-context softwaredev-delivery-commits softwaredev-delivery-github softwaredev-delivery-patches softwaredev-delivery-release softwaredev-delivery-migrations softwaredev-code-errors softwaredev-code-quality softwaredev-code-performance softwaredev-environment-deps softwaredev-environment-ssh softwaredev-docs) # --- Options --- RUN_NCD=true diff --git a/tools/way-match/test-integration.sh b/tools/way-match/test-integration.sh index d29fb47..36f3c24 100755 --- a/tools/way-match/test-integration.sh +++ b/tools/way-match/test-integration.sh @@ -61,39 +61,39 @@ echo "" # Use "NONE" for prompts that shouldn't match anything TEST_CASES=( # Direct matches — vocabulary terms present - "softwaredev-testing|write some unit tests for this module" - "softwaredev-testing|run pytest with coverage" - "softwaredev-testing|mock the database connection in tests" - "softwaredev-api|design the REST API for user management" - "softwaredev-api|what status code should this endpoint return" - "softwaredev-api|add versioning to the API" - "softwaredev-debugging|debug why this function returns null" - "softwaredev-debugging|troubleshoot the failing deployment" - "softwaredev-debugging|bisect to find which commit broke it" - "softwaredev-security|fix the SQL injection vulnerability" - "softwaredev-security|store passwords with bcrypt" - "softwaredev-security|sanitize the form input" - "softwaredev-design|design the database schema" - "softwaredev-design|use the factory pattern here" - "softwaredev-design|model the component interfaces" - "softwaredev-config|set up the .env file for production" - "softwaredev-config|manage environment variables" - "softwaredev-config|configure the yaml settings" - "softwaredev-adr-context|plan how to build the notification system" - "softwaredev-adr-context|why was this feature designed this way" - "softwaredev-adr-context|pick up work on the auth implementation" + "softwaredev-code-testing|write some unit tests for this module" + "softwaredev-code-testing|run pytest with coverage" + "softwaredev-code-testing|mock the database connection in tests" + "softwaredev-docs-api|design the REST API for user management" + "softwaredev-docs-api|what status code should this endpoint return" + "softwaredev-docs-api|add versioning to the API" + "softwaredev-environment-debugging|debug why this function returns null" + "softwaredev-environment-debugging|troubleshoot the failing deployment" + "softwaredev-environment-debugging|bisect to find which commit broke it" + "softwaredev-code-security|fix the SQL injection vulnerability" + "softwaredev-code-security|store passwords with bcrypt" + "softwaredev-code-security|sanitize the form input" + "softwaredev-architecture-design|design the database schema" + "softwaredev-architecture-design|use the factory pattern here" + "softwaredev-architecture-design|model the component interfaces" + "softwaredev-environment-config|set up the .env file for production" + "softwaredev-environment-config|manage environment variables" + "softwaredev-environment-config|configure the yaml settings" + "softwaredev-architecture-adr-context|plan how to build the notification system" + "softwaredev-architecture-adr-context|why was this feature designed this way" + "softwaredev-architecture-adr-context|pick up work on the auth implementation" # Negative cases — should not trigger any semantic way "NONE|what is the capital of France" "NONE|tell me about photosynthesis" "NONE|how tall is Mount Everest" "NONE|write a haiku about rain" # Realistic prompts that are borderline - "softwaredev-testing|does this code have enough test coverage" - "softwaredev-api|the endpoint is returning 500 errors" - "softwaredev-debugging|the app keeps crashing on startup" - "softwaredev-security|are our API keys exposed anywhere" - "softwaredev-design|should we use a monolith or microservices architecture" - "softwaredev-config|the database connection string needs updating" + "softwaredev-code-testing|does this code have enough test coverage" + "softwaredev-docs-api|the endpoint is returning 500 errors" + "softwaredev-environment-debugging|the app keeps crashing on startup" + "softwaredev-code-security|are our API keys exposed anywhere" + "softwaredev-architecture-design|should we use a monolith or microservices architecture" + "softwaredev-environment-config|the database connection string needs updating" ) # --- Run tests ---