knit-mcp 0.11.2 → 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 +4 -1
- package/dist/{cache-3LPETDUT.js → cache-46OKHDRR.js} +5 -4
- package/dist/{chunk-RZOVZYTF.js → chunk-GATMQQK5.js} +2 -54
- package/dist/{chunk-TWHNYJAJ.js → chunk-KKLAJLPO.js} +12 -10
- package/dist/{chunk-ORKWLA33.js → chunk-OINYMLOV.js} +10 -2
- package/dist/chunk-POXT5OYN.js +66 -0
- package/dist/{chunk-LV73YTVN.js → chunk-QQNHF4XY.js} +28 -4
- package/dist/{doctor-4DN2P2JR.js → chunk-TE6NP6BZ.js} +47 -4
- package/dist/{chunk-HROSQ5MS.js → chunk-WADRF26Z.js} +34 -1
- package/dist/cli.js +20 -9
- package/dist/doctor-NI22FPXV.js +12 -0
- package/dist/{export-CGSEUYZA.js → export-4BO6HCXP.js} +1 -1
- package/dist/{install-agents-OBDCWCPB.js → install-agents-AH7EBB4J.js} +5 -4
- package/dist/{instructions-JARSXQPO.js → instructions-CF6K57Z2.js} +5 -1
- package/dist/{refresh-SMJ2NGIW.js → refresh-S62AZ3QA.js} +15 -3
- package/dist/{setup-5TUUWLIJ.js → setup-62ZH7GEI.js} +31 -1
- package/dist/{tools-MIROTK2A.js → tools-ZQHRWMVK.js} +182 -63
- package/dist/update-check-GQVDVT2N.js +14 -0
- package/package.json +5 -2
- package/dist/{status-VJDB75X2.js → status-2SEITNIE.js} +3 -3
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<a href="https://github.com/PDgit12/knit/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/PDgit12/knit/ci.yml?style=for-the-badge&label=CI&color=10b981" alt="CI" /></a>
|
|
4
4
|
<img src="https://img.shields.io/badge/license-MIT-3b82f6?style=for-the-badge" alt="license" />
|
|
5
5
|
<img src="https://img.shields.io/badge/node-%E2%89%A518-339933?style=for-the-badge&logo=node.js&logoColor=white" alt="node" />
|
|
6
|
-
<img src="https://img.shields.io/badge/tests-
|
|
6
|
+
<img src="https://img.shields.io/badge/tests-665%20passing-22c55e?style=for-the-badge" alt="tests" />
|
|
7
7
|
<img src="https://img.shields.io/badge/MCP%20tools-53-7c3aed?style=for-the-badge" alt="tools" />
|
|
8
8
|
</p>
|
|
9
9
|
|
|
@@ -452,6 +452,9 @@ 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. |
|
|
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. |
|
|
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. |
|
|
455
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. |
|
|
456
459
|
| **v0.11.1** | Audit-driven hardening · 3 CRITICAL (source_id path traversal, post-edit tsc shell injection, live calibration bug) + 10 HIGH fixes from a 5-agent audit, implemented in 3 parallel `knit_spawn_team_worktree` teams. HOOKS_VERSION 11 (auto-upgrades existing users). New `knit_delete_requirements` tool. Honest comparison vs mem0/Letta added. 53 tools, 636 tests. |
|
|
457
460
|
| **v0.11.0** | Verify Layer + auto-config foundation · mandatory `knit_verify_claim` REVIEW gate · post-edit diff verify + universal `tsc` check · drift detector · self-healing classifier (per-project calibration) · `knit_index_requirements` + `knit_generate_test_cases` (BM25 over long specs) · `knit_get_fingerprint` + `knit_infer_domains` + `knit_compose_template` (zero-config CLAUDE.md). 52 tools, 625 tests. |
|
|
@@ -2,15 +2,16 @@ import {
|
|
|
2
2
|
detectProjectRoot,
|
|
3
3
|
getBrain,
|
|
4
4
|
refreshBrain
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-KKLAJLPO.js";
|
|
6
|
+
import "./chunk-GATMQQK5.js";
|
|
7
|
+
import "./chunk-WADRF26Z.js";
|
|
8
|
+
import "./chunk-WKQHCLLO.js";
|
|
8
9
|
import "./chunk-MOOVNMIN.js";
|
|
9
10
|
import "./chunk-ST4X7LZT.js";
|
|
10
11
|
import "./chunk-M3YZOJNW.js";
|
|
12
|
+
import "./chunk-POXT5OYN.js";
|
|
11
13
|
import "./chunk-VB2TIR6L.js";
|
|
12
14
|
import "./chunk-7UFS67HP.js";
|
|
13
|
-
import "./chunk-WKQHCLLO.js";
|
|
14
15
|
import "./chunk-27TA2ZQZ.js";
|
|
15
16
|
export {
|
|
16
17
|
detectProjectRoot,
|
|
@@ -62,7 +62,7 @@ async function fetchAgent(name, opts = {}) {
|
|
|
62
62
|
throw new AgentFetchError(`Unknown agent: "${name}". Not in engram's registry.`);
|
|
63
63
|
}
|
|
64
64
|
const cachePath = agentsCacheFile(ref, cat, bare);
|
|
65
|
-
if (existsSync(cachePath)) {
|
|
65
|
+
if (!opts.refresh && existsSync(cachePath)) {
|
|
66
66
|
return readFileSync(cachePath, "utf-8");
|
|
67
67
|
}
|
|
68
68
|
if (process.env.KNIT_OFFLINE === "1" || process.env.ENGRAM_OFFLINE === "1") {
|
|
@@ -250,7 +250,7 @@ async function installAgentsForProject(rootPath, config, knowledge, knowledgeBas
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
try {
|
|
253
|
-
const baseMd = await fetchAgent(name, opts.refresh ? { ref: void 0 } : {});
|
|
253
|
+
const baseMd = await fetchAgent(name, opts.refresh ? { ref: void 0, refresh: true } : {});
|
|
254
254
|
const relevant = knowledgeBase ? selectRelevantLearnings(knowledgeBase.entries, name) : [];
|
|
255
255
|
const personalized = personalizeAgent(baseMd, {
|
|
256
256
|
config,
|
|
@@ -285,55 +285,6 @@ function agentsNeededByProject(config) {
|
|
|
285
285
|
return Array.from(names);
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
// src/mcp/update-check.ts
|
|
289
|
-
var REGISTRY_DIST_TAGS_URL = "https://registry.npmjs.org/-/package/knit-mcp/dist-tags";
|
|
290
|
-
var FETCH_TIMEOUT_MS = 2e3;
|
|
291
|
-
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
292
|
-
var cachedLatest = null;
|
|
293
|
-
var lastCheckedAt = 0;
|
|
294
|
-
var inFlight = null;
|
|
295
|
-
function getCachedLatestVersion() {
|
|
296
|
-
if (Date.now() - lastCheckedAt > CACHE_TTL_MS) {
|
|
297
|
-
prewarmLatestVersion();
|
|
298
|
-
}
|
|
299
|
-
return cachedLatest;
|
|
300
|
-
}
|
|
301
|
-
function prewarmLatestVersion() {
|
|
302
|
-
if (inFlight) return;
|
|
303
|
-
if (Date.now() - lastCheckedAt < CACHE_TTL_MS && cachedLatest !== null) return;
|
|
304
|
-
inFlight = doFetch().finally(() => {
|
|
305
|
-
inFlight = null;
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
async function doFetch() {
|
|
309
|
-
const controller = new AbortController();
|
|
310
|
-
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
311
|
-
try {
|
|
312
|
-
const res = await fetch(REGISTRY_DIST_TAGS_URL, { signal: controller.signal });
|
|
313
|
-
if (!res.ok) return;
|
|
314
|
-
const data = await res.json();
|
|
315
|
-
if (typeof data.latest === "string" && data.latest.length > 0) {
|
|
316
|
-
cachedLatest = data.latest;
|
|
317
|
-
lastCheckedAt = Date.now();
|
|
318
|
-
}
|
|
319
|
-
} catch {
|
|
320
|
-
} finally {
|
|
321
|
-
clearTimeout(timeout);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
function isNewerVersion(latest, current) {
|
|
325
|
-
const parse = (v) => {
|
|
326
|
-
const stripped = v.replace(/[-+].*$/, "");
|
|
327
|
-
const parts = stripped.split(".").map((n) => parseInt(n, 10) || 0);
|
|
328
|
-
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
329
|
-
};
|
|
330
|
-
const [a1, a2, a3] = parse(latest);
|
|
331
|
-
const [b1, b2, b3] = parse(current);
|
|
332
|
-
if (a1 !== b1) return a1 > b1;
|
|
333
|
-
if (a2 !== b2) return a2 > b2;
|
|
334
|
-
return a3 > b3;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
288
|
// src/engine/sessions.ts
|
|
338
289
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, appendFileSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
339
290
|
import { dirname as dirname2 } from "path";
|
|
@@ -466,9 +417,6 @@ function parseLine(line) {
|
|
|
466
417
|
|
|
467
418
|
export {
|
|
468
419
|
installAgentsForProject,
|
|
469
|
-
getCachedLatestVersion,
|
|
470
|
-
prewarmLatestVersion,
|
|
471
|
-
isNewerVersion,
|
|
472
420
|
appendSession,
|
|
473
421
|
searchSessions,
|
|
474
422
|
getRecentSessions,
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
installAgentsForProject,
|
|
3
|
+
pruneSessionsByAge
|
|
4
|
+
} from "./chunk-GATMQQK5.js";
|
|
1
5
|
import {
|
|
2
6
|
HOOKS_VERSION,
|
|
3
7
|
generateSettings
|
|
4
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-WADRF26Z.js";
|
|
5
9
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
importFromMarkdown,
|
|
11
|
+
loadKnowledgeBaseSafe,
|
|
12
|
+
saveKnowledgeBase
|
|
13
|
+
} from "./chunk-WKQHCLLO.js";
|
|
10
14
|
import {
|
|
11
15
|
buildKnowledge,
|
|
12
16
|
buildReverseDependencies
|
|
@@ -17,6 +21,9 @@ import {
|
|
|
17
21
|
import {
|
|
18
22
|
readLearnings
|
|
19
23
|
} from "./chunk-M3YZOJNW.js";
|
|
24
|
+
import {
|
|
25
|
+
prewarmLatestVersion
|
|
26
|
+
} from "./chunk-POXT5OYN.js";
|
|
20
27
|
import {
|
|
21
28
|
persistScanResult,
|
|
22
29
|
scanIntegrations
|
|
@@ -26,11 +33,6 @@ import {
|
|
|
26
33
|
generateClaudeMd,
|
|
27
34
|
spliceKnitBlock
|
|
28
35
|
} from "./chunk-7UFS67HP.js";
|
|
29
|
-
import {
|
|
30
|
-
importFromMarkdown,
|
|
31
|
-
loadKnowledgeBaseSafe,
|
|
32
|
-
saveKnowledgeBase
|
|
33
|
-
} from "./chunk-WKQHCLLO.js";
|
|
34
36
|
import {
|
|
35
37
|
knowledgePath,
|
|
36
38
|
knowledgebasePath,
|
|
@@ -9,8 +9,16 @@ import { existsSync, mkdirSync, appendFileSync, readFileSync, statSync } from "f
|
|
|
9
9
|
import { dirname, basename } from "path";
|
|
10
10
|
function appendGlobalLearning(entry) {
|
|
11
11
|
const path = globalLearningsPath();
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
try {
|
|
13
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
14
|
+
appendFileSync(path, JSON.stringify(entry) + "\n", "utf-8");
|
|
15
|
+
} catch (err) {
|
|
16
|
+
process.stderr.write(
|
|
17
|
+
`[knit] global learning append failed at ${path}: ${err.message}
|
|
18
|
+
`
|
|
19
|
+
);
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
14
22
|
}
|
|
15
23
|
function searchGlobalLearnings(query, limit = 10) {
|
|
16
24
|
const lines = readAllLines();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/mcp/update-check.ts
|
|
2
|
+
var REGISTRY_DIST_TAGS_URL = "https://registry.npmjs.org/-/package/knit-mcp/dist-tags";
|
|
3
|
+
var FETCH_TIMEOUT_MS = 2e3;
|
|
4
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
5
|
+
var cachedLatest = null;
|
|
6
|
+
var lastCheckedAt = 0;
|
|
7
|
+
var inFlight = null;
|
|
8
|
+
function getCachedLatestVersion() {
|
|
9
|
+
if (Date.now() - lastCheckedAt > CACHE_TTL_MS) {
|
|
10
|
+
prewarmLatestVersion();
|
|
11
|
+
}
|
|
12
|
+
return cachedLatest;
|
|
13
|
+
}
|
|
14
|
+
function prewarmLatestVersion() {
|
|
15
|
+
if (inFlight) return;
|
|
16
|
+
if (Date.now() - lastCheckedAt < CACHE_TTL_MS && cachedLatest !== null) return;
|
|
17
|
+
inFlight = doFetch().finally(() => {
|
|
18
|
+
inFlight = null;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async function doFetch() {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(REGISTRY_DIST_TAGS_URL, { signal: controller.signal });
|
|
26
|
+
if (!res.ok) return;
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
if (typeof data.latest === "string" && data.latest.length > 0) {
|
|
29
|
+
cachedLatest = data.latest;
|
|
30
|
+
lastCheckedAt = Date.now();
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
} finally {
|
|
34
|
+
clearTimeout(timeout);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function isNewerVersion(latest, current) {
|
|
38
|
+
const parse = (v) => {
|
|
39
|
+
const stripped = v.replace(/[-+].*$/, "");
|
|
40
|
+
const parts = stripped.split(".").map((n) => parseInt(n, 10) || 0);
|
|
41
|
+
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
42
|
+
};
|
|
43
|
+
const [a1, a2, a3] = parse(latest);
|
|
44
|
+
const [b1, b2, b3] = parse(current);
|
|
45
|
+
if (a1 !== b1) return a1 > b1;
|
|
46
|
+
if (a2 !== b2) return a2 > b2;
|
|
47
|
+
return a3 > b3;
|
|
48
|
+
}
|
|
49
|
+
function __setCachedLatestForTests(version, checkedAtMs = Date.now()) {
|
|
50
|
+
cachedLatest = version;
|
|
51
|
+
lastCheckedAt = checkedAtMs;
|
|
52
|
+
inFlight = null;
|
|
53
|
+
}
|
|
54
|
+
function __resetUpdateCheckForTests() {
|
|
55
|
+
cachedLatest = null;
|
|
56
|
+
lastCheckedAt = 0;
|
|
57
|
+
inFlight = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
getCachedLatestVersion,
|
|
62
|
+
prewarmLatestVersion,
|
|
63
|
+
isNewerVersion,
|
|
64
|
+
__setCachedLatestForTests,
|
|
65
|
+
__resetUpdateCheckForTests
|
|
66
|
+
};
|
|
@@ -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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
6
|
-
} from "./chunk-
|
|
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
|
-
|
|
178
|
-
|
|
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 =
|
|
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-
|
|
23
|
-
const { statusCommand } = await import("./status-
|
|
24
|
-
const { refreshCommand } = await import("./refresh-
|
|
25
|
-
const { installAgentsCommand } = await import("./install-agents-
|
|
26
|
-
const { exportCommand } = await import("./export-
|
|
27
|
-
const { doctorCommand } = await import("./doctor-
|
|
22
|
+
const { setupCommand } = await import("./setup-62ZH7GEI.js");
|
|
23
|
+
const { statusCommand } = await import("./status-2SEITNIE.js");
|
|
24
|
+
const { refreshCommand } = await import("./refresh-S62AZ3QA.js");
|
|
25
|
+
const { installAgentsCommand } = await import("./install-agents-AH7EBB4J.js");
|
|
26
|
+
const { exportCommand } = await import("./export-4BO6HCXP.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,13 +98,24 @@ 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-
|
|
102
|
-
const { getActiveToolDefinitionsForBrain, handleToolCall } = await import("./tools-
|
|
103
|
-
const { buildInstructions } = await import("./instructions-
|
|
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
|
+
const { prewarmLatestVersion, getCachedLatestVersion, isNewerVersion } = await import("./update-check-GQVDVT2N.js");
|
|
106
107
|
const ROOT_PATH = detectProjectRoot();
|
|
107
108
|
const PER_PROJECT_INSTRUCTIONS = buildInstructions(loadScanResult(ROOT_PATH));
|
|
109
|
+
prewarmLatestVersion();
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
const latest = getCachedLatestVersion();
|
|
112
|
+
if (latest && isNewerVersion(latest, VERSION)) {
|
|
113
|
+
process.stderr.write(
|
|
114
|
+
`[knit] update available: v${VERSION} installed, v${latest} on npm \u2014 restart Claude Code to upgrade (clear npx cache if needed: \`rm -rf ~/.npm/_npx/\`). Changelog: https://github.com/PDgit12/knit/blob/main/CHANGELOG.md
|
|
115
|
+
`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}, 250);
|
|
108
119
|
const server = new Server(
|
|
109
120
|
{ name: "knit-brain", version: VERSION },
|
|
110
121
|
{
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getBrain
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-HROSQ5MS.js";
|
|
3
|
+
} from "./chunk-KKLAJLPO.js";
|
|
5
4
|
import {
|
|
6
5
|
installAgentsForProject
|
|
7
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GATMQQK5.js";
|
|
7
|
+
import "./chunk-WADRF26Z.js";
|
|
8
|
+
import "./chunk-WKQHCLLO.js";
|
|
8
9
|
import "./chunk-MOOVNMIN.js";
|
|
9
10
|
import "./chunk-ST4X7LZT.js";
|
|
10
11
|
import "./chunk-M3YZOJNW.js";
|
|
12
|
+
import "./chunk-POXT5OYN.js";
|
|
11
13
|
import "./chunk-VB2TIR6L.js";
|
|
12
14
|
import "./chunk-7UFS67HP.js";
|
|
13
|
-
import "./chunk-WKQHCLLO.js";
|
|
14
15
|
import "./chunk-27TA2ZQZ.js";
|
|
15
16
|
|
|
16
17
|
// src/commands/install-agents.ts
|
|
@@ -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-
|
|
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
|
};
|
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
findFalsePositives
|
|
9
9
|
} from "./chunk-M3YZOJNW.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
KNIT_MARKER_START,
|
|
12
|
+
generateClaudeMd,
|
|
13
|
+
spliceKnitBlock
|
|
12
14
|
} from "./chunk-7UFS67HP.js";
|
|
13
15
|
import {
|
|
14
16
|
knowledgePath,
|
|
@@ -57,9 +59,19 @@ async function refreshCommand(targetDir) {
|
|
|
57
59
|
tokenOptimization: "standard"
|
|
58
60
|
};
|
|
59
61
|
const genSpinner = ora({ text: chalk.dim("Regenerating CLAUDE.md..."), spinner: "dots" }).start();
|
|
60
|
-
|
|
62
|
+
const claudeMdPath = join(rootPath, "CLAUDE.md");
|
|
63
|
+
const newBlock = generateClaudeMd(config, knowledge, falsePositives);
|
|
64
|
+
const existing = existsSync(claudeMdPath) ? readFileSync(claudeMdPath, "utf-8") : "";
|
|
65
|
+
if (existing && existing.includes(KNIT_MARKER_START)) {
|
|
66
|
+
const { content } = spliceKnitBlock(existing, newBlock);
|
|
67
|
+
writeFileSync(claudeMdPath, content, "utf-8");
|
|
68
|
+
} else if (!existing) {
|
|
69
|
+
writeFileSync(claudeMdPath, newBlock, "utf-8");
|
|
70
|
+
} else {
|
|
71
|
+
genSpinner.warn(chalk.yellow("CLAUDE.md is user-curated (no Knit markers). Skipping write to avoid clobber."));
|
|
72
|
+
}
|
|
61
73
|
writeFileSync(knowledgePath(rootPath), JSON.stringify(knowledge, null, 2), "utf-8");
|
|
62
|
-
genSpinner.succeed(chalk.dim("CLAUDE.md + knowledge.json updated"));
|
|
74
|
+
if (genSpinner.isSpinning) genSpinner.succeed(chalk.dim("CLAUDE.md + knowledge.json updated"));
|
|
63
75
|
console.log();
|
|
64
76
|
console.log(chalk.bold(" Refresh complete"));
|
|
65
77
|
if (falsePositives.length > 0) {
|
|
@@ -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
|
|
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,40 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
notifyToolsListChanged
|
|
3
|
-
} from "./chunk-WMESQUZU.js";
|
|
4
|
-
import {
|
|
5
|
-
KNIT_INSTRUCTIONS
|
|
6
|
-
} from "./chunk-LV73YTVN.js";
|
|
7
|
-
import {
|
|
8
|
-
VERSION
|
|
9
|
-
} from "./chunk-UTVFELXS.js";
|
|
10
1
|
import {
|
|
11
2
|
appendSession,
|
|
12
|
-
getCachedLatestVersion,
|
|
13
3
|
getRecentSessions,
|
|
14
4
|
installAgentsForProject,
|
|
15
|
-
isNewerVersion,
|
|
16
5
|
loadAllSessions,
|
|
17
6
|
pruneSessionsByAge,
|
|
18
7
|
searchSessions,
|
|
19
8
|
sessionCount
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import {
|
|
22
|
-
scanProject,
|
|
23
|
-
scanProjectFingerprint
|
|
24
|
-
} from "./chunk-ST4X7LZT.js";
|
|
25
|
-
import {
|
|
26
|
-
loadScanResult,
|
|
27
|
-
persistScanResult,
|
|
28
|
-
scanIntegrations
|
|
29
|
-
} from "./chunk-VB2TIR6L.js";
|
|
30
|
-
import "./chunk-7UFS67HP.js";
|
|
9
|
+
} from "./chunk-GATMQQK5.js";
|
|
31
10
|
import {
|
|
32
11
|
appendGlobalLearning,
|
|
33
12
|
buildGlobalLearning,
|
|
34
13
|
getRecentGlobalLearnings,
|
|
35
14
|
loadAllGlobalLearnings,
|
|
36
15
|
searchGlobalLearnings
|
|
37
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-OINYMLOV.js";
|
|
17
|
+
import {
|
|
18
|
+
notifyToolsListChanged
|
|
19
|
+
} from "./chunk-WMESQUZU.js";
|
|
20
|
+
import {
|
|
21
|
+
VERSION
|
|
22
|
+
} from "./chunk-UTVFELXS.js";
|
|
23
|
+
import {
|
|
24
|
+
KNIT_INSTRUCTIONS
|
|
25
|
+
} from "./chunk-QQNHF4XY.js";
|
|
38
26
|
import {
|
|
39
27
|
addEntry,
|
|
40
28
|
bumpClassificationTier,
|
|
@@ -45,6 +33,20 @@ import {
|
|
|
45
33
|
recordCacheHit,
|
|
46
34
|
saveKnowledgeBase
|
|
47
35
|
} from "./chunk-WKQHCLLO.js";
|
|
36
|
+
import {
|
|
37
|
+
scanProject,
|
|
38
|
+
scanProjectFingerprint
|
|
39
|
+
} from "./chunk-ST4X7LZT.js";
|
|
40
|
+
import {
|
|
41
|
+
getCachedLatestVersion,
|
|
42
|
+
isNewerVersion
|
|
43
|
+
} from "./chunk-POXT5OYN.js";
|
|
44
|
+
import {
|
|
45
|
+
loadScanResult,
|
|
46
|
+
persistScanResult,
|
|
47
|
+
scanIntegrations
|
|
48
|
+
} from "./chunk-VB2TIR6L.js";
|
|
49
|
+
import "./chunk-7UFS67HP.js";
|
|
48
50
|
import {
|
|
49
51
|
calibrationPath,
|
|
50
52
|
canonicalRepoRoot,
|
|
@@ -395,7 +397,48 @@ function tools(_) {
|
|
|
395
397
|
| \`knit_define_team\` | Create a custom team. |
|
|
396
398
|
| \`knit_start_team_review\` | Start a parallel review board. |
|
|
397
399
|
| \`knit_post_team_findings\` | Each team posts to the shared board. |
|
|
398
|
-
| \`knit_get_board_summary\` | Cross-team findings, severity-gated.
|
|
400
|
+
| \`knit_get_board_summary\` | Cross-team findings, severity-gated. |
|
|
401
|
+
| \`knit_spawn_team_worktree\` | Spawn a git worktree for a team to do isolated parallel writes. |
|
|
402
|
+
| \`knit_list_team_worktrees\` | List active team worktrees. |
|
|
403
|
+
| \`knit_finalize_team_worktree\` | Merge or discard a team worktree branch. |
|
|
404
|
+
|
|
405
|
+
**Cross-project memory** (v0.4+):
|
|
406
|
+
| Tool | Use when |
|
|
407
|
+
|------|----------|
|
|
408
|
+
| \`knit_record_global_learning\` | Cross-project insight (applies beyond current repo). |
|
|
409
|
+
| \`knit_search_global_learnings\` | Search learnings across all your projects. |
|
|
410
|
+
| \`knit_reflect\` | Surface candidate patterns from session history. |
|
|
411
|
+
| \`knit_get_suggestions\` | Adaptive warnings derived from past patterns for given domains. |
|
|
412
|
+
| \`knit_install_agent\` | Fetch a specialized VoltAgent on demand. |
|
|
413
|
+
| \`knit_prune_sessions\` | Garbage-collect old session log entries. |
|
|
414
|
+
| \`knit_consolidate_learnings\` | Promote stable patterns to consolidated tier. |
|
|
415
|
+
| \`knit_get_learning\` | Fetch a single learning by id. |
|
|
416
|
+
|
|
417
|
+
**Feature gating + protocol guard** (v0.5+):
|
|
418
|
+
| Tool | Use when |
|
|
419
|
+
|------|----------|
|
|
420
|
+
| \`knit_list_features\` | See which tools are hidden behind tier gates and how to enable them. |
|
|
421
|
+
| \`knit_enable_feature\` / \`knit_disable_feature\` | Toggle a feature on/off for this project. |
|
|
422
|
+
| \`knit_set_protocol_strictness\` / \`knit_get_protocol_strictness\` | off / warn / block \u2014 controls Protocol Guard hook. |
|
|
423
|
+
| \`knit_scan_integrations\` | Detect external integrations the project uses. |
|
|
424
|
+
| \`knit_compounding_metrics\` | Per-session token + hit-rate metrics. |
|
|
425
|
+
| \`knit_get_metrics_history\` | History of compounding metrics for trend lines. |
|
|
426
|
+
| \`knit_verify_claim\` | Cheap fact-check against the knowledge graph before quoting code. |
|
|
427
|
+
|
|
428
|
+
**Self-healing classifier** (v0.11):
|
|
429
|
+
| Tool | Use when |
|
|
430
|
+
|------|----------|
|
|
431
|
+
| \`knit_get_calibration\` / \`knit_reset_calibration\` | Inspect or reset per-project classifier tuning. |
|
|
432
|
+
| \`knit_get_fingerprint\` | Project fingerprint used for shape-aware tier gating. |
|
|
433
|
+
| \`knit_infer_domains\` | Auto-derive domain tags from files. |
|
|
434
|
+
| \`knit_compose_template\` | Compose a generated artifact from a template. |
|
|
435
|
+
|
|
436
|
+
**Requirements ingestion** (v0.11):
|
|
437
|
+
| Tool | Use when |
|
|
438
|
+
|------|----------|
|
|
439
|
+
| \`knit_index_requirements\` | Chunk + BM25-index a long spec for retrieval. |
|
|
440
|
+
| \`knit_generate_test_cases\` | Retrieve relevant chunks for a feature query (token-bounded). |
|
|
441
|
+
| \`knit_list_requirements\` / \`knit_delete_requirements\` | Manage indexed sources. |`;
|
|
399
442
|
}
|
|
400
443
|
|
|
401
444
|
// src/engine/worktrees.ts
|
|
@@ -639,7 +682,6 @@ function classifyOrigin(supporting) {
|
|
|
639
682
|
else global = true;
|
|
640
683
|
if (local && global) return "mixed";
|
|
641
684
|
}
|
|
642
|
-
if (local && global) return "mixed";
|
|
643
685
|
return local ? "local" : "global";
|
|
644
686
|
}
|
|
645
687
|
function getAdaptiveSuggestions(kb, taskDomains) {
|
|
@@ -1487,6 +1529,9 @@ function chunkRequirements(content, minChars = 50) {
|
|
|
1487
1529
|
return chunks;
|
|
1488
1530
|
}
|
|
1489
1531
|
function saveSource(rootPath, source) {
|
|
1532
|
+
if (typeof source.sourceId !== "string" || !/^[A-Za-z0-9._-]{1,80}$/.test(source.sourceId)) {
|
|
1533
|
+
throw new Error(`[knit] saveSource: invalid sourceId "${source.sourceId}"`);
|
|
1534
|
+
}
|
|
1490
1535
|
const path = requirementSourcePath(rootPath, source.sourceId);
|
|
1491
1536
|
mkdirSync5(dirname5(path), { recursive: true });
|
|
1492
1537
|
const tmp = `${path}.tmp`;
|
|
@@ -1494,6 +1539,7 @@ function saveSource(rootPath, source) {
|
|
|
1494
1539
|
renameSync3(tmp, path);
|
|
1495
1540
|
}
|
|
1496
1541
|
function loadSource(rootPath, sourceId) {
|
|
1542
|
+
if (typeof sourceId !== "string" || !/^[A-Za-z0-9._-]{1,80}$/.test(sourceId)) return null;
|
|
1497
1543
|
const path = requirementSourcePath(rootPath, sourceId);
|
|
1498
1544
|
if (!existsSync5(path)) return null;
|
|
1499
1545
|
try {
|
|
@@ -1883,8 +1929,8 @@ function handleSearchLearnings(params, brain) {
|
|
|
1883
1929
|
const limit = Math.max(1, Math.min(50, parseInt(params.limit || "10", 10) || 10));
|
|
1884
1930
|
if (!query && domains.length === 0) {
|
|
1885
1931
|
return JSON.stringify({
|
|
1932
|
+
status: "error",
|
|
1886
1933
|
error: "Provide either query (BM25 free-text) or domains (tag filter), or both. query=auth domains=#api filters BM25 results to entries tagged #api.",
|
|
1887
|
-
query: [],
|
|
1888
1934
|
results: [],
|
|
1889
1935
|
count: 0
|
|
1890
1936
|
});
|
|
@@ -1966,20 +2012,23 @@ var TOKEN_BUDGETS = {
|
|
|
1966
2012
|
/** Generated CLAUDE.md block. v0.7 trim landed at ~2KB on typical projects;
|
|
1967
2013
|
* 6.5KB target allows for projects with many domains / large project map. */
|
|
1968
2014
|
claude_md_bytes: 6500,
|
|
1969
|
-
/** Tier-gated tools/list response. v0.12 typical:
|
|
1970
|
-
* bytes ≈
|
|
1971
|
-
*
|
|
1972
|
-
*
|
|
1973
|
-
tool_registry_bytes:
|
|
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,
|
|
1974
2020
|
/** MCP server `instructions` field — sent at handshake. v0.11.1 surfaces
|
|
1975
2021
|
* 9 new tools (verify_claim, calibration, requirements ingestion,
|
|
1976
|
-
* fingerprint, infer_domains, compose_template) → ~3.5KB.
|
|
1977
|
-
*
|
|
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. */
|
|
1978
2026
|
instructions_bytes: 4e3,
|
|
1979
2027
|
/** Sum of the three above — the per-session fixed cost Knit imposes.
|
|
1980
|
-
* v0.12 typical: ~
|
|
1981
|
-
*
|
|
1982
|
-
|
|
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
|
|
1983
2032
|
};
|
|
1984
2033
|
function verdict(actual, target) {
|
|
1985
2034
|
if (actual <= target) return "healthy";
|
|
@@ -2889,9 +2938,7 @@ function handleGetLearning(params, brain) {
|
|
|
2889
2938
|
if (!id) return errorResponse("id parameter is required");
|
|
2890
2939
|
const entry = brain.knowledgeBase.entries.find((e) => e.id === id);
|
|
2891
2940
|
if (!entry) {
|
|
2892
|
-
return
|
|
2893
|
-
error: `No learning with id="${id}". List active ones via knit_search_learnings (default returns id + summary).`
|
|
2894
|
-
});
|
|
2941
|
+
return errorResponse(`No learning with id="${id}". List active ones via knit_search_learnings (default returns id + summary).`);
|
|
2895
2942
|
}
|
|
2896
2943
|
recordCacheHit(brain.knowledgeBase);
|
|
2897
2944
|
return JSON.stringify({
|
|
@@ -2914,11 +2961,11 @@ function handleRecordLearning(params, brain) {
|
|
|
2914
2961
|
const entry = {
|
|
2915
2962
|
date,
|
|
2916
2963
|
summary: redactSecrets(params.summary || "Untitled learning"),
|
|
2917
|
-
domains: (params.domains || "general").split(",").map((d) => d.trim()),
|
|
2964
|
+
domains: (params.domains || "general").split(",").map((d) => redactSecrets(d.trim())),
|
|
2918
2965
|
approach: redactSecrets(params.approach || ""),
|
|
2919
2966
|
outcome: ["success", "partial", "failure"].includes(params.outcome) ? params.outcome : "success",
|
|
2920
2967
|
lesson: redactSecrets(params.lesson || ""),
|
|
2921
|
-
tags: (params.tags || "").split(/\s+/).filter((t) => t.startsWith("#"))
|
|
2968
|
+
tags: (params.tags || "").split(/\s+/).filter((t) => t.startsWith("#")).map((t) => redactSecrets(t))
|
|
2922
2969
|
};
|
|
2923
2970
|
addEntry(brain.knowledgeBase, entry);
|
|
2924
2971
|
saveKnowledgeBase(knowledgebasePath(brain.rootPath), brain.knowledgeBase);
|
|
@@ -2946,7 +2993,7 @@ function handleRecordLearning(params, brain) {
|
|
|
2946
2993
|
}
|
|
2947
2994
|
function handleRecordFalsePositive(params, brain) {
|
|
2948
2995
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2949
|
-
const tags = [...(params.tags || "").split(/\s+/).filter((t) => t.startsWith("#")), "#false-positive"];
|
|
2996
|
+
const tags = [...(params.tags || "").split(/\s+/).filter((t) => t.startsWith("#")).map((t) => redactSecrets(t)), "#false-positive"];
|
|
2950
2997
|
const entry = {
|
|
2951
2998
|
date,
|
|
2952
2999
|
summary: redactSecrets(params.summary || "Untitled FP"),
|
|
@@ -3085,7 +3132,8 @@ function handleIndexRequirements(params, brain) {
|
|
|
3085
3132
|
return JSON.stringify({ status: "error", error: "Invalid source_id \u2014 must be 1-80 chars, alphanumeric + . _ - only" });
|
|
3086
3133
|
}
|
|
3087
3134
|
const sourceId = userSourceId || slugifySourceId(filePath);
|
|
3088
|
-
const
|
|
3135
|
+
const rawLabel = (params.label || "").trim();
|
|
3136
|
+
const label = rawLabel ? redactSecrets(rawLabel) : void 0;
|
|
3089
3137
|
const source = {
|
|
3090
3138
|
sourceId,
|
|
3091
3139
|
sourcePath: filePath,
|
|
@@ -3142,8 +3190,13 @@ function handleGenerateTestCases(params, brain) {
|
|
|
3142
3190
|
if (s) sourcesToSearch.push(s);
|
|
3143
3191
|
}
|
|
3144
3192
|
}
|
|
3193
|
+
const MAX_RESPONSE_BYTES = 100 * 1024;
|
|
3145
3194
|
const hits = retrieveTopChunks(sourcesToSearch, feature, topN);
|
|
3146
|
-
|
|
3195
|
+
let contextBytes = hits.reduce((s, h) => s + Buffer.byteLength(h.chunk.text, "utf-8"), 0);
|
|
3196
|
+
while (contextBytes > MAX_RESPONSE_BYTES && hits.length > 1) {
|
|
3197
|
+
const dropped = hits.pop();
|
|
3198
|
+
contextBytes -= Buffer.byteLength(dropped.chunk.text, "utf-8");
|
|
3199
|
+
}
|
|
3147
3200
|
const totalBytes = sourcesToSearch.reduce((s, src) => s + src.sourceBytes, 0);
|
|
3148
3201
|
const reductionPct = totalBytes > 0 ? Math.round(100 - contextBytes / totalBytes * 100) : 0;
|
|
3149
3202
|
return JSON.stringify({
|
|
@@ -3510,6 +3563,57 @@ function parseLoadSessionInclude(raw) {
|
|
|
3510
3563
|
raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
|
|
3511
3564
|
);
|
|
3512
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
|
+
}
|
|
3513
3617
|
function handleLoadSession(params, brain) {
|
|
3514
3618
|
const include = parseLoadSessionInclude(params.include);
|
|
3515
3619
|
const wantAll = include.has("all");
|
|
@@ -3581,6 +3685,18 @@ function handleLoadSession(params, brain) {
|
|
|
3581
3685
|
if (wantAll || include.has("patterns")) {
|
|
3582
3686
|
patterns = reflect(brain.knowledgeBase).slice(0, 3).map((p) => ({ type: p.type, description: p.description, confidence: p.confidence }));
|
|
3583
3687
|
}
|
|
3688
|
+
let updateAvailable;
|
|
3689
|
+
const cachedLatest = getCachedLatestVersion();
|
|
3690
|
+
if (cachedLatest && isNewerVersion(cachedLatest, VERSION)) {
|
|
3691
|
+
updateAvailable = {
|
|
3692
|
+
current: VERSION,
|
|
3693
|
+
latest: cachedLatest,
|
|
3694
|
+
upgrade: "Restart Claude Code (quit fully + reopen) to spawn a fresh MCP. If npx serves cache, run: rm -rf ~/.npm/_npx/$(ls ~/.npm/_npx | head -1) then reopen.",
|
|
3695
|
+
changelog: "https://github.com/PDgit12/knit/blob/main/CHANGELOG.md"
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
const budgetHealth = computeBudgetHealth(brain);
|
|
3699
|
+
const learningsHealth = computeLearningsHealth(brain);
|
|
3584
3700
|
const response = {
|
|
3585
3701
|
session_context: {
|
|
3586
3702
|
last_session: lastSession,
|
|
@@ -3598,6 +3714,9 @@ function handleLoadSession(params, brain) {
|
|
|
3598
3714
|
...metrics !== void 0 ? { metrics } : {},
|
|
3599
3715
|
...recentSessions !== void 0 ? { recent_sessions: recentSessions } : {}
|
|
3600
3716
|
},
|
|
3717
|
+
...updateAvailable ? { update_available: updateAvailable } : {},
|
|
3718
|
+
...budgetHealth ? { budget_health: budgetHealth } : {},
|
|
3719
|
+
...learningsHealth && learningsHealth.verdict === "low-utilization" ? { learnings_health: learningsHealth } : {},
|
|
3601
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."
|
|
3602
3721
|
};
|
|
3603
3722
|
return JSON.stringify(response);
|
|
@@ -3611,7 +3730,7 @@ function handleSaveSessionSummary(params, brain) {
|
|
|
3611
3730
|
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
3612
3731
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3613
3732
|
summary: redactSecrets((params.summary || "").slice(0, 500)),
|
|
3614
|
-
tags: (params.tags || "").split(/\s+/).filter((t) => t.startsWith("#")),
|
|
3733
|
+
tags: (params.tags || "").split(/\s+/).filter((t) => t.startsWith("#")).map((t) => redactSecrets(t)),
|
|
3615
3734
|
outcome,
|
|
3616
3735
|
filesTouched: params.files_touched ? params.files_touched.split(",").map((f) => f.trim()).filter(Boolean) : void 0,
|
|
3617
3736
|
domainsTouched: params.domains ? params.domains.split(",").map((d) => d.trim()).filter(Boolean) : void 0
|
|
@@ -3813,32 +3932,32 @@ function getToolDefinitions() {
|
|
|
3813
3932
|
// ── Query (read the brain) ───────────────────────────────────
|
|
3814
3933
|
{
|
|
3815
3934
|
name: "knit_query_imports",
|
|
3816
|
-
description: "Reverse deps
|
|
3935
|
+
description: "[GRAPH] Reverse deps \u2014 WHO IMPORTS this file. Use to find blast radius before editing.",
|
|
3817
3936
|
inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Relative file path." } }, required: ["file_path"] }
|
|
3818
3937
|
},
|
|
3819
3938
|
{
|
|
3820
3939
|
name: "knit_query_dependents",
|
|
3821
|
-
description: "Forward deps
|
|
3940
|
+
description: "[GRAPH] Forward deps \u2014 WHAT THIS FILE IMPORTS. Opposite of knit_query_imports (who imports it).",
|
|
3822
3941
|
inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Relative file path." } }, required: ["file_path"] }
|
|
3823
3942
|
},
|
|
3824
3943
|
{
|
|
3825
3944
|
name: "knit_query_exports",
|
|
3826
|
-
description: "Exports from a file: functions, classes, types, constants.",
|
|
3945
|
+
description: "[GRAPH] Exports from a file: functions, classes, types, constants. Use when verifying a claim or finding the canonical definition.",
|
|
3827
3946
|
inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Relative file path." } }, required: ["file_path"] }
|
|
3828
3947
|
},
|
|
3829
3948
|
{
|
|
3830
3949
|
name: "knit_query_tests",
|
|
3831
|
-
description: 'Tests
|
|
3950
|
+
description: '[GRAPH] Tests covering a file, OR list all untested files with filter="untested". Use before declaring "tested" on changed code.',
|
|
3832
3951
|
inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Relative file path (optional)." }, filter: { type: "string", description: '"untested" to list all untested files.' } } }
|
|
3833
3952
|
},
|
|
3834
3953
|
{
|
|
3835
3954
|
name: "knit_find_fanout",
|
|
3836
|
-
description: "High-fanout files \u2014 imported by many others.",
|
|
3955
|
+
description: "[GRAPH] High-fanout files \u2014 imported by many others. Editing these is high-risk; surfaces in classify_task risk_tier.",
|
|
3837
3956
|
inputSchema: { type: "object", properties: { min_importers: { type: "string", description: "Minimum importers to qualify (default: 3)." } } }
|
|
3838
3957
|
},
|
|
3839
3958
|
{
|
|
3840
3959
|
name: "knit_search_learnings",
|
|
3841
|
-
description:
|
|
3960
|
+
description: "[MEMORY] Search THIS PROJECT's learnings. Use BEFORE re-investigating. Companions: knit_search_global_learnings (cross-project), knit_search_sessions (past tasks). BM25 + graph boost via files=.",
|
|
3842
3961
|
inputSchema: {
|
|
3843
3962
|
type: "object",
|
|
3844
3963
|
properties: {
|
|
@@ -3862,7 +3981,7 @@ function getToolDefinitions() {
|
|
|
3862
3981
|
// ── Update (write to the brain) ──────────────────────────────
|
|
3863
3982
|
{
|
|
3864
3983
|
name: "knit_classify_task",
|
|
3865
|
-
description: "
|
|
3984
|
+
description: "[PROTOCOL] BEFORE any Edit/Write. Returns risk_tier (drives plan mode), scope_tier (drives phases), change_kind, auto_plan_mode. Optional context_budget_remaining (0-100) downgrades gracefully.",
|
|
3866
3985
|
inputSchema: { type: "object", properties: { files_to_touch: { type: "string", description: 'Comma-separated files, or "unknown" for new projects.' }, description: { type: "string", description: "Brief task description." }, verbose: { type: "string", description: '"true" to include reasoning + cross_domain_ripple + files_count (debug fields).' }, context_budget_remaining: { type: "string", description: "Integer 0\u2013100 \u2014 percent of host agent context window remaining. <30 triggers scope downgrade + skips OPTIMIZE phase. Defaults to 100." } }, required: ["files_to_touch"] }
|
|
3867
3986
|
},
|
|
3868
3987
|
{
|
|
@@ -3872,7 +3991,7 @@ function getToolDefinitions() {
|
|
|
3872
3991
|
},
|
|
3873
3992
|
{
|
|
3874
3993
|
name: "knit_record_learning",
|
|
3875
|
-
description: "Record a non-obvious
|
|
3994
|
+
description: "[MEMORY-WRITE] Record a non-obvious THIS-PROJECT insight. Skip substring repeats. For cross-project: knit_record_global_learning. For FPs: knit_record_false_positive.",
|
|
3876
3995
|
inputSchema: { type: "object", properties: { summary: { type: "string", description: "One-line summary." }, domains: { type: "string", description: "Comma-separated domains." }, approach: { type: "string", description: "What approach was taken." }, outcome: { type: "string", description: "success | partial | failure." }, lesson: { type: "string", description: "What to repeat or avoid." }, tags: { type: "string", description: 'Space-separated tags (e.g. "#api #auth").' } }, required: ["summary", "lesson", "tags"] }
|
|
3877
3996
|
},
|
|
3878
3997
|
{
|
|
@@ -3927,7 +4046,7 @@ function getToolDefinitions() {
|
|
|
3927
4046
|
},
|
|
3928
4047
|
{
|
|
3929
4048
|
name: "knit_save_handoff",
|
|
3930
|
-
description: "Save state
|
|
4049
|
+
description: "[END OF SESSION \u2014 UNFINISHED] Save state when work is incomplete or context degraded. failed_attempts is the load-bearing field. For finished work use knit_save_session_summary.",
|
|
3931
4050
|
inputSchema: { type: "object", properties: { goal: { type: "string", description: "What we're trying to accomplish." }, current_state: { type: "string", description: "Where we are now." }, files_in_flight: { type: "string", description: "Files being modified." }, what_changed: { type: "string", description: "Commits and edits." }, failed_attempts: { type: "string", description: "What was tried and why it failed." }, decisions_made: { type: "string", description: "Important choices." }, next_step: { type: "string", description: "ONE most important next thing." } }, required: ["goal", "current_state", "failed_attempts", "next_step"] }
|
|
3932
4051
|
},
|
|
3933
4052
|
{
|
|
@@ -3978,12 +4097,12 @@ function getToolDefinitions() {
|
|
|
3978
4097
|
// ── Session memory ───────────────────────────────────────────
|
|
3979
4098
|
{
|
|
3980
4099
|
name: "knit_load_session",
|
|
3981
|
-
description: "Call at session start. Returns handoff, top learnings,
|
|
4100
|
+
description: "[PROTOCOL FIRST] Call once at session start. Returns handoff, top learnings, FPs, update_available. Opt in via include=patterns,teams,metrics,recent_sessions,full_learnings,all.",
|
|
3982
4101
|
inputSchema: { type: "object", properties: { include: { type: "string", description: "Comma-separated optional sections." } } }
|
|
3983
4102
|
},
|
|
3984
4103
|
{
|
|
3985
4104
|
name: "knit_save_session_summary",
|
|
3986
|
-
description: "
|
|
4105
|
+
description: "[END OF SESSION] Record a session summary so future knit_search_sessions can find this work. Pair with knit_save_handoff when work is unfinished.",
|
|
3987
4106
|
inputSchema: {
|
|
3988
4107
|
type: "object",
|
|
3989
4108
|
properties: {
|
|
@@ -4008,7 +4127,7 @@ function getToolDefinitions() {
|
|
|
4008
4127
|
},
|
|
4009
4128
|
{
|
|
4010
4129
|
name: "knit_search_sessions",
|
|
4011
|
-
description: '
|
|
4130
|
+
description: '[MEMORY] "Have I done this task before?" \u2014 search past SESSION summaries. Different from knit_search_learnings (lessons) and knit_search_global_learnings (cross-project).',
|
|
4012
4131
|
inputSchema: {
|
|
4013
4132
|
type: "object",
|
|
4014
4133
|
properties: {
|
|
@@ -4055,7 +4174,7 @@ function getToolDefinitions() {
|
|
|
4055
4174
|
// ── Cross-project learnings (Model C — global pool) ─────────
|
|
4056
4175
|
{
|
|
4057
4176
|
name: "knit_record_global_learning",
|
|
4058
|
-
description: "
|
|
4177
|
+
description: "[MEMORY-WRITE] Record a learning that generalizes across MULTIPLE projects. Sparingly \u2014 most learnings are project-specific. Companion: knit_record_learning (this project only).",
|
|
4059
4178
|
inputSchema: {
|
|
4060
4179
|
type: "object",
|
|
4061
4180
|
properties: {
|
|
@@ -4069,7 +4188,7 @@ function getToolDefinitions() {
|
|
|
4069
4188
|
},
|
|
4070
4189
|
{
|
|
4071
4190
|
name: "knit_search_global_learnings",
|
|
4072
|
-
description: "Search
|
|
4191
|
+
description: "[MEMORY] Search learnings ACROSS ALL projects on this machine. Use when starting a new domain. Companion: knit_search_learnings (this project only).",
|
|
4073
4192
|
inputSchema: {
|
|
4074
4193
|
type: "object",
|
|
4075
4194
|
properties: {
|
|
@@ -4166,12 +4285,12 @@ function getToolDefinitions() {
|
|
|
4166
4285
|
},
|
|
4167
4286
|
{
|
|
4168
4287
|
name: "knit_verify_claim",
|
|
4169
|
-
description: 'Fact-check one claim
|
|
4288
|
+
description: '[REVIEW] Fact-check one claim before LEARN on standard/complex scope. Patterns: "A imports B", "X exports Y", "A is tested by B", "X exists". Verdict: verified | contradicted | unparseable.',
|
|
4170
4289
|
inputSchema: { type: "object", properties: { claim: { type: "string", description: "One claim about the codebase to verify." } }, required: ["claim"] }
|
|
4171
4290
|
},
|
|
4172
4291
|
{
|
|
4173
4292
|
name: "knit_get_learning",
|
|
4174
|
-
description: "
|
|
4293
|
+
description: "[MEMORY] Expand ONE learning by id (from a prior knit_search_learnings hit). Hierarchical retrieval \u2014 search returns headlines, this fetches details. Saves tokens vs. dumping full bodies.",
|
|
4175
4294
|
inputSchema: { type: "object", properties: { id: { type: "string", description: "Learning id from a prior knit_search_learnings result." } }, required: ["id"] }
|
|
4176
4295
|
},
|
|
4177
4296
|
{
|
|
@@ -4260,7 +4379,7 @@ function handleToolCall(toolName, params, brain) {
|
|
|
4260
4379
|
const allowAbsolute = toolName === "knit_index_requirements";
|
|
4261
4380
|
const bad = decoded.includes("..") || decoded.includes("\0") || !allowAbsolute && (decoded.startsWith("/") || /^[A-Za-z]:\//.test(decoded));
|
|
4262
4381
|
if (bad) {
|
|
4263
|
-
return JSON.stringify({ error: "Invalid file path \u2014 no traversal or absolute paths allowed" });
|
|
4382
|
+
return JSON.stringify({ status: "error", error: "Invalid file path \u2014 no traversal or absolute paths allowed" });
|
|
4264
4383
|
}
|
|
4265
4384
|
params.file_path = decoded;
|
|
4266
4385
|
}
|
|
@@ -4271,7 +4390,7 @@ function handleToolCall(toolName, params, brain) {
|
|
|
4271
4390
|
}
|
|
4272
4391
|
const handler = handlers[toolName];
|
|
4273
4392
|
if (!handler) {
|
|
4274
|
-
return JSON.stringify({ error: `Unknown tool: ${toolName}` });
|
|
4393
|
+
return JSON.stringify({ status: "error", error: `Unknown tool: ${toolName}` });
|
|
4275
4394
|
}
|
|
4276
4395
|
return handler(params, brain);
|
|
4277
4396
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__resetUpdateCheckForTests,
|
|
3
|
+
__setCachedLatestForTests,
|
|
4
|
+
getCachedLatestVersion,
|
|
5
|
+
isNewerVersion,
|
|
6
|
+
prewarmLatestVersion
|
|
7
|
+
} from "./chunk-POXT5OYN.js";
|
|
8
|
+
export {
|
|
9
|
+
__resetUpdateCheckForTests,
|
|
10
|
+
__setCachedLatestForTests,
|
|
11
|
+
getCachedLatestVersion,
|
|
12
|
+
isNewerVersion,
|
|
13
|
+
prewarmLatestVersion
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knit-mcp",
|
|
3
|
-
"version": "0.
|
|
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": "
|
|
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": [
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readLearnings
|
|
3
|
-
} from "./chunk-M3YZOJNW.js";
|
|
4
1
|
import {
|
|
5
2
|
getKBSummary,
|
|
6
3
|
getStaleEntries,
|
|
7
4
|
getTopEntries,
|
|
8
5
|
loadKnowledgeBase
|
|
9
6
|
} from "./chunk-WKQHCLLO.js";
|
|
7
|
+
import {
|
|
8
|
+
readLearnings
|
|
9
|
+
} from "./chunk-M3YZOJNW.js";
|
|
10
10
|
import {
|
|
11
11
|
knowledgePath,
|
|
12
12
|
knowledgebasePath,
|