coding-friend-cli 1.34.1 → 1.35.0

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.
Files changed (35) hide show
  1. package/README.md +1 -0
  2. package/dist/chunk-2DTXVMPQ.js +26 -0
  3. package/dist/{chunk-DHH6SRXV.js → chunk-57EGAXYI.js} +3 -2
  4. package/dist/{chunk-AZ6AK5M4.js → chunk-B2NFXRFP.js} +12 -2
  5. package/dist/{chunk-QCKIBJF6.js → chunk-C5YWESSF.js} +2 -6
  6. package/dist/chunk-E5J24GD5.js +190 -0
  7. package/dist/{chunk-G4QGJEV7.js → chunk-H5TXJZD7.js} +6 -6
  8. package/dist/{chunk-FAANBGCB.js → chunk-L53RNEV3.js} +116 -73
  9. package/dist/{chunk-DMJCO6LJ.js → chunk-RLUNNJWJ.js} +2 -1
  10. package/dist/{chunk-LXZ47XCD.js → chunk-WNTMKIKC.js} +1 -1
  11. package/dist/chunk-WSUYLIYX.js +61 -0
  12. package/dist/clean-67ACB4OS.js +95 -0
  13. package/dist/{config-44VQBNMS.js → config-HTQAALMA.js} +181 -120
  14. package/dist/{dev-QXVERU42.js → dev-PFUF67SH.js} +7 -7
  15. package/dist/{disable-ZWQVU3QZ.js → disable-CDOM7SSI.js} +2 -2
  16. package/dist/{enable-NXP6APNI.js → enable-SF7742UR.js} +2 -2
  17. package/dist/{host-6IBKKCPA.js → host-HKA4OHBM.js} +3 -3
  18. package/dist/index.js +38 -34
  19. package/dist/{init-KY3ZPEWP.js → init-YM7YEEGK.js} +153 -85
  20. package/dist/{install-22BLHQ6D.js → install-VYZSTXN6.js} +8 -8
  21. package/dist/{mcp-OGZAZZRF.js → mcp-7MO2MCLE.js} +69 -66
  22. package/dist/{mcp-serve-learn-UNW6VH5M.js → mcp-serve-learn-OKVC5WV3.js} +19 -3
  23. package/dist/{memory-JM45DEIX.js → memory-XALEMWQF.js} +7 -7
  24. package/dist/{permission-DNW7DE24.js → permission-C5R3LMZQ.js} +2 -2
  25. package/dist/postinstall.js +1 -1
  26. package/dist/{session-RCEE6P35.js → session-M4KRQ4VU.js} +2 -2
  27. package/dist/{status-NMSVOSC4.js → status-2XM5OZDR.js} +19 -14
  28. package/dist/{statusline-GKVAX7GR.js → statusline-VFIA7OS2.js} +3 -3
  29. package/dist/{uninstall-6ZJRS2H4.js → uninstall-QKEC3O64.js} +6 -6
  30. package/dist/{update-P5ZR2JVQ.js → update-IV4Y3T72.js} +6 -6
  31. package/package.json +1 -1
  32. package/dist/chunk-CZDXMFEZ.js +0 -118
  33. package/dist/chunk-FI5HEQ43.js +0 -85
  34. package/dist/{chunk-Z5Q533UG.js → chunk-HPNRQYLM.js} +3 -3
  35. package/dist/{update-check-5SCD2JR2.js → update-check-BPCQ25AR.js} +3 -3
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  resolveDocsDir
3
- } from "./chunk-AZ6AK5M4.js";
3
+ } from "./chunk-B2NFXRFP.js";
4
4
  import {
5
5
  getLibPath
6
6
  } from "./chunk-RZRT7NGT.js";
7
- import "./chunk-DHH6SRXV.js";
8
- import "./chunk-TWKNGPBO.js";
7
+ import "./chunk-57EGAXYI.js";
9
8
  import {
10
9
  run,
11
10
  streamExec
12
11
  } from "./chunk-EVGXUDX4.js";
12
+ import "./chunk-TWKNGPBO.js";
13
13
  import {
14
14
  log,
15
15
  printBanner
package/dist/index.js CHANGED
@@ -14,64 +14,68 @@ 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-22BLHQ6D.js");
17
+ const { installCommand } = await import("./install-VYZSTXN6.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-6ZJRS2H4.js");
21
+ const { uninstallCommand } = await import("./uninstall-QKEC3O64.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) => {
25
- const { disableCommand } = await import("./disable-ZWQVU3QZ.js");
25
+ const { disableCommand } = await import("./disable-CDOM7SSI.js");
26
26
  await disableCommand(opts);
27
27
  });
28
28
  program.command("enable").description("Re-enable the Coding Friend plugin").option("--user", "Enable at user scope (all projects)").option("--global", "Enable at user scope (all projects)").option("--project", "Enable at project scope").option("--local", "Enable at local scope").action(async (opts) => {
29
- const { enableCommand } = await import("./enable-NXP6APNI.js");
29
+ const { enableCommand } = await import("./enable-SF7742UR.js");
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-KY3ZPEWP.js");
33
+ const { initCommand } = await import("./init-YM7YEEGK.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-44VQBNMS.js");
37
+ const { configCommand } = await import("./config-HTQAALMA.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) => {
41
- const { hostCommand } = await import("./host-6IBKKCPA.js");
41
+ const { hostCommand } = await import("./host-HKA4OHBM.js");
42
42
  await hostCommand(path, opts);
43
43
  });
44
- program.command("mcp").description("Setup MCP server for learning docs").argument("[path]", "path to docs folder").action(async (path) => {
45
- const { mcpCommand } = await import("./mcp-OGZAZZRF.js");
46
- await mcpCommand(path);
44
+ program.command("mcp").description("Setup MCP server for learning docs").action(async () => {
45
+ const { mcpCommand } = await import("./mcp-7MO2MCLE.js");
46
+ await mcpCommand();
47
47
  });
48
48
  program.command("mcp-serve").description("Start the cf-memory MCP server (used internally by npx)").argument("<memoryDir>", "path to memory directory").action(async (memoryDir) => {
49
49
  const { mcpServeCommand } = await import("./mcp-serve-3FEPFVVQ.js");
50
50
  await mcpServeCommand(memoryDir);
51
51
  });
52
52
  program.command("mcp-serve-learn").description("Start the learn MCP server (used internally by npx)").argument("<docsDir>", "path to docs directory").action(async (docsDir) => {
53
- const { mcpServeLearnCommand } = await import("./mcp-serve-learn-UNW6VH5M.js");
53
+ const { mcpServeLearnCommand } = await import("./mcp-serve-learn-OKVC5WV3.js");
54
54
  await mcpServeLearnCommand(docsDir);
55
55
  });
56
56
  program.command("permission").description("Manage Claude Code permission rules for Coding Friend").option("--all", "Apply all recommended permissions without prompts").option("--user", "Save to user-level settings (~/.claude/settings.json)").option(
57
57
  "--project",
58
58
  "Save to project-level settings (.claude/settings.local.json)"
59
59
  ).action(async (opts) => {
60
- const { permissionCommand } = await import("./permission-DNW7DE24.js");
60
+ const { permissionCommand } = await import("./permission-C5R3LMZQ.js");
61
61
  await permissionCommand(opts);
62
62
  });
63
63
  program.command("statusline").description("Setup coding-friend statusline in Claude Code").action(async () => {
64
- const { statuslineCommand } = await import("./statusline-GKVAX7GR.js");
64
+ const { statuslineCommand } = await import("./statusline-VFIA7OS2.js");
65
65
  await statuslineCommand();
66
66
  });
67
67
  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) => {
68
- const { updateCommand } = await import("./update-P5ZR2JVQ.js");
68
+ const { updateCommand } = await import("./update-IV4Y3T72.js");
69
69
  await updateCommand(opts);
70
70
  });
71
71
  program.command("status").description("Show comprehensive Coding Friend status").action(async () => {
72
- const { statusCommand } = await import("./status-NMSVOSC4.js");
72
+ const { statusCommand } = await import("./status-2XM5OZDR.js");
73
73
  await statusCommand();
74
74
  });
75
+ program.command("clean").description("Clean files generated by Coding Friend in docs/").action(async () => {
76
+ const { cleanCommand } = await import("./clean-67ACB4OS.js");
77
+ await cleanCommand();
78
+ });
75
79
  var session = program.command("session").description("Save and load Claude Code sessions across machines");
76
80
  program.addHelpText(
77
81
  "after",
@@ -84,11 +88,11 @@ session.command("save").description("Save current Claude Code session to sync fo
84
88
  "-s, --session-id <id>",
85
89
  "session UUID to save (default: auto-detect newest)"
86
90
  ).option("-l, --label <label>", "label for this session").action(async (opts) => {
87
- const { sessionSaveCommand } = await import("./session-RCEE6P35.js");
91
+ const { sessionSaveCommand } = await import("./session-M4KRQ4VU.js");
88
92
  await sessionSaveCommand(opts);
89
93
  });
90
94
  session.command("load").description("Load a saved session from sync folder").action(async () => {
91
- const { sessionLoadCommand } = await import("./session-RCEE6P35.js");
95
+ const { sessionLoadCommand } = await import("./session-M4KRQ4VU.js");
92
96
  await sessionLoadCommand();
93
97
  });
94
98
  var memory = program.command("memory").description("AI memory system \u2014 store and search project knowledge");
@@ -108,43 +112,43 @@ Memory subcommands:
108
112
  memory mcp Show MCP server setup instructions`
109
113
  );
110
114
  memory.command("status").description("Show memory system status").action(async () => {
111
- const { memoryStatusCommand } = await import("./memory-JM45DEIX.js");
115
+ const { memoryStatusCommand } = await import("./memory-XALEMWQF.js");
112
116
  await memoryStatusCommand();
113
117
  });
114
118
  memory.command("search").description("Search memories by query").argument("<query>", "search query").action(async (query) => {
115
- const { memorySearchCommand } = await import("./memory-JM45DEIX.js");
119
+ const { memorySearchCommand } = await import("./memory-XALEMWQF.js");
116
120
  await memorySearchCommand(query);
117
121
  });
118
122
  memory.command("list").description(
119
123
  "List memories in current project, or all projects with --projects"
120
124
  ).option("--projects", "List all project databases with size and metadata").action(async (opts) => {
121
- const { memoryListCommand } = await import("./memory-JM45DEIX.js");
125
+ const { memoryListCommand } = await import("./memory-XALEMWQF.js");
122
126
  await memoryListCommand(opts);
123
127
  });
124
128
  memory.command("init").description(
125
129
  "Initialize memory system \u2014 interactive wizard (first time) or config menu"
126
130
  ).action(async () => {
127
- const { memoryInitCommand } = await import("./memory-JM45DEIX.js");
131
+ const { memoryInitCommand } = await import("./memory-XALEMWQF.js");
128
132
  await memoryInitCommand();
129
133
  });
130
134
  memory.command("config").description("Configure memory system settings").action(async () => {
131
- const { memoryConfigCommand } = await import("./memory-JM45DEIX.js");
135
+ const { memoryConfigCommand } = await import("./memory-XALEMWQF.js");
132
136
  await memoryConfigCommand();
133
137
  });
134
138
  memory.command("start-daemon").description("Start the memory daemon (Tier 2 \u2014 MiniSearch)").action(async () => {
135
- const { memoryStartDaemonCommand } = await import("./memory-JM45DEIX.js");
139
+ const { memoryStartDaemonCommand } = await import("./memory-XALEMWQF.js");
136
140
  await memoryStartDaemonCommand();
137
141
  });
138
142
  memory.command("stop-daemon").description("Stop the memory daemon").action(async () => {
139
- const { memoryStopDaemonCommand } = await import("./memory-JM45DEIX.js");
143
+ const { memoryStopDaemonCommand } = await import("./memory-XALEMWQF.js");
140
144
  await memoryStopDaemonCommand();
141
145
  });
142
146
  memory.command("rebuild").description("Rebuild the daemon search index").action(async () => {
143
- const { memoryRebuildCommand } = await import("./memory-JM45DEIX.js");
147
+ const { memoryRebuildCommand } = await import("./memory-XALEMWQF.js");
144
148
  await memoryRebuildCommand();
145
149
  });
146
150
  memory.command("mcp").description("Show MCP server setup instructions").action(async () => {
147
- const { memoryMcpCommand } = await import("./memory-JM45DEIX.js");
151
+ const { memoryMcpCommand } = await import("./memory-XALEMWQF.js");
148
152
  await memoryMcpCommand();
149
153
  });
150
154
  memory.command("rm").description("Remove a project database").option("--project-id <id>", "Project ID to remove").option("--all", "Remove all project databases").option(
@@ -152,7 +156,7 @@ memory.command("rm").description("Remove a project database").option("--project-
152
156
  "Remove orphaned projects (source dir missing or 0 memories)"
153
157
  ).action(
154
158
  async (opts) => {
155
- const { memoryRmCommand } = await import("./memory-JM45DEIX.js");
159
+ const { memoryRmCommand } = await import("./memory-XALEMWQF.js");
156
160
  await memoryRmCommand(opts);
157
161
  }
158
162
  );
@@ -178,41 +182,41 @@ Dev subcommands:
178
182
  dev update [path] Update local dev plugin to latest version`
179
183
  );
180
184
  dev.command("on").description("Switch to local plugin source").argument("[path]", "path to local coding-friend repo (default: cwd)").action(async (path) => {
181
- const { devOnCommand } = await import("./dev-QXVERU42.js");
185
+ const { devOnCommand } = await import("./dev-PFUF67SH.js");
182
186
  await devOnCommand(path);
183
187
  });
184
188
  dev.command("off").description("Switch back to remote marketplace").action(async () => {
185
- const { devOffCommand } = await import("./dev-QXVERU42.js");
189
+ const { devOffCommand } = await import("./dev-PFUF67SH.js");
186
190
  await devOffCommand();
187
191
  });
188
192
  dev.command("status").description("Show current dev mode").action(async () => {
189
- const { devStatusCommand } = await import("./dev-QXVERU42.js");
193
+ const { devStatusCommand } = await import("./dev-PFUF67SH.js");
190
194
  await devStatusCommand();
191
195
  });
192
196
  dev.command("sync").description(
193
197
  "Copy local source files to plugin cache (no version bump needed)"
194
198
  ).action(async () => {
195
- const { devSyncCommand } = await import("./dev-QXVERU42.js");
199
+ const { devSyncCommand } = await import("./dev-PFUF67SH.js");
196
200
  await devSyncCommand();
197
201
  });
198
202
  dev.command("restart").description("Reinstall local dev plugin (off + on)").argument(
199
203
  "[path]",
200
204
  "path to local coding-friend repo (default: saved path or cwd)"
201
205
  ).action(async (path) => {
202
- const { devRestartCommand } = await import("./dev-QXVERU42.js");
206
+ const { devRestartCommand } = await import("./dev-PFUF67SH.js");
203
207
  await devRestartCommand(path);
204
208
  });
205
209
  dev.command("update").description("Update local dev plugin to latest version (off + on)").argument(
206
210
  "[path]",
207
211
  "path to local coding-friend repo (default: saved path or cwd)"
208
212
  ).action(async (path) => {
209
- const { devUpdateCommand } = await import("./dev-QXVERU42.js");
213
+ const { devUpdateCommand } = await import("./dev-PFUF67SH.js");
210
214
  await devUpdateCommand(path);
211
215
  });
212
216
  program.hook("postAction", async () => {
213
217
  const topCmd = process.argv[2];
214
218
  if (topCmd === "update") return;
215
- const { checkAndNotifyCliUpdate } = await import("./update-check-5SCD2JR2.js");
219
+ const { checkAndNotifyCliUpdate } = await import("./update-check-BPCQ25AR.js");
216
220
  checkAndNotifyCliUpdate(pkg.version);
217
221
  });
218
222
  await program.parseAsync();
@@ -2,12 +2,16 @@ import {
2
2
  ensureMemoryBuilt,
3
3
  isMemoryInitialized,
4
4
  memoryInitWizard
5
- } from "./chunk-FAANBGCB.js";
6
- import "./chunk-FI5HEQ43.js";
5
+ } from "./chunk-L53RNEV3.js";
6
+ import "./chunk-E5J24GD5.js";
7
+ import {
8
+ isLearnMcpRegistered,
9
+ registerLearnMcp
10
+ } from "./chunk-WSUYLIYX.js";
7
11
  import {
8
12
  memoryConfigMenu
9
- } from "./chunk-QCKIBJF6.js";
10
- import "./chunk-AZ6AK5M4.js";
13
+ } from "./chunk-C5YWESSF.js";
14
+ import "./chunk-B2NFXRFP.js";
11
15
  import {
12
16
  getLibPath
13
17
  } from "./chunk-RZRT7NGT.js";
@@ -21,14 +25,14 @@ import {
21
25
  saveStatuslineConfig,
22
26
  selectStatuslineComponents,
23
27
  writeStatuslineSettings
24
- } from "./chunk-LXZ47XCD.js";
28
+ } from "./chunk-WNTMKIKC.js";
25
29
  import {
26
30
  DEFAULT_CONFIG
27
- } from "./chunk-DHH6SRXV.js";
31
+ } from "./chunk-57EGAXYI.js";
28
32
  import {
29
33
  ensureShellCompletion,
30
34
  hasShellCompletion
31
- } from "./chunk-DMJCO6LJ.js";
35
+ } from "./chunk-RLUNNJWJ.js";
32
36
  import {
33
37
  BACK,
34
38
  applyDocsDirChange,
@@ -39,7 +43,11 @@ import {
39
43
  getScopeLabel,
40
44
  injectBackChoice,
41
45
  showConfigHint
42
- } from "./chunk-Z5Q533UG.js";
46
+ } from "./chunk-HPNRQYLM.js";
47
+ import {
48
+ commandExists,
49
+ run
50
+ } from "./chunk-EVGXUDX4.js";
43
51
  import {
44
52
  claudeLocalSettingsPath,
45
53
  claudeProjectSettingsPath,
@@ -48,10 +56,6 @@ import {
48
56
  localConfigPath,
49
57
  resolvePath
50
58
  } from "./chunk-TWKNGPBO.js";
51
- import {
52
- commandExists,
53
- run
54
- } from "./chunk-EVGXUDX4.js";
55
59
  import {
56
60
  log,
57
61
  printBanner
@@ -73,7 +77,7 @@ import {
73
77
 
74
78
  // src/commands/init.ts
75
79
  import { checkbox, confirm, input, select } from "@inquirer/prompts";
76
- import { existsSync, readFileSync, writeFileSync } from "fs";
80
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
77
81
  import { homedir } from "os";
78
82
  import { join } from "path";
79
83
  import chalk from "chalk";
@@ -115,14 +119,7 @@ function paddedScopeLabel(scope) {
115
119
  }
116
120
  function printSetupStatus(globalCfg, localCfg, gitAvailable) {
117
121
  const docsDir = getDocsDir(globalCfg, localCfg);
118
- const subfolders = [
119
- "plans",
120
- "memory",
121
- "research",
122
- "learn",
123
- "sessions",
124
- "reviews"
125
- ];
122
+ const subfolders = ["plans", "memory", "research", "sessions", "reviews"];
126
123
  const folderStatus = subfolders.map((sub) => ({
127
124
  name: `${docsDir}/${sub}`,
128
125
  exists: existsSync(`${docsDir}/${sub}`)
@@ -246,7 +243,6 @@ async function stepDocsDir(globalCfg, localCfg) {
246
243
  "plans",
247
244
  "memory",
248
245
  "research",
249
- "learn",
250
246
  "sessions",
251
247
  "reviews"
252
248
  ];
@@ -272,7 +268,6 @@ async function stepDocsDir(globalCfg, localCfg) {
272
268
  "plans",
273
269
  "memory",
274
270
  "research",
275
- "learn",
276
271
  "sessions",
277
272
  "reviews"
278
273
  ];
@@ -313,7 +308,6 @@ async function stepGitignore(docsDir) {
313
308
  `${docsDir}/plans/`,
314
309
  `${docsDir}/memory/`,
315
310
  `${docsDir}/research/`,
316
- `${docsDir}/learn/`,
317
311
  `${docsDir}/sessions/`,
318
312
  `${docsDir}/reviews/`,
319
313
  `${docsDir}/context/`,
@@ -389,56 +383,43 @@ async function selectLanguage(message) {
389
383
  }
390
384
  return choice;
391
385
  }
392
- async function stepLearnConfig(globalCfg, localCfg, gitAvailable) {
393
- const currentLearn = localCfg?.learn ?? globalCfg?.learn;
394
- const scopeLabel = getScopeLabel("learn", globalCfg, localCfg);
395
- const docsDir = getDocsDir(globalCfg, localCfg);
386
+ async function stepLearnConfig(globalCfg) {
387
+ const globalLearn = globalCfg?.learn;
388
+ const defaultLearnDir = DEFAULT_CONFIG.learn.outputDir;
389
+ const currentOutputDir = globalLearn?.outputDir ?? defaultLearnDir;
396
390
  printStepHeader(
397
- `/cf-learn config ${formatScopeLabel(scopeLabel)}`,
391
+ "/cf-learn config",
398
392
  "Controls where and how /cf-learn saves your learning notes."
399
393
  );
400
- const globalLearn = globalCfg?.learn;
401
- if (globalLearn) {
402
- const parts = [
403
- globalLearn.language || "en",
404
- globalLearn.outputDir || `${docsDir}/learn`
405
- ];
406
- if (globalLearn.categories) {
407
- parts.push(`${globalLearn.categories.length} categories`);
408
- }
409
- if (await offerGlobalShortcut(parts.join(", "))) {
410
- const gOutputDir = globalLearn.outputDir || `${docsDir}/learn`;
411
- const gIsExternal = !gOutputDir.startsWith(`${docsDir}/`);
412
- return {
413
- outputDir: gOutputDir,
414
- autoCommit: globalLearn.autoCommit || false,
415
- isExternal: gIsExternal
416
- };
417
- }
418
- }
419
394
  const language = await selectLanguage(
420
395
  "What language should /cf-learn notes be written in?"
421
396
  );
422
397
  const locationChoice = await select({
423
- message: "Where to store learning docs?",
398
+ message: `Current learn folder: ${currentOutputDir}`,
424
399
  choices: injectBackChoice(
425
400
  [
426
- { name: `In this project (${docsDir}/learn/)`, value: "local" },
427
- { name: "A separate folder", value: "external" }
401
+ {
402
+ name: `Use default (${defaultLearnDir})`,
403
+ value: "default"
404
+ },
405
+ { name: "Custom path\u2026", value: "custom" }
428
406
  ],
429
407
  "Cancel init"
430
408
  )
431
409
  });
432
410
  handleBack(locationChoice);
433
- let outputDir = `${docsDir}/learn`;
434
- let isExternal = false;
435
- if (locationChoice === "external") {
411
+ let outputDir = defaultLearnDir;
412
+ if (locationChoice === "custom") {
436
413
  outputDir = await input({
437
414
  message: "Enter path (absolute or ~/...):",
438
- default: currentLearn?.outputDir ?? void 0,
439
- validate: (val) => val.length > 0 ? true : "Path cannot be empty"
415
+ default: currentOutputDir !== defaultLearnDir ? currentOutputDir : void 0,
416
+ validate: (val) => {
417
+ if (val.length === 0) return "Path cannot be empty";
418
+ if (!val.startsWith("/") && !val.startsWith("~/"))
419
+ return "Path must be absolute (e.g. /home/user/learn) or start with ~/ (e.g. ~/learn)";
420
+ return true;
421
+ }
440
422
  });
441
- isExternal = true;
442
423
  const resolved = resolvePath(outputDir);
443
424
  if (!existsSync(resolved)) {
444
425
  const create = await confirm({
@@ -446,12 +427,12 @@ async function stepLearnConfig(globalCfg, localCfg, gitAvailable) {
446
427
  default: true
447
428
  });
448
429
  if (create) {
449
- run("mkdir", ["-p", resolved]);
430
+ mkdirSync(resolved, { recursive: true });
450
431
  log.success(`Created ${resolved}`);
451
432
  }
452
433
  }
453
434
  }
454
- const existingCats = currentLearn?.categories;
435
+ const existingCats = globalLearn?.categories;
455
436
  const defaultNames = DEFAULT_CONFIG.learn.categories.map((c) => c.name).join(", ");
456
437
  const catChoices = [
457
438
  { name: `Use defaults (${defaultNames})`, value: "defaults" }
@@ -507,10 +488,18 @@ async function stepLearnConfig(globalCfg, localCfg, gitAvailable) {
507
488
  if (customCats.length > 0) categories = customCats;
508
489
  }
509
490
  let autoCommit = false;
510
- if (isExternal && gitAvailable) {
491
+ const resolvedOutputDir = resolvePath(outputDir);
492
+ const gitInLearnDir = run("git", [
493
+ "-C",
494
+ resolvedOutputDir,
495
+ "rev-parse",
496
+ "--is-inside-work-tree"
497
+ ]);
498
+ const learnDirIsGit = gitInLearnDir !== null;
499
+ if (learnDirIsGit) {
511
500
  autoCommit = await confirm({
512
501
  message: "Auto-commit learning docs to git after each /cf-learn?",
513
- default: currentLearn?.autoCommit ?? false
502
+ default: globalLearn?.autoCommit ?? false
514
503
  });
515
504
  }
516
505
  const indexChoice = await select({
@@ -535,17 +524,11 @@ async function stepLearnConfig(globalCfg, localCfg, gitAvailable) {
535
524
  autoCommit,
536
525
  readmeIndex
537
526
  };
538
- const scope = await askScope();
539
- if (scope === "back") {
540
- log.dim("Skipped /cf-learn config.");
541
- return { outputDir, autoCommit, isExternal };
542
- }
543
- const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
544
- const existingConfig = readJson(targetPath);
527
+ const existingConfig = readJson(globalConfigPath());
545
528
  const existingLearn = existingConfig?.learn ?? {};
546
- mergeJson(targetPath, { learn: { ...existingLearn, ...learnObj } });
547
- log.success(`Saved to ${targetPath}`);
548
- return { outputDir, autoCommit, isExternal };
529
+ mergeJson(globalConfigPath(), { learn: { ...existingLearn, ...learnObj } });
530
+ log.success(`Saved to ${globalConfigPath()}`);
531
+ return { outputDir, autoCommit };
549
532
  }
550
533
  async function stepShellCompletion() {
551
534
  if (hasShellCompletion()) {
@@ -657,6 +640,62 @@ async function stepMemory(docsDir, opts) {
657
640
  const memoryDir = join(process.cwd(), docsDir, "memory");
658
641
  await memoryInitWizard(memoryDir, mcpDir);
659
642
  }
643
+ async function stepTdd(globalCfg, localCfg) {
644
+ const currentValue = getMergedValue("tdd", globalCfg, localCfg);
645
+ const scopeLabel = getScopeLabel("tdd", globalCfg, localCfg);
646
+ printStepHeader(
647
+ `TDD ${formatScopeLabel(scopeLabel)}${currentValue !== void 0 ? ` (${currentValue})` : ""}`,
648
+ "Enable TDD (RED\u2192GREEN\u2192REFACTOR) by default for all implementations."
649
+ );
650
+ const tddChoice = await confirm({
651
+ message: "Enable TDD by default? (writes failing tests before code \u2014 RED \u2192 GREEN \u2192 REFACTOR)",
652
+ default: currentValue ?? false
653
+ });
654
+ const scope = await askScope();
655
+ if (scope === "back") {
656
+ log.dim("Skipped TDD config.");
657
+ return;
658
+ }
659
+ const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
660
+ mergeJson(targetPath, { tdd: tddChoice });
661
+ log.success(`Saved to ${targetPath}`);
662
+ }
663
+ async function stepAutoApprove(globalCfg, localCfg) {
664
+ const currentValue = getMergedValue("autoApprove", globalCfg, localCfg);
665
+ const scopeLabel = getScopeLabel("autoApprove", globalCfg, localCfg);
666
+ printStepHeader(
667
+ `Auto-approve ${formatScopeLabel(scopeLabel)}${currentValue !== void 0 ? ` (${currentValue})` : ""}`,
668
+ "Auto-approves safe tool calls, blocks destructive ones, prompts for ambiguous."
669
+ );
670
+ const autoApproveChoice = await confirm({
671
+ message: "Enable auto-approve? (auto-approves read-only tools + working-dir file edits, LLM classifier for unknowns)",
672
+ default: currentValue ?? false
673
+ });
674
+ const scope = await askScope();
675
+ if (scope === "back") {
676
+ log.dim("Skipped auto-approve config.");
677
+ return;
678
+ }
679
+ const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
680
+ mergeJson(targetPath, { autoApprove: autoApproveChoice });
681
+ log.success(`Saved to ${targetPath}`);
682
+ if (autoApproveChoice) {
683
+ const { runDangerousRulesAudit } = await import("./permissions-LYADLYYK.js");
684
+ await runDangerousRulesAudit(
685
+ [
686
+ claudeProjectSettingsPath(),
687
+ claudeLocalSettingsPath(),
688
+ claudeSettingsPath()
689
+ ],
690
+ log,
691
+ (message) => confirm({ message, default: true })
692
+ );
693
+ log.dim(
694
+ "Tip: Fine-tune with autoApproveAllowExtra / autoApproveIgnore in config.json"
695
+ );
696
+ log.dim("Docs: https://cf.dinhanhthi.com/docs/reference/auto-approve/");
697
+ }
698
+ }
660
699
  async function stepClaudePermissions(externalLearnDir, autoCommit) {
661
700
  const scope = await select({
662
701
  message: "Where should permissions be saved?",
@@ -736,6 +775,8 @@ async function initMenu(gitAvailable) {
736
775
  const completionStatus = hasShellCompletion() ? chalk.green("installed") : chalk.yellow("not installed");
737
776
  const statuslineStatus = isStatuslineConfigured() ? chalk.green("configured") : chalk.yellow("not configured");
738
777
  const memoryStatus = isMemoryInitialized() ? chalk.green("initialized") : chalk.yellow("not configured");
778
+ const tddScope = getScopeLabel("tdd", globalCfg, localCfg);
779
+ const tddVal = getMergedValue("tdd", globalCfg, localCfg);
739
780
  const autoApproveScope = getScopeLabel("autoApprove", globalCfg, localCfg);
740
781
  const autoApproveVal = getMergedValue(
741
782
  "autoApprove",
@@ -788,6 +829,11 @@ async function initMenu(gitAvailable) {
788
829
  value: "memory",
789
830
  description: " Persistent project knowledge \u2014 search tier, embeddings, MCP"
790
831
  },
832
+ {
833
+ name: `TDD ${formatScopeLabel(tddScope)}${tddVal !== void 0 ? ` (${tddVal})` : ""}`,
834
+ value: "tdd",
835
+ description: " Enable TDD (RED\u2192GREEN\u2192REFACTOR) by default for all implementations"
836
+ },
791
837
  {
792
838
  name: `Auto-approve ${formatScopeLabel(autoApproveScope)}${autoApproveVal !== void 0 ? ` (${autoApproveVal})` : ""}`,
793
839
  value: "autoApprove",
@@ -819,7 +865,7 @@ async function initMenu(gitAvailable) {
819
865
  await stepDocsLanguage(globalCfg, localCfg);
820
866
  break;
821
867
  case "learn":
822
- await stepLearnConfig(globalCfg, localCfg, gitAvailable);
868
+ await stepLearnConfig(globalCfg);
823
869
  break;
824
870
  case "completion":
825
871
  await stepShellCompletion();
@@ -830,6 +876,19 @@ async function initMenu(gitAvailable) {
830
876
  case "memory":
831
877
  await stepMemory(docsDir, { menuMode: true });
832
878
  break;
879
+ case "tdd": {
880
+ const tddChoice = await confirm({
881
+ message: "Enable TDD by default? (writes failing tests before code \u2014 RED \u2192 GREEN \u2192 REFACTOR)",
882
+ default: tddVal ?? false
883
+ });
884
+ const tddTargetScope = await askScope();
885
+ if (tddTargetScope !== "back") {
886
+ const targetPath = tddTargetScope === "global" ? globalConfigPath() : localConfigPath();
887
+ mergeJson(targetPath, { tdd: tddChoice });
888
+ log.success(`Saved to ${targetPath}`);
889
+ }
890
+ break;
891
+ }
833
892
  case "autoApprove": {
834
893
  const autoApproveChoice = await confirm({
835
894
  message: "Enable auto-approve? (auto-approves read-only tools + working-dir file edits, LLM classifier for unknowns)",
@@ -862,14 +921,10 @@ async function initMenu(gitAvailable) {
862
921
  break;
863
922
  }
864
923
  case "permissions": {
865
- const learnCfg = localCfg?.learn ?? globalCfg?.learn;
866
- const learnOutputDir = learnCfg?.outputDir || `${docsDir}/learn`;
867
- const learnIsExternal = !learnOutputDir.startsWith(`${docsDir}/`);
924
+ const learnCfg = globalCfg?.learn;
925
+ const learnOutputDir = learnCfg?.outputDir ?? DEFAULT_CONFIG.learn.outputDir;
868
926
  const learnAutoCommit = learnCfg?.autoCommit || false;
869
- await stepClaudePermissions(
870
- learnIsExternal ? learnOutputDir : null,
871
- learnAutoCommit
872
- );
927
+ await stepClaudePermissions(learnOutputDir, learnAutoCommit);
873
928
  break;
874
929
  }
875
930
  }
@@ -982,19 +1037,32 @@ async function initCommand() {
982
1037
  log.dim("Skipped \u2014 not inside a git repo.");
983
1038
  }
984
1039
  await stepDocsLanguage(globalCfg, localCfg);
985
- const { outputDir, autoCommit, isExternal } = await stepLearnConfig(
986
- updatedGlobal,
987
- updatedLocal,
988
- gitAvailable
989
- );
1040
+ const { outputDir, autoCommit } = await stepLearnConfig(updatedGlobal);
1041
+ if (isLearnMcpRegistered()) {
1042
+ log.dim("coding-friend-learn: already registered");
1043
+ log.dim(
1044
+ " (If it points to an old path, run: claude mcp remove --scope user coding-friend-learn && cf mcp)"
1045
+ );
1046
+ } else {
1047
+ const registered = registerLearnMcp(outputDir);
1048
+ if (registered) {
1049
+ log.success(
1050
+ "Registered CF Learn MCP (user scope). Restart Claude Code to activate."
1051
+ );
1052
+ }
1053
+ }
990
1054
  await stepShellCompletion();
991
1055
  await stepStatusline();
992
1056
  await stepMemory(docsDir);
1057
+ const finalGlobal = readJson(globalConfigPath());
1058
+ const finalLocal = readJson(localConfigPath());
1059
+ await stepTdd(finalGlobal, finalLocal);
1060
+ await stepAutoApprove(finalGlobal, finalLocal);
993
1061
  printStepHeader(
994
1062
  "Configure Claude permissions",
995
1063
  "Grants Coding Friend skills/hooks the permissions they need, so you get fewer prompts."
996
1064
  );
997
- await stepClaudePermissions(isExternal ? outputDir : null, autoCommit);
1065
+ await stepClaudePermissions(outputDir, autoCommit);
998
1066
  if (!existsSync(localConfigPath())) {
999
1067
  writeJson(localConfigPath(), {});
1000
1068
  }