@phren/cli 0.0.1
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/LICENSE +21 -0
- package/README.md +590 -0
- package/mcp/dist/capabilities/cli.js +61 -0
- package/mcp/dist/capabilities/index.js +15 -0
- package/mcp/dist/capabilities/mcp.js +61 -0
- package/mcp/dist/capabilities/types.js +57 -0
- package/mcp/dist/capabilities/vscode.js +61 -0
- package/mcp/dist/capabilities/web-ui.js +61 -0
- package/mcp/dist/cli-actions.js +302 -0
- package/mcp/dist/cli-config.js +580 -0
- package/mcp/dist/cli-extract.js +305 -0
- package/mcp/dist/cli-govern.js +371 -0
- package/mcp/dist/cli-graph.js +169 -0
- package/mcp/dist/cli-hooks-citations.js +44 -0
- package/mcp/dist/cli-hooks-context.js +56 -0
- package/mcp/dist/cli-hooks-globs.js +83 -0
- package/mcp/dist/cli-hooks-output.js +130 -0
- package/mcp/dist/cli-hooks-retrieval.js +2 -0
- package/mcp/dist/cli-hooks-session.js +1402 -0
- package/mcp/dist/cli-hooks.js +350 -0
- package/mcp/dist/cli-namespaces.js +989 -0
- package/mcp/dist/cli-ops.js +253 -0
- package/mcp/dist/cli-search.js +407 -0
- package/mcp/dist/cli.js +108 -0
- package/mcp/dist/content-archive.js +278 -0
- package/mcp/dist/content-citation.js +391 -0
- package/mcp/dist/content-dedup.js +622 -0
- package/mcp/dist/content-learning.js +472 -0
- package/mcp/dist/content-metadata.js +186 -0
- package/mcp/dist/content-validate.js +462 -0
- package/mcp/dist/core-finding.js +54 -0
- package/mcp/dist/core-project.js +36 -0
- package/mcp/dist/core-search.js +50 -0
- package/mcp/dist/data-access.js +400 -0
- package/mcp/dist/data-tasks.js +821 -0
- package/mcp/dist/embedding.js +344 -0
- package/mcp/dist/entrypoint.js +387 -0
- package/mcp/dist/finding-context.js +172 -0
- package/mcp/dist/finding-impact.js +181 -0
- package/mcp/dist/finding-journal.js +122 -0
- package/mcp/dist/finding-lifecycle.js +259 -0
- package/mcp/dist/governance-audit.js +22 -0
- package/mcp/dist/governance-locks.js +96 -0
- package/mcp/dist/governance-policy.js +648 -0
- package/mcp/dist/governance-scores.js +355 -0
- package/mcp/dist/hooks.js +449 -0
- package/mcp/dist/impact-scoring.js +22 -0
- package/mcp/dist/index-query.js +168 -0
- package/mcp/dist/index.js +205 -0
- package/mcp/dist/init-config.js +336 -0
- package/mcp/dist/init-preferences.js +62 -0
- package/mcp/dist/init-setup.js +1305 -0
- package/mcp/dist/init-shared.js +29 -0
- package/mcp/dist/init.js +1730 -0
- package/mcp/dist/link-checksums.js +62 -0
- package/mcp/dist/link-context.js +257 -0
- package/mcp/dist/link-doctor.js +591 -0
- package/mcp/dist/link-skills.js +212 -0
- package/mcp/dist/link.js +596 -0
- package/mcp/dist/logger.js +15 -0
- package/mcp/dist/machine-identity.js +38 -0
- package/mcp/dist/mcp-config.js +254 -0
- package/mcp/dist/mcp-data.js +315 -0
- package/mcp/dist/mcp-extract-facts.js +78 -0
- package/mcp/dist/mcp-extract.js +133 -0
- package/mcp/dist/mcp-finding.js +557 -0
- package/mcp/dist/mcp-graph.js +339 -0
- package/mcp/dist/mcp-hooks.js +256 -0
- package/mcp/dist/mcp-memory.js +58 -0
- package/mcp/dist/mcp-ops.js +328 -0
- package/mcp/dist/mcp-search.js +628 -0
- package/mcp/dist/mcp-session.js +651 -0
- package/mcp/dist/mcp-skills.js +189 -0
- package/mcp/dist/mcp-tasks.js +551 -0
- package/mcp/dist/mcp-types.js +7 -0
- package/mcp/dist/memory-ui-assets.js +6 -0
- package/mcp/dist/memory-ui-data.js +513 -0
- package/mcp/dist/memory-ui-graph.js +1910 -0
- package/mcp/dist/memory-ui-page.js +353 -0
- package/mcp/dist/memory-ui-scripts.js +1387 -0
- package/mcp/dist/memory-ui-server.js +1218 -0
- package/mcp/dist/memory-ui-styles.js +555 -0
- package/mcp/dist/memory-ui.js +9 -0
- package/mcp/dist/package-metadata.js +13 -0
- package/mcp/dist/phren-art.js +52 -0
- package/mcp/dist/phren-core.js +108 -0
- package/mcp/dist/phren-dotenv.js +67 -0
- package/mcp/dist/phren-paths.js +476 -0
- package/mcp/dist/proactivity.js +172 -0
- package/mcp/dist/profile-store.js +228 -0
- package/mcp/dist/project-config.js +85 -0
- package/mcp/dist/project-locator.js +25 -0
- package/mcp/dist/project-topics.js +1134 -0
- package/mcp/dist/provider-adapters.js +176 -0
- package/mcp/dist/runtime-profile.js +18 -0
- package/mcp/dist/session-checkpoints.js +131 -0
- package/mcp/dist/session-utils.js +68 -0
- package/mcp/dist/shared-content.js +8 -0
- package/mcp/dist/shared-embedding-cache.js +143 -0
- package/mcp/dist/shared-fragment-graph.js +456 -0
- package/mcp/dist/shared-governance.js +4 -0
- package/mcp/dist/shared-index.js +1334 -0
- package/mcp/dist/shared-ollama.js +192 -0
- package/mcp/dist/shared-paths.js +1 -0
- package/mcp/dist/shared-retrieval.js +796 -0
- package/mcp/dist/shared-search-fallback.js +375 -0
- package/mcp/dist/shared-sqljs.js +42 -0
- package/mcp/dist/shared-stemmer.js +171 -0
- package/mcp/dist/shared-vector-index.js +199 -0
- package/mcp/dist/shared.js +114 -0
- package/mcp/dist/shell-entry.js +209 -0
- package/mcp/dist/shell-input.js +943 -0
- package/mcp/dist/shell-palette.js +119 -0
- package/mcp/dist/shell-render.js +252 -0
- package/mcp/dist/shell-state-store.js +81 -0
- package/mcp/dist/shell-types.js +13 -0
- package/mcp/dist/shell-view-list.js +14 -0
- package/mcp/dist/shell-view.js +707 -0
- package/mcp/dist/shell.js +352 -0
- package/mcp/dist/skill-files.js +117 -0
- package/mcp/dist/skill-registry.js +279 -0
- package/mcp/dist/skill-state.js +28 -0
- package/mcp/dist/startup-embedding.js +57 -0
- package/mcp/dist/status.js +323 -0
- package/mcp/dist/synonyms.json +670 -0
- package/mcp/dist/task-hygiene.js +251 -0
- package/mcp/dist/task-lifecycle.js +347 -0
- package/mcp/dist/tasks-github.js +76 -0
- package/mcp/dist/telemetry.js +165 -0
- package/mcp/dist/test-global-setup.js +37 -0
- package/mcp/dist/tool-registry.js +104 -0
- package/mcp/dist/update.js +97 -0
- package/mcp/dist/utils.js +543 -0
- package/package.json +67 -0
- package/skills/README.md +7 -0
- package/skills/consolidate/SKILL.md +152 -0
- package/skills/discover/SKILL.md +175 -0
- package/skills/init/SKILL.md +216 -0
- package/skills/profiles/SKILL.md +121 -0
- package/skills/sync/SKILL.md +261 -0
- package/starter/README.md +74 -0
- package/starter/global/CLAUDE.md +89 -0
- package/starter/global/skills/humanize.md +30 -0
- package/starter/global/skills/pipeline.md +35 -0
- package/starter/global/skills/release.md +35 -0
- package/starter/machines.yaml +8 -0
- package/starter/my-api/.claude/skills/README.md +7 -0
- package/starter/my-api/CLAUDE.md +33 -0
- package/starter/my-api/FINDINGS.md +9 -0
- package/starter/my-api/summary.md +7 -0
- package/starter/my-api/tasks.md +7 -0
- package/starter/my-first-project/.claude/skills/README.md +7 -0
- package/starter/my-first-project/CLAUDE.md +49 -0
- package/starter/my-first-project/FINDINGS.md +24 -0
- package/starter/my-first-project/summary.md +11 -0
- package/starter/my-first-project/tasks.md +25 -0
- package/starter/my-frontend/.claude/skills/README.md +7 -0
- package/starter/my-frontend/CLAUDE.md +33 -0
- package/starter/my-frontend/FINDINGS.md +9 -0
- package/starter/my-frontend/summary.md +7 -0
- package/starter/my-frontend/tasks.md +7 -0
- package/starter/profiles/default.yaml +4 -0
- package/starter/profiles/personal.yaml +4 -0
- package/starter/profiles/work.yaml +4 -0
- package/starter/templates/README.md +7 -0
- package/starter/templates/frontend/CLAUDE.md +23 -0
- package/starter/templates/frontend/FINDINGS.md +7 -0
- package/starter/templates/frontend/reference/README.md +4 -0
- package/starter/templates/frontend/summary.md +7 -0
- package/starter/templates/frontend/tasks.md +11 -0
- package/starter/templates/library/CLAUDE.md +22 -0
- package/starter/templates/library/FINDINGS.md +7 -0
- package/starter/templates/library/reference/README.md +4 -0
- package/starter/templates/library/summary.md +7 -0
- package/starter/templates/library/tasks.md +11 -0
- package/starter/templates/monorepo/CLAUDE.md +21 -0
- package/starter/templates/monorepo/FINDINGS.md +7 -0
- package/starter/templates/monorepo/reference/README.md +4 -0
- package/starter/templates/monorepo/summary.md +7 -0
- package/starter/templates/monorepo/tasks.md +11 -0
- package/starter/templates/python-project/CLAUDE.md +21 -0
- package/starter/templates/python-project/FINDINGS.md +7 -0
- package/starter/templates/python-project/reference/README.md +4 -0
- package/starter/templates/python-project/summary.md +7 -0
- package/starter/templates/python-project/tasks.md +10 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as crypto from "crypto";
|
|
4
|
+
import { getProjectDirs } from "./shared.js";
|
|
5
|
+
import { TASK_FILE_ALIASES } from "./data-tasks.js";
|
|
6
|
+
function fileChecksum(filePath) {
|
|
7
|
+
const content = fs.readFileSync(filePath);
|
|
8
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
9
|
+
}
|
|
10
|
+
function checksumStorePath(phrenPath) {
|
|
11
|
+
return path.join(phrenPath, ".governance", "file-checksums.json");
|
|
12
|
+
}
|
|
13
|
+
function loadChecksums(phrenPath) {
|
|
14
|
+
const file = checksumStorePath(phrenPath);
|
|
15
|
+
if (!fs.existsSync(file))
|
|
16
|
+
return {};
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
22
|
+
process.stderr.write(`[phren] loadChecksums: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function saveChecksums(phrenPath, store) {
|
|
27
|
+
const file = checksumStorePath(phrenPath);
|
|
28
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
29
|
+
fs.writeFileSync(file, JSON.stringify(store, null, 2) + "\n");
|
|
30
|
+
}
|
|
31
|
+
export function updateFileChecksums(phrenPath, profileName) {
|
|
32
|
+
const store = loadChecksums(phrenPath);
|
|
33
|
+
const now = new Date().toISOString();
|
|
34
|
+
const tracked = [];
|
|
35
|
+
const dirs = getProjectDirs(phrenPath, profileName);
|
|
36
|
+
for (const dir of dirs) {
|
|
37
|
+
for (const name of ["FINDINGS.md", ...TASK_FILE_ALIASES, "CANONICAL_MEMORIES.md"]) {
|
|
38
|
+
const full = path.join(dir, name);
|
|
39
|
+
if (!fs.existsSync(full))
|
|
40
|
+
continue;
|
|
41
|
+
const rel = path.relative(phrenPath, full).replace(/\\/g, "/");
|
|
42
|
+
store[rel] = { sha256: fileChecksum(full), updatedAt: now };
|
|
43
|
+
tracked.push(rel);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
saveChecksums(phrenPath, store);
|
|
47
|
+
return { updated: tracked.length, files: tracked };
|
|
48
|
+
}
|
|
49
|
+
export function verifyFileChecksums(phrenPath) {
|
|
50
|
+
const store = loadChecksums(phrenPath);
|
|
51
|
+
const results = [];
|
|
52
|
+
for (const [rel, entry] of Object.entries(store)) {
|
|
53
|
+
const full = path.join(phrenPath, rel);
|
|
54
|
+
if (!fs.existsSync(full)) {
|
|
55
|
+
results.push({ file: rel, status: "missing" });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const current = fileChecksum(full);
|
|
59
|
+
results.push({ file: rel, status: current === entry.sha256 ? "ok" : "mismatch" });
|
|
60
|
+
}
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as yaml from "js-yaml";
|
|
4
|
+
import { isValidProjectName } from "./utils.js";
|
|
5
|
+
import { homeDir, homePath } from "./shared.js";
|
|
6
|
+
import { resolveTaskFilePath } from "./data-tasks.js";
|
|
7
|
+
function log(msg) { process.stdout.write(msg + "\n"); }
|
|
8
|
+
function contextFilePath() {
|
|
9
|
+
return homePath(".phren-context.md");
|
|
10
|
+
}
|
|
11
|
+
export function claudeProjectKey() {
|
|
12
|
+
return homeDir().replace(/[/\\:]/g, "-").replace(/^-/, "");
|
|
13
|
+
}
|
|
14
|
+
function displayName(slug) {
|
|
15
|
+
if (!slug)
|
|
16
|
+
return "";
|
|
17
|
+
return slug.split("-").map(w => w[0]?.toUpperCase() + w.slice(1)).join(" ");
|
|
18
|
+
}
|
|
19
|
+
function allKnownProjects(phrenPath) {
|
|
20
|
+
const profilesDir = path.join(phrenPath, "profiles");
|
|
21
|
+
if (!fs.existsSync(profilesDir))
|
|
22
|
+
return [];
|
|
23
|
+
const projects = new Set();
|
|
24
|
+
for (const f of fs.readdirSync(profilesDir)) {
|
|
25
|
+
if (!f.endsWith(".yaml"))
|
|
26
|
+
continue;
|
|
27
|
+
const data = yaml.load(fs.readFileSync(path.join(profilesDir, f), "utf8"), { schema: yaml.CORE_SCHEMA });
|
|
28
|
+
for (const p of (data?.projects ?? []))
|
|
29
|
+
projects.add(p);
|
|
30
|
+
}
|
|
31
|
+
return [...projects].sort();
|
|
32
|
+
}
|
|
33
|
+
// ── Context file writing ────────────────────────────────────────────────────
|
|
34
|
+
function writeContextFile(managedContent) {
|
|
35
|
+
const contextFile = contextFilePath();
|
|
36
|
+
const wrapped = `<!-- phren-managed -->\n${managedContent}\n<!-- phren-managed -->`;
|
|
37
|
+
if (fs.existsSync(contextFile)) {
|
|
38
|
+
const existing = fs.readFileSync(contextFile, "utf8");
|
|
39
|
+
if (existing.includes("<!-- phren-managed -->")) {
|
|
40
|
+
const startIdx = existing.indexOf("<!-- phren-managed -->");
|
|
41
|
+
const endIdx = existing.indexOf("<!-- phren-managed -->");
|
|
42
|
+
const before = startIdx > 0 ? existing.slice(0, startIdx).trimEnd() : "";
|
|
43
|
+
const after = endIdx !== -1 ? existing.slice(endIdx + "<!-- phren-managed -->".length).trimStart() : "";
|
|
44
|
+
const parts = [before, wrapped, after].filter(Boolean);
|
|
45
|
+
fs.writeFileSync(contextFile, parts.join("\n") + "\n");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
fs.writeFileSync(contextFile, wrapped + "\n");
|
|
50
|
+
}
|
|
51
|
+
function formatMcpStatus(status) {
|
|
52
|
+
if (status === "installed" || status === "already_configured") {
|
|
53
|
+
return "MCP: active (search_knowledge, get_project_summary, list_projects)";
|
|
54
|
+
}
|
|
55
|
+
if (status === "disabled" || status === "already_disabled") {
|
|
56
|
+
return "MCP: disabled (hooks-only fallback active)";
|
|
57
|
+
}
|
|
58
|
+
if (status === "not_built")
|
|
59
|
+
return "MCP: not built. Run: cd mcp && npm install && npm run build";
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
export function writeContextDefault(machine, profile, mcpStatus, projects, phrenPath) {
|
|
63
|
+
const all = allKnownProjects(phrenPath);
|
|
64
|
+
const inactive = all.filter(p => !projects.includes(p));
|
|
65
|
+
const mcpLine = formatMcpStatus(mcpStatus);
|
|
66
|
+
const lines = [
|
|
67
|
+
"# phren context",
|
|
68
|
+
`Machine: ${machine}`,
|
|
69
|
+
`Profile: ${profile}`,
|
|
70
|
+
`Active projects: ${projects.join(", ")}`,
|
|
71
|
+
`Not on this machine: ${inactive.length ? inactive.join(", ") : "none"}`,
|
|
72
|
+
...(mcpLine ? [mcpLine] : []),
|
|
73
|
+
`Last synced: ${new Date().toISOString().slice(0, 10)}`,
|
|
74
|
+
];
|
|
75
|
+
writeContextFile(lines.join("\n"));
|
|
76
|
+
log(` wrote ${contextFilePath()}`);
|
|
77
|
+
}
|
|
78
|
+
export function writeContextDebugging(machine, profile, mcpStatus, projects, phrenPath) {
|
|
79
|
+
const mcpLine = formatMcpStatus(mcpStatus);
|
|
80
|
+
let content = [
|
|
81
|
+
"# phren context (debugging)",
|
|
82
|
+
`Machine: ${machine}`,
|
|
83
|
+
`Profile: ${profile}`,
|
|
84
|
+
`Last synced: ${new Date().toISOString().slice(0, 10)}`,
|
|
85
|
+
...(mcpLine ? [mcpLine] : []),
|
|
86
|
+
].join("\n") + "\n\n## Project Findings\n";
|
|
87
|
+
const MAX_FILE_BYTES = 50 * 1024;
|
|
88
|
+
for (const project of projects) {
|
|
89
|
+
if (project === "global")
|
|
90
|
+
continue;
|
|
91
|
+
const findings = path.join(phrenPath, project, "FINDINGS.md");
|
|
92
|
+
if (fs.existsSync(findings)) {
|
|
93
|
+
let body = fs.readFileSync(findings, "utf8");
|
|
94
|
+
if (body.length > MAX_FILE_BYTES) {
|
|
95
|
+
body = body.slice(-MAX_FILE_BYTES);
|
|
96
|
+
const firstNewline = body.indexOf("\n");
|
|
97
|
+
if (firstNewline !== -1)
|
|
98
|
+
body = body.slice(firstNewline + 1);
|
|
99
|
+
body = `(truncated to most recent entries)\n${body}`;
|
|
100
|
+
}
|
|
101
|
+
content += `\n### ${project}\n${body}\n`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
writeContextFile(content);
|
|
105
|
+
log(` wrote ${contextFilePath()} (debugging mode)`);
|
|
106
|
+
}
|
|
107
|
+
export function writeContextPlanning(machine, profile, mcpStatus, projects, phrenPath) {
|
|
108
|
+
const mcpLine = formatMcpStatus(mcpStatus);
|
|
109
|
+
let content = [
|
|
110
|
+
"# phren context (planning)",
|
|
111
|
+
`Machine: ${machine}`,
|
|
112
|
+
`Profile: ${profile}`,
|
|
113
|
+
`Last synced: ${new Date().toISOString().slice(0, 10)}`,
|
|
114
|
+
...(mcpLine ? [mcpLine] : []),
|
|
115
|
+
].join("\n");
|
|
116
|
+
const MAX_CONTEXT_BYTES = 100 * 1024;
|
|
117
|
+
for (const project of projects) {
|
|
118
|
+
if (project === "global")
|
|
119
|
+
continue;
|
|
120
|
+
if (content.length >= MAX_CONTEXT_BYTES) {
|
|
121
|
+
content += `\n\n(remaining projects truncated, context size limit reached)\n`;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
const summaryFile = path.join(phrenPath, project, "summary.md");
|
|
125
|
+
const taskFile = resolveTaskFilePath(phrenPath, project);
|
|
126
|
+
if (!fs.existsSync(summaryFile) && !taskFile)
|
|
127
|
+
continue;
|
|
128
|
+
content += `\n\n## ${project}\n`;
|
|
129
|
+
if (fs.existsSync(summaryFile))
|
|
130
|
+
content += fs.readFileSync(summaryFile, "utf8") + "\n";
|
|
131
|
+
if (taskFile && fs.existsSync(taskFile)) {
|
|
132
|
+
let task = fs.readFileSync(taskFile, "utf8");
|
|
133
|
+
const remaining = MAX_CONTEXT_BYTES - content.length;
|
|
134
|
+
if (task.length > remaining && remaining > 0) {
|
|
135
|
+
task = task.slice(0, remaining) + "\n(task truncated)\n";
|
|
136
|
+
}
|
|
137
|
+
content += `\n### Task\n${task}\n`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
writeContextFile(content);
|
|
141
|
+
log(` wrote ${contextFilePath()} (planning mode)`);
|
|
142
|
+
}
|
|
143
|
+
export function writeContextClean(machine, profile, mcpStatus, projects) {
|
|
144
|
+
const mcpLine = formatMcpStatus(mcpStatus);
|
|
145
|
+
let content = `# phren context (clean)\nMachine: ${machine} | Profile: ${profile} | Projects: ${projects.join(", ")}\n`;
|
|
146
|
+
if (mcpLine)
|
|
147
|
+
content += mcpLine + "\n";
|
|
148
|
+
writeContextFile(content);
|
|
149
|
+
log(` wrote ${contextFilePath()} (clean mode)`);
|
|
150
|
+
}
|
|
151
|
+
// ── Memory management ───────────────────────────────────────────────────────
|
|
152
|
+
export function readBackNativeMemory(phrenPath, projects) {
|
|
153
|
+
const projectKey = claudeProjectKey();
|
|
154
|
+
const memoryDir = homePath(".claude", "projects", projectKey, "memory");
|
|
155
|
+
if (!fs.existsSync(memoryDir))
|
|
156
|
+
return;
|
|
157
|
+
for (const project of projects) {
|
|
158
|
+
if (project === "global")
|
|
159
|
+
continue;
|
|
160
|
+
if (!isValidProjectName(project)) {
|
|
161
|
+
log(` skip native memory sync for invalid project name: ${project}`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const nativeFile = path.join(memoryDir, `MEMORY-${project}.md`);
|
|
165
|
+
if (!fs.existsSync(nativeFile))
|
|
166
|
+
continue;
|
|
167
|
+
const content = fs.readFileSync(nativeFile, "utf8");
|
|
168
|
+
const notesMatch = content.match(/^## Notes\n([\s\S]*)$/m);
|
|
169
|
+
if (!notesMatch)
|
|
170
|
+
continue;
|
|
171
|
+
const notes = notesMatch[1]
|
|
172
|
+
.replace(/<!-- Session findings, patterns, decisions -->\n?/, "")
|
|
173
|
+
.trim();
|
|
174
|
+
if (!notes)
|
|
175
|
+
continue;
|
|
176
|
+
const targetFile = path.join(phrenPath, project, "native-notes.md");
|
|
177
|
+
const existing = fs.existsSync(targetFile) ? fs.readFileSync(targetFile, "utf8").trim() : "";
|
|
178
|
+
if (existing === notes)
|
|
179
|
+
continue;
|
|
180
|
+
fs.mkdirSync(path.join(phrenPath, project), { recursive: true });
|
|
181
|
+
fs.writeFileSync(targetFile, notes + "\n");
|
|
182
|
+
log(` synced native memory notes for ${project}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export function rebuildMemory(phrenPath, projects) {
|
|
186
|
+
const projectKey = claudeProjectKey();
|
|
187
|
+
const memoryDir = homePath(".claude", "projects", projectKey, "memory");
|
|
188
|
+
const memoryFile = path.join(memoryDir, "MEMORY.md");
|
|
189
|
+
const hasSummaries = projects.some(p => p !== "global" && fs.existsSync(path.join(phrenPath, p, "summary.md")));
|
|
190
|
+
if (!hasSummaries)
|
|
191
|
+
return;
|
|
192
|
+
fs.mkdirSync(memoryDir, { recursive: true });
|
|
193
|
+
let header = "";
|
|
194
|
+
if (fs.existsSync(memoryFile)) {
|
|
195
|
+
const existing = fs.readFileSync(memoryFile, "utf8");
|
|
196
|
+
const idx = existing.indexOf("<!-- phren:projects:start -->");
|
|
197
|
+
if (idx !== -1)
|
|
198
|
+
header = existing.slice(0, idx);
|
|
199
|
+
}
|
|
200
|
+
let managed = "<!-- phren:projects:start -->\n<!-- Auto-generated by phren link. Do not edit below this line. -->\n\n## Active Projects\n\n| Project | What | Memory |\n|---------|------|--------|\n";
|
|
201
|
+
for (const project of projects) {
|
|
202
|
+
if (project === "global")
|
|
203
|
+
continue;
|
|
204
|
+
const summaryFile = path.join(phrenPath, project, "summary.md");
|
|
205
|
+
if (!fs.existsSync(summaryFile))
|
|
206
|
+
continue;
|
|
207
|
+
const summary = fs.readFileSync(summaryFile, "utf8");
|
|
208
|
+
const whatMatch = summary.match(/^\*\*What:\*\*\s*(.+)/m);
|
|
209
|
+
const what = whatMatch?.[1]?.trim() ?? "(see summary)";
|
|
210
|
+
managed += `| ${displayName(project)} | ${what} | MEMORY-${project}.md |\n`;
|
|
211
|
+
}
|
|
212
|
+
managed += "\n<!-- phren:projects:end -->";
|
|
213
|
+
const freshHeader = "# Root Memory\n\n## Machine Context\nRead `~/.phren-context.md` for profile, active projects, last sync date.\n\n## Cross-Project Notes\n- Read a project's CLAUDE.md before making changes.\n- Per-project memory files (MEMORY-{name}.md) have commands, versions, findings.\n\n";
|
|
214
|
+
fs.writeFileSync(memoryFile, (header || freshHeader) + managed + "\n");
|
|
215
|
+
log(` rebuilt ${memoryFile} (pointer format)`);
|
|
216
|
+
const SUMMARY_START = "<!-- phren:summary:start -->";
|
|
217
|
+
const SUMMARY_END = "<!-- phren:summary:end -->";
|
|
218
|
+
for (const project of projects) {
|
|
219
|
+
if (project === "global")
|
|
220
|
+
continue;
|
|
221
|
+
const summaryFile = path.join(phrenPath, project, "summary.md");
|
|
222
|
+
if (!fs.existsSync(summaryFile))
|
|
223
|
+
continue;
|
|
224
|
+
const summaryContent = fs.readFileSync(summaryFile, "utf8");
|
|
225
|
+
const projectMemory = path.join(memoryDir, `MEMORY-${project}.md`);
|
|
226
|
+
if (!fs.existsSync(projectMemory)) {
|
|
227
|
+
// Create fresh file with managed summary section and user-editable notes block
|
|
228
|
+
fs.writeFileSync(projectMemory, `# ${displayName(project)}\n\n${SUMMARY_START}\n${summaryContent.trimEnd()}\n${SUMMARY_END}\n\n## Notes\n<!-- Session findings, patterns, decisions -->\n`);
|
|
229
|
+
log(` created ${projectMemory}`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// Refresh only the managed summary section; preserve user-editable content outside it
|
|
233
|
+
const existing = fs.readFileSync(projectMemory, "utf8");
|
|
234
|
+
const startIdx = existing.indexOf(SUMMARY_START);
|
|
235
|
+
const endIdx = existing.indexOf(SUMMARY_END);
|
|
236
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
237
|
+
const before = existing.slice(0, startIdx);
|
|
238
|
+
const after = existing.slice(endIdx + SUMMARY_END.length);
|
|
239
|
+
const updated = `${before}${SUMMARY_START}\n${summaryContent.trimEnd()}\n${SUMMARY_END}${after}`;
|
|
240
|
+
if (updated !== existing) {
|
|
241
|
+
fs.writeFileSync(projectMemory, updated);
|
|
242
|
+
log(` refreshed ${projectMemory} (summary section updated)`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (startIdx === -1) {
|
|
246
|
+
// Legacy file without managed section: prepend summary block before the first heading or append
|
|
247
|
+
const firstHeading = existing.indexOf("\n## ");
|
|
248
|
+
const insertAt = firstHeading !== -1 ? firstHeading : existing.length;
|
|
249
|
+
const updated = existing.slice(0, insertAt) +
|
|
250
|
+
`\n\n${SUMMARY_START}\n${summaryContent.trimEnd()}\n${SUMMARY_END}` +
|
|
251
|
+
existing.slice(insertAt);
|
|
252
|
+
fs.writeFileSync(projectMemory, updated);
|
|
253
|
+
log(` refreshed ${projectMemory} (added managed summary section)`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|