knit-mcp 0.7.1 → 0.9.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 +21 -0
- package/dist/{cache-BVFD5F5Y.js → cache-JSN6ETUF.js} +6 -4
- package/dist/{chunk-X6TGTET3.js → chunk-4K4FHOKE.js} +176 -13
- package/dist/chunk-5NCSZYRJ.js +66 -0
- package/dist/{chunk-TRZ3LD6B.js → chunk-B2KZ5UR7.js} +11 -1
- package/dist/{chunk-3XR77YJM.js → chunk-BU3VHX3W.js} +63 -1
- package/dist/chunk-FLNV2IQC.js +201 -0
- package/dist/chunk-KLNUEE3O.js +164 -0
- package/dist/{chunk-SLN5ABF5.js → chunk-MOOVNMIN.js} +1 -160
- package/dist/chunk-UTVFELXS.js +9 -0
- package/dist/{chunk-HBMF62U4.js → chunk-XFS2XGZI.js} +8 -0
- package/dist/cli.js +13 -16
- package/dist/{export-IKPBLZOO.js → export-I5Y26WUL.js} +2 -2
- package/dist/{install-agents-HXVYULKK.js → install-agents-D2KJQUH3.js} +6 -4
- package/dist/instructions-4FI32YZU.js +10 -0
- package/dist/integration-scanner-PS47AHGM.js +15 -0
- package/dist/{refresh-JJUSH2J5.js → refresh-BXN32CNA.js} +6 -4
- package/dist/{status-XN6VHO66.js → status-RQWRIM2Y.js} +1 -1
- package/dist/{tools-NZUUKPYI.js → tools-EISDGPS5.js} +877 -27
- package/package.json +1 -1
- package/dist/instructions-33TUHLTK.js +0 -29
package/README.md
CHANGED
|
@@ -59,6 +59,27 @@ Adds the Knit MCP server to your Claude Code config (`~/.claude.json`). No per-p
|
|
|
59
59
|
|
|
60
60
|
**Supported shells:** macOS, Linux, WSL, Git Bash, and Windows PowerShell. The generated hooks use POSIX-style single-quoted `node -e '…'` payloads. Windows `cmd.exe` does not treat single quotes as delimiters and is not supported as the hook-runner shell — on Windows, use PowerShell (default in modern Windows Terminal) or Git Bash. If you hit a hook error on Windows, file an issue with the shell you're using.
|
|
61
61
|
|
|
62
|
+
### Quiet mode (no hook enforcement)
|
|
63
|
+
|
|
64
|
+
Knit ships Protocol Guard in `warn` mode by default — hooks print reminders, they never block. If you want it fully silent (no PreToolUse classification gate, no reminder messages), run this once inside Claude Code:
|
|
65
|
+
|
|
66
|
+
> `knit_set_protocol_strictness({ level: "off" })`
|
|
67
|
+
|
|
68
|
+
The other hooks (LEARN compliance, KB metrics, final build verification) stay as observability nudges — they print, they don't gate. To remove them too, see Uninstall below.
|
|
69
|
+
|
|
70
|
+
### Uninstall
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
rm -rf ~/.knit # all per-project + global memory
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then:
|
|
77
|
+
1. Remove `"knit-brain"` from `mcpServers` in `~/.claude.json`
|
|
78
|
+
2. Delete the `<!-- knit:start --> ... <!-- knit:end -->` block from each project's `CLAUDE.md`
|
|
79
|
+
3. Remove `_knitOwned` entries from each project's `.claude/settings.local.json` (or delete the file if Knit was the only thing in it)
|
|
80
|
+
|
|
81
|
+
Total time: ~30 seconds per project. Knit doesn't write anywhere else on your machine.
|
|
82
|
+
|
|
62
83
|
## How data is stored
|
|
63
84
|
|
|
64
85
|
Knit data is centralized — not in every repo's working tree:
|
|
@@ -2,13 +2,15 @@ import {
|
|
|
2
2
|
detectProjectRoot,
|
|
3
3
|
getBrain,
|
|
4
4
|
refreshBrain
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-4K4FHOKE.js";
|
|
6
|
+
import "./chunk-BU3VHX3W.js";
|
|
7
|
+
import "./chunk-MOOVNMIN.js";
|
|
8
8
|
import "./chunk-7PPC6IG6.js";
|
|
9
9
|
import "./chunk-M3YZOJNW.js";
|
|
10
|
+
import "./chunk-FLNV2IQC.js";
|
|
11
|
+
import "./chunk-KLNUEE3O.js";
|
|
10
12
|
import "./chunk-BAUQEFYY.js";
|
|
11
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-XFS2XGZI.js";
|
|
12
14
|
export {
|
|
13
15
|
detectProjectRoot,
|
|
14
16
|
getBrain,
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
installAgentsForProject,
|
|
3
|
+
prewarmLatestVersion,
|
|
3
4
|
pruneSessionsByAge
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-BU3VHX3W.js";
|
|
5
6
|
import {
|
|
6
|
-
KNIT_MARKER_START,
|
|
7
7
|
buildKnowledge,
|
|
8
|
-
buildReverseDependencies
|
|
9
|
-
|
|
10
|
-
spliceKnitBlock
|
|
11
|
-
} from "./chunk-SLN5ABF5.js";
|
|
8
|
+
buildReverseDependencies
|
|
9
|
+
} from "./chunk-MOOVNMIN.js";
|
|
12
10
|
import {
|
|
13
11
|
scanProject
|
|
14
12
|
} from "./chunk-7PPC6IG6.js";
|
|
15
13
|
import {
|
|
16
14
|
readLearnings
|
|
17
15
|
} from "./chunk-M3YZOJNW.js";
|
|
16
|
+
import {
|
|
17
|
+
persistScanResult,
|
|
18
|
+
scanIntegrations
|
|
19
|
+
} from "./chunk-FLNV2IQC.js";
|
|
20
|
+
import {
|
|
21
|
+
KNIT_MARKER_START,
|
|
22
|
+
generateClaudeMd,
|
|
23
|
+
spliceKnitBlock
|
|
24
|
+
} from "./chunk-KLNUEE3O.js";
|
|
18
25
|
import {
|
|
19
26
|
importFromMarkdown,
|
|
20
27
|
loadKnowledgeBase,
|
|
@@ -34,11 +41,12 @@ import {
|
|
|
34
41
|
migrationBreadcrumbPath,
|
|
35
42
|
projectDataDir,
|
|
36
43
|
protocolConfigPath,
|
|
44
|
+
searchMarkerPath,
|
|
37
45
|
sessionMarkerPath,
|
|
38
46
|
sessionsJsonlPath,
|
|
39
47
|
sessionsLogPath,
|
|
40
48
|
teamsPath
|
|
41
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-XFS2XGZI.js";
|
|
42
50
|
|
|
43
51
|
// src/mcp/cache.ts
|
|
44
52
|
import { execSync } from "child_process";
|
|
@@ -65,7 +73,7 @@ function generateLearningsContent(config) {
|
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
// src/generators/settings.ts
|
|
68
|
-
var HOOKS_VERSION =
|
|
76
|
+
var HOOKS_VERSION = 7;
|
|
69
77
|
function generateSettings(config, rootPath) {
|
|
70
78
|
return {
|
|
71
79
|
mcpServers: {
|
|
@@ -111,6 +119,9 @@ function generateHooks(config, rootPath) {
|
|
|
111
119
|
const PROTOCOL_CONFIG = protocolConfigPath(rootPath);
|
|
112
120
|
const CLASSIFIED_MARKER = classificationMarkerPath(rootPath);
|
|
113
121
|
const SESSION_MARKER = sessionMarkerPath(rootPath);
|
|
122
|
+
const SEARCH_MARKER = searchMarkerPath(rootPath);
|
|
123
|
+
const CLAUDE_MD = `${rootPath}/CLAUDE.md`;
|
|
124
|
+
void knowledgePath;
|
|
114
125
|
const hooks = {
|
|
115
126
|
SessionStart: [
|
|
116
127
|
// Protocol Guard layer 1: drop a marker that knit_load_session
|
|
@@ -149,6 +160,10 @@ function generateHooks(config, rootPath) {
|
|
|
149
160
|
const fs = require("fs");
|
|
150
161
|
const p = ${jsLit(CLASSIFIED_MARKER)};
|
|
151
162
|
if (fs.existsSync(p)) fs.rmSync(p, { force: true });
|
|
163
|
+
// v0.9 #5: per-turn search marker is also cleared at the turn boundary
|
|
164
|
+
// so the next non-trivial task has to call knit_search_learnings again.
|
|
165
|
+
const sm = ${jsLit(SEARCH_MARKER)};
|
|
166
|
+
if (fs.existsSync(sm)) fs.rmSync(sm, { force: true });
|
|
152
167
|
} catch (e) {}
|
|
153
168
|
`),
|
|
154
169
|
timeout: 5
|
|
@@ -192,6 +207,7 @@ function generateHooks(config, rootPath) {
|
|
|
192
207
|
const fs = require("fs");
|
|
193
208
|
const cfgPath = ${jsLit(PROTOCOL_CONFIG)};
|
|
194
209
|
const markerPath = ${jsLit(CLASSIFIED_MARKER)};
|
|
210
|
+
const searchMarkerPath = ${jsLit(SEARCH_MARKER)};
|
|
195
211
|
let level = "warn";
|
|
196
212
|
if (fs.existsSync(cfgPath)) {
|
|
197
213
|
try {
|
|
@@ -202,13 +218,33 @@ function generateHooks(config, rootPath) {
|
|
|
202
218
|
}
|
|
203
219
|
}
|
|
204
220
|
if (level === "off") return;
|
|
221
|
+
// Classification gate (v0.5).
|
|
205
222
|
const hasMarker = fs.existsSync(markerPath);
|
|
206
|
-
if (hasMarker)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
223
|
+
if (!hasMarker) {
|
|
224
|
+
if (level === "block") {
|
|
225
|
+
console.error("[knit] BLOCKED: call knit_classify_task before Edit/Write. The Protocol Guard prevents implementation without classification.");
|
|
226
|
+
process.exit(2);
|
|
227
|
+
}
|
|
228
|
+
console.error("[knit] reminder: call knit_classify_task before Edit/Write. Set strictness=block via knit_set_protocol_strictness to make this a hard gate.");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// v0.9 #5: search gate. For standard/complex tasks, knit_search_learnings
|
|
232
|
+
// (or knit_search_global_learnings) must run before the Edit lands \u2014
|
|
233
|
+
// otherwise the agent is re-investigating without checking memory.
|
|
234
|
+
try {
|
|
235
|
+
const marker = JSON.parse(fs.readFileSync(markerPath, "utf-8"));
|
|
236
|
+
if (marker && (marker.tier === "standard" || marker.tier === "complex")) {
|
|
237
|
+
if (!fs.existsSync(searchMarkerPath)) {
|
|
238
|
+
if (level === "block") {
|
|
239
|
+
console.error("[knit] BLOCKED: " + marker.tier + " task \u2014 call knit_search_learnings or knit_search_global_learnings before Edit/Write so memory is checked before re-investigation.");
|
|
240
|
+
process.exit(2);
|
|
241
|
+
}
|
|
242
|
+
console.error("[knit] reminder: " + marker.tier + " task \u2014 call knit_search_learnings before Edit/Write. Skipping memory check means re-doing work the project already learned.");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch (markerErr) {
|
|
246
|
+
// Marker exists but JSON unreadable \u2014 be lenient.
|
|
210
247
|
}
|
|
211
|
-
console.error("[knit] reminder: call knit_classify_task before Edit/Write. Set strictness=block via knit_set_protocol_strictness to make this a hard gate.");
|
|
212
248
|
} catch (hookErr) {
|
|
213
249
|
console.error("[knit] protocol-guard hook crashed, allowing tool through:", hookErr && hookErr.message ? hookErr.message : hookErr);
|
|
214
250
|
}
|
|
@@ -216,11 +252,106 @@ function generateHooks(config, rootPath) {
|
|
|
216
252
|
timeout: 5
|
|
217
253
|
}
|
|
218
254
|
]
|
|
255
|
+
},
|
|
256
|
+
// v0.9 #9 — Pre-write content inspection. Reads the proposed Write/Edit
|
|
257
|
+
// content from tool_input, parses local import statements, and reports
|
|
258
|
+
// any relative paths that don't resolve on disk. Warn-level by default
|
|
259
|
+
// (the existing classification gate handles block mode); soft signal,
|
|
260
|
+
// never blocks on its own.
|
|
261
|
+
{
|
|
262
|
+
_knitOwned: true,
|
|
263
|
+
matcher: "Write|Edit|MultiEdit",
|
|
264
|
+
hooks: [
|
|
265
|
+
{
|
|
266
|
+
type: "command",
|
|
267
|
+
command: nodeHook(`
|
|
268
|
+
let d = "";
|
|
269
|
+
process.stdin.on("data", (c) => d += c);
|
|
270
|
+
process.stdin.on("end", () => {
|
|
271
|
+
try {
|
|
272
|
+
const fs = require("fs");
|
|
273
|
+
const path = require("path");
|
|
274
|
+
const i = JSON.parse(d);
|
|
275
|
+
const filePath = (i.tool_input && i.tool_input.file_path) || "";
|
|
276
|
+
if (!/\\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(filePath)) return;
|
|
277
|
+
// Pull proposed content from any of the Edit/Write shapes.
|
|
278
|
+
let content = (i.tool_input && (i.tool_input.content || i.tool_input.new_string)) || "";
|
|
279
|
+
if (i.tool_input && Array.isArray(i.tool_input.edits)) {
|
|
280
|
+
content = i.tool_input.edits.map((e) => e && e.new_string ? e.new_string : "").join("\\n");
|
|
281
|
+
}
|
|
282
|
+
if (!content) return;
|
|
283
|
+
const dir = path.dirname(filePath);
|
|
284
|
+
const re = /^import\\s+(?:[^'"]+?\\s+from\\s+)?['"]([^'"]+)['"]/gm;
|
|
285
|
+
const unresolved = [];
|
|
286
|
+
let m;
|
|
287
|
+
while ((m = re.exec(content)) !== null) {
|
|
288
|
+
const target = m[1];
|
|
289
|
+
if (!target.startsWith(".") && !target.startsWith("/")) continue;
|
|
290
|
+
const candidates = [target, target + ".ts", target + ".tsx", target + ".js", target + ".jsx", target + "/index.ts", target + "/index.tsx", target + "/index.js"];
|
|
291
|
+
let resolved = false;
|
|
292
|
+
for (const c of candidates) {
|
|
293
|
+
const abs = path.resolve(dir, c);
|
|
294
|
+
if (fs.existsSync(abs)) { resolved = true; break; }
|
|
295
|
+
}
|
|
296
|
+
if (!resolved) unresolved.push(target);
|
|
297
|
+
}
|
|
298
|
+
if (unresolved.length > 0) {
|
|
299
|
+
console.error("[knit] heads-up: proposed edit references " + unresolved.length + " unresolved relative import(s): " + unresolved.join(", ") + ". Likely hallucinated paths \u2014 verify with knit_query_imports or knit_verify_claim before relying on them.");
|
|
300
|
+
}
|
|
301
|
+
} catch (e) {}
|
|
302
|
+
});
|
|
303
|
+
`),
|
|
304
|
+
timeout: 5
|
|
305
|
+
}
|
|
306
|
+
]
|
|
219
307
|
}
|
|
220
308
|
],
|
|
221
309
|
PostToolUse: [],
|
|
222
310
|
Stop: []
|
|
223
311
|
};
|
|
312
|
+
hooks.PostToolUse.push({
|
|
313
|
+
_knitOwned: true,
|
|
314
|
+
matcher: "Write|Edit|MultiEdit",
|
|
315
|
+
hooks: [
|
|
316
|
+
{
|
|
317
|
+
type: "command",
|
|
318
|
+
command: nodeHook(`
|
|
319
|
+
let d = "";
|
|
320
|
+
process.stdin.on("data", (c) => d += c);
|
|
321
|
+
process.stdin.on("end", () => {
|
|
322
|
+
try {
|
|
323
|
+
const fs = require("fs");
|
|
324
|
+
const path = require("path");
|
|
325
|
+
const i = JSON.parse(d);
|
|
326
|
+
const f = (i.tool_input && i.tool_input.file_path) || (i.tool_response && i.tool_response.filePath) || "";
|
|
327
|
+
if (!/\\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(f)) return;
|
|
328
|
+
if (!fs.existsSync(f)) return;
|
|
329
|
+
const content = fs.readFileSync(f, "utf-8");
|
|
330
|
+
const dir = path.dirname(f);
|
|
331
|
+
const re = /^import\\s+(?:[^'"]+?\\s+from\\s+)?['"]([^'"]+)['"]/gm;
|
|
332
|
+
const unresolved = [];
|
|
333
|
+
let m;
|
|
334
|
+
while ((m = re.exec(content)) !== null) {
|
|
335
|
+
const target = m[1];
|
|
336
|
+
if (!target.startsWith(".") && !target.startsWith("/")) continue;
|
|
337
|
+
const candidates = [target, target + ".ts", target + ".tsx", target + ".js", target + ".jsx", target + "/index.ts", target + "/index.tsx", target + "/index.js"];
|
|
338
|
+
let resolved = false;
|
|
339
|
+
for (const c of candidates) {
|
|
340
|
+
if (fs.existsSync(path.resolve(dir, c))) { resolved = true; break; }
|
|
341
|
+
}
|
|
342
|
+
if (!resolved) unresolved.push(target);
|
|
343
|
+
}
|
|
344
|
+
if (unresolved.length > 0) {
|
|
345
|
+
console.error("[knit] post-write check: " + f + " has " + unresolved.length + " unresolved relative import(s): " + unresolved.join(", ") + ". Run typecheck before relying on this file.");
|
|
346
|
+
}
|
|
347
|
+
} catch (e) {}
|
|
348
|
+
});
|
|
349
|
+
`),
|
|
350
|
+
timeout: 5,
|
|
351
|
+
statusMessage: "Knit: validating imports..."
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
});
|
|
224
355
|
if (config.stack.language === "typescript" && config.stack.typecheckCommand) {
|
|
225
356
|
hooks.PostToolUse.push({
|
|
226
357
|
_knitOwned: true,
|
|
@@ -351,6 +482,27 @@ function generateHooks(config, rootPath) {
|
|
|
351
482
|
]
|
|
352
483
|
});
|
|
353
484
|
}
|
|
485
|
+
hooks.Stop.push({
|
|
486
|
+
_knitOwned: true,
|
|
487
|
+
hooks: [
|
|
488
|
+
{
|
|
489
|
+
type: "command",
|
|
490
|
+
command: nodeHook(`
|
|
491
|
+
try {
|
|
492
|
+
const fs = require("fs");
|
|
493
|
+
const p = ${jsLit(CLAUDE_MD)};
|
|
494
|
+
if (!fs.existsSync(p)) return;
|
|
495
|
+
const size = fs.statSync(p).size;
|
|
496
|
+
if (size > 12500) {
|
|
497
|
+
console.error("[knit] budget watch: CLAUDE.md is " + Math.round(size/1024*10)/10 + "KB (target 6.5KB; over-budget threshold 12.5KB). Call knit_brain_status to confirm and consider regenerating via knit refresh.");
|
|
498
|
+
}
|
|
499
|
+
} catch (e) {}
|
|
500
|
+
`),
|
|
501
|
+
timeout: 5,
|
|
502
|
+
statusMessage: "Knit: budget check..."
|
|
503
|
+
}
|
|
504
|
+
]
|
|
505
|
+
});
|
|
354
506
|
hooks.Stop.push({
|
|
355
507
|
_knitOwned: true,
|
|
356
508
|
hooks: [
|
|
@@ -497,6 +649,7 @@ function getBrain(rootPath) {
|
|
|
497
649
|
if (cache && cache.rootPath === rootPath) {
|
|
498
650
|
return cache;
|
|
499
651
|
}
|
|
652
|
+
void prewarmLatestVersion();
|
|
500
653
|
let autoInitialized = false;
|
|
501
654
|
const haveCentralized = existsSync(knowledgePath(rootPath));
|
|
502
655
|
const haveLegacy = existsSync(legacyKnowledgePath(rootPath));
|
|
@@ -563,6 +716,16 @@ function autoInitialize(rootPath) {
|
|
|
563
716
|
} catch (e) {
|
|
564
717
|
const msg = e instanceof Error ? e.message : String(e);
|
|
565
718
|
process.stderr.write(`[knit] session prune background error: ${msg}
|
|
719
|
+
`);
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
Promise.resolve().then(() => {
|
|
723
|
+
try {
|
|
724
|
+
const result = scanIntegrations(rootPath);
|
|
725
|
+
persistScanResult(rootPath, result);
|
|
726
|
+
} catch (e) {
|
|
727
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
728
|
+
process.stderr.write(`[knit] integration scan background error: ${msg}
|
|
566
729
|
`);
|
|
567
730
|
}
|
|
568
731
|
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/mcp/instructions.ts
|
|
2
|
+
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
|
+
|
|
4
|
+
ALWAYS at session start:
|
|
5
|
+
1. Call knit_load_session \u2014 returns prior handoff, top learnings, false positives. If has_unfinished_work is true, resume that handoff instead of starting fresh.
|
|
6
|
+
2. For any non-trivial task, call knit_classify_task BEFORE editing or writing \u2014 returns tier (inquiry / trivial / standard / complex) and phases.
|
|
7
|
+
3. If tier=complex with auto_plan_mode=true, call EnterPlanMode immediately. Do not start editing.
|
|
8
|
+
4. If tier=inquiry, just answer \u2014 no plan mode, no phases. Re-classify only if scope grows into writes.
|
|
9
|
+
5. Before reporting a task done, call knit_record_learning if anything non-obvious surfaced (not a substring restatement of prior learnings).
|
|
10
|
+
|
|
11
|
+
When to reach for other Knit tools:
|
|
12
|
+
- knit_query_imports / knit_query_exports / knit_query_dependents / knit_query_tests \u2014 use instead of grep when the knowledge index is fresh.
|
|
13
|
+
- knit_search_learnings \u2014 call before re-investigating a domain. The point of memory is to skip what you've already learned.
|
|
14
|
+
- knit_search_sessions \u2014 answers "have I done this before?"
|
|
15
|
+
- knit_search_global_learnings \u2014 same, but across all your projects (Knit's cross-project pool).
|
|
16
|
+
- knit_get_workflow({phase}) \u2014 fetch protocol depth for one phase on demand. Do not try to remember the workflow; ask for it.
|
|
17
|
+
- knit_list_features \u2014 if you want to do X but the tool isn't visible, this surfaces what's hidden and how to enable it.
|
|
18
|
+
- knit_save_handoff \u2014 call when context degrades or session ends so the next session resumes cleanly.
|
|
19
|
+
|
|
20
|
+
The protocol enforces a 4-tier classifier:
|
|
21
|
+
- Inquiry: read-only "what / where / audit / explain" \u2014 just answer.
|
|
22
|
+
- Trivial: single-file obvious change \u2014 EXECUTE \u2192 VERIFY \u2192 LEARN.
|
|
23
|
+
- Standard: bug fix or single-domain feature \u2014 RESEARCH \u2192 EXECUTE \u2192 OPTIMIZE \u2192 REVIEW \u2192 LEARN.
|
|
24
|
+
- Complex: cross-domain, types/auth-touching, high-fanout, or any task spanning more than one commit \u2014 full 6 phases with auto plan mode on RESEARCH.
|
|
25
|
+
|
|
26
|
+
Knit provides inputs; you make the calls. When in doubt, under-classify \u2014 easier to escalate mid-task than to downgrade.
|
|
27
|
+
|
|
28
|
+
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.`;
|
|
29
|
+
var KNIT_INSTRUCTIONS = KNIT_INSTRUCTIONS_BASE;
|
|
30
|
+
function buildInstructions(scan) {
|
|
31
|
+
if (!scan) return KNIT_INSTRUCTIONS_BASE;
|
|
32
|
+
const addenda = [];
|
|
33
|
+
if (scan.detected.ruflo.present) {
|
|
34
|
+
addenda.push(
|
|
35
|
+
"DETECTED: Ruflo (multi-agent orchestration) is installed alongside Knit on this project. For multi-agent swarms, federation, or large-scale orchestration, defer to Ruflo's tools (`memory_store`, `swarm_init`, `agent_spawn`, etc). Knit's domain in this project: per-project memory + tier-routed classification + token discipline. Do NOT duplicate Ruflo's routing logic with Knit's tier protocol when Ruflo is driving the workflow."
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (scan.detected.gstack.present) {
|
|
39
|
+
addenda.push(
|
|
40
|
+
"DETECTED: gstack slash commands are installed. For routing decisions (`/plan`, `/ship`, `/qa`, `/cso`, `/investigate`), prefer the gstack command. Knit operates underneath as the memory + classification layer; the gstack command can invoke Knit tools internally."
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (scan.detected.codetour.present) {
|
|
44
|
+
addenda.push(
|
|
45
|
+
"DETECTED: CodeTour is configured (.tours/*.tour). When asked to walk through code or explain architecture, surface relevant tours via the CodeTour extension rather than reconstructing the explanation from scratch."
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (scan.detected.conductor.present) {
|
|
49
|
+
addenda.push(
|
|
50
|
+
"DETECTED: Conductor is installed. For cross-workspace handoff and context-restore flows, defer to Conductor's primitives; Knit's `knit_save_handoff` / `knit_load_session` continue to handle the per-project memory layer."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (scan.detected.custom_workflow_sections.length > 0) {
|
|
54
|
+
addenda.push(
|
|
55
|
+
`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.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (addenda.length === 0) return KNIT_INSTRUCTIONS_BASE;
|
|
59
|
+
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.";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
KNIT_INSTRUCTIONS_BASE,
|
|
64
|
+
KNIT_INSTRUCTIONS,
|
|
65
|
+
buildInstructions
|
|
66
|
+
};
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
canonicalRepoRoot,
|
|
3
3
|
globalLearningsPath,
|
|
4
4
|
projectId
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XFS2XGZI.js";
|
|
6
6
|
|
|
7
7
|
// src/engine/global-learnings.ts
|
|
8
8
|
import { existsSync, mkdirSync, appendFileSync, readFileSync, statSync } from "fs";
|
|
@@ -44,6 +44,15 @@ function getRecentGlobalLearnings(n = 5) {
|
|
|
44
44
|
}
|
|
45
45
|
return out;
|
|
46
46
|
}
|
|
47
|
+
function loadAllGlobalLearnings() {
|
|
48
|
+
const lines = readAllLines();
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
const entry = parseLine(line);
|
|
52
|
+
if (entry) out.push(entry);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
47
56
|
function buildGlobalLearning(sourceProjectRoot, payload) {
|
|
48
57
|
return {
|
|
49
58
|
id: payload.id ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -83,5 +92,6 @@ export {
|
|
|
83
92
|
appendGlobalLearning,
|
|
84
93
|
searchGlobalLearnings,
|
|
85
94
|
getRecentGlobalLearnings,
|
|
95
|
+
loadAllGlobalLearnings,
|
|
86
96
|
buildGlobalLearning
|
|
87
97
|
};
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
projectAgentFile,
|
|
12
12
|
projectAgentsDir,
|
|
13
13
|
sessionsJsonlPath
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-XFS2XGZI.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";
|
|
@@ -285,6 +285,55 @@ 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
|
+
|
|
288
337
|
// src/engine/sessions.ts
|
|
289
338
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, appendFileSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
290
339
|
import { dirname as dirname2 } from "path";
|
|
@@ -327,6 +376,15 @@ function getRecentSessions(rootPath, n = 3) {
|
|
|
327
376
|
function sessionCount(rootPath) {
|
|
328
377
|
return readAllLines(rootPath).length;
|
|
329
378
|
}
|
|
379
|
+
function loadAllSessions(rootPath) {
|
|
380
|
+
const lines = readAllLines(rootPath);
|
|
381
|
+
const out = [];
|
|
382
|
+
for (const line of lines) {
|
|
383
|
+
const entry = parseLine(line);
|
|
384
|
+
if (entry) out.push(entry);
|
|
385
|
+
}
|
|
386
|
+
return out;
|
|
387
|
+
}
|
|
330
388
|
function pruneSessionsByAge(rootPath, maxAgeDays) {
|
|
331
389
|
const path = sessionsJsonlPath(rootPath);
|
|
332
390
|
if (!existsSync3(path)) return { kept: 0, pruned: 0 };
|
|
@@ -400,9 +458,13 @@ function parseLine(line) {
|
|
|
400
458
|
|
|
401
459
|
export {
|
|
402
460
|
installAgentsForProject,
|
|
461
|
+
getCachedLatestVersion,
|
|
462
|
+
prewarmLatestVersion,
|
|
463
|
+
isNewerVersion,
|
|
403
464
|
appendSession,
|
|
404
465
|
searchSessions,
|
|
405
466
|
getRecentSessions,
|
|
406
467
|
sessionCount,
|
|
468
|
+
loadAllSessions,
|
|
407
469
|
pruneSessionsByAge
|
|
408
470
|
};
|