symbiot 0.9.4 → 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 +61 -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,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">
|
|
@@ -1154,6 +1202,13 @@ if(clean.length)h+="<div class='muted' style='margin-top:10px'>clean: "+esc(clea
|
|
|
1154
1202
|
if(!repos.length)h+="<div class='muted'>No repos found under your home folder.</div>";
|
|
1155
1203
|
out.innerHTML=h;});}
|
|
1156
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;});}
|
|
1157
1212
|
function selectNode(id){sel=id;render();updateBar(id);var n=nodeById(id);api('/api/node?id='+encodeURIComponent(id)).then(showDetail);
|
|
1158
1213
|
if(n&&n.type==='repo'&&n.meta&&n.meta.path){loadReview(n.label,n.meta.path);}else{hideReview();}}
|
|
1159
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};}
|
|
@@ -1184,6 +1239,7 @@ document.getElementById('remap').addEventListener('click',loadMap);
|
|
|
1184
1239
|
document.getElementById('recbtn').addEventListener('click',loadRec);
|
|
1185
1240
|
document.getElementById('driftrun').addEventListener('click',function(){driftLoaded=false;loadDrift();});
|
|
1186
1241
|
document.getElementById('addtask').addEventListener('click',addTaskUI);
|
|
1242
|
+
document.getElementById('pushtasks').addEventListener('click',pushTasksUI);
|
|
1187
1243
|
document.getElementById('newtask').addEventListener('keydown',function(e){if(e.key==='Enter')addTaskUI();});
|
|
1188
1244
|
initGraphEvents();syncP();refresh();loadMap();
|
|
1189
1245
|
</script></body></html>`;
|
|
@@ -1248,6 +1304,7 @@ async function cmdApp() {
|
|
|
1248
1304
|
if (u.pathname === "/api/tasks/add" && req.method === "POST") { const b = await readBody(req); return json(res, addTask(b.text, b.repo)); }
|
|
1249
1305
|
if (u.pathname === "/api/tasks/toggle" && req.method === "POST") { const b = await readBody(req); return json(res, toggleTask(String(b.id || ""))); }
|
|
1250
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());
|
|
1251
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)); }
|
|
1252
1309
|
if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
|
|
1253
1310
|
if (u.pathname === "/api/quit") { res.writeHead(200); res.end("bye"); setTimeout(() => process.exit(0), 150); return; }
|
|
@@ -1271,6 +1328,7 @@ ${c.b("Usage")}
|
|
|
1271
1328
|
symbiot todo what's still on your plate
|
|
1272
1329
|
symbiot app open the visual app in your browser
|
|
1273
1330
|
symbiot drift what's out of sync / at risk across repos
|
|
1331
|
+
symbiot push write your tasks into each repo for your agent
|
|
1274
1332
|
symbiot models recommend AI models for your hardware
|
|
1275
1333
|
symbiot login connect it to an AI (once)
|
|
1276
1334
|
symbiot whoami show how it's connected
|
|
@@ -1302,6 +1360,7 @@ async function main() {
|
|
|
1302
1360
|
if (cmd === "app" || cmd === "ui") return cmdApp();
|
|
1303
1361
|
if (cmd === "models" || cmd === "hardware") return cmdModels();
|
|
1304
1362
|
if (cmd === "drift") return cmdDrift();
|
|
1363
|
+
if (cmd === "push") return cmdPush();
|
|
1305
1364
|
if (cmd === "week") return cmdRun("week");
|
|
1306
1365
|
if (cmd === "standup") return cmdRun("standup");
|
|
1307
1366
|
if (cmd === "todo") return cmdRun("todo");
|
|
@@ -1317,4 +1376,4 @@ const isMain = (() => {
|
|
|
1317
1376
|
})();
|
|
1318
1377
|
if (isMain) main();
|
|
1319
1378
|
|
|
1320
|
-
export { authorship, repoState, readmeInfo, repoShape, houseRules, findAllRepos, buildMap, reportFooter, detectHardware, recommendModels, computeDrift, driftRepo, gitDefaultBranch, EMBEDDED_UI };
|
|
1379
|
+
export { authorship, repoState, readmeInfo, repoShape, houseRules, findAllRepos, buildMap, reportFooter, detectHardware, recommendModels, computeDrift, driftRepo, gitDefaultBranch, buildTasksMd, EMBEDDED_UI };
|
package/package.json
CHANGED