pi-compass 0.2.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 +74 -0
- package/dist/analyzers/build-script-detector.d.ts +3 -0
- package/dist/analyzers/build-script-detector.d.ts.map +1 -0
- package/dist/analyzers/build-script-detector.js +75 -0
- package/dist/analyzers/build-script-detector.js.map +1 -0
- package/dist/analyzers/convention-detector.d.ts +3 -0
- package/dist/analyzers/convention-detector.d.ts.map +1 -0
- package/dist/analyzers/convention-detector.js +47 -0
- package/dist/analyzers/convention-detector.js.map +1 -0
- package/dist/analyzers/directory-tree.d.ts +4 -0
- package/dist/analyzers/directory-tree.d.ts.map +1 -0
- package/dist/analyzers/directory-tree.js +60 -0
- package/dist/analyzers/directory-tree.js.map +1 -0
- package/dist/analyzers/entry-point-detector.d.ts +3 -0
- package/dist/analyzers/entry-point-detector.d.ts.map +1 -0
- package/dist/analyzers/entry-point-detector.js +87 -0
- package/dist/analyzers/entry-point-detector.js.map +1 -0
- package/dist/analyzers/framework-detector.d.ts +3 -0
- package/dist/analyzers/framework-detector.d.ts.map +1 -0
- package/dist/analyzers/framework-detector.js +63 -0
- package/dist/analyzers/framework-detector.js.map +1 -0
- package/dist/analyzers/index.d.ts +8 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +8 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/key-file-detector.d.ts +3 -0
- package/dist/analyzers/key-file-detector.d.ts.map +1 -0
- package/dist/analyzers/key-file-detector.js +32 -0
- package/dist/analyzers/key-file-detector.js.map +1 -0
- package/dist/analyzers/package-detector.d.ts +3 -0
- package/dist/analyzers/package-detector.d.ts.map +1 -0
- package/dist/analyzers/package-detector.js +78 -0
- package/dist/analyzers/package-detector.js.map +1 -0
- package/dist/codemap-formatter.d.ts +4 -0
- package/dist/codemap-formatter.d.ts.map +1 -0
- package/dist/codemap-formatter.js +72 -0
- package/dist/codemap-formatter.js.map +1 -0
- package/dist/codemap-generator.d.ts +10 -0
- package/dist/codemap-generator.d.ts.map +1 -0
- package/dist/codemap-generator.js +80 -0
- package/dist/codemap-generator.js.map +1 -0
- package/dist/codemap-injector.d.ts +12 -0
- package/dist/codemap-injector.d.ts.map +1 -0
- package/dist/codemap-injector.js +20 -0
- package/dist/codemap-injector.js.map +1 -0
- package/dist/fs-utils.d.ts +3 -0
- package/dist/fs-utils.d.ts.map +1 -0
- package/dist/fs-utils.js +21 -0
- package/dist/fs-utils.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/onboard-command.d.ts +5 -0
- package/dist/onboard-command.d.ts.map +1 -0
- package/dist/onboard-command.js +33 -0
- package/dist/onboard-command.js.map +1 -0
- package/dist/onboard-tools.d.ts +4 -0
- package/dist/onboard-tools.d.ts.map +1 -0
- package/dist/onboard-tools.js +77 -0
- package/dist/onboard-tools.js.map +1 -0
- package/dist/project.d.ts +4 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +20 -0
- package/dist/project.js.map +1 -0
- package/dist/storage.d.ts +12 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +46 -0
- package/dist/storage.js.map +1 -0
- package/dist/tour-command.d.ts +5 -0
- package/dist/tour-command.d.ts.map +1 -0
- package/dist/tour-command.js +26 -0
- package/dist/tour-command.js.map +1 -0
- package/dist/tour-generator.d.ts +6 -0
- package/dist/tour-generator.d.ts.map +1 -0
- package/dist/tour-generator.js +204 -0
- package/dist/tour-generator.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +71 -0
- package/src/analyzers/build-script-detector.ts +85 -0
- package/src/analyzers/convention-detector.ts +52 -0
- package/src/analyzers/directory-tree.ts +65 -0
- package/src/analyzers/entry-point-detector.ts +98 -0
- package/src/analyzers/framework-detector.ts +76 -0
- package/src/analyzers/index.ts +7 -0
- package/src/analyzers/key-file-detector.ts +36 -0
- package/src/analyzers/package-detector.ts +87 -0
- package/src/codemap-formatter.ts +90 -0
- package/src/codemap-generator.ts +110 -0
- package/src/codemap-injector.ts +44 -0
- package/src/fs-utils.ts +19 -0
- package/src/index.ts +90 -0
- package/src/onboard-command.ts +60 -0
- package/src/onboard-tools.ts +116 -0
- package/src/project.ts +29 -0
- package/src/storage.ts +82 -0
- package/src/tour-command.ts +50 -0
- package/src/tour-generator.ts +237 -0
- package/src/types.ts +104 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { DirectoryEntry } from "../types.js";
|
|
4
|
+
|
|
5
|
+
const IGNORED_DIRS = new Set([
|
|
6
|
+
"node_modules", ".git", "dist", "build", ".next", ".nuxt",
|
|
7
|
+
"__pycache__", ".tox", ".mypy_cache", ".pytest_cache",
|
|
8
|
+
"target", ".gradle", ".idea", ".vscode",
|
|
9
|
+
"vendor", "coverage", ".turbo", ".cache",
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export function buildDirectoryTree(cwd: string, depth = 2): readonly DirectoryEntry[] {
|
|
13
|
+
return scanDir(cwd, cwd, depth);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function scanDir(dir: string, root: string, remaining: number): DirectoryEntry[] {
|
|
17
|
+
if (remaining <= 0) return [];
|
|
18
|
+
|
|
19
|
+
let entries: string[];
|
|
20
|
+
try {
|
|
21
|
+
entries = readdirSync(dir);
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result: DirectoryEntry[] = [];
|
|
27
|
+
|
|
28
|
+
for (const name of entries.sort()) {
|
|
29
|
+
if (name.startsWith(".") && name !== ".github") continue;
|
|
30
|
+
if (IGNORED_DIRS.has(name)) continue;
|
|
31
|
+
|
|
32
|
+
const fullPath = join(dir, name);
|
|
33
|
+
let stat;
|
|
34
|
+
try {
|
|
35
|
+
stat = statSync(fullPath);
|
|
36
|
+
} catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (stat.isDirectory()) {
|
|
41
|
+
const children = remaining > 1 ? scanDir(fullPath, root, remaining - 1) : undefined;
|
|
42
|
+
result.push({ name, type: "dir", ...(children ? { children } : {}) });
|
|
43
|
+
} else if (stat.isFile()) {
|
|
44
|
+
result.push({ name, type: "file" });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function formatDirectoryTree(entries: readonly DirectoryEntry[], indent = ""): string {
|
|
52
|
+
const lines: string[] = [];
|
|
53
|
+
for (let i = 0; i < entries.length; i++) {
|
|
54
|
+
const entry = entries[i]!;
|
|
55
|
+
const isLast = i === entries.length - 1;
|
|
56
|
+
const prefix = isLast ? "└── " : "├── ";
|
|
57
|
+
const childIndent = indent + (isLast ? " " : "│ ");
|
|
58
|
+
|
|
59
|
+
lines.push(`${indent}${prefix}${entry.name}${entry.type === "dir" ? "/" : ""}`);
|
|
60
|
+
if (entry.children && entry.children.length > 0) {
|
|
61
|
+
lines.push(formatDirectoryTree(entry.children, childIndent));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { PackageInfo, EntryPoint } from "../types.js";
|
|
4
|
+
|
|
5
|
+
const COMMON_ENTRY_POINTS: readonly { path: string; kind: EntryPoint["kind"] }[] = [
|
|
6
|
+
{ path: "src/index.ts", kind: "index" },
|
|
7
|
+
{ path: "src/index.tsx", kind: "index" },
|
|
8
|
+
{ path: "src/index.js", kind: "index" },
|
|
9
|
+
{ path: "src/main.ts", kind: "main" },
|
|
10
|
+
{ path: "src/main.tsx", kind: "main" },
|
|
11
|
+
{ path: "src/main.js", kind: "main" },
|
|
12
|
+
{ path: "src/app.ts", kind: "main" },
|
|
13
|
+
{ path: "src/app.tsx", kind: "main" },
|
|
14
|
+
{ path: "src/app.js", kind: "main" },
|
|
15
|
+
{ path: "index.ts", kind: "index" },
|
|
16
|
+
{ path: "index.js", kind: "index" },
|
|
17
|
+
{ path: "main.go", kind: "main" },
|
|
18
|
+
{ path: "cmd/main.go", kind: "main" },
|
|
19
|
+
{ path: "src/main.rs", kind: "main" },
|
|
20
|
+
{ path: "src/lib.rs", kind: "main" },
|
|
21
|
+
{ path: "manage.py", kind: "main" },
|
|
22
|
+
{ path: "app.py", kind: "main" },
|
|
23
|
+
{ path: "main.py", kind: "main" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const ROUTE_DIRS: readonly string[] = [
|
|
27
|
+
"src/routes",
|
|
28
|
+
"src/pages",
|
|
29
|
+
"src/api",
|
|
30
|
+
"app/routes",
|
|
31
|
+
"app/api",
|
|
32
|
+
"pages",
|
|
33
|
+
"routes",
|
|
34
|
+
"api",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const CONFIG_FILES: readonly string[] = [
|
|
38
|
+
"next.config.js",
|
|
39
|
+
"next.config.ts",
|
|
40
|
+
"next.config.mjs",
|
|
41
|
+
"vite.config.ts",
|
|
42
|
+
"vite.config.js",
|
|
43
|
+
"webpack.config.js",
|
|
44
|
+
"webpack.config.ts",
|
|
45
|
+
"nuxt.config.ts",
|
|
46
|
+
"astro.config.mjs",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
export function detectEntryPoints(
|
|
50
|
+
cwd: string,
|
|
51
|
+
packages: readonly PackageInfo[],
|
|
52
|
+
): readonly EntryPoint[] {
|
|
53
|
+
const results: EntryPoint[] = [];
|
|
54
|
+
const seen = new Set<string>();
|
|
55
|
+
|
|
56
|
+
for (const pkg of packages) {
|
|
57
|
+
if (pkg.manager === "npm") {
|
|
58
|
+
const mainField = extractMainField(cwd);
|
|
59
|
+
if (mainField && !seen.has(mainField)) {
|
|
60
|
+
seen.add(mainField);
|
|
61
|
+
results.push({ path: mainField, kind: "main" });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const { path, kind } of COMMON_ENTRY_POINTS) {
|
|
67
|
+
if (!seen.has(path) && existsSync(join(cwd, path))) {
|
|
68
|
+
seen.add(path);
|
|
69
|
+
results.push({ path, kind });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const dir of ROUTE_DIRS) {
|
|
74
|
+
if (existsSync(join(cwd, dir))) {
|
|
75
|
+
results.push({ path: dir, kind: "route" });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const config of CONFIG_FILES) {
|
|
80
|
+
if (!seen.has(config) && existsSync(join(cwd, config))) {
|
|
81
|
+
seen.add(config);
|
|
82
|
+
results.push({ path: config, kind: "config" });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function extractMainField(cwd: string): string | null {
|
|
90
|
+
try {
|
|
91
|
+
const raw = readFileSync(join(cwd, "package.json"), "utf-8");
|
|
92
|
+
const pkg = JSON.parse(raw) as Record<string, unknown>;
|
|
93
|
+
if (typeof pkg["main"] === "string") return pkg["main"];
|
|
94
|
+
} catch {
|
|
95
|
+
// absent
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { PackageInfo, FrameworkDetection } from "../types.js";
|
|
2
|
+
|
|
3
|
+
interface FrameworkRule {
|
|
4
|
+
readonly dep: string;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly confidence: "definite" | "likely";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const FRAMEWORK_RULES: readonly FrameworkRule[] = [
|
|
10
|
+
{ dep: "next", name: "Next.js", confidence: "definite" },
|
|
11
|
+
{ dep: "react", name: "React", confidence: "definite" },
|
|
12
|
+
{ dep: "vue", name: "Vue", confidence: "definite" },
|
|
13
|
+
{ dep: "@angular/core", name: "Angular", confidence: "definite" },
|
|
14
|
+
{ dep: "svelte", name: "Svelte", confidence: "definite" },
|
|
15
|
+
{ dep: "express", name: "Express", confidence: "definite" },
|
|
16
|
+
{ dep: "fastify", name: "Fastify", confidence: "definite" },
|
|
17
|
+
{ dep: "hono", name: "Hono", confidence: "definite" },
|
|
18
|
+
{ dep: "koa", name: "Koa", confidence: "definite" },
|
|
19
|
+
{ dep: "nestjs", name: "NestJS", confidence: "likely" },
|
|
20
|
+
{ dep: "@nestjs/core", name: "NestJS", confidence: "definite" },
|
|
21
|
+
{ dep: "nuxt", name: "Nuxt", confidence: "definite" },
|
|
22
|
+
{ dep: "remix", name: "Remix", confidence: "likely" },
|
|
23
|
+
{ dep: "@remix-run/react", name: "Remix", confidence: "definite" },
|
|
24
|
+
{ dep: "astro", name: "Astro", confidence: "definite" },
|
|
25
|
+
{ dep: "gatsby", name: "Gatsby", confidence: "definite" },
|
|
26
|
+
{ dep: "tailwindcss", name: "Tailwind CSS", confidence: "definite" },
|
|
27
|
+
{ dep: "prisma", name: "Prisma", confidence: "definite" },
|
|
28
|
+
{ dep: "@prisma/client", name: "Prisma", confidence: "definite" },
|
|
29
|
+
{ dep: "drizzle-orm", name: "Drizzle", confidence: "definite" },
|
|
30
|
+
{ dep: "django", name: "Django", confidence: "definite" },
|
|
31
|
+
{ dep: "flask", name: "Flask", confidence: "definite" },
|
|
32
|
+
{ dep: "fastapi", name: "FastAPI", confidence: "definite" },
|
|
33
|
+
{ dep: "spring-boot", name: "Spring Boot", confidence: "likely" },
|
|
34
|
+
{ dep: "actix-web", name: "Actix Web", confidence: "definite" },
|
|
35
|
+
{ dep: "rocket", name: "Rocket", confidence: "definite" },
|
|
36
|
+
{ dep: "axum", name: "Axum", confidence: "definite" },
|
|
37
|
+
{ dep: "tokio", name: "Tokio", confidence: "definite" },
|
|
38
|
+
{ dep: "gin-gonic/gin", name: "Gin", confidence: "definite" },
|
|
39
|
+
{ dep: "labstack/echo", name: "Echo", confidence: "definite" },
|
|
40
|
+
{ dep: "gofiber/fiber", name: "Fiber", confidence: "definite" },
|
|
41
|
+
{ dep: "laravel/framework", name: "Laravel", confidence: "definite" },
|
|
42
|
+
{ dep: "symfony/framework-bundle", name: "Symfony", confidence: "definite" },
|
|
43
|
+
{ dep: "vitest", name: "Vitest", confidence: "definite" },
|
|
44
|
+
{ dep: "jest", name: "Jest", confidence: "definite" },
|
|
45
|
+
{ dep: "playwright", name: "Playwright", confidence: "definite" },
|
|
46
|
+
{ dep: "@playwright/test", name: "Playwright", confidence: "definite" },
|
|
47
|
+
{ dep: "cypress", name: "Cypress", confidence: "definite" },
|
|
48
|
+
{ dep: "pytest", name: "pytest", confidence: "definite" },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export function detectFrameworks(
|
|
52
|
+
packages: readonly PackageInfo[],
|
|
53
|
+
): readonly FrameworkDetection[] {
|
|
54
|
+
const allDeps = new Set<string>();
|
|
55
|
+
for (const pkg of packages) {
|
|
56
|
+
for (const dep of pkg.dependencies) {
|
|
57
|
+
allDeps.add(dep);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const seen = new Set<string>();
|
|
62
|
+
const results: FrameworkDetection[] = [];
|
|
63
|
+
|
|
64
|
+
for (const rule of FRAMEWORK_RULES) {
|
|
65
|
+
if (allDeps.has(rule.dep) && !seen.has(rule.name)) {
|
|
66
|
+
seen.add(rule.name);
|
|
67
|
+
results.push({
|
|
68
|
+
name: rule.name,
|
|
69
|
+
confidence: rule.confidence,
|
|
70
|
+
source: rule.dep,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { buildDirectoryTree, formatDirectoryTree } from "./directory-tree.js";
|
|
2
|
+
export { detectPackages } from "./package-detector.js";
|
|
3
|
+
export { detectFrameworks } from "./framework-detector.js";
|
|
4
|
+
export { detectEntryPoints } from "./entry-point-detector.js";
|
|
5
|
+
export { detectBuildScripts } from "./build-script-detector.js";
|
|
6
|
+
export { detectConventions } from "./convention-detector.js";
|
|
7
|
+
export { detectKeyFiles } from "./key-file-detector.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { KeyFile } from "../types.js";
|
|
4
|
+
|
|
5
|
+
const KEY_FILES: readonly { path: string; description: string }[] = [
|
|
6
|
+
{ path: "README.md", description: "Project documentation" },
|
|
7
|
+
{ path: "LICENSE", description: "License file" },
|
|
8
|
+
{ path: "LICENSE.md", description: "License file" },
|
|
9
|
+
{ path: "CHANGELOG.md", description: "Release history" },
|
|
10
|
+
{ path: "CONTRIBUTING.md", description: "Contribution guidelines" },
|
|
11
|
+
{ path: "SECURITY.md", description: "Security policy" },
|
|
12
|
+
{ path: "ARCHITECTURE.md", description: "Architecture documentation" },
|
|
13
|
+
{ path: "AGENTS.md", description: "AI agent conventions" },
|
|
14
|
+
{ path: "CLAUDE.md", description: "Claude Code instructions" },
|
|
15
|
+
{ path: ".env.example", description: "Environment variable template" },
|
|
16
|
+
{ path: ".env.sample", description: "Environment variable template" },
|
|
17
|
+
{ path: "Dockerfile", description: "Container build definition" },
|
|
18
|
+
{ path: "docker-compose.yml", description: "Container orchestration" },
|
|
19
|
+
{ path: "docker-compose.yaml", description: "Container orchestration" },
|
|
20
|
+
{ path: "Makefile", description: "Build automation" },
|
|
21
|
+
{ path: ".github/workflows", description: "GitHub Actions CI/CD" },
|
|
22
|
+
{ path: ".gitlab-ci.yml", description: "GitLab CI/CD" },
|
|
23
|
+
{ path: "Jenkinsfile", description: "Jenkins pipeline" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export function detectKeyFiles(cwd: string): readonly KeyFile[] {
|
|
27
|
+
const results: KeyFile[] = [];
|
|
28
|
+
|
|
29
|
+
for (const { path, description } of KEY_FILES) {
|
|
30
|
+
if (existsSync(join(cwd, path))) {
|
|
31
|
+
results.push({ path, description });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { PackageInfo } from "../types.js";
|
|
4
|
+
import { readTextFile, readJsonFile } from "../fs-utils.js";
|
|
5
|
+
|
|
6
|
+
export function detectPackages(cwd: string): readonly PackageInfo[] {
|
|
7
|
+
const results: PackageInfo[] = [];
|
|
8
|
+
|
|
9
|
+
const npmPkg = readJsonFile(join(cwd, "package.json"));
|
|
10
|
+
if (npmPkg) {
|
|
11
|
+
const deps = Object.keys(npmPkg.dependencies ?? {});
|
|
12
|
+
const devDeps = Object.keys(npmPkg.devDependencies ?? {});
|
|
13
|
+
const name = typeof npmPkg.name === "string" ? npmPkg.name : "";
|
|
14
|
+
const version = typeof npmPkg.version === "string" ? npmPkg.version : null;
|
|
15
|
+
results.push({
|
|
16
|
+
manager: "npm",
|
|
17
|
+
name,
|
|
18
|
+
...(version ? { version } : {}),
|
|
19
|
+
dependencies: [...deps, ...devDeps],
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const goMod = readTextFile(join(cwd, "go.mod"));
|
|
24
|
+
if (goMod) {
|
|
25
|
+
const moduleMatch = goMod.match(/^module\s+(\S+)/m);
|
|
26
|
+
const deps = [...goMod.matchAll(/^\t(\S+)\s/gm)].map((m) => m[1]!);
|
|
27
|
+
results.push({
|
|
28
|
+
manager: "go",
|
|
29
|
+
name: moduleMatch?.[1] ?? "",
|
|
30
|
+
dependencies: deps,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cargo = readTextFile(join(cwd, "Cargo.toml"));
|
|
35
|
+
if (cargo) {
|
|
36
|
+
const nameMatch = cargo.match(/^name\s*=\s*"([^"]+)"/m);
|
|
37
|
+
const versionMatch = cargo.match(/^version\s*=\s*"([^"]+)"/m);
|
|
38
|
+
const deps = [...cargo.matchAll(/^\[(?:dev-)?dependencies\.([^\]]+)\]/gm)].map((m) => m[1]!);
|
|
39
|
+
const inlineDeps = [...cargo.matchAll(/^(\w[\w-]*)\s*=\s*(?:"|{)/gm)]
|
|
40
|
+
.map((m) => m[1]!)
|
|
41
|
+
.filter((d) => d !== "name" && d !== "version" && d !== "edition" && d !== "authors");
|
|
42
|
+
const cargoVersion = versionMatch?.[1];
|
|
43
|
+
results.push({
|
|
44
|
+
manager: "cargo",
|
|
45
|
+
name: nameMatch?.[1] ?? "",
|
|
46
|
+
...(cargoVersion ? { version: cargoVersion } : {}),
|
|
47
|
+
dependencies: [...new Set([...deps, ...inlineDeps])],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const pyproject = readTextFile(join(cwd, "pyproject.toml"));
|
|
52
|
+
if (pyproject) {
|
|
53
|
+
const nameMatch = pyproject.match(/^name\s*=\s*"([^"]+)"/m);
|
|
54
|
+
const versionMatch = pyproject.match(/^version\s*=\s*"([^"]+)"/m);
|
|
55
|
+
const depsSection = pyproject.match(/^dependencies\s*=\s*\[([\s\S]*?)\]/m);
|
|
56
|
+
const deps = depsSection
|
|
57
|
+
? [...depsSection[1]!.matchAll(/"([^">=<\s]+)/g)].map((m) => m[1]!)
|
|
58
|
+
: [];
|
|
59
|
+
const hasPoetry = pyproject.includes("[tool.poetry]");
|
|
60
|
+
const pyVersion = versionMatch?.[1];
|
|
61
|
+
results.push({
|
|
62
|
+
manager: hasPoetry ? "poetry" : "pip",
|
|
63
|
+
name: nameMatch?.[1] ?? "",
|
|
64
|
+
...(pyVersion ? { version: pyVersion } : {}),
|
|
65
|
+
dependencies: deps,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const composer = readJsonFile(join(cwd, "composer.json"));
|
|
70
|
+
if (composer) {
|
|
71
|
+
results.push({
|
|
72
|
+
manager: "composer",
|
|
73
|
+
name: typeof composer.name === "string" ? composer.name : "",
|
|
74
|
+
dependencies: Object.keys(composer.require ?? {}),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (existsSync(join(cwd, "build.gradle")) || existsSync(join(cwd, "build.gradle.kts"))) {
|
|
79
|
+
results.push({ manager: "gradle", name: "", dependencies: [] });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (existsSync(join(cwd, "pom.xml"))) {
|
|
83
|
+
results.push({ manager: "maven", name: "", dependencies: [] });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { CodeMap } from "./types.js";
|
|
2
|
+
import { formatDirectoryTree } from "./analyzers/directory-tree.js";
|
|
3
|
+
|
|
4
|
+
export function formatCodemapMarkdown(codemap: CodeMap): string {
|
|
5
|
+
const sections: string[] = [
|
|
6
|
+
`## Codebase Map: ${codemap.projectName}`,
|
|
7
|
+
`Generated: ${codemap.generatedAt} | Hash: ${codemap.contentHash}`,
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
if (codemap.directoryTree.length > 0) {
|
|
11
|
+
sections.push(
|
|
12
|
+
"",
|
|
13
|
+
"### Directory Structure",
|
|
14
|
+
"```",
|
|
15
|
+
formatDirectoryTree(codemap.directoryTree),
|
|
16
|
+
"```",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (codemap.packages.length > 0) {
|
|
21
|
+
sections.push("", "### Packages");
|
|
22
|
+
for (const pkg of codemap.packages) {
|
|
23
|
+
const version = pkg.version ? ` v${pkg.version}` : "";
|
|
24
|
+
sections.push(`- **${pkg.name || "(unnamed)"}**${version} (${pkg.manager})`);
|
|
25
|
+
if (pkg.dependencies.length > 0) {
|
|
26
|
+
const depList = pkg.dependencies.slice(0, 20).join(", ");
|
|
27
|
+
const more = pkg.dependencies.length > 20 ? ` (+${pkg.dependencies.length - 20} more)` : "";
|
|
28
|
+
sections.push(` Dependencies: ${depList}${more}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (codemap.frameworks.length > 0) {
|
|
34
|
+
sections.push("", "### Frameworks");
|
|
35
|
+
for (const fw of codemap.frameworks) {
|
|
36
|
+
const version = fw.version ? ` v${fw.version}` : "";
|
|
37
|
+
const confidence = fw.confidence === "likely" ? " (likely)" : "";
|
|
38
|
+
sections.push(`- ${fw.name}${version}${confidence}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (codemap.entryPoints.length > 0) {
|
|
43
|
+
sections.push("", "### Entry Points");
|
|
44
|
+
for (const ep of codemap.entryPoints) {
|
|
45
|
+
sections.push(`- \`${ep.path}\` (${ep.kind})`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (codemap.buildScripts.length > 0) {
|
|
50
|
+
sections.push("", "### Build / Test / Deploy");
|
|
51
|
+
for (const script of codemap.buildScripts) {
|
|
52
|
+
sections.push(`- **${script.name}**: \`${script.command}\` (${script.source})`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (codemap.conventions.length > 0) {
|
|
57
|
+
sections.push("", "### Conventions");
|
|
58
|
+
for (const conv of codemap.conventions) {
|
|
59
|
+
sections.push(``, `#### ${conv.source}`, conv.content);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (codemap.keyFiles.length > 0) {
|
|
64
|
+
sections.push("", "### Key Files");
|
|
65
|
+
for (const kf of codemap.keyFiles) {
|
|
66
|
+
sections.push(`- \`${kf.path}\` -- ${kf.description}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return sections.join("\n");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function truncateCodemap(markdown: string, maxChars: number): string {
|
|
74
|
+
if (markdown.length <= maxChars) return markdown;
|
|
75
|
+
|
|
76
|
+
const lines = markdown.split("\n");
|
|
77
|
+
let length = 0;
|
|
78
|
+
const kept: string[] = [];
|
|
79
|
+
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
if (length + line.length + 1 > maxChars) {
|
|
82
|
+
kept.push("", "... (codemap truncated, run /onboard to see full output)");
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
kept.push(line);
|
|
86
|
+
length += line.length + 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return kept.join("\n");
|
|
90
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { CodeMap, CacheEntry } from "./types.js";
|
|
5
|
+
import {
|
|
6
|
+
buildDirectoryTree,
|
|
7
|
+
detectPackages,
|
|
8
|
+
detectFrameworks,
|
|
9
|
+
detectEntryPoints,
|
|
10
|
+
detectBuildScripts,
|
|
11
|
+
detectConventions,
|
|
12
|
+
detectKeyFiles,
|
|
13
|
+
} from "./analyzers/index.js";
|
|
14
|
+
import { loadCachedCodemap, saveCachedCodemap } from "./storage.js";
|
|
15
|
+
|
|
16
|
+
const HASH_FILES = [
|
|
17
|
+
"package.json",
|
|
18
|
+
"package-lock.json",
|
|
19
|
+
"tsconfig.json",
|
|
20
|
+
"go.mod",
|
|
21
|
+
"go.sum",
|
|
22
|
+
"Cargo.toml",
|
|
23
|
+
"Cargo.lock",
|
|
24
|
+
"pyproject.toml",
|
|
25
|
+
"composer.json",
|
|
26
|
+
"composer.lock",
|
|
27
|
+
"pom.xml",
|
|
28
|
+
"build.gradle",
|
|
29
|
+
"build.gradle.kts",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function computeContentHash(cwd: string): string {
|
|
33
|
+
const hash = createHash("sha256");
|
|
34
|
+
|
|
35
|
+
for (const file of HASH_FILES) {
|
|
36
|
+
try {
|
|
37
|
+
hash.update(readFileSync(join(cwd, file), "utf-8"));
|
|
38
|
+
} catch {
|
|
39
|
+
// absent
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const entries = readdirSync(cwd).sort();
|
|
45
|
+
hash.update(entries.join("\n"));
|
|
46
|
+
} catch {
|
|
47
|
+
// absent
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return hash.digest("hex").substring(0, 16);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function generateCodemap(
|
|
54
|
+
cwd: string,
|
|
55
|
+
projectId: string,
|
|
56
|
+
projectName: string,
|
|
57
|
+
): CodeMap {
|
|
58
|
+
const directoryTree = buildDirectoryTree(cwd);
|
|
59
|
+
const packages = detectPackages(cwd);
|
|
60
|
+
const frameworks = detectFrameworks(packages);
|
|
61
|
+
const entryPoints = detectEntryPoints(cwd, packages);
|
|
62
|
+
const buildScripts = detectBuildScripts(cwd);
|
|
63
|
+
const conventions = detectConventions(cwd);
|
|
64
|
+
const keyFiles = detectKeyFiles(cwd);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
projectId,
|
|
68
|
+
projectName,
|
|
69
|
+
generatedAt: new Date().toISOString(),
|
|
70
|
+
contentHash: computeContentHash(cwd),
|
|
71
|
+
directoryTree,
|
|
72
|
+
packages,
|
|
73
|
+
frameworks,
|
|
74
|
+
entryPoints,
|
|
75
|
+
buildScripts,
|
|
76
|
+
conventions,
|
|
77
|
+
keyFiles,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface CodemapResult {
|
|
82
|
+
readonly codemap: CodeMap;
|
|
83
|
+
readonly fromCache: boolean;
|
|
84
|
+
readonly stale: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getOrGenerateCodemap(
|
|
88
|
+
cwd: string,
|
|
89
|
+
projectId: string,
|
|
90
|
+
projectName: string,
|
|
91
|
+
baseDir?: string,
|
|
92
|
+
): CodemapResult {
|
|
93
|
+
const cached = loadCachedCodemap(projectId, baseDir);
|
|
94
|
+
if (cached) {
|
|
95
|
+
const currentHash = computeContentHash(cwd);
|
|
96
|
+
if (cached.contentHash === currentHash) {
|
|
97
|
+
return { codemap: cached.data, fromCache: true, stale: false };
|
|
98
|
+
}
|
|
99
|
+
return { codemap: cached.data, fromCache: true, stale: true };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const codemap = generateCodemap(cwd, projectId, projectName);
|
|
103
|
+
const entry: CacheEntry<CodeMap> = {
|
|
104
|
+
data: codemap,
|
|
105
|
+
contentHash: codemap.contentHash,
|
|
106
|
+
createdAt: codemap.generatedAt,
|
|
107
|
+
};
|
|
108
|
+
saveCachedCodemap(projectId, entry, baseDir);
|
|
109
|
+
return { codemap, fromCache: false, stale: false };
|
|
110
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CodeMap, CompassState } from "./types.js";
|
|
2
|
+
import { formatCodemapMarkdown, truncateCodemap } from "./codemap-formatter.js";
|
|
3
|
+
|
|
4
|
+
export interface BeforeAgentStartEvent {
|
|
5
|
+
type: "before_agent_start";
|
|
6
|
+
prompt: string;
|
|
7
|
+
systemPrompt: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface InjectionResult {
|
|
11
|
+
systemPrompt: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildCodemapInjection(
|
|
15
|
+
codemap: CodeMap,
|
|
16
|
+
stale: boolean,
|
|
17
|
+
maxChars: number,
|
|
18
|
+
): string {
|
|
19
|
+
const markdown = formatCodemapMarkdown(codemap);
|
|
20
|
+
const truncated = truncateCodemap(markdown, maxChars);
|
|
21
|
+
const staleNote = stale
|
|
22
|
+
? "\n\n> This codemap may be outdated. Run `/onboard` to refresh."
|
|
23
|
+
: "";
|
|
24
|
+
|
|
25
|
+
return `\n\n${truncated}${staleNote}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function handleBeforeAgentStart(
|
|
29
|
+
event: BeforeAgentStartEvent,
|
|
30
|
+
state: CompassState,
|
|
31
|
+
maxChars: number,
|
|
32
|
+
): InjectionResult | undefined {
|
|
33
|
+
if (state.codemapInjected) return undefined;
|
|
34
|
+
if (!state.cachedCodemap) return undefined;
|
|
35
|
+
|
|
36
|
+
const block = buildCodemapInjection(
|
|
37
|
+
state.cachedCodemap.data,
|
|
38
|
+
state.stale,
|
|
39
|
+
maxChars,
|
|
40
|
+
);
|
|
41
|
+
if (!block) return undefined;
|
|
42
|
+
|
|
43
|
+
return { systemPrompt: event.systemPrompt + block };
|
|
44
|
+
}
|
package/src/fs-utils.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export function readTextFile(path: string): string | null {
|
|
4
|
+
try {
|
|
5
|
+
return readFileSync(path, "utf-8");
|
|
6
|
+
} catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function readJsonFile(path: string): Record<string, unknown> | null {
|
|
12
|
+
const text = readTextFile(path);
|
|
13
|
+
if (text === null) return null;
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(text) as Record<string, unknown>;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|