@phren/cli 0.0.8 → 0.0.10
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/icon.svg +416 -0
- package/mcp/dist/capabilities/mcp.js +5 -5
- package/mcp/dist/capabilities/vscode.js +3 -3
- package/mcp/dist/cli-actions.js +113 -3
- package/mcp/dist/cli-govern.js +166 -1
- package/mcp/dist/cli-hooks.js +2 -2
- package/mcp/dist/cli-namespaces.js +497 -4
- package/mcp/dist/cli.js +7 -1
- package/mcp/dist/content-citation.js +12 -22
- package/mcp/dist/data-access.js +1 -1
- package/mcp/dist/data-tasks.js +39 -1
- package/mcp/dist/entrypoint.js +6 -0
- package/mcp/dist/mcp-finding.js +26 -1
- package/mcp/dist/mcp-tasks.js +23 -2
- package/mcp/dist/memory-ui-assets.js +37 -1
- package/mcp/dist/memory-ui-graph.js +19 -5
- package/mcp/dist/memory-ui-page.js +7 -30
- package/mcp/dist/memory-ui-scripts.js +1 -103
- package/mcp/dist/memory-ui-server.js +1 -82
- package/mcp/dist/shared-retrieval.js +7 -47
- package/mcp/dist/shell-input.js +4 -22
- package/mcp/dist/shell-render.js +2 -2
- package/mcp/dist/shell-view.js +1 -2
- package/mcp/dist/shell.js +0 -5
- package/mcp/dist/tool-registry.js +1 -0
- package/package.json +2 -1
- package/skills/docs.md +170 -0
|
@@ -103,20 +103,20 @@ ${TASK_UI_STYLES}
|
|
|
103
103
|
<dl>
|
|
104
104
|
<dt>What is the Review Queue?</dt>
|
|
105
105
|
<dd>Fragments flagged by governance for human review. Items accumulate here when <code>phren maintain govern</code> is run.</dd>
|
|
106
|
-
<dt>
|
|
107
|
-
<dd>
|
|
108
|
-
<dt>
|
|
109
|
-
<dd>
|
|
106
|
+
<dt>Can I approve, reject, or edit items here?</dt>
|
|
107
|
+
<dd>No. The web UI review queue is read-only and exists for inspection only.</dd>
|
|
108
|
+
<dt>How do I clear items?</dt>
|
|
109
|
+
<dd>Use maintenance flows such as <code>phren maintain prune</code>, or update the underlying findings/tasks directly.</dd>
|
|
110
110
|
<dt>Is this automatic?</dt>
|
|
111
|
-
<dd>No. Agents do not auto-
|
|
111
|
+
<dd>No. Agents do not auto-accept review-queue items.</dd>
|
|
112
112
|
<dt>How do items get here?</dt>
|
|
113
113
|
<dd><code>phren maintain govern</code> flags stale or low-confidence fragments for review.</dd>
|
|
114
|
-
<dt>How to
|
|
114
|
+
<dt>How to reduce noise?</dt>
|
|
115
115
|
<dd>Run <code>phren maintain prune</code> to auto-remove expired items without manual review.</dd>
|
|
116
116
|
</dl>
|
|
117
117
|
</details>
|
|
118
118
|
|
|
119
|
-
<p style="font-size:var(--text-sm);color:var(--muted);margin-bottom:12px;letter-spacing:-0.01em">Fragments flagged for review.
|
|
119
|
+
<p style="font-size:var(--text-sm);color:var(--muted);margin-bottom:12px;letter-spacing:-0.01em">Fragments flagged for review. Inspect them here; the web UI does not mutate queue items.</p>
|
|
120
120
|
|
|
121
121
|
<div id="review-summary-banner" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;align-items:center"></div>
|
|
122
122
|
|
|
@@ -136,16 +136,8 @@ ${TASK_UI_STYLES}
|
|
|
136
136
|
|
|
137
137
|
<div id="review-kbd-hints" style="font-size:var(--text-xs);color:var(--muted);margin-bottom:12px;display:none;gap:16px;flex-wrap:wrap">
|
|
138
138
|
<span><kbd>j</kbd>/<kbd>k</kbd> navigate</span>
|
|
139
|
-
<span><kbd>a</kbd> approve</span>
|
|
140
|
-
<span><kbd>r</kbd> reject</span>
|
|
141
|
-
<span><kbd>e</kbd> edit</span>
|
|
142
139
|
</div>
|
|
143
140
|
|
|
144
|
-
<label class="review-select-all" id="review-select-all" style="display:none">
|
|
145
|
-
<input type="checkbox" id="review-select-all-checkbox" />
|
|
146
|
-
Select all
|
|
147
|
-
</label>
|
|
148
|
-
|
|
149
141
|
<div class="review-cards" id="review-cards-list">
|
|
150
142
|
<div class="review-cards-loading" style="text-align:center;padding:40px;color:var(--muted)">Loading...</div>
|
|
151
143
|
</div>
|
|
@@ -297,21 +289,6 @@ ${TASK_UI_STYLES}
|
|
|
297
289
|
</div>
|
|
298
290
|
</div>
|
|
299
291
|
|
|
300
|
-
<div class="batch-bar" id="batch-bar">
|
|
301
|
-
<span class="batch-bar-count" id="batch-count">0 selected</span>
|
|
302
|
-
<button class="btn btn-sm btn-approve" id="batch-approve-btn">Approve All</button>
|
|
303
|
-
<button class="btn btn-sm btn-reject" id="batch-reject-btn">Reject All</button>
|
|
304
|
-
<select class="btn btn-sm" id="batch-tag-select" style="cursor:pointer">
|
|
305
|
-
<option value="">Approve by tag...</option>
|
|
306
|
-
<option value="decision">[decision]</option>
|
|
307
|
-
<option value="pitfall">[pitfall]</option>
|
|
308
|
-
<option value="pattern">[pattern]</option>
|
|
309
|
-
<option value="fix">[fix]</option>
|
|
310
|
-
<option value="warning">[warning]</option>
|
|
311
|
-
</select>
|
|
312
|
-
<button class="btn btn-sm" id="batch-cancel-btn">Cancel</button>
|
|
313
|
-
</div>
|
|
314
|
-
|
|
315
292
|
<div class="toast-container" id="toast-container"></div>
|
|
316
293
|
|
|
317
294
|
<div class="cmdpal-overlay" id="cmdpal">
|
|
@@ -579,96 +579,7 @@ export function renderProjectReferenceEnhancementScript(authToken) {
|
|
|
579
579
|
})();`;
|
|
580
580
|
}
|
|
581
581
|
export function renderReviewQueueEditSyncScript() {
|
|
582
|
-
return
|
|
583
|
-
function normalizeQueueText(raw) {
|
|
584
|
-
return String(raw == null ? '' : raw)
|
|
585
|
-
.replace(/\\r\\n?/g, '\\n')
|
|
586
|
-
.replace(/\\0/g, ' ')
|
|
587
|
-
.replace(/<!--[\\s\\S]*?-->/g, ' ')
|
|
588
|
-
.replace(/\\\\[nrt]/g, ' ')
|
|
589
|
-
.replace(/\\\\\"/g, '"')
|
|
590
|
-
.replace(/\\\\\\\\/g, '\\\\')
|
|
591
|
-
.replace(/\\n+/g, ' ')
|
|
592
|
-
.replace(/\\s+/g, ' ')
|
|
593
|
-
.trim();
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function rebuildEditedQueueLine(line, newText) {
|
|
597
|
-
var dateMatch = String(line || '').match(/^- \\[(\\d{4}-\\d{2}-\\d{2})\\]/);
|
|
598
|
-
var confidenceMatch = String(line || '').match(/\\[confidence\\s+([01](?:\\.\\d+)?)\\]/i);
|
|
599
|
-
var normalizedText = normalizeQueueText(newText);
|
|
600
|
-
var date = dateMatch ? dateMatch[1] : new Date().toISOString().slice(0, 10);
|
|
601
|
-
var confidencePart = confidenceMatch
|
|
602
|
-
? ' [confidence ' + Number(confidenceMatch[1]).toFixed(2) + ']'
|
|
603
|
-
: '';
|
|
604
|
-
return {
|
|
605
|
-
text: normalizedText,
|
|
606
|
-
line: '- [' + date + '] ' + normalizedText + confidencePart
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function escapeHtml(text) {
|
|
611
|
-
return String(text)
|
|
612
|
-
.replace(/&/g, '&')
|
|
613
|
-
.replace(/</g, '<')
|
|
614
|
-
.replace(/>/g, '>')
|
|
615
|
-
.replace(/"/g, '"');
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
function syncEditedCard(card, project, nextLine, nextText) {
|
|
619
|
-
if (!card || !project || !nextLine) return;
|
|
620
|
-
card.setAttribute('data-key', project + '\\\\x00' + nextLine);
|
|
621
|
-
card.setAttribute('data-project', project);
|
|
622
|
-
var approveBtn = card.querySelector('.btn-approve');
|
|
623
|
-
if (approveBtn) {
|
|
624
|
-
approveBtn.setAttribute('data-project', project);
|
|
625
|
-
approveBtn.setAttribute('data-line', nextLine);
|
|
626
|
-
}
|
|
627
|
-
var rejectBtn = card.querySelector('.btn-reject');
|
|
628
|
-
if (rejectBtn) {
|
|
629
|
-
rejectBtn.setAttribute('data-project', project);
|
|
630
|
-
rejectBtn.setAttribute('data-line', nextLine);
|
|
631
|
-
}
|
|
632
|
-
var editForm = card.querySelector('.review-card-edit form');
|
|
633
|
-
if (editForm) {
|
|
634
|
-
editForm.setAttribute('data-project', project);
|
|
635
|
-
editForm.setAttribute('data-line', nextLine);
|
|
636
|
-
}
|
|
637
|
-
var editTextarea = card.querySelector('textarea[name="new_text"]');
|
|
638
|
-
if (editTextarea) editTextarea.value = nextText;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function maybeSyncEditedCard(card, project, line, newText, attemptsLeft) {
|
|
642
|
-
if (!card || !project) return;
|
|
643
|
-
var rebuilt = rebuildEditedQueueLine(line, newText);
|
|
644
|
-
var textEl = card.querySelector('.review-card-text');
|
|
645
|
-
var editSection = card.querySelector('.review-card-edit');
|
|
646
|
-
if (editSection && editSection.style.display === 'none') {
|
|
647
|
-
if (textEl) textEl.innerHTML = escapeHtml(rebuilt.text);
|
|
648
|
-
syncEditedCard(card, project, rebuilt.line, rebuilt.text);
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
if (attemptsLeft > 0) {
|
|
652
|
-
setTimeout(function() {
|
|
653
|
-
maybeSyncEditedCard(card, project, line, newText, attemptsLeft - 1);
|
|
654
|
-
}, 150);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
document.addEventListener('submit', function(event) {
|
|
659
|
-
var form = event.target;
|
|
660
|
-
if (!form || typeof form.getAttribute !== 'function' || typeof form.querySelector !== 'function') return;
|
|
661
|
-
if (!form.closest || !form.closest('.review-card-edit')) return;
|
|
662
|
-
var project = form.getAttribute('data-project') || '';
|
|
663
|
-
var line = form.getAttribute('data-line') || '';
|
|
664
|
-
var textarea = form.querySelector('textarea[name="new_text"]');
|
|
665
|
-
var newText = textarea ? textarea.value : '';
|
|
666
|
-
var card = form.closest('.review-card');
|
|
667
|
-
setTimeout(function() {
|
|
668
|
-
maybeSyncEditedCard(card, project, line, newText, 20);
|
|
669
|
-
}, 0);
|
|
670
|
-
}, true);
|
|
671
|
-
})();`;
|
|
582
|
+
return "";
|
|
672
583
|
}
|
|
673
584
|
export function renderTasksAndSettingsScript(authToken) {
|
|
674
585
|
return `(function() {
|
|
@@ -1342,9 +1253,6 @@ export function renderEventWiringScript() {
|
|
|
1342
1253
|
var highlightBtn = document.getElementById('highlight-only-btn');
|
|
1343
1254
|
if (highlightBtn) highlightBtn.addEventListener('click', function() { toggleHighlightOnly(this); });
|
|
1344
1255
|
|
|
1345
|
-
var selectAllCheckbox = document.getElementById('review-select-all-checkbox');
|
|
1346
|
-
if (selectAllCheckbox) selectAllCheckbox.addEventListener('change', function() { toggleSelectAll(this.checked); });
|
|
1347
|
-
|
|
1348
1256
|
// --- Graph controls ---
|
|
1349
1257
|
var graphZoomIn = document.getElementById('graph-zoom-in');
|
|
1350
1258
|
if (graphZoomIn) graphZoomIn.addEventListener('click', function() { graphZoom(1.2); });
|
|
@@ -1363,16 +1271,6 @@ export function renderEventWiringScript() {
|
|
|
1363
1271
|
var sessionsFilterProject = document.getElementById('sessions-filter-project');
|
|
1364
1272
|
if (sessionsFilterProject) sessionsFilterProject.addEventListener('change', function() { loadSessions(); });
|
|
1365
1273
|
|
|
1366
|
-
// --- Batch bar ---
|
|
1367
|
-
var batchApproveBtn = document.getElementById('batch-approve-btn');
|
|
1368
|
-
if (batchApproveBtn) batchApproveBtn.addEventListener('click', function() { batchAction('approve'); });
|
|
1369
|
-
var batchRejectBtn = document.getElementById('batch-reject-btn');
|
|
1370
|
-
if (batchRejectBtn) batchRejectBtn.addEventListener('click', function() { batchAction('reject'); });
|
|
1371
|
-
var batchTagSelect = document.getElementById('batch-tag-select');
|
|
1372
|
-
if (batchTagSelect) batchTagSelect.addEventListener('change', function() { if(this.value){batchActionByTag(this.value,'approve');this.value='';} });
|
|
1373
|
-
var batchCancelBtn = document.getElementById('batch-cancel-btn');
|
|
1374
|
-
if (batchCancelBtn) batchCancelBtn.addEventListener('click', function() { clearBatchSelection(); });
|
|
1375
|
-
|
|
1376
1274
|
// --- Command palette ---
|
|
1377
1275
|
var cmdpal = document.getElementById('cmdpal');
|
|
1378
1276
|
if (cmdpal) cmdpal.addEventListener('click', function(e) { closeCmdPal(e); });
|
|
@@ -5,7 +5,7 @@ import * as fs from "fs";
|
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import * as querystring from "querystring";
|
|
7
7
|
import { spawn, execFileSync } from "child_process";
|
|
8
|
-
import {
|
|
8
|
+
import { computePhrenLiveStateToken, getProjectDirs, } from "./shared.js";
|
|
9
9
|
import { editFinding, readReviewQueue, removeFinding, readFindings, addFinding as addFindingStore, readTasksAcrossProjects, addTask as addTaskStore, completeTask as completeTaskStore, removeTask as removeTaskStore, TASKS_FILENAME, } from "./data-access.js";
|
|
10
10
|
import { isValidProjectName, errorMessage } from "./utils.js";
|
|
11
11
|
import { readInstallPreferences, writeInstallPreferences, writeGovernanceInstallPreferences } from "./init-preferences.js";
|
|
@@ -251,40 +251,6 @@ function readProjectQueue(phrenPath, profile) {
|
|
|
251
251
|
}
|
|
252
252
|
return items;
|
|
253
253
|
}
|
|
254
|
-
function runQueueAction(_phrenPath, pathname, _project, _line, _newText) {
|
|
255
|
-
if (pathname === "/api/approve" || pathname === "/approve")
|
|
256
|
-
return { ok: false, error: "Queue approval has been removed. Use the review queue as a read-only reference." };
|
|
257
|
-
if (pathname === "/api/reject" || pathname === "/reject")
|
|
258
|
-
return { ok: false, error: "Queue rejection has been removed. Use the review queue as a read-only reference." };
|
|
259
|
-
if (pathname === "/api/edit" || pathname === "/edit")
|
|
260
|
-
return { ok: false, error: "Queue editing has been removed. Use the review queue as a read-only reference." };
|
|
261
|
-
return { ok: false, error: "unknown action" };
|
|
262
|
-
}
|
|
263
|
-
function handleLegacyQueueActionResult(res, result) {
|
|
264
|
-
if (result.ok) {
|
|
265
|
-
res.writeHead(302, { location: "/" });
|
|
266
|
-
res.end();
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
const code = result.code;
|
|
270
|
-
if (code === PhrenError.PERMISSION_DENIED || result.error.includes("requires maintainer/admin role")) {
|
|
271
|
-
res.writeHead(403, { "content-type": "text/plain; charset=utf-8" });
|
|
272
|
-
res.end(result.error);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (code === PhrenError.NOT_FOUND) {
|
|
276
|
-
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
277
|
-
res.end(result.error);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (code === PhrenError.INVALID_PROJECT_NAME || code === PhrenError.EMPTY_INPUT) {
|
|
281
|
-
res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
282
|
-
res.end(result.error);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
res.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
|
|
286
|
-
res.end(result.error);
|
|
287
|
-
}
|
|
288
254
|
function parseTopicsPayload(raw) {
|
|
289
255
|
try {
|
|
290
256
|
const parsed = JSON.parse(raw);
|
|
@@ -1024,53 +990,6 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
1024
990
|
res.end(JSON.stringify({ ok: true, token }));
|
|
1025
991
|
return;
|
|
1026
992
|
}
|
|
1027
|
-
if (req.method === "POST" && ["/api/approve", "/api/reject", "/api/edit"].includes(pathname)) {
|
|
1028
|
-
void readFormBody(req, res).then((parsed) => {
|
|
1029
|
-
if (!parsed)
|
|
1030
|
-
return;
|
|
1031
|
-
if (!requirePostAuth(req, res, url, parsed, authToken, true))
|
|
1032
|
-
return;
|
|
1033
|
-
if (!requireCsrf(res, parsed, csrfTokens, true))
|
|
1034
|
-
return;
|
|
1035
|
-
const project = String(parsed.project || "");
|
|
1036
|
-
const line = String(parsed.line || "");
|
|
1037
|
-
const newText = String(parsed.new_text || "");
|
|
1038
|
-
if (!project || !line || !isValidProjectName(project)) {
|
|
1039
|
-
res.writeHead(400, { "content-type": "application/json" });
|
|
1040
|
-
res.end(JSON.stringify({ ok: false, error: "Missing or invalid project/line" }));
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
const result = runQueueAction(phrenPath, pathname, project, line, newText);
|
|
1044
|
-
res.writeHead(200, { "content-type": "application/json" });
|
|
1045
|
-
res.end(JSON.stringify({ ok: result.ok, error: result.ok ? undefined : result.error }));
|
|
1046
|
-
});
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
if (req.method === "POST" && ["/approve", "/reject", "/edit"].includes(pathname)) {
|
|
1050
|
-
void readFormBody(req, res).then((parsed) => {
|
|
1051
|
-
if (!parsed)
|
|
1052
|
-
return;
|
|
1053
|
-
if (!requirePostAuth(req, res, url, parsed, authToken))
|
|
1054
|
-
return;
|
|
1055
|
-
if (!requireCsrf(res, parsed, csrfTokens))
|
|
1056
|
-
return;
|
|
1057
|
-
const project = String(parsed.project || "");
|
|
1058
|
-
const line = String(parsed.line || "");
|
|
1059
|
-
const newText = String(parsed.new_text || "");
|
|
1060
|
-
if (!project || !line) {
|
|
1061
|
-
res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
1062
|
-
res.end("Missing project/line");
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
|
-
if (!isValidProjectName(project)) {
|
|
1066
|
-
res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
1067
|
-
res.end("Invalid project name");
|
|
1068
|
-
return;
|
|
1069
|
-
}
|
|
1070
|
-
handleLegacyQueueActionResult(res, runQueueAction(phrenPath, pathname, project, line, newText));
|
|
1071
|
-
});
|
|
1072
|
-
return;
|
|
1073
|
-
}
|
|
1074
993
|
// GET /api/findings/:project — list findings for a project
|
|
1075
994
|
if (req.method === "GET" && pathname.startsWith("/api/findings/")) {
|
|
1076
995
|
const project = decodeURIComponent(pathname.slice("/api/findings/".length));
|
|
@@ -3,7 +3,7 @@ import { getQualityMultiplier, entryScoreKey, } from "./shared-governance.js";
|
|
|
3
3
|
import { queryDocRows, queryRows, cosineFallback, extractSnippet, getDocSourceKey, getEntityBoostDocs, decodeFiniteNumber, rowToDocWithRowid, } from "./shared-index.js";
|
|
4
4
|
import { filterTrustedFindingsDetailed, } from "./shared-content.js";
|
|
5
5
|
import { parseCitationComment } from "./content-citation.js";
|
|
6
|
-
import { getHighImpactFindings
|
|
6
|
+
import { getHighImpactFindings } from "./finding-impact.js";
|
|
7
7
|
import { buildFtsQueryVariants, buildRelaxedFtsQuery, isFeatureEnabled, STOP_WORDS } from "./utils.js";
|
|
8
8
|
import * as fs from "fs";
|
|
9
9
|
import * as path from "path";
|
|
@@ -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)\/(?!(?:home|usr|var|tmp|etc|opt|api|mnt)\b)[a-z][\w-]*\b/.test(p) || /\bskill\b/.test(p))
|
|
40
40
|
return "skill";
|
|
41
41
|
if (/(bug|error|fix|broken|regression|fail|stack trace)/.test(p))
|
|
42
42
|
return "debug";
|
|
@@ -49,8 +49,6 @@ export function detectTaskIntent(prompt) {
|
|
|
49
49
|
return "general";
|
|
50
50
|
}
|
|
51
51
|
function intentBoost(intent, docType) {
|
|
52
|
-
if (intent === "skill" && docType === "skill")
|
|
53
|
-
return 4;
|
|
54
52
|
if (intent === "debug" && (docType === "findings" || docType === "reference"))
|
|
55
53
|
return 3;
|
|
56
54
|
if (intent === "review" && (docType === "canonical" || docType === "changelog"))
|
|
@@ -347,23 +345,10 @@ export function searchDocuments(db, safeQuery, prompt, keywords, detectedProject
|
|
|
347
345
|
if (ftsDocs.length === 0 && relaxedQuery && relaxedQuery !== safeQuery) {
|
|
348
346
|
runScopedFtsQuery(relaxedQuery);
|
|
349
347
|
}
|
|
350
|
-
// Tier 1.5: Fragment graph expansion
|
|
351
|
-
const fragmentExpansionDocs = [];
|
|
352
|
-
const queryLower = (prompt + " " + keywords).toLowerCase();
|
|
353
|
-
const fragmentBoostDocKeys = getEntityBoostDocs(db, queryLower);
|
|
354
|
-
for (const docKey of fragmentBoostDocKeys) {
|
|
355
|
-
if (ftsSeenKeys.has(docKey))
|
|
356
|
-
continue;
|
|
357
|
-
const rows = queryDocRows(db, "SELECT project, filename, type, content, path FROM docs WHERE path = ? LIMIT 1", [docKey]);
|
|
358
|
-
if (rows?.length) {
|
|
359
|
-
ftsSeenKeys.add(docKey);
|
|
360
|
-
fragmentExpansionDocs.push(rows[0]);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
348
|
// Tier 2: Token-overlap semantic — always run, scored independently
|
|
364
349
|
const semanticDocs = semanticFallbackDocs(db, `${prompt}\n${keywords}`, detectedProject);
|
|
365
350
|
// Merge with Reciprocal Rank Fusion so documents found by both tiers rank highest
|
|
366
|
-
const merged = rrfMerge([ftsDocs,
|
|
351
|
+
const merged = rrfMerge([ftsDocs, semanticDocs]);
|
|
367
352
|
if (merged.length === 0)
|
|
368
353
|
return null;
|
|
369
354
|
return merged.slice(0, 12);
|
|
@@ -400,7 +385,7 @@ export async function searchDocumentsAsync(db, safeQuery, prompt, keywords, dete
|
|
|
400
385
|
}
|
|
401
386
|
catch (err) {
|
|
402
387
|
// Vector search failure is non-fatal — return sync result
|
|
403
|
-
if (process.env.PHREN_DEBUG)
|
|
388
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
404
389
|
process.stderr.write(`[phren] hybridSearch vectorFallback: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
405
390
|
return syncResult;
|
|
406
391
|
}
|
|
@@ -500,7 +485,7 @@ export async function searchKnowledgeRows(db, options) {
|
|
|
500
485
|
}
|
|
501
486
|
}
|
|
502
487
|
catch (err) {
|
|
503
|
-
if (process.env.PHREN_DEBUG) {
|
|
488
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG)) {
|
|
504
489
|
process.stderr.write(`[phren] vectorFallback: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
505
490
|
}
|
|
506
491
|
}
|
|
@@ -515,7 +500,6 @@ export function applyTrustFilter(rows, ttlDays, minConfidence, decay, phrenPath)
|
|
|
515
500
|
const queueItems = [];
|
|
516
501
|
const auditEntries = [];
|
|
517
502
|
const highImpactFindingIds = phrenPath ? getHighImpactFindings(phrenPath, 3) : undefined;
|
|
518
|
-
const impactCounts = phrenPath ? getImpactSurfaceCounts(phrenPath, 1) : undefined;
|
|
519
503
|
const filtered = rows
|
|
520
504
|
.map((doc) => {
|
|
521
505
|
if (!TRUST_FILTERED_TYPES.has(doc.type))
|
|
@@ -526,7 +510,6 @@ export function applyTrustFilter(rows, ttlDays, minConfidence, decay, phrenPath)
|
|
|
526
510
|
decay,
|
|
527
511
|
project: doc.project,
|
|
528
512
|
highImpactFindingIds,
|
|
529
|
-
impactCounts,
|
|
530
513
|
});
|
|
531
514
|
if (trust.issues.length > 0) {
|
|
532
515
|
const stale = trust.issues.filter((i) => i.reason === "stale").map((i) => i.bullet);
|
|
@@ -630,7 +613,7 @@ export function rankResults(rows, intent, gitCtx, detectedProject, phrenPathLoca
|
|
|
630
613
|
const scored = ranked.map((doc) => {
|
|
631
614
|
const globBoost = getProjectGlobBoost(phrenPathLocal, doc.project, cwd, gitCtx?.changedFiles);
|
|
632
615
|
const key = entryScoreKey(doc.project, doc.filename, doc.content);
|
|
633
|
-
const entity = entityBoostPaths.has(doc.path) ? 1.
|
|
616
|
+
const entity = entityBoostPaths.has(doc.path) ? 1.3 : 1;
|
|
634
617
|
const date = getRecentDate(doc);
|
|
635
618
|
const fileRel = fileRelevanceBoost(doc.path, changedFiles);
|
|
636
619
|
const branchMat = branchMatchBoost(doc.content, gitCtx?.branch);
|
|
@@ -645,12 +628,7 @@ export function rankResults(rows, intent, gitCtx, detectedProject, phrenPathLoca
|
|
|
645
628
|
&& queryOverlap < WEAK_CROSS_PROJECT_OVERLAP_MAX
|
|
646
629
|
? WEAK_CROSS_PROJECT_OVERLAP_PENALTY
|
|
647
630
|
: 0;
|
|
648
|
-
// Boost skills whose filename matches a query token (e.g. "swarm" matches swarm.md)
|
|
649
|
-
const skillNameBoost = doc.type === "skill" && queryTokens.length > 0
|
|
650
|
-
? queryTokens.some((t) => doc.filename.replace(/\.md$/i, "").toLowerCase() === t) ? 4 : 0
|
|
651
|
-
: 0;
|
|
652
631
|
const score = Math.round((intentBoost(intent, doc.type) +
|
|
653
|
-
skillNameBoost +
|
|
654
632
|
fileRel +
|
|
655
633
|
branchMat +
|
|
656
634
|
globBoost +
|
|
@@ -754,7 +732,7 @@ export function markStaleCitations(snippet) {
|
|
|
754
732
|
}
|
|
755
733
|
}
|
|
756
734
|
catch (err) {
|
|
757
|
-
if (process.env.PHREN_DEBUG)
|
|
735
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
758
736
|
process.stderr.write(`[phren] applyCitationAnnotations fileRead: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
759
737
|
stale = true;
|
|
760
738
|
}
|
|
@@ -771,23 +749,6 @@ export function markStaleCitations(snippet) {
|
|
|
771
749
|
}
|
|
772
750
|
return result.join("\n");
|
|
773
751
|
}
|
|
774
|
-
function annotateContradictions(snippet) {
|
|
775
|
-
return snippet.split('\n').map(line => {
|
|
776
|
-
const conflictMatch = line.match(/<!-- conflicts_with: "(.*?)" -->/);
|
|
777
|
-
const contradictMatch = line.match(/<!-- phren:contradicts "(.*?)" -->/);
|
|
778
|
-
const statusMatch = line.match(/phren:status "contradicted"/);
|
|
779
|
-
if (conflictMatch) {
|
|
780
|
-
return line.replace(conflictMatch[0], '') + ` [CONTRADICTED — conflicts with: "${conflictMatch[1]}"]`;
|
|
781
|
-
}
|
|
782
|
-
if (contradictMatch) {
|
|
783
|
-
return line.replace(contradictMatch[0], '') + ` [CONTRADICTED — see: "${contradictMatch[1]}"]`;
|
|
784
|
-
}
|
|
785
|
-
if (statusMatch) {
|
|
786
|
-
return line + ' [CONTRADICTED]';
|
|
787
|
-
}
|
|
788
|
-
return line;
|
|
789
|
-
}).join('\n');
|
|
790
|
-
}
|
|
791
752
|
export function selectSnippets(rows, keywords, tokenBudget, lineBudget, charBudget) {
|
|
792
753
|
const selected = [];
|
|
793
754
|
let usedTokens = 36;
|
|
@@ -813,7 +774,6 @@ export function selectSnippets(rows, keywords, tokenBudget, lineBudget, charBudg
|
|
|
813
774
|
if (TRUST_FILTERED_TYPES.has(doc.type)) {
|
|
814
775
|
snippet = markStaleCitations(snippet);
|
|
815
776
|
}
|
|
816
|
-
snippet = annotateContradictions(snippet);
|
|
817
777
|
snippet = dedupSnippetBullets(snippet);
|
|
818
778
|
if (!snippet.trim())
|
|
819
779
|
continue;
|
package/mcp/dist/shell-input.js
CHANGED
|
@@ -274,14 +274,6 @@ export async function executePalette(host, input) {
|
|
|
274
274
|
host.setMessage(" Usage: :find add <text> | :find remove <id|match>");
|
|
275
275
|
return;
|
|
276
276
|
}
|
|
277
|
-
if (command === "mq") {
|
|
278
|
-
const project = host.ensureProjectSelected();
|
|
279
|
-
if (!project)
|
|
280
|
-
return;
|
|
281
|
-
const action = (parts[1] || "").toLowerCase();
|
|
282
|
-
host.setMessage(" Queue approve/reject/edit have been removed. The review queue is now read-only.");
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
277
|
if (command === "machine" && parts[1]?.toLowerCase() === "map") {
|
|
286
278
|
if (parts.length < 4) {
|
|
287
279
|
host.setMessage(" Usage: :machine map <hostname> <profile>");
|
|
@@ -409,7 +401,7 @@ export async function executePalette(host, input) {
|
|
|
409
401
|
if (queueResult.ok) {
|
|
410
402
|
const conflictItems = queueResult.data.filter((q) => q.section === "Conflicts");
|
|
411
403
|
if (conflictItems.length) {
|
|
412
|
-
lines.push(` ${style.yellow(`${conflictItems.length} conflict(s) in Review Queue`)} (:
|
|
404
|
+
lines.push(` ${style.yellow(`${conflictItems.length} conflict(s) in Review Queue`)} (inspect in :review queue)`);
|
|
413
405
|
}
|
|
414
406
|
}
|
|
415
407
|
}
|
|
@@ -473,8 +465,7 @@ function suggestCommand(input) {
|
|
|
473
465
|
const known = [
|
|
474
466
|
"help", "projects", "tasks", "task", "findings", "review queue", "machines", "health",
|
|
475
467
|
"open", "search", "add", "complete", "move", "reprioritize", "pin", "unpin", "context",
|
|
476
|
-
"work next", "tidy", "find add", "find remove", "
|
|
477
|
-
"mq edit", "machine map", "profile add-project", "profile remove-project",
|
|
468
|
+
"work next", "tidy", "find add", "find remove", "machine map", "profile add-project", "profile remove-project",
|
|
478
469
|
"run fix", "relink", "rerun hooks", "update", "govern", "consolidate",
|
|
479
470
|
"undo", "diff", "conflicts", "reset",
|
|
480
471
|
];
|
|
@@ -494,7 +485,7 @@ export function completeInput(line, phrenPath, profile, state) {
|
|
|
494
485
|
":projects", ":tasks", ":task", ":findings", ":review queue", ":machines", ":health",
|
|
495
486
|
":open", ":search", ":add", ":complete", ":move", ":reprioritize", ":pin",
|
|
496
487
|
":unpin", ":context", ":work next", ":tidy", ":find add", ":find remove",
|
|
497
|
-
":
|
|
488
|
+
":machine map",
|
|
498
489
|
":profile add-project", ":profile remove-project",
|
|
499
490
|
":run fix", ":relink", ":rerun hooks", ":update", ":govern", ":consolidate",
|
|
500
491
|
":undo", ":diff", ":conflicts", ":reset", ":help",
|
|
@@ -528,15 +519,6 @@ export function completeInput(line, phrenPath, profile, state) {
|
|
|
528
519
|
...result.data.items.Done,
|
|
529
520
|
].map((item) => `:${cmd} ${item.id}`);
|
|
530
521
|
}
|
|
531
|
-
if (cmd === "mq" && ["approve", "reject", "edit"].includes((parts[1] || "").toLowerCase())) {
|
|
532
|
-
const project = state.project;
|
|
533
|
-
if (!project)
|
|
534
|
-
return [];
|
|
535
|
-
const result = readReviewQueue(phrenPath, project);
|
|
536
|
-
if (!result.ok)
|
|
537
|
-
return [];
|
|
538
|
-
return result.data.map((item) => `:mq ${parts[1].toLowerCase()} ${item.id}`);
|
|
539
|
-
}
|
|
540
522
|
if (cmd === "find" && (parts[1] || "").toLowerCase() === "remove") {
|
|
541
523
|
const project = state.project;
|
|
542
524
|
if (!project)
|
|
@@ -640,7 +622,7 @@ async function activateSelected(host) {
|
|
|
640
622
|
break;
|
|
641
623
|
case "Review Queue":
|
|
642
624
|
if (item.text) {
|
|
643
|
-
host.setMessage(` ${style.dim(item.id ?? "")} ${item.text} ${style.dim("[
|
|
625
|
+
host.setMessage(` ${style.dim(item.id ?? "")} ${item.text} ${style.dim("[ read-only ]")}`);
|
|
644
626
|
}
|
|
645
627
|
break;
|
|
646
628
|
case "Skills":
|
package/mcp/dist/shell-render.js
CHANGED
|
@@ -182,7 +182,7 @@ export function shellHelpText() {
|
|
|
182
182
|
` ${style.bold("Projects")} ${k("↵")} ${d("open project tasks")} ${k("i")} ${d("cycle intro mode")}`,
|
|
183
183
|
` ${style.bold("Tasks")} ${k("a")} ${d("add task")} ${k("d")} ${d("toggle active/queue")} ${k("↵")} ${d("mark complete")}`,
|
|
184
184
|
` ${style.bold("Fragments")} ${k("a")} ${d("tell phren")} ${k("d")} ${d("delete selected")}`,
|
|
185
|
-
` ${style.bold("Review Queue")} ${k("
|
|
185
|
+
` ${style.bold("Review Queue")} ${k("↵")} ${d("inspect selected item")} ${d("(read-only)")}`,
|
|
186
186
|
` ${style.bold("Skills")} ${k("t")} ${d("toggle enabled")} ${k("d")} ${d("remove")}`,
|
|
187
187
|
"",
|
|
188
188
|
hdr("Palette commands (:cmd)"),
|
|
@@ -195,7 +195,7 @@ export function shellHelpText() {
|
|
|
195
195
|
` ${cmd(":pin <id>")} ${cmd(":unpin <id>")} ${cmd(":work next")} ${cmd(":tidy [keep]")}`,
|
|
196
196
|
` ${cmd(":find add <text>")} ${cmd(":find remove <id|match>")}`,
|
|
197
197
|
` ${cmd(":intro always|once-per-version|off")}`,
|
|
198
|
-
` ${cmd(":
|
|
198
|
+
` ${cmd(":review queue")} ${d("inspect review queue (read-only)")}`,
|
|
199
199
|
` ${cmd(":govern")} ${cmd(":consolidate")} ${cmd(":search <query>")}`,
|
|
200
200
|
` ${cmd(":undo")} ${cmd(":diff")} ${cmd(":conflicts")} ${cmd(":reset")}`,
|
|
201
201
|
` ${cmd(":run fix")} ${cmd(":relink")} ${cmd(":rerun hooks")} ${cmd(":update")}`,
|
package/mcp/dist/shell-view.js
CHANGED
|
@@ -57,7 +57,6 @@ function renderBottomBar(state, navMode, inputCtx, inputBuf) {
|
|
|
57
57
|
add: "add task",
|
|
58
58
|
"learn-add": "add finding",
|
|
59
59
|
"skill-add": "new skill name",
|
|
60
|
-
"mq-edit": "edit Review Queue item",
|
|
61
60
|
};
|
|
62
61
|
const label = labels[inputCtx] || inputCtx;
|
|
63
62
|
return `${sep}\n ${style.boldCyan(label + " ›")} ${inputBuf}${style.cyan("█")}`;
|
|
@@ -66,7 +65,7 @@ function renderBottomBar(state, navMode, inputCtx, inputBuf) {
|
|
|
66
65
|
Projects: [`${k("↵")} ${d("open project")}`, `${k("i")} ${d("intro mode")}`],
|
|
67
66
|
Tasks: [`${k("a")} ${d("add")}`, `${k("↵")} ${d("mark done")}`, `${k("d")} ${d("toggle active")}`],
|
|
68
67
|
Findings: [`${k("a")} ${d("add")}`, `${k("d")} ${d("remove")}`],
|
|
69
|
-
"Review Queue": [`${k("
|
|
68
|
+
"Review Queue": [`${k("↵")} ${d("inspect")}`],
|
|
70
69
|
Skills: [`${k("t")} ${d("toggle")}`, `${k("d")} ${d("remove")}`],
|
|
71
70
|
Hooks: [`${k("a")} ${d("enable")}`, `${k("d")} ${d("disable")}`],
|
|
72
71
|
Health: [`${k("↑↓")} ${d("scroll")}`, `${k("esc")} ${d("back")}`],
|
package/mcp/dist/shell.js
CHANGED
|
@@ -162,11 +162,6 @@ export class PhrenShell {
|
|
|
162
162
|
this.setMessage(` Created skill "${name}" — edit ${dest}`);
|
|
163
163
|
break;
|
|
164
164
|
}
|
|
165
|
-
case "mq-edit": {
|
|
166
|
-
this.setMessage(" Queue editing has been removed. The review queue is now read-only.");
|
|
167
|
-
this.inputMqId = "";
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
165
|
}
|
|
171
166
|
}
|
|
172
167
|
// ── Raw key handling ───────────────────────────────────────────────────
|
|
@@ -12,6 +12,7 @@ const CATEGORY_BY_MODULE = {
|
|
|
12
12
|
"mcp-ops": "Operations and review",
|
|
13
13
|
"mcp-skills": "Skills management",
|
|
14
14
|
"mcp-hooks": "Hooks management",
|
|
15
|
+
"mcp-config": "Configuration",
|
|
15
16
|
"mcp-extract": "Extraction",
|
|
16
17
|
};
|
|
17
18
|
const MODULE_ORDER = Object.keys(CATEGORY_BY_MODULE);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phren/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "Knowledge layer for AI agents. Claude remembers you. Phren remembers your work.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"mcp/dist",
|
|
11
|
+
"icon.svg",
|
|
11
12
|
"starter",
|
|
12
13
|
"skills"
|
|
13
14
|
],
|