@compilr-dev/agents 0.0.1
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 +1277 -0
- package/dist/agent.d.ts +1272 -0
- package/dist/agent.js +1912 -0
- package/dist/anchors/builtin.d.ts +24 -0
- package/dist/anchors/builtin.js +61 -0
- package/dist/anchors/index.d.ts +6 -0
- package/dist/anchors/index.js +5 -0
- package/dist/anchors/manager.d.ts +115 -0
- package/dist/anchors/manager.js +412 -0
- package/dist/anchors/types.d.ts +168 -0
- package/dist/anchors/types.js +10 -0
- package/dist/context/index.d.ts +12 -0
- package/dist/context/index.js +10 -0
- package/dist/context/manager.d.ts +224 -0
- package/dist/context/manager.js +770 -0
- package/dist/context/types.d.ts +377 -0
- package/dist/context/types.js +7 -0
- package/dist/costs/index.d.ts +8 -0
- package/dist/costs/index.js +7 -0
- package/dist/costs/tracker.d.ts +121 -0
- package/dist/costs/tracker.js +295 -0
- package/dist/costs/types.d.ts +157 -0
- package/dist/costs/types.js +8 -0
- package/dist/errors.d.ts +178 -0
- package/dist/errors.js +249 -0
- package/dist/guardrails/builtin.d.ts +27 -0
- package/dist/guardrails/builtin.js +223 -0
- package/dist/guardrails/index.d.ts +6 -0
- package/dist/guardrails/index.js +5 -0
- package/dist/guardrails/manager.d.ts +117 -0
- package/dist/guardrails/manager.js +288 -0
- package/dist/guardrails/types.d.ts +159 -0
- package/dist/guardrails/types.js +7 -0
- package/dist/hooks/index.d.ts +31 -0
- package/dist/hooks/index.js +29 -0
- package/dist/hooks/manager.d.ts +147 -0
- package/dist/hooks/manager.js +600 -0
- package/dist/hooks/types.d.ts +368 -0
- package/dist/hooks/types.js +12 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +73 -0
- package/dist/mcp/client.d.ts +93 -0
- package/dist/mcp/client.js +287 -0
- package/dist/mcp/errors.d.ts +60 -0
- package/dist/mcp/errors.js +78 -0
- package/dist/mcp/index.d.ts +43 -0
- package/dist/mcp/index.js +45 -0
- package/dist/mcp/manager.d.ts +120 -0
- package/dist/mcp/manager.js +276 -0
- package/dist/mcp/tools.d.ts +54 -0
- package/dist/mcp/tools.js +99 -0
- package/dist/mcp/types.d.ts +150 -0
- package/dist/mcp/types.js +40 -0
- package/dist/memory/index.d.ts +8 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/loader.d.ts +114 -0
- package/dist/memory/loader.js +463 -0
- package/dist/memory/types.d.ts +182 -0
- package/dist/memory/types.js +8 -0
- package/dist/messages/index.d.ts +82 -0
- package/dist/messages/index.js +155 -0
- package/dist/permissions/index.d.ts +5 -0
- package/dist/permissions/index.js +4 -0
- package/dist/permissions/manager.d.ts +125 -0
- package/dist/permissions/manager.js +379 -0
- package/dist/permissions/types.d.ts +162 -0
- package/dist/permissions/types.js +7 -0
- package/dist/providers/claude.d.ts +90 -0
- package/dist/providers/claude.js +348 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +11 -0
- package/dist/providers/mock.d.ts +133 -0
- package/dist/providers/mock.js +204 -0
- package/dist/providers/types.d.ts +168 -0
- package/dist/providers/types.js +4 -0
- package/dist/rate-limit/index.d.ts +45 -0
- package/dist/rate-limit/index.js +47 -0
- package/dist/rate-limit/limiter.d.ts +104 -0
- package/dist/rate-limit/limiter.js +326 -0
- package/dist/rate-limit/provider-wrapper.d.ts +112 -0
- package/dist/rate-limit/provider-wrapper.js +201 -0
- package/dist/rate-limit/retry.d.ts +108 -0
- package/dist/rate-limit/retry.js +287 -0
- package/dist/rate-limit/types.d.ts +181 -0
- package/dist/rate-limit/types.js +22 -0
- package/dist/rehearsal/file-analyzer.d.ts +22 -0
- package/dist/rehearsal/file-analyzer.js +351 -0
- package/dist/rehearsal/git-analyzer.d.ts +22 -0
- package/dist/rehearsal/git-analyzer.js +472 -0
- package/dist/rehearsal/index.d.ts +35 -0
- package/dist/rehearsal/index.js +36 -0
- package/dist/rehearsal/manager.d.ts +100 -0
- package/dist/rehearsal/manager.js +290 -0
- package/dist/rehearsal/types.d.ts +235 -0
- package/dist/rehearsal/types.js +8 -0
- package/dist/skills/index.d.ts +160 -0
- package/dist/skills/index.js +282 -0
- package/dist/state/agent-state.d.ts +41 -0
- package/dist/state/agent-state.js +88 -0
- package/dist/state/checkpointer.d.ts +110 -0
- package/dist/state/checkpointer.js +362 -0
- package/dist/state/errors.d.ts +66 -0
- package/dist/state/errors.js +88 -0
- package/dist/state/index.d.ts +35 -0
- package/dist/state/index.js +37 -0
- package/dist/state/serializer.d.ts +55 -0
- package/dist/state/serializer.js +172 -0
- package/dist/state/types.d.ts +312 -0
- package/dist/state/types.js +14 -0
- package/dist/tools/builtin/bash-output.d.ts +61 -0
- package/dist/tools/builtin/bash-output.js +90 -0
- package/dist/tools/builtin/bash.d.ts +150 -0
- package/dist/tools/builtin/bash.js +354 -0
- package/dist/tools/builtin/edit.d.ts +50 -0
- package/dist/tools/builtin/edit.js +215 -0
- package/dist/tools/builtin/glob.d.ts +62 -0
- package/dist/tools/builtin/glob.js +244 -0
- package/dist/tools/builtin/grep.d.ts +74 -0
- package/dist/tools/builtin/grep.js +363 -0
- package/dist/tools/builtin/index.d.ts +44 -0
- package/dist/tools/builtin/index.js +69 -0
- package/dist/tools/builtin/kill-shell.d.ts +44 -0
- package/dist/tools/builtin/kill-shell.js +80 -0
- package/dist/tools/builtin/read-file.d.ts +57 -0
- package/dist/tools/builtin/read-file.js +184 -0
- package/dist/tools/builtin/shell-manager.d.ts +176 -0
- package/dist/tools/builtin/shell-manager.js +337 -0
- package/dist/tools/builtin/task.d.ts +202 -0
- package/dist/tools/builtin/task.js +350 -0
- package/dist/tools/builtin/todo.d.ts +207 -0
- package/dist/tools/builtin/todo.js +453 -0
- package/dist/tools/builtin/utils.d.ts +27 -0
- package/dist/tools/builtin/utils.js +70 -0
- package/dist/tools/builtin/web-fetch.d.ts +96 -0
- package/dist/tools/builtin/web-fetch.js +290 -0
- package/dist/tools/builtin/write-file.d.ts +54 -0
- package/dist/tools/builtin/write-file.js +147 -0
- package/dist/tools/define.d.ts +60 -0
- package/dist/tools/define.js +65 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +37 -0
- package/dist/tools/registry.d.ts +79 -0
- package/dist/tools/registry.js +151 -0
- package/dist/tools/types.d.ts +59 -0
- package/dist/tools/types.js +4 -0
- package/dist/tracing/hooks.d.ts +58 -0
- package/dist/tracing/hooks.js +377 -0
- package/dist/tracing/index.d.ts +51 -0
- package/dist/tracing/index.js +55 -0
- package/dist/tracing/logging.d.ts +78 -0
- package/dist/tracing/logging.js +310 -0
- package/dist/tracing/manager.d.ts +160 -0
- package/dist/tracing/manager.js +468 -0
- package/dist/tracing/otel.d.ts +102 -0
- package/dist/tracing/otel.js +246 -0
- package/dist/tracing/types.d.ts +346 -0
- package/dist/tracing/types.js +38 -0
- package/dist/utils/index.d.ts +23 -0
- package/dist/utils/index.js +44 -0
- package/package.json +79 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiting & Retry Types
|
|
3
|
+
*
|
|
4
|
+
* Provides types for rate limiting and automatic retry functionality.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for rate limiter
|
|
8
|
+
*/
|
|
9
|
+
export interface RateLimiterConfig {
|
|
10
|
+
/**
|
|
11
|
+
* Maximum requests per minute
|
|
12
|
+
* @default 60
|
|
13
|
+
*/
|
|
14
|
+
requestsPerMinute?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Maximum tokens per minute (for token-based limiting)
|
|
17
|
+
* Set to 0 to disable token-based limiting
|
|
18
|
+
* @default 0
|
|
19
|
+
*/
|
|
20
|
+
tokensPerMinute?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum concurrent requests
|
|
23
|
+
* Set to 0 for unlimited
|
|
24
|
+
* @default 0
|
|
25
|
+
*/
|
|
26
|
+
maxConcurrent?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to throw immediately when rate limited instead of waiting
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
throwOnLimit?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Rate limiter statistics
|
|
35
|
+
*/
|
|
36
|
+
export interface RateLimiterStats {
|
|
37
|
+
/**
|
|
38
|
+
* Current number of available request tokens
|
|
39
|
+
*/
|
|
40
|
+
availableRequests: number;
|
|
41
|
+
/**
|
|
42
|
+
* Current number of available LLM tokens
|
|
43
|
+
*/
|
|
44
|
+
availableTokens: number;
|
|
45
|
+
/**
|
|
46
|
+
* Current number of concurrent requests
|
|
47
|
+
*/
|
|
48
|
+
currentConcurrent: number;
|
|
49
|
+
/**
|
|
50
|
+
* Total requests made
|
|
51
|
+
*/
|
|
52
|
+
totalRequests: number;
|
|
53
|
+
/**
|
|
54
|
+
* Total tokens consumed
|
|
55
|
+
*/
|
|
56
|
+
totalTokens: number;
|
|
57
|
+
/**
|
|
58
|
+
* Number of times rate limit was hit
|
|
59
|
+
*/
|
|
60
|
+
rateLimitHits: number;
|
|
61
|
+
/**
|
|
62
|
+
* Total time spent waiting due to rate limits (ms)
|
|
63
|
+
*/
|
|
64
|
+
totalWaitTimeMs: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Configuration for automatic retry
|
|
68
|
+
*/
|
|
69
|
+
export interface RetryConfig {
|
|
70
|
+
/**
|
|
71
|
+
* Maximum number of retry attempts
|
|
72
|
+
* @default 3
|
|
73
|
+
*/
|
|
74
|
+
maxRetries?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Base delay between retries in milliseconds
|
|
77
|
+
* @default 1000
|
|
78
|
+
*/
|
|
79
|
+
baseDelayMs?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Maximum delay between retries in milliseconds
|
|
82
|
+
* @default 60000
|
|
83
|
+
*/
|
|
84
|
+
maxDelayMs?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Multiplier for exponential backoff
|
|
87
|
+
* @default 2
|
|
88
|
+
*/
|
|
89
|
+
backoffMultiplier?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Whether to add random jitter to delays
|
|
92
|
+
* @default true
|
|
93
|
+
*/
|
|
94
|
+
jitter?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Custom function to determine if an error is retryable
|
|
97
|
+
* If not provided, uses default logic (429, 5xx, connection errors)
|
|
98
|
+
*/
|
|
99
|
+
isRetryable?: (error: Error) => boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Callback invoked before each retry attempt
|
|
102
|
+
*/
|
|
103
|
+
onRetry?: (attempt: number, error: Error, delayMs: number) => void;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Combined rate limit and retry configuration
|
|
107
|
+
*/
|
|
108
|
+
export interface RateLimitRetryConfig {
|
|
109
|
+
/**
|
|
110
|
+
* Rate limiter configuration
|
|
111
|
+
*/
|
|
112
|
+
rateLimit?: RateLimiterConfig;
|
|
113
|
+
/**
|
|
114
|
+
* Retry configuration
|
|
115
|
+
*/
|
|
116
|
+
retry?: RetryConfig;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Result of acquiring a rate limit token
|
|
120
|
+
*/
|
|
121
|
+
export interface AcquireResult {
|
|
122
|
+
/**
|
|
123
|
+
* Whether the acquisition was successful
|
|
124
|
+
*/
|
|
125
|
+
acquired: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Time waited to acquire the token (ms)
|
|
128
|
+
*/
|
|
129
|
+
waitedMs: number;
|
|
130
|
+
/**
|
|
131
|
+
* If not acquired, the estimated wait time (ms)
|
|
132
|
+
*/
|
|
133
|
+
estimatedWaitMs?: number;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Rate limiter interface
|
|
137
|
+
*/
|
|
138
|
+
export interface RateLimiter {
|
|
139
|
+
/**
|
|
140
|
+
* Acquire permission to make a request
|
|
141
|
+
* Waits if necessary (unless throwOnLimit is true)
|
|
142
|
+
*
|
|
143
|
+
* @param estimatedTokens - Estimated tokens for the request (optional)
|
|
144
|
+
* @returns Acquire result
|
|
145
|
+
*/
|
|
146
|
+
acquire(estimatedTokens?: number): Promise<AcquireResult>;
|
|
147
|
+
/**
|
|
148
|
+
* Release a concurrent request slot
|
|
149
|
+
*/
|
|
150
|
+
release(): void;
|
|
151
|
+
/**
|
|
152
|
+
* Report actual token usage after request completes
|
|
153
|
+
* Used to update token bucket
|
|
154
|
+
*
|
|
155
|
+
* @param tokens - Actual tokens used
|
|
156
|
+
*/
|
|
157
|
+
reportUsage(tokens: number): void;
|
|
158
|
+
/**
|
|
159
|
+
* Get current rate limiter statistics
|
|
160
|
+
*/
|
|
161
|
+
getStats(): RateLimiterStats;
|
|
162
|
+
/**
|
|
163
|
+
* Reset the rate limiter
|
|
164
|
+
*/
|
|
165
|
+
reset(): void;
|
|
166
|
+
/**
|
|
167
|
+
* Check if a request can be made immediately
|
|
168
|
+
*/
|
|
169
|
+
canAcquire(estimatedTokens?: number): boolean;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Error thrown when rate limit is exceeded and throwOnLimit is true
|
|
173
|
+
*/
|
|
174
|
+
export declare class RateLimitExceededError extends Error {
|
|
175
|
+
readonly estimatedWaitMs: number;
|
|
176
|
+
constructor(message: string, estimatedWaitMs: number);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if an error is a RateLimitExceededError
|
|
180
|
+
*/
|
|
181
|
+
export declare function isRateLimitExceededError(error: unknown): error is RateLimitExceededError;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiting & Retry Types
|
|
3
|
+
*
|
|
4
|
+
* Provides types for rate limiting and automatic retry functionality.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Error thrown when rate limit is exceeded and throwOnLimit is true
|
|
8
|
+
*/
|
|
9
|
+
export class RateLimitExceededError extends Error {
|
|
10
|
+
estimatedWaitMs;
|
|
11
|
+
constructor(message, estimatedWaitMs) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.estimatedWaitMs = estimatedWaitMs;
|
|
14
|
+
this.name = 'RateLimitExceededError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check if an error is a RateLimitExceededError
|
|
19
|
+
*/
|
|
20
|
+
export function isRateLimitExceededError(error) {
|
|
21
|
+
return error instanceof RateLimitExceededError;
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Rehearsal Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes destructive file operations and provides impact assessment.
|
|
5
|
+
* Handles: rm, unlink, rmdir, file deletions, overwrites
|
|
6
|
+
*/
|
|
7
|
+
import type { RehearsalAnalyzer, RehearsalResult, RehearsalContext } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* File Rehearsal Analyzer
|
|
10
|
+
*/
|
|
11
|
+
export declare class FileRehearsalAnalyzer implements RehearsalAnalyzer {
|
|
12
|
+
readonly id = "file-analyzer";
|
|
13
|
+
readonly name = "File Operations Analyzer";
|
|
14
|
+
readonly category: "file";
|
|
15
|
+
readonly patterns: RegExp[];
|
|
16
|
+
canAnalyze(operation: string): boolean;
|
|
17
|
+
analyze(operation: string, context: RehearsalContext): Promise<RehearsalResult>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a File rehearsal analyzer
|
|
21
|
+
*/
|
|
22
|
+
export declare function createFileAnalyzer(): FileRehearsalAnalyzer;
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Rehearsal Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes destructive file operations and provides impact assessment.
|
|
5
|
+
* Handles: rm, unlink, rmdir, file deletions, overwrites
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
/**
|
|
10
|
+
* Patterns for destructive file operations
|
|
11
|
+
*/
|
|
12
|
+
const FILE_DESTRUCTIVE_PATTERNS = [
|
|
13
|
+
// rm commands
|
|
14
|
+
/^rm\s+/i,
|
|
15
|
+
/^rm\s+-rf?\s+/i,
|
|
16
|
+
/^rm\s+-fr?\s+/i,
|
|
17
|
+
// rmdir
|
|
18
|
+
/^rmdir\s+/i,
|
|
19
|
+
// unlink (Node.js)
|
|
20
|
+
/fs\.unlink/i,
|
|
21
|
+
/fs\.rm/i,
|
|
22
|
+
/fs\.rmdir/i,
|
|
23
|
+
// Truncate operations
|
|
24
|
+
/>\s*[^|]/, // redirect that truncates
|
|
25
|
+
/truncate\s+/i,
|
|
26
|
+
// Move/overwrite
|
|
27
|
+
/^mv\s+.*\s+\S+$/i, // mv can overwrite
|
|
28
|
+
/^cp\s+.*-f/i, // cp -f overwrites
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Get file stats safely
|
|
32
|
+
*/
|
|
33
|
+
function getFileStats(filePath) {
|
|
34
|
+
try {
|
|
35
|
+
return fs.statSync(filePath);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Count lines in a file
|
|
43
|
+
*/
|
|
44
|
+
function countLines(filePath) {
|
|
45
|
+
try {
|
|
46
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
47
|
+
return content.split('\n').length;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get all files in a directory recursively
|
|
55
|
+
*/
|
|
56
|
+
function getFilesRecursively(dirPath, maxDepth = 10) {
|
|
57
|
+
const files = [];
|
|
58
|
+
function walk(currentPath, depth) {
|
|
59
|
+
if (depth > maxDepth)
|
|
60
|
+
return;
|
|
61
|
+
try {
|
|
62
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
// Skip node_modules and .git for performance
|
|
67
|
+
if (entry.name === 'node_modules' || entry.name === '.git') {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
walk(fullPath, depth + 1);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
files.push(fullPath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Ignore permission errors
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
walk(dirPath, 0);
|
|
82
|
+
return files;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parse file paths from rm command
|
|
86
|
+
*/
|
|
87
|
+
function parseRmTargets(operation) {
|
|
88
|
+
// Tokenize the command
|
|
89
|
+
const tokens = [];
|
|
90
|
+
let current = '';
|
|
91
|
+
let inQuote = false;
|
|
92
|
+
let quoteChar = '';
|
|
93
|
+
// Remove the 'rm' prefix
|
|
94
|
+
const withoutRm = operation.replace(/^rm\s+/, '');
|
|
95
|
+
for (const char of withoutRm) {
|
|
96
|
+
if ((char === '"' || char === "'") && !inQuote) {
|
|
97
|
+
inQuote = true;
|
|
98
|
+
quoteChar = char;
|
|
99
|
+
}
|
|
100
|
+
else if (char === quoteChar && inQuote) {
|
|
101
|
+
inQuote = false;
|
|
102
|
+
quoteChar = '';
|
|
103
|
+
}
|
|
104
|
+
else if (char === ' ' && !inQuote) {
|
|
105
|
+
if (current) {
|
|
106
|
+
tokens.push(current);
|
|
107
|
+
current = '';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
current += char;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (current) {
|
|
115
|
+
tokens.push(current);
|
|
116
|
+
}
|
|
117
|
+
// Filter out flags (tokens starting with -)
|
|
118
|
+
const targets = tokens.filter((t) => !t.startsWith('-'));
|
|
119
|
+
return targets;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Expand glob patterns (basic implementation)
|
|
123
|
+
*/
|
|
124
|
+
function expandPath(targetPath, cwd) {
|
|
125
|
+
const absolutePath = path.isAbsolute(targetPath) ? targetPath : path.join(cwd, targetPath);
|
|
126
|
+
// If it contains wildcards, we'd need glob - for now, just return the path
|
|
127
|
+
if (targetPath.includes('*') || targetPath.includes('?')) {
|
|
128
|
+
// Basic glob support - just return warning
|
|
129
|
+
return [absolutePath];
|
|
130
|
+
}
|
|
131
|
+
return [absolutePath];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Analyze impact of deleting a path
|
|
135
|
+
*/
|
|
136
|
+
function analyzePathDeletion(targetPath, cwd) {
|
|
137
|
+
const absolutePath = path.isAbsolute(targetPath) ? targetPath : path.join(cwd, targetPath);
|
|
138
|
+
const stats = getFileStats(absolutePath);
|
|
139
|
+
if (!stats) {
|
|
140
|
+
return { files: [], totalLines: 0, totalSize: 0 };
|
|
141
|
+
}
|
|
142
|
+
const files = [];
|
|
143
|
+
let totalLines = 0;
|
|
144
|
+
let totalSize = 0;
|
|
145
|
+
if (stats.isDirectory()) {
|
|
146
|
+
const allFiles = getFilesRecursively(absolutePath);
|
|
147
|
+
for (const filePath of allFiles) {
|
|
148
|
+
const fileStats = getFileStats(filePath);
|
|
149
|
+
if (fileStats) {
|
|
150
|
+
const lines = countLines(filePath);
|
|
151
|
+
totalLines += lines;
|
|
152
|
+
totalSize += fileStats.size;
|
|
153
|
+
files.push({
|
|
154
|
+
path: filePath,
|
|
155
|
+
changeType: 'delete',
|
|
156
|
+
linesAffected: lines,
|
|
157
|
+
lastModified: fileStats.mtime,
|
|
158
|
+
size: fileStats.size,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
const lines = countLines(absolutePath);
|
|
165
|
+
totalLines = lines;
|
|
166
|
+
totalSize = stats.size;
|
|
167
|
+
files.push({
|
|
168
|
+
path: absolutePath,
|
|
169
|
+
changeType: 'delete',
|
|
170
|
+
linesAffected: lines,
|
|
171
|
+
lastModified: stats.mtime,
|
|
172
|
+
size: stats.size,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return { files, totalLines, totalSize };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Format bytes to human readable
|
|
179
|
+
*/
|
|
180
|
+
function formatBytes(bytes) {
|
|
181
|
+
if (bytes < 1024)
|
|
182
|
+
return `${String(bytes)} B`;
|
|
183
|
+
if (bytes < 1024 * 1024)
|
|
184
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
185
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
186
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
187
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Calculate time investment based on file modification times
|
|
191
|
+
*/
|
|
192
|
+
function calculateTimeInvestment(files, _sessionStartTime) {
|
|
193
|
+
if (files.length === 0)
|
|
194
|
+
return undefined;
|
|
195
|
+
const now = new Date();
|
|
196
|
+
const recentFiles = files.filter((f) => {
|
|
197
|
+
if (!f.lastModified)
|
|
198
|
+
return false;
|
|
199
|
+
const hoursSinceModified = (now.getTime() - f.lastModified.getTime()) / (1000 * 60 * 60);
|
|
200
|
+
return hoursSinceModified < 24;
|
|
201
|
+
});
|
|
202
|
+
if (recentFiles.length === 0)
|
|
203
|
+
return undefined;
|
|
204
|
+
// Find the most recent modification
|
|
205
|
+
const mostRecent = recentFiles.reduce((latest, f) => {
|
|
206
|
+
if (!f.lastModified)
|
|
207
|
+
return latest;
|
|
208
|
+
if (!latest || f.lastModified > latest)
|
|
209
|
+
return f.lastModified;
|
|
210
|
+
return latest;
|
|
211
|
+
}, null);
|
|
212
|
+
if (!mostRecent)
|
|
213
|
+
return undefined;
|
|
214
|
+
const hoursSince = (now.getTime() - mostRecent.getTime()) / (1000 * 60 * 60);
|
|
215
|
+
if (hoursSince < 1) {
|
|
216
|
+
return `${String(recentFiles.length)} file(s) modified in the last hour`;
|
|
217
|
+
}
|
|
218
|
+
else if (hoursSince < 24) {
|
|
219
|
+
return `${String(recentFiles.length)} file(s) modified in the last ${String(Math.round(hoursSince))} hours`;
|
|
220
|
+
}
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Determine severity based on impact
|
|
225
|
+
*/
|
|
226
|
+
function calculateSeverity(filesAffected, linesAffected, sizeBytes, isRecursive) {
|
|
227
|
+
// Critical: large recursive deletions
|
|
228
|
+
if (isRecursive && (filesAffected > 50 || linesAffected > 5000 || sizeBytes > 10 * 1024 * 1024)) {
|
|
229
|
+
return 'critical';
|
|
230
|
+
}
|
|
231
|
+
// High: significant deletions
|
|
232
|
+
if (filesAffected > 20 || linesAffected > 1000 || sizeBytes > 1024 * 1024) {
|
|
233
|
+
return 'high';
|
|
234
|
+
}
|
|
235
|
+
// Medium: moderate deletions
|
|
236
|
+
if (filesAffected > 5 || linesAffected > 100) {
|
|
237
|
+
return 'medium';
|
|
238
|
+
}
|
|
239
|
+
return 'low';
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Determine recommendation based on analysis
|
|
243
|
+
*/
|
|
244
|
+
function calculateRecommendation(severity, filesExist) {
|
|
245
|
+
if (!filesExist) {
|
|
246
|
+
return 'proceed'; // Nothing to delete
|
|
247
|
+
}
|
|
248
|
+
if (severity === 'critical') {
|
|
249
|
+
return 'abort';
|
|
250
|
+
}
|
|
251
|
+
if (severity === 'high') {
|
|
252
|
+
return 'confirm';
|
|
253
|
+
}
|
|
254
|
+
if (severity === 'medium') {
|
|
255
|
+
return 'caution';
|
|
256
|
+
}
|
|
257
|
+
return 'proceed';
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* File Rehearsal Analyzer
|
|
261
|
+
*/
|
|
262
|
+
export class FileRehearsalAnalyzer {
|
|
263
|
+
id = 'file-analyzer';
|
|
264
|
+
name = 'File Operations Analyzer';
|
|
265
|
+
category = 'file';
|
|
266
|
+
patterns = FILE_DESTRUCTIVE_PATTERNS;
|
|
267
|
+
canAnalyze(operation) {
|
|
268
|
+
return this.patterns.some((pattern) => pattern.test(operation));
|
|
269
|
+
}
|
|
270
|
+
async analyze(operation, context) {
|
|
271
|
+
await Promise.resolve(); // Ensure async method has await
|
|
272
|
+
const startTime = Date.now();
|
|
273
|
+
const cwd = context.workingDirectory;
|
|
274
|
+
// Detect operation type
|
|
275
|
+
const isRm = /^rm\s+/i.test(operation);
|
|
276
|
+
const isRecursive = /-r/i.test(operation) || /-R/i.test(operation);
|
|
277
|
+
const isForce = /-f/i.test(operation);
|
|
278
|
+
let allFiles = [];
|
|
279
|
+
let totalLines = 0;
|
|
280
|
+
let totalSize = 0;
|
|
281
|
+
const warnings = [];
|
|
282
|
+
if (isRm) {
|
|
283
|
+
const targets = parseRmTargets(operation);
|
|
284
|
+
for (const target of targets) {
|
|
285
|
+
const expandedPaths = expandPath(target, cwd);
|
|
286
|
+
for (const targetPath of expandedPaths) {
|
|
287
|
+
// Check for dangerous paths
|
|
288
|
+
if (targetPath === '/' || targetPath === cwd || targetPath === process.env.HOME) {
|
|
289
|
+
warnings.push(`⚠️ DANGER: Attempting to delete critical path: ${targetPath}`);
|
|
290
|
+
}
|
|
291
|
+
const analysis = analyzePathDeletion(targetPath, cwd);
|
|
292
|
+
allFiles = [...allFiles, ...analysis.files];
|
|
293
|
+
totalLines += analysis.totalLines;
|
|
294
|
+
totalSize += analysis.totalSize;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (isRecursive) {
|
|
298
|
+
warnings.push('Recursive deletion (-r) will remove all contents');
|
|
299
|
+
}
|
|
300
|
+
if (isForce) {
|
|
301
|
+
warnings.push('Force flag (-f) will ignore nonexistent files and skip prompts');
|
|
302
|
+
}
|
|
303
|
+
if (operation.includes('*')) {
|
|
304
|
+
warnings.push('Glob pattern (*) detected - verify targets before proceeding');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const timeInvestment = calculateTimeInvestment(allFiles, context.sessionStartTime);
|
|
308
|
+
const severity = calculateSeverity(allFiles.length, totalLines, totalSize, isRecursive);
|
|
309
|
+
const recommendation = calculateRecommendation(severity, allFiles.length > 0);
|
|
310
|
+
// Build summary
|
|
311
|
+
let summary;
|
|
312
|
+
if (allFiles.length === 0) {
|
|
313
|
+
summary = 'No files found to delete';
|
|
314
|
+
}
|
|
315
|
+
else if (allFiles.length === 1) {
|
|
316
|
+
summary = `Will delete 1 file (${formatBytes(totalSize)}, ${String(totalLines)} lines)`;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
summary = `Will delete ${String(allFiles.length)} files (${formatBytes(totalSize)}, ${String(totalLines)} lines total)`;
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
operation,
|
|
323
|
+
category: 'file',
|
|
324
|
+
isDestructive: allFiles.length > 0,
|
|
325
|
+
isReversible: false,
|
|
326
|
+
impact: {
|
|
327
|
+
filesAffected: allFiles.length,
|
|
328
|
+
linesAffected: totalLines,
|
|
329
|
+
affectedFiles: allFiles.slice(0, 50), // Limit to first 50 for large deletions
|
|
330
|
+
timeInvestment,
|
|
331
|
+
dataSize: totalSize,
|
|
332
|
+
summary,
|
|
333
|
+
},
|
|
334
|
+
severity,
|
|
335
|
+
warnings,
|
|
336
|
+
recommendation,
|
|
337
|
+
alternatives: [
|
|
338
|
+
'mv <files> /tmp/ (move to temp instead of deleting)',
|
|
339
|
+
'ls -la <path> (list files first)',
|
|
340
|
+
'Create a backup before deleting',
|
|
341
|
+
],
|
|
342
|
+
analysisTimeMs: Date.now() - startTime,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Create a File rehearsal analyzer
|
|
348
|
+
*/
|
|
349
|
+
export function createFileAnalyzer() {
|
|
350
|
+
return new FileRehearsalAnalyzer();
|
|
351
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Rehearsal Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes destructive git operations and provides impact assessment.
|
|
5
|
+
* Handles: git reset, git checkout --, git restore, git clean, git stash drop
|
|
6
|
+
*/
|
|
7
|
+
import type { RehearsalAnalyzer, RehearsalResult, RehearsalContext } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Git Rehearsal Analyzer
|
|
10
|
+
*/
|
|
11
|
+
export declare class GitRehearsalAnalyzer implements RehearsalAnalyzer {
|
|
12
|
+
readonly id = "git-analyzer";
|
|
13
|
+
readonly name = "Git Operations Analyzer";
|
|
14
|
+
readonly category: "git";
|
|
15
|
+
readonly patterns: RegExp[];
|
|
16
|
+
canAnalyze(operation: string): boolean;
|
|
17
|
+
analyze(operation: string, context: RehearsalContext): Promise<RehearsalResult>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a Git rehearsal analyzer
|
|
21
|
+
*/
|
|
22
|
+
export declare function createGitAnalyzer(): GitRehearsalAnalyzer;
|