@phren/cli 0.0.8 → 0.0.10
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/icon.svg +416 -0
- package/mcp/dist/capabilities/mcp.js +5 -5
- package/mcp/dist/capabilities/vscode.js +3 -3
- package/mcp/dist/cli-actions.js +113 -3
- package/mcp/dist/cli-govern.js +166 -1
- package/mcp/dist/cli-hooks.js +2 -2
- package/mcp/dist/cli-namespaces.js +497 -4
- package/mcp/dist/cli.js +7 -1
- package/mcp/dist/content-citation.js +12 -22
- package/mcp/dist/data-access.js +1 -1
- package/mcp/dist/data-tasks.js +39 -1
- package/mcp/dist/entrypoint.js +6 -0
- package/mcp/dist/mcp-finding.js +26 -1
- package/mcp/dist/mcp-tasks.js +23 -2
- package/mcp/dist/memory-ui-assets.js +37 -1
- package/mcp/dist/memory-ui-graph.js +19 -5
- package/mcp/dist/memory-ui-page.js +7 -30
- package/mcp/dist/memory-ui-scripts.js +1 -103
- package/mcp/dist/memory-ui-server.js +1 -82
- package/mcp/dist/shared-retrieval.js +7 -47
- package/mcp/dist/shell-input.js +4 -22
- package/mcp/dist/shell-render.js +2 -2
- package/mcp/dist/shell-view.js +1 -2
- package/mcp/dist/shell.js +0 -5
- package/mcp/dist/tool-registry.js +1 -0
- package/package.json +2 -1
- package/skills/docs.md +170 -0
package/mcp/dist/cli-govern.js
CHANGED
|
@@ -227,6 +227,167 @@ export async function handleConsolidateMemories(args = []) {
|
|
|
227
227
|
return;
|
|
228
228
|
console.log(`Updated backups (${backups.length}): ${backups.join(", ")}`);
|
|
229
229
|
}
|
|
230
|
+
export async function handleGcMaintain(args = []) {
|
|
231
|
+
const dryRun = args.includes("--dry-run");
|
|
232
|
+
const phrenPath = getPhrenPath();
|
|
233
|
+
const { execSync } = await import("child_process");
|
|
234
|
+
const report = {
|
|
235
|
+
gitGcRan: false,
|
|
236
|
+
commitsSquashed: 0,
|
|
237
|
+
sessionsRemoved: 0,
|
|
238
|
+
runtimeLogsRemoved: 0,
|
|
239
|
+
};
|
|
240
|
+
// 1. Run git gc --aggressive on the ~/.phren repo
|
|
241
|
+
if (dryRun) {
|
|
242
|
+
console.log("[dry-run] Would run: git gc --aggressive");
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
try {
|
|
246
|
+
execSync("git gc --aggressive --quiet", { cwd: phrenPath, stdio: "pipe" });
|
|
247
|
+
report.gitGcRan = true;
|
|
248
|
+
console.log("git gc --aggressive: done");
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
console.error(`git gc failed: ${errorMessage(err)}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// 2. Squash old auto-save commits (older than 7 days) into weekly summaries
|
|
255
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10);
|
|
256
|
+
let oldCommits = [];
|
|
257
|
+
try {
|
|
258
|
+
const raw = execSync(`git log --oneline --before="${sevenDaysAgo}" --format="%H %s"`, { cwd: phrenPath, encoding: "utf8" }).trim();
|
|
259
|
+
if (raw) {
|
|
260
|
+
oldCommits = raw.split("\n").filter((l) => l.includes("auto-save:") || l.includes("[auto]"));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Not a git repo or no commits — skip silently
|
|
265
|
+
}
|
|
266
|
+
if (oldCommits.length === 0) {
|
|
267
|
+
console.log("Commit squash: no old auto-save commits to squash.");
|
|
268
|
+
}
|
|
269
|
+
else if (dryRun) {
|
|
270
|
+
console.log(`[dry-run] Would squash ${oldCommits.length} auto-save commits older than 7 days into weekly summaries.`);
|
|
271
|
+
report.commitsSquashed = oldCommits.length;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
// Group by ISO week (YYYY-Www) based on commit timestamp
|
|
275
|
+
const commitsByWeek = new Map();
|
|
276
|
+
for (const line of oldCommits) {
|
|
277
|
+
const hash = line.split(" ")[0];
|
|
278
|
+
try {
|
|
279
|
+
const dateStr = execSync(`git log -1 --format="%ci" ${hash}`, { cwd: phrenPath, encoding: "utf8" }).trim();
|
|
280
|
+
const date = new Date(dateStr);
|
|
281
|
+
const weekStart = new Date(date);
|
|
282
|
+
weekStart.setDate(date.getDate() - date.getDay()); // start of week (Sunday)
|
|
283
|
+
const weekKey = weekStart.toISOString().slice(0, 10);
|
|
284
|
+
if (!commitsByWeek.has(weekKey))
|
|
285
|
+
commitsByWeek.set(weekKey, []);
|
|
286
|
+
commitsByWeek.get(weekKey).push(hash);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// Skip commits we can't resolve
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// For each week with multiple commits, soft-reset to oldest and amend into a summary
|
|
293
|
+
for (const [weekKey, hashes] of commitsByWeek.entries()) {
|
|
294
|
+
if (hashes.length < 2)
|
|
295
|
+
continue;
|
|
296
|
+
try {
|
|
297
|
+
const oldest = hashes[hashes.length - 1];
|
|
298
|
+
const newest = hashes[0];
|
|
299
|
+
// Use git rebase --onto to squash: squash all into the oldest parent
|
|
300
|
+
const parentOfOldest = execSync(`git rev-parse ${oldest}^`, { cwd: phrenPath, encoding: "utf8" }).trim();
|
|
301
|
+
// Build rebase script via env variable to squash all but first to "squash"
|
|
302
|
+
const rebaseScript = hashes
|
|
303
|
+
.map((h, i) => `${i === hashes.length - 1 ? "pick" : "squash"} ${h} auto-save`)
|
|
304
|
+
.reverse()
|
|
305
|
+
.join("\n");
|
|
306
|
+
const scriptPath = path.join(phrenPath, ".runtime", `gc-rebase-${weekKey}.sh`);
|
|
307
|
+
fs.writeFileSync(scriptPath, rebaseScript);
|
|
308
|
+
// Use GIT_SEQUENCE_EDITOR to feed our script
|
|
309
|
+
execSync(`GIT_SEQUENCE_EDITOR="cat ${scriptPath} >" git rebase -i ${parentOfOldest}`, { cwd: phrenPath, stdio: "pipe" });
|
|
310
|
+
fs.unlinkSync(scriptPath);
|
|
311
|
+
report.commitsSquashed += hashes.length - 1;
|
|
312
|
+
console.log(`Squashed ${hashes.length} auto-save commits for week of ${weekKey} (${newest.slice(0, 7)}..${oldest.slice(0, 7)}).`);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Squashing is best-effort — log and continue
|
|
316
|
+
console.warn(` Could not squash auto-save commits for week ${weekKey} (possibly non-linear history). Skipping.`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (report.commitsSquashed === 0) {
|
|
320
|
+
console.log("Commit squash: all old auto-save weeks have only one commit, nothing to squash.");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// 3. Prune stale session markers from ~/.phren/.sessions/ older than 30 days
|
|
324
|
+
const sessionsDir = path.join(phrenPath, ".sessions");
|
|
325
|
+
const thirtyDaysAgo = Date.now() - 30 * 86400000;
|
|
326
|
+
if (fs.existsSync(sessionsDir)) {
|
|
327
|
+
const entries = fs.readdirSync(sessionsDir);
|
|
328
|
+
for (const entry of entries) {
|
|
329
|
+
const fullPath = path.join(sessionsDir, entry);
|
|
330
|
+
try {
|
|
331
|
+
const stat = fs.statSync(fullPath);
|
|
332
|
+
if (stat.mtimeMs < thirtyDaysAgo) {
|
|
333
|
+
if (dryRun) {
|
|
334
|
+
console.log(`[dry-run] Would remove session marker: .sessions/${entry}`);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
fs.unlinkSync(fullPath);
|
|
338
|
+
}
|
|
339
|
+
report.sessionsRemoved++;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
// Skip unreadable entries
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const sessionsVerb = dryRun ? "Would remove" : "Removed";
|
|
348
|
+
console.log(`${sessionsVerb} ${report.sessionsRemoved} stale session marker(s) from .sessions/`);
|
|
349
|
+
// 4. Trim runtime logs from ~/.phren/.runtime/ older than 30 days
|
|
350
|
+
const runtimeDir = path.join(phrenPath, ".runtime");
|
|
351
|
+
const logExtensions = new Set([".log", ".jsonl", ".json"]);
|
|
352
|
+
if (fs.existsSync(runtimeDir)) {
|
|
353
|
+
const entries = fs.readdirSync(runtimeDir);
|
|
354
|
+
for (const entry of entries) {
|
|
355
|
+
const ext = path.extname(entry);
|
|
356
|
+
if (!logExtensions.has(ext))
|
|
357
|
+
continue;
|
|
358
|
+
// Never trim the active audit log or telemetry config
|
|
359
|
+
if (entry === "audit.log" || entry === "telemetry.json")
|
|
360
|
+
continue;
|
|
361
|
+
const fullPath = path.join(runtimeDir, entry);
|
|
362
|
+
try {
|
|
363
|
+
const stat = fs.statSync(fullPath);
|
|
364
|
+
if (stat.mtimeMs < thirtyDaysAgo) {
|
|
365
|
+
if (dryRun) {
|
|
366
|
+
console.log(`[dry-run] Would remove runtime log: .runtime/${entry}`);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
fs.unlinkSync(fullPath);
|
|
370
|
+
}
|
|
371
|
+
report.runtimeLogsRemoved++;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Skip unreadable entries
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const logsVerb = dryRun ? "Would remove" : "Removed";
|
|
380
|
+
console.log(`${logsVerb} ${report.runtimeLogsRemoved} stale runtime log(s) from .runtime/`);
|
|
381
|
+
// 5. Summary
|
|
382
|
+
if (!dryRun) {
|
|
383
|
+
appendAuditLog(phrenPath, "maintain_gc", `gitGc=${report.gitGcRan} squashed=${report.commitsSquashed} sessions=${report.sessionsRemoved} logs=${report.runtimeLogsRemoved}`);
|
|
384
|
+
}
|
|
385
|
+
console.log(`\nGC complete:${dryRun ? " (dry-run)" : ""}` +
|
|
386
|
+
` git_gc=${report.gitGcRan}` +
|
|
387
|
+
` commits_squashed=${report.commitsSquashed}` +
|
|
388
|
+
` sessions_pruned=${report.sessionsRemoved}` +
|
|
389
|
+
` logs_pruned=${report.runtimeLogsRemoved}`);
|
|
390
|
+
}
|
|
230
391
|
// ── Maintain router ──────────────────────────────────────────────────────────
|
|
231
392
|
export async function handleMaintain(args) {
|
|
232
393
|
const sub = args[0];
|
|
@@ -245,6 +406,8 @@ export async function handleMaintain(args) {
|
|
|
245
406
|
return handleExtractMemories(rest[0]);
|
|
246
407
|
case "restore":
|
|
247
408
|
return handleRestoreBackup(rest);
|
|
409
|
+
case "gc":
|
|
410
|
+
return handleGcMaintain(rest);
|
|
248
411
|
default:
|
|
249
412
|
console.log(`phren maintain - memory maintenance and governance
|
|
250
413
|
|
|
@@ -258,7 +421,9 @@ Subcommands:
|
|
|
258
421
|
Deduplicate FINDINGS.md bullets. Run after a burst of work
|
|
259
422
|
when findings feel repetitive, or monthly to keep things clean.
|
|
260
423
|
phren maintain extract [project] Mine git/GitHub signals into memory candidates
|
|
261
|
-
phren maintain restore [project] List and restore from .bak files
|
|
424
|
+
phren maintain restore [project] List and restore from .bak files
|
|
425
|
+
phren maintain gc [--dry-run] Garbage-collect the ~/.phren repo: git gc, squash old
|
|
426
|
+
auto-save commits, prune stale session markers and runtime logs`);
|
|
262
427
|
if (sub) {
|
|
263
428
|
console.error(`\nUnknown maintain subcommand: "${sub}"`);
|
|
264
429
|
process.exit(1);
|
package/mcp/dist/cli-hooks.js
CHANGED
|
@@ -345,7 +345,7 @@ export async function handleHookPrompt() {
|
|
|
345
345
|
parts.push(`Findings ready for consolidation:`);
|
|
346
346
|
parts.push(notices.join("\n"));
|
|
347
347
|
parts.push(`Run phren-consolidate when ready.`);
|
|
348
|
-
parts.push(
|
|
348
|
+
parts.push(`<phren-notice>`);
|
|
349
349
|
}
|
|
350
350
|
if (noticeFile) {
|
|
351
351
|
try {
|
|
@@ -367,7 +367,7 @@ export async function handleHookPrompt() {
|
|
|
367
367
|
}
|
|
368
368
|
catch (err) {
|
|
369
369
|
const msg = errorMessage(err);
|
|
370
|
-
process.stdout.write(`\n<phren-error>phren hook failed: ${msg}. Check ~/.phren/.runtime/debug.log for details
|
|
370
|
+
process.stdout.write(`\n<phren-error>phren hook failed: ${msg}. Check ~/.phren/.runtime/debug.log for details.<phren-error>\n`);
|
|
371
371
|
debugLog(`hook-prompt error: ${msg}`);
|
|
372
372
|
process.exit(0);
|
|
373
373
|
}
|