Help us improve
Share bugs, ideas, or general feedback.
From harlan-agent-kit
Find deepening opportunities in a TypeScript package, leaning on TS-package-native seams (package.json `exports`, subpath/conditional exports, workspace `packages/*`, factories + hooks, plugin shapes, citty/hookable/unbuild conventions). Use when the user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a TS library/CLI/monorepo more testable and AI-navigable. Works on single-repo packages and pnpm monorepos.
npx claudepluginhub harlan-zw/harlan-agent-kit --plugin harlan-agent-kitHow this skill is triggered — by the user, by Claude, or both
Slash command
/harlan-agent-kit:improve-ts-pkg-architectureThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Surface architectural friction in a TypeScript package (library, CLI, or pnpm monorepo) and propose **deepening opportunities** — refactors that turn shallow modules into deep ones, using the package's own publishable surface as the seam. The aim is testability and AI-navigability.
Guides technical evaluation of code review feedback: read fully, restate for understanding, verify against codebase, respond with reasoning or pushback before implementing.
Share bugs, ideas, or general feedback.
Surface architectural friction in a TypeScript package (library, CLI, or pnpm monorepo) and propose deepening opportunities — refactors that turn shallow modules into deep ones, using the package's own publishable surface as the seam. The aim is testability and AI-navigability.
Vocabulary, principles, and forbidden patterns live in their canonical files; this file references them. Use the terms in LANGUAGE.md exactly. This skill is informed by the project's domain model (CONTEXT.md, docs/adr/).
packages/*), testing strategy.exports, conditional exports, packages/*, catalogs, bin, hookable, unplugin, citty).Always run ripast first. Every claim about depth, caller counts, locality, or cross-package leaks must be backed by a ripast invocation; text search misses shadowed identifiers, type-only imports, and re-exports through a subpath. If you cannot cite ripast output, drop the claim.
Pass --tsconfig tsconfig.json (root, or per-package in a monorepo) so subpath aliases and workspace refs resolve. For scan (rg-driven) and noisy tree output, scope with --glob 'src/**,packages/*/src/**,test/**'; trim per project (bin/**, scripts/**, playground/** as needed).
Opening pass — run before forming any candidate:
| Command | What it surfaces |
|---|---|
npx -y @ripast/cli unused --tsconfig tsconfig.json --exports local | Top-level declarations with zero project references → deletion-test slam-dunks |
npx -y @ripast/cli tree --exports exported --tsconfig tsconfig.json | Public surface per file → shallow modules + leaks (anything exported that isn't in the exports map is a confessed private leak) |
npx -y @ripast/cli tree --exports local --tsconfig tsconfig.json | Internals per file → locality opportunities |
Per-candidate, before listing (scan is rg-driven — use --glob here):
| Command | What it surfaces |
|---|---|
npx -y @ripast/cli scan <symbol> --glob ... | Caller count + kind classification → drives deletion test + §2 thresholds |
npx -y @ripast/cli scan <symbol> --kind identifier-reference,import-specifier --glob ... | Same, minus string-literal noise |
npx -y @ripast/cli scan <symbol> --graph mermaid --glob ... | Importer graph → cross-package leaks (graph spanning packages/a/src/ + packages/b/src/ via deep import) |
Cite numbers when presenting.
Read the package's published surface first: package.json exports, bin, peerDependencies, sideEffects, engines. The exports map is the contract. Then read CONTEXT.md and docs/adr/ if present.
Orient on the package shape:
bin field), a plugin (peer-dep on rollup/vite/eslint/etc.), or a pnpm monorepo with packages/*?types/import/node/browser/workerd), packages/*, catalog: versions, hookable hooks, unplugin/factory shape, citty subcommands, c12 config loading?EventEmitter, a custom plugin registry, ad-hoc env reads) instead of using one the ecosystem provides?Then use the Agent tool with subagent_type=Explore to walk the codebase. Note friction signals:
new a class, utils that wrap one-liners. Spot via tree --exports exported.scan <symbol> --graph mermaid spanning 10+ directories.process.env deep inside, singletons at import, side effects on import.exports map. Detect with scan <symbol> --graph mermaid straddling two packages/*. See TS-PKG-SEAMS.md Workspace packages.exports map leaks — deep paths (pkg/dist/internal/foo) instead of declared subpaths. Either expose a subpath or stop the leak.hookable is the answer. Detect via two-way edges in scan --graph mermaid.npx -y publint && npx -y @arethetypeswrong/cli --pack . plus du -sh dist/.Apply the deletion test to anything you suspect is shallow. Lean on TS-PKG-SEAMS.md when classifying friction.
Present a numbered list of deepening opportunities. For each candidate:
src/index.ts, src/cli.ts, packages/core/src/foo.ts, package.json exports, pnpm-workspace.yaml)createPipeline(opts) factory exported from a new ./pipeline subpath; the existing files become private implementation under src/pipeline/, and the factory exposes a hookable hook bus for the three current extension points")Use CONTEXT.md for the domain, LANGUAGE.md for the architecture, TS-PKG-SEAMS.md for the framework seam, and the relevant convention section from PKG-CONVENTIONS.md when the candidate is a convention gap. Example phrasings: "the Order intake module exposed as a ./intake subpath", "establish PKG-CONVENTIONS.md §Hook bus for the build pipeline" — not "the FooBarHandler", not "the Order service".
Reject before listing. Validate caller-count thresholds with scan <symbol> --kind identifier-reference,import-specifier; no guesswork.
packages/* ≥2 independent consumers AND zero coupling back to the host; factory ≥2 callers OR real branching/options; pure value/type mapping ≥4 callers.scan <import-specifier> --kind import-specifier.Forbidden patterns (always flag, never propose) — full list in PKG-CONVENTIONS.md §"Forbidden patterns".
ADR conflicts: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly (e.g. "contradicts ADR-0007 — but worth reopening because…"). Don't list every theoretical refactor an ADR forbids.
Do NOT propose interfaces yet. Ask the user: "Which of these would you like to explore?"
Once the user picks a candidate, drop into a grilling conversation. Walk the design tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive. For TS-pkg candidates, also walk: which runtimes it must work in (node / browser / workerd / edge / bun), whether it appears in the exports map (and at which subpath / conditional), whether it lives in src/ or a workspace packages/*, what the treeshake / sideEffects story is.
Side effects happen inline as decisions crystallize:
Naming a deepened module after a concept not in CONTEXT.md? Add the term to CONTEXT.md — same discipline as /grill-with-docs (see CONTEXT-FORMAT.md). Create the file lazily if it doesn't exist.
Sharpening a fuzzy term during the conversation? Update CONTEXT.md right there.
User rejects the candidate with a load-bearing reason? Offer an ADR, framed as: "Want me to record this as an ADR so future architecture reviews don't re-suggest it?" Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. See ADR-FORMAT.md.
Want to explore alternative interfaces for the deepened module? See INTERFACE-DESIGN.md. Sub-agents are pre-seeded with TS-pkg-native shapes (single factory, factory + hook bus, subpath-exposed surface, ports & adapters) so the design space is grounded in what the ecosystem already offers.
Need to know the true blast radius of a rename/move before committing? npx -y @ripast/cli scan <symbol> (counts) or npx -y @ripast/cli scan <symbol> --graph mermaid (importer graph). Quote numbers before promising scope.
Decision crystallized into a concrete refactor? Execute through ripast, not Edit. Pick the primitive:
Pass --tsconfig tsconfig.json (or the per-package one) on rename, move, and rename-file so all callers are rewritten.
| Refactor | Command |
|---|---|
| Rename a symbol across files | npx -y @ripast/cli rename <from> <to> --tsconfig tsconfig.json --apply (add --scope <file> if multi-declared) |
| Move an exported declaration | npx -y @ripast/cli move <symbol> --from <a> --to <b> --tsconfig tsconfig.json --apply |
Move a file (e.g. src/utils/foo.ts → src/pipeline/foo.ts to close a leak) | npx -y @ripast/cli rename-file <old> <new> --tsconfig tsconfig.json --apply |
All mutating commands default to dry-run — preview the diff, then --apply. --verify (default on for rename/move) blocks the apply on new type diagnostics; fix them, never --no-verify past them. Edit is only correct for single-file or <5-match changes. ripast carries out the move; it does not justify the deepening.
The refactor changes the exports map? Update package.json exports in the same pass. If a subpath is added or removed, the change is a SemVer-visible event — note it for the next release.