coding-friend-cli 1.34.0 → 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 (36) hide show
  1. package/README.md +1 -0
  2. package/dist/chunk-2DTXVMPQ.js +26 -0
  3. package/dist/{chunk-OYPOADCX.js → chunk-57EGAXYI.js} +3 -3
  4. package/dist/{chunk-K6EDQGDN.js → chunk-B2NFXRFP.js} +17 -12
  5. package/dist/{chunk-3UIH5U3Y.js → chunk-C5YWESSF.js} +2 -6
  6. package/dist/chunk-E5J24GD5.js +190 -0
  7. package/dist/{chunk-UGTNIFBH.js → chunk-H5TXJZD7.js} +6 -6
  8. package/dist/{chunk-5X3JZKYW.js → chunk-L53RNEV3.js} +116 -73
  9. package/dist/{chunk-DMJCO6LJ.js → chunk-RLUNNJWJ.js} +2 -1
  10. package/dist/{chunk-XOHLBLN2.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/{chunk-CXG6I6ZF.js → config-HTQAALMA.js} +182 -194
  14. package/dist/{dev-S6VZUD33.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-Y3WIXDSS.js → host-HKA4OHBM.js} +3 -3
  18. package/dist/index.js +38 -34
  19. package/dist/{init-WDFYY5MM.js → init-YM7YEEGK.js} +154 -99
  20. package/dist/{install-FKL6WLIG.js → install-VYZSTXN6.js} +8 -8
  21. package/dist/{mcp-J4SD646S.js → mcp-7MO2MCLE.js} +72 -67
  22. package/dist/{mcp-serve-learn-UNW6VH5M.js → mcp-serve-learn-OKVC5WV3.js} +19 -3
  23. package/dist/{memory-RLZR7XQY.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-6WQGKIRG.js → session-M4KRQ4VU.js} +2 -2
  27. package/dist/{status-BX2SVBBW.js → status-2XM5OZDR.js} +19 -14
  28. package/dist/{statusline-MLLKBHHR.js → statusline-VFIA7OS2.js} +3 -3
  29. package/dist/{uninstall-6ZJRS2H4.js → uninstall-QKEC3O64.js} +6 -6
  30. package/dist/{update-G5HZINBK.js → update-IV4Y3T72.js} +6 -6
  31. package/package.json +1 -1
  32. package/dist/chunk-AYIREX73.js +0 -85
  33. package/dist/chunk-TVAY4OQL.js +0 -118
  34. package/dist/config-MCEQOLE5.js +0 -22
  35. package/dist/{chunk-Z5Q533UG.js → chunk-HPNRQYLM.js} +3 -3
  36. package/dist/{update-check-5SCD2JR2.js → update-check-BPCQ25AR.js} +3 -3
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-FKL6WLIG.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-WDFYY5MM.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-MCEQOLE5.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-Y3WIXDSS.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-J4SD646S.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-MLLKBHHR.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-G5HZINBK.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-BX2SVBBW.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-6WQGKIRG.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-6WQGKIRG.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-RLZR7XQY.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-RLZR7XQY.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-RLZR7XQY.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-RLZR7XQY.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-RLZR7XQY.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-RLZR7XQY.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-RLZR7XQY.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-RLZR7XQY.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-RLZR7XQY.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-RLZR7XQY.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-S6VZUD33.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-S6VZUD33.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-S6VZUD33.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-S6VZUD33.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-S6VZUD33.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-S6VZUD33.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();
@@ -1,16 +1,17 @@
1
- import {
2
- editCodex
3
- } from "./chunk-CXG6I6ZF.js";
4
1
  import {
5
2
  ensureMemoryBuilt,
6
3
  isMemoryInitialized,
7
4
  memoryInitWizard
8
- } from "./chunk-5X3JZKYW.js";
9
- import "./chunk-AYIREX73.js";
5
+ } from "./chunk-L53RNEV3.js";
6
+ import "./chunk-E5J24GD5.js";
7
+ import {
8
+ isLearnMcpRegistered,
9
+ registerLearnMcp
10
+ } from "./chunk-WSUYLIYX.js";
10
11
  import {
11
12
  memoryConfigMenu
12
- } from "./chunk-3UIH5U3Y.js";
13
- import "./chunk-K6EDQGDN.js";
13
+ } from "./chunk-C5YWESSF.js";
14
+ import "./chunk-B2NFXRFP.js";
14
15
  import {
15
16
  getLibPath
16
17
  } from "./chunk-RZRT7NGT.js";
@@ -24,14 +25,14 @@ import {
24
25
  saveStatuslineConfig,
25
26
  selectStatuslineComponents,
26
27
  writeStatuslineSettings
27
- } from "./chunk-XOHLBLN2.js";
28
+ } from "./chunk-WNTMKIKC.js";
28
29
  import {
29
30
  DEFAULT_CONFIG
30
- } from "./chunk-OYPOADCX.js";
31
+ } from "./chunk-57EGAXYI.js";
31
32
  import {
32
33
  ensureShellCompletion,
33
34
  hasShellCompletion
34
- } from "./chunk-DMJCO6LJ.js";
35
+ } from "./chunk-RLUNNJWJ.js";
35
36
  import {
36
37
  BACK,
37
38
  applyDocsDirChange,
@@ -42,7 +43,11 @@ import {
42
43
  getScopeLabel,
43
44
  injectBackChoice,
44
45
  showConfigHint
45
- } from "./chunk-Z5Q533UG.js";
46
+ } from "./chunk-HPNRQYLM.js";
47
+ import {
48
+ commandExists,
49
+ run
50
+ } from "./chunk-EVGXUDX4.js";
46
51
  import {
47
52
  claudeLocalSettingsPath,
48
53
  claudeProjectSettingsPath,
@@ -51,10 +56,6 @@ import {
51
56
  localConfigPath,
52
57
  resolvePath
53
58
  } from "./chunk-TWKNGPBO.js";
54
- import {
55
- commandExists,
56
- run
57
- } from "./chunk-EVGXUDX4.js";
58
59
  import {
59
60
  log,
60
61
  printBanner
@@ -76,7 +77,7 @@ import {
76
77
 
77
78
  // src/commands/init.ts
78
79
  import { checkbox, confirm, input, select } from "@inquirer/prompts";
79
- import { existsSync, readFileSync, writeFileSync } from "fs";
80
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
80
81
  import { homedir } from "os";
81
82
  import { join } from "path";
82
83
  import chalk from "chalk";
@@ -118,14 +119,7 @@ function paddedScopeLabel(scope) {
118
119
  }
119
120
  function printSetupStatus(globalCfg, localCfg, gitAvailable) {
120
121
  const docsDir = getDocsDir(globalCfg, localCfg);
121
- const subfolders = [
122
- "plans",
123
- "memory",
124
- "research",
125
- "learn",
126
- "sessions",
127
- "reviews"
128
- ];
122
+ const subfolders = ["plans", "memory", "research", "sessions", "reviews"];
129
123
  const folderStatus = subfolders.map((sub) => ({
130
124
  name: `${docsDir}/${sub}`,
131
125
  exists: existsSync(`${docsDir}/${sub}`)
@@ -249,7 +243,6 @@ async function stepDocsDir(globalCfg, localCfg) {
249
243
  "plans",
250
244
  "memory",
251
245
  "research",
252
- "learn",
253
246
  "sessions",
254
247
  "reviews"
255
248
  ];
@@ -275,7 +268,6 @@ async function stepDocsDir(globalCfg, localCfg) {
275
268
  "plans",
276
269
  "memory",
277
270
  "research",
278
- "learn",
279
271
  "sessions",
280
272
  "reviews"
281
273
  ];
@@ -316,7 +308,6 @@ async function stepGitignore(docsDir) {
316
308
  `${docsDir}/plans/`,
317
309
  `${docsDir}/memory/`,
318
310
  `${docsDir}/research/`,
319
- `${docsDir}/learn/`,
320
311
  `${docsDir}/sessions/`,
321
312
  `${docsDir}/reviews/`,
322
313
  `${docsDir}/context/`,
@@ -392,56 +383,43 @@ async function selectLanguage(message) {
392
383
  }
393
384
  return choice;
394
385
  }
395
- async function stepLearnConfig(globalCfg, localCfg, gitAvailable) {
396
- const currentLearn = localCfg?.learn ?? globalCfg?.learn;
397
- const scopeLabel = getScopeLabel("learn", globalCfg, localCfg);
398
- 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;
399
390
  printStepHeader(
400
- `/cf-learn config ${formatScopeLabel(scopeLabel)}`,
391
+ "/cf-learn config",
401
392
  "Controls where and how /cf-learn saves your learning notes."
402
393
  );
403
- const globalLearn = globalCfg?.learn;
404
- if (globalLearn) {
405
- const parts = [
406
- globalLearn.language || "en",
407
- globalLearn.outputDir || `${docsDir}/learn`
408
- ];
409
- if (globalLearn.categories) {
410
- parts.push(`${globalLearn.categories.length} categories`);
411
- }
412
- if (await offerGlobalShortcut(parts.join(", "))) {
413
- const gOutputDir = globalLearn.outputDir || `${docsDir}/learn`;
414
- const gIsExternal = !gOutputDir.startsWith(`${docsDir}/`);
415
- return {
416
- outputDir: gOutputDir,
417
- autoCommit: globalLearn.autoCommit || false,
418
- isExternal: gIsExternal
419
- };
420
- }
421
- }
422
394
  const language = await selectLanguage(
423
395
  "What language should /cf-learn notes be written in?"
424
396
  );
425
397
  const locationChoice = await select({
426
- message: "Where to store learning docs?",
398
+ message: `Current learn folder: ${currentOutputDir}`,
427
399
  choices: injectBackChoice(
428
400
  [
429
- { name: `In this project (${docsDir}/learn/)`, value: "local" },
430
- { name: "A separate folder", value: "external" }
401
+ {
402
+ name: `Use default (${defaultLearnDir})`,
403
+ value: "default"
404
+ },
405
+ { name: "Custom path\u2026", value: "custom" }
431
406
  ],
432
407
  "Cancel init"
433
408
  )
434
409
  });
435
410
  handleBack(locationChoice);
436
- let outputDir = `${docsDir}/learn`;
437
- let isExternal = false;
438
- if (locationChoice === "external") {
411
+ let outputDir = defaultLearnDir;
412
+ if (locationChoice === "custom") {
439
413
  outputDir = await input({
440
414
  message: "Enter path (absolute or ~/...):",
441
- default: currentLearn?.outputDir ?? void 0,
442
- 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
+ }
443
422
  });
444
- isExternal = true;
445
423
  const resolved = resolvePath(outputDir);
446
424
  if (!existsSync(resolved)) {
447
425
  const create = await confirm({
@@ -449,12 +427,12 @@ async function stepLearnConfig(globalCfg, localCfg, gitAvailable) {
449
427
  default: true
450
428
  });
451
429
  if (create) {
452
- run("mkdir", ["-p", resolved]);
430
+ mkdirSync(resolved, { recursive: true });
453
431
  log.success(`Created ${resolved}`);
454
432
  }
455
433
  }
456
434
  }
457
- const existingCats = currentLearn?.categories;
435
+ const existingCats = globalLearn?.categories;
458
436
  const defaultNames = DEFAULT_CONFIG.learn.categories.map((c) => c.name).join(", ");
459
437
  const catChoices = [
460
438
  { name: `Use defaults (${defaultNames})`, value: "defaults" }
@@ -510,10 +488,18 @@ async function stepLearnConfig(globalCfg, localCfg, gitAvailable) {
510
488
  if (customCats.length > 0) categories = customCats;
511
489
  }
512
490
  let autoCommit = false;
513
- 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) {
514
500
  autoCommit = await confirm({
515
501
  message: "Auto-commit learning docs to git after each /cf-learn?",
516
- default: currentLearn?.autoCommit ?? false
502
+ default: globalLearn?.autoCommit ?? false
517
503
  });
518
504
  }
519
505
  const indexChoice = await select({
@@ -538,17 +524,11 @@ async function stepLearnConfig(globalCfg, localCfg, gitAvailable) {
538
524
  autoCommit,
539
525
  readmeIndex
540
526
  };
541
- const scope = await askScope();
542
- if (scope === "back") {
543
- log.dim("Skipped /cf-learn config.");
544
- return { outputDir, autoCommit, isExternal };
545
- }
546
- const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
547
- const existingConfig = readJson(targetPath);
527
+ const existingConfig = readJson(globalConfigPath());
548
528
  const existingLearn = existingConfig?.learn ?? {};
549
- mergeJson(targetPath, { learn: { ...existingLearn, ...learnObj } });
550
- log.success(`Saved to ${targetPath}`);
551
- return { outputDir, autoCommit, isExternal };
529
+ mergeJson(globalConfigPath(), { learn: { ...existingLearn, ...learnObj } });
530
+ log.success(`Saved to ${globalConfigPath()}`);
531
+ return { outputDir, autoCommit };
552
532
  }
553
533
  async function stepShellCompletion() {
554
534
  if (hasShellCompletion()) {
@@ -660,6 +640,62 @@ async function stepMemory(docsDir, opts) {
660
640
  const memoryDir = join(process.cwd(), docsDir, "memory");
661
641
  await memoryInitWizard(memoryDir, mcpDir);
662
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
+ }
663
699
  async function stepClaudePermissions(externalLearnDir, autoCommit) {
664
700
  const scope = await select({
665
701
  message: "Where should permissions be saved?",
@@ -739,14 +775,14 @@ async function initMenu(gitAvailable) {
739
775
  const completionStatus = hasShellCompletion() ? chalk.green("installed") : chalk.yellow("not installed");
740
776
  const statuslineStatus = isStatuslineConfigured() ? chalk.green("configured") : chalk.yellow("not configured");
741
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);
742
780
  const autoApproveScope = getScopeLabel("autoApprove", globalCfg, localCfg);
743
781
  const autoApproveVal = getMergedValue(
744
782
  "autoApprove",
745
783
  globalCfg,
746
784
  localCfg
747
785
  );
748
- const codexScope = getScopeLabel("codex", globalCfg, localCfg);
749
- const codexEnabled = getMergedValue("codex", globalCfg, localCfg)?.enabled;
750
786
  const projectRules = getExistingRules(claudeLocalSettingsPath());
751
787
  const userRules = getExistingRules(claudeSettingsPath());
752
788
  const allRules = getAllRules();
@@ -793,16 +829,16 @@ async function initMenu(gitAvailable) {
793
829
  value: "memory",
794
830
  description: " Persistent project knowledge \u2014 search tier, embeddings, MCP"
795
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
+ },
796
837
  {
797
838
  name: `Auto-approve ${formatScopeLabel(autoApproveScope)}${autoApproveVal !== void 0 ? ` (${autoApproveVal})` : ""}`,
798
839
  value: "autoApprove",
799
840
  description: " Auto-approve safe tool calls, block destructive ones, prompt for ambiguous"
800
841
  },
801
- {
802
- name: `Codex cross-engine review ${formatScopeLabel(codexScope)}${codexEnabled !== void 0 ? ` (${codexEnabled ? "enabled" : "disabled"})` : ""}`,
803
- value: "codex",
804
- description: " Enable OpenAI Codex as 6th specialist in /cf-review (opt-in)"
805
- },
806
842
  {
807
843
  name: `Permissions (${permissionStatus} rules)`,
808
844
  value: "permissions",
@@ -829,7 +865,7 @@ async function initMenu(gitAvailable) {
829
865
  await stepDocsLanguage(globalCfg, localCfg);
830
866
  break;
831
867
  case "learn":
832
- await stepLearnConfig(globalCfg, localCfg, gitAvailable);
868
+ await stepLearnConfig(globalCfg);
833
869
  break;
834
870
  case "completion":
835
871
  await stepShellCompletion();
@@ -840,6 +876,19 @@ async function initMenu(gitAvailable) {
840
876
  case "memory":
841
877
  await stepMemory(docsDir, { menuMode: true });
842
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
+ }
843
892
  case "autoApprove": {
844
893
  const autoApproveChoice = await confirm({
845
894
  message: "Enable auto-approve? (auto-approves read-only tools + working-dir file edits, LLM classifier for unknowns)",
@@ -871,18 +920,11 @@ async function initMenu(gitAvailable) {
871
920
  }
872
921
  break;
873
922
  }
874
- case "codex":
875
- await editCodex(globalCfg, localCfg);
876
- break;
877
923
  case "permissions": {
878
- const learnCfg = localCfg?.learn ?? globalCfg?.learn;
879
- const learnOutputDir = learnCfg?.outputDir || `${docsDir}/learn`;
880
- const learnIsExternal = !learnOutputDir.startsWith(`${docsDir}/`);
924
+ const learnCfg = globalCfg?.learn;
925
+ const learnOutputDir = learnCfg?.outputDir ?? DEFAULT_CONFIG.learn.outputDir;
881
926
  const learnAutoCommit = learnCfg?.autoCommit || false;
882
- await stepClaudePermissions(
883
- learnIsExternal ? learnOutputDir : null,
884
- learnAutoCommit
885
- );
927
+ await stepClaudePermissions(learnOutputDir, learnAutoCommit);
886
928
  break;
887
929
  }
888
930
  }
@@ -995,26 +1037,39 @@ async function initCommand() {
995
1037
  log.dim("Skipped \u2014 not inside a git repo.");
996
1038
  }
997
1039
  await stepDocsLanguage(globalCfg, localCfg);
998
- const { outputDir, autoCommit, isExternal } = await stepLearnConfig(
999
- updatedGlobal,
1000
- updatedLocal,
1001
- gitAvailable
1002
- );
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
+ }
1003
1054
  await stepShellCompletion();
1004
1055
  await stepStatusline();
1005
1056
  await stepMemory(docsDir);
1057
+ const finalGlobal = readJson(globalConfigPath());
1058
+ const finalLocal = readJson(localConfigPath());
1059
+ await stepTdd(finalGlobal, finalLocal);
1060
+ await stepAutoApprove(finalGlobal, finalLocal);
1006
1061
  printStepHeader(
1007
1062
  "Configure Claude permissions",
1008
1063
  "Grants Coding Friend skills/hooks the permissions they need, so you get fewer prompts."
1009
1064
  );
1010
- await stepClaudePermissions(isExternal ? outputDir : null, autoCommit);
1065
+ await stepClaudePermissions(outputDir, autoCommit);
1011
1066
  if (!existsSync(localConfigPath())) {
1012
1067
  writeJson(localConfigPath(), {});
1013
1068
  }
1014
1069
  console.log();
1015
1070
  log.congrats("Setup complete!");
1016
1071
  log.dim(
1017
- "Available commands: /cf-ask, /cf-plan, /cf-fix, /cf-commit, /cf-review, /cf-review-codex, /cf-review-out, /cf-review-in, /cf-ship, /cf-optimize, /cf-scan, /cf-remember, /cf-learn, /cf-teach, /cf-research, /cf-session, /cf-warm, /cf-help"
1072
+ "Available commands: /cf-ask, /cf-plan, /cf-design, /cf-fix, /cf-commit, /cf-review, /cf-review-out, /cf-review-in, /cf-ship, /cf-optimize, /cf-scan, /cf-remember, /cf-learn, /cf-teach, /cf-research, /cf-session, /cf-warm, /cf-help"
1018
1073
  );
1019
1074
  }
1020
1075
  export {