@cliperhq/cliper 1.0.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 +266 -0
- package/dist/commands/analyze.d.ts +6 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +216 -0
- package/dist/commands/export.d.ts +6 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +64 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +173 -0
- package/dist/commands/scope.d.ts +2 -0
- package/dist/commands/scope.d.ts.map +1 -0
- package/dist/commands/scope.js +124 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +100 -0
- package/dist/commands/sync.d.ts +6 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +83 -0
- package/dist/context/builder.d.ts +19 -0
- package/dist/context/builder.d.ts.map +1 -0
- package/dist/context/builder.js +143 -0
- package/dist/gaps/detector.d.ts +10 -0
- package/dist/gaps/detector.d.ts.map +1 -0
- package/dist/gaps/detector.js +139 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/resolver/urlFetcher.d.ts +9 -0
- package/dist/resolver/urlFetcher.d.ts.map +1 -0
- package/dist/resolver/urlFetcher.js +134 -0
- package/dist/scanner/dependencies.d.ts +14 -0
- package/dist/scanner/dependencies.d.ts.map +1 -0
- package/dist/scanner/dependencies.js +199 -0
- package/dist/scanner/fileContent.d.ts +8 -0
- package/dist/scanner/fileContent.d.ts.map +1 -0
- package/dist/scanner/fileContent.js +133 -0
- package/dist/scanner/fileTree.d.ts +2 -0
- package/dist/scanner/fileTree.d.ts.map +1 -0
- package/dist/scanner/fileTree.js +152 -0
- package/dist/scanner/gitContext.d.ts +19 -0
- package/dist/scanner/gitContext.d.ts.map +1 -0
- package/dist/scanner/gitContext.js +60 -0
- package/dist/scope/autoScope.d.ts +2 -0
- package/dist/scope/autoScope.d.ts.map +1 -0
- package/dist/scope/autoScope.js +226 -0
- package/dist/scope/config.d.ts +10 -0
- package/dist/scope/config.d.ts.map +1 -0
- package/dist/scope/config.js +71 -0
- package/index.js +2 -0
- package/package.json +37 -0
- package/src/commands/analyze.ts +201 -0
- package/src/commands/export.ts +33 -0
- package/src/commands/init.ts +174 -0
- package/src/commands/scope.ts +77 -0
- package/src/commands/status.ts +67 -0
- package/src/commands/sync.ts +51 -0
- package/src/context/builder.ts +178 -0
- package/src/gaps/detector.ts +131 -0
- package/src/index.ts +54 -0
- package/src/resolver/urlFetcher.ts +119 -0
- package/src/scanner/dependencies.ts +196 -0
- package/src/scanner/fileContent.ts +121 -0
- package/src/scanner/fileTree.ts +149 -0
- package/src/scanner/gitContext.ts +74 -0
- package/src/scope/autoScope.ts +182 -0
- package/src/scope/config.ts +39 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import simpleGit from "simple-git";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import { glob } from "glob";
|
|
5
|
+
|
|
6
|
+
// Files always included regardless of scope
|
|
7
|
+
const ALWAYS_INCLUDE = [
|
|
8
|
+
"package.json",
|
|
9
|
+
"package-lock.json",
|
|
10
|
+
"yarn.lock",
|
|
11
|
+
"pnpm-lock.yaml",
|
|
12
|
+
"tsconfig.json",
|
|
13
|
+
"Cargo.toml",
|
|
14
|
+
"Cargo.lock",
|
|
15
|
+
"go.mod",
|
|
16
|
+
"go.sum",
|
|
17
|
+
"requirements.txt",
|
|
18
|
+
"pyproject.toml",
|
|
19
|
+
"README.md",
|
|
20
|
+
".env.example",
|
|
21
|
+
"docker-compose.yml",
|
|
22
|
+
"Dockerfile",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
type ProjectType = "rust" | "node" | "python" | "go" | "unknown";
|
|
26
|
+
|
|
27
|
+
function detectProjectType(projectRoot: string): ProjectType {
|
|
28
|
+
if (fs.existsSync(path.join(projectRoot, "Cargo.toml"))) return "rust";
|
|
29
|
+
if (fs.existsSync(path.join(projectRoot, "package.json"))) return "node";
|
|
30
|
+
if (fs.existsSync(path.join(projectRoot, "pyproject.toml")) ||
|
|
31
|
+
fs.existsSync(path.join(projectRoot, "requirements.txt"))) return "python";
|
|
32
|
+
if (fs.existsSync(path.join(projectRoot, "go.mod"))) return "go";
|
|
33
|
+
return "unknown";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseRustWorkspaceMembers(projectRoot: string): string[] {
|
|
37
|
+
const cargoPath = path.join(projectRoot, "Cargo.toml");
|
|
38
|
+
if (!fs.existsSync(cargoPath)) return [];
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const content = fs.readFileSync(cargoPath, "utf-8");
|
|
42
|
+
// Parse [workspace] members = [...] from Cargo.toml
|
|
43
|
+
const workspaceMatch = content.match(/\[workspace\][\s\S]*?members\s*=\s*\[([\s\S]*?)\]/);
|
|
44
|
+
if (!workspaceMatch) return [];
|
|
45
|
+
|
|
46
|
+
const membersRaw = workspaceMatch[1];
|
|
47
|
+
return membersRaw
|
|
48
|
+
.split(",")
|
|
49
|
+
.map((m) => m.replace(/["'\s]/g, "").trim())
|
|
50
|
+
.filter((m) => m.length > 0 && !m.startsWith("#"))
|
|
51
|
+
.map((m) => m.replace(/\/\*$/, "").replace(/\*$/, "").trim()); // strip glob wildcards
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function getRustScope(projectRoot: string): Promise<string[]> {
|
|
58
|
+
const scope: Set<string> = new Set();
|
|
59
|
+
|
|
60
|
+
// Parse workspace members from Cargo.toml
|
|
61
|
+
const members = parseRustWorkspaceMembers(projectRoot);
|
|
62
|
+
|
|
63
|
+
if (members.length > 0) {
|
|
64
|
+
// It's a workspace — include src/ of each member
|
|
65
|
+
for (const member of members) {
|
|
66
|
+
const memberSrc = path.join(member, "src");
|
|
67
|
+
const fullMemberSrc = path.join(projectRoot, memberSrc);
|
|
68
|
+
if (fs.existsSync(fullMemberSrc)) {
|
|
69
|
+
scope.add(memberSrc);
|
|
70
|
+
}
|
|
71
|
+
// Also include member Cargo.toml
|
|
72
|
+
const memberCargo = path.join(member, "Cargo.toml");
|
|
73
|
+
if (fs.existsSync(path.join(projectRoot, memberCargo))) {
|
|
74
|
+
scope.add(memberCargo);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
// Single crate — just include src/
|
|
79
|
+
const srcPath = path.join(projectRoot, "src");
|
|
80
|
+
if (fs.existsSync(srcPath)) scope.add("src");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Also catch any */src dirs not listed in workspace (safety net)
|
|
84
|
+
const allSrcDirs = await glob("*/src", {
|
|
85
|
+
cwd: projectRoot,
|
|
86
|
+
ignore: ["node_modules/**", ".git/**", "target/**"],
|
|
87
|
+
});
|
|
88
|
+
for (const s of allSrcDirs) scope.add(s);
|
|
89
|
+
|
|
90
|
+
return Array.from(scope);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function getNodeScope(projectRoot: string): Promise<string[]> {
|
|
94
|
+
const scope: Set<string> = new Set();
|
|
95
|
+
const srcPath = path.join(projectRoot, "src");
|
|
96
|
+
if (fs.existsSync(srcPath)) scope.add("src");
|
|
97
|
+
|
|
98
|
+
// Check for monorepo patterns (packages/, apps/, libs/)
|
|
99
|
+
for (const dir of ["packages", "apps", "libs"]) {
|
|
100
|
+
const fullPath = path.join(projectRoot, dir);
|
|
101
|
+
if (fs.existsSync(fullPath)) scope.add(dir);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Array.from(scope);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function getPythonScope(projectRoot: string): Promise<string[]> {
|
|
108
|
+
const scope: Set<string> = new Set();
|
|
109
|
+
// Python projects typically have a src/ or a package dir matching project name
|
|
110
|
+
const srcPath = path.join(projectRoot, "src");
|
|
111
|
+
if (fs.existsSync(srcPath)) scope.add("src");
|
|
112
|
+
|
|
113
|
+
// Look for dirs with __init__.py
|
|
114
|
+
const initFiles = await glob("*/__init__.py", {
|
|
115
|
+
cwd: projectRoot,
|
|
116
|
+
ignore: ["node_modules/**", ".git/**", "venv/**", ".venv/**"],
|
|
117
|
+
});
|
|
118
|
+
for (const f of initFiles) scope.add(path.dirname(f));
|
|
119
|
+
|
|
120
|
+
return Array.from(scope);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function getGoScope(projectRoot: string): Promise<string[]> {
|
|
124
|
+
const scope: Set<string> = new Set();
|
|
125
|
+
const internalPath = path.join(projectRoot, "internal");
|
|
126
|
+
const pkgPath = path.join(projectRoot, "pkg");
|
|
127
|
+
const cmdPath = path.join(projectRoot, "cmd");
|
|
128
|
+
if (fs.existsSync(internalPath)) scope.add("internal");
|
|
129
|
+
if (fs.existsSync(pkgPath)) scope.add("pkg");
|
|
130
|
+
if (fs.existsSync(cmdPath)) scope.add("cmd");
|
|
131
|
+
return Array.from(scope);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function autoDetectScope(projectRoot: string): Promise<string[]> {
|
|
135
|
+
const git = simpleGit(projectRoot);
|
|
136
|
+
const autoScope: Set<string> = new Set();
|
|
137
|
+
const projectType = detectProjectType(projectRoot);
|
|
138
|
+
|
|
139
|
+
// Step 1: Detect language-specific source dirs
|
|
140
|
+
let langScope: string[] = [];
|
|
141
|
+
switch (projectType) {
|
|
142
|
+
case "rust": langScope = await getRustScope(projectRoot); break;
|
|
143
|
+
case "node": langScope = await getNodeScope(projectRoot); break;
|
|
144
|
+
case "python": langScope = await getPythonScope(projectRoot); break;
|
|
145
|
+
case "go": langScope = await getGoScope(projectRoot); break;
|
|
146
|
+
default: {
|
|
147
|
+
const srcPath = path.join(projectRoot, "src");
|
|
148
|
+
if (fs.existsSync(srcPath)) langScope = ["src"];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const s of langScope) autoScope.add(s);
|
|
152
|
+
|
|
153
|
+
// Step 2: Layer git activity on top — add dirs of recently modified files
|
|
154
|
+
try {
|
|
155
|
+
const log = await git.raw([
|
|
156
|
+
"log", "--since=7 days ago", "--name-only", "--pretty=format:", "--diff-filter=AM"
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
const recentFiles = log
|
|
160
|
+
.split("\n")
|
|
161
|
+
.map((l) => l.trim())
|
|
162
|
+
.filter((l) => l.length > 0);
|
|
163
|
+
|
|
164
|
+
for (const file of recentFiles) {
|
|
165
|
+
const fullPath = path.join(projectRoot, file);
|
|
166
|
+
if (fs.existsSync(fullPath)) {
|
|
167
|
+
const dir = path.dirname(file);
|
|
168
|
+
if (dir !== ".") autoScope.add(dir);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
// Not a git repo or no recent commits — lang scope is enough
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Step 3: Always include root-level config files
|
|
176
|
+
for (const file of ALWAYS_INCLUDE) {
|
|
177
|
+
const fullPath = path.join(projectRoot, file);
|
|
178
|
+
if (fs.existsSync(fullPath)) autoScope.add(file);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return Array.from(autoScope).filter((s) => s !== "." && s.length > 0);
|
|
182
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
export interface ScopeConfig {
|
|
5
|
+
active: string[];
|
|
6
|
+
watched: string[];
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const DEFAULT_CONFIG: ScopeConfig = {
|
|
11
|
+
active: [],
|
|
12
|
+
watched: [],
|
|
13
|
+
updatedAt: new Date().toISOString(),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function getCliperDir(projectRoot: string): string {
|
|
17
|
+
return path.join(projectRoot, ".cliper");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getScopeConfigPath(projectRoot: string): string {
|
|
21
|
+
return path.join(getCliperDir(projectRoot), "scope.json");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function loadScopeConfig(projectRoot: string): ScopeConfig {
|
|
25
|
+
const configPath = getScopeConfigPath(projectRoot);
|
|
26
|
+
if (!fs.existsSync(configPath)) return { ...DEFAULT_CONFIG };
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
29
|
+
} catch {
|
|
30
|
+
return { ...DEFAULT_CONFIG };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function saveScopeConfig(projectRoot: string, config: ScopeConfig): void {
|
|
35
|
+
const dir = getCliperDir(projectRoot);
|
|
36
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
config.updatedAt = new Date().toISOString();
|
|
38
|
+
fs.writeFileSync(getScopeConfigPath(projectRoot), JSON.stringify(config, null, 2));
|
|
39
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|