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
@@ -0,0 +1,61 @@
1
+ import {
2
+ runWithStderr
3
+ } from "./chunk-EVGXUDX4.js";
4
+ import {
5
+ resolvePath
6
+ } from "./chunk-TWKNGPBO.js";
7
+ import {
8
+ log
9
+ } from "./chunk-NREZK463.js";
10
+
11
+ // src/lib/learn-prompts.ts
12
+ var MCP_NAME = "coding-friend-learn";
13
+ function registerLearnMcp(learnDir) {
14
+ const resolved = resolvePath(learnDir);
15
+ const result = runWithStderr("claude", [
16
+ "mcp",
17
+ "add",
18
+ "--scope",
19
+ "user",
20
+ MCP_NAME,
21
+ "--",
22
+ "npx",
23
+ "-y",
24
+ "coding-friend-cli",
25
+ "mcp-serve-learn",
26
+ resolved
27
+ ]);
28
+ if (result.exitCode !== 0) {
29
+ const stderr = result.stderr ?? "";
30
+ if (stderr.includes("ENOENT") || stderr.includes("command not found")) {
31
+ log.warn(
32
+ `claude CLI not found \u2014 add MCP manually:
33
+ claude mcp add --scope user ${MCP_NAME} -- npx -y coding-friend-cli mcp-serve-learn ${resolved}`
34
+ );
35
+ } else {
36
+ log.warn(`Could not register MCP: ${stderr || "unknown error"}`);
37
+ }
38
+ return false;
39
+ }
40
+ return true;
41
+ }
42
+ function isLearnMcpRegistered() {
43
+ const result = runWithStderr("claude", ["mcp", "get", MCP_NAME]);
44
+ return result.exitCode === 0;
45
+ }
46
+ function unregisterLearnMcp() {
47
+ const result = runWithStderr("claude", [
48
+ "mcp",
49
+ "remove",
50
+ "--scope",
51
+ "user",
52
+ MCP_NAME
53
+ ]);
54
+ return result.exitCode === 0;
55
+ }
56
+
57
+ export {
58
+ registerLearnMcp,
59
+ isLearnMcpRegistered,
60
+ unregisterLearnMcp
61
+ };
@@ -0,0 +1,95 @@
1
+ import {
2
+ loadConfig,
3
+ resolveMemoryDir
4
+ } from "./chunk-B2NFXRFP.js";
5
+ import "./chunk-57EGAXYI.js";
6
+ import {
7
+ resolvePath
8
+ } from "./chunk-TWKNGPBO.js";
9
+ import {
10
+ log,
11
+ printBanner
12
+ } from "./chunk-NREZK463.js";
13
+ import "./chunk-5UVDWG5L.js";
14
+
15
+ // src/commands/clean.ts
16
+ import { existsSync, readdirSync, rmSync } from "fs";
17
+ import { join, relative } from "path";
18
+ import { checkbox, confirm } from "@inquirer/prompts";
19
+ import chalk from "chalk";
20
+ function countFiles(dir) {
21
+ if (!existsSync(dir)) return 0;
22
+ let count = 0;
23
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
24
+ const entryPath = join(dir, entry.name);
25
+ if (entry.isDirectory()) {
26
+ count += countFiles(entryPath);
27
+ } else {
28
+ count++;
29
+ }
30
+ }
31
+ return count;
32
+ }
33
+ function clearDirContents(dir) {
34
+ for (const entry of readdirSync(dir)) {
35
+ rmSync(join(dir, entry), { recursive: true });
36
+ }
37
+ }
38
+ async function cleanCommand() {
39
+ console.log();
40
+ printBanner("\u{1F9F9} Coding Friend Clean");
41
+ console.log();
42
+ const config = loadConfig();
43
+ const docsDir = resolvePath(config.docsDir ?? "docs");
44
+ const candidates = [
45
+ { key: "context", path: join(docsDir, "context") },
46
+ { key: "memory", path: resolveMemoryDir() },
47
+ { key: "research", path: join(docsDir, "research") },
48
+ { key: "reviews", path: join(docsDir, "reviews") },
49
+ { key: "plans", path: join(docsDir, "plans") },
50
+ { key: "sessions", path: join(docsDir, "sessions") },
51
+ { key: "learn", path: join(docsDir, "learn") }
52
+ ];
53
+ const cleanable = candidates.map((c) => ({ ...c, fileCount: countFiles(c.path) })).filter((c) => existsSync(c.path) && c.fileCount > 0);
54
+ if (cleanable.length === 0) {
55
+ log.info("Nothing to clean.");
56
+ console.log();
57
+ return;
58
+ }
59
+ const selected = await checkbox({
60
+ message: "Select directories to clean:",
61
+ choices: cleanable.map((c) => ({
62
+ name: `${c.key} \u2014 ${c.fileCount} ${c.fileCount === 1 ? "file" : "files"}`,
63
+ value: c.key
64
+ }))
65
+ });
66
+ if (selected.length === 0) {
67
+ log.dim(" Nothing selected.");
68
+ console.log();
69
+ return;
70
+ }
71
+ console.log();
72
+ let cleared = 0;
73
+ for (const entry of cleanable.filter((c) => selected.includes(c.key))) {
74
+ const relPath = relative(process.cwd(), entry.path);
75
+ console.log(
76
+ `${chalk.yellow("\u2192")} Clean ${chalk.bold(entry.key)} (${relPath})`
77
+ );
78
+ const isMemory = entry.key === "memory";
79
+ const message = isMemory ? `Delete all contents of ${relPath}/? (includes SQLite databases \u2014 stop memory daemon first if running)` : `Delete all contents of ${relPath}/?`;
80
+ const ok = await confirm({ message, default: false });
81
+ if (ok) {
82
+ clearDirContents(entry.path);
83
+ log.success(`Cleared: ${relPath}`);
84
+ cleared++;
85
+ } else {
86
+ log.dim(" Skipped.");
87
+ }
88
+ console.log();
89
+ }
90
+ log.info(`Done. ${cleared} of ${selected.length} directories cleared.`);
91
+ console.log();
92
+ }
93
+ export {
94
+ cleanCommand
95
+ };
@@ -1,7 +1,16 @@
1
+ import {
2
+ listMdFilesRecursive
3
+ } from "./chunk-2DTXVMPQ.js";
4
+ import {
5
+ registerLearnMcp,
6
+ unregisterLearnMcp
7
+ } from "./chunk-WSUYLIYX.js";
1
8
  import {
2
9
  memoryConfigMenu
3
- } from "./chunk-QCKIBJF6.js";
4
- import "./chunk-AZ6AK5M4.js";
10
+ } from "./chunk-C5YWESSF.js";
11
+ import {
12
+ resolveLearnDir
13
+ } from "./chunk-B2NFXRFP.js";
5
14
  import "./chunk-RZRT7NGT.js";
6
15
  import {
7
16
  findStatuslineHookPath,
@@ -13,16 +22,16 @@ import {
13
22
  saveStatuslineConfig,
14
23
  selectStatuslineComponents,
15
24
  writeStatuslineSettings
16
- } from "./chunk-LXZ47XCD.js";
25
+ } from "./chunk-WNTMKIKC.js";
17
26
  import {
18
27
  ALL_COMPONENT_IDS,
19
28
  DEFAULT_CONFIG
20
- } from "./chunk-DHH6SRXV.js";
29
+ } from "./chunk-57EGAXYI.js";
21
30
  import {
22
31
  ensureShellCompletion,
23
32
  hasShellCompletion,
24
33
  removeShellCompletion
25
- } from "./chunk-DMJCO6LJ.js";
34
+ } from "./chunk-RLUNNJWJ.js";
26
35
  import {
27
36
  BACK,
28
37
  applyDocsDirChange,
@@ -32,7 +41,8 @@ import {
32
41
  getScopeLabel,
33
42
  injectBackChoice,
34
43
  showConfigHint
35
- } from "./chunk-Z5Q533UG.js";
44
+ } from "./chunk-HPNRQYLM.js";
45
+ import "./chunk-EVGXUDX4.js";
36
46
  import {
37
47
  claudeLocalSettingsPath,
38
48
  claudeProjectSettingsPath,
@@ -41,9 +51,6 @@ import {
41
51
  localConfigPath,
42
52
  resolvePath
43
53
  } from "./chunk-TWKNGPBO.js";
44
- import {
45
- run
46
- } from "./chunk-EVGXUDX4.js";
47
54
  import {
48
55
  log,
49
56
  printBanner
@@ -60,29 +67,14 @@ import {
60
67
  // src/commands/config.ts
61
68
  import { checkbox, confirm, input, select } from "@inquirer/prompts";
62
69
  import chalk from "chalk";
63
- import { existsSync, readFileSync, writeFileSync } from "fs";
64
- function getNestedFieldScope(section, field, globalCfg, localCfg) {
65
- const globalSection = globalCfg?.[section];
66
- const localSection = localCfg?.[section];
67
- const inGlobal = globalSection?.[field] !== void 0;
68
- const inLocal = localSection?.[field] !== void 0;
69
- if (inGlobal && inLocal) return "both";
70
- if (inGlobal) return "global";
71
- if (inLocal) return "local";
72
- return "-";
73
- }
74
- function getLearnFieldScope(field, globalCfg, localCfg) {
75
- return getNestedFieldScope("learn", field, globalCfg, localCfg);
76
- }
77
- function getMergedNestedValue(section, field, globalCfg, localCfg) {
78
- const localSection = localCfg?.[section];
79
- if (localSection?.[field] !== void 0) return localSection[field];
80
- const globalSection = globalCfg?.[section];
81
- return globalSection?.[field];
82
- }
83
- function getMergedLearnValue(field, globalCfg, localCfg) {
84
- return getMergedNestedValue("learn", field, globalCfg, localCfg);
85
- }
70
+ import {
71
+ cpSync,
72
+ existsSync,
73
+ mkdirSync,
74
+ readFileSync,
75
+ rmSync,
76
+ writeFileSync
77
+ } from "fs";
86
78
  function writeToScope(scope, data) {
87
79
  const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
88
80
  mergeJson(targetPath, data);
@@ -145,43 +137,92 @@ async function editLanguage(globalCfg, localCfg) {
145
137
  if (scope === "back") return;
146
138
  writeToScope(scope, { language: lang });
147
139
  }
148
- async function editLearnOutputDir(globalCfg, localCfg) {
149
- const currentValue = getMergedLearnValue("outputDir", globalCfg, localCfg);
150
- if (currentValue) {
151
- log.dim(`Current: ${currentValue}`);
152
- }
140
+ async function editLearnOutputDir(globalCfg) {
141
+ const defaultLearnDir = DEFAULT_CONFIG.learn.outputDir;
142
+ const oldOutputDir = globalCfg?.learn?.outputDir ?? defaultLearnDir;
143
+ log.dim(`Current: ${oldOutputDir}`);
153
144
  const locationChoice = await select({
154
145
  message: "Where to store learning docs?",
155
146
  choices: [
156
- { name: "In this project (docs/learn/)", value: "local" },
157
- { name: "A separate folder", value: "external" }
147
+ {
148
+ name: `Use default (${defaultLearnDir})`,
149
+ value: "default"
150
+ },
151
+ { name: "Custom path\u2026", value: "custom" }
158
152
  ]
159
153
  });
160
- let outputDir = "docs/learn";
161
- if (locationChoice === "external") {
162
- outputDir = await input({
154
+ let newOutputDir = defaultLearnDir;
155
+ if (locationChoice === "custom") {
156
+ newOutputDir = await input({
163
157
  message: "Enter path (absolute or ~/...):",
164
- default: currentValue ?? void 0,
165
- validate: (val) => val.length > 0 ? true : "Path cannot be empty"
158
+ default: oldOutputDir !== defaultLearnDir ? oldOutputDir : void 0,
159
+ validate: (val) => {
160
+ if (val.length === 0) return "Path cannot be empty";
161
+ if (!val.startsWith("/") && !val.startsWith("~/"))
162
+ return "Path must be absolute (e.g. /home/user/learn) or start with ~/ (e.g. ~/learn)";
163
+ return true;
164
+ }
166
165
  });
167
- const resolved = resolvePath(outputDir);
166
+ const resolved = resolvePath(newOutputDir);
168
167
  if (!existsSync(resolved)) {
169
168
  const create = await confirm({
170
169
  message: `Folder ${resolved} doesn't exist. Create it?`,
171
170
  default: true
172
171
  });
173
172
  if (create) {
174
- run("mkdir", ["-p", resolved]);
173
+ mkdirSync(resolved, { recursive: true });
175
174
  log.success(`Created ${resolved}`);
176
175
  }
177
176
  }
178
177
  }
179
- const scope = await askScope();
180
- if (scope === "back") return;
181
- writeLearnField(scope, "outputDir", outputDir);
178
+ const oldResolved = resolvePath(oldOutputDir);
179
+ const newResolved = resolvePath(newOutputDir);
180
+ writeLearnField("global", "outputDir", newOutputDir);
181
+ if (oldResolved !== newResolved) {
182
+ const mdFiles = existsSync(oldResolved) ? listMdFilesRecursive(oldResolved) : [];
183
+ if (mdFiles.length > 0) {
184
+ const doMove = await confirm({
185
+ message: `Found ${mdFiles.length} learning doc(s) in ${oldResolved}. Move entire folder to ${newResolved}? (overwrites any existing files at target)`,
186
+ default: true
187
+ });
188
+ if (doMove) {
189
+ const existingAtTarget = existsSync(newResolved) ? listMdFilesRecursive(newResolved) : [];
190
+ if (existingAtTarget.length > 0) {
191
+ const doOverwrite = await confirm({
192
+ message: `${newResolved} already contains ${existingAtTarget.length} file(s). Overwrite them?`,
193
+ default: false
194
+ });
195
+ if (!doOverwrite) {
196
+ log.dim("Move cancelled. Config saved with new path.");
197
+ return;
198
+ }
199
+ }
200
+ mkdirSync(newResolved, { recursive: true });
201
+ cpSync(oldResolved, newResolved, { recursive: true });
202
+ rmSync(oldResolved, { recursive: true, force: true });
203
+ log.success(
204
+ `Moved ${mdFiles.length} doc(s) from ${oldResolved} to ${newResolved}.`
205
+ );
206
+ }
207
+ }
208
+ if (!globalCfg?.learn?.disabled) {
209
+ unregisterLearnMcp();
210
+ const registered = registerLearnMcp(newResolved);
211
+ if (registered) {
212
+ log.success(
213
+ `Updated CF Learn MCP to point to ${newResolved}. Restart Claude Code to apply.`
214
+ );
215
+ } else {
216
+ log.warn(
217
+ `Could not re-register MCP. Run manually:
218
+ claude mcp add --scope user coding-friend-learn -- npx -y coding-friend-cli mcp-serve-learn ${newResolved}`
219
+ );
220
+ }
221
+ }
222
+ }
182
223
  }
183
- async function editLearnLanguage(globalCfg, localCfg) {
184
- const currentValue = getMergedLearnValue("language", globalCfg, localCfg);
224
+ async function editLearnLanguage(globalCfg) {
225
+ const currentValue = globalCfg?.learn?.language;
185
226
  if (currentValue) {
186
227
  log.dim(`Current: ${currentValue}`);
187
228
  }
@@ -202,16 +243,10 @@ async function editLearnLanguage(globalCfg, localCfg) {
202
243
  lang = await input({ message: "Enter language name:" });
203
244
  if (!lang) lang = "en";
204
245
  }
205
- const scope = await askScope();
206
- if (scope === "back") return;
207
- writeLearnField(scope, "language", lang);
246
+ writeLearnField("global", "language", lang);
208
247
  }
209
- async function editLearnCategories(globalCfg, localCfg) {
210
- const existingCats = getMergedLearnValue(
211
- "categories",
212
- globalCfg,
213
- localCfg
214
- );
248
+ async function editLearnCategories(globalCfg) {
249
+ const existingCats = globalCfg?.learn?.categories;
215
250
  const defaultNames = DEFAULT_CONFIG.learn.categories.map((c) => c.name).join(", ");
216
251
  const catChoices = [
217
252
  { name: `Use defaults (${defaultNames})`, value: "defaults" }
@@ -262,33 +297,24 @@ async function editLearnCategories(globalCfg, localCfg) {
262
297
  }
263
298
  if (customCats.length > 0) categories = customCats;
264
299
  }
265
- const scope = await askScope();
266
- if (scope === "back") return;
267
- writeLearnField(scope, "categories", categories);
300
+ writeLearnField("global", "categories", categories);
268
301
  }
269
- async function editLearnAutoCommit(globalCfg, localCfg) {
270
- const currentValue = getMergedLearnValue(
271
- "autoCommit",
272
- globalCfg,
273
- localCfg
274
- );
302
+ async function editLearnAutoCommit(globalCfg) {
303
+ const currentValue = globalCfg?.learn?.autoCommit;
275
304
  if (currentValue !== void 0) {
276
305
  log.dim(`Current: ${currentValue}`);
277
306
  }
307
+ log.dim(
308
+ "Note: auto-commit only works if your learn folder is a git repository."
309
+ );
278
310
  const value = await confirm({
279
311
  message: "Auto-commit learning docs to git after each /cf-learn?",
280
312
  default: currentValue ?? false
281
313
  });
282
- const scope = await askScope();
283
- if (scope === "back") return;
284
- writeLearnField(scope, "autoCommit", value);
314
+ writeLearnField("global", "autoCommit", value);
285
315
  }
286
- async function editLearnReadmeIndex(globalCfg, localCfg) {
287
- const currentValue = getMergedLearnValue(
288
- "readmeIndex",
289
- globalCfg,
290
- localCfg
291
- );
316
+ async function editLearnReadmeIndex(globalCfg) {
317
+ const currentValue = globalCfg?.learn?.readmeIndex;
292
318
  if (currentValue !== void 0) {
293
319
  log.dim(`Current: ${currentValue}`);
294
320
  }
@@ -303,62 +329,72 @@ async function editLearnReadmeIndex(globalCfg, localCfg) {
303
329
  let readmeIndex = false;
304
330
  if (indexChoice === "single") readmeIndex = true;
305
331
  else if (indexChoice === "per-category") readmeIndex = "per-category";
306
- const scope = await askScope();
307
- if (scope === "back") return;
308
- writeLearnField(scope, "readmeIndex", readmeIndex);
332
+ writeLearnField("global", "readmeIndex", readmeIndex);
333
+ }
334
+ async function editLearnDisabled(globalCfg) {
335
+ const current = globalCfg?.learn?.disabled ?? false;
336
+ log.dim(`Current: ${current ? "disabled" : "enabled"}`);
337
+ const value = await confirm({
338
+ message: current ? "CF Learn is disabled. Re-enable it? (resumes file writing and re-registers MCP)" : "Disable CF Learn? (stops file writing and MCP serving)",
339
+ default: !current
340
+ });
341
+ writeLearnField("global", "disabled", value ? !current : current);
342
+ if (!current && value) {
343
+ const unregistered = unregisterLearnMcp();
344
+ if (unregistered) {
345
+ log.dim(
346
+ "Removed coding-friend-learn from Claude Code MCP. Restart Claude Code to apply."
347
+ );
348
+ } else {
349
+ log.warn(
350
+ "Could not unregister MCP automatically. Run manually:\n claude mcp remove --scope user coding-friend-learn"
351
+ );
352
+ }
353
+ } else if (current && value) {
354
+ const freshGlobal = readJson(globalConfigPath());
355
+ const learnDir = resolveLearnDir(freshGlobal);
356
+ registerLearnMcp(learnDir);
357
+ log.dim(
358
+ "Re-registered coding-friend-learn. Restart Claude Code to activate."
359
+ );
360
+ }
309
361
  }
310
362
  async function learnSubMenu() {
311
363
  while (true) {
312
364
  const globalCfg = readJson(globalConfigPath());
313
- const localCfg = readJson(localConfigPath());
314
- const outputDirScope = getLearnFieldScope("outputDir", globalCfg, localCfg);
315
- const outputDirVal = getMergedLearnValue(
316
- "outputDir",
317
- globalCfg,
318
- localCfg
319
- );
320
- const langScope = getLearnFieldScope("language", globalCfg, localCfg);
321
- const langVal = getMergedLearnValue("language", globalCfg, localCfg);
322
- const catScope = getLearnFieldScope("categories", globalCfg, localCfg);
323
- const autoCommitScope = getLearnFieldScope(
324
- "autoCommit",
325
- globalCfg,
326
- localCfg
327
- );
328
- const autoCommitVal = getMergedLearnValue(
329
- "autoCommit",
330
- globalCfg,
331
- localCfg
332
- );
333
- const readmeScope = getLearnFieldScope("readmeIndex", globalCfg, localCfg);
334
- const readmeVal = getMergedLearnValue(
335
- "readmeIndex",
336
- globalCfg,
337
- localCfg
338
- );
365
+ const outputDirVal = globalCfg?.learn?.outputDir ?? DEFAULT_CONFIG.learn.outputDir;
366
+ const langVal = globalCfg?.learn?.language;
367
+ const autoCommitVal = globalCfg?.learn?.autoCommit;
368
+ const readmeVal = globalCfg?.learn?.readmeIndex;
369
+ const isDisabled = globalCfg?.learn?.disabled ?? false;
370
+ const disabledLabel = isDisabled ? chalk.yellow(" [disabled]") : "";
339
371
  const choice = await select({
340
- message: "Learn settings:",
372
+ message: `Learn settings${disabledLabel}:`,
341
373
  choices: injectBackChoice(
342
374
  [
343
375
  {
344
- name: `Output dir ${formatScopeLabel(outputDirScope)}${outputDirVal ? ` (${outputDirVal})` : ""}`,
376
+ name: `Output dir (${outputDirVal})`,
345
377
  value: "outputDir"
346
378
  },
347
379
  {
348
- name: `Language ${formatScopeLabel(langScope)}${langVal ? ` (${langVal})` : ""}`,
380
+ name: `Language${langVal ? ` (${langVal})` : ""}`,
349
381
  value: "language"
350
382
  },
351
383
  {
352
- name: `Categories ${formatScopeLabel(catScope)}`,
384
+ name: "Categories",
353
385
  value: "categories"
354
386
  },
355
387
  {
356
- name: `Auto-commit ${formatScopeLabel(autoCommitScope)}${autoCommitVal !== void 0 ? ` (${autoCommitVal})` : ""}`,
388
+ name: `Auto-commit${autoCommitVal !== void 0 ? ` (${autoCommitVal})` : ""}`,
357
389
  value: "autoCommit"
358
390
  },
359
391
  {
360
- name: `README index ${formatScopeLabel(readmeScope)}${readmeVal !== void 0 ? ` (${readmeVal})` : ""}`,
392
+ name: `README index${readmeVal !== void 0 ? ` (${readmeVal})` : ""}`,
361
393
  value: "readmeIndex"
394
+ },
395
+ {
396
+ name: isDisabled ? "Enable CF Learn" : "Disable CF Learn",
397
+ value: "disabled"
362
398
  }
363
399
  ],
364
400
  "Back"
@@ -367,23 +403,39 @@ async function learnSubMenu() {
367
403
  if (choice === BACK) return;
368
404
  switch (choice) {
369
405
  case "outputDir":
370
- await editLearnOutputDir(globalCfg, localCfg);
406
+ await editLearnOutputDir(globalCfg);
371
407
  break;
372
408
  case "language":
373
- await editLearnLanguage(globalCfg, localCfg);
409
+ await editLearnLanguage(globalCfg);
374
410
  break;
375
411
  case "categories":
376
- await editLearnCategories(globalCfg, localCfg);
412
+ await editLearnCategories(globalCfg);
377
413
  break;
378
414
  case "autoCommit":
379
- await editLearnAutoCommit(globalCfg, localCfg);
415
+ await editLearnAutoCommit(globalCfg);
380
416
  break;
381
417
  case "readmeIndex":
382
- await editLearnReadmeIndex(globalCfg, localCfg);
418
+ await editLearnReadmeIndex(globalCfg);
419
+ break;
420
+ case "disabled":
421
+ await editLearnDisabled(globalCfg);
383
422
  break;
384
423
  }
385
424
  }
386
425
  }
426
+ async function editTdd(globalCfg, localCfg) {
427
+ const currentValue = getMergedValue("tdd", globalCfg, localCfg);
428
+ if (currentValue !== void 0) {
429
+ log.dim(`Current: ${currentValue}`);
430
+ }
431
+ const value = await confirm({
432
+ message: "Enable TDD by default? (writes failing tests before code \u2014 RED \u2192 GREEN \u2192 REFACTOR)",
433
+ default: currentValue ?? false
434
+ });
435
+ const scope = await askScope();
436
+ if (scope === "back") return;
437
+ writeToScope(scope, { tdd: value });
438
+ }
387
439
  async function editAutoApprove(globalCfg, localCfg) {
388
440
  const currentValue = getMergedValue("autoApprove", globalCfg, localCfg);
389
441
  if (currentValue !== void 0) {
@@ -465,7 +517,6 @@ async function editGitignore(globalCfg, localCfg) {
465
517
  `${docsDir}/plans/`,
466
518
  `${docsDir}/memory/`,
467
519
  `${docsDir}/research/`,
468
- `${docsDir}/learn/`,
469
520
  `${docsDir}/sessions/`,
470
521
  `${docsDir}/reviews/`,
471
522
  `${docsDir}/context/`,
@@ -593,7 +644,7 @@ async function editPermissions() {
593
644
  )
594
645
  });
595
646
  if (choice === BACK) return;
596
- const { permissionCommand } = await import("./permission-DNW7DE24.js");
647
+ const { permissionCommand } = await import("./permission-C5R3LMZQ.js");
597
648
  switch (choice) {
598
649
  case "interactive":
599
650
  await permissionCommand({});
@@ -620,6 +671,8 @@ async function configCommand() {
620
671
  const langVal = getMergedValue("language", globalCfg, localCfg);
621
672
  const learnScope = getScopeLabel("learn", globalCfg, localCfg);
622
673
  const memoryScope = getScopeLabel("memory", globalCfg, localCfg);
674
+ const tddScope = getScopeLabel("tdd", globalCfg, localCfg);
675
+ const tddVal = getMergedValue("tdd", globalCfg, localCfg);
623
676
  const autoApproveScope = getScopeLabel("autoApprove", globalCfg, localCfg);
624
677
  const autoApproveVal = getMergedValue(
625
678
  "autoApprove",
@@ -661,6 +714,11 @@ async function configCommand() {
661
714
  value: "memory",
662
715
  description: " Tier, auto-capture, auto-start, embedding provider"
663
716
  },
717
+ {
718
+ name: `TDD ${formatScopeLabel(tddScope)}${tddVal !== void 0 ? ` (${tddVal})` : ""}`,
719
+ value: "tdd",
720
+ description: " Enable TDD (RED\u2192GREEN\u2192REFACTOR) by default for all implementations"
721
+ },
664
722
  {
665
723
  name: `Auto-approve ${formatScopeLabel(autoApproveScope)}${autoApproveVal !== void 0 ? ` (${autoApproveVal})` : ""}`,
666
724
  value: "autoApprove",
@@ -706,6 +764,9 @@ async function configCommand() {
706
764
  case "memory":
707
765
  await memoryConfigMenu();
708
766
  break;
767
+ case "tdd":
768
+ await editTdd(globalCfg, localCfg);
769
+ break;
709
770
  case "autoApprove":
710
771
  await editAutoApprove(globalCfg, localCfg);
711
772
  break;
@@ -1,23 +1,23 @@
1
1
  import {
2
2
  ensureStatusline
3
- } from "./chunk-LXZ47XCD.js";
4
- import "./chunk-DHH6SRXV.js";
3
+ } from "./chunk-WNTMKIKC.js";
4
+ import "./chunk-57EGAXYI.js";
5
5
  import {
6
6
  ensureShellCompletion
7
- } from "./chunk-DMJCO6LJ.js";
7
+ } from "./chunk-RLUNNJWJ.js";
8
8
  import {
9
9
  isMarketplaceRegistered,
10
10
  isPluginInstalled
11
11
  } from "./chunk-R6HDMRYL.js";
12
+ import {
13
+ commandExists,
14
+ run
15
+ } from "./chunk-EVGXUDX4.js";
12
16
  import {
13
17
  devStatePath,
14
18
  knownMarketplacesPath,
15
19
  pluginCachePath
16
20
  } from "./chunk-TWKNGPBO.js";
17
- import {
18
- commandExists,
19
- run
20
- } from "./chunk-EVGXUDX4.js";
21
21
  import {
22
22
  log,
23
23
  printBanner
@@ -4,9 +4,9 @@ import {
4
4
  } from "./chunk-R6HDMRYL.js";
5
5
  import {
6
6
  resolveScope
7
- } from "./chunk-Z5Q533UG.js";
8
- import "./chunk-TWKNGPBO.js";
7
+ } from "./chunk-HPNRQYLM.js";
9
8
  import "./chunk-EVGXUDX4.js";
9
+ import "./chunk-TWKNGPBO.js";
10
10
  import {
11
11
  log
12
12
  } from "./chunk-NREZK463.js";
@@ -4,9 +4,9 @@ import {
4
4
  } from "./chunk-R6HDMRYL.js";
5
5
  import {
6
6
  resolveScope
7
- } from "./chunk-Z5Q533UG.js";
8
- import "./chunk-TWKNGPBO.js";
7
+ } from "./chunk-HPNRQYLM.js";
9
8
  import "./chunk-EVGXUDX4.js";
9
+ import "./chunk-TWKNGPBO.js";
10
10
  import {
11
11
  log
12
12
  } from "./chunk-NREZK463.js";