@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.
Files changed (68) hide show
  1. package/README.md +266 -0
  2. package/dist/commands/analyze.d.ts +6 -0
  3. package/dist/commands/analyze.d.ts.map +1 -0
  4. package/dist/commands/analyze.js +216 -0
  5. package/dist/commands/export.d.ts +6 -0
  6. package/dist/commands/export.d.ts.map +1 -0
  7. package/dist/commands/export.js +64 -0
  8. package/dist/commands/init.d.ts +7 -0
  9. package/dist/commands/init.d.ts.map +1 -0
  10. package/dist/commands/init.js +173 -0
  11. package/dist/commands/scope.d.ts +2 -0
  12. package/dist/commands/scope.d.ts.map +1 -0
  13. package/dist/commands/scope.js +124 -0
  14. package/dist/commands/status.d.ts +2 -0
  15. package/dist/commands/status.d.ts.map +1 -0
  16. package/dist/commands/status.js +100 -0
  17. package/dist/commands/sync.d.ts +6 -0
  18. package/dist/commands/sync.d.ts.map +1 -0
  19. package/dist/commands/sync.js +83 -0
  20. package/dist/context/builder.d.ts +19 -0
  21. package/dist/context/builder.d.ts.map +1 -0
  22. package/dist/context/builder.js +143 -0
  23. package/dist/gaps/detector.d.ts +10 -0
  24. package/dist/gaps/detector.d.ts.map +1 -0
  25. package/dist/gaps/detector.js +139 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +48 -0
  29. package/dist/resolver/urlFetcher.d.ts +9 -0
  30. package/dist/resolver/urlFetcher.d.ts.map +1 -0
  31. package/dist/resolver/urlFetcher.js +134 -0
  32. package/dist/scanner/dependencies.d.ts +14 -0
  33. package/dist/scanner/dependencies.d.ts.map +1 -0
  34. package/dist/scanner/dependencies.js +199 -0
  35. package/dist/scanner/fileContent.d.ts +8 -0
  36. package/dist/scanner/fileContent.d.ts.map +1 -0
  37. package/dist/scanner/fileContent.js +133 -0
  38. package/dist/scanner/fileTree.d.ts +2 -0
  39. package/dist/scanner/fileTree.d.ts.map +1 -0
  40. package/dist/scanner/fileTree.js +152 -0
  41. package/dist/scanner/gitContext.d.ts +19 -0
  42. package/dist/scanner/gitContext.d.ts.map +1 -0
  43. package/dist/scanner/gitContext.js +60 -0
  44. package/dist/scope/autoScope.d.ts +2 -0
  45. package/dist/scope/autoScope.d.ts.map +1 -0
  46. package/dist/scope/autoScope.js +226 -0
  47. package/dist/scope/config.d.ts +10 -0
  48. package/dist/scope/config.d.ts.map +1 -0
  49. package/dist/scope/config.js +71 -0
  50. package/index.js +2 -0
  51. package/package.json +37 -0
  52. package/src/commands/analyze.ts +201 -0
  53. package/src/commands/export.ts +33 -0
  54. package/src/commands/init.ts +174 -0
  55. package/src/commands/scope.ts +77 -0
  56. package/src/commands/status.ts +67 -0
  57. package/src/commands/sync.ts +51 -0
  58. package/src/context/builder.ts +178 -0
  59. package/src/gaps/detector.ts +131 -0
  60. package/src/index.ts +54 -0
  61. package/src/resolver/urlFetcher.ts +119 -0
  62. package/src/scanner/dependencies.ts +196 -0
  63. package/src/scanner/fileContent.ts +121 -0
  64. package/src/scanner/fileTree.ts +149 -0
  65. package/src/scanner/gitContext.ts +74 -0
  66. package/src/scope/autoScope.ts +182 -0
  67. package/src/scope/config.ts +39 -0
  68. 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
+ }