@beastmode-develeap/beastmode 0.1.270 → 0.1.272
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/dist/index.js +30 -0
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +242 -23
- package/dist/web/build-commit.txt +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +1 -1
package/dist/web/board.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
</script>
|
|
17
17
|
<!--BOARD_DATA-->
|
|
18
|
-
<script>window.__BUILD_STAMP__ = "20260516-
|
|
18
|
+
<script>window.__BUILD_STAMP__ = "20260516-114618-d6ededa";</script>
|
|
19
19
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
20
20
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
21
21
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
@@ -8514,6 +8514,18 @@ function ProjectsPage({ selectedProject, onProjectChange }) {
|
|
|
8514
8514
|
const [onboardingTier, setOnboardingTier] = useState('standard');
|
|
8515
8515
|
const [showPicker, setShowPicker] = useState(false);
|
|
8516
8516
|
const [adding, setAdding] = useState(false);
|
|
8517
|
+
// Per-project analysis state (status pill + last-analyzed timestamp).
|
|
8518
|
+
// Keyed by project name; value is the /analyze/status JSON response.
|
|
8519
|
+
const [analysisStatuses, setAnalysisStatuses] = useState({});
|
|
8520
|
+
// Which project card is currently expanded (only one at a time).
|
|
8521
|
+
const [expandedProject, setExpandedProject] = useState(null);
|
|
8522
|
+
// Cached guide content keyed by project name; each entry holds optional l1/l2 strings.
|
|
8523
|
+
const [guideContent, setGuideContent] = useState({});
|
|
8524
|
+
// Per-project transient errors surfaced inside the card body.
|
|
8525
|
+
const [guideErrors, setGuideErrors] = useState({});
|
|
8526
|
+
// Per-project "show full guide" toggle (l2 visible).
|
|
8527
|
+
const [showFullGuide, setShowFullGuide] = useState({});
|
|
8528
|
+
const pollTimersRef = useRef({});
|
|
8517
8529
|
|
|
8518
8530
|
const fetchProjects = useCallback(() => {
|
|
8519
8531
|
api('GET', '/api/projects')
|
|
@@ -8524,6 +8536,101 @@ function ProjectsPage({ selectedProject, onProjectChange }) {
|
|
|
8524
8536
|
|
|
8525
8537
|
useEffect(() => { fetchProjects(); }, []);
|
|
8526
8538
|
|
|
8539
|
+
const fetchAnalysisStatus = useCallback(async (name) => {
|
|
8540
|
+
try {
|
|
8541
|
+
const status = await api('GET', '/api/projects/' + name + '/analyze/status');
|
|
8542
|
+
setAnalysisStatuses(prev => ({ ...prev, [name]: status }));
|
|
8543
|
+
return status;
|
|
8544
|
+
} catch (e) {
|
|
8545
|
+
setAnalysisStatuses(prev => ({ ...prev, [name]: { status: 'idle', error: e.message } }));
|
|
8546
|
+
return null;
|
|
8547
|
+
}
|
|
8548
|
+
}, []);
|
|
8549
|
+
|
|
8550
|
+
// Refresh per-project analysis status whenever the project list changes.
|
|
8551
|
+
useEffect(() => {
|
|
8552
|
+
projects.forEach(p => { fetchAnalysisStatus(p.name); });
|
|
8553
|
+
}, [projects, fetchAnalysisStatus]);
|
|
8554
|
+
|
|
8555
|
+
// Clean up any running poll intervals on unmount.
|
|
8556
|
+
useEffect(() => () => {
|
|
8557
|
+
Object.values(pollTimersRef.current).forEach(t => clearInterval(t));
|
|
8558
|
+
pollTimersRef.current = {};
|
|
8559
|
+
}, []);
|
|
8560
|
+
|
|
8561
|
+
const fetchGuide = useCallback(async (name, level) => {
|
|
8562
|
+
try {
|
|
8563
|
+
const resp = await api('GET', '/api/projects/' + name + '/codebase-guide?level=' + level);
|
|
8564
|
+
setGuideContent(prev => ({
|
|
8565
|
+
...prev,
|
|
8566
|
+
[name]: { ...(prev[name] || {}), [level]: resp.content || '' },
|
|
8567
|
+
}));
|
|
8568
|
+
setGuideErrors(prev => ({ ...prev, [name]: null }));
|
|
8569
|
+
return resp;
|
|
8570
|
+
} catch (e) {
|
|
8571
|
+
setGuideErrors(prev => ({ ...prev, [name]: e.message }));
|
|
8572
|
+
return null;
|
|
8573
|
+
}
|
|
8574
|
+
}, []);
|
|
8575
|
+
|
|
8576
|
+
const toggleExpand = useCallback(async (name) => {
|
|
8577
|
+
if (expandedProject === name) {
|
|
8578
|
+
setExpandedProject(null);
|
|
8579
|
+
return;
|
|
8580
|
+
}
|
|
8581
|
+
setExpandedProject(name);
|
|
8582
|
+
// Fetch L1 content lazily on first expand if analysis is complete.
|
|
8583
|
+
const st = analysisStatuses[name];
|
|
8584
|
+
if (st && st.status === 'complete' && !(guideContent[name] && guideContent[name].l1)) {
|
|
8585
|
+
await fetchGuide(name, 'l1');
|
|
8586
|
+
}
|
|
8587
|
+
}, [expandedProject, analysisStatuses, guideContent, fetchGuide]);
|
|
8588
|
+
|
|
8589
|
+
const stopPolling = useCallback((name) => {
|
|
8590
|
+
if (pollTimersRef.current[name]) {
|
|
8591
|
+
clearInterval(pollTimersRef.current[name]);
|
|
8592
|
+
delete pollTimersRef.current[name];
|
|
8593
|
+
}
|
|
8594
|
+
}, []);
|
|
8595
|
+
|
|
8596
|
+
const startPolling = useCallback((name) => {
|
|
8597
|
+
stopPolling(name);
|
|
8598
|
+
pollTimersRef.current[name] = setInterval(async () => {
|
|
8599
|
+
const st = await fetchAnalysisStatus(name);
|
|
8600
|
+
if (!st) return;
|
|
8601
|
+
if (st.status === 'complete') {
|
|
8602
|
+
stopPolling(name);
|
|
8603
|
+
// Drop stale L1/L2 caches so the next expand re-fetches them.
|
|
8604
|
+
setGuideContent(prev => { const n = { ...prev }; delete n[name]; return n; });
|
|
8605
|
+
setShowFullGuide(prev => ({ ...prev, [name]: false }));
|
|
8606
|
+
if (expandedProject === name) {
|
|
8607
|
+
await fetchGuide(name, 'l1');
|
|
8608
|
+
}
|
|
8609
|
+
} else if (st.status === 'failed' || st.status === 'idle') {
|
|
8610
|
+
stopPolling(name);
|
|
8611
|
+
}
|
|
8612
|
+
}, 5000);
|
|
8613
|
+
}, [fetchAnalysisStatus, fetchGuide, stopPolling, expandedProject]);
|
|
8614
|
+
|
|
8615
|
+
const reanalyze = useCallback(async (name) => {
|
|
8616
|
+
setGuideErrors(prev => ({ ...prev, [name]: null }));
|
|
8617
|
+
setAnalysisStatuses(prev => ({ ...prev, [name]: { status: 'running' } }));
|
|
8618
|
+
try {
|
|
8619
|
+
await api('POST', '/api/projects/' + name + '/analyze?force=true', {});
|
|
8620
|
+
startPolling(name);
|
|
8621
|
+
} catch (e) {
|
|
8622
|
+
setAnalysisStatuses(prev => ({ ...prev, [name]: { status: 'failed', error: e.message } }));
|
|
8623
|
+
}
|
|
8624
|
+
}, [startPolling]);
|
|
8625
|
+
|
|
8626
|
+
const toggleFullGuide = useCallback(async (name) => {
|
|
8627
|
+
const next = !showFullGuide[name];
|
|
8628
|
+
setShowFullGuide(prev => ({ ...prev, [name]: next }));
|
|
8629
|
+
if (next && !(guideContent[name] && guideContent[name].l2)) {
|
|
8630
|
+
await fetchGuide(name, 'l2');
|
|
8631
|
+
}
|
|
8632
|
+
}, [showFullGuide, guideContent, fetchGuide]);
|
|
8633
|
+
|
|
8527
8634
|
const addProject = async () => {
|
|
8528
8635
|
const isGithub = addMode === 'github';
|
|
8529
8636
|
if (isGithub && !githubUrl.trim()) return;
|
|
@@ -8543,19 +8650,26 @@ function ProjectsPage({ selectedProject, onProjectChange }) {
|
|
|
8543
8650
|
setSuccess(isGithub ? `Cloned and added: ${registeredPath}` : 'Project added successfully');
|
|
8544
8651
|
|
|
8545
8652
|
if (onboardingTier === 'standard' || onboardingTier === 'full') {
|
|
8546
|
-
setSuccess('Project added — running codebase analysis...');
|
|
8547
8653
|
try {
|
|
8548
|
-
await api('POST', '/api/projects/' + projName + '/analyze', {});
|
|
8549
|
-
|
|
8550
|
-
|
|
8654
|
+
const analyzeResult = await api('POST', '/api/projects/' + projName + '/analyze', {});
|
|
8655
|
+
if (analyzeResult && analyzeResult.cached) {
|
|
8656
|
+
setSuccess('Project added — codebase already analyzed');
|
|
8657
|
+
} else {
|
|
8658
|
+
setSuccess('Project added — analyzing codebase…');
|
|
8659
|
+
// Begin polling so the operator sees the status pill flip to
|
|
8660
|
+
// "Analyzed" on the new project card in real time. Card may
|
|
8661
|
+
// render slightly later (after fetchProjects), but startPolling
|
|
8662
|
+
// is idempotent and the interval keeps refreshing status.
|
|
8663
|
+
startPolling(projName);
|
|
8664
|
+
}
|
|
8665
|
+
} catch {
|
|
8666
|
+
setSuccess('Project added (analysis failed — can retry later)');
|
|
8667
|
+
}
|
|
8551
8668
|
}
|
|
8552
8669
|
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
navigate('#/new-session');
|
|
8557
|
-
}, 1000);
|
|
8558
|
-
}
|
|
8670
|
+
// Full tier no longer auto-redirects. The operator stays on the
|
|
8671
|
+
// Projects page so they can watch the analysis status flip and
|
|
8672
|
+
// navigate to /new-session manually when ready.
|
|
8559
8673
|
|
|
8560
8674
|
setTimeout(() => setSuccess(null), 5000);
|
|
8561
8675
|
fetchProjects();
|
|
@@ -8649,22 +8763,127 @@ function ProjectsPage({ selectedProject, onProjectChange }) {
|
|
|
8649
8763
|
icon="projects"
|
|
8650
8764
|
title="No projects yet"
|
|
8651
8765
|
description="Add a project path to start building." />`
|
|
8652
|
-
: projects.map(proj =>
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8766
|
+
: projects.map(proj => {
|
|
8767
|
+
const st = analysisStatuses[proj.name] || { status: 'idle' };
|
|
8768
|
+
const expanded = expandedProject === proj.name;
|
|
8769
|
+
const pillState = st.status === 'complete' ? 'complete'
|
|
8770
|
+
: st.status === 'running' ? 'running'
|
|
8771
|
+
: st.status === 'failed' ? 'failed'
|
|
8772
|
+
: 'none';
|
|
8773
|
+
const pillLabel = pillState === 'complete' ? '\u2713 Analyzed'
|
|
8774
|
+
: pillState === 'running' ? '\u23f3 Analyzing\u2026'
|
|
8775
|
+
: pillState === 'failed' ? '\u2717 Failed'
|
|
8776
|
+
: '\u2014 Not analyzed';
|
|
8777
|
+
const pillColor = pillState === 'complete' ? '#22c55e'
|
|
8778
|
+
: pillState === 'running' ? '#f59e0b'
|
|
8779
|
+
: pillState === 'failed' ? '#ef4444'
|
|
8780
|
+
: '#94a3b8';
|
|
8781
|
+
const tsLabel = pillState === 'complete' && st.analyzed_at
|
|
8782
|
+
? 'Analyzed ' + timeAgo(st.analyzed_at)
|
|
8783
|
+
: pillState === 'failed' ? 'Analysis failed'
|
|
8784
|
+
: pillState === 'running' ? 'Analyzing\u2026'
|
|
8785
|
+
: 'Not analyzed';
|
|
8786
|
+
const guide = guideContent[proj.name] || {};
|
|
8787
|
+
const guideErr = guideErrors[proj.name];
|
|
8788
|
+
const isBusy = pillState === 'running';
|
|
8789
|
+
return html`
|
|
8790
|
+
<div class="ext-item" key=${proj.name} style="flex-wrap:wrap;flex-direction:column;align-items:stretch;">
|
|
8791
|
+
<div style="display:flex;align-items:center;gap:12px;width:100%;cursor:pointer;"
|
|
8792
|
+
onClick=${() => toggleExpand(proj.name)}>
|
|
8793
|
+
<div style="flex:1;min-width:0;">
|
|
8794
|
+
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
|
8795
|
+
<div class="ext-name">${proj.name}</div>
|
|
8796
|
+
<span style="display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;
|
|
8797
|
+
background:${pillColor};color:#fff;white-space:nowrap;"
|
|
8798
|
+
title=${st.error || ''}
|
|
8799
|
+
data-testid="project-status-pill"
|
|
8800
|
+
data-state=${pillState}>${pillLabel}</span>
|
|
8801
|
+
${proj.stack && (proj.stack.detected || proj.stack.framework)
|
|
8802
|
+
? html`<span style="display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;
|
|
8803
|
+
background:var(--bg-subtle);color:var(--text-secondary);">
|
|
8804
|
+
${proj.stack.detected || proj.stack.framework}
|
|
8805
|
+
</span>`
|
|
8806
|
+
: null}
|
|
8807
|
+
</div>
|
|
8808
|
+
<div class="ext-meta" style="margin-top:4px;">
|
|
8809
|
+
${proj.repo ? proj.repo + ' \u2014 ' : ''}
|
|
8810
|
+
${proj.path || ''}
|
|
8811
|
+
</div>
|
|
8812
|
+
<div class="ext-meta" style="margin-top:2px;font-size:11px;color:var(--text-secondary);"
|
|
8813
|
+
data-testid="project-analyzed-timestamp">${tsLabel}</div>
|
|
8814
|
+
</div>
|
|
8815
|
+
<div class="ext-actions" style="display:flex;gap:6px;flex-wrap:wrap;"
|
|
8816
|
+
onClick=${(e) => e.stopPropagation()}>
|
|
8817
|
+
<button class="btn btn-sm btn-secondary"
|
|
8818
|
+
onClick=${() => reanalyze(proj.name)}
|
|
8819
|
+
disabled=${isBusy}
|
|
8820
|
+
data-testid="project-reanalyze-btn">
|
|
8821
|
+
${isBusy ? 'Analyzing\u2026' : 'Re-analyze'}
|
|
8822
|
+
</button>
|
|
8823
|
+
<button class="btn btn-sm btn-danger" onClick=${() => removeProject(proj.name)}>Remove</button>
|
|
8824
|
+
<button class="btn btn-sm btn-secondary"
|
|
8825
|
+
onClick=${() => toggleExpand(proj.name)}
|
|
8826
|
+
data-testid="project-expand-btn">${expanded ? '\u25b4' : '\u25be'}</button>
|
|
8660
8827
|
</div>
|
|
8661
8828
|
</div>
|
|
8662
|
-
|
|
8663
|
-
<
|
|
8664
|
-
|
|
8829
|
+
${expanded ? html`
|
|
8830
|
+
<div style="margin-top:12px;padding:12px;border-top:1px solid var(--border);"
|
|
8831
|
+
data-testid="codebase-summary-panel">
|
|
8832
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
|
|
8833
|
+
<strong style="font-size:13px;">Codebase summary</strong>
|
|
8834
|
+
${pillState === 'complete' && guide.l1 ? html`
|
|
8835
|
+
<button class="btn btn-sm btn-secondary"
|
|
8836
|
+
onClick=${() => toggleFullGuide(proj.name)}
|
|
8837
|
+
data-testid="project-show-full-guide">
|
|
8838
|
+
${showFullGuide[proj.name] ? 'Hide full guide' : 'Show full guide'}
|
|
8839
|
+
</button>
|
|
8840
|
+
` : null}
|
|
8841
|
+
</div>
|
|
8842
|
+
${guideErr ? html`<div class="error-msg" style="margin-bottom:8px;">${guideErr}</div>` : null}
|
|
8843
|
+
${pillState === 'complete'
|
|
8844
|
+
? (guide.l1 == null
|
|
8845
|
+
? html`<div style="color:var(--text-secondary);font-size:12px;">Loading guide\u2026</div>`
|
|
8846
|
+
: html`
|
|
8847
|
+
<pre style="white-space:pre-wrap;font-family:inherit;font-size:13px;line-height:1.5;
|
|
8848
|
+
background:var(--bg-subtle);padding:10px;border-radius:6px;margin:0;"
|
|
8849
|
+
data-testid="codebase-guide-l1">${guide.l1}</pre>
|
|
8850
|
+
${showFullGuide[proj.name]
|
|
8851
|
+
? (guide.l2 == null
|
|
8852
|
+
? html`<div style="margin-top:8px;color:var(--text-secondary);font-size:12px;">Loading full guide\u2026</div>`
|
|
8853
|
+
: html`
|
|
8854
|
+
<pre style="white-space:pre-wrap;font-family:inherit;font-size:12px;line-height:1.5;
|
|
8855
|
+
background:var(--bg-subtle);padding:10px;border-radius:6px;margin-top:8px;"
|
|
8856
|
+
data-testid="codebase-guide-l2">${guide.l2}</pre>
|
|
8857
|
+
`)
|
|
8858
|
+
: null}
|
|
8859
|
+
`)
|
|
8860
|
+
: pillState === 'running'
|
|
8861
|
+
? html`<div style="display:flex;align-items:center;gap:8px;color:var(--text-secondary);">
|
|
8862
|
+
<span class="spinner" style="display:inline-block;width:12px;height:12px;border:2px solid var(--border);
|
|
8863
|
+
border-top-color:var(--accent);border-radius:50%;
|
|
8864
|
+
animation:spin 0.8s linear infinite;"></span>
|
|
8865
|
+
Analyzing codebase\u2026
|
|
8866
|
+
</div>`
|
|
8867
|
+
: pillState === 'failed'
|
|
8868
|
+
? html`<div>
|
|
8869
|
+
<div class="error-msg" style="margin-bottom:8px;">${st.error || 'Analysis failed'}</div>
|
|
8870
|
+
<button class="btn btn-sm btn-primary"
|
|
8871
|
+
onClick=${() => reanalyze(proj.name)}
|
|
8872
|
+
data-testid="project-retry-btn">Retry</button>
|
|
8873
|
+
</div>`
|
|
8874
|
+
: html`<div style="color:var(--text-secondary);">
|
|
8875
|
+
Not analyzed yet \u2014
|
|
8876
|
+
<button class="btn btn-sm btn-primary"
|
|
8877
|
+
style="margin-left:6px;"
|
|
8878
|
+
onClick=${() => reanalyze(proj.name)}
|
|
8879
|
+
data-testid="project-run-analysis-btn">Run analysis</button>
|
|
8880
|
+
</div>`}
|
|
8881
|
+
</div>
|
|
8882
|
+
` : null}
|
|
8665
8883
|
<${EnvironmentPanel} projectName=${proj.name} />
|
|
8666
8884
|
</div>
|
|
8667
|
-
|
|
8885
|
+
`;
|
|
8886
|
+
})}
|
|
8668
8887
|
</div>
|
|
8669
8888
|
${showPicker && html`<${DirectoryPicker} initialPath=${newPath || null} onClose=${() => setShowPicker(false)} onSelect=${(p) => { setNewPath(p); setShowPicker(false); }} />`}
|
|
8670
8889
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d6ededab513bbf5d0a90dfb7e86675775d9a9a68
|
package/dist/web/build-stamp.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
20260516-
|
|
1
|
+
20260516-114618-d6ededa
|