symbiot 0.9.4 → 0.10.1
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 +72 -2
- 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,62 @@ 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
|
+
// 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;
|
|
130
|
+
const groups = {}; for (const t of tasks) { const k = t.repo || ""; (groups[k] = groups[k] || []).push(t); }
|
|
131
|
+
const written = [], unresolved = [];
|
|
132
|
+
for (const name of Object.keys(groups)) {
|
|
133
|
+
const list = groups[name], path = name && byName[name];
|
|
134
|
+
if (!path) { unresolved.push({ name: name || "(no repo)", count: list.length }); continue; }
|
|
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).
|
|
138
|
+
const st = repoState(path);
|
|
139
|
+
const commits = sh(`git -C ${JSON.stringify(path)} log --format='%s' -10 2>/dev/null`).split("\n").filter(Boolean);
|
|
140
|
+
const open = openWork([{ path, name }]).slice(0, 12);
|
|
141
|
+
const det = detectRepo({ path, name, recency: 0 });
|
|
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)`);
|
|
147
|
+
const dir = join(path, ".symbiot"); mkdirSync(dir, { recursive: true });
|
|
148
|
+
const file = join(dir, "TASKS.md");
|
|
149
|
+
writeFileSync(file, buildTasksMd(name, { branch: st.branch, commits, open, drift: risk, stack }, list));
|
|
150
|
+
written.push({ name, file, count: list.length });
|
|
151
|
+
} catch (e) { unresolved.push({ name, count: list.length, error: String((e && e.message) || e) }); }
|
|
152
|
+
}
|
|
153
|
+
return { empty: false, written, unresolved };
|
|
154
|
+
}
|
|
155
|
+
function cmdPush() {
|
|
156
|
+
const r = pushTasks();
|
|
157
|
+
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; }
|
|
158
|
+
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" : "") + ")")}`); }
|
|
159
|
+
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})`); }
|
|
160
|
+
console.log("\n" + c.d("Open each repo in your IDE/agent and point it at .symbiot/TASKS.md."));
|
|
161
|
+
}
|
|
106
162
|
function envKey(provider) {
|
|
107
163
|
for (const e of (PROVIDERS[provider].env || [])) if (process.env[e]) return process.env[e];
|
|
108
164
|
return null;
|
|
@@ -1021,7 +1077,8 @@ footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
|
|
|
1021
1077
|
<div class="rfoot" id="outfoot" style="display:none"></div>
|
|
1022
1078
|
</section>
|
|
1023
1079
|
<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>
|
|
1080
|
+
<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>
|
|
1081
|
+
<div id="pushout"></div>
|
|
1025
1082
|
<div id="tasklist"></div>
|
|
1026
1083
|
</section>
|
|
1027
1084
|
<section id="panel-drift" class="hidden">
|
|
@@ -1154,6 +1211,15 @@ if(clean.length)h+="<div class='muted' style='margin-top:10px'>clean: "+esc(clea
|
|
|
1154
1211
|
if(!repos.length)h+="<div class='muted'>No repos found under your home folder.</div>";
|
|
1155
1212
|
out.innerHTML=h;});}
|
|
1156
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();});}
|
|
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…</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 — wrote "+n+" file"+(n===1?"":"s")+"</span></div>";
|
|
1219
|
+
if(n){h+="<ul>";r.written.forEach(function(w){h+="<li class='info'>✓ <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'>⚠ 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't write tasks: "+esc(String((e&&e.message)||e))+"</div>";});}
|
|
1157
1223
|
function selectNode(id){sel=id;render();updateBar(id);var n=nodeById(id);api('/api/node?id='+encodeURIComponent(id)).then(showDetail);
|
|
1158
1224
|
if(n&&n.type==='repo'&&n.meta&&n.meta.path){loadReview(n.label,n.meta.path);}else{hideReview();}}
|
|
1159
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};}
|
|
@@ -1184,6 +1250,7 @@ document.getElementById('remap').addEventListener('click',loadMap);
|
|
|
1184
1250
|
document.getElementById('recbtn').addEventListener('click',loadRec);
|
|
1185
1251
|
document.getElementById('driftrun').addEventListener('click',function(){driftLoaded=false;loadDrift();});
|
|
1186
1252
|
document.getElementById('addtask').addEventListener('click',addTaskUI);
|
|
1253
|
+
document.getElementById('pushtasks').addEventListener('click',pushTasksUI);
|
|
1187
1254
|
document.getElementById('newtask').addEventListener('keydown',function(e){if(e.key==='Enter')addTaskUI();});
|
|
1188
1255
|
initGraphEvents();syncP();refresh();loadMap();
|
|
1189
1256
|
</script></body></html>`;
|
|
@@ -1248,6 +1315,7 @@ async function cmdApp() {
|
|
|
1248
1315
|
if (u.pathname === "/api/tasks/add" && req.method === "POST") { const b = await readBody(req); return json(res, addTask(b.text, b.repo)); }
|
|
1249
1316
|
if (u.pathname === "/api/tasks/toggle" && req.method === "POST") { const b = await readBody(req); return json(res, toggleTask(String(b.id || ""))); }
|
|
1250
1317
|
if (u.pathname === "/api/tasks/remove" && req.method === "POST") { const b = await readBody(req); return json(res, removeTask(String(b.id || ""))); }
|
|
1318
|
+
if (u.pathname === "/api/tasks/push" && req.method === "POST") return json(res, pushTasks());
|
|
1251
1319
|
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)); }
|
|
1252
1320
|
if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
|
|
1253
1321
|
if (u.pathname === "/api/quit") { res.writeHead(200); res.end("bye"); setTimeout(() => process.exit(0), 150); return; }
|
|
@@ -1271,6 +1339,7 @@ ${c.b("Usage")}
|
|
|
1271
1339
|
symbiot todo what's still on your plate
|
|
1272
1340
|
symbiot app open the visual app in your browser
|
|
1273
1341
|
symbiot drift what's out of sync / at risk across repos
|
|
1342
|
+
symbiot push write your tasks into each repo for your agent
|
|
1274
1343
|
symbiot models recommend AI models for your hardware
|
|
1275
1344
|
symbiot login connect it to an AI (once)
|
|
1276
1345
|
symbiot whoami show how it's connected
|
|
@@ -1302,6 +1371,7 @@ async function main() {
|
|
|
1302
1371
|
if (cmd === "app" || cmd === "ui") return cmdApp();
|
|
1303
1372
|
if (cmd === "models" || cmd === "hardware") return cmdModels();
|
|
1304
1373
|
if (cmd === "drift") return cmdDrift();
|
|
1374
|
+
if (cmd === "push") return cmdPush();
|
|
1305
1375
|
if (cmd === "week") return cmdRun("week");
|
|
1306
1376
|
if (cmd === "standup") return cmdRun("standup");
|
|
1307
1377
|
if (cmd === "todo") return cmdRun("todo");
|
|
@@ -1317,4 +1387,4 @@ const isMain = (() => {
|
|
|
1317
1387
|
})();
|
|
1318
1388
|
if (isMain) main();
|
|
1319
1389
|
|
|
1320
|
-
export { authorship, repoState, readmeInfo, repoShape, houseRules, findAllRepos, buildMap, reportFooter, detectHardware, recommendModels, computeDrift, driftRepo, gitDefaultBranch, EMBEDDED_UI };
|
|
1390
|
+
export { authorship, repoState, readmeInfo, repoShape, houseRules, findAllRepos, buildMap, reportFooter, detectHardware, recommendModels, computeDrift, driftRepo, gitDefaultBranch, buildTasksMd, EMBEDDED_UI };
|
package/package.json
CHANGED