knit-mcp 0.11.4 → 0.12.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 CHANGED
@@ -452,6 +452,7 @@ LongMemEval-S R@5/R@10 + LOCOMO LLM-as-Judge runs are on the roadmap (v0.13+). U
452
452
 
453
453
  | Version | Headline |
454
454
  |---|---|
455
+ | **v0.12.0** | **Picture Perfect: Structural Enforcement.** Diagnostic → enforcing. Budget verdict surfaces in the MCP `instructions` field at handshake (before any tool description is read). `knit_load_session` carries `budget_health` + `learnings_health` nudges. `engram doctor` exits non-zero on over-budget; `engram setup` runs doctor as final step. New PostToolUse hook warns immediately on over-budget CLAUDE.md edits (HOOKS_VERSION 11→12; auto-rolls to existing users). This repo dogfoods: hand-curated 16KB CLAUDE.md migrated to lean 3.8KB + `.claude/MARKETING.md` sidecar. New `npm run bench:tokens` measures real MCP-on vs MCP-off cost: 93% smaller per-recall call, 50% smaller per-classify, payback at 3 recall calls. 53 tools, 705 tests. |
455
456
  | **v0.11.4** | Dogfood audit · ran a full audit of Knit's own codebase using its own `knit_spawn_team_worktree` primitive (4 parallel teams: Core Logic, Infrastructure, UI, Quality Assurance). Fixes: HIGH `engram refresh` no longer clobbers user-curated CLAUDE.md (now uses `spliceKnitBlock` like `cache.ts`); `saveSource`/`loadSource` validate `sourceId`; `appendGlobalLearning` propagates write failures; `redactSecrets` applied to `label`/`tags`/`domains` across all persistence boundaries; 100KB response ceiling on `knit_generate_test_cases`; full v0.11 tool surface now documented in `workflow-protocol.ts` generator (was frozen at the v0.4 surface). Plus: 16 key tools reclassified with `[PROTOCOL]`/`[REVIEW]`/`[MEMORY]`/`[GRAPH]` prefixes so the LLM picks the right tool reliably. 53 tools, 687 tests. |
456
457
  | **v0.11.3** | Propagation patch · `update_available` flag now surfaces in `knit_load_session` response (≈100% session reach vs. brain_status' low reach) + startup stderr nag on stale versions. Helps FUTURE upgrades land faster; doesn't retroactively reach v0.10.x users. 53 tools, 665 tests. |
457
458
  | **v0.11.2** | Pre-publish polish · chunk cap (2000) + `errorResponse` envelope across handlers + CLAUDE.md generator surfaces v0.11 tools · new `engram doctor` install health-check CLI · upgrade-path smoke test caught + fixed a data-loss bug in cache.ts (Case B was wiping user permissions on upgrade) · 11 real exploit-payload integration tests prove C1/C2/H1 fixes hold · `npm run bench` ships a synthetic retrieval harness (50 Q&A) measuring 86% top-1 / 96% R@5. 53 tools, 664 tests. |
@@ -2,9 +2,9 @@ import {
2
2
  detectProjectRoot,
3
3
  getBrain,
4
4
  refreshBrain
5
- } from "./chunk-I63UMEBF.js";
6
- import "./chunk-HROSQ5MS.js";
5
+ } from "./chunk-KKLAJLPO.js";
7
6
  import "./chunk-GATMQQK5.js";
7
+ import "./chunk-WADRF26Z.js";
8
8
  import "./chunk-WKQHCLLO.js";
9
9
  import "./chunk-MOOVNMIN.js";
10
10
  import "./chunk-ST4X7LZT.js";
@@ -1,11 +1,11 @@
1
- import {
2
- HOOKS_VERSION,
3
- generateSettings
4
- } from "./chunk-HROSQ5MS.js";
5
1
  import {
6
2
  installAgentsForProject,
7
3
  pruneSessionsByAge
8
4
  } from "./chunk-GATMQQK5.js";
5
+ import {
6
+ HOOKS_VERSION,
7
+ generateSettings
8
+ } from "./chunk-WADRF26Z.js";
9
9
  import {
10
10
  importFromMarkdown,
11
11
  loadKnowledgeBaseSafe,
@@ -1,4 +1,6 @@
1
1
  // src/mcp/instructions.ts
2
+ import { statSync } from "fs";
3
+ import { join } from "path";
2
4
  var KNIT_INSTRUCTIONS_BASE = `Knit is a memory + workflow layer for this project. It provides per-project memory across sessions, a knowledge graph (imports/exports/tests), and a tier-routed workflow protocol.
3
5
 
4
6
  ALWAYS at session start:
@@ -32,8 +34,28 @@ Knit provides inputs; you make the calls. When in doubt, under-classify \u2014 e
32
34
 
33
35
  Citation rule: when you state a fact about this codebase ("file X imports Y", "function Z is defined in W", "tests for A live in B"), cite the Knit tool result that verified it \u2014 e.g. "(per knit_query_imports)". If you can't cite a tool result, mark the claim as 'unverified' explicitly. This makes hallucinations visible at the claim level instead of letting them ship as confident-sounding prose. The verifier exists; use it.`;
34
36
  var KNIT_INSTRUCTIONS = KNIT_INSTRUCTIONS_BASE;
35
- function buildInstructions(scan) {
36
- if (!scan) return KNIT_INSTRUCTIONS_BASE;
37
+ var CLAUDE_MD_BUDGET_BYTES = 6500;
38
+ function buildBudgetVerdict(rootPath) {
39
+ let bytes = 0;
40
+ try {
41
+ bytes = statSync(join(rootPath, "CLAUDE.md")).size;
42
+ } catch {
43
+ return "";
44
+ }
45
+ if (bytes <= CLAUDE_MD_BUDGET_BYTES) return "";
46
+ const verdict = bytes > CLAUDE_MD_BUDGET_BYTES * 1.25 ? "over-budget" : "warn";
47
+ const kb = Math.round(bytes / 1024 * 10) / 10;
48
+ const targetKb = Math.round(CLAUDE_MD_BUDGET_BYTES / 1024 * 10) / 10;
49
+ return `BUDGET ${verdict}: CLAUDE.md is ${kb}KB / ${targetKb}KB target. Run \`engram doctor\` to see the full per-surface report and \`engram refresh\` to regenerate the marker block.`;
50
+ }
51
+ function buildInstructions(scan, rootPath) {
52
+ const budgetLine = rootPath ? buildBudgetVerdict(rootPath) : "";
53
+ const budgetSuffix = budgetLine ? `
54
+
55
+ \u2014 Budget check \u2014
56
+
57
+ ${budgetLine}` : "";
58
+ if (!scan) return KNIT_INSTRUCTIONS_BASE + budgetSuffix;
37
59
  const addenda = [];
38
60
  if (scan.detected.ruflo.present) {
39
61
  addenda.push(
@@ -60,12 +82,14 @@ function buildInstructions(scan) {
60
82
  `DETECTED: this project's CLAUDE.md has user-curated workflow sections (${scan.detected.custom_workflow_sections.join("; ")}). Treat that as the canonical workflow doc for project-specific phases; Knit's tier protocol applies underneath as the routing layer.`
61
83
  );
62
84
  }
63
- if (addenda.length === 0) return KNIT_INSTRUCTIONS_BASE;
64
- return KNIT_INSTRUCTIONS_BASE + "\n\n\u2014 Per-project integrations \u2014\n\n" + addenda.join("\n\n") + "\n\nGeneral rule: when an integration above provides a higher-level routing primitive (slash command, swarm orchestrator, methodology framework), use it. Knit handles the substrate it doesn't cover: memory, classification, and the workflow protocol for tasks the integration doesn't route.";
85
+ if (addenda.length === 0) return KNIT_INSTRUCTIONS_BASE + budgetSuffix;
86
+ return KNIT_INSTRUCTIONS_BASE + "\n\n\u2014 Per-project integrations \u2014\n\n" + addenda.join("\n\n") + "\n\nGeneral rule: when an integration above provides a higher-level routing primitive (slash command, swarm orchestrator, methodology framework), use it. Knit handles the substrate it doesn't cover: memory, classification, and the workflow protocol for tasks the integration doesn't route." + budgetSuffix;
65
87
  }
66
88
 
67
89
  export {
68
90
  KNIT_INSTRUCTIONS_BASE,
69
91
  KNIT_INSTRUCTIONS,
92
+ CLAUDE_MD_BUDGET_BYTES,
93
+ buildBudgetVerdict,
70
94
  buildInstructions
71
95
  };
@@ -1,9 +1,12 @@
1
+ import {
2
+ HOOKS_VERSION
3
+ } from "./chunk-WADRF26Z.js";
1
4
  import {
2
5
  VERSION
3
6
  } from "./chunk-UTVFELXS.js";
4
7
  import {
5
- HOOKS_VERSION
6
- } from "./chunk-HROSQ5MS.js";
8
+ CLAUDE_MD_BUDGET_BYTES
9
+ } from "./chunk-QQNHF4XY.js";
7
10
  import {
8
11
  knowledgebasePath,
9
12
  projectDataDir
@@ -125,6 +128,45 @@ function runDoctor(rootPath) {
125
128
  } catch {
126
129
  }
127
130
  }
131
+ const claudeMdPath = join(rootPath, "CLAUDE.md");
132
+ if (existsSync(claudeMdPath)) {
133
+ try {
134
+ const bytes = statSync(claudeMdPath).size;
135
+ const kb = Math.round(bytes / 1024 * 10) / 10;
136
+ const targetKb = Math.round(CLAUDE_MD_BUDGET_BYTES / 1024 * 10) / 10;
137
+ if (bytes <= CLAUDE_MD_BUDGET_BYTES) {
138
+ checks.push({
139
+ name: "Token budget",
140
+ status: "ok",
141
+ detail: `CLAUDE.md ${kb}KB / ${targetKb}KB target \u2014 healthy`
142
+ });
143
+ } else if (bytes <= CLAUDE_MD_BUDGET_BYTES * 1.25) {
144
+ checks.push({
145
+ name: "Token budget",
146
+ status: "warn",
147
+ detail: `CLAUDE.md ${kb}KB / ${targetKb}KB target \u2014 over budget, within 25% slack. Run \`engram refresh\` or trim the file.`
148
+ });
149
+ } else {
150
+ checks.push({
151
+ name: "Token budget",
152
+ status: "error",
153
+ detail: `CLAUDE.md ${kb}KB / ${targetKb}KB target \u2014 over budget by >25%. Move long-form content to .claude/MARKETING.md or run \`engram refresh\`.`
154
+ });
155
+ }
156
+ } catch (err) {
157
+ checks.push({
158
+ name: "Token budget",
159
+ status: "warn",
160
+ detail: `CLAUDE.md unreadable: ${err.message}`
161
+ });
162
+ }
163
+ } else {
164
+ checks.push({
165
+ name: "Token budget",
166
+ status: "info",
167
+ detail: "no CLAUDE.md yet \u2014 created on first MCP call"
168
+ });
169
+ }
128
170
  const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
129
171
  if (Number.isFinite(nodeMajor) && nodeMajor < 18) {
130
172
  checks.push({
@@ -173,7 +215,8 @@ async function doctorCommand(directory) {
173
215
  console.log(chalk.green(" All checks passed \u2014 install is healthy."));
174
216
  }
175
217
  }
218
+
176
219
  export {
177
- doctorCommand,
178
- runDoctor
220
+ runDoctor,
221
+ doctorCommand
179
222
  };
@@ -15,7 +15,7 @@ import {
15
15
  } from "./chunk-27TA2ZQZ.js";
16
16
 
17
17
  // src/generators/settings.ts
18
- var HOOKS_VERSION = 11;
18
+ var HOOKS_VERSION = 12;
19
19
  function generateSettings(config, rootPath) {
20
20
  return {
21
21
  mcpServers: {
@@ -288,6 +288,39 @@ function generateHooks(config, rootPath) {
288
288
  }
289
289
  ]
290
290
  });
291
+ hooks.PostToolUse.push({
292
+ _knitOwned: true,
293
+ matcher: "Write|Edit|MultiEdit",
294
+ hooks: [
295
+ {
296
+ type: "command",
297
+ command: nodeHook(`
298
+ let d = "";
299
+ process.stdin.on("data", (c) => d += c);
300
+ process.stdin.on("end", () => {
301
+ try {
302
+ const fs = require("fs");
303
+ const path = require("path");
304
+ const i = JSON.parse(d);
305
+ const ti = i.tool_input || {};
306
+ const f = ti.file_path || (i.tool_response && i.tool_response.filePath) || "";
307
+ if (!f) return;
308
+ if (path.basename(f) !== "CLAUDE.md") return;
309
+ const TARGET = 6500;
310
+ const SLACK = 6500 * 1.25;
311
+ let size = 0;
312
+ try { size = fs.statSync(f).size; } catch { return; }
313
+ if (size <= TARGET) return;
314
+ const kb = Math.round(size/1024*10)/10;
315
+ const verdict = size > SLACK ? "over-budget" : "warn";
316
+ process.stderr.write("[knit] BUDGET " + verdict + ": " + f + " is now " + kb + "KB (target 6.5KB). Move long-form content to .claude/MARKETING.md or run \\\`engram refresh\\\`.\\n");
317
+ } catch (e) { try { process.stderr.write('[knit] claude-md size watch hook failed: ' + (e && e.message ? e.message : e) + '\\n'); } catch {} }
318
+ });
319
+ `),
320
+ timeout: 5
321
+ }
322
+ ]
323
+ });
291
324
  hooks.PostToolUse.push({
292
325
  _knitOwned: true,
293
326
  matcher: "Write|Edit|MultiEdit",
package/dist/cli.js CHANGED
@@ -19,12 +19,12 @@ if (hasSubcommand) {
19
19
  async function runCLI() {
20
20
  const gradient = (await import("gradient-string")).default;
21
21
  const chalk = (await import("chalk")).default;
22
- const { setupCommand } = await import("./setup-5TUUWLIJ.js");
22
+ const { setupCommand } = await import("./setup-62ZH7GEI.js");
23
23
  const { statusCommand } = await import("./status-2SEITNIE.js");
24
24
  const { refreshCommand } = await import("./refresh-S62AZ3QA.js");
25
- const { installAgentsCommand } = await import("./install-agents-WDBQBWMN.js");
25
+ const { installAgentsCommand } = await import("./install-agents-AH7EBB4J.js");
26
26
  const { exportCommand } = await import("./export-4BO6HCXP.js");
27
- const { doctorCommand } = await import("./doctor-4DN2P2JR.js");
27
+ const { doctorCommand } = await import("./doctor-NI22FPXV.js");
28
28
  const ENGRAM_GRADIENT = gradient(["#7c3aed", "#2563eb", "#06b6d4"]);
29
29
  const banner = `
30
30
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
@@ -98,9 +98,9 @@ async function runMCP() {
98
98
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
99
99
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
100
100
  const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
101
- const { getBrain, detectProjectRoot, refreshBrain } = await import("./cache-7HSMIYDJ.js");
102
- const { getActiveToolDefinitionsForBrain, handleToolCall } = await import("./tools-ECHCPLCB.js");
103
- const { buildInstructions } = await import("./instructions-JARSXQPO.js");
101
+ const { getBrain, detectProjectRoot, refreshBrain } = await import("./cache-46OKHDRR.js");
102
+ const { getActiveToolDefinitionsForBrain, handleToolCall } = await import("./tools-ZQHRWMVK.js");
103
+ const { buildInstructions } = await import("./instructions-CF6K57Z2.js");
104
104
  const { registerToolsListChangedNotifier } = await import("./notifier-4L27HKHI.js");
105
105
  const { loadScanResult } = await import("./integration-scanner-LBD2PIZ3.js");
106
106
  const { prewarmLatestVersion, getCachedLatestVersion, isNewerVersion } = await import("./update-check-GQVDVT2N.js");
@@ -0,0 +1,12 @@
1
+ import {
2
+ doctorCommand,
3
+ runDoctor
4
+ } from "./chunk-TE6NP6BZ.js";
5
+ import "./chunk-WADRF26Z.js";
6
+ import "./chunk-UTVFELXS.js";
7
+ import "./chunk-QQNHF4XY.js";
8
+ import "./chunk-27TA2ZQZ.js";
9
+ export {
10
+ doctorCommand,
11
+ runDoctor
12
+ };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  getBrain
3
- } from "./chunk-I63UMEBF.js";
4
- import "./chunk-HROSQ5MS.js";
3
+ } from "./chunk-KKLAJLPO.js";
5
4
  import {
6
5
  installAgentsForProject
7
6
  } from "./chunk-GATMQQK5.js";
7
+ import "./chunk-WADRF26Z.js";
8
8
  import "./chunk-WKQHCLLO.js";
9
9
  import "./chunk-MOOVNMIN.js";
10
10
  import "./chunk-ST4X7LZT.js";
@@ -1,10 +1,14 @@
1
1
  import {
2
+ CLAUDE_MD_BUDGET_BYTES,
2
3
  KNIT_INSTRUCTIONS,
3
4
  KNIT_INSTRUCTIONS_BASE,
5
+ buildBudgetVerdict,
4
6
  buildInstructions
5
- } from "./chunk-LV73YTVN.js";
7
+ } from "./chunk-QQNHF4XY.js";
6
8
  export {
9
+ CLAUDE_MD_BUDGET_BYTES,
7
10
  KNIT_INSTRUCTIONS,
8
11
  KNIT_INSTRUCTIONS_BASE,
12
+ buildBudgetVerdict,
9
13
  buildInstructions
10
14
  };
@@ -1,3 +1,11 @@
1
+ import {
2
+ runDoctor
3
+ } from "./chunk-TE6NP6BZ.js";
4
+ import "./chunk-WADRF26Z.js";
5
+ import "./chunk-UTVFELXS.js";
6
+ import "./chunk-QQNHF4XY.js";
7
+ import "./chunk-27TA2ZQZ.js";
8
+
1
9
  // src/commands/setup.ts
2
10
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
3
11
  import { join, dirname } from "path";
@@ -89,7 +97,7 @@ For new projects, call \`knit_brain_status\` first \u2014 triggers auto-initiali
89
97
  console.log(chalk.bold(" How it works"));
90
98
  console.log(` ${chalk.cyan("1.")} Open ${chalk.bold("any project")} in Claude Code`);
91
99
  console.log(` ${chalk.cyan("2.")} Agent calls \`knit_classify_task\` \u2192 brain auto-initializes`);
92
- console.log(` ${chalk.cyan("3.")} Agent gets 35 tools: imports, exports, tests, learnings, teams`);
100
+ console.log(` ${chalk.cyan("3.")} Agent gets 53+ tools: imports, exports, tests, learnings, teams, requirements`);
93
101
  console.log(` ${chalk.cyan("4.")} Brain compounds with every session \u2014 gets smarter over time`);
94
102
  console.log();
95
103
  console.log(chalk.dim(" No CLI needed after this. The MCP server handles everything."));
@@ -98,6 +106,28 @@ For new projects, call \`knit_brain_status\` first \u2014 triggers auto-initiali
98
106
  console.log(chalk.dim(` Config written to: ${settingsPath}`));
99
107
  }
100
108
  console.log();
109
+ console.log(chalk.bold(" Install health check"));
110
+ console.log();
111
+ try {
112
+ const report = runDoctor(process.cwd());
113
+ for (const c of report.checks) {
114
+ const icon = c.status === "ok" ? chalk.green("\u2713") : c.status === "warn" ? chalk.yellow("\u26A0") : c.status === "error" ? chalk.red("\u2717") : chalk.gray("\xB7");
115
+ console.log(` ${icon} ${c.name.padEnd(22)} ${chalk.dim(c.detail)}`);
116
+ }
117
+ const errors = report.checks.filter((c) => c.status === "error").length;
118
+ const warnings = report.checks.filter((c) => c.status === "warn").length;
119
+ console.log();
120
+ if (errors > 0) {
121
+ console.log(chalk.red(` ${errors} error(s) \u2014 run \`engram doctor\` for full details + fix commands.`));
122
+ } else if (warnings > 0) {
123
+ console.log(chalk.yellow(` ${warnings} warning(s) \u2014 setup complete, but check items above.`));
124
+ } else {
125
+ console.log(chalk.green(" All checks passed \u2014 install is healthy."));
126
+ }
127
+ } catch (err) {
128
+ console.log(chalk.dim(` (doctor skipped: ${err.message})`));
129
+ }
130
+ console.log();
101
131
  }
102
132
  export {
103
133
  setupCommand
@@ -1,3 +1,12 @@
1
+ import {
2
+ appendSession,
3
+ getRecentSessions,
4
+ installAgentsForProject,
5
+ loadAllSessions,
6
+ pruneSessionsByAge,
7
+ searchSessions,
8
+ sessionCount
9
+ } from "./chunk-GATMQQK5.js";
1
10
  import {
2
11
  appendGlobalLearning,
3
12
  buildGlobalLearning,
@@ -8,21 +17,12 @@ import {
8
17
  import {
9
18
  notifyToolsListChanged
10
19
  } from "./chunk-WMESQUZU.js";
11
- import {
12
- KNIT_INSTRUCTIONS
13
- } from "./chunk-LV73YTVN.js";
14
20
  import {
15
21
  VERSION
16
22
  } from "./chunk-UTVFELXS.js";
17
23
  import {
18
- appendSession,
19
- getRecentSessions,
20
- installAgentsForProject,
21
- loadAllSessions,
22
- pruneSessionsByAge,
23
- searchSessions,
24
- sessionCount
25
- } from "./chunk-GATMQQK5.js";
24
+ KNIT_INSTRUCTIONS
25
+ } from "./chunk-QQNHF4XY.js";
26
26
  import {
27
27
  addEntry,
28
28
  bumpClassificationTier,
@@ -2012,20 +2012,23 @@ var TOKEN_BUDGETS = {
2012
2012
  /** Generated CLAUDE.md block. v0.7 trim landed at ~2KB on typical projects;
2013
2013
  * 6.5KB target allows for projects with many domains / large project map. */
2014
2014
  claude_md_bytes: 6500,
2015
- /** Tier-gated tools/list response. v0.12 typical: 38 Tier-1 active × ~280
2016
- * bytes ≈ 10.6KB. 11KB target allows headroom for the v0.12 + v0.13
2017
- * growth without immediately warning; full 51-tool exposure
2018
- * (everything enabled) sits in warn range, surfacing the bloat. */
2019
- tool_registry_bytes: 11e3,
2015
+ /** Tier-gated tools/list response. v0.12 typical: 40 Tier-1 active × ~280
2016
+ * bytes ≈ 11.2KB. 12KB target gives Tier-1 healthy headroom; full
2017
+ * 51-tool exposure (everything enabled) sits in warn range, surfacing
2018
+ * the bloat. Bumped from 11000 in v0.12 to reflect actual Tier-1 size. */
2019
+ tool_registry_bytes: 12e3,
2020
2020
  /** MCP server `instructions` field — sent at handshake. v0.11.1 surfaces
2021
2021
  * 9 new tools (verify_claim, calibration, requirements ingestion,
2022
- * fingerprint, infer_domains, compose_template) → ~3.5KB. The
2023
- * discoverability-vs-budget trade-off favors surfacing real tools. */
2022
+ * fingerprint, infer_domains, compose_template) → ~3.5KB. v0.12 may
2023
+ * append a one-line budget verdict (~200B) when CLAUDE.md is over
2024
+ * budget. The discoverability-vs-budget trade-off favors surfacing
2025
+ * real tools. */
2024
2026
  instructions_bytes: 4e3,
2025
2027
  /** Sum of the three above — the per-session fixed cost Knit imposes.
2026
- * v0.12 typical: ~14KB (CLAUDE.md ~2KB + tools ~10.6KB + instructions ~2KB);
2027
- * 20KB target covers the union with slack as more tools come online. */
2028
- per_session_overhead_bytes: 2e4
2028
+ * v0.12 typical: ~15KB (CLAUDE.md ~2KB + tools ~11.2KB + instructions
2029
+ * ~2.6KB); 22KB target covers the union with slack as more tools
2030
+ * come online. Bumped from 20000 alongside tool_registry. */
2031
+ per_session_overhead_bytes: 22e3
2029
2032
  };
2030
2033
  function verdict(actual, target) {
2031
2034
  if (actual <= target) return "healthy";
@@ -3560,6 +3563,57 @@ function parseLoadSessionInclude(raw) {
3560
3563
  raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
3561
3564
  );
3562
3565
  }
3566
+ function computeBudgetHealth(brain) {
3567
+ let claudeMdBytes = 0;
3568
+ try {
3569
+ claudeMdBytes = statSync3(join3(brain.rootPath, "CLAUDE.md")).size;
3570
+ } catch {
3571
+ }
3572
+ const shape = detectProjectShape(brain);
3573
+ const listing = computeFeatureListing(shape);
3574
+ const toolRegistryBytes = listing.totals.active * 280;
3575
+ const instructionsBytes = KNIT_INSTRUCTIONS.length;
3576
+ const perSessionOverheadBytes = claudeMdBytes + toolRegistryBytes + instructionsBytes;
3577
+ const cv = verdict(claudeMdBytes, TOKEN_BUDGETS.claude_md_bytes);
3578
+ const tv = verdict(toolRegistryBytes, TOKEN_BUDGETS.tool_registry_bytes);
3579
+ const iv = verdict(instructionsBytes, TOKEN_BUDGETS.instructions_bytes);
3580
+ const ov = verdict(perSessionOverheadBytes, TOKEN_BUDGETS.per_session_overhead_bytes);
3581
+ const verdicts = [cv, tv, iv, ov];
3582
+ const overall = verdicts.includes("over-budget") ? "over-budget" : verdicts.includes("warn") ? "warn" : "healthy";
3583
+ if (overall === "healthy") return void 0;
3584
+ const rank = (v) => v === "over-budget" ? 2 : v === "warn" ? 1 : 0;
3585
+ const surfaces = [
3586
+ ["claude_md", cv],
3587
+ ["tool_registry", tv],
3588
+ ["instructions", iv]
3589
+ ];
3590
+ surfaces.sort((a, b) => rank(b[1]) - rank(a[1]));
3591
+ const worst = surfaces[0][0];
3592
+ const suggestions = {
3593
+ claude_md: "CLAUDE.md is over the 6.5KB target \u2014 run `engram refresh` to splice the lean marker-block, or check that the file is using the generator (not hand-curated).",
3594
+ tool_registry: "Tool registry over budget \u2014 call knit_list_features to see which Tier-2/3 tools are active and disable any you do not need.",
3595
+ instructions: "Instructions block over budget \u2014 likely a v0.x \u2192 v0.y growth. Restart Claude Code to pick up the trimmed instructions."
3596
+ };
3597
+ return {
3598
+ verdict: overall,
3599
+ per_session_kb: Math.round(perSessionOverheadBytes / 1024 * 10) / 10,
3600
+ worst_surface: worst,
3601
+ suggestion: suggestions[worst]
3602
+ };
3603
+ }
3604
+ function computeLearningsHealth(brain) {
3605
+ const total = brain.knowledgeBase.entries.length;
3606
+ if (total < 5) return void 0;
3607
+ const accessed = brain.knowledgeBase.entries.filter((e) => e.accessCount > 0).length;
3608
+ const pct = total > 0 ? Math.round(accessed / total * 100) : 0;
3609
+ if (pct >= 30) return { total, accessed_pct: pct, verdict: "healthy" };
3610
+ return {
3611
+ total,
3612
+ accessed_pct: pct,
3613
+ verdict: "low-utilization",
3614
+ suggestion: `${total} learnings recorded but only ${pct}% have been recalled. Either call knit_search_learnings before re-investigating, or prune stale entries with knit_consolidate_learnings.`
3615
+ };
3616
+ }
3563
3617
  function handleLoadSession(params, brain) {
3564
3618
  const include = parseLoadSessionInclude(params.include);
3565
3619
  const wantAll = include.has("all");
@@ -3641,6 +3695,8 @@ function handleLoadSession(params, brain) {
3641
3695
  changelog: "https://github.com/PDgit12/knit/blob/main/CHANGELOG.md"
3642
3696
  };
3643
3697
  }
3698
+ const budgetHealth = computeBudgetHealth(brain);
3699
+ const learningsHealth = computeLearningsHealth(brain);
3644
3700
  const response = {
3645
3701
  session_context: {
3646
3702
  last_session: lastSession,
@@ -3659,6 +3715,8 @@ function handleLoadSession(params, brain) {
3659
3715
  ...recentSessions !== void 0 ? { recent_sessions: recentSessions } : {}
3660
3716
  },
3661
3717
  ...updateAvailable ? { update_available: updateAvailable } : {},
3718
+ ...budgetHealth ? { budget_health: budgetHealth } : {},
3719
+ ...learningsHealth && learningsHealth.verdict === "low-utilization" ? { learnings_health: learningsHealth } : {},
3662
3720
  instruction: handoff2 ? "UNFINISHED WORK DETECTED. Read the handoff above \u2014 pick up where the last session left off. Do NOT start fresh." : topLearnings.length > 0 ? `Session loaded. ${topLearnings.length} key learnings, ${fps.length} false positives. Call knit_classify_task to begin. Use include=patterns,teams,metrics,recent_sessions,full_learnings,full_knowledge for more.` : "Fresh brain \u2014 no past learnings yet. Call knit_classify_task to begin."
3663
3721
  };
3664
3722
  return JSON.stringify(response);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knit-mcp",
3
- "version": "0.11.4",
3
+ "version": "0.12.0",
4
4
  "description": "Knit — second brain for Claude Code. MCP server giving any AI agent project-scoped memory, tiered workflow protocol, and parallel team worktrees.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,10 @@
14
14
  "lint": "eslint src/ tests/ --ext .ts",
15
15
  "test": "vitest run",
16
16
  "test:watch": "vitest",
17
- "bench": "tsx benchmarks/retrieval-synthetic.ts",
17
+ "bench": "npm run bench:retrieval",
18
+ "bench:retrieval": "tsx benchmarks/retrieval-synthetic.ts",
19
+ "bench:tokens": "tsx benchmarks/token-economy.ts",
20
+ "bench:all": "npm run bench:retrieval && npm run bench:tokens",
18
21
  "prepublishOnly": "npm run typecheck && npm run lint && npm run test && npm run build"
19
22
  },
20
23
  "keywords": [