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.
- package/README.md +1 -0
- package/dist/chunk-2DTXVMPQ.js +26 -0
- package/dist/{chunk-DHH6SRXV.js → chunk-57EGAXYI.js} +3 -2
- package/dist/{chunk-AZ6AK5M4.js → chunk-B2NFXRFP.js} +12 -2
- package/dist/{chunk-QCKIBJF6.js → chunk-C5YWESSF.js} +2 -6
- package/dist/chunk-E5J24GD5.js +190 -0
- package/dist/{chunk-G4QGJEV7.js → chunk-H5TXJZD7.js} +6 -6
- package/dist/{chunk-FAANBGCB.js → chunk-L53RNEV3.js} +116 -73
- package/dist/{chunk-DMJCO6LJ.js → chunk-RLUNNJWJ.js} +2 -1
- package/dist/{chunk-LXZ47XCD.js → chunk-WNTMKIKC.js} +1 -1
- package/dist/chunk-WSUYLIYX.js +61 -0
- package/dist/clean-67ACB4OS.js +95 -0
- package/dist/{config-44VQBNMS.js → config-HTQAALMA.js} +181 -120
- package/dist/{dev-QXVERU42.js → dev-PFUF67SH.js} +7 -7
- package/dist/{disable-ZWQVU3QZ.js → disable-CDOM7SSI.js} +2 -2
- package/dist/{enable-NXP6APNI.js → enable-SF7742UR.js} +2 -2
- package/dist/{host-6IBKKCPA.js → host-HKA4OHBM.js} +3 -3
- package/dist/index.js +38 -34
- package/dist/{init-KY3ZPEWP.js → init-YM7YEEGK.js} +153 -85
- package/dist/{install-22BLHQ6D.js → install-VYZSTXN6.js} +8 -8
- package/dist/{mcp-OGZAZZRF.js → mcp-7MO2MCLE.js} +69 -66
- package/dist/{mcp-serve-learn-UNW6VH5M.js → mcp-serve-learn-OKVC5WV3.js} +19 -3
- package/dist/{memory-JM45DEIX.js → memory-XALEMWQF.js} +7 -7
- package/dist/{permission-DNW7DE24.js → permission-C5R3LMZQ.js} +2 -2
- package/dist/postinstall.js +1 -1
- package/dist/{session-RCEE6P35.js → session-M4KRQ4VU.js} +2 -2
- package/dist/{status-NMSVOSC4.js → status-2XM5OZDR.js} +19 -14
- package/dist/{statusline-GKVAX7GR.js → statusline-VFIA7OS2.js} +3 -3
- package/dist/{uninstall-6ZJRS2H4.js → uninstall-QKEC3O64.js} +6 -6
- package/dist/{update-P5ZR2JVQ.js → update-IV4Y3T72.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-CZDXMFEZ.js +0 -118
- package/dist/chunk-FI5HEQ43.js +0 -85
- package/dist/{chunk-Z5Q533UG.js → chunk-HPNRQYLM.js} +3 -3
- 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-
|
|
4
|
-
import
|
|
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-
|
|
25
|
+
} from "./chunk-WNTMKIKC.js";
|
|
17
26
|
import {
|
|
18
27
|
ALL_COMPONENT_IDS,
|
|
19
28
|
DEFAULT_CONFIG
|
|
20
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-57EGAXYI.js";
|
|
21
30
|
import {
|
|
22
31
|
ensureShellCompletion,
|
|
23
32
|
hasShellCompletion,
|
|
24
33
|
removeShellCompletion
|
|
25
|
-
} from "./chunk-
|
|
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-
|
|
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 {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
{
|
|
157
|
-
|
|
147
|
+
{
|
|
148
|
+
name: `Use default (${defaultLearnDir})`,
|
|
149
|
+
value: "default"
|
|
150
|
+
},
|
|
151
|
+
{ name: "Custom path\u2026", value: "custom" }
|
|
158
152
|
]
|
|
159
153
|
});
|
|
160
|
-
let
|
|
161
|
-
if (locationChoice === "
|
|
162
|
-
|
|
154
|
+
let newOutputDir = defaultLearnDir;
|
|
155
|
+
if (locationChoice === "custom") {
|
|
156
|
+
newOutputDir = await input({
|
|
163
157
|
message: "Enter path (absolute or ~/...):",
|
|
164
|
-
default:
|
|
165
|
-
validate: (val) =>
|
|
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(
|
|
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
|
-
|
|
173
|
+
mkdirSync(resolved, { recursive: true });
|
|
175
174
|
log.success(`Created ${resolved}`);
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
177
|
}
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
writeLearnField(
|
|
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
|
|
184
|
-
const currentValue =
|
|
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
|
-
|
|
206
|
-
if (scope === "back") return;
|
|
207
|
-
writeLearnField(scope, "language", lang);
|
|
246
|
+
writeLearnField("global", "language", lang);
|
|
208
247
|
}
|
|
209
|
-
async function editLearnCategories(globalCfg
|
|
210
|
-
const existingCats =
|
|
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
|
-
|
|
266
|
-
if (scope === "back") return;
|
|
267
|
-
writeLearnField(scope, "categories", categories);
|
|
300
|
+
writeLearnField("global", "categories", categories);
|
|
268
301
|
}
|
|
269
|
-
async function editLearnAutoCommit(globalCfg
|
|
270
|
-
const currentValue =
|
|
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
|
-
|
|
283
|
-
if (scope === "back") return;
|
|
284
|
-
writeLearnField(scope, "autoCommit", value);
|
|
314
|
+
writeLearnField("global", "autoCommit", value);
|
|
285
315
|
}
|
|
286
|
-
async function editLearnReadmeIndex(globalCfg
|
|
287
|
-
const currentValue =
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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:
|
|
372
|
+
message: `Learn settings${disabledLabel}:`,
|
|
341
373
|
choices: injectBackChoice(
|
|
342
374
|
[
|
|
343
375
|
{
|
|
344
|
-
name: `Output dir
|
|
376
|
+
name: `Output dir (${outputDirVal})`,
|
|
345
377
|
value: "outputDir"
|
|
346
378
|
},
|
|
347
379
|
{
|
|
348
|
-
name: `Language
|
|
380
|
+
name: `Language${langVal ? ` (${langVal})` : ""}`,
|
|
349
381
|
value: "language"
|
|
350
382
|
},
|
|
351
383
|
{
|
|
352
|
-
name:
|
|
384
|
+
name: "Categories",
|
|
353
385
|
value: "categories"
|
|
354
386
|
},
|
|
355
387
|
{
|
|
356
|
-
name: `Auto-commit
|
|
388
|
+
name: `Auto-commit${autoCommitVal !== void 0 ? ` (${autoCommitVal})` : ""}`,
|
|
357
389
|
value: "autoCommit"
|
|
358
390
|
},
|
|
359
391
|
{
|
|
360
|
-
name: `README index
|
|
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
|
|
406
|
+
await editLearnOutputDir(globalCfg);
|
|
371
407
|
break;
|
|
372
408
|
case "language":
|
|
373
|
-
await editLearnLanguage(globalCfg
|
|
409
|
+
await editLearnLanguage(globalCfg);
|
|
374
410
|
break;
|
|
375
411
|
case "categories":
|
|
376
|
-
await editLearnCategories(globalCfg
|
|
412
|
+
await editLearnCategories(globalCfg);
|
|
377
413
|
break;
|
|
378
414
|
case "autoCommit":
|
|
379
|
-
await editLearnAutoCommit(globalCfg
|
|
415
|
+
await editLearnAutoCommit(globalCfg);
|
|
380
416
|
break;
|
|
381
417
|
case "readmeIndex":
|
|
382
|
-
await editLearnReadmeIndex(globalCfg
|
|
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-
|
|
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-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-WNTMKIKC.js";
|
|
4
|
+
import "./chunk-57EGAXYI.js";
|
|
5
5
|
import {
|
|
6
6
|
ensureShellCompletion
|
|
7
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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";
|