@mrpatronz/nexusflow 0.1.0 → 0.1.2

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.
Files changed (69) hide show
  1. package/.github/dependabot.yml +25 -0
  2. package/.github/workflows/release.yml +90 -0
  3. package/.vscode/launch.json +17 -0
  4. package/.vscode/tasks.json +17 -0
  5. package/README.md +1 -1
  6. package/dist/commands/commit.d.ts +18 -0
  7. package/dist/commands/commit.d.ts.map +1 -0
  8. package/dist/commands/commit.js +105 -0
  9. package/dist/commands/commit.js.map +1 -0
  10. package/dist/commands/diff.d.ts +11 -0
  11. package/dist/commands/diff.d.ts.map +1 -0
  12. package/dist/commands/diff.js +101 -0
  13. package/dist/commands/diff.js.map +1 -0
  14. package/dist/commands/sync.d.ts +11 -0
  15. package/dist/commands/sync.d.ts.map +1 -0
  16. package/dist/commands/sync.js +96 -0
  17. package/dist/commands/sync.js.map +1 -0
  18. package/dist/generators/base.d.ts.map +1 -1
  19. package/dist/generators/base.js +3 -0
  20. package/dist/generators/base.js.map +1 -1
  21. package/dist/generators/index.d.ts +5 -2
  22. package/dist/generators/index.d.ts.map +1 -1
  23. package/dist/generators/index.js +88 -38
  24. package/dist/generators/index.js.map +1 -1
  25. package/dist/generators/plan-generator.d.ts +40 -0
  26. package/dist/generators/plan-generator.d.ts.map +1 -0
  27. package/dist/generators/plan-generator.js +312 -0
  28. package/dist/generators/plan-generator.js.map +1 -0
  29. package/dist/gui/assets/index-B9Ph2bHH.css +2 -0
  30. package/dist/gui/assets/index-s4nRkpqY.js +21 -0
  31. package/dist/gui/index.html +2 -2
  32. package/dist/index.js +57 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/server.d.ts.map +1 -1
  35. package/dist/server.js +141 -1
  36. package/dist/server.js.map +1 -1
  37. package/dist/types.d.ts +13 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/utils/multi-git.d.ts +108 -0
  40. package/dist/utils/multi-git.d.ts.map +1 -0
  41. package/dist/utils/multi-git.js +206 -0
  42. package/dist/utils/multi-git.js.map +1 -0
  43. package/extension/package-lock.json +2811 -0
  44. package/extension/package.json +61 -0
  45. package/extension/src/extension.ts +168 -0
  46. package/extension/tsconfig.json +19 -0
  47. package/gui/src/App.tsx +377 -24
  48. package/gui/src/features/changes/ChangesViewer.tsx +212 -0
  49. package/gui/src/features/knowledge/KnowledgeBase.tsx +84 -0
  50. package/gui/src/features/onboarding/OnboardingWizard.tsx +230 -0
  51. package/gui/src/features/plan/ImplementationPlan.tsx +32 -0
  52. package/gui/src/features/services/ServiceConsole.tsx +159 -0
  53. package/gui/src/features/sessions/SessionHistory.tsx +195 -0
  54. package/gui/src/features/workspace/WorkspaceBuilder.tsx +414 -0
  55. package/gui/src/features/workspace/WorkspaceList.tsx +382 -0
  56. package/gui/src/types.ts +61 -0
  57. package/package.json +6 -4
  58. package/src/commands/commit.ts +131 -0
  59. package/src/commands/diff.ts +125 -0
  60. package/src/commands/sync.ts +110 -0
  61. package/src/generators/base.ts +3 -0
  62. package/src/generators/index.ts +95 -40
  63. package/src/generators/plan-generator.ts +390 -0
  64. package/src/index.ts +59 -0
  65. package/src/server.ts +152 -1
  66. package/src/types.ts +17 -0
  67. package/src/utils/multi-git.ts +306 -0
  68. package/dist/gui/assets/index-Cq9V485S.js +0 -21
  69. package/dist/gui/assets/index-Dh1b5gSZ.css +0 -2
package/gui/src/App.tsx CHANGED
@@ -19,9 +19,14 @@ import {
19
19
  X,
20
20
  MessageSquare,
21
21
  MessageSquareCode,
22
+ BookOpen,
23
+ ListOrdered,
24
+ Save,
25
+ Edit,
22
26
  } from 'lucide-react';
23
27
  import './App.css';
24
28
 
29
+
25
30
  // Types matched with src/types.ts
26
31
  interface NexusFlowConfig {
27
32
  version: string;
@@ -84,6 +89,7 @@ interface RunningService {
84
89
  }
85
90
 
86
91
  const API_BASE = import.meta.env.DEV ? 'http://localhost:3000' : '';
92
+ const isVsCode = new URLSearchParams(window.location.search).get('env') === 'vscode';
87
93
 
88
94
  export default function App() {
89
95
  const [view, setView] = useState<'guide' | 'create' | 'workspaces' | 'settings'>('guide');
@@ -143,7 +149,7 @@ export default function App() {
143
149
  const [orchTools, setOrchTools] = useState<OrchestrationDetection[]>([]);
144
150
  const [runningServices, setRunningServices] = useState<RunningService[]>([]);
145
151
  const [servicesLoading, setServicesLoading] = useState(false);
146
- const [subTab, setSubTab] = useState<'services' | 'changes' | 'sessions'>('sessions');
152
+ const [subTab, setSubTab] = useState<'services' | 'changes' | 'sessions' | 'knowledge' | 'plan'>('sessions');
147
153
  const [sessions, setSessions] = useState<any[]>([]);
148
154
  const [sessionsLoading, setSessionsLoading] = useState(false);
149
155
  const [activeSession, setActiveSession] = useState<any | null>(null);
@@ -151,6 +157,20 @@ export default function App() {
151
157
  const [transcriptLoading, setTranscriptLoading] = useState(false);
152
158
  const [gitChanges, setGitChanges] = useState<any[]>([]);
153
159
  const [gitChangesLoading, setGitChangesLoading] = useState(false);
160
+ const [knowledgeContent, setKnowledgeContent] = useState<string>('');
161
+ const [knowledgeLoading, setKnowledgeLoading] = useState<boolean>(false);
162
+ const [isEditingKnowledge, setIsEditingKnowledge] = useState<boolean>(false);
163
+ const [editedKnowledge, setEditedKnowledge] = useState<string>('');
164
+ const [saveKnowledgeLoading, setSaveKnowledgeLoading] = useState<boolean>(false);
165
+ const [planContent, setPlanContent] = useState<string>('');
166
+ const [planLoading, setPlanLoading] = useState<boolean>(false);
167
+ const [syncLoading, setSyncLoading] = useState<boolean>(false);
168
+ const [syncResults, setSyncResults] = useState<any[] | null>(null);
169
+ const [commitMessage, setCommitMessage] = useState<string>('');
170
+ const [showCommitModal, setShowCommitModal] = useState<boolean>(false);
171
+ const [commitLoading, setCommitLoading] = useState<boolean>(false);
172
+ const [commitResults, setCommitResults] = useState<any[] | null>(null);
173
+
154
174
 
155
175
  // Log Viewer
156
176
  const [selectedLogService, setSelectedLogService] = useState<string | null>(null);
@@ -193,6 +213,21 @@ export default function App() {
193
213
  }
194
214
  }, [activeWsId, subTab]);
195
215
 
216
+ // Load knowledge when subTab switches to 'knowledge' or active workspace changes
217
+ useEffect(() => {
218
+ if (activeWsId && subTab === 'knowledge') {
219
+ fetchKnowledge(activeWsId);
220
+ }
221
+ }, [activeWsId, subTab]);
222
+
223
+ // Load plan when subTab switches to 'plan' or active workspace changes
224
+ useEffect(() => {
225
+ if (activeWsId && subTab === 'plan') {
226
+ fetchPlan(activeWsId);
227
+ }
228
+ }, [activeWsId, subTab]);
229
+
230
+
196
231
 
197
232
  // Poll logs for active service logs
198
233
  useEffect(() => {
@@ -390,6 +425,96 @@ export default function App() {
390
425
  };
391
426
 
392
427
 
428
+ const fetchKnowledge = async (wsId: string) => {
429
+ setKnowledgeLoading(true);
430
+ try {
431
+ const encodedId = encodeURIComponent(wsId);
432
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/knowledge`);
433
+ const data = await res.json();
434
+ setKnowledgeContent(data.content || '');
435
+ setEditedKnowledge(data.content || '');
436
+ } catch (e) {
437
+ console.error(e);
438
+ } finally {
439
+ setKnowledgeLoading(false);
440
+ }
441
+ };
442
+
443
+ const handleSaveKnowledge = async (wsId: string) => {
444
+ setSaveKnowledgeLoading(true);
445
+ try {
446
+ const encodedId = encodeURIComponent(wsId);
447
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/knowledge`, {
448
+ method: 'PUT',
449
+ headers: { 'Content-Type': 'application/json' },
450
+ body: JSON.stringify({ content: editedKnowledge }),
451
+ });
452
+ if (res.ok) {
453
+ setKnowledgeContent(editedKnowledge);
454
+ setIsEditingKnowledge(false);
455
+ }
456
+ } catch (e) {
457
+ console.error(e);
458
+ } finally {
459
+ setSaveKnowledgeLoading(false);
460
+ }
461
+ };
462
+
463
+ const fetchPlan = async (wsId: string) => {
464
+ setPlanLoading(true);
465
+ try {
466
+ const encodedId = encodeURIComponent(wsId);
467
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/plan`);
468
+ const data = await res.json();
469
+ setPlanContent(data.content || '');
470
+ } catch (e) {
471
+ console.error(e);
472
+ } finally {
473
+ setPlanLoading(false);
474
+ }
475
+ };
476
+
477
+
478
+ const handleSyncAll = async (wsId: string) => {
479
+ setSyncLoading(true);
480
+ setSyncResults(null);
481
+ try {
482
+ const encodedId = encodeURIComponent(wsId);
483
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/sync`, { method: 'POST' });
484
+ const data = await res.json();
485
+ setSyncResults(data.results || []);
486
+ fetchGitChanges(wsId);
487
+ } catch (e) {
488
+ console.error(e);
489
+ } finally {
490
+ setSyncLoading(false);
491
+ }
492
+ };
493
+
494
+ const handleCommitAll = async (wsId: string) => {
495
+ if (!commitMessage.trim()) return;
496
+ setCommitLoading(true);
497
+ setCommitResults(null);
498
+ try {
499
+ const encodedId = encodeURIComponent(wsId);
500
+ const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/commit`, {
501
+ method: 'POST',
502
+ headers: { 'Content-Type': 'application/json' },
503
+ body: JSON.stringify({ message: commitMessage }),
504
+ });
505
+ const data = await res.json();
506
+ setCommitResults(data.results || []);
507
+ setCommitMessage('');
508
+ setShowCommitModal(false);
509
+ fetchGitChanges(wsId);
510
+ } catch (e) {
511
+ console.error(e);
512
+ } finally {
513
+ setCommitLoading(false);
514
+ }
515
+ };
516
+
517
+
393
518
  // ─── Actions ────────────────────────────────────────────────────────────
394
519
 
395
520
  const handleToggleRepo = (repo: RepoInfo) => {
@@ -434,14 +559,18 @@ export default function App() {
434
559
  fetchWorkspaces();
435
560
 
436
561
  if (selectedEditor) {
437
- await fetch(`${API_BASE}/api/open-editor`, {
438
- method: 'POST',
439
- headers: { 'Content-Type': 'application/json' },
440
- body: JSON.stringify({
441
- workspacePath: data.workspacePath,
442
- command: selectedEditor.command,
443
- }),
444
- });
562
+ if (isVsCode) {
563
+ window.parent.postMessage({ type: 'openWorkspaceFolder', workspacePath: data.workspacePath }, '*');
564
+ } else {
565
+ await fetch(`${API_BASE}/api/open-editor`, {
566
+ method: 'POST',
567
+ headers: { 'Content-Type': 'application/json' },
568
+ body: JSON.stringify({
569
+ workspacePath: data.workspacePath,
570
+ command: selectedEditor.command,
571
+ }),
572
+ });
573
+ }
445
574
  }
446
575
  } else {
447
576
  alert(`Error: ${data.error || 'Failed to create workspace'}`);
@@ -497,9 +626,18 @@ export default function App() {
497
626
  });
498
627
  const data = await res.json();
499
628
  if (res.ok && data.success) {
500
- navigator.clipboard.writeText(data.resumeCommand);
501
- alert(`Session Resumed!\n\n1. Editor launched.\n2. Command "${data.resumeCommand}" copied to clipboard! Paste it into your terminal inside the workspace to continue.`);
502
- setActiveWsId(ws.branchName);
629
+ if (isVsCode) {
630
+ window.parent.postMessage({
631
+ type: 'executeTerminalCommand',
632
+ command: data.resumeCommand,
633
+ cwd: ws.workspacePath
634
+ }, '*');
635
+ setActiveWsId(ws.branchName);
636
+ } else {
637
+ navigator.clipboard.writeText(data.resumeCommand);
638
+ alert(`Session Resumed!\n\n1. Editor launched.\n2. Command "${data.resumeCommand}" copied to clipboard! Paste it into your terminal inside the workspace to continue.`);
639
+ setActiveWsId(ws.branchName);
640
+ }
503
641
  } else {
504
642
  alert(`Error: ${data.error || 'Failed to resume session'}`);
505
643
  }
@@ -512,6 +650,10 @@ export default function App() {
512
650
  };
513
651
 
514
652
  const handleOpenInEditor = async (workspacePath: string) => {
653
+ if (isVsCode) {
654
+ window.parent.postMessage({ type: 'openWorkspaceFolder', workspacePath }, '*');
655
+ return;
656
+ }
515
657
  const editor = editors.find((e) => e.detected) || editors[0];
516
658
  if (!editor) {
517
659
  alert('No detected editors available.');
@@ -688,7 +830,7 @@ Core Instructions:
688
830
  <input
689
831
  type="text"
690
832
  className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-xs shadow-inner"
691
- placeholder="e.g. C:\Users\patro\dev"
833
+ placeholder="e.g. C:\Users\username\dev"
692
834
  value={config.devDir}
693
835
  onChange={(e) => setConfig({ ...config, devDir: e.target.value })}
694
836
  />
@@ -704,7 +846,7 @@ Core Instructions:
704
846
  <input
705
847
  type="text"
706
848
  className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-xs shadow-inner"
707
- placeholder="e.g. C:\Users\patro\dev\workspaces"
849
+ placeholder="e.g. C:\Users\username\dev\workspaces"
708
850
  value={config.workspacesDir}
709
851
  onChange={(e) => setConfig({ ...config, workspacesDir: e.target.value })}
710
852
  />
@@ -1528,6 +1670,26 @@ Core Instructions:
1528
1670
  >
1529
1671
  Active Changes (AI Diffs)
1530
1672
  </button>
1673
+ <button
1674
+ className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
1675
+ subTab === 'knowledge'
1676
+ ? 'border-indigo-500 text-white'
1677
+ : 'border-transparent text-gray-500 hover:text-gray-300'
1678
+ }`}
1679
+ onClick={() => setSubTab('knowledge')}
1680
+ >
1681
+ Knowledge Base
1682
+ </button>
1683
+ <button
1684
+ className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
1685
+ subTab === 'plan'
1686
+ ? 'border-indigo-500 text-white'
1687
+ : 'border-transparent text-gray-500 hover:text-gray-300'
1688
+ }`}
1689
+ onClick={() => setSubTab('plan')}
1690
+ >
1691
+ Implementation Plan
1692
+ </button>
1531
1693
  </div>
1532
1694
 
1533
1695
  {subTab === 'sessions' && (
@@ -1695,19 +1857,108 @@ Core Instructions:
1695
1857
  {/* Active Changes View */}
1696
1858
  {subTab === 'changes' && (
1697
1859
  <div>
1698
- <header className="flex justify-between items-center mb-4">
1860
+ <header className="flex justify-between items-center mb-4 flex-wrap gap-2">
1699
1861
  <h4 className="text-sm font-bold text-white flex items-center gap-2">
1700
1862
  <FolderGit2 size={16} className="text-cyan-400" /> Active Workspace Git Diffs
1701
1863
  </h4>
1702
- <button
1703
- className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-300"
1704
- onClick={() => fetchGitChanges(ws.branchName)}
1705
- disabled={gitChangesLoading}
1706
- >
1707
- <RefreshCw size={11} className={gitChangesLoading ? 'animate-spin text-indigo-400' : ''} /> Refresh Changes
1708
- </button>
1864
+ <div className="flex gap-2">
1865
+ <button
1866
+ className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-300"
1867
+ onClick={() => fetchGitChanges(ws.branchName)}
1868
+ disabled={gitChangesLoading}
1869
+ >
1870
+ <RefreshCw size={11} className={gitChangesLoading ? 'animate-spin text-indigo-400' : ''} /> Refresh Changes
1871
+ </button>
1872
+ <button
1873
+ className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-[10px] font-semibold transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
1874
+ onClick={() => handleSyncAll(ws.branchName)}
1875
+ disabled={syncLoading}
1876
+ >
1877
+ {syncLoading ? <RefreshCw size={11} className="animate-spin text-indigo-300" /> : <RefreshCw size={11} />} Sync All
1878
+ </button>
1879
+ <button
1880
+ className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-[10px] font-semibold transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
1881
+ onClick={() => setShowCommitModal(true)}
1882
+ disabled={gitChanges.every(repo => repo.files.length === 0) || commitLoading}
1883
+ >
1884
+ Commit & Push All
1885
+ </button>
1886
+ </div>
1709
1887
  </header>
1710
1888
 
1889
+ {syncResults && (
1890
+ <div className="bg-[#090d1a]/60 border border-gray-800 rounded-xl p-4 mb-4 text-xs">
1891
+ <div className="flex justify-between items-center mb-2 border-b border-gray-800/80 pb-2">
1892
+ <h5 className="font-bold text-white font-mono text-[11px]">Rebase/Sync Results</h5>
1893
+ <button className="text-gray-500 hover:text-gray-300 text-[10px] cursor-pointer" onClick={() => setSyncResults(null)}>Dismiss</button>
1894
+ </div>
1895
+ <div className="space-y-1.5">
1896
+ {syncResults.map((r: any) => (
1897
+ <div key={r.repoName} className="flex justify-between items-center font-mono text-[10px]">
1898
+ <span className="text-gray-300">{r.repoName}</span>
1899
+ <span className={r.success ? "text-emerald-400" : "text-rose-400"}>
1900
+ {r.success ? `✅ Synced (${r.message})` : `⚠️ Conflict: ${r.message}`}
1901
+ </span>
1902
+ </div>
1903
+ ))}
1904
+ </div>
1905
+ </div>
1906
+ )}
1907
+
1908
+ {commitResults && (
1909
+ <div className="bg-[#090d1a]/60 border border-gray-800 rounded-xl p-4 mb-4 text-xs">
1910
+ <div className="flex justify-between items-center mb-2 border-b border-gray-800/80 pb-2">
1911
+ <h5 className="font-bold text-white font-mono text-[11px]">Commit/Push Results</h5>
1912
+ <button className="text-gray-500 hover:text-gray-300 text-[10px] cursor-pointer" onClick={() => setCommitResults(null)}>Dismiss</button>
1913
+ </div>
1914
+ <div className="space-y-1.5">
1915
+ {commitResults.map((r: any) => (
1916
+ <div key={r.repoName} className="flex justify-between items-center font-mono text-[10px]">
1917
+ <span className="text-gray-300">{r.repoName}</span>
1918
+ <span className={r.success ? "text-emerald-400" : "text-rose-400"}>
1919
+ {r.success ? `✅ Committed ${r.filesChanged} file(s) (${r.commitHash || 'no hash'})` : `⚠️ Error: ${r.message}`}
1920
+ </span>
1921
+ </div>
1922
+ ))}
1923
+ </div>
1924
+ </div>
1925
+ )}
1926
+
1927
+ {showCommitModal && (
1928
+ <div className="bg-[#0b0f19] border border-gray-800 rounded-xl p-4 mb-4">
1929
+ <h5 className="text-xs font-bold text-white mb-2">Enter Commit Message</h5>
1930
+ <input
1931
+ type="text"
1932
+ className="w-full bg-gray-950 border border-gray-800 rounded-lg px-3 py-2 text-xs font-mono text-gray-350 focus:outline-none focus:border-indigo-500 mb-3"
1933
+ placeholder="feat: implement logic..."
1934
+ value={commitMessage}
1935
+ onChange={(e) => setCommitMessage(e.target.value)}
1936
+ onKeyDown={(e) => {
1937
+ if (e.key === 'Enter') handleCommitAll(ws.branchName);
1938
+ }}
1939
+ />
1940
+ <div className="flex justify-end gap-2">
1941
+ <button
1942
+ className="px-3 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-850 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-400"
1943
+ onClick={() => {
1944
+ setShowCommitModal(false);
1945
+ setCommitMessage('');
1946
+ }}
1947
+ disabled={commitLoading}
1948
+ >
1949
+ Cancel
1950
+ </button>
1951
+ <button
1952
+ className="px-3 py-1.5 bg-emerald-600 hover:bg-emerald-500 rounded-lg text-[10px] font-semibold text-white transition-all cursor-pointer"
1953
+ onClick={() => handleCommitAll(ws.branchName)}
1954
+ disabled={commitLoading || !commitMessage.trim()}
1955
+ >
1956
+ {commitLoading ? 'Committing...' : 'Commit & Push'}
1957
+ </button>
1958
+ </div>
1959
+ </div>
1960
+ )}
1961
+
1711
1962
  {gitChangesLoading ? (
1712
1963
  <div className="flex justify-center py-10">
1713
1964
  <RefreshCw className="animate-spin text-indigo-400" size={20} />
@@ -1723,16 +1974,39 @@ Core Instructions:
1723
1974
  ) : (
1724
1975
  gitChanges.map((repo) => {
1725
1976
  if (repo.files.length === 0) return null;
1977
+ const totalFilesChanged = repo.files.length;
1978
+ const repoAdditions = repo.files.reduce((acc: number, f: any) => acc + (f.additions || 0), 0);
1979
+ const repoDeletions = repo.files.reduce((acc: number, f: any) => acc + (f.deletions || 0), 0);
1980
+
1726
1981
  return (
1727
1982
  <div key={repo.repoName} className="bg-gray-950/20 border border-gray-800/60 rounded-xl p-4">
1728
1983
  <div className="flex justify-between items-center mb-3">
1729
- <h5 className="text-xs font-bold text-white font-mono">{repo.repoName}</h5>
1984
+ <div className="flex items-center gap-2">
1985
+ <h5 className="text-xs font-bold text-white font-mono">{repo.repoName}</h5>
1986
+ <span className="text-[9px] px-1.5 py-0.5 rounded bg-gray-900 border border-gray-800 text-gray-400 font-mono">
1987
+ {totalFilesChanged} file{totalFilesChanged === 1 ? '' : 's'} changed
1988
+ </span>
1989
+ {(repoAdditions > 0 || repoDeletions > 0) && (
1990
+ <span className="text-[9px] font-mono">
1991
+ <span className="text-emerald-400 font-bold">+{repoAdditions}</span>{' '}
1992
+ <span className="text-rose-400 font-bold">-{repoDeletions}</span>
1993
+ </span>
1994
+ )}
1995
+ </div>
1730
1996
  <span className="text-[9px] text-gray-500 font-mono truncate max-w-[280px]">{repo.repoPath}</span>
1731
1997
  </div>
1732
1998
  <div className="space-y-1.5">
1733
1999
  {repo.files.map((fileInfo: any) => (
1734
2000
  <div key={fileInfo.file} className="flex justify-between items-center bg-[#090d1a]/40 px-3 py-2 rounded-lg border border-gray-800/30 text-xs">
1735
- <span className="font-mono text-gray-300 text-[11px] truncate max-w-[320px]">{fileInfo.file}</span>
2001
+ <div className="flex flex-col min-w-0">
2002
+ <span className="font-mono text-gray-300 text-[11px] truncate max-w-[320px]">{fileInfo.file}</span>
2003
+ {(fileInfo.additions > 0 || fileInfo.deletions > 0) && (
2004
+ <span className="text-[9px] font-mono mt-0.5">
2005
+ <span className="text-emerald-500">+{fileInfo.additions}</span>{' '}
2006
+ <span className="text-rose-500">-{fileInfo.deletions}</span>
2007
+ </span>
2008
+ )}
2009
+ </div>
1736
2010
  <span className={`text-[9px] px-2 py-0.5 rounded font-bold uppercase ${
1737
2011
  fileInfo.type === 'added' ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' :
1738
2012
  fileInfo.type === 'deleted' ? 'bg-rose-500/10 text-rose-400 border border-rose-500/20' :
@@ -1751,6 +2025,85 @@ Core Instructions:
1751
2025
  )}
1752
2026
  </div>
1753
2027
  )}
2028
+
2029
+ {/* Knowledge Base View */}
2030
+ {subTab === 'knowledge' && (
2031
+ <div className="bg-[#090d1a]/20 border border-gray-800/60 rounded-xl p-4">
2032
+ <header className="flex justify-between items-center mb-4">
2033
+ <h4 className="text-sm font-bold text-white flex items-center gap-2">
2034
+ <BookOpen size={16} className="text-cyan-400" /> Persistent Knowledge Memory (nexusflow-knowledge.md)
2035
+ </h4>
2036
+ <div className="flex gap-2">
2037
+ {isEditingKnowledge ? (
2038
+ <>
2039
+ <button
2040
+ className="px-2.5 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-850 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-400"
2041
+ onClick={() => {
2042
+ setEditedKnowledge(knowledgeContent);
2043
+ setIsEditingKnowledge(false);
2044
+ }}
2045
+ disabled={saveKnowledgeLoading}
2046
+ >
2047
+ Cancel
2048
+ </button>
2049
+ <button
2050
+ className="inline-flex items-center gap-1.5 px-2.5 py-1.5 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-[10px] font-semibold transition-all cursor-pointer"
2051
+ onClick={() => handleSaveKnowledge(ws.branchName)}
2052
+ disabled={saveKnowledgeLoading}
2053
+ >
2054
+ {saveKnowledgeLoading ? <RefreshCw className="animate-spin" size={10} /> : <Save size={10} />} Save
2055
+ </button>
2056
+ </>
2057
+ ) : (
2058
+ <button
2059
+ className="inline-flex items-center gap-1.5 px-2.5 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-350"
2060
+ onClick={() => setIsEditingKnowledge(true)}
2061
+ disabled={knowledgeLoading}
2062
+ >
2063
+ <Edit size={10} /> Edit Knowledge
2064
+ </button>
2065
+ )}
2066
+ </div>
2067
+ </header>
2068
+
2069
+ {knowledgeLoading ? (
2070
+ <div className="flex justify-center py-10">
2071
+ <RefreshCw className="animate-spin text-indigo-400" size={20} />
2072
+ </div>
2073
+ ) : isEditingKnowledge ? (
2074
+ <textarea
2075
+ className="w-full h-96 bg-gray-950/60 border border-gray-800/80 rounded-xl p-4 text-xs font-mono text-gray-305 focus:outline-none focus:border-indigo-500/80 resize-y"
2076
+ value={editedKnowledge}
2077
+ onChange={(e) => setEditedKnowledge(e.target.value)}
2078
+ />
2079
+ ) : (
2080
+ <div className="bg-gray-950/40 border border-gray-800/30 rounded-xl p-4 text-gray-350 text-xs leading-relaxed overflow-auto font-mono whitespace-pre-wrap max-h-96">
2081
+ {knowledgeContent || "No knowledge file generated yet."}
2082
+ </div>
2083
+ )}
2084
+ </div>
2085
+ )}
2086
+
2087
+ {/* Implementation Plan View */}
2088
+ {subTab === 'plan' && (
2089
+ <div className="bg-[#090d1a]/20 border border-gray-800/60 rounded-xl p-4">
2090
+ <header className="flex justify-between items-center mb-4">
2091
+ <h4 className="text-sm font-bold text-white flex items-center gap-2">
2092
+ <ListOrdered size={16} className="text-cyan-400" /> Inter-Repo Implementation Plan (nexusflow-plan.md)
2093
+ </h4>
2094
+ </header>
2095
+
2096
+ {planLoading ? (
2097
+ <div className="flex justify-center py-10">
2098
+ <RefreshCw className="animate-spin text-indigo-400" size={20} />
2099
+ </div>
2100
+ ) : (
2101
+ <div className="bg-gray-950/40 border border-gray-800/30 rounded-xl p-4 text-gray-350 text-xs leading-relaxed overflow-auto font-mono whitespace-pre-wrap max-h-96">
2102
+ {planContent || "No implementation plan generated yet."}
2103
+ </div>
2104
+ )}
2105
+ </div>
2106
+ )}
1754
2107
  </div>
1755
2108
  )}
1756
2109
  </div>