@eloquence98/ctx 0.2.0 → 0.3.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/README.md +67 -29
- package/dist/cli.js +6 -12
- package/dist/formatter.d.ts +2 -0
- package/dist/formatter.js +49 -0
- package/dist/parser.js +17 -29
- package/dist/scanner.js +20 -4
- package/dist/types.d.ts +4 -12
- package/package.json +2 -2
- package/dist/adapters/express.d.ts +0 -2
- package/dist/adapters/express.js +0 -58
- package/dist/adapters/index.d.ts +0 -4
- package/dist/adapters/index.js +0 -16
- package/dist/adapters/nextjs.d.ts +0 -2
- package/dist/adapters/nextjs.js +0 -102
- package/dist/adapters/types.d.ts +0 -11
- package/dist/adapters/types.js +0 -1
- package/dist/adapters/vanilla.d.ts +0 -2
- package/dist/adapters/vanilla.js +0 -24
- package/dist/config.d.ts +0 -9
- package/dist/config.js +0 -25
- package/dist/detectors/index.d.ts +0 -3
- package/dist/detectors/index.js +0 -73
- package/dist/formatters/ai-optimized.d.ts +0 -2
- package/dist/formatters/ai-optimized.js +0 -215
- package/dist/formatters/ai.d.ts +0 -2
- package/dist/formatters/ai.js +0 -51
- package/dist/formatters/human.d.ts +0 -2
- package/dist/formatters/human.js +0 -53
- package/dist/formatters/index.d.ts +0 -2
- package/dist/formatters/index.js +0 -2
- package/dist/formatters/markdown.d.ts +0 -2
- package/dist/formatters/markdown.js +0 -77
- package/dist/formatters/raw.d.ts +0 -2
- package/dist/formatters/raw.js +0 -47
- package/dist/organizer.d.ts +0 -2
- package/dist/organizer.js +0 -70
package/README.md
CHANGED
|
@@ -1,50 +1,88 @@
|
|
|
1
1
|
# ctx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Dump a truthful structural index of a codebase.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
No analysis. No opinions. No guessing.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
ctx scans a directory and prints a map of folders, files, and statically detectable exported symbols. It tells you exactly what exists—nothing more, nothing less.
|
|
8
|
+
|
|
9
|
+
## ⚡️ Quick Start
|
|
10
|
+
|
|
11
|
+
No installation required. Run it directly with npx:
|
|
11
12
|
|
|
12
13
|
```bash
|
|
13
|
-
|
|
14
|
-
ctx ./src
|
|
14
|
+
npx @eloquence98/ctx ./path-to-project
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## 📖 What it does
|
|
18
|
+
|
|
19
|
+
ctx provides a high-level map of a project. It identifies:
|
|
20
|
+
|
|
21
|
+
- 📂 Folders
|
|
22
|
+
- 📄 Files
|
|
23
|
+
- ➡️ Exported Symbols (when statically detectable)
|
|
24
|
+
|
|
25
|
+
## Example Output
|
|
26
|
+
|
|
17
27
|
```bash
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
src/
|
|
29
|
+
├─ app.tsx → App
|
|
30
|
+
├─ utils.ts → formatDate, parseCurrency
|
|
31
|
+
└─ components/
|
|
32
|
+
├─ button.tsx → Button
|
|
33
|
+
├─ modal.tsx → Modal, ModalProps
|
|
34
|
+
└─ styles.css
|
|
20
35
|
```
|
|
21
36
|
|
|
22
|
-
|
|
37
|
+
If exports cannot be determined (e.g., non-code files or complex dynamic exports), the file is listed without symbols.
|
|
23
38
|
|
|
24
|
-
|
|
25
|
-
# Codebase Context
|
|
39
|
+
## 🧠 Why this exists
|
|
26
40
|
|
|
27
|
-
|
|
28
|
-
- /admin
|
|
29
|
-
- /admin/orders
|
|
30
|
-
- /client/[slug]/orders
|
|
41
|
+
When working with LLMs (ChatGPT, Claude, etc.), new contributors, or legacy codebases, you don't always need the content of the files immediately; you need to understand the topology of the project first.
|
|
31
42
|
|
|
32
|
-
|
|
33
|
-
- navbar.tsx: Navbar
|
|
34
|
-
- sidebar.tsx: Sidebar
|
|
43
|
+
ctx gives you that map.
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
1. Copy the output.
|
|
46
|
+
2. Paste it into an LLM context window.
|
|
47
|
+
3. Ask informed questions about the architecture before dumping raw code.
|
|
39
48
|
|
|
40
|
-
##
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
## 🚫 What it does NOT do
|
|
50
|
+
|
|
51
|
+
ctx is intentionally dumb. That is why it is reliable.
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
It does not:
|
|
45
54
|
|
|
46
|
-
|
|
55
|
+
- ❌ Interpret architecture or infer domains.
|
|
56
|
+
- ❌ Explain code intent.
|
|
57
|
+
- ❌ Refactor or execute code.
|
|
58
|
+
- ❌ Read node_modules or .git folders.
|
|
59
|
+
- ❌ Read environment variables.
|
|
60
|
+
|
|
61
|
+
It is not a framework detector, a dependency graph tool, or a documentation generator.
|
|
62
|
+
|
|
63
|
+
## ⚙️ Configuration
|
|
64
|
+
|
|
65
|
+
No configuration required.
|
|
66
|
+
|
|
67
|
+
ctx automatically ignores:
|
|
68
|
+
|
|
69
|
+
- node_modules
|
|
70
|
+
- .git
|
|
71
|
+
- Build outputs (dist, build, etc.)
|
|
72
|
+
- Environment files (.env)
|
|
73
|
+
- Test files (.test., .spec.)
|
|
74
|
+
|
|
75
|
+
## 💡 Philosophy
|
|
76
|
+
|
|
77
|
+
Don't explain the code. Show the codebase as it exists.
|
|
78
|
+
|
|
79
|
+
## ⚡️ Install (optional)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm install -g @eloquence98/ctx
|
|
83
|
+
ctx ./src
|
|
84
|
+
```
|
|
47
85
|
|
|
48
86
|
## License
|
|
49
87
|
|
|
50
|
-
MIT
|
|
88
|
+
[MIT](https://choosealicense.com/licenses/mit/)
|
package/dist/cli.js
CHANGED
|
@@ -2,26 +2,20 @@
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { scan } from "./scanner.js";
|
|
4
4
|
import { parse } from "./parser.js";
|
|
5
|
-
import {
|
|
6
|
-
import { formatAI } from "./formatters/ai.js";
|
|
7
|
-
import { formatHuman } from "./formatters/human.js";
|
|
5
|
+
import { format } from "./formatter.js";
|
|
8
6
|
const args = process.argv.slice(2);
|
|
9
|
-
const targetPath = args
|
|
10
|
-
const humanMode = args.includes("--human");
|
|
7
|
+
const targetPath = args[0] || ".";
|
|
11
8
|
async function main() {
|
|
12
9
|
const dir = path.resolve(process.cwd(), targetPath);
|
|
13
|
-
//
|
|
10
|
+
// Scan
|
|
14
11
|
const files = await scan(dir);
|
|
15
12
|
if (files.length === 0) {
|
|
16
13
|
console.log("No files found.");
|
|
17
14
|
process.exit(1);
|
|
18
15
|
}
|
|
19
|
-
//
|
|
16
|
+
// Parse
|
|
20
17
|
const parsed = await Promise.all(files.map(parse));
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
// 4. Format
|
|
24
|
-
const output = humanMode ? formatHuman(context) : formatAI(context);
|
|
25
|
-
console.log(output);
|
|
18
|
+
// Format and print
|
|
19
|
+
console.log(format(parsed, dir));
|
|
26
20
|
}
|
|
27
21
|
main().catch(console.error);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
export function format(files, baseDir) {
|
|
3
|
+
// Build tree structure
|
|
4
|
+
const root = { name: path.basename(baseDir), children: new Map() };
|
|
5
|
+
for (const file of files) {
|
|
6
|
+
const rel = path.relative(baseDir, file.path);
|
|
7
|
+
const parts = rel.split(path.sep);
|
|
8
|
+
let current = root;
|
|
9
|
+
for (let i = 0; i < parts.length; i++) {
|
|
10
|
+
const part = parts[i];
|
|
11
|
+
const isFile = i === parts.length - 1;
|
|
12
|
+
if (!current.children.has(part)) {
|
|
13
|
+
current.children.set(part, {
|
|
14
|
+
name: part,
|
|
15
|
+
children: new Map(),
|
|
16
|
+
exports: isFile ? file.exports : undefined,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
else if (isFile) {
|
|
20
|
+
current.children.get(part).exports = file.exports;
|
|
21
|
+
}
|
|
22
|
+
current = current.children.get(part);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Render tree
|
|
26
|
+
const lines = [];
|
|
27
|
+
lines.push(root.name + "/");
|
|
28
|
+
renderTree(root, "", lines);
|
|
29
|
+
return lines.join("\n");
|
|
30
|
+
}
|
|
31
|
+
function renderTree(node, prefix, lines) {
|
|
32
|
+
const children = [...node.children.values()];
|
|
33
|
+
for (let i = 0; i < children.length; i++) {
|
|
34
|
+
const child = children[i];
|
|
35
|
+
const isLast = i === children.length - 1;
|
|
36
|
+
const connector = isLast ? "└─ " : "├─ ";
|
|
37
|
+
const extension = isLast ? " " : "│ ";
|
|
38
|
+
// Is it a file (has exports defined) or folder?
|
|
39
|
+
const isFile = child.exports !== undefined;
|
|
40
|
+
if (isFile) {
|
|
41
|
+
const exportsStr = child.exports.length > 0 ? ` → ${child.exports.join(", ")}` : "";
|
|
42
|
+
lines.push(prefix + connector + child.name + exportsStr);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
lines.push(prefix + connector + child.name + "/");
|
|
46
|
+
renderTree(child, prefix + extension, lines);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/parser.js
CHANGED
|
@@ -3,38 +3,26 @@ import path from "path";
|
|
|
3
3
|
export async function parse(filePath) {
|
|
4
4
|
const content = await fs.readFile(filePath, "utf-8");
|
|
5
5
|
const name = path.basename(filePath);
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const types = [];
|
|
9
|
-
const components = [];
|
|
10
|
-
// Export function
|
|
6
|
+
const exports = [];
|
|
7
|
+
// export function Name
|
|
11
8
|
for (const match of content.matchAll(/export\s+(?:async\s+)?function\s+(\w+)/g)) {
|
|
12
|
-
|
|
13
|
-
isPascalCase(fn) ? components.push(fn) : functions.push(fn);
|
|
9
|
+
exports.push(match[1]);
|
|
14
10
|
}
|
|
15
|
-
//
|
|
16
|
-
for (const match of content.matchAll(/export\s+const\s+(\w+)
|
|
17
|
-
|
|
18
|
-
if (isPascalCase(n)) {
|
|
19
|
-
components.push(n);
|
|
20
|
-
}
|
|
21
|
-
else if (n.startsWith("use")) {
|
|
22
|
-
functions.push(n);
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
constants.push(n);
|
|
26
|
-
}
|
|
11
|
+
// export const Name
|
|
12
|
+
for (const match of content.matchAll(/export\s+const\s+(\w+)/g)) {
|
|
13
|
+
exports.push(match[1]);
|
|
27
14
|
}
|
|
28
|
-
//
|
|
15
|
+
// export type/interface Name
|
|
29
16
|
for (const match of content.matchAll(/export\s+(?:type|interface)\s+(\w+)/g)) {
|
|
30
|
-
|
|
17
|
+
exports.push(match[1]);
|
|
31
18
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
// export class Name
|
|
20
|
+
for (const match of content.matchAll(/export\s+class\s+(\w+)/g)) {
|
|
21
|
+
exports.push(match[1]);
|
|
22
|
+
}
|
|
23
|
+
// export default function Name / class Name
|
|
24
|
+
for (const match of content.matchAll(/export\s+default\s+(?:function|class)\s+(\w+)/g)) {
|
|
25
|
+
exports.push(match[1]);
|
|
26
|
+
}
|
|
27
|
+
return { path: filePath, name, exports };
|
|
40
28
|
}
|
package/dist/scanner.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
-
const IGNORE = [
|
|
3
|
+
const IGNORE = [
|
|
4
|
+
"node_modules",
|
|
5
|
+
".git",
|
|
6
|
+
".next",
|
|
7
|
+
"dist",
|
|
8
|
+
"build",
|
|
9
|
+
".env",
|
|
10
|
+
".env.local",
|
|
11
|
+
".env.production",
|
|
12
|
+
"__pycache__",
|
|
13
|
+
".vscode",
|
|
14
|
+
".idea",
|
|
15
|
+
"coverage",
|
|
16
|
+
];
|
|
4
17
|
const EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
|
|
5
18
|
export async function scan(dir) {
|
|
6
19
|
const files = [];
|
|
@@ -13,18 +26,21 @@ export async function scan(dir) {
|
|
|
13
26
|
return;
|
|
14
27
|
}
|
|
15
28
|
for (const entry of entries) {
|
|
29
|
+
// Skip ignored
|
|
16
30
|
if (IGNORE.includes(entry.name))
|
|
17
31
|
continue;
|
|
18
32
|
if (entry.name.startsWith("."))
|
|
19
33
|
continue;
|
|
34
|
+
if (entry.name.includes(".test."))
|
|
35
|
+
continue;
|
|
36
|
+
if (entry.name.includes(".spec."))
|
|
37
|
+
continue;
|
|
20
38
|
const full = path.join(current, entry.name);
|
|
21
39
|
if (entry.isDirectory()) {
|
|
22
40
|
await walk(full);
|
|
23
41
|
}
|
|
24
42
|
else if (EXTENSIONS.includes(path.extname(entry.name))) {
|
|
25
|
-
|
|
26
|
-
files.push(full);
|
|
27
|
-
}
|
|
43
|
+
files.push(full);
|
|
28
44
|
}
|
|
29
45
|
}
|
|
30
46
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
export interface ParsedFile {
|
|
2
2
|
path: string;
|
|
3
3
|
name: string;
|
|
4
|
-
exports:
|
|
5
|
-
functions: string[];
|
|
6
|
-
constants: string[];
|
|
7
|
-
types: string[];
|
|
8
|
-
components: string[];
|
|
9
|
-
};
|
|
4
|
+
exports: string[];
|
|
10
5
|
}
|
|
11
|
-
export interface
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
hooks: ParsedFile[];
|
|
15
|
-
utils: ParsedFile[];
|
|
16
|
-
other: ParsedFile[];
|
|
6
|
+
export interface FolderContent {
|
|
7
|
+
folder: string;
|
|
8
|
+
files: ParsedFile[];
|
|
17
9
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eloquence98/ctx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Scan your codebase. Get a clean summary. Paste it to AI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,4 +36,4 @@
|
|
|
36
36
|
"tsx": "^4.0.0",
|
|
37
37
|
"typescript": "^5.0.0"
|
|
38
38
|
}
|
|
39
|
-
}
|
|
39
|
+
}
|
package/dist/adapters/express.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import fs from "fs/promises";
|
|
2
|
-
import path from "path";
|
|
3
|
-
export const expressAdapter = {
|
|
4
|
-
name: "express",
|
|
5
|
-
async detect(dir) {
|
|
6
|
-
try {
|
|
7
|
-
const pkg = await fs.readFile(path.join(dir, "package.json"), "utf-8");
|
|
8
|
-
const parsed = JSON.parse(pkg);
|
|
9
|
-
const deps = { ...parsed.dependencies, ...parsed.devDependencies };
|
|
10
|
-
return ("express" in deps ||
|
|
11
|
-
"fastify" in deps ||
|
|
12
|
-
"koa" in deps ||
|
|
13
|
-
"hono" in deps);
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
async analyze(dir, files) {
|
|
20
|
-
const sections = new Map();
|
|
21
|
-
const folderMappings = {
|
|
22
|
-
Routes: ["routes", "routers", "api"],
|
|
23
|
-
Controllers: ["controllers", "handlers"],
|
|
24
|
-
Services: ["services"],
|
|
25
|
-
Models: ["models", "entities", "schemas"],
|
|
26
|
-
Middleware: ["middleware", "middlewares"],
|
|
27
|
-
Utils: ["utils", "helpers", "lib"],
|
|
28
|
-
Config: ["config", "configs"],
|
|
29
|
-
};
|
|
30
|
-
for (const file of files) {
|
|
31
|
-
const relativePath = path.relative(dir, file.filePath);
|
|
32
|
-
const parts = relativePath.split(path.sep);
|
|
33
|
-
let matched = false;
|
|
34
|
-
for (const [sectionName, folders] of Object.entries(folderMappings)) {
|
|
35
|
-
const folderIndex = parts.findIndex((p) => folders.includes(p.toLowerCase()));
|
|
36
|
-
if (folderIndex !== -1) {
|
|
37
|
-
if (!sections.has(sectionName)) {
|
|
38
|
-
sections.set(sectionName, []);
|
|
39
|
-
}
|
|
40
|
-
sections.get(sectionName).push(file);
|
|
41
|
-
matched = true;
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (!matched) {
|
|
46
|
-
const folder = parts.length > 1 ? parts[0] : "_root";
|
|
47
|
-
if (!sections.has(folder)) {
|
|
48
|
-
sections.set(folder, []);
|
|
49
|
-
}
|
|
50
|
-
sections.get(folder).push(file);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
projectType: "Express/Fastify",
|
|
55
|
-
sections,
|
|
56
|
-
};
|
|
57
|
-
},
|
|
58
|
-
};
|
package/dist/adapters/index.d.ts
DELETED
package/dist/adapters/index.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { expressAdapter } from "./express.js";
|
|
2
|
-
import { nextjsAdapter } from "./nextjs.js";
|
|
3
|
-
import { vanillaAdapter } from "./vanilla.js";
|
|
4
|
-
const adapters = {
|
|
5
|
-
nextjs: nextjsAdapter,
|
|
6
|
-
react: vanillaAdapter,
|
|
7
|
-
express: expressAdapter,
|
|
8
|
-
nestjs: expressAdapter, // Similar structure
|
|
9
|
-
vue: vanillaAdapter,
|
|
10
|
-
sveltekit: vanillaAdapter,
|
|
11
|
-
node: vanillaAdapter,
|
|
12
|
-
vanilla: vanillaAdapter,
|
|
13
|
-
};
|
|
14
|
-
export function getAdapter(projectType) {
|
|
15
|
-
return adapters[projectType] || vanillaAdapter;
|
|
16
|
-
}
|
package/dist/adapters/nextjs.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import fs from "fs/promises";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { getRoutes } from "../scanner.js";
|
|
4
|
-
export const nextjsAdapter = {
|
|
5
|
-
name: "nextjs",
|
|
6
|
-
async detect(dir) {
|
|
7
|
-
const configFiles = ["next.config.js", "next.config.mjs", "next.config.ts"];
|
|
8
|
-
for (const file of configFiles) {
|
|
9
|
-
try {
|
|
10
|
-
await fs.access(path.join(dir, file));
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
// Check package.json for next dependency
|
|
18
|
-
try {
|
|
19
|
-
const pkg = await fs.readFile(path.join(dir, "package.json"), "utf-8");
|
|
20
|
-
const parsed = JSON.parse(pkg);
|
|
21
|
-
const deps = { ...parsed.dependencies, ...parsed.devDependencies };
|
|
22
|
-
return "next" in deps;
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
async analyze(dir, files) {
|
|
29
|
-
const sections = new Map();
|
|
30
|
-
// Get routes
|
|
31
|
-
let routes = [];
|
|
32
|
-
const appDir = await findAppDirectory(dir);
|
|
33
|
-
if (appDir) {
|
|
34
|
-
routes = await getRoutes(appDir);
|
|
35
|
-
}
|
|
36
|
-
// Group by known Next.js folders
|
|
37
|
-
const folderMappings = {
|
|
38
|
-
Features: ["features", "modules", "domains"],
|
|
39
|
-
Components: ["components"],
|
|
40
|
-
Hooks: ["hooks"],
|
|
41
|
-
Lib: ["lib", "utils", "helpers", "services"],
|
|
42
|
-
API: ["api"],
|
|
43
|
-
};
|
|
44
|
-
for (const file of files) {
|
|
45
|
-
const relativePath = path.relative(dir, file.filePath);
|
|
46
|
-
const parts = relativePath.split(path.sep);
|
|
47
|
-
let matched = false;
|
|
48
|
-
for (const [sectionName, folders] of Object.entries(folderMappings)) {
|
|
49
|
-
const folderIndex = parts.findIndex((p) => folders.includes(p));
|
|
50
|
-
if (folderIndex !== -1) {
|
|
51
|
-
const subParts = parts.slice(folderIndex + 1);
|
|
52
|
-
if (subParts.length >= 1) {
|
|
53
|
-
// Use subfolder name if exists, otherwise use section name
|
|
54
|
-
const subFolder = subParts.length > 1 ? subParts[0] : "_direct";
|
|
55
|
-
const key = subFolder === "_direct"
|
|
56
|
-
? sectionName
|
|
57
|
-
: `${sectionName}/${subParts[0]}`;
|
|
58
|
-
if (!sections.has(key)) {
|
|
59
|
-
sections.set(key, []);
|
|
60
|
-
}
|
|
61
|
-
sections.get(key).push(file);
|
|
62
|
-
matched = true;
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
// If no match, group by top folder
|
|
68
|
-
if (!matched) {
|
|
69
|
-
const folder = parts.length > 1 ? parts[0] : "_root";
|
|
70
|
-
const key = `Other/${folder}`;
|
|
71
|
-
if (!sections.has(key)) {
|
|
72
|
-
sections.set(key, []);
|
|
73
|
-
}
|
|
74
|
-
sections.get(key).push(file);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
projectType: "Next.js",
|
|
79
|
-
sections,
|
|
80
|
-
routes,
|
|
81
|
-
};
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
async function findAppDirectory(basePath) {
|
|
85
|
-
const possiblePaths = [
|
|
86
|
-
path.join(basePath, "app"),
|
|
87
|
-
path.join(basePath, "src", "app"),
|
|
88
|
-
path.join(basePath, "src/app"),
|
|
89
|
-
];
|
|
90
|
-
for (const p of possiblePaths) {
|
|
91
|
-
try {
|
|
92
|
-
const stat = await fs.stat(p);
|
|
93
|
-
if (stat.isDirectory()) {
|
|
94
|
-
return p;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return null;
|
|
102
|
-
}
|
package/dist/adapters/types.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { FileExports } from "../types.js";
|
|
2
|
-
export interface AdapterContext {
|
|
3
|
-
projectType: string;
|
|
4
|
-
sections: Map<string, FileExports[]>;
|
|
5
|
-
routes?: string[];
|
|
6
|
-
}
|
|
7
|
-
export interface Adapter {
|
|
8
|
-
name: string;
|
|
9
|
-
detect: (dir: string) => Promise<boolean>;
|
|
10
|
-
analyze: (dir: string, files: FileExports[]) => Promise<AdapterContext>;
|
|
11
|
-
}
|
package/dist/adapters/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/adapters/vanilla.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
export const vanillaAdapter = {
|
|
3
|
-
name: "vanilla",
|
|
4
|
-
async detect() {
|
|
5
|
-
// Always returns true - this is the fallback
|
|
6
|
-
return true;
|
|
7
|
-
},
|
|
8
|
-
async analyze(dir, files) {
|
|
9
|
-
const sections = new Map();
|
|
10
|
-
for (const file of files) {
|
|
11
|
-
const relativePath = path.relative(dir, file.filePath);
|
|
12
|
-
const parts = relativePath.split(path.sep);
|
|
13
|
-
const folder = parts.length > 1 ? parts[0] : "_root";
|
|
14
|
-
if (!sections.has(folder)) {
|
|
15
|
-
sections.set(folder, []);
|
|
16
|
-
}
|
|
17
|
-
sections.get(folder).push(file);
|
|
18
|
-
}
|
|
19
|
-
return {
|
|
20
|
-
projectType: "JavaScript/TypeScript",
|
|
21
|
-
sections,
|
|
22
|
-
};
|
|
23
|
-
},
|
|
24
|
-
};
|
package/dist/config.d.ts
DELETED
package/dist/config.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export const defaultConfig = {
|
|
2
|
-
entry: "./src",
|
|
3
|
-
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
4
|
-
ignore: [
|
|
5
|
-
"node_modules",
|
|
6
|
-
".git",
|
|
7
|
-
".directory",
|
|
8
|
-
"dist",
|
|
9
|
-
"build",
|
|
10
|
-
".next",
|
|
11
|
-
"*.test.*",
|
|
12
|
-
"*.spec.*",
|
|
13
|
-
"__tests__",
|
|
14
|
-
"*.d.ts",
|
|
15
|
-
".DS_Store",
|
|
16
|
-
],
|
|
17
|
-
maxDepth: 10,
|
|
18
|
-
};
|
|
19
|
-
export const folderCategories = {
|
|
20
|
-
routes: ["app", "pages"],
|
|
21
|
-
features: ["features", "modules", "domains"],
|
|
22
|
-
hooks: ["hooks"],
|
|
23
|
-
lib: ["lib", "utils", "helpers", "services"],
|
|
24
|
-
components: ["components", "ui"],
|
|
25
|
-
};
|
package/dist/detectors/index.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import fs from "fs/promises";
|
|
2
|
-
import path from "path";
|
|
3
|
-
async function readPackageJson(dir) {
|
|
4
|
-
try {
|
|
5
|
-
const content = await fs.readFile(path.join(dir, "package.json"), "utf-8");
|
|
6
|
-
return JSON.parse(content);
|
|
7
|
-
}
|
|
8
|
-
catch {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
async function fileExists(filePath) {
|
|
13
|
-
try {
|
|
14
|
-
await fs.access(filePath);
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
export async function detectProject(dir) {
|
|
22
|
-
const pkg = await readPackageJson(dir);
|
|
23
|
-
const deps = {
|
|
24
|
-
...pkg?.dependencies,
|
|
25
|
-
...pkg?.devDependencies,
|
|
26
|
-
};
|
|
27
|
-
// Check config files first (more specific)
|
|
28
|
-
const hasNextConfig = (await fileExists(path.join(dir, "next.config.js"))) ||
|
|
29
|
-
(await fileExists(path.join(dir, "next.config.mjs"))) ||
|
|
30
|
-
(await fileExists(path.join(dir, "next.config.ts")));
|
|
31
|
-
if (hasNextConfig || deps["next"]) {
|
|
32
|
-
return "nextjs";
|
|
33
|
-
}
|
|
34
|
-
// NestJS
|
|
35
|
-
if (deps["@nestjs/core"]) {
|
|
36
|
-
return "nestjs";
|
|
37
|
-
}
|
|
38
|
-
// SvelteKit
|
|
39
|
-
if (deps["@sveltejs/kit"]) {
|
|
40
|
-
return "sveltekit";
|
|
41
|
-
}
|
|
42
|
-
// Vue
|
|
43
|
-
if (deps["vue"] || deps["nuxt"]) {
|
|
44
|
-
return "vue";
|
|
45
|
-
}
|
|
46
|
-
// Express
|
|
47
|
-
if (deps["express"] || deps["fastify"] || deps["koa"] || deps["hono"]) {
|
|
48
|
-
return "express";
|
|
49
|
-
}
|
|
50
|
-
// React (without Next.js)
|
|
51
|
-
if (deps["react"] && !deps["next"]) {
|
|
52
|
-
return "react";
|
|
53
|
-
}
|
|
54
|
-
// Node.js project (has package.json but no framework)
|
|
55
|
-
if (pkg) {
|
|
56
|
-
return "node";
|
|
57
|
-
}
|
|
58
|
-
// No package.json
|
|
59
|
-
return "vanilla";
|
|
60
|
-
}
|
|
61
|
-
export function getProjectLabel(type) {
|
|
62
|
-
const labels = {
|
|
63
|
-
nextjs: "Next.js",
|
|
64
|
-
react: "React",
|
|
65
|
-
express: "Express/Fastify",
|
|
66
|
-
nestjs: "NestJS",
|
|
67
|
-
vue: "Vue/Nuxt",
|
|
68
|
-
sveltekit: "SvelteKit",
|
|
69
|
-
node: "Node.js",
|
|
70
|
-
vanilla: "JavaScript/TypeScript",
|
|
71
|
-
};
|
|
72
|
-
return labels[type];
|
|
73
|
-
}
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
export function formatAIOptimized(data) {
|
|
2
|
-
const lines = [];
|
|
3
|
-
lines.push(`# Project Context (${data.projectType})`);
|
|
4
|
-
lines.push("");
|
|
5
|
-
// 1. Routes
|
|
6
|
-
if (data.routes && data.routes.length > 0) {
|
|
7
|
-
const routeGroups = parseRouteGroups(data.routes);
|
|
8
|
-
// Filter out empty shell routes
|
|
9
|
-
const filteredGroups = new Map();
|
|
10
|
-
for (const [key, value] of routeGroups) {
|
|
11
|
-
if (value.length > 0) {
|
|
12
|
-
filteredGroups.set(key, value);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
lines.push(`## Routes (${filteredGroups.size})`);
|
|
16
|
-
for (const [group, paths] of filteredGroups) {
|
|
17
|
-
// Max 4 routes, no "+N more"
|
|
18
|
-
const displayPaths = paths.slice(0, 4);
|
|
19
|
-
lines.push(`/${group} → ${displayPaths.join(", ")}`);
|
|
20
|
-
}
|
|
21
|
-
lines.push("");
|
|
22
|
-
}
|
|
23
|
-
// 2. Core Domains
|
|
24
|
-
const features = getSectionsByPattern(data.sections, "features");
|
|
25
|
-
if (features.size > 0) {
|
|
26
|
-
lines.push(`## Core Domains (${features.size})`);
|
|
27
|
-
for (const [name, files] of features) {
|
|
28
|
-
lines.push(formatDomainLine(name, files));
|
|
29
|
-
}
|
|
30
|
-
lines.push("");
|
|
31
|
-
}
|
|
32
|
-
// 3. Auth & Session
|
|
33
|
-
const authFiles = getAuthFiles(data.sections);
|
|
34
|
-
if (authFiles.length > 0) {
|
|
35
|
-
lines.push("## Auth & Session");
|
|
36
|
-
lines.push("sign-in, session handling, token management");
|
|
37
|
-
lines.push("");
|
|
38
|
-
}
|
|
39
|
-
// 4. Shared Lib
|
|
40
|
-
const lib = getSectionsByPattern(data.sections, "lib");
|
|
41
|
-
if (lib.size > 0 || data.sections.has("LIB") || data.sections.has("Lib")) {
|
|
42
|
-
lines.push("## Shared Lib");
|
|
43
|
-
lines.push("utils — formatting, helpers");
|
|
44
|
-
const config = getSectionsByPattern(data.sections, "config");
|
|
45
|
-
if (config.size > 0) {
|
|
46
|
-
lines.push("config — api, uploads, pricing");
|
|
47
|
-
}
|
|
48
|
-
lines.push("");
|
|
49
|
-
}
|
|
50
|
-
// 5. Hooks
|
|
51
|
-
const hooks = data.sections.get("HOOKS") || data.sections.get("Hooks");
|
|
52
|
-
if (hooks && hooks.length > 0) {
|
|
53
|
-
const hookNames = hooks
|
|
54
|
-
.flatMap((f) => f.functions)
|
|
55
|
-
.filter((n) => n.startsWith("use"));
|
|
56
|
-
lines.push(`## Hooks (${hookNames.length})`);
|
|
57
|
-
lines.push(hookNames.join(", "));
|
|
58
|
-
lines.push("");
|
|
59
|
-
}
|
|
60
|
-
// 6. UI Layer
|
|
61
|
-
const components = getSectionsByPattern(data.sections, "components");
|
|
62
|
-
if (components.size > 0) {
|
|
63
|
-
const totalComponents = countTotalFiles(components);
|
|
64
|
-
lines.push("## UI Layer");
|
|
65
|
-
const folders = [...components.keys()]
|
|
66
|
-
.map((k) => k.split("/").pop()?.toLowerCase())
|
|
67
|
-
.filter(Boolean);
|
|
68
|
-
const uniqueFolders = [...new Set(folders)];
|
|
69
|
-
lines.push(`~${totalComponents} components (${uniqueFolders.join(", ")})`);
|
|
70
|
-
lines.push("");
|
|
71
|
-
}
|
|
72
|
-
return lines.join("\n");
|
|
73
|
-
}
|
|
74
|
-
// === Route Parsing ===
|
|
75
|
-
function parseRouteGroups(routes) {
|
|
76
|
-
const groups = new Map();
|
|
77
|
-
let currentTopLevel = "";
|
|
78
|
-
let currentDynamic = "";
|
|
79
|
-
for (const route of routes) {
|
|
80
|
-
const trimmed = route.trim();
|
|
81
|
-
// Skip group markers like (auth), (website)
|
|
82
|
-
if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
// Calculate depth by counting leading spaces
|
|
86
|
-
const depth = route.search(/\S/);
|
|
87
|
-
// Top-level route
|
|
88
|
-
if (depth === 0) {
|
|
89
|
-
currentTopLevel = trimmed;
|
|
90
|
-
currentDynamic = "";
|
|
91
|
-
if (!groups.has(currentTopLevel)) {
|
|
92
|
-
groups.set(currentTopLevel, []);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// First-level dynamic segment like [slug]
|
|
96
|
-
else if (depth === 2 && trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
97
|
-
currentDynamic = trimmed;
|
|
98
|
-
const dynamicKey = `${currentTopLevel}/${currentDynamic}`;
|
|
99
|
-
if (!groups.has(dynamicKey)) {
|
|
100
|
-
groups.set(dynamicKey, []);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// Child routes
|
|
104
|
-
else if (depth >= 2) {
|
|
105
|
-
// Skip dynamic segments and technical routes
|
|
106
|
-
if (isSkippableRoute(trimmed)) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
// Add to dynamic group if exists, otherwise to top level
|
|
110
|
-
if (currentDynamic && depth > 2) {
|
|
111
|
-
const dynamicKey = `${currentTopLevel}/${currentDynamic}`;
|
|
112
|
-
const children = groups.get(dynamicKey);
|
|
113
|
-
if (children && !children.includes(trimmed)) {
|
|
114
|
-
children.push(trimmed);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else if (currentTopLevel) {
|
|
118
|
-
const children = groups.get(currentTopLevel);
|
|
119
|
-
if (children && !children.includes(trimmed)) {
|
|
120
|
-
children.push(trimmed);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Clean up empty groups and dynamic groups with no children
|
|
126
|
-
const cleaned = new Map();
|
|
127
|
-
for (const [key, value] of groups) {
|
|
128
|
-
// Keep if has children OR is a standalone route
|
|
129
|
-
if (value.length > 0 || !key.includes("/")) {
|
|
130
|
-
cleaned.set(key, value);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return cleaned;
|
|
134
|
-
}
|
|
135
|
-
function isSkippableRoute(route) {
|
|
136
|
-
const skipPatterns = [
|
|
137
|
-
"[",
|
|
138
|
-
"]",
|
|
139
|
-
"error",
|
|
140
|
-
"sync",
|
|
141
|
-
"verify-email",
|
|
142
|
-
"success",
|
|
143
|
-
"...nextauth",
|
|
144
|
-
];
|
|
145
|
-
const lower = route.toLowerCase();
|
|
146
|
-
return skipPatterns.some((p) => lower.includes(p));
|
|
147
|
-
}
|
|
148
|
-
// === Section Helpers ===
|
|
149
|
-
function getSectionsByPattern(sections, pattern) {
|
|
150
|
-
const result = new Map();
|
|
151
|
-
for (const [key, value] of sections) {
|
|
152
|
-
if (key.toLowerCase().includes(pattern.toLowerCase())) {
|
|
153
|
-
result.set(key, value);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return result;
|
|
157
|
-
}
|
|
158
|
-
function getAuthFiles(sections) {
|
|
159
|
-
for (const [key, value] of sections) {
|
|
160
|
-
if (key.toLowerCase().includes("auth")) {
|
|
161
|
-
return value;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return [];
|
|
165
|
-
}
|
|
166
|
-
function countTotalFiles(sections) {
|
|
167
|
-
let count = 0;
|
|
168
|
-
for (const files of sections.values()) {
|
|
169
|
-
count += files.length;
|
|
170
|
-
}
|
|
171
|
-
return count;
|
|
172
|
-
}
|
|
173
|
-
// === Domain Formatting ===
|
|
174
|
-
function formatDomainLine(name, files) {
|
|
175
|
-
const cleanName = name
|
|
176
|
-
.replace(/features\//i, "")
|
|
177
|
-
.replace(/FEATURES\//i, "")
|
|
178
|
-
.toLowerCase();
|
|
179
|
-
const actionFiles = files.filter((f) => f.fileName.includes("action") || f.fileName.includes("actions"));
|
|
180
|
-
const actionCount = actionFiles.reduce((sum, f) => sum + f.functions.length, 0);
|
|
181
|
-
const intent = getIntent(cleanName, files);
|
|
182
|
-
return `${cleanName} — ${intent} (${actionCount} actions)`;
|
|
183
|
-
}
|
|
184
|
-
function getIntent(domain, files) {
|
|
185
|
-
const allFunctions = files
|
|
186
|
-
.flatMap((f) => f.functions)
|
|
187
|
-
.join(" ")
|
|
188
|
-
.toLowerCase();
|
|
189
|
-
// Domain-specific intent mapping
|
|
190
|
-
if (domain === "users") {
|
|
191
|
-
return "authenticate, edit profile, manage credentials";
|
|
192
|
-
}
|
|
193
|
-
if (domain === "orders") {
|
|
194
|
-
return "create/edit/cancel";
|
|
195
|
-
}
|
|
196
|
-
if (domain === "estimates") {
|
|
197
|
-
return "create/edit/convert";
|
|
198
|
-
}
|
|
199
|
-
if (domain === "files") {
|
|
200
|
-
return "upload/download";
|
|
201
|
-
}
|
|
202
|
-
// Fallback: derive from function names
|
|
203
|
-
const intents = [];
|
|
204
|
-
if (allFunctions.includes("create"))
|
|
205
|
-
intents.push("create");
|
|
206
|
-
if (allFunctions.includes("edit") || allFunctions.includes("update")) {
|
|
207
|
-
intents.push("edit");
|
|
208
|
-
}
|
|
209
|
-
if (allFunctions.includes("delete") || allFunctions.includes("cancel")) {
|
|
210
|
-
intents.push("cancel");
|
|
211
|
-
}
|
|
212
|
-
if (intents.length === 0)
|
|
213
|
-
return "manage";
|
|
214
|
-
return intents.slice(0, 3).join("/");
|
|
215
|
-
}
|
package/dist/formatters/ai.d.ts
DELETED
package/dist/formatters/ai.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
export function formatAI(ctx) {
|
|
2
|
-
const lines = ["# Codebase Context", ""];
|
|
3
|
-
if (ctx.routes.length) {
|
|
4
|
-
lines.push("## Routes", ...ctx.routes.map((r) => `- ${r}`), "");
|
|
5
|
-
}
|
|
6
|
-
if (ctx.components.length) {
|
|
7
|
-
lines.push("## Components");
|
|
8
|
-
for (const f of ctx.components) {
|
|
9
|
-
const exp = f.exports.components.join(", ");
|
|
10
|
-
if (exp) {
|
|
11
|
-
lines.push(`- ${f.name}: ${exp}`);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
lines.push("");
|
|
15
|
-
}
|
|
16
|
-
if (ctx.hooks.length) {
|
|
17
|
-
lines.push("## Hooks");
|
|
18
|
-
for (const f of ctx.hooks) {
|
|
19
|
-
const hookFns = f.exports.functions.filter((fn) => fn.startsWith("use"));
|
|
20
|
-
if (hookFns.length) {
|
|
21
|
-
lines.push(`- ${hookFns.join(", ")}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
lines.push("");
|
|
25
|
-
}
|
|
26
|
-
if (ctx.utils.length) {
|
|
27
|
-
lines.push("## Utils");
|
|
28
|
-
for (const f of ctx.utils) {
|
|
29
|
-
const exp = [...f.exports.functions, ...f.exports.constants].join(", ");
|
|
30
|
-
if (exp) {
|
|
31
|
-
lines.push(`- ${f.name}: ${exp}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
lines.push("");
|
|
35
|
-
}
|
|
36
|
-
if (ctx.other.length) {
|
|
37
|
-
lines.push("## Other");
|
|
38
|
-
for (const f of ctx.other) {
|
|
39
|
-
const exp = [
|
|
40
|
-
...f.exports.functions,
|
|
41
|
-
...f.exports.constants,
|
|
42
|
-
...f.exports.components,
|
|
43
|
-
].join(", ");
|
|
44
|
-
if (exp) {
|
|
45
|
-
lines.push(`- ${f.name}: ${exp}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
lines.push("");
|
|
49
|
-
}
|
|
50
|
-
return lines.join("\n");
|
|
51
|
-
}
|
package/dist/formatters/human.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
export function formatHuman(ctx) {
|
|
2
|
-
const lines = [
|
|
3
|
-
"╭─────────────────────────────────────╮",
|
|
4
|
-
"│ CODEBASE OVERVIEW │",
|
|
5
|
-
"╰─────────────────────────────────────╯",
|
|
6
|
-
"",
|
|
7
|
-
];
|
|
8
|
-
if (ctx.routes.length) {
|
|
9
|
-
lines.push("📍 ROUTES", "");
|
|
10
|
-
for (const r of ctx.routes) {
|
|
11
|
-
lines.push(` ${r}`);
|
|
12
|
-
}
|
|
13
|
-
lines.push("");
|
|
14
|
-
}
|
|
15
|
-
if (ctx.components.length) {
|
|
16
|
-
lines.push(`🧩 COMPONENTS (${ctx.components.length} files)`, "");
|
|
17
|
-
for (const f of ctx.components.slice(0, 10)) {
|
|
18
|
-
if (f.exports.components.length) {
|
|
19
|
-
lines.push(` ${f.name}`);
|
|
20
|
-
for (const c of f.exports.components.slice(0, 3)) {
|
|
21
|
-
lines.push(` └─ ${c}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
if (ctx.components.length > 10) {
|
|
26
|
-
lines.push(` ... and ${ctx.components.length - 10} more`);
|
|
27
|
-
}
|
|
28
|
-
lines.push("");
|
|
29
|
-
}
|
|
30
|
-
if (ctx.hooks.length) {
|
|
31
|
-
const allHooks = ctx.hooks.flatMap((f) => f.exports.functions.filter((fn) => fn.startsWith("use")));
|
|
32
|
-
if (allHooks.length) {
|
|
33
|
-
lines.push(`🪝 HOOKS (${allHooks.length})`, "");
|
|
34
|
-
for (const hook of allHooks) {
|
|
35
|
-
lines.push(` • ${hook}()`);
|
|
36
|
-
}
|
|
37
|
-
lines.push("");
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (ctx.utils.length) {
|
|
41
|
-
lines.push(`🔧 UTILITIES`, "");
|
|
42
|
-
for (const f of ctx.utils.slice(0, 10)) {
|
|
43
|
-
if (f.exports.functions.length) {
|
|
44
|
-
lines.push(` ${f.name}`);
|
|
45
|
-
for (const fn of f.exports.functions.slice(0, 3)) {
|
|
46
|
-
lines.push(` • ${fn}()`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
lines.push("");
|
|
51
|
-
}
|
|
52
|
-
return lines.join("\n");
|
|
53
|
-
}
|
package/dist/formatters/index.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
export function formatMarkdown(data) {
|
|
2
|
-
let output = "";
|
|
3
|
-
// Project type header
|
|
4
|
-
output += `📦 Project: ${data.projectType}\n\n`;
|
|
5
|
-
// Routes section (if exists)
|
|
6
|
-
if (data.routes && data.routes.length > 0) {
|
|
7
|
-
output += `=== ROUTES ===\n\n`;
|
|
8
|
-
for (const route of data.routes) {
|
|
9
|
-
output += `${route}\n`;
|
|
10
|
-
}
|
|
11
|
-
output += "\n";
|
|
12
|
-
}
|
|
13
|
-
// All sections
|
|
14
|
-
for (const [sectionName, files] of data.sections) {
|
|
15
|
-
if (sectionName === "_root")
|
|
16
|
-
continue;
|
|
17
|
-
if (files.length === 0)
|
|
18
|
-
continue;
|
|
19
|
-
// Check if any file has exports
|
|
20
|
-
const hasExports = files.some((f) => f.functions.length > 0 ||
|
|
21
|
-
f.constants.length > 0 ||
|
|
22
|
-
f.types.length > 0 ||
|
|
23
|
-
f.interfaces.length > 0 ||
|
|
24
|
-
f.classes.length > 0);
|
|
25
|
-
if (!hasExports)
|
|
26
|
-
continue;
|
|
27
|
-
output += `=== ${sectionName.toUpperCase()} ===\n\n`;
|
|
28
|
-
for (const file of files) {
|
|
29
|
-
output += formatFile(file);
|
|
30
|
-
}
|
|
31
|
-
output += "\n";
|
|
32
|
-
}
|
|
33
|
-
// Root files last
|
|
34
|
-
const rootFiles = data.sections.get("_root");
|
|
35
|
-
if (rootFiles && rootFiles.length > 0) {
|
|
36
|
-
const hasRootExports = rootFiles.some((f) => f.functions.length > 0 ||
|
|
37
|
-
f.constants.length > 0 ||
|
|
38
|
-
f.types.length > 0 ||
|
|
39
|
-
f.interfaces.length > 0 ||
|
|
40
|
-
f.classes.length > 0);
|
|
41
|
-
if (hasRootExports) {
|
|
42
|
-
output += `=== ROOT FILES ===\n\n`;
|
|
43
|
-
for (const file of rootFiles) {
|
|
44
|
-
output += formatFile(file);
|
|
45
|
-
}
|
|
46
|
-
output += "\n";
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
output += `=== DONE ===\n`;
|
|
50
|
-
return output;
|
|
51
|
-
}
|
|
52
|
-
function formatFile(file) {
|
|
53
|
-
const hasExports = file.functions.length > 0 ||
|
|
54
|
-
file.constants.length > 0 ||
|
|
55
|
-
file.types.length > 0 ||
|
|
56
|
-
file.interfaces.length > 0 ||
|
|
57
|
-
file.classes.length > 0;
|
|
58
|
-
if (!hasExports)
|
|
59
|
-
return "";
|
|
60
|
-
let output = `${file.fileName}\n`;
|
|
61
|
-
for (const fn of file.functions) {
|
|
62
|
-
output += `• function: ${fn}\n`;
|
|
63
|
-
}
|
|
64
|
-
for (const constant of file.constants) {
|
|
65
|
-
output += `• constant: ${constant}\n`;
|
|
66
|
-
}
|
|
67
|
-
for (const type of file.types) {
|
|
68
|
-
output += `• type: ${type}\n`;
|
|
69
|
-
}
|
|
70
|
-
for (const iface of file.interfaces) {
|
|
71
|
-
output += `• interface: ${iface}\n`;
|
|
72
|
-
}
|
|
73
|
-
for (const cls of file.classes) {
|
|
74
|
-
output += `• class: ${cls}\n`;
|
|
75
|
-
}
|
|
76
|
-
return output;
|
|
77
|
-
}
|
package/dist/formatters/raw.d.ts
DELETED
package/dist/formatters/raw.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
export function formatRaw(data) {
|
|
2
|
-
const lines = [];
|
|
3
|
-
lines.push(`# ${data.projectType} Project Context`);
|
|
4
|
-
lines.push("");
|
|
5
|
-
// Routes
|
|
6
|
-
if (data.routes && data.routes.length > 0) {
|
|
7
|
-
lines.push("## Routes");
|
|
8
|
-
lines.push("");
|
|
9
|
-
for (const route of data.routes) {
|
|
10
|
-
lines.push(`- ${route}`);
|
|
11
|
-
}
|
|
12
|
-
lines.push("");
|
|
13
|
-
}
|
|
14
|
-
// Sections
|
|
15
|
-
for (const [sectionName, files] of data.sections) {
|
|
16
|
-
if (sectionName === "_root")
|
|
17
|
-
continue;
|
|
18
|
-
if (files.length === 0)
|
|
19
|
-
continue;
|
|
20
|
-
lines.push(`## ${sectionName}`);
|
|
21
|
-
lines.push("");
|
|
22
|
-
for (const file of files) {
|
|
23
|
-
const exports = getExportsSummary(file);
|
|
24
|
-
if (exports) {
|
|
25
|
-
lines.push(`- ${file.fileName}: ${exports}`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
lines.push("");
|
|
29
|
-
}
|
|
30
|
-
return lines.join("\n");
|
|
31
|
-
}
|
|
32
|
-
function getExportsSummary(file) {
|
|
33
|
-
const parts = [];
|
|
34
|
-
if (file.functions.length > 0) {
|
|
35
|
-
parts.push(file.functions.map((f) => `${f}()`).join(", "));
|
|
36
|
-
}
|
|
37
|
-
if (file.constants.length > 0) {
|
|
38
|
-
parts.push(file.constants.join(", "));
|
|
39
|
-
}
|
|
40
|
-
if (file.types.length > 0) {
|
|
41
|
-
parts.push(file.types.map((t) => `type ${t}`).join(", "));
|
|
42
|
-
}
|
|
43
|
-
if (file.interfaces.length > 0) {
|
|
44
|
-
parts.push(file.interfaces.map((i) => `interface ${i}`).join(", "));
|
|
45
|
-
}
|
|
46
|
-
return parts.join(" | ");
|
|
47
|
-
}
|
package/dist/organizer.d.ts
DELETED
package/dist/organizer.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import fs from "fs/promises";
|
|
3
|
-
export async function organize(files, baseDir) {
|
|
4
|
-
const routes = await getRoutes(baseDir);
|
|
5
|
-
const components = [];
|
|
6
|
-
const hooks = [];
|
|
7
|
-
const utils = [];
|
|
8
|
-
const other = [];
|
|
9
|
-
for (const file of files) {
|
|
10
|
-
const rel = file.path.toLowerCase();
|
|
11
|
-
const hasHooks = file.exports.functions.some((fn) => fn.startsWith("use"));
|
|
12
|
-
if (hasHooks || file.name.startsWith("use") || rel.includes("/hooks/")) {
|
|
13
|
-
hooks.push(file);
|
|
14
|
-
}
|
|
15
|
-
else if (rel.includes("/components/") || rel.includes("/ui/")) {
|
|
16
|
-
components.push(file);
|
|
17
|
-
}
|
|
18
|
-
else if (rel.includes("/lib/") ||
|
|
19
|
-
rel.includes("/utils/") ||
|
|
20
|
-
rel.includes("/helpers/")) {
|
|
21
|
-
utils.push(file);
|
|
22
|
-
}
|
|
23
|
-
else if (!rel.includes("/app/") && !rel.includes("/pages/")) {
|
|
24
|
-
other.push(file);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return { routes, components, hooks, utils, other };
|
|
28
|
-
}
|
|
29
|
-
async function getRoutes(baseDir) {
|
|
30
|
-
const possibleDirs = [
|
|
31
|
-
path.join(baseDir, "app"),
|
|
32
|
-
path.join(baseDir, "src", "app"),
|
|
33
|
-
path.join(baseDir, "pages"),
|
|
34
|
-
path.join(baseDir, "src", "pages"),
|
|
35
|
-
];
|
|
36
|
-
for (const dir of possibleDirs) {
|
|
37
|
-
try {
|
|
38
|
-
await fs.access(dir);
|
|
39
|
-
return walkRoutes(dir, "");
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
async function walkRoutes(dir, prefix) {
|
|
48
|
-
const routes = [];
|
|
49
|
-
let entries;
|
|
50
|
-
try {
|
|
51
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return routes;
|
|
55
|
-
}
|
|
56
|
-
for (const entry of entries) {
|
|
57
|
-
if (!entry.isDirectory())
|
|
58
|
-
continue;
|
|
59
|
-
if (entry.name.startsWith("_"))
|
|
60
|
-
continue;
|
|
61
|
-
if (entry.name.startsWith("("))
|
|
62
|
-
continue;
|
|
63
|
-
if (entry.name === "api")
|
|
64
|
-
continue;
|
|
65
|
-
const route = prefix + "/" + entry.name;
|
|
66
|
-
routes.push(route);
|
|
67
|
-
routes.push(...(await walkRoutes(path.join(dir, entry.name), route)));
|
|
68
|
-
}
|
|
69
|
-
return routes;
|
|
70
|
-
}
|