@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 CHANGED
@@ -10,7 +10,7 @@
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- Every time you start a new session, your AI agent forgets everything it learned. Phren fixes that. Findings, decisions, and patterns persist as markdown in a git repo you control. No database, no hosted service, no vendor lock-in.
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
- That single command creates `~/.phren`, wires up MCP, installs hooks, and gives your agents a memory they can actually keep. Re-running on a new machine with an existing remote picks up right where you left off.
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
- ## What phren tracks
26
+ ---
27
27
 
28
- - **Findings**: bugs hit, patterns discovered, decisions and their reasoning. Tagged by type (`[pattern]`, `[decision]`, `[pitfall]`, `[observation]`) with per-type decay rates
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
- ## How it works
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
- - **Surfaces relevant context on every prompt** via hooks. Agents build on what they know instead of starting fresh
37
- - **Trust scores decay over time.** Old findings lose confidence. Decisions never decay. Observations expire in 14 days
38
- - **Syncs across machines** through git push/pull. No coordination service
39
- - **Works with Claude Code, Copilot, Cursor, and Codex.** One store, every agent
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
- ## Quick start
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
- ```bash
45
- npx @phren/cli init # set up phren (interactive walkthrough)
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
- Init detects your tools, registers MCP servers, and installs lifecycle hooks. After it finishes, open a prompt in any tracked project. Phren is already injecting context.
50
+ ---
49
51
 
50
- To add a project later, run `phren add` from that directory. To browse what phren knows, run `phren` to open the interactive shell.
52
+ ## Key features
51
53
 
52
- ## Team stores
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
- Phren supports shared team knowledge repos alongside your personal store. A team store is a separate git repo that multiple people push to. Findings, tasks, and skills saved there are visible to everyone on the team.
62
+ Helps you reason about contradictions instead of hiding them.
55
63
 
56
- Create a team store:
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 team init my-team --remote git@github.com:org/phren-team.git
60
- phren team add-project my-team my-project
76
+ phren store subscribe team-store arc intranet
77
+ phren store unsubscribe team-store legacy-projects
61
78
  ```
62
79
 
63
- Join an existing team store:
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 team join git@github.com:org/phren-team.git
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
- Each team store syncs independently. Run `phren team list` to see all registered stores.
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 projectDirs = getProjectDirs(store.path);
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
- return getProjectDirs(store.path).length;
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 getProjectDirs(store.path)) {
83
+ for (const dir of getStoreProjectDirs(store)) {
85
84
  const status = getProjectConsolidationStatus(dir);
86
85
  if (status && status.recommended) {
87
86
  results.push(status);
@@ -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 = getProjectDirs(store.path)
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) {
@@ -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 = getProjectDirs(store.path).map((dir) => path.basename(dir));
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;