nap-ninja
Use when hardcoded business info is detected, when centralizing contact data into business.json, or when the user says ‘nap ninja’, ‘business info’, ‘centralize contacts’, or ‘hardcoded phone/email/address’. Also use when armadillo-sync detects scattered NAP data.
| Model | Source | Category |
|---|---|---|
| sonnet | core | Data Quality |
Context: fork
Full Reference
NAP-Ninja
Section titled “NAP-Ninja”Centralized business information enforcement. Scans for hardcoded NAP (Name, Address, Phone) data, centralizes it into business.json, replaces all instances with references, and enables ongoing enforcement.
Mandatory Announcement — FIRST OUTPUT before anything else:
┏━ 🔧 nap-ninja ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ [one-line description of what you're doing] ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛No exceptions. Box frame first, then work.
business.json Schema
Section titled “business.json Schema”Lives at project root. Only populated sections required — omit empty ones entirely.
{ "business": { "name": "Acme Corp", "legalName": "Acme Corporation LLC", "tagline": "Building better widgets since 1999", "phone": "+1-555-867-5309", "altPhone": "+1-555-867-5310", "fax": "+1-555-867-5311", "email": "hello@acme.com", "supportEmail": "support@acme.com", "url": "https://acme.com" }, "address": { "street": "123 Main St", "suite": "Suite 400", "city": "Austin", "state": "TX", "zip": "78701", "country": "US", "formatted": "123 Main St, Suite 400, Austin, TX 78701" }, "additionalLocations": [ { "label": "Downtown Office", "street": "456 Congress Ave", "city": "Austin", "state": "TX", "zip": "78701", "phone": "+1-555-867-5312" } ], "social": { "facebook": "https://facebook.com/acme", "facebookHandle": "@acme", "instagram": "https://instagram.com/acme", "instagramHandle": "@acme", "twitter": "https://x.com/acme", "twitterHandle": "@acmecorp", "linkedin": "https://linkedin.com/company/acme", "youtube": "https://youtube.com/@acme", "youtubeHandle": "@acme", "tiktok": "https://tiktok.com/@acme", "tiktokHandle": "@acme", "pinterest": "https://pinterest.com/acme", "yelp": "https://yelp.com/biz/acme-austin", "googleBusiness": "https://g.page/acme" }, "hours": { "monday": "9:00 AM - 5:00 PM", "tuesday": "9:00 AM - 5:00 PM", "wednesday": "9:00 AM - 5:00 PM", "thursday": "9:00 AM - 5:00 PM", "friday": "9:00 AM - 5:00 PM", "saturday": "Closed", "sunday": "Closed", "holidayNote": "Closed on major US holidays" }, "schema": { "type": "LocalBusiness", "priceRange": "$$", "areaServed": ["Austin", "Round Rock", "Cedar Park"], "paymentAccepted": ["Cash", "Credit Card"], "currenciesAccepted": "USD" }}The Process
Section titled “The Process”Phase 1: DETECT
Section titled “Phase 1: DETECT”- Check if
business.jsonexists at project root - If yes: Load it and scan codebase for hardcoded values that match any value in the file. Report violations with file:line references.
- If no: Scan codebase for NAP-like patterns:
| Pattern | Regex / Heuristic |
|---|---|
| Phone numbers | (\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4} |
| Email addresses | [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,} |
| Street addresses | Lines with street numbers + state abbreviations + zip codes |
| Social URLs | facebook.com/, instagram.com/, x.com/, linkedin.com/, youtube.com/, tiktok.com/, pinterest.com/ |
| Social handles | @username patterns near social context |
| Repeated strings | Any non-trivial string (8+ chars) appearing in 3+ different files |
- Present findings in a table grouped by type
- Ask user: “Which of these are business data to centralize?”
Exclude from scanning: node_modules/, .git/, dist/, build/, .next/, .astro/, __pycache__/, *.lock, *.map, image/font files, business.json itself.
Phase 2: CENTRALIZE
Section titled “Phase 2: CENTRALIZE”- Build
business.jsonfrom confirmed values using the schema above - Show the generated file to user for approval
- Write
business.jsonto project root - Commit:
feat(nap-ninja): create business.json
Phase 3: REPLACE
Section titled “Phase 3: REPLACE”Framework-aware replacement — detect from project files:
| Detection | Framework | Import Pattern |
|---|---|---|
next.config.* or app/layout.* | Next.js | import business from '@/business.json' |
astro.config.* | Astro | import business from '../business.json' in frontmatter |
svelte.config.* | SvelteKit | import business from '$lib/../business.json' |
nuxt.config.* | Nuxt | import business from '~/business.json' |
manage.py or settings.py | Django | json.load(open(BASE_DIR / 'business.json')) in context processor |
*.html only (no framework) | Static HTML | Leave <!-- NAP: field.path --> comment markers + note |
| Anything else | Generic | import business from './business.json' or require('./business.json') |
For each file with hardcoded values:
- Add the import statement if not present
- Replace each hardcoded value with the reference (e.g.,
business.business.phone) - Verify the replacement renders correctly
- Commit per logical group:
refactor(nap-ninja): centralize NAP data in <component>
Phase 4: VERIFY
Section titled “Phase 4: VERIFY”- Grep entire codebase for every string value in
business.json:- Exact match for all strings
- Normalized variants for phone numbers (strip all formatting: dashes, parens, spaces, dots)
- Exclude:
business.jsonitself,node_modules/,.git/, build output, lock files - Report results:
✓ 0 hardcoded instances remain→ proceed to Phase 5✗ N instances remain→ show file:line for each, fix them, re-verify
- Run the project’s existing test suite to confirm nothing broke
Phase 5: ENABLE
Section titled “Phase 5: ENABLE”- Confirm the NAP-Ninja hook is active (auto-enabled when
business.jsonexists) - Tell the user:
NAP-Ninja is watching. Future writes that hardcode valuesfrom business.json will get flagged.
▸ To pause: tell me "turn off nap-ninja" or set napNinja: false in .claude/settings.json▸ To rescan: /nap-ninjaToggle
Section titled “Toggle”Turning Off
Section titled “Turning Off”When user says “turn off nap ninja”, “disable nap”, “pause nap-ninja”:
- Read
.claude/settings.json(or create if missing) - Set
"napNinja": false - Response:
NAP-Ninja paused. Run /nap-ninja to re-enable and rescan.
Turning On
Section titled “Turning On”When user runs /nap-ninja or says “turn on nap ninja”:
- Remove
napNinja: falsefrom settings (or settrue) - Run a quick verification scan
- Response:
NAP-Ninja re-enabled. Watching for hardcoded business data.
Common Mistakes
Section titled “Common Mistakes”| Mistake | Fix |
|---|---|
| Replacing values in test fixtures | Skip test directories during REPLACE phase |
| Missing phone number variants | Always normalize: strip +1, -, (, ), ., spaces |
| Flagging short strings like “US” or “TX” | Only match strings 4+ characters |
| Breaking import paths | Use framework-appropriate path resolution |
| Not checking structured data (JSON-LD) | Scan <script type="application/ld+json"> blocks too |
Replacing in business.json itself | Always exclude the config file from scans |
Integration
Section titled “Integration”Routed by: armadillo-shepherd → Data Quality section
Enforced by: nap-enforcement rule (passive) + nap-ninja-hook.sh (active)
Pairs with: armadillo-sync (auto-detect on project setup), seo-audit / seo-pulse (structured data consistency)