@phren/cli 0.0.28 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mcp/dist/capabilities/cli.js +2 -5
- package/mcp/dist/capabilities/mcp.js +5 -8
- package/mcp/dist/capabilities/types.js +2 -5
- package/mcp/dist/capabilities/vscode.js +2 -5
- package/mcp/dist/capabilities/web-ui.js +2 -5
- package/mcp/dist/{cli-actions.js → cli/actions.js} +22 -21
- package/mcp/dist/{cli.js → cli/cli.js} +13 -13
- package/mcp/dist/{cli-config.js → cli/config.js} +9 -9
- package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
- package/mcp/dist/{cli-govern.js → cli/govern.js} +10 -9
- package/mcp/dist/{cli-graph.js → cli/graph.js} +10 -9
- package/mcp/dist/{cli-hooks-citations.js → cli/hooks-citations.js} +2 -2
- package/mcp/dist/{cli-hooks-context.js → cli/hooks-context.js} +23 -23
- package/mcp/dist/{cli-hooks-globs.js → cli/hooks-globs.js} +4 -4
- package/mcp/dist/{cli-hooks-output.js → cli/hooks-output.js} +9 -10
- package/mcp/dist/{cli-hooks-session.js → cli/hooks-session.js} +42 -57
- package/mcp/dist/{cli-hooks.js → cli/hooks.js} +27 -26
- package/mcp/dist/{cli-namespaces.js → cli/namespaces.js} +25 -24
- package/mcp/dist/{cli-ops.js → cli/ops.js} +9 -9
- package/mcp/dist/{cli-search.js → cli/search.js} +8 -7
- package/mcp/dist/cli-hooks-git.js +243 -0
- package/mcp/dist/cli-hooks-prompt.js +319 -0
- package/mcp/dist/cli-hooks-session-handlers.js +349 -0
- package/mcp/dist/cli-hooks-stop.js +557 -0
- package/mcp/dist/{content-archive.js → content/archive.js} +8 -9
- package/mcp/dist/{content-citation.js → content/citation.js} +5 -5
- package/mcp/dist/{content-dedup.js → content/dedup.js} +9 -12
- package/mcp/dist/{content-learning.js → content/learning.js} +12 -12
- package/mcp/dist/{content-validate.js → content/validate.js} +5 -5
- package/mcp/dist/{core-finding.js → core/finding.js} +4 -4
- package/mcp/dist/{core-project.js → core/project.js} +4 -4
- package/mcp/dist/{core-search.js → core/search.js} +2 -2
- package/mcp/dist/{data-access.js → data/access.js} +131 -13
- package/mcp/dist/{data-tasks.js → data/tasks.js} +7 -5
- package/mcp/dist/embedding.js +9 -14
- package/mcp/dist/entrypoint.js +11 -11
- package/mcp/dist/{finding-context.js → finding/context.js} +2 -2
- package/mcp/dist/{finding-impact.js → finding/impact.js} +3 -3
- package/mcp/dist/{finding-journal.js → finding/journal.js} +4 -4
- package/mcp/dist/{finding-lifecycle.js → finding/lifecycle.js} +4 -4
- package/mcp/dist/{governance-audit.js → governance/audit.js} +2 -2
- package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
- package/mcp/dist/{governance-policy.js → governance/policy.js} +10 -12
- package/mcp/dist/{governance-rbac.js → governance/rbac.js} +3 -3
- package/mcp/dist/{governance-scores.js → governance/scores.js} +8 -10
- package/mcp/dist/hooks.js +39 -31
- package/mcp/dist/index-query.js +4 -1
- package/mcp/dist/index.js +53 -29
- package/mcp/dist/{init-config.js → init/config.js} +6 -6
- package/mcp/dist/{init.js → init/init.js} +28 -29
- package/mcp/dist/{init-preferences.js → init/preferences.js} +3 -3
- package/mcp/dist/{init-setup.js → init/setup.js} +17 -19
- package/mcp/dist/{init-shared.js → init/shared.js} +3 -3
- package/mcp/dist/init-bootstrap.js +68 -0
- package/mcp/dist/init-detect.js +38 -0
- package/mcp/dist/init-dryrun.js +55 -0
- package/mcp/dist/init-env.js +114 -0
- package/mcp/dist/init-fresh.js +239 -0
- package/mcp/dist/init-hooks.js +26 -0
- package/mcp/dist/init-mcp.js +65 -0
- package/mcp/dist/init-migrate.js +51 -0
- package/mcp/dist/init-modes.js +135 -0
- package/mcp/dist/init-npm.js +37 -0
- package/mcp/dist/init-project-local.js +99 -0
- package/mcp/dist/init-semantic.js +48 -0
- package/mcp/dist/init-types.js +1 -0
- package/mcp/dist/init-uninstall.js +482 -0
- package/mcp/dist/init-update.js +96 -0
- package/mcp/dist/init-walkthrough-merge.js +90 -0
- package/mcp/dist/init-walkthrough.js +529 -0
- package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
- package/mcp/dist/{link-context.js → link/context.js} +4 -4
- package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
- package/mcp/dist/{link.js → link/link.js} +26 -31
- package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
- package/mcp/dist/logger.js +11 -3
- package/mcp/dist/phren-art.js +0 -6
- package/mcp/dist/phren-paths.js +30 -12
- package/mcp/dist/proactivity.js +2 -2
- package/mcp/dist/profile-store.js +5 -6
- package/mcp/dist/project-config.js +2 -2
- package/mcp/dist/project-topics.js +1 -1
- package/mcp/dist/query-correlation.js +1 -1
- package/mcp/dist/{session-checkpoints.js → session/checkpoints.js} +3 -3
- package/mcp/dist/{session-utils.js → session/utils.js} +1 -1
- package/mcp/dist/{shared-content.js → shared/content.js} +7 -7
- package/mcp/dist/{shared-data-utils.js → shared/data-utils.js} +3 -3
- package/mcp/dist/{shared-embedding-cache.js → shared/embedding-cache.js} +3 -3
- package/mcp/dist/{shared-fragment-graph.js → shared/fragment-graph.js} +15 -24
- package/mcp/dist/shared/governance.js +4 -0
- package/mcp/dist/{shared-index.js → shared/index.js} +92 -123
- package/mcp/dist/{shared-ollama.js → shared/ollama.js} +2 -2
- package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +16 -21
- package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +17 -20
- package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
- package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
- package/mcp/dist/shared.js +4 -59
- package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
- package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
- package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
- package/mcp/dist/{shell-render.js → shell/render.js} +1 -1
- package/mcp/dist/{shell.js → shell/shell.js} +11 -11
- package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
- package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
- package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
- package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
- package/mcp/dist/{skill-registry.js → skill/registry.js} +4 -4
- package/mcp/dist/{skill-state.js → skill/state.js} +1 -1
- package/mcp/dist/startup-embedding.js +2 -2
- package/mcp/dist/status.js +15 -14
- package/mcp/dist/{tasks-github.js → task/github.js} +2 -2
- package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
- package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +7 -7
- package/mcp/dist/telemetry.js +3 -4
- package/mcp/dist/tool-registry.js +29 -17
- package/mcp/dist/tools/config.js +515 -0
- package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
- package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
- package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
- package/mcp/dist/{mcp-finding.js → tools/finding.js} +97 -124
- package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
- package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
- package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
- package/mcp/dist/{mcp-ops.js → tools/ops.js} +169 -71
- package/mcp/dist/{mcp-search.js → tools/search.js} +19 -23
- package/mcp/dist/{mcp-session.js → tools/session.js} +48 -23
- package/mcp/dist/{mcp-skills.js → tools/skills.js} +33 -35
- package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
- package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
- package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
- package/mcp/dist/{memory-ui-page.js → ui/page.js} +4 -6
- package/mcp/dist/{memory-ui-server.js → ui/server.js} +30 -22
- package/mcp/dist/update.js +2 -2
- package/mcp/dist/utils.js +51 -11
- package/package.json +2 -2
- package/scripts/preuninstall.mjs +31 -0
- package/starter/global/CLAUDE.md +3 -2
- package/mcp/dist/mcp-config.js +0 -551
- package/mcp/dist/shared-governance.js +0 -4
- /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
- /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
- /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
- /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
- /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
- /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
- /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
- /package/mcp/dist/{memory-ui-styles.js → ui/styles.js} +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { WEB_UI_STYLES, renderWebUiScript } from "./
|
|
2
|
-
import { renderGraphScript } from "./
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { renderSharedWebUiHelpers, renderSkillUiEnhancementScript, renderProjectReferenceEnhancementScript, renderTasksAndSettingsScript, renderSearchScript, renderEventWiringScript, renderGraphHostScript, } from "./memory-ui-scripts.js";
|
|
1
|
+
import { WEB_UI_STYLES, renderWebUiScript } from "./assets.js";
|
|
2
|
+
import { renderGraphScript } from "./graph.js";
|
|
3
|
+
import { PROJECT_REFERENCE_UI_STYLES, REVIEW_UI_STYLES, SETTINGS_TAB_UI_STYLES, TASK_UI_STYLES } from "./styles.js";
|
|
4
|
+
import { renderSharedWebUiHelpers, renderSkillUiEnhancementScript, renderProjectReferenceEnhancementScript, renderTasksAndSettingsScript, renderSearchScript, renderEventWiringScript, renderGraphHostScript, } from "./scripts.js";
|
|
6
5
|
function h(s) {
|
|
7
6
|
return s
|
|
8
7
|
.replace(/&/g, "&")
|
|
@@ -11,7 +10,6 @@ function h(s) {
|
|
|
11
10
|
.replace(/"/g, """);
|
|
12
11
|
}
|
|
13
12
|
export function renderWebUiPage(phrenPath, authToken, nonce) {
|
|
14
|
-
const sync = readSyncSnapshot(phrenPath);
|
|
15
13
|
const nonceAttr = nonce ? ` nonce="${h(nonce)}"` : "";
|
|
16
14
|
return `<!doctype html>
|
|
17
15
|
<html lang="en">
|
|
@@ -5,18 +5,19 @@ 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 { computePhrenLiveStateToken, getProjectDirs, } from "
|
|
9
|
-
import { editFinding, readReviewQueue, removeFinding, readFindings, addFinding as addFindingStore, readTasksAcrossProjects, addTask as addTaskStore, completeTask as completeTaskStore, removeTask as removeTaskStore, updateTask as updateTaskStore, TASKS_FILENAME, } from "
|
|
10
|
-
import { isValidProjectName, errorMessage, queueFilePath, safeProjectPath } from "
|
|
11
|
-
import { readInstallPreferences, writeInstallPreferences, writeGovernanceInstallPreferences } from "
|
|
12
|
-
import { buildGraph, collectProjectsForUI, collectSkillsForUI, getHooksData,
|
|
13
|
-
import { CONSOLIDATION_ENTRY_THRESHOLD } from "
|
|
14
|
-
import { ensureTopicReferenceDoc, getProjectTopicsResponse, listProjectReferenceDocs, pinProjectTopicSuggestion, readReferenceContent, reclassifyLegacyTopicDocs, unpinProjectTopicSuggestion, writeProjectTopics, } from "
|
|
15
|
-
import { getWorkflowPolicy, updateWorkflowPolicy, mergeConfig, getRetentionPolicy, getProjectConfigOverrides, VALID_TASK_MODES } from "
|
|
16
|
-
import { readProjectConfig, updateProjectConfigOverrides } from "
|
|
17
|
-
import { findSkill } from "
|
|
18
|
-
import { setSkillEnabledAndSync } from "
|
|
19
|
-
import { repairPreexistingInstall } from "
|
|
8
|
+
import { computePhrenLiveStateToken, getProjectDirs, } from "../shared.js";
|
|
9
|
+
import { editFinding, readReviewQueue, removeFinding, readFindings, addFinding as addFindingStore, readTasksAcrossProjects, addTask as addTaskStore, completeTask as completeTaskStore, removeTask as removeTaskStore, updateTask as updateTaskStore, TASKS_FILENAME, } from "../data/access.js";
|
|
10
|
+
import { isValidProjectName, errorMessage, queueFilePath, safeProjectPath } from "../utils.js";
|
|
11
|
+
import { readInstallPreferences, writeInstallPreferences, writeGovernanceInstallPreferences } from "../init/preferences.js";
|
|
12
|
+
import { buildGraph, collectProjectsForUI, collectSkillsForUI, getHooksData, isAllowedSkillPath, readSyncSnapshot, recentAccepted, recentUsage, } from "./data.js";
|
|
13
|
+
import { CONSOLIDATION_ENTRY_THRESHOLD } from "../content/validate.js";
|
|
14
|
+
import { ensureTopicReferenceDoc, getProjectTopicsResponse, listProjectReferenceDocs, pinProjectTopicSuggestion, readReferenceContent, reclassifyLegacyTopicDocs, unpinProjectTopicSuggestion, writeProjectTopics, } from "../project-topics.js";
|
|
15
|
+
import { getWorkflowPolicy, updateWorkflowPolicy, mergeConfig, getRetentionPolicy, getProjectConfigOverrides, VALID_TASK_MODES } from "../governance/policy.js";
|
|
16
|
+
import { readProjectConfig, updateProjectConfigOverrides } from "../project-config.js";
|
|
17
|
+
import { findSkill } from "../skill/registry.js";
|
|
18
|
+
import { setSkillEnabledAndSync } from "../skill/files.js";
|
|
19
|
+
import { repairPreexistingInstall } from "../init/setup.js";
|
|
20
|
+
import { logger } from "../logger.js";
|
|
20
21
|
const CSRF_TOKEN_TTL_MS = 15 * 60 * 1000;
|
|
21
22
|
const MAX_FORM_BODY_BYTES = 1_048_576;
|
|
22
23
|
const WEB_UI_READY_ATTEMPTS = 12;
|
|
@@ -102,7 +103,7 @@ async function bindWebUiPort(server, requestedPort, allowPortFallback) {
|
|
|
102
103
|
const candidate = candidates[i];
|
|
103
104
|
try {
|
|
104
105
|
if (candidate !== requestedPort) {
|
|
105
|
-
|
|
106
|
+
logger.info("web-ui", `port ${candidate - 1} is busy, retrying on ${candidate}`);
|
|
106
107
|
}
|
|
107
108
|
return await listenOnLoopback(server, candidate);
|
|
108
109
|
}
|
|
@@ -292,8 +293,7 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
292
293
|
repairPreexistingInstall(phrenPath);
|
|
293
294
|
}
|
|
294
295
|
catch (err) {
|
|
295
|
-
|
|
296
|
-
process.stderr.write(`[phren] web-ui repair: ${errorMessage(err)}\n`);
|
|
296
|
+
logger.debug("web-ui", `web-ui repair: ${errorMessage(err)}`);
|
|
297
297
|
}
|
|
298
298
|
const authToken = opts?.authToken;
|
|
299
299
|
const csrfTokens = opts?.csrfTokens;
|
|
@@ -351,7 +351,15 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
353
|
runGit(["add", "--", "*.md", "*.json", "*.yaml", "*.yml", "*.jsonl", "*.txt"]);
|
|
354
|
-
|
|
354
|
+
// Use --only to commit exactly the files we just staged,
|
|
355
|
+
// avoiding committing unrelated previously-staged changes.
|
|
356
|
+
const stagedFiles = runGit(["diff", "--cached", "--name-only"]);
|
|
357
|
+
if (!stagedFiles) {
|
|
358
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
359
|
+
res.end(JSON.stringify({ ok: true, message: "Nothing to sync — no matching files to commit." }));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
runGit(["commit", "-m", message, "--only", "--", ...stagedFiles.split("\n").filter(Boolean)]);
|
|
355
363
|
let pushed = false;
|
|
356
364
|
try {
|
|
357
365
|
const remotes = runGit(["remote"]);
|
|
@@ -565,7 +573,7 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
565
573
|
if (req.method === "GET" && pathname.startsWith("/api/skill-content")) {
|
|
566
574
|
const qs = url.includes("?") ? querystring.parse(url.slice(url.indexOf("?") + 1)) : {};
|
|
567
575
|
const filePath = String(qs.path || "");
|
|
568
|
-
if (!filePath || !
|
|
576
|
+
if (!filePath || !isAllowedSkillPath(filePath, phrenPath)) {
|
|
569
577
|
res.writeHead(400, { "content-type": "application/json" });
|
|
570
578
|
res.end(JSON.stringify({ ok: false, error: "Invalid path" }));
|
|
571
579
|
return;
|
|
@@ -594,7 +602,7 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
594
602
|
return;
|
|
595
603
|
const filePath = String(parsed.path || "");
|
|
596
604
|
const content = String(parsed.content || "");
|
|
597
|
-
if (!filePath || !
|
|
605
|
+
if (!filePath || !isAllowedSkillPath(filePath, phrenPath)) {
|
|
598
606
|
res.writeHead(200, { "content-type": "application/json" });
|
|
599
607
|
res.end(JSON.stringify({ ok: false, error: "Invalid path" }));
|
|
600
608
|
return;
|
|
@@ -800,7 +808,7 @@ export function createWebUiHttpServer(phrenPath, renderPage, profile, opts) {
|
|
|
800
808
|
return;
|
|
801
809
|
}
|
|
802
810
|
try {
|
|
803
|
-
const { runSearch } = await import("
|
|
811
|
+
const { runSearch } = await import("../cli/search.js");
|
|
804
812
|
const result = await runSearch({ query, limit: Math.min(searchLimit, 50), project: searchProject, type: searchType }, phrenPath, profile || "");
|
|
805
813
|
// Build file date map from source headers like [project/filename]
|
|
806
814
|
const fileDates = {};
|
|
@@ -1379,7 +1387,7 @@ export async function startWebUiServer(phrenPath, port, renderPage, profile, opt
|
|
|
1379
1387
|
process.stdout.write(`phren web-ui running at ${publicUrl}\n`);
|
|
1380
1388
|
process.stderr.write(`open: ${reviewUrl}\n`);
|
|
1381
1389
|
if (!ready) {
|
|
1382
|
-
|
|
1390
|
+
logger.warn("web-ui", "health check did not confirm readiness before launch");
|
|
1383
1391
|
}
|
|
1384
1392
|
const shouldAutoOpen = opts.autoOpen ?? Boolean(process.stdout.isTTY);
|
|
1385
1393
|
if (shouldAutoOpen && ready) {
|
|
@@ -1390,12 +1398,12 @@ export async function startWebUiServer(phrenPath, port, renderPage, profile, opt
|
|
|
1390
1398
|
await launchWebUiBrowser(reviewUrl);
|
|
1391
1399
|
}
|
|
1392
1400
|
catch (err) {
|
|
1393
|
-
|
|
1401
|
+
logger.warn("web-ui", `browser launch failed: ${errorMessage(err)}`);
|
|
1394
1402
|
process.stdout.write(`secure session URL: ${reviewUrl}\n`);
|
|
1395
1403
|
}
|
|
1396
1404
|
}
|
|
1397
1405
|
else if (shouldAutoOpen && !ready) {
|
|
1398
|
-
|
|
1406
|
+
logger.warn("web-ui", "skipped auto-open because readiness check failed; use the secure URL below");
|
|
1399
1407
|
process.stdout.write(`secure session URL: ${reviewUrl}\n`);
|
|
1400
1408
|
}
|
|
1401
1409
|
else {
|
package/mcp/dist/update.js
CHANGED
|
@@ -4,6 +4,7 @@ import { execFileSync } from "child_process";
|
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { errorMessage } from "./utils.js";
|
|
6
6
|
import { PACKAGE_NAME, PACKAGE_SPEC } from "./package-metadata.js";
|
|
7
|
+
import { logger } from "./logger.js";
|
|
7
8
|
function shellCommand(bin) {
|
|
8
9
|
return process.platform === "win32" ? `${bin}.cmd` : bin;
|
|
9
10
|
}
|
|
@@ -63,8 +64,7 @@ export async function runPhrenUpdate(opts = {}) {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
catch (err) {
|
|
66
|
-
|
|
67
|
-
process.stderr.write(`[phren] runPhrenUpdate gitStatus: ${errorMessage(err)}\n`);
|
|
67
|
+
logger.debug("runPhrenUpdate gitStatus", errorMessage(err));
|
|
68
68
|
}
|
|
69
69
|
const pull = run("git", ["pull", "--rebase", "--autostash"], root);
|
|
70
70
|
run(shellCommand("npm"), ["install"], root);
|
package/mcp/dist/utils.js
CHANGED
|
@@ -5,6 +5,28 @@ import * as yaml from "js-yaml";
|
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { findPhrenPath } from "./phren-paths.js";
|
|
7
7
|
import { bootstrapPhrenDotEnv } from "./phren-dotenv.js";
|
|
8
|
+
// Lazy import of logDebug to break circular dependency:
|
|
9
|
+
// utils.ts -> phren-paths.ts -> logger.ts -> phren-paths.ts -> utils.ts
|
|
10
|
+
let _logDebug;
|
|
11
|
+
async function ensureLogDebug() {
|
|
12
|
+
if (!_logDebug) {
|
|
13
|
+
try {
|
|
14
|
+
const mod = await import("./logger.js");
|
|
15
|
+
_logDebug = mod.logger.debug;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
_logDebug = () => { };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function getLogDebug() {
|
|
23
|
+
if (!_logDebug) {
|
|
24
|
+
// Kick off the async import for future calls; fall back to no-op for this call
|
|
25
|
+
void ensureLogDebug();
|
|
26
|
+
return () => { };
|
|
27
|
+
}
|
|
28
|
+
return _logDebug;
|
|
29
|
+
}
|
|
8
30
|
const _moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
9
31
|
function loadSynonymsJson(fileName) {
|
|
10
32
|
const filePath = path.join(_moduleDir, fileName);
|
|
@@ -12,8 +34,7 @@ function loadSynonymsJson(fileName) {
|
|
|
12
34
|
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
13
35
|
}
|
|
14
36
|
catch (err) {
|
|
15
|
-
|
|
16
|
-
process.stderr.write(`[phren] ${fileName} load failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
37
|
+
getLogDebug()("loadSynonymsJson", `${fileName} load failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
17
38
|
return {};
|
|
18
39
|
}
|
|
19
40
|
}
|
|
@@ -244,10 +265,7 @@ export function sanitizeFts5Query(raw) {
|
|
|
244
265
|
let q = raw.replace(/[^a-zA-Z0-9 \-"*]/g, " ");
|
|
245
266
|
q = q.replace(/\s+/g, " ");
|
|
246
267
|
q = q.trim();
|
|
247
|
-
// Q83:
|
|
248
|
-
// (e.g. "foo*"). A bare trailing asterisk (or lone "*") produces invalid
|
|
249
|
-
// FTS5 syntax. Strip any asterisk that is not immediately preceded by a
|
|
250
|
-
// word character so the query remains valid.
|
|
268
|
+
// Q83: see docs/decisions/Q83-fts5-asterisk-validation.md
|
|
251
269
|
q = q.replace(/(?<!\w)\*/g, "");
|
|
252
270
|
// Also strip a trailing asterisk that is preceded only by whitespace at word
|
|
253
271
|
// end of the whole query (handles "foo *" → "foo").
|
|
@@ -276,8 +294,7 @@ function parseSynonymsYaml(filePath) {
|
|
|
276
294
|
return loaded;
|
|
277
295
|
}
|
|
278
296
|
catch (err) {
|
|
279
|
-
|
|
280
|
-
process.stderr.write(`[phren] synonyms.yaml parse failed (${filePath}): ${err instanceof Error ? err.message : String(err)}\n`);
|
|
297
|
+
getLogDebug()("parseSynonymsYaml", `synonyms.yaml parse failed (${filePath}): ${err instanceof Error ? err.message : String(err)}`);
|
|
281
298
|
return {};
|
|
282
299
|
}
|
|
283
300
|
}
|
|
@@ -315,8 +332,7 @@ function parseLearnedSynonymsJson(filePath) {
|
|
|
315
332
|
return loaded;
|
|
316
333
|
}
|
|
317
334
|
catch (err) {
|
|
318
|
-
|
|
319
|
-
process.stderr.write(`[phren] learned-synonyms parse failed (${filePath}): ${err instanceof Error ? err.message : String(err)}\n`);
|
|
335
|
+
getLogDebug()("parseLearnedSynonymsJson", `learned-synonyms parse failed (${filePath}): ${err instanceof Error ? err.message : String(err)}`);
|
|
320
336
|
return {};
|
|
321
337
|
}
|
|
322
338
|
}
|
|
@@ -512,8 +528,21 @@ export function buildRobustFtsQuery(raw, project, phrenPath) {
|
|
|
512
528
|
// nothing; it trades precision for recall while staying in the FTS index.
|
|
513
529
|
export function buildRelaxedFtsQuery(raw, project, phrenPath) {
|
|
514
530
|
const clauses = buildFtsClauses(raw, project, phrenPath);
|
|
515
|
-
if (clauses.length
|
|
531
|
+
if (clauses.length === 0)
|
|
516
532
|
return "";
|
|
533
|
+
// Short queries (1-2 terms): OR the clauses together with prefix expansion
|
|
534
|
+
if (clauses.length === 1) {
|
|
535
|
+
const term = clauses[0];
|
|
536
|
+
// Add prefix wildcard for unquoted-style terms to broaden recall
|
|
537
|
+
const inner = term.replace(/^"(.*)"$/, "$1");
|
|
538
|
+
if (inner.length >= 3) {
|
|
539
|
+
return `(${term} OR "${inner}"*)`;
|
|
540
|
+
}
|
|
541
|
+
return term;
|
|
542
|
+
}
|
|
543
|
+
if (clauses.length === 2) {
|
|
544
|
+
return `(${clauses[0]} OR ${clauses[1]})`;
|
|
545
|
+
}
|
|
517
546
|
const salientClauses = clauses
|
|
518
547
|
.map((clause, index) => ({ clause, index, score: clauseSignalScore(clause) }))
|
|
519
548
|
.sort((a, b) => {
|
|
@@ -539,5 +568,16 @@ export function buildFtsQueryVariants(raw, project, phrenPath) {
|
|
|
539
568
|
buildRobustFtsQuery(raw, project, phrenPath),
|
|
540
569
|
buildRelaxedFtsQuery(raw, project, phrenPath),
|
|
541
570
|
].filter(Boolean);
|
|
571
|
+
// For short queries, add a prefix-expanded variant to catch partial matches
|
|
572
|
+
const clauses = buildFtsClauses(raw, project, phrenPath);
|
|
573
|
+
if (clauses.length <= 2) {
|
|
574
|
+
const prefixParts = clauses
|
|
575
|
+
.map(c => c.replace(/^"(.*)"$/, "$1"))
|
|
576
|
+
.filter(t => t.length >= 3)
|
|
577
|
+
.map(t => `"${t}"*`);
|
|
578
|
+
if (prefixParts.length > 0) {
|
|
579
|
+
variants.push(prefixParts.join(" OR "));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
542
582
|
return [...new Set(variants)];
|
|
543
583
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phren/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"description": "Knowledge layer for AI agents. Phren learns and recalls.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
18
18
|
"chalk": "^5.6.2",
|
|
19
|
-
"esbuild": "^0.27.4",
|
|
20
19
|
"glob": "^13.0.6",
|
|
21
20
|
"graphology": "^0.26.0",
|
|
22
21
|
"graphology-layout-forceatlas2": "^0.10.1",
|
|
@@ -27,6 +26,7 @@
|
|
|
27
26
|
"zod": "^4.3.6"
|
|
28
27
|
},
|
|
29
28
|
"devDependencies": {
|
|
29
|
+
"esbuild": "^0.27.4",
|
|
30
30
|
"@playwright/test": "^1.58.2",
|
|
31
31
|
"@types/js-yaml": "^4.0.9",
|
|
32
32
|
"@types/node": "^25.5.0",
|
package/scripts/preuninstall.mjs
CHANGED
|
@@ -105,4 +105,35 @@ if (fs.existsSync(copilotHooks)) {
|
|
|
105
105
|
try { fs.unlinkSync(copilotHooks); } catch { /* best-effort */ }
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// ── Codex MCP config (TOML + JSON) ──
|
|
109
|
+
const codexToml = homePath(".codex", "config.toml");
|
|
110
|
+
if (fs.existsSync(codexToml)) {
|
|
111
|
+
try {
|
|
112
|
+
const content = fs.readFileSync(codexToml, "utf8");
|
|
113
|
+
// Remove [mcp_servers.phren] section
|
|
114
|
+
const cleaned = content.replace(/\[mcp_servers\.phren\][^\[]*/, "").trim();
|
|
115
|
+
if (cleaned !== content.trim()) fs.writeFileSync(codexToml, cleaned + "\n");
|
|
116
|
+
} catch { /* best-effort */ }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Session wrapper scripts ──
|
|
120
|
+
const localBinDir = homePath(".local", "bin");
|
|
121
|
+
for (const tool of ["copilot", "cursor", "codex"]) {
|
|
122
|
+
const wrapperPath = path.join(localBinDir, tool);
|
|
123
|
+
try {
|
|
124
|
+
if (fs.existsSync(wrapperPath)) {
|
|
125
|
+
const content = fs.readFileSync(wrapperPath, "utf8");
|
|
126
|
+
if (content.includes("PHREN_PATH") && content.includes("phren")) {
|
|
127
|
+
fs.unlinkSync(wrapperPath);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch { /* best-effort */ }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Machine context file ──
|
|
134
|
+
const contextFile = homePath(".phren-context.md");
|
|
135
|
+
if (fs.existsSync(contextFile)) {
|
|
136
|
+
try { fs.unlinkSync(contextFile); } catch { /* best-effort */ }
|
|
137
|
+
}
|
|
138
|
+
|
|
108
139
|
console.log("phren: cleaned up hooks and MCP config from agent settings.");
|
package/starter/global/CLAUDE.md
CHANGED
|
@@ -19,9 +19,10 @@ These skills are available as a full set via phren, or individually from the Cla
|
|
|
19
19
|
| Skill | What it does |
|
|
20
20
|
|-------|-------------|
|
|
21
21
|
| `/phren-sync` | Pull phren to a new machine or push config changes back to the repo |
|
|
22
|
-
| `/phren-init` | Scaffold a new project with summary, CLAUDE.md,
|
|
22
|
+
| `/phren-init` | Scaffold a new project with summary, CLAUDE.md, tasks |
|
|
23
23
|
| `/phren-discover` | Research what's missing in a project and surface gaps and opportunities |
|
|
24
24
|
| `/phren-consolidate` | Find patterns across all project FINDINGS.md files |
|
|
25
|
+
| `/phren-profiles` | Manages machine-to-profile mapping |
|
|
25
26
|
|
|
26
27
|
### Your own skills
|
|
27
28
|
|
|
@@ -48,7 +49,7 @@ In shared mode, skills and project config live in `~/.phren` (or wherever `PHREN
|
|
|
48
49
|
If you're using `phren init --mode project-local`, the root is `<repo>/.phren` instead. Project-local mode does not use profiles, machine mappings, or global hooks.
|
|
49
50
|
|
|
50
51
|
- `~/.phren/global/`: skills and config that apply everywhere
|
|
51
|
-
- `~/.phren/<project>/`: per-project CLAUDE.md, skills,
|
|
52
|
+
- `~/.phren/<project>/`: per-project CLAUDE.md, skills, tasks, findings
|
|
52
53
|
- `~/.phren/profiles/`: YAML files mapping project sets to machine roles
|
|
53
54
|
- `~/.phren/machines.yaml`: maps machine hostnames to profiles
|
|
54
55
|
|