knit-mcp 0.9.0 → 0.11.2
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 +368 -215
- package/dist/cache-3LPETDUT.js +19 -0
- package/dist/{chunk-XFS2XGZI.js → chunk-27TA2ZQZ.js} +24 -0
- package/dist/{chunk-KLNUEE3O.js → chunk-7UFS67HP.js} +9 -1
- package/dist/{chunk-4K4FHOKE.js → chunk-HROSQ5MS.js} +256 -344
- package/dist/{chunk-5NCSZYRJ.js → chunk-LV73YTVN.js} +5 -0
- package/dist/{chunk-B2KZ5UR7.js → chunk-ORKWLA33.js} +1 -1
- package/dist/{chunk-BU3VHX3W.js → chunk-RZOVZYTF.js} +12 -4
- package/dist/{chunk-7PPC6IG6.js → chunk-ST4X7LZT.js} +60 -2
- package/dist/chunk-TWHNYJAJ.js +328 -0
- package/dist/{chunk-FLNV2IQC.js → chunk-VB2TIR6L.js} +2 -2
- package/dist/{chunk-BAUQEFYY.js → chunk-WKQHCLLO.js} +45 -10
- package/dist/cli.js +18 -9
- package/dist/doctor-4DN2P2JR.js +179 -0
- package/dist/{export-I5Y26WUL.js → export-CGSEUYZA.js} +3 -3
- package/dist/{install-agents-D2KJQUH3.js → install-agents-OBDCWCPB.js} +8 -7
- package/dist/{instructions-4FI32YZU.js → instructions-JARSXQPO.js} +1 -1
- package/dist/{integration-scanner-PS47AHGM.js → integration-scanner-LBD2PIZ3.js} +3 -3
- package/dist/{refresh-BXN32CNA.js → refresh-SMJ2NGIW.js} +3 -3
- package/dist/{status-RQWRIM2Y.js → status-VJDB75X2.js} +2 -2
- package/dist/{tools-EISDGPS5.js → tools-MIROTK2A.js} +1146 -91
- package/package.json +2 -1
- package/dist/cache-JSN6ETUF.js +0 -18
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
isBundledCore,
|
|
6
6
|
knownAgents,
|
|
7
7
|
rawAgentUrl
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ST4X7LZT.js";
|
|
9
9
|
import {
|
|
10
10
|
agentsCacheFile,
|
|
11
11
|
projectAgentFile,
|
|
12
12
|
projectAgentsDir,
|
|
13
13
|
sessionsJsonlPath
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-27TA2ZQZ.js";
|
|
15
15
|
|
|
16
16
|
// src/engine/install-agents.ts
|
|
17
17
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -339,8 +339,16 @@ import { existsSync as existsSync3, mkdirSync as mkdirSync3, appendFileSync, rea
|
|
|
339
339
|
import { dirname as dirname2 } from "path";
|
|
340
340
|
function appendSession(rootPath, entry) {
|
|
341
341
|
const path = sessionsJsonlPath(rootPath);
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
try {
|
|
343
|
+
mkdirSync3(dirname2(path), { recursive: true });
|
|
344
|
+
appendFileSync(path, JSON.stringify(entry) + "\n", "utf-8");
|
|
345
|
+
} catch (err) {
|
|
346
|
+
process.stderr.write(
|
|
347
|
+
`[knit] session append failed at ${path}: ${err.message}
|
|
348
|
+
`
|
|
349
|
+
);
|
|
350
|
+
throw err;
|
|
351
|
+
}
|
|
344
352
|
}
|
|
345
353
|
function searchSessions(rootPath, query, limit = 10) {
|
|
346
354
|
const lines = readAllLines(rootPath);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/engine/scanner.ts
|
|
2
|
-
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
|
|
@@ -89,6 +89,63 @@ function scanProject(rootPath) {
|
|
|
89
89
|
git: detectGit(rootPath)
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
+
function scanProjectFingerprint(rootPath) {
|
|
93
|
+
const stack = detectStack(rootPath);
|
|
94
|
+
const pm = detectPackageManager(rootPath);
|
|
95
|
+
const languages = [];
|
|
96
|
+
if (stack.language && stack.language !== "unknown") languages.push(stack.language);
|
|
97
|
+
if (stack.language !== "python" && existsSync(join(rootPath, "pyproject.toml"))) languages.push("python");
|
|
98
|
+
if (stack.language !== "go" && existsSync(join(rootPath, "go.mod"))) languages.push("go");
|
|
99
|
+
if (stack.language !== "rust" && existsSync(join(rootPath, "Cargo.toml"))) languages.push("rust");
|
|
100
|
+
return {
|
|
101
|
+
languages,
|
|
102
|
+
framework: stack.framework,
|
|
103
|
+
testRunner: stack.testFramework,
|
|
104
|
+
linter: detectLinter(rootPath, stack.language),
|
|
105
|
+
buildCommand: stack.buildCommand,
|
|
106
|
+
lintCommand: stack.lintCommand,
|
|
107
|
+
typecheckCommand: stack.typecheckCommand,
|
|
108
|
+
packageManager: pm === "unknown" ? null : pm,
|
|
109
|
+
ciFiles: detectCiFiles(rootPath),
|
|
110
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function detectLinter(rootPath, language) {
|
|
114
|
+
if (existsSync(join(rootPath, ".eslintrc.json")) || existsSync(join(rootPath, ".eslintrc.js")) || existsSync(join(rootPath, "eslint.config.js")) || existsSync(join(rootPath, "eslint.config.mjs")) || existsSync(join(rootPath, "eslint.config.ts"))) return "eslint";
|
|
115
|
+
if (existsSync(join(rootPath, ".ruff.toml")) || existsSync(join(rootPath, "ruff.toml"))) return "ruff";
|
|
116
|
+
if (existsSync(join(rootPath, ".golangci.yml")) || existsSync(join(rootPath, ".golangci.yaml"))) return "golangci-lint";
|
|
117
|
+
if (existsSync(join(rootPath, "clippy.toml"))) return "clippy";
|
|
118
|
+
if (language === "python" && existsSync(join(rootPath, "pyproject.toml"))) {
|
|
119
|
+
try {
|
|
120
|
+
const py = readFileSync(join(rootPath, "pyproject.toml"), "utf-8");
|
|
121
|
+
if (py.includes("ruff")) return "ruff";
|
|
122
|
+
if (py.includes("flake8")) return "flake8";
|
|
123
|
+
if (py.includes("pylint")) return "pylint";
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (language === "go") return "golangci-lint";
|
|
128
|
+
if (language === "rust") return "clippy";
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function detectCiFiles(rootPath) {
|
|
132
|
+
const out = [];
|
|
133
|
+
const ghDir = join(rootPath, ".github", "workflows");
|
|
134
|
+
if (existsSync(ghDir)) {
|
|
135
|
+
try {
|
|
136
|
+
for (const f of readdirSync(ghDir)) {
|
|
137
|
+
if (f.endsWith(".yml") || f.endsWith(".yaml")) {
|
|
138
|
+
out.push(join(".github/workflows", f));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const f of [".gitlab-ci.yml", ".circleci/config.yml", ".travis.yml", "Jenkinsfile", "azure-pipelines.yml"]) {
|
|
145
|
+
if (existsSync(join(rootPath, f))) out.push(f);
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
92
149
|
function detectPackageManager(root) {
|
|
93
150
|
if (existsSync(join(root, "bun.lockb")) || existsSync(join(root, "bun.lock"))) return "bun";
|
|
94
151
|
if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
@@ -278,5 +335,6 @@ export {
|
|
|
278
335
|
categoryOf,
|
|
279
336
|
rawAgentUrl,
|
|
280
337
|
isBundledCore,
|
|
281
|
-
scanProject
|
|
338
|
+
scanProject,
|
|
339
|
+
scanProjectFingerprint
|
|
282
340
|
};
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOOKS_VERSION,
|
|
3
|
+
generateSettings
|
|
4
|
+
} from "./chunk-HROSQ5MS.js";
|
|
5
|
+
import {
|
|
6
|
+
installAgentsForProject,
|
|
7
|
+
prewarmLatestVersion,
|
|
8
|
+
pruneSessionsByAge
|
|
9
|
+
} from "./chunk-RZOVZYTF.js";
|
|
10
|
+
import {
|
|
11
|
+
buildKnowledge,
|
|
12
|
+
buildReverseDependencies
|
|
13
|
+
} from "./chunk-MOOVNMIN.js";
|
|
14
|
+
import {
|
|
15
|
+
scanProject
|
|
16
|
+
} from "./chunk-ST4X7LZT.js";
|
|
17
|
+
import {
|
|
18
|
+
readLearnings
|
|
19
|
+
} from "./chunk-M3YZOJNW.js";
|
|
20
|
+
import {
|
|
21
|
+
persistScanResult,
|
|
22
|
+
scanIntegrations
|
|
23
|
+
} from "./chunk-VB2TIR6L.js";
|
|
24
|
+
import {
|
|
25
|
+
KNIT_MARKER_START,
|
|
26
|
+
generateClaudeMd,
|
|
27
|
+
spliceKnitBlock
|
|
28
|
+
} from "./chunk-7UFS67HP.js";
|
|
29
|
+
import {
|
|
30
|
+
importFromMarkdown,
|
|
31
|
+
loadKnowledgeBaseSafe,
|
|
32
|
+
saveKnowledgeBase
|
|
33
|
+
} from "./chunk-WKQHCLLO.js";
|
|
34
|
+
import {
|
|
35
|
+
knowledgePath,
|
|
36
|
+
knowledgebasePath,
|
|
37
|
+
learningsDir,
|
|
38
|
+
learningsFilePath,
|
|
39
|
+
legacyClaudeDir,
|
|
40
|
+
legacyKnowledgePath,
|
|
41
|
+
legacyKnowledgebasePath,
|
|
42
|
+
legacyLearningsDir,
|
|
43
|
+
legacyTeamsPath,
|
|
44
|
+
migrationBreadcrumbPath,
|
|
45
|
+
projectDataDir,
|
|
46
|
+
teamsPath
|
|
47
|
+
} from "./chunk-27TA2ZQZ.js";
|
|
48
|
+
|
|
49
|
+
// src/mcp/cache.ts
|
|
50
|
+
import { execSync } from "child_process";
|
|
51
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
52
|
+
import { join, basename, dirname } from "path";
|
|
53
|
+
|
|
54
|
+
// src/generators/learnings.ts
|
|
55
|
+
function generateLearningsContent(config) {
|
|
56
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
57
|
+
return `# Project Learnings \u2014 ${config.name}
|
|
58
|
+
|
|
59
|
+
> Recursive learning log. Check this BEFORE starting any task.
|
|
60
|
+
> Grep by \`#tag\` to find relevant lessons for the domain you're working in.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## ${date} Project initialized with Knit workflow
|
|
65
|
+
**Domain(s):** All \u2014 workflow infrastructure
|
|
66
|
+
**Approach:** Auto-detected stack (${config.stack.language}${config.stack.framework ? " + " + config.stack.framework : ""}), generated ${config.domains.length} domains, wired hooks for ${config.targetAgent}.
|
|
67
|
+
**Outcome:** Success \u2014 workflow infrastructure in place
|
|
68
|
+
**Lesson:** This learnings file is the institutional memory. Every task should append an entry. Every session should check relevant tags before starting work. The LEARN phase is a hard exit gate \u2014 no task completes without updating this file.
|
|
69
|
+
**Tags:** #workflow #all #bootstrap
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/mcp/cache.ts
|
|
74
|
+
var cache = null;
|
|
75
|
+
var hooksRefreshed = /* @__PURE__ */ new Set();
|
|
76
|
+
function maybeRefreshHooks(rootPath, config) {
|
|
77
|
+
if (hooksRefreshed.has(rootPath)) return;
|
|
78
|
+
hooksRefreshed.add(rootPath);
|
|
79
|
+
const settingsPath = join(rootPath, ".claude", "settings.local.json");
|
|
80
|
+
if (!existsSync(settingsPath)) return;
|
|
81
|
+
try {
|
|
82
|
+
const existing = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
83
|
+
const storedVersion = existing?._knitHooks?.version ?? (existing?._engramHooks ? 0 : 0);
|
|
84
|
+
if (storedVersion < HOOKS_VERSION) {
|
|
85
|
+
writeKnitHooks(rootPath, config);
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function getBrain(rootPath) {
|
|
91
|
+
if (cache && cache.rootPath === rootPath) {
|
|
92
|
+
return cache;
|
|
93
|
+
}
|
|
94
|
+
void prewarmLatestVersion();
|
|
95
|
+
let autoInitialized = false;
|
|
96
|
+
const haveCentralized = existsSync(knowledgePath(rootPath));
|
|
97
|
+
const haveLegacy = existsSync(legacyKnowledgePath(rootPath));
|
|
98
|
+
if (!haveCentralized) {
|
|
99
|
+
if (haveLegacy) {
|
|
100
|
+
migrateLegacyData(rootPath);
|
|
101
|
+
} else {
|
|
102
|
+
autoInitialize(rootPath);
|
|
103
|
+
autoInitialized = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const scan = scanProject(rootPath);
|
|
107
|
+
const knowledge = buildKnowledge(rootPath, scan);
|
|
108
|
+
const reverseDeps = buildReverseDependencies(knowledge.importGraph);
|
|
109
|
+
const projectName = detectProjectName(rootPath);
|
|
110
|
+
const kbLoad = loadKnowledgeBaseSafe(knowledgebasePath(rootPath), projectName);
|
|
111
|
+
const knowledgeBase = kbLoad.kb;
|
|
112
|
+
const config = {
|
|
113
|
+
name: projectName,
|
|
114
|
+
packageManager: scan.packageManager,
|
|
115
|
+
stack: scan.stack,
|
|
116
|
+
domains: scan.domains,
|
|
117
|
+
targetAgent: "claude-code",
|
|
118
|
+
tokenOptimization: "standard"
|
|
119
|
+
};
|
|
120
|
+
writeFileSync(knowledgePath(rootPath), JSON.stringify(knowledge, null, 2), "utf-8");
|
|
121
|
+
if (!kbLoad.loadFailed) {
|
|
122
|
+
saveKnowledgeBase(knowledgebasePath(rootPath), knowledgeBase);
|
|
123
|
+
}
|
|
124
|
+
if (!autoInitialized) {
|
|
125
|
+
maybeRefreshHooks(rootPath, config);
|
|
126
|
+
}
|
|
127
|
+
cache = {
|
|
128
|
+
rootPath,
|
|
129
|
+
knowledge,
|
|
130
|
+
reverseDeps,
|
|
131
|
+
knowledgeBase,
|
|
132
|
+
config,
|
|
133
|
+
loadedAt: Date.now(),
|
|
134
|
+
autoInitialized
|
|
135
|
+
};
|
|
136
|
+
return cache;
|
|
137
|
+
}
|
|
138
|
+
function autoInitialize(rootPath) {
|
|
139
|
+
const scan = scanProject(rootPath);
|
|
140
|
+
const knowledge = buildKnowledge(rootPath, scan);
|
|
141
|
+
const projectName = detectProjectName(rootPath);
|
|
142
|
+
const config = {
|
|
143
|
+
name: projectName,
|
|
144
|
+
packageManager: scan.packageManager,
|
|
145
|
+
stack: scan.stack,
|
|
146
|
+
domains: scan.domains,
|
|
147
|
+
targetAgent: "claude-code",
|
|
148
|
+
tokenOptimization: "standard"
|
|
149
|
+
};
|
|
150
|
+
mkdirSync(projectDataDir(rootPath), { recursive: true });
|
|
151
|
+
mkdirSync(learningsDir(rootPath), { recursive: true });
|
|
152
|
+
writeProjectClaudeMd(rootPath, config, knowledge);
|
|
153
|
+
writeKnitHooks(rootPath, config);
|
|
154
|
+
installAgentsForProject(rootPath, config, knowledge, null).catch((err) => {
|
|
155
|
+
process.stderr.write(`[knit] agent install background error: ${err?.message ?? err}
|
|
156
|
+
`);
|
|
157
|
+
});
|
|
158
|
+
Promise.resolve().then(() => {
|
|
159
|
+
try {
|
|
160
|
+
pruneSessionsByAge(rootPath, 90);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
163
|
+
process.stderr.write(`[knit] session prune background error: ${msg}
|
|
164
|
+
`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
Promise.resolve().then(() => {
|
|
168
|
+
try {
|
|
169
|
+
const result = scanIntegrations(rootPath);
|
|
170
|
+
persistScanResult(rootPath, result);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
173
|
+
process.stderr.write(`[knit] integration scan background error: ${msg}
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
const learningsPath = learningsFilePath(rootPath, projectName);
|
|
178
|
+
if (!existsSync(learningsPath)) {
|
|
179
|
+
writeFileSync(learningsPath, generateLearningsContent(config), "utf-8");
|
|
180
|
+
}
|
|
181
|
+
const kbPath = knowledgebasePath(rootPath);
|
|
182
|
+
const kb = loadKnowledgeBaseSafe(kbPath, projectName).kb;
|
|
183
|
+
const entries = readLearnings(learningsPath);
|
|
184
|
+
importFromMarkdown(kb, entries);
|
|
185
|
+
saveKnowledgeBase(kbPath, kb);
|
|
186
|
+
writeFileSync(knowledgePath(rootPath), JSON.stringify(knowledge, null, 2), "utf-8");
|
|
187
|
+
}
|
|
188
|
+
function migrateLegacyData(rootPath) {
|
|
189
|
+
mkdirSync(projectDataDir(rootPath), { recursive: true });
|
|
190
|
+
mkdirSync(learningsDir(rootPath), { recursive: true });
|
|
191
|
+
copyIfExists(legacyKnowledgePath(rootPath), knowledgePath(rootPath));
|
|
192
|
+
copyIfExists(legacyKnowledgebasePath(rootPath), knowledgebasePath(rootPath));
|
|
193
|
+
copyIfExists(legacyTeamsPath(rootPath), teamsPath(rootPath));
|
|
194
|
+
const legacyLearn = legacyLearningsDir(rootPath);
|
|
195
|
+
if (existsSync(legacyLearn)) {
|
|
196
|
+
for (const file of readdirSync(legacyLearn)) {
|
|
197
|
+
const src = join(legacyLearn, file);
|
|
198
|
+
const dst = join(learningsDir(rootPath), file);
|
|
199
|
+
try {
|
|
200
|
+
if (statSync(src).isFile() && !existsSync(dst)) {
|
|
201
|
+
copyFileSync(src, dst);
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const breadcrumb = migrationBreadcrumbPath(rootPath);
|
|
208
|
+
const newPath = projectDataDir(rootPath);
|
|
209
|
+
if (!existsSync(breadcrumb) && existsSync(legacyClaudeDir(rootPath))) {
|
|
210
|
+
const note = `Knit data migrated to ~/.knit/ on ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.
|
|
211
|
+
|
|
212
|
+
Centralized location for this project:
|
|
213
|
+
${newPath}
|
|
214
|
+
|
|
215
|
+
The legacy files in this .claude/ directory are no longer read by engram and
|
|
216
|
+
can be deleted at your discretion. Future learnings, knowledge indexes, and
|
|
217
|
+
session memory live in the new path.
|
|
218
|
+
`;
|
|
219
|
+
try {
|
|
220
|
+
writeFileSync(breadcrumb, note, "utf-8");
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function writeProjectClaudeMd(rootPath, config, knowledge) {
|
|
226
|
+
const claudeMdPath = join(rootPath, "CLAUDE.md");
|
|
227
|
+
const block = generateClaudeMd(config, knowledge);
|
|
228
|
+
if (!existsSync(claudeMdPath)) {
|
|
229
|
+
writeFileSync(claudeMdPath, block, "utf-8");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const existing = readFileSync(claudeMdPath, "utf-8");
|
|
233
|
+
if (existing.includes(KNIT_MARKER_START)) {
|
|
234
|
+
const { content } = spliceKnitBlock(existing, block);
|
|
235
|
+
writeFileSync(claudeMdPath, content, "utf-8");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const sidecarDir = join(rootPath, ".claude");
|
|
239
|
+
const sidecarPath = join(sidecarDir, "KNIT.md");
|
|
240
|
+
mkdirSync(sidecarDir, { recursive: true });
|
|
241
|
+
const sidecar = `<!-- This file is Knit's per-project workflow. -->
|
|
242
|
+
<!-- Your CLAUDE.md exists without Knit markers, so Knit wrote here instead of clobbering it. -->
|
|
243
|
+
<!-- To include this content in CLAUDE.md, add: @.claude/KNIT.md -->
|
|
244
|
+
|
|
245
|
+
${block}`;
|
|
246
|
+
writeFileSync(sidecarPath, sidecar, "utf-8");
|
|
247
|
+
}
|
|
248
|
+
function copyIfExists(src, dst) {
|
|
249
|
+
if (existsSync(src) && !existsSync(dst)) {
|
|
250
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
251
|
+
copyFileSync(src, dst);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function writeKnitHooks(rootPath, config) {
|
|
255
|
+
const claudeDir = join(rootPath, ".claude");
|
|
256
|
+
const settingsPath = join(claudeDir, "settings.local.json");
|
|
257
|
+
const fresh = generateSettings(config, rootPath);
|
|
258
|
+
if (!existsSync(settingsPath)) {
|
|
259
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
260
|
+
writeFileSync(settingsPath, JSON.stringify(fresh, null, 2), "utf-8");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
let existing;
|
|
264
|
+
try {
|
|
265
|
+
const parsed = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
266
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
existing = parsed;
|
|
270
|
+
} catch {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const userHooksRaw = existing.hooks;
|
|
274
|
+
let userHooks;
|
|
275
|
+
if (userHooksRaw === void 0) {
|
|
276
|
+
userHooks = {};
|
|
277
|
+
} else if (userHooksRaw && typeof userHooksRaw === "object" && !Array.isArray(userHooksRaw)) {
|
|
278
|
+
for (const v of Object.values(userHooksRaw)) {
|
|
279
|
+
if (!Array.isArray(v)) return;
|
|
280
|
+
}
|
|
281
|
+
userHooks = { ...userHooksRaw };
|
|
282
|
+
} else {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
for (const event of Object.keys(fresh.hooks)) {
|
|
286
|
+
const userEntries = Array.isArray(userHooks[event]) ? userHooks[event] : [];
|
|
287
|
+
const preserved = userEntries.filter((entry) => {
|
|
288
|
+
if (!entry || typeof entry !== "object") return true;
|
|
289
|
+
const e = entry;
|
|
290
|
+
return e._knitOwned !== true && e._engramOwned !== true;
|
|
291
|
+
});
|
|
292
|
+
userHooks[event] = [...preserved, ...fresh.hooks[event]];
|
|
293
|
+
}
|
|
294
|
+
const merged = {
|
|
295
|
+
...existing,
|
|
296
|
+
hooks: userHooks,
|
|
297
|
+
_knitHooks: { ...fresh._knitHooks, merged: true }
|
|
298
|
+
};
|
|
299
|
+
delete merged._engramHooks;
|
|
300
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
301
|
+
writeFileSync(settingsPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
302
|
+
}
|
|
303
|
+
function detectProjectName(rootPath) {
|
|
304
|
+
let name = basename(rootPath);
|
|
305
|
+
try {
|
|
306
|
+
const pkg = JSON.parse(readFileSync(join(rootPath, "package.json"), "utf-8"));
|
|
307
|
+
if (pkg.name) name = pkg.name;
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
return name;
|
|
311
|
+
}
|
|
312
|
+
function refreshBrain(rootPath) {
|
|
313
|
+
cache = null;
|
|
314
|
+
return getBrain(rootPath);
|
|
315
|
+
}
|
|
316
|
+
function detectProjectRoot() {
|
|
317
|
+
try {
|
|
318
|
+
return execSync("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
319
|
+
} catch {
|
|
320
|
+
return process.cwd();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export {
|
|
325
|
+
getBrain,
|
|
326
|
+
refreshBrain,
|
|
327
|
+
detectProjectRoot
|
|
328
|
+
};
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
KNIT_MARKER_START,
|
|
4
4
|
LEGACY_ENGRAM_MARKER_END,
|
|
5
5
|
LEGACY_ENGRAM_MARKER_START
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7UFS67HP.js";
|
|
7
7
|
import {
|
|
8
8
|
integrationsConfigPath,
|
|
9
9
|
projectDataDir
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-27TA2ZQZ.js";
|
|
11
11
|
|
|
12
12
|
// src/engine/integration-scanner.ts
|
|
13
13
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, renameSync, unlinkSync } from "fs";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/engine/knowledgebase.ts
|
|
2
2
|
import { randomUUID } from "crypto";
|
|
3
|
-
import { readFileSync, writeFileSync, statSync, existsSync, mkdirSync } from "fs";
|
|
3
|
+
import { readFileSync, writeFileSync, statSync, existsSync, mkdirSync, renameSync, unlinkSync } from "fs";
|
|
4
4
|
import { dirname } from "path";
|
|
5
5
|
function createKnowledgeBase(projectName) {
|
|
6
6
|
return {
|
|
@@ -17,28 +17,53 @@ function createKnowledgeBase(projectName) {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
function loadKnowledgeBase(filePath, projectName) {
|
|
20
|
+
return loadKnowledgeBaseSafe(filePath, projectName).kb;
|
|
21
|
+
}
|
|
22
|
+
function loadKnowledgeBaseSafe(filePath, projectName) {
|
|
20
23
|
if (!existsSync(filePath)) {
|
|
21
|
-
return createKnowledgeBase(projectName);
|
|
24
|
+
return { kb: createKnowledgeBase(projectName), loadFailed: false };
|
|
22
25
|
}
|
|
23
26
|
try {
|
|
24
27
|
const stat = statSync(filePath);
|
|
25
28
|
if (stat.size > 10 * 1024 * 1024) {
|
|
26
|
-
|
|
29
|
+
process.stderr.write(
|
|
30
|
+
`[knit] knowledgebase.json at ${filePath} exceeds 10MB \u2014 refusing to load. Original file preserved.
|
|
31
|
+
`
|
|
32
|
+
);
|
|
33
|
+
return { kb: createKnowledgeBase(projectName), loadFailed: true };
|
|
27
34
|
}
|
|
28
35
|
const raw = readFileSync(filePath, "utf-8");
|
|
29
36
|
const kb = JSON.parse(raw);
|
|
30
|
-
if (kb.version !== 1
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
if (kb.version !== 1 || !Array.isArray(kb.entries) || !kb.metrics || typeof kb.metrics.totalSessions !== "number") {
|
|
38
|
+
process.stderr.write(
|
|
39
|
+
`[knit] knowledgebase.json at ${filePath} failed structural validation \u2014 refusing to overwrite. Original file preserved.
|
|
40
|
+
`
|
|
41
|
+
);
|
|
42
|
+
return { kb: createKnowledgeBase(projectName), loadFailed: true };
|
|
43
|
+
}
|
|
44
|
+
return { kb, loadFailed: false };
|
|
45
|
+
} catch (err) {
|
|
46
|
+
process.stderr.write(
|
|
47
|
+
`[knit] knowledgebase.json at ${filePath} could not be parsed (${err.message}) \u2014 refusing to overwrite. Original file preserved.
|
|
48
|
+
`
|
|
49
|
+
);
|
|
50
|
+
return { kb: createKnowledgeBase(projectName), loadFailed: true };
|
|
36
51
|
}
|
|
37
52
|
}
|
|
38
53
|
function saveKnowledgeBase(filePath, kb) {
|
|
39
54
|
const dir = dirname(filePath);
|
|
40
55
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
41
|
-
|
|
56
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
57
|
+
try {
|
|
58
|
+
writeFileSync(tmpPath, JSON.stringify(kb, null, 2), "utf-8");
|
|
59
|
+
renameSync(tmpPath, filePath);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
try {
|
|
62
|
+
unlinkSync(tmpPath);
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
42
67
|
}
|
|
43
68
|
function addEntry(kb, entry) {
|
|
44
69
|
const kbEntry = {
|
|
@@ -102,6 +127,13 @@ function getStaleEntries(kb, olderThanDays = 30) {
|
|
|
102
127
|
function recordCacheHit(kb) {
|
|
103
128
|
kb.metrics.cacheHits++;
|
|
104
129
|
}
|
|
130
|
+
function bumpMetric(kb, key, delta = 1) {
|
|
131
|
+
kb.metrics[key] = (kb.metrics[key] ?? 0) + delta;
|
|
132
|
+
}
|
|
133
|
+
function bumpClassificationTier(kb, tier, delta = 1) {
|
|
134
|
+
if (!kb.metrics.classificationsByTier) kb.metrics.classificationsByTier = {};
|
|
135
|
+
kb.metrics.classificationsByTier[tier] = (kb.metrics.classificationsByTier[tier] ?? 0) + delta;
|
|
136
|
+
}
|
|
105
137
|
function getKBSummary(kb) {
|
|
106
138
|
const totalEntries = kb.entries.length;
|
|
107
139
|
const accessedEntries = kb.entries.filter((e) => e.accessCount > 0).length;
|
|
@@ -126,6 +158,7 @@ function getKBSummary(kb) {
|
|
|
126
158
|
|
|
127
159
|
export {
|
|
128
160
|
loadKnowledgeBase,
|
|
161
|
+
loadKnowledgeBaseSafe,
|
|
129
162
|
saveKnowledgeBase,
|
|
130
163
|
addEntry,
|
|
131
164
|
importFromMarkdown,
|
|
@@ -134,5 +167,7 @@ export {
|
|
|
134
167
|
getTopEntries,
|
|
135
168
|
getStaleEntries,
|
|
136
169
|
recordCacheHit,
|
|
170
|
+
bumpMetric,
|
|
171
|
+
bumpClassificationTier,
|
|
137
172
|
getKBSummary
|
|
138
173
|
};
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
var args = process.argv.slice(2);
|
|
9
|
-
var hasSubcommand = args.length > 0 && ["setup", "status", "refresh", "install-agents", "export", "--help", "-h", "--version", "-V"].includes(args[0]);
|
|
9
|
+
var hasSubcommand = args.length > 0 && ["setup", "status", "refresh", "install-agents", "export", "doctor", "--help", "-h", "--version", "-V"].includes(args[0]);
|
|
10
10
|
var isTTY = process.stdin.isTTY;
|
|
11
11
|
if (hasSubcommand) {
|
|
12
12
|
runCLI();
|
|
@@ -20,10 +20,11 @@ async function runCLI() {
|
|
|
20
20
|
const gradient = (await import("gradient-string")).default;
|
|
21
21
|
const chalk = (await import("chalk")).default;
|
|
22
22
|
const { setupCommand } = await import("./setup-5TUUWLIJ.js");
|
|
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-
|
|
23
|
+
const { statusCommand } = await import("./status-VJDB75X2.js");
|
|
24
|
+
const { refreshCommand } = await import("./refresh-SMJ2NGIW.js");
|
|
25
|
+
const { installAgentsCommand } = await import("./install-agents-OBDCWCPB.js");
|
|
26
|
+
const { exportCommand } = await import("./export-CGSEUYZA.js");
|
|
27
|
+
const { doctorCommand } = await import("./doctor-4DN2P2JR.js");
|
|
27
28
|
const ENGRAM_GRADIENT = gradient(["#7c3aed", "#2563eb", "#06b6d4"]);
|
|
28
29
|
const banner = `
|
|
29
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
|
|
@@ -75,6 +76,14 @@ async function runCLI() {
|
|
|
75
76
|
process.exit(1);
|
|
76
77
|
}
|
|
77
78
|
});
|
|
79
|
+
program.command("doctor").description("Install health check: version, MCP registration, HOOKS_VERSION drift, knowledgebase, dangling symlinks").argument("[directory]", "Project directory", ".").action(async (directory) => {
|
|
80
|
+
try {
|
|
81
|
+
await doctorCommand(directory);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(chalk.red(" Error:"), error instanceof Error ? error.message : error);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
78
87
|
program.command("export").description("Export knit learnings into a target format (e.g. an Obsidian vault)").argument("<format>", "Export format (currently only: obsidian)").argument("<vault-path>", "Output directory (Obsidian vault path)").option("--filter <tag>", "Only export entries tagged with this tag (e.g. #auth)").action(async (format, vaultPath, options) => {
|
|
79
88
|
try {
|
|
80
89
|
await exportCommand(format, vaultPath, options);
|
|
@@ -89,11 +98,11 @@ async function runMCP() {
|
|
|
89
98
|
const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
|
|
90
99
|
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
91
100
|
const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
92
|
-
const { getBrain, detectProjectRoot, refreshBrain } = await import("./cache-
|
|
93
|
-
const { getActiveToolDefinitionsForBrain, handleToolCall } = await import("./tools-
|
|
94
|
-
const { buildInstructions } = await import("./instructions-
|
|
101
|
+
const { getBrain, detectProjectRoot, refreshBrain } = await import("./cache-3LPETDUT.js");
|
|
102
|
+
const { getActiveToolDefinitionsForBrain, handleToolCall } = await import("./tools-MIROTK2A.js");
|
|
103
|
+
const { buildInstructions } = await import("./instructions-JARSXQPO.js");
|
|
95
104
|
const { registerToolsListChangedNotifier } = await import("./notifier-4L27HKHI.js");
|
|
96
|
-
const { loadScanResult } = await import("./integration-scanner-
|
|
105
|
+
const { loadScanResult } = await import("./integration-scanner-LBD2PIZ3.js");
|
|
97
106
|
const ROOT_PATH = detectProjectRoot();
|
|
98
107
|
const PER_PROJECT_INSTRUCTIONS = buildInstructions(loadScanResult(ROOT_PATH));
|
|
99
108
|
const server = new Server(
|