@yemi33/squad 0.1.10 → 0.1.11

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/dashboard.html CHANGED
@@ -243,6 +243,28 @@
243
243
  .cmd-hints::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
244
244
  .cmd-hints code { color: var(--blue); font-size: 10px; background: var(--surface2); padding: 1px 5px; border-radius: 3px; }
245
245
 
246
+ .cmd-history-btn {
247
+ background: none; border: 1px solid var(--border); color: var(--muted); font-size: 11px;
248
+ cursor: pointer; padding: 3px 10px; border-radius: 4px; transition: all 0.2s;
249
+ }
250
+ .cmd-history-btn:hover { color: var(--text); border-color: var(--text); }
251
+ .cmd-history-list { list-style: none; padding: 0; margin: 0; }
252
+ .cmd-history-item {
253
+ display: flex; align-items: flex-start; gap: 10px; padding: 10px 0;
254
+ border-bottom: 1px solid var(--border);
255
+ }
256
+ .cmd-history-item:last-child { border-bottom: none; }
257
+ .cmd-history-item-body { flex: 1; min-width: 0; }
258
+ .cmd-history-item-text { font-size: 12px; color: var(--text); white-space: pre-wrap; word-break: break-word; font-family: Consolas, monospace; }
259
+ .cmd-history-item-meta { font-size: 10px; color: var(--muted); margin-top: 3px; display: flex; gap: 8px; }
260
+ .cmd-history-item-meta .chip { background: var(--surface2); padding: 1px 6px; border-radius: 3px; }
261
+ .cmd-history-resubmit {
262
+ background: var(--surface2); border: 1px solid var(--border); color: var(--blue);
263
+ font-size: 11px; cursor: pointer; padding: 4px 10px; border-radius: 4px; white-space: nowrap;
264
+ transition: all 0.2s; flex-shrink: 0;
265
+ }
266
+ .cmd-history-resubmit:hover { background: rgba(88,166,255,0.1); border-color: var(--blue); }
267
+ .cmd-history-empty { color: var(--muted); font-size: 12px; padding: 20px 0; text-align: center; }
246
268
  .cmd-toast {
247
269
  display: none; padding: 8px 14px; border-radius: 4px; font-size: 12px;
248
270
  margin-top: 10px; animation: fadeIn 0.3s;
@@ -403,6 +425,7 @@
403
425
  <span><code>/note</code> team note</span>
404
426
  <span><code>/prd</code> PRD item</span>
405
427
  <span><code>#project</code> target project</span>
428
+ <button class="cmd-history-btn" onclick="cmdShowHistory()">Past Commands</button>
406
429
  </div>
407
430
  <div class="cmd-toast" id="cmd-toast"></div>
408
431
  </section>
@@ -1662,6 +1685,42 @@ function cmdKeyDown(e) {
1662
1685
  }
1663
1686
  }
1664
1687
 
1688
+ // ArrowUp/ArrowDown to navigate command history (only when no popup visible)
1689
+ if (e.key === 'ArrowUp' && !isPopupVisible) {
1690
+ const input = document.getElementById('cmd-input');
1691
+ // Only intercept if cursor is at start of input (or input is single-line)
1692
+ if (input.selectionStart === 0 || !input.value.includes('\n')) {
1693
+ const history = cmdGetHistory();
1694
+ if (history.length === 0) return;
1695
+ if (_cmdHistoryIdx === -1) _cmdHistoryDraft = input.value; // Save current draft
1696
+ if (_cmdHistoryIdx < history.length - 1) {
1697
+ _cmdHistoryIdx++;
1698
+ input.value = history[_cmdHistoryIdx].text;
1699
+ cmdAutoResize();
1700
+ cmdRenderMeta();
1701
+ e.preventDefault();
1702
+ // Move cursor to end
1703
+ setTimeout(() => input.setSelectionRange(input.value.length, input.value.length), 0);
1704
+ }
1705
+ return;
1706
+ }
1707
+ }
1708
+ if (e.key === 'ArrowDown' && !isPopupVisible) {
1709
+ const input = document.getElementById('cmd-input');
1710
+ if (input.selectionStart === input.value.length || !input.value.includes('\n')) {
1711
+ if (_cmdHistoryIdx >= 0) {
1712
+ _cmdHistoryIdx--;
1713
+ const history = cmdGetHistory();
1714
+ input.value = _cmdHistoryIdx >= 0 ? history[_cmdHistoryIdx].text : (_cmdHistoryDraft || '');
1715
+ cmdAutoResize();
1716
+ cmdRenderMeta();
1717
+ e.preventDefault();
1718
+ setTimeout(() => input.setSelectionRange(input.value.length, input.value.length), 0);
1719
+ }
1720
+ return;
1721
+ }
1722
+ }
1723
+
1665
1724
  // Ctrl+Enter to submit
1666
1725
  if (e.key === 'Enter' && e.ctrlKey) {
1667
1726
  e.preventDefault();
@@ -1689,6 +1748,10 @@ async function cmdSubmit() {
1689
1748
  } else {
1690
1749
  await cmdSubmitWorkItem(parsed);
1691
1750
  }
1751
+ // Save to history on success
1752
+ cmdSaveHistory(raw, parsed.intent);
1753
+ _cmdHistoryIdx = -1;
1754
+ _cmdHistoryDraft = '';
1692
1755
  // Clear on success
1693
1756
  input.value = '';
1694
1757
  cmdAutoResize();
@@ -1781,6 +1844,75 @@ async function cmdSubmitPrd(parsed) {
1781
1844
  const projLabel = (parsed.projects || []).length > 0 ? ' (' + parsed.projects.join(', ') + ')' : '';
1782
1845
  showToast('cmd-toast', 'PRD item ' + (data.id || id) + ' added' + projLabel, true);
1783
1846
  }
1847
+ // ─── Command History ──────────────────────────────────────────────────────────
1848
+ const CMD_HISTORY_KEY = 'squad-cmd-history';
1849
+ const CMD_HISTORY_MAX = 50;
1850
+ let _cmdHistoryIdx = -1; // -1 = not browsing history
1851
+ let _cmdHistoryDraft = ''; // saves current draft when browsing
1852
+
1853
+ function cmdGetHistory() {
1854
+ try { return JSON.parse(localStorage.getItem(CMD_HISTORY_KEY) || '[]'); } catch { return []; }
1855
+ }
1856
+
1857
+ function cmdSaveHistory(raw, intent) {
1858
+ const history = cmdGetHistory();
1859
+ history.unshift({ text: raw, intent, timestamp: new Date().toISOString() });
1860
+ if (history.length > CMD_HISTORY_MAX) history.length = CMD_HISTORY_MAX;
1861
+ localStorage.setItem(CMD_HISTORY_KEY, JSON.stringify(history));
1862
+ }
1863
+
1864
+ function cmdShowHistory() {
1865
+ const history = cmdGetHistory();
1866
+ const title = document.getElementById('modal-title');
1867
+ const body = document.getElementById('modal-body');
1868
+ title.textContent = 'Past Commands (' + history.length + ')';
1869
+
1870
+ if (history.length === 0) {
1871
+ body.innerHTML = '<div class="cmd-history-empty">No commands yet. Submit something from the command center.</div>';
1872
+ } else {
1873
+ const intentColors = { 'work-item': 'var(--blue)', 'note': 'var(--green)', 'plan': 'var(--purple,#a855f7)', 'prd': 'var(--yellow,#d29922)' };
1874
+ const intentLabels = { 'work-item': 'Work Item', 'note': 'Note', 'plan': 'Plan', 'prd': 'PRD' };
1875
+ body.innerHTML = '<ul class="cmd-history-list">' + history.map((item, i) => {
1876
+ const date = new Date(item.timestamp);
1877
+ const ago = timeSinceStr(date);
1878
+ const intentLabel = intentLabels[item.intent] || item.intent || 'work-item';
1879
+ const intentColor = intentColors[item.intent] || 'var(--blue)';
1880
+ return '<li class="cmd-history-item">' +
1881
+ '<div class="cmd-history-item-body">' +
1882
+ '<div class="cmd-history-item-text">' + escHtml(item.text) + '</div>' +
1883
+ '<div class="cmd-history-item-meta">' +
1884
+ '<span class="chip" style="color:' + intentColor + '">' + intentLabel + '</span>' +
1885
+ '<span>' + ago + '</span>' +
1886
+ '<span>' + date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}) + '</span>' +
1887
+ '</div>' +
1888
+ '</div>' +
1889
+ '<button class="cmd-history-resubmit" onclick="cmdResubmit(' + i + ')">Resubmit</button>' +
1890
+ '</li>';
1891
+ }).join('') + '</ul>';
1892
+ }
1893
+
1894
+ document.getElementById('modal').classList.add('open');
1895
+ }
1896
+
1897
+ function cmdResubmit(idx) {
1898
+ const history = cmdGetHistory();
1899
+ const item = history[idx];
1900
+ if (!item) return;
1901
+ document.getElementById('modal').classList.remove('open');
1902
+ const input = document.getElementById('cmd-input');
1903
+ input.value = item.text;
1904
+ cmdAutoResize();
1905
+ cmdRenderMeta();
1906
+ input.focus();
1907
+ }
1908
+
1909
+ function timeSinceStr(date) {
1910
+ const s = Math.floor((Date.now() - date.getTime()) / 1000);
1911
+ if (s < 60) return s + 's ago';
1912
+ if (s < 3600) return Math.floor(s / 60) + 'm ago';
1913
+ if (s < 86400) return Math.floor(s / 3600) + 'h ago';
1914
+ return Math.floor(s / 86400) + 'd ago';
1915
+ }
1784
1916
  </script>
1785
1917
  </body>
1786
1918
  </html>
@@ -80,9 +80,13 @@ try {
80
80
  if (Buffer.byteLength(sysPrompt) < 30000) {
81
81
  actualArgs = ['-p', '--system-prompt', sysPrompt, ...extraArgs];
82
82
  } else {
83
- // Too large for inline — prepend system prompt to the user prompt via stdin
84
- actualArgs = ['-p', ...extraArgs];
85
- // We'll inject system prompt into stdin along with the prompt below
83
+ // Too large for inline — split: short identity as --system-prompt, rest prepended to user prompt
84
+ // Extract first section (agent identity) as the system prompt, rest goes into user context
85
+ const splitIdx = sysPrompt.indexOf('\n---\n');
86
+ const shortSys = splitIdx > 0 && splitIdx < 2000
87
+ ? sysPrompt.slice(0, splitIdx)
88
+ : sysPrompt.slice(0, 1500) + '\n\n[System prompt truncated for CLI arg limit — full context provided below in user message]';
89
+ actualArgs = ['-p', '--system-prompt', shortSys, ...extraArgs];
86
90
  }
87
91
  }
88
92
  } catch {
@@ -100,9 +104,10 @@ fs.appendFileSync(debugPath, `PID=${proc.pid || 'none'}\nargs=${actualArgs.join(
100
104
  const pidFile = promptFile.replace(/prompt-/, 'pid-').replace(/\.md$/, '.pid');
101
105
  fs.writeFileSync(pidFile, String(proc.pid || ''));
102
106
 
103
- // Send prompt via stdin — if system prompt couldn't be passed via args, prepend it
104
- if (!actualArgs.includes('--system-prompt') && !actualArgs.includes('--system-prompt-file')) {
105
- proc.stdin.write(`<system>\n${sysPrompt}\n</system>\n\n${prompt}`);
107
+ // Send prompt via stdin — if system prompt was truncated, prepend the full context
108
+ if (Buffer.byteLength(sysPrompt) >= 30000) {
109
+ // System prompt was too large for CLI — prepend full context to user prompt
110
+ proc.stdin.write(`## Full Agent Context\n\n${sysPrompt}\n\n---\n\n## Your Task\n\n${prompt}`);
106
111
  } else {
107
112
  proc.stdin.write(prompt);
108
113
  }
package/engine.js CHANGED
@@ -506,10 +506,10 @@ function getRepoHostToolRule(project) {
506
506
 
507
507
  // ─── System Prompt Builder ──────────────────────────────────────────────────
508
508
 
509
+ // Lean system prompt: agent identity + rules only (~2-4KB, never grows)
509
510
  function buildSystemPrompt(agentId, config, project) {
510
511
  const agent = config.agents[agentId];
511
512
  const charter = getAgentCharter(agentId);
512
- const notes = getNotes();
513
513
  project = project || config.project || {};
514
514
 
515
515
  let prompt = '';
@@ -519,57 +519,66 @@ function buildSystemPrompt(agentId, config, project) {
519
519
  prompt += `Agent ID: ${agentId}\n`;
520
520
  prompt += `Skills: ${(agent.skills || []).join(', ')}\n\n`;
521
521
 
522
- // Charter (detailed instructions)
522
+ // Charter (detailed instructions — typically 1-2KB)
523
523
  if (charter) {
524
524
  prompt += `## Your Charter\n\n${charter}\n\n`;
525
525
  }
526
526
 
527
- // Agent history (past tasks)
528
- const history = safeRead(path.join(AGENTS_DIR, agentId, 'history.md'));
529
- if (history && history.trim() !== '# Agent History') {
530
- prompt += `## Your Recent History\n\n${history}\n\n`;
531
- }
532
-
533
- // Project context
527
+ // Project context (fixed size)
534
528
  prompt += `## Project: ${project.name || 'Unknown Project'}\n\n`;
535
529
  prompt += `- Repo: ${project.repoName || 'Unknown'} (${project.adoOrg || 'Unknown'}/${project.adoProject || 'Unknown'})\n`;
536
530
  prompt += `- Repo ID: ${project.repositoryId || ''}\n`;
537
531
  prompt += `- Repo host: ${getRepoHostLabel(project)}\n`;
538
532
  prompt += `- Main branch: ${project.mainBranch || 'main'}\n\n`;
539
533
 
540
- // Project conventions (from CLAUDE.md)
541
- if (project.localPath) {
542
- const claudeMd = safeRead(path.join(project.localPath, 'CLAUDE.md'));
543
- if (claudeMd && claudeMd.trim()) {
544
- // Truncate to 4KB to avoid bloating the system prompt
545
- const truncated = claudeMd.length > 4096 ? claudeMd.slice(0, 4096) + '\n\n...(truncated)' : claudeMd;
546
- prompt += `## Project Conventions (from CLAUDE.md)\n\n${truncated}\n\n`;
547
- }
548
- }
549
-
550
- // Critical rules
534
+ // Critical rules (fixed size)
551
535
  prompt += `## Critical Rules\n\n`;
552
536
  prompt += `1. Use git worktrees — NEVER checkout on main working tree\n`;
553
537
  prompt += `2. ${getRepoHostToolRule(project)}\n`;
554
- prompt += `3. Follow the project conventions above (from CLAUDE.md) if present\n`;
538
+ prompt += `3. Follow the project conventions in CLAUDE.md if present\n`;
555
539
  prompt += `4. Write learnings to: ${SQUAD_DIR}/notes/inbox/${agentId}-${dateStamp()}.md\n`;
556
540
  prompt += `5. Do NOT write to agents/*/status.json — the engine manages agent status automatically\n`;
557
541
  prompt += `6. If you discover a repeatable workflow, save it as a skill:\n`;
558
542
  prompt += ` - Squad-wide: \`${SKILLS_DIR}/<name>.md\` (no PR needed)\n`;
559
543
  prompt += ` - Project-specific: \`<project>/.claude/skills/<name>.md\` (requires a PR since it modifies the repo)\n\n`;
560
544
 
545
+ return prompt;
546
+ }
547
+
548
+ // Bulk context: history, notes, conventions, skills — prepended to user/task prompt.
549
+ // This is the content that grows over time and would bloat the system prompt.
550
+ function buildAgentContext(agentId, config, project) {
551
+ project = project || config.project || {};
552
+ const notes = getNotes();
553
+ let context = '';
554
+
555
+ // Agent history (past tasks)
556
+ const history = safeRead(path.join(AGENTS_DIR, agentId, 'history.md'));
557
+ if (history && history.trim() !== '# Agent History') {
558
+ context += `## Your Recent History\n\n${history}\n\n`;
559
+ }
560
+
561
+ // Project conventions (from CLAUDE.md)
562
+ if (project.localPath) {
563
+ const claudeMd = safeRead(path.join(project.localPath, 'CLAUDE.md'));
564
+ if (claudeMd && claudeMd.trim()) {
565
+ const truncated = claudeMd.length > 8192 ? claudeMd.slice(0, 8192) + '\n\n...(truncated)' : claudeMd;
566
+ context += `## Project Conventions (from CLAUDE.md)\n\n${truncated}\n\n`;
567
+ }
568
+ }
569
+
561
570
  // Skills
562
571
  const skillIndex = getSkillIndex();
563
572
  if (skillIndex) {
564
- prompt += skillIndex + '\n';
573
+ context += skillIndex + '\n';
565
574
  }
566
575
 
567
- // Team notes
576
+ // Team notes (the big one — can be 50KB)
568
577
  if (notes) {
569
- prompt += `## Team Notes\n\n${notes}\n\n`;
578
+ context += `## Team Notes (MUST READ)\n\n${notes}\n\n`;
570
579
  }
571
580
 
572
- return prompt;
581
+ return context;
573
582
  }
574
583
 
575
584
  function sanitizeBranch(name) {
@@ -631,12 +640,18 @@ function spawnAgent(dispatchItem, config) {
631
640
  }
632
641
  }
633
642
 
634
- // Build the system prompt
643
+ // Build lean system prompt (identity + rules, ~2-4KB) and bulk context (history, notes, skills)
635
644
  const systemPrompt = buildSystemPrompt(agentId, config, project);
645
+ const agentContext = buildAgentContext(agentId, config, project);
646
+
647
+ // Prepend bulk context to task prompt — keeps system prompt small and stable
648
+ const fullTaskPrompt = agentContext
649
+ ? `## Agent Context\n\n${agentContext}\n---\n\n## Your Task\n\n${taskPrompt}`
650
+ : taskPrompt;
636
651
 
637
652
  // Write prompt and system prompt to temp files (avoids shell escaping issues)
638
653
  const promptPath = path.join(ENGINE_DIR, `prompt-${id}.md`);
639
- safeWrite(promptPath, taskPrompt);
654
+ safeWrite(promptPath, fullTaskPrompt);
640
655
 
641
656
  const sysPromptPath = path.join(ENGINE_DIR, `sysprompt-${id}.md`);
642
657
  safeWrite(sysPromptPath, systemPrompt);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/squad",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.squad/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "squad": "bin/squad.js"