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 ---