@phren/cli 0.0.54 → 0.0.56
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 +70 -92
- package/{mcp/dist → dist}/cli/cli.js +15 -3
- package/{mcp/dist → dist}/cli/hooks-session.js +47 -0
- package/{mcp/dist → dist}/cli/namespaces.js +120 -0
- package/{mcp/dist → dist}/entrypoint.js +3 -0
- package/{mcp/dist → dist}/generated/memory-ui-graph.browser.js +26 -23
- package/{mcp/dist → dist}/memory-ui-graph.runtime.js +26 -23
- package/{mcp/dist → dist}/package-metadata.js +1 -1
- package/{mcp/dist → dist}/profile-store.js +3 -0
- package/{mcp/dist → dist}/session/utils.js +24 -1
- package/{mcp/dist → dist}/test-global-setup.js +4 -4
- package/{mcp/dist → dist}/tools/session.js +10 -37
- package/dist/ui/assets.js +8 -0
- package/{mcp/dist → dist}/ui/page.js +25 -1
- package/{mcp/dist → dist}/ui/scripts.js +303 -18
- package/{mcp/dist → dist}/ui/server.js +132 -3
- package/{mcp/dist → dist}/ui/styles.js +6 -0
- package/package.json +24 -38
- package/LICENSE +0 -21
- package/icon.svg +0 -416
- package/mcp/dist/ui/assets.js +0 -8
- /package/{mcp/dist → dist}/capabilities/cli.js +0 -0
- /package/{mcp/dist → dist}/capabilities/index.js +0 -0
- /package/{mcp/dist → dist}/capabilities/mcp.js +0 -0
- /package/{mcp/dist → dist}/capabilities/types.js +0 -0
- /package/{mcp/dist → dist}/capabilities/vscode.js +0 -0
- /package/{mcp/dist → dist}/capabilities/web-ui.js +0 -0
- /package/{mcp/dist → dist}/cli/actions.js +0 -0
- /package/{mcp/dist → dist}/cli/config.js +0 -0
- /package/{mcp/dist → dist}/cli/extract.js +0 -0
- /package/{mcp/dist → dist}/cli/govern.js +0 -0
- /package/{mcp/dist → dist}/cli/graph.js +0 -0
- /package/{mcp/dist → dist}/cli/hooks-citations.js +0 -0
- /package/{mcp/dist → dist}/cli/hooks-context.js +0 -0
- /package/{mcp/dist → dist}/cli/hooks-globs.js +0 -0
- /package/{mcp/dist → dist}/cli/hooks-output.js +0 -0
- /package/{mcp/dist → dist}/cli/hooks.js +0 -0
- /package/{mcp/dist → dist}/cli/ops.js +0 -0
- /package/{mcp/dist → dist}/cli/search.js +0 -0
- /package/{mcp/dist → dist}/cli/team.js +0 -0
- /package/{mcp/dist → dist}/cli-hooks-git.js +0 -0
- /package/{mcp/dist → dist}/cli-hooks-prompt.js +0 -0
- /package/{mcp/dist → dist}/cli-hooks-session-handlers.js +0 -0
- /package/{mcp/dist → dist}/cli-hooks-stop.js +0 -0
- /package/{mcp/dist → dist}/content/archive.js +0 -0
- /package/{mcp/dist → dist}/content/citation.js +0 -0
- /package/{mcp/dist → dist}/content/dedup.js +0 -0
- /package/{mcp/dist → dist}/content/learning.js +0 -0
- /package/{mcp/dist → dist}/content/metadata.js +0 -0
- /package/{mcp/dist → dist}/content/validate.js +0 -0
- /package/{mcp/dist → dist}/core/finding.js +0 -0
- /package/{mcp/dist → dist}/core/project.js +0 -0
- /package/{mcp/dist → dist}/core/search.js +0 -0
- /package/{mcp/dist → dist}/data/access.js +0 -0
- /package/{mcp/dist → dist}/data/tasks.js +0 -0
- /package/{mcp/dist → dist}/embedding.js +0 -0
- /package/{mcp/dist → dist}/finding/context.js +0 -0
- /package/{mcp/dist → dist}/finding/impact.js +0 -0
- /package/{mcp/dist → dist}/finding/journal.js +0 -0
- /package/{mcp/dist → dist}/finding/lifecycle.js +0 -0
- /package/{mcp/dist → dist}/governance/audit.js +0 -0
- /package/{mcp/dist → dist}/governance/locks.js +0 -0
- /package/{mcp/dist → dist}/governance/policy.js +0 -0
- /package/{mcp/dist → dist}/governance/rbac.js +0 -0
- /package/{mcp/dist → dist}/governance/scores.js +0 -0
- /package/{mcp/dist → dist}/hooks.js +0 -0
- /package/{mcp/dist → dist}/index-query.js +0 -0
- /package/{mcp/dist → dist}/index.js +0 -0
- /package/{mcp/dist → dist}/init/config.js +0 -0
- /package/{mcp/dist → dist}/init/init-configure.js +0 -0
- /package/{mcp/dist → dist}/init/init-hooks-mode.js +0 -0
- /package/{mcp/dist → dist}/init/init-mcp-mode.js +0 -0
- /package/{mcp/dist → dist}/init/init-uninstall.js +0 -0
- /package/{mcp/dist → dist}/init/init-walkthrough.js +0 -0
- /package/{mcp/dist → dist}/init/init.js +0 -0
- /package/{mcp/dist → dist}/init/preferences.js +0 -0
- /package/{mcp/dist → dist}/init/setup.js +0 -0
- /package/{mcp/dist → dist}/init/shared.js +0 -0
- /package/{mcp/dist → dist}/init-bootstrap.js +0 -0
- /package/{mcp/dist → dist}/init-detect.js +0 -0
- /package/{mcp/dist → dist}/init-env.js +0 -0
- /package/{mcp/dist → dist}/init-fresh.js +0 -0
- /package/{mcp/dist → dist}/init-hooks.js +0 -0
- /package/{mcp/dist → dist}/init-mcp.js +0 -0
- /package/{mcp/dist → dist}/init-modes.js +0 -0
- /package/{mcp/dist → dist}/init-npm.js +0 -0
- /package/{mcp/dist → dist}/init-project-local.js +0 -0
- /package/{mcp/dist → dist}/init-semantic.js +0 -0
- /package/{mcp/dist → dist}/init-types.js +0 -0
- /package/{mcp/dist → dist}/init-uninstall.js +0 -0
- /package/{mcp/dist → dist}/init-update.js +0 -0
- /package/{mcp/dist → dist}/init-walkthrough.js +0 -0
- /package/{mcp/dist → dist}/link/checksums.js +0 -0
- /package/{mcp/dist → dist}/link/context.js +0 -0
- /package/{mcp/dist → dist}/link/doctor.js +0 -0
- /package/{mcp/dist → dist}/link/link.js +0 -0
- /package/{mcp/dist → dist}/link/skills.js +0 -0
- /package/{mcp/dist → dist}/logger.js +0 -0
- /package/{mcp/dist → dist}/machine-identity.js +0 -0
- /package/{mcp/dist → dist}/phren-art.js +0 -0
- /package/{mcp/dist → dist}/phren-core.js +0 -0
- /package/{mcp/dist → dist}/phren-dotenv.js +0 -0
- /package/{mcp/dist → dist}/phren-paths.js +0 -0
- /package/{mcp/dist → dist}/proactivity.js +0 -0
- /package/{mcp/dist → dist}/project-config.js +0 -0
- /package/{mcp/dist → dist}/project-locator.js +0 -0
- /package/{mcp/dist → dist}/project-topics.js +0 -0
- /package/{mcp/dist → dist}/provider-adapters.js +0 -0
- /package/{mcp/dist → dist}/query-correlation.js +0 -0
- /package/{mcp/dist → dist}/runtime-profile.js +0 -0
- /package/{mcp/dist → dist}/session/checkpoints.js +0 -0
- /package/{mcp/dist → dist}/shared/content.js +0 -0
- /package/{mcp/dist → dist}/shared/data-utils.js +0 -0
- /package/{mcp/dist → dist}/shared/embedding-cache.js +0 -0
- /package/{mcp/dist → dist}/shared/fragment-graph.js +0 -0
- /package/{mcp/dist → dist}/shared/governance.js +0 -0
- /package/{mcp/dist → dist}/shared/index.js +0 -0
- /package/{mcp/dist → dist}/shared/ollama.js +0 -0
- /package/{mcp/dist → dist}/shared/process.js +0 -0
- /package/{mcp/dist → dist}/shared/retrieval.js +0 -0
- /package/{mcp/dist → dist}/shared/search-fallback.js +0 -0
- /package/{mcp/dist → dist}/shared/sqljs.js +0 -0
- /package/{mcp/dist → dist}/shared/stemmer.js +0 -0
- /package/{mcp/dist → dist}/shared/vector-index.js +0 -0
- /package/{mcp/dist → dist}/shared.js +0 -0
- /package/{mcp/dist → dist}/shell/entry.js +0 -0
- /package/{mcp/dist → dist}/shell/input.js +0 -0
- /package/{mcp/dist → dist}/shell/palette.js +0 -0
- /package/{mcp/dist → dist}/shell/render.js +0 -0
- /package/{mcp/dist → dist}/shell/shell.js +0 -0
- /package/{mcp/dist → dist}/shell/state-store.js +0 -0
- /package/{mcp/dist → dist}/shell/types.js +0 -0
- /package/{mcp/dist → dist}/shell/view-list.js +0 -0
- /package/{mcp/dist → dist}/shell/view.js +0 -0
- /package/{mcp/dist → dist}/skill/files.js +0 -0
- /package/{mcp/dist → dist}/skill/registry.js +0 -0
- /package/{mcp/dist → dist}/skill/state.js +0 -0
- /package/{mcp/dist → dist}/startup-embedding.js +0 -0
- /package/{mcp/dist → dist}/status.js +0 -0
- /package/{mcp/dist → dist}/store-registry.js +0 -0
- /package/{mcp/dist → dist}/store-routing.js +0 -0
- /package/{mcp/dist → dist}/synonyms.json +0 -0
- /package/{mcp/dist → dist}/task/github.js +0 -0
- /package/{mcp/dist → dist}/task/hygiene.js +0 -0
- /package/{mcp/dist → dist}/task/lifecycle.js +0 -0
- /package/{mcp/dist → dist}/telemetry.js +0 -0
- /package/{mcp/dist → dist}/tool-registry.js +0 -0
- /package/{mcp/dist → dist}/tools/config.js +0 -0
- /package/{mcp/dist → dist}/tools/data.js +0 -0
- /package/{mcp/dist → dist}/tools/extract-facts.js +0 -0
- /package/{mcp/dist → dist}/tools/extract.js +0 -0
- /package/{mcp/dist → dist}/tools/finding.js +0 -0
- /package/{mcp/dist → dist}/tools/graph.js +0 -0
- /package/{mcp/dist → dist}/tools/hooks.js +0 -0
- /package/{mcp/dist → dist}/tools/memory.js +0 -0
- /package/{mcp/dist → dist}/tools/ops.js +0 -0
- /package/{mcp/dist → dist}/tools/search.js +0 -0
- /package/{mcp/dist → dist}/tools/skills.js +0 -0
- /package/{mcp/dist → dist}/tools/tasks.js +0 -0
- /package/{mcp/dist → dist}/tools/types.js +0 -0
- /package/{mcp/dist → dist}/ui/data.js +0 -0
- /package/{mcp/dist → dist}/ui/graph.js +0 -0
- /package/{mcp/dist → dist}/ui/memory-ui.js +0 -0
- /package/{mcp/dist → dist}/update.js +0 -0
- /package/{mcp/dist → dist}/utils.js +0 -0
package/README.md
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
# phren MCP server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server that indexes your personal phren and exposes it to AI agents via full-text search.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
<a href="https://www.npmjs.com/package/@phren/cli"><img src="https://img.shields.io/npm/v/%40phren%2Fcli?style=flat&labelColor=0D0D0D&color=7C3AED" alt="npm version"></a>
|
|
7
|
-
<a href="https://github.com/alaarab/phren/blob/main/LICENSE"><img src="https://img.shields.io/github/license/alaarab/phren?style=flat&labelColor=0D0D0D&color=7C3AED" alt="license"></a>
|
|
8
|
-
<a href="https://alaarab.github.io/phren/"><img src="https://img.shields.io/badge/docs-alaarab.github.io%2Fphren-7C3AED?style=flat&labelColor=0D0D0D" alt="docs"></a>
|
|
9
|
-
<a href="https://alaarab.github.io/phren/whitepaper.pdf"><img src="https://img.shields.io/badge/whitepaper-PDF-7C3AED?style=flat&labelColor=0D0D0D" alt="whitepaper"></a>
|
|
10
|
-
</p>
|
|
5
|
+
On startup it walks your phren directory, reads all `.md` files, and builds an in-memory SQLite FTS5 index.
|
|
11
6
|
|
|
12
|
-
|
|
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
|
-
</p>
|
|
7
|
+
Public surface: 53 MCP tools across 12 modules (search, tasks, findings, memory, data, graph, sessions, ops/review, skills, hooks, config, extraction).
|
|
15
8
|
|
|
16
|
-
|
|
9
|
+
Notable shipped capabilities:
|
|
10
|
+
- finding lifecycle tools: `supersede_finding`, `retract_finding`, `resolve_contradiction`, `get_contradictions`
|
|
11
|
+
- finding provenance: `add_finding.source` (`human|agent|hook|extract|consolidation|unknown`)
|
|
12
|
+
- cross-session continuity: task checkpoints + `session_history`
|
|
13
|
+
- finding impact scoring from injected-context outcomes
|
|
14
|
+
- skill registry behavior: scope precedence, alias-collision handling, visibility gating, generated `skill-manifest.json`
|
|
15
|
+
- lifecycle penalties: superseded 0.25×, retracted 0.1×, contradicted 0.4× confidence in retrieval
|
|
16
|
+
- inactive findings stripped from FTS index (superseded/retracted findings cannot appear in search)
|
|
17
|
+
- auto-tagging: findings without type tags are inferred from content at write time
|
|
18
|
+
- session context diff: `session_start` reports new findings since last session
|
|
19
|
+
- decay resistance: confirmed findings decay 3× slower when repeatedly useful
|
|
17
20
|
|
|
18
21
|
## Install
|
|
19
22
|
|
|
@@ -21,109 +24,84 @@ Persistent memory for AI agents. Findings, tasks, and patterns live in markdown
|
|
|
21
24
|
npx @phren/cli init
|
|
22
25
|
```
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
This sets up phren without needing `sudo` or a global install. On Windows, use `npm install -g @phren/cli` if `npx` isn't available.
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
Or add manually to Claude Code:
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
31
|
+
```bash
|
|
32
|
+
claude mcp add phren -- phren ~/.phren
|
|
33
|
+
```
|
|
40
34
|
|
|
41
|
-
|
|
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
|
|
35
|
+
## Environment variables
|
|
45
36
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
| Variable | Default | Description |
|
|
38
|
+
|----------|---------|-------------|
|
|
39
|
+
| `PHREN_PATH` | `~/.phren` | Path to your phren instance |
|
|
40
|
+
| `PHREN_PROFILE` | *(none)* | Active profile name. When unset, phren uses `machines.yaml` when available and otherwise falls back to an unscoped view |
|
|
41
|
+
| `PHREN_ACTOR` | OS user / env | Actor identity used in governance/audit RBAC checks |
|
|
49
42
|
|
|
50
|
-
|
|
43
|
+
## Tools
|
|
51
44
|
|
|
52
|
-
|
|
45
|
+
See [docs/api-reference.md](../docs/api-reference.md) for the full API reference.
|
|
53
46
|
|
|
54
|
-
|
|
55
|
-
Explore connections visually. Drag nodes to reorganize; graph auto-settles. Click a fragment to see every finding linked to it across all projects.
|
|
47
|
+
## Integration model
|
|
56
48
|
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
- **Retract**: "We were wrong about this; here's why"
|
|
60
|
-
- **Contradict**: "We have two findings that conflict; this is why"
|
|
49
|
+
- Claude: full native lifecycle hooks (`SessionStart`, `UserPromptSubmit`, `Stop`, `PostToolUse`) + MCP
|
|
50
|
+
- Copilot CLI / Cursor / Codex: MCP + generated hook config + session wrapper binaries
|
|
61
51
|
|
|
62
|
-
|
|
52
|
+
## Governance and security highlights
|
|
63
53
|
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
- RBAC uses `.config/access-control.json` and `.runtime/access-control.local.json`
|
|
55
|
+
- Web UI binds loopback-only, uses per-run auth token, enforces CSRF for mutations, and sets CSP headers
|
|
56
|
+
- Telemetry is opt-in only (`phren config telemetry on`) and stored locally in `.runtime/telemetry.json`
|
|
66
57
|
|
|
67
|
-
###
|
|
68
|
-
Mark findings as needing review (`[Review]` section). Phren surfaces review items on every session start. Approve, reject, or edit in place.
|
|
58
|
+
### search_knowledge
|
|
69
59
|
|
|
70
|
-
|
|
71
|
-
Per-project retention policies. Confidence decay curves. Access control. Audit logs. Configure with `phren config` or the web UI.
|
|
60
|
+
Full-text search across all indexed markdown files with synonym expansion.
|
|
72
61
|
|
|
73
|
-
### Store subscriptions
|
|
74
|
-
Subscribe to specific projects in a team store — others stay hidden from search and context injection:
|
|
75
|
-
```bash
|
|
76
|
-
phren store subscribe team-store arc intranet
|
|
77
|
-
phren store unsubscribe team-store legacy-projects
|
|
78
62
|
```
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## CLI quick reference
|
|
92
|
-
|
|
93
|
-
```bash
|
|
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
|
|
63
|
+
query: string - FTS5 query (supports AND, OR, NOT, "phrase matching")
|
|
64
|
+
limit?: number - Max results, 1-20, default 5
|
|
65
|
+
type?: string - Filter: "claude", "findings", "reference", "summary", "task", "skill"
|
|
66
|
+
project?: string - Filter to a specific project
|
|
67
|
+
tag?: string - Filter findings by type tag: decision, pitfall, pattern, tradeoff, architecture, bug
|
|
68
|
+
since?: string - Filter findings by date: "7d", "30d", "YYYY-MM", "YYYY-MM-DD"
|
|
69
|
+
status?: string - Filter by lifecycle status: active, superseded, contradicted, stale, invalid_citation, retracted
|
|
70
|
+
include_history?: boolean - Include superseded/retracted findings (default false)
|
|
71
|
+
synthesize?: boolean - Generate a synthesis paragraph from top results using an LLM
|
|
104
72
|
```
|
|
105
73
|
|
|
106
|
-
|
|
74
|
+
### get_project_summary
|
|
107
75
|
|
|
108
|
-
|
|
76
|
+
Returns a project's summary.md content, path to its CLAUDE.md, and a list of indexed files.
|
|
109
77
|
|
|
110
|
-
|
|
78
|
+
```
|
|
79
|
+
name: string - Project name (e.g. "my-app", "backend")
|
|
80
|
+
```
|
|
111
81
|
|
|
112
|
-
|
|
82
|
+
### list_projects
|
|
113
83
|
|
|
114
|
-
|
|
84
|
+
Lists all projects in the active profile with a brief description and which docs exist.
|
|
115
85
|
|
|
116
|
-
|
|
86
|
+
No parameters.
|
|
117
87
|
|
|
118
|
-
##
|
|
88
|
+
## How it works
|
|
119
89
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
90
|
+
1. Reads `PHREN_PATH` (or defaults to `~/.phren`)
|
|
91
|
+
2. Resolves the active profile from `PHREN_PROFILE`, or from `machines.yaml` when the env var is unset
|
|
92
|
+
3. If no active profile can be resolved yet, falls back to an unscoped view of top-level project directories
|
|
93
|
+
4. Walks each project directory, reads `.md` files, classifies them by filename
|
|
94
|
+
5. Builds an in-memory SQLite FTS5 index with Porter stemming
|
|
95
|
+
6. Serves tools over stdio using the MCP protocol
|
|
124
96
|
|
|
125
|
-
|
|
97
|
+
File types are derived from filenames: `CLAUDE.md` -> "claude", `summary.md` -> "summary", `FINDINGS.md` -> "findings", `tasks.md` -> "task", files under `reference/` -> "reference", files under `skills/` -> "skill".
|
|
126
98
|
|
|
127
|
-
|
|
99
|
+
## Development
|
|
128
100
|
|
|
129
|
-
|
|
101
|
+
```bash
|
|
102
|
+
cd ~/phren
|
|
103
|
+
npm install
|
|
104
|
+
npm run build # Compile TypeScript
|
|
105
|
+
npm run dev # Run with tsx (hot reload)
|
|
106
|
+
npm test # Run all tests
|
|
107
|
+
```
|
|
@@ -7,10 +7,10 @@ import { handleExtractMemories } from "./extract.js";
|
|
|
7
7
|
import { handleGovernMemories, handlePruneMemories, handleConsolidateMemories, handleMaintain, handleBackgroundMaintenance, } from "./govern.js";
|
|
8
8
|
import { handleConfig, handleIndexPolicy, handleRetentionPolicy, handleWorkflowPolicy, } from "./config.js";
|
|
9
9
|
import { parseSearchArgs } from "./search.js";
|
|
10
|
-
import { handleDetectSkills, handleFindingNamespace, handleHooksNamespace, handleProjectsNamespace, handleSkillsNamespace, handleSkillList, handlePromoteNamespace, handleStoreNamespace, handleTaskNamespace, } from "./namespaces.js";
|
|
10
|
+
import { handleDetectSkills, handleFindingNamespace, handleHooksNamespace, handleProfileNamespace, handleProjectsNamespace, handleSkillsNamespace, handleSkillList, handlePromoteNamespace, handleReviewNamespace, handleStoreNamespace, handleTaskNamespace, } from "./namespaces.js";
|
|
11
11
|
import { handleTeamNamespace } from "./team.js";
|
|
12
12
|
import { handleTaskView, handleSessionsView, handleQuickstart, handleDebugInjection, handleInspectIndex, } from "./ops.js";
|
|
13
|
-
import { handleAddFinding, handleDoctor, handleFragmentSearch, handleMemoryUi, handleTruths, handlePinCanonical, handleQualityFeedback, handleRelatedDocs,
|
|
13
|
+
import { handleAddFinding, handleDoctor, handleFragmentSearch, handleMemoryUi, handleTruths, handlePinCanonical, handleQualityFeedback, handleRelatedDocs, handleConsolidationStatus, handleSessionContext, handleSearch, handleShell, handleStatus, handleUpdate, } from "./actions.js";
|
|
14
14
|
import { handleGraphNamespace } from "./graph.js";
|
|
15
15
|
import { resolveRuntimeProfile } from "../runtime-profile.js";
|
|
16
16
|
// ── CLI router ───────────────────────────────────────────────────────────────
|
|
@@ -103,7 +103,7 @@ export async function runCliCommand(command, args) {
|
|
|
103
103
|
case "graph":
|
|
104
104
|
return handleGraphNamespace(args);
|
|
105
105
|
case "review":
|
|
106
|
-
return
|
|
106
|
+
return handleReviewNamespace(args);
|
|
107
107
|
case "consolidation-status":
|
|
108
108
|
return handleConsolidationStatus(args);
|
|
109
109
|
case "session-context":
|
|
@@ -112,10 +112,22 @@ export async function runCliCommand(command, args) {
|
|
|
112
112
|
return handleTruths(args[0]);
|
|
113
113
|
case "store":
|
|
114
114
|
return handleStoreNamespace(args);
|
|
115
|
+
case "profile":
|
|
116
|
+
return handleProfileNamespace(args);
|
|
115
117
|
case "team":
|
|
116
118
|
return handleTeamNamespace(args);
|
|
117
119
|
case "promote":
|
|
118
120
|
return handlePromoteNamespace(args);
|
|
121
|
+
case "agent": {
|
|
122
|
+
try {
|
|
123
|
+
const { runAgentCli } = await import("@phren/agent");
|
|
124
|
+
return runAgentCli(args);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
console.error("@phren/agent is not installed. Install it with: npm i -g @phren/agent");
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
119
131
|
default:
|
|
120
132
|
console.error(`Unknown command: ${command}\nRun 'phren --help' for available commands.`);
|
|
121
133
|
process.exit(1);
|
|
@@ -13,6 +13,8 @@ import { isTaskFileName, TASKS_FILENAME } from "../data/tasks.js";
|
|
|
13
13
|
import { buildIndex, queryRows, } from "../shared/index.js";
|
|
14
14
|
import { filterTaskByPriority } from "../shared/retrieval.js";
|
|
15
15
|
import { logger } from "../logger.js";
|
|
16
|
+
import * as crypto from "crypto";
|
|
17
|
+
import { sessionFileForId, readSessionStateFile, writeSessionStateFile, } from "../session/utils.js";
|
|
16
18
|
const SYNC_LOCK_STALE_MS = 10 * 60 * 1000; // 10 minutes
|
|
17
19
|
const MAINTENANCE_LOCK_STALE_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
18
20
|
export { buildHookContext, handleGuardSkip } from "./hooks-context.js";
|
|
@@ -671,6 +673,51 @@ export async function handleHookSessionStart() {
|
|
|
671
673
|
catch (err) {
|
|
672
674
|
debugLog(`session-start onboarding detection failed: ${errorMessage(err)}`);
|
|
673
675
|
}
|
|
676
|
+
// ── Bridge: create a real session record so session_history tracks hook sessions ──
|
|
677
|
+
// Uses a file lock to prevent concurrent SessionStart hooks from racing on
|
|
678
|
+
// the active-hook-session pointer (read previous ID, end it, write new ID).
|
|
679
|
+
try {
|
|
680
|
+
const activeSessionFile = runtimeFile(phrenPath, "active-hook-session");
|
|
681
|
+
withFileLock(activeSessionFile, () => {
|
|
682
|
+
// Retroactively end the previous hook session (if any)
|
|
683
|
+
try {
|
|
684
|
+
const prevId = fs.readFileSync(activeSessionFile, "utf-8").trim();
|
|
685
|
+
if (prevId) {
|
|
686
|
+
const prevFile = sessionFileForId(phrenPath, prevId);
|
|
687
|
+
const prevState = readSessionStateFile(prevFile);
|
|
688
|
+
if (prevState && !prevState.endedAt) {
|
|
689
|
+
writeSessionStateFile(prevFile, { ...prevState, endedAt: startedAt });
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch (err) {
|
|
694
|
+
// ENOENT is expected on the very first session — only log other errors
|
|
695
|
+
if (err.code !== "ENOENT") {
|
|
696
|
+
debugLog(`session-bridge end-previous failed: ${errorMessage(err)}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// Create new session record
|
|
700
|
+
const sessionId = crypto.randomUUID();
|
|
701
|
+
const sessionState = {
|
|
702
|
+
sessionId,
|
|
703
|
+
project: activeProject || undefined,
|
|
704
|
+
startedAt,
|
|
705
|
+
findingsAdded: 0,
|
|
706
|
+
tasksCompleted: 0,
|
|
707
|
+
hookCreated: true,
|
|
708
|
+
};
|
|
709
|
+
writeSessionStateFile(sessionFileForId(phrenPath, sessionId), sessionState);
|
|
710
|
+
// Write active session ID atomically so other hooks (stop, tool) can reference it.
|
|
711
|
+
// Plain text format (not JSON) because the reader uses readFileSync + trim.
|
|
712
|
+
const tmpPath = `${activeSessionFile}.${process.pid}.${Date.now()}.tmp`;
|
|
713
|
+
fs.writeFileSync(tmpPath, sessionId + "\n");
|
|
714
|
+
fs.renameSync(tmpPath, activeSessionFile);
|
|
715
|
+
debugLog(`session-bridge created session ${sessionId.slice(0, 8)} for hook-driven session`);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
catch (err) {
|
|
719
|
+
debugLog(`session-bridge failed: ${errorMessage(err)}`);
|
|
720
|
+
}
|
|
674
721
|
}
|
|
675
722
|
// ── Q21: Conversation memory capture ─────────────────────────────────────────
|
|
676
723
|
const INSIGHT_KEYWORDS = [
|
|
@@ -1771,6 +1771,82 @@ export async function handleStoreNamespace(args) {
|
|
|
1771
1771
|
printStoreUsage();
|
|
1772
1772
|
process.exit(1);
|
|
1773
1773
|
}
|
|
1774
|
+
// ── Profile namespace ────────────────────────────────────────────────────────
|
|
1775
|
+
function printProfileUsage() {
|
|
1776
|
+
console.log("Usage:");
|
|
1777
|
+
console.log(" phren profile list List all available profiles");
|
|
1778
|
+
console.log(" phren profile switch <name> Switch to an active profile");
|
|
1779
|
+
}
|
|
1780
|
+
export function handleProfileNamespace(args) {
|
|
1781
|
+
const subcommand = args[0];
|
|
1782
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
1783
|
+
printProfileUsage();
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
const phrenPath = getPhrenPath();
|
|
1787
|
+
if (subcommand === "list") {
|
|
1788
|
+
const { listProfiles } = require("../profile-store.js");
|
|
1789
|
+
const result = listProfiles(phrenPath);
|
|
1790
|
+
if (!result.ok) {
|
|
1791
|
+
console.error(`Failed to list profiles: ${result.error}`);
|
|
1792
|
+
process.exit(1);
|
|
1793
|
+
}
|
|
1794
|
+
const profiles = result.data || [];
|
|
1795
|
+
if (profiles.length === 0) {
|
|
1796
|
+
console.log("No profiles available.");
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
const { listMachines } = require("../profile-store.js");
|
|
1800
|
+
const machinesResult = listMachines(phrenPath);
|
|
1801
|
+
const machines = machinesResult.ok ? machinesResult.data : {};
|
|
1802
|
+
const { getMachineName } = require("../machine-identity.js");
|
|
1803
|
+
const currentMachine = getMachineName();
|
|
1804
|
+
const activeProfile = machines[currentMachine];
|
|
1805
|
+
console.log(`${profiles.length} profile(s):\n`);
|
|
1806
|
+
for (const profile of profiles) {
|
|
1807
|
+
const isCurrent = profile.name === activeProfile ? " (current)" : "";
|
|
1808
|
+
const projectCount = profile.projects?.length ?? 0;
|
|
1809
|
+
console.log(` ${profile.name}${isCurrent}`);
|
|
1810
|
+
console.log(` projects: ${projectCount}`);
|
|
1811
|
+
console.log();
|
|
1812
|
+
}
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
if (subcommand === "switch") {
|
|
1816
|
+
const profileName = args[1];
|
|
1817
|
+
if (!profileName) {
|
|
1818
|
+
console.error("Usage: phren profile switch <name>");
|
|
1819
|
+
process.exit(1);
|
|
1820
|
+
}
|
|
1821
|
+
const { setMachineProfile, getDefaultMachineAlias, listProfiles } = require("../profile-store.js");
|
|
1822
|
+
// Validate that profile exists
|
|
1823
|
+
const listResult = listProfiles(phrenPath);
|
|
1824
|
+
if (!listResult.ok) {
|
|
1825
|
+
console.error(`Failed to list profiles: ${listResult.error}`);
|
|
1826
|
+
process.exit(1);
|
|
1827
|
+
}
|
|
1828
|
+
const profiles = listResult.data || [];
|
|
1829
|
+
if (!profiles.some((p) => p.name === profileName)) {
|
|
1830
|
+
console.error(`Profile not found: "${profileName}"`);
|
|
1831
|
+
console.log("Available profiles:");
|
|
1832
|
+
for (const p of profiles) {
|
|
1833
|
+
console.log(` - ${p.name}`);
|
|
1834
|
+
}
|
|
1835
|
+
process.exit(1);
|
|
1836
|
+
}
|
|
1837
|
+
const machineAlias = getDefaultMachineAlias();
|
|
1838
|
+
const result = setMachineProfile(phrenPath, machineAlias, profileName);
|
|
1839
|
+
if (!result.ok) {
|
|
1840
|
+
console.error(`Failed to switch profile: ${result.error}`);
|
|
1841
|
+
process.exit(1);
|
|
1842
|
+
}
|
|
1843
|
+
console.log(`Switched to profile: ${profileName} (machine: ${machineAlias})`);
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
console.error(`Unknown profile subcommand: ${subcommand}`);
|
|
1847
|
+
printProfileUsage();
|
|
1848
|
+
process.exit(1);
|
|
1849
|
+
}
|
|
1774
1850
|
// ── Promote namespace ────────────────────────────────────────────────────────
|
|
1775
1851
|
export async function handlePromoteNamespace(args) {
|
|
1776
1852
|
if (!args[0] || args[0] === "--help" || args[0] === "-h") {
|
|
@@ -1836,6 +1912,50 @@ export async function handlePromoteNamespace(args) {
|
|
|
1836
1912
|
console.log(`Promoted to ${toStore}/${project}:`);
|
|
1837
1913
|
console.log(` "${match.text.slice(0, 120)}${match.text.length > 120 ? "..." : ""}"`);
|
|
1838
1914
|
}
|
|
1915
|
+
// ── Review namespace ────────────────────────────────────────────────────────
|
|
1916
|
+
export async function handleReviewNamespace(args) {
|
|
1917
|
+
const subcommand = args[0];
|
|
1918
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
1919
|
+
console.log("Usage:");
|
|
1920
|
+
console.log(" phren review [project] Show review queue items");
|
|
1921
|
+
console.log(" phren review approve <project> <text> Approve and remove item");
|
|
1922
|
+
console.log(" phren review reject <project> <text> Reject and remove item");
|
|
1923
|
+
console.log("");
|
|
1924
|
+
console.log("Examples:");
|
|
1925
|
+
console.log(' phren review myproject');
|
|
1926
|
+
console.log(' phren review approve myproject "Always validate input"');
|
|
1927
|
+
console.log(' phren review reject myproject "Avoid async in loops"');
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
// Handle "approve" and "reject" subcommands
|
|
1931
|
+
if (subcommand === "approve" || subcommand === "reject") {
|
|
1932
|
+
const action = subcommand;
|
|
1933
|
+
const project = args[1];
|
|
1934
|
+
const lineText = args.slice(2).join(" ");
|
|
1935
|
+
if (!project || !lineText) {
|
|
1936
|
+
console.error(`Usage: phren review ${action} <project> <text>`);
|
|
1937
|
+
process.exit(1);
|
|
1938
|
+
}
|
|
1939
|
+
if (!isValidProjectName(project)) {
|
|
1940
|
+
console.error(`Invalid project name: "${project}".`);
|
|
1941
|
+
process.exit(1);
|
|
1942
|
+
}
|
|
1943
|
+
const phrenPath = getPhrenPath();
|
|
1944
|
+
const { approveQueueItem, rejectQueueItem } = await import("../data/access.js");
|
|
1945
|
+
const result = action === "approve"
|
|
1946
|
+
? approveQueueItem(phrenPath, project, lineText)
|
|
1947
|
+
: rejectQueueItem(phrenPath, project, lineText);
|
|
1948
|
+
if (!result.ok) {
|
|
1949
|
+
console.error(`Failed to ${action} item: ${result.error ?? "Unknown error"}`);
|
|
1950
|
+
process.exit(1);
|
|
1951
|
+
}
|
|
1952
|
+
console.log(`${action === "approve" ? "✓ Approved" : "✗ Rejected"}: ${lineText.slice(0, 100)}${lineText.length > 100 ? "..." : ""}`);
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
// Default: show review queue (first arg is project name if not a subcommand)
|
|
1956
|
+
const { handleReview } = await import("./actions.js");
|
|
1957
|
+
return handleReview(args);
|
|
1958
|
+
}
|
|
1839
1959
|
function countStoreProjects(store) {
|
|
1840
1960
|
if (!fs.existsSync(store.path))
|
|
1841
1961
|
return 0;
|
|
@@ -18,6 +18,8 @@ const HELP_TEXT = `phren - persistent knowledge for your agents
|
|
|
18
18
|
phren web-ui Open the knowledge graph
|
|
19
19
|
phren tasks Cross-project task view
|
|
20
20
|
phren graph Fragment knowledge graph
|
|
21
|
+
phren agent <task> Run the coding agent
|
|
22
|
+
phren agent -i Interactive agent TUI
|
|
21
23
|
|
|
22
24
|
phren store list List registered stores
|
|
23
25
|
phren team init <name> Create a team store
|
|
@@ -187,6 +189,7 @@ const CLI_COMMANDS = [
|
|
|
187
189
|
"store",
|
|
188
190
|
"team",
|
|
189
191
|
"promote",
|
|
192
|
+
"agent",
|
|
190
193
|
];
|
|
191
194
|
async function flushTopLevelOutput() {
|
|
192
195
|
await Promise.all([
|