@phren/cli 0.0.17 → 0.0.18
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-page.js +20 -37
- package/mcp/dist/memory-ui-scripts.js +181 -75
- 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
|
@@ -86,42 +86,22 @@ ${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
|
-
<span id="review-filter-count" class="text-muted" style="font-size:var(--text-sm);margin-left:8px"></span>
|
|
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">
|
|
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">
|
|
121
99
|
<input type="checkbox" onchange="toggleSelectAll(this.checked)" 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">
|
|
@@ -131,10 +111,6 @@ ${TASK_UI_STYLES}
|
|
|
131
111
|
<button class="btn btn-sm" onclick="clearBatchSelection()">Clear</button>
|
|
132
112
|
</div>
|
|
133
113
|
|
|
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>
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
114
|
<div class="review-cards" id="review-cards-list">
|
|
139
115
|
<div class="review-cards-loading" style="text-align:center;padding:40px;color:var(--muted)">Loading...</div>
|
|
140
116
|
</div>
|
|
@@ -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">
|
|
@@ -974,6 +974,35 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
974
974
|
|
|
975
975
|
var isProject = Boolean(selectedProject);
|
|
976
976
|
|
|
977
|
+
// Render project info section
|
|
978
|
+
var infoSection = document.getElementById('settings-project-info-section');
|
|
979
|
+
var infoEl = document.getElementById('settings-project-info');
|
|
980
|
+
if (infoSection && infoEl) {
|
|
981
|
+
if (isProject && data.projectInfo) {
|
|
982
|
+
var pi = data.projectInfo;
|
|
983
|
+
infoSection.style.display = '';
|
|
984
|
+
var infoHtml = '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;font-size:var(--text-sm)">';
|
|
985
|
+
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>';
|
|
986
|
+
infoHtml += '<div><strong style="color:var(--ink)">Ownership</strong><div class="text-muted">' + esc(pi.ownership) + '</div></div>';
|
|
987
|
+
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>';
|
|
988
|
+
infoHtml += '<div><strong style="color:var(--ink)">Findings</strong><div class="text-muted">' + pi.findingCount + ' entries</div></div>';
|
|
989
|
+
infoHtml += '<div><strong style="color:var(--ink)">Tasks</strong><div class="text-muted">' + pi.taskCount + ' in queue</div></div>';
|
|
990
|
+
infoHtml += '</div>';
|
|
991
|
+
var files = [];
|
|
992
|
+
if (pi.hasFindings) files.push('FINDINGS.md');
|
|
993
|
+
if (pi.hasTasks) files.push('tasks.md');
|
|
994
|
+
if (pi.hasSummary) files.push('summary.md');
|
|
995
|
+
if (pi.hasClaudeMd) files.push('CLAUDE.md');
|
|
996
|
+
if (files.length) {
|
|
997
|
+
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>';
|
|
998
|
+
}
|
|
999
|
+
infoEl.innerHTML = infoHtml;
|
|
1000
|
+
} else {
|
|
1001
|
+
infoSection.style.display = 'none';
|
|
1002
|
+
infoEl.innerHTML = '';
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
977
1006
|
function sourceBadge(isOverride) {
|
|
978
1007
|
if (!isProject) return '';
|
|
979
1008
|
return isOverride
|
|
@@ -1189,21 +1218,21 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
1189
1218
|
}
|
|
1190
1219
|
list.innerHTML = '<table style="width:100%;border-collapse:collapse;font-size:var(--text-sm)">' +
|
|
1191
1220
|
'<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">
|
|
1221
|
+
'<th style="padding:8px">Session</th><th style="padding:8px">Project</th><th style="padding:8px">Started</th>' +
|
|
1222
|
+
'<th style="padding:8px">Ended</th><th style="padding:8px">Duration</th><th style="padding:8px">Findings</th></tr></thead><tbody>' +
|
|
1194
1223
|
sessions.map(function(s) {
|
|
1195
1224
|
var id = s.sessionId.slice(0, 8);
|
|
1196
|
-
var
|
|
1225
|
+
var startDate = (s.startedAt || '').slice(0, 16).replace('T', ' ');
|
|
1226
|
+
var endDate = s.endedAt ? (s.endedAt || '').slice(0, 16).replace('T', ' ') : '<span style="color:var(--green)">active</span>';
|
|
1197
1227
|
var dur = s.durationMins != null ? s.durationMins + 'm' : '—';
|
|
1198
|
-
var status = s.status === 'active' ? '<span style="color:var(--green)">● active</span>' : 'ended';
|
|
1199
1228
|
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
1229
|
return '<tr style="border-bottom:1px solid var(--border);cursor:pointer" data-ts-action="showSessionDetail" data-session-id="' + esc(s.sessionId) + '">' +
|
|
1201
1230
|
'<td style="padding:8px;font-family:monospace">' + esc(id) + summarySnip + '</td>' +
|
|
1202
1231
|
'<td style="padding:8px">' + esc(s.project || '—') + '</td>' +
|
|
1203
|
-
'<td style="padding:8px">' + esc(
|
|
1232
|
+
'<td style="padding:8px">' + esc(startDate) + '</td>' +
|
|
1233
|
+
'<td style="padding:8px">' + endDate + '</td>' +
|
|
1204
1234
|
'<td style="padding:8px">' + esc(dur) + '</td>' +
|
|
1205
|
-
'<td style="padding:8px">' + (s.findingsAdded || 0) + '</td>'
|
|
1206
|
-
'<td style="padding:8px">' + status + '</td></tr>';
|
|
1235
|
+
'<td style="padding:8px">' + (s.findingsAdded || 0) + '</td></tr>';
|
|
1207
1236
|
}).join('') + '</tbody></table>';
|
|
1208
1237
|
}
|
|
1209
1238
|
|
|
@@ -1359,94 +1388,161 @@ export function renderSearchScript(authToken) {
|
|
|
1359
1388
|
|
|
1360
1389
|
var esc = window._phrenEsc;
|
|
1361
1390
|
|
|
1391
|
+
function relativeDate(iso) {
|
|
1392
|
+
if (!iso) return '';
|
|
1393
|
+
var d = new Date(iso);
|
|
1394
|
+
var now = new Date();
|
|
1395
|
+
var diff = now.getTime() - d.getTime();
|
|
1396
|
+
var days = Math.floor(diff / 86400000);
|
|
1397
|
+
if (days < 1) return 'today';
|
|
1398
|
+
if (days === 1) return '1d ago';
|
|
1399
|
+
if (days < 7) return days + 'd ago';
|
|
1400
|
+
if (days < 30) return Math.floor(days / 7) + 'w ago';
|
|
1401
|
+
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
|
1402
|
+
return months[d.getMonth()] + ' ' + d.getDate();
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// Multi-select project filter
|
|
1406
|
+
var _selectedProjects = [];
|
|
1407
|
+
function getSelectedProjects() { return _selectedProjects.slice(); }
|
|
1408
|
+
function toggleProjectFilter(name) {
|
|
1409
|
+
var idx = _selectedProjects.indexOf(name);
|
|
1410
|
+
if (idx === -1) _selectedProjects.push(name);
|
|
1411
|
+
else _selectedProjects.splice(idx, 1);
|
|
1412
|
+
renderProjectFilterLabel();
|
|
1413
|
+
renderProjectFilterChecks();
|
|
1414
|
+
}
|
|
1415
|
+
function renderProjectFilterLabel() {
|
|
1416
|
+
var btn = document.getElementById('search-project-btn');
|
|
1417
|
+
if (!btn) return;
|
|
1418
|
+
if (!_selectedProjects.length) btn.textContent = 'All projects';
|
|
1419
|
+
else if (_selectedProjects.length === 1) btn.textContent = _selectedProjects[0];
|
|
1420
|
+
else btn.textContent = _selectedProjects.length + ' projects';
|
|
1421
|
+
}
|
|
1422
|
+
function renderProjectFilterChecks() {
|
|
1423
|
+
var items = document.querySelectorAll('#search-project-dropdown input[type=checkbox]');
|
|
1424
|
+
for (var i = 0; i < items.length; i++) {
|
|
1425
|
+
items[i].checked = _selectedProjects.indexOf(items[i].value) !== -1;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
window._phrenToggleProjectDropdown = function() {
|
|
1429
|
+
var dd = document.getElementById('search-project-dropdown');
|
|
1430
|
+
if (dd) dd.style.display = dd.style.display === 'none' ? 'block' : 'none';
|
|
1431
|
+
};
|
|
1432
|
+
document.addEventListener('click', function(e) {
|
|
1433
|
+
var wrap = document.getElementById('search-project-wrap');
|
|
1434
|
+
var dd = document.getElementById('search-project-dropdown');
|
|
1435
|
+
if (wrap && dd && !wrap.contains(e.target)) dd.style.display = 'none';
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
function parseResults(lines) {
|
|
1439
|
+
var cards = [];
|
|
1440
|
+
var current = null;
|
|
1441
|
+
for (var i = 0; i < lines.length; i++) {
|
|
1442
|
+
var line = lines[i];
|
|
1443
|
+
if (!line.trim()) continue;
|
|
1444
|
+
if (line.startsWith('[') && line.indexOf(']') > 0) {
|
|
1445
|
+
if (current) cards.push(current);
|
|
1446
|
+
var bracket = line.indexOf(']');
|
|
1447
|
+
var source = line.slice(1, bracket);
|
|
1448
|
+
var meta = line.slice(bracket + 1).trim();
|
|
1449
|
+
current = { source: source, meta: meta, snippets: [] };
|
|
1450
|
+
} else if (line === '(keyword fallback)') {
|
|
1451
|
+
// skip
|
|
1452
|
+
} else if (current) {
|
|
1453
|
+
current.snippets.push(line);
|
|
1454
|
+
} else {
|
|
1455
|
+
cards.push({ source: '', meta: '', snippets: [line] });
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
if (current) cards.push(current);
|
|
1459
|
+
return cards;
|
|
1460
|
+
}
|
|
1461
|
+
function renderCards(cards) {
|
|
1462
|
+
var html = '';
|
|
1463
|
+
for (var c = 0; c < cards.length; c++) {
|
|
1464
|
+
var card = cards[c];
|
|
1465
|
+
html += '<div class="card" style="margin-bottom:8px">';
|
|
1466
|
+
html += '<div class="card-header" style="padding:10px 14px;display:flex;align-items:center">';
|
|
1467
|
+
if (card.source) {
|
|
1468
|
+
html += '<span style="font-weight:500;font-size:var(--text-sm)">' + esc(card.source) + '</span>';
|
|
1469
|
+
}
|
|
1470
|
+
if (card.meta) {
|
|
1471
|
+
html += '<span class="text-muted" style="font-size:var(--text-xs);margin-left:8px">' + esc(card.meta) + '</span>';
|
|
1472
|
+
}
|
|
1473
|
+
html += '</div>';
|
|
1474
|
+
if (card.snippets.length) {
|
|
1475
|
+
html += '<div class="card-body" style="padding:10px 14px;font-size:var(--text-sm);white-space:pre-wrap;color:var(--ink-secondary)">';
|
|
1476
|
+
html += esc(card.snippets.join('\\n'));
|
|
1477
|
+
html += '</div>';
|
|
1478
|
+
}
|
|
1479
|
+
html += '</div>';
|
|
1480
|
+
}
|
|
1481
|
+
return html;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1362
1484
|
function doSearch() {
|
|
1363
1485
|
var q = document.getElementById('search-query').value.trim();
|
|
1364
1486
|
if (!q) return;
|
|
1365
|
-
var
|
|
1487
|
+
var projects = getSelectedProjects();
|
|
1366
1488
|
var type = document.getElementById('search-type-filter').value;
|
|
1367
1489
|
var statusEl = document.getElementById('search-status');
|
|
1368
1490
|
var resultsEl = document.getElementById('search-results');
|
|
1369
1491
|
statusEl.textContent = 'Searching...';
|
|
1370
1492
|
resultsEl.innerHTML = '';
|
|
1371
1493
|
|
|
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;
|
|
1494
|
+
var fetches = [];
|
|
1495
|
+
if (projects.length <= 1) {
|
|
1496
|
+
var url = '/api/search?q=' + encodeURIComponent(q) + '&limit=20';
|
|
1497
|
+
if (projects.length === 1) url += '&project=' + encodeURIComponent(projects[0]);
|
|
1498
|
+
if (type) url += '&type=' + encodeURIComponent(type);
|
|
1499
|
+
fetches.push(fetch(searchAuthUrl(url)).then(function(r) { return r.json(); }));
|
|
1500
|
+
} else {
|
|
1501
|
+
for (var pi = 0; pi < projects.length; pi++) {
|
|
1502
|
+
(function(proj) {
|
|
1503
|
+
var purl = '/api/search?q=' + encodeURIComponent(q) + '&limit=20&project=' + encodeURIComponent(proj);
|
|
1504
|
+
if (type) purl += '&type=' + encodeURIComponent(type);
|
|
1505
|
+
fetches.push(fetch(searchAuthUrl(purl)).then(function(r) { return r.json(); }));
|
|
1506
|
+
})(projects[pi]);
|
|
1387
1507
|
}
|
|
1508
|
+
}
|
|
1388
1509
|
|
|
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
|
-
}
|
|
1510
|
+
Promise.all(fetches).then(function(results) {
|
|
1511
|
+
var allCards = [];
|
|
1512
|
+
var hasError = false;
|
|
1513
|
+
for (var ri = 0; ri < results.length; ri++) {
|
|
1514
|
+
if (!results[ri].ok) { hasError = true; continue; }
|
|
1515
|
+
var parsed = parseResults(results[ri].results || []);
|
|
1516
|
+
allCards = allCards.concat(parsed);
|
|
1409
1517
|
}
|
|
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>';
|
|
1518
|
+
if (!allCards.length) {
|
|
1519
|
+
statusEl.textContent = hasError ? 'Search error.' : 'No results.';
|
|
1520
|
+
resultsEl.innerHTML = '<div style="padding:40px;color:var(--muted);text-align:center">' + (hasError ? 'Search failed' : 'No results for \\u201c' + esc(q) + '\\u201d') + '</div>';
|
|
1521
|
+
return;
|
|
1431
1522
|
}
|
|
1432
|
-
|
|
1523
|
+
statusEl.textContent = allCards.length + ' result(s)';
|
|
1524
|
+
resultsEl.innerHTML = renderCards(allCards);
|
|
1433
1525
|
}).catch(function(err) {
|
|
1434
1526
|
statusEl.textContent = '';
|
|
1435
1527
|
resultsEl.innerHTML = '<div style="padding:24px;color:var(--muted);text-align:center">Search error: ' + esc(String(err)) + '</div>';
|
|
1436
1528
|
});
|
|
1437
1529
|
}
|
|
1438
1530
|
|
|
1439
|
-
// Populate project filter
|
|
1531
|
+
// Populate project filter dropdown
|
|
1440
1532
|
function loadSearchProjects() {
|
|
1441
1533
|
if (_searchProjectsLoaded) return;
|
|
1442
1534
|
_searchProjectsLoaded = true;
|
|
1443
1535
|
fetch(searchAuthUrl('/api/projects')).then(function(r) { return r.json(); }).then(function(data) {
|
|
1444
1536
|
if (!data.ok) return;
|
|
1445
|
-
var
|
|
1537
|
+
var dd = document.getElementById('search-project-dropdown');
|
|
1538
|
+
if (!dd) return;
|
|
1539
|
+
var html = '';
|
|
1446
1540
|
(data.projects || []).forEach(function(p) {
|
|
1447
|
-
var
|
|
1448
|
-
|
|
1449
|
-
|
|
1541
|
+
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>';
|
|
1542
|
+
});
|
|
1543
|
+
dd.innerHTML = html;
|
|
1544
|
+
dd.querySelectorAll('input[type=checkbox]').forEach(function(cb) {
|
|
1545
|
+
cb.addEventListener('change', function() { toggleProjectFilter(cb.value); });
|
|
1450
1546
|
});
|
|
1451
1547
|
}).catch(function() {});
|
|
1452
1548
|
}
|
|
@@ -1495,13 +1591,9 @@ export function renderEventWiringScript() {
|
|
|
1495
1591
|
// --- Review filters ---
|
|
1496
1592
|
var reviewFilterProject = document.getElementById('review-filter-project');
|
|
1497
1593
|
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
1594
|
|
|
1503
1595
|
var highlightBtn = document.getElementById('highlight-only-btn');
|
|
1504
|
-
if (highlightBtn) highlightBtn.addEventListener('
|
|
1596
|
+
if (highlightBtn) highlightBtn.addEventListener('change', function() { filterReviewCards(); });
|
|
1505
1597
|
|
|
1506
1598
|
// --- Graph controls ---
|
|
1507
1599
|
var graphZoomIn = document.getElementById('graph-zoom-in');
|
|
@@ -1521,6 +1613,20 @@ export function renderEventWiringScript() {
|
|
|
1521
1613
|
var sessionsFilterProject = document.getElementById('sessions-filter-project');
|
|
1522
1614
|
if (sessionsFilterProject) sessionsFilterProject.addEventListener('change', function() { loadSessions(); });
|
|
1523
1615
|
|
|
1616
|
+
// --- Mascot click animation ---
|
|
1617
|
+
var mascotSvg = document.querySelector('.header-brand svg');
|
|
1618
|
+
if (mascotSvg) {
|
|
1619
|
+
mascotSvg.addEventListener('click', function() {
|
|
1620
|
+
mascotSvg.classList.remove('popped');
|
|
1621
|
+
void mascotSvg.offsetWidth;
|
|
1622
|
+
mascotSvg.classList.add('popped');
|
|
1623
|
+
mascotSvg.addEventListener('animationend', function handler() {
|
|
1624
|
+
mascotSvg.classList.remove('popped');
|
|
1625
|
+
mascotSvg.removeEventListener('animationend', handler);
|
|
1626
|
+
});
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1524
1630
|
// --- Command palette ---
|
|
1525
1631
|
var cmdpal = document.getElementById('cmdpal');
|
|
1526
1632
|
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;
|
package/mcp/dist/phren-paths.js
CHANGED
|
@@ -108,7 +108,7 @@ function hasRootManifest(candidate) {
|
|
|
108
108
|
}
|
|
109
109
|
function hasInstallMarkers(candidate) {
|
|
110
110
|
return fs.existsSync(path.join(candidate, "machines.yaml"))
|
|
111
|
-
|| fs.existsSync(path.join(candidate, ".
|
|
111
|
+
|| fs.existsSync(path.join(candidate, ".config"))
|
|
112
112
|
|| fs.existsSync(path.join(candidate, "global"));
|
|
113
113
|
}
|
|
114
114
|
function isPhrenRootCandidate(candidate) {
|
|
@@ -460,7 +460,7 @@ export function computePhrenLiveStateToken(phrenPath) {
|
|
|
460
460
|
pushDirTokens(parts, path.join(phrenPath, "profiles"));
|
|
461
461
|
}
|
|
462
462
|
pushDirTokens(parts, path.join(phrenPath, "global", "skills"));
|
|
463
|
-
pushFileToken(parts, path.join(phrenPath, ".
|
|
463
|
+
pushFileToken(parts, path.join(phrenPath, ".config", "access-control.json"));
|
|
464
464
|
pushFileToken(parts, rootManifestPath(phrenPath));
|
|
465
465
|
pushFileToken(parts, runtimeHealthFile(phrenPath));
|
|
466
466
|
pushFileToken(parts, runtimeFile(phrenPath, "audit.log"));
|
package/mcp/dist/proactivity.js
CHANGED
|
@@ -20,6 +20,37 @@ function parseProactivityLevel(raw) {
|
|
|
20
20
|
function resolveProactivityPhrenPath(explicitPhrenPath) {
|
|
21
21
|
return explicitPhrenPath ?? findPhrenPath();
|
|
22
22
|
}
|
|
23
|
+
/** Read per-user preferences from ~/.phren/.users/<actor>/preferences.json. Actor from PHREN_ACTOR env var. */
|
|
24
|
+
export function readUserPreferences(explicitPhrenPath) {
|
|
25
|
+
const phrenPath = resolveProactivityPhrenPath(explicitPhrenPath);
|
|
26
|
+
if (!phrenPath)
|
|
27
|
+
return {};
|
|
28
|
+
const actor = (process.env.PHREN_ACTOR || "").trim();
|
|
29
|
+
if (!actor || !/^[a-zA-Z0-9_@.-]{1,128}$/.test(actor))
|
|
30
|
+
return {};
|
|
31
|
+
// Sanitize actor name to safe path component (no path traversal)
|
|
32
|
+
const safeActor = actor.replace(/[^a-zA-Z0-9_@.-]/g, "_");
|
|
33
|
+
const prefsFile = `${phrenPath}/.users/${safeActor}/preferences.json`;
|
|
34
|
+
if (!fs.existsSync(prefsFile))
|
|
35
|
+
return {};
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(fs.readFileSync(prefsFile, "utf8"));
|
|
38
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
39
|
+
return {};
|
|
40
|
+
return {
|
|
41
|
+
proactivity: parseProactivityLevel(parsed.proactivity),
|
|
42
|
+
proactivityFindings: parseProactivityLevel(parsed.proactivityFindings),
|
|
43
|
+
proactivityTask: parseProactivityLevel(parsed.proactivityTask),
|
|
44
|
+
findingSensitivity: ["minimal", "conservative", "balanced", "aggressive"].includes(String(parsed.findingSensitivity))
|
|
45
|
+
? parsed.findingSensitivity
|
|
46
|
+
: undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
debugLog(`readUserPreferences: failed to parse ${prefsFile}: ${errorMessage(err)}`);
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
23
54
|
function readGovernanceProactivityPreferences(explicitPhrenPath) {
|
|
24
55
|
const phrenPath = resolveProactivityPhrenPath(explicitPhrenPath);
|
|
25
56
|
if (!phrenPath)
|
|
@@ -41,6 +72,10 @@ function readGovernanceProactivityPreferences(explicitPhrenPath) {
|
|
|
41
72
|
return {};
|
|
42
73
|
}
|
|
43
74
|
function getConfiguredProactivityDefault(explicitPhrenPath) {
|
|
75
|
+
// Resolution chain: user prefs (highest) → governance prefs → install prefs → default
|
|
76
|
+
const userPrefs = readUserPreferences(explicitPhrenPath);
|
|
77
|
+
if (userPrefs.proactivity)
|
|
78
|
+
return userPrefs.proactivity;
|
|
44
79
|
const governancePreference = readGovernanceProactivityPreferences(explicitPhrenPath).proactivity;
|
|
45
80
|
if (governancePreference)
|
|
46
81
|
return governancePreference;
|
|
@@ -75,6 +110,12 @@ function getWorkflowPolicySensitivityLevel(explicitPhrenPath) {
|
|
|
75
110
|
}
|
|
76
111
|
}
|
|
77
112
|
function getConfiguredProactivityLevelForFindingsDefault(explicitPhrenPath) {
|
|
113
|
+
// User prefs take priority over governance prefs
|
|
114
|
+
const userPrefs = readUserPreferences(explicitPhrenPath);
|
|
115
|
+
if (userPrefs.proactivityFindings)
|
|
116
|
+
return userPrefs.proactivityFindings;
|
|
117
|
+
if (userPrefs.proactivity)
|
|
118
|
+
return userPrefs.proactivity;
|
|
78
119
|
const prefs = readGovernanceProactivityPreferences(explicitPhrenPath);
|
|
79
120
|
return prefs.proactivityFindings
|
|
80
121
|
?? prefs.proactivity
|
|
@@ -82,6 +123,12 @@ function getConfiguredProactivityLevelForFindingsDefault(explicitPhrenPath) {
|
|
|
82
123
|
?? getConfiguredProactivityDefault(explicitPhrenPath);
|
|
83
124
|
}
|
|
84
125
|
function getConfiguredProactivityLevelForTaskDefault(explicitPhrenPath) {
|
|
126
|
+
// User prefs take priority over governance prefs
|
|
127
|
+
const userPrefs = readUserPreferences(explicitPhrenPath);
|
|
128
|
+
if (userPrefs.proactivityTask)
|
|
129
|
+
return userPrefs.proactivityTask;
|
|
130
|
+
if (userPrefs.proactivity)
|
|
131
|
+
return userPrefs.proactivity;
|
|
85
132
|
const prefs = readGovernanceProactivityPreferences(explicitPhrenPath);
|
|
86
133
|
return prefs.proactivityTask
|
|
87
134
|
?? prefs.proactivity
|