@phren/cli 0.0.39 → 0.0.40
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/cli/hooks.js
CHANGED
|
@@ -9,7 +9,7 @@ import { mergeConfig, } from "../shared/governance.js";
|
|
|
9
9
|
import { buildIndex, detectProject, } from "../shared/index.js";
|
|
10
10
|
import { isProjectHookEnabled } from "../project-config.js";
|
|
11
11
|
import { checkConsolidationNeeded, } from "../shared/content.js";
|
|
12
|
-
import { buildRobustFtsQuery, extractKeywordEntries, isFeatureEnabled, clampInt, errorMessage,
|
|
12
|
+
import { buildRobustFtsQuery, extractKeywordEntries, isFeatureEnabled, clampInt, errorMessage, } from "../utils.js";
|
|
13
13
|
import { getHooksEnabledPreference } from "../init/init.js";
|
|
14
14
|
import { logger } from "../logger.js";
|
|
15
15
|
import { isToolHookEnabled } from "../hooks.js";
|
|
@@ -37,65 +37,9 @@ import { getGitContext, trackSessionMetrics, } from "./hooks-session.js";
|
|
|
37
37
|
import { approximateTokens } from "../shared/retrieval.js";
|
|
38
38
|
import { resolveRuntimeProfile } from "../runtime-profile.js";
|
|
39
39
|
import { handleTaskPromptLifecycle } from "../task/lifecycle.js";
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
for (const values of Object.values(map)) {
|
|
44
|
-
if (values.includes(term))
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
function termAppearsInText(term, text) {
|
|
50
|
-
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\s+/g, "\\s+");
|
|
51
|
-
const pattern = new RegExp(`\\b${escaped}\\b`, "i");
|
|
52
|
-
return pattern.test(text);
|
|
53
|
-
}
|
|
54
|
-
function autoLearnQuerySynonyms(phrenPath, project, keywordEntries, rows) {
|
|
55
|
-
if (!project)
|
|
56
|
-
return;
|
|
57
|
-
const synonymMap = loadSynonymMap(project, phrenPath);
|
|
58
|
-
const knownTerms = new Set([
|
|
59
|
-
...Object.keys(synonymMap),
|
|
60
|
-
...Object.values(synonymMap).flat(),
|
|
61
|
-
]);
|
|
62
|
-
const queryTerms = [...new Set(keywordEntries
|
|
63
|
-
.map((item) => item.trim().toLowerCase())
|
|
64
|
-
.filter((item) => item.length > 2 && !STOP_WORDS.has(item)))];
|
|
65
|
-
const unknownTerms = queryTerms.filter((term) => !synonymTermKnown(term, synonymMap));
|
|
66
|
-
if (unknownTerms.length === 0)
|
|
67
|
-
return;
|
|
68
|
-
const corpus = rows
|
|
69
|
-
.slice(0, 8)
|
|
70
|
-
.map((row) => row.content.slice(0, 6000))
|
|
71
|
-
.join("\n")
|
|
72
|
-
.toLowerCase();
|
|
73
|
-
if (!corpus.trim())
|
|
74
|
-
return;
|
|
75
|
-
const learned = [];
|
|
76
|
-
for (const unknown of unknownTerms.slice(0, 3)) {
|
|
77
|
-
const related = [...knownTerms]
|
|
78
|
-
.filter((candidate) => candidate.length > 2
|
|
79
|
-
&& candidate !== unknown
|
|
80
|
-
&& !STOP_WORDS.has(candidate)
|
|
81
|
-
&& !queryTerms.includes(candidate)
|
|
82
|
-
&& termAppearsInText(candidate, corpus))
|
|
83
|
-
.slice(0, 4);
|
|
84
|
-
if (related.length === 0)
|
|
85
|
-
continue;
|
|
86
|
-
try {
|
|
87
|
-
learnSynonym(phrenPath, project, unknown, related);
|
|
88
|
-
learned.push({ term: unknown, related });
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
debugLog(`hook-prompt synonym-learn failed for "${unknown}": ${errorMessage(err)}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (learned.length > 0) {
|
|
95
|
-
const details = learned.map((entry) => `${entry.term}->${entry.related.join(",")}`).join("; ");
|
|
96
|
-
debugLog(`hook-prompt learned synonyms project=${project} ${details}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
40
|
+
// Auto-learn from prompts was removed — it learned conversational noise ("bro", "idk", typos)
|
|
41
|
+
// as synonyms for high-frequency terms. Manual `phren config synonyms add` still works.
|
|
42
|
+
// Future: finding-based co-occurrence mining (phren maintain command, not live hook).
|
|
99
43
|
async function readStdin() {
|
|
100
44
|
return new Promise((resolve, reject) => {
|
|
101
45
|
const chunks = [];
|
|
@@ -195,7 +139,6 @@ export async function handleHookPrompt() {
|
|
|
195
139
|
stage.searchMs = Date.now() - tSearch0;
|
|
196
140
|
if (!rows || !rows.length)
|
|
197
141
|
process.exit(0);
|
|
198
|
-
autoLearnQuerySynonyms(getPhrenPath(), detectedProject, keywordEntries, rows);
|
|
199
142
|
const tTrust0 = Date.now();
|
|
200
143
|
const policy = resolvedConfig.retentionPolicy;
|
|
201
144
|
const memoryTtlDays = Number.parseInt(process.env.PHREN_MEMORY_TTL_DAYS || String(policy.ttlDays), 10);
|
package/mcp/dist/entrypoint.js
CHANGED
|
@@ -340,6 +340,7 @@ export async function runTopLevelCommand(argv) {
|
|
|
340
340
|
applyStarterUpdate: initArgs.includes("--apply-starter-update"),
|
|
341
341
|
dryRun: initArgs.includes("--dry-run"),
|
|
342
342
|
yes: initArgs.includes("--yes") || initArgs.includes("-y"),
|
|
343
|
+
express: initArgs.includes("--express"),
|
|
343
344
|
_walkthroughCloneUrl: cloneUrl,
|
|
344
345
|
});
|
|
345
346
|
return finish();
|
|
@@ -114,7 +114,7 @@ function detectRepoRootForStorage(phrenPath) {
|
|
|
114
114
|
return detectProjectDir(process.cwd(), phrenPath);
|
|
115
115
|
}
|
|
116
116
|
// Interactive walkthrough for first-time init
|
|
117
|
-
export async function runWalkthrough(phrenPath) {
|
|
117
|
+
export async function runWalkthrough(phrenPath, options) {
|
|
118
118
|
const prompts = await createWalkthroughPrompts();
|
|
119
119
|
const style = await createWalkthroughStyle();
|
|
120
120
|
const divider = style.header("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
@@ -137,6 +137,42 @@ export async function runWalkthrough(phrenPath) {
|
|
|
137
137
|
printSection("Welcome");
|
|
138
138
|
log("Let's set up persistent memory for your AI agents.");
|
|
139
139
|
log("Every option can be changed later.\n");
|
|
140
|
+
// Express mode: skip the entire walkthrough with recommended defaults
|
|
141
|
+
const useExpress = options?.express === true
|
|
142
|
+
|| (options?.express !== false && await prompts.confirm("Use recommended settings? (global storage, MCP on, hooks on, auto tasks)", true));
|
|
143
|
+
if (useExpress) {
|
|
144
|
+
const expressResult = {
|
|
145
|
+
storageChoice: "global",
|
|
146
|
+
storagePath: path.resolve(homePath(".phren")),
|
|
147
|
+
machine: getMachineName(),
|
|
148
|
+
profile: "personal",
|
|
149
|
+
mcp: "on",
|
|
150
|
+
hooks: "on",
|
|
151
|
+
projectOwnershipDefault: "phren-managed",
|
|
152
|
+
findingsProactivity: "high",
|
|
153
|
+
taskProactivity: "high",
|
|
154
|
+
lowConfidenceThreshold: 0.7,
|
|
155
|
+
riskySections: ["Stale", "Conflicts"],
|
|
156
|
+
taskMode: "auto",
|
|
157
|
+
bootstrapCurrentProject: false,
|
|
158
|
+
ollamaEnabled: false,
|
|
159
|
+
autoCaptureEnabled: false,
|
|
160
|
+
semanticDedupEnabled: false,
|
|
161
|
+
semanticConflictEnabled: false,
|
|
162
|
+
findingSensitivity: "balanced",
|
|
163
|
+
domain: "software",
|
|
164
|
+
};
|
|
165
|
+
printSummary([
|
|
166
|
+
`Storage: global (${expressResult.storagePath})`,
|
|
167
|
+
`Machine: ${expressResult.machine}`,
|
|
168
|
+
"MCP: enabled",
|
|
169
|
+
"Hooks: enabled",
|
|
170
|
+
"Project ownership: phren-managed",
|
|
171
|
+
"Task mode: auto",
|
|
172
|
+
"Domain: software",
|
|
173
|
+
]);
|
|
174
|
+
return expressResult;
|
|
175
|
+
}
|
|
140
176
|
printSection("Storage Location");
|
|
141
177
|
log("Where should phren store data?");
|
|
142
178
|
const storageChoice = await prompts.select("Storage location", [
|
package/mcp/dist/init/init.js
CHANGED
|
@@ -7,7 +7,7 @@ import * as fs from "fs";
|
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import { execFileSync } from "child_process";
|
|
9
9
|
import { getMachineName, persistMachineName } from "../machine-identity.js";
|
|
10
|
-
import { atomicWriteText, debugLog, expandHomePath,
|
|
10
|
+
import { atomicWriteText, debugLog, expandHomePath, writeRootManifest, } from "../shared.js";
|
|
11
11
|
import { isValidProjectName, errorMessage } from "../utils.js";
|
|
12
12
|
import { logger } from "../logger.js";
|
|
13
13
|
export { configureClaude, configureVSCode, configureCursorMcp, configureCopilotMcp, configureCodexMcp, logMcpTargetStatus, resetVSCodeProbeCache, patchJsonFile, } from "./config.js";
|
|
@@ -25,7 +25,7 @@ import { configureMcpTargets, configureHooksIfEnabled, applyOnboardingPreference
|
|
|
25
25
|
import { runWalkthrough, createWalkthroughPrompts, createWalkthroughStyle } from "./init-walkthrough.js";
|
|
26
26
|
import { getMcpEnabledPreference, getHooksEnabledPreference, writeInstallPreferences, readInstallPreferences, } from "./preferences.js";
|
|
27
27
|
import { ensureGovernanceFiles, repairPreexistingInstall, runPostInitVerify, applyStarterTemplateUpdates, listTemplates, applyTemplate, ensureProjectScaffold, ensureLocalGitRepo, bootstrapFromExisting, updateMachinesYaml, detectProjectDir, isProjectTracked, } from "./setup.js";
|
|
28
|
-
import { DEFAULT_PHREN_PATH, STARTER_DIR, VERSION, log
|
|
28
|
+
import { DEFAULT_PHREN_PATH, STARTER_DIR, VERSION, log } from "./shared.js";
|
|
29
29
|
import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, } from "../project-config.js";
|
|
30
30
|
import { getWorkflowPolicy } from "../shared/governance.js";
|
|
31
31
|
import { addProjectToProfile } from "../profile-store.js";
|
|
@@ -140,8 +140,10 @@ export async function runInit(opts = {}) {
|
|
|
140
140
|
}
|
|
141
141
|
let hasExistingInstall = hasInstallMarkers(phrenPath);
|
|
142
142
|
// Interactive walkthrough for first-time installs (skip with --yes or non-TTY)
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
// --express bypasses the TTY check since it skips all interactive prompts
|
|
144
|
+
const isTTY = process.stdin.isTTY && process.stdout.isTTY;
|
|
145
|
+
if (!hasExistingInstall && !dryRun && !opts.yes && (isTTY || opts.express)) {
|
|
146
|
+
const answers = await runWalkthrough(phrenPath, { express: opts.express });
|
|
145
147
|
opts._walkthroughStorageChoice = answers.storageChoice;
|
|
146
148
|
opts._walkthroughStoragePath = answers.storagePath;
|
|
147
149
|
opts._walkthroughStorageRepoRoot = answers.storageRepoRoot;
|
|
@@ -343,20 +345,6 @@ export async function runInit(opts = {}) {
|
|
|
343
345
|
log(` Default project ownership: ${ownershipDefault}`);
|
|
344
346
|
log(` Task mode: ${getWorkflowPolicy(phrenPath).taskMode}`);
|
|
345
347
|
log(` Git repo: ${existingGitRepo.detail}`);
|
|
346
|
-
// Confirmation prompt before writing config
|
|
347
|
-
if (!opts.yes) {
|
|
348
|
-
const settingsPath = hookConfigPath("claude");
|
|
349
|
-
const modifications = [];
|
|
350
|
-
modifications.push(` ${settingsPath} (update MCP server + hooks)`);
|
|
351
|
-
log(`\nWill modify:`);
|
|
352
|
-
for (const mod of modifications)
|
|
353
|
-
log(mod);
|
|
354
|
-
const confirmed = await confirmPrompt("\nProceed?");
|
|
355
|
-
if (!confirmed) {
|
|
356
|
-
log("Aborted.");
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
348
|
// Always reconfigure MCP and hooks (picks up new features on upgrade)
|
|
361
349
|
configureMcpTargets(phrenPath, { mcpEnabled, hooksEnabled }, "Updated");
|
|
362
350
|
configureHooksIfEnabled(phrenPath, hooksEnabled, "Updated");
|
|
@@ -539,17 +527,6 @@ export async function runInit(opts = {}) {
|
|
|
539
527
|
if (repairedAssets.length > 0) {
|
|
540
528
|
log(` Recreated missing generated assets: ${repairedAssets.join(", ")}`);
|
|
541
529
|
}
|
|
542
|
-
// Confirmation prompt before writing agent config
|
|
543
|
-
if (!opts.yes) {
|
|
544
|
-
const settingsPath = hookConfigPath("claude");
|
|
545
|
-
log(`\nWill modify:`);
|
|
546
|
-
log(` ${settingsPath} (add MCP server + hooks)`);
|
|
547
|
-
const confirmed = await confirmPrompt("\nProceed?");
|
|
548
|
-
if (!confirmed) {
|
|
549
|
-
log("Aborted.");
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
530
|
// Configure MCP for all detected AI coding tools and hooks
|
|
554
531
|
configureMcpTargets(phrenPath, { mcpEnabled, hooksEnabled }, "Configured");
|
|
555
532
|
configureHooksIfEnabled(phrenPath, hooksEnabled, "Configured");
|
package/mcp/dist/init/setup.js
CHANGED
|
@@ -891,8 +891,15 @@ export function ensureLocalGitRepo(phrenPath) {
|
|
|
891
891
|
stdio: ["ignore", "pipe", "ignore"],
|
|
892
892
|
timeout: EXEC_TIMEOUT_QUICK_MS,
|
|
893
893
|
}).trim();
|
|
894
|
-
|
|
895
|
-
|
|
894
|
+
// Normalize both paths: resolve symlinks (macOS /var→/private/var) and
|
|
895
|
+
// on Windows resolve 8.3 short names (RUNNER~1→runneradmin) + case-insensitive
|
|
896
|
+
const realpath = process.platform === "win32" ? fs.realpathSync.native : fs.realpathSync;
|
|
897
|
+
let resolvedTopLevel = realpath(path.resolve(topLevel));
|
|
898
|
+
let resolvedPhrenPath = realpath(path.resolve(phrenPath));
|
|
899
|
+
if (process.platform === "win32") {
|
|
900
|
+
resolvedTopLevel = resolvedTopLevel.toLowerCase();
|
|
901
|
+
resolvedPhrenPath = resolvedPhrenPath.toLowerCase();
|
|
902
|
+
}
|
|
896
903
|
if (resolvedTopLevel === resolvedPhrenPath) {
|
|
897
904
|
// phrenPath IS the repo root — it has its own git repo
|
|
898
905
|
return { ok: true, initialized: false, detail: "existing git repo" };
|