@phren/cli 0.0.52 → 0.0.54
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/README.md +84 -28
- package/mcp/dist/cli/namespaces.js +42 -2
- package/mcp/dist/content/validate.js +2 -3
- package/mcp/dist/data/access.js +2 -3
- package/mcp/dist/data/tasks.js +2 -2
- package/mcp/dist/generated/memory-ui-graph.browser.js +28 -25
- package/mcp/dist/memory-ui-graph.runtime.js +28 -25
- package/mcp/dist/profile-store.js +2 -3
- package/mcp/dist/shared/index.js +2 -2
- package/mcp/dist/shell/view.js +5 -3
- package/mcp/dist/store-registry.js +41 -0
- package/mcp/dist/tools/data.js +8 -7
- package/mcp/dist/tools/search.js +8 -9
- package/mcp/dist/tools/session.js +2 -2
- package/mcp/dist/ui/data.js +25 -9
- package/mcp/dist/ui/page.js +1 -0
- package/mcp/dist/ui/scripts.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
|
|
13
|
+
Persistent memory for AI agents. Findings, tasks, and patterns live in markdown files in a git repo you control. No database, no vendor lock-in. Works with Claude, Copilot, Cursor, and Codex.
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
---
|
|
@@ -21,52 +21,108 @@ Every time you start a new session, your AI agent forgets everything it learned.
|
|
|
21
21
|
npx @phren/cli init
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
One command. Sets up `~/.phren`, wires up MCP for your tools, installs hooks. Next time you open a project, context starts flowing automatically. On a new machine? Re-run init and you're back in sync.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
---
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
- **Fragments**: named concepts (auth, build, React) that connect findings across projects. Search for a topic and phren pulls in everything linked to that fragment
|
|
30
|
-
- **Tasks**: work items that persist across sessions with priority, pinning, and GitHub issue linking
|
|
31
|
-
- **Sessions**: conversation boundaries with summaries and checkpoints, so the next session picks up where the last one left off
|
|
32
|
-
- **Skills**: reusable slash commands you teach phren. Drop them in `~/.phren/global/skills/` and they work everywhere
|
|
28
|
+
## What actually happens
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
**When you open a prompt:**
|
|
31
|
+
- Hooks extract keywords from your question
|
|
32
|
+
- Phren searches findings across projects (FTS5 full-text with semantic fallback)
|
|
33
|
+
- Relevant snippets inject into your prompt before you hit send
|
|
34
|
+
- You ask; Claude already knows the gotchas
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
- **Shell and web UI** for browsing, searching, and triaging (`phren` or `phren web-ui`)
|
|
36
|
+
**When you discover something:**
|
|
37
|
+
- `phren add-finding <project> "finding text"` captures it with optional tags (`[decision]`, `[pattern]`, `[pitfall]`, `[bug]`)
|
|
38
|
+
- Trust scores decay over time; decisions never do; observations expire in 14 days
|
|
39
|
+
- Findings link to fragments (named concepts like "auth" or "build") that connect knowledge across projects
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
**Sessions:**
|
|
42
|
+
- Mark boundaries with `session_start` / `session_end`
|
|
43
|
+
- Next session sees your prior summary, active tasks, recent findings, and where you left off
|
|
44
|
+
- Checkpoints track edited files and failing tests so you can resume exactly where you stopped
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
**Tasks:**
|
|
47
|
+
- Add with priority/section. Pin across sessions. Link to GitHub issues.
|
|
48
|
+
- Track completions and cross-project rollups.
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
---
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
## Key features
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
### Fragment graph
|
|
55
|
+
Explore connections visually. Drag nodes to reorganize; graph auto-settles. Click a fragment to see every finding linked to it across all projects.
|
|
56
|
+
|
|
57
|
+
### Finding lifecycle
|
|
58
|
+
- **Supersede**: "Finding X is obsoleted by finding Y"
|
|
59
|
+
- **Retract**: "We were wrong about this; here's why"
|
|
60
|
+
- **Contradict**: "We have two findings that conflict; this is why"
|
|
53
61
|
|
|
54
|
-
|
|
62
|
+
Helps you reason about contradictions instead of hiding them.
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
### Multi-agent support
|
|
65
|
+
Same store works with Claude Code, Copilot, Cursor, and Codex. Agents tag findings with their tool, so you see who discovered what.
|
|
57
66
|
|
|
67
|
+
### Review queue
|
|
68
|
+
Mark findings as needing review (`[Review]` section). Phren surfaces review items on every session start. Approve, reject, or edit in place.
|
|
69
|
+
|
|
70
|
+
### Governance & policies
|
|
71
|
+
Per-project retention policies. Confidence decay curves. Access control. Audit logs. Configure with `phren config` or the web UI.
|
|
72
|
+
|
|
73
|
+
### Store subscriptions
|
|
74
|
+
Subscribe to specific projects in a team store — others stay hidden from search and context injection:
|
|
58
75
|
```bash
|
|
59
|
-
phren
|
|
60
|
-
phren
|
|
76
|
+
phren store subscribe team-store arc intranet
|
|
77
|
+
phren store unsubscribe team-store legacy-projects
|
|
61
78
|
```
|
|
62
79
|
|
|
63
|
-
|
|
80
|
+
### Progressive disclosure
|
|
81
|
+
Enable `PHREN_FEATURE_PROGRESSIVE_DISCLOSURE=1` to get compact memory indices instead of full snippets. Call `get_memory_detail(id)` to expand only what you need.
|
|
82
|
+
|
|
83
|
+
### Semantic dedup & conflict detection
|
|
84
|
+
Optional: enable LLM-based duplicate detection and contradiction flagging on `add_finding`. Prevents near-duplicate entries and catches "always use X" vs "never use X" contradictions.
|
|
85
|
+
|
|
86
|
+
### Skills & hooks
|
|
87
|
+
Drop custom slash commands into `~/.phren/global/skills/`. Hooks run on user prompt, tool use, and session events — wire phren into your own workflows.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## CLI quick reference
|
|
64
92
|
|
|
65
93
|
```bash
|
|
66
|
-
phren
|
|
94
|
+
phren Interactive shell (explore/search)
|
|
95
|
+
phren search <query> Full-text search with FTS5
|
|
96
|
+
phren add-finding <project> "insight" Capture a finding
|
|
97
|
+
phren task add <project> "item" Add a task
|
|
98
|
+
phren session_start <project> Start a session
|
|
99
|
+
phren store list List personal + team stores
|
|
100
|
+
phren team init <name> --remote <url> Create a team store
|
|
101
|
+
phren team join <url> Join a team store
|
|
102
|
+
phren web-ui [--port 3499] Launch the web UI
|
|
103
|
+
phren doctor Health check & auto-fix
|
|
67
104
|
```
|
|
68
105
|
|
|
69
|
-
|
|
106
|
+
See full CLI docs at [alaarab.github.io/phren](https://alaarab.github.io/phren/).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Team stores
|
|
111
|
+
|
|
112
|
+
Shared knowledge repos for teams. One person creates with `phren team init`, others join with `phren team join`. Findings, tasks, and skills sync across team members.
|
|
113
|
+
|
|
114
|
+
Each team store can be configured with per-project subscriptions so people only see what they care about.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Platforms
|
|
119
|
+
|
|
120
|
+
- **Claude Code** (VS Code, Web, Desktop) — MCP hooks + CLI
|
|
121
|
+
- **Copilot** (VS Code, GitHub.com) — MCP hooks
|
|
122
|
+
- **Cursor** (IDE) — MCP hooks + built-in skill system
|
|
123
|
+
- **Codex** (Claude Agent SDK) — MCP tools + hooks
|
|
124
|
+
|
|
125
|
+
All use the same phren store. No vendor lock-in.
|
|
70
126
|
|
|
71
127
|
---
|
|
72
128
|
|
|
@@ -1535,6 +1535,8 @@ function printStoreUsage() {
|
|
|
1535
1535
|
console.log(" phren store remove <name> Remove a store (local only)");
|
|
1536
1536
|
console.log(" phren store sync Pull all stores");
|
|
1537
1537
|
console.log(" phren store activity [--limit N] Recent team findings");
|
|
1538
|
+
console.log(" phren store subscribe <name> <project...> Subscribe store to projects");
|
|
1539
|
+
console.log(" phren store unsubscribe <name> <project...> Unsubscribe store from projects");
|
|
1538
1540
|
}
|
|
1539
1541
|
export async function handleStoreNamespace(args) {
|
|
1540
1542
|
const subcommand = args[0];
|
|
@@ -1669,7 +1671,8 @@ export async function handleStoreNamespace(args) {
|
|
|
1669
1671
|
for (const store of teamStores) {
|
|
1670
1672
|
if (!fs.existsSync(store.path))
|
|
1671
1673
|
continue;
|
|
1672
|
-
const
|
|
1674
|
+
const { getStoreProjectDirs } = await import("../store-registry.js");
|
|
1675
|
+
const projectDirs = getStoreProjectDirs(store);
|
|
1673
1676
|
for (const dir of projectDirs) {
|
|
1674
1677
|
const projectName = path.basename(dir);
|
|
1675
1678
|
const journalEntries = readTeamJournalEntries(store.path, projectName);
|
|
@@ -1728,6 +1731,42 @@ export async function handleStoreNamespace(args) {
|
|
|
1728
1731
|
}
|
|
1729
1732
|
return;
|
|
1730
1733
|
}
|
|
1734
|
+
if (subcommand === "subscribe") {
|
|
1735
|
+
const storeName = args[1];
|
|
1736
|
+
const projects = args.slice(2);
|
|
1737
|
+
if (!storeName || projects.length === 0) {
|
|
1738
|
+
console.error("Usage: phren store subscribe <store-name> <project1> [project2...]");
|
|
1739
|
+
process.exit(1);
|
|
1740
|
+
}
|
|
1741
|
+
try {
|
|
1742
|
+
const { subscribeStoreProjects } = await import("../store-registry.js");
|
|
1743
|
+
subscribeStoreProjects(phrenPath, storeName, projects);
|
|
1744
|
+
console.log(`Added ${projects.length} project(s) to "${storeName}"`);
|
|
1745
|
+
}
|
|
1746
|
+
catch (err) {
|
|
1747
|
+
console.error(`Failed to subscribe: ${errorMessage(err)}`);
|
|
1748
|
+
process.exit(1);
|
|
1749
|
+
}
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
if (subcommand === "unsubscribe") {
|
|
1753
|
+
const storeName = args[1];
|
|
1754
|
+
const projects = args.slice(2);
|
|
1755
|
+
if (!storeName || projects.length === 0) {
|
|
1756
|
+
console.error("Usage: phren store unsubscribe <store-name> <project1> [project2...]");
|
|
1757
|
+
process.exit(1);
|
|
1758
|
+
}
|
|
1759
|
+
try {
|
|
1760
|
+
const { unsubscribeStoreProjects } = await import("../store-registry.js");
|
|
1761
|
+
unsubscribeStoreProjects(phrenPath, storeName, projects);
|
|
1762
|
+
console.log(`Removed ${projects.length} project(s) from "${storeName}"`);
|
|
1763
|
+
}
|
|
1764
|
+
catch (err) {
|
|
1765
|
+
console.error(`Failed to unsubscribe: ${errorMessage(err)}`);
|
|
1766
|
+
process.exit(1);
|
|
1767
|
+
}
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1731
1770
|
console.error(`Unknown store subcommand: ${subcommand}`);
|
|
1732
1771
|
printStoreUsage();
|
|
1733
1772
|
process.exit(1);
|
|
@@ -1801,7 +1840,8 @@ function countStoreProjects(store) {
|
|
|
1801
1840
|
if (!fs.existsSync(store.path))
|
|
1802
1841
|
return 0;
|
|
1803
1842
|
try {
|
|
1804
|
-
|
|
1843
|
+
const storeRegistry = require("../store-registry.js");
|
|
1844
|
+
return storeRegistry.getStoreProjectDirs(store).length;
|
|
1805
1845
|
}
|
|
1806
1846
|
catch {
|
|
1807
1847
|
return 0;
|
|
@@ -7,6 +7,7 @@ import { errorMessage } from "../utils.js";
|
|
|
7
7
|
import { countActiveFindings } from "./archive.js";
|
|
8
8
|
import { isTaskFileName } from "../data/tasks.js";
|
|
9
9
|
import { METADATA_REGEX } from "./metadata.js";
|
|
10
|
+
import { getNonPrimaryStores, getStoreProjectDirs } from "../store-registry.js";
|
|
10
11
|
/** Maximum allowed length for a single finding entry (token budget protection). */
|
|
11
12
|
export const MAX_FINDING_LENGTH = 2000;
|
|
12
13
|
function safeParseDate(s) {
|
|
@@ -76,12 +77,10 @@ export function checkConsolidationNeeded(phrenPath, profile) {
|
|
|
76
77
|
}
|
|
77
78
|
// Include projects from team stores
|
|
78
79
|
try {
|
|
79
|
-
const storeRegistry = require("../store-registry.js");
|
|
80
|
-
const { getNonPrimaryStores } = storeRegistry;
|
|
81
80
|
for (const store of getNonPrimaryStores(phrenPath)) {
|
|
82
81
|
if (!fs.existsSync(store.path))
|
|
83
82
|
continue;
|
|
84
|
-
for (const dir of
|
|
83
|
+
for (const dir of getStoreProjectDirs(store)) {
|
|
85
84
|
const status = getProjectConsolidationStatus(dir);
|
|
86
85
|
if (status && status.recommended) {
|
|
87
86
|
results.push(status);
|
package/mcp/dist/data/access.js
CHANGED
|
@@ -12,6 +12,7 @@ import { parseCitationComment, parseSourceComment, } from "../content/citation.j
|
|
|
12
12
|
import { parseFindingLifecycle, } from "../finding/lifecycle.js";
|
|
13
13
|
import { METADATA_REGEX, isCitationLine, isArchiveStart, isArchiveEnd, parseFindingId, parseAllContradictions, stripComments, normalizeFindingText, } from "../content/metadata.js";
|
|
14
14
|
import { withSafeLock, ensureProject } from "../shared/data-utils.js";
|
|
15
|
+
import { getNonPrimaryStores, getStoreProjectDirs } from "../store-registry.js";
|
|
15
16
|
export { readTasks, readTasksAcrossProjects, resolveTaskItem, addTask, addTasks, completeTasks, completeTask, removeTask, removeTasks, updateTask, linkTaskIssue, pinTask, unpinTask, workNextTask, tidyDoneTasks, taskMarkdown, appendChildFinding, promoteTask, TASKS_FILENAME, TASK_FILE_ALIASES, canonicalTaskFilePath, resolveTaskFilePath, isTaskFileName, } from "./tasks.js";
|
|
16
17
|
export { addProjectToProfile, listMachines, listProfiles, listProjectCards, removeProjectFromProfile, setMachineProfile, } from "../profile-store.js";
|
|
17
18
|
export { loadShellState, resetShellState, saveShellState, } from "../shell/state-store.js";
|
|
@@ -570,12 +571,10 @@ export function readReviewQueueAcrossProjects(phrenPath, profile) {
|
|
|
570
571
|
}
|
|
571
572
|
// Include projects from team stores
|
|
572
573
|
try {
|
|
573
|
-
const storeRegistry = require("../store-registry.js");
|
|
574
|
-
const { getNonPrimaryStores } = storeRegistry;
|
|
575
574
|
for (const store of getNonPrimaryStores(phrenPath)) {
|
|
576
575
|
if (!fs.existsSync(store.path))
|
|
577
576
|
continue;
|
|
578
|
-
const storeDirs =
|
|
577
|
+
const storeDirs = getStoreProjectDirs(store)
|
|
579
578
|
.map((d) => path.basename(d))
|
|
580
579
|
.filter((p) => p !== "global");
|
|
581
580
|
for (const storeProject of storeDirs) {
|
package/mcp/dist/data/tasks.js
CHANGED
|
@@ -5,6 +5,7 @@ import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, } from "../s
|
|
|
5
5
|
import { validateTaskFormat } from "../shared/content.js";
|
|
6
6
|
import { safeProjectPath } from "../utils.js";
|
|
7
7
|
import { withSafeLock, ensureProject } from "../shared/data-utils.js";
|
|
8
|
+
import { getNonPrimaryStores, getStoreProjectDirs } from "../store-registry.js";
|
|
8
9
|
const ACTIVE_HEADINGS = new Set(["active", "in progress", "in-progress", "current", "wip"]);
|
|
9
10
|
const QUEUE_HEADINGS = new Set(["queue", "queued", "task", "todo", "upcoming", "next"]);
|
|
10
11
|
const DONE_HEADINGS = new Set(["done", "completed", "finished", "archived"]);
|
|
@@ -368,11 +369,10 @@ export function readTasksAcrossProjects(phrenPath, profile) {
|
|
|
368
369
|
}
|
|
369
370
|
// Non-primary store projects (no profile — team stores don't have profiles)
|
|
370
371
|
try {
|
|
371
|
-
const { getNonPrimaryStores } = require("../store-registry.js");
|
|
372
372
|
for (const store of getNonPrimaryStores(phrenPath)) {
|
|
373
373
|
if (!fs.existsSync(store.path))
|
|
374
374
|
continue;
|
|
375
|
-
const storeProjects =
|
|
375
|
+
const storeProjects = getStoreProjectDirs(store).map((dir) => path.basename(dir));
|
|
376
376
|
for (const project of storeProjects) {
|
|
377
377
|
if (seen.has(project))
|
|
378
378
|
continue;
|