@phren/cli 0.0.11 → 0.0.13
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/README.md +9 -9
- 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-actions.js +54 -67
- package/mcp/dist/cli-config.js +4 -5
- package/mcp/dist/cli-extract.js +3 -2
- package/mcp/dist/cli-graph.js +17 -3
- package/mcp/dist/cli-hooks-output.js +1 -1
- package/mcp/dist/cli-hooks-session.js +1 -1
- package/mcp/dist/cli-hooks.js +5 -3
- package/mcp/dist/cli.js +1 -1
- package/mcp/dist/content-archive.js +21 -12
- package/mcp/dist/content-citation.js +13 -2
- package/mcp/dist/content-learning.js +6 -4
- package/mcp/dist/content-metadata.js +10 -0
- package/mcp/dist/core-finding.js +1 -1
- package/mcp/dist/data-access.js +10 -31
- package/mcp/dist/data-tasks.js +5 -26
- package/mcp/dist/embedding.js +0 -1
- package/mcp/dist/entrypoint.js +4 -0
- package/mcp/dist/finding-impact.js +1 -32
- package/mcp/dist/finding-journal.js +1 -1
- package/mcp/dist/finding-lifecycle.js +2 -7
- package/mcp/dist/governance-locks.js +6 -0
- package/mcp/dist/governance-policy.js +1 -7
- package/mcp/dist/governance-scores.js +1 -7
- package/mcp/dist/hooks.js +23 -0
- package/mcp/dist/init-config.js +1 -1
- package/mcp/dist/init-preferences.js +1 -1
- package/mcp/dist/init-setup.js +1 -50
- package/mcp/dist/init-shared.js +53 -1
- package/mcp/dist/init.js +21 -6
- package/mcp/dist/link-context.js +1 -1
- package/mcp/dist/link-doctor.js +11 -54
- package/mcp/dist/link.js +4 -53
- package/mcp/dist/mcp-extract-facts.js +11 -6
- package/mcp/dist/mcp-finding.js +10 -14
- package/mcp/dist/mcp-graph.js +6 -6
- package/mcp/dist/mcp-hooks.js +1 -1
- package/mcp/dist/mcp-search.js +3 -8
- package/mcp/dist/mcp-session.js +12 -2
- package/mcp/dist/memory-ui-assets.js +1 -36
- package/mcp/dist/memory-ui-graph.js +152 -50
- package/mcp/dist/memory-ui-page.js +7 -5
- package/mcp/dist/memory-ui-scripts.js +42 -36
- package/mcp/dist/phren-core.js +2 -0
- package/mcp/dist/phren-paths.js +1 -2
- package/mcp/dist/proactivity.js +5 -5
- package/mcp/dist/project-config.js +1 -1
- package/mcp/dist/provider-adapters.js +1 -1
- package/mcp/dist/query-correlation.js +22 -19
- package/mcp/dist/session-checkpoints.js +14 -14
- package/mcp/dist/shared-data-utils.js +28 -0
- package/mcp/dist/shared-fragment-graph.js +11 -11
- package/mcp/dist/shared-governance.js +1 -1
- package/mcp/dist/shared-retrieval.js +2 -10
- package/mcp/dist/shared-search-fallback.js +2 -12
- package/mcp/dist/shared.js +2 -3
- package/mcp/dist/shell-entry.js +1 -1
- package/mcp/dist/shell-input.js +62 -52
- package/mcp/dist/shell-palette.js +6 -1
- package/mcp/dist/shell-render.js +9 -5
- package/mcp/dist/shell-state-store.js +1 -4
- package/mcp/dist/shell-view.js +4 -4
- package/mcp/dist/shell.js +4 -54
- package/mcp/dist/status.js +2 -8
- package/mcp/dist/utils.js +1 -1
- package/package.json +1 -2
- package/skills/docs.md +11 -11
- package/starter/README.md +1 -1
- package/starter/global/CLAUDE.md +2 -2
- package/starter/global/skills/audit.md +10 -10
- package/mcp/dist/cli-hooks-retrieval.js +0 -2
- package/mcp/dist/impact-scoring.js +0 -22
|
@@ -27,11 +27,20 @@ export function renderGraphScript() {
|
|
|
27
27
|
/* ── colour & size maps ─────────────────────────────────────────────── */
|
|
28
28
|
/* Fixed colors for non-finding node types — phren purple/cyan palette */
|
|
29
29
|
var COLORS = {
|
|
30
|
-
project: '#
|
|
31
|
-
'task-active': '#10b981', 'task-queue': '#
|
|
32
|
-
entity: '#
|
|
30
|
+
project: '#9B7FD4',
|
|
31
|
+
'task-active': '#10b981', 'task-queue': '#7B68AE',
|
|
32
|
+
entity: '#D4892E', reference: '#6b8e7a',
|
|
33
33
|
other: '#B8A0D6'
|
|
34
34
|
};
|
|
35
|
+
|
|
36
|
+
/* Finding topic slug -> semantic color for visual hierarchy */
|
|
37
|
+
var FINDING_TOPIC_COLORS = {
|
|
38
|
+
architecture: '#3B82F6', debugging: '#EF4444', security: '#EF4444',
|
|
39
|
+
performance: '#F59E0B', testing: '#10B981', devops: '#10B981',
|
|
40
|
+
tooling: '#3B82F6', api: '#3B82F6', database: '#3B82F6',
|
|
41
|
+
frontend: '#8B5CF6', auth: '#EF4444', data: '#6B7280',
|
|
42
|
+
mobile: '#06B6D4', ai_ml: '#8B5CF6', general: '#6B7280'
|
|
43
|
+
};
|
|
35
44
|
var RADII = {
|
|
36
45
|
project: 18,
|
|
37
46
|
'task-active': 7, 'task-queue': 7,
|
|
@@ -53,7 +62,8 @@ export function renderGraphScript() {
|
|
|
53
62
|
waypointIdx: 0, /* current waypoint index */
|
|
54
63
|
tripDist: 0, /* total distance of current trip (for ease-in-out) */
|
|
55
64
|
tripProgress: 0, /* distance traveled so far this trip */
|
|
56
|
-
targetNodeId: null
|
|
65
|
+
targetNodeId: null, /* id of destination node (set in phrenMoveTo) */
|
|
66
|
+
targetNodeRef: null /* live reference to destination node object */
|
|
57
67
|
};
|
|
58
68
|
|
|
59
69
|
/* ── phren sprite image ─────────────────────────────────────────────── */
|
|
@@ -92,6 +102,14 @@ export function renderGraphScript() {
|
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
/* find shortest edge path from nearest node to target node via BFS */
|
|
105
|
+
/* look up a visible node by id — used to resolve live positions */
|
|
106
|
+
function phrenNodeById(id) {
|
|
107
|
+
for (var i = 0; i < visibleNodes.length; i++) {
|
|
108
|
+
if (visibleNodes[i].id === id) return visibleNodes[i];
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
95
113
|
function phrenFindEdgePath(targetNode) {
|
|
96
114
|
if (!targetNode || visibleNodes.length === 0) return [];
|
|
97
115
|
/* find nearest visible node to phren's current position */
|
|
@@ -134,14 +152,11 @@ export function renderGraphScript() {
|
|
|
134
152
|
}
|
|
135
153
|
}
|
|
136
154
|
if (!found) return [];
|
|
137
|
-
/* reconstruct path */
|
|
155
|
+
/* reconstruct path — store node IDs so positions are resolved live */
|
|
138
156
|
var path = [];
|
|
139
|
-
var nodeById = {};
|
|
140
|
-
for (var i = 0; i < visibleNodes.length; i++) nodeById[visibleNodes[i].id] = visibleNodes[i];
|
|
141
157
|
var cur = targetId;
|
|
142
158
|
while (cur !== undefined && cur !== startId) {
|
|
143
|
-
|
|
144
|
-
if (nd) path.unshift({ x: nd.x, y: nd.y });
|
|
159
|
+
path.unshift(cur);
|
|
145
160
|
cur = prev[cur];
|
|
146
161
|
}
|
|
147
162
|
/* cap path length to avoid very long walks */
|
|
@@ -160,9 +175,10 @@ export function renderGraphScript() {
|
|
|
160
175
|
phren.tripDist = Math.sqrt(tdx * tdx + tdy * tdy);
|
|
161
176
|
phren.tripProgress = 0;
|
|
162
177
|
/* try edge-walking if a target node is given */
|
|
163
|
-
phren.waypoints = targetNode ? phrenFindEdgePath(targetNode) : [];
|
|
178
|
+
phren.waypoints = targetNode ? phrenFindEdgePath(targetNode) : []; /* array of node IDs */
|
|
164
179
|
phren.waypointIdx = 0;
|
|
165
180
|
phren.targetNodeId = targetNode ? targetNode.id : null;
|
|
181
|
+
phren.targetNodeRef = targetNode || null; /* live reference for position tracking */
|
|
166
182
|
/* ensure animation loop is running so phren movement renders */
|
|
167
183
|
if (!animFrame) animFrame = requestAnimationFrame(tick);
|
|
168
184
|
}
|
|
@@ -183,11 +199,18 @@ export function renderGraphScript() {
|
|
|
183
199
|
function phrenUpdate(dt) {
|
|
184
200
|
phren.idlePhase += dt;
|
|
185
201
|
if (phren.moving) {
|
|
202
|
+
/* keep target coordinates synced with live node position (force layout moves nodes) */
|
|
203
|
+
if (phren.targetNodeRef && typeof phren.targetNodeRef.x === 'number') {
|
|
204
|
+
phren.targetX = phren.targetNodeRef.x;
|
|
205
|
+
phren.targetY = phren.targetNodeRef.y;
|
|
206
|
+
}
|
|
186
207
|
/* determine current move target: next waypoint or final destination */
|
|
187
208
|
var wx, wy;
|
|
188
209
|
if (phren.waypoints.length > 0 && phren.waypointIdx < phren.waypoints.length) {
|
|
189
|
-
|
|
190
|
-
|
|
210
|
+
/* waypoints are node IDs — resolve to live positions */
|
|
211
|
+
var wpNode = phrenNodeById(phren.waypoints[phren.waypointIdx]);
|
|
212
|
+
if (wpNode) { wx = wpNode.x; wy = wpNode.y; }
|
|
213
|
+
else { wx = phren.targetX; wy = phren.targetY; phren.waypointIdx = phren.waypoints.length; }
|
|
191
214
|
} else {
|
|
192
215
|
wx = phren.targetX;
|
|
193
216
|
wy = phren.targetY;
|
|
@@ -333,9 +356,11 @@ export function renderGraphScript() {
|
|
|
333
356
|
}
|
|
334
357
|
|
|
335
358
|
function topicGroupColor(group) {
|
|
336
|
-
/* group is 'topic:<slug>' for findings */
|
|
359
|
+
/* group is 'topic:<slug>' for findings — use semantic color if available */
|
|
337
360
|
if (typeof group === 'string' && group.indexOf('topic:') === 0) {
|
|
338
|
-
|
|
361
|
+
var slug = group.slice(6);
|
|
362
|
+
if (FINDING_TOPIC_COLORS[slug]) return FINDING_TOPIC_COLORS[slug];
|
|
363
|
+
return topicSlugToColor(slug);
|
|
339
364
|
}
|
|
340
365
|
return COLORS[group] || COLORS.other;
|
|
341
366
|
}
|
|
@@ -371,9 +396,13 @@ export function renderGraphScript() {
|
|
|
371
396
|
function clamp(v, lo, hi) { return v < lo ? lo : v > hi ? hi : v; }
|
|
372
397
|
|
|
373
398
|
function nodeRadius(n) {
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
|
|
399
|
+
var base;
|
|
400
|
+
if (n.group === 'entity') base = Math.min(6 + (n.refCount || 0), 16);
|
|
401
|
+
else if (typeof n.group === 'string' && n.group.indexOf('topic:') === 0) base = RADII.other;
|
|
402
|
+
else base = RADII[n.group] || RADII.other;
|
|
403
|
+
/* size boost by connection count */
|
|
404
|
+
var conns = n._connectionCount || 0;
|
|
405
|
+
return base + Math.min(conns * 0.5, 6);
|
|
377
406
|
}
|
|
378
407
|
|
|
379
408
|
function nodeColor(n) { return topicGroupColor(n.group); }
|
|
@@ -865,6 +894,14 @@ export function renderGraphScript() {
|
|
|
865
894
|
}
|
|
866
895
|
}
|
|
867
896
|
|
|
897
|
+
/* compute connection count per node for size scaling */
|
|
898
|
+
for (var i = 0; i < visibleNodes.length; i++) visibleNodes[i]._connectionCount = 0;
|
|
899
|
+
for (var i = 0; i < visibleLinks.length; i++) {
|
|
900
|
+
var lk = visibleLinks[i];
|
|
901
|
+
if (lk._source) lk._source._connectionCount = (lk._source._connectionCount || 0) + 1;
|
|
902
|
+
if (lk._target) lk._target._connectionCount = (lk._target._connectionCount || 0) + 1;
|
|
903
|
+
}
|
|
904
|
+
|
|
868
905
|
updateLegend();
|
|
869
906
|
/* only restart simulation if node count changed significantly */
|
|
870
907
|
var oldCount = _prevVisibleCount || 0;
|
|
@@ -940,14 +977,26 @@ export function renderGraphScript() {
|
|
|
940
977
|
var sMatch = ((lk._source.label || '').toLowerCase().indexOf(_sq) !== -1 || (lk._source.fullLabel || '').toLowerCase().indexOf(_sq) !== -1);
|
|
941
978
|
var tMatch = ((lk._target.label || '').toLowerCase().indexOf(_sq) !== -1 || (lk._target.fullLabel || '').toLowerCase().indexOf(_sq) !== -1);
|
|
942
979
|
ctx.beginPath();
|
|
943
|
-
ctx.strokeStyle = (sMatch || tMatch) ? 'rgba(
|
|
980
|
+
ctx.strokeStyle = (sMatch || tMatch) ? 'rgba(255,255,255,0.20)' : 'rgba(255,255,255,0.04)';
|
|
981
|
+
ctx.moveTo(lk._source.x, lk._source.y);
|
|
982
|
+
ctx.lineTo(lk._target.x, lk._target.y);
|
|
983
|
+
ctx.stroke();
|
|
984
|
+
}
|
|
985
|
+
} else if (selectedNode) {
|
|
986
|
+
/* When a node is selected, highlight connected edges */
|
|
987
|
+
for (var i = 0; i < links.length; i++) {
|
|
988
|
+
var lk = links[i];
|
|
989
|
+
if (!lk._source || !lk._target) continue;
|
|
990
|
+
var isConnected = (lk._source === selectedNode || lk._target === selectedNode);
|
|
991
|
+
ctx.beginPath();
|
|
992
|
+
ctx.strokeStyle = isConnected ? 'rgba(255,255,255,0.25)' : 'rgba(255,255,255,0.08)';
|
|
944
993
|
ctx.moveTo(lk._source.x, lk._source.y);
|
|
945
994
|
ctx.lineTo(lk._target.x, lk._target.y);
|
|
946
995
|
ctx.stroke();
|
|
947
996
|
}
|
|
948
997
|
} else {
|
|
949
998
|
ctx.beginPath();
|
|
950
|
-
ctx.strokeStyle = 'rgba(
|
|
999
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.08)';
|
|
951
1000
|
for (var i = 0; i < links.length; i++) {
|
|
952
1001
|
var lk = links[i];
|
|
953
1002
|
if (!lk._source || !lk._target) continue;
|
|
@@ -1052,35 +1101,69 @@ export function renderGraphScript() {
|
|
|
1052
1101
|
ctx.setLineDash([]);
|
|
1053
1102
|
}
|
|
1054
1103
|
|
|
1055
|
-
/* 3. nodes
|
|
1056
|
-
|
|
1104
|
+
/* 3. nodes with glow, selection dimming, and visual hierarchy */
|
|
1105
|
+
/* Build set of nodes connected to selected node for dimming */
|
|
1106
|
+
var _connectedToSelected = null;
|
|
1107
|
+
if (selectedNode) {
|
|
1108
|
+
_connectedToSelected = {};
|
|
1109
|
+
_connectedToSelected[selectedNode.id] = true;
|
|
1110
|
+
for (var i = 0; i < links.length; i++) {
|
|
1111
|
+
var lk = links[i];
|
|
1112
|
+
if (!lk._source || !lk._target) continue;
|
|
1113
|
+
if (lk._source === selectedNode) _connectedToSelected[lk._target.id] = true;
|
|
1114
|
+
if (lk._target === selectedNode) _connectedToSelected[lk._source.id] = true;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1057
1118
|
for (var i = 0; i < nodes.length; i++) {
|
|
1058
|
-
var
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
var
|
|
1068
|
-
var
|
|
1069
|
-
var
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1119
|
+
var nd = nodes[i];
|
|
1120
|
+
var col = nodeColor(nd);
|
|
1121
|
+
var r = nodeRadius(nd);
|
|
1122
|
+
var mult = qualityMultiplier(nd);
|
|
1123
|
+
var opacity = 0.3 + (mult - 0.2) * (0.7 / 1.3);
|
|
1124
|
+
opacity = clamp(opacity, 0.3, 1.0);
|
|
1125
|
+
|
|
1126
|
+
/* dim non-matching nodes when search is active */
|
|
1127
|
+
if (searchQuery) {
|
|
1128
|
+
var sq = searchQuery.toLowerCase();
|
|
1129
|
+
var sl = (nd.label || '').toLowerCase();
|
|
1130
|
+
var sf = (nd.fullLabel || '').toLowerCase();
|
|
1131
|
+
if (sl.indexOf(sq) === -1 && sf.indexOf(sq) === -1) opacity = 0.1;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/* dim non-connected nodes when a node is selected */
|
|
1135
|
+
if (_connectedToSelected && !_connectedToSelected[nd.id]) {
|
|
1136
|
+
opacity *= 0.4;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
ctx.globalAlpha = opacity;
|
|
1140
|
+
|
|
1141
|
+
/* node glow based on type */
|
|
1142
|
+
var glowBlur = 0;
|
|
1143
|
+
if (nd === selectedNode) {
|
|
1144
|
+
glowBlur = 30;
|
|
1145
|
+
ctx.shadowColor = 'rgba(255,255,255,0.6)';
|
|
1146
|
+
} else if (nd.group === 'project') {
|
|
1147
|
+
glowBlur = 20;
|
|
1148
|
+
ctx.shadowColor = colorWithAlpha(col, 0.5);
|
|
1149
|
+
} else if (nd.group === 'entity') {
|
|
1150
|
+
glowBlur = 12;
|
|
1151
|
+
ctx.shadowColor = 'rgba(212,137,46,0.4)';
|
|
1152
|
+
} else if (typeof nd.group === 'string' && nd.group.indexOf('topic:') === 0) {
|
|
1153
|
+
glowBlur = 6;
|
|
1154
|
+
ctx.shadowColor = colorWithAlpha(col, 0.25);
|
|
1083
1155
|
}
|
|
1156
|
+
if (glowBlur > 0) {
|
|
1157
|
+
ctx.shadowBlur = glowBlur / scale;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
ctx.beginPath();
|
|
1161
|
+
ctx.arc(nd.x, nd.y, r, 0, Math.PI * 2);
|
|
1162
|
+
ctx.fillStyle = col;
|
|
1163
|
+
ctx.fill();
|
|
1164
|
+
|
|
1165
|
+
ctx.shadowBlur = 0;
|
|
1166
|
+
ctx.shadowColor = 'transparent';
|
|
1084
1167
|
}
|
|
1085
1168
|
ctx.globalAlpha = 1.0;
|
|
1086
1169
|
|
|
@@ -1155,13 +1238,17 @@ export function renderGraphScript() {
|
|
|
1155
1238
|
}
|
|
1156
1239
|
}
|
|
1157
1240
|
|
|
1158
|
-
/* 6. selection highlight */
|
|
1241
|
+
/* 6. selection highlight with bloom */
|
|
1159
1242
|
if (selectedNode) {
|
|
1243
|
+
ctx.save();
|
|
1244
|
+
ctx.shadowColor = 'rgba(255,255,255,0.5)';
|
|
1245
|
+
ctx.shadowBlur = 30 / scale;
|
|
1160
1246
|
ctx.beginPath();
|
|
1161
1247
|
ctx.arc(selectedNode.x, selectedNode.y, nodeRadius(selectedNode) + 5, 0, Math.PI * 2);
|
|
1162
|
-
ctx.strokeStyle = '
|
|
1248
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.7)';
|
|
1163
1249
|
ctx.lineWidth = 2.5 / scale;
|
|
1164
1250
|
ctx.stroke();
|
|
1251
|
+
ctx.restore();
|
|
1165
1252
|
}
|
|
1166
1253
|
|
|
1167
1254
|
/* 6c. phren character */
|
|
@@ -1407,17 +1494,22 @@ export function renderGraphScript() {
|
|
|
1407
1494
|
|
|
1408
1495
|
/* ── interaction ────────────────────────────────────────────────────── */
|
|
1409
1496
|
function setupInteraction() {
|
|
1497
|
+
var _mouseDownX = 0, _mouseDownY = 0, _mouseDownHit = null;
|
|
1498
|
+
var CLICK_THRESHOLD = 5; /* px — distinguish click from drag */
|
|
1499
|
+
|
|
1410
1500
|
canvas.addEventListener('mousedown', function(e) {
|
|
1411
1501
|
var rect = canvas.getBoundingClientRect();
|
|
1412
1502
|
var mx = e.clientX - rect.left;
|
|
1413
1503
|
var my = e.clientY - rect.top;
|
|
1504
|
+
_mouseDownX = mx;
|
|
1505
|
+
_mouseDownY = my;
|
|
1414
1506
|
var hit = hitTest(mx, my);
|
|
1507
|
+
_mouseDownHit = hit;
|
|
1415
1508
|
if (hit) {
|
|
1416
1509
|
dragging = hit;
|
|
1417
1510
|
dragPullDepths = collectConnectedDepths(hit, 2);
|
|
1418
1511
|
dragOffX = (mx - panX) / scale - hit.x;
|
|
1419
1512
|
dragOffY = (my - panY) / scale - hit.y;
|
|
1420
|
-
renderGraphDetails(hit);
|
|
1421
1513
|
} else {
|
|
1422
1514
|
panning = true;
|
|
1423
1515
|
panStartX = mx - panX;
|
|
@@ -1465,14 +1557,24 @@ export function renderGraphScript() {
|
|
|
1465
1557
|
}
|
|
1466
1558
|
});
|
|
1467
1559
|
|
|
1468
|
-
canvas.addEventListener('mouseup', function() {
|
|
1560
|
+
canvas.addEventListener('mouseup', function(e) {
|
|
1561
|
+
var rect = canvas.getBoundingClientRect();
|
|
1562
|
+
var mx = e.clientX - rect.left;
|
|
1563
|
+
var my = e.clientY - rect.top;
|
|
1564
|
+
var dragDist = Math.sqrt((_mouseDownX - mx) * (_mouseDownX - mx) + (_mouseDownY - my) * (_mouseDownY - my));
|
|
1469
1565
|
if (dragging) {
|
|
1566
|
+
var clickedNode = dragging;
|
|
1470
1567
|
dragging = null;
|
|
1471
1568
|
dragPullDepths = null;
|
|
1472
1569
|
/* restart simulation so gravity pulls nodes back into place */
|
|
1473
1570
|
alpha = Math.max(alpha, 0.3);
|
|
1571
|
+
/* only select + walk phren if this was a click, not a drag */
|
|
1572
|
+
if (dragDist < CLICK_THRESHOLD && _mouseDownHit && clickedNode === _mouseDownHit) {
|
|
1573
|
+
renderGraphDetails(_mouseDownHit);
|
|
1574
|
+
}
|
|
1474
1575
|
}
|
|
1475
1576
|
panning = false;
|
|
1577
|
+
_mouseDownHit = null;
|
|
1476
1578
|
});
|
|
1477
1579
|
|
|
1478
1580
|
canvas.addEventListener('mouseleave', function() {
|
|
@@ -2,7 +2,7 @@ import { WEB_UI_STYLES, renderWebUiScript } from "./memory-ui-assets.js";
|
|
|
2
2
|
import { renderGraphScript } from "./memory-ui-graph.js";
|
|
3
3
|
import { readSyncSnapshot } from "./memory-ui-data.js";
|
|
4
4
|
import { PROJECT_REFERENCE_UI_STYLES, SETTINGS_TAB_UI_STYLES, TASK_UI_STYLES } from "./memory-ui-styles.js";
|
|
5
|
-
import { renderSkillUiEnhancementScript, renderProjectReferenceEnhancementScript, renderReviewQueueEditSyncScript, renderTasksAndSettingsScript, renderSearchScript, renderEventWiringScript, } from "./memory-ui-scripts.js";
|
|
5
|
+
import { renderSharedWebUiHelpers, renderSkillUiEnhancementScript, renderProjectReferenceEnhancementScript, renderReviewQueueEditSyncScript, renderTasksAndSettingsScript, renderSearchScript, renderEventWiringScript, } from "./memory-ui-scripts.js";
|
|
6
6
|
function h(s) {
|
|
7
7
|
return s
|
|
8
8
|
.replace(/&/g, "&")
|
|
@@ -21,7 +21,6 @@ export function renderWebUiPage(phrenPath, authToken, nonce) {
|
|
|
21
21
|
<link rel="preconnect" href="https://fonts.bunny.net" />
|
|
22
22
|
<link href="https://fonts.bunny.net/css?family=inter:400,500,600,700&display=swap" rel="stylesheet" />
|
|
23
23
|
<title>phren</title>
|
|
24
|
-
<script${nonceAttr} src="https://cdn.jsdelivr.net/npm/marked@12/marked.min.js"></script>
|
|
25
24
|
<style>
|
|
26
25
|
${WEB_UI_STYLES}
|
|
27
26
|
${PROJECT_REFERENCE_UI_STYLES}
|
|
@@ -104,9 +103,9 @@ ${TASK_UI_STYLES}
|
|
|
104
103
|
<dt>What is the Review Queue?</dt>
|
|
105
104
|
<dd>Fragments flagged by governance for human review. Items accumulate here when <code>phren maintain govern</code> is run.</dd>
|
|
106
105
|
<dt>Can I approve, reject, or edit items here?</dt>
|
|
107
|
-
<dd>
|
|
106
|
+
<dd>Yes. Each review card has <strong>Approve</strong>, <strong>Reject</strong>, and <strong>Edit</strong> buttons. Approve accepts the fragment, Reject removes it, and Edit lets you revise the text before accepting. You can also use batch actions to approve or reject multiple items at once.</dd>
|
|
108
107
|
<dt>How do I clear items?</dt>
|
|
109
|
-
<dd>
|
|
108
|
+
<dd>Approve or reject items directly in the UI, or use maintenance flows such as <code>phren maintain prune</code>.</dd>
|
|
110
109
|
<dt>Is this automatic?</dt>
|
|
111
110
|
<dd>No. Agents do not auto-accept review-queue items.</dd>
|
|
112
111
|
<dt>How do items get here?</dt>
|
|
@@ -116,7 +115,7 @@ ${TASK_UI_STYLES}
|
|
|
116
115
|
</dl>
|
|
117
116
|
</details>
|
|
118
117
|
|
|
119
|
-
<p style="font-size:var(--text-sm);color:var(--muted);margin-bottom:12px;letter-spacing:-0.01em">Fragments flagged for review.
|
|
118
|
+
<p style="font-size:var(--text-sm);color:var(--muted);margin-bottom:12px;letter-spacing:-0.01em">Fragments flagged for review. Approve, reject, or edit items directly from this tab.</p>
|
|
120
119
|
|
|
121
120
|
<div id="review-summary-banner" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;align-items:center"></div>
|
|
122
121
|
|
|
@@ -331,6 +330,9 @@ ${renderGraphScript()}
|
|
|
331
330
|
${renderReviewQueueEditSyncScript()}
|
|
332
331
|
</script>
|
|
333
332
|
<script${nonceAttr}>
|
|
333
|
+
${renderSharedWebUiHelpers(h(authToken || ""))}
|
|
334
|
+
</script>
|
|
335
|
+
<script${nonceAttr}>
|
|
334
336
|
${renderSkillUiEnhancementScript(h(authToken || ""))}
|
|
335
337
|
</script>
|
|
336
338
|
<script${nonceAttr}>
|
|
@@ -1,22 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Returns a <script> block with shared browser helpers used across all UI IIFEs:
|
|
3
|
+
* window._phrenEsc(s) — HTML-escape a value
|
|
4
|
+
* window._phrenAuthToken — the current auth token
|
|
5
|
+
* window._phrenAuthUrl(base) — append _auth param to a URL
|
|
6
|
+
* window._phrenAuthBody(body) — append _auth param to a form body
|
|
7
|
+
* window._phrenFetchCsrfToken(cb) — fetch the CSRF token and call cb(token)
|
|
8
|
+
*/
|
|
9
|
+
export function renderSharedWebUiHelpers(authToken) {
|
|
10
|
+
return `(function() {
|
|
11
|
+
window._phrenAuthToken = '${authToken}';
|
|
12
|
+
window._phrenEsc = function(s) {
|
|
13
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
14
|
+
};
|
|
15
|
+
window._phrenAuthUrl = function(base) {
|
|
16
|
+
var tok = window._phrenAuthToken;
|
|
17
|
+
return base + (base.indexOf('?') === -1 ? '?' : '&') + '_auth=' + encodeURIComponent(tok);
|
|
18
|
+
};
|
|
19
|
+
window._phrenAuthBody = function(body) {
|
|
20
|
+
var tok = window._phrenAuthToken;
|
|
21
|
+
return body + (tok ? '&_auth=' + encodeURIComponent(tok) : '');
|
|
22
|
+
};
|
|
23
|
+
window._phrenFetchCsrfToken = function(cb) {
|
|
24
|
+
var tok = window._phrenAuthToken;
|
|
25
|
+
var url = '/api/csrf-token' + (tok ? '?_auth=' + encodeURIComponent(tok) : '');
|
|
26
|
+
fetch(url).then(function(r) { return r.json(); }).then(function(d) { cb(d.token || null); }).catch(function() { cb(null); });
|
|
27
|
+
};
|
|
28
|
+
})();`;
|
|
29
|
+
}
|
|
30
|
+
export function renderSkillUiEnhancementScript(_authToken) {
|
|
2
31
|
return `(function() {
|
|
3
|
-
var _skillAuthToken = '${authToken}';
|
|
4
32
|
var _skillCurrent = null;
|
|
5
33
|
var _skillEditing = false;
|
|
34
|
+
var esc = window._phrenEsc;
|
|
35
|
+
var authUrl = window._phrenAuthUrl;
|
|
36
|
+
var authBody = window._phrenAuthBody;
|
|
37
|
+
var fetchCsrfToken = window._phrenFetchCsrfToken;
|
|
6
38
|
|
|
7
|
-
function esc(s) {
|
|
8
|
-
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
9
|
-
}
|
|
10
|
-
function authUrl(base) {
|
|
11
|
-
return base + (base.indexOf('?') === -1 ? '?' : '&') + '_auth=' + encodeURIComponent(_skillAuthToken);
|
|
12
|
-
}
|
|
13
|
-
function authBody(body) {
|
|
14
|
-
return body + (_skillAuthToken ? '&_auth=' + encodeURIComponent(_skillAuthToken) : '');
|
|
15
|
-
}
|
|
16
|
-
function fetchCsrfToken(cb) {
|
|
17
|
-
var url = '/api/csrf-token' + (_skillAuthToken ? '?_auth=' + encodeURIComponent(_skillAuthToken) : '');
|
|
18
|
-
fetch(url).then(function(r) { return r.json(); }).then(function(d) { cb(d.token || null); }).catch(function() { cb(null); });
|
|
19
|
-
}
|
|
20
39
|
function renderSkillReader(content) {
|
|
21
40
|
var reader = document.getElementById('skills-reader');
|
|
22
41
|
if (!_skillCurrent || !reader) return;
|
|
@@ -166,9 +185,8 @@ export function renderSkillUiEnhancementScript(authToken) {
|
|
|
166
185
|
});
|
|
167
186
|
})();`;
|
|
168
187
|
}
|
|
169
|
-
export function renderProjectReferenceEnhancementScript(
|
|
188
|
+
export function renderProjectReferenceEnhancementScript(_authToken) {
|
|
170
189
|
return `(function() {
|
|
171
|
-
var _referenceAuthToken = '${authToken}';
|
|
172
190
|
var _referenceState = {
|
|
173
191
|
project: '',
|
|
174
192
|
topicsData: null,
|
|
@@ -177,20 +195,11 @@ export function renderProjectReferenceEnhancementScript(authToken) {
|
|
|
177
195
|
selectedKey: '',
|
|
178
196
|
editor: null
|
|
179
197
|
};
|
|
198
|
+
var esc = window._phrenEsc;
|
|
199
|
+
var authUrl = window._phrenAuthUrl;
|
|
200
|
+
var authBody = window._phrenAuthBody;
|
|
201
|
+
var fetchCsrfToken = window._phrenFetchCsrfToken;
|
|
180
202
|
|
|
181
|
-
function esc(s) {
|
|
182
|
-
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
183
|
-
}
|
|
184
|
-
function authUrl(base) {
|
|
185
|
-
return base + (base.indexOf('?') === -1 ? '?' : '&') + '_auth=' + encodeURIComponent(_referenceAuthToken);
|
|
186
|
-
}
|
|
187
|
-
function authBody(body) {
|
|
188
|
-
return body + (_referenceAuthToken ? '&_auth=' + encodeURIComponent(_referenceAuthToken) : '');
|
|
189
|
-
}
|
|
190
|
-
function fetchCsrfToken(cb) {
|
|
191
|
-
var url = '/api/csrf-token' + (_referenceAuthToken ? '?_auth=' + encodeURIComponent(_referenceAuthToken) : '');
|
|
192
|
-
fetch(url).then(function(r) { return r.json(); }).then(function(d) { cb(d.token || null); }).catch(function() { cb(null); });
|
|
193
|
-
}
|
|
194
203
|
function currentProject() {
|
|
195
204
|
var selected = document.querySelector('.project-card.selected');
|
|
196
205
|
return selected ? (selected.getAttribute('data-project') || '') : '';
|
|
@@ -585,15 +594,12 @@ export function renderTasksAndSettingsScript(authToken) {
|
|
|
585
594
|
return `(function() {
|
|
586
595
|
var _tsAuthToken = '${authToken}';
|
|
587
596
|
var _allTasks = [];
|
|
597
|
+
var esc = window._phrenEsc;
|
|
588
598
|
|
|
589
599
|
function tsAuthUrl(base) {
|
|
590
600
|
return base + (base.indexOf('?') === -1 ? '?' : '&') + '_auth=' + encodeURIComponent(_tsAuthToken);
|
|
591
601
|
}
|
|
592
602
|
|
|
593
|
-
function esc(s) {
|
|
594
|
-
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
595
|
-
}
|
|
596
|
-
|
|
597
603
|
function priorityBadge(p) {
|
|
598
604
|
if (!p) return '';
|
|
599
605
|
var colors = { high: '#ef4444', medium: '#f59e0b', low: '#6b7280' };
|
|
@@ -1287,10 +1293,10 @@ export function renderSearchScript(authToken) {
|
|
|
1287
1293
|
var _searchProjectsLoaded = false;
|
|
1288
1294
|
|
|
1289
1295
|
function searchAuthUrl(path) {
|
|
1290
|
-
return _searchAuthToken ? path + (path.includes('?') ? '&' : '?') + '
|
|
1296
|
+
return window._phrenAuthUrl ? window._phrenAuthUrl(path) : (_searchAuthToken ? path + (path.includes('?') ? '&' : '?') + '_auth=' + encodeURIComponent(_searchAuthToken) : path);
|
|
1291
1297
|
}
|
|
1292
1298
|
|
|
1293
|
-
|
|
1299
|
+
var esc = window._phrenEsc;
|
|
1294
1300
|
|
|
1295
1301
|
function doSearch() {
|
|
1296
1302
|
var q = document.getElementById('search-query').value.trim();
|
package/mcp/dist/phren-core.js
CHANGED
|
@@ -24,6 +24,8 @@ export const EXTRA_ENTITY_PATTERNS = [
|
|
|
24
24
|
// ISO date references: 2025-03-11, 2025/03/11
|
|
25
25
|
{ re: /\b\d{4}[-/]\d{2}[-/]\d{2}\b/g, label: "date" },
|
|
26
26
|
];
|
|
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", ".governance", "profiles", "templates"]);
|
|
27
29
|
// Default timeout for execFileSync calls (30s for most operations, 10s for quick probes like `which`)
|
|
28
30
|
export const EXEC_TIMEOUT_MS = 30_000;
|
|
29
31
|
export const EXEC_TIMEOUT_QUICK_MS = 10_000;
|
package/mcp/dist/phren-paths.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as path from "path";
|
|
|
4
4
|
import * as crypto from "crypto";
|
|
5
5
|
import * as yaml from "js-yaml";
|
|
6
6
|
import { bootstrapPhrenDotEnv } from "./phren-dotenv.js";
|
|
7
|
-
import { PhrenError, isRecord } from "./phren-core.js";
|
|
7
|
+
import { PhrenError, isRecord, RESERVED_PROJECT_DIR_NAMES } from "./phren-core.js";
|
|
8
8
|
import { errorMessage, isValidProjectName, safeProjectPath } from "./utils.js";
|
|
9
9
|
bootstrapPhrenDotEnv();
|
|
10
10
|
export const ROOT_MANIFEST_FILENAME = "phren.root.yaml";
|
|
@@ -280,7 +280,6 @@ export function resolveFindingsPath(projectDir) {
|
|
|
280
280
|
return findingsPath;
|
|
281
281
|
return undefined;
|
|
282
282
|
}
|
|
283
|
-
const RESERVED_PROJECT_DIR_NAMES = new Set(["profiles", "templates", "global"]);
|
|
284
283
|
function isProjectDirEntry(entry) {
|
|
285
284
|
return entry.isDirectory()
|
|
286
285
|
&& !entry.name.startsWith(".")
|
package/mcp/dist/proactivity.js
CHANGED
|
@@ -93,21 +93,21 @@ function resolveProactivityLevel(raw, fallback) {
|
|
|
93
93
|
}
|
|
94
94
|
export function getProactivityLevel(explicitPhrenPath) {
|
|
95
95
|
bootstrapPhrenDotEnv();
|
|
96
|
-
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY
|
|
96
|
+
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY, getConfiguredProactivityDefault(explicitPhrenPath));
|
|
97
97
|
}
|
|
98
98
|
export function getProactivityLevelForFindings(explicitPhrenPath) {
|
|
99
99
|
bootstrapPhrenDotEnv();
|
|
100
|
-
const findingsPreference = parseProactivityLevel(process.env.PHREN_PROACTIVITY_FINDINGS
|
|
100
|
+
const findingsPreference = parseProactivityLevel(process.env.PHREN_PROACTIVITY_FINDINGS);
|
|
101
101
|
if (findingsPreference)
|
|
102
102
|
return findingsPreference;
|
|
103
|
-
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY
|
|
103
|
+
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY, getConfiguredProactivityLevelForFindingsDefault(explicitPhrenPath));
|
|
104
104
|
}
|
|
105
105
|
export function getProactivityLevelForTask(explicitPhrenPath) {
|
|
106
106
|
bootstrapPhrenDotEnv();
|
|
107
|
-
const taskPreference = parseProactivityLevel(process.env.PHREN_PROACTIVITY_TASKS
|
|
107
|
+
const taskPreference = parseProactivityLevel(process.env.PHREN_PROACTIVITY_TASKS);
|
|
108
108
|
if (taskPreference)
|
|
109
109
|
return taskPreference;
|
|
110
|
-
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY
|
|
110
|
+
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY, getConfiguredProactivityLevelForTaskDefault(explicitPhrenPath));
|
|
111
111
|
}
|
|
112
112
|
export function hasExplicitFindingSignal(...texts) {
|
|
113
113
|
return texts.some((text) => {
|
|
@@ -5,7 +5,7 @@ import * as yaml from "js-yaml";
|
|
|
5
5
|
import { readInstallPreferences } from "./init-preferences.js";
|
|
6
6
|
import { debugLog } from "./shared.js";
|
|
7
7
|
import { errorMessage, safeProjectPath } from "./utils.js";
|
|
8
|
-
import { withFileLock } from "./governance
|
|
8
|
+
import { withFileLock } from "./shared-governance.js";
|
|
9
9
|
export const PROJECT_OWNERSHIP_MODES = ["phren-managed", "detached", "repo-managed"];
|
|
10
10
|
export const PROJECT_HOOK_EVENTS = ["UserPromptSubmit", "Stop", "SessionStart", "PostToolUse"];
|
|
11
11
|
export function parseProjectOwnershipMode(raw) {
|
|
@@ -18,7 +18,7 @@ function homePathForEnv(env, ...parts) {
|
|
|
18
18
|
return joinPortable(homeDir(env), ...parts);
|
|
19
19
|
}
|
|
20
20
|
function defaultPhrenPath(env = process.env) {
|
|
21
|
-
return env.PHREN_PATH ||
|
|
21
|
+
return env.PHREN_PATH || homePathForEnv(env, ".phren");
|
|
22
22
|
}
|
|
23
23
|
function normalizeWindowsPathToWsl(input) {
|
|
24
24
|
if (!input)
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import * as fs from "fs";
|
|
8
8
|
import { runtimeFile, debugLog } from "./shared.js";
|
|
9
9
|
import { isFeatureEnabled, errorMessage } from "./utils.js";
|
|
10
|
+
import { withFileLock } from "./shared-governance.js";
|
|
10
11
|
const CORRELATION_FILENAME = "query-correlations.jsonl";
|
|
11
12
|
const RECENT_WINDOW = 500;
|
|
12
13
|
const MIN_TOKEN_OVERLAP = 2;
|
|
@@ -56,28 +57,30 @@ export function markCorrelationsHelpful(phrenPath, sessionId, docKey) {
|
|
|
56
57
|
const correlationFile = runtimeFile(phrenPath, CORRELATION_FILENAME);
|
|
57
58
|
if (!fs.existsSync(correlationFile))
|
|
58
59
|
return;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
withFileLock(correlationFile, () => {
|
|
61
|
+
const raw = fs.readFileSync(correlationFile, "utf8");
|
|
62
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
63
|
+
let modified = false;
|
|
64
|
+
const updated = lines.map((line) => {
|
|
65
|
+
try {
|
|
66
|
+
const entry = JSON.parse(line);
|
|
67
|
+
if (entry.sessionId === sessionId &&
|
|
68
|
+
`${entry.project}/${entry.filename}` === docKey &&
|
|
69
|
+
!entry.helpful) {
|
|
70
|
+
entry.helpful = true;
|
|
71
|
+
modified = true;
|
|
72
|
+
return JSON.stringify(entry);
|
|
73
|
+
}
|
|
71
74
|
}
|
|
75
|
+
catch {
|
|
76
|
+
// keep original line
|
|
77
|
+
}
|
|
78
|
+
return line;
|
|
79
|
+
});
|
|
80
|
+
if (modified) {
|
|
81
|
+
fs.writeFileSync(correlationFile, updated.join("\n") + "\n");
|
|
72
82
|
}
|
|
73
|
-
catch {
|
|
74
|
-
// keep original line
|
|
75
|
-
}
|
|
76
|
-
return line;
|
|
77
83
|
});
|
|
78
|
-
if (modified) {
|
|
79
|
-
fs.writeFileSync(correlationFile, updated.join("\n") + "\n");
|
|
80
|
-
}
|
|
81
84
|
}
|
|
82
85
|
catch (err) {
|
|
83
86
|
debugLog(`query-correlation mark-helpful failed: ${errorMessage(err)}`);
|