@phren/cli 0.0.17 → 0.0.19
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/mcp/dist/capabilities/cli.js +1 -1
- package/mcp/dist/capabilities/mcp.js +1 -1
- package/mcp/dist/capabilities/vscode.js +1 -1
- package/mcp/dist/capabilities/web-ui.js +1 -1
- package/mcp/dist/cli-hooks-session.js +3 -3
- package/mcp/dist/cli-namespaces.js +1 -1
- package/mcp/dist/data-tasks.js +1 -1
- package/mcp/dist/entrypoint.js +1 -1
- package/mcp/dist/governance-policy.js +76 -32
- package/mcp/dist/governance-rbac.js +152 -0
- package/mcp/dist/init-preferences.js +1 -1
- package/mcp/dist/init-setup.js +7 -7
- package/mcp/dist/init.js +2 -2
- package/mcp/dist/link-checksums.js +1 -1
- package/mcp/dist/link-doctor.js +1 -1
- package/mcp/dist/mcp-config.js +102 -2
- package/mcp/dist/mcp-finding.js +16 -0
- package/mcp/dist/mcp-search.js +44 -5
- package/mcp/dist/mcp-session.js +4 -0
- package/mcp/dist/mcp-tasks.js +22 -0
- package/mcp/dist/memory-ui-assets.js +2 -2
- package/mcp/dist/memory-ui-graph.js +9 -19
- package/mcp/dist/memory-ui-page.js +26 -43
- package/mcp/dist/memory-ui-scripts.js +198 -76
- package/mcp/dist/memory-ui-server.js +57 -2
- package/mcp/dist/phren-core.js +1 -1
- package/mcp/dist/phren-paths.js +2 -2
- package/mcp/dist/proactivity.js +47 -0
- package/mcp/dist/profile-store.js +100 -1
- package/mcp/dist/shared-index.js +2 -2
- package/mcp/dist/shared-retrieval.js +42 -1
- package/mcp/dist/shell-input.js +1 -1
- package/package.json +1 -1
|
@@ -257,8 +257,8 @@ export function renderGraphScript() {
|
|
|
257
257
|
/* ease-in-out via sine curve over the full trip distance */
|
|
258
258
|
var t = phren.tripDist > 0 ? Math.min(1, phren.tripProgress / phren.tripDist) : 1;
|
|
259
259
|
var easeInOut = 0.5 - 0.5 * Math.cos(Math.PI * t);
|
|
260
|
-
var baseSpeed = Math.max(
|
|
261
|
-
var speed = Math.max(
|
|
260
|
+
var baseSpeed = Math.max(0.8, phren.tripDist * 0.03);
|
|
261
|
+
var speed = Math.max(0.4, baseSpeed * (0.15 + 0.85 * easeInOut));
|
|
262
262
|
/* clamp speed to remaining distance — prevents overshoot oscillation */
|
|
263
263
|
if (speed >= dist) {
|
|
264
264
|
phren.x = wx;
|
|
@@ -338,10 +338,10 @@ export function renderGraphScript() {
|
|
|
338
338
|
/* sprite rendering */
|
|
339
339
|
if (phrenImgReady) {
|
|
340
340
|
ctx.save();
|
|
341
|
-
/*
|
|
342
|
-
var spriteScreenSize =
|
|
341
|
+
/* scale-aware sprite: 32px at default zoom, shrinks when zoomed out, caps at 40px */
|
|
342
|
+
var spriteScreenSize = Math.min(40, Math.max(16, 32 * Math.sqrt(scale)));
|
|
343
343
|
if (phren.arriving && phren.arriveTimer < 0.4) {
|
|
344
|
-
spriteScreenSize
|
|
344
|
+
spriteScreenSize += 6 * (1 - phren.arriveTimer / 0.4);
|
|
345
345
|
}
|
|
346
346
|
var spriteSize = spriteScreenSize * s; /* convert to graph coords */
|
|
347
347
|
/* bob up/down when walking — sine wave synced to walk progress */
|
|
@@ -366,18 +366,7 @@ export function renderGraphScript() {
|
|
|
366
366
|
ctx.translate(-px, -py);
|
|
367
367
|
}
|
|
368
368
|
ctx.drawImage(phrenImg, px - spriteSize / 2, py - spriteSize / 2 + totalYOffset, spriteSize, spriteSize);
|
|
369
|
-
/*
|
|
370
|
-
if (phren.arriving && phren.arriveTimer < 0.6) {
|
|
371
|
-
var ringAlpha = 0.6 * (1 - phren.arriveTimer / 0.6);
|
|
372
|
-
ctx.beginPath();
|
|
373
|
-
ctx.arc(px, py + totalYOffset, spriteSize * 0.55, 0, Math.PI * 2);
|
|
374
|
-
ctx.strokeStyle = 'rgba(0,229,255,' + ringAlpha + ')';
|
|
375
|
-
ctx.lineWidth = 2 * s;
|
|
376
|
-
ctx.shadowColor = 'rgba(0,229,255,' + (ringAlpha * 0.5) + ')';
|
|
377
|
-
ctx.shadowBlur = 8;
|
|
378
|
-
ctx.stroke();
|
|
379
|
-
ctx.shadowBlur = 0;
|
|
380
|
-
}
|
|
369
|
+
/* no glow ring — phren stands clean next to the node */
|
|
381
370
|
ctx.restore();
|
|
382
371
|
}
|
|
383
372
|
}
|
|
@@ -1362,9 +1351,10 @@ export function renderGraphScript() {
|
|
|
1362
1351
|
/* ── detail panel ───────────────────────────────────────────────────── */
|
|
1363
1352
|
function renderGraphDetails(node) {
|
|
1364
1353
|
selectedNode = node;
|
|
1365
|
-
/* phren moves toward the selected node,
|
|
1354
|
+
/* phren moves toward the selected node, stopping just beside it (not on top) */
|
|
1366
1355
|
if (node && typeof node.x === 'number' && typeof node.y === 'number') {
|
|
1367
|
-
|
|
1356
|
+
var nr = nodeRadius(node) + 12; /* offset by node radius + margin */
|
|
1357
|
+
phrenMoveTo(node.x + nr, node.y - nr * 0.5, node);
|
|
1368
1358
|
}
|
|
1369
1359
|
/* fire external callback so VS Code extension can react to selection */
|
|
1370
1360
|
if (_nodeSelectCb && node) {
|
|
@@ -86,53 +86,29 @@ ${TASK_UI_STYLES}
|
|
|
86
86
|
|
|
87
87
|
<!-- ── Review Tab ────────────────────────────────────────── -->
|
|
88
88
|
<div id="tab-review" class="tab-content">
|
|
89
|
-
<div class="
|
|
90
|
-
<div class="card-header"><h2>Sync State</h2></div>
|
|
91
|
-
<div class="card-body">
|
|
92
|
-
<div id="sync-state-summary" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;font-size:var(--text-base)">
|
|
93
|
-
<div><strong>Auto-save</strong><div class="text-muted">${h(sync.autoSaveStatus || "n/a")}</div></div>
|
|
94
|
-
<div><strong>Last pull</strong><div class="text-muted">${h(sync.lastPullStatus || "n/a")} ${h(sync.lastPullAt || "")}</div></div>
|
|
95
|
-
<div><strong>Last push</strong><div class="text-muted">${h(sync.lastPushStatus || "n/a")} ${h(sync.lastPushAt || "")}</div></div>
|
|
96
|
-
<div><strong>Unsynced commits</strong><div class="text-muted">${h(String(sync.unsyncedCommits || 0))}</div></div>
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
<details class="review-help" style="margin-bottom:16px">
|
|
101
|
-
<summary>How review works</summary>
|
|
102
|
-
<p style="margin-top:8px;font-size:var(--text-sm);color:var(--muted)">Items waiting for your review. Approve to keep, reject to remove. You can also edit the text before approving, or use the checkboxes to bulk approve or reject multiple items at once.</p>
|
|
103
|
-
</details>
|
|
104
|
-
|
|
105
|
-
<p style="font-size:var(--text-sm);color:var(--muted);margin-bottom:12px;letter-spacing:-0.01em">Items waiting for your review. Approve to keep, reject to remove.</p>
|
|
106
|
-
|
|
107
|
-
<div id="review-summary-banner" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;align-items:center"></div>
|
|
108
|
-
|
|
109
|
-
<div class="review-filters" id="review-filters" style="display:none">
|
|
89
|
+
<div class="review-toolbar" id="review-filters" style="display:none">
|
|
110
90
|
<select id="review-filter-project">
|
|
111
91
|
<option value="">All projects</option>
|
|
112
92
|
</select>
|
|
113
|
-
<
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<label id="review-select-all" style="display:none;margin-left:auto;align-items:center;gap:6px;font-size:var(--text-sm);color:var(--muted);cursor:pointer;user-select:none">
|
|
121
|
-
<input type="checkbox" onchange="toggleSelectAll(this.checked)" style="width:14px;height:14px;cursor:pointer;accent-color:var(--accent)" />
|
|
93
|
+
<label class="review-flagged-toggle" id="review-flagged-toggle">
|
|
94
|
+
<input type="checkbox" id="highlight-only-btn" />
|
|
95
|
+
<span>Flagged only</span>
|
|
96
|
+
</label>
|
|
97
|
+
<span id="review-filter-count" class="text-muted" style="font-size:var(--text-sm);margin-left:auto"></span>
|
|
98
|
+
<label id="review-select-all" style="display:none;align-items:center;gap:6px;font-size:var(--text-sm);color:var(--muted);cursor:pointer;user-select:none">
|
|
99
|
+
<input type="checkbox" id="review-select-all-cb" style="width:14px;height:14px;cursor:pointer;accent-color:var(--accent)" />
|
|
122
100
|
Select all
|
|
123
101
|
</label>
|
|
124
|
-
<
|
|
102
|
+
<span id="review-sync-status" class="review-sync-dot" title="Sync status">
|
|
103
|
+
<span class="review-sync-indicator" id="review-sync-indicator"></span>
|
|
104
|
+
</span>
|
|
125
105
|
</div>
|
|
126
106
|
|
|
127
107
|
<div id="batch-bar" class="batch-bar">
|
|
128
108
|
<span id="batch-count" class="batch-bar-count"></span>
|
|
129
|
-
<button class="btn btn-sm btn-approve"
|
|
130
|
-
<button class="btn btn-sm btn-reject"
|
|
131
|
-
<button class="btn btn-sm"
|
|
132
|
-
</div>
|
|
133
|
-
|
|
134
|
-
<div id="review-kbd-hints" style="font-size:var(--text-xs);color:var(--muted);margin-bottom:12px;display:none;gap:16px;flex-wrap:wrap">
|
|
135
|
-
<span><kbd>j</kbd>/<kbd>k</kbd> navigate</span>
|
|
109
|
+
<button class="btn btn-sm btn-approve" data-batch-action="approve">Approve selected</button>
|
|
110
|
+
<button class="btn btn-sm btn-reject" data-batch-action="reject">Reject selected</button>
|
|
111
|
+
<button class="btn btn-sm" data-batch-action="clear">Clear</button>
|
|
136
112
|
</div>
|
|
137
113
|
|
|
138
114
|
<div class="review-cards" id="review-cards-list">
|
|
@@ -156,9 +132,10 @@ ${TASK_UI_STYLES}
|
|
|
156
132
|
<div style="max-width:720px;margin:0 auto">
|
|
157
133
|
<div style="display:flex;gap:8px;margin-bottom:16px">
|
|
158
134
|
<input type="text" id="search-query" placeholder="Search fragments, findings, tasks..." style="flex:1;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 12px;background:var(--surface);color:var(--ink);font-size:var(--text-base);font-family:var(--font);outline:none" />
|
|
159
|
-
<
|
|
160
|
-
<
|
|
161
|
-
|
|
135
|
+
<div id="search-project-wrap" style="position:relative">
|
|
136
|
+
<button id="search-project-btn" type="button" onclick="window._phrenToggleProjectDropdown()" style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 10px;background:var(--surface);color:var(--ink);font-size:var(--text-sm);cursor:pointer;font-family:var(--font);min-width:120px;text-align:left;white-space:nowrap">All projects</button>
|
|
137
|
+
<div id="search-project-dropdown" style="display:none;position:absolute;top:100%;left:0;z-index:50;margin-top:4px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:0 4px 12px rgba(0,0,0,.15);max-height:240px;overflow-y:auto;min-width:160px"></div>
|
|
138
|
+
</div>
|
|
162
139
|
<select id="search-type-filter" style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 10px;background:var(--surface);color:var(--ink);font-size:var(--text-sm)">
|
|
163
140
|
<option value="">All types</option>
|
|
164
141
|
<option value="finding">Findings</option>
|
|
@@ -275,6 +252,12 @@ ${TASK_UI_STYLES}
|
|
|
275
252
|
<div id="settings-scope-note" style="font-size:var(--text-sm);color:var(--muted)">Showing global settings. Select a project to view and edit per-project overrides.</div>
|
|
276
253
|
</div>
|
|
277
254
|
</section>
|
|
255
|
+
<section id="settings-project-info-section" class="settings-section" style="display:none;border-top:3px solid color-mix(in srgb, var(--accent) 45%, var(--border))">
|
|
256
|
+
<div class="settings-section-header">Project Info</div>
|
|
257
|
+
<div class="settings-section-body">
|
|
258
|
+
<div id="settings-project-info" style="color:var(--muted)"></div>
|
|
259
|
+
</div>
|
|
260
|
+
</section>
|
|
278
261
|
<section class="settings-section settings-section-findings">
|
|
279
262
|
<div class="settings-section-header">Findings</div>
|
|
280
263
|
<div class="settings-section-body">
|
|
@@ -337,10 +320,10 @@ ${renderSkillUiEnhancementScript(h(authToken || ""))}
|
|
|
337
320
|
${renderProjectReferenceEnhancementScript(h(authToken || ""))}
|
|
338
321
|
</script>
|
|
339
322
|
<script${nonceAttr}>
|
|
340
|
-
${renderTasksAndSettingsScript(
|
|
323
|
+
${renderTasksAndSettingsScript(authToken || "")}
|
|
341
324
|
</script>
|
|
342
325
|
<script${nonceAttr}>
|
|
343
|
-
${renderSearchScript(
|
|
326
|
+
${renderSearchScript(authToken || "")}
|
|
344
327
|
</script>
|
|
345
328
|
<script${nonceAttr}>
|
|
346
329
|
${renderEventWiringScript()}
|
|
@@ -592,8 +592,9 @@ export function renderReviewQueueEditSyncScript() {
|
|
|
592
592
|
return "";
|
|
593
593
|
}
|
|
594
594
|
export function renderTasksAndSettingsScript(authToken) {
|
|
595
|
+
const safeToken = JSON.stringify(authToken).slice(1, -1);
|
|
595
596
|
return `(function() {
|
|
596
|
-
var _tsAuthToken = '${
|
|
597
|
+
var _tsAuthToken = '${safeToken}';
|
|
597
598
|
var _allTasks = [];
|
|
598
599
|
var esc = window._phrenEsc;
|
|
599
600
|
|
|
@@ -974,6 +975,35 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
974
975
|
|
|
975
976
|
var isProject = Boolean(selectedProject);
|
|
976
977
|
|
|
978
|
+
// Render project info section
|
|
979
|
+
var infoSection = document.getElementById('settings-project-info-section');
|
|
980
|
+
var infoEl = document.getElementById('settings-project-info');
|
|
981
|
+
if (infoSection && infoEl) {
|
|
982
|
+
if (isProject && data.projectInfo) {
|
|
983
|
+
var pi = data.projectInfo;
|
|
984
|
+
infoSection.style.display = '';
|
|
985
|
+
var infoHtml = '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;font-size:var(--text-sm)">';
|
|
986
|
+
infoHtml += '<div><strong style="color:var(--ink)">Disk path</strong><div class="text-muted" style="font-family:var(--mono);font-size:var(--text-xs);word-break:break-all">' + esc(pi.diskPath) + '</div></div>';
|
|
987
|
+
infoHtml += '<div><strong style="color:var(--ink)">Ownership</strong><div class="text-muted">' + esc(pi.ownership) + '</div></div>';
|
|
988
|
+
infoHtml += '<div><strong style="color:var(--ink)">Config file</strong><div class="text-muted" style="font-family:var(--mono);font-size:var(--text-xs);word-break:break-all">' + esc(pi.configFile) + '</div>' + (pi.configExists ? '<span style="color:var(--green);font-size:var(--text-xs)">exists</span>' : '<span style="color:var(--muted);font-size:var(--text-xs)">not created</span>') + '</div>';
|
|
989
|
+
infoHtml += '<div><strong style="color:var(--ink)">Findings</strong><div class="text-muted">' + pi.findingCount + ' entries</div></div>';
|
|
990
|
+
infoHtml += '<div><strong style="color:var(--ink)">Tasks</strong><div class="text-muted">' + pi.taskCount + ' in queue</div></div>';
|
|
991
|
+
infoHtml += '</div>';
|
|
992
|
+
var files = [];
|
|
993
|
+
if (pi.hasFindings) files.push('FINDINGS.md');
|
|
994
|
+
if (pi.hasTasks) files.push('tasks.md');
|
|
995
|
+
if (pi.hasSummary) files.push('summary.md');
|
|
996
|
+
if (pi.hasClaudeMd) files.push('CLAUDE.md');
|
|
997
|
+
if (files.length) {
|
|
998
|
+
infoHtml += '<div style="margin-top:10px;font-size:var(--text-xs);color:var(--muted)">Files: ' + files.map(function(f) { return '<span class="badge" style="margin-right:4px">' + esc(f) + '</span>'; }).join('') + '</div>';
|
|
999
|
+
}
|
|
1000
|
+
infoEl.innerHTML = infoHtml;
|
|
1001
|
+
} else {
|
|
1002
|
+
infoSection.style.display = 'none';
|
|
1003
|
+
infoEl.innerHTML = '';
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
977
1007
|
function sourceBadge(isOverride) {
|
|
978
1008
|
if (!isProject) return '';
|
|
979
1009
|
return isOverride
|
|
@@ -1189,21 +1219,21 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
1189
1219
|
}
|
|
1190
1220
|
list.innerHTML = '<table style="width:100%;border-collapse:collapse;font-size:var(--text-sm)">' +
|
|
1191
1221
|
'<thead><tr style="border-bottom:1px solid var(--border);text-align:left">' +
|
|
1192
|
-
'<th style="padding:8px">Session</th><th style="padding:8px">Project</th><th style="padding:8px">
|
|
1193
|
-
'<th style="padding:8px">
|
|
1222
|
+
'<th style="padding:8px">Session</th><th style="padding:8px">Project</th><th style="padding:8px">Started</th>' +
|
|
1223
|
+
'<th style="padding:8px">Ended</th><th style="padding:8px">Duration</th><th style="padding:8px">Findings</th></tr></thead><tbody>' +
|
|
1194
1224
|
sessions.map(function(s) {
|
|
1195
1225
|
var id = s.sessionId.slice(0, 8);
|
|
1196
|
-
var
|
|
1226
|
+
var startDate = (s.startedAt || '').slice(0, 16).replace('T', ' ');
|
|
1227
|
+
var endDate = s.endedAt ? (s.endedAt || '').slice(0, 16).replace('T', ' ') : '<span style="color:var(--green)">active</span>';
|
|
1197
1228
|
var dur = s.durationMins != null ? s.durationMins + 'm' : '—';
|
|
1198
|
-
var status = s.status === 'active' ? '<span style="color:var(--green)">● active</span>' : 'ended';
|
|
1199
1229
|
var summarySnip = s.summary ? '<div class="text-muted" style="font-size:var(--text-xs);max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc(s.summary.slice(0, 80)) + '</div>' : '';
|
|
1200
1230
|
return '<tr style="border-bottom:1px solid var(--border);cursor:pointer" data-ts-action="showSessionDetail" data-session-id="' + esc(s.sessionId) + '">' +
|
|
1201
1231
|
'<td style="padding:8px;font-family:monospace">' + esc(id) + summarySnip + '</td>' +
|
|
1202
1232
|
'<td style="padding:8px">' + esc(s.project || '—') + '</td>' +
|
|
1203
|
-
'<td style="padding:8px">' + esc(
|
|
1233
|
+
'<td style="padding:8px">' + esc(startDate) + '</td>' +
|
|
1234
|
+
'<td style="padding:8px">' + endDate + '</td>' +
|
|
1204
1235
|
'<td style="padding:8px">' + esc(dur) + '</td>' +
|
|
1205
|
-
'<td style="padding:8px">' + (s.findingsAdded || 0) + '</td>'
|
|
1206
|
-
'<td style="padding:8px">' + status + '</td></tr>';
|
|
1236
|
+
'<td style="padding:8px">' + (s.findingsAdded || 0) + '</td></tr>';
|
|
1207
1237
|
}).join('') + '</tbody></table>';
|
|
1208
1238
|
}
|
|
1209
1239
|
|
|
@@ -1359,94 +1389,161 @@ export function renderSearchScript(authToken) {
|
|
|
1359
1389
|
|
|
1360
1390
|
var esc = window._phrenEsc;
|
|
1361
1391
|
|
|
1392
|
+
function relativeDate(iso) {
|
|
1393
|
+
if (!iso) return '';
|
|
1394
|
+
var d = new Date(iso);
|
|
1395
|
+
var now = new Date();
|
|
1396
|
+
var diff = now.getTime() - d.getTime();
|
|
1397
|
+
var days = Math.floor(diff / 86400000);
|
|
1398
|
+
if (days < 1) return 'today';
|
|
1399
|
+
if (days === 1) return '1d ago';
|
|
1400
|
+
if (days < 7) return days + 'd ago';
|
|
1401
|
+
if (days < 30) return Math.floor(days / 7) + 'w ago';
|
|
1402
|
+
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
|
1403
|
+
return months[d.getMonth()] + ' ' + d.getDate();
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// Multi-select project filter
|
|
1407
|
+
var _selectedProjects = [];
|
|
1408
|
+
function getSelectedProjects() { return _selectedProjects.slice(); }
|
|
1409
|
+
function toggleProjectFilter(name) {
|
|
1410
|
+
var idx = _selectedProjects.indexOf(name);
|
|
1411
|
+
if (idx === -1) _selectedProjects.push(name);
|
|
1412
|
+
else _selectedProjects.splice(idx, 1);
|
|
1413
|
+
renderProjectFilterLabel();
|
|
1414
|
+
renderProjectFilterChecks();
|
|
1415
|
+
}
|
|
1416
|
+
function renderProjectFilterLabel() {
|
|
1417
|
+
var btn = document.getElementById('search-project-btn');
|
|
1418
|
+
if (!btn) return;
|
|
1419
|
+
if (!_selectedProjects.length) btn.textContent = 'All projects';
|
|
1420
|
+
else if (_selectedProjects.length === 1) btn.textContent = _selectedProjects[0];
|
|
1421
|
+
else btn.textContent = _selectedProjects.length + ' projects';
|
|
1422
|
+
}
|
|
1423
|
+
function renderProjectFilterChecks() {
|
|
1424
|
+
var items = document.querySelectorAll('#search-project-dropdown input[type=checkbox]');
|
|
1425
|
+
for (var i = 0; i < items.length; i++) {
|
|
1426
|
+
items[i].checked = _selectedProjects.indexOf(items[i].value) !== -1;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
window._phrenToggleProjectDropdown = function() {
|
|
1430
|
+
var dd = document.getElementById('search-project-dropdown');
|
|
1431
|
+
if (dd) dd.style.display = dd.style.display === 'none' ? 'block' : 'none';
|
|
1432
|
+
};
|
|
1433
|
+
document.addEventListener('click', function(e) {
|
|
1434
|
+
var wrap = document.getElementById('search-project-wrap');
|
|
1435
|
+
var dd = document.getElementById('search-project-dropdown');
|
|
1436
|
+
if (wrap && dd && !wrap.contains(e.target)) dd.style.display = 'none';
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
function parseResults(lines) {
|
|
1440
|
+
var cards = [];
|
|
1441
|
+
var current = null;
|
|
1442
|
+
for (var i = 0; i < lines.length; i++) {
|
|
1443
|
+
var line = lines[i];
|
|
1444
|
+
if (!line.trim()) continue;
|
|
1445
|
+
if (line.startsWith('[') && line.indexOf(']') > 0) {
|
|
1446
|
+
if (current) cards.push(current);
|
|
1447
|
+
var bracket = line.indexOf(']');
|
|
1448
|
+
var source = line.slice(1, bracket);
|
|
1449
|
+
var meta = line.slice(bracket + 1).trim();
|
|
1450
|
+
current = { source: source, meta: meta, snippets: [] };
|
|
1451
|
+
} else if (line === '(keyword fallback)') {
|
|
1452
|
+
// skip
|
|
1453
|
+
} else if (current) {
|
|
1454
|
+
current.snippets.push(line);
|
|
1455
|
+
} else {
|
|
1456
|
+
cards.push({ source: '', meta: '', snippets: [line] });
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
if (current) cards.push(current);
|
|
1460
|
+
return cards;
|
|
1461
|
+
}
|
|
1462
|
+
function renderCards(cards) {
|
|
1463
|
+
var html = '';
|
|
1464
|
+
for (var c = 0; c < cards.length; c++) {
|
|
1465
|
+
var card = cards[c];
|
|
1466
|
+
html += '<div class="card" style="margin-bottom:8px">';
|
|
1467
|
+
html += '<div class="card-header" style="padding:10px 14px;display:flex;align-items:center">';
|
|
1468
|
+
if (card.source) {
|
|
1469
|
+
html += '<span style="font-weight:500;font-size:var(--text-sm)">' + esc(card.source) + '</span>';
|
|
1470
|
+
}
|
|
1471
|
+
if (card.meta) {
|
|
1472
|
+
html += '<span class="text-muted" style="font-size:var(--text-xs);margin-left:8px">' + esc(card.meta) + '</span>';
|
|
1473
|
+
}
|
|
1474
|
+
html += '</div>';
|
|
1475
|
+
if (card.snippets.length) {
|
|
1476
|
+
html += '<div class="card-body" style="padding:10px 14px;font-size:var(--text-sm);white-space:pre-wrap;color:var(--ink-secondary)">';
|
|
1477
|
+
html += esc(card.snippets.join('\\n'));
|
|
1478
|
+
html += '</div>';
|
|
1479
|
+
}
|
|
1480
|
+
html += '</div>';
|
|
1481
|
+
}
|
|
1482
|
+
return html;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1362
1485
|
function doSearch() {
|
|
1363
1486
|
var q = document.getElementById('search-query').value.trim();
|
|
1364
1487
|
if (!q) return;
|
|
1365
|
-
var
|
|
1488
|
+
var projects = getSelectedProjects();
|
|
1366
1489
|
var type = document.getElementById('search-type-filter').value;
|
|
1367
1490
|
var statusEl = document.getElementById('search-status');
|
|
1368
1491
|
var resultsEl = document.getElementById('search-results');
|
|
1369
1492
|
statusEl.textContent = 'Searching...';
|
|
1370
1493
|
resultsEl.innerHTML = '';
|
|
1371
1494
|
|
|
1372
|
-
var
|
|
1373
|
-
if (
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
resultsEl.innerHTML = '<div style="padding:40px;color:var(--muted);text-align:center">No results for \\u201c' + esc(q) + '\\u201d</div>';
|
|
1386
|
-
return;
|
|
1495
|
+
var fetches = [];
|
|
1496
|
+
if (projects.length <= 1) {
|
|
1497
|
+
var url = '/api/search?q=' + encodeURIComponent(q) + '&limit=20';
|
|
1498
|
+
if (projects.length === 1) url += '&project=' + encodeURIComponent(projects[0]);
|
|
1499
|
+
if (type) url += '&type=' + encodeURIComponent(type);
|
|
1500
|
+
fetches.push(fetch(searchAuthUrl(url)).then(function(r) { return r.json(); }));
|
|
1501
|
+
} else {
|
|
1502
|
+
for (var pi = 0; pi < projects.length; pi++) {
|
|
1503
|
+
(function(proj) {
|
|
1504
|
+
var purl = '/api/search?q=' + encodeURIComponent(q) + '&limit=20&project=' + encodeURIComponent(proj);
|
|
1505
|
+
if (type) purl += '&type=' + encodeURIComponent(type);
|
|
1506
|
+
fetches.push(fetch(searchAuthUrl(purl)).then(function(r) { return r.json(); }));
|
|
1507
|
+
})(projects[pi]);
|
|
1387
1508
|
}
|
|
1509
|
+
}
|
|
1388
1510
|
|
|
1389
|
-
|
|
1390
|
-
var
|
|
1391
|
-
var
|
|
1392
|
-
for (var
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
if (current) cards.push(current);
|
|
1397
|
-
var bracket = line.indexOf(']');
|
|
1398
|
-
var source = line.slice(1, bracket);
|
|
1399
|
-
var meta = line.slice(bracket + 1).trim();
|
|
1400
|
-
current = { source: source, meta: meta, snippets: [] };
|
|
1401
|
-
} else if (line === '(keyword fallback)') {
|
|
1402
|
-
// skip
|
|
1403
|
-
} else if (current) {
|
|
1404
|
-
current.snippets.push(line);
|
|
1405
|
-
} else {
|
|
1406
|
-
// orphan line, show as standalone
|
|
1407
|
-
cards.push({ source: '', meta: '', snippets: [line] });
|
|
1408
|
-
}
|
|
1511
|
+
Promise.all(fetches).then(function(results) {
|
|
1512
|
+
var allCards = [];
|
|
1513
|
+
var hasError = false;
|
|
1514
|
+
for (var ri = 0; ri < results.length; ri++) {
|
|
1515
|
+
if (!results[ri].ok) { hasError = true; continue; }
|
|
1516
|
+
var parsed = parseResults(results[ri].results || []);
|
|
1517
|
+
allCards = allCards.concat(parsed);
|
|
1409
1518
|
}
|
|
1410
|
-
if (
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
for (var c = 0; c < cards.length; c++) {
|
|
1415
|
-
var card = cards[c];
|
|
1416
|
-
html += '<div class="card" style="margin-bottom:8px">';
|
|
1417
|
-
html += '<div class="card-header" style="padding:10px 14px">';
|
|
1418
|
-
if (card.source) {
|
|
1419
|
-
html += '<span style="font-weight:500;font-size:var(--text-sm)">' + esc(card.source) + '</span>';
|
|
1420
|
-
}
|
|
1421
|
-
if (card.meta) {
|
|
1422
|
-
html += '<span class="text-muted" style="font-size:var(--text-xs);margin-left:8px">' + esc(card.meta) + '</span>';
|
|
1423
|
-
}
|
|
1424
|
-
html += '</div>';
|
|
1425
|
-
if (card.snippets.length) {
|
|
1426
|
-
html += '<div class="card-body" style="padding:10px 14px;font-size:var(--text-sm);white-space:pre-wrap;color:var(--ink-secondary)">';
|
|
1427
|
-
html += esc(card.snippets.join('\\n'));
|
|
1428
|
-
html += '</div>';
|
|
1429
|
-
}
|
|
1430
|
-
html += '</div>';
|
|
1519
|
+
if (!allCards.length) {
|
|
1520
|
+
statusEl.textContent = hasError ? 'Search error.' : 'No results.';
|
|
1521
|
+
resultsEl.innerHTML = '<div style="padding:40px;color:var(--muted);text-align:center">' + (hasError ? 'Search failed' : 'No results for \\u201c' + esc(q) + '\\u201d') + '</div>';
|
|
1522
|
+
return;
|
|
1431
1523
|
}
|
|
1432
|
-
|
|
1524
|
+
statusEl.textContent = allCards.length + ' result(s)';
|
|
1525
|
+
resultsEl.innerHTML = renderCards(allCards);
|
|
1433
1526
|
}).catch(function(err) {
|
|
1434
1527
|
statusEl.textContent = '';
|
|
1435
1528
|
resultsEl.innerHTML = '<div style="padding:24px;color:var(--muted);text-align:center">Search error: ' + esc(String(err)) + '</div>';
|
|
1436
1529
|
});
|
|
1437
1530
|
}
|
|
1438
1531
|
|
|
1439
|
-
// Populate project filter
|
|
1532
|
+
// Populate project filter dropdown
|
|
1440
1533
|
function loadSearchProjects() {
|
|
1441
1534
|
if (_searchProjectsLoaded) return;
|
|
1442
1535
|
_searchProjectsLoaded = true;
|
|
1443
1536
|
fetch(searchAuthUrl('/api/projects')).then(function(r) { return r.json(); }).then(function(data) {
|
|
1444
1537
|
if (!data.ok) return;
|
|
1445
|
-
var
|
|
1538
|
+
var dd = document.getElementById('search-project-dropdown');
|
|
1539
|
+
if (!dd) return;
|
|
1540
|
+
var html = '';
|
|
1446
1541
|
(data.projects || []).forEach(function(p) {
|
|
1447
|
-
var
|
|
1448
|
-
|
|
1449
|
-
|
|
1542
|
+
html += '<label class="search-ms-item" style="display:flex;align-items:center;gap:8px;padding:6px 12px;cursor:pointer;font-size:var(--text-sm);white-space:nowrap"><input type="checkbox" value="' + esc(p.name) + '" style="accent-color:var(--accent);cursor:pointer" /><span>' + esc(p.name) + '</span></label>';
|
|
1543
|
+
});
|
|
1544
|
+
dd.innerHTML = html;
|
|
1545
|
+
dd.querySelectorAll('input[type=checkbox]').forEach(function(cb) {
|
|
1546
|
+
cb.addEventListener('change', function() { toggleProjectFilter(cb.value); });
|
|
1450
1547
|
});
|
|
1451
1548
|
}).catch(function() {});
|
|
1452
1549
|
}
|
|
@@ -1495,13 +1592,24 @@ export function renderEventWiringScript() {
|
|
|
1495
1592
|
// --- Review filters ---
|
|
1496
1593
|
var reviewFilterProject = document.getElementById('review-filter-project');
|
|
1497
1594
|
if (reviewFilterProject) reviewFilterProject.addEventListener('change', function() { filterReviewCards(); });
|
|
1498
|
-
var reviewFilterMachine = document.getElementById('review-filter-machine');
|
|
1499
|
-
if (reviewFilterMachine) reviewFilterMachine.addEventListener('change', function() { filterReviewCards(); });
|
|
1500
|
-
var reviewFilterModel = document.getElementById('review-filter-model');
|
|
1501
|
-
if (reviewFilterModel) reviewFilterModel.addEventListener('change', function() { filterReviewCards(); });
|
|
1502
1595
|
|
|
1503
1596
|
var highlightBtn = document.getElementById('highlight-only-btn');
|
|
1504
|
-
if (highlightBtn) highlightBtn.addEventListener('
|
|
1597
|
+
if (highlightBtn) highlightBtn.addEventListener('change', function() { filterReviewCards(); });
|
|
1598
|
+
|
|
1599
|
+
var selectAllCb = document.getElementById('review-select-all-cb');
|
|
1600
|
+
if (selectAllCb) selectAllCb.addEventListener('change', function() { toggleSelectAll(this.checked); });
|
|
1601
|
+
|
|
1602
|
+
// Batch bar buttons
|
|
1603
|
+
document.addEventListener('click', function(e) {
|
|
1604
|
+
var target = e.target;
|
|
1605
|
+
if (!target || typeof target.closest !== 'function') return;
|
|
1606
|
+
var actionEl = target.closest('[data-batch-action]');
|
|
1607
|
+
if (!actionEl) return;
|
|
1608
|
+
var action = actionEl.getAttribute('data-batch-action');
|
|
1609
|
+
if (action === 'approve') { batchAction('approve'); }
|
|
1610
|
+
else if (action === 'reject') { batchAction('reject'); }
|
|
1611
|
+
else if (action === 'clear') { clearBatchSelection(); }
|
|
1612
|
+
});
|
|
1505
1613
|
|
|
1506
1614
|
// --- Graph controls ---
|
|
1507
1615
|
var graphZoomIn = document.getElementById('graph-zoom-in');
|
|
@@ -1521,6 +1629,20 @@ export function renderEventWiringScript() {
|
|
|
1521
1629
|
var sessionsFilterProject = document.getElementById('sessions-filter-project');
|
|
1522
1630
|
if (sessionsFilterProject) sessionsFilterProject.addEventListener('change', function() { loadSessions(); });
|
|
1523
1631
|
|
|
1632
|
+
// --- Mascot click animation ---
|
|
1633
|
+
var mascotSvg = document.querySelector('.header-brand svg');
|
|
1634
|
+
if (mascotSvg) {
|
|
1635
|
+
mascotSvg.addEventListener('click', function() {
|
|
1636
|
+
mascotSvg.classList.remove('popped');
|
|
1637
|
+
void mascotSvg.offsetWidth;
|
|
1638
|
+
mascotSvg.classList.add('popped');
|
|
1639
|
+
mascotSvg.addEventListener('animationend', function handler() {
|
|
1640
|
+
mascotSvg.classList.remove('popped');
|
|
1641
|
+
mascotSvg.removeEventListener('animationend', handler);
|
|
1642
|
+
});
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1524
1646
|
// --- Command palette ---
|
|
1525
1647
|
var cmdpal = document.getElementById('cmdpal');
|
|
1526
1648
|
if (cmdpal) cmdpal.addEventListener('click', function(e) { closeCmdPal(e); });
|
|
@@ -13,7 +13,7 @@ import { buildGraph, collectProjectsForUI, collectSkillsForUI, getHooksData, isA
|
|
|
13
13
|
import { CONSOLIDATION_ENTRY_THRESHOLD } from "./content-validate.js";
|
|
14
14
|
import { ensureTopicReferenceDoc, getProjectTopicsResponse, listProjectReferenceDocs, pinProjectTopicSuggestion, readReferenceContent, reclassifyLegacyTopicDocs, unpinProjectTopicSuggestion, writeProjectTopics, } from "./project-topics.js";
|
|
15
15
|
import { getWorkflowPolicy, updateWorkflowPolicy, mergeConfig, getRetentionPolicy, getProjectConfigOverrides, VALID_TASK_MODES } from "./governance-policy.js";
|
|
16
|
-
import { updateProjectConfigOverrides } from "./project-config.js";
|
|
16
|
+
import { readProjectConfig, updateProjectConfigOverrides } from "./project-config.js";
|
|
17
17
|
import { findSkill } from "./skill-registry.js";
|
|
18
18
|
import { setSkillEnabledAndSync } from "./skill-files.js";
|
|
19
19
|
import { listAllSessions, getSessionArtifacts } from "./mcp-session.js";
|
|
@@ -803,8 +803,31 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
803
803
|
try {
|
|
804
804
|
const { runSearch } = await import("./cli-search.js");
|
|
805
805
|
const result = await runSearch({ query, limit: Math.min(searchLimit, 50), project: searchProject, type: searchType }, phrenPath, profile || "");
|
|
806
|
+
// Build file date map from source headers like [project/filename]
|
|
807
|
+
const fileDates = {};
|
|
808
|
+
for (const line of result.lines) {
|
|
809
|
+
const srcMatch = line.match(/^\[([^\]]+)\]\s/);
|
|
810
|
+
if (srcMatch) {
|
|
811
|
+
const sourceKey = srcMatch[1];
|
|
812
|
+
if (fileDates[sourceKey])
|
|
813
|
+
continue;
|
|
814
|
+
const slashIdx = sourceKey.indexOf("/");
|
|
815
|
+
if (slashIdx > 0) {
|
|
816
|
+
const proj = sourceKey.slice(0, slashIdx);
|
|
817
|
+
const file = sourceKey.slice(slashIdx + 1);
|
|
818
|
+
try {
|
|
819
|
+
const filePath = path.join(phrenPath, proj, file);
|
|
820
|
+
if (fs.existsSync(filePath)) {
|
|
821
|
+
const stat = fs.statSync(filePath);
|
|
822
|
+
fileDates[sourceKey] = stat.mtime.toISOString();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
catch { /* skip */ }
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
806
829
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
807
|
-
res.end(JSON.stringify({ ok: true, query, results: result.lines }));
|
|
830
|
+
res.end(JSON.stringify({ ok: true, query, results: result.lines, fileDates }));
|
|
808
831
|
}
|
|
809
832
|
catch (err) {
|
|
810
833
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
@@ -977,6 +1000,37 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
977
1000
|
const settingsProject = String(qs.project || "");
|
|
978
1001
|
const merged = settingsProject && isValidProjectName(settingsProject) ? mergeConfig(phrenPath, settingsProject) : null;
|
|
979
1002
|
const overrides = settingsProject && isValidProjectName(settingsProject) ? getProjectConfigOverrides(phrenPath, settingsProject) : null;
|
|
1003
|
+
// Build project info when a specific project is selected
|
|
1004
|
+
let projectInfo = null;
|
|
1005
|
+
if (settingsProject && isValidProjectName(settingsProject)) {
|
|
1006
|
+
const projectDir = path.join(phrenPath, settingsProject);
|
|
1007
|
+
const configFile = path.join(projectDir, "phren.project.yaml");
|
|
1008
|
+
const projConfig = readProjectConfig(phrenPath, settingsProject);
|
|
1009
|
+
const findingsPath = path.join(projectDir, "FINDINGS.md");
|
|
1010
|
+
const taskPath = path.join(projectDir, "tasks.md");
|
|
1011
|
+
let findingCount = 0;
|
|
1012
|
+
if (fs.existsSync(findingsPath)) {
|
|
1013
|
+
findingCount = (fs.readFileSync(findingsPath, "utf8").match(/^- /gm) || []).length;
|
|
1014
|
+
}
|
|
1015
|
+
let taskCount = 0;
|
|
1016
|
+
if (fs.existsSync(taskPath)) {
|
|
1017
|
+
const queueMatch = fs.readFileSync(taskPath, "utf8").match(/## Queue[\s\S]*?(?=## |$)/);
|
|
1018
|
+
if (queueMatch)
|
|
1019
|
+
taskCount = (queueMatch[0].match(/^- /gm) || []).length;
|
|
1020
|
+
}
|
|
1021
|
+
projectInfo = {
|
|
1022
|
+
diskPath: projConfig.sourcePath || projectDir,
|
|
1023
|
+
ownership: projConfig.ownership || "default",
|
|
1024
|
+
configFile,
|
|
1025
|
+
configExists: fs.existsSync(configFile),
|
|
1026
|
+
hasFindings: fs.existsSync(findingsPath),
|
|
1027
|
+
hasTasks: fs.existsSync(taskPath),
|
|
1028
|
+
hasSummary: fs.existsSync(path.join(projectDir, "summary.md")),
|
|
1029
|
+
hasClaudeMd: fs.existsSync(path.join(projectDir, "CLAUDE.md")),
|
|
1030
|
+
findingCount,
|
|
1031
|
+
taskCount,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
980
1034
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
981
1035
|
res.end(JSON.stringify({
|
|
982
1036
|
ok: true,
|
|
@@ -994,6 +1048,7 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
994
1048
|
workflowPolicy,
|
|
995
1049
|
merged,
|
|
996
1050
|
overrides,
|
|
1051
|
+
projectInfo,
|
|
997
1052
|
}));
|
|
998
1053
|
}
|
|
999
1054
|
catch (err) {
|
package/mcp/dist/phren-core.js
CHANGED
|
@@ -25,7 +25,7 @@ export const EXTRA_ENTITY_PATTERNS = [
|
|
|
25
25
|
{ re: /\b\d{4}[-/]\d{2}[-/]\d{2}\b/g, label: "date" },
|
|
26
26
|
];
|
|
27
27
|
/** Union of all directory names reserved by phren infrastructure — not valid project names. */
|
|
28
|
-
export const RESERVED_PROJECT_DIR_NAMES = new Set(["global", ".runtime", ".sessions", ".
|
|
28
|
+
export const RESERVED_PROJECT_DIR_NAMES = new Set(["global", ".runtime", ".sessions", ".config", "profiles", "templates"]);
|
|
29
29
|
// Default timeout for execFileSync calls (30s for most operations, 10s for quick probes like `which`)
|
|
30
30
|
export const EXEC_TIMEOUT_MS = 30_000;
|
|
31
31
|
export const EXEC_TIMEOUT_QUICK_MS = 10_000;
|