jawere 1.0.13 → 1.5.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/dist/scanner.js DELETED
@@ -1,520 +0,0 @@
1
- // src/scanner.ts — Background codebase scanner
2
- // Runs before the user prompt becomes available.
3
- // Generates .codebase/tree.yaml (annotated project tree + file summaries)
4
- // and .codebase/meta.json (scan metadata for cache invalidation).
5
- // Also generates .codebase/checksums.json for content-hash change detection.
6
- import { existsSync, statSync, readFileSync } from 'fs';
7
- import { readFile, writeFile, mkdir, readdir } from 'fs/promises';
8
- import { resolve, relative, join, basename, dirname } from 'path';
9
- import { execSync } from 'child_process';
10
- import { createHash } from 'crypto';
11
- const SCAN_INTERVAL_MS = 5 * 60 * 1000; // 5 min cache validity
12
- const CODEBASE_DIR = '.codebase';
13
- const TREE_FILE = '.codebase/tree.yaml';
14
- const META_FILE = '.codebase/meta.json';
15
- const CHECKSUMS_FILE = '.codebase/checksums.json';
16
- // Files/dirs to skip entirely
17
- const SKIP_DIRS = new Set([
18
- 'node_modules', '.git', 'dist', '.codebase',
19
- '__pycache__', '.cache', '.next', '.turbo',
20
- ]);
21
- const SKIP_FILES = new Set([
22
- 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock',
23
- ]);
24
- // Files we can summarize by reading first N lines
25
- const SUMMARIZABLE_EXTENSIONS = new Set([
26
- '.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.rb',
27
- '.c', '.cpp', '.h', '.hpp', '.java', '.kt', '.swift',
28
- '.yaml', '.yml', '.json', '.toml', '.md', '.css', '.scss',
29
- '.sql', '.sh', '.bash', '.zsh', '.Dockerfile',
30
- ]);
31
- // ── Helpers ─────────────────────────────────────────────────────────
32
- /** Compute SHA-256 hash of a file's contents (first 64KB for speed). */
33
- function hashFile(filepath) {
34
- try {
35
- const fd = readFileSync(filepath);
36
- const sample = fd.length > 65536 ? fd.subarray(0, 65536) : fd;
37
- return createHash('sha256').update(sample).digest('hex').slice(0, 16);
38
- }
39
- catch {
40
- return null;
41
- }
42
- }
43
- async function getGitHash(workDir) {
44
- try {
45
- return execSync('git rev-parse HEAD', {
46
- cwd: workDir,
47
- timeout: 5000,
48
- stdio: ['ignore', 'pipe', 'ignore'],
49
- }).toString().trim();
50
- }
51
- catch {
52
- return null;
53
- }
54
- }
55
- function isBinary(buffer) {
56
- const sample = buffer.slice(0, 4096);
57
- for (let i = 0; i < sample.length; i++) {
58
- if (sample[i] === 0)
59
- return true;
60
- }
61
- return false;
62
- }
63
- function classifyFile(filename, dir) {
64
- const name = filename.toLowerCase();
65
- if (name.includes('.test.') || name.includes('.spec.'))
66
- return 'test';
67
- if (name.endsWith('.d.ts'))
68
- return 'types';
69
- if (dir.includes('/convex/'))
70
- return 'backend';
71
- if (dir.includes('/src/')) {
72
- if (name.includes('config') || name.includes('env'))
73
- return 'config';
74
- if (name.includes('test') || name.includes('spec'))
75
- return 'test';
76
- if (name.includes('prompt'))
77
- return 'prompt';
78
- if (name.includes('tool'))
79
- return 'core';
80
- if (name.includes('agent'))
81
- return 'core';
82
- if (name.includes('index'))
83
- return 'core';
84
- if (name.includes('crypto') || name.includes('encrypt'))
85
- return 'security';
86
- return 'core';
87
- }
88
- if (dir.includes('/scripts/') || dir.includes('/bin/')) {
89
- if (name.includes('build'))
90
- return 'build';
91
- return 'entry';
92
- }
93
- if (name === 'package.json')
94
- return 'config';
95
- if (name === 'tsconfig.json')
96
- return 'config';
97
- if (name.endsWith('.json'))
98
- return 'config';
99
- if (name.endsWith('.md'))
100
- return 'docs';
101
- if (name.includes('readme'))
102
- return 'docs';
103
- if (name.includes('.gitignore'))
104
- return 'config';
105
- if (name.includes('docker'))
106
- return 'container';
107
- if (name.includes('.env'))
108
- return 'secret';
109
- if (dir.includes('/.github/'))
110
- return 'ci';
111
- return 'other';
112
- }
113
- function extractExports(lines) {
114
- const exports = [];
115
- for (const line of lines) {
116
- const trimmed = line.trim();
117
- // Named exports: export const/function/class/interface/type/async function
118
- const namedMatch = trimmed.match(/^export\s+(const|function|class|interface|type|enum|async\s+function|let|var)\s+(\w+)/);
119
- if (namedMatch) {
120
- exports.push(namedMatch[2]);
121
- continue;
122
- }
123
- // Default export
124
- if (trimmed.startsWith('export default')) {
125
- exports.push('default');
126
- continue;
127
- }
128
- }
129
- return exports;
130
- }
131
- function extractImports(lines) {
132
- const deps = [];
133
- for (const line of lines) {
134
- // Relative imports only (internal deps)
135
- const relMatch = line.match(/from\s+['"](\.[^'"]+)['"]/);
136
- if (relMatch) {
137
- deps.push(relMatch[1]);
138
- }
139
- }
140
- return deps;
141
- }
142
- function extractKeyFunctions(lines) {
143
- const funcs = [];
144
- for (const line of lines) {
145
- // Exported functions
146
- const expFunc = line.match(/^export\s+(async\s+)?function\s+(\w+)/);
147
- if (expFunc) {
148
- funcs.push(expFunc[2]);
149
- continue;
150
- }
151
- // Top-level functions
152
- const topFunc = line.match(/^(async\s+)?function\s+(\w+)/);
153
- if (topFunc) {
154
- funcs.push(topFunc[2]);
155
- continue;
156
- }
157
- // Arrow functions assigned to const
158
- const arrowFunc = line.match(/^(export\s+)?const\s+(\w+)\s*=\s*(async\s*)?\(/);
159
- if (arrowFunc) {
160
- funcs.push(arrowFunc[2]);
161
- }
162
- }
163
- return funcs;
164
- }
165
- function generateDescription(filepath, lines, exports) {
166
- const basenameLower = basename(filepath).toLowerCase();
167
- const ext = filepath.includes('.') ? filepath.split('.').pop()?.toLowerCase() : '';
168
- // ── By filename (specific) ──
169
- if (basenameLower === 'package.json')
170
- return 'NPM package manifest — scripts, dependencies, metadata.';
171
- if (basenameLower === 'package-lock.json')
172
- return 'NPM dependency lockfile.';
173
- if (basenameLower === 'tsconfig.json')
174
- return 'TypeScript compiler configuration.';
175
- if (basenameLower === 'readme.md')
176
- return 'Project documentation and README.';
177
- if (basenameLower === '.gitignore')
178
- return 'Git ignore rules.';
179
- if (basenameLower === '.env.local' || basenameLower === '.env.prod')
180
- return 'Environment variables (local/prod).';
181
- if (basenameLower === 'convex.json')
182
- return 'Convex project configuration.';
183
- if (basenameLower === '.releaserc')
184
- return 'Semantic release configuration.';
185
- if (basenameLower === 'ci.yml' || basenameLower === '.gitlab-ci.yml')
186
- return 'CI/CD pipeline configuration.';
187
- // ── By file role ──
188
- if (basenameLower.includes('index'))
189
- return 'Main entry point and REPL loop.';
190
- if (basenameLower.includes('agent'))
191
- return 'Agent loop — API calls, tool execution, spinner, retry logic.';
192
- if (basenameLower.includes('tool'))
193
- return 'Tool definitions and implementations for agent actions.';
194
- if (basenameLower.includes('config'))
195
- return 'Configuration loading and management.';
196
- if (basenameLower.includes('crypto') || basenameLower.includes('encrypt'))
197
- return 'Encryption/decryption utilities.';
198
- if (basenameLower.includes('prompt'))
199
- return 'System prompt definition for the agent.';
200
- if (basenameLower.includes('convex'))
201
- return 'Convex backend client for session persistence.';
202
- if (basenameLower.includes('scanner'))
203
- return 'Background codebase scanner — generates tree.yaml cache.';
204
- if (basenameLower.includes('schema'))
205
- return 'Database schema definition.';
206
- if (basenameLower.includes('session'))
207
- return 'Session management logic.';
208
- if (basenameLower.includes('seed'))
209
- return 'Database seeding script.';
210
- if (basenameLower.includes('build'))
211
- return 'Build/bundler script.';
212
- if (basenameLower.includes('test') || basenameLower.includes('spec'))
213
- return 'Test file.';
214
- // ── By extension ──
215
- if (ext === 'md')
216
- return 'Documentation file.';
217
- if (ext === 'json')
218
- return 'JSON configuration file.';
219
- if (ext === 'yml' || ext === 'yaml')
220
- return 'YAML configuration file.';
221
- if (ext === 'js')
222
- return 'JavaScript module.';
223
- if (ext === 'ts' || ext === 'tsx') {
224
- if (exports.length > 0) {
225
- return `Exports: ${exports.slice(0, 5).join(', ')}${exports.length > 5 ? ', …' : ''}.`;
226
- }
227
- return 'TypeScript source file.';
228
- }
229
- if (ext === 'd.ts')
230
- return 'TypeScript type declarations.';
231
- // Fallback: derive from exports
232
- if (exports.length > 0) {
233
- return `Exports: ${exports.slice(0, 5).join(', ')}${exports.length > 5 ? ', …' : ''}.`;
234
- }
235
- return 'Source file.';
236
- }
237
- async function summarizeFile(filepath) {
238
- const ext = filepath.includes('.') ? `.${filepath.split('.').pop()}` : '';
239
- if (!SUMMARIZABLE_EXTENSIONS.has(ext))
240
- return null;
241
- let content;
242
- try {
243
- content = await readFile(filepath, 'utf-8');
244
- }
245
- catch {
246
- return null;
247
- }
248
- const lines = content.split('\n');
249
- const totalLines = lines.length;
250
- const exports = extractExports(lines);
251
- const keyFunctions = extractKeyFunctions(lines);
252
- const dependsOn = extractImports(lines);
253
- const description = generateDescription(filepath, lines, exports);
254
- return {
255
- lines: totalLines,
256
- exports,
257
- key_functions: keyFunctions,
258
- depends_on: dependsOn,
259
- description,
260
- };
261
- }
262
- // ── Cache check ──────────────────────────────────────────────────────
263
- export async function cacheIsStale(workDir) {
264
- const treePath = resolve(workDir, TREE_FILE);
265
- const metaPath = resolve(workDir, META_FILE);
266
- if (!existsSync(treePath) || !existsSync(metaPath))
267
- return true;
268
- let meta;
269
- try {
270
- meta = JSON.parse(await readFile(metaPath, 'utf-8'));
271
- }
272
- catch {
273
- return true;
274
- }
275
- // Check age
276
- if (Date.now() - meta.scannedAt > SCAN_INTERVAL_MS)
277
- return true;
278
- // Check git hash (if available)
279
- const currentHash = await getGitHash(workDir);
280
- if (currentHash && currentHash !== meta.gitHash)
281
- return true;
282
- return false;
283
- }
284
- /**
285
- * Load stored checksums for quick file-change detection.
286
- * The agent can use this to skip re-reading files that haven't changed.
287
- */
288
- export async function loadChecksums(workDir) {
289
- const path = resolve(workDir, CHECKSUMS_FILE);
290
- try {
291
- if (!existsSync(path))
292
- return null;
293
- return JSON.parse(await readFile(path, 'utf-8'));
294
- }
295
- catch {
296
- return null;
297
- }
298
- }
299
- // ── Scanner ──────────────────────────────────────────────────────────
300
- async function getAllFiles(dir, baseDir) {
301
- const files = [];
302
- let entries;
303
- try {
304
- entries = await readdir(dir, { withFileTypes: true });
305
- }
306
- catch {
307
- return files;
308
- }
309
- for (const entry of entries) {
310
- if (entry.name.startsWith('.'))
311
- continue; // skip hidden
312
- if (SKIP_DIRS.has(entry.name))
313
- continue;
314
- if (SKIP_FILES.has(entry.name))
315
- continue;
316
- const fullPath = join(dir, entry.name);
317
- const relPath = relative(baseDir, fullPath);
318
- if (entry.isDirectory()) {
319
- const children = await getAllFiles(fullPath, baseDir);
320
- files.push(...children);
321
- }
322
- else if (entry.isFile()) {
323
- files.push(relPath);
324
- }
325
- }
326
- return files;
327
- }
328
- function buildTree(files) {
329
- const tree = {};
330
- for (const file of files) {
331
- const dir = dirname(file);
332
- const name = basename(file);
333
- const key = dir === '.' ? 'root' : dir + '/';
334
- if (!tree[key])
335
- tree[key] = [];
336
- let size = 0;
337
- try {
338
- size = statSync(file).size;
339
- }
340
- catch { /* ignore */ }
341
- const type = classifyFile(name, dir);
342
- tree[key].push({
343
- name,
344
- type,
345
- desc: '',
346
- size,
347
- });
348
- }
349
- // Sort each directory: dirs first, then files alphabetically
350
- for (const key of Object.keys(tree)) {
351
- tree[key].sort((a, b) => a.name.localeCompare(b.name));
352
- }
353
- return tree;
354
- }
355
- function getProjectInfo(workDir) {
356
- let name = basename(workDir);
357
- let version = '0.0.0';
358
- try {
359
- const pkg = JSON.parse(readFileSync(resolve(workDir, 'package.json'), 'utf-8'));
360
- if (pkg.name)
361
- name = pkg.name;
362
- if (pkg.version)
363
- version = pkg.version;
364
- }
365
- catch { /* use defaults */ }
366
- return { name, version };
367
- }
368
- async function scanCodebase(workDir) {
369
- const codebaseDir = resolve(workDir, CODEBASE_DIR);
370
- await mkdir(codebaseDir, { recursive: true });
371
- // 1. Get all files
372
- const allFiles = await getAllFiles(workDir, workDir);
373
- const files = allFiles.sort();
374
- // 2. Build tree
375
- const tree = buildTree(files);
376
- // 3. Summarize source files
377
- const summaries = {};
378
- for (const file of files) {
379
- const summary = await summarizeFile(resolve(workDir, file));
380
- if (summary) {
381
- summaries[file] = summary;
382
- }
383
- }
384
- // 4. Get project info
385
- const { name, version } = getProjectInfo(workDir);
386
- // 5. Generate checksums (content-hash tracking for change detection)
387
- const gitHash = await getGitHash(workDir);
388
- const checksums = {
389
- scannedAt: Date.now(),
390
- gitHash,
391
- files: {},
392
- };
393
- for (const file of files) {
394
- const fullPath = resolve(workDir, file);
395
- const hash = hashFile(fullPath);
396
- if (hash) {
397
- try {
398
- const st = statSync(fullPath);
399
- checksums.files[file] = { hash, size: st.size, scannedAt: Date.now() };
400
- }
401
- catch { /* skip unreadable files */ }
402
- }
403
- }
404
- await writeFile(resolve(workDir, CHECKSUMS_FILE), JSON.stringify(checksums, null, 2) + '\n', 'utf-8');
405
- // 6. Generate tree.yaml
406
- const treeYaml = generateTreeYaml(name, version, tree, summaries);
407
- await writeFile(resolve(workDir, TREE_FILE), treeYaml, 'utf-8');
408
- // 7. Generate meta.json
409
- const meta = {
410
- scannedAt: Date.now(),
411
- fileCount: files.length,
412
- gitHash,
413
- workDir,
414
- scanner: 'background-v1',
415
- };
416
- await writeFile(resolve(workDir, META_FILE), JSON.stringify(meta, null, 2) + '\n', 'utf-8');
417
- return { fileCount: files.length };
418
- }
419
- function generateTreeYaml(name, version, tree, summaries) {
420
- const lines = [];
421
- lines.push(`# ${name} — Full Codebase Tree (auto-generated)`);
422
- lines.push(`# Generated: ${new Date().toISOString().split('T')[0]}`);
423
- lines.push('# Updated by background scanner agent before each session.');
424
- lines.push('# This file is consumed by the main agent so it doesn\'t need to re-scan.');
425
- lines.push('');
426
- lines.push('project:');
427
- lines.push(` name: ${name}`);
428
- lines.push(` version: ${version}`);
429
- lines.push(' language: typescript');
430
- lines.push(' type: terminal-ai-coding-agent');
431
- lines.push('');
432
- lines.push('tree:');
433
- // Sort keys: root first, then alphabetically
434
- const keys = Object.keys(tree).sort((a, b) => {
435
- if (a === 'root')
436
- return -1;
437
- if (b === 'root')
438
- return 1;
439
- return a.localeCompare(b);
440
- });
441
- for (const key of keys) {
442
- const entries = tree[key];
443
- const indent = ' ' + (key === 'root' ? '' : ' ');
444
- const displayKey = key === 'root' ? 'root:' : `${key}:`;
445
- lines.push(` ${displayKey}`);
446
- for (const entry of entries) {
447
- const sizeStr = entry.size ? ` (${formatSize(entry.size)})` : '';
448
- lines.push(` - ${entry.name}:`);
449
- lines.push(` type: ${entry.type}`);
450
- if (entry.desc) {
451
- lines.push(` desc: "${entry.desc}"`);
452
- }
453
- else {
454
- // Auto-describe from summary if available
455
- const filePath = key === 'root' ? entry.name : `${key}${entry.name}`;
456
- const summary = summaries[filePath];
457
- if (summary) {
458
- lines.push(` desc: "${escapeYaml(summary.description)}"`);
459
- }
460
- }
461
- if (entry.size) {
462
- lines.push(` size: ${entry.size}`);
463
- }
464
- }
465
- }
466
- // Summaries section
467
- lines.push('');
468
- lines.push('# ── File summaries (for quick agent context) ────────────────────────');
469
- lines.push('');
470
- lines.push('summaries:');
471
- const summaryKeys = Object.keys(summaries).sort();
472
- for (const file of summaryKeys) {
473
- const s = summaries[file];
474
- lines.push(` "${file}":`);
475
- lines.push(` lines: ${s.lines}`);
476
- lines.push(` exports: [${s.exports.map((e) => `"${e}"`).join(', ')}]`);
477
- if (s.key_functions.length > 0) {
478
- lines.push(` key_functions: [${s.key_functions.map((f) => `"${f}"`).join(', ')}]`);
479
- }
480
- if (s.depends_on.length > 0) {
481
- lines.push(` depends_on: [${s.depends_on.map((d) => `"${d}"`).join(', ')}]`);
482
- }
483
- lines.push(` description: |`);
484
- lines.push(` ${s.description}`);
485
- }
486
- return lines.join('\n') + '\n';
487
- }
488
- function escapeYaml(str) {
489
- return str.replace(/"/g, '\\"').replace(/\n/g, '\\n');
490
- }
491
- function formatSize(bytes) {
492
- if (bytes < 1024)
493
- return `${bytes}B`;
494
- if (bytes < 1024 * 1024)
495
- return `${(bytes / 1024).toFixed(1)}KB`;
496
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
497
- }
498
- // ── Public API ───────────────────────────────────────────────────────
499
- /**
500
- * Run the background scanner.
501
- * Checks cache validity first; if stale, re-scans the codebase.
502
- *
503
- * @param workDir Working directory to scan
504
- * @param force Force rescan even if cache is fresh
505
- * @returns Scan result with file count
506
- */
507
- export async function runScanner(workDir, force = false) {
508
- if (!force && !(await cacheIsStale(workDir))) {
509
- // Cache is fresh — read meta for fileCount
510
- try {
511
- const meta = JSON.parse(await readFile(resolve(workDir, META_FILE), 'utf-8'));
512
- return { fileCount: meta.fileCount, cached: true };
513
- }
514
- catch {
515
- // Meta corrupt, fall through to scan
516
- }
517
- }
518
- const { fileCount } = await scanCodebase(workDir);
519
- return { fileCount, cached: false };
520
- }
package/dist/spinner.d.ts DELETED
@@ -1,23 +0,0 @@
1
- /**
2
- * Terminal spinner — a lightweight animated indicator shown while the agent
3
- * is waiting for an API response or processing tool results.
4
- *
5
- * Usage:
6
- * const spin = createSpinner();
7
- * spin.start('Thinking…');
8
- * // ... do work ...
9
- * spin.update('Running tool…');
10
- * // ... more work ...
11
- * spin.stop();
12
- */
13
- export interface Spinner {
14
- /** Start (or restart) the spinner with a status message */
15
- start(message: string): void;
16
- /** Update the status message without stopping the animation */
17
- update(message: string): void;
18
- /** Stop and clear the spinner. If message is provided, show it as the final status. */
19
- stop(finalMessage?: string): void;
20
- /** Whether the spinner is currently running */
21
- readonly running: boolean;
22
- }
23
- export declare function createSpinner(): Spinner;
package/dist/spinner.js DELETED
@@ -1,83 +0,0 @@
1
- /**
2
- * Terminal spinner — a lightweight animated indicator shown while the agent
3
- * is waiting for an API response or processing tool results.
4
- *
5
- * Usage:
6
- * const spin = createSpinner();
7
- * spin.start('Thinking…');
8
- * // ... do work ...
9
- * spin.update('Running tool…');
10
- * // ... more work ...
11
- * spin.stop();
12
- */
13
- // Spinner frames — classic braille dots
14
- const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
15
- // Gruvbox colors
16
- const GRAY = '\x1b[38;2;146;131;116m';
17
- const GREEN = '\x1b[38;2;184;187;3m';
18
- const RESET = '\x1b[0m';
19
- const FRAME_INTERVAL = 80; // ms per frame
20
- export function createSpinner() {
21
- let interval = null;
22
- let frameIdx = 0;
23
- let currentMessage = '';
24
- // Remember the cursor position where the spinner line was drawn.
25
- // We use this to overwrite the line cleanly.
26
- let lastLineLen = 0;
27
- function draw(message) {
28
- const frame = FRAMES[frameIdx % FRAMES.length];
29
- const line = ` ${GREEN}${frame}${RESET} ${GRAY}${message}${RESET}`;
30
- // Clear the previous line first with \r and spaces, then write new line
31
- const clear = ' '.repeat(Math.max(0, lastLineLen));
32
- process.stderr.write(`\r${clear}\r${line}`);
33
- lastLineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
34
- }
35
- function clear() {
36
- if (lastLineLen > 0) {
37
- process.stderr.write(`\r${' '.repeat(lastLineLen)}\r`);
38
- lastLineLen = 0;
39
- }
40
- }
41
- const self = {
42
- get running() {
43
- return interval !== null;
44
- },
45
- start(message) {
46
- if (interval !== null) {
47
- // Already running — just change the message
48
- currentMessage = message;
49
- draw(message);
50
- return;
51
- }
52
- currentMessage = message;
53
- frameIdx = 0;
54
- draw(message);
55
- interval = setInterval(() => {
56
- frameIdx++;
57
- draw(currentMessage);
58
- }, FRAME_INTERVAL);
59
- },
60
- update(message) {
61
- currentMessage = message;
62
- // Next frame tick will pick up the new message
63
- },
64
- stop(finalMessage) {
65
- if (interval !== null) {
66
- clearInterval(interval);
67
- interval = null;
68
- }
69
- if (finalMessage) {
70
- // Show final status on the spinner line
71
- const line = ` ${GREEN}✓${RESET} ${GRAY}${finalMessage}${RESET}`;
72
- const clear = ' '.repeat(Math.max(0, lastLineLen));
73
- process.stderr.write(`\r${clear}\r${line}\n`);
74
- }
75
- else {
76
- clear();
77
- }
78
- lastLineLen = 0;
79
- currentMessage = '';
80
- },
81
- };
82
- return self;
83
- }
@@ -1 +0,0 @@
1
- export declare const SYSTEM_PROMPT = "You are a coding agent running in a terminal. You help users by reading files, running shell commands, editing code, and writing files. You do NOT introduce yourself, describe your capabilities, or make small talk \u2014 just work.\n\n\u2500\u2500 Critical: Be Extremely Concise \u2500\u2500\n\nThe user pays per token. Wasting tokens is unacceptable.\n \u2022 Your text responses should be 2-6 lines MAX unless the user explicitly asks for detail.\n \u2022 Never greet, never say \"Sure!\", \"Here you go!\", \"Let me help you with that!\" \u2014 just do the work.\n \u2022 Don't explain what you're about to do. Just do it and report the result.\n \u2022 Don't summarize unless asked. The user can see what happened.\n \u2022 Skip all pleasantries, acknowledgements, and filler.\n\n\u2500\u2500 Working Memory (Critical for Efficiency) \u2500\u2500\n\nYou have a working memory file at .codebase/state.md that persists across turns.\nUse it to avoid redundant work \u2014 this is your MOST IMPORTANT efficiency tool.\n\n AT THE START of every turn (before any other action):\n 1. Read .codebase/state.md to recall what you already know\n 2. Read .codebase/tree.yaml if you haven't already (check state.md)\n\n DURING work:\n \u2022 After reading a file, update state.md with the file path + summary\n \u2022 After editing a file, update state.md with what you changed\n \u2022 Track your current task and progress in state.md\n\n RULE: Never re-read a file you've already read this session UNLESS:\n \u2022 You modified it since reading\n \u2022 The tree.yaml hash changed (indicating external modification)\n \u2022 You need a specific section you didn't read before (use offset)\n\n\u2500\u2500 Codebase Context \u2500\u2500\n\nA pre-scan of the project is available. Before doing anything else:\n 1. Read .codebase/state.md to check what's already known\n 2. Read .codebase/tree.yaml to understand the project structure\n 3. Read .codebase/meta.json for scan metadata\n 4. Use this knowledge to navigate efficiently \u2014 don't re-scan what's already documented\n\n\u2500\u2500 Your Capabilities \u2500\u2500\n\nYou have access to a set of tools that let you interact with the filesystem:\n\n bash Execute shell commands in the working directory. Run scripts, install\n dependencies, list files, search with grep/find, git operations,\n compilers, linters, and tests.\n read Read file contents. Use offset/limit for large files. Continue with\n offset until you have the full file.\n edit Precise file edits via exact-text replacement. oldText must match\n exactly once. Merge nearby changes into one edit. Keep oldText as\n small as possible while still unique.\n write Create new files or completely overwrite existing ones. Creates\n parent directories automatically. For new files or full rewrites only.\n ls List directory contents with sizes (dirs first, then files alphabetical).\n find Find files by fuzzy name or glob (e.g. \"agentloop\" or \"*.ts\").\n Skips hidden dirs and node_modules, .git, dist, etc.\n grep Search file contents with regex. Returns file paths with line numbers.\n Skips binary files and files over 500KB.\n\n\u2500\u2500 Parallel Tool Execution \u2500\u2500\n\nYou can call multiple tools simultaneously in a single response when they are\nindependent of each other. This is faster and saves tokens \u2014 use it whenever possible.\n\n GOOD \u2014 read 3 files in parallel:\n \u2022 Call read(fileA), read(fileB), read(fileC) all at once\n\n GOOD \u2014 run independent commands in parallel:\n \u2022 Call ls(src/), find(\"*.test.ts\"), grep(\"TODO\", \"src/\") all at once\n\n GOOD \u2014 read a file AND list a directory at the same time:\n \u2022 Call read(config.ts) + ls(src/) together\n\n BAD \u2014 these depend on each other, so must be sequential:\n \u2022 grep then read (grep finds a file, then you read it)\n \u2022 bash then read (you run a command, then read its output file)\n \u2022 write then bash (you create a file, then run it)\n\n Rule of thumb: if tool B's input depends on tool A's output, they must be\n sequential. Otherwise, batch them together in one response.\n\n\u2500\u2500 Response Style \u2500\u2500\n\nYou are running in a terminal. Use clean plain text \u2014 never markdown.\n \u2022 No **bold**, no ## headings, no [links](url), no `code spans`\n \u2022 Use \u2500\u2500 section separators for structure\n \u2022 Use indentation for lists and code blocks\n \u2022 Show file paths like this: path/to/file.ts\n \u2022 Wrap code snippets with 2-space indent, not triple backticks\n \u2022 Keep responses tight \u2014 skip filler and pleasantries\n\n\u2500\u2500 Rules \u2500\u2500\n\n 1. Think before acting. Reason internally about the best approach.\n 2. Use bash first for exploration (ls, find, grep) before editing.\n 3. Edit precisely \u2014 minimal oldText, unique matches only.\n 4. Merge nearby edits into a single call.\n 5. Write for new files or complete rewrites only.\n 6. Stay safe. Never run rm -rf, force-push to main, etc. without confirmation.\n 7. Work in the current directory. Use relative paths.\n 8. When done, give a short summary (1-3 lines) of what you changed.\n\n\u2500\u2500 Final Output Format \u2500\u2500\n\nYour final message (when all tool calls are complete) MUST be a brief summary:\n\n \u2500\u2500 Changes Made \u2500\u2500\n \u2022 file/path.ts \u2014 what you changed (one line)\n \u2500\u2500\n\nOnly include files you actually modified. Do NOT include:\n - Markdown formatting, long explanations, or step-by-step walkthroughs\n - Tool-by-tool recaps of what commands you ran\n - Pleasantries, filler, or \"let me know if you need anything else\"\n\nKeep it tight. The user sees tool calls live \u2014 they just want the summary.";