@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.
Files changed (160) hide show
  1. package/README.md +1277 -0
  2. package/dist/agent.d.ts +1272 -0
  3. package/dist/agent.js +1912 -0
  4. package/dist/anchors/builtin.d.ts +24 -0
  5. package/dist/anchors/builtin.js +61 -0
  6. package/dist/anchors/index.d.ts +6 -0
  7. package/dist/anchors/index.js +5 -0
  8. package/dist/anchors/manager.d.ts +115 -0
  9. package/dist/anchors/manager.js +412 -0
  10. package/dist/anchors/types.d.ts +168 -0
  11. package/dist/anchors/types.js +10 -0
  12. package/dist/context/index.d.ts +12 -0
  13. package/dist/context/index.js +10 -0
  14. package/dist/context/manager.d.ts +224 -0
  15. package/dist/context/manager.js +770 -0
  16. package/dist/context/types.d.ts +377 -0
  17. package/dist/context/types.js +7 -0
  18. package/dist/costs/index.d.ts +8 -0
  19. package/dist/costs/index.js +7 -0
  20. package/dist/costs/tracker.d.ts +121 -0
  21. package/dist/costs/tracker.js +295 -0
  22. package/dist/costs/types.d.ts +157 -0
  23. package/dist/costs/types.js +8 -0
  24. package/dist/errors.d.ts +178 -0
  25. package/dist/errors.js +249 -0
  26. package/dist/guardrails/builtin.d.ts +27 -0
  27. package/dist/guardrails/builtin.js +223 -0
  28. package/dist/guardrails/index.d.ts +6 -0
  29. package/dist/guardrails/index.js +5 -0
  30. package/dist/guardrails/manager.d.ts +117 -0
  31. package/dist/guardrails/manager.js +288 -0
  32. package/dist/guardrails/types.d.ts +159 -0
  33. package/dist/guardrails/types.js +7 -0
  34. package/dist/hooks/index.d.ts +31 -0
  35. package/dist/hooks/index.js +29 -0
  36. package/dist/hooks/manager.d.ts +147 -0
  37. package/dist/hooks/manager.js +600 -0
  38. package/dist/hooks/types.d.ts +368 -0
  39. package/dist/hooks/types.js +12 -0
  40. package/dist/index.d.ts +45 -0
  41. package/dist/index.js +73 -0
  42. package/dist/mcp/client.d.ts +93 -0
  43. package/dist/mcp/client.js +287 -0
  44. package/dist/mcp/errors.d.ts +60 -0
  45. package/dist/mcp/errors.js +78 -0
  46. package/dist/mcp/index.d.ts +43 -0
  47. package/dist/mcp/index.js +45 -0
  48. package/dist/mcp/manager.d.ts +120 -0
  49. package/dist/mcp/manager.js +276 -0
  50. package/dist/mcp/tools.d.ts +54 -0
  51. package/dist/mcp/tools.js +99 -0
  52. package/dist/mcp/types.d.ts +150 -0
  53. package/dist/mcp/types.js +40 -0
  54. package/dist/memory/index.d.ts +8 -0
  55. package/dist/memory/index.js +7 -0
  56. package/dist/memory/loader.d.ts +114 -0
  57. package/dist/memory/loader.js +463 -0
  58. package/dist/memory/types.d.ts +182 -0
  59. package/dist/memory/types.js +8 -0
  60. package/dist/messages/index.d.ts +82 -0
  61. package/dist/messages/index.js +155 -0
  62. package/dist/permissions/index.d.ts +5 -0
  63. package/dist/permissions/index.js +4 -0
  64. package/dist/permissions/manager.d.ts +125 -0
  65. package/dist/permissions/manager.js +379 -0
  66. package/dist/permissions/types.d.ts +162 -0
  67. package/dist/permissions/types.js +7 -0
  68. package/dist/providers/claude.d.ts +90 -0
  69. package/dist/providers/claude.js +348 -0
  70. package/dist/providers/index.d.ts +8 -0
  71. package/dist/providers/index.js +11 -0
  72. package/dist/providers/mock.d.ts +133 -0
  73. package/dist/providers/mock.js +204 -0
  74. package/dist/providers/types.d.ts +168 -0
  75. package/dist/providers/types.js +4 -0
  76. package/dist/rate-limit/index.d.ts +45 -0
  77. package/dist/rate-limit/index.js +47 -0
  78. package/dist/rate-limit/limiter.d.ts +104 -0
  79. package/dist/rate-limit/limiter.js +326 -0
  80. package/dist/rate-limit/provider-wrapper.d.ts +112 -0
  81. package/dist/rate-limit/provider-wrapper.js +201 -0
  82. package/dist/rate-limit/retry.d.ts +108 -0
  83. package/dist/rate-limit/retry.js +287 -0
  84. package/dist/rate-limit/types.d.ts +181 -0
  85. package/dist/rate-limit/types.js +22 -0
  86. package/dist/rehearsal/file-analyzer.d.ts +22 -0
  87. package/dist/rehearsal/file-analyzer.js +351 -0
  88. package/dist/rehearsal/git-analyzer.d.ts +22 -0
  89. package/dist/rehearsal/git-analyzer.js +472 -0
  90. package/dist/rehearsal/index.d.ts +35 -0
  91. package/dist/rehearsal/index.js +36 -0
  92. package/dist/rehearsal/manager.d.ts +100 -0
  93. package/dist/rehearsal/manager.js +290 -0
  94. package/dist/rehearsal/types.d.ts +235 -0
  95. package/dist/rehearsal/types.js +8 -0
  96. package/dist/skills/index.d.ts +160 -0
  97. package/dist/skills/index.js +282 -0
  98. package/dist/state/agent-state.d.ts +41 -0
  99. package/dist/state/agent-state.js +88 -0
  100. package/dist/state/checkpointer.d.ts +110 -0
  101. package/dist/state/checkpointer.js +362 -0
  102. package/dist/state/errors.d.ts +66 -0
  103. package/dist/state/errors.js +88 -0
  104. package/dist/state/index.d.ts +35 -0
  105. package/dist/state/index.js +37 -0
  106. package/dist/state/serializer.d.ts +55 -0
  107. package/dist/state/serializer.js +172 -0
  108. package/dist/state/types.d.ts +312 -0
  109. package/dist/state/types.js +14 -0
  110. package/dist/tools/builtin/bash-output.d.ts +61 -0
  111. package/dist/tools/builtin/bash-output.js +90 -0
  112. package/dist/tools/builtin/bash.d.ts +150 -0
  113. package/dist/tools/builtin/bash.js +354 -0
  114. package/dist/tools/builtin/edit.d.ts +50 -0
  115. package/dist/tools/builtin/edit.js +215 -0
  116. package/dist/tools/builtin/glob.d.ts +62 -0
  117. package/dist/tools/builtin/glob.js +244 -0
  118. package/dist/tools/builtin/grep.d.ts +74 -0
  119. package/dist/tools/builtin/grep.js +363 -0
  120. package/dist/tools/builtin/index.d.ts +44 -0
  121. package/dist/tools/builtin/index.js +69 -0
  122. package/dist/tools/builtin/kill-shell.d.ts +44 -0
  123. package/dist/tools/builtin/kill-shell.js +80 -0
  124. package/dist/tools/builtin/read-file.d.ts +57 -0
  125. package/dist/tools/builtin/read-file.js +184 -0
  126. package/dist/tools/builtin/shell-manager.d.ts +176 -0
  127. package/dist/tools/builtin/shell-manager.js +337 -0
  128. package/dist/tools/builtin/task.d.ts +202 -0
  129. package/dist/tools/builtin/task.js +350 -0
  130. package/dist/tools/builtin/todo.d.ts +207 -0
  131. package/dist/tools/builtin/todo.js +453 -0
  132. package/dist/tools/builtin/utils.d.ts +27 -0
  133. package/dist/tools/builtin/utils.js +70 -0
  134. package/dist/tools/builtin/web-fetch.d.ts +96 -0
  135. package/dist/tools/builtin/web-fetch.js +290 -0
  136. package/dist/tools/builtin/write-file.d.ts +54 -0
  137. package/dist/tools/builtin/write-file.js +147 -0
  138. package/dist/tools/define.d.ts +60 -0
  139. package/dist/tools/define.js +65 -0
  140. package/dist/tools/index.d.ts +10 -0
  141. package/dist/tools/index.js +37 -0
  142. package/dist/tools/registry.d.ts +79 -0
  143. package/dist/tools/registry.js +151 -0
  144. package/dist/tools/types.d.ts +59 -0
  145. package/dist/tools/types.js +4 -0
  146. package/dist/tracing/hooks.d.ts +58 -0
  147. package/dist/tracing/hooks.js +377 -0
  148. package/dist/tracing/index.d.ts +51 -0
  149. package/dist/tracing/index.js +55 -0
  150. package/dist/tracing/logging.d.ts +78 -0
  151. package/dist/tracing/logging.js +310 -0
  152. package/dist/tracing/manager.d.ts +160 -0
  153. package/dist/tracing/manager.js +468 -0
  154. package/dist/tracing/otel.d.ts +102 -0
  155. package/dist/tracing/otel.js +246 -0
  156. package/dist/tracing/types.d.ts +346 -0
  157. package/dist/tracing/types.js +38 -0
  158. package/dist/utils/index.d.ts +23 -0
  159. package/dist/utils/index.js +44 -0
  160. package/package.json +79 -0
@@ -0,0 +1,472 @@
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 { execSync } from 'node:child_process';
8
+ /**
9
+ * Patterns for destructive git operations
10
+ */
11
+ const GIT_DESTRUCTIVE_PATTERNS = [
12
+ // git reset (all forms)
13
+ /^git\s+reset\s+(--hard|--mixed|--soft)?\s*/i,
14
+ // git checkout -- (discard changes)
15
+ /^git\s+checkout\s+--\s*/i,
16
+ /^git\s+checkout\s+\.\s*$/i,
17
+ /^git\s+checkout\s+HEAD\s+--\s*/i,
18
+ // git restore (discard changes)
19
+ /^git\s+restore\s+/i,
20
+ // git clean (remove untracked)
21
+ /^git\s+clean\s+/i,
22
+ // git stash drop
23
+ /^git\s+stash\s+drop/i,
24
+ // git branch -D (force delete)
25
+ /^git\s+branch\s+-D\s+/i,
26
+ // git push --force
27
+ /^git\s+push\s+.*--force/i,
28
+ /^git\s+push\s+-f\s+/i,
29
+ ];
30
+ /**
31
+ * Execute a git command and return output
32
+ */
33
+ function execGit(command, cwd) {
34
+ try {
35
+ return execSync(command, {
36
+ cwd,
37
+ encoding: 'utf-8',
38
+ stdio: ['pipe', 'pipe', 'pipe'],
39
+ }).trim();
40
+ }
41
+ catch {
42
+ return '';
43
+ }
44
+ }
45
+ /**
46
+ * Check if directory is a git repository
47
+ */
48
+ function isGitRepo(cwd) {
49
+ try {
50
+ execSync('git rev-parse --git-dir', {
51
+ cwd,
52
+ encoding: 'utf-8',
53
+ stdio: ['pipe', 'pipe', 'pipe'],
54
+ });
55
+ return true;
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
61
+ /**
62
+ * Get list of modified files with their status
63
+ */
64
+ function getModifiedFiles(cwd) {
65
+ const output = execGit('git status --porcelain', cwd);
66
+ if (!output)
67
+ return [];
68
+ const files = [];
69
+ for (const line of output.split('\n')) {
70
+ if (!line.trim())
71
+ continue;
72
+ const _status = line.substring(0, 2); // Status prefix (e.g., "M ", "D ", "??")
73
+ const filePath = line.substring(3).trim();
74
+ files.push({
75
+ path: filePath,
76
+ changeType: 'reset', // For git operations, changes would be reset
77
+ hasUncommittedChanges: true,
78
+ });
79
+ }
80
+ return files;
81
+ }
82
+ /**
83
+ * Get diff stats for modified files
84
+ */
85
+ function getDiffStats(cwd) {
86
+ const output = execGit('git diff --stat --numstat', cwd);
87
+ if (!output)
88
+ return { files: 0, insertions: 0, deletions: 0 };
89
+ let insertions = 0;
90
+ let deletions = 0;
91
+ let files = 0;
92
+ for (const line of output.split('\n')) {
93
+ const match = line.match(/^(\d+)\s+(\d+)\s+/);
94
+ if (match) {
95
+ insertions += parseInt(match[1], 10);
96
+ deletions += parseInt(match[2], 10);
97
+ files++;
98
+ }
99
+ }
100
+ return { files, insertions, deletions };
101
+ }
102
+ /**
103
+ * Get staged diff stats
104
+ */
105
+ function getStagedDiffStats(cwd) {
106
+ const output = execGit('git diff --cached --stat --numstat', cwd);
107
+ if (!output)
108
+ return { files: 0, insertions: 0, deletions: 0 };
109
+ let insertions = 0;
110
+ let deletions = 0;
111
+ let files = 0;
112
+ for (const line of output.split('\n')) {
113
+ const match = line.match(/^(\d+)\s+(\d+)\s+/);
114
+ if (match) {
115
+ insertions += parseInt(match[1], 10);
116
+ deletions += parseInt(match[2], 10);
117
+ files++;
118
+ }
119
+ }
120
+ return { files, insertions, deletions };
121
+ }
122
+ /**
123
+ * Get untracked files that would be affected by git clean
124
+ */
125
+ function getUntrackedFiles(cwd) {
126
+ const output = execGit('git clean -n -d', cwd);
127
+ if (!output)
128
+ return [];
129
+ const files = [];
130
+ for (const line of output.split('\n')) {
131
+ const match = line.match(/^Would remove (.+)$/);
132
+ if (match) {
133
+ files.push({
134
+ path: match[1],
135
+ changeType: 'delete',
136
+ hasUncommittedChanges: false,
137
+ });
138
+ }
139
+ }
140
+ return files;
141
+ }
142
+ /**
143
+ * Calculate time since last commit
144
+ */
145
+ function getTimeSinceLastCommit(cwd) {
146
+ const output = execGit('git log -1 --format=%cr', cwd);
147
+ return output || undefined;
148
+ }
149
+ /**
150
+ * Determine severity based on impact
151
+ */
152
+ function calculateSeverity(filesAffected, linesAffected, hasUncommittedChanges) {
153
+ if (!hasUncommittedChanges && filesAffected === 0) {
154
+ return 'low';
155
+ }
156
+ if (linesAffected > 500 || filesAffected > 20) {
157
+ return 'critical';
158
+ }
159
+ if (linesAffected > 100 || filesAffected > 10) {
160
+ return 'high';
161
+ }
162
+ if (linesAffected > 20 || filesAffected > 3) {
163
+ return 'medium';
164
+ }
165
+ return 'low';
166
+ }
167
+ /**
168
+ * Determine recommendation based on analysis
169
+ */
170
+ function calculateRecommendation(severity, isReversible) {
171
+ if (severity === 'critical') {
172
+ return isReversible ? 'confirm' : 'abort';
173
+ }
174
+ if (severity === 'high') {
175
+ return 'confirm';
176
+ }
177
+ if (severity === 'medium') {
178
+ return 'caution';
179
+ }
180
+ return 'proceed';
181
+ }
182
+ /**
183
+ * Analyze git reset command
184
+ */
185
+ async function analyzeGitReset(operation, context) {
186
+ await Promise.resolve(); // Ensure async function has await
187
+ const cwd = context.workingDirectory;
188
+ const isHard = operation.includes('--hard');
189
+ const isMixed = operation.includes('--mixed') || (!isHard && !operation.includes('--soft'));
190
+ const modifiedFiles = getModifiedFiles(cwd);
191
+ const diffStats = getDiffStats(cwd);
192
+ const stagedStats = getStagedDiffStats(cwd);
193
+ const totalLines = diffStats.insertions + diffStats.deletions + stagedStats.insertions + stagedStats.deletions;
194
+ const warnings = [];
195
+ if (isHard) {
196
+ warnings.push('--hard will permanently discard all uncommitted changes');
197
+ if (totalLines > 100) {
198
+ warnings.push(`You will lose ${String(totalLines)} lines of changes`);
199
+ }
200
+ }
201
+ if (isMixed && stagedStats.files > 0) {
202
+ warnings.push(`${String(stagedStats.files)} staged files will be unstaged`);
203
+ }
204
+ const timeSinceCommit = getTimeSinceLastCommit(cwd);
205
+ const timeInvestment = timeSinceCommit
206
+ ? `Changes made since last commit (${timeSinceCommit})`
207
+ : undefined;
208
+ return {
209
+ isDestructive: isHard,
210
+ isReversible: !isHard, // soft and mixed are reversible
211
+ impact: {
212
+ filesAffected: modifiedFiles.length,
213
+ linesAffected: totalLines,
214
+ affectedFiles: modifiedFiles,
215
+ timeInvestment,
216
+ summary: isHard
217
+ ? `Will discard ${String(modifiedFiles.length)} modified files (${String(totalLines)} lines of changes)`
218
+ : `Will unstage ${String(stagedStats.files)} files`,
219
+ },
220
+ warnings,
221
+ alternatives: isHard
222
+ ? ['git stash (save changes for later)', 'git commit (save changes permanently)']
223
+ : undefined,
224
+ };
225
+ }
226
+ /**
227
+ * Analyze git checkout -- or git restore
228
+ */
229
+ async function analyzeGitDiscard(operation, context) {
230
+ await Promise.resolve(); // Ensure async function has await
231
+ const cwd = context.workingDirectory;
232
+ // Check if specific files are targeted
233
+ const filePattern = operation.match(/(?:checkout\s+--|restore\s+(?:--staged\s+)?)\s*(.+)$/i);
234
+ const targetPath = filePattern?.[1]?.trim() || '.';
235
+ let affectedFiles;
236
+ let diffStats;
237
+ if (targetPath === '.' || targetPath === '*') {
238
+ // All files
239
+ affectedFiles = getModifiedFiles(cwd);
240
+ diffStats = getDiffStats(cwd);
241
+ }
242
+ else {
243
+ // Specific file(s)
244
+ const output = execGit(`git diff --numstat -- "${targetPath}"`, cwd);
245
+ affectedFiles = [];
246
+ diffStats = { files: 0, insertions: 0, deletions: 0 };
247
+ if (output) {
248
+ for (const line of output.split('\n')) {
249
+ const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/);
250
+ if (match) {
251
+ diffStats.insertions += parseInt(match[1], 10);
252
+ diffStats.deletions += parseInt(match[2], 10);
253
+ diffStats.files++;
254
+ affectedFiles.push({
255
+ path: match[3],
256
+ changeType: 'reset',
257
+ hasUncommittedChanges: true,
258
+ });
259
+ }
260
+ }
261
+ }
262
+ }
263
+ const totalLines = diffStats.insertions + diffStats.deletions;
264
+ const warnings = [];
265
+ if (affectedFiles.length > 0) {
266
+ warnings.push('This will permanently discard uncommitted changes');
267
+ }
268
+ if (totalLines > 50) {
269
+ warnings.push(`${String(totalLines)} lines of changes will be lost`);
270
+ }
271
+ return {
272
+ isDestructive: affectedFiles.length > 0,
273
+ isReversible: false,
274
+ impact: {
275
+ filesAffected: affectedFiles.length,
276
+ linesAffected: totalLines,
277
+ affectedFiles,
278
+ summary: affectedFiles.length > 0
279
+ ? `Will discard changes in ${String(affectedFiles.length)} file(s) (${String(totalLines)} lines)`
280
+ : 'No changes to discard',
281
+ },
282
+ warnings,
283
+ alternatives: ['git stash (save changes for later)', 'git diff (review changes first)'],
284
+ };
285
+ }
286
+ /**
287
+ * Analyze git clean command
288
+ */
289
+ async function analyzeGitClean(operation, context) {
290
+ await Promise.resolve(); // Ensure async function has await
291
+ const cwd = context.workingDirectory;
292
+ const untrackedFiles = getUntrackedFiles(cwd);
293
+ const warnings = [];
294
+ if (untrackedFiles.length > 0) {
295
+ warnings.push('This will permanently delete untracked files');
296
+ }
297
+ if (operation.includes('-x')) {
298
+ warnings.push('Including ignored files (from .gitignore)');
299
+ }
300
+ if (operation.includes('-d')) {
301
+ warnings.push('Including untracked directories');
302
+ }
303
+ return {
304
+ isDestructive: untrackedFiles.length > 0,
305
+ isReversible: false,
306
+ impact: {
307
+ filesAffected: untrackedFiles.length,
308
+ linesAffected: 0,
309
+ affectedFiles: untrackedFiles,
310
+ summary: untrackedFiles.length > 0
311
+ ? `Will delete ${String(untrackedFiles.length)} untracked file(s)`
312
+ : 'No untracked files to remove',
313
+ },
314
+ warnings,
315
+ alternatives: ['git clean -n (preview what would be deleted)'],
316
+ };
317
+ }
318
+ /**
319
+ * Analyze git stash drop
320
+ */
321
+ async function analyzeGitStashDrop(operation, context) {
322
+ await Promise.resolve(); // Ensure async function has await
323
+ const cwd = context.workingDirectory;
324
+ // Get stash info
325
+ const stashMatch = operation.match(/stash@\{(\d+)\}/);
326
+ const stashIndex = stashMatch ? stashMatch[1] : '0';
327
+ const stashInfo = execGit(`git stash show stash@{${stashIndex}} --stat`, cwd);
328
+ const stashMessage = execGit(`git stash list | head -${String(parseInt(stashIndex, 10) + 1)} | tail -1`, cwd);
329
+ let filesAffected = 0;
330
+ if (stashInfo) {
331
+ const match = stashInfo.match(/(\d+) files? changed/);
332
+ filesAffected = match ? parseInt(match[1], 10) : 0;
333
+ }
334
+ return {
335
+ isDestructive: true,
336
+ isReversible: false,
337
+ impact: {
338
+ filesAffected,
339
+ linesAffected: 0,
340
+ affectedFiles: [],
341
+ summary: stashMessage
342
+ ? `Will drop stash: ${stashMessage}`
343
+ : `Will drop stash@{${stashIndex}}`,
344
+ },
345
+ warnings: ['Dropped stashes cannot be recovered'],
346
+ alternatives: [
347
+ 'git stash apply (apply without dropping)',
348
+ 'git stash branch (create branch from stash)',
349
+ ],
350
+ };
351
+ }
352
+ /**
353
+ * Analyze git push --force
354
+ */
355
+ async function analyzeGitForcePush(_operation, context) {
356
+ await Promise.resolve(); // Ensure async function has await
357
+ const cwd = context.workingDirectory;
358
+ const currentBranch = execGit('git rev-parse --abbrev-ref HEAD', cwd);
359
+ const warnings = [
360
+ 'Force push will overwrite remote history',
361
+ 'Other collaborators may lose their work',
362
+ ];
363
+ if (currentBranch === 'main' || currentBranch === 'master') {
364
+ warnings.unshift('⚠️ DANGER: Force pushing to main/master branch!');
365
+ }
366
+ return {
367
+ isDestructive: true,
368
+ isReversible: false, // Technically recoverable with reflog, but practically destructive
369
+ impact: {
370
+ filesAffected: 0,
371
+ linesAffected: 0,
372
+ affectedFiles: [],
373
+ summary: `Will force push to remote, overwriting history on ${currentBranch}`,
374
+ },
375
+ warnings,
376
+ alternatives: [
377
+ 'git push --force-with-lease (safer, fails if remote has new commits)',
378
+ 'git pull --rebase && git push (sync with remote first)',
379
+ ],
380
+ };
381
+ }
382
+ /**
383
+ * Git Rehearsal Analyzer
384
+ */
385
+ export class GitRehearsalAnalyzer {
386
+ id = 'git-analyzer';
387
+ name = 'Git Operations Analyzer';
388
+ category = 'git';
389
+ patterns = GIT_DESTRUCTIVE_PATTERNS;
390
+ canAnalyze(operation) {
391
+ return this.patterns.some((pattern) => pattern.test(operation));
392
+ }
393
+ async analyze(operation, context) {
394
+ const startTime = Date.now();
395
+ const cwd = context.workingDirectory;
396
+ // Check if we're in a git repo
397
+ if (!isGitRepo(cwd)) {
398
+ return {
399
+ operation,
400
+ category: 'git',
401
+ isDestructive: false,
402
+ isReversible: true,
403
+ impact: {
404
+ filesAffected: 0,
405
+ linesAffected: 0,
406
+ affectedFiles: [],
407
+ summary: 'Not a git repository',
408
+ },
409
+ severity: 'low',
410
+ warnings: ['Not in a git repository - command will fail'],
411
+ recommendation: 'abort',
412
+ analysisTimeMs: Date.now() - startTime,
413
+ };
414
+ }
415
+ // Determine which analysis to perform
416
+ let partialResult;
417
+ if (/git\s+reset/i.test(operation)) {
418
+ partialResult = await analyzeGitReset(operation, context);
419
+ }
420
+ else if (/git\s+(checkout\s+--|restore)/i.test(operation)) {
421
+ partialResult = await analyzeGitDiscard(operation, context);
422
+ }
423
+ else if (/git\s+clean/i.test(operation)) {
424
+ partialResult = await analyzeGitClean(operation, context);
425
+ }
426
+ else if (/git\s+stash\s+drop/i.test(operation)) {
427
+ partialResult = await analyzeGitStashDrop(operation, context);
428
+ }
429
+ else if (/git\s+push\s+.*--force|-f/i.test(operation)) {
430
+ partialResult = await analyzeGitForcePush(operation, context);
431
+ }
432
+ else {
433
+ // Generic destructive git operation
434
+ partialResult = {
435
+ isDestructive: true,
436
+ isReversible: false,
437
+ impact: {
438
+ filesAffected: 0,
439
+ linesAffected: 0,
440
+ affectedFiles: [],
441
+ summary: 'Potentially destructive git operation',
442
+ },
443
+ warnings: ['This git operation may cause data loss'],
444
+ };
445
+ }
446
+ const severity = calculateSeverity(partialResult.impact?.filesAffected ?? 0, partialResult.impact?.linesAffected ?? 0, partialResult.isDestructive ?? false);
447
+ const recommendation = calculateRecommendation(severity, partialResult.isReversible ?? false);
448
+ return {
449
+ operation,
450
+ category: 'git',
451
+ isDestructive: partialResult.isDestructive ?? false,
452
+ isReversible: partialResult.isReversible ?? false,
453
+ impact: partialResult.impact ?? {
454
+ filesAffected: 0,
455
+ linesAffected: 0,
456
+ affectedFiles: [],
457
+ summary: 'Unknown impact',
458
+ },
459
+ severity,
460
+ warnings: partialResult.warnings ?? [],
461
+ recommendation,
462
+ alternatives: partialResult.alternatives,
463
+ analysisTimeMs: Date.now() - startTime,
464
+ };
465
+ }
466
+ }
467
+ /**
468
+ * Create a Git rehearsal analyzer
469
+ */
470
+ export function createGitAnalyzer() {
471
+ return new GitRehearsalAnalyzer();
472
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Rehearsal System
3
+ *
4
+ * Provides impact analysis for destructive operations before execution.
5
+ * This is a novel safety feature that goes beyond simple dry-run by
6
+ * actually analyzing what would be affected.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { RehearsalManager } from '@compilr-dev/agents';
11
+ *
12
+ * const rehearsal = new RehearsalManager({
13
+ * workingDirectory: '/path/to/project',
14
+ * });
15
+ *
16
+ * // Before executing a destructive command
17
+ * const result = await rehearsal.rehearse('git reset --hard HEAD~1');
18
+ *
19
+ * console.log(result.impact.summary);
20
+ * // "Will discard 5 modified files (127 lines of changes)"
21
+ *
22
+ * console.log(result.warnings);
23
+ * // ["--hard will permanently discard all uncommitted changes"]
24
+ *
25
+ * if (result.recommendation === 'abort') {
26
+ * console.log('Operation too dangerous!');
27
+ * } else if (result.recommendation === 'confirm') {
28
+ * // Ask user for confirmation
29
+ * }
30
+ * ```
31
+ */
32
+ export type { ImpactSeverity, RehearsalRecommendation, OperationCategory, AffectedFile, RehearsalImpact, RehearsalResult, RehearsalContext, RehearsalAnalyzer, RehearsalManagerOptions, RehearsalEventType, RehearsalEvent, RehearsalEventHandler, } from './types.js';
33
+ export { RehearsalManager, createRehearsalManager } from './manager.js';
34
+ export { GitRehearsalAnalyzer, createGitAnalyzer } from './git-analyzer.js';
35
+ export { FileRehearsalAnalyzer, createFileAnalyzer } from './file-analyzer.js';
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Rehearsal System
3
+ *
4
+ * Provides impact analysis for destructive operations before execution.
5
+ * This is a novel safety feature that goes beyond simple dry-run by
6
+ * actually analyzing what would be affected.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { RehearsalManager } from '@compilr-dev/agents';
11
+ *
12
+ * const rehearsal = new RehearsalManager({
13
+ * workingDirectory: '/path/to/project',
14
+ * });
15
+ *
16
+ * // Before executing a destructive command
17
+ * const result = await rehearsal.rehearse('git reset --hard HEAD~1');
18
+ *
19
+ * console.log(result.impact.summary);
20
+ * // "Will discard 5 modified files (127 lines of changes)"
21
+ *
22
+ * console.log(result.warnings);
23
+ * // ["--hard will permanently discard all uncommitted changes"]
24
+ *
25
+ * if (result.recommendation === 'abort') {
26
+ * console.log('Operation too dangerous!');
27
+ * } else if (result.recommendation === 'confirm') {
28
+ * // Ask user for confirmation
29
+ * }
30
+ * ```
31
+ */
32
+ // Manager
33
+ export { RehearsalManager, createRehearsalManager } from './manager.js';
34
+ // Built-in analyzers
35
+ export { GitRehearsalAnalyzer, createGitAnalyzer } from './git-analyzer.js';
36
+ export { FileRehearsalAnalyzer, createFileAnalyzer } from './file-analyzer.js';
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Rehearsal Manager
3
+ *
4
+ * Coordinates rehearsal analyzers to provide impact analysis
5
+ * for destructive operations before they are executed.
6
+ */
7
+ import type { RehearsalAnalyzer, RehearsalResult, RehearsalManagerOptions, RehearsalEventHandler } from './types.js';
8
+ /**
9
+ * RehearsalManager - Coordinates impact analysis for destructive operations
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const manager = new RehearsalManager({
14
+ * workingDirectory: '/path/to/project',
15
+ * });
16
+ *
17
+ * // Analyze before executing
18
+ * const result = await manager.rehearse('rm -rf node_modules');
19
+ *
20
+ * if (result.recommendation === 'abort') {
21
+ * console.log('Operation too dangerous:', result.warnings);
22
+ * } else if (result.recommendation === 'confirm') {
23
+ * const confirmed = await askUser(result.impact.summary);
24
+ * if (confirmed) {
25
+ * // Execute the operation
26
+ * }
27
+ * }
28
+ * ```
29
+ */
30
+ export declare class RehearsalManager {
31
+ private readonly analyzers;
32
+ private readonly workingDirectory;
33
+ private readonly warningThreshold;
34
+ private readonly sessionStartTime;
35
+ private readonly sessionModifiedFiles;
36
+ private readonly trackSessionFiles;
37
+ private eventHandler?;
38
+ constructor(options?: RehearsalManagerOptions);
39
+ /**
40
+ * Register an analyzer
41
+ */
42
+ registerAnalyzer(analyzer: RehearsalAnalyzer): this;
43
+ /**
44
+ * Unregister an analyzer
45
+ */
46
+ unregisterAnalyzer(id: string): boolean;
47
+ /**
48
+ * Get all registered analyzers
49
+ */
50
+ getAnalyzers(): RehearsalAnalyzer[];
51
+ /**
52
+ * Set event handler
53
+ */
54
+ onEvent(handler: RehearsalEventHandler): this;
55
+ /**
56
+ * Emit an event
57
+ */
58
+ private emit;
59
+ /**
60
+ * Track a file as modified in this session
61
+ */
62
+ trackFileModification(filePath: string): void;
63
+ /**
64
+ * Get files modified in this session
65
+ */
66
+ getSessionModifiedFiles(): string[];
67
+ /**
68
+ * Check if an operation is potentially destructive
69
+ */
70
+ isDestructive(operation: string): boolean;
71
+ /**
72
+ * Find the appropriate analyzer for an operation
73
+ */
74
+ findAnalyzer(operation: string): RehearsalAnalyzer | undefined;
75
+ /**
76
+ * Rehearse an operation - analyze its impact before execution
77
+ *
78
+ * @param operation - The command or operation to analyze
79
+ * @returns Impact analysis result
80
+ */
81
+ rehearse(operation: string): Promise<RehearsalResult>;
82
+ /**
83
+ * Rehearse multiple operations
84
+ */
85
+ rehearseAll(operations: string[]): Promise<RehearsalResult[]>;
86
+ /**
87
+ * Check if an operation should proceed based on rehearsal
88
+ *
89
+ * @returns true if safe to proceed, false if should abort
90
+ */
91
+ shouldProceed(operation: string): Promise<boolean>;
92
+ /**
93
+ * Get a formatted summary of a rehearsal result
94
+ */
95
+ formatResult(result: RehearsalResult): string;
96
+ }
97
+ /**
98
+ * Create a RehearsalManager with default configuration
99
+ */
100
+ export declare function createRehearsalManager(options?: RehearsalManagerOptions): RehearsalManager;