@c0mpute/code 0.4.1 → 0.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.
Files changed (2) hide show
  1. package/cli.mjs +36 -6
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -26,6 +26,9 @@ const MAX_STEPS = Number(process.env.C0MPUTE_MAX_STEPS || 40);
26
26
  const CWD = process.cwd();
27
27
  const ROOT = CWD; // the project boundary: the agent may not touch files outside this without approval
28
28
  const AUTO = process.env.C0MPUTE_YOLO === '1';
29
+ // ── workspace: a persistent per-project .c0mpute/ dir the agent keeps across sessions ──
30
+ const WS_DIR = join(ROOT, '.c0mpute');
31
+ const WS_JOURNAL = join(WS_DIR, 'journal.md');
29
32
 
30
33
  // ── ansi ──
31
34
  const e = (n) => (s) => `\x1b[${n}m${s}\x1b[0m`;
@@ -89,6 +92,29 @@ function loadProjectNotes() {
89
92
  return null;
90
93
  }
91
94
 
95
+ // ── workspace memory: continuity across sessions ──
96
+ // A running journal of completed tasks in .c0mpute/journal.md. Loaded into context at
97
+ // startup so the agent remembers what it already did in this project; appended after
98
+ // every verified coding task. Per-project, plain markdown, no deps. The user can commit
99
+ // it to share project history with the team, or .gitignore it to keep it local.
100
+ function loadWorkspace() {
101
+ try {
102
+ const lines = readFileSync(WS_JOURNAL, 'utf8').split('\n').filter(l => l.trimStart().startsWith('- '));
103
+ return lines.length ? lines.slice(-15).join('\n') : null;
104
+ } catch { return null; }
105
+ }
106
+ function recordWork(task, summary) {
107
+ try {
108
+ const day = new Date().toISOString().slice(0, 10);
109
+ const note = String(summary || task).replace(/\s+/g, ' ').trim().slice(0, 160);
110
+ if (!note) return;
111
+ mkdirSync(WS_DIR, { recursive: true });
112
+ let prior = ''; try { prior = readFileSync(WS_JOURNAL, 'utf8'); } catch {}
113
+ if (!prior) prior = '# c0mpute workspace — work journal\n# Persistent across sessions; the agent reads recent entries for continuity.\n\n';
114
+ writeFileSync(WS_JOURNAL, prior + `- ${day} ${note}\n`);
115
+ } catch {}
116
+ }
117
+
92
118
  // ── streaming over the network ──
93
119
  const PULSE = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▂']; // compute pulse
94
120
  async function think(messages) {
@@ -292,7 +318,7 @@ const LABELS = { read: 'Read', list: 'List', search: 'Search', edit: 'Update', w
292
318
  async function runTask(task, history) {
293
319
  console.log('');
294
320
  history.push({ role: 'user', content: task });
295
- let ran = false, nudges = 0, lastRunFailed = false, doneNudges = 0;
321
+ let ran = false, nudges = 0, lastRunFailed = false, doneNudges = 0, doneSummary = '';
296
322
  busy = true; interrupted = false;
297
323
  // is this a coding task (enforce actions) or chat/greeting (a prose reply is fine)?
298
324
  const isCoding = /\b(fix|bug|error|fail|implement|add|refactor|test|debug|rename|update|create|build|install|broken|crash|exception|traceback|function|class|import|run)\b/i.test(task) || /[\w./-]+\.\w{1,5}\b/.test(task);
@@ -315,7 +341,7 @@ async function runTask(task, history) {
315
341
  if (verb === 'done') {
316
342
  // don't accept "done" while the last command was still failing — that's a false finish
317
343
  if (lastRunFailed && doneNudges < 2) { doneNudges++; history.push({ role: 'user', content: 'The last command reported failures/errors, so the task is NOT verified. Keep fixing and re-run the test until it passes. If you are genuinely stuck, say plainly what is still broken instead of using `done`.' }); continue; }
318
- ran = true; break;
344
+ ran = true; doneSummary = act.body.split('\n').map(s => s.trim()).filter(Boolean)[0] || ''; break;
319
345
  }
320
346
  const path0 = arg.split(/\s+/)[0] || '';
321
347
  const shown = verb === 'search' ? arg : (verb === 'run' ? (arg || act.body.split('\n')[0]) : path0);
@@ -347,7 +373,7 @@ async function runTask(task, history) {
347
373
  }
348
374
  } finally { busy = false; }
349
375
  if (interrupted) { interrupted = false; console.log(c.dim(' ⊘ stopped.') + '\n'); }
350
- else if (ran) console.log(MARK + ' ' + c.dim('done') + '\n');
376
+ else if (ran) { if (isCoding) recordWork(task, doneSummary); console.log(MARK + ' ' + c.dim('done') + '\n'); }
351
377
  else console.log('');
352
378
  }
353
379
 
@@ -392,14 +418,17 @@ async function main() {
392
418
  if (a !== 'y' && a !== 'yes') { console.log(c.dim(' exiting — cd into your project and run again.')); RL?.close(); return; }
393
419
  }
394
420
  const notes = loadProjectNotes();
421
+ const ws = loadWorkspace();
395
422
  console.log('\n' + box([
396
423
  `${MARK} ${c.b('c0mpute code')}${VERSION ? c.gry(' v' + VERSION) : ''}`,
397
424
  c.dim('your coding agent, running on the c0mpute network'),
398
425
  '',
399
426
  `${c.dim('model')} ${MODEL} ${c.dim('cwd')} ${CWD.replace(homedir(), '~')} ${c.dim(isGit ? 'git · diffs on' : 'no git')}`,
400
- c.dim(`edits ask first · reads run automatically${notes ? ` · memory ${notes.name}` : ''} · /help`),
427
+ c.dim(`edits ask first · reads run automatically${notes ? ` · memory ${notes.name}` : ''}${ws ? ' · workspace' : ''} · /help`),
401
428
  ]));
402
- const sysmsg = SYSTEM + (notes ? `\n\nPROJECT NOTES (from ${notes.name}, treat as authoritative project context):\n${notes.text}` : '');
429
+ const sysmsg = SYSTEM
430
+ + (notes ? `\n\nPROJECT NOTES (from ${notes.name}, treat as authoritative project context):\n${notes.text}` : '')
431
+ + (ws ? `\n\nRECENT WORK (your journal from past sessions in this project, oldest first — for continuity; don't redo finished work):\n${ws}` : '');
403
432
  const history = [{ role: 'system', content: sysmsg }];
404
433
  const fin = () => { if (redactCount) console.log(c.dim(` ${redactCount} secret${redactCount > 1 ? 's' : ''} redacted before leaving your machine`)); };
405
434
  // ctrl-c: interrupt a running task; at an idle prompt, exit cleanly
@@ -414,7 +443,8 @@ async function main() {
414
443
  if (task === '/exit' || task === '/quit') { fin(); break; }
415
444
  if (task === '/login') { await setupKey(); continue; }
416
445
  if (task === '/init') { await initProject(); continue; }
417
- if (task === '/help') { console.log(c.dim(' describe a coding task; I locate, read, edit, and run tests to verify.\n reads auto-run · edits/commands ask first · files outside this dir always ask.\n /init write project memory · /login set key · /exit quit · ctrl-c interrupt')); continue; }
446
+ if (task === '/workspace') { const j = loadWorkspace(); console.log(j ? '\n' + MARK + ' ' + c.dim(`workspace journal (${WS_JOURNAL.replace(homedir(), '~')}):`) + '\n' + j.split('\n').map(l => ' ' + c.dim(l)).join('\n') : c.dim(' no workspace yet it starts after your first completed task.')); continue; }
447
+ if (task === '/help') { console.log(c.dim(' describe a coding task; I locate, read, edit, and run tests to verify.\n reads auto-run · edits/commands ask first · files outside this dir always ask.\n /init write project memory · /workspace show project journal · /login set key · /exit quit · ctrl-c interrupt')); continue; }
418
448
  try { await runTask(task, history); } catch (x) { console.log(c.red(' ! ' + x.message)); }
419
449
  }
420
450
  RL?.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c0mpute/code",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Decentralized coding agent — thinking runs on the c0mpute network, file edits and commands run locally under your approval. Claude Code-style UX, no single provider.",
5
5
  "license": "MIT",
6
6
  "type": "module",