@phren/cli 0.0.28 → 0.0.33

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.
Files changed (153) hide show
  1. package/mcp/dist/capabilities/cli.js +2 -5
  2. package/mcp/dist/capabilities/mcp.js +5 -8
  3. package/mcp/dist/capabilities/types.js +2 -5
  4. package/mcp/dist/capabilities/vscode.js +2 -5
  5. package/mcp/dist/capabilities/web-ui.js +2 -5
  6. package/mcp/dist/{cli-actions.js → cli/actions.js} +25 -21
  7. package/mcp/dist/{cli.js → cli/cli.js} +13 -13
  8. package/mcp/dist/{cli-config.js → cli/config.js} +12 -12
  9. package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
  10. package/mcp/dist/{cli-govern.js → cli/govern.js} +28 -17
  11. package/mcp/dist/{cli-graph.js → cli/graph.js} +10 -9
  12. package/mcp/dist/{cli-hooks-citations.js → cli/hooks-citations.js} +2 -2
  13. package/mcp/dist/{cli-hooks-context.js → cli/hooks-context.js} +23 -23
  14. package/mcp/dist/{cli-hooks-globs.js → cli/hooks-globs.js} +4 -4
  15. package/mcp/dist/{cli-hooks-output.js → cli/hooks-output.js} +9 -10
  16. package/mcp/dist/{cli-hooks-session.js → cli/hooks-session.js} +58 -117
  17. package/mcp/dist/{cli-hooks.js → cli/hooks.js} +27 -26
  18. package/mcp/dist/{cli-namespaces.js → cli/namespaces.js} +25 -24
  19. package/mcp/dist/{cli-ops.js → cli/ops.js} +9 -9
  20. package/mcp/dist/{cli-search.js → cli/search.js} +12 -11
  21. package/mcp/dist/cli-hooks-git.js +243 -0
  22. package/mcp/dist/cli-hooks-prompt.js +323 -0
  23. package/mcp/dist/cli-hooks-session-handlers.js +337 -0
  24. package/mcp/dist/cli-hooks-stop.js +519 -0
  25. package/mcp/dist/{content-archive.js → content/archive.js} +16 -29
  26. package/mcp/dist/{content-citation.js → content/citation.js} +5 -5
  27. package/mcp/dist/{content-dedup.js → content/dedup.js} +9 -12
  28. package/mcp/dist/{content-learning.js → content/learning.js} +41 -20
  29. package/mcp/dist/{content-validate.js → content/validate.js} +5 -5
  30. package/mcp/dist/{core-finding.js → core/finding.js} +4 -4
  31. package/mcp/dist/{core-project.js → core/project.js} +4 -4
  32. package/mcp/dist/{core-search.js → core/search.js} +2 -2
  33. package/mcp/dist/{data-access.js → data/access.js} +142 -15
  34. package/mcp/dist/{data-tasks.js → data/tasks.js} +7 -5
  35. package/mcp/dist/embedding.js +9 -14
  36. package/mcp/dist/entrypoint.js +11 -11
  37. package/mcp/dist/{finding-context.js → finding/context.js} +2 -2
  38. package/mcp/dist/{finding-impact.js → finding/impact.js} +3 -3
  39. package/mcp/dist/{finding-journal.js → finding/journal.js} +4 -4
  40. package/mcp/dist/{finding-lifecycle.js → finding/lifecycle.js} +13 -7
  41. package/mcp/dist/governance/audit.js +30 -0
  42. package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
  43. package/mcp/dist/{governance-policy.js → governance/policy.js} +23 -12
  44. package/mcp/dist/{governance-rbac.js → governance/rbac.js} +4 -4
  45. package/mcp/dist/{governance-scores.js → governance/scores.js} +10 -11
  46. package/mcp/dist/hooks.js +53 -37
  47. package/mcp/dist/index-query.js +4 -1
  48. package/mcp/dist/index.js +54 -30
  49. package/mcp/dist/{init-config.js → init/config.js} +6 -6
  50. package/mcp/dist/{init.js → init/init.js} +80 -69
  51. package/mcp/dist/{init-preferences.js → init/preferences.js} +3 -3
  52. package/mcp/dist/{init-setup.js → init/setup.js} +17 -19
  53. package/mcp/dist/{init-shared.js → init/shared.js} +4 -4
  54. package/mcp/dist/init-bootstrap.js +21 -0
  55. package/mcp/dist/init-detect.js +38 -0
  56. package/mcp/dist/init-env.js +114 -0
  57. package/mcp/dist/init-fresh.js +234 -0
  58. package/mcp/dist/init-hooks.js +26 -0
  59. package/mcp/dist/init-mcp.js +65 -0
  60. package/mcp/dist/init-modes.js +135 -0
  61. package/mcp/dist/init-npm.js +37 -0
  62. package/mcp/dist/init-project-local.js +99 -0
  63. package/mcp/dist/init-semantic.js +48 -0
  64. package/mcp/dist/init-types.js +1 -0
  65. package/mcp/dist/init-uninstall.js +504 -0
  66. package/mcp/dist/init-update.js +96 -0
  67. package/mcp/dist/init-walkthrough.js +524 -0
  68. package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
  69. package/mcp/dist/{link-context.js → link/context.js} +4 -4
  70. package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
  71. package/mcp/dist/{link.js → link/link.js} +26 -31
  72. package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
  73. package/mcp/dist/logger.js +11 -3
  74. package/mcp/dist/package-metadata.js +1 -1
  75. package/mcp/dist/phren-art.js +4 -126
  76. package/mcp/dist/phren-paths.js +30 -12
  77. package/mcp/dist/proactivity.js +3 -3
  78. package/mcp/dist/profile-store.js +5 -6
  79. package/mcp/dist/project-config.js +2 -2
  80. package/mcp/dist/project-topics.js +17 -47
  81. package/mcp/dist/provider-adapters.js +1 -1
  82. package/mcp/dist/query-correlation.js +1 -1
  83. package/mcp/dist/runtime-profile.js +1 -1
  84. package/mcp/dist/{session-checkpoints.js → session/checkpoints.js} +3 -3
  85. package/mcp/dist/{session-utils.js → session/utils.js} +1 -1
  86. package/mcp/dist/{shared-content.js → shared/content.js} +7 -7
  87. package/mcp/dist/{shared-data-utils.js → shared/data-utils.js} +28 -3
  88. package/mcp/dist/{shared-embedding-cache.js → shared/embedding-cache.js} +3 -3
  89. package/mcp/dist/{shared-fragment-graph.js → shared/fragment-graph.js} +19 -42
  90. package/mcp/dist/shared/governance.js +4 -0
  91. package/mcp/dist/{shared-index.js → shared/index.js} +105 -132
  92. package/mcp/dist/{shared-ollama.js → shared/ollama.js} +25 -7
  93. package/mcp/dist/shared/process.js +24 -0
  94. package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +22 -24
  95. package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +18 -20
  96. package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
  97. package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
  98. package/mcp/dist/shared.js +6 -60
  99. package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
  100. package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
  101. package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
  102. package/mcp/dist/{shell-render.js → shell/render.js} +2 -2
  103. package/mcp/dist/{shell.js → shell/shell.js} +11 -11
  104. package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
  105. package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
  106. package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
  107. package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
  108. package/mcp/dist/{skill-registry.js → skill/registry.js} +5 -5
  109. package/mcp/dist/{skill-state.js → skill/state.js} +1 -4
  110. package/mcp/dist/startup-embedding.js +2 -2
  111. package/mcp/dist/status.js +15 -14
  112. package/mcp/dist/{tasks-github.js → task/github.js} +3 -2
  113. package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
  114. package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +8 -13
  115. package/mcp/dist/telemetry.js +3 -4
  116. package/mcp/dist/tool-registry.js +29 -17
  117. package/mcp/dist/tools/config.js +530 -0
  118. package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
  119. package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
  120. package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
  121. package/mcp/dist/tools/finding.js +584 -0
  122. package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
  123. package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
  124. package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
  125. package/mcp/dist/tools/ops.js +468 -0
  126. package/mcp/dist/tools/search.js +672 -0
  127. package/mcp/dist/{mcp-session.js → tools/session.js} +51 -25
  128. package/mcp/dist/{mcp-skills.js → tools/skills.js} +42 -35
  129. package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
  130. package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
  131. package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
  132. package/mcp/dist/{memory-ui-page.js → ui/page.js} +5 -7
  133. package/mcp/dist/ui/server.js +1024 -0
  134. package/mcp/dist/update.js +2 -2
  135. package/mcp/dist/utils.js +63 -19
  136. package/package.json +2 -2
  137. package/scripts/preuninstall.mjs +31 -0
  138. package/starter/global/CLAUDE.md +3 -2
  139. package/mcp/dist/governance-audit.js +0 -22
  140. package/mcp/dist/mcp-config.js +0 -551
  141. package/mcp/dist/mcp-finding.js +0 -594
  142. package/mcp/dist/mcp-ops.js +0 -363
  143. package/mcp/dist/mcp-search.js +0 -668
  144. package/mcp/dist/memory-ui-server.js +0 -1411
  145. package/mcp/dist/shared-governance.js +0 -4
  146. /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
  147. /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
  148. /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
  149. /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
  150. /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
  151. /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
  152. /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
  153. /package/mcp/dist/{memory-ui-styles.js → ui/styles.js} +0 -0
package/mcp/dist/index.js CHANGED
@@ -4,22 +4,22 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
6
  import { findPhrenPathWithArg, debugLog, runtimeDir, } from "./shared.js";
7
- import { log as structuredLog } from "./logger.js";
8
- import { buildIndex, updateFileInIndex as updateFileInIndexFn, } from "./shared-index.js";
7
+ import { log as structuredLog, logger } from "./logger.js";
8
+ import { buildIndex, updateFileInIndex as updateFileInIndexFn, } from "./shared/index.js";
9
9
  import { runCustomHooks } from "./hooks.js";
10
- import { register as registerSearch } from "./mcp-search.js";
11
- import { register as registerTask } from "./mcp-tasks.js";
12
- import { register as registerFinding } from "./mcp-finding.js";
13
- import { register as registerMemory } from "./mcp-memory.js";
14
- import { register as registerData } from "./mcp-data.js";
15
- import { register as registerGraph } from "./mcp-graph.js";
16
- import { register as registerSession } from "./mcp-session.js";
17
- import { register as registerOps } from "./mcp-ops.js";
18
- import { register as registerSkills } from "./mcp-skills.js";
19
- import { register as registerHooks } from "./mcp-hooks.js";
20
- import { register as registerExtract } from "./mcp-extract.js";
21
- import { register as registerConfig } from "./mcp-config.js";
22
- import { mcpResponse } from "./mcp-types.js";
10
+ import { register as registerSearch } from "./tools/search.js";
11
+ import { register as registerTask } from "./tools/tasks.js";
12
+ import { register as registerFinding } from "./tools/finding.js";
13
+ import { register as registerMemory } from "./tools/memory.js";
14
+ import { register as registerData } from "./tools/data.js";
15
+ import { register as registerGraph } from "./tools/graph.js";
16
+ import { register as registerSession } from "./tools/session.js";
17
+ import { register as registerOps } from "./tools/ops.js";
18
+ import { register as registerSkills } from "./tools/skills.js";
19
+ import { register as registerHooks } from "./tools/hooks.js";
20
+ import { register as registerExtract } from "./tools/extract.js";
21
+ import { register as registerConfig } from "./tools/config.js";
22
+ import { mcpResponse } from "./tools/types.js";
23
23
  import { errorMessage } from "./utils.js";
24
24
  import { runTopLevelCommand } from "./entrypoint.js";
25
25
  import { startEmbeddingWarmup } from "./startup-embedding.js";
@@ -48,14 +48,12 @@ function cleanStaleLocks(phrenPath) {
48
48
  }
49
49
  }
50
50
  catch (err) {
51
- if ((process.env.PHREN_DEBUG))
52
- process.stderr.write(`[phren] cleanStaleLocks statFile: ${errorMessage(err)}\n`);
51
+ logger.warn("cleanStaleLocks", `statFile: ${errorMessage(err)}`);
53
52
  }
54
53
  }
55
54
  }
56
55
  catch (err) {
57
- if ((process.env.PHREN_DEBUG))
58
- process.stderr.write(`[phren] cleanStaleLocks readdir: ${errorMessage(err)}\n`);
56
+ logger.warn("cleanStaleLocks", `readdir: ${errorMessage(err)}`);
59
57
  }
60
58
  }
61
59
  async function main() {
@@ -67,14 +65,14 @@ async function main() {
67
65
  db = await buildIndex(phrenPath, profile);
68
66
  indexReady = true;
69
67
  // Load embedding cache and kick off background embedding (fire-and-forget)
70
- const { getEmbeddingCache } = await import("./shared-embedding-cache.js");
68
+ const { getEmbeddingCache } = await import("./shared/embedding-cache.js");
71
69
  const embCache = getEmbeddingCache(phrenPath);
72
70
  void startEmbeddingWarmup(db, embCache);
73
71
  }
74
72
  catch (error) {
75
73
  const msg = error instanceof Error ? error.message : String(error);
76
74
  structuredLog("error", "startup", `Failed to build phren index: ${msg}`);
77
- console.error("Failed to build phren index at startup:", error);
75
+ console.error("Failed to build phren index at startup:", msg);
78
76
  process.exit(1);
79
77
  }
80
78
  let writeQueue = Promise.resolve();
@@ -83,21 +81,29 @@ async function main() {
83
81
  const WRITE_TIMEOUT_MS = 30_000;
84
82
  async function rebuildIndex() {
85
83
  runCustomHooks(phrenPath, "pre-index");
86
- indexReady = false;
84
+ const oldDb = db;
87
85
  try {
88
- db?.close();
86
+ indexReady = false;
87
+ db = await buildIndex(phrenPath, profile);
88
+ indexReady = true;
89
+ try {
90
+ oldDb?.close();
91
+ }
92
+ catch (err) {
93
+ logger.warn("rebuildIndex", `dbClose: ${errorMessage(err)}`);
94
+ }
89
95
  }
90
96
  catch (err) {
91
- if ((process.env.PHREN_DEBUG))
92
- process.stderr.write(`[phren] rebuildIndex dbClose: ${errorMessage(err)}\n`);
97
+ // Restore old state on failure
98
+ db = oldDb;
99
+ indexReady = !!oldDb;
100
+ throw err;
93
101
  }
94
- db = await buildIndex(phrenPath, profile);
95
- indexReady = true;
96
102
  runCustomHooks(phrenPath, "post-index");
97
103
  }
98
104
  async function withWriteQueue(fn) {
99
105
  if (writeQueueDepth >= MAX_QUEUE_DEPTH) {
100
- throw new Error(`Write queue full (${MAX_QUEUE_DEPTH} items). Try again shortly.`);
106
+ return mcpResponse({ ok: false, error: `Write queue full (${MAX_QUEUE_DEPTH} items). Try again shortly.`, errorCode: "TIMEOUT" });
101
107
  }
102
108
  writeQueueDepth++;
103
109
  const run = writeQueue.then(async () => {
@@ -158,8 +164,7 @@ async function main() {
158
164
  trackToolCall(phrenPath, registeredName);
159
165
  }
160
166
  catch (err) {
161
- if ((process.env.PHREN_DEBUG))
162
- process.stderr.write(`[phren] trackToolCall: ${errorMessage(err)}\n`);
167
+ logger.warn("trackToolCall", errorMessage(err));
163
168
  }
164
169
  return handler(...args);
165
170
  };
@@ -197,6 +202,25 @@ async function main() {
197
202
  const transport = new StdioServerTransport();
198
203
  await server.connect(transport);
199
204
  console.error(`phren-mcp running (${phrenPath})`);
205
+ // Graceful shutdown: drain write queue and close DB before exit
206
+ async function shutdown(signal) {
207
+ structuredLog("info", "shutdown", `Received ${signal}, draining write queue...`);
208
+ try {
209
+ await writeQueue;
210
+ }
211
+ catch {
212
+ // Write queue errors already logged
213
+ }
214
+ try {
215
+ db?.close();
216
+ }
217
+ catch (err) {
218
+ logger.warn("shutdown", `dbClose: ${errorMessage(err)}`);
219
+ }
220
+ process.exit(0);
221
+ }
222
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
223
+ process.on("SIGINT", () => void shutdown("SIGINT"));
200
224
  }
201
225
  if (!handledTopLevelCommand) {
202
226
  main().catch((err) => {
@@ -4,12 +4,12 @@
4
4
  */
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
- import { buildLifecycleCommands, commandExists } from "./hooks.js";
8
- import { isRecord, hookConfigPath, homePath, readRootManifest, atomicWriteText, } from "./shared.js";
9
- import { isFeatureEnabled, errorMessage } from "./utils.js";
10
- import { probeVsCodeConfig, resolveCodexMcpConfig, resolveCopilotMcpConfig, resolveCursorMcpConfig, } from "./provider-adapters.js";
11
- import { getMcpEnabledPreference, getHooksEnabledPreference } from "./init-preferences.js";
12
- import { resolveEntryScript, log, VERSION } from "./init-shared.js";
7
+ import { buildLifecycleCommands, commandExists } from "../hooks.js";
8
+ import { isRecord, hookConfigPath, homePath, readRootManifest, atomicWriteText, } from "../shared.js";
9
+ import { isFeatureEnabled, errorMessage } from "../utils.js";
10
+ import { probeVsCodeConfig, resolveCodexMcpConfig, resolveCopilotMcpConfig, resolveCursorMcpConfig, } from "../provider-adapters.js";
11
+ import { getMcpEnabledPreference, getHooksEnabledPreference } from "./preferences.js";
12
+ import { resolveEntryScript, log, VERSION } from "./shared.js";
13
13
  function getObjectProp(value, key) {
14
14
  const candidate = value[key];
15
15
  return isRecord(candidate) ? candidate : undefined;
@@ -6,24 +6,25 @@ import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import * as crypto from "crypto";
8
8
  import { execFileSync, spawnSync } from "child_process";
9
- import { configureAllHooks } from "./hooks.js";
10
- import { getMachineName, machineFilePath, persistMachineName } from "./machine-identity.js";
11
- import { atomicWriteText, debugLog, isRecord, hookConfigPath, homeDir, homePath, expandHomePath, findPhrenPath, getProjectDirs, readRootManifest, writeRootManifest, } from "./shared.js";
12
- import { isValidProjectName, errorMessage } from "./utils.js";
13
- import { codexJsonCandidates, copilotMcpCandidates, cursorMcpCandidates, vscodeMcpCandidates, } from "./provider-adapters.js";
14
- export { configureClaude, configureVSCode, configureCursorMcp, configureCopilotMcp, configureCodexMcp, logMcpTargetStatus, resetVSCodeProbeCache, patchJsonFile, } from "./init-config.js";
15
- export { getMcpEnabledPreference, setMcpEnabledPreference, getHooksEnabledPreference, setHooksEnabledPreference, } from "./init-preferences.js";
16
- export { PROJECT_OWNERSHIP_MODES, parseProjectOwnershipMode, getProjectOwnershipDefault, } from "./project-config.js";
17
- export { PROACTIVITY_LEVELS, getProactivityLevel, getProactivityLevelForFindings, getProactivityLevelForTask, } from "./proactivity.js";
18
- export { ensureGovernanceFiles, repairPreexistingInstall, runPostInitVerify, getVerifyOutcomeNote, listTemplates, detectProjectDir, isProjectTracked, ensureLocalGitRepo, resolvePreferredHomeDir, inferInitScaffoldFromRepo, } from "./init-setup.js";
9
+ import { configureAllHooks } from "../hooks.js";
10
+ import { getMachineName, machineFilePath, persistMachineName } from "../machine-identity.js";
11
+ import { atomicWriteText, debugLog, isRecord, hookConfigPath, homeDir, homePath, expandHomePath, findPhrenPath, getProjectDirs, readRootManifest, writeRootManifest, } from "../shared.js";
12
+ import { isValidProjectName, errorMessage } from "../utils.js";
13
+ import { codexJsonCandidates, copilotMcpCandidates, cursorMcpCandidates, vscodeMcpCandidates, } from "../provider-adapters.js";
14
+ import { logger } from "../logger.js";
15
+ export { configureClaude, configureVSCode, configureCursorMcp, configureCopilotMcp, configureCodexMcp, logMcpTargetStatus, resetVSCodeProbeCache, patchJsonFile, } from "./config.js";
16
+ export { getMcpEnabledPreference, setMcpEnabledPreference, getHooksEnabledPreference, setHooksEnabledPreference, } from "./preferences.js";
17
+ export { PROJECT_OWNERSHIP_MODES, parseProjectOwnershipMode, getProjectOwnershipDefault, } from "../project-config.js";
18
+ export { PROACTIVITY_LEVELS, getProactivityLevel, getProactivityLevelForFindings, getProactivityLevelForTask, } from "../proactivity.js";
19
+ export { ensureGovernanceFiles, repairPreexistingInstall, runPostInitVerify, getVerifyOutcomeNote, listTemplates, detectProjectDir, isProjectTracked, ensureLocalGitRepo, resolvePreferredHomeDir, inferInitScaffoldFromRepo, } from "./setup.js";
19
20
  // Imports from helpers (used internally in this file)
20
- import { configureClaude, configureVSCode, configureCursorMcp, configureCopilotMcp, configureCodexMcp, logMcpTargetStatus, removeMcpServerAtPath, removeTomlMcpServer, isPhrenCommand, patchJsonFile, } from "./init-config.js";
21
- import { getMcpEnabledPreference, getHooksEnabledPreference, setMcpEnabledPreference, setHooksEnabledPreference, writeInstallPreferences, writeGovernanceInstallPreferences, readInstallPreferences, } from "./init-preferences.js";
22
- import { ensureGovernanceFiles, repairPreexistingInstall, runPostInitVerify, applyStarterTemplateUpdates, listTemplates, applyTemplate, ensureProjectScaffold, ensureLocalGitRepo, bootstrapFromExisting, ensureGitignoreEntry, upsertProjectEnvVar, updateMachinesYaml, detectProjectDir, isProjectTracked, inferInitScaffoldFromRepo, } from "./init-setup.js";
23
- import { DEFAULT_PHREN_PATH, STARTER_DIR, VERSION, log, confirmPrompt } from "./init-shared.js";
24
- import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, } from "./project-config.js";
25
- import { getWorkflowPolicy, updateWorkflowPolicy } from "./shared-governance.js";
26
- import { addProjectToProfile } from "./profile-store.js";
21
+ import { configureClaude, configureVSCode, configureCursorMcp, configureCopilotMcp, configureCodexMcp, logMcpTargetStatus, removeMcpServerAtPath, removeTomlMcpServer, isPhrenCommand, patchJsonFile, } from "./config.js";
22
+ import { getMcpEnabledPreference, getHooksEnabledPreference, setMcpEnabledPreference, setHooksEnabledPreference, writeInstallPreferences, writeGovernanceInstallPreferences, readInstallPreferences, } from "./preferences.js";
23
+ import { ensureGovernanceFiles, repairPreexistingInstall, runPostInitVerify, applyStarterTemplateUpdates, listTemplates, applyTemplate, ensureProjectScaffold, ensureLocalGitRepo, bootstrapFromExisting, ensureGitignoreEntry, upsertProjectEnvVar, updateMachinesYaml, detectProjectDir, isProjectTracked, inferInitScaffoldFromRepo, } from "./setup.js";
24
+ import { DEFAULT_PHREN_PATH, STARTER_DIR, VERSION, log, confirmPrompt } from "./shared.js";
25
+ import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, } from "../project-config.js";
26
+ import { getWorkflowPolicy, updateWorkflowPolicy } from "../shared/governance.js";
27
+ import { addProjectToProfile } from "../profile-store.js";
27
28
  const PHREN_NPM_PACKAGE_NAME = "@phren/cli";
28
29
  function parseVersion(version) {
29
30
  const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?/);
@@ -289,7 +290,7 @@ async function runWalkthrough(phrenPath) {
289
290
  log(style.success(`✓ ${item}`));
290
291
  }
291
292
  };
292
- const { renderPhrenArt } = await import("./phren-art.js");
293
+ const { renderPhrenArt } = await import("../phren-art.js");
293
294
  log("");
294
295
  log(renderPhrenArt(" "));
295
296
  log("");
@@ -434,37 +435,31 @@ async function runWalkthrough(phrenPath) {
434
435
  log(" Change later: set PHREN_OLLAMA_URL=off to disable");
435
436
  let ollamaEnabled = false;
436
437
  try {
437
- const { checkOllamaAvailable, checkModelAvailable, getOllamaUrl } = await import("./shared-ollama.js");
438
- if (getOllamaUrl()) {
439
- const ollamaUp = await checkOllamaAvailable();
440
- if (ollamaUp) {
441
- const modelReady = await checkModelAvailable();
442
- if (modelReady) {
443
- log(" Ollama detected with nomic-embed-text ready.");
444
- ollamaEnabled = await prompts.confirm("Enable semantic search for fuzzy/paraphrase recovery?", false);
445
- }
446
- else {
447
- log(" Ollama detected, but nomic-embed-text is not pulled yet.");
448
- ollamaEnabled = await prompts.confirm("Enable semantic search for fuzzy/paraphrase recovery? (will pull nomic-embed-text)", false);
449
- if (ollamaEnabled) {
450
- log(" Run after init: ollama pull nomic-embed-text");
451
- }
452
- }
438
+ const { checkOllamaStatus } = await import("../shared/ollama.js");
439
+ const status = await checkOllamaStatus();
440
+ if (status === "ready") {
441
+ log(" Ollama detected with nomic-embed-text ready.");
442
+ ollamaEnabled = await prompts.confirm("Enable semantic search for fuzzy/paraphrase recovery?", false);
443
+ }
444
+ else if (status === "no_model") {
445
+ log(" Ollama detected, but nomic-embed-text is not pulled yet.");
446
+ ollamaEnabled = await prompts.confirm("Enable semantic search for fuzzy/paraphrase recovery? (will pull nomic-embed-text)", false);
447
+ if (ollamaEnabled) {
448
+ log(" Run after init: ollama pull nomic-embed-text");
453
449
  }
454
- else {
455
- log(" Ollama not detected. Install it to enable semantic search:");
456
- log(" https://ollama.com → then: ollama pull nomic-embed-text");
457
- ollamaEnabled = await prompts.confirm("Enable semantic search (Ollama not installed yet)?", false);
458
- if (ollamaEnabled) {
459
- log(style.success(" Semantic search enabled — will activate once Ollama is running."));
460
- log(" To disable: set PHREN_OLLAMA_URL=off in your shell profile");
461
- }
450
+ }
451
+ else if (status === "not_running") {
452
+ log(" Ollama not detected. Install it to enable semantic search:");
453
+ log(" https://ollama.com → then: ollama pull nomic-embed-text");
454
+ ollamaEnabled = await prompts.confirm("Enable semantic search (Ollama not installed yet)?", false);
455
+ if (ollamaEnabled) {
456
+ log(style.success(" Semantic search enabled will activate once Ollama is running."));
457
+ log(" To disable: set PHREN_OLLAMA_URL=off in your shell profile");
462
458
  }
463
459
  }
464
460
  }
465
461
  catch (err) {
466
- if ((process.env.PHREN_DEBUG))
467
- process.stderr.write(`[phren] init ollamaCheck: ${errorMessage(err)}\n`);
462
+ logger.debug("init", `init ollamaCheck: ${errorMessage(err)}`);
468
463
  }
469
464
  printSection("Auto-Capture (Optional)");
470
465
  log("After each session, phren scans the conversation for insight-signal phrases");
@@ -663,7 +658,7 @@ async function runWalkthrough(phrenPath) {
663
658
  };
664
659
  }
665
660
  export async function warmSemanticSearch(phrenPath, profile) {
666
- const { checkOllamaAvailable, checkModelAvailable, getOllamaUrl, getEmbeddingModel } = await import("./shared-ollama.js");
661
+ const { checkOllamaAvailable, checkModelAvailable, getOllamaUrl, getEmbeddingModel } = await import("../shared/ollama.js");
667
662
  const ollamaUrl = getOllamaUrl();
668
663
  if (!ollamaUrl)
669
664
  return "Semantic search: disabled.";
@@ -674,10 +669,10 @@ export async function warmSemanticSearch(phrenPath, profile) {
674
669
  if (!await checkModelAvailable()) {
675
670
  return `Semantic search not warmed: model ${model} is not pulled yet.`;
676
671
  }
677
- const { buildIndex, listIndexedDocumentPaths } = await import("./shared-index.js");
678
- const { getEmbeddingCache, formatEmbeddingCoverage } = await import("./shared-embedding-cache.js");
679
- const { backgroundEmbedMissingDocs } = await import("./startup-embedding.js");
680
- const { getPersistentVectorIndex } = await import("./shared-vector-index.js");
672
+ const { buildIndex, listIndexedDocumentPaths } = await import("../shared/index.js");
673
+ const { getEmbeddingCache, formatEmbeddingCoverage } = await import("../shared/embedding-cache.js");
674
+ const { backgroundEmbedMissingDocs } = await import("../startup-embedding.js");
675
+ const { getPersistentVectorIndex } = await import("../shared/vector-index.js");
681
676
  const db = await buildIndex(phrenPath, profile);
682
677
  try {
683
678
  const cache = getEmbeddingCache(phrenPath);
@@ -1450,29 +1445,23 @@ export async function runInit(opts = {}) {
1450
1445
  const walkthroughCoveredOllama = Boolean(process.env._PHREN_WALKTHROUGH_OLLAMA_SKIP) || (!hasExistingInstall && !opts.yes);
1451
1446
  if (!walkthroughCoveredOllama) {
1452
1447
  try {
1453
- const { checkOllamaAvailable, checkModelAvailable, getOllamaUrl } = await import("./shared-ollama.js");
1454
- if (getOllamaUrl()) {
1455
- const ollamaUp = await checkOllamaAvailable();
1456
- if (ollamaUp) {
1457
- const modelReady = await checkModelAvailable();
1458
- if (modelReady) {
1459
- log("\n Semantic search: Ollama + nomic-embed-text ready.");
1460
- }
1461
- else {
1462
- log("\n Semantic search: Ollama running, but nomic-embed-text not pulled.");
1463
- log(" Run: ollama pull nomic-embed-text");
1464
- }
1465
- }
1466
- else {
1467
- log("\n Tip: Install Ollama for semantic search (optional).");
1468
- log(" https://ollama.com → then: ollama pull nomic-embed-text");
1469
- log(" (Set PHREN_OLLAMA_URL=off to hide this message)");
1470
- }
1448
+ const { checkOllamaStatus } = await import("../shared/ollama.js");
1449
+ const status = await checkOllamaStatus();
1450
+ if (status === "ready") {
1451
+ log("\n Semantic search: Ollama + nomic-embed-text ready.");
1452
+ }
1453
+ else if (status === "no_model") {
1454
+ log("\n Semantic search: Ollama running, but nomic-embed-text not pulled.");
1455
+ log(" Run: ollama pull nomic-embed-text");
1456
+ }
1457
+ else if (status === "not_running") {
1458
+ log("\n Tip: Install Ollama for semantic search (optional).");
1459
+ log(" https://ollama.com → then: ollama pull nomic-embed-text");
1460
+ log(" (Set PHREN_OLLAMA_URL=off to hide this message)");
1471
1461
  }
1472
1462
  }
1473
1463
  catch (err) {
1474
- if ((process.env.PHREN_DEBUG))
1475
- process.stderr.write(`[phren] init ollamaInstallHint: ${errorMessage(err)}\n`);
1464
+ logger.debug("init", `init ollamaInstallHint: ${errorMessage(err)}`);
1476
1465
  }
1477
1466
  }
1478
1467
  for (const envLabel of writeWalkthroughEnvDefaults(phrenPath, opts)) {
@@ -2004,6 +1993,28 @@ export async function runUninstall(opts = {}) {
2004
1993
  catch (err) {
2005
1994
  debugLog(`uninstall: cleanup failed for ${contextFile}: ${errorMessage(err)}`);
2006
1995
  }
1996
+ // Remove global CLAUDE.md symlink (created by linkGlobal -> ~/.claude/CLAUDE.md)
1997
+ const globalClaudeLink = homePath(".claude", "CLAUDE.md");
1998
+ try {
1999
+ if (fs.lstatSync(globalClaudeLink).isSymbolicLink()) {
2000
+ fs.unlinkSync(globalClaudeLink);
2001
+ log(` Removed global CLAUDE.md symlink (${globalClaudeLink})`);
2002
+ }
2003
+ }
2004
+ catch {
2005
+ // Does not exist or not a symlink — nothing to do
2006
+ }
2007
+ // Remove copilot-instructions.md symlink (created by linkGlobal -> ~/.github/copilot-instructions.md)
2008
+ const copilotInstrLink = homePath(".github", "copilot-instructions.md");
2009
+ try {
2010
+ if (fs.lstatSync(copilotInstrLink).isSymbolicLink()) {
2011
+ fs.unlinkSync(copilotInstrLink);
2012
+ log(` Removed copilot-instructions.md symlink (${copilotInstrLink})`);
2013
+ }
2014
+ }
2015
+ catch {
2016
+ // Does not exist or not a symlink — nothing to do
2017
+ }
2007
2018
  // Sweep agent skill directories for symlinks pointing into the phren store
2008
2019
  if (phrenPath) {
2009
2020
  try {
@@ -4,9 +4,9 @@
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
6
  import * as crypto from "crypto";
7
- import { debugLog, installPreferencesFile } from "./phren-paths.js";
8
- import { errorMessage } from "./utils.js";
9
- import { withFileLock } from "./shared-governance.js";
7
+ import { debugLog, installPreferencesFile } from "../phren-paths.js";
8
+ import { errorMessage } from "../utils.js";
9
+ import { withFileLock } from "../shared/governance.js";
10
10
  function preferencesFile(phrenPath) {
11
11
  return installPreferencesFile(phrenPath);
12
12
  }
@@ -5,19 +5,20 @@ import * as fs from "fs";
5
5
  import * as path from "path";
6
6
  import * as os from "os";
7
7
  import * as yaml from "js-yaml";
8
- import { atomicWriteText, debugLog, findProjectNameCaseInsensitive, hookConfigPath, EXEC_TIMEOUT_QUICK_MS, readRootManifest, sessionsDir, runtimeHealthFile, isRecord, } from "./shared.js";
9
- import { addProjectToProfile, listProfiles, resolveActiveProfile, setMachineProfile } from "./profile-store.js";
10
- import { getMachineName } from "./machine-identity.js";
8
+ import { atomicWriteText, debugLog, findProjectNameCaseInsensitive, hookConfigPath, EXEC_TIMEOUT_QUICK_MS, readRootManifest, sessionsDir, runtimeHealthFile, isRecord, } from "../shared.js";
9
+ import { addProjectToProfile, listProfiles, resolveActiveProfile, setMachineProfile } from "../profile-store.js";
10
+ import { getMachineName } from "../machine-identity.js";
11
11
  import { execFileSync } from "child_process";
12
- import { GOVERNANCE_SCHEMA_VERSION, } from "./shared-governance.js";
13
- import { STOP_WORDS, errorMessage } from "./utils.js";
14
- import { ROOT, STARTER_DIR, VERSION, resolveEntryScript, commandVersion, versionAtLeast, nearestWritableTarget } from "./init-shared.js";
15
- import { readInstallPreferences } from "./init-preferences.js";
16
- import { TASKS_FILENAME } from "./data-tasks.js";
17
- import { getProjectOwnershipDefault, parseProjectOwnershipMode, readProjectConfig, writeProjectConfig, } from "./project-config.js";
18
- import { getBuiltinTopicConfig, normalizeBuiltinTopicDomain } from "./project-topics.js";
19
- import { writeSkillMd } from "./link-skills.js";
20
- import { syncScopeSkillsToDir } from "./skill-files.js";
12
+ import { GOVERNANCE_SCHEMA_VERSION, } from "../shared/governance.js";
13
+ import { STOP_WORDS, errorMessage } from "../utils.js";
14
+ import { ROOT, STARTER_DIR, VERSION, resolveEntryScript, commandVersion, versionAtLeast, nearestWritableTarget } from "./shared.js";
15
+ import { readInstallPreferences } from "./preferences.js";
16
+ import { TASKS_FILENAME } from "../data/tasks.js";
17
+ import { getProjectOwnershipDefault, parseProjectOwnershipMode, readProjectConfig, writeProjectConfig, } from "../project-config.js";
18
+ import { getBuiltinTopicConfig, normalizeBuiltinTopicDomain } from "../project-topics.js";
19
+ import { writeSkillMd } from "../link/skills.js";
20
+ import { syncScopeSkillsToDir } from "../skill/files.js";
21
+ import { logger } from "../logger.js";
21
22
  const LEGACY_SAMPLE_PROJECTS = new Set(["my-api", "my-frontend"]);
22
23
  function normalizeProjects(raw) {
23
24
  if (!Array.isArray(raw))
@@ -1052,16 +1053,14 @@ export function updateMachinesYaml(phrenPath, machine, profile) {
1052
1053
  }
1053
1054
  }
1054
1055
  catch (err) {
1055
- if ((process.env.PHREN_DEBUG))
1056
- process.stderr.write(`[phren] updateMachinesYaml parse: ${errorMessage(err)}\n`);
1056
+ logger.debug("setup", `updateMachinesYaml parse: ${errorMessage(err)}`);
1057
1057
  }
1058
1058
  // Passive init/link refreshes should keep an existing mapping; explicit overrides can remap.
1059
1059
  if (hasExistingMapping && !machine && !profile)
1060
1060
  return;
1061
1061
  const mapping = setMachineProfile(phrenPath, machineName, profileName);
1062
- if (!mapping.ok && (process.env.PHREN_DEBUG)) {
1063
- process.stderr.write(`[phren] updateMachinesYaml setMachineProfile: ${mapping.error}\n`);
1064
- }
1062
+ if (!mapping.ok)
1063
+ logger.debug("setup", `updateMachinesYaml setMachineProfile: ${mapping.error}`);
1065
1064
  }
1066
1065
  /**
1067
1066
  * Detect if a directory looks like a project that should be bootstrapped.
@@ -1262,8 +1261,7 @@ export function runPostInitVerify(phrenPath) {
1262
1261
  ftsOk = entries.some(d => d.isDirectory() && !d.name.startsWith("."));
1263
1262
  }
1264
1263
  catch (err) {
1265
- if ((process.env.PHREN_DEBUG))
1266
- process.stderr.write(`[phren] runPostInitVerify projectScan: ${errorMessage(err)}\n`);
1264
+ logger.debug("setup", `runPostInitVerify projectScan: ${errorMessage(err)}`);
1267
1265
  ftsOk = false;
1268
1266
  }
1269
1267
  checks.push({
@@ -5,9 +5,9 @@
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import { execFileSync } from "child_process";
8
- import { homePath, EXEC_TIMEOUT_QUICK_MS, debugLog } from "./shared.js";
9
- import { errorMessage } from "./utils.js";
10
- import { ROOT as PACKAGE_ROOT, VERSION } from "./package-metadata.js";
8
+ import { homePath, EXEC_TIMEOUT_QUICK_MS, debugLog } from "../shared.js";
9
+ import { errorMessage } from "../utils.js";
10
+ import { ROOT as PACKAGE_ROOT, VERSION } from "../package-metadata.js";
11
11
  export const ROOT = PACKAGE_ROOT;
12
12
  export { VERSION };
13
13
  export const STARTER_DIR = path.join(ROOT, "starter");
@@ -33,7 +33,7 @@ export function commandVersion(cmd, args = ["--version"]) {
33
33
  return null;
34
34
  }
35
35
  }
36
- export function parseSemverTriple(raw) {
36
+ function parseSemverTriple(raw) {
37
37
  const match = raw.match(/(\d+)\.(\d+)\.(\d+)/);
38
38
  if (!match)
39
39
  return null;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Bootstrap-current-project prompting and execution for init.
3
+ */
4
+ import { debugLog } from "./shared.js";
5
+ import { bootstrapFromExisting, } from "./init/setup.js";
6
+ import { log } from "./init/shared.js";
7
+ /**
8
+ * Bootstrap a project from an existing directory into phren.
9
+ */
10
+ export function bootstrapProject(phrenPath, projectPath, profile, ownership, label) {
11
+ try {
12
+ const created = bootstrapFromExisting(phrenPath, projectPath, {
13
+ profile,
14
+ ownership,
15
+ });
16
+ log(`\n${label} "${created.project}" (${created.ownership})`);
17
+ }
18
+ catch (e) {
19
+ debugLog(`Bootstrap from CWD failed: ${e instanceof Error ? e.message : String(e)}`);
20
+ }
21
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Detection helpers for init: bootstrap target, install markers, path resolution.
3
+ */
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import { expandHomePath } from "./shared.js";
7
+ import { detectProjectDir, isProjectTracked } from "./init/setup.js";
8
+ import { DEFAULT_PHREN_PATH } from "./init/shared.js";
9
+ export function normalizedBootstrapProjectName(projectPath) {
10
+ return path.basename(projectPath).toLowerCase().replace(/[^a-z0-9_-]/g, "-");
11
+ }
12
+ export function getPendingBootstrapTarget(phrenPath, _opts) {
13
+ const cwdProject = detectProjectDir(process.cwd(), phrenPath);
14
+ if (!cwdProject)
15
+ return null;
16
+ const projectName = normalizedBootstrapProjectName(cwdProject);
17
+ if (isProjectTracked(phrenPath, projectName))
18
+ return null;
19
+ return { path: cwdProject, mode: "detected" };
20
+ }
21
+ export function hasInstallMarkers(phrenPath) {
22
+ // Require at least two markers to consider this a real install.
23
+ // A partial clone or failed init may create one directory but not finish.
24
+ if (!fs.existsSync(phrenPath))
25
+ return false;
26
+ let found = 0;
27
+ if (fs.existsSync(path.join(phrenPath, "machines.yaml")))
28
+ found++;
29
+ if (fs.existsSync(path.join(phrenPath, ".config")))
30
+ found++;
31
+ if (fs.existsSync(path.join(phrenPath, "global")))
32
+ found++;
33
+ return found >= 2;
34
+ }
35
+ export function resolveInitPhrenPath(opts) {
36
+ const raw = opts._walkthroughStoragePath || process.env.PHREN_PATH || DEFAULT_PHREN_PATH;
37
+ return path.resolve(expandHomePath(raw));
38
+ }