@yemi33/squad 0.1.14 → 0.1.16
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 +2 -5
- package/dashboard.html +199 -36
- package/dashboard.js +109 -2
- package/docs/blog-first-successful-dispatch.md +1 -1
- package/engine.js +38 -44
- package/package.json +1 -1
- package/playbooks/explore.md +1 -0
package/README.md
CHANGED
|
@@ -128,7 +128,6 @@ You can also run scripts directly: `node ~/.squad/engine.js start`, `node ~/.squ
|
|
|
128
128
|
│ engine.js ← tick 60s │
|
|
129
129
|
│ dashboard.js ← :7331 │
|
|
130
130
|
│ config.json ← projects │
|
|
131
|
-
│ mcp-servers.json ← auto-sync │
|
|
132
131
|
│ agents/ ← 5 agents │
|
|
133
132
|
│ playbooks/ ← templates │
|
|
134
133
|
│ prd.json ← squad PRD │
|
|
@@ -231,7 +230,7 @@ When dispatching agents, the engine reads each project's `CLAUDE.md` and injects
|
|
|
231
230
|
|
|
232
231
|
## MCP Server Integration
|
|
233
232
|
|
|
234
|
-
Agents need MCP tools to interact with your repo host (create PRs, post review comments, etc.).
|
|
233
|
+
Agents need MCP tools to interact with your repo host (create PRs, post review comments, etc.). Agents inherit MCP servers directly from `~/.claude.json` as Claude Code processes — add servers there and they're immediately available to all agents on next spawn.
|
|
235
234
|
|
|
236
235
|
**Example:** If you use Azure DevOps, configure the `azure-ado` MCP server in your Claude Code settings. If you use GitHub, configure the `github` MCP server. Agents will discover and use whichever tools are available.
|
|
237
236
|
|
|
@@ -314,7 +313,7 @@ No bash or shell involved — Node spawns Node directly. Prompts with special ch
|
|
|
314
313
|
- **System prompt** — identity, charter, history, project context, critical rules, skill index, team notes
|
|
315
314
|
- **Task prompt** — rendered playbook with `{{variables}}` filled from config
|
|
316
315
|
- **Working directory** — project root (agent creates worktrees as needed)
|
|
317
|
-
- **MCP servers** —
|
|
316
|
+
- **MCP servers** — inherited from `~/.claude.json` (no extra config needed)
|
|
318
317
|
- **Full tool access** — all built-in tools plus all MCP tools
|
|
319
318
|
- **Permission mode** — `bypassPermissions` (no interactive prompts)
|
|
320
319
|
- **Output format** — `stream-json` (real-time streaming for live dashboard + heartbeat)
|
|
@@ -446,7 +445,6 @@ squad start
|
|
|
446
445
|
|
|
447
446
|
**Machine-specific (reconfigure per machine):**
|
|
448
447
|
- `config.json` — contains absolute paths to project directories. Re-link via `squad add <dir>`.
|
|
449
|
-
- `mcp-servers.json` — auto-synced from `~/.claude.json` on engine start.
|
|
450
448
|
|
|
451
449
|
To move to a new machine: `npm install -g @yemi33/squad && squad init --force`, then re-run `squad add` for each project.
|
|
452
450
|
|
|
@@ -471,7 +469,6 @@ To move to a new machine: `npm install -g @yemi33/squad && squad init --force`,
|
|
|
471
469
|
prd.json <- Squad-level PRD (multi-project items)
|
|
472
470
|
config.template.json <- Template for new installs
|
|
473
471
|
package.json <- npm package definition
|
|
474
|
-
mcp-servers.json <- MCP servers (auto-synced, gitignored)
|
|
475
472
|
routing.md <- Dispatch rules table (editable)
|
|
476
473
|
team.md <- Team roster
|
|
477
474
|
notes.md <- Team rules + consolidated learnings (runtime)
|
package/dashboard.html
CHANGED
|
@@ -58,6 +58,11 @@
|
|
|
58
58
|
.modal-qa-loading .dot-pulse span:nth-child(2) { animation-delay: 0.2s; }
|
|
59
59
|
.modal-qa-loading .dot-pulse span:nth-child(3) { animation-delay: 0.4s; }
|
|
60
60
|
@keyframes dotPulse { 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1); } }
|
|
61
|
+
.modal-qa-selection-pill { display: flex; align-items: center; gap: 6px; padding: 4px 8px; margin-bottom: 6px; background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.25); border-radius: 4px; font-size: 11px; }
|
|
62
|
+
.modal-qa-selection-pill .pill-label { color: var(--blue); font-weight: 600; white-space: nowrap; }
|
|
63
|
+
.modal-qa-selection-pill .pill-text { color: var(--muted); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-style: italic; }
|
|
64
|
+
.modal-qa-selection-pill .pill-clear { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 14px; padding: 0 2px; line-height: 1; }
|
|
65
|
+
.modal-qa-selection-pill .pill-clear:hover { color: var(--red); }
|
|
61
66
|
.modal-qa-input-wrap { display: flex; gap: 6px; }
|
|
62
67
|
.modal-qa-input { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 6px 10px; font-size: 12px; color: var(--text); font-family: inherit; }
|
|
63
68
|
.modal-qa-input:focus { border-color: var(--blue); outline: none; }
|
|
@@ -239,7 +244,19 @@
|
|
|
239
244
|
transition: border-color 0.2s, box-shadow 0.2s; padding: 0;
|
|
240
245
|
}
|
|
241
246
|
.cmd-input-wrap:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(88,166,255,0.12); }
|
|
247
|
+
.cmd-highlight-layer {
|
|
248
|
+
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
|
|
249
|
+
padding: 14px 16px; font-size: 14px; font-family: inherit; line-height: 1.5;
|
|
250
|
+
white-space: pre-wrap; word-wrap: break-word; overflow: hidden;
|
|
251
|
+
pointer-events: none; color: transparent; border-radius: 10px;
|
|
252
|
+
}
|
|
253
|
+
.cmd-highlight-layer .hl-cmd { color: transparent; background: rgba(88,166,255,0.18); border-radius: 3px; }
|
|
254
|
+
.cmd-highlight-layer .hl-mention { color: transparent; background: rgba(63,185,80,0.18); border-radius: 3px; }
|
|
255
|
+
.cmd-highlight-layer .hl-priority { color: transparent; background: rgba(210,153,34,0.18); border-radius: 3px; }
|
|
256
|
+
.cmd-highlight-layer .hl-project { color: transparent; background: rgba(188,140,255,0.18); border-radius: 3px; }
|
|
257
|
+
.cmd-highlight-layer .hl-flag { color: transparent; background: rgba(248,81,73,0.18); border-radius: 3px; }
|
|
242
258
|
.cmd-input-wrap textarea {
|
|
259
|
+
position: relative; z-index: 1;
|
|
243
260
|
flex: 1; background: transparent; border: none; color: var(--text);
|
|
244
261
|
padding: 14px 16px; font-size: 14px; font-family: inherit; resize: none;
|
|
245
262
|
outline: none; line-height: 1.5; min-height: 48px; max-height: 200px;
|
|
@@ -358,11 +375,15 @@
|
|
|
358
375
|
.dispatch-item { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; font-size: 12px; }
|
|
359
376
|
.dispatch-type { font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 8px; text-transform: uppercase; white-space: nowrap; }
|
|
360
377
|
.dispatch-type.implement { background: rgba(88,166,255,0.15); color: var(--blue); }
|
|
378
|
+
.dispatch-type.implement\:large { background: rgba(88,166,255,0.25); color: var(--blue); }
|
|
361
379
|
.dispatch-type.review { background: rgba(188,140,255,0.15); color: var(--purple); }
|
|
362
380
|
.dispatch-type.fix { background: rgba(210,153,34,0.15); color: var(--yellow); }
|
|
363
381
|
.dispatch-type.analyze { background: rgba(63,185,80,0.15); color: var(--green); }
|
|
364
382
|
.dispatch-type.explore { background: rgba(139,148,158,0.15); color: var(--muted); }
|
|
365
383
|
.dispatch-type.test { background: rgba(227,179,65,0.15); color: var(--orange); }
|
|
384
|
+
.dispatch-type.plan { background: rgba(168,85,247,0.15); color: #a855f7; }
|
|
385
|
+
.dispatch-type.plan-to-prd { background: rgba(168,85,247,0.1); color: #a855f7; }
|
|
386
|
+
.dispatch-type.ask { background: rgba(63,185,80,0.15); color: var(--green); }
|
|
366
387
|
.dispatch-type.manual { background: rgba(139,148,158,0.15); color: var(--muted); }
|
|
367
388
|
.dispatch-agent { font-weight: 600; color: var(--text); }
|
|
368
389
|
.dispatch-task { flex: 1; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
@@ -478,8 +499,9 @@
|
|
|
478
499
|
<section class="cmd-center">
|
|
479
500
|
<h2>Command Center</h2>
|
|
480
501
|
<div class="cmd-input-wrap" id="cmd-input-wrap">
|
|
502
|
+
<div class="cmd-highlight-layer" id="cmd-highlight" aria-hidden="true"></div>
|
|
481
503
|
<textarea id="cmd-input" rows="1" placeholder='What do you need? e.g. "Fix the auth bug @dallas", "explain the dispatch flow", or "/note always use feature flags"'
|
|
482
|
-
oninput="cmdInputChanged()" onkeydown="cmdKeyDown(event)"></textarea>
|
|
504
|
+
oninput="cmdInputChanged()" onkeydown="cmdKeyDown(event)" onscroll="syncHighlightScroll()"></textarea>
|
|
483
505
|
<button class="cmd-send-btn" id="cmd-send-btn" onclick="cmdSubmit()">Send <kbd>Ctrl+Enter</kbd></button>
|
|
484
506
|
</div>
|
|
485
507
|
<div class="cmd-mention-popup" id="cmd-mention-popup"></div>
|
|
@@ -526,7 +548,7 @@
|
|
|
526
548
|
</section>
|
|
527
549
|
|
|
528
550
|
<section>
|
|
529
|
-
<h2>Notes Inbox <span class="count" id="inbox-count">0</span></h2>
|
|
551
|
+
<h2>Notes Inbox <span class="count" id="inbox-count">0</span> <span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">auto-consolidates at 3 notes</span></h2>
|
|
530
552
|
<div class="inbox-list" id="inbox-list">Loading...</div>
|
|
531
553
|
</section>
|
|
532
554
|
|
|
@@ -546,6 +568,11 @@
|
|
|
546
568
|
<div id="skills-list"><p class="empty">No skills yet. Agents create these when they discover repeatable workflows.</p></div>
|
|
547
569
|
</section>
|
|
548
570
|
|
|
571
|
+
<section>
|
|
572
|
+
<h2>MCP Servers <span class="count" id="mcp-count">0</span></h2>
|
|
573
|
+
<div id="mcp-list"><p class="empty">No MCP servers synced.</p></div>
|
|
574
|
+
</section>
|
|
575
|
+
|
|
549
576
|
<section>
|
|
550
577
|
<h2>Dispatch Queue</h2>
|
|
551
578
|
<div class="dispatch-stats" id="dispatch-stats"></div>
|
|
@@ -601,6 +628,11 @@
|
|
|
601
628
|
<div class="modal-body" id="modal-body"></div>
|
|
602
629
|
<div class="modal-qa" id="modal-qa">
|
|
603
630
|
<div class="modal-qa-thread" id="modal-qa-thread"></div>
|
|
631
|
+
<div class="modal-qa-selection-pill" id="modal-qa-pill" style="display:none">
|
|
632
|
+
<span class="pill-label">Selection:</span>
|
|
633
|
+
<span class="pill-text" id="modal-qa-pill-text"></span>
|
|
634
|
+
<button class="pill-clear" onclick="clearQaSelection()" title="Clear selection">×</button>
|
|
635
|
+
</div>
|
|
604
636
|
<div class="modal-qa-input-wrap">
|
|
605
637
|
<input type="text" class="modal-qa-input" id="modal-qa-input" placeholder="Ask about this document (or select text first)..." onkeydown="if(event.key==='Enter')modalAskSubmit()">
|
|
606
638
|
<button class="modal-qa-btn" id="modal-qa-btn" onclick="modalAskSubmit()">Ask</button>
|
|
@@ -850,23 +882,45 @@ function renderInbox(inbox) {
|
|
|
850
882
|
<span>${escHtml(item.name)}</span><span>${item.age}</span>
|
|
851
883
|
</div>
|
|
852
884
|
<div class="inbox-preview" onclick="openModal(${i})" style="cursor:pointer">${escHtml(item.content.slice(0,200))}</div>
|
|
853
|
-
<div style="display:flex;gap:6px;margin-top:6px">
|
|
854
|
-
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();
|
|
885
|
+
<div style="display:flex;gap:6px;margin-top:6px;align-items:center">
|
|
886
|
+
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();promoteToKB('${escHtml(item.name)}')">Add to Knowledge Base</button>
|
|
855
887
|
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();openInboxInExplorer('${escHtml(item.name)}')">Open in Explorer</button>
|
|
856
888
|
</div>
|
|
857
889
|
</div>
|
|
858
890
|
`).join('');
|
|
859
891
|
}
|
|
860
892
|
|
|
861
|
-
|
|
893
|
+
function promoteToKB(name) {
|
|
894
|
+
const categories = [
|
|
895
|
+
{ id: 'architecture', label: 'Architecture' },
|
|
896
|
+
{ id: 'conventions', label: 'Conventions' },
|
|
897
|
+
{ id: 'project-notes', label: 'Project Notes' },
|
|
898
|
+
{ id: 'build-reports', label: 'Build Reports' },
|
|
899
|
+
{ id: 'reviews', label: 'Reviews' },
|
|
900
|
+
];
|
|
901
|
+
const picker = '<div style="padding:16px 20px">' +
|
|
902
|
+
'<p style="font-size:13px;color:var(--text);margin-bottom:12px">Choose a category for <strong>' + escHtml(name) + '</strong>:</p>' +
|
|
903
|
+
'<div style="display:flex;flex-direction:column;gap:8px">' +
|
|
904
|
+
categories.map(c =>
|
|
905
|
+
'<button class="pr-pager-btn" style="font-size:12px;padding:8px 16px;text-align:left" onclick="doPromoteToKB(\'' + escHtml(name) + '\',\'' + c.id + '\')">' + c.label + '</button>'
|
|
906
|
+
).join('') +
|
|
907
|
+
'</div></div>';
|
|
908
|
+
document.getElementById('modal-title').textContent = 'Add to Knowledge Base';
|
|
909
|
+
document.getElementById('modal-body').innerHTML = picker;
|
|
910
|
+
document.getElementById('modal').classList.add('open');
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
async function doPromoteToKB(name, category) {
|
|
862
914
|
try {
|
|
863
|
-
const res = await fetch('/api/inbox/
|
|
915
|
+
const res = await fetch('/api/inbox/promote-kb', {
|
|
864
916
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
865
|
-
body: JSON.stringify({ name })
|
|
917
|
+
body: JSON.stringify({ name, category })
|
|
866
918
|
});
|
|
867
919
|
const data = await res.json();
|
|
868
920
|
if (res.ok) {
|
|
921
|
+
closeModal();
|
|
869
922
|
refresh();
|
|
923
|
+
refreshKnowledgeBase();
|
|
870
924
|
} else {
|
|
871
925
|
alert('Failed: ' + (data.error || 'unknown'));
|
|
872
926
|
}
|
|
@@ -1002,7 +1056,9 @@ function openModal(i) {
|
|
|
1002
1056
|
const item = inboxData[i];
|
|
1003
1057
|
if (!item) return;
|
|
1004
1058
|
document.getElementById('modal-title').textContent = item.name;
|
|
1005
|
-
document.getElementById('modal-body').
|
|
1059
|
+
document.getElementById('modal-body').innerHTML =
|
|
1060
|
+
'<div style="margin-bottom:12px"><button class="pr-pager-btn" style="font-size:10px;padding:3px 10px" onclick="promoteToKB(\'' + escHtml(item.name) + '\')">Add to Knowledge Base</button></div>' +
|
|
1061
|
+
'<pre style="white-space:pre-wrap;word-wrap:break-word;margin:0;font-family:Consolas,monospace;font-size:12px;line-height:1.7;color:var(--muted)">' + escHtml(item.content) + '</pre>';
|
|
1006
1062
|
document.getElementById('modal').classList.add('open');
|
|
1007
1063
|
}
|
|
1008
1064
|
function closeModal() {
|
|
@@ -1012,6 +1068,7 @@ function closeModal() {
|
|
|
1012
1068
|
document.getElementById('modal-qa-thread').innerHTML = '';
|
|
1013
1069
|
document.getElementById('modal-qa-input').value = '';
|
|
1014
1070
|
document.getElementById('modal-qa-input').placeholder = 'Ask about this document (or select text first)...';
|
|
1071
|
+
document.getElementById('modal-qa-pill').style.display = 'none';
|
|
1015
1072
|
document.getElementById('ask-selection-btn').style.display = 'none';
|
|
1016
1073
|
}
|
|
1017
1074
|
|
|
@@ -1254,6 +1311,7 @@ async function refresh() {
|
|
|
1254
1311
|
renderMetrics(data.metrics || {});
|
|
1255
1312
|
renderWorkItems(data.workItems || []);
|
|
1256
1313
|
renderSkills(data.skills || []);
|
|
1314
|
+
renderMcpServers(data.mcpServers || []);
|
|
1257
1315
|
// Refresh KB and plans less frequently (every 3rd cycle = ~12s)
|
|
1258
1316
|
if (!window._kbRefreshCount) window._kbRefreshCount = 0;
|
|
1259
1317
|
if (window._kbRefreshCount++ % 3 === 0) { refreshKnowledgeBase(); refreshPlans(); }
|
|
@@ -1282,6 +1340,25 @@ function renderProjects(projects) {
|
|
|
1282
1340
|
|
|
1283
1341
|
}
|
|
1284
1342
|
|
|
1343
|
+
// -- MCP Servers --
|
|
1344
|
+
function renderMcpServers(servers) {
|
|
1345
|
+
const el = document.getElementById('mcp-list');
|
|
1346
|
+
const countEl = document.getElementById('mcp-count');
|
|
1347
|
+
countEl.textContent = servers.length;
|
|
1348
|
+
if (!servers.length) {
|
|
1349
|
+
el.innerHTML = '<p class="empty">No MCP servers found. Add them to <code>~/.claude.json</code> and they\'ll appear here automatically.</p>';
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
el.innerHTML = '<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px">' +
|
|
1353
|
+
servers.map(s =>
|
|
1354
|
+
'<div style="font-size:11px;padding:5px 10px;background:var(--surface2);border:1px solid var(--border);border-radius:6px;color:var(--text)" title="' + escHtml(s.args || s.command) + '">' +
|
|
1355
|
+
escHtml(s.name) +
|
|
1356
|
+
'</div>'
|
|
1357
|
+
).join('') +
|
|
1358
|
+
'</div>' +
|
|
1359
|
+
'<p style="font-size:10px;color:var(--muted);margin:0">Synced from <code style="color:var(--blue)">~/.claude.json</code> — add MCP servers there to make them available to all agents.</p>';
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1285
1362
|
// -- Squad Skills --
|
|
1286
1363
|
function renderSkills(skills) {
|
|
1287
1364
|
const el = document.getElementById('skills-list');
|
|
@@ -1645,11 +1722,11 @@ function cmdParseInput(raw) {
|
|
|
1645
1722
|
if (/--parallel\b/i.test(text)) {
|
|
1646
1723
|
result.branchStrategy = 'parallel';
|
|
1647
1724
|
text = text.replace(/--parallel\b/i, '').trim();
|
|
1648
|
-
} else if (/--
|
|
1725
|
+
} else if (/--stack\b/i.test(text)) {
|
|
1649
1726
|
result.branchStrategy = 'shared-branch';
|
|
1650
|
-
text = text.replace(/--
|
|
1727
|
+
text = text.replace(/--stack\b/i, '').trim();
|
|
1651
1728
|
} else {
|
|
1652
|
-
result.branchStrategy = '
|
|
1729
|
+
result.branchStrategy = 'parallel'; // default — items without depends_on get independent branches
|
|
1653
1730
|
}
|
|
1654
1731
|
} else if (/^\/prd\b/i.test(text)) {
|
|
1655
1732
|
result.intent = 'prd';
|
|
@@ -1712,19 +1789,19 @@ function cmdRenderMeta() {
|
|
|
1712
1789
|
let chips = [];
|
|
1713
1790
|
|
|
1714
1791
|
// Intent chip
|
|
1715
|
-
|
|
1716
|
-
|
|
1792
|
+
if (parsed.intent === 'plan') {
|
|
1793
|
+
const strategy = parsed.branchStrategy || 'parallel';
|
|
1794
|
+
const stratLabel = strategy === 'parallel' ? 'parallel branches' : 'stacked';
|
|
1795
|
+
chips.push('<span class="cmd-chip" style="background:var(--purple,#a855f7);color:#fff">Plan → PRD → Dispatch (' + stratLabel + ')</span>');
|
|
1796
|
+
} else {
|
|
1797
|
+
const intentLabels = { 'work-item': 'Work Item', 'note': 'Note', 'prd': 'PRD Item' };
|
|
1798
|
+
chips.push('<span class="cmd-chip intent">' + intentLabels[parsed.intent] + '</span>');
|
|
1799
|
+
}
|
|
1717
1800
|
|
|
1718
1801
|
// Type chip (only for work items)
|
|
1719
1802
|
if (parsed.intent === 'work-item') {
|
|
1720
1803
|
chips.push('<span class="cmd-chip">' + parsed.type + '</span>');
|
|
1721
1804
|
}
|
|
1722
|
-
// Pipeline chip for /plan
|
|
1723
|
-
if (parsed.intent === 'plan') {
|
|
1724
|
-
const strategy = parsed.branchStrategy || 'shared-branch';
|
|
1725
|
-
const stratLabel = strategy === 'parallel' ? 'parallel branches' : 'shared branch';
|
|
1726
|
-
chips.push('<span class="cmd-chip" style="background:var(--purple,#a855f7);color:#fff">plan → prd → agents (' + stratLabel + ')</span>');
|
|
1727
|
-
}
|
|
1728
1805
|
|
|
1729
1806
|
// Priority chip
|
|
1730
1807
|
chips.push('<span class="cmd-chip priority-' + parsed.priority + '">' + parsed.priority + ' priority</span>');
|
|
@@ -1832,8 +1909,30 @@ function cmdAutoResize() {
|
|
|
1832
1909
|
ta.style.height = Math.min(ta.scrollHeight, 200) + 'px';
|
|
1833
1910
|
}
|
|
1834
1911
|
|
|
1912
|
+
function cmdUpdateHighlight() {
|
|
1913
|
+
const text = document.getElementById('cmd-input').value;
|
|
1914
|
+
const hl = document.getElementById('cmd-highlight');
|
|
1915
|
+
if (!text) { hl.innerHTML = ''; return; }
|
|
1916
|
+
// Escape HTML then wrap tokens with highlight spans
|
|
1917
|
+
let html = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1918
|
+
html = html.replace(/(\/(?:plan|prd|note|decide)\b)/gi, '<span class="hl-cmd">$1</span>');
|
|
1919
|
+
html = html.replace(/(@\w+)/g, '<span class="hl-mention">$1</span>');
|
|
1920
|
+
html = html.replace(/(![a-z]+\b)/gi, '<span class="hl-priority">$1</span>');
|
|
1921
|
+
html = html.replace(/(#\S+)/g, '<span class="hl-project">$1</span>');
|
|
1922
|
+
html = html.replace(/(--(?:stack|parallel)\b)/gi, '<span class="hl-flag">$1</span>');
|
|
1923
|
+
hl.innerHTML = html + '\n'; // trailing newline prevents layout shift
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
function syncHighlightScroll() {
|
|
1927
|
+
const ta = document.getElementById('cmd-input');
|
|
1928
|
+
const hl = document.getElementById('cmd-highlight');
|
|
1929
|
+
hl.scrollTop = ta.scrollTop;
|
|
1930
|
+
hl.scrollLeft = ta.scrollLeft;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1835
1933
|
function cmdInputChanged() {
|
|
1836
1934
|
cmdAutoResize();
|
|
1935
|
+
cmdUpdateHighlight();
|
|
1837
1936
|
cmdRenderMeta();
|
|
1838
1937
|
|
|
1839
1938
|
// Check for @ mention or # project trigger
|
|
@@ -2014,7 +2113,7 @@ async function cmdSubmitPlan(parsed) {
|
|
|
2014
2113
|
title: parsed.title,
|
|
2015
2114
|
description: parsed.description || '',
|
|
2016
2115
|
priority: parsed.priority,
|
|
2017
|
-
branch_strategy: parsed.branchStrategy || '
|
|
2116
|
+
branch_strategy: parsed.branchStrategy || 'parallel',
|
|
2018
2117
|
};
|
|
2019
2118
|
if (parsed.project) body.project = parsed.project;
|
|
2020
2119
|
if (parsed.agents.length === 1) body.agent = parsed.agents[0];
|
|
@@ -2053,36 +2152,98 @@ async function cmdSubmitPrd(parsed) {
|
|
|
2053
2152
|
|
|
2054
2153
|
let _modalDocContext = { title: '', content: '', selection: '' };
|
|
2055
2154
|
|
|
2056
|
-
// Track text selection in
|
|
2155
|
+
// Track text selection anywhere in content areas for the floating "Ask about this" button
|
|
2057
2156
|
document.addEventListener('mouseup', function(e) {
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2157
|
+
// Small delay to let the selection finalize
|
|
2158
|
+
setTimeout(() => {
|
|
2159
|
+
const btn = document.getElementById('ask-selection-btn');
|
|
2160
|
+
if (!btn) return;
|
|
2161
|
+
const sel = window.getSelection();
|
|
2162
|
+
const text = sel?.toString()?.trim();
|
|
2163
|
+
|
|
2164
|
+
if (!text || text.length < 3) {
|
|
2165
|
+
btn.style.display = 'none';
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// Check if selection is in any content area: modal body, detail panel, notes, kb
|
|
2170
|
+
const anchor = sel?.anchorNode;
|
|
2171
|
+
if (!anchor) { btn.style.display = 'none'; return; }
|
|
2172
|
+
|
|
2173
|
+
const modalBody = document.getElementById('modal-body');
|
|
2174
|
+
const detailContent = document.getElementById('detail-content');
|
|
2175
|
+
const isInModal = modalBody?.contains(anchor);
|
|
2176
|
+
const isInDetail = detailContent?.contains(anchor);
|
|
2177
|
+
|
|
2178
|
+
if (isInModal || isInDetail) {
|
|
2179
|
+
_modalDocContext.selection = text;
|
|
2180
|
+
// If we don't have document context yet (e.g. detail panel), capture it
|
|
2181
|
+
if (!_modalDocContext.content && isInDetail) {
|
|
2182
|
+
_modalDocContext.content = detailContent.textContent || '';
|
|
2183
|
+
_modalDocContext.title = document.getElementById('detail-agent-name')?.textContent || 'Agent Detail';
|
|
2184
|
+
}
|
|
2185
|
+
btn.style.display = 'block';
|
|
2186
|
+
// Position near the cursor but within viewport
|
|
2187
|
+
const x = Math.min(e.clientX, window.innerWidth - 140);
|
|
2188
|
+
const y = Math.max(e.clientY - 35, 10);
|
|
2189
|
+
btn.style.left = x + 'px';
|
|
2190
|
+
btn.style.top = y + 'px';
|
|
2191
|
+
btn.style.position = 'fixed';
|
|
2192
|
+
} else {
|
|
2193
|
+
btn.style.display = 'none';
|
|
2194
|
+
}
|
|
2195
|
+
}, 10);
|
|
2071
2196
|
});
|
|
2072
2197
|
|
|
2073
2198
|
function modalAskAboutSelection() {
|
|
2074
2199
|
document.getElementById('ask-selection-btn').style.display = 'none';
|
|
2200
|
+
|
|
2201
|
+
// If the modal isn't open but we have a selection (from detail panel), open modal for Q&A
|
|
2202
|
+
const modal = document.getElementById('modal');
|
|
2203
|
+
if (!modal.classList.contains('open')) {
|
|
2204
|
+
document.getElementById('modal-title').textContent = 'Q&A: ' + (_modalDocContext.title || 'Document');
|
|
2205
|
+
document.getElementById('modal-body').textContent = _modalDocContext.content.slice(0, 3000) + (_modalDocContext.content.length > 3000 ? '\n\n...(truncated for display)' : '');
|
|
2206
|
+
modal.classList.add('open');
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
// Show the selection pill
|
|
2210
|
+
const pill = document.getElementById('modal-qa-pill');
|
|
2211
|
+
const pillText = document.getElementById('modal-qa-pill-text');
|
|
2212
|
+
const sel = _modalDocContext.selection || '';
|
|
2213
|
+
if (sel) {
|
|
2214
|
+
pillText.textContent = sel.slice(0, 80) + (sel.length > 80 ? '...' : '');
|
|
2215
|
+
pill.style.display = 'flex';
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2075
2218
|
const input = document.getElementById('modal-qa-input');
|
|
2076
2219
|
input.value = '';
|
|
2077
|
-
input.placeholder = '
|
|
2220
|
+
input.placeholder = 'What do you want to know about this?';
|
|
2078
2221
|
input.focus();
|
|
2079
2222
|
}
|
|
2080
2223
|
|
|
2224
|
+
function clearQaSelection() {
|
|
2225
|
+
_modalDocContext.selection = '';
|
|
2226
|
+
document.getElementById('modal-qa-pill').style.display = 'none';
|
|
2227
|
+
document.getElementById('modal-qa-input').placeholder = 'Ask about this document (or select text first)...';
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2081
2230
|
async function modalAskSubmit() {
|
|
2082
2231
|
const input = document.getElementById('modal-qa-input');
|
|
2083
2232
|
const question = input.value.trim();
|
|
2084
2233
|
if (!question) return;
|
|
2085
|
-
|
|
2234
|
+
|
|
2235
|
+
// Capture content from modal body if not already set
|
|
2236
|
+
if (!_modalDocContext.content) {
|
|
2237
|
+
const body = document.getElementById('modal-body');
|
|
2238
|
+
if (body) {
|
|
2239
|
+
_modalDocContext.content = body.textContent || body.innerText || '';
|
|
2240
|
+
_modalDocContext.title = document.getElementById('modal-title')?.textContent || '';
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
if (!_modalDocContext.content) {
|
|
2244
|
+
showToast('cmd-toast', 'No document content to ask about', false);
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2086
2247
|
|
|
2087
2248
|
const thread = document.getElementById('modal-qa-thread');
|
|
2088
2249
|
const btn = document.getElementById('modal-qa-btn');
|
|
@@ -2131,6 +2292,8 @@ async function modalAskSubmit() {
|
|
|
2131
2292
|
}
|
|
2132
2293
|
|
|
2133
2294
|
_modalDocContext.selection = ''; // Clear selection after asking
|
|
2295
|
+
document.getElementById('modal-qa-pill').style.display = 'none';
|
|
2296
|
+
document.getElementById('modal-qa-input').placeholder = 'Ask another question...';
|
|
2134
2297
|
btn.disabled = false;
|
|
2135
2298
|
thread.scrollTop = thread.scrollHeight;
|
|
2136
2299
|
input.focus();
|
package/dashboard.js
CHANGED
|
@@ -442,6 +442,20 @@ function getWorkItems() {
|
|
|
442
442
|
return allItems;
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
function getMcpServers() {
|
|
446
|
+
try {
|
|
447
|
+
const home = process.env.USERPROFILE || process.env.HOME || '';
|
|
448
|
+
const claudeJsonPath = path.join(home, '.claude.json');
|
|
449
|
+
const data = JSON.parse(safeRead(claudeJsonPath) || '{}');
|
|
450
|
+
const servers = data.mcpServers || {};
|
|
451
|
+
return Object.entries(servers).map(([name, cfg]) => ({
|
|
452
|
+
name,
|
|
453
|
+
command: cfg.command || '',
|
|
454
|
+
args: (cfg.args || []).slice(-1)[0] || '',
|
|
455
|
+
}));
|
|
456
|
+
} catch { return []; }
|
|
457
|
+
}
|
|
458
|
+
|
|
445
459
|
function getStatus() {
|
|
446
460
|
const prdInfo = getPrdInfo();
|
|
447
461
|
return {
|
|
@@ -458,6 +472,7 @@ function getStatus() {
|
|
|
458
472
|
metrics: getMetrics(),
|
|
459
473
|
workItems: getWorkItems(),
|
|
460
474
|
skills: getSkills(),
|
|
475
|
+
mcpServers: getMcpServers(),
|
|
461
476
|
projects: PROJECTS.map(p => ({ name: p.name, path: p.localPath, description: p.description || '' })),
|
|
462
477
|
timestamp: new Date().toISOString(),
|
|
463
478
|
};
|
|
@@ -758,7 +773,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
758
773
|
priority: body.priority || 'high', description: body.description || '',
|
|
759
774
|
status: 'pending', created: new Date().toISOString(), createdBy: 'dashboard',
|
|
760
775
|
chain: 'plan-to-prd',
|
|
761
|
-
branchStrategy: body.branch_strategy || '
|
|
776
|
+
branchStrategy: body.branch_strategy || 'parallel',
|
|
762
777
|
};
|
|
763
778
|
if (body.project) item.project = body.project;
|
|
764
779
|
if (body.agent) item.agent = body.agent;
|
|
@@ -1095,7 +1110,12 @@ ${body.question}
|
|
|
1095
1110
|
|
|
1096
1111
|
## Instructions
|
|
1097
1112
|
|
|
1098
|
-
Answer concisely and directly.
|
|
1113
|
+
Answer concisely and directly. Follow these rules:
|
|
1114
|
+
1. **Cite sources**: When the document includes file paths, line numbers, PR URLs, or code references — include them in your answer. Format: \`(source: path/to/file.ts:42)\`
|
|
1115
|
+
2. **Quote the document**: Reference the exact text that supports your answer.
|
|
1116
|
+
3. **Flag missing sources**: If the document makes a claim without a source reference (no file path, no PR link, no line number), say: "Note: this claim has no source reference in the document — verify independently."
|
|
1117
|
+
4. **Be honest**: If the document doesn't contain the answer, say so clearly. Don't speculate beyond what's written.
|
|
1118
|
+
5. Use markdown formatting.`;
|
|
1099
1119
|
|
|
1100
1120
|
const sysPrompt = 'You are a concise technical assistant. Answer based on the document provided. No preamble.';
|
|
1101
1121
|
|
|
@@ -1189,6 +1209,45 @@ Answer concisely and directly. Reference specific parts of the document. If the
|
|
|
1189
1209
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
1190
1210
|
}
|
|
1191
1211
|
|
|
1212
|
+
// POST /api/inbox/promote-kb — promote an inbox item to the knowledge base
|
|
1213
|
+
if (req.method === 'POST' && req.url === '/api/inbox/promote-kb') {
|
|
1214
|
+
try {
|
|
1215
|
+
const body = await readBody(req);
|
|
1216
|
+
const { name, category } = body;
|
|
1217
|
+
if (!name) return jsonReply(res, 400, { error: 'name required' });
|
|
1218
|
+
const validCategories = ['architecture', 'conventions', 'project-notes', 'build-reports', 'reviews'];
|
|
1219
|
+
if (!category || !validCategories.includes(category)) {
|
|
1220
|
+
return jsonReply(res, 400, { error: 'category required: ' + validCategories.join(', ') });
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const inboxPath = path.join(SQUAD_DIR, 'notes', 'inbox', name);
|
|
1224
|
+
const content = safeRead(inboxPath);
|
|
1225
|
+
if (!content) return jsonReply(res, 404, { error: 'inbox item not found' });
|
|
1226
|
+
|
|
1227
|
+
// Add frontmatter if not present
|
|
1228
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1229
|
+
let kbContent = content;
|
|
1230
|
+
if (!content.startsWith('---')) {
|
|
1231
|
+
const titleMatch = content.match(/^#+ (.+)$/m);
|
|
1232
|
+
const title = titleMatch ? titleMatch[1].trim() : name.replace('.md', '');
|
|
1233
|
+
kbContent = `---\ntitle: ${title}\ncategory: ${category}\ndate: ${today}\nsource: inbox/${name}\n---\n\n${content}`;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Write to knowledge base
|
|
1237
|
+
const kbDir = path.join(SQUAD_DIR, 'knowledge', category);
|
|
1238
|
+
if (!fs.existsSync(kbDir)) fs.mkdirSync(kbDir, { recursive: true });
|
|
1239
|
+
const kbFile = path.join(kbDir, name);
|
|
1240
|
+
safeWrite(kbFile, kbContent);
|
|
1241
|
+
|
|
1242
|
+
// Move inbox item to archive
|
|
1243
|
+
const archiveDir = path.join(SQUAD_DIR, 'notes', 'archive');
|
|
1244
|
+
if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
|
|
1245
|
+
try { fs.renameSync(inboxPath, path.join(archiveDir, `kb-${category}-${name}`)); } catch {}
|
|
1246
|
+
|
|
1247
|
+
return jsonReply(res, 200, { ok: true, category, file: name });
|
|
1248
|
+
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1192
1251
|
// POST /api/inbox/open — open inbox file in Windows explorer
|
|
1193
1252
|
if (req.method === 'POST' && req.url === '/api/inbox/open') {
|
|
1194
1253
|
try {
|
|
@@ -1335,6 +1394,54 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
1335
1394
|
console.log(` Could not auto-open browser: ${e.message}`);
|
|
1336
1395
|
console.log(` Please open http://localhost:${PORT} manually.`);
|
|
1337
1396
|
}
|
|
1397
|
+
|
|
1398
|
+
// ─── Engine Watchdog ─────────────────────────────────────────────────────
|
|
1399
|
+
// Every 30s, check if engine PID is alive. If dead but control.json says
|
|
1400
|
+
// running, auto-restart it. Prevents silent engine death.
|
|
1401
|
+
const { execSync, spawn: cpSpawn } = require('child_process');
|
|
1402
|
+
setInterval(() => {
|
|
1403
|
+
try {
|
|
1404
|
+
const control = getEngineState();
|
|
1405
|
+
if (control.state !== 'running' || !control.pid) return;
|
|
1406
|
+
|
|
1407
|
+
// Check if PID is alive
|
|
1408
|
+
let alive = false;
|
|
1409
|
+
try {
|
|
1410
|
+
if (process.platform === 'win32') {
|
|
1411
|
+
const out = execSync(`tasklist /FI "PID eq ${control.pid}" /NH`, { encoding: 'utf8', timeout: 3000 });
|
|
1412
|
+
alive = out.includes(String(control.pid));
|
|
1413
|
+
} else {
|
|
1414
|
+
process.kill(control.pid, 0); // signal 0 = check existence
|
|
1415
|
+
alive = true;
|
|
1416
|
+
}
|
|
1417
|
+
} catch { alive = false; }
|
|
1418
|
+
|
|
1419
|
+
if (!alive) {
|
|
1420
|
+
console.log(`[watchdog] Engine PID ${control.pid} is dead — auto-restarting...`);
|
|
1421
|
+
|
|
1422
|
+
// Set state to stopped first
|
|
1423
|
+
const controlPath = path.join(SQUAD_DIR, 'engine', 'control.json');
|
|
1424
|
+
safeWrite(controlPath, { state: 'stopped', pid: null, crashed_at: new Date().toISOString() });
|
|
1425
|
+
|
|
1426
|
+
// Restart engine
|
|
1427
|
+
const childEnv = { ...process.env };
|
|
1428
|
+
for (const key of Object.keys(childEnv)) {
|
|
1429
|
+
if (key === 'CLAUDECODE' || key.startsWith('CLAUDE_CODE') || key.startsWith('CLAUDECODE_')) delete childEnv[key];
|
|
1430
|
+
}
|
|
1431
|
+
const engineProc = cpSpawn(process.execPath, [path.join(SQUAD_DIR, 'engine.js'), 'start'], {
|
|
1432
|
+
cwd: SQUAD_DIR,
|
|
1433
|
+
stdio: 'ignore',
|
|
1434
|
+
detached: true,
|
|
1435
|
+
env: childEnv,
|
|
1436
|
+
});
|
|
1437
|
+
engineProc.unref();
|
|
1438
|
+
console.log(`[watchdog] Engine restarted (new PID: ${engineProc.pid})`);
|
|
1439
|
+
}
|
|
1440
|
+
} catch (e) {
|
|
1441
|
+
console.error(`[watchdog] Error: ${e.message}`);
|
|
1442
|
+
}
|
|
1443
|
+
}, 30000);
|
|
1444
|
+
console.log(` Engine watchdog: active (checks every 30s)`);
|
|
1338
1445
|
});
|
|
1339
1446
|
|
|
1340
1447
|
server.on('error', e => {
|
|
@@ -87,7 +87,7 @@ When the engine restarts, the in-memory `activeProcesses` Map is lost. Active di
|
|
|
87
87
|
|
|
88
88
|
**Dispatch 1773292681199** — Dallas, central work item, auto-route:
|
|
89
89
|
|
|
90
|
-
1. Engine spawns `node spawn-agent.js prompt.md sysprompt.md --output-format stream-json --verbose --permission-mode bypassPermissions
|
|
90
|
+
1. Engine spawns `node spawn-agent.js prompt.md sysprompt.md --output-format stream-json --verbose --permission-mode bypassPermissions`
|
|
91
91
|
2. spawn-agent.js resolves `cli.js`, spawns `node cli.js -p --system-prompt <content> ...`
|
|
92
92
|
3. Prompt piped via stdin — no shell interpretation
|
|
93
93
|
4. MCP servers connect (azure-ado, azure-kusto, mobile, DevBox)
|
package/engine.js
CHANGED
|
@@ -212,39 +212,6 @@ function getInboxFiles() {
|
|
|
212
212
|
try { return fs.readdirSync(INBOX_DIR).filter(f => f.endsWith('.md')); } catch { return []; }
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
// ─── MCP Server Sync ─────────────────────────────────────────────────────────
|
|
216
|
-
|
|
217
|
-
const MCP_SERVERS_PATH = path.join(SQUAD_DIR, 'mcp-servers.json');
|
|
218
|
-
|
|
219
|
-
function syncMcpServers() {
|
|
220
|
-
// Sync MCP servers from ~/.claude.json into squad's mcp-servers.json
|
|
221
|
-
const home = process.env.USERPROFILE || process.env.HOME || '';
|
|
222
|
-
const claudeJsonPath = path.join(home, '.claude.json');
|
|
223
|
-
|
|
224
|
-
if (!fs.existsSync(claudeJsonPath)) {
|
|
225
|
-
console.log(' ~/.claude.json not found — skipping MCP sync');
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
const claudeJson = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8'));
|
|
231
|
-
const servers = claudeJson.mcpServers;
|
|
232
|
-
if (!servers || Object.keys(servers).length === 0) {
|
|
233
|
-
console.log(' No MCP servers found in ~/.claude.json');
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
safeWrite(MCP_SERVERS_PATH, { mcpServers: servers });
|
|
238
|
-
const names = Object.keys(servers);
|
|
239
|
-
console.log(` MCP servers synced (${names.length}): ${names.join(', ')}`);
|
|
240
|
-
log('info', `Synced ${names.length} MCP servers from ~/.claude.json: ${names.join(', ')}`);
|
|
241
|
-
return true;
|
|
242
|
-
} catch (e) {
|
|
243
|
-
console.log(` MCP sync failed: ${e.message}`);
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
215
|
// ─── Skills ──────────────────────────────────────────────────────────────────
|
|
249
216
|
|
|
250
217
|
const SKILLS_DIR = path.join(SQUAD_DIR, 'skills');
|
|
@@ -313,6 +280,32 @@ function getSkillIndex() {
|
|
|
313
280
|
} catch { return ''; }
|
|
314
281
|
}
|
|
315
282
|
|
|
283
|
+
function getKnowledgeBaseIndex() {
|
|
284
|
+
try {
|
|
285
|
+
const kbDir = path.join(SQUAD_DIR, 'knowledge');
|
|
286
|
+
if (!fs.existsSync(kbDir)) return '';
|
|
287
|
+
const categories = ['architecture', 'conventions', 'project-notes', 'build-reports', 'reviews'];
|
|
288
|
+
let entries = [];
|
|
289
|
+
for (const cat of categories) {
|
|
290
|
+
const catDir = path.join(kbDir, cat);
|
|
291
|
+
const files = safeReadDir(catDir).filter(f => f.endsWith('.md'));
|
|
292
|
+
for (const f of files) {
|
|
293
|
+
const content = safeRead(path.join(catDir, f)) || '';
|
|
294
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
295
|
+
const title = titleMatch ? titleMatch[1].trim() : f.replace(/\.md$/, '');
|
|
296
|
+
entries.push({ cat, file: f, title });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (entries.length === 0) return '';
|
|
300
|
+
let index = '## Knowledge Base Reference\n\n';
|
|
301
|
+
index += 'Deep-reference docs from past work. Read the file if you need detail.\n\n';
|
|
302
|
+
for (const e of entries) {
|
|
303
|
+
index += `- \`knowledge/${e.cat}/${e.file}\` — ${e.title}\n`;
|
|
304
|
+
}
|
|
305
|
+
return index + '\n';
|
|
306
|
+
} catch { return ''; }
|
|
307
|
+
}
|
|
308
|
+
|
|
316
309
|
function getPrs(project) {
|
|
317
310
|
if (project) {
|
|
318
311
|
const prPath = project.workSources?.pullRequests?.path;
|
|
@@ -412,7 +405,8 @@ function renderPlaybook(type, vars) {
|
|
|
412
405
|
content += `- What you learned about the codebase\n`;
|
|
413
406
|
content += `- Patterns you discovered or established\n`;
|
|
414
407
|
content += `- Gotchas or warnings for future agents\n`;
|
|
415
|
-
content += `- Conventions to follow\n
|
|
408
|
+
content += `- Conventions to follow\n`;
|
|
409
|
+
content += `- **SOURCE REFERENCES for every finding** — file paths with line numbers, PR URLs, API endpoints, config keys. Format: \`(source: path/to/file.ts:42)\` or \`(source: PR-12345)\`. Without references, findings cannot be verified.\n\n`;
|
|
416
410
|
content += `### Skill Extraction (IMPORTANT)\n\n`;
|
|
417
411
|
content += `If during this task you discovered a **repeatable workflow** — a multi-step procedure, workaround, build process, or pattern that other agents should follow in similar situations — output it as a fenced skill block. The engine will automatically extract it.\n\n`;
|
|
418
412
|
content += `Format your skill as a fenced code block with the \`skill\` language tag:\n\n`;
|
|
@@ -574,6 +568,12 @@ function buildAgentContext(agentId, config, project) {
|
|
|
574
568
|
context += skillIndex + '\n';
|
|
575
569
|
}
|
|
576
570
|
|
|
571
|
+
// Knowledge base index (paths + titles only — agents can Read if needed)
|
|
572
|
+
const kbIndex = getKnowledgeBaseIndex();
|
|
573
|
+
if (kbIndex) {
|
|
574
|
+
context += kbIndex + '\n';
|
|
575
|
+
}
|
|
576
|
+
|
|
577
577
|
// Team notes (the big one — can be 50KB)
|
|
578
578
|
if (notes) {
|
|
579
579
|
context += `## Team Notes (MUST READ)\n\n${notes}\n\n`;
|
|
@@ -669,11 +669,8 @@ function spawnAgent(dispatchItem, config) {
|
|
|
669
669
|
args.push('--allowedTools', claudeConfig.allowedTools);
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
-
// MCP servers
|
|
673
|
-
|
|
674
|
-
if (fs.existsSync(mcpConfigPath)) {
|
|
675
|
-
args.push('--mcp-config', mcpConfigPath);
|
|
676
|
-
}
|
|
672
|
+
// MCP servers: agents inherit from ~/.claude.json directly as Claude Code processes.
|
|
673
|
+
// No --mcp-config needed — avoids redundant config and ensures agents always have latest servers.
|
|
677
674
|
|
|
678
675
|
log('info', `Spawning agent: ${agentId} (${id}) in ${cwd}`);
|
|
679
676
|
log('info', `Task type: ${type} | Branch: ${branchName || 'none'}`);
|
|
@@ -3980,7 +3977,7 @@ async function tickInner() {
|
|
|
3980
3977
|
// 2. Consolidate inbox
|
|
3981
3978
|
consolidateInbox(config);
|
|
3982
3979
|
|
|
3983
|
-
// 2.5. Periodic cleanup (every 10 ticks = ~5 minutes)
|
|
3980
|
+
// 2.5. Periodic cleanup + MCP sync (every 10 ticks = ~5 minutes)
|
|
3984
3981
|
if (tickCount % 10 === 0) {
|
|
3985
3982
|
runCleanup(config);
|
|
3986
3983
|
}
|
|
@@ -4065,9 +4062,6 @@ const commands = {
|
|
|
4065
4062
|
const config = getConfig();
|
|
4066
4063
|
const interval = config.engine?.tickInterval || 60000;
|
|
4067
4064
|
|
|
4068
|
-
// Sync MCP servers from Claude Code
|
|
4069
|
-
syncMcpServers();
|
|
4070
|
-
|
|
4071
4065
|
// Validate project paths
|
|
4072
4066
|
const projects = getProjects(config);
|
|
4073
4067
|
for (const p of projects) {
|
|
@@ -4542,7 +4536,7 @@ const commands = {
|
|
|
4542
4536
|
},
|
|
4543
4537
|
|
|
4544
4538
|
'mcp-sync'() {
|
|
4545
|
-
|
|
4539
|
+
console.log('MCP servers are read directly from ~/.claude.json — no sync needed.');
|
|
4546
4540
|
},
|
|
4547
4541
|
|
|
4548
4542
|
discover() {
|
package/package.json
CHANGED
package/playbooks/explore.md
CHANGED
|
@@ -38,6 +38,7 @@ Write your findings to `{{team_root}}/notes/inbox/{{agent_id}}-explore-{{task_id
|
|
|
38
38
|
- **Dependencies**: what depends on what
|
|
39
39
|
- **Gaps**: anything missing, broken, or unclear
|
|
40
40
|
- **Recommendations**: suggestions for the team
|
|
41
|
+
- **Source References**: for EVERY finding, include the source — file paths, line numbers, PR URLs, API endpoints, config keys. Format: `(source: path/to/file.ts:42)` or `(source: PR-12345)`. This is critical — other agents and humans need to verify your findings.
|
|
41
42
|
|
|
42
43
|
### 5. Create Deliverable (if the task asks for one)
|
|
43
44
|
If the task asks you to write a design doc, architecture doc, or any durable artifact:
|