@phren/cli 0.0.6 → 0.0.7
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 +3 -3
- package/mcp/dist/cli-hooks-session.js +9 -3
- package/mcp/dist/cli-hooks.js +2 -2
- package/mcp/dist/content-learning.js +3 -3
- package/mcp/dist/data-access.js +36 -3
- package/mcp/dist/finding-lifecycle.js +56 -29
- package/mcp/dist/governance-locks.js +11 -6
- package/mcp/dist/index.js +2 -1
- package/mcp/dist/init-preferences.js +18 -3
- package/mcp/dist/init.js +11 -0
- package/mcp/dist/mcp-config.js +0 -8
- package/mcp/dist/mcp-data.js +22 -3
- package/mcp/dist/mcp-extract.js +1 -0
- package/mcp/dist/mcp-finding.js +1 -1
- package/mcp/dist/mcp-hooks.js +36 -16
- package/mcp/dist/mcp-memory.js +0 -1
- package/mcp/dist/mcp-ops.js +5 -10
- package/mcp/dist/mcp-search.js +7 -1
- package/mcp/dist/mcp-session.js +13 -12
- package/mcp/dist/mcp-skills.js +5 -2
- package/mcp/dist/mcp-tasks.js +7 -4
- package/mcp/dist/memory-ui-assets.js +2 -2
- package/mcp/dist/memory-ui-data.js +2 -2
- package/mcp/dist/memory-ui-graph.js +178 -23
- package/mcp/dist/project-config.js +37 -18
- package/mcp/dist/shared-retrieval.js +4 -4
- package/mcp/dist/shared.js +1 -10
- package/package.json +1 -1
|
@@ -445,7 +445,7 @@ export function collectProjectsForUI(phrenPath, profile) {
|
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
catch (err) {
|
|
448
|
-
if (
|
|
448
|
+
if (process.env.PHREN_DEBUG)
|
|
449
449
|
process.stderr.write(`[phren] memory-ui filterByProfile: ${errorMessage(err)}\n`);
|
|
450
450
|
}
|
|
451
451
|
const results = [];
|
|
@@ -461,7 +461,7 @@ export function collectProjectsForUI(phrenPath, profile) {
|
|
|
461
461
|
let findingCount = 0;
|
|
462
462
|
if (fs.existsSync(findingsPath)) {
|
|
463
463
|
const content = fs.readFileSync(findingsPath, "utf8");
|
|
464
|
-
findingCount = (content.match(/^-
|
|
464
|
+
findingCount = (content.match(/^- /gm) || []).length;
|
|
465
465
|
}
|
|
466
466
|
const sparkline = new Array(8).fill(0);
|
|
467
467
|
if (fs.existsSync(findingsPath)) {
|
|
@@ -50,7 +50,10 @@ export function renderGraphScript() {
|
|
|
50
50
|
trailPoints: [], /* fading trail behind movement */
|
|
51
51
|
initialized: false,
|
|
52
52
|
waypoints: [], /* edge-walk waypoints [{x,y}] */
|
|
53
|
-
waypointIdx: 0
|
|
53
|
+
waypointIdx: 0, /* current waypoint index */
|
|
54
|
+
tripDist: 0, /* total distance of current trip (for ease-in-out) */
|
|
55
|
+
tripProgress: 0, /* distance traveled so far this trip */
|
|
56
|
+
targetNodeId: null /* id of destination node (set in phrenMoveTo) */
|
|
54
57
|
};
|
|
55
58
|
|
|
56
59
|
/* ── phren sprite image ─────────────────────────────────────────────── */
|
|
@@ -74,6 +77,18 @@ export function renderGraphScript() {
|
|
|
74
77
|
phren.targetX = phren.x;
|
|
75
78
|
phren.targetY = phren.y;
|
|
76
79
|
phren.initialized = true;
|
|
80
|
+
/* set initial current node to nearest visible node */
|
|
81
|
+
var _initNearest = null, _initNearestD = Infinity;
|
|
82
|
+
for (var _ni = 0; _ni < nodes.length; _ni++) {
|
|
83
|
+
var _nnd = nodes[_ni];
|
|
84
|
+
var _ndx = _nnd.x - phren.x, _ndy = _nnd.y - phren.y;
|
|
85
|
+
var _ndd = _ndx * _ndx + _ndy * _ndy;
|
|
86
|
+
if (_ndd < _initNearestD) { _initNearestD = _ndd; _initNearest = _nnd; }
|
|
87
|
+
}
|
|
88
|
+
if (_initNearest) {
|
|
89
|
+
phrenCurrentNodeId = _initNearest.id;
|
|
90
|
+
phrenRefreshAdjacentLinks();
|
|
91
|
+
}
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
/* find shortest edge path from nearest node to target node via BFS */
|
|
@@ -140,13 +155,31 @@ export function renderGraphScript() {
|
|
|
140
155
|
phren.moving = true;
|
|
141
156
|
phren.arriving = false;
|
|
142
157
|
phren.trailPoints = [{ x: phren.x, y: phren.y, age: 0 }];
|
|
158
|
+
/* record trip distance for ease-in-out interpolation */
|
|
159
|
+
var tdx = x - phren.x, tdy = y - phren.y;
|
|
160
|
+
phren.tripDist = Math.sqrt(tdx * tdx + tdy * tdy);
|
|
161
|
+
phren.tripProgress = 0;
|
|
143
162
|
/* try edge-walking if a target node is given */
|
|
144
163
|
phren.waypoints = targetNode ? phrenFindEdgePath(targetNode) : [];
|
|
145
164
|
phren.waypointIdx = 0;
|
|
165
|
+
phren.targetNodeId = targetNode ? targetNode.id : null;
|
|
146
166
|
/* ensure animation loop is running so phren movement renders */
|
|
147
167
|
if (!animFrame) animFrame = requestAnimationFrame(tick);
|
|
148
168
|
}
|
|
149
169
|
|
|
170
|
+
function phrenRefreshAdjacentLinks() {
|
|
171
|
+
phrenAdjacentLinks = [];
|
|
172
|
+
if (!phrenCurrentNodeId) return;
|
|
173
|
+
for (var i = 0; i < visibleLinks.length; i++) {
|
|
174
|
+
var lk = visibleLinks[i];
|
|
175
|
+
var sid = lk._source ? lk._source.id : (lk.source && typeof lk.source === 'object' ? lk.source.id : lk.source);
|
|
176
|
+
var tid = lk._target ? lk._target.id : (lk.target && typeof lk.target === 'object' ? lk.target.id : lk.target);
|
|
177
|
+
if (sid === phrenCurrentNodeId || tid === phrenCurrentNodeId) {
|
|
178
|
+
phrenAdjacentLinks.push(lk);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
150
183
|
function phrenUpdate(dt) {
|
|
151
184
|
phren.idlePhase += dt;
|
|
152
185
|
if (phren.moving) {
|
|
@@ -172,25 +205,35 @@ export function renderGraphScript() {
|
|
|
172
205
|
phren.moving = false;
|
|
173
206
|
phren.arriving = true;
|
|
174
207
|
phren.arriveTimer = 0;
|
|
208
|
+
/* update keyboard-nav current node */
|
|
209
|
+
if (phren.targetNodeId) {
|
|
210
|
+
phrenCurrentNodeId = phren.targetNodeId;
|
|
211
|
+
phrenSelectedEdgeIdx = -1;
|
|
212
|
+
phrenRefreshAdjacentLinks();
|
|
213
|
+
}
|
|
175
214
|
}
|
|
176
215
|
} else {
|
|
177
|
-
/* ease-out
|
|
178
|
-
var
|
|
216
|
+
/* ease-in-out via sine curve over the full trip distance */
|
|
217
|
+
var t = phren.tripDist > 0 ? Math.min(1, phren.tripProgress / phren.tripDist) : 1;
|
|
218
|
+
var easeInOut = 0.5 - 0.5 * Math.cos(Math.PI * t);
|
|
219
|
+
var baseSpeed = Math.max(3, phren.tripDist * 0.12);
|
|
220
|
+
var speed = Math.max(1.5, baseSpeed * (0.15 + 0.85 * easeInOut));
|
|
179
221
|
phren.x += (dx / dist) * speed;
|
|
180
222
|
phren.y += (dy / dist) * speed;
|
|
181
|
-
|
|
223
|
+
phren.tripProgress += speed;
|
|
224
|
+
/* record trail — longer buffer for gradual fade */
|
|
182
225
|
phren.trailPoints.push({ x: phren.x, y: phren.y, age: 0 });
|
|
183
|
-
if (phren.trailPoints.length >
|
|
226
|
+
if (phren.trailPoints.length > 50) phren.trailPoints.shift();
|
|
184
227
|
}
|
|
185
228
|
}
|
|
186
229
|
if (phren.arriving) {
|
|
187
230
|
phren.arriveTimer += dt;
|
|
188
|
-
if (phren.arriveTimer > 0
|
|
231
|
+
if (phren.arriveTimer > 1.0) phren.arriving = false;
|
|
189
232
|
}
|
|
190
|
-
/* age trail points */
|
|
233
|
+
/* age trail points — 2s lifetime for a longer, gradual fade */
|
|
191
234
|
for (var i = phren.trailPoints.length - 1; i >= 0; i--) {
|
|
192
235
|
phren.trailPoints[i].age += dt;
|
|
193
|
-
if (phren.trailPoints[i].age >
|
|
236
|
+
if (phren.trailPoints[i].age > 2.0) phren.trailPoints.splice(i, 1);
|
|
194
237
|
}
|
|
195
238
|
}
|
|
196
239
|
|
|
@@ -199,15 +242,16 @@ export function renderGraphScript() {
|
|
|
199
242
|
var px = phren.x, py = phren.y;
|
|
200
243
|
var s = 1 / scale; /* unit size in graph coords */
|
|
201
244
|
|
|
202
|
-
/* trail — purple tinted */
|
|
245
|
+
/* trail — purple tinted, 2s fade for a longer gradual tail */
|
|
203
246
|
if (phren.trailPoints.length > 1) {
|
|
204
247
|
for (var i = 1; i < phren.trailPoints.length; i++) {
|
|
205
248
|
var pt = phren.trailPoints[i];
|
|
206
249
|
var prev = phren.trailPoints[i - 1];
|
|
207
|
-
var
|
|
250
|
+
var fadeT = 1 - pt.age / 2.0;
|
|
251
|
+
var alpha = Math.max(0, 0.38 * fadeT * fadeT); /* quadratic for softer tail */
|
|
208
252
|
ctx.beginPath();
|
|
209
253
|
ctx.strokeStyle = 'rgba(123,104,174,' + alpha + ')';
|
|
210
|
-
ctx.lineWidth = (
|
|
254
|
+
ctx.lineWidth = (3.0 * fadeT) * s;
|
|
211
255
|
ctx.moveTo(prev.x, prev.y);
|
|
212
256
|
ctx.lineTo(pt.x, pt.y);
|
|
213
257
|
ctx.stroke();
|
|
@@ -231,16 +275,33 @@ export function renderGraphScript() {
|
|
|
231
275
|
spriteScreenSize = 48 + 8 * (1 - phren.arriveTimer / 0.4);
|
|
232
276
|
}
|
|
233
277
|
var spriteSize = spriteScreenSize * s; /* convert to graph coords */
|
|
234
|
-
/* bob up/down
|
|
235
|
-
var
|
|
278
|
+
/* bob up/down when walking — sine wave synced to walk progress */
|
|
279
|
+
var walkPhase = phren.tripDist > 0
|
|
280
|
+
? (phren.tripProgress / phren.tripDist) * Math.PI * 6
|
|
281
|
+
: phren.idlePhase * 8;
|
|
282
|
+
var bobOffset = phren.moving ? Math.sin(walkPhase) * 2.5 * s : 0;
|
|
283
|
+
/* bounce on arrival — damped overshoot then settle */
|
|
284
|
+
var bounceOffset = (phren.arriving && phren.arriveTimer < 0.55)
|
|
285
|
+
? -3 * Math.sin(phren.arriveTimer * Math.PI * 4.5) * Math.exp(-phren.arriveTimer * 8) * s
|
|
286
|
+
: 0;
|
|
287
|
+
/* idle breathing — gentle scale pulse (3s loop) when resting */
|
|
288
|
+
var idleScale = (!phren.moving && !phren.arriving)
|
|
289
|
+
? 1.0 + 0.02 * Math.sin(phren.idlePhase * (2 * Math.PI / 3))
|
|
290
|
+
: 1.0;
|
|
291
|
+
var totalYOffset = bobOffset + bounceOffset;
|
|
236
292
|
/* crisp pixel art — no smoothing */
|
|
237
293
|
ctx.imageSmoothingEnabled = false;
|
|
238
|
-
|
|
294
|
+
if (idleScale !== 1.0) {
|
|
295
|
+
ctx.translate(px, py);
|
|
296
|
+
ctx.scale(idleScale, idleScale);
|
|
297
|
+
ctx.translate(-px, -py);
|
|
298
|
+
}
|
|
299
|
+
ctx.drawImage(phrenImg, px - spriteSize / 2, py - spriteSize / 2 + totalYOffset, spriteSize, spriteSize);
|
|
239
300
|
/* arrival flash: cyan glow ring */
|
|
240
|
-
if (phren.arriving && phren.arriveTimer < 0.
|
|
241
|
-
var ringAlpha = 0.6 * (1 - phren.arriveTimer / 0.
|
|
301
|
+
if (phren.arriving && phren.arriveTimer < 0.6) {
|
|
302
|
+
var ringAlpha = 0.6 * (1 - phren.arriveTimer / 0.6);
|
|
242
303
|
ctx.beginPath();
|
|
243
|
-
ctx.arc(px, py +
|
|
304
|
+
ctx.arc(px, py + totalYOffset, spriteSize * 0.55, 0, Math.PI * 2);
|
|
244
305
|
ctx.strokeStyle = 'rgba(0,229,255,' + ringAlpha + ')';
|
|
245
306
|
ctx.lineWidth = 2 * s;
|
|
246
307
|
ctx.shadowColor = 'rgba(0,229,255,' + (ringAlpha * 0.5) + ')';
|
|
@@ -296,11 +357,15 @@ export function renderGraphScript() {
|
|
|
296
357
|
var alpha = 1.0;
|
|
297
358
|
var animFrame = null;
|
|
298
359
|
var canvas, ctx, tooltip;
|
|
360
|
+
var _nodeSelectCb = null; /* external callback for node selection */
|
|
299
361
|
var pulseT = 0;
|
|
300
362
|
var _tooltipNode = null, _tooltipTimer = null;
|
|
301
363
|
var _prevVisibleCount = 0;
|
|
302
364
|
var focusedNodeIndex = -1;
|
|
303
365
|
var liveRegion = null;
|
|
366
|
+
var phrenCurrentNodeId = null; /* id of node phren is currently at */
|
|
367
|
+
var phrenSelectedEdgeIdx = -1; /* index into phrenAdjacentLinks (-1 = none) */
|
|
368
|
+
var phrenAdjacentLinks = []; /* edges connected to current node */
|
|
304
369
|
|
|
305
370
|
/* ── helpers ────────────────────────────────────────────────────────── */
|
|
306
371
|
function clamp(v, lo, hi) { return v < lo ? lo : v > hi ? hi : v; }
|
|
@@ -932,6 +997,44 @@ export function renderGraphScript() {
|
|
|
932
997
|
}
|
|
933
998
|
ctx.lineWidth = baseEdgeWidth;
|
|
934
999
|
|
|
1000
|
+
/* 1b. selected edge highlight for phren keyboard navigation */
|
|
1001
|
+
if (phrenSelectedEdgeIdx >= 0 && phrenSelectedEdgeIdx < phrenAdjacentLinks.length) {
|
|
1002
|
+
var selLk = phrenAdjacentLinks[phrenSelectedEdgeIdx];
|
|
1003
|
+
if (selLk._source && selLk._target) {
|
|
1004
|
+
var selSid = selLk._source.id;
|
|
1005
|
+
var selDest = (selSid === phrenCurrentNodeId) ? selLk._target : selLk._source;
|
|
1006
|
+
ctx.save();
|
|
1007
|
+
/* glow edge */
|
|
1008
|
+
ctx.beginPath();
|
|
1009
|
+
ctx.strokeStyle = 'rgba(0,229,255,0.85)';
|
|
1010
|
+
ctx.lineWidth = 2.5 / scale;
|
|
1011
|
+
ctx.shadowColor = 'rgba(0,229,255,0.45)';
|
|
1012
|
+
ctx.shadowBlur = 10 / scale;
|
|
1013
|
+
ctx.moveTo(selLk._source.x, selLk._source.y);
|
|
1014
|
+
ctx.lineTo(selLk._target.x, selLk._target.y);
|
|
1015
|
+
ctx.stroke();
|
|
1016
|
+
ctx.shadowBlur = 0;
|
|
1017
|
+
/* destination label near edge midpoint */
|
|
1018
|
+
var selMx = (selLk._source.x + selLk._target.x) / 2;
|
|
1019
|
+
var selMy = (selLk._source.y + selLk._target.y) / 2;
|
|
1020
|
+
var selLbl = (selDest.label || selDest.id || '').slice(0, 32);
|
|
1021
|
+
if (selLbl) {
|
|
1022
|
+
var selFs = Math.max(10, Math.round(11 / scale));
|
|
1023
|
+
ctx.font = '600 ' + selFs + 'px sans-serif';
|
|
1024
|
+
ctx.textAlign = 'center';
|
|
1025
|
+
ctx.textBaseline = 'bottom';
|
|
1026
|
+
var selTw = ctx.measureText(selLbl).width;
|
|
1027
|
+
var selPad = 5 / scale;
|
|
1028
|
+
var selBh = selFs + 2 * selPad;
|
|
1029
|
+
ctx.fillStyle = 'rgba(10,12,30,0.88)';
|
|
1030
|
+
ctx.fillRect(selMx - selTw / 2 - selPad, selMy - selBh - 4 / scale, selTw + 2 * selPad, selBh);
|
|
1031
|
+
ctx.fillStyle = 'rgba(0,229,255,1)';
|
|
1032
|
+
ctx.fillText(selLbl, selMx, selMy - 4 / scale);
|
|
1033
|
+
}
|
|
1034
|
+
ctx.restore();
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
935
1038
|
/* 2. health rings with dash patterns + text labels (WCAG 1.4.1) */
|
|
936
1039
|
for (var i = 0; i < nodes.length; i++) {
|
|
937
1040
|
var nd = nodes[i];
|
|
@@ -1106,6 +1209,7 @@ export function renderGraphScript() {
|
|
|
1106
1209
|
|
|
1107
1210
|
/* ── hit testing ────────────────────────────────────────────────────── */
|
|
1108
1211
|
function hitTest(mx, my) {
|
|
1212
|
+
/* mx/my must be CSS pixels (same as e.clientX - rect.left from mouse events) */
|
|
1109
1213
|
var gx = (mx - panX) / scale;
|
|
1110
1214
|
var gy = (my - panY) / scale;
|
|
1111
1215
|
var closest = null, closestDist = Infinity;
|
|
@@ -1129,6 +1233,13 @@ export function renderGraphScript() {
|
|
|
1129
1233
|
if (node && typeof node.x === 'number' && typeof node.y === 'number') {
|
|
1130
1234
|
phrenMoveTo(node.x, node.y, node);
|
|
1131
1235
|
}
|
|
1236
|
+
/* fire external callback so VS Code extension can react to selection */
|
|
1237
|
+
if (_nodeSelectCb && node) {
|
|
1238
|
+
var rect = canvas ? canvas.getBoundingClientRect() : { left: 0, top: 0 };
|
|
1239
|
+
var cx = node.x * scale + panX + rect.left;
|
|
1240
|
+
var cy = node.y * scale + panY + rect.top;
|
|
1241
|
+
try { _nodeSelectCb(node, cx, cy); } catch(e) { /* swallow callback errors */ }
|
|
1242
|
+
}
|
|
1132
1243
|
var metaEl = document.getElementById('graph-detail-meta');
|
|
1133
1244
|
var bodyEl = document.getElementById('graph-detail-body');
|
|
1134
1245
|
if (!metaEl || !bodyEl) return;
|
|
@@ -1413,7 +1524,20 @@ export function renderGraphScript() {
|
|
|
1413
1524
|
render();
|
|
1414
1525
|
break;
|
|
1415
1526
|
case 'Enter':
|
|
1416
|
-
|
|
1527
|
+
case ' ':
|
|
1528
|
+
e.preventDefault();
|
|
1529
|
+
if (phrenSelectedEdgeIdx >= 0 && phrenSelectedEdgeIdx < phrenAdjacentLinks.length) {
|
|
1530
|
+
/* walk phren along selected edge */
|
|
1531
|
+
var wLk = phrenAdjacentLinks[phrenSelectedEdgeIdx];
|
|
1532
|
+
var wSid = wLk._source ? wLk._source.id : null;
|
|
1533
|
+
var wDest = (wLk._source && wLk._target)
|
|
1534
|
+
? (wSid === phrenCurrentNodeId ? wLk._target : wLk._source)
|
|
1535
|
+
: null;
|
|
1536
|
+
if (wDest) {
|
|
1537
|
+
phrenMoveTo(wDest.x, wDest.y, wDest);
|
|
1538
|
+
announce('Walking to ' + (wDest.label || wDest.id || ''));
|
|
1539
|
+
}
|
|
1540
|
+
} else if (e.key === 'Enter' && focusedNodeIndex >= 0 && focusedNodeIndex < visibleNodes.length) {
|
|
1417
1541
|
var sn = visibleNodes[focusedNodeIndex];
|
|
1418
1542
|
renderGraphDetails(sn);
|
|
1419
1543
|
announceNode(sn);
|
|
@@ -1423,7 +1547,10 @@ export function renderGraphScript() {
|
|
|
1423
1547
|
}
|
|
1424
1548
|
break;
|
|
1425
1549
|
case 'Escape':
|
|
1426
|
-
if (
|
|
1550
|
+
if (phrenSelectedEdgeIdx >= 0) {
|
|
1551
|
+
phrenSelectedEdgeIdx = -1;
|
|
1552
|
+
render();
|
|
1553
|
+
} else if (selectedNode) {
|
|
1427
1554
|
var prevFocus = focusedNodeIndex;
|
|
1428
1555
|
renderGraphDetails(null);
|
|
1429
1556
|
focusedNodeIndex = prevFocus;
|
|
@@ -1436,12 +1563,22 @@ export function renderGraphScript() {
|
|
|
1436
1563
|
break;
|
|
1437
1564
|
case 'ArrowLeft':
|
|
1438
1565
|
e.preventDefault();
|
|
1439
|
-
|
|
1566
|
+
if (phrenAdjacentLinks.length > 0) {
|
|
1567
|
+
phrenSelectedEdgeIdx = phrenSelectedEdgeIdx < 0
|
|
1568
|
+
? phrenAdjacentLinks.length - 1
|
|
1569
|
+
: (phrenSelectedEdgeIdx - 1 + phrenAdjacentLinks.length) % phrenAdjacentLinks.length;
|
|
1570
|
+
} else {
|
|
1571
|
+
panX += PAN_STEP;
|
|
1572
|
+
}
|
|
1440
1573
|
render();
|
|
1441
1574
|
break;
|
|
1442
1575
|
case 'ArrowRight':
|
|
1443
1576
|
e.preventDefault();
|
|
1444
|
-
|
|
1577
|
+
if (phrenAdjacentLinks.length > 0) {
|
|
1578
|
+
phrenSelectedEdgeIdx = (phrenSelectedEdgeIdx + 1) % phrenAdjacentLinks.length;
|
|
1579
|
+
} else {
|
|
1580
|
+
panX -= PAN_STEP;
|
|
1581
|
+
}
|
|
1445
1582
|
render();
|
|
1446
1583
|
break;
|
|
1447
1584
|
case 'ArrowUp':
|
|
@@ -1678,7 +1815,8 @@ export function renderGraphScript() {
|
|
|
1678
1815
|
lastTickTime = timestamp;
|
|
1679
1816
|
simulate();
|
|
1680
1817
|
render();
|
|
1681
|
-
|
|
1818
|
+
var phrenStillActive = phren.moving || phren.arriving || phren.trailPoints.length > 0;
|
|
1819
|
+
if (alpha > 0 || dragging || phrenStillActive) {
|
|
1682
1820
|
animFrame = requestAnimationFrame(tick);
|
|
1683
1821
|
} else {
|
|
1684
1822
|
animFrame = null;
|
|
@@ -1897,12 +2035,29 @@ export function renderGraphScript() {
|
|
|
1897
2035
|
}
|
|
1898
2036
|
|
|
1899
2037
|
applyFilters();
|
|
1900
|
-
phrenInit(visibleNodes);
|
|
1901
2038
|
buildFilterBar();
|
|
1902
2039
|
initPositions();
|
|
2040
|
+
phrenInit(visibleNodes); /* must run after initPositions so node coords are valid */
|
|
1903
2041
|
setupInteraction();
|
|
1904
2042
|
startSimulation();
|
|
1905
2043
|
announceGraphSummary();
|
|
2044
|
+
},
|
|
2045
|
+
/** Register a callback fired when the user selects a node. */
|
|
2046
|
+
onNodeSelect: function(cb) { _nodeSelectCb = cb; },
|
|
2047
|
+
/** Hit-test at CSS screen coordinates (same units as e.clientX/clientY minus canvas rect).
|
|
2048
|
+
* Note: pass CSS pixels, NOT canvas.width/height (which are native/DPR-scaled). */
|
|
2049
|
+
getNodeAt: function(x, y) { return hitTest(x, y); },
|
|
2050
|
+
/** Returns the node id where phren is currently located, or null. */
|
|
2051
|
+
getCurrentNode: function() { return phrenCurrentNodeId; },
|
|
2052
|
+
/** Programmatically move the phren character to a node (by id). */
|
|
2053
|
+
walkTo: function(nodeId) {
|
|
2054
|
+
for (var i = 0; i < visibleNodes.length; i++) {
|
|
2055
|
+
if (visibleNodes[i].id === nodeId) {
|
|
2056
|
+
phrenMoveTo(visibleNodes[i].x, visibleNodes[i].y, visibleNodes[i]);
|
|
2057
|
+
return true;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
return false;
|
|
1906
2061
|
}
|
|
1907
2062
|
};
|
|
1908
2063
|
})();
|
|
@@ -5,13 +5,14 @@ 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 } from "./utils.js";
|
|
8
|
+
import { withFileLock } from "./governance-locks.js";
|
|
8
9
|
export const PROJECT_OWNERSHIP_MODES = ["phren-managed", "detached", "repo-managed"];
|
|
9
10
|
export const PROJECT_HOOK_EVENTS = ["UserPromptSubmit", "Stop", "SessionStart", "PostToolUse"];
|
|
10
11
|
export function parseProjectOwnershipMode(raw) {
|
|
11
12
|
if (!raw)
|
|
12
13
|
return undefined;
|
|
13
14
|
const normalized = raw.trim().toLowerCase();
|
|
14
|
-
if (normalized === "phren" || normalized === "managed"
|
|
15
|
+
if (normalized === "phren" || normalized === "managed")
|
|
15
16
|
return "phren-managed";
|
|
16
17
|
if (normalized === "repo" || normalized === "external")
|
|
17
18
|
return "repo-managed";
|
|
@@ -37,17 +38,22 @@ export function readProjectConfig(phrenPath, project) {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
export function writeProjectConfig(phrenPath, project, patch) {
|
|
40
|
-
const configPath = projectConfigPath(phrenPath, project);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
const configPath = path.resolve(projectConfigPath(phrenPath, project));
|
|
42
|
+
if (!configPath.startsWith(phrenPath + path.sep) && configPath !== phrenPath) {
|
|
43
|
+
throw new Error(`Project config path escapes phren store`);
|
|
44
|
+
}
|
|
45
|
+
return withFileLock(configPath, () => {
|
|
46
|
+
const current = readProjectConfig(phrenPath, project);
|
|
47
|
+
const next = {
|
|
48
|
+
...current,
|
|
49
|
+
...patch,
|
|
50
|
+
};
|
|
51
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
52
|
+
const tmpPath = `${configPath}.tmp-${crypto.randomUUID()}`;
|
|
53
|
+
fs.writeFileSync(tmpPath, yaml.dump(next, { lineWidth: 1000 }));
|
|
54
|
+
fs.renameSync(tmpPath, configPath);
|
|
55
|
+
return next;
|
|
56
|
+
});
|
|
51
57
|
}
|
|
52
58
|
export function getProjectSourcePath(phrenPath, project, config) {
|
|
53
59
|
const raw = (config ?? readProjectConfig(phrenPath, project)).sourcePath;
|
|
@@ -75,11 +81,24 @@ export function isProjectHookEnabled(phrenPath, project, event, config) {
|
|
|
75
81
|
return true;
|
|
76
82
|
}
|
|
77
83
|
export function writeProjectHookConfig(phrenPath, project, patch) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
// Move read+merge inside the lock so concurrent writers cannot clobber each other.
|
|
85
|
+
const configPath = path.resolve(projectConfigPath(phrenPath, project));
|
|
86
|
+
if (!configPath.startsWith(phrenPath + path.sep) && configPath !== phrenPath) {
|
|
87
|
+
throw new Error(`Project config path escapes phren store`);
|
|
88
|
+
}
|
|
89
|
+
return withFileLock(configPath, () => {
|
|
90
|
+
const current = readProjectConfig(phrenPath, project);
|
|
91
|
+
const next = {
|
|
92
|
+
...current,
|
|
93
|
+
hooks: {
|
|
94
|
+
...normalizeHookConfig(current),
|
|
95
|
+
...patch,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
99
|
+
const tmpPath = `${configPath}.tmp-${crypto.randomUUID()}`;
|
|
100
|
+
fs.writeFileSync(tmpPath, yaml.dump(next, { lineWidth: 1000 }));
|
|
101
|
+
fs.renameSync(tmpPath, configPath);
|
|
102
|
+
return next;
|
|
84
103
|
});
|
|
85
104
|
}
|
|
@@ -36,7 +36,7 @@ const LOW_VALUE_BULLET_FRACTION = 0.5;
|
|
|
36
36
|
// ── Intent and scoring helpers ───────────────────────────────────────────────
|
|
37
37
|
export function detectTaskIntent(prompt) {
|
|
38
38
|
const p = prompt.toLowerCase();
|
|
39
|
-
if (
|
|
39
|
+
if (/(^|\s)\/[a-z][a-z0-9_-]{1,63}(?=$|\s|[.,:;!?])/.test(p) || /\b(skill|swarm|lineup|slash command)\b/.test(p))
|
|
40
40
|
return "skill";
|
|
41
41
|
if (/(bug|error|fix|broken|regression|fail|stack trace)/.test(p))
|
|
42
42
|
return "debug";
|
|
@@ -400,7 +400,7 @@ export async function searchDocumentsAsync(db, safeQuery, prompt, keywords, dete
|
|
|
400
400
|
}
|
|
401
401
|
catch (err) {
|
|
402
402
|
// Vector search failure is non-fatal — return sync result
|
|
403
|
-
if (
|
|
403
|
+
if (process.env.PHREN_DEBUG)
|
|
404
404
|
process.stderr.write(`[phren] hybridSearch vectorFallback: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
405
405
|
return syncResult;
|
|
406
406
|
}
|
|
@@ -500,7 +500,7 @@ export async function searchKnowledgeRows(db, options) {
|
|
|
500
500
|
}
|
|
501
501
|
}
|
|
502
502
|
catch (err) {
|
|
503
|
-
if (
|
|
503
|
+
if (process.env.PHREN_DEBUG) {
|
|
504
504
|
process.stderr.write(`[phren] vectorFallback: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
505
505
|
}
|
|
506
506
|
}
|
|
@@ -754,7 +754,7 @@ export function markStaleCitations(snippet) {
|
|
|
754
754
|
}
|
|
755
755
|
}
|
|
756
756
|
catch (err) {
|
|
757
|
-
if (
|
|
757
|
+
if (process.env.PHREN_DEBUG)
|
|
758
758
|
process.stderr.write(`[phren] applyCitationAnnotations fileRead: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
759
759
|
stale = true;
|
|
760
760
|
}
|
package/mcp/dist/shared.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { debugLog, runtimeFile } from "./phren-paths.js";
|
|
4
|
-
import { errorMessage
|
|
4
|
+
import { errorMessage } from "./utils.js";
|
|
5
5
|
export { HOOK_TOOL_NAMES, hookConfigPath } from "./provider-adapters.js";
|
|
6
6
|
export { EXEC_TIMEOUT_MS, EXEC_TIMEOUT_QUICK_MS, PhrenError, phrenOk, phrenErr, forwardErr, parsePhrenErrorCode, isRecord, withDefaults, FINDING_TYPES, FINDING_TAGS, KNOWN_OBSERVATION_TAGS, DOC_TYPES, capCache, } from "./phren-core.js";
|
|
7
7
|
export { ROOT_MANIFEST_FILENAME, homeDir, homePath, expandHomePath, defaultPhrenPath, rootManifestPath, readRootManifest, writeRootManifest, resolveInstallContext, findNearestPhrenPath, isProjectLocalMode, runtimeDir, tryUnlink, sessionsDir, runtimeFile, installPreferencesFile, runtimeHealthFile, shellStateFile, sessionMetricsFile, memoryScoresFile, memoryUsageLogFile, sessionMarker, debugLog, appendIndexEvent, resolveFindingsPath, findPhrenPath, ensurePhrenPath, findPhrenPathWithArg, normalizeProjectNameForCreate, findProjectNameCaseInsensitive, getProjectDirs, collectNativeMemoryFiles, computePhrenLiveStateToken, getPhrenPath, qualityMarkers, atomicWriteText, } from "./phren-paths.js";
|
|
@@ -28,15 +28,6 @@ export function isMemoryScopeVisible(itemScope, activeScope) {
|
|
|
28
28
|
export function impactLogFile(phrenPath) {
|
|
29
29
|
return runtimeFile(phrenPath, "impact.jsonl");
|
|
30
30
|
}
|
|
31
|
-
function isProjectDirEntry(entry) {
|
|
32
|
-
return entry.isDirectory()
|
|
33
|
-
&& !entry.name.startsWith(".")
|
|
34
|
-
&& !entry.name.endsWith(".archived")
|
|
35
|
-
&& !RESERVED_PROJECT_DIR_NAMES.has(entry.name);
|
|
36
|
-
}
|
|
37
|
-
function isCanonicalProjectDirName(name) {
|
|
38
|
-
return name === name.toLowerCase() && isValidProjectName(name);
|
|
39
|
-
}
|
|
40
31
|
export function appendAuditLog(phrenPath, event, details) {
|
|
41
32
|
const logPath = runtimeFile(phrenPath, "audit.log");
|
|
42
33
|
const line = `[${new Date().toISOString()}] ${event} ${details}\n`;
|