plumb-tools 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/analyze-dependencies.d.ts +23 -0
- package/dist/analyze-dependencies.d.ts.map +1 -0
- package/dist/analyze-dependencies.js +127 -0
- package/dist/analyze-dependencies.js.map +1 -0
- package/dist/analyze-public-surface.d.ts +21 -0
- package/dist/analyze-public-surface.d.ts.map +1 -0
- package/dist/analyze-public-surface.js +157 -0
- package/dist/analyze-public-surface.js.map +1 -0
- package/dist/detect-cycles.d.ts +17 -0
- package/dist/detect-cycles.d.ts.map +1 -0
- package/dist/detect-cycles.js +63 -0
- package/dist/detect-cycles.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/tool.d.ts +15 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +16 -0
- package/dist/tool.js.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shashank Gupta
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# plumb-tools
|
|
2
|
+
|
|
3
|
+
LLM-free TypeScript architecture and boundary analysis toolset. Provides the static-analysis engine behind [`plumb-mcp-server`](https://www.npmjs.com/package/plumb-mcp-server) and the [Plumb](https://plumb.sgupta.dev) case study.
|
|
4
|
+
|
|
5
|
+
No API key required. No inference. Returns grounded structural facts that an agent or a human can reason over.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What it does
|
|
10
|
+
|
|
11
|
+
Three exported `AnalysisTool` implementations, each wrapping a well-known OSS static-analysis tool:
|
|
12
|
+
|
|
13
|
+
| Export | Underlying tool | What it returns |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `dependencyGraphTool` | dependency-cruiser | Flat edge list: every import in the repo with `from`, `to`, `importPath`, `line`, `external`, `dynamic` |
|
|
16
|
+
| `publicSurfaceTool` | ts-morph | Per-package public API surface: the set of modules reachable from each `src/index.ts` entrypoint |
|
|
17
|
+
| `detectCyclesTool` | madge | Circular dependency rings as lists of repo-relative file paths |
|
|
18
|
+
|
|
19
|
+
These three tools are composed by the agent layer (in `plumb-mcp-server` and the Plumb arch-reviewer agent) to classify dependency edges and surface architectural boundary violations.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pnpm add plumb-tools
|
|
27
|
+
# or
|
|
28
|
+
npm install plumb-tools
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Requires Node.js 18+.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Library usage
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import {
|
|
39
|
+
analyzeDependencies,
|
|
40
|
+
analyzePublicSurface,
|
|
41
|
+
detectCycles,
|
|
42
|
+
} from 'plumb-tools';
|
|
43
|
+
|
|
44
|
+
const repoPath = '/absolute/path/to/your/repo';
|
|
45
|
+
|
|
46
|
+
const graph = await analyzeDependencies({ repoPath });
|
|
47
|
+
// graph.edges: DependencyEdge[]
|
|
48
|
+
|
|
49
|
+
const surface = await analyzePublicSurface({ repoPath });
|
|
50
|
+
// surface.packages: PackageSurface[]
|
|
51
|
+
|
|
52
|
+
const cycles = await detectCycles({ repoPath });
|
|
53
|
+
// cycles.cycles: DependencyCycle[]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You can also invoke the pre-wired `AnalysisTool` objects directly (these are what `plumb-mcp-server` registers as MCP tools):
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { dependencyGraphTool, publicSurfaceTool, detectCyclesTool } from 'plumb-tools';
|
|
60
|
+
|
|
61
|
+
const result = await dependencyGraphTool.execute({ repoPath });
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Exported types
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// dependency graph
|
|
70
|
+
type DependencyEdge = { from: string; to: string | null; importPath: string; line: number | null; external: boolean; dynamic: boolean };
|
|
71
|
+
type DependencyGraphFacts = { repoPath: string; modules: string[]; edges: DependencyEdge[]; generatedWith: { tool: 'dependency-cruiser'; version: string } };
|
|
72
|
+
|
|
73
|
+
// public surface
|
|
74
|
+
type PackageSurface = { packageRoot: string; entrypoint: string | null; publicModules: string[] };
|
|
75
|
+
type PublicSurfaceFacts = { repoPath: string; packages: PackageSurface[]; generatedWith: string };
|
|
76
|
+
type ImportSurfaceClass = 'via-entrypoint' | 'bypasses-entrypoint' | 'intra-package' | 'not-a-package-import';
|
|
77
|
+
|
|
78
|
+
// cycles
|
|
79
|
+
type DependencyCycle = { members: string[] };
|
|
80
|
+
type CycleFacts = { repoPath: string; cycles: DependencyCycle[]; generatedWith: string };
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Known limitations
|
|
86
|
+
|
|
87
|
+
- The `doNotFollow` field on `dependencyGraphTool`'s input schema is accepted but not forwarded to dependency-cruiser at runtime. It is a documented stub.
|
|
88
|
+
- No timeout or file-count limit is imposed on analysis. Point it at a developer-sized repository; avoid enormous monorepos or pathologically deep trees.
|
|
89
|
+
- `analyzePublicSurface` only recognises packages that have a `src/index.ts` entrypoint. Packages without one get `entrypoint: null` and `publicModules: []`.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface DependencyEdge {
|
|
2
|
+
from: string;
|
|
3
|
+
to: string | null;
|
|
4
|
+
importPath: string;
|
|
5
|
+
line: number | null;
|
|
6
|
+
external: boolean;
|
|
7
|
+
dynamic: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface DependencyGraphFacts {
|
|
10
|
+
repoPath: string;
|
|
11
|
+
modules: string[];
|
|
12
|
+
edges: DependencyEdge[];
|
|
13
|
+
generatedWith: {
|
|
14
|
+
tool: 'dependency-cruiser';
|
|
15
|
+
version: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface AnalyzeOptions {
|
|
19
|
+
doNotFollow?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function findImportLine(absSourcePath: string, importPath: string): number | null;
|
|
22
|
+
export declare function analyzeDependencies(repoPath: string, options?: AnalyzeOptions): Promise<DependencyGraphFacts>;
|
|
23
|
+
//# sourceMappingURL=analyze-dependencies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-dependencies.d.ts","sourceRoot":"","sources":["../src/analyze-dependencies.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IAInB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,aAAa,EAAE;QAAE,IAAI,EAAE,oBAAoB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAChE;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAYD,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmCvF;AAoCD,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,oBAAoB,CAAC,CA+C/B"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { cruise } from 'dependency-cruiser';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
// dependency-cruiser does not emit per-import line numbers. Scan the source
|
|
6
|
+
// for the specifier as an import/export/require/dynamic-import token — first
|
|
7
|
+
// matching line wins.
|
|
8
|
+
//
|
|
9
|
+
// WHY anchored to import syntax: a bare quoted-string search would match any
|
|
10
|
+
// comment or string literal that mentions the path. Anchoring to the actual
|
|
11
|
+
// import/export/require call sites means only real module references are cited.
|
|
12
|
+
//
|
|
13
|
+
// Returns null when the specifier cannot be found, or when the file cannot be
|
|
14
|
+
// read — so callers see an honest "unknown" rather than a misleading line 1.
|
|
15
|
+
export function findImportLine(absSourcePath, importPath) {
|
|
16
|
+
let lines;
|
|
17
|
+
try {
|
|
18
|
+
lines = readFileSync(absSourcePath, 'utf-8').split('\n');
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const escaped = importPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
24
|
+
// Matches:
|
|
25
|
+
// import ... from 'path' (static import)
|
|
26
|
+
// export ... from 'path' (re-export)
|
|
27
|
+
// require('path') (CommonJS)
|
|
28
|
+
// import('path') (dynamic import)
|
|
29
|
+
// Both single and double quotes are accepted.
|
|
30
|
+
//
|
|
31
|
+
// Comment-aware: a line whose non-whitespace content begins with '//' or
|
|
32
|
+
// '*' (line comment or block-comment continuation) is skipped even if it
|
|
33
|
+
// contains a syntactically valid import expression. This prevents a
|
|
34
|
+
// commented-out import like `// import 'x'` from being mis-cited.
|
|
35
|
+
const re = new RegExp(`(?:^|\\b)(?:import|export|require)\\b[^'"]*['"]${escaped}['"]` +
|
|
36
|
+
`|(?:^|\\b)import\\s*\\(['"]${escaped}['"]\\)`);
|
|
37
|
+
const idx = lines.findIndex((l) => {
|
|
38
|
+
const trimmed = l.trimStart();
|
|
39
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'))
|
|
40
|
+
return false;
|
|
41
|
+
return re.test(l);
|
|
42
|
+
});
|
|
43
|
+
if (idx < 0) {
|
|
44
|
+
console.warn(`[tools] findImportLine: could not locate import of '${importPath}' in ${absSourcePath}`);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return idx + 1;
|
|
48
|
+
}
|
|
49
|
+
function isExternal(dep) {
|
|
50
|
+
return (dep.coreModule ||
|
|
51
|
+
dep.couldNotResolve ||
|
|
52
|
+
(!dep.dependencyTypes.includes('local') &&
|
|
53
|
+
!dep.dependencyTypes.includes('localmodule')));
|
|
54
|
+
}
|
|
55
|
+
function readDcVersion() {
|
|
56
|
+
try {
|
|
57
|
+
let dir = dirname(fileURLToPath(import.meta.resolve('dependency-cruiser')));
|
|
58
|
+
for (;;) {
|
|
59
|
+
try {
|
|
60
|
+
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
|
|
61
|
+
if (pkg.name === 'dependency-cruiser' && pkg.version != null) {
|
|
62
|
+
return pkg.version;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// not a package root — keep walking up
|
|
67
|
+
}
|
|
68
|
+
const parent = dirname(dir);
|
|
69
|
+
if (parent === dir)
|
|
70
|
+
break;
|
|
71
|
+
dir = parent;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// ignore — return fallback below
|
|
76
|
+
}
|
|
77
|
+
return 'unknown';
|
|
78
|
+
}
|
|
79
|
+
export async function analyzeDependencies(repoPath, options) {
|
|
80
|
+
// Phase 1: doNotFollow reserved; dependency-cruiser config is hardcoded for now.
|
|
81
|
+
void options;
|
|
82
|
+
const raw = await cruise(['.'], {
|
|
83
|
+
baseDir: repoPath,
|
|
84
|
+
// Follow everything except node_modules; they still appear as edges (external: true).
|
|
85
|
+
doNotFollow: { path: 'node_modules' },
|
|
86
|
+
});
|
|
87
|
+
const result = raw.output;
|
|
88
|
+
const edgeList = [];
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
for (const mod of result.modules) {
|
|
91
|
+
if (mod.source.includes('node_modules'))
|
|
92
|
+
continue;
|
|
93
|
+
seen.add(mod.source);
|
|
94
|
+
for (const dep of mod.dependencies) {
|
|
95
|
+
const ext = isExternal(dep);
|
|
96
|
+
edgeList.push({
|
|
97
|
+
from: mod.source,
|
|
98
|
+
to: ext ? null : dep.resolved,
|
|
99
|
+
importPath: dep.module,
|
|
100
|
+
line: findImportLine(join(repoPath, mod.source), dep.module),
|
|
101
|
+
external: ext,
|
|
102
|
+
dynamic: dep.dynamic,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Deterministic order: primary key = from-file, secondary = line number
|
|
107
|
+
// (nulls sort last within a file).
|
|
108
|
+
edgeList.sort((a, b) => {
|
|
109
|
+
const c = a.from.localeCompare(b.from);
|
|
110
|
+
if (c !== 0)
|
|
111
|
+
return c;
|
|
112
|
+
if (a.line === null && b.line === null)
|
|
113
|
+
return 0;
|
|
114
|
+
if (a.line === null)
|
|
115
|
+
return 1;
|
|
116
|
+
if (b.line === null)
|
|
117
|
+
return -1;
|
|
118
|
+
return a.line - b.line;
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
repoPath,
|
|
122
|
+
modules: [...seen].sort(),
|
|
123
|
+
edges: edgeList,
|
|
124
|
+
generatedWith: { tool: 'dependency-cruiser', version: readDcVersion() },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=analyze-dependencies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-dependencies.js","sourceRoot":"","sources":["../src/analyze-dependencies.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AnalysisTool } from './tool.js';
|
|
3
|
+
export interface PackageSurface {
|
|
4
|
+
packageRoot: string;
|
|
5
|
+
entrypoint: string | null;
|
|
6
|
+
publicModules: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface PublicSurfaceFacts {
|
|
9
|
+
repoPath: string;
|
|
10
|
+
packages: PackageSurface[];
|
|
11
|
+
generatedWith: string;
|
|
12
|
+
}
|
|
13
|
+
export type ImportSurfaceClass = 'via-entrypoint' | 'bypasses-entrypoint' | 'intra-package' | 'not-a-package-import' | 'unknown';
|
|
14
|
+
export declare function analyzePublicSurface(repoPath: string): Promise<PublicSurfaceFacts>;
|
|
15
|
+
export declare function classifyImport(facts: PublicSurfaceFacts, fromFile: string, resolvedTarget: string | null): ImportSurfaceClass;
|
|
16
|
+
export declare const PublicSurfaceInputSchema: z.ZodObject<{
|
|
17
|
+
repoPath: z.ZodString;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export type PublicSurfaceInput = z.infer<typeof PublicSurfaceInputSchema>;
|
|
20
|
+
export declare const publicSurfaceTool: AnalysisTool<PublicSurfaceInput, PublicSurfaceFacts>;
|
|
21
|
+
//# sourceMappingURL=analyze-public-surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-public-surface.d.ts","sourceRoot":"","sources":["../src/analyze-public-surface.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,kBAAkB,GAC1B,gBAAgB,GAChB,qBAAqB,GACrB,eAAe,GACf,sBAAsB,GACtB,SAAS,CAAC;AAmFd,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,CAAC,CAwD7B;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,kBAAkB,EACzB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GAAG,IAAI,GAC5B,kBAAkB,CA6BpB;AAED,eAAO,MAAM,wBAAwB;;iBAEnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,iBAAiB,EAAE,YAAY,CAAC,kBAAkB,EAAE,kBAAkB,CAQlF,CAAC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Project } from 'ts-morph';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { globSync } from 'node:fs';
|
|
7
|
+
function readTsMorphVersion() {
|
|
8
|
+
try {
|
|
9
|
+
let dir = dirname(fileURLToPath(import.meta.resolve('ts-morph')));
|
|
10
|
+
for (;;) {
|
|
11
|
+
try {
|
|
12
|
+
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
|
|
13
|
+
if (pkg.name === 'ts-morph' && pkg.version != null) {
|
|
14
|
+
return pkg.version;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// not a package root — keep walking up
|
|
19
|
+
}
|
|
20
|
+
const parent = dirname(dir);
|
|
21
|
+
if (parent === dir)
|
|
22
|
+
break;
|
|
23
|
+
dir = parent;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// ignore
|
|
28
|
+
}
|
|
29
|
+
return 'unknown';
|
|
30
|
+
}
|
|
31
|
+
// Collect all repo-relative module paths reachable via re-export chains from entrypoint.
|
|
32
|
+
// Only follows `export * from '...'` and `export { ... } from '...'` declarations.
|
|
33
|
+
function collectPublicModules(project, entrypointAbsPath, repoPath) {
|
|
34
|
+
const visited = new Set();
|
|
35
|
+
const queue = [entrypointAbsPath];
|
|
36
|
+
while (queue.length > 0) {
|
|
37
|
+
const absPath = queue.shift();
|
|
38
|
+
if (absPath === undefined || visited.has(absPath))
|
|
39
|
+
continue;
|
|
40
|
+
visited.add(absPath);
|
|
41
|
+
const sf = project.getSourceFile(absPath);
|
|
42
|
+
if (sf === undefined) {
|
|
43
|
+
console.warn(`[tools] analyzePublicSurface: source file not found in project: ${absPath}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
for (const exportDecl of sf.getExportDeclarations()) {
|
|
47
|
+
const moduleSpecifier = exportDecl.getModuleSpecifierValue();
|
|
48
|
+
if (moduleSpecifier === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
const resolved = exportDecl.getModuleSpecifierSourceFile();
|
|
51
|
+
if (resolved === undefined) {
|
|
52
|
+
console.warn(`[tools] analyzePublicSurface: could not resolve re-export '${moduleSpecifier}' in ${absPath}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const resolvedPath = resolved.getFilePath();
|
|
56
|
+
if (!visited.has(resolvedPath)) {
|
|
57
|
+
queue.push(resolvedPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const result = [];
|
|
62
|
+
for (const absPath of visited) {
|
|
63
|
+
if (absPath.startsWith(repoPath + '/')) {
|
|
64
|
+
result.push(absPath.slice(repoPath.length + 1));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
result.sort();
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
// Defensive: use basename so the predicate works whether Node passes a bare
|
|
71
|
+
// segment ("node_modules") or a path-joined form — behaviour is identical on
|
|
72
|
+
// current Node (bare segments), but this avoids a silent gap if that changes.
|
|
73
|
+
const excludeNodeModules = (name) => basename(String(name)) === 'node_modules';
|
|
74
|
+
export async function analyzePublicSurface(repoPath) {
|
|
75
|
+
// Discover all src/index.ts files to identify packages.
|
|
76
|
+
const entrypointPaths = globSync('**/src/index.ts', {
|
|
77
|
+
cwd: repoPath,
|
|
78
|
+
exclude: excludeNodeModules,
|
|
79
|
+
});
|
|
80
|
+
// Build one ts-morph project over the repo so cross-file resolution works.
|
|
81
|
+
const project = new Project({
|
|
82
|
+
skipAddingFilesFromTsConfig: true,
|
|
83
|
+
compilerOptions: {
|
|
84
|
+
allowJs: false,
|
|
85
|
+
resolveJsonModule: false,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
// Add all discovered entrypoints and their transitive local dependencies.
|
|
89
|
+
for (const rel of entrypointPaths) {
|
|
90
|
+
project.addSourceFileAtPath(join(repoPath, rel));
|
|
91
|
+
}
|
|
92
|
+
// Also add every .ts file under those package roots so resolution succeeds.
|
|
93
|
+
const tsFiles = globSync('**/src/**/*.ts', {
|
|
94
|
+
cwd: repoPath,
|
|
95
|
+
exclude: excludeNodeModules,
|
|
96
|
+
});
|
|
97
|
+
for (const rel of tsFiles) {
|
|
98
|
+
const absPath = join(repoPath, rel);
|
|
99
|
+
if (project.getSourceFile(absPath) === undefined) {
|
|
100
|
+
project.addSourceFileAtPath(absPath);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const tsMorphVersion = readTsMorphVersion();
|
|
104
|
+
const packages = [];
|
|
105
|
+
for (const entrypointRel of entrypointPaths.sort()) {
|
|
106
|
+
// packageRoot is the directory above src/index.ts, repo-relative POSIX.
|
|
107
|
+
// entrypointRel looks like "shared/src/index.ts" → packageRoot = "shared"
|
|
108
|
+
const packageRoot = entrypointRel.replace(/\/src\/index\.ts$/, '');
|
|
109
|
+
const entrypointAbsPath = join(repoPath, entrypointRel);
|
|
110
|
+
const publicModules = collectPublicModules(project, entrypointAbsPath, repoPath);
|
|
111
|
+
packages.push({
|
|
112
|
+
packageRoot,
|
|
113
|
+
entrypoint: entrypointRel,
|
|
114
|
+
publicModules,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
repoPath,
|
|
119
|
+
packages,
|
|
120
|
+
generatedWith: `ts-morph@${tsMorphVersion}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export function classifyImport(facts, fromFile, resolvedTarget) {
|
|
124
|
+
if (resolvedTarget === null)
|
|
125
|
+
return 'unknown';
|
|
126
|
+
// Find the package P whose packageRoot contains resolvedTarget.
|
|
127
|
+
// Use the longest matching packageRoot to handle nested packages correctly
|
|
128
|
+
// (e.g. "remotes/analytics" must win over a hypothetical "remotes" package).
|
|
129
|
+
const targetPackage = facts.packages
|
|
130
|
+
.filter((p) => resolvedTarget === p.entrypoint ||
|
|
131
|
+
resolvedTarget.startsWith(p.packageRoot + '/'))
|
|
132
|
+
.reduce((best, p) => best === undefined || p.packageRoot.length > best.packageRoot.length
|
|
133
|
+
? p
|
|
134
|
+
: best, undefined);
|
|
135
|
+
if (targetPackage === undefined)
|
|
136
|
+
return 'not-a-package-import';
|
|
137
|
+
// fromFile is inside P if it starts with packageRoot + '/'.
|
|
138
|
+
const fromIsInside = fromFile.startsWith(targetPackage.packageRoot + '/');
|
|
139
|
+
if (fromIsInside)
|
|
140
|
+
return 'intra-package';
|
|
141
|
+
// fromFile is outside P.
|
|
142
|
+
if (resolvedTarget === targetPackage.entrypoint)
|
|
143
|
+
return 'via-entrypoint';
|
|
144
|
+
return 'bypasses-entrypoint';
|
|
145
|
+
}
|
|
146
|
+
export const PublicSurfaceInputSchema = z.object({
|
|
147
|
+
repoPath: z.string(),
|
|
148
|
+
});
|
|
149
|
+
export const publicSurfaceTool = {
|
|
150
|
+
name: 'public-surface',
|
|
151
|
+
description: 'Analyses a repository to discover packages (directories containing src/index.ts) and their public module surface using ts-morph. Returns structural facts only — no findings, no LLM calls.',
|
|
152
|
+
inputSchema: PublicSurfaceInputSchema,
|
|
153
|
+
run(input) {
|
|
154
|
+
return analyzePublicSurface(input.repoPath);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=analyze-public-surface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-public-surface.js","sourceRoot":"","sources":["../src/analyze-public-surface.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AnalysisTool } from './tool.js';
|
|
3
|
+
export interface DependencyCycle {
|
|
4
|
+
members: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface CycleFacts {
|
|
7
|
+
repoPath: string;
|
|
8
|
+
cycles: DependencyCycle[];
|
|
9
|
+
generatedWith: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function detectCycles(repoPath: string): Promise<CycleFacts>;
|
|
12
|
+
export declare const DetectCyclesInputSchema: z.ZodObject<{
|
|
13
|
+
repoPath: z.ZodString;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export type DetectCyclesInput = z.infer<typeof DetectCyclesInputSchema>;
|
|
16
|
+
export declare const detectCyclesTool: AnalysisTool<DetectCyclesInput, CycleFacts>;
|
|
17
|
+
//# sourceMappingURL=detect-cycles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-cycles.d.ts","sourceRoot":"","sources":["../src/detect-cycles.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;CACvB;AA2BD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAsBxE;AAED,eAAO,MAAM,uBAAuB;;iBAElC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,gBAAgB,EAAE,YAAY,CAAC,iBAAiB,EAAE,UAAU,CAQxE,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import madge from 'madge';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
function readMadgeVersion() {
|
|
7
|
+
try {
|
|
8
|
+
let dir = dirname(fileURLToPath(import.meta.resolve('madge')));
|
|
9
|
+
for (;;) {
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
|
|
12
|
+
if (pkg.name === 'madge' && pkg.version != null) {
|
|
13
|
+
return pkg.version;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// not a package root — keep walking up
|
|
18
|
+
}
|
|
19
|
+
const parent = dirname(dir);
|
|
20
|
+
if (parent === dir)
|
|
21
|
+
break;
|
|
22
|
+
dir = parent;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// ignore
|
|
27
|
+
}
|
|
28
|
+
return 'unknown';
|
|
29
|
+
}
|
|
30
|
+
export async function detectCycles(repoPath) {
|
|
31
|
+
let result;
|
|
32
|
+
try {
|
|
33
|
+
result = await madge(repoPath, {
|
|
34
|
+
fileExtensions: ['ts', 'tsx'],
|
|
35
|
+
detectiveOptions: {
|
|
36
|
+
ts: { skipTypeImports: true },
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.warn('[tools] detectCycles: madge failed', err);
|
|
42
|
+
return { repoPath, cycles: [], generatedWith: `madge@${readMadgeVersion()}` };
|
|
43
|
+
}
|
|
44
|
+
const rawCycles = result.circular();
|
|
45
|
+
const cycles = rawCycles.map((members) => ({ members }));
|
|
46
|
+
return {
|
|
47
|
+
repoPath,
|
|
48
|
+
cycles,
|
|
49
|
+
generatedWith: `madge@${readMadgeVersion()}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export const DetectCyclesInputSchema = z.object({
|
|
53
|
+
repoPath: z.string(),
|
|
54
|
+
});
|
|
55
|
+
export const detectCyclesTool = {
|
|
56
|
+
name: 'detect-cycles',
|
|
57
|
+
description: 'Detects circular dependencies in a TypeScript repository using madge. Returns module-level cycles as lists of repo-relative file paths. Produces structural facts only — no findings, no LLM calls.',
|
|
58
|
+
inputSchema: DetectCyclesInputSchema,
|
|
59
|
+
run(input) {
|
|
60
|
+
return detectCycles(input.repoPath);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=detect-cycles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-cycles.js","sourceRoot":"","sources":["../src/detect-cycles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,SAAS,gBAAgB;IACvB,IAAI,CAAC;QACH,IAAI,GAAG,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC/D,SAAS,CAAC;YACR,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAGtE,CAAC;gBACF,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;oBAChD,OAAO,GAAG,CAAC,OAAO,CAAC;gBACrB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,uCAAuC;YACzC,CAAC;YACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,MAAM,KAAK,GAAG;gBAAE,MAAM;YAC1B,GAAG,GAAG,MAAM,CAAC;QACf,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,IAAI,MAA2B,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;YAC7B,cAAc,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC;YAC7B,gBAAgB,EAAE;gBAChB,EAAE,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;aAC9B;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAAC;QACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,SAAS,gBAAgB,EAAE,EAAE,EAAE,CAAC;IAChF,CAAC;IAED,MAAM,SAAS,GAAe,MAAM,CAAC,QAAQ,EAAE,CAAC;IAChD,MAAM,MAAM,GAAsB,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IAE5E,OAAO;QACL,QAAQ;QACR,MAAM;QACN,aAAa,EAAE,SAAS,gBAAgB,EAAE,EAAE;KAC7C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;CACrB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,gBAAgB,GAAgD;IAC3E,IAAI,EAAE,eAAe;IACrB,WAAW,EACT,qMAAqM;IACvM,WAAW,EAAE,uBAAuB;IACpC,GAAG,CAAC,KAAwB;QAC1B,OAAO,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;CACF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed wrappers around static-analysis tools live here (later phases).
|
|
3
|
+
* This package never depends on an LLM provider.
|
|
4
|
+
*/
|
|
5
|
+
export declare const version = "0.1.0";
|
|
6
|
+
export type { AnalyzeOptions, DependencyEdge, DependencyGraphFacts, } from './analyze-dependencies.js';
|
|
7
|
+
export { analyzeDependencies, findImportLine } from './analyze-dependencies.js';
|
|
8
|
+
export type { AnalysisTool, DependencyGraphInput } from './tool.js';
|
|
9
|
+
export { dependencyGraphTool, DependencyGraphInputSchema } from './tool.js';
|
|
10
|
+
export type { PackageSurface, PublicSurfaceFacts, ImportSurfaceClass, PublicSurfaceInput, } from './analyze-public-surface.js';
|
|
11
|
+
export { analyzePublicSurface, classifyImport, publicSurfaceTool, PublicSurfaceInputSchema, } from './analyze-public-surface.js';
|
|
12
|
+
export type { DependencyCycle, CycleFacts, DetectCyclesInput, } from './detect-cycles.js';
|
|
13
|
+
export { detectCycles, detectCyclesTool, DetectCyclesInputSchema, } from './detect-cycles.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,YAAY,EACV,cAAc,EACd,cAAc,EACd,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhF,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAE5E,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AAErC,YAAY,EACV,eAAe,EACf,UAAU,EACV,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed wrappers around static-analysis tools live here (later phases).
|
|
3
|
+
* This package never depends on an LLM provider.
|
|
4
|
+
*/
|
|
5
|
+
export const version = '0.1.0';
|
|
6
|
+
export { analyzeDependencies, findImportLine } from './analyze-dependencies.js';
|
|
7
|
+
export { dependencyGraphTool, DependencyGraphInputSchema } from './tool.js';
|
|
8
|
+
export { analyzePublicSurface, classifyImport, publicSurfaceTool, PublicSurfaceInputSchema, } from './analyze-public-surface.js';
|
|
9
|
+
export { detectCycles, detectCyclesTool, DetectCyclesInputSchema, } from './detect-cycles.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAO/B,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhF,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAQ5E,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AAOrC,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC"}
|
package/dist/tool.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { DependencyGraphFacts } from './analyze-dependencies.js';
|
|
3
|
+
export interface AnalysisTool<TInput, TOutput> {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodType<TInput>;
|
|
7
|
+
run(input: TInput): Promise<TOutput>;
|
|
8
|
+
}
|
|
9
|
+
export declare const DependencyGraphInputSchema: z.ZodObject<{
|
|
10
|
+
repoPath: z.ZodString;
|
|
11
|
+
doNotFollow: z.ZodOptional<z.ZodString>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export type DependencyGraphInput = z.infer<typeof DependencyGraphInputSchema>;
|
|
14
|
+
export declare const dependencyGraphTool: AnalysisTool<DependencyGraphInput, DependencyGraphFacts>;
|
|
15
|
+
//# sourceMappingURL=tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAItE,MAAM,WAAW,YAAY,CAAC,MAAM,EAAE,OAAO;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtC;AAED,eAAO,MAAM,0BAA0B;;;iBAGrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,mBAAmB,EAAE,YAAY,CAAC,oBAAoB,EAAE,oBAAoB,CASxF,CAAC"}
|
package/dist/tool.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { analyzeDependencies } from './analyze-dependencies.js';
|
|
3
|
+
export const DependencyGraphInputSchema = z.object({
|
|
4
|
+
repoPath: z.string(),
|
|
5
|
+
doNotFollow: z.string().optional(),
|
|
6
|
+
});
|
|
7
|
+
export const dependencyGraphTool = {
|
|
8
|
+
name: 'dependency-graph',
|
|
9
|
+
description: 'Analyses module dependencies in a repository using dependency-cruiser and returns raw edges (from, to, importPath, line, external, dynamic). Produces facts only — no findings.',
|
|
10
|
+
inputSchema: DependencyGraphInputSchema,
|
|
11
|
+
run(input) {
|
|
12
|
+
const opts = input.doNotFollow !== undefined ? { doNotFollow: input.doNotFollow } : undefined;
|
|
13
|
+
return analyzeDependencies(input.repoPath, opts);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=tool.js.map
|
package/dist/tool.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.js","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAYhE,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACnC,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,mBAAmB,GAA6D;IAC3F,IAAI,EAAE,kBAAkB;IACxB,WAAW,EACT,iLAAiL;IACnL,WAAW,EAAE,0BAA0B;IACvC,GAAG,CAAC,KAA2B;QAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9F,OAAO,mBAAmB,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACnD,CAAC;CACF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "plumb-tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"description": "LLM-free TypeScript architecture and boundary analysis toolset: dependency-cruiser dependency graph, ts-morph public-surface mapping, and madge cycle detection",
|
|
14
|
+
"author": "Shashank Gupta",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"architecture",
|
|
18
|
+
"dependency-analysis",
|
|
19
|
+
"typescript",
|
|
20
|
+
"monorepo",
|
|
21
|
+
"boundaries",
|
|
22
|
+
"dependency-cruiser",
|
|
23
|
+
"madge",
|
|
24
|
+
"ts-morph",
|
|
25
|
+
"static-analysis"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"dependency-cruiser": "^17.4.3",
|
|
37
|
+
"madge": "^8.0.0",
|
|
38
|
+
"ts-morph": "^28.0.0",
|
|
39
|
+
"zod": "^4.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/madge": "^5.0.3"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"build": "tsc -p tsconfig.build.json"
|
|
47
|
+
}
|
|
48
|
+
}
|