@phren/cli 0.0.34 → 0.0.36
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/data/access.js +34 -0
- package/mcp/dist/hooks.js +5 -2
- package/mcp/dist/init/init.js +6 -7
- package/mcp/dist/init/setup.js +20 -0
- package/mcp/dist/shared/fragment-graph.js +1 -1
- package/mcp/dist/shell/entry.js +1 -1
- package/mcp/dist/tools/search.js +0 -1
- package/mcp/dist/ui/data.js +56 -2
- package/package.json +1 -1
package/mcp/dist/data/access.js
CHANGED
|
@@ -113,6 +113,7 @@ export function readFindings(phrenPath, project, opts = {}) {
|
|
|
113
113
|
let date = "unknown";
|
|
114
114
|
let index = 1;
|
|
115
115
|
let inArchiveBlock = false;
|
|
116
|
+
let headingTag;
|
|
116
117
|
const includeArchived = opts.includeArchived ?? false;
|
|
117
118
|
for (let i = 0; i < lines.length; i++) {
|
|
118
119
|
const line = lines[i];
|
|
@@ -134,6 +135,39 @@ export function readFindings(phrenPath, project, opts = {}) {
|
|
|
134
135
|
date = extractedDate;
|
|
135
136
|
continue;
|
|
136
137
|
}
|
|
138
|
+
// Support heading-based findings: ## topic / ### title / paragraph
|
|
139
|
+
const h2TagMatch = line.match(/^##\s+([a-z_-]+)\s*$/i);
|
|
140
|
+
if (h2TagMatch && !line.match(/^##\s+\d{4}/)) {
|
|
141
|
+
// Track topic heading (but not date headings like ## 2026-03-22)
|
|
142
|
+
headingTag = h2TagMatch[1].toLowerCase();
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const h3Match = line.match(/^###\s+(.+)$/);
|
|
146
|
+
if (h3Match && headingTag) {
|
|
147
|
+
let body = "";
|
|
148
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
149
|
+
const next = lines[j].trim();
|
|
150
|
+
if (!next)
|
|
151
|
+
continue;
|
|
152
|
+
if (next.startsWith("#") || next.startsWith("- "))
|
|
153
|
+
break;
|
|
154
|
+
body = next;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
const title = h3Match[1].trim();
|
|
158
|
+
const syntheticText = body ? `[${headingTag}] ${title} — ${body}` : `[${headingTag}] ${title}`;
|
|
159
|
+
items.push({
|
|
160
|
+
id: `L${index}`,
|
|
161
|
+
date,
|
|
162
|
+
text: syntheticText,
|
|
163
|
+
source: "unknown",
|
|
164
|
+
status: "active",
|
|
165
|
+
archived: inArchiveBlock,
|
|
166
|
+
tier: inArchiveBlock ? "archived" : "current",
|
|
167
|
+
});
|
|
168
|
+
index++;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
137
171
|
if (!line.startsWith("- "))
|
|
138
172
|
continue;
|
|
139
173
|
const next = lines[i + 1] || "";
|
package/mcp/dist/hooks.js
CHANGED
|
@@ -218,12 +218,15 @@ export function installPhrenCliWrapper(phrenPath) {
|
|
|
218
218
|
if (!existing.includes("PHREN_CLI_WRAPPER"))
|
|
219
219
|
return false;
|
|
220
220
|
}
|
|
221
|
-
catch {
|
|
221
|
+
catch {
|
|
222
|
+
// File exists but unreadable — don't overwrite, could be a real binary
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
222
225
|
}
|
|
223
226
|
const content = `#!/bin/sh
|
|
224
227
|
# PHREN_CLI_WRAPPER — managed by phren init; safe to delete
|
|
225
228
|
set -u
|
|
226
|
-
PHREN_PATH="\${PHREN_PATH:-${
|
|
229
|
+
PHREN_PATH="\${PHREN_PATH:-${phrenPath}}"
|
|
227
230
|
export PHREN_PATH
|
|
228
231
|
exec node ${shellEscape(entry)} "$@"
|
|
229
232
|
`;
|
package/mcp/dist/init/init.js
CHANGED
|
@@ -118,7 +118,7 @@ export function parseMcpMode(raw) {
|
|
|
118
118
|
function normalizedBootstrapProjectName(projectPath) {
|
|
119
119
|
return path.basename(projectPath).toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
120
120
|
}
|
|
121
|
-
function getPendingBootstrapTarget(phrenPath,
|
|
121
|
+
function getPendingBootstrapTarget(phrenPath, _opts) {
|
|
122
122
|
const cwdProject = detectProjectDir(process.cwd(), phrenPath);
|
|
123
123
|
if (!cwdProject)
|
|
124
124
|
return null;
|
|
@@ -970,13 +970,12 @@ function configureHooksIfEnabled(phrenPath, hooksEnabled, verb) {
|
|
|
970
970
|
log(` Hooks are disabled by preference (run: npx phren hooks-mode on)`);
|
|
971
971
|
}
|
|
972
972
|
// Install phren CLI wrapper at ~/.local/bin/phren so the bare command works
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
}
|
|
973
|
+
const wrapperInstalled = installPhrenCliWrapper(phrenPath);
|
|
974
|
+
if (wrapperInstalled) {
|
|
975
|
+
log(` ${verb} CLI wrapper: ~/.local/bin/phren`);
|
|
977
976
|
}
|
|
978
|
-
|
|
979
|
-
|
|
977
|
+
else {
|
|
978
|
+
log(` Note: phren CLI wrapper not installed (existing non-managed binary, or no entry script found)`);
|
|
980
979
|
}
|
|
981
980
|
}
|
|
982
981
|
export async function runInit(opts = {}) {
|
package/mcp/dist/init/setup.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as path from "path";
|
|
|
6
6
|
import * as os from "os";
|
|
7
7
|
import * as yaml from "js-yaml";
|
|
8
8
|
import { atomicWriteText, debugLog, findProjectNameCaseInsensitive, hookConfigPath, EXEC_TIMEOUT_QUICK_MS, readRootManifest, sessionsDir, runtimeHealthFile, isRecord, } from "../shared.js";
|
|
9
|
+
import { homePath } from "../phren-paths.js";
|
|
9
10
|
import { addProjectToProfile, listProfiles, resolveActiveProfile, setMachineProfile } from "../profile-store.js";
|
|
10
11
|
import { getMachineName } from "../machine-identity.js";
|
|
11
12
|
import { execFileSync } from "child_process";
|
|
@@ -1271,6 +1272,25 @@ export function runPostInitVerify(phrenPath) {
|
|
|
1271
1272
|
fix: ftsOk ? undefined : "Create a project: `cd ~/your-project && phren add`",
|
|
1272
1273
|
});
|
|
1273
1274
|
checks.push(getHookEntrypointCheck());
|
|
1275
|
+
// Check that the CLI wrapper at ~/.local/bin/phren exists and is executable (soft check — not mandatory)
|
|
1276
|
+
const cliWrapperPath = homePath(".local", "bin", "phren");
|
|
1277
|
+
let cliWrapperOk = false;
|
|
1278
|
+
try {
|
|
1279
|
+
const stat = fs.statSync(cliWrapperPath);
|
|
1280
|
+
cliWrapperOk = stat.isFile();
|
|
1281
|
+
if (cliWrapperOk)
|
|
1282
|
+
fs.accessSync(cliWrapperPath, fs.constants.X_OK);
|
|
1283
|
+
}
|
|
1284
|
+
catch {
|
|
1285
|
+
cliWrapperOk = false;
|
|
1286
|
+
}
|
|
1287
|
+
checks.push({
|
|
1288
|
+
name: "cli-wrapper",
|
|
1289
|
+
ok: true, // always pass — wrapper is optional (global install or npx work too)
|
|
1290
|
+
detail: cliWrapperOk
|
|
1291
|
+
? `CLI wrapper exists: ${cliWrapperPath}`
|
|
1292
|
+
: `CLI wrapper not found (optional — use 'npm i -g @phren/cli' or 'npx @phren/cli' instead)`,
|
|
1293
|
+
});
|
|
1274
1294
|
const ok = checks.every((c) => c.ok);
|
|
1275
1295
|
return { ok, checks };
|
|
1276
1296
|
}
|
|
@@ -232,7 +232,7 @@ function parseUserDefinedFragments(phrenPath, project) {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
/** Clear the user fragment cache (call between index builds). */
|
|
235
|
-
function
|
|
235
|
+
function _clearUserFragmentCache() {
|
|
236
236
|
_userFragmentCache.clear();
|
|
237
237
|
_buildUserFragmentCache.clear();
|
|
238
238
|
_activeBuildCacheKeyPrefix = null;
|
package/mcp/dist/shell/entry.js
CHANGED
|
@@ -63,7 +63,7 @@ async function playStartupIntro(phrenPath, plan = resolveStartupIntroPlan(phrenP
|
|
|
63
63
|
// Start animated phren during loading
|
|
64
64
|
const animator = createPhrenAnimator({ facing: "right" });
|
|
65
65
|
animator.start();
|
|
66
|
-
const
|
|
66
|
+
const _cols = process.stdout.columns || 80;
|
|
67
67
|
const tagline = style.dim("local memory for working agents");
|
|
68
68
|
const versionBadge = badge(`v${VERSION}`, style.boldBlue);
|
|
69
69
|
const logoLines = [
|
package/mcp/dist/tools/search.js
CHANGED
|
@@ -456,7 +456,6 @@ async function handleSearchKnowledge(ctx, { query, limit, project, type, tag, si
|
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
458
|
async function handleGetProjectSummary(ctx, { name }) {
|
|
459
|
-
const { phrenPath } = ctx;
|
|
460
459
|
const db = ctx.db();
|
|
461
460
|
const docs = queryDocRows(db, "SELECT project, filename, type, content, path FROM docs WHERE project = ?", [name]);
|
|
462
461
|
if (!docs) {
|
package/mcp/dist/ui/data.js
CHANGED
|
@@ -194,8 +194,62 @@ export async function buildGraph(phrenPath, profile, focusProject, existingDb) {
|
|
|
194
194
|
const MAX_UNTAGGED = isFocused ? Infinity : 100;
|
|
195
195
|
let taggedCount = 0;
|
|
196
196
|
let untaggedAdded = 0;
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
// Support heading-based findings: ## topic / ### title / paragraph
|
|
198
|
+
let currentHeadingTag;
|
|
199
|
+
let _currentHeadingTitle;
|
|
200
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
201
|
+
const line = lines[lineIdx];
|
|
202
|
+
// Track heading context for heading-based findings
|
|
203
|
+
const h2Match = line.match(/^##\s+([a-z_-]+)\s*$/i);
|
|
204
|
+
if (h2Match) {
|
|
205
|
+
currentHeadingTag = h2Match[1].toLowerCase();
|
|
206
|
+
_currentHeadingTitle = undefined;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const h3Match = line.match(/^###\s+(.+)$/);
|
|
210
|
+
if (h3Match && currentHeadingTag) {
|
|
211
|
+
// Read the next non-empty line as the body
|
|
212
|
+
let body = "";
|
|
213
|
+
for (let j = lineIdx + 1; j < lines.length; j++) {
|
|
214
|
+
const next = lines[j].trim();
|
|
215
|
+
if (!next)
|
|
216
|
+
continue;
|
|
217
|
+
if (next.startsWith("#"))
|
|
218
|
+
break;
|
|
219
|
+
body = next;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
const title = h3Match[1].trim();
|
|
223
|
+
const text = body ? `${title} — ${body}` : title;
|
|
224
|
+
if (text.length >= 10) {
|
|
225
|
+
if (taggedCount >= MAX_TAGGED)
|
|
226
|
+
continue;
|
|
227
|
+
const topic = classifyTopicForText(`[${currentHeadingTag}] ${text}`, projectTopics);
|
|
228
|
+
const scoreKey = entryScoreKey(project, "FINDINGS.md", `[${currentHeadingTag}] ${text}`);
|
|
229
|
+
const nodeId = stableId("finding", scoreKey);
|
|
230
|
+
taggedCount++;
|
|
231
|
+
nodes.push({
|
|
232
|
+
id: nodeId,
|
|
233
|
+
label: text.length > 55 ? `${text.slice(0, 52)}...` : text,
|
|
234
|
+
fullLabel: text,
|
|
235
|
+
group: `topic:${topic.slug}`,
|
|
236
|
+
refCount: taggedCount,
|
|
237
|
+
project,
|
|
238
|
+
tagged: true,
|
|
239
|
+
scoreKey,
|
|
240
|
+
scoreKeys: [scoreKey],
|
|
241
|
+
refDocs: [{ doc: `${project}/FINDINGS.md`, project, scoreKey }],
|
|
242
|
+
topicSlug: topic.slug,
|
|
243
|
+
topicLabel: topic.label,
|
|
244
|
+
});
|
|
245
|
+
links.push({ source: project, target: nodeId });
|
|
246
|
+
for (const other of exactProjectMentions(text, projectSet, project)) {
|
|
247
|
+
links.push({ source: project, target: other });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// Standard bullet-based findings: - [tag] text
|
|
199
253
|
const tagMatch = line.match(/^-\s+\[([a-z_-]+)\]\s+(.+?)(?:\s*<!--.*-->)?$/);
|
|
200
254
|
if (tagMatch) {
|
|
201
255
|
if (taggedCount >= MAX_TAGGED)
|