knit-mcp 0.6.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.
@@ -0,0 +1,238 @@
1
+ import {
2
+ getRecentGlobalLearnings
3
+ } from "./chunk-FEOG4WTP.js";
4
+ import {
5
+ loadKnowledgeBase
6
+ } from "./chunk-BAUQEFYY.js";
7
+ import {
8
+ globalLearningsPath,
9
+ knitRoot
10
+ } from "./chunk-YI37OAJ7.js";
11
+
12
+ // src/commands/export.ts
13
+ import { existsSync, mkdirSync, readdirSync, writeFileSync, statSync } from "fs";
14
+ import { join } from "path";
15
+ import chalk from "chalk";
16
+ async function exportCommand(format, vaultPath, options = {}) {
17
+ switch (format) {
18
+ case "obsidian":
19
+ await exportObsidian(vaultPath, options);
20
+ return;
21
+ default:
22
+ throw new Error(
23
+ `Unsupported export format: "${format}". Supported formats: obsidian`
24
+ );
25
+ }
26
+ }
27
+ async function exportObsidian(vaultPath, options) {
28
+ const learningsSubdir = join(vaultPath, "learnings");
29
+ const globalLearningsSubdir = join(vaultPath, "global-learnings");
30
+ mkdirSync(vaultPath, { recursive: true });
31
+ mkdirSync(learningsSubdir, { recursive: true });
32
+ mkdirSync(globalLearningsSubdir, { recursive: true });
33
+ const filter = options.filter;
34
+ const exported = [];
35
+ const projectEntryCounts = /* @__PURE__ */ new Map();
36
+ const projectsDir = join(knitRoot(), "projects");
37
+ let projectHashes = [];
38
+ if (existsSync(projectsDir)) {
39
+ try {
40
+ projectHashes = readdirSync(projectsDir).filter((name) => {
41
+ try {
42
+ return statSync(join(projectsDir, name)).isDirectory();
43
+ } catch {
44
+ return false;
45
+ }
46
+ });
47
+ } catch {
48
+ projectHashes = [];
49
+ }
50
+ }
51
+ for (const projectHash of projectHashes) {
52
+ const kbPath = join(projectsDir, projectHash, "knowledgebase.json");
53
+ if (!existsSync(kbPath)) continue;
54
+ const kb = loadKnowledgeBase(kbPath, projectHash);
55
+ const projectName = kb.projectName || projectHash;
56
+ const seenFilenames = /* @__PURE__ */ new Set();
57
+ for (const entry of kb.entries) {
58
+ if (!matchesFilter(entry.tags, filter)) continue;
59
+ const filename = uniqueFilename(seenFilenames, entry.summary);
60
+ const filePath = join(learningsSubdir, filename);
61
+ writeFileSync(filePath, renderProjectLearning(entry, kb, projectName), "utf-8");
62
+ exported.push({
63
+ filename,
64
+ subdir: "learnings",
65
+ summary: entry.summary,
66
+ tags: entry.tags,
67
+ projectName
68
+ });
69
+ projectEntryCounts.set(projectName, (projectEntryCounts.get(projectName) || 0) + 1);
70
+ }
71
+ }
72
+ const perProjectCount = exported.length;
73
+ let globalEntries = [];
74
+ if (existsSync(globalLearningsPath())) {
75
+ globalEntries = getRecentGlobalLearnings(1e5);
76
+ }
77
+ const seenGlobalFilenames = /* @__PURE__ */ new Set();
78
+ for (const entry of globalEntries) {
79
+ if (!matchesFilter(entry.tags, filter)) continue;
80
+ const filename = uniqueFilename(seenGlobalFilenames, entry.summary);
81
+ const filePath = join(globalLearningsSubdir, filename);
82
+ writeFileSync(filePath, renderGlobalLearning(entry), "utf-8");
83
+ exported.push({
84
+ filename,
85
+ subdir: "global-learnings",
86
+ summary: entry.summary,
87
+ tags: entry.tags,
88
+ projectName: entry.projectName
89
+ });
90
+ }
91
+ const globalCount = exported.length - perProjectCount;
92
+ writeFileSync(
93
+ join(vaultPath, "Engram Index.md"),
94
+ renderIndex(exported, perProjectCount, globalCount, projectEntryCounts),
95
+ "utf-8"
96
+ );
97
+ if (!process.env.ENGRAM_EXPORT_QUIET) {
98
+ console.log(chalk.green(" \u2713"), `Exported ${perProjectCount} per-project + ${globalCount} global learnings to ${vaultPath}`);
99
+ }
100
+ }
101
+ function matchesFilter(tags, filter) {
102
+ if (!filter) return true;
103
+ const wanted = filter.toLowerCase();
104
+ return tags.some((t) => t.toLowerCase() === wanted);
105
+ }
106
+ function sanitizeFilename(summary) {
107
+ const base = summary.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
108
+ return base.length > 0 ? base : "entry";
109
+ }
110
+ function uniqueFilename(seen, summary) {
111
+ const base = sanitizeFilename(summary);
112
+ let candidate = `${base}.md`;
113
+ let i = 2;
114
+ while (seen.has(candidate)) {
115
+ candidate = `${base}-${i}.md`;
116
+ i++;
117
+ }
118
+ seen.add(candidate);
119
+ return candidate;
120
+ }
121
+ function stripHash(tag) {
122
+ return tag.startsWith("#") ? tag.slice(1) : tag;
123
+ }
124
+ function withHash(tag) {
125
+ return tag.startsWith("#") ? tag : `#${tag}`;
126
+ }
127
+ function renderProjectLearning(entry, _kb, projectName) {
128
+ const yamlTags = entry.tags.map(stripHash).join(", ");
129
+ const domains = entry.domains.join(", ");
130
+ const inlineTags = entry.tags.map(withHash).join(" ");
131
+ return `---
132
+ date: ${entry.date}
133
+ outcome: ${entry.outcome}
134
+ domains: [${domains}]
135
+ source_project: ${projectName}
136
+ tags: [${yamlTags}]
137
+ ---
138
+
139
+ # ${entry.summary}
140
+
141
+ **Approach:** ${entry.approach}
142
+
143
+ **Lesson:** ${entry.lesson}
144
+
145
+ <!-- Tag links: [[#auth]] [[#stripe]] for Obsidian graph -->
146
+ ${inlineTags}
147
+ `;
148
+ }
149
+ function renderGlobalLearning(entry) {
150
+ const yamlTags = entry.tags.map(stripHash).join(", ");
151
+ const inlineTags = entry.tags.map(withHash).join(" ");
152
+ const outcome = entry.outcome ?? "unknown";
153
+ return `---
154
+ date: ${entry.date}
155
+ outcome: ${outcome}
156
+ source_project: ${entry.projectName}
157
+ tags: [${yamlTags}]
158
+ ---
159
+
160
+ # ${entry.summary}
161
+
162
+ **Lesson:** ${entry.lesson}
163
+
164
+ <!-- Tag links: [[#auth]] [[#stripe]] for Obsidian graph -->
165
+ ${inlineTags}
166
+ `;
167
+ }
168
+ function renderIndex(exported, perProjectCount, globalCount, projectEntryCounts) {
169
+ const byTag = /* @__PURE__ */ new Map();
170
+ for (const e of exported) {
171
+ if (e.tags.length === 0) {
172
+ const k = "#untagged";
173
+ const arr = byTag.get(k) || [];
174
+ arr.push(e);
175
+ byTag.set(k, arr);
176
+ continue;
177
+ }
178
+ for (const t of e.tags) {
179
+ const k = withHash(t);
180
+ const arr = byTag.get(k) || [];
181
+ arr.push(e);
182
+ byTag.set(k, arr);
183
+ }
184
+ }
185
+ const tagSections = [...byTag.entries()].sort((a, b) => {
186
+ if (b[1].length !== a[1].length) return b[1].length - a[1].length;
187
+ return a[0].localeCompare(b[0]);
188
+ });
189
+ const perProjectByName = /* @__PURE__ */ new Map();
190
+ for (const e of exported) {
191
+ if (e.subdir !== "learnings") continue;
192
+ const arr = perProjectByName.get(e.projectName) || [];
193
+ arr.push(e);
194
+ perProjectByName.set(e.projectName, arr);
195
+ }
196
+ let out = `# Engram Knowledge Index
197
+
198
+ Generated ${(/* @__PURE__ */ new Date()).toISOString()}. ${perProjectCount} per-project learnings + ${globalCount} global learnings.
199
+
200
+ ## By tag
201
+
202
+ `;
203
+ if (tagSections.length === 0) {
204
+ out += `_No entries exported._
205
+
206
+ `;
207
+ } else {
208
+ for (const [tag, entries] of tagSections) {
209
+ out += `### ${tag} (${entries.length} entries)
210
+ `;
211
+ for (const entry of entries) {
212
+ const linkPath = `${entry.subdir}/${entry.filename.replace(/\.md$/, "")}`;
213
+ out += `- [[${linkPath}]]
214
+ `;
215
+ }
216
+ out += "\n";
217
+ }
218
+ }
219
+ out += `## All projects
220
+
221
+ `;
222
+ if (perProjectByName.size === 0) {
223
+ out += `_No per-project learnings exported._
224
+ `;
225
+ } else {
226
+ const projects = [...perProjectByName.entries()].sort((a, b) => a[0].localeCompare(b[0]));
227
+ for (const [project, entries] of projects) {
228
+ const total = projectEntryCounts.get(project) ?? entries.length;
229
+ const links = entries.map((e) => `[[learnings/${e.filename.replace(/\.md$/, "")}]]`).join(", ");
230
+ out += `- ${project}: ${total} entries \u2014 ${links}
231
+ `;
232
+ }
233
+ }
234
+ return out;
235
+ }
236
+ export {
237
+ exportCommand
238
+ };
@@ -0,0 +1,76 @@
1
+ import {
2
+ getBrain
3
+ } from "./chunk-NZXLCN4Q.js";
4
+ import "./chunk-QMICM263.js";
5
+ import "./chunk-GRSYI2RR.js";
6
+ import {
7
+ installAgentsForProject
8
+ } from "./chunk-TH5QPD5E.js";
9
+ import "./chunk-LW6NOFHF.js";
10
+ import "./chunk-BAUQEFYY.js";
11
+ import "./chunk-YI37OAJ7.js";
12
+
13
+ // src/commands/install-agents.ts
14
+ import { existsSync } from "fs";
15
+ import { join } from "path";
16
+ import chalk from "chalk";
17
+ import ora from "ora";
18
+ async function installAgentsCommand(targetDir, options) {
19
+ const rootPath = targetDir === "." ? process.cwd() : targetDir;
20
+ if (!existsSync(join(rootPath, "CLAUDE.md"))) {
21
+ console.log(chalk.yellow(" No CLAUDE.md found in this directory."));
22
+ console.log(chalk.dim(" Open this project in Claude Code with engram MCP first \u2014 it auto-initializes on first tool call."));
23
+ process.exit(1);
24
+ }
25
+ const spinner = ora({ text: chalk.dim("Loading brain\u2026"), spinner: "dots" }).start();
26
+ const brain = getBrain(rootPath);
27
+ spinner.succeed(chalk.dim(`Brain loaded (${brain.knowledge.summary.totalFiles} files indexed)`));
28
+ const installSpinner = ora({
29
+ text: chalk.dim(options.all ? "Installing all known agents\u2026" : "Installing agents for this project\u2026"),
30
+ spinner: "dots"
31
+ }).start();
32
+ const result = await installAgentsForProject(
33
+ rootPath,
34
+ brain.config,
35
+ brain.knowledge,
36
+ brain.knowledgeBase,
37
+ { refresh: options.refresh, all: options.all }
38
+ );
39
+ installSpinner.succeed(chalk.dim("Install complete"));
40
+ console.log();
41
+ if (result.installed.length > 0) {
42
+ console.log(chalk.bold(" Installed"));
43
+ for (const name of result.installed) {
44
+ console.log(` ${chalk.green("\u2713")} knit-${name}.md`);
45
+ }
46
+ console.log();
47
+ }
48
+ if (result.alreadyCurrent.length > 0) {
49
+ console.log(chalk.bold(" Already current"));
50
+ for (const name of result.alreadyCurrent) {
51
+ console.log(` ${chalk.dim("\xB7")} knit-${name}.md ${chalk.dim("(no change)")}`);
52
+ }
53
+ console.log();
54
+ }
55
+ if (result.skippedUserCurated.length > 0) {
56
+ console.log(chalk.bold(" Skipped (user-curated files; engram won't clobber)"));
57
+ for (const name of result.skippedUserCurated) {
58
+ console.log(` ${chalk.yellow("!")} knit-${name}.md`);
59
+ }
60
+ console.log();
61
+ }
62
+ if (result.failed.length > 0) {
63
+ console.log(chalk.bold(chalk.red(" Failed")));
64
+ for (const f of result.failed) {
65
+ console.log(` ${chalk.red("\u2717")} ${f.name} \u2014 ${chalk.dim(f.error)}`);
66
+ }
67
+ console.log();
68
+ }
69
+ const total = result.installed.length + result.alreadyCurrent.length;
70
+ console.log(chalk.dim(` ${total} agent${total === 1 ? "" : "s"} ready in <project>/.claude/agents/`));
71
+ console.log(chalk.dim(" Claude Code will find them automatically when teams dispatch."));
72
+ console.log();
73
+ }
74
+ export {
75
+ installAgentsCommand
76
+ };
@@ -0,0 +1,76 @@
1
+ import {
2
+ buildKnowledge,
3
+ generateClaudeMd
4
+ } from "./chunk-QMICM263.js";
5
+ import {
6
+ findFalsePositives
7
+ } from "./chunk-GRSYI2RR.js";
8
+ import {
9
+ scanProject
10
+ } from "./chunk-LW6NOFHF.js";
11
+ import {
12
+ knowledgePath,
13
+ learningsDir,
14
+ projectDataDir
15
+ } from "./chunk-YI37OAJ7.js";
16
+
17
+ // src/commands/refresh.ts
18
+ import { readFileSync, readdirSync, writeFileSync, existsSync } from "fs";
19
+ import { join } from "path";
20
+ import chalk from "chalk";
21
+ import ora from "ora";
22
+ async function refreshCommand(targetDir) {
23
+ const rootPath = targetDir === "." ? process.cwd() : targetDir;
24
+ if (!existsSync(join(rootPath, "CLAUDE.md")) || !existsSync(projectDataDir(rootPath))) {
25
+ console.log(chalk.red(" No Engram setup found. Open this project in Claude Code with the Knit MCP \u2014 it will auto-initialize."));
26
+ process.exit(1);
27
+ }
28
+ const spinner = ora({ text: chalk.dim("Re-scanning project..."), spinner: "dots" }).start();
29
+ const scan = scanProject(rootPath);
30
+ spinner.succeed(chalk.dim("Project re-scanned"));
31
+ const knowledgeSpinner = ora({ text: chalk.dim("Rebuilding knowledge index..."), spinner: "dots" }).start();
32
+ const knowledge = buildKnowledge(rootPath, scan);
33
+ knowledgeSpinner.succeed(
34
+ `${chalk.bold("Indexed:")} ${chalk.cyan(String(knowledge.summary.totalFiles))} files ${chalk.dim("|")} ${chalk.bold("Imports:")} ${chalk.cyan(String(Object.keys(knowledge.importGraph).length))} mapped ${chalk.dim("|")} ${chalk.bold("Untested:")} ${chalk.cyan(String(knowledge.summary.untestedFiles.length))} flagged`
35
+ );
36
+ const learnDir = learningsDir(rootPath);
37
+ const learningsFiles = existsSync(learnDir) ? readdirSync(learnDir).filter((f) => f.endsWith(".md")) : [];
38
+ const falsePositives = learningsFiles.flatMap(
39
+ (f) => findFalsePositives(join(learnDir, f))
40
+ );
41
+ let projectName = "project";
42
+ try {
43
+ const pkg = JSON.parse(readFileSync(join(rootPath, "package.json"), "utf-8"));
44
+ if (pkg.name) projectName = pkg.name;
45
+ } catch {
46
+ const parts = rootPath.split("/");
47
+ projectName = parts[parts.length - 1] || "project";
48
+ }
49
+ const config = {
50
+ name: projectName,
51
+ packageManager: scan.packageManager,
52
+ stack: scan.stack,
53
+ domains: scan.domains,
54
+ targetAgent: "claude-code",
55
+ tokenOptimization: "standard"
56
+ };
57
+ const genSpinner = ora({ text: chalk.dim("Regenerating CLAUDE.md..."), spinner: "dots" }).start();
58
+ writeFileSync(join(rootPath, "CLAUDE.md"), generateClaudeMd(config, knowledge, falsePositives), "utf-8");
59
+ writeFileSync(knowledgePath(rootPath), JSON.stringify(knowledge, null, 2), "utf-8");
60
+ genSpinner.succeed(chalk.dim("CLAUDE.md + knowledge.json updated"));
61
+ console.log();
62
+ console.log(chalk.bold(" Refresh complete"));
63
+ if (falsePositives.length > 0) {
64
+ console.log(` ${chalk.green("\u2713")} ${chalk.dim(`${falsePositives.length} false positives injected into CLAUDE.md`)}`);
65
+ }
66
+ if (knowledge.summary.highFanoutFiles.length > 0) {
67
+ console.log(` ${chalk.green("\u2713")} ${chalk.dim(`High-fanout files: ${knowledge.summary.highFanoutFiles.join(", ")}`)}`);
68
+ }
69
+ if (knowledge.summary.untestedFiles.length > 0) {
70
+ console.log(` ${chalk.yellow("!")} ${chalk.dim(`${knowledge.summary.untestedFiles.length} untested files \u2014 see Project Map in CLAUDE.md`)}`);
71
+ }
72
+ console.log();
73
+ }
74
+ export {
75
+ refreshCommand
76
+ };
@@ -0,0 +1,104 @@
1
+ // src/commands/setup.ts
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { homedir } from "os";
5
+ import chalk from "chalk";
6
+ import ora from "ora";
7
+ var MCP_CONFIG = {
8
+ "knit-brain": {
9
+ command: "npx",
10
+ args: ["-y", "@piyushdua/engram-dev@latest"]
11
+ }
12
+ };
13
+ async function setupCommand(options) {
14
+ const isGlobal = options.global || !options.local;
15
+ const settingsPath = isGlobal ? join(homedir(), ".claude.json") : join(process.cwd(), ".claude", "settings.json");
16
+ const label = isGlobal ? "global (~/.claude.json)" : "local (.claude/settings.json)";
17
+ console.log(` Adding Knit MCP to ${chalk.cyan(label)}`);
18
+ console.log();
19
+ const spinner = ora({ text: chalk.dim("Configuring..."), spinner: "dots" }).start();
20
+ if (isGlobal) {
21
+ const oldPath = join(homedir(), ".claude", "settings.json");
22
+ if (existsSync(oldPath)) {
23
+ try {
24
+ const old = JSON.parse(readFileSync(oldPath, "utf-8"));
25
+ if (old.mcpServers?.["knit-brain"]) {
26
+ delete old.mcpServers["knit-brain"];
27
+ if (Object.keys(old.mcpServers).length === 0) delete old.mcpServers;
28
+ writeFileSync(oldPath, JSON.stringify(old, null, 2), "utf-8");
29
+ }
30
+ } catch {
31
+ }
32
+ }
33
+ }
34
+ let settings = {};
35
+ const dir = dirname(settingsPath);
36
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
37
+ if (existsSync(settingsPath)) {
38
+ try {
39
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
40
+ } catch {
41
+ settings = {};
42
+ }
43
+ }
44
+ if (!settings.mcpServers) settings.mcpServers = {};
45
+ const mcpServers = settings.mcpServers;
46
+ if (mcpServers["knit-brain"]) {
47
+ spinner.succeed(chalk.dim("Knit MCP already configured"));
48
+ console.log();
49
+ console.log(chalk.bold(" Already set up. Open any project in Claude Code \u2014 the brain activates automatically."));
50
+ console.log();
51
+ return;
52
+ }
53
+ mcpServers["knit-brain"] = MCP_CONFIG["knit-brain"];
54
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
55
+ spinner.succeed(chalk.dim("Knit MCP configured"));
56
+ console.log();
57
+ console.log(chalk.bold(" Setup complete"));
58
+ console.log();
59
+ console.log(` ${chalk.green("\u2713")} MCP server added to ${chalk.cyan(label)}`);
60
+ console.log(` ${chalk.green("\u2713")} Auto-initializes on first use \u2014 no per-project setup needed`);
61
+ console.log();
62
+ const globalClaudeMd = join(homedir(), ".claude", "CLAUDE.md");
63
+ const engramInstruction = `
64
+
65
+ ## Engram Brain (MCP)
66
+
67
+ You have the Knit MCP server connected. USE IT on every task:
68
+
69
+ 1. **First:** Call \`knit_classify_task\` with files you plan to touch \u2014 get tier and phases
70
+ 2. **Research:** Call \`knit_build_context\` \u2014 get domain context with ripple effects
71
+ 3. **During:** Use \`knit_query_imports\`, \`knit_query_exports\`, \`knit_query_tests\` instead of grepping
72
+ 4. **End:** Call \`knit_record_learning\` before saying "done" \u2014 persist what you learned
73
+
74
+ For new projects, call \`knit_brain_status\` first \u2014 triggers auto-initialization.
75
+ `;
76
+ if (existsSync(globalClaudeMd)) {
77
+ const existing = readFileSync(globalClaudeMd, "utf-8");
78
+ if (!existing.includes("Engram Brain (MCP)")) {
79
+ writeFileSync(globalClaudeMd, existing + engramInstruction, "utf-8");
80
+ console.log(` ${chalk.green("\u2713")} Engram instructions added to ${chalk.cyan("~/.claude/CLAUDE.md")}`);
81
+ }
82
+ } else {
83
+ const dir2 = join(homedir(), ".claude");
84
+ if (!existsSync(dir2)) mkdirSync(dir2, { recursive: true });
85
+ writeFileSync(globalClaudeMd, `# Claude Code Global Instructions${engramInstruction}`, "utf-8");
86
+ console.log(` ${chalk.green("\u2713")} Created ${chalk.cyan("~/.claude/CLAUDE.md")} with Engram instructions`);
87
+ }
88
+ console.log();
89
+ console.log(chalk.bold(" How it works"));
90
+ console.log(` ${chalk.cyan("1.")} Open ${chalk.bold("any project")} in Claude Code`);
91
+ console.log(` ${chalk.cyan("2.")} Agent calls \`knit_classify_task\` \u2192 brain auto-initializes`);
92
+ console.log(` ${chalk.cyan("3.")} Agent gets 20 tools: imports, exports, tests, learnings, teams`);
93
+ console.log(` ${chalk.cyan("4.")} Brain compounds with every session \u2014 gets smarter over time`);
94
+ console.log();
95
+ console.log(chalk.dim(" No CLI needed after this. The MCP server handles everything."));
96
+ console.log();
97
+ if (isGlobal) {
98
+ console.log(chalk.dim(` Config written to: ${settingsPath}`));
99
+ }
100
+ console.log();
101
+ }
102
+ export {
103
+ setupCommand
104
+ };
@@ -0,0 +1,145 @@
1
+ import {
2
+ readLearnings
3
+ } from "./chunk-GRSYI2RR.js";
4
+ import {
5
+ getKBSummary,
6
+ getStaleEntries,
7
+ getTopEntries,
8
+ loadKnowledgeBase
9
+ } from "./chunk-BAUQEFYY.js";
10
+ import {
11
+ knowledgePath,
12
+ knowledgebasePath,
13
+ learningsDir
14
+ } from "./chunk-YI37OAJ7.js";
15
+
16
+ // src/commands/status.ts
17
+ import { existsSync, readFileSync, readdirSync } from "fs";
18
+ import { join } from "path";
19
+ import chalk from "chalk";
20
+ async function statusCommand(targetDir) {
21
+ const rootPath = targetDir === "." ? process.cwd() : targetDir;
22
+ const kbPath = knowledgebasePath(rootPath);
23
+ const knowledgeIndexPath = knowledgePath(rootPath);
24
+ if (!existsSync(kbPath) && !existsSync(knowledgeIndexPath)) {
25
+ console.log(chalk.yellow(" No Engram data found. The brain will auto-initialize when you open this project in Claude Code."));
26
+ console.log();
27
+ return;
28
+ }
29
+ if (existsSync(knowledgeIndexPath)) {
30
+ try {
31
+ const knowledge = JSON.parse(readFileSync(knowledgeIndexPath, "utf-8"));
32
+ const s = knowledge.summary;
33
+ console.log(chalk.bold(" Knowledge Index"));
34
+ console.log();
35
+ console.log(` ${chalk.cyan("Files:")} ${s.totalFiles} indexed (${s.totalLines.toLocaleString()} lines)`);
36
+ console.log(` ${chalk.cyan("Imports:")} ${Object.keys(knowledge.importGraph || {}).length} edges mapped`);
37
+ console.log(` ${chalk.cyan("Exports:")} ${Object.keys(knowledge.exports || {}).length} files with exports`);
38
+ console.log(` ${chalk.cyan("Untested:")} ${(s.untestedFiles || []).length} files`);
39
+ console.log(` ${chalk.cyan("High-fanout:")} ${(s.highFanoutFiles || []).length} files`);
40
+ if (s.largestFiles && s.largestFiles.length > 0) {
41
+ console.log();
42
+ console.log(chalk.bold(" Largest Files"));
43
+ console.log();
44
+ for (const f of s.largestFiles.slice(0, 5)) {
45
+ const bar = "\u2588".repeat(Math.min(Math.round(f.lines / 20), 30));
46
+ console.log(` ${chalk.dim(f.path.padEnd(40))} ${chalk.cyan(bar)} ${f.lines}`);
47
+ }
48
+ }
49
+ if (s.languageBreakdown) {
50
+ console.log();
51
+ console.log(chalk.bold(" Language Breakdown"));
52
+ console.log();
53
+ for (const [ext, count] of Object.entries(s.languageBreakdown).sort((a, b) => b[1] - a[1])) {
54
+ const bar = "\u2588".repeat(Math.min(count, 30));
55
+ console.log(` ${chalk.dim(String(ext).padEnd(10))} ${chalk.cyan(bar)} ${count}`);
56
+ }
57
+ }
58
+ } catch {
59
+ }
60
+ }
61
+ if (existsSync(kbPath)) {
62
+ const kb = loadKnowledgeBase(kbPath, "project");
63
+ const summary = getKBSummary(kb);
64
+ console.log();
65
+ console.log(chalk.bold(" Knowledge Base"));
66
+ console.log();
67
+ console.log(` ${chalk.cyan("Learnings:")} ${summary.totalEntries} total`);
68
+ console.log(` ${chalk.green("Accessed:")} ${summary.accessedEntries} (${summary.hitRate}% hit rate)`);
69
+ console.log(` ${chalk.dim("Never used:")} ${summary.neverAccessed}`);
70
+ console.log(` ${chalk.yellow("False positives:")} ${summary.falsePositives}`);
71
+ const stale = getStaleEntries(kb);
72
+ if (stale.length > 0) {
73
+ console.log(` ${chalk.red("Stale (30d+):")} ${stale.length} \u2014 candidates for cleanup`);
74
+ }
75
+ console.log();
76
+ console.log(chalk.bold(" Session History"));
77
+ console.log();
78
+ console.log(` ${chalk.cyan("Total sessions:")} ${summary.totalSessions}`);
79
+ console.log(` ${chalk.green("Cache hits:")} ${summary.cacheHits} (learnings prevented re-investigation)`);
80
+ if (summary.avgFilesPerSession > 0) {
81
+ console.log(` ${chalk.dim("Avg files/session:")} ${summary.avgFilesPerSession}`);
82
+ }
83
+ if (kb.metrics.sessions.length > 0) {
84
+ console.log();
85
+ console.log(chalk.bold(" Recent Sessions"));
86
+ console.log();
87
+ console.log(` ${chalk.dim("Date".padEnd(12))} ${chalk.dim("Branch".padEnd(20))} ${chalk.dim("Files".padEnd(8))} ${chalk.dim("Learnings")}`);
88
+ console.log(` ${chalk.dim("\u2500".repeat(55))}`);
89
+ for (const session of kb.metrics.sessions.slice(-10).reverse()) {
90
+ console.log(` ${chalk.dim(session.date.padEnd(12))} ${(session.branch || "unknown").padEnd(20)} ${String(session.filesModified).padEnd(8)} +${session.learningsAdded}`);
91
+ }
92
+ }
93
+ const top = getTopEntries(kb, 5);
94
+ if (top.length > 0) {
95
+ console.log();
96
+ console.log(chalk.bold(" Most Valuable Learnings"));
97
+ console.log();
98
+ for (const entry of top) {
99
+ const badge = entry.accessCount > 0 ? chalk.green(`${entry.accessCount}x`) : chalk.dim("0x");
100
+ console.log(` ${badge} ${chalk.dim(entry.date)} ${entry.summary}`);
101
+ }
102
+ }
103
+ if (summary.topDomains.length > 0) {
104
+ console.log();
105
+ console.log(chalk.bold(" Domain Distribution"));
106
+ console.log();
107
+ for (const [domain, count] of summary.topDomains) {
108
+ const bar = "\u2588".repeat(Math.min(count, 20));
109
+ console.log(` ${chalk.cyan(domain.padEnd(20))} ${bar} ${count}`);
110
+ }
111
+ }
112
+ if (summary.cacheHits > 0 || summary.accessedEntries > 0) {
113
+ console.log();
114
+ console.log(chalk.bold(" Impact"));
115
+ console.log();
116
+ if (summary.cacheHits > 0) console.log(` ${chalk.green("\u2713")} ${summary.cacheHits} re-investigations prevented by learnings`);
117
+ if (summary.accessedEntries > 0) console.log(` ${chalk.green("\u2713")} ${summary.accessedEntries} learnings actively used`);
118
+ if (summary.falsePositives > 0) console.log(` ${chalk.green("\u2713")} ${summary.falsePositives} false positives suppressing noise`);
119
+ if (summary.neverAccessed > 0) console.log(` ${chalk.yellow("!")} ${summary.neverAccessed} learnings never accessed \u2014 review for cleanup`);
120
+ }
121
+ }
122
+ const learnDir = learningsDir(rootPath);
123
+ if (existsSync(learnDir)) {
124
+ const files = readdirSync(learnDir).filter((f) => f.endsWith(".md") && f !== "sessions.md");
125
+ if (files.length > 0) {
126
+ console.log();
127
+ console.log(chalk.bold(" Learnings Files"));
128
+ console.log();
129
+ for (const file of files) {
130
+ const entries = readLearnings(join(learnDir, file));
131
+ console.log(` ${chalk.dim(file.padEnd(30))} ${entries.length} entries`);
132
+ }
133
+ }
134
+ const sessionsPath = join(learnDir, "sessions.md");
135
+ if (existsSync(sessionsPath)) {
136
+ const content = readFileSync(sessionsPath, "utf-8");
137
+ const sessionCount = (content.match(/^## Session/gm) || []).length;
138
+ console.log(` ${chalk.dim("sessions.md".padEnd(30))} ${sessionCount} sessions logged`);
139
+ }
140
+ }
141
+ console.log();
142
+ }
143
+ export {
144
+ statusCommand
145
+ };