@jabworks/condux 0.8.2 → 0.9.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jabworks/condux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "OpenCode plugin for the condux workflow toolkit — bundles the condux skills and injects the specialist agents (coder, explorer, planner, researcher) plus an opt-in plan-review listener.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -15,7 +15,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
|
|
17
17
|
const SKILL_BASE = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
const PLUGIN_ROOT = path.resolve(SKILL_BASE, '..', '..'
|
|
18
|
+
const PLUGIN_ROOT = path.resolve(SKILL_BASE, '..', '..');
|
|
19
19
|
const HOME = os.homedir();
|
|
20
20
|
const EXEC_TIMEOUT = 5000;
|
|
21
21
|
const PACKAGE = '@jabworks/condux';
|
|
@@ -45,9 +45,9 @@ the test suite; also right for headless/CI runs). Use this for ad-hoc review of
|
|
|
45
45
|
are not in plan mode. After submitting, read `<file>.feedback.md` to action it.
|
|
46
46
|
|
|
47
47
|
**Directory mode** reviews a whole spec folder (e.g. `specs/wan-config/` from
|
|
48
|
-
the `technical-spec` skill): every `*.md` in the tree
|
|
49
|
-
|
|
50
|
-
notes are tagged with their source file, edits to any file live-reload (with a
|
|
48
|
+
the `technical-spec` skill): every `*.md` in the tree appears in a doc-site
|
|
49
|
+
sidebar — collapsible folders (`index.md` first, dotdirs skipped), a filter
|
|
50
|
+
over doc names and headings, breadcrumb and prev/next — notes are tagged with their source file, edits to any file live-reload (with a
|
|
51
51
|
per-file revision diff), and the decision lands in `<dir>/review.feedback.md`
|
|
52
52
|
grouped by file. Works in manual and `--steer` modes; this is also the live
|
|
53
53
|
preview surface for `technical-spec` (its old preview server is retired).
|
|
@@ -132,8 +132,7 @@
|
|
|
132
132
|
.nav .tab:last-child { border-right: none; }
|
|
133
133
|
.nav .tab:hover { color: var(--foreground); }
|
|
134
134
|
.nav .tab.active { background: var(--accent); color: var(--foreground); }
|
|
135
|
-
.nav .
|
|
136
|
-
.nav a.doc { font-weight: 600; color: var(--foreground); margin-top: 10px; }
|
|
135
|
+
.nav a.doc { font-weight: 600; color: var(--foreground); }
|
|
137
136
|
.nav a.doc.active { border-left-color: var(--primary); background: var(--accent); }
|
|
138
137
|
.nav a.file .t { font-size: 12px; }
|
|
139
138
|
.nav .none { color: var(--subtle); font-size: 12px; padding: 4px 6px; }
|
|
@@ -145,6 +144,29 @@
|
|
|
145
144
|
.nav a.active { color: var(--foreground); border-left-color: var(--primary); background: var(--accent); }
|
|
146
145
|
.nav a.lvl2 { padding-left: 16px; } .nav a.lvl3 { padding-left: 28px; font-size: 12px; color: var(--subtle); }
|
|
147
146
|
|
|
147
|
+
/* Directory mode: doc-site tree — folder rows fold, filter narrows, the
|
|
148
|
+
active doc alone carries its headings. All of it DIRMODE-gated. */
|
|
149
|
+
.nav .tree-filter { display: none; margin: 0 0 10px; }
|
|
150
|
+
.nav .tree-filter input { width: 100%; font: inherit; font-size: 13px; color: var(--foreground); background: var(--background);
|
|
151
|
+
border: 1px solid var(--border); border-radius: var(--radius); padding: 6px 8px; outline: none; }
|
|
152
|
+
.nav .tree-filter input:focus { border-color: var(--ring); }
|
|
153
|
+
.nav a.folder { font-weight: 600; color: var(--muted-foreground); }
|
|
154
|
+
.nav a.folder .arrow { flex: none; width: 12px; color: var(--subtle); font-size: 10px; }
|
|
155
|
+
.crumbs { display: none; max-width: 760px; margin: 0 auto 18px; font: 12px var(--mono); color: var(--subtle);
|
|
156
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
157
|
+
.crumbs .seg { color: var(--muted-foreground); }
|
|
158
|
+
.crumbs a.seg { color: var(--muted-foreground); text-decoration: none; cursor: pointer; }
|
|
159
|
+
.crumbs a.seg:hover { color: var(--foreground); text-decoration: underline; }
|
|
160
|
+
.crumbs .sep { margin: 0 6px; color: var(--subtle); }
|
|
161
|
+
.crumbs .cur { color: var(--foreground); }
|
|
162
|
+
.pnav { display: none; max-width: 760px; margin: 32px auto 0; padding-top: 14px; border-top: 1px solid var(--border);
|
|
163
|
+
justify-content: space-between; gap: 12px; }
|
|
164
|
+
.pnav a { display: inline-flex; align-items: center; gap: 6px; max-width: 48%; color: var(--muted-foreground);
|
|
165
|
+
text-decoration: none; font-size: 13px; padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--radius);
|
|
166
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
167
|
+
.pnav a:hover { background: var(--accent); color: var(--foreground); }
|
|
168
|
+
.pnav .spacer { flex: 1; }
|
|
169
|
+
|
|
148
170
|
/* Center: plan */
|
|
149
171
|
.main { overflow-y: auto; padding: 28px 44px 120px; position: relative;
|
|
150
172
|
background-image:
|
|
@@ -282,7 +304,10 @@
|
|
|
282
304
|
<button type="button" class="tab active" id="tab-contents">Contents</button>
|
|
283
305
|
<button type="button" class="tab" id="tab-files">Files</button>
|
|
284
306
|
</div>
|
|
285
|
-
<div id="pane-contents"
|
|
307
|
+
<div id="pane-contents">
|
|
308
|
+
<div class="tree-filter" id="tree-filter"><input type="search" id="tree-q" placeholder="Filter docs…" aria-label="Filter documents"></div>
|
|
309
|
+
<nav id="toc"></nav>
|
|
310
|
+
</div>
|
|
286
311
|
<div id="pane-files" style="display:none"><nav id="filelist"></nav></div>
|
|
287
312
|
</aside>
|
|
288
313
|
|
|
@@ -296,7 +321,9 @@
|
|
|
296
321
|
<button type="button" class="btn btn-ghost btn-sm" id="rb-dismiss">Dismiss</button>
|
|
297
322
|
</span>
|
|
298
323
|
</div>
|
|
324
|
+
<div class="crumbs" id="crumbs"></div>
|
|
299
325
|
<div class="doc" id="plan">Loading plan…</div>
|
|
326
|
+
<div class="pnav" id="pnav"></div>
|
|
300
327
|
<div class="diffwrap" id="diffwrap"><div class="diffview" id="diffview"></div></div>
|
|
301
328
|
<div class="popover" id="popover">
|
|
302
329
|
<div class="q" id="pop-quote"></div>
|
|
@@ -411,42 +438,123 @@
|
|
|
411
438
|
});
|
|
412
439
|
}
|
|
413
440
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
441
|
+
// ---- Directory-mode doc-site tree: collapsible folders, filter, chrome ----
|
|
442
|
+
// Collapse state lives in memory only (review tabs are short-lived); seeded
|
|
443
|
+
// so the active doc's ancestors are always open. The filter is a transient
|
|
444
|
+
// view — it never touches openFolders (snapshot restored on clear) and
|
|
445
|
+
// never affects what gets submitted.
|
|
446
|
+
let openFolders=new Set(), treeFilter='', preFilterOpen=null;
|
|
447
|
+
function parentOf(p){ const s=p.lastIndexOf('/'); return s>=0?p.slice(0,s):''; }
|
|
448
|
+
function seedOpen(doc){ let f=parentOf(doc); while(f){ openFolders.add(f); f=parentOf(f); } }
|
|
449
|
+
function headingAnchor(doc,h,lvl){
|
|
450
|
+
const a=document.createElement('a'); a.href='#'+h.id;
|
|
451
|
+
a.className='lvl'+lvl; a.dataset.target=h.id; a.dataset.doc=doc;
|
|
452
|
+
const t=document.createElement('span'); t.className='t'; t.textContent=h.text; a.appendChild(t);
|
|
453
|
+
const bd=document.createElement('span'); bd.className='badge badge-warning'; bd.style.display='none'; bd.dataset.for=h.id; a.appendChild(bd);
|
|
454
|
+
a.onclick=function(e){ e.preventDefault(); if(doc!==activeDoc)switchDoc(doc);
|
|
455
|
+
document.getElementById(h.id).scrollIntoView({behavior:'smooth',block:'start'}); };
|
|
456
|
+
return a;
|
|
457
|
+
}
|
|
458
|
+
function renderTree(){
|
|
459
|
+
const q=treeFilter.trim().toLowerCase(), mcache={};
|
|
460
|
+
// docs[] arrives folder-grouped (root first, index.md first per folder);
|
|
461
|
+
// rebuilding the tree and walking docs-then-sorted-dirs reproduces that
|
|
462
|
+
// order exactly — docs[] stays the one source of truth.
|
|
463
|
+
const root={dirs:{},docs:[]};
|
|
417
464
|
docs.forEach(function(doc){
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
465
|
+
const parts=doc.split('/'); let node=root;
|
|
466
|
+
for(let i=0;i<parts.length-1;i++) node=node.dirs[parts[i]]||(node.dirs[parts[i]]={dirs:{},docs:[]});
|
|
467
|
+
node.docs.push(doc);
|
|
468
|
+
});
|
|
469
|
+
function matches(doc){
|
|
470
|
+
if(!q) return {show:true,heads:null};
|
|
471
|
+
if(mcache[doc]) return mcache[doc];
|
|
472
|
+
const heads=docState[doc].headings.filter(function(h){return h.text.toLowerCase().indexOf(q)>=0;});
|
|
473
|
+
const name=doc.toLowerCase().indexOf(q)>=0;
|
|
474
|
+
return (mcache[doc]={show:name||heads.length>0,heads:heads});
|
|
475
|
+
}
|
|
476
|
+
function indent(el,depth){ el.style.paddingLeft=(6+Math.min(depth,3)*12)+'px'; }
|
|
477
|
+
function hasMatch(node){
|
|
478
|
+
return node.docs.some(function(d){return matches(d).show;})
|
|
479
|
+
|| Object.keys(node.dirs).some(function(k){return hasMatch(node.dirs[k]);});
|
|
480
|
+
}
|
|
481
|
+
(function walk(node,pathStr,depth){
|
|
482
|
+
node.docs.forEach(function(doc){
|
|
483
|
+
const m=matches(doc); if(!m.show) return;
|
|
484
|
+
const d=document.createElement('a'); d.href='#'; d.className='doc'+(doc===activeDoc?' active':''); d.dataset.doc=doc; d.title=doc;
|
|
485
|
+
indent(d,depth);
|
|
486
|
+
const t=document.createElement('span'); t.className='t'; t.textContent=doc.slice(pathStr?pathStr.length+1:0); d.appendChild(t);
|
|
426
487
|
const bd=document.createElement('span'); bd.className='badge badge-warning'; bd.style.display='none'; bd.dataset.fordoc=doc; d.appendChild(bd);
|
|
427
488
|
d.onclick=function(e){ e.preventDefault(); switchDoc(doc); main.scrollTop=0; };
|
|
428
489
|
tocEl.appendChild(d);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
a
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
490
|
+
// Headings: the active doc's under it normally; matched ones under any
|
|
491
|
+
// hit while filtering. A doc with no headings gets no stub.
|
|
492
|
+
const hs=q?m.heads:(doc===activeDoc?docState[doc].headings:[]);
|
|
493
|
+
hs.forEach(function(h){ const a=headingAnchor(doc,h,Math.min(h.level+1,3)); indent(a,depth+1); tocEl.appendChild(a); });
|
|
494
|
+
});
|
|
495
|
+
Object.keys(node.dirs).sort().forEach(function(name){
|
|
496
|
+
const sub=pathStr?pathStr+'/'+name:name;
|
|
497
|
+
if(q&&!hasMatch(node.dirs[name])) return;
|
|
498
|
+
const open=q?true:openFolders.has(sub);
|
|
499
|
+
const row=document.createElement('a'); row.href='#'; row.className='folder'; row.title=sub;
|
|
500
|
+
indent(row,depth);
|
|
501
|
+
const ar=document.createElement('span'); ar.className='arrow'; ar.textContent=open?'▾':'▸'; row.appendChild(ar);
|
|
502
|
+
const t=document.createElement('span'); t.className='t'; t.textContent=name+'/'; row.appendChild(t);
|
|
503
|
+
const bd=document.createElement('span'); bd.className='badge badge-warning'; bd.style.display='none'; bd.dataset.forfolder=sub; bd.dataset.open=open?'1':''; row.appendChild(bd);
|
|
504
|
+
row.onclick=function(e){ e.preventDefault(); if(q)return;
|
|
505
|
+
if(openFolders.has(sub))openFolders.delete(sub); else openFolders.add(sub); buildToc(); };
|
|
506
|
+
tocEl.appendChild(row);
|
|
507
|
+
if(open) walk(node.dirs[name],sub,depth+1);
|
|
438
508
|
});
|
|
509
|
+
})(root,'',0);
|
|
510
|
+
if(q&&!tocEl.children.length){ const n=document.createElement('div'); n.className='none'; n.textContent='No matches.'; tocEl.appendChild(n); }
|
|
511
|
+
}
|
|
512
|
+
function buildToc(){
|
|
513
|
+
tocEl.innerHTML='';
|
|
514
|
+
if(DIRMODE){ renderTree(); }
|
|
515
|
+
else docs.forEach(function(doc){
|
|
516
|
+
docState[doc].headings.forEach(function(h){ tocEl.appendChild(headingAnchor(doc,h,h.level)); });
|
|
439
517
|
});
|
|
440
518
|
observeHeadings(); updateBadges();
|
|
441
519
|
}
|
|
442
520
|
|
|
521
|
+
function updateChrome(){
|
|
522
|
+
if(!DIRMODE||!activeDoc) return;
|
|
523
|
+
const crumbs=document.getElementById('crumbs'); crumbs.innerHTML=''; crumbs.style.display='block';
|
|
524
|
+
const rootName=document.title.replace(/^review:\s*/,'');
|
|
525
|
+
const r=document.createElement('span'); r.className='seg'; r.textContent=rootName; crumbs.appendChild(r);
|
|
526
|
+
const parts=activeDoc.split('/');
|
|
527
|
+
parts.forEach(function(seg,i){
|
|
528
|
+
const sep=document.createElement('span'); sep.className='sep'; sep.textContent='›'; crumbs.appendChild(sep);
|
|
529
|
+
if(i<parts.length-1){
|
|
530
|
+
const f=parts.slice(0,i+1).join('/');
|
|
531
|
+
const a=document.createElement('a'); a.className='seg'; a.textContent=seg;
|
|
532
|
+
a.onclick=function(e){ e.preventDefault();
|
|
533
|
+
const qEl=document.getElementById('tree-q');
|
|
534
|
+
if(qEl&&qEl.value){ qEl.value=''; qEl.oninput(); } // a filtered tree ignores fold state — clear first
|
|
535
|
+
let g=f; while(g){ openFolders.add(g); g=parentOf(g); } buildToc(); };
|
|
536
|
+
crumbs.appendChild(a);
|
|
537
|
+
} else { const c=document.createElement('span'); c.className='seg cur'; c.textContent=seg; crumbs.appendChild(c); }
|
|
538
|
+
});
|
|
539
|
+
const pnav=document.getElementById('pnav'); pnav.innerHTML=''; pnav.style.display='flex';
|
|
540
|
+
const idx=docs.indexOf(activeDoc);
|
|
541
|
+
function jump(d,label){
|
|
542
|
+
const a=document.createElement('a'); a.href='#'; a.title=d;
|
|
543
|
+
a.textContent=label; a.onclick=function(e){ e.preventDefault(); switchDoc(d); main.scrollTop=0; };
|
|
544
|
+
return a;
|
|
545
|
+
}
|
|
546
|
+
if(idx>0) pnav.appendChild(jump(docs[idx-1],'← '+docs[idx-1].split('/').pop()));
|
|
547
|
+
const sp=document.createElement('span'); sp.className='spacer'; pnav.appendChild(sp);
|
|
548
|
+
if(idx>=0&&idx<docs.length-1) pnav.appendChild(jump(docs[idx+1],docs[idx+1].split('/').pop()+' →'));
|
|
549
|
+
}
|
|
550
|
+
|
|
443
551
|
function switchDoc(doc){
|
|
444
552
|
if(activeDoc&&docState[activeDoc]) docState[activeDoc].scroll=main.scrollTop;
|
|
445
553
|
activeDoc=doc;
|
|
446
554
|
docs.forEach(function(d){ docState[d].pane.style.display=d===doc?'':'none'; });
|
|
447
|
-
tocEl.querySelectorAll('a.doc').forEach(function(a){ a.classList.toggle('active',a.dataset.doc===doc); });
|
|
448
555
|
main.scrollTop=docState[doc].scroll||0;
|
|
449
|
-
|
|
556
|
+
if(DIRMODE){ seedOpen(doc); buildToc(); updateChrome(); }
|
|
557
|
+
else observeHeadings();
|
|
450
558
|
}
|
|
451
559
|
|
|
452
560
|
// ---- Selection -> highlight ----
|
|
@@ -589,6 +697,13 @@
|
|
|
589
697
|
if(a.doc)docCounts[a.doc]=(docCounts[a.doc]||0)+1; });
|
|
590
698
|
document.querySelectorAll('.nav .badge[data-for]').forEach(function(b){ const c=counts[b.dataset.for]||0; b.textContent=c; b.style.display=c?'inline-flex':'none'; });
|
|
591
699
|
document.querySelectorAll('.nav .badge[data-fordoc]').forEach(function(b){ const c=docCounts[b.dataset.fordoc]||0; b.textContent=c; b.style.display=c?'inline-flex':'none'; });
|
|
700
|
+
// Collapsed folders roll up their docs' counts (doc-level only — heading
|
|
701
|
+
// badges would double-count) so pending feedback is never invisible.
|
|
702
|
+
document.querySelectorAll('.nav .badge[data-forfolder]').forEach(function(b){
|
|
703
|
+
const f=b.dataset.forfolder+'/'; let c=0;
|
|
704
|
+
Object.keys(docCounts).forEach(function(d){ if(d.indexOf(f)===0)c+=docCounts[d]; });
|
|
705
|
+
b.textContent=c; b.style.display=(c&&!b.dataset.open)?'inline-flex':'none';
|
|
706
|
+
});
|
|
592
707
|
}
|
|
593
708
|
|
|
594
709
|
let spy;
|
|
@@ -761,6 +876,19 @@
|
|
|
761
876
|
// decided — and single-file review when the server was started with
|
|
762
877
|
// --no-reject (discovery's design stage).
|
|
763
878
|
if(DIRMODE||meta.noReject){ const rj=document.getElementById('btn-reject'); if(rj)rj.style.display='none'; }
|
|
879
|
+
if(DIRMODE){
|
|
880
|
+
document.getElementById('tree-filter').style.display='block';
|
|
881
|
+
const qEl=document.getElementById('tree-q');
|
|
882
|
+
qEl.oninput=function(){
|
|
883
|
+
const v=qEl.value;
|
|
884
|
+
if(v&&preFilterOpen===null) preFilterOpen=new Set(openFolders); // snapshot on first keystroke
|
|
885
|
+
treeFilter=v;
|
|
886
|
+
if(!v&&preFilterOpen!==null){ openFolders=new Set(preFilterOpen); preFilterOpen=null;
|
|
887
|
+
if(activeDoc)seedOpen(activeDoc); } // doc switched while filtered — keep its ancestors open
|
|
888
|
+
buildToc();
|
|
889
|
+
};
|
|
890
|
+
qEl.onkeydown=function(e){ if(e.key==='Escape'){ qEl.value=''; qEl.oninput(); qEl.blur(); } };
|
|
891
|
+
}
|
|
764
892
|
planEl.innerHTML='';
|
|
765
893
|
docs.forEach(function(d,i){
|
|
766
894
|
const pane=document.createElement('div'); pane.className='docpane'; pane.style.display='none';
|
|
@@ -768,7 +896,8 @@
|
|
|
768
896
|
docState[d]={md:null,pane:pane,scroll:0,key:'d'+i,headings:[]};
|
|
769
897
|
});
|
|
770
898
|
for(let i=0;i<docs.length;i++){ const d=docs[i]; renderDocPane(d,await fetchDoc(d)); }
|
|
771
|
-
|
|
899
|
+
if(!DIRMODE||!docs.length) buildToc(); // DIRMODE: switchDoc below builds the tree once, seeded
|
|
900
|
+
paint();
|
|
772
901
|
if(docs.length) switchDoc(docs[0]);
|
|
773
902
|
buildFilesIndex();
|
|
774
903
|
}
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
## The review surface
|
|
4
4
|
|
|
5
5
|
A three-pane layout over a **decision bar**: a left sidebar with two tabs —
|
|
6
|
-
**Contents** (heading outline with per-section note counts; in directory mode
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
**Contents** (heading outline with per-section note counts; in directory mode
|
|
7
|
+
it becomes a doc-site tree: collapsible folders — everything folded except the
|
|
8
|
+
active doc's ancestors, collapsed folders roll up their docs' note counts — a
|
|
9
|
+
filter box matching doc names and headings, and the active doc's headings
|
|
10
|
+
nested under it; a breadcrumb and prev/next footer ride the content pane) and
|
|
11
|
+
**Files** (paths the plan touches, parsed from inline code and
|
|
9
12
|
fences, badged **exists**/**new** against the repo; click jumps to the first
|
|
10
13
|
mention, repeat clicks cycle) — the centered rendered plan (middle), and a
|
|
11
14
|
**Review** rail (right) holding your notes and messages. The decisions live in
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"hooks": [
|
|
17
17
|
{
|
|
18
18
|
"type": "command",
|
|
19
|
-
"command": "node \"${PLUGIN_ROOT}/skills/
|
|
19
|
+
"command": "node \"${PLUGIN_ROOT}/skills/plan-review/references/annotate-server.js\" --codex-stop",
|
|
20
20
|
"timeout": 345600
|
|
21
21
|
}
|
|
22
22
|
]
|