@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.
@@ -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>What does Approve do?</dt>
107
- <dd>Keeps the memory and marks it as reviewed. It stays in your project findings.</dd>
108
- <dt>What does Reject do?</dt>
109
- <dd>Permanently removes the memory from your project.</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-approve. You review each item manually.</dd>
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 clear the queue faster?</dt>
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. Approve to keep, reject to discard.</p>
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 `(function() {
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, '&amp;')
613
- .replace(/</g, '&lt;')
614
- .replace(/>/g, '&gt;')
615
- .replace(/"/g, '&quot;');
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 { PhrenError, computePhrenLiveStateToken, getProjectDirs, } from "./shared.js";
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, getImpactSurfaceCounts } from "./finding-impact.js";
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 (/(^|\s)\/[a-z][a-z0-9_-]{1,63}(?=$|\s|[.,:;!?])/.test(p) || /\b(skill|swarm|lineup|slash command)\b/.test(p))
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, fragmentExpansionDocs, semanticDocs]);
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.5 : 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;
@@ -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`)} (:mq approve|reject)`);
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", "mq approve", "mq reject",
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
- ":mq approve", ":mq reject", ":mq edit", ":machine map",
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("[ a approve · d reject ]")}`);
625
+ host.setMessage(` ${style.dim(item.id ?? "")} ${item.text} ${style.dim("[ read-only ]")}`);
644
626
  }
645
627
  break;
646
628
  case "Skills":
@@ -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("a")} ${d("approve")} ${k("r")} ${d("reject")} ${k("e")} ${d("edit")}`,
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(":mq approve|reject|edit <id>")}`,
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")}`,
@@ -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("a")} ${d("keep")}`, `${k("d")} ${d("discard")}`, `${k("e")} ${d("edit")}`],
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.8",
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
  ],