@agentuity/coder 1.0.37
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 +57 -0
- package/dist/chain-preview.d.ts +55 -0
- package/dist/chain-preview.d.ts.map +1 -0
- package/dist/chain-preview.js +472 -0
- package/dist/chain-preview.js.map +1 -0
- package/dist/client.d.ts +43 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +402 -0
- package/dist/client.js.map +1 -0
- package/dist/commands.d.ts +22 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +99 -0
- package/dist/commands.js.map +1 -0
- package/dist/footer.d.ts +34 -0
- package/dist/footer.d.ts.map +1 -0
- package/dist/footer.js +249 -0
- package/dist/footer.js.map +1 -0
- package/dist/handlers.d.ts +24 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +83 -0
- package/dist/handlers.js.map +1 -0
- package/dist/hub-overlay.d.ts +107 -0
- package/dist/hub-overlay.d.ts.map +1 -0
- package/dist/hub-overlay.js +1794 -0
- package/dist/hub-overlay.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1585 -0
- package/dist/index.js.map +1 -0
- package/dist/output-viewer.d.ts +49 -0
- package/dist/output-viewer.d.ts.map +1 -0
- package/dist/output-viewer.js +389 -0
- package/dist/output-viewer.js.map +1 -0
- package/dist/overlay.d.ts +40 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +225 -0
- package/dist/overlay.js.map +1 -0
- package/dist/protocol.d.ts +118 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +3 -0
- package/dist/protocol.js.map +1 -0
- package/dist/remote-session.d.ts +113 -0
- package/dist/remote-session.d.ts.map +1 -0
- package/dist/remote-session.js +645 -0
- package/dist/remote-session.js.map +1 -0
- package/dist/remote-tui.d.ts +40 -0
- package/dist/remote-tui.d.ts.map +1 -0
- package/dist/remote-tui.js +606 -0
- package/dist/remote-tui.js.map +1 -0
- package/dist/renderers.d.ts +34 -0
- package/dist/renderers.d.ts.map +1 -0
- package/dist/renderers.js +669 -0
- package/dist/renderers.js.map +1 -0
- package/dist/review.d.ts +15 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +154 -0
- package/dist/review.js.map +1 -0
- package/dist/titlebar.d.ts +3 -0
- package/dist/titlebar.d.ts.map +1 -0
- package/dist/titlebar.js +59 -0
- package/dist/titlebar.js.map +1 -0
- package/dist/todo/index.d.ts +3 -0
- package/dist/todo/index.d.ts.map +1 -0
- package/dist/todo/index.js +3 -0
- package/dist/todo/index.js.map +1 -0
- package/dist/todo/store.d.ts +6 -0
- package/dist/todo/store.d.ts.map +1 -0
- package/dist/todo/store.js +43 -0
- package/dist/todo/store.js.map +1 -0
- package/dist/todo/types.d.ts +13 -0
- package/dist/todo/types.d.ts.map +1 -0
- package/dist/todo/types.js +2 -0
- package/dist/todo/types.js.map +1 -0
- package/package.json +44 -0
- package/src/chain-preview.ts +621 -0
- package/src/client.ts +515 -0
- package/src/commands.ts +132 -0
- package/src/footer.ts +305 -0
- package/src/handlers.ts +113 -0
- package/src/hub-overlay.ts +2324 -0
- package/src/index.ts +1907 -0
- package/src/output-viewer.ts +480 -0
- package/src/overlay.ts +294 -0
- package/src/protocol.ts +157 -0
- package/src/remote-session.ts +800 -0
- package/src/remote-tui.ts +707 -0
- package/src/renderers.ts +740 -0
- package/src/review.ts +201 -0
- package/src/titlebar.ts +63 -0
- package/src/todo/index.ts +2 -0
- package/src/todo/store.ts +49 -0
- package/src/todo/types.ts +14 -0
package/src/review.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive code review command.
|
|
3
|
+
*
|
|
4
|
+
* Provides `/review` with 4 modes:
|
|
5
|
+
* 1. PR-style review against a base branch
|
|
6
|
+
* 2. Review uncommitted changes (staged + unstaged)
|
|
7
|
+
* 3. Review a specific commit
|
|
8
|
+
* 4. Custom review instructions (no diff)
|
|
9
|
+
*
|
|
10
|
+
* Gathers diff/instructions, parses stats, and sends
|
|
11
|
+
* a formatted review prompt to the @reviewer agent.
|
|
12
|
+
*/
|
|
13
|
+
import type { ExtensionAPI, ExtensionCommandContext } from '@mariozechner/pi-coding-agent';
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
|
|
16
|
+
// ── Noise Filters ──
|
|
17
|
+
// Files excluded from diff stats (still included in the diff itself for context)
|
|
18
|
+
const EXCLUDED_PATTERNS = [
|
|
19
|
+
/\.(lock|lockb)$/, // lock files
|
|
20
|
+
/\.min\.(js|css)$/, // minified
|
|
21
|
+
/\.(png|jpg|jpeg|gif|svg|ico|webp)$/, // images
|
|
22
|
+
/\.(woff|woff2|ttf|eot)$/, // fonts
|
|
23
|
+
/node_modules\//, // dependencies
|
|
24
|
+
/\.map$/, // source maps
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const MAX_DIFF_SIZE = 100_000; // 100KB max for context window
|
|
28
|
+
|
|
29
|
+
// ── Types ──
|
|
30
|
+
|
|
31
|
+
interface DiffStats {
|
|
32
|
+
files: number;
|
|
33
|
+
added: number;
|
|
34
|
+
removed: number;
|
|
35
|
+
excludedFiles: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Git Helpers ──
|
|
39
|
+
|
|
40
|
+
function execGit(args: string[]): string {
|
|
41
|
+
try {
|
|
42
|
+
return execFileSync('git', args, {
|
|
43
|
+
encoding: 'utf-8',
|
|
44
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
45
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
46
|
+
});
|
|
47
|
+
} catch (err: unknown) {
|
|
48
|
+
const stderr =
|
|
49
|
+
err && typeof err === 'object' && 'stderr' in err ? String((err as any).stderr) : '';
|
|
50
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
51
|
+
if (process.env['AGENTUITY_DEBUG']) {
|
|
52
|
+
console.error(`[agentuity-coder] git ${args.join(' ')} failed: ${stderr || msg}`);
|
|
53
|
+
}
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function execGitDiff(diffArgs: string[]): string {
|
|
59
|
+
return execGit(['diff', ...diffArgs]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function execGitShow(hash: string): string {
|
|
63
|
+
return execGit(['show', hash]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Diff Parsing ──
|
|
67
|
+
|
|
68
|
+
function isExcluded(filePath: string): boolean {
|
|
69
|
+
return EXCLUDED_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseDiffStats(diff: string): DiffStats {
|
|
73
|
+
if (!diff.trim()) {
|
|
74
|
+
return { files: 0, added: 0, removed: 0, excludedFiles: [] };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Split on "diff --git" boundaries
|
|
78
|
+
const fileDiffs = diff.split(/^diff --git /m).filter(Boolean);
|
|
79
|
+
|
|
80
|
+
let files = 0;
|
|
81
|
+
let added = 0;
|
|
82
|
+
let removed = 0;
|
|
83
|
+
const excludedFiles: string[] = [];
|
|
84
|
+
|
|
85
|
+
for (const fileDiff of fileDiffs) {
|
|
86
|
+
// Extract file path from "a/path b/path" header
|
|
87
|
+
const headerMatch = fileDiff.match(/^a\/(.+?) b\//);
|
|
88
|
+
if (!headerMatch) continue;
|
|
89
|
+
const filePath = headerMatch[1] ?? '';
|
|
90
|
+
|
|
91
|
+
if (isExcluded(filePath)) {
|
|
92
|
+
excludedFiles.push(filePath);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
files++;
|
|
97
|
+
|
|
98
|
+
// Count added/removed lines (lines starting with + or - but not headers)
|
|
99
|
+
const lines = fileDiff.split('\n');
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
102
|
+
added++;
|
|
103
|
+
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
104
|
+
removed++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { files, added, removed, excludedFiles };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Review Prompt Builder ──
|
|
113
|
+
|
|
114
|
+
function buildReviewPrompt(context: string, diff: string, stats: DiffStats): string {
|
|
115
|
+
const header = `## Code Review Request\n\n${context}\n\n**Stats:** ${stats.files} files changed, +${stats.added}/-${stats.removed} lines`;
|
|
116
|
+
|
|
117
|
+
const excludedNote =
|
|
118
|
+
stats.excludedFiles.length > 0
|
|
119
|
+
? `\n\n_Excluded from stats: ${stats.excludedFiles.length} noise files (lock files, images, etc.)_`
|
|
120
|
+
: '';
|
|
121
|
+
|
|
122
|
+
// Truncate diff if too large
|
|
123
|
+
let diffContent = diff;
|
|
124
|
+
if (diff.length > MAX_DIFF_SIZE) {
|
|
125
|
+
diffContent = diff.slice(0, MAX_DIFF_SIZE) + '\n\n[Diff truncated - too large for review]';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return `${header}${excludedNote}\n\n\`\`\`diff\n${diffContent}\n\`\`\`\n\nPlease review these changes for:\n1. Correctness and potential bugs\n2. Security concerns\n3. Code quality and best practices\n4. Performance implications`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Main Handler ──
|
|
132
|
+
|
|
133
|
+
export async function handleReview(
|
|
134
|
+
_args: string,
|
|
135
|
+
ctx: ExtensionCommandContext,
|
|
136
|
+
pi: ExtensionAPI
|
|
137
|
+
): Promise<void> {
|
|
138
|
+
// Non-interactive fallback
|
|
139
|
+
if (!ctx.hasUI) {
|
|
140
|
+
pi.sendUserMessage('@reviewer Review recent code changes', { deliverAs: 'followUp' });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Show mode selector
|
|
145
|
+
const mode = await ctx.ui.select('Review Mode', [
|
|
146
|
+
'Review against a base branch (PR Style)',
|
|
147
|
+
'Review uncommitted changes',
|
|
148
|
+
'Review a specific commit',
|
|
149
|
+
'Custom review instructions',
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
if (!mode) return;
|
|
153
|
+
|
|
154
|
+
let diff = '';
|
|
155
|
+
let context = '';
|
|
156
|
+
|
|
157
|
+
if (mode.includes('base branch')) {
|
|
158
|
+
const branch = await ctx.ui.input('Base branch', 'main');
|
|
159
|
+
if (!branch) return;
|
|
160
|
+
diff = execGitDiff([`${branch}...HEAD`]);
|
|
161
|
+
if (!diff.trim()) {
|
|
162
|
+
ctx.ui.notify(`No changes found between ${branch} and HEAD`, 'warning');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
context = `Reviewing changes from ${branch} to HEAD`;
|
|
166
|
+
} else if (mode.includes('uncommitted')) {
|
|
167
|
+
const staged = execGitDiff(['--cached']);
|
|
168
|
+
const unstaged = execGitDiff([]);
|
|
169
|
+
diff = staged + '\n' + unstaged;
|
|
170
|
+
if (!diff.trim()) {
|
|
171
|
+
ctx.ui.notify('No uncommitted changes found', 'warning');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
context = 'Reviewing uncommitted changes (staged + unstaged)';
|
|
175
|
+
} else if (mode.includes('specific commit')) {
|
|
176
|
+
const hash = await ctx.ui.input('Commit hash', '');
|
|
177
|
+
if (!hash) return;
|
|
178
|
+
diff = execGitShow(hash);
|
|
179
|
+
if (!diff.trim()) {
|
|
180
|
+
ctx.ui.notify(`No diff found for commit ${hash}`, 'warning');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
context = `Reviewing commit ${hash}`;
|
|
184
|
+
} else {
|
|
185
|
+
// Custom instructions — no diff
|
|
186
|
+
const instructions = await ctx.ui.input('Review instructions', '');
|
|
187
|
+
if (!instructions) return;
|
|
188
|
+
pi.sendUserMessage(`@reviewer ${instructions}`, { deliverAs: 'followUp' });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Parse diff stats
|
|
193
|
+
const stats = parseDiffStats(diff);
|
|
194
|
+
|
|
195
|
+
// Show summary notification
|
|
196
|
+
ctx.ui.notify(`${context}: ${stats.files} files, +${stats.added}/-${stats.removed}`, 'info');
|
|
197
|
+
|
|
198
|
+
// Compose and send review message
|
|
199
|
+
const reviewPrompt = buildReviewPrompt(context, diff, stats);
|
|
200
|
+
pi.sendUserMessage(`@reviewer ${reviewPrompt}`, { deliverAs: 'followUp' });
|
|
201
|
+
}
|
package/src/titlebar.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const SPINNER_FRAMES = [
|
|
5
|
+
'\u280B',
|
|
6
|
+
'\u2819',
|
|
7
|
+
'\u2839',
|
|
8
|
+
'\u2838',
|
|
9
|
+
'\u283C',
|
|
10
|
+
'\u2834',
|
|
11
|
+
'\u2826',
|
|
12
|
+
'\u2827',
|
|
13
|
+
'\u2807',
|
|
14
|
+
'\u280F',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
18
|
+
let frameIndex = 0;
|
|
19
|
+
|
|
20
|
+
function getBaseTitle(pi: ExtensionAPI): string {
|
|
21
|
+
const cwd = path.basename(process.cwd());
|
|
22
|
+
// getSessionName() may not be in published types yet — use optional chaining
|
|
23
|
+
const session = (pi as any).getSessionName?.() as string | undefined;
|
|
24
|
+
return session ? `\u2A3A Agentuity - ${session} - ${cwd}` : `\u2A3A Agentuity - ${cwd}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function setupTitlebar(pi: ExtensionAPI): void {
|
|
28
|
+
// Set initial title on session start
|
|
29
|
+
pi.on('session_start', async (_event, ctx) => {
|
|
30
|
+
if (!ctx.hasUI) return;
|
|
31
|
+
ctx.ui.setTitle(getBaseTitle(pi));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Spinner during agent work
|
|
35
|
+
pi.on('agent_start', async (_event, ctx) => {
|
|
36
|
+
if (!ctx.hasUI) return;
|
|
37
|
+
stopSpinner(ctx, pi);
|
|
38
|
+
timer = setInterval(() => {
|
|
39
|
+
const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length];
|
|
40
|
+
ctx.ui.setTitle(`${frame} ${getBaseTitle(pi)}`);
|
|
41
|
+
frameIndex++;
|
|
42
|
+
}, 80);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
pi.on('agent_end', async (_event, ctx) => {
|
|
46
|
+
if (!ctx.hasUI) return;
|
|
47
|
+
stopSpinner(ctx, pi);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
pi.on('session_shutdown', async (_event, ctx) => {
|
|
51
|
+
if (!ctx.hasUI) return;
|
|
52
|
+
stopSpinner(ctx, pi);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function stopSpinner(ctx: ExtensionContext, pi: ExtensionAPI): void {
|
|
57
|
+
if (timer) {
|
|
58
|
+
clearInterval(timer);
|
|
59
|
+
timer = null;
|
|
60
|
+
}
|
|
61
|
+
frameIndex = 0;
|
|
62
|
+
ctx.ui.setTitle(getBaseTitle(pi));
|
|
63
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Todo, TodoError, TodoErrorCode } from './types.ts';
|
|
2
|
+
|
|
3
|
+
const store = new Map<string, Todo>();
|
|
4
|
+
|
|
5
|
+
function makeTodoError(code: TodoErrorCode, message: string, field?: 'id' | 'text'): TodoError {
|
|
6
|
+
const error: TodoError = { code, message };
|
|
7
|
+
if (field !== undefined) {
|
|
8
|
+
error.field = field;
|
|
9
|
+
}
|
|
10
|
+
return error;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createTodo(text: string): Todo {
|
|
14
|
+
const trimmed = text.trim();
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
throw makeTodoError('VALIDATION_ERROR', 'text must not be empty', 'text');
|
|
17
|
+
}
|
|
18
|
+
const todo: Todo = {
|
|
19
|
+
id: crypto.randomUUID(),
|
|
20
|
+
text: trimmed,
|
|
21
|
+
completed: false,
|
|
22
|
+
createdAt: Date.now(),
|
|
23
|
+
};
|
|
24
|
+
store.set(todo.id, todo);
|
|
25
|
+
return todo;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function listTodos(): Todo[] {
|
|
29
|
+
return Array.from(store.values()).sort((a, b) => b.createdAt - a.createdAt);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function completeTodo(id: string): Todo {
|
|
33
|
+
const todo = store.get(id);
|
|
34
|
+
if (!todo) {
|
|
35
|
+
throw makeTodoError('NOT_FOUND', `todo with id '${id}' not found`, 'id');
|
|
36
|
+
}
|
|
37
|
+
const updated: Todo = { ...todo, completed: true };
|
|
38
|
+
store.set(id, updated);
|
|
39
|
+
return updated;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function deleteTodo(id: string): Todo {
|
|
43
|
+
const todo = store.get(id);
|
|
44
|
+
if (!todo) {
|
|
45
|
+
throw makeTodoError('NOT_FOUND', `todo with id '${id}' not found`, 'id');
|
|
46
|
+
}
|
|
47
|
+
store.delete(id);
|
|
48
|
+
return todo;
|
|
49
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Todo {
|
|
2
|
+
id: string;
|
|
3
|
+
text: string;
|
|
4
|
+
completed: boolean;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type TodoErrorCode = 'VALIDATION_ERROR' | 'NOT_FOUND';
|
|
9
|
+
|
|
10
|
+
export interface TodoError {
|
|
11
|
+
code: TodoErrorCode;
|
|
12
|
+
message: string;
|
|
13
|
+
field?: 'id' | 'text';
|
|
14
|
+
}
|