symbiot 0.10.0 → 0.10.2

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/index.mjs +21 -10
  2. package/package.json +2 -2
package/index.mjs CHANGED
@@ -122,22 +122,31 @@ function buildTasksMd(name, ctx, list) {
122
122
  function pushTasks() {
123
123
  const tasks = loadTasks().filter((t) => !t.done);
124
124
  if (!tasks.length) return { empty: true, written: [], unresolved: [] };
125
- const byName = {}; for (const r of findAllRepos(BASE)) if (!byName[r.name]) byName[r.name] = r.path;
125
+ // Resolve repo name -> path from the already-scanned map when we have it
126
+ // (avoids a fresh full scan); fall back to a scan only if needed.
127
+ const byName = {};
128
+ const src = (LAST_MAP && LAST_MAP.nodes) ? LAST_MAP.nodes.filter((n) => n.type === "repo").map((n) => ({ name: n.label, path: n.meta && n.meta.path })) : findAllRepos(BASE);
129
+ for (const r of src) if (r.path && !byName[r.name]) byName[r.name] = r.path;
126
130
  const groups = {}; for (const t of tasks) { const k = t.repo || ""; (groups[k] = groups[k] || []).push(t); }
127
131
  const written = [], unresolved = [];
128
132
  for (const name of Object.keys(groups)) {
129
133
  const list = groups[name], path = name && byName[name];
130
134
  if (!path) { unresolved.push({ name: name || "(no repo)", count: list.length }); continue; }
131
135
  try {
136
+ // Cheap, per-repo signals only — no full drift scan (that can be very slow
137
+ // on big repos and would block the request).
132
138
  const st = repoState(path);
133
139
  const commits = sh(`git -C ${JSON.stringify(path)} log --format='%s' -10 2>/dev/null`).split("\n").filter(Boolean);
134
- const drift = driftRepo(path, {}).flags.map((f) => (f.level === "warn" ? "⚠ " : "") + f.text);
135
140
  const open = openWork([{ path, name }]).slice(0, 12);
136
141
  const det = detectRepo({ path, name, recency: 0 });
137
142
  const stack = [...det.langs.slice(0, 4), ...det.tools].join(", ");
143
+ const risk = [];
144
+ if (st.stale) risk.push("stale checkout — working tree is an old snapshot, not new work");
145
+ if (st.behind) risk.push(`${st.behind} behind upstream on ${st.branch}`);
146
+ if (st.dirty && !st.stale) risk.push(`${st.dirty} uncommitted (${st.mod} mod / ${st.del} del / ${st.add} new)`);
138
147
  const dir = join(path, ".symbiot"); mkdirSync(dir, { recursive: true });
139
148
  const file = join(dir, "TASKS.md");
140
- writeFileSync(file, buildTasksMd(name, { branch: st.branch, commits, open, drift, stack }, list));
149
+ writeFileSync(file, buildTasksMd(name, { branch: st.branch, commits, open, drift: risk, stack }, list));
141
150
  written.push({ name, file, count: list.length });
142
151
  } catch (e) { unresolved.push({ name, count: list.length, error: String((e && e.message) || e) }); }
143
152
  }
@@ -1202,13 +1211,15 @@ if(clean.length)h+="<div class='muted' style='margin-top:10px'>clean: "+esc(clea
1202
1211
  if(!repos.length)h+="<div class='muted'>No repos found under your home folder.</div>";
1203
1212
  out.innerHTML=h;});}
1204
1213
  function addTaskUI(){var i=document.getElementById('newtask');var v=(i.value||'').trim();if(!v)return;api('/api/tasks/add',{text:v,repo:''}).then(function(){i.value='';loadTasks();});}
1205
- function pushTasksUI(){var o=document.getElementById('pushout');o.innerHTML="<div class='muted' style='margin-top:10px'>Writing .symbiot/TASKS.md into your repos&hellip;</div>";
1206
- api('/api/tasks/push').then(function(r){if(r.empty){o.innerHTML="<div class='muted' style='margin-top:10px'>No open tasks to send. Tick ideas in a repo review, or add tasks above.</div>";return;}
1207
- var h="<div class='drift' style='margin-top:10px'>";
1208
- if(r.written&&r.written.length){h+="<div class='dn'>Sent to "+r.written.length+" repo(s):</div><ul>";r.written.forEach(function(w){h+="<li class='info'>&middot; <b>"+esc(w.name)+"</b> &rarr; "+esc(w.file)+" <span class='ev'>("+w.count+")</span></li>";});h+="</ul>";}
1209
- if(r.unresolved&&r.unresolved.length){h+="<div class='dd' style='margin-top:6px'>Not written (repo not found): "+esc(r.unresolved.map(function(u){return u.name;}).join(", "))+"</div>";}
1210
- h+="<div class='dd' style='margin-top:8px'>Point your agent at <b>.symbiot/TASKS.md</b> in each repo.</div></div>";
1211
- o.innerHTML=h;});}
1214
+ function pushTasksUI(){var o=document.getElementById('pushout');var btn=document.getElementById('pushtasks');btn.disabled=true;
1215
+ o.innerHTML="<div class='muted' style='margin-top:10px'><span class='dot-c' style='background:var(--amber)'></span> Writing .symbiot/TASKS.md into your repos&hellip;</div>";
1216
+ api('/api/tasks/push',{}).then(function(r){btn.disabled=false;
1217
+ if(r.empty){o.innerHTML="<div class='muted' style='margin-top:10px'>No open tasks to send. Tick ideas in a repo review, or add tasks above.</div>";return;}
1218
+ var n=(r.written||[]).length;var h="<div class='drift' style='margin-top:10px'><div class='dh'><span class='dot-c'></span><span class='dn'>Done &mdash; wrote "+n+" file"+(n===1?"":"s")+"</span></div>";
1219
+ if(n){h+="<ul>";r.written.forEach(function(w){h+="<li class='info'>&#10003; <b>"+esc(w.name)+"</b> <span class='ev'>"+esc(w.file)+" ("+w.count+" task"+(w.count===1?"":"s")+")</span></li>";});h+="</ul>";}
1220
+ if(r.unresolved&&r.unresolved.length){h+="<div class='dd' style='margin-top:6px'>&#9888; not written (repo not found under your home folder): "+esc(r.unresolved.map(function(u){return u.name;}).join(", "))+"</div>";}
1221
+ h+="<div class='dd' style='margin-top:8px'>Now tell your coding agent, in each repo: <b>“Read .symbiot/TASKS.md and implement the unchecked items.”</b></div></div>";
1222
+ o.innerHTML=h;}).catch(function(e){btn.disabled=false;o.innerHTML="<div class='err' style='margin-top:10px'>Couldn&#39;t write tasks: "+esc(String((e&&e.message)||e))+"</div>";});}
1212
1223
  function selectNode(id){sel=id;render();updateBar(id);var n=nodeById(id);api('/api/node?id='+encodeURIComponent(id)).then(showDetail);
1213
1224
  if(n&&n.type==='repo'&&n.meta&&n.meta.path){loadReview(n.label,n.meta.path);}else{hideReview();}}
1214
1225
  function screenToGraph(el,ev){var rc=el.getBoundingClientRect();var mx=(ev.clientX-rc.left)/rc.width*GW;var my=(ev.clientY-rc.top)/rc.height*GH;return {x:(mx-view.x)/view.k,y:(my-view.y)/view.k};}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "symbiot",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice — Claude, OpenAI, Gemini, or a local model.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,6 +42,6 @@
42
42
  "@anthropic-ai/sdk": "^0.128.0"
43
43
  },
44
44
  "scripts": {
45
- "test": "node test/fixtures.mjs"
45
+ "test": "node test/fixtures.mjs && node test/smoke.mjs"
46
46
  }
47
47
  }