@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.
@@ -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, loadSynonymMap, learnSynonym, STOP_WORDS, } from "../utils.js";
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
- function synonymTermKnown(term, map) {
41
- if (Object.prototype.hasOwnProperty.call(map, term))
42
- return true;
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);
@@ -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", [
@@ -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, hookConfigPath, writeRootManifest, } from "../shared.js";
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, confirmPrompt } from "./shared.js";
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
- if (!hasExistingInstall && !dryRun && !opts.yes && process.stdin.isTTY && process.stdout.isTTY) {
144
- const answers = await runWalkthrough(phrenPath);
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");
@@ -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
- const resolvedTopLevel = path.resolve(topLevel);
895
- const resolvedPhrenPath = path.resolve(phrenPath);
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" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phren/cli",
3
- "version": "0.0.39",
3
+ "version": "0.0.40",
4
4
  "description": "Knowledge layer for AI agents. Phren learns and recalls.",
5
5
  "type": "module",
6
6
  "bin": {