@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
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Environment flags, onboarding preferences, and repair asset helpers for init.
3
+ */
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import * as crypto from "crypto";
7
+ import { writeInstallPreferences, writeGovernanceInstallPreferences, } from "./init/preferences.js";
8
+ import { updateWorkflowPolicy } from "./shared/governance.js";
9
+ import { ensureGitignoreEntry, upsertProjectEnvVar, } from "./init/setup.js";
10
+ export function applyOnboardingPreferences(phrenPath, opts) {
11
+ if (opts.projectOwnershipDefault) {
12
+ writeInstallPreferences(phrenPath, { projectOwnershipDefault: opts.projectOwnershipDefault });
13
+ }
14
+ const runtimePatch = {};
15
+ if (opts.findingsProactivity)
16
+ runtimePatch.proactivityFindings = opts.findingsProactivity;
17
+ if (opts.taskProactivity)
18
+ runtimePatch.proactivityTask = opts.taskProactivity;
19
+ if (Object.keys(runtimePatch).length > 0) {
20
+ writeInstallPreferences(phrenPath, runtimePatch);
21
+ }
22
+ const governancePatch = {};
23
+ if (opts.findingsProactivity)
24
+ governancePatch.proactivityFindings = opts.findingsProactivity;
25
+ if (opts.taskProactivity)
26
+ governancePatch.proactivityTask = opts.taskProactivity;
27
+ if (Object.keys(governancePatch).length > 0) {
28
+ writeGovernanceInstallPreferences(phrenPath, governancePatch);
29
+ }
30
+ const workflowPatch = {};
31
+ if (typeof opts.lowConfidenceThreshold === "number")
32
+ workflowPatch.lowConfidenceThreshold = opts.lowConfidenceThreshold;
33
+ if (Array.isArray(opts.riskySections))
34
+ workflowPatch.riskySections = opts.riskySections;
35
+ if (opts.taskMode)
36
+ workflowPatch.taskMode = opts.taskMode;
37
+ if (opts.findingSensitivity)
38
+ workflowPatch.findingSensitivity = opts.findingSensitivity;
39
+ if (Object.keys(workflowPatch).length > 0) {
40
+ updateWorkflowPolicy(phrenPath, workflowPatch);
41
+ }
42
+ }
43
+ export function writeWalkthroughEnvDefaults(phrenPath, opts) {
44
+ const envFile = path.join(phrenPath, ".env");
45
+ let envContent = fs.existsSync(envFile) ? fs.readFileSync(envFile, "utf8") : "# phren feature flags — generated by init\n";
46
+ const envFlags = [];
47
+ const autoCaptureChoice = opts._walkthroughAutoCapture;
48
+ const hasAutoCaptureFlag = /^\s*PHREN_FEATURE_AUTO_CAPTURE=.*$/m.test(envContent);
49
+ if (typeof autoCaptureChoice === "boolean") {
50
+ envFlags.push({
51
+ flag: `PHREN_FEATURE_AUTO_CAPTURE=${autoCaptureChoice ? "1" : "0"}`,
52
+ label: `Auto-capture ${autoCaptureChoice ? "enabled" : "disabled"}`,
53
+ });
54
+ }
55
+ else if (!hasAutoCaptureFlag) {
56
+ // Default to enabled on fresh installs and non-walkthrough init.
57
+ envFlags.push({ flag: "PHREN_FEATURE_AUTO_CAPTURE=1", label: "Auto-capture enabled" });
58
+ }
59
+ if (opts._walkthroughSemanticDedup)
60
+ envFlags.push({ flag: "PHREN_FEATURE_SEMANTIC_DEDUP=1", label: "Semantic dedup" });
61
+ if (opts._walkthroughSemanticConflict)
62
+ envFlags.push({ flag: "PHREN_FEATURE_SEMANTIC_CONFLICT=1", label: "Conflict detection" });
63
+ if (envFlags.length === 0)
64
+ return [];
65
+ let changed = false;
66
+ const enabledLabels = [];
67
+ for (const { flag, label } of envFlags) {
68
+ const key = flag.split("=")[0];
69
+ const lineRe = new RegExp(`^\\s*${key}=.*$`, "m");
70
+ if (lineRe.test(envContent)) {
71
+ const before = envContent;
72
+ envContent = envContent.replace(lineRe, flag);
73
+ if (envContent !== before) {
74
+ changed = true;
75
+ enabledLabels.push(label);
76
+ }
77
+ }
78
+ else {
79
+ if (!envContent.endsWith("\n"))
80
+ envContent += "\n";
81
+ envContent += `${flag}\n`;
82
+ changed = true;
83
+ enabledLabels.push(label);
84
+ }
85
+ }
86
+ if (changed) {
87
+ const tmpPath = `${envFile}.tmp-${crypto.randomUUID()}`;
88
+ fs.writeFileSync(tmpPath, envContent);
89
+ fs.renameSync(tmpPath, envFile);
90
+ }
91
+ return enabledLabels.map((label) => `${label} (${envFile})`);
92
+ }
93
+ export function collectRepairedAssetLabels(repaired) {
94
+ const repairedAssets = [];
95
+ if (repaired.createdContextFile)
96
+ repairedAssets.push("~/.phren-context.md");
97
+ if (repaired.createdRootMemory)
98
+ repairedAssets.push("generated MEMORY.md");
99
+ repairedAssets.push(...repaired.createdGlobalAssets);
100
+ repairedAssets.push(...repaired.createdRuntimeAssets);
101
+ repairedAssets.push(...repaired.createdFeatureDefaults);
102
+ repairedAssets.push(...repaired.createdSkillArtifacts);
103
+ return repairedAssets;
104
+ }
105
+ export function applyProjectStorageBindings(repoRoot, phrenPath) {
106
+ const updates = [];
107
+ if (ensureGitignoreEntry(repoRoot, ".phren/")) {
108
+ updates.push(`${path.join(repoRoot, ".gitignore")} (.phren/)`);
109
+ }
110
+ if (upsertProjectEnvVar(repoRoot, "PHREN_PATH", phrenPath)) {
111
+ updates.push(`${path.join(repoRoot, ".env")} (PHREN_PATH=${phrenPath})`);
112
+ }
113
+ return updates;
114
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Fresh install logic: copy starter, scaffold project, write initial config.
3
+ */
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import { atomicWriteText, debugLog, hookConfigPath, writeRootManifest, } from "./shared.js";
7
+ import { isValidProjectName, errorMessage } from "./utils.js";
8
+ import { getMachineName, persistMachineName } from "./machine-identity.js";
9
+ import { writeInstallPreferences, } from "./init/preferences.js";
10
+ import { ensureGovernanceFiles, repairPreexistingInstall, runPostInitVerify, listTemplates, applyTemplate, ensureProjectScaffold, ensureLocalGitRepo, updateMachinesYaml, detectProjectDir, } from "./init/setup.js";
11
+ import { getWorkflowPolicy } from "./shared/governance.js";
12
+ import { addProjectToProfile } from "./profile-store.js";
13
+ import { STARTER_DIR, VERSION, log, confirmPrompt } from "./init/shared.js";
14
+ import { configureMcpTargets } from "./init-mcp.js";
15
+ import { configureHooksIfEnabled } from "./init-hooks.js";
16
+ import { applyOnboardingPreferences, writeWalkthroughEnvDefaults, collectRepairedAssetLabels, } from "./init-env.js";
17
+ import { warmSemanticSearch } from "./init-semantic.js";
18
+ import { bootstrapProject } from "./init-bootstrap.js";
19
+ import { logger } from "./logger.js";
20
+ function copyDir(src, dest) {
21
+ fs.mkdirSync(dest, { recursive: true });
22
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
23
+ if (src === STARTER_DIR && entry.isDirectory() && ["my-api", "my-frontend", "my-first-project"].includes(entry.name)) {
24
+ continue;
25
+ }
26
+ const srcPath = path.join(src, entry.name);
27
+ const destPath = path.join(dest, entry.name);
28
+ if (entry.isDirectory()) {
29
+ copyDir(srcPath, destPath);
30
+ }
31
+ else {
32
+ fs.copyFileSync(srcPath, destPath);
33
+ }
34
+ }
35
+ }
36
+ export async function runFreshInstall(phrenPath, opts, params) {
37
+ const { mcpEnabled, hooksEnabled, skillsScope, ownershipDefault, syncIntent, shouldBootstrapCurrentProject, bootstrapOwnership, } = params;
38
+ log("\nSetting up phren...\n");
39
+ const walkthroughProject = opts._walkthroughProject;
40
+ if (walkthroughProject) {
41
+ if (!walkthroughProject.trim()) {
42
+ console.error("Error: project name cannot be empty.");
43
+ process.exit(1);
44
+ }
45
+ if (walkthroughProject.length > 100) {
46
+ console.error("Error: project name must be 100 characters or fewer.");
47
+ process.exit(1);
48
+ }
49
+ if (!isValidProjectName(walkthroughProject)) {
50
+ console.error(`Error: invalid project name "${walkthroughProject}". Use lowercase letters, numbers, and hyphens.`);
51
+ process.exit(1);
52
+ }
53
+ }
54
+ const cwdProjectPath = !walkthroughProject ? detectProjectDir(process.cwd(), phrenPath) : null;
55
+ const useTemplateProject = Boolean(walkthroughProject) || Boolean(opts.template);
56
+ const firstProjectName = walkthroughProject || "my-first-project";
57
+ const firstProjectDomain = opts._walkthroughDomain ?? "software";
58
+ if (fs.existsSync(STARTER_DIR)) {
59
+ copyDir(STARTER_DIR, phrenPath);
60
+ writeRootManifest(phrenPath, {
61
+ version: 1,
62
+ installMode: "shared",
63
+ syncMode: "managed-git",
64
+ });
65
+ if (useTemplateProject) {
66
+ const targetProject = walkthroughProject || firstProjectName;
67
+ const projectDir = path.join(phrenPath, targetProject);
68
+ const templateApplied = Boolean(opts.template && applyTemplate(projectDir, opts.template, targetProject));
69
+ if (templateApplied) {
70
+ log(` Applied "${opts.template}" template to ${targetProject}`);
71
+ }
72
+ ensureProjectScaffold(projectDir, targetProject, firstProjectDomain, opts._walkthroughInferredScaffold);
73
+ const targetProfile = opts.profile || "default";
74
+ const addToProfile = addProjectToProfile(phrenPath, targetProfile, targetProject);
75
+ if (!addToProfile.ok) {
76
+ debugLog(`fresh init addProjectToProfile failed for ${targetProfile}/${targetProject}: ${addToProfile.error}`);
77
+ }
78
+ if (opts.template && !templateApplied) {
79
+ log(` Template "${opts.template}" not found. Available: ${listTemplates().join(", ") || "none"}`);
80
+ }
81
+ log(` Seeded project "${targetProject}"`);
82
+ }
83
+ log(` Created phren v${VERSION} \u2192 ${phrenPath}`);
84
+ }
85
+ else {
86
+ log(` Starter not found in package, creating minimal structure...`);
87
+ writeRootManifest(phrenPath, {
88
+ version: 1,
89
+ installMode: "shared",
90
+ syncMode: "managed-git",
91
+ });
92
+ fs.mkdirSync(path.join(phrenPath, "global", "skills"), { recursive: true });
93
+ fs.mkdirSync(path.join(phrenPath, "profiles"), { recursive: true });
94
+ atomicWriteText(path.join(phrenPath, "global", "CLAUDE.md"), `# Global Context\n\nThis file is loaded in every project.\n\n## General preferences\n\n<!-- Your coding style, preferred tools, things Claude should always know -->\n`);
95
+ if (useTemplateProject) {
96
+ const projectDir = path.join(phrenPath, firstProjectName);
97
+ if (opts.template && applyTemplate(projectDir, opts.template, firstProjectName)) {
98
+ log(` Applied "${opts.template}" template to ${firstProjectName}`);
99
+ }
100
+ ensureProjectScaffold(projectDir, firstProjectName, firstProjectDomain, opts._walkthroughInferredScaffold);
101
+ }
102
+ const profileName = opts.profile || "default";
103
+ const profileProjects = useTemplateProject
104
+ ? ` - global\n - ${firstProjectName}`
105
+ : ` - global`;
106
+ atomicWriteText(path.join(phrenPath, "profiles", `${profileName}.yaml`), `name: ${profileName}\ndescription: Default profile\nprojects:\n${profileProjects}\n`);
107
+ }
108
+ // Bootstrap CWD project if opted in
109
+ if (cwdProjectPath && shouldBootstrapCurrentProject) {
110
+ bootstrapProject(phrenPath, cwdProjectPath, opts.profile, bootstrapOwnership, "Added current project");
111
+ }
112
+ // Persist machine and config
113
+ const effectiveMachine = opts.machine?.trim() || getMachineName();
114
+ persistMachineName(effectiveMachine);
115
+ updateMachinesYaml(phrenPath, effectiveMachine, opts.profile);
116
+ ensureGovernanceFiles(phrenPath);
117
+ const repaired = repairPreexistingInstall(phrenPath);
118
+ applyOnboardingPreferences(phrenPath, opts);
119
+ const localGitRepo = ensureLocalGitRepo(phrenPath);
120
+ log(` Updated machines.yaml with machine "${effectiveMachine}"`);
121
+ const mcpLabel = mcpEnabled ? "ON (recommended)" : "OFF (hooks-only fallback)";
122
+ const hooksLabel = hooksEnabled ? "ON (active)" : "OFF (disabled)";
123
+ log(` MCP mode: ${mcpLabel}`);
124
+ log(` Hooks mode: ${hooksLabel}`);
125
+ log(` Default project ownership: ${ownershipDefault}`);
126
+ log(` Task mode: ${getWorkflowPolicy(phrenPath).taskMode}`);
127
+ log(` Git repo: ${localGitRepo.detail}`);
128
+ if (repaired.removedLegacyProjects > 0) {
129
+ log(` Removed ${repaired.removedLegacyProjects} legacy starter project entr${repaired.removedLegacyProjects === 1 ? "y" : "ies"} from profiles.`);
130
+ }
131
+ const repairedAssets = collectRepairedAssetLabels(repaired);
132
+ if (repairedAssets.length > 0) {
133
+ log(` Recreated missing generated assets: ${repairedAssets.join(", ")}`);
134
+ }
135
+ // Confirmation prompt before writing agent config
136
+ if (!opts.yes) {
137
+ const settingsPath = hookConfigPath("claude");
138
+ log(`\nWill modify:`);
139
+ log(` ${settingsPath} (add MCP server + hooks)`);
140
+ const confirmed = await confirmPrompt("\nProceed?");
141
+ if (!confirmed) {
142
+ log("Aborted.");
143
+ return;
144
+ }
145
+ }
146
+ // Configure MCP and hooks
147
+ configureMcpTargets(phrenPath, { mcpEnabled, hooksEnabled }, "Configured");
148
+ configureHooksIfEnabled(phrenPath, hooksEnabled, "Configured");
149
+ writeInstallPreferences(phrenPath, { mcpEnabled, hooksEnabled, skillsScope, installedVersion: VERSION, syncIntent });
150
+ // Post-init verification
151
+ log(`\nVerifying setup...`);
152
+ const verify = runPostInitVerify(phrenPath);
153
+ for (const check of verify.checks) {
154
+ log(` ${check.ok ? "pass" : "FAIL"} ${check.name}: ${check.detail}`);
155
+ }
156
+ log(`\nWhat was created:`);
157
+ log(` ${phrenPath}/global/CLAUDE.md Global instructions loaded in every session`);
158
+ log(` ${phrenPath}/global/skills/ Phren slash commands`);
159
+ log(` ${phrenPath}/profiles/ Machine-to-project mappings`);
160
+ log(` ${phrenPath}/.config/ Memory quality settings and config`);
161
+ // Ollama status summary (skip if already covered in walkthrough)
162
+ const walkthroughCoveredOllama = Boolean(process.env._PHREN_WALKTHROUGH_OLLAMA_SKIP) || !opts.yes;
163
+ if (!walkthroughCoveredOllama) {
164
+ try {
165
+ const { checkOllamaStatus } = await import("./shared/ollama.js");
166
+ const status = await checkOllamaStatus();
167
+ if (status === "ready") {
168
+ log("\n Semantic search: Ollama + nomic-embed-text ready.");
169
+ }
170
+ else if (status === "no_model") {
171
+ log("\n Semantic search: Ollama running, but nomic-embed-text not pulled.");
172
+ log(" Run: ollama pull nomic-embed-text");
173
+ }
174
+ else if (status === "not_running") {
175
+ log("\n Tip: Install Ollama for semantic search (optional).");
176
+ log(" https://ollama.com → then: ollama pull nomic-embed-text");
177
+ log(" (Set PHREN_OLLAMA_URL=off to hide this message)");
178
+ }
179
+ }
180
+ catch (err) {
181
+ logger.debug("init ollamaInstallHint", errorMessage(err));
182
+ }
183
+ }
184
+ for (const envLabel of writeWalkthroughEnvDefaults(phrenPath, opts)) {
185
+ log(` ${envLabel}`);
186
+ }
187
+ if (opts._walkthroughSemanticSearch) {
188
+ log(`\nWarming semantic search...`);
189
+ try {
190
+ log(` ${await warmSemanticSearch(phrenPath, opts.profile)}`);
191
+ }
192
+ catch (err) {
193
+ log(` Semantic search warmup failed: ${errorMessage(err)}`);
194
+ }
195
+ }
196
+ log(`\n\x1b[95m◆\x1b[0m phren initialized`);
197
+ log(`\nNext steps:`);
198
+ let step = 1;
199
+ log(` ${step++}. Start a new Claude session in your project directory — phren injects context automatically`);
200
+ log(` ${step++}. Run \`npx phren doctor\` to verify everything is wired correctly`);
201
+ log(` ${step++}. Change defaults anytime: \`npx phren config project-ownership\`, \`npx phren config workflow\`, \`npx phren config proactivity.findings\`, \`npx phren config proactivity.tasks\``);
202
+ const gh = opts._walkthroughGithub;
203
+ if (gh) {
204
+ const remote = gh.username
205
+ ? `git@github.com:${gh.username}/${gh.repo}.git`
206
+ : `git@github.com:YOUR_USERNAME/${gh.repo}.git`;
207
+ log(` ${step++}. Push your phren to GitHub (private repo recommended):`);
208
+ log(` cd ${phrenPath}`);
209
+ log(` git add . && git commit -m "Initial phren setup"`);
210
+ if (gh.username) {
211
+ log(` gh repo create ${gh.username}/${gh.repo} --private --source=. --push`);
212
+ log(` # or manually: git remote add origin ${remote} && git push -u origin main`);
213
+ }
214
+ else {
215
+ log(` git remote add origin ${remote}`);
216
+ log(` git push -u origin main`);
217
+ }
218
+ }
219
+ else {
220
+ log(` ${step++}. Push to GitHub for cross-machine sync (private repo recommended):`);
221
+ log(` cd ${phrenPath}`);
222
+ log(` git add . && git commit -m "Initial phren setup"`);
223
+ log(` git remote add origin git@github.com:YOUR_USERNAME/my-phren.git`);
224
+ log(` git push -u origin main`);
225
+ }
226
+ log(` ${step++}. Add more projects: cd ~/your-project && npx phren add`);
227
+ if (!mcpEnabled) {
228
+ log(` ${step++}. Turn MCP on: npx phren mcp-mode on`);
229
+ }
230
+ log(` ${step++}. After your first week, run phren-discover to surface gaps in your project knowledge`);
231
+ log(` ${step++}. After working across projects, run phren-consolidate to find cross-project patterns`);
232
+ log(`\n Read ${phrenPath}/README.md for a guided tour of each file.`);
233
+ log(``);
234
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Hook configuration helper for init.
3
+ */
4
+ import { configureAllHooks } from "./hooks.js";
5
+ import { debugLog } from "./shared.js";
6
+ import { errorMessage } from "./utils.js";
7
+ import { log } from "./init/shared.js";
8
+ /**
9
+ * Configure hooks if enabled, or log a disabled message.
10
+ * @param verb - label used in log messages, e.g. "Updated" or "Configured"
11
+ */
12
+ export function configureHooksIfEnabled(phrenPath, hooksEnabled, verb) {
13
+ if (hooksEnabled) {
14
+ try {
15
+ const hooked = configureAllHooks(phrenPath, { allTools: true });
16
+ if (hooked.length)
17
+ log(` ${verb} hooks: ${hooked.join(", ")}`);
18
+ }
19
+ catch (err) {
20
+ debugLog(`configureAllHooks failed: ${errorMessage(err)}`);
21
+ }
22
+ }
23
+ else {
24
+ log(` Hooks are disabled by preference (run: npx phren hooks-mode on)`);
25
+ }
26
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * MCP target configuration for all detected AI coding tools.
3
+ */
4
+ import { configureClaude, configureVSCode, configureCursorMcp, configureCopilotMcp, configureCodexMcp, logMcpTargetStatus, } from "./init/config.js";
5
+ import { debugLog } from "./shared.js";
6
+ import { errorMessage } from "./utils.js";
7
+ import { log } from "./init/shared.js";
8
+ /**
9
+ * Configure MCP for all detected AI coding tools (Claude, VS Code, Cursor, Copilot, Codex).
10
+ * @param verb - label used in log messages, e.g. "Updated" or "Configured"
11
+ */
12
+ export function configureMcpTargets(phrenPath, opts, verb = "Configured") {
13
+ let claudeStatus = "no_settings";
14
+ try {
15
+ const status = configureClaude(phrenPath, { mcpEnabled: opts.mcpEnabled, hooksEnabled: opts.hooksEnabled });
16
+ claudeStatus = status ?? "installed";
17
+ if (status === "disabled" || status === "already_disabled") {
18
+ log(` ${verb} Claude Code hooks (MCP disabled)`);
19
+ }
20
+ else {
21
+ log(` ${verb} Claude Code MCP + hooks`);
22
+ }
23
+ }
24
+ catch (e) {
25
+ log(` Could not configure Claude Code settings (${e}), add manually`);
26
+ }
27
+ let vsStatus = "no_vscode";
28
+ try {
29
+ vsStatus = configureVSCode(phrenPath, { mcpEnabled: opts.mcpEnabled }) ?? "no_vscode";
30
+ logMcpTargetStatus("VS Code", vsStatus, verb);
31
+ }
32
+ catch (err) {
33
+ debugLog(`configureVSCode failed: ${errorMessage(err)}`);
34
+ }
35
+ let cursorStatus = "no_cursor";
36
+ try {
37
+ cursorStatus = configureCursorMcp(phrenPath, { mcpEnabled: opts.mcpEnabled }) ?? "no_cursor";
38
+ logMcpTargetStatus("Cursor", cursorStatus, verb);
39
+ }
40
+ catch (err) {
41
+ debugLog(`configureCursorMcp failed: ${errorMessage(err)}`);
42
+ }
43
+ let copilotStatus = "no_copilot";
44
+ try {
45
+ copilotStatus = configureCopilotMcp(phrenPath, { mcpEnabled: opts.mcpEnabled }) ?? "no_copilot";
46
+ logMcpTargetStatus("Copilot CLI", copilotStatus, verb);
47
+ }
48
+ catch (err) {
49
+ debugLog(`configureCopilotMcp failed: ${errorMessage(err)}`);
50
+ }
51
+ let codexStatus = "no_codex";
52
+ try {
53
+ codexStatus = configureCodexMcp(phrenPath, { mcpEnabled: opts.mcpEnabled }) ?? "no_codex";
54
+ logMcpTargetStatus("Codex", codexStatus, verb);
55
+ }
56
+ catch (err) {
57
+ debugLog(`configureCodexMcp failed: ${errorMessage(err)}`);
58
+ }
59
+ const allStatuses = [claudeStatus, vsStatus, cursorStatus, copilotStatus, codexStatus];
60
+ if (allStatuses.some((s) => s === "installed" || s === "already_configured"))
61
+ return "installed";
62
+ if (allStatuses.some((s) => s === "disabled" || s === "already_disabled"))
63
+ return "disabled";
64
+ return claudeStatus;
65
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * MCP-mode and hooks-mode toggle commands.
3
+ */
4
+ import { configureClaude, configureVSCode, configureCursorMcp, configureCopilotMcp, configureCodexMcp, } from "./init/config.js";
5
+ import { configureAllHooks } from "./hooks.js";
6
+ import { debugLog, findPhrenPath, readRootManifest, } from "./shared.js";
7
+ import { errorMessage } from "./utils.js";
8
+ import { getMcpEnabledPreference, getHooksEnabledPreference, setMcpEnabledPreference, setHooksEnabledPreference, } from "./init/preferences.js";
9
+ import { DEFAULT_PHREN_PATH, log } from "./init/shared.js";
10
+ export function parseMcpMode(raw) {
11
+ if (!raw)
12
+ return undefined;
13
+ const normalized = raw.trim().toLowerCase();
14
+ if (normalized === "on" || normalized === "off")
15
+ return normalized;
16
+ return undefined;
17
+ }
18
+ export async function runMcpMode(modeArg) {
19
+ const phrenPath = findPhrenPath() || (process.env.PHREN_PATH) || DEFAULT_PHREN_PATH;
20
+ const manifest = readRootManifest(phrenPath);
21
+ const normalizedArg = modeArg?.trim().toLowerCase();
22
+ if (!normalizedArg || normalizedArg === "status") {
23
+ const current = getMcpEnabledPreference(phrenPath);
24
+ const hooks = getHooksEnabledPreference(phrenPath);
25
+ log(`MCP mode: ${current ? "on (recommended)" : "off (hooks-only fallback)"}`);
26
+ log(`Hooks mode: ${hooks ? "on (active)" : "off (disabled)"}`);
27
+ log(`Change mode: npx phren mcp-mode on|off`);
28
+ log(`Hooks toggle: npx phren hooks-mode on|off`);
29
+ return;
30
+ }
31
+ const mode = parseMcpMode(normalizedArg);
32
+ if (!mode) {
33
+ throw new Error(`Invalid mode "${modeArg}". Use: on | off | status`);
34
+ }
35
+ const enabled = mode === "on";
36
+ if (manifest?.installMode === "project-local") {
37
+ const vscodeStatus = configureVSCode(phrenPath, { mcpEnabled: enabled, scope: "workspace" });
38
+ setMcpEnabledPreference(phrenPath, enabled);
39
+ log(`MCP mode set to ${mode}.`);
40
+ log(`VS Code status: ${vscodeStatus}`);
41
+ log(`Project-local mode only configures workspace VS Code MCP.`);
42
+ return;
43
+ }
44
+ let claudeStatus = "no_settings";
45
+ let vscodeStatus = "no_vscode";
46
+ let cursorStatus = "no_cursor";
47
+ let copilotStatus = "no_copilot";
48
+ let codexStatus = "no_codex";
49
+ try {
50
+ claudeStatus = configureClaude(phrenPath, { mcpEnabled: enabled }) ?? claudeStatus;
51
+ }
52
+ catch (err) {
53
+ debugLog(`mcp-mode: configureClaude failed: ${errorMessage(err)}`);
54
+ }
55
+ try {
56
+ vscodeStatus = configureVSCode(phrenPath, { mcpEnabled: enabled }) ?? vscodeStatus;
57
+ }
58
+ catch (err) {
59
+ debugLog(`mcp-mode: configureVSCode failed: ${errorMessage(err)}`);
60
+ }
61
+ try {
62
+ cursorStatus = configureCursorMcp(phrenPath, { mcpEnabled: enabled }) ?? cursorStatus;
63
+ }
64
+ catch (err) {
65
+ debugLog(`mcp-mode: configureCursorMcp failed: ${errorMessage(err)}`);
66
+ }
67
+ try {
68
+ copilotStatus = configureCopilotMcp(phrenPath, { mcpEnabled: enabled }) ?? copilotStatus;
69
+ }
70
+ catch (err) {
71
+ debugLog(`mcp-mode: configureCopilotMcp failed: ${errorMessage(err)}`);
72
+ }
73
+ try {
74
+ codexStatus = configureCodexMcp(phrenPath, { mcpEnabled: enabled }) ?? codexStatus;
75
+ }
76
+ catch (err) {
77
+ debugLog(`mcp-mode: configureCodexMcp failed: ${errorMessage(err)}`);
78
+ }
79
+ // Persist preference only after config writes have been attempted
80
+ setMcpEnabledPreference(phrenPath, enabled);
81
+ log(`MCP mode set to ${mode}.`);
82
+ log(`Claude status: ${claudeStatus}`);
83
+ log(`VS Code status: ${vscodeStatus}`);
84
+ log(`Cursor status: ${cursorStatus}`);
85
+ log(`Copilot CLI status: ${copilotStatus}`);
86
+ log(`Codex status: ${codexStatus}`);
87
+ log(`Restart your agent to apply changes.`);
88
+ }
89
+ export async function runHooksMode(modeArg) {
90
+ const phrenPath = findPhrenPath() || (process.env.PHREN_PATH) || DEFAULT_PHREN_PATH;
91
+ const manifest = readRootManifest(phrenPath);
92
+ const normalizedArg = modeArg?.trim().toLowerCase();
93
+ if (!normalizedArg || normalizedArg === "status") {
94
+ const current = getHooksEnabledPreference(phrenPath);
95
+ log(`Hooks mode: ${current ? "on (active)" : "off (disabled)"}`);
96
+ log(`Change mode: npx phren hooks-mode on|off`);
97
+ return;
98
+ }
99
+ const mode = parseMcpMode(normalizedArg);
100
+ if (!mode) {
101
+ throw new Error(`Invalid mode "${modeArg}". Use: on | off | status`);
102
+ }
103
+ if (manifest?.installMode === "project-local") {
104
+ throw new Error("hooks-mode is unsupported in project-local mode");
105
+ }
106
+ const enabled = mode === "on";
107
+ let claudeStatus = "no_settings";
108
+ try {
109
+ claudeStatus = configureClaude(phrenPath, {
110
+ mcpEnabled: getMcpEnabledPreference(phrenPath),
111
+ hooksEnabled: enabled,
112
+ }) ?? claudeStatus;
113
+ }
114
+ catch (err) {
115
+ debugLog(`hooks-mode: configureClaude failed: ${errorMessage(err)}`);
116
+ }
117
+ if (enabled) {
118
+ try {
119
+ const hooked = configureAllHooks(phrenPath, { allTools: true });
120
+ if (hooked.length)
121
+ log(`Updated hooks: ${hooked.join(", ")}`);
122
+ }
123
+ catch (err) {
124
+ debugLog(`hooks-mode: configureAllHooks failed: ${errorMessage(err)}`);
125
+ }
126
+ }
127
+ else {
128
+ log("Hooks will no-op immediately via preference and Claude hooks are removed.");
129
+ }
130
+ // Persist preference only after config writes have been attempted
131
+ setHooksEnabledPreference(phrenPath, enabled);
132
+ log(`Hooks mode set to ${mode}.`);
133
+ log(`Claude status: ${claudeStatus}`);
134
+ log(`Restart your agent to apply changes.`);
135
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Version comparison utilities for phren init and update flows.
3
+ */
4
+ function parseVersion(version) {
5
+ const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?/);
6
+ if (!match)
7
+ return { major: 0, minor: 0, patch: 0, pre: "" };
8
+ return {
9
+ major: Number.parseInt(match[1], 10) || 0,
10
+ minor: Number.parseInt(match[2], 10) || 0,
11
+ patch: Number.parseInt(match[3], 10) || 0,
12
+ pre: match[4] || "",
13
+ };
14
+ }
15
+ /**
16
+ * Compare two semver strings. Returns true when `current` is strictly newer
17
+ * than `previous`. Pre-release versions (e.g. 1.2.3-rc.1) sort before the
18
+ * corresponding release (1.2.3). Among pre-release tags, comparison is
19
+ * lexicographic.
20
+ */
21
+ export function isVersionNewer(current, previous) {
22
+ if (!previous)
23
+ return false;
24
+ const c = parseVersion(current);
25
+ const p = parseVersion(previous);
26
+ if (c.major !== p.major)
27
+ return c.major > p.major;
28
+ if (c.minor !== p.minor)
29
+ return c.minor > p.minor;
30
+ if (c.patch !== p.patch)
31
+ return c.patch > p.patch;
32
+ if (c.pre && !p.pre)
33
+ return false;
34
+ if (!c.pre && p.pre)
35
+ return true;
36
+ return c.pre > p.pre;
37
+ }