hunter-party-ts/boundary-hunter-ts/SKILL.md
Audit TypeScript modules for black-box boundary violations — leaked internals via exports, coupling through shared types, Law of Demeter chains, missing abstraction layers around externals, and over-exported APIs. Use when: reviewing module structure, shrinking public API surface, enforcing encapsulation, preparing modules for replacement, or untangling tight coupling between layers.
npx skillsauth add skyosev/agent-skills boundary-hunter-tsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Audit TypeScript code for module boundary violations — places where implementation details leak through exports, where modules reach into each other's internals, or where coupling makes replacement impossible. The goal: every module is a black box, replaceable from its interface alone.
A module is its interface. Everything not exported is an implementation detail. Everything exported is a promise. Exports should describe what the module does, never how it does it. If a consumer must understand internals to use the API correctly, the boundary is broken.
Minimal public surface. Export only what is consumed or serves as a deliberate extension point. Every additional
export is a coupling point that constrains future changes. index.ts barrel files should re-export the public API,
not the entire directory.
Depend on abstractions, not concretions. Modules should depend on interfaces (types, contracts) owned by the consumer or a shared kernel — not on concrete implementations from other modules. If module A imports a class from module B, A is coupled to B's implementation. If A imports an interface that B happens to implement, A is coupled only to the interface contract.
Dependency direction must follow architectural intent. In a layered architecture, dependencies flow inward: infrastructure → application → domain. A domain module importing a concrete implementation from infrastructure is a boundary violation. Exception: type-only imports from a shared kernel or contracts layer that both sides depend on are not violations — this is standard DDD practice. Type-only import cycles between co-evolving modules in the same layer may be acceptable with explicit justification, but default to breaking them. Runtime cycles are always violations.
Wrap externals — don't let them leak. Third-party types and APIs should not appear in domain or application layer interfaces. Wrap them behind owned types so the external can be replaced without changing consumers. Infrastructure/adapter modules are the wrapping layer — they may use external types in their implementation and even in their own interface when they serve as the system edge. The rule is strict for inward layers, relaxed at the boundary with the outside world.
One reach, not a chain. A consumer should call a module's API directly — not reach through it into a transitive
dependency's API. a.getB().getC().doThing() (Law of Demeter violation) exposes B's and C's existence to A's caller.
If a consumer needs something from a transitive dependency, the immediate dependency should expose it through its own
API.
Primitives flow; implementation types stay home. Data that flows between modules should be expressed as primitive
types, plain objects, or shared domain types — not as module-internal classes or enums that force consumers to import
from the implementation. Branded/opaque types (e.g., UserId, Millimetres) are fine to cross boundaries when they
represent domain contracts; they are violations when they encode implementation details (e.g., internal cache
keys, serialization formats).
Internal helpers, intermediate types, constants, or utility functions that are exported but serve no external consumer.
Signals:
Action: Remove export, or if consumed externally, evaluate whether the consumer should own the concept.
index.ts that re-exports everything from every internal file, turning the entire module into a public API.
Signals:
export * from './internal-file' patternsmodule/internal/helper) bypassing the barrelAction: Replace export * with explicit named re-exports of the public API. Block deep imports via package.json
exports field or path conventions.
Two modules that share a type where neither owns it, or where one module's internal type appears in another module's function signatures.
Signals:
Action: Move shared types to a shared kernel / contracts layer owned by neither module. Or, define the type in the consumer and have the producer conform to it (dependency inversion).
Long chains of property access or method calls that traverse multiple levels of abstraction.
Signals:
a.b.c.d chains accessing nested properties across module boundaries({ config: { theme: { colors } } }) => ...Action: Expose a direct API on the immediate dependency. If the consumer needs c.d, then the module owning c
should provide a method or accessor for it.
Third-party library types or APIs used directly in domain or application layer interfaces, coupling consumers to a specific library.
Signals:
node_modulesExpress.Request, React.FC) in domain or application layersAcceptable: Infrastructure/adapter modules using external types in their own interface — they are the wrapping layer. Flag only when these types leak inward into domain/application consumers.
Action: Define an owned interface that wraps the external type. The wrapping module is the only place that imports from the external. Consumers depend on the owned interface.
Two or more modules that import from each other, directly or transitively.
Signals:
Action: Runtime cycles and cycles that cross architectural layers are must-fix — break by extracting the shared concept into a third module, or inverting the dependency direction. Type-only import cycles between co-evolving modules in the same layer may be acceptable if explicitly justified.
A lower-level module importing from a higher-level module, breaking the intended layering.
Signals:
Action: Invert the dependency. Define an interface in the lower layer; implement it in the higher layer. Wire via dependency injection, factory, or configuration.
main/master)BASE=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo main)
SCOPE=$(git diff --name-only $(git merge-base HEAD $BASE)...HEAD)
Constrain all subsequent scans to the resolved surface.index.ts or a standalone file that other files import from.
If the project uses package.json exports or tsconfig path aliases to define boundaries, use those as the source
of truth. List all modules and their barrel files.index.ts or direct imports). Classify each
as: type, function, constant, class, enum.node_modules. Note which external types appear
in the module's public interface.For each module:
# Find all exports
rg 'export (function|const|class|enum|type|interface|default)' path/to/module/
# For each exported symbol, check external usage
rg 'import.*{.*SymbolName.*}.*from.*module-path' --type ts
This misses default imports, namespace imports, re-exports, and alias paths. Verify each candidate finding manually
before reporting — a grep miss is not proof of zero usage.For each module's consumers:
(module-name) and @alias below with actual module names and tsconfig path aliases from the project:
EXCLUDE='--glob !**/*.test.* --glob !**/*.spec.* --glob !**/node_modules/**'
# Relative deep imports (substitute actual module directory name)
rg --pcre2 "from ['\"]\..*/(module-name)/(?!index)" --type ts $EXCLUDE
# Alias-based deep imports (substitute actual tsconfig paths alias)
rg --pcre2 "from ['\"]@alias/(module-name)/" --type ts $EXCLUDE
# Find deep property chains (3+ levels)
rg --pcre2 '\w+\.\w+\.\w+\.\w+' --type ts $EXCLUDE
For each module, answer:
Produce a single report. Save as YYYY-MM-DD-boundary-hunter-audit-{$LLM-name}.md in the project's docs folder (or project root if no docs folder exists).
# Boundary Hunter Audit — {date}
## Scope
- Surface: {diff / path / codebase}
- Files: {count or list}
- Exclusions: {list}
## Module Map
| Module | Public Exports | External Deps | Fan-In | Fan-Out |
| ------------- | -------------- | ------------- | ------ | ------- |
| domain/shapes | 5 types, 2 fns | 0 | 8 | 1 |
| infra/svg | 3 fns | 1 (d3-path) | 2 | 4 |
## Dependency Graph Issues
### Cycles
- A → B → A (via {symbols})
### Direction Violations
- domain/X imports from infra/Y ({symbol}, {file:line})
## Export Surface Issues
### Dead Exports
| # | Module | Export | Type | External Consumers |
| - | ----------- | ---------- | -------- | ------------------ |
| 1 | module/path | `helperFn` | function | 0 |
### Leaked Internals
| # | Module | Export | Why Internal | Action |
| - | ----------- | --------------- | --------------------- | ------------- |
| 1 | module/path | `InternalCache` | Implementation detail | Remove export |
### External Type Leaks
| # | Module | Export / Signature | External Type | Action |
| - | ----------- | ------------------------- | ------------- | ---------------------- |
| 1 | module/path | `process(input: LibType)` | `lib@LibType` | Wrap behind owned type |
## Consumer Access Violations
### Deep Imports (Bypassing Barrel)
| # | Consumer | Imported Path | Should Use |
| - | ------------ | ------------------------ | ---------- |
| 1 | file.ts:line | `module/internal/helper` | `module` |
### Law of Demeter Violations
| # | Location | Chain | Depth | Action |
| - | ------------ | --------- | ----- | ----------------- |
| 1 | file.ts:line | `a.b.c.d` | 4 | Expose API on `a` |
## Replaceability Assessment
### {Module Name}
- Replaceable from interface? {yes/no — why}
- Implicit contracts: {ordering, side effects, timing, etc.}
- Coupling risk: {low/med/high}
## Recommendations (Priority Order)
1. **Must-fix**: {runtime cycles, direction violations, external leaks in domain layer}
2. **Should-fix**: {dead exports, leaked internals, deep imports}
3. **Consider**: {replaceability improvements, barrel cleanup, train wrecks}
file/path.ext:line with the exact code or import statement.rg and import analysis to count actual consumers, not hypothetical ones.development
Transforms vague feature ideas into precise, codebase-grounded technical requirements. Use when requirements are ambiguous/incomplete, the user struggles to describe behavior, terminology is unclear, or multiple concepts are mixed. Output is a requirements spec—NOT an implementation plan.
tools
Audit TypeScript type definitions for design debt — duplicated shapes, missing derivations, over-engineered generics, under-constrained type parameters, reinvented utility types, and disorganized type architecture. Type structure and maintainability, not type enforcement. Use when: reviewing type definitions for maintainability, reducing type duplication, simplifying over-engineered type-level logic, or reorganizing type architecture after growth.
development
Audit TypeScript test code for quality gaps — missing coverage on critical paths, brittle tests coupled to implementation, over-mocking, assertion-free tests, missing edge cases, and duplicated test setup. Focuses on test effectiveness, not production code structure. Use when: reviewing TypeScript test suites for reliability, reducing false-positive test failures, improving coverage of critical business logic, or cleaning up test debt.
tools
Audit TypeScript class and interface design for SOLID violations — god classes, rigid extension points, broken substitutability, fat interfaces, and concrete dependency chains. Focuses on responsibility assignment and abstraction fitness. Use when: reviewing class hierarchies, preparing for extension with new variants, reducing coupling between services, or improving testability of class-heavy code.