knit-mcp 0.6.4 → 0.7.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.
@@ -5,7 +5,7 @@ import {
5
5
  pruneSessionsByAge,
6
6
  searchSessions,
7
7
  sessionCount
8
- } from "./chunk-Y3I4NAKM.js";
8
+ } from "./chunk-3XR77YJM.js";
9
9
  import {
10
10
  scanProject
11
11
  } from "./chunk-7PPC6IG6.js";
@@ -14,7 +14,7 @@ import {
14
14
  buildGlobalLearning,
15
15
  getRecentGlobalLearnings,
16
16
  searchGlobalLearnings
17
- } from "./chunk-FEOG4WTP.js";
17
+ } from "./chunk-TRZ3LD6B.js";
18
18
  import {
19
19
  addEntry,
20
20
  getFalsePositives,
@@ -26,17 +26,19 @@ import {
26
26
  import {
27
27
  canonicalRepoRoot,
28
28
  classificationMarkerPath,
29
+ featuresConfigPath,
29
30
  knowledgebasePath,
30
31
  learningsDir,
32
+ projectAgentsDir,
31
33
  projectDataDir,
32
34
  protocolConfigPath,
33
35
  sessionsLogPath,
34
36
  teamsPath,
35
37
  worktreesRegistryPath
36
- } from "./chunk-YI37OAJ7.js";
38
+ } from "./chunk-HBMF62U4.js";
37
39
 
38
40
  // src/mcp/handlers.ts
39
- import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, readdirSync, existsSync as existsSync4 } from "fs";
41
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, readdirSync, existsSync as existsSync4, renameSync as renameSync2, unlinkSync } from "fs";
40
42
  import { join as join2 } from "path";
41
43
  import { statSync as statSync2 } from "fs";
42
44
 
@@ -81,9 +83,9 @@ var SECTIONS = {
81
83
  tools
82
84
  };
83
85
  function overview(_) {
84
- return `# Engram workflow \u2014 overview
86
+ return `# Knit workflow \u2014 overview
85
87
 
86
- This protocol is a decision aid. Read once, follow loosely, escalate when something doesn't fit. The principle: engram gives you data; you make the calls.
88
+ This protocol is a decision aid. Read once, follow loosely, escalate when something doesn't fit. The principle: Knit gives you data; you make the calls.
87
89
 
88
90
  **Navigation:** call \`knit_get_workflow({phase})\` with any of:
89
91
  - \`overview\` (this) \xB7 \`tier\` \xB7 \`phases\`
@@ -95,7 +97,7 @@ Start of every session: call \`knit_load_session\` first. It returns prior sessi
95
97
  When in doubt: under-classify. Easier to escalate mid-task than to downgrade.`;
96
98
  }
97
99
  function tier(_) {
98
- return `# Tier classification \u2014 you decide, engram informs
100
+ return `# Tier classification \u2014 you decide, Knit informs
99
101
 
100
102
  Four tiers. You read the user's message and decide. No regex, no auto-rules.
101
103
 
@@ -377,7 +379,7 @@ function spawnWorktree(rootPath, teamName, taskDescription) {
377
379
  const repoRoot = canonicalRepoRoot(rootPath);
378
380
  const slug = slugify(teamName);
379
381
  const ts = Date.now();
380
- const branch = `engram/team-${slug}-${ts}`;
382
+ const branch = `knit/team-${slug}-${ts}`;
381
383
  const worktreePath = resolve(dirname(repoRoot), `${basename(repoRoot)}-knit-${slug}-${ts}`);
382
384
  if (existsSync(worktreePath)) {
383
385
  throw new Error(`Worktree path already exists: ${worktreePath}`);
@@ -692,6 +694,108 @@ function redactSecrets(input) {
692
694
  return out;
693
695
  }
694
696
 
697
+ // src/mcp/features.ts
698
+ var TOOL_REGISTRY = [
699
+ // ── Tier 1 — Memory + retrieval (8) ─────────────────────────────
700
+ { tool: "knit_load_session", tier: 1, category: "memory", rationale: "Session-start primer; universal" },
701
+ { tool: "knit_search_learnings", tier: 1, category: "memory", rationale: "Project-local learnings lookup; universal" },
702
+ { tool: "knit_search_global_learnings", tier: 1, category: "memory", rationale: "Cross-project learnings pool; seeded by default" },
703
+ { tool: "knit_search_sessions", tier: 1, category: "memory", rationale: '"Have I done this before?" \u2014 universal' },
704
+ { tool: "knit_record_learning", tier: 1, category: "memory", rationale: "LEARN step persistence; universal" },
705
+ { tool: "knit_record_global_learning", tier: 1, category: "memory", rationale: "Cross-project memory write; useful from day one" },
706
+ { tool: "knit_save_session_summary", tier: 1, category: "memory", rationale: "Session-end persistence; universal" },
707
+ { tool: "knit_save_handoff", tier: 1, category: "memory", rationale: "Context-degradation handoff; universal" },
708
+ // ── Tier 1 — Knowledge graph (5) ────────────────────────────────
709
+ { tool: "knit_query_imports", tier: 1, category: "knowledge-graph", rationale: "Core differentiator; returns empty honestly on docs-only projects" },
710
+ { tool: "knit_query_exports", tier: 1, category: "knowledge-graph", rationale: "Core differentiator; same honest-empty behavior" },
711
+ { tool: "knit_query_dependents", tier: 1, category: "knowledge-graph", rationale: "Core differentiator" },
712
+ { tool: "knit_query_tests", tier: 1, category: "knowledge-graph", rationale: "Core differentiator" },
713
+ { tool: "knit_find_fanout", tier: 1, category: "knowledge-graph", rationale: "Core differentiator" },
714
+ // ── Tier 1 — Workflow + classification (4) ──────────────────────
715
+ { tool: "knit_classify_task", tier: 1, category: "workflow", rationale: "Tier router; called before any non-trivial task" },
716
+ { tool: "knit_build_context", tier: 1, category: "workflow", rationale: "Domain Context Object builder" },
717
+ { tool: "knit_get_workflow", tier: 1, category: "workflow", rationale: "On-demand phase depth fetcher" },
718
+ { tool: "knit_get_suggestions", tier: 1, category: "workflow", rationale: "Adaptive warnings from past patterns" },
719
+ // ── Tier 1 — False positives + reflection (3) ───────────────────
720
+ { tool: "knit_record_false_positive", tier: 1, category: "fp-reflection", rationale: "Universal \u2014 reviewer agents flag FPs on any project" },
721
+ { tool: "knit_get_false_positives", tier: 1, category: "fp-reflection", rationale: "Universal" },
722
+ { tool: "knit_reflect", tier: 1, category: "fp-reflection", rationale: 'Returns "not enough data" on sparse projects; always available' },
723
+ // ── Tier 1 — Protocol Guard config (2) ──────────────────────────
724
+ { tool: "knit_set_protocol_strictness", tier: 1, category: "protocol-config", rationale: "Universal \u2014 every install ships Protocol Guard" },
725
+ { tool: "knit_get_protocol_strictness", tier: 1, category: "protocol-config", rationale: "Universal" },
726
+ // ── Tier 1 — Diagnostics + meta (4) ─────────────────────────────
727
+ { tool: "knit_brain_status", tier: 1, category: "diagnostics", rationale: "Health + token-accounting; universal" },
728
+ { tool: "knit_list_features", tier: 1, category: "diagnostics", rationale: "The discoverability escape hatch itself" },
729
+ { tool: "knit_enable_feature", tier: 1, category: "diagnostics", rationale: "Flip on a Tier-2/3 feature flag \u2014 must always be reachable so hidden tools are recoverable" },
730
+ { tool: "knit_disable_feature", tier: 1, category: "diagnostics", rationale: "Flip off a previously-enabled feature flag" },
731
+ // ── Tier 2 — Team worktrees (9) ─────────────────────────────────
732
+ { tool: "knit_spawn_team_worktree", tier: 2, category: "teams", rationale: "Multi-domain parallel write orchestration", enable_via: 'knit_enable_feature("teams") or auto-exposed when \u22653 domains detected' },
733
+ { tool: "knit_finalize_team_worktree", tier: 2, category: "teams", rationale: "Merge/discard a team worktree", enable_via: 'knit_enable_feature("teams")' },
734
+ { tool: "knit_list_team_worktrees", tier: 2, category: "teams", rationale: "Active team worktree registry", enable_via: 'knit_enable_feature("teams")' },
735
+ { tool: "knit_define_team", tier: 2, category: "teams", rationale: "Custom team definition", enable_via: 'knit_enable_feature("teams")' },
736
+ { tool: "knit_get_teams", tier: 2, category: "teams", rationale: "List configured teams", enable_via: 'knit_enable_feature("teams")' },
737
+ { tool: "knit_get_team_prompt", tier: 2, category: "teams", rationale: "Team-specific agent prompt", enable_via: 'knit_enable_feature("teams")' },
738
+ { tool: "knit_start_team_review", tier: 2, category: "teams", rationale: "Begin a multi-team review", enable_via: 'knit_enable_feature("teams")' },
739
+ { tool: "knit_post_team_findings", tier: 2, category: "teams", rationale: "Submit team findings to shared board", enable_via: 'knit_enable_feature("teams")' },
740
+ { tool: "knit_get_board_summary", tier: 2, category: "teams", rationale: "Roll up team findings", enable_via: 'knit_enable_feature("teams")' },
741
+ // ── Tier 2 — Subagents (1) ──────────────────────────────────────
742
+ { tool: "knit_install_agent", tier: 2, category: "subagents", rationale: "VoltAgent subagent installer", enable_via: 'Auto-exposed when .claude/agents/ exists or knit_enable_feature("subagents")' },
743
+ // ── Tier 3 — Admin (1) ──────────────────────────────────────────
744
+ { tool: "knit_prune_sessions", tier: 3, category: "admin", rationale: "Manual session pruning; auto-prune handles this normally", enable_via: 'knit_enable_feature("admin")' },
745
+ // ── Tier 3 — One-time setup (1) ─────────────────────────────────
746
+ { tool: "knit_setup_project", tier: 3, category: "admin", rationale: "Initial bootstrap only; hidden after first run", enable_via: 'knit_enable_feature("admin") or pass through auto-init' }
747
+ ];
748
+ function isToolActive(info, shape) {
749
+ if (info.tier === 1) return true;
750
+ if (info.tier === 3) return shape.enabledFeatures.has("admin");
751
+ if (info.category === "teams") {
752
+ return shape.domainCount >= 3 || shape.enabledFeatures.has("teams");
753
+ }
754
+ if (info.category === "subagents") {
755
+ return shape.hasInstalledSubagents || shape.enabledFeatures.has("subagents");
756
+ }
757
+ return false;
758
+ }
759
+ function computeFeatureListing(shape) {
760
+ const active = [];
761
+ const available = [];
762
+ const by_category = {
763
+ memory: { active: 0, available: 0 },
764
+ "knowledge-graph": { active: 0, available: 0 },
765
+ workflow: { active: 0, available: 0 },
766
+ "fp-reflection": { active: 0, available: 0 },
767
+ "protocol-config": { active: 0, available: 0 },
768
+ diagnostics: { active: 0, available: 0 },
769
+ teams: { active: 0, available: 0 },
770
+ subagents: { active: 0, available: 0 },
771
+ admin: { active: 0, available: 0 }
772
+ };
773
+ for (const info of TOOL_REGISTRY) {
774
+ if (isToolActive(info, shape)) {
775
+ active.push({ name: info.tool, tier: info.tier, category: info.category });
776
+ by_category[info.category].active++;
777
+ } else {
778
+ available.push({
779
+ name: info.tool,
780
+ tier: info.tier,
781
+ category: info.category,
782
+ reason: info.rationale,
783
+ enable_via: info.enable_via
784
+ });
785
+ by_category[info.category].available++;
786
+ }
787
+ }
788
+ return {
789
+ active,
790
+ available,
791
+ totals: { active: active.length, available: available.length, total: TOOL_REGISTRY.length },
792
+ by_category
793
+ };
794
+ }
795
+ function isEnableableFeature(name) {
796
+ return name === "teams" || name === "subagents" || name === "admin";
797
+ }
798
+
695
799
  // src/engine/teams.ts
696
800
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, statSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
697
801
  import { dirname as dirname2 } from "path";
@@ -1059,6 +1163,112 @@ function handleBrainStatus(_params, brain) {
1059
1163
  instruction: "Brain is ready. Next: call knit_classify_task with the files you plan to touch to get your tier and phases."
1060
1164
  });
1061
1165
  }
1166
+ function loadEnabledFeatures(rootPath) {
1167
+ const enabled = /* @__PURE__ */ new Set();
1168
+ try {
1169
+ const path = featuresConfigPath(rootPath);
1170
+ if (!existsSync4(path)) return enabled;
1171
+ const parsed = JSON.parse(readFileSync4(path, "utf-8"));
1172
+ if (Array.isArray(parsed?.enabled)) {
1173
+ for (const name of parsed.enabled) {
1174
+ if (isEnableableFeature(name)) enabled.add(name);
1175
+ }
1176
+ }
1177
+ } catch {
1178
+ }
1179
+ return enabled;
1180
+ }
1181
+ function saveEnabledFeatures(rootPath, enabled) {
1182
+ const path = featuresConfigPath(rootPath);
1183
+ const payload = {
1184
+ enabled: [...enabled].sort(),
1185
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1186
+ };
1187
+ const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`;
1188
+ try {
1189
+ writeFileSync4(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
1190
+ renameSync2(tmpPath, path);
1191
+ } catch (err) {
1192
+ try {
1193
+ unlinkSync(tmpPath);
1194
+ } catch {
1195
+ }
1196
+ throw err;
1197
+ }
1198
+ }
1199
+ function detectProjectShape(brain) {
1200
+ return {
1201
+ hasAnalyzableCode: brain.knowledge.summary.totalFiles >= 10,
1202
+ domainCount: brain.config.domains?.length ?? 0,
1203
+ hasInstalledSubagents: existsSync4(projectAgentsDir(brain.rootPath)),
1204
+ sessionCount: sessionCount(brain.rootPath),
1205
+ enabledFeatures: loadEnabledFeatures(brain.rootPath)
1206
+ };
1207
+ }
1208
+ function handleListFeatures(_params, brain) {
1209
+ const shape = detectProjectShape(brain);
1210
+ const listing = computeFeatureListing(shape);
1211
+ return JSON.stringify({
1212
+ ...listing,
1213
+ project_shape: {
1214
+ has_analyzable_code: shape.hasAnalyzableCode,
1215
+ domain_count: shape.domainCount,
1216
+ has_installed_subagents: shape.hasInstalledSubagents,
1217
+ session_count: shape.sessionCount,
1218
+ enabled_features: [...shape.enabledFeatures]
1219
+ },
1220
+ instruction: 'If a tool you want is in `available` rather than `active`, the `enable_via` field tells you how to switch it on. Most commonly: knit_enable_feature({ feature: "teams" | "subagents" | "admin" }).'
1221
+ });
1222
+ }
1223
+ function handleEnableFeature(params, brain) {
1224
+ const feature = (params.feature || "").trim().toLowerCase();
1225
+ if (!isEnableableFeature(feature)) {
1226
+ return JSON.stringify({
1227
+ status: "error",
1228
+ error: `Invalid feature: "${params.feature}". Valid values: teams, subagents, admin.`
1229
+ });
1230
+ }
1231
+ const enabled = loadEnabledFeatures(brain.rootPath);
1232
+ const wasAlreadyOn = enabled.has(feature);
1233
+ enabled.add(feature);
1234
+ if (!wasAlreadyOn) {
1235
+ saveEnabledFeatures(brain.rootPath, enabled);
1236
+ }
1237
+ return JSON.stringify({
1238
+ status: wasAlreadyOn ? "already-enabled" : "enabled",
1239
+ feature,
1240
+ enabled_features: [...enabled].sort(),
1241
+ instruction: "New tools may now appear in tools/list on the next request. Call knit_list_features to confirm."
1242
+ });
1243
+ }
1244
+ function handleDisableFeature(params, brain) {
1245
+ const feature = (params.feature || "").trim().toLowerCase();
1246
+ if (!isEnableableFeature(feature)) {
1247
+ return JSON.stringify({
1248
+ status: "error",
1249
+ error: `Invalid feature: "${params.feature}". Valid values: teams, subagents, admin.`
1250
+ });
1251
+ }
1252
+ const enabled = loadEnabledFeatures(brain.rootPath);
1253
+ const wasOn = enabled.delete(feature);
1254
+ if (wasOn) {
1255
+ saveEnabledFeatures(brain.rootPath, enabled);
1256
+ }
1257
+ return JSON.stringify({
1258
+ status: wasOn ? "disabled" : "already-disabled",
1259
+ feature,
1260
+ enabled_features: [...enabled].sort(),
1261
+ instruction: "Disabled tools will be filtered out of tools/list on the next request."
1262
+ });
1263
+ }
1264
+ function detectsInquiryIntent(description) {
1265
+ if (!description) return false;
1266
+ const inquiryStart = /^\s*(what|where|how|why|when|which|who|can|could|should|does|do|is|are|will|would|tell\s+me|show\s+me|find|list|status\s+of|audit|explain|investigate|analyze|review|describe|summari[sz]e|inspect)\b/i;
1267
+ const inquiryVerb = /\b(audit|explain|investigate|analy[sz]e|review|examine|describe|summari[sz]e|enumerate|inspect)\b/i;
1268
+ const actionDirective = /\b(fix|implement|build|add|refactor|ship|deploy|write|create|update|modify|change|edit|migrate|rename|delete|remove|install|setup|configure|merge|publish|release|patch)\s+(this|that|it|the|a|an|all|every|my|our|your)\b/i;
1269
+ if (actionDirective.test(description)) return false;
1270
+ return inquiryStart.test(description) || inquiryVerb.test(description);
1271
+ }
1062
1272
  function handleClassifyTask(params, brain) {
1063
1273
  const rawFiles = (params.files_to_touch || "").split(",").map((f) => f.trim()).filter(Boolean);
1064
1274
  const files = rawFiles.filter((f) => f !== "unknown");
@@ -1069,6 +1279,27 @@ function handleClassifyTask(params, brain) {
1069
1279
  const importers = brain.reverseDeps[file] || [];
1070
1280
  if (importers.length >= 3) crossDomainRipple.push(`${file} is high-fanout (${importers.length} dependents)`);
1071
1281
  }
1282
+ if (detectsInquiryIntent(params.description || "")) {
1283
+ try {
1284
+ writeClassificationMarker(brain.rootPath, {
1285
+ turnId: `${Date.now()}-${process.pid}`,
1286
+ classifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
1287
+ tier: "inquiry",
1288
+ files
1289
+ });
1290
+ } catch {
1291
+ }
1292
+ return JSON.stringify({
1293
+ tier: "inquiry",
1294
+ affected_domains: [...domains],
1295
+ phases: [],
1296
+ files_count: files.length,
1297
+ cross_domain_ripple: crossDomainRipple,
1298
+ auto_plan_mode: false,
1299
+ instruction: "Read-only task. Answer directly \u2014 no plan mode, no LEARN unless something durable surfaced. If scope grows into writes, re-classify with knit_classify_task.",
1300
+ reasoning: `Inquiry: read-only intent detected${files.length > 0 ? `, ${files.length} file(s) referenced for context` : ""}`
1301
+ });
1302
+ }
1072
1303
  const isTypes = files.some((f) => f.includes("types") || f.includes("schema"));
1073
1304
  const isAuth = files.some((f) => f.includes("auth") || f.includes("security"));
1074
1305
  const isNewProject = files.length === 0 || rawFiles.includes("unknown");
@@ -1085,14 +1316,21 @@ function handleClassifyTask(params, brain) {
1085
1316
  });
1086
1317
  } catch {
1087
1318
  }
1088
- return JSON.stringify({
1319
+ const verbose = params.verbose === "true" || params.verbose === "1";
1320
+ const base = {
1089
1321
  tier: tier2,
1090
1322
  affected_domains: [...domains],
1091
1323
  phases: phases2,
1324
+ auto_plan_mode: tier2 === "complex",
1325
+ instruction
1326
+ };
1327
+ if (!verbose) {
1328
+ return JSON.stringify(base);
1329
+ }
1330
+ return JSON.stringify({
1331
+ ...base,
1092
1332
  files_count: files.length,
1093
1333
  cross_domain_ripple: crossDomainRipple,
1094
- auto_plan_mode: tier2 === "complex",
1095
- instruction,
1096
1334
  reasoning: tier2 === "complex" ? `Complex: ${domains.size} domains affected${isTypes ? ", touches shared types" : ""}${isAuth ? ", security-sensitive" : ""}` : tier2 === "standard" ? `Standard: ${domains.size} domain(s), ${files.length} file(s)` : `Trivial: 1 domain, simple change`
1097
1335
  });
1098
1336
  }
@@ -1493,7 +1731,15 @@ function handleSearchGlobalLearnings(params, _brain) {
1493
1731
  instruction: matches.length === 0 ? "No cross-project matches. This area might be new across all your projects." : `Found ${matches.length} cross-project learning(s). Review before duplicating work.`
1494
1732
  });
1495
1733
  }
1496
- function handleLoadSession(_params, brain) {
1734
+ function parseLoadSessionInclude(raw) {
1735
+ if (!raw) return /* @__PURE__ */ new Set();
1736
+ return new Set(
1737
+ raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
1738
+ );
1739
+ }
1740
+ function handleLoadSession(params, brain) {
1741
+ const include = parseLoadSessionInclude(params.include);
1742
+ const wantAll = include.has("all");
1497
1743
  const root = brain.rootPath;
1498
1744
  const sessionsFile = sessionsLogPath(root);
1499
1745
  let lastSession = null;
@@ -1502,45 +1748,67 @@ function handleLoadSession(_params, brain) {
1502
1748
  const sessions = content.split(/^## Session/m).slice(1);
1503
1749
  if (sessions.length > 0) {
1504
1750
  const last = sessions[sessions.length - 1].trim();
1505
- lastSession = last.slice(0, 300);
1751
+ lastSession = last.slice(0, 200);
1506
1752
  }
1507
1753
  }
1508
1754
  const handoffPath = join2(root, "handoff.md");
1509
1755
  let handoff2 = null;
1510
1756
  if (existsSync4(handoffPath)) {
1511
- handoff2 = readFileSync4(handoffPath, "utf-8").slice(0, 2e3);
1512
- }
1513
- const topLearnings = brain.knowledgeBase.entries.filter((e) => e.accessCount > 0).sort((a, b) => b.accessCount - a.accessCount).slice(0, 5).map((e) => ({ summary: e.summary, lesson: e.lesson, tags: e.tags, accessed: e.accessCount }));
1514
- const fps = brain.knowledgeBase.entries.filter((e) => e.tags.includes("#false-positive")).map((e) => ({ summary: e.summary, lesson: e.lesson }));
1515
- const teamsFile = teamsPath(root);
1516
- let teams = [];
1517
- if (existsSync4(teamsFile)) {
1518
- try {
1519
- const t = JSON.parse(readFileSync4(teamsFile, "utf-8"));
1520
- teams = t.map((team) => team.name);
1521
- } catch {
1522
- }
1757
+ handoff2 = readFileSync4(handoffPath, "utf-8").slice(0, 1500);
1523
1758
  }
1524
- const knowledge = {
1759
+ const learningsLimit = wantAll || include.has("full_learnings") ? 5 : 3;
1760
+ const topLearnings = brain.knowledgeBase.entries.filter((e) => e.accessCount > 0).sort((a, b) => b.accessCount - a.accessCount).slice(0, learningsLimit).map((e) => ({ summary: e.summary, lesson: e.lesson, tags: e.tags, accessed: e.accessCount }));
1761
+ const fpsLimit = wantAll || include.has("full_learnings") ? 50 : 5;
1762
+ const fps = brain.knowledgeBase.entries.filter((e) => e.tags.includes("#false-positive")).slice(0, fpsLimit).map((e) => ({ summary: e.summary, lesson: e.lesson }));
1763
+ const wantFullKnowledge = wantAll || include.has("full_knowledge");
1764
+ const knowledge = wantFullKnowledge ? {
1525
1765
  files: brain.knowledge.summary.totalFiles,
1526
1766
  imports: Object.keys(brain.knowledge.importGraph).length,
1527
1767
  high_fanout: brain.knowledge.summary.highFanoutFiles,
1528
1768
  untested: brain.knowledge.summary.untestedFiles.slice(0, 5)
1769
+ } : {
1770
+ files: brain.knowledge.summary.totalFiles,
1771
+ imports: Object.keys(brain.knowledge.importGraph).length,
1772
+ high_fanout_count: brain.knowledge.summary.highFanoutFiles.length,
1773
+ untested_count: brain.knowledge.summary.untestedFiles.length
1529
1774
  };
1530
- const metrics = {
1531
- total_sessions: brain.knowledgeBase.metrics.totalSessions,
1532
- total_learnings: brain.knowledgeBase.entries.length,
1533
- cache_hits: brain.knowledgeBase.metrics.cacheHits
1534
- };
1535
- const recentSessions = getRecentSessions(root, 3).map((s) => ({
1536
- date: s.date,
1537
- branch: s.branch ?? null,
1538
- summary: s.summary ?? "",
1539
- tags: s.tags ?? [],
1540
- outcome: s.outcome
1541
- }));
1542
- const patterns = reflect(brain.knowledgeBase).slice(0, 3).map((p) => ({ type: p.type, description: p.description, confidence: p.confidence }));
1543
- return JSON.stringify({
1775
+ let teams;
1776
+ if (wantAll || include.has("teams")) {
1777
+ const teamsFile = teamsPath(root);
1778
+ if (existsSync4(teamsFile)) {
1779
+ try {
1780
+ const t = JSON.parse(readFileSync4(teamsFile, "utf-8"));
1781
+ teams = t.map((team) => team.name);
1782
+ } catch {
1783
+ teams = [];
1784
+ }
1785
+ } else {
1786
+ teams = [];
1787
+ }
1788
+ }
1789
+ let metrics;
1790
+ if (wantAll || include.has("metrics")) {
1791
+ metrics = {
1792
+ total_sessions: brain.knowledgeBase.metrics.totalSessions,
1793
+ total_learnings: brain.knowledgeBase.entries.length,
1794
+ cache_hits: brain.knowledgeBase.metrics.cacheHits
1795
+ };
1796
+ }
1797
+ let recentSessions;
1798
+ if (wantAll || include.has("recent_sessions")) {
1799
+ recentSessions = getRecentSessions(root, 3).map((s) => ({
1800
+ date: s.date,
1801
+ branch: s.branch ?? null,
1802
+ summary: s.summary ?? "",
1803
+ tags: s.tags ?? [],
1804
+ outcome: s.outcome
1805
+ }));
1806
+ }
1807
+ let patterns;
1808
+ if (wantAll || include.has("patterns")) {
1809
+ patterns = reflect(brain.knowledgeBase).slice(0, 3).map((p) => ({ type: p.type, description: p.description, confidence: p.confidence }));
1810
+ }
1811
+ const response = {
1544
1812
  session_context: {
1545
1813
  last_session: lastSession,
1546
1814
  handoff: handoff2,
@@ -1549,16 +1817,17 @@ function handleLoadSession(_params, brain) {
1549
1817
  intelligence: {
1550
1818
  top_learnings: topLearnings,
1551
1819
  false_positives: fps,
1552
- patterns
1820
+ ...patterns !== void 0 ? { patterns } : {}
1553
1821
  },
1554
1822
  project: {
1555
1823
  knowledge,
1556
- teams,
1557
- metrics,
1558
- recent_sessions: recentSessions
1824
+ ...teams !== void 0 ? { teams } : {},
1825
+ ...metrics !== void 0 ? { metrics } : {},
1826
+ ...recentSessions !== void 0 ? { recent_sessions: recentSessions } : {}
1559
1827
  },
1560
- 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 available. ${fps.length} false positives to suppress. Call knit_classify_task to begin.` : recentSessions.length > 0 ? `Session loaded. ${recentSessions.length} recent sessions on file. Call knit_classify_task to begin.` : "Fresh brain \u2014 no past learnings yet. Call knit_classify_task to begin your first task."
1561
- });
1828
+ 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."
1829
+ };
1830
+ return JSON.stringify(response);
1562
1831
  }
1563
1832
  function handleSaveSessionSummary(params, brain) {
1564
1833
  const validOutcomes = ["shipped", "wip", "failed", "unknown"];
@@ -1741,73 +2010,73 @@ function getToolDefinitions() {
1741
2010
  // ── Query (read the brain) ───────────────────────────────────
1742
2011
  {
1743
2012
  name: "knit_query_imports",
1744
- description: "Who imports this file? Returns the reverse dependency list \u2014 call before editing to know the blast radius.",
2013
+ description: "Reverse deps for a file \u2014 who imports it.",
1745
2014
  inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Relative file path." } }, required: ["file_path"] }
1746
2015
  },
1747
2016
  {
1748
2017
  name: "knit_query_dependents",
1749
- description: "What does this file import? Returns the file's own dependencies.",
2018
+ description: "Forward deps for a file \u2014 what it imports.",
1750
2019
  inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Relative file path." } }, required: ["file_path"] }
1751
2020
  },
1752
2021
  {
1753
2022
  name: "knit_query_exports",
1754
- description: "What does this file expose? Functions, classes, interfaces, types, constants.",
2023
+ description: "Exports from a file: functions, classes, types, constants.",
1755
2024
  inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Relative file path." } }, required: ["file_path"] }
1756
2025
  },
1757
2026
  {
1758
2027
  name: "knit_query_tests",
1759
- description: 'Is this file tested? Or pass filter="untested" to list all untested files.',
2028
+ description: 'Tests for a file, or list all untested files with filter="untested".',
1760
2029
  inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Relative file path (optional)." }, filter: { type: "string", description: '"untested" to list all untested files.' } } }
1761
2030
  },
1762
2031
  {
1763
2032
  name: "knit_find_fanout",
1764
- description: "High-fanout files \u2014 imported by many others. These are the contracts; change carefully.",
2033
+ description: "High-fanout files \u2014 imported by many others.",
1765
2034
  inputSchema: { type: "object", properties: { min_importers: { type: "string", description: "Minimum importers to qualify (default: 3)." } } }
1766
2035
  },
1767
2036
  {
1768
2037
  name: "knit_search_learnings",
1769
- description: "Search past learnings by domain tag. Returns prior lessons and mistakes to avoid.",
2038
+ description: "Search learnings by domain tag.",
1770
2039
  inputSchema: { type: "object", properties: { domains: { type: "string", description: "Comma-separated domain tags." } }, required: ["domains"] }
1771
2040
  },
1772
2041
  {
1773
2042
  name: "knit_get_false_positives",
1774
- description: "Confirmed non-issues. Pass to review agents so they don't re-flag them.",
2043
+ description: "List confirmed non-issues to suppress in review prompts.",
1775
2044
  inputSchema: { type: "object", properties: {} }
1776
2045
  },
1777
2046
  {
1778
2047
  name: "knit_brain_status",
1779
- description: "Brain health + token-accounting: learnings, hit rate, CLAUDE.md size, session count.",
2048
+ description: "Brain health: learnings, hit rate, CLAUDE.md size, session count.",
1780
2049
  inputSchema: { type: "object", properties: {} }
1781
2050
  },
1782
2051
  // ── Update (write to the brain) ──────────────────────────────
1783
2052
  {
1784
2053
  name: "knit_classify_task",
1785
- description: "Call first on every task. Classifies tier (trivial/standard/complex), returns phases + domains + auto plan mode flag. Also triggers project auto-init.",
1786
- 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." } }, required: ["files_to_touch"] }
2054
+ description: "Call first on every task. Returns tier (inquiry/trivial/standard/complex), phases, auto_plan_mode.",
2055
+ 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).' } }, required: ["files_to_touch"] }
1787
2056
  },
1788
2057
  {
1789
2058
  name: "knit_build_context",
1790
- description: "Build a context object for the current task: domains, ripple effects, pitfalls, false positives.",
2059
+ description: "Build the Domain Context Object: affected domains, ripple, pitfalls, false positives.",
1791
2060
  inputSchema: { type: "object", properties: { files_to_touch: { type: "string", description: "Comma-separated files." } }, required: ["files_to_touch"] }
1792
2061
  },
1793
2062
  {
1794
2063
  name: "knit_record_learning",
1795
- description: "Record a non-obvious, reusable insight. Quality check first: would session N+1 searching this tag be glad it exists? If no, skip.",
2064
+ description: "Record a non-obvious, reusable insight. Skip if a future search wouldn't be glad it exists.",
1796
2065
  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"] }
1797
2066
  },
1798
2067
  {
1799
2068
  name: "knit_record_false_positive",
1800
- description: "Mark a finding as a confirmed non-issue so future review agents stop re-flagging it.",
2069
+ description: "Mark a finding as confirmed non-issue so future reviewers suppress it.",
1801
2070
  inputSchema: { type: "object", properties: { summary: { type: "string", description: "What was flagged." }, reason: { type: "string", description: "Why it's not a real issue." }, tags: { type: "string", description: "Domain tags." } }, required: ["summary", "reason"] }
1802
2071
  },
1803
2072
  {
1804
2073
  name: "knit_save_handoff",
1805
- description: "Save state for the next session when context degrades. failed_attempts is the load-bearing field.",
2074
+ description: "Save state for the next session. failed_attempts is the load-bearing field.",
1806
2075
  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"] }
1807
2076
  },
1808
2077
  {
1809
2078
  name: "knit_setup_project",
1810
- description: "Describe a project (especially non-code: research, legal, marketing). Generates domain-specific teams.",
2079
+ description: "Bootstrap domain teams for a non-code project (research/legal/marketing).",
1811
2080
  inputSchema: {
1812
2081
  type: "object",
1813
2082
  properties: {
@@ -1822,12 +2091,12 @@ function getToolDefinitions() {
1822
2091
  // ── Teams (parallel review board) ────────────────────────────
1823
2092
  {
1824
2093
  name: "knit_get_teams",
1825
- description: "List agent teams configured for this project.",
2094
+ description: "List teams configured for this project.",
1826
2095
  inputSchema: { type: "object", properties: {} }
1827
2096
  },
1828
2097
  {
1829
2098
  name: "knit_define_team",
1830
- description: "Create or update a custom agent team.",
2099
+ description: "Create or update a custom team.",
1831
2100
  inputSchema: { type: "object", properties: { name: { type: "string", description: "Team name." }, role: { type: "string", description: "Team role." }, focus: { type: "string", description: "Team focus area." }, agents: { type: "string", description: "Comma-separated agent types." }, file_patterns: { type: "string", description: "Comma-separated globs." }, checklist: { type: "string", description: "Pipe-separated review items." } }, required: ["name", "role", "focus"] }
1832
2101
  },
1833
2102
  {
@@ -1837,12 +2106,12 @@ function getToolDefinitions() {
1837
2106
  },
1838
2107
  {
1839
2108
  name: "knit_get_team_prompt",
1840
- description: "Get the prompt for a team, including other teams' findings.",
2109
+ description: "Get a team's prompt with other teams' findings included.",
1841
2110
  inputSchema: { type: "object", properties: { team_name: { type: "string", description: "Team name." }, files_to_review: { type: "string", description: "Comma-separated files." } }, required: ["team_name"] }
1842
2111
  },
1843
2112
  {
1844
2113
  name: "knit_post_team_findings",
1845
- description: "Post a team's findings to the shared board.",
2114
+ description: "Post team findings to the shared board.",
1846
2115
  inputSchema: { type: "object", properties: { team_name: { type: "string", description: "Team posting." }, findings: { type: "string", description: "JSON array of findings." } }, required: ["team_name", "findings"] }
1847
2116
  },
1848
2117
  {
@@ -1853,12 +2122,12 @@ function getToolDefinitions() {
1853
2122
  // ── Session memory ───────────────────────────────────────────
1854
2123
  {
1855
2124
  name: "knit_load_session",
1856
- description: "Call at session start. Returns last sessions, handoff, top learnings, false positives, teams, project knowledge in one round trip.",
1857
- inputSchema: { type: "object", properties: {} }
2125
+ description: "Call at session start. Returns handoff, top learnings, false positives by default. Opt in to more via include=patterns,teams,metrics,recent_sessions,full_learnings,full_knowledge,all.",
2126
+ inputSchema: { type: "object", properties: { include: { type: "string", description: "Comma-separated optional sections." } } }
1858
2127
  },
1859
2128
  {
1860
2129
  name: "knit_save_session_summary",
1861
- description: "Opt-in. Record a narrative summary if this session accomplished something a future session would search for. Quality check first.",
2130
+ description: "Opt-in. Record a session summary a future search would want to find.",
1862
2131
  inputSchema: {
1863
2132
  type: "object",
1864
2133
  properties: {
@@ -1873,7 +2142,7 @@ function getToolDefinitions() {
1873
2142
  },
1874
2143
  {
1875
2144
  name: "knit_prune_sessions",
1876
- description: "Prune entries older than max_age_days (default 90) from this project's sessions.jsonl. Atomic rewrite. Also runs automatically on auto-init.",
2145
+ description: "Prune sessions older than max_age_days (default 90). Atomic rewrite.",
1877
2146
  inputSchema: {
1878
2147
  type: "object",
1879
2148
  properties: {
@@ -1883,7 +2152,7 @@ function getToolDefinitions() {
1883
2152
  },
1884
2153
  {
1885
2154
  name: "knit_search_sessions",
1886
- description: "Search past sessions by free text over summary + tags + branch. Check before duplicating prior work.",
2155
+ description: 'Search past sessions by free text. "Have I done this before?"',
1887
2156
  inputSchema: {
1888
2157
  type: "object",
1889
2158
  properties: {
@@ -1896,7 +2165,7 @@ function getToolDefinitions() {
1896
2165
  // ── Workflow on demand ───────────────────────────────────────
1897
2166
  {
1898
2167
  name: "knit_get_workflow",
1899
- description: "Fetch protocol depth for one phase. Sections: overview, tier, phases, research, ideate, plan, execute, optimize, review, tdd, learn, handoff, ship, tools. Omit phase to list all.",
2168
+ description: "Fetch one workflow section: overview, tier, phases, research, ideate, plan, execute, optimize, review, tdd, learn, handoff, ship, tools. Omit phase to list all.",
1900
2169
  inputSchema: {
1901
2170
  type: "object",
1902
2171
  properties: {
@@ -1907,7 +2176,7 @@ function getToolDefinitions() {
1907
2176
  // ── Parallel team worktrees ──────────────────────────────────
1908
2177
  {
1909
2178
  name: "knit_spawn_team_worktree",
1910
- description: "Create a git worktree for a team. Multiple agents within the team can work in parallel inside it without colliding with other teams.",
2179
+ description: "Create a git worktree for a team so they can write in parallel without colliding.",
1911
2180
  inputSchema: {
1912
2181
  type: "object",
1913
2182
  properties: {
@@ -1919,7 +2188,7 @@ function getToolDefinitions() {
1919
2188
  },
1920
2189
  {
1921
2190
  name: "knit_list_team_worktrees",
1922
- description: "List active team worktrees. Pass include_finalized=true for full history.",
2191
+ description: "List active team worktrees. include_finalized=true for full history.",
1923
2192
  inputSchema: {
1924
2193
  type: "object",
1925
2194
  properties: {
@@ -1930,7 +2199,7 @@ function getToolDefinitions() {
1930
2199
  // ── Cross-project learnings (Model C — global pool) ─────────
1931
2200
  {
1932
2201
  name: "knit_record_global_learning",
1933
- description: "Opt-in. Record a learning to the cross-project pool when the insight generalizes beyond this project (e.g., Stripe webhook signature rules). Per-project knit_record_learning stays primary.",
2202
+ description: "Opt-in. Record a learning to the cross-project pool when it generalizes beyond this project.",
1934
2203
  inputSchema: {
1935
2204
  type: "object",
1936
2205
  properties: {
@@ -1944,7 +2213,7 @@ function getToolDefinitions() {
1944
2213
  },
1945
2214
  {
1946
2215
  name: "knit_search_global_learnings",
1947
- description: "Search the cross-project learnings pool. Use to check whether a similar lesson has been recorded in any project on this machine.",
2216
+ description: "Search the cross-project learnings pool across all projects on this machine.",
1948
2217
  inputSchema: {
1949
2218
  type: "object",
1950
2219
  properties: {
@@ -1957,17 +2226,17 @@ function getToolDefinitions() {
1957
2226
  // ── Pattern reflection (now backed by Model C, useful with ≥3 entries) ──
1958
2227
  {
1959
2228
  name: "knit_reflect",
1960
- description: "Detect patterns across recorded learnings (per-project + global pool). Returns recurring themes, repeated failures, domain co-occurrences. Needs \u22653 learnings to surface anything meaningful.",
2229
+ description: "Detect patterns across learnings. Needs \u22653 entries to surface anything.",
1961
2230
  inputSchema: { type: "object", properties: {} }
1962
2231
  },
1963
2232
  {
1964
2233
  name: "knit_get_suggestions",
1965
- description: 'Adaptive suggestions for the current task based on past patterns in given domains. "Based on history, watch out for X."',
2234
+ description: 'Adaptive warnings from past patterns. "Based on history, watch out for X."',
1966
2235
  inputSchema: { type: "object", properties: { domains: { type: "string", description: "Comma-separated domains for this task." } }, required: ["domains"] }
1967
2236
  },
1968
2237
  {
1969
2238
  name: "knit_install_agent",
1970
- description: "Install or refresh one subagent. Writes <project>/.claude/agents/knit-<name>.md, personalized with project context. Use mid-session if a team needs an agent that isn't on disk yet.",
2239
+ description: "Install one VoltAgent subagent into .claude/agents/, personalized with project context.",
1971
2240
  inputSchema: {
1972
2241
  type: "object",
1973
2242
  properties: {
@@ -1979,7 +2248,7 @@ function getToolDefinitions() {
1979
2248
  },
1980
2249
  {
1981
2250
  name: "knit_finalize_team_worktree",
1982
- description: "Merge or discard a team's worktree. Merge surfaces conflict files without destroying the worktree on failure.",
2251
+ description: "Merge or discard a team's worktree. Surfaces conflicts without destroying the worktree.",
1983
2252
  inputSchema: {
1984
2253
  type: "object",
1985
2254
  properties: {
@@ -1992,16 +2261,51 @@ function getToolDefinitions() {
1992
2261
  // ── Protocol Guard ───────────────────────────────────────────
1993
2262
  {
1994
2263
  name: "knit_set_protocol_strictness",
1995
- description: "Set Protocol Guard strictness for this project. off = no checks. warn = reminder only (default). block = hard-fail Edit/Write without prior knit_classify_task.",
2264
+ description: "Set Protocol Guard strictness: off | warn (default) | block.",
1996
2265
  inputSchema: { type: "object", properties: { level: { type: "string", description: "One of: off | warn | block." } }, required: ["level"] }
1997
2266
  },
1998
2267
  {
1999
2268
  name: "knit_get_protocol_strictness",
2000
- description: "Read current Protocol Guard strictness level for this project.",
2269
+ description: "Read current Protocol Guard strictness.",
2270
+ inputSchema: { type: "object", properties: {} }
2271
+ },
2272
+ // ── Meta — feature discoverability ───────────────────────────
2273
+ {
2274
+ name: "knit_list_features",
2275
+ description: "List active vs hidden Knit tools and why. Call when a tool you expect isn't available.",
2001
2276
  inputSchema: { type: "object", properties: {} }
2277
+ },
2278
+ {
2279
+ name: "knit_enable_feature",
2280
+ description: "Enable a Tier-2/3 feature flag (teams, subagents, admin). Persisted.",
2281
+ inputSchema: {
2282
+ type: "object",
2283
+ properties: { feature: { type: "string", description: "One of: teams, subagents, admin." } },
2284
+ required: ["feature"]
2285
+ }
2286
+ },
2287
+ {
2288
+ name: "knit_disable_feature",
2289
+ description: "Disable a feature flag. Auto-exposed tools stay visible regardless.",
2290
+ inputSchema: {
2291
+ type: "object",
2292
+ properties: { feature: { type: "string", description: "One of: teams, subagents, admin." } },
2293
+ required: ["feature"]
2294
+ }
2002
2295
  }
2003
2296
  ];
2004
2297
  }
2298
+ function getActiveToolDefinitions(shape) {
2299
+ const all = getToolDefinitions();
2300
+ if (!shape) return all;
2301
+ const activeNames = new Set(
2302
+ TOOL_REGISTRY.filter((info) => isToolActive(info, shape)).map((info) => info.tool)
2303
+ );
2304
+ return all.filter((def) => activeNames.has(def.name));
2305
+ }
2306
+ function getActiveToolDefinitionsForBrain(brain) {
2307
+ return getActiveToolDefinitions(detectProjectShape(brain));
2308
+ }
2005
2309
  var handlers = {
2006
2310
  knit_query_imports: handleQueryImports,
2007
2311
  knit_query_dependents: handleQueryDependents,
@@ -2037,7 +2341,10 @@ var handlers = {
2037
2341
  knit_get_suggestions: handleGetSuggestions,
2038
2342
  knit_install_agent: handleInstallAgent,
2039
2343
  knit_set_protocol_strictness: handleSetProtocolStrictness,
2040
- knit_get_protocol_strictness: handleGetProtocolStrictness
2344
+ knit_get_protocol_strictness: handleGetProtocolStrictness,
2345
+ knit_list_features: handleListFeatures,
2346
+ knit_enable_feature: handleEnableFeature,
2347
+ knit_disable_feature: handleDisableFeature
2041
2348
  };
2042
2349
  function handleToolCall(toolName, params, brain) {
2043
2350
  if (params.file_path) {
@@ -2059,6 +2366,8 @@ function handleToolCall(toolName, params, brain) {
2059
2366
  return handler(params, brain);
2060
2367
  }
2061
2368
  export {
2369
+ getActiveToolDefinitions,
2370
+ getActiveToolDefinitionsForBrain,
2062
2371
  getToolDefinitions,
2063
2372
  handleToolCall
2064
2373
  };