coding-friend-cli 1.17.2 → 1.17.4
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 +2 -2
- package/dist/{chunk-YO6JKGR3.js → chunk-DVMWMXDZ.js} +6 -6
- package/dist/{chunk-G6CEEMAR.js → chunk-UWQPMVJY.js} +1 -1
- package/dist/{config-LZFXXOI4.js → config-HVWEV2K6.js} +1 -1
- package/dist/{dev-R3IYWZ3M.js → dev-AZSOM775.js} +1 -1
- package/dist/index.js +26 -26
- package/dist/{init-MF7ISADJ.js → init-73ECEDU7.js} +1 -1
- package/dist/{install-Q4PWEU43.js → install-USFLRCS5.js} +2 -2
- package/dist/{memory-3M2IY6MR.js → memory-BQK2R7BV.js} +10 -10
- package/dist/postinstall.js +1 -1
- package/dist/{uninstall-3PSUDGI4.js → uninstall-QSNKGNHR.js} +1 -1
- package/dist/{update-WL6SFGGO.js → update-PNHTIB6M.js} +2 -2
- package/lib/cf-memory/CHANGELOG.md +5 -0
- package/lib/cf-memory/README.md +2 -2
- package/lib/cf-memory/package.json +1 -1
- package/lib/cf-memory/src/__tests__/project-id.test.ts +43 -0
- package/lib/cf-memory/src/backends/sqlite/index.ts +13 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,8 +70,8 @@ cf memory list --projects # List all project databases with size and metadata
|
|
|
70
70
|
cf memory rm --project-id <id> # Remove a specific project database
|
|
71
71
|
cf memory rm --all # Remove all project databases
|
|
72
72
|
cf memory rm --prune # Remove orphaned projects (source dir missing or 0 memories)
|
|
73
|
-
cf memory start
|
|
74
|
-
cf memory stop
|
|
73
|
+
cf memory start-daemon # Start memory daemon (enables Tier 2 search)
|
|
74
|
+
cf memory stop-daemon # Stop memory daemon
|
|
75
75
|
cf memory rebuild # Rebuild search index from markdown files
|
|
76
76
|
cf memory init # Initialize SQLite backend (Tier 1) and import memories
|
|
77
77
|
cf memory mcp # Show MCP server config for clients
|
|
@@ -33,7 +33,7 @@ _cf_completions() {
|
|
|
33
33
|
|
|
34
34
|
# Subcommands for 'memory'
|
|
35
35
|
if [[ "\${COMP_WORDS[1]}" == "memory" && \${COMP_CWORD} -eq 2 ]]; then
|
|
36
|
-
COMPREPLY=($(compgen -W "status search list rm init start stop rebuild mcp" -- "$cur"))
|
|
36
|
+
COMPREPLY=($(compgen -W "status search list rm init start-daemon stop-daemon rebuild mcp" -- "$cur"))
|
|
37
37
|
return
|
|
38
38
|
fi
|
|
39
39
|
|
|
@@ -129,8 +129,8 @@ var ZSH_FUNCTION_BODY = `_cf() {
|
|
|
129
129
|
'list:List memories (--projects for all DBs)'
|
|
130
130
|
'rm:Remove a project database'
|
|
131
131
|
'init:Initialize Tier 1 (SQLite + Hybrid Search)'
|
|
132
|
-
'start:Start the memory daemon (Tier 2)'
|
|
133
|
-
'stop:Stop the memory daemon'
|
|
132
|
+
'start-daemon:Start the memory daemon (Tier 2)'
|
|
133
|
+
'stop-daemon:Stop the memory daemon'
|
|
134
134
|
'rebuild:Rebuild the daemon search index'
|
|
135
135
|
'mcp:Show MCP server setup instructions'
|
|
136
136
|
)
|
|
@@ -198,8 +198,8 @@ complete -c cf -n "__fish_seen_subcommand_from memory" -a search -d "Search memo
|
|
|
198
198
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a list -d "List memories (--projects for all DBs)"
|
|
199
199
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a rm -d "Remove a project database"
|
|
200
200
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a init -d "Initialize Tier 1 (SQLite + Hybrid Search)"
|
|
201
|
-
complete -c cf -n "__fish_seen_subcommand_from memory" -a start -d "Start the memory daemon (Tier 2)"
|
|
202
|
-
complete -c cf -n "__fish_seen_subcommand_from memory" -a stop -d "Stop the memory daemon"
|
|
201
|
+
complete -c cf -n "__fish_seen_subcommand_from memory" -a start-daemon -d "Start the memory daemon (Tier 2)"
|
|
202
|
+
complete -c cf -n "__fish_seen_subcommand_from memory" -a stop-daemon -d "Stop the memory daemon"
|
|
203
203
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a rebuild -d "Rebuild the daemon search index"
|
|
204
204
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a mcp -d "Show MCP server setup instructions"
|
|
205
205
|
# Session subcommands
|
|
@@ -213,7 +213,7 @@ Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
|
|
|
213
213
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
214
214
|
$commands = @('install','uninstall','disable','enable','init','config','host','mcp','memory','permission','statusline','update','dev','session')
|
|
215
215
|
$devSubcommands = @('on','off','status','restart','sync','update')
|
|
216
|
-
$memorySubcommands = @('status','search','list','rm','init','start','stop','rebuild','mcp')
|
|
216
|
+
$memorySubcommands = @('status','search','list','rm','init','start-daemon','stop-daemon','rebuild','mcp')
|
|
217
217
|
$sessionSubcommands = @('save','load')
|
|
218
218
|
$scopeFlags = @('--user','--global','--project','--local')
|
|
219
219
|
$updateFlags = @('--cli','--plugin','--statusline','--user','--global','--project','--local')
|
package/dist/index.js
CHANGED
|
@@ -14,11 +14,11 @@ program.name("cf").description(
|
|
|
14
14
|
"coding-friend CLI \u2014 host learning docs, setup MCP, init projects"
|
|
15
15
|
).version(pkg.version, "-v, --version");
|
|
16
16
|
program.command("install").description("Install the Coding Friend plugin into Claude Code").option("--user", "Install at user scope (all projects)").option("--global", "Install at user scope (all projects)").option("--project", "Install at project scope (shared via git)").option("--local", "Install at local scope (this machine only)").action(async (opts) => {
|
|
17
|
-
const { installCommand } = await import("./install-
|
|
17
|
+
const { installCommand } = await import("./install-USFLRCS5.js");
|
|
18
18
|
await installCommand(opts);
|
|
19
19
|
});
|
|
20
20
|
program.command("uninstall").description("Uninstall the Coding Friend plugin from Claude Code").option("--user", "Uninstall from user scope (all projects)").option("--global", "Uninstall from user scope (all projects)").option("--project", "Uninstall from project scope").option("--local", "Uninstall from local scope").action(async (opts) => {
|
|
21
|
-
const { uninstallCommand } = await import("./uninstall-
|
|
21
|
+
const { uninstallCommand } = await import("./uninstall-QSNKGNHR.js");
|
|
22
22
|
await uninstallCommand(opts);
|
|
23
23
|
});
|
|
24
24
|
program.command("disable").description("Disable the Coding Friend plugin without uninstalling").option("--user", "Disable at user scope (all projects)").option("--global", "Disable at user scope (all projects)").option("--project", "Disable at project scope").option("--local", "Disable at local scope").action(async (opts) => {
|
|
@@ -30,11 +30,11 @@ program.command("enable").description("Re-enable the Coding Friend plugin").opti
|
|
|
30
30
|
await enableCommand(opts);
|
|
31
31
|
});
|
|
32
32
|
program.command("init").description("Initialize coding-friend in current project").action(async () => {
|
|
33
|
-
const { initCommand } = await import("./init-
|
|
33
|
+
const { initCommand } = await import("./init-73ECEDU7.js");
|
|
34
34
|
await initCommand();
|
|
35
35
|
});
|
|
36
36
|
program.command("config").description("Manage Coding Friend configuration").action(async () => {
|
|
37
|
-
const { configCommand } = await import("./config-
|
|
37
|
+
const { configCommand } = await import("./config-HVWEV2K6.js");
|
|
38
38
|
await configCommand();
|
|
39
39
|
});
|
|
40
40
|
program.command("host").description("Build and serve learning docs as a static website").argument("[path]", "path to docs folder").option("-p, --port <port>", "port number", "3333").action(async (path, opts) => {
|
|
@@ -54,7 +54,7 @@ program.command("statusline").description("Setup coding-friend statusline in Cla
|
|
|
54
54
|
await statuslineCommand();
|
|
55
55
|
});
|
|
56
56
|
program.command("update").description("Update coding-friend plugin, CLI, and statusline").option("--cli", "Update only the CLI (npm package)").option("--plugin", "Update only the Claude Code plugin").option("--statusline", "Update only the statusline").option("--user", "Update plugin at user scope (all projects)").option("--global", "Update plugin at user scope (all projects)").option("--project", "Update plugin at project scope").option("--local", "Update plugin at local scope").action(async (opts) => {
|
|
57
|
-
const { updateCommand } = await import("./update-
|
|
57
|
+
const { updateCommand } = await import("./update-PNHTIB6M.js");
|
|
58
58
|
await updateCommand(opts);
|
|
59
59
|
});
|
|
60
60
|
var session = program.command("session").description("Save and load Claude Code sessions across machines");
|
|
@@ -86,45 +86,45 @@ Memory subcommands:
|
|
|
86
86
|
memory list List memories in current project (--projects for all DBs)
|
|
87
87
|
memory rm Remove a project database (--project-id <id>, --all, or --prune)
|
|
88
88
|
memory init Initialize Tier 1 (install SQLite deps, import existing memories)
|
|
89
|
-
memory start
|
|
90
|
-
memory stop
|
|
89
|
+
memory start-daemon Start the memory daemon (Tier 2)
|
|
90
|
+
memory stop-daemon Stop the memory daemon
|
|
91
91
|
memory rebuild Rebuild the daemon search index
|
|
92
92
|
memory mcp Show MCP server setup instructions`
|
|
93
93
|
);
|
|
94
94
|
memory.command("status").description("Show memory system status").action(async () => {
|
|
95
|
-
const { memoryStatusCommand } = await import("./memory-
|
|
95
|
+
const { memoryStatusCommand } = await import("./memory-BQK2R7BV.js");
|
|
96
96
|
await memoryStatusCommand();
|
|
97
97
|
});
|
|
98
98
|
memory.command("search").description("Search memories by query").argument("<query>", "search query").action(async (query) => {
|
|
99
|
-
const { memorySearchCommand } = await import("./memory-
|
|
99
|
+
const { memorySearchCommand } = await import("./memory-BQK2R7BV.js");
|
|
100
100
|
await memorySearchCommand(query);
|
|
101
101
|
});
|
|
102
102
|
memory.command("list").description(
|
|
103
103
|
"List memories in current project, or all projects with --projects"
|
|
104
104
|
).option("--projects", "List all project databases with size and metadata").action(async (opts) => {
|
|
105
|
-
const { memoryListCommand } = await import("./memory-
|
|
105
|
+
const { memoryListCommand } = await import("./memory-BQK2R7BV.js");
|
|
106
106
|
await memoryListCommand(opts);
|
|
107
107
|
});
|
|
108
108
|
memory.command("init").description(
|
|
109
109
|
"Initialize Tier 1 \u2014 install SQLite deps and import existing memories"
|
|
110
110
|
).action(async () => {
|
|
111
|
-
const { memoryInitCommand } = await import("./memory-
|
|
111
|
+
const { memoryInitCommand } = await import("./memory-BQK2R7BV.js");
|
|
112
112
|
await memoryInitCommand();
|
|
113
113
|
});
|
|
114
|
-
memory.command("start").description("Start the memory daemon (Tier 2 \u2014 MiniSearch)").action(async () => {
|
|
115
|
-
const {
|
|
116
|
-
await
|
|
114
|
+
memory.command("start-daemon").description("Start the memory daemon (Tier 2 \u2014 MiniSearch)").action(async () => {
|
|
115
|
+
const { memoryStartDaemonCommand } = await import("./memory-BQK2R7BV.js");
|
|
116
|
+
await memoryStartDaemonCommand();
|
|
117
117
|
});
|
|
118
|
-
memory.command("stop").description("Stop the memory daemon").action(async () => {
|
|
119
|
-
const {
|
|
120
|
-
await
|
|
118
|
+
memory.command("stop-daemon").description("Stop the memory daemon").action(async () => {
|
|
119
|
+
const { memoryStopDaemonCommand } = await import("./memory-BQK2R7BV.js");
|
|
120
|
+
await memoryStopDaemonCommand();
|
|
121
121
|
});
|
|
122
122
|
memory.command("rebuild").description("Rebuild the daemon search index").action(async () => {
|
|
123
|
-
const { memoryRebuildCommand } = await import("./memory-
|
|
123
|
+
const { memoryRebuildCommand } = await import("./memory-BQK2R7BV.js");
|
|
124
124
|
await memoryRebuildCommand();
|
|
125
125
|
});
|
|
126
126
|
memory.command("mcp").description("Show MCP server setup instructions").action(async () => {
|
|
127
|
-
const { memoryMcpCommand } = await import("./memory-
|
|
127
|
+
const { memoryMcpCommand } = await import("./memory-BQK2R7BV.js");
|
|
128
128
|
await memoryMcpCommand();
|
|
129
129
|
});
|
|
130
130
|
memory.command("rm").description("Remove a project database").option("--project-id <id>", "Project ID to remove").option("--all", "Remove all project databases").option(
|
|
@@ -132,7 +132,7 @@ memory.command("rm").description("Remove a project database").option("--project-
|
|
|
132
132
|
"Remove orphaned projects (source dir missing or 0 memories)"
|
|
133
133
|
).action(
|
|
134
134
|
async (opts) => {
|
|
135
|
-
const { memoryRmCommand } = await import("./memory-
|
|
135
|
+
const { memoryRmCommand } = await import("./memory-BQK2R7BV.js");
|
|
136
136
|
await memoryRmCommand(opts);
|
|
137
137
|
}
|
|
138
138
|
);
|
|
@@ -149,35 +149,35 @@ Dev subcommands:
|
|
|
149
149
|
dev update [path] Update local dev plugin to latest version`
|
|
150
150
|
);
|
|
151
151
|
dev.command("on").description("Switch to local plugin source").argument("[path]", "path to local coding-friend repo (default: cwd)").action(async (path) => {
|
|
152
|
-
const { devOnCommand } = await import("./dev-
|
|
152
|
+
const { devOnCommand } = await import("./dev-AZSOM775.js");
|
|
153
153
|
await devOnCommand(path);
|
|
154
154
|
});
|
|
155
155
|
dev.command("off").description("Switch back to remote marketplace").action(async () => {
|
|
156
|
-
const { devOffCommand } = await import("./dev-
|
|
156
|
+
const { devOffCommand } = await import("./dev-AZSOM775.js");
|
|
157
157
|
await devOffCommand();
|
|
158
158
|
});
|
|
159
159
|
dev.command("status").description("Show current dev mode").action(async () => {
|
|
160
|
-
const { devStatusCommand } = await import("./dev-
|
|
160
|
+
const { devStatusCommand } = await import("./dev-AZSOM775.js");
|
|
161
161
|
await devStatusCommand();
|
|
162
162
|
});
|
|
163
163
|
dev.command("sync").description(
|
|
164
164
|
"Copy local source files to plugin cache (no version bump needed)"
|
|
165
165
|
).action(async () => {
|
|
166
|
-
const { devSyncCommand } = await import("./dev-
|
|
166
|
+
const { devSyncCommand } = await import("./dev-AZSOM775.js");
|
|
167
167
|
await devSyncCommand();
|
|
168
168
|
});
|
|
169
169
|
dev.command("restart").description("Reinstall local dev plugin (off + on)").argument(
|
|
170
170
|
"[path]",
|
|
171
171
|
"path to local coding-friend repo (default: saved path or cwd)"
|
|
172
172
|
).action(async (path) => {
|
|
173
|
-
const { devRestartCommand } = await import("./dev-
|
|
173
|
+
const { devRestartCommand } = await import("./dev-AZSOM775.js");
|
|
174
174
|
await devRestartCommand(path);
|
|
175
175
|
});
|
|
176
176
|
dev.command("update").description("Update local dev plugin to latest version (off + on)").argument(
|
|
177
177
|
"[path]",
|
|
178
178
|
"path to local coding-friend repo (default: saved path or cwd)"
|
|
179
179
|
).action(async (path) => {
|
|
180
|
-
const { devUpdateCommand } = await import("./dev-
|
|
180
|
+
const { devUpdateCommand } = await import("./dev-AZSOM775.js");
|
|
181
181
|
await devUpdateCommand(path);
|
|
182
182
|
});
|
|
183
183
|
program.parse();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getLatestVersion,
|
|
3
3
|
semverCompare
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UWQPMVJY.js";
|
|
5
5
|
import {
|
|
6
6
|
enableMarketplaceAutoUpdate,
|
|
7
7
|
isMarketplaceRegistered,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import "./chunk-POC2WHU2.js";
|
|
14
14
|
import {
|
|
15
15
|
ensureShellCompletion
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-DVMWMXDZ.js";
|
|
17
17
|
import {
|
|
18
18
|
resolveScope
|
|
19
19
|
} from "./chunk-C5LYVVEI.js";
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
} from "./chunk-W5CD7WTX.js";
|
|
16
16
|
|
|
17
17
|
// src/commands/memory.ts
|
|
18
|
-
import { createHash } from "crypto";
|
|
19
18
|
import { existsSync, readdirSync, statSync, rmSync } from "fs";
|
|
20
19
|
import { join, resolve, sep } from "path";
|
|
21
20
|
import { homedir } from "os";
|
|
@@ -87,7 +86,7 @@ async function memoryStatusCommand() {
|
|
|
87
86
|
if (running && daemonInfo) {
|
|
88
87
|
const uptime = (Date.now() - daemonInfo.startedAt) / 1e3;
|
|
89
88
|
log.info(
|
|
90
|
-
`Daemon: ${chalk.green("running")} (PID ${daemonInfo.pid}, uptime ${formatUptime(uptime)}) ${chalk.dim('Turn it off by "cf memory stop"')}`
|
|
89
|
+
`Daemon: ${chalk.green("running")} (PID ${daemonInfo.pid}, uptime ${formatUptime(uptime)}) ${chalk.dim('Turn it off by "cf memory stop-daemon"')}`
|
|
91
90
|
);
|
|
92
91
|
} else if (sqliteAvailable) {
|
|
93
92
|
log.info(
|
|
@@ -95,7 +94,7 @@ async function memoryStatusCommand() {
|
|
|
95
94
|
);
|
|
96
95
|
} else {
|
|
97
96
|
log.info(
|
|
98
|
-
`Daemon: ${chalk.dim("stopped")} ${chalk.dim('(run "cf memory start" for Tier 2 search)')}`
|
|
97
|
+
`Daemon: ${chalk.dim("stopped")} ${chalk.dim('(run "cf memory start-daemon" for Tier 2 search)')}`
|
|
99
98
|
);
|
|
100
99
|
}
|
|
101
100
|
if (sqliteAvailable) {
|
|
@@ -204,7 +203,7 @@ async function memoryListCommand(opts) {
|
|
|
204
203
|
console.log(result);
|
|
205
204
|
}
|
|
206
205
|
}
|
|
207
|
-
async function
|
|
206
|
+
async function memoryStartDaemonCommand() {
|
|
208
207
|
const memoryDir = getMemoryDir();
|
|
209
208
|
const mcpDir = getLibPath("cf-memory");
|
|
210
209
|
ensureBuilt(mcpDir);
|
|
@@ -226,7 +225,7 @@ async function memoryStartCommand() {
|
|
|
226
225
|
process.exit(1);
|
|
227
226
|
}
|
|
228
227
|
}
|
|
229
|
-
async function
|
|
228
|
+
async function memoryStopDaemonCommand() {
|
|
230
229
|
const mcpDir = getLibPath("cf-memory");
|
|
231
230
|
ensureBuilt(mcpDir);
|
|
232
231
|
const { stopDaemon, isDaemonRunning } = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
@@ -273,7 +272,7 @@ async function memoryRebuildCommand() {
|
|
|
273
272
|
if (!await isDaemonRunning()) {
|
|
274
273
|
log.info("No SQLite deps and daemon not running. Nothing to rebuild.");
|
|
275
274
|
log.dim("Install Tier 1 deps: cf memory init");
|
|
276
|
-
log.dim("Or start the daemon: cf memory start");
|
|
275
|
+
log.dim("Or start the daemon: cf memory start-daemon");
|
|
277
276
|
return;
|
|
278
277
|
}
|
|
279
278
|
const { DaemonClient } = await import(join(mcpDir, "dist/lib/daemon-client.js"));
|
|
@@ -443,12 +442,13 @@ async function memoryListProjectsCommand() {
|
|
|
443
442
|
return;
|
|
444
443
|
}
|
|
445
444
|
const currentMemoryDir = resolve(getMemoryDir());
|
|
446
|
-
const
|
|
445
|
+
const { projectId } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
|
|
446
|
+
const currentProjectId = projectId(currentMemoryDir);
|
|
447
447
|
log.step(`Scanning ${dirs.length} project(s)...
|
|
448
448
|
`);
|
|
449
449
|
const projects = [];
|
|
450
450
|
for (const id of dirs) {
|
|
451
|
-
const knownDir = id ===
|
|
451
|
+
const knownDir = id === currentProjectId ? currentMemoryDir : void 0;
|
|
452
452
|
projects.push(getProjectInfo(join(baseDir, id), id, mcpDir, knownDir));
|
|
453
453
|
}
|
|
454
454
|
projects.sort((a, b) => b.size - a.size);
|
|
@@ -643,7 +643,7 @@ export {
|
|
|
643
643
|
memoryRebuildCommand,
|
|
644
644
|
memoryRmCommand,
|
|
645
645
|
memorySearchCommand,
|
|
646
|
-
|
|
646
|
+
memoryStartDaemonCommand,
|
|
647
647
|
memoryStatusCommand,
|
|
648
|
-
|
|
648
|
+
memoryStopDaemonCommand
|
|
649
649
|
};
|
package/dist/postinstall.js
CHANGED
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
getLatestVersion,
|
|
3
3
|
semverCompare,
|
|
4
4
|
updateCommand
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UWQPMVJY.js";
|
|
6
6
|
import "./chunk-ORACWEDN.js";
|
|
7
7
|
import "./chunk-POC2WHU2.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-DVMWMXDZ.js";
|
|
9
9
|
import "./chunk-C5LYVVEI.js";
|
|
10
10
|
import "./chunk-CYQU33FY.js";
|
|
11
11
|
import "./chunk-RWUTFVRB.js";
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# CF Memory Changelog
|
|
2
2
|
|
|
3
|
+
## v0.1.3 (2026-03-19)
|
|
4
|
+
|
|
5
|
+
- Use path-based project IDs instead of SHA256 hashes for human-readable project directories (e.g. `-Users-thi-git-foo` instead of `a1b2c3d4e5f6`) [#9c4cac0](https://github.com/dinhanhthi/coding-friend/commit/9c4cac0)
|
|
6
|
+
- Rename `cf memory start`/`stop` to `cf memory start-daemon`/`stop-daemon` in documentation [#acbe789](https://github.com/dinhanhthi/coding-friend/commit/acbe789)
|
|
7
|
+
|
|
3
8
|
## v0.1.1 (2026-03-17)
|
|
4
9
|
|
|
5
10
|
- Fix `today()` to capture full timestamp (`YYYY-MM-DD HH:MM`) instead of date-only for memory created/updated fields [#31e0824](https://github.com/dinhanhthi/coding-friend/commit/31e0824)
|
package/lib/cf-memory/README.md
CHANGED
|
@@ -262,8 +262,8 @@ The `cf` CLI exposes memory commands that use this package:
|
|
|
262
262
|
| `cf memory status` | Show current tier, daemon status, memory count |
|
|
263
263
|
| `cf memory search <query>` | Search memories from the terminal |
|
|
264
264
|
| `cf memory list` | List all stored memories |
|
|
265
|
-
| `cf memory start`
|
|
266
|
-
| `cf memory stop`
|
|
265
|
+
| `cf memory start-daemon` | Start the MiniSearch daemon (Tier 2) |
|
|
266
|
+
| `cf memory stop-daemon` | Stop the daemon |
|
|
267
267
|
| `cf memory rebuild` | Rebuild search index (Tier 1 direct or via daemon) |
|
|
268
268
|
| `cf memory init` | Install Tier 1 deps + import existing memories into SQLite |
|
|
269
269
|
| `cf memory mcp` | Print MCP server config for use in Claude Desktop / other clients |
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { projectId } from "../backends/sqlite/index.js";
|
|
3
|
+
|
|
4
|
+
describe("projectId", () => {
|
|
5
|
+
it("strips /docs/memory suffix and encodes path", () => {
|
|
6
|
+
expect(projectId("/Users/thi/git/coding-friend/docs/memory")).toBe(
|
|
7
|
+
"-Users-thi-git-coding-friend",
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("strips /memory suffix when docsDir is custom", () => {
|
|
12
|
+
expect(projectId("/Users/thi/git/foo/memory")).toBe("-Users-thi-git-foo");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("handles path without known suffix", () => {
|
|
16
|
+
expect(projectId("/Users/thi/git/foo/custom-docs")).toBe(
|
|
17
|
+
"-Users-thi-git-foo-custom-docs",
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("strips trailing slash before encoding", () => {
|
|
22
|
+
expect(projectId("/Users/thi/git/foo/docs/memory/")).toBe(
|
|
23
|
+
projectId("/Users/thi/git/foo/docs/memory"),
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("strips multiple trailing slashes", () => {
|
|
28
|
+
expect(projectId("/Users/thi/git/foo/docs/memory///")).toBe(
|
|
29
|
+
"-Users-thi-git-foo",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("produces same result with and without trailing slash", () => {
|
|
34
|
+
const withSlash = projectId("/a/b/c/docs/memory/");
|
|
35
|
+
const withoutSlash = projectId("/a/b/c/docs/memory");
|
|
36
|
+
expect(withSlash).toBe(withoutSlash);
|
|
37
|
+
expect(withSlash).toBe("-a-b-c");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("handles deeply nested paths", () => {
|
|
41
|
+
expect(projectId("/a/b/c/d/e/f/g")).toBe("-a-b-c-d-e-f-g");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -7,11 +7,10 @@
|
|
|
7
7
|
* Markdown files remain the source of truth. SQLite is a derived index
|
|
8
8
|
* that can be rebuilt from markdown at any time.
|
|
9
9
|
*
|
|
10
|
-
* DB path: ~/.coding-friend/memory/projects/{
|
|
10
|
+
* DB path: ~/.coding-friend/memory/projects/{encoded-path}/db.sqlite
|
|
11
11
|
*/
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
|
-
import crypto from "node:crypto";
|
|
15
14
|
import type { MemoryBackend } from "../../lib/backend.js";
|
|
16
15
|
import { MarkdownBackend } from "../markdown.js";
|
|
17
16
|
import {
|
|
@@ -46,14 +45,17 @@ import {
|
|
|
46
45
|
import { hybridSearch } from "./search.js";
|
|
47
46
|
|
|
48
47
|
/**
|
|
49
|
-
*
|
|
48
|
+
* Derive the project root from a docsDir path by stripping known suffixes.
|
|
49
|
+
* Then encode it into a filesystem-safe directory name for project isolation.
|
|
50
|
+
* Uses the same encoding as Claude Code's project directories:
|
|
51
|
+
* resolve + strip known suffixes + strip trailing slashes + replace all "/" with "-"
|
|
52
|
+
* e.g. /Users/thi/git/foo/docs/memory → -Users-thi-git-foo
|
|
50
53
|
*/
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.slice(0, 12);
|
|
54
|
+
export function projectId(docsDir: string): string {
|
|
55
|
+
let resolved = path.resolve(docsDir).replace(/\/+$/, "");
|
|
56
|
+
// Strip known memory directory suffixes to get the project root
|
|
57
|
+
resolved = resolved.replace(/\/docs\/memory$/, "").replace(/\/memory$/, "");
|
|
58
|
+
return resolved.replace(/\//g, "-");
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
export interface SqliteBackendOptions {
|
|
@@ -83,8 +85,8 @@ export class SqliteBackend implements MemoryBackend {
|
|
|
83
85
|
this.dbPath = opts.dbPath;
|
|
84
86
|
} else {
|
|
85
87
|
const depsDir = opts?.depsDir ?? this.getDefaultDepsDir();
|
|
86
|
-
const
|
|
87
|
-
const projectDir = path.join(depsDir, "projects",
|
|
88
|
+
const id = projectId(docsDir);
|
|
89
|
+
const projectDir = path.join(depsDir, "projects", id);
|
|
88
90
|
this.dbPath = path.join(projectDir, "db.sqlite");
|
|
89
91
|
}
|
|
90
92
|
|