symbiot 0.9.3 → 0.10.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/README.md +13 -0
- package/index.mjs +67 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,19 @@ where no Chromium-family browser is found it opens your default browser instead.
|
|
|
52
52
|
npm install -g symbiot # or run without installing: npx symbiot week
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
## Hand tasks to your coding agent: `symbiot push`
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
symbiot push # or the "Send to repos" button in the app's Tasks tab
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Writes a **`.symbiot/TASKS.md`** into each repo your tasks reference — a checklist
|
|
62
|
+
**plus the context an agent needs to get oriented**: the stack, recent commits,
|
|
63
|
+
open `TODO/FIXME` markers (with `file:line`), and current drift. Then point Claude
|
|
64
|
+
Code / Cursor / any agent at it: *"Read `.symbiot/TASKS.md` and implement the
|
|
65
|
+
unchecked items."* Tasks come from ticking a repo review's ideas, or adding your
|
|
66
|
+
own in the Tasks tab.
|
|
67
|
+
|
|
55
68
|
## What's out of sync? `symbiot drift`
|
|
56
69
|
|
|
57
70
|
```bash
|
package/index.mjs
CHANGED
|
@@ -103,6 +103,53 @@ function addTask(text, repo) {
|
|
|
103
103
|
}
|
|
104
104
|
function toggleTask(id) { const t = loadTasks(); const it = t.find((x) => x.id === id); if (it) { it.done = !it.done; saveTasks(t); } return it || { error: "not found" }; }
|
|
105
105
|
function removeTask(id) { saveTasks(loadTasks().filter((x) => x.id !== id)); return { ok: true }; }
|
|
106
|
+
// Push tasks into the repos as an agent-readable brief (.symbiot/TASKS.md):
|
|
107
|
+
// a checklist plus the deterministic context an agent needs to get oriented.
|
|
108
|
+
function buildTasksMd(name, ctx, list) {
|
|
109
|
+
const L = [];
|
|
110
|
+
L.push(`# Symbiot tasks — ${name}`);
|
|
111
|
+
L.push(`_generated ${new Date().toISOString().slice(0, 10)}${ctx.branch ? ` · branch ${ctx.branch}` : ""} · by symbiot ${VERSION}_`, "");
|
|
112
|
+
L.push("## Context for an AI agent", `Tasks for the \`${name}\` repo. Get oriented from the signals below before actioning them.`);
|
|
113
|
+
if (ctx.stack) L.push(`- **Stack:** ${ctx.stack}`);
|
|
114
|
+
if (ctx.commits && ctx.commits.length) { L.push("- **Recent commits:**"); for (const c of ctx.commits) L.push(` - ${c}`); }
|
|
115
|
+
if (ctx.open && ctx.open.length) { L.push("- **Open markers (TODO/FIXME + uncommitted):**"); for (const o of ctx.open) L.push(` - ${o}`); }
|
|
116
|
+
if (ctx.drift && ctx.drift.length) { L.push("- **Current drift / risk:**"); for (const d of ctx.drift) L.push(` - ${d}`); }
|
|
117
|
+
L.push("", "## Tasks");
|
|
118
|
+
for (const t of list) L.push(`- [ ] ${t.text}`);
|
|
119
|
+
L.push("", "---", 'To action these, tell your coding agent: "Read `.symbiot/TASKS.md` and implement the unchecked items in this repo, using the context above. Confirm with me before anything destructive."');
|
|
120
|
+
return L.join("\n") + "\n";
|
|
121
|
+
}
|
|
122
|
+
function pushTasks() {
|
|
123
|
+
const tasks = loadTasks().filter((t) => !t.done);
|
|
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;
|
|
126
|
+
const groups = {}; for (const t of tasks) { const k = t.repo || ""; (groups[k] = groups[k] || []).push(t); }
|
|
127
|
+
const written = [], unresolved = [];
|
|
128
|
+
for (const name of Object.keys(groups)) {
|
|
129
|
+
const list = groups[name], path = name && byName[name];
|
|
130
|
+
if (!path) { unresolved.push({ name: name || "(no repo)", count: list.length }); continue; }
|
|
131
|
+
try {
|
|
132
|
+
const st = repoState(path);
|
|
133
|
+
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
|
+
const open = openWork([{ path, name }]).slice(0, 12);
|
|
136
|
+
const det = detectRepo({ path, name, recency: 0 });
|
|
137
|
+
const stack = [...det.langs.slice(0, 4), ...det.tools].join(", ");
|
|
138
|
+
const dir = join(path, ".symbiot"); mkdirSync(dir, { recursive: true });
|
|
139
|
+
const file = join(dir, "TASKS.md");
|
|
140
|
+
writeFileSync(file, buildTasksMd(name, { branch: st.branch, commits, open, drift, stack }, list));
|
|
141
|
+
written.push({ name, file, count: list.length });
|
|
142
|
+
} catch (e) { unresolved.push({ name, count: list.length, error: String((e && e.message) || e) }); }
|
|
143
|
+
}
|
|
144
|
+
return { empty: false, written, unresolved };
|
|
145
|
+
}
|
|
146
|
+
function cmdPush() {
|
|
147
|
+
const r = pushTasks();
|
|
148
|
+
if (r.empty) { console.log(c.y("No open tasks to push.") + c.d(" Add some in `symbiot app` — a repo review's ideas, or the Tasks tab.")); return; }
|
|
149
|
+
if (r.written.length) { console.log("\n" + c.g("●") + " " + c.b("Pushed tasks into repos:")); for (const w of r.written) console.log(` ${c.g("✓")} ${w.name} ${c.d(w.file + " (" + w.count + " task" + (w.count > 1 ? "s" : "") + ")")}`); }
|
|
150
|
+
if (r.unresolved.length) { console.log("\n" + c.y(`Not written (repo not found under ${BASE}):`)); for (const u of r.unresolved) console.log(` · ${u.name} (${u.count})`); }
|
|
151
|
+
console.log("\n" + c.d("Open each repo in your IDE/agent and point it at .symbiot/TASKS.md."));
|
|
152
|
+
}
|
|
106
153
|
function envKey(provider) {
|
|
107
154
|
for (const e of (PROVIDERS[provider].env || [])) if (process.env[e]) return process.env[e];
|
|
108
155
|
return null;
|
|
@@ -1021,7 +1068,8 @@ footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
|
|
|
1021
1068
|
<div class="rfoot" id="outfoot" style="display:none"></div>
|
|
1022
1069
|
</section>
|
|
1023
1070
|
<section id="panel-tasks" class="hidden">
|
|
1024
|
-
<div class="row"><input id="newtask" placeholder="Add a task..." style="flex:1"><button class="act" id="addtask">Add</button></div>
|
|
1071
|
+
<div class="row"><input id="newtask" placeholder="Add a task..." style="flex:1"><button class="act" id="addtask">Add</button><button class="ghost" id="pushtasks" title="Write .symbiot/TASKS.md into each repo for your coding agent">Send to repos</button></div>
|
|
1072
|
+
<div id="pushout"></div>
|
|
1025
1073
|
<div id="tasklist"></div>
|
|
1026
1074
|
</section>
|
|
1027
1075
|
<section id="panel-drift" class="hidden">
|
|
@@ -1114,18 +1162,19 @@ el.innerHTML="<h3>"+esc(d.label)+"</h3><div class='chips'>"+chips+"</div><button
|
|
|
1114
1162
|
document.getElementById('suggest').addEventListener('click',function(){var o=document.getElementById('sugout');o.innerHTML="<div class='out2'>Thinking...</div>";api('/api/suggest',{path:d.path}).then(function(r){if(r.error==='not-connected'){o.innerHTML="<div class='out2'>Connect a model in Settings to get suggestions - Ollama is free and runs locally.</div>";return;}o.innerHTML="<div class='out2'>"+esc(r.text||'(no output)')+"</div>"+(r.footer?"<div class='rfoot'>"+esc(r.footer)+"</div>":"");});});return;}
|
|
1115
1163
|
if(d.type==='lang'||d.type==='tool'){var lis=(d.repos||[]).map(function(r){return "<li>"+esc(r)+"</li>";}).join("");el.innerHTML="<h3>"+esc(d.label)+"</h3><div class='k'>Used in "+((d.repos||[]).length)+" repos</div><ul>"+lis+"</ul>";return;}
|
|
1116
1164
|
if(d.type==='person'){var st=d.stats||{};el.innerHTML="<h3>"+esc(d.label)+"</h3><div class='k'>"+st.repos+" repos · "+st.commits+" commits · "+st.languages+" languages · "+st.tools+" tools</div>";return;}}
|
|
1117
|
-
var reviewCache={};
|
|
1165
|
+
var reviewCache={};var IDEAS=[];var IREPO="";
|
|
1118
1166
|
function hideReview(){var el=document.getElementById('review');el.classList.add('hidden');el.innerHTML='';}
|
|
1119
|
-
function reviewHtml(name,body,ideas,tasks,footer){
|
|
1167
|
+
function reviewHtml(name,body,ideas,tasks,footer){IDEAS=ideas||[];IREPO=name;
|
|
1120
1168
|
var h="<h4>AI review · <span class='rname'>"+esc(name)+"</span></h4><div class='body'>"+esc(body)+"</div>";
|
|
1121
1169
|
if(ideas&&ideas.length){h+="<div class='ideas'><h4>Upgrade ideas · tick to add to Tasks</h4>";
|
|
1122
|
-
ideas.forEach(function(idea){var t=(tasks||[]).filter(function(x){return x.text===idea&&x.repo===name;})[0];var tid=t?t.id:"";
|
|
1123
|
-
h+="<label class='idea'><input type='checkbox' class='ideachk' data-
|
|
1170
|
+
ideas.forEach(function(idea,i){var t=(tasks||[]).filter(function(x){return x.text===idea&&x.repo===name;})[0];var tid=t?t.id:"";
|
|
1171
|
+
h+="<label class='idea'><input type='checkbox' class='ideachk' data-idx='"+i+"' data-tid='"+esc(tid)+"'"+(t?" checked":"")+"><span>"+esc(idea)+"</span></label>";});
|
|
1124
1172
|
h+="</div>";}
|
|
1125
1173
|
if(footer)h+="<div class='rfoot'>"+esc(footer)+"</div>";
|
|
1126
1174
|
return h;}
|
|
1127
1175
|
function wireIdeas(){document.querySelectorAll('.ideachk').forEach(function(cb){cb.addEventListener('change',function(){
|
|
1128
|
-
|
|
1176
|
+
var idea=IDEAS[+cb.getAttribute('data-idx')];if(idea==null)return;
|
|
1177
|
+
if(cb.checked){api('/api/tasks/add',{text:idea,repo:IREPO}).then(function(it){if(it&&it.id)cb.setAttribute('data-tid',it.id);});}
|
|
1129
1178
|
else{var id=cb.getAttribute('data-tid');if(id){api('/api/tasks/remove',{id:id}).then(function(){cb.setAttribute('data-tid','');});}}});});}
|
|
1130
1179
|
function loadReview(name,path){var el=document.getElementById('review');el.classList.remove('hidden');
|
|
1131
1180
|
if(reviewCache[path]){var c=reviewCache[path];api('/api/tasks').then(function(tasks){el.innerHTML=reviewHtml(name,c.text,c.ideas,tasks,c.footer);wireIdeas();});return;}
|
|
@@ -1153,6 +1202,13 @@ if(clean.length)h+="<div class='muted' style='margin-top:10px'>clean: "+esc(clea
|
|
|
1153
1202
|
if(!repos.length)h+="<div class='muted'>No repos found under your home folder.</div>";
|
|
1154
1203
|
out.innerHTML=h;});}
|
|
1155
1204
|
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…</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'>· <b>"+esc(w.name)+"</b> → "+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;});}
|
|
1156
1212
|
function selectNode(id){sel=id;render();updateBar(id);var n=nodeById(id);api('/api/node?id='+encodeURIComponent(id)).then(showDetail);
|
|
1157
1213
|
if(n&&n.type==='repo'&&n.meta&&n.meta.path){loadReview(n.label,n.meta.path);}else{hideReview();}}
|
|
1158
1214
|
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};}
|
|
@@ -1183,6 +1239,7 @@ document.getElementById('remap').addEventListener('click',loadMap);
|
|
|
1183
1239
|
document.getElementById('recbtn').addEventListener('click',loadRec);
|
|
1184
1240
|
document.getElementById('driftrun').addEventListener('click',function(){driftLoaded=false;loadDrift();});
|
|
1185
1241
|
document.getElementById('addtask').addEventListener('click',addTaskUI);
|
|
1242
|
+
document.getElementById('pushtasks').addEventListener('click',pushTasksUI);
|
|
1186
1243
|
document.getElementById('newtask').addEventListener('keydown',function(e){if(e.key==='Enter')addTaskUI();});
|
|
1187
1244
|
initGraphEvents();syncP();refresh();loadMap();
|
|
1188
1245
|
</script></body></html>`;
|
|
@@ -1247,6 +1304,7 @@ async function cmdApp() {
|
|
|
1247
1304
|
if (u.pathname === "/api/tasks/add" && req.method === "POST") { const b = await readBody(req); return json(res, addTask(b.text, b.repo)); }
|
|
1248
1305
|
if (u.pathname === "/api/tasks/toggle" && req.method === "POST") { const b = await readBody(req); return json(res, toggleTask(String(b.id || ""))); }
|
|
1249
1306
|
if (u.pathname === "/api/tasks/remove" && req.method === "POST") { const b = await readBody(req); return json(res, removeTask(String(b.id || ""))); }
|
|
1307
|
+
if (u.pathname === "/api/tasks/push" && req.method === "POST") return json(res, pushTasks());
|
|
1250
1308
|
if (u.pathname === "/api/run" && req.method === "POST") { const b = await readBody(req); const cmd = ["week", "standup", "todo"].includes(b.cmd) ? b.cmd : "week"; return json(res, await produce(cmd)); }
|
|
1251
1309
|
if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
|
|
1252
1310
|
if (u.pathname === "/api/quit") { res.writeHead(200); res.end("bye"); setTimeout(() => process.exit(0), 150); return; }
|
|
@@ -1270,6 +1328,7 @@ ${c.b("Usage")}
|
|
|
1270
1328
|
symbiot todo what's still on your plate
|
|
1271
1329
|
symbiot app open the visual app in your browser
|
|
1272
1330
|
symbiot drift what's out of sync / at risk across repos
|
|
1331
|
+
symbiot push write your tasks into each repo for your agent
|
|
1273
1332
|
symbiot models recommend AI models for your hardware
|
|
1274
1333
|
symbiot login connect it to an AI (once)
|
|
1275
1334
|
symbiot whoami show how it's connected
|
|
@@ -1301,6 +1360,7 @@ async function main() {
|
|
|
1301
1360
|
if (cmd === "app" || cmd === "ui") return cmdApp();
|
|
1302
1361
|
if (cmd === "models" || cmd === "hardware") return cmdModels();
|
|
1303
1362
|
if (cmd === "drift") return cmdDrift();
|
|
1363
|
+
if (cmd === "push") return cmdPush();
|
|
1304
1364
|
if (cmd === "week") return cmdRun("week");
|
|
1305
1365
|
if (cmd === "standup") return cmdRun("standup");
|
|
1306
1366
|
if (cmd === "todo") return cmdRun("todo");
|
|
@@ -1316,4 +1376,4 @@ const isMain = (() => {
|
|
|
1316
1376
|
})();
|
|
1317
1377
|
if (isMain) main();
|
|
1318
1378
|
|
|
1319
|
-
export { authorship, repoState, readmeInfo, repoShape, houseRules, findAllRepos, buildMap, reportFooter, detectHardware, recommendModels, computeDrift, driftRepo, gitDefaultBranch };
|
|
1379
|
+
export { authorship, repoState, readmeInfo, repoShape, houseRules, findAllRepos, buildMap, reportFooter, detectHardware, recommendModels, computeDrift, driftRepo, gitDefaultBranch, buildTasksMd, EMBEDDED_UI };
|
package/package.json
CHANGED