symbiot 0.10.3 → 0.11.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/index.mjs +33 -14
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -766,25 +766,41 @@ async function repoReview(path) {
|
|
|
766
766
|
const commits = sh(`git -C ${JSON.stringify(path)} log ${authorArgs(auth.emails)} --format='%ad %s' --date=short -50 2>/dev/null`).trim()
|
|
767
767
|
|| sh(`git -C ${JSON.stringify(path)} log --format='%ad %s' --date=short -50 2>/dev/null`).trim();
|
|
768
768
|
const footer = reportFooter(path, auth, state, rd);
|
|
769
|
+
// Cheap maturity gauge, so the model can be told to show restraint instead of
|
|
770
|
+
// always inventing new features (over-engineering is a failure, not a win).
|
|
771
|
+
const testFiles = Number(sh(`git -C ${JSON.stringify(path)} ls-files 2>/dev/null | grep -icE '(^|/)(tests?|spec|__tests__)/|\\.(test|spec)\\.'`).trim()) || 0;
|
|
772
|
+
const churn = Number(sh(`git -C ${JSON.stringify(path)} log --since='14 days ago' --oneline 2>/dev/null | wc -l`).trim()) || 0;
|
|
773
|
+
const posture = state.stale ? "the checkout is a stale snapshot — the priority is getting current, not new work"
|
|
774
|
+
: (testFiles === 0 && shape.total > 40) ? "a real codebase with NO test files — favour tests/stabilising over new features"
|
|
775
|
+
: (churn >= 25) ? "very rapid churn lately — likely over-iterating; be conservative, prefer consolidation/finishing over new features"
|
|
776
|
+
: (state.dirty > 20) ? "a lot of unfinished/uncommitted work — finish what's open before starting new things"
|
|
777
|
+
: "no red flags — but still only propose what clearly pays off";
|
|
769
778
|
const system =
|
|
770
|
-
`You are
|
|
771
|
-
`"review": 3-6 sentences on what it does and who it's for.
|
|
772
|
-
`
|
|
773
|
-
`
|
|
774
|
-
|
|
779
|
+
`You are a pragmatic, restraint-minded reviewer of ONE software project. Respond with ONLY a JSON object (no fences): {"review": string, "verdict": string, "ideas": string[]}. ` +
|
|
780
|
+
`"review": 3-6 sentences on what it does and who it's for. ` +
|
|
781
|
+
`"verdict": ONE honest sentence on whether this project needs new work now, and of what KIND. Over-engineering is a failure: if it's already capable, or shows churn / missing tests / unfinished work, say so and steer toward STABILISING (tests, docs, finishing, removing) or simply SHIPPING — not more features. ` +
|
|
782
|
+
`"ideas": AT MOST 5, ranked by value, ONLY items that clearly pay off. Do NOT pad the list — returning 0–2 ideas with a "stabilise, don't add" verdict is a good, correct answer. Prefer fixes / tests / simplification / finishing over new features unless a feature is clearly warranted by the evidence. ` +
|
|
783
|
+
`Ground every claim in the evidence; recent commits beat the README; never propose building something already in the structure; never advise against the stated conventions. ` +
|
|
784
|
+
(state.stale ? `CRITICAL: the working tree is a STALE checkout — never suggest committing it. ` : ``);
|
|
775
785
|
const prompt =
|
|
776
|
-
`Project: ${name}\nBranch ${state.branch} · ${auth.total} commits total (${auth.mineCount} yours${auth.filterDropped ? ", filter dropped so counting everyone" : ""})\n
|
|
786
|
+
`Project: ${name}\nBranch ${state.branch} · ${auth.total} commits total (${auth.mineCount} yours${auth.filterDropped ? ", filter dropped so counting everyone" : ""})\n` +
|
|
787
|
+
`Maturity signal: ${posture}. (${testFiles} test files, ${churn} commits in the last 14 days.)\n\n` +
|
|
777
788
|
(rd.file ? `README (last changed ${rd.lastDate || "?"}, ${rd.commitsAgo} commits ago — may be out of date):\n${rd.excerpt}\n\n` : "(no README)\n\n") +
|
|
778
789
|
`Structure (top folders · file counts): ${shape.dirs.join(", ")}\nManifests: ${shape.manifests.join(", ") || "none"}\nDocs present: ${shape.docs.join(", ") || "none"}\n\n` +
|
|
779
790
|
`Recent commits (newest first):\n${commits || "(none)"}\n\n` +
|
|
780
791
|
(rules ? `Conventions this team has chosen — do NOT advise against these:\n${rules}\n\n` : "") +
|
|
781
792
|
(state.stale ? `Working tree: STALE (≈ ${state.staleBy ? "HEAD~" + state.staleBy : "older"}); its "changes" are the gap to an old snapshot, not new work.\n\n` : "") +
|
|
782
|
-
`Return the JSON.`;
|
|
793
|
+
`Return the JSON. Remember: fewer, higher-value ideas beat a long list; a "don't add — stabilise" verdict with 0–2 ideas is a valid answer.`;
|
|
783
794
|
const raw = await write(system, prompt);
|
|
784
|
-
if (!raw) return { text: "(couldn't reach the model)", ideas: [], footer };
|
|
795
|
+
if (!raw) return { text: "(couldn't reach the model)", ideas: [], verdict: "", footer };
|
|
785
796
|
const j = extractJson(raw);
|
|
786
|
-
if (j && (j.review || j.ideas
|
|
787
|
-
|
|
797
|
+
if (j && (j.review || j.ideas || j.verdict)) return {
|
|
798
|
+
text: String(j.review || "").trim() || raw,
|
|
799
|
+
verdict: String(j.verdict || "").trim(),
|
|
800
|
+
ideas: Array.isArray(j.ideas) ? j.ideas.map((x) => String(x).trim()).filter(Boolean).slice(0, 5) : [],
|
|
801
|
+
footer,
|
|
802
|
+
};
|
|
803
|
+
return { text: raw, ideas: [], verdict: "", footer };
|
|
788
804
|
}
|
|
789
805
|
function extractJson(s) {
|
|
790
806
|
if (!s) return null;
|
|
@@ -1015,6 +1031,7 @@ footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
|
|
|
1015
1031
|
.review .body{color:var(--text);line-height:1.65;font-size:14px;white-space:pre-wrap}
|
|
1016
1032
|
.review .rname{color:var(--bone);font-weight:600}
|
|
1017
1033
|
.rfoot{margin-top:12px;font-size:11px;color:var(--faint);font-family:ui-monospace,Menlo,Consolas,monospace;border-top:1px solid var(--line);padding-top:8px;line-height:1.5}
|
|
1034
|
+
.verdict{margin-top:12px;padding:10px 12px;border-left:3px solid var(--amber);background:var(--ink3);border-radius:0 8px 8px 0;color:var(--bone);font-size:13.5px;line-height:1.5}
|
|
1018
1035
|
.ideas{margin-top:14px}
|
|
1019
1036
|
.ideas h4{margin:0 0 6px;color:var(--amber);font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700}
|
|
1020
1037
|
.idea{display:flex;align-items:flex-start;gap:9px;padding:5px 0;font-size:13.5px;color:var(--text)}
|
|
@@ -1173,12 +1190,14 @@ if(d.type==='lang'||d.type==='tool'){var lis=(d.repos||[]).map(function(r){retur
|
|
|
1173
1190
|
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;}}
|
|
1174
1191
|
var reviewCache={};var IDEAS=[];var IREPO="";
|
|
1175
1192
|
function hideReview(){var el=document.getElementById('review');el.classList.add('hidden');el.innerHTML='';}
|
|
1176
|
-
function reviewHtml(name,body,ideas,tasks,footer){IDEAS=ideas||[];IREPO=name;
|
|
1193
|
+
function reviewHtml(name,body,verdict,ideas,tasks,footer){IDEAS=ideas||[];IREPO=name;
|
|
1177
1194
|
var h="<h4>AI review · <span class='rname'>"+esc(name)+"</span></h4><div class='body'>"+esc(body)+"</div>";
|
|
1178
|
-
if(
|
|
1195
|
+
if(verdict)h+="<div class='verdict'>⚖️ "+esc(verdict)+"</div>";
|
|
1196
|
+
if(ideas&&ideas.length){h+="<div class='ideas'><h4>Ideas worth doing · tick to add to Tasks</h4>";
|
|
1179
1197
|
ideas.forEach(function(idea,i){var t=(tasks||[]).filter(function(x){return x.text===idea&&x.repo===name;})[0];var tid=t?t.id:"";
|
|
1180
1198
|
h+="<label class='idea'><input type='checkbox' class='ideachk' data-idx='"+i+"' data-tid='"+esc(tid)+"'"+(t?" checked":"")+"><span>"+esc(idea)+"</span></label>";});
|
|
1181
1199
|
h+="</div>";}
|
|
1200
|
+
else h+="<div class='muted' style='margin-top:10px'>No new features suggested — the verdict above is the call.</div>";
|
|
1182
1201
|
if(footer)h+="<div class='rfoot'>"+esc(footer)+"</div>";
|
|
1183
1202
|
return h;}
|
|
1184
1203
|
function wireIdeas(){document.querySelectorAll('.ideachk').forEach(function(cb){cb.addEventListener('change',function(){
|
|
@@ -1186,11 +1205,11 @@ var idea=IDEAS[+cb.getAttribute('data-idx')];if(idea==null)return;
|
|
|
1186
1205
|
if(cb.checked){api('/api/tasks/add',{text:idea,repo:IREPO}).then(function(it){if(it&&it.id)cb.setAttribute('data-tid',it.id);});}
|
|
1187
1206
|
else{var id=cb.getAttribute('data-tid');if(id){api('/api/tasks/remove',{id:id}).then(function(){cb.setAttribute('data-tid','');});}}});});}
|
|
1188
1207
|
function loadReview(name,path){var el=document.getElementById('review');el.classList.remove('hidden');
|
|
1189
|
-
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;}
|
|
1208
|
+
if(reviewCache[path]){var c=reviewCache[path];api('/api/tasks').then(function(tasks){el.innerHTML=reviewHtml(name,c.text,c.verdict,c.ideas,tasks,c.footer);wireIdeas();});return;}
|
|
1190
1209
|
el.innerHTML="<h4>AI review · <span class='rname'>"+esc(name)+"</span></h4><div class='body'>Reading the project…</div>";
|
|
1191
1210
|
Promise.all([api('/api/review',{path:path}),api('/api/tasks')]).then(function(res){var r=res[0]||{},tasks=res[1]||[];
|
|
1192
1211
|
if(r.error==='not-connected'){el.innerHTML="<h4>AI review · <span class='rname'>"+esc(name)+"</span></h4><div class='body'>Connect a model in Settings to get a review - Ollama is free and runs locally.</div>"+(r.footer?"<div class='rfoot'>"+esc(r.footer)+"</div>":"");return;}
|
|
1193
|
-
var body=r.text||'(no output)';reviewCache[path]={text:body,ideas:r.ideas||[],footer:r.footer};el.innerHTML=reviewHtml(name,body,r.ideas||[],tasks,r.footer);wireIdeas();});}
|
|
1212
|
+
var body=r.text||'(no output)';reviewCache[path]={text:body,verdict:r.verdict||"",ideas:r.ideas||[],footer:r.footer};el.innerHTML=reviewHtml(name,body,r.verdict||"",r.ideas||[],tasks,r.footer);wireIdeas();});}
|
|
1194
1213
|
function renderTasks(list){var el=document.getElementById('tasklist');if(!list||!list.length){el.innerHTML="<div class='muted' style='margin-top:12px'>No tasks yet. Tick an idea in a repo's review, or add one above.</div>";return;}
|
|
1195
1214
|
var h="";list.forEach(function(t){h+="<div class='task"+(t.done?" done":"")+"' data-id='"+esc(t.id)+"'><input type='checkbox' class='taskchk'"+(t.done?" checked":"")+"><span class='t'>"+esc(t.text)+"</span>"+(t.repo?"<span class='rp'>"+esc(t.repo)+"</span>":"")+"<button class='rm' title='remove'>×</button></div>";});
|
|
1196
1215
|
el.innerHTML=h;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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": {
|