@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,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.extractFileContents = extractFileContents;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const glob_1 = require("glob");
|
|
43
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
44
|
+
const MAX_TOTAL_CHARS = 150000;
|
|
45
|
+
const BINARY_EXTENSIONS = new Set([
|
|
46
|
+
".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".webp",
|
|
47
|
+
".pdf", ".zip", ".tar", ".gz", ".exe", ".bin", ".wasm",
|
|
48
|
+
".ttf", ".woff", ".woff2", ".eot", ".mp4", ".mp3",
|
|
49
|
+
]);
|
|
50
|
+
const PRIORITY_EXTENSIONS = [
|
|
51
|
+
".ts", ".tsx", ".js", ".jsx", ".py", ".rs", ".go",
|
|
52
|
+
".java", ".kt", ".swift", ".rb", ".php", ".cs",
|
|
53
|
+
".json", ".yaml", ".yml", ".toml", ".env.example",
|
|
54
|
+
".md", ".mdx", ".sql", ".graphql", ".prisma",
|
|
55
|
+
];
|
|
56
|
+
function isBinary(filePath) {
|
|
57
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
58
|
+
return BINARY_EXTENSIONS.has(ext);
|
|
59
|
+
}
|
|
60
|
+
function getPriority(filePath) {
|
|
61
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
62
|
+
const idx = PRIORITY_EXTENSIONS.indexOf(ext);
|
|
63
|
+
return idx === -1 ? 999 : idx;
|
|
64
|
+
}
|
|
65
|
+
async function extractFileContents(projectRoot, activeScope, watchedScope, maxFileSizeKB = 50) {
|
|
66
|
+
const MAX_FILE_SIZE_BYTES = maxFileSizeKB * 1024;
|
|
67
|
+
const ig = (0, ignore_1.default)();
|
|
68
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
69
|
+
if (fs.existsSync(gitignorePath)) {
|
|
70
|
+
ig.add(fs.readFileSync(gitignorePath, "utf-8"));
|
|
71
|
+
}
|
|
72
|
+
const allPaths = new Set();
|
|
73
|
+
// Collect files from active scope and watched scope
|
|
74
|
+
for (const scopePath of [...activeScope, ...watchedScope]) {
|
|
75
|
+
const fullScopePath = path.join(projectRoot, scopePath);
|
|
76
|
+
if (!fs.existsSync(fullScopePath))
|
|
77
|
+
continue;
|
|
78
|
+
const stat = fs.statSync(fullScopePath);
|
|
79
|
+
if (stat.isFile()) {
|
|
80
|
+
allPaths.add(scopePath);
|
|
81
|
+
}
|
|
82
|
+
else if (stat.isDirectory()) {
|
|
83
|
+
const files = await (0, glob_1.glob)("**/*", {
|
|
84
|
+
cwd: fullScopePath,
|
|
85
|
+
nodir: true,
|
|
86
|
+
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
87
|
+
});
|
|
88
|
+
for (const f of files) {
|
|
89
|
+
const rel = path.join(scopePath, f);
|
|
90
|
+
if (!ig.ignores(rel))
|
|
91
|
+
allPaths.add(rel);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Sort by priority
|
|
96
|
+
const sortedPaths = Array.from(allPaths).sort((a, b) => getPriority(a) - getPriority(b));
|
|
97
|
+
const results = [];
|
|
98
|
+
let totalChars = 0;
|
|
99
|
+
for (const relativePath of sortedPaths) {
|
|
100
|
+
if (totalChars >= MAX_TOTAL_CHARS)
|
|
101
|
+
break;
|
|
102
|
+
if (isBinary(relativePath))
|
|
103
|
+
continue;
|
|
104
|
+
const fullPath = path.join(projectRoot, relativePath);
|
|
105
|
+
if (!fs.existsSync(fullPath))
|
|
106
|
+
continue;
|
|
107
|
+
const stat = fs.statSync(fullPath);
|
|
108
|
+
if (stat.size > MAX_FILE_SIZE_BYTES) {
|
|
109
|
+
results.push({
|
|
110
|
+
relativePath,
|
|
111
|
+
content: `[File too large to include: ${Math.round(stat.size / 1024)}KB]`,
|
|
112
|
+
truncated: true,
|
|
113
|
+
size: stat.size,
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
let content = fs.readFileSync(fullPath, "utf-8");
|
|
119
|
+
let truncated = false;
|
|
120
|
+
if (totalChars + content.length > MAX_TOTAL_CHARS) {
|
|
121
|
+
content = content.slice(0, MAX_TOTAL_CHARS - totalChars);
|
|
122
|
+
truncated = true;
|
|
123
|
+
}
|
|
124
|
+
totalChars += content.length;
|
|
125
|
+
results.push({ relativePath, content, truncated, size: stat.size });
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Skip files that can't be read
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return results;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=fileContent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileTree.d.ts","sourceRoot":"","sources":["../../src/scanner/fileTree.ts"],"names":[],"mappings":"AA0IA,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EAAE,EACrB,YAAY,EAAE,MAAM,EAAE,GACrB,MAAM,CAMR"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.generateFileTree = generateFileTree;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
43
|
+
const IGNORE_DIRS = new Set([
|
|
44
|
+
"node_modules", ".git", "dist", "build", ".next", ".nuxt",
|
|
45
|
+
"coverage", ".cache", "__pycache__", ".pytest_cache", "vendor",
|
|
46
|
+
".cliper",
|
|
47
|
+
]);
|
|
48
|
+
function loadGitignore(projectRoot) {
|
|
49
|
+
const ig = (0, ignore_1.default)();
|
|
50
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
51
|
+
if (fs.existsSync(gitignorePath)) {
|
|
52
|
+
ig.add(fs.readFileSync(gitignorePath, "utf-8"));
|
|
53
|
+
}
|
|
54
|
+
return ig;
|
|
55
|
+
}
|
|
56
|
+
function getModifiedTime(filePath) {
|
|
57
|
+
try {
|
|
58
|
+
return fs.statSync(filePath).mtime;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function buildTree(dirPath, projectRoot, activeScope, watchedScope, ig, depth = 0, maxDepth = 4) {
|
|
65
|
+
if (depth > maxDepth)
|
|
66
|
+
return [];
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const nodes = [];
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
77
|
+
const relativePath = path.relative(projectRoot, fullPath);
|
|
78
|
+
if (IGNORE_DIRS.has(entry.name))
|
|
79
|
+
continue;
|
|
80
|
+
if (ig.ignores(relativePath))
|
|
81
|
+
continue;
|
|
82
|
+
if (entry.name.startsWith(".") && entry.name !== ".env.example")
|
|
83
|
+
continue;
|
|
84
|
+
const inScope = activeScope.some((s) => relativePath.startsWith(s) || s.startsWith(relativePath));
|
|
85
|
+
const watched = watchedScope.some((s) => relativePath.startsWith(s) || relativePath === s);
|
|
86
|
+
const node = {
|
|
87
|
+
name: entry.name,
|
|
88
|
+
isDir: entry.isDirectory(),
|
|
89
|
+
inScope,
|
|
90
|
+
watched,
|
|
91
|
+
relativePath,
|
|
92
|
+
modifiedAt: entry.isFile() ? getModifiedTime(fullPath) : undefined,
|
|
93
|
+
};
|
|
94
|
+
if (entry.isDirectory()) {
|
|
95
|
+
node.children = buildTree(fullPath, projectRoot, activeScope, watchedScope, ig, depth + 1, maxDepth);
|
|
96
|
+
}
|
|
97
|
+
nodes.push(node);
|
|
98
|
+
}
|
|
99
|
+
return nodes.sort((a, b) => {
|
|
100
|
+
if (a.isDir !== b.isDir)
|
|
101
|
+
return a.isDir ? -1 : 1;
|
|
102
|
+
return a.name.localeCompare(b.name);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function formatTimeAgo(date) {
|
|
106
|
+
const diff = Date.now() - date.getTime();
|
|
107
|
+
const mins = Math.floor(diff / 60000);
|
|
108
|
+
const hours = Math.floor(diff / 3600000);
|
|
109
|
+
const days = Math.floor(diff / 86400000);
|
|
110
|
+
if (mins < 60)
|
|
111
|
+
return `${mins}m ago`;
|
|
112
|
+
if (hours < 24)
|
|
113
|
+
return `${hours}h ago`;
|
|
114
|
+
return `${days}d ago`;
|
|
115
|
+
}
|
|
116
|
+
function renderTree(nodes, prefix = "", isRoot = false) {
|
|
117
|
+
const lines = [];
|
|
118
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
119
|
+
const node = nodes[i];
|
|
120
|
+
const isLast = i === nodes.length - 1;
|
|
121
|
+
const connector = isLast ? "└── " : "├── ";
|
|
122
|
+
const childPrefix = prefix + (isLast ? " " : "│ ");
|
|
123
|
+
let annotation = "";
|
|
124
|
+
if (node.inScope)
|
|
125
|
+
annotation += " ← ACTIVE SCOPE";
|
|
126
|
+
else if (node.watched)
|
|
127
|
+
annotation += " ← WATCHED";
|
|
128
|
+
let timeAnnotation = "";
|
|
129
|
+
if (node.modifiedAt && !node.isDir) {
|
|
130
|
+
timeAnnotation = ` (${formatTimeAgo(node.modifiedAt)})`;
|
|
131
|
+
}
|
|
132
|
+
if (node.isDir && !node.inScope && !node.watched && !isRoot) {
|
|
133
|
+
const childCount = node.children?.length ?? 0;
|
|
134
|
+
lines.push(`${prefix}${connector}${node.name}/${annotation || ` ← out of scope (${childCount} items)`}`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
lines.push(`${prefix}${connector}${node.name}${node.isDir ? "/" : ""}${annotation}${timeAnnotation}`);
|
|
138
|
+
if (node.children && node.children.length > 0 && (node.inScope || node.watched || isRoot)) {
|
|
139
|
+
lines.push(renderTree(node.children, childPrefix));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return lines.filter(Boolean).join("\n");
|
|
144
|
+
}
|
|
145
|
+
function generateFileTree(projectRoot, activeScope, watchedScope) {
|
|
146
|
+
const ig = loadGitignore(projectRoot);
|
|
147
|
+
const projectName = path.basename(projectRoot);
|
|
148
|
+
const nodes = buildTree(projectRoot, projectRoot, activeScope, watchedScope, ig, 0);
|
|
149
|
+
const tree = renderTree(nodes, "", true);
|
|
150
|
+
return `${projectName}/\n${tree}`;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=fileTree.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface GitContext {
|
|
2
|
+
branch: string;
|
|
3
|
+
lastCommit: {
|
|
4
|
+
hash: string;
|
|
5
|
+
message: string;
|
|
6
|
+
author: string;
|
|
7
|
+
date: string;
|
|
8
|
+
timeAgo: string;
|
|
9
|
+
} | null;
|
|
10
|
+
recentCommits: Array<{
|
|
11
|
+
hash: string;
|
|
12
|
+
message: string;
|
|
13
|
+
date: string;
|
|
14
|
+
}>;
|
|
15
|
+
uncommittedChanges: string[];
|
|
16
|
+
isGitRepo: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function getGitContext(projectRoot: string): Promise<GitContext>;
|
|
19
|
+
//# sourceMappingURL=gitContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitContext.d.ts","sourceRoot":"","sources":["../../src/scanner/gitContext.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,CAAC;IACT,aAAa,EAAE,KAAK,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;CACpB;AAYD,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA2C5E"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getGitContext = getGitContext;
|
|
7
|
+
const simple_git_1 = __importDefault(require("simple-git"));
|
|
8
|
+
function timeAgo(dateStr) {
|
|
9
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
10
|
+
const mins = Math.floor(diff / 60000);
|
|
11
|
+
const hours = Math.floor(diff / 3600000);
|
|
12
|
+
const days = Math.floor(diff / 86400000);
|
|
13
|
+
if (mins < 60)
|
|
14
|
+
return `${mins} minutes ago`;
|
|
15
|
+
if (hours < 24)
|
|
16
|
+
return `${hours} hours ago`;
|
|
17
|
+
return `${days} days ago`;
|
|
18
|
+
}
|
|
19
|
+
async function getGitContext(projectRoot) {
|
|
20
|
+
const git = (0, simple_git_1.default)(projectRoot);
|
|
21
|
+
try {
|
|
22
|
+
const isRepo = await git.checkIsRepo();
|
|
23
|
+
if (!isRepo) {
|
|
24
|
+
return { branch: "", lastCommit: null, recentCommits: [], uncommittedChanges: [], isGitRepo: false };
|
|
25
|
+
}
|
|
26
|
+
const [branch, log, status] = await Promise.all([
|
|
27
|
+
git.revparse(["--abbrev-ref", "HEAD"]),
|
|
28
|
+
git.log({ maxCount: 5 }),
|
|
29
|
+
git.status(),
|
|
30
|
+
]);
|
|
31
|
+
const [latest, ...rest] = log.all;
|
|
32
|
+
return {
|
|
33
|
+
isGitRepo: true,
|
|
34
|
+
branch: branch.trim(),
|
|
35
|
+
lastCommit: latest
|
|
36
|
+
? {
|
|
37
|
+
hash: latest.hash.slice(0, 7),
|
|
38
|
+
message: latest.message,
|
|
39
|
+
author: latest.author_name,
|
|
40
|
+
date: latest.date,
|
|
41
|
+
timeAgo: timeAgo(latest.date),
|
|
42
|
+
}
|
|
43
|
+
: null,
|
|
44
|
+
recentCommits: rest.map((c) => ({
|
|
45
|
+
hash: c.hash.slice(0, 7),
|
|
46
|
+
message: c.message,
|
|
47
|
+
date: c.date,
|
|
48
|
+
})),
|
|
49
|
+
uncommittedChanges: [
|
|
50
|
+
...status.modified,
|
|
51
|
+
...status.created,
|
|
52
|
+
...status.deleted,
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return { branch: "", lastCommit: null, recentCommits: [], uncommittedChanges: [], isGitRepo: false };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=gitContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoScope.d.ts","sourceRoot":"","sources":["../../src/scope/autoScope.ts"],"names":[],"mappings":"AAqIA,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgD5E"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.autoDetectScope = autoDetectScope;
|
|
40
|
+
const simple_git_1 = __importDefault(require("simple-git"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const glob_1 = require("glob");
|
|
44
|
+
// Files always included regardless of scope
|
|
45
|
+
const ALWAYS_INCLUDE = [
|
|
46
|
+
"package.json",
|
|
47
|
+
"package-lock.json",
|
|
48
|
+
"yarn.lock",
|
|
49
|
+
"pnpm-lock.yaml",
|
|
50
|
+
"tsconfig.json",
|
|
51
|
+
"Cargo.toml",
|
|
52
|
+
"Cargo.lock",
|
|
53
|
+
"go.mod",
|
|
54
|
+
"go.sum",
|
|
55
|
+
"requirements.txt",
|
|
56
|
+
"pyproject.toml",
|
|
57
|
+
"README.md",
|
|
58
|
+
".env.example",
|
|
59
|
+
"docker-compose.yml",
|
|
60
|
+
"Dockerfile",
|
|
61
|
+
];
|
|
62
|
+
function detectProjectType(projectRoot) {
|
|
63
|
+
if (fs.existsSync(path.join(projectRoot, "Cargo.toml")))
|
|
64
|
+
return "rust";
|
|
65
|
+
if (fs.existsSync(path.join(projectRoot, "package.json")))
|
|
66
|
+
return "node";
|
|
67
|
+
if (fs.existsSync(path.join(projectRoot, "pyproject.toml")) ||
|
|
68
|
+
fs.existsSync(path.join(projectRoot, "requirements.txt")))
|
|
69
|
+
return "python";
|
|
70
|
+
if (fs.existsSync(path.join(projectRoot, "go.mod")))
|
|
71
|
+
return "go";
|
|
72
|
+
return "unknown";
|
|
73
|
+
}
|
|
74
|
+
function parseRustWorkspaceMembers(projectRoot) {
|
|
75
|
+
const cargoPath = path.join(projectRoot, "Cargo.toml");
|
|
76
|
+
if (!fs.existsSync(cargoPath))
|
|
77
|
+
return [];
|
|
78
|
+
try {
|
|
79
|
+
const content = fs.readFileSync(cargoPath, "utf-8");
|
|
80
|
+
// Parse [workspace] members = [...] from Cargo.toml
|
|
81
|
+
const workspaceMatch = content.match(/\[workspace\][\s\S]*?members\s*=\s*\[([\s\S]*?)\]/);
|
|
82
|
+
if (!workspaceMatch)
|
|
83
|
+
return [];
|
|
84
|
+
const membersRaw = workspaceMatch[1];
|
|
85
|
+
return membersRaw
|
|
86
|
+
.split(",")
|
|
87
|
+
.map((m) => m.replace(/["'\s]/g, "").trim())
|
|
88
|
+
.filter((m) => m.length > 0 && !m.startsWith("#"))
|
|
89
|
+
.map((m) => m.replace(/\/\*$/, "").replace(/\*$/, "").trim()); // strip glob wildcards
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function getRustScope(projectRoot) {
|
|
96
|
+
const scope = new Set();
|
|
97
|
+
// Parse workspace members from Cargo.toml
|
|
98
|
+
const members = parseRustWorkspaceMembers(projectRoot);
|
|
99
|
+
if (members.length > 0) {
|
|
100
|
+
// It's a workspace — include src/ of each member
|
|
101
|
+
for (const member of members) {
|
|
102
|
+
const memberSrc = path.join(member, "src");
|
|
103
|
+
const fullMemberSrc = path.join(projectRoot, memberSrc);
|
|
104
|
+
if (fs.existsSync(fullMemberSrc)) {
|
|
105
|
+
scope.add(memberSrc);
|
|
106
|
+
}
|
|
107
|
+
// Also include member Cargo.toml
|
|
108
|
+
const memberCargo = path.join(member, "Cargo.toml");
|
|
109
|
+
if (fs.existsSync(path.join(projectRoot, memberCargo))) {
|
|
110
|
+
scope.add(memberCargo);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Single crate — just include src/
|
|
116
|
+
const srcPath = path.join(projectRoot, "src");
|
|
117
|
+
if (fs.existsSync(srcPath))
|
|
118
|
+
scope.add("src");
|
|
119
|
+
}
|
|
120
|
+
// Also catch any */src dirs not listed in workspace (safety net)
|
|
121
|
+
const allSrcDirs = await (0, glob_1.glob)("*/src", {
|
|
122
|
+
cwd: projectRoot,
|
|
123
|
+
ignore: ["node_modules/**", ".git/**", "target/**"],
|
|
124
|
+
});
|
|
125
|
+
for (const s of allSrcDirs)
|
|
126
|
+
scope.add(s);
|
|
127
|
+
return Array.from(scope);
|
|
128
|
+
}
|
|
129
|
+
async function getNodeScope(projectRoot) {
|
|
130
|
+
const scope = new Set();
|
|
131
|
+
const srcPath = path.join(projectRoot, "src");
|
|
132
|
+
if (fs.existsSync(srcPath))
|
|
133
|
+
scope.add("src");
|
|
134
|
+
// Check for monorepo patterns (packages/, apps/, libs/)
|
|
135
|
+
for (const dir of ["packages", "apps", "libs"]) {
|
|
136
|
+
const fullPath = path.join(projectRoot, dir);
|
|
137
|
+
if (fs.existsSync(fullPath))
|
|
138
|
+
scope.add(dir);
|
|
139
|
+
}
|
|
140
|
+
return Array.from(scope);
|
|
141
|
+
}
|
|
142
|
+
async function getPythonScope(projectRoot) {
|
|
143
|
+
const scope = new Set();
|
|
144
|
+
// Python projects typically have a src/ or a package dir matching project name
|
|
145
|
+
const srcPath = path.join(projectRoot, "src");
|
|
146
|
+
if (fs.existsSync(srcPath))
|
|
147
|
+
scope.add("src");
|
|
148
|
+
// Look for dirs with __init__.py
|
|
149
|
+
const initFiles = await (0, glob_1.glob)("*/__init__.py", {
|
|
150
|
+
cwd: projectRoot,
|
|
151
|
+
ignore: ["node_modules/**", ".git/**", "venv/**", ".venv/**"],
|
|
152
|
+
});
|
|
153
|
+
for (const f of initFiles)
|
|
154
|
+
scope.add(path.dirname(f));
|
|
155
|
+
return Array.from(scope);
|
|
156
|
+
}
|
|
157
|
+
async function getGoScope(projectRoot) {
|
|
158
|
+
const scope = new Set();
|
|
159
|
+
const internalPath = path.join(projectRoot, "internal");
|
|
160
|
+
const pkgPath = path.join(projectRoot, "pkg");
|
|
161
|
+
const cmdPath = path.join(projectRoot, "cmd");
|
|
162
|
+
if (fs.existsSync(internalPath))
|
|
163
|
+
scope.add("internal");
|
|
164
|
+
if (fs.existsSync(pkgPath))
|
|
165
|
+
scope.add("pkg");
|
|
166
|
+
if (fs.existsSync(cmdPath))
|
|
167
|
+
scope.add("cmd");
|
|
168
|
+
return Array.from(scope);
|
|
169
|
+
}
|
|
170
|
+
async function autoDetectScope(projectRoot) {
|
|
171
|
+
const git = (0, simple_git_1.default)(projectRoot);
|
|
172
|
+
const autoScope = new Set();
|
|
173
|
+
const projectType = detectProjectType(projectRoot);
|
|
174
|
+
// Step 1: Detect language-specific source dirs
|
|
175
|
+
let langScope = [];
|
|
176
|
+
switch (projectType) {
|
|
177
|
+
case "rust":
|
|
178
|
+
langScope = await getRustScope(projectRoot);
|
|
179
|
+
break;
|
|
180
|
+
case "node":
|
|
181
|
+
langScope = await getNodeScope(projectRoot);
|
|
182
|
+
break;
|
|
183
|
+
case "python":
|
|
184
|
+
langScope = await getPythonScope(projectRoot);
|
|
185
|
+
break;
|
|
186
|
+
case "go":
|
|
187
|
+
langScope = await getGoScope(projectRoot);
|
|
188
|
+
break;
|
|
189
|
+
default: {
|
|
190
|
+
const srcPath = path.join(projectRoot, "src");
|
|
191
|
+
if (fs.existsSync(srcPath))
|
|
192
|
+
langScope = ["src"];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const s of langScope)
|
|
196
|
+
autoScope.add(s);
|
|
197
|
+
// Step 2: Layer git activity on top — add dirs of recently modified files
|
|
198
|
+
try {
|
|
199
|
+
const log = await git.raw([
|
|
200
|
+
"log", "--since=7 days ago", "--name-only", "--pretty=format:", "--diff-filter=AM"
|
|
201
|
+
]);
|
|
202
|
+
const recentFiles = log
|
|
203
|
+
.split("\n")
|
|
204
|
+
.map((l) => l.trim())
|
|
205
|
+
.filter((l) => l.length > 0);
|
|
206
|
+
for (const file of recentFiles) {
|
|
207
|
+
const fullPath = path.join(projectRoot, file);
|
|
208
|
+
if (fs.existsSync(fullPath)) {
|
|
209
|
+
const dir = path.dirname(file);
|
|
210
|
+
if (dir !== ".")
|
|
211
|
+
autoScope.add(dir);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Not a git repo or no recent commits — lang scope is enough
|
|
217
|
+
}
|
|
218
|
+
// Step 3: Always include root-level config files
|
|
219
|
+
for (const file of ALWAYS_INCLUDE) {
|
|
220
|
+
const fullPath = path.join(projectRoot, file);
|
|
221
|
+
if (fs.existsSync(fullPath))
|
|
222
|
+
autoScope.add(file);
|
|
223
|
+
}
|
|
224
|
+
return Array.from(autoScope).filter((s) => s !== "." && s.length > 0);
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=autoScope.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ScopeConfig {
|
|
2
|
+
active: string[];
|
|
3
|
+
watched: string[];
|
|
4
|
+
updatedAt: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getCliperDir(projectRoot: string): string;
|
|
7
|
+
export declare function getScopeConfigPath(projectRoot: string): string;
|
|
8
|
+
export declare function loadScopeConfig(projectRoot: string): ScopeConfig;
|
|
9
|
+
export declare function saveScopeConfig(projectRoot: string, config: ScopeConfig): void;
|
|
10
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/scope/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAQhE;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAK9E"}
|