knodin 0.7.5 → 0.7.6

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.
Files changed (105) hide show
  1. package/README.md +4 -1
  2. package/dist/bin/cli.js +108 -1859
  3. package/dist/bin/launcher.js +3 -10
  4. package/dist/chunks/chunk-DMQAGX77.js +654 -0
  5. package/dist/chunks/chunk-F4Z3Z766.js +4 -0
  6. package/dist/chunks/chunk-SIJAQVSX.js +3 -0
  7. package/dist/chunks/chunk-X6M4HUUE.js +2 -0
  8. package/dist/chunks/chunk-YPRMY2LP.js +8 -0
  9. package/dist/chunks/pure-compression-cli-4TA2TQD5.js +5 -0
  10. package/dist/chunks/server-7EDF4CBY.js +14 -0
  11. package/dist/chunks/structural-fast-path-KD5KQSPX.js +4 -0
  12. package/dist/src/init-progress-worker.js +2 -106
  13. package/dist/src/repair-progress-worker.js +2 -120
  14. package/docs/INSTALLATION.md +17 -1
  15. package/docs/releases/0.7.6.md +25 -0
  16. package/package.json +1 -111
  17. package/dist/src/agent-integration.js +0 -238
  18. package/dist/src/artifact-refresh.js +0 -81
  19. package/dist/src/cli-args.js +0 -274
  20. package/dist/src/cli-model.js +0 -358
  21. package/dist/src/codeflow-replay.js +0 -80
  22. package/dist/src/compact-structural.js +0 -96
  23. package/dist/src/competitive-cold-mcp.js +0 -40
  24. package/dist/src/competitive-constraints.js +0 -20
  25. package/dist/src/competitive-manifest.js +0 -411
  26. package/dist/src/competitive-measurement.js +0 -183
  27. package/dist/src/competitive-runner.js +0 -487
  28. package/dist/src/competitive-sandbox.js +0 -108
  29. package/dist/src/context-export.js +0 -422
  30. package/dist/src/context.js +0 -102
  31. package/dist/src/diagnostics.js +0 -449
  32. package/dist/src/docs-sections.js +0 -141
  33. package/dist/src/doctor.js +0 -380
  34. package/dist/src/engine/ann-hnsw.js +0 -271
  35. package/dist/src/engine/embeddings.js +0 -193
  36. package/dist/src/engine/file-walker.js +0 -43
  37. package/dist/src/engine/git-history.js +0 -289
  38. package/dist/src/engine/index.js +0 -13377
  39. package/dist/src/engine/perf.js +0 -115
  40. package/dist/src/engine/prune.js +0 -112
  41. package/dist/src/engine/scip-import.js +0 -408
  42. package/dist/src/engine/source-policy.js +0 -69
  43. package/dist/src/engine/sqlite.js +0 -71
  44. package/dist/src/engine/symbol-delete.js +0 -58
  45. package/dist/src/execution-profile.js +0 -203
  46. package/dist/src/failure-diagnosis.js +0 -649
  47. package/dist/src/fleet.js +0 -7
  48. package/dist/src/git-executable.js +0 -31
  49. package/dist/src/graph-query-health.js +0 -115
  50. package/dist/src/hook-manager-integration.js +0 -156
  51. package/dist/src/index-activity.js +0 -125
  52. package/dist/src/init-progress.js +0 -155
  53. package/dist/src/init.js +0 -1254
  54. package/dist/src/lifecycle-health.js +0 -250
  55. package/dist/src/lsp-readonly.js +0 -217
  56. package/dist/src/output-compression.js +0 -629
  57. package/dist/src/output-telemetry.js +0 -363
  58. package/dist/src/pr-triage.js +0 -638
  59. package/dist/src/progressive-evidence.js +0 -473
  60. package/dist/src/pure-compression-cli.js +0 -101
  61. package/dist/src/relationship-adapters.js +0 -370
  62. package/dist/src/release-attestation.js +0 -533
  63. package/dist/src/release-preflight.js +0 -510
  64. package/dist/src/repair-progress.js +0 -262
  65. package/dist/src/repository-init-process.js +0 -173
  66. package/dist/src/repository-management.js +0 -1231
  67. package/dist/src/response-budget.js +0 -194
  68. package/dist/src/server.js +0 -172
  69. package/dist/src/structural-fast-path.js +0 -338
  70. package/dist/src/structural-snapshot.js +0 -33
  71. package/dist/src/system-config.js +0 -615
  72. package/dist/src/terminal-help.js +0 -83
  73. package/dist/src/tools/knodin-tools.js +0 -1529
  74. package/dist/src/update-ceremony.js +0 -158
  75. package/dist/src/update-policy.js +0 -944
  76. package/dist/src/update-trust.js +0 -503
  77. package/dist/src/version.js +0 -13
  78. package/dist/src/visualization.js +0 -162
  79. package/dist/src/wait-for-fresh.js +0 -98
  80. package/dist/src/worktree-lifecycle.js +0 -231
  81. package/docs/CONTAINED-EXECUTION.md +0 -77
  82. package/docs/DIAGNOSTICS.md +0 -45
  83. package/docs/GIT-HISTORY-REVIEW.md +0 -39
  84. package/docs/PROGRESSIVE-EVIDENCE.md +0 -37
  85. package/docs/PT-ACCESS-RECOMMENDATION.md +0 -91
  86. package/docs/RELEASE-0.3-EVIDENCE.md +0 -73
  87. package/docs/SCIP-IMPORT.md +0 -57
  88. package/docs/SIGNED-UPDATES.md +0 -151
  89. package/docs/TELEMETRY.md +0 -46
  90. package/docs/assets/knodin-favicon.svg +0 -4
  91. package/docs/releases/0.3.0.md +0 -46
  92. package/docs/releases/0.4.0.md +0 -68
  93. package/docs/releases/0.4.1.md +0 -28
  94. package/docs/releases/0.4.2.md +0 -27
  95. package/docs/releases/0.4.3.md +0 -23
  96. package/docs/releases/0.5.0.md +0 -29
  97. package/docs/releases/0.5.1.md +0 -17
  98. package/docs/releases/0.6.0.md +0 -18
  99. package/docs/releases/0.7.0.md +0 -24
  100. package/docs/releases/0.7.1.md +0 -21
  101. package/docs/releases/0.7.2.md +0 -21
  102. package/docs/releases/0.7.3.md +0 -23
  103. package/docs/releases/0.7.4.md +0 -17
  104. package/docs/releases/0.7.5.md +0 -20
  105. package/schemas/release-attestation-v1.schema.json +0 -210
@@ -1,194 +0,0 @@
1
- const encoder = new TextEncoder();
2
- export const MIN_RESPONSE_BUDGET_BYTES = 256;
3
- const MAX_REPORTED_TOTAL_PATHS = 128;
4
- function bytes(value) {
5
- return encoder.encode(JSON.stringify(value)).byteLength;
6
- }
7
- function positiveInt(value, fallback) {
8
- return value == null || !Number.isFinite(value) ? fallback : Math.max(1, Math.trunc(value));
9
- }
10
- function collectArrays(value, path = "$") {
11
- if (Array.isArray(value)) {
12
- return [{ path, value }, ...value.flatMap((item, i) => collectArrays(item, `${path}/${i}`))];
13
- }
14
- if (!value || typeof value !== "object")
15
- return [];
16
- return Object.entries(value).flatMap(([key, child]) => collectArrays(child, `${path}/${key}`));
17
- }
18
- function collectStrings(value, path = "$") {
19
- if (!value || typeof value !== "object")
20
- return [];
21
- const entries = Array.isArray(value) ? value.entries() : Object.entries(value);
22
- const found = [];
23
- for (const [key, child] of entries) {
24
- const childPath = `${path}/${String(key)}`;
25
- if (typeof child === "string") {
26
- found.push({ parent: value, key, path: childPath, value: child });
27
- }
28
- else {
29
- found.push(...collectStrings(child, childPath));
30
- }
31
- }
32
- return found;
33
- }
34
- const PROTECTED_CONTRACT_KEYS = new Set([
35
- "identity",
36
- "symbol",
37
- "file",
38
- "filePath",
39
- "error",
40
- "status",
41
- "operation",
42
- "kind",
43
- "name",
44
- "target",
45
- "to",
46
- ]);
47
- function isTruncatableDetail(entry) {
48
- return typeof entry.key !== "string" || !PROTECTED_CONTRACT_KEYS.has(entry.key);
49
- }
50
- /**
51
- * Applies limits to the value that is actually JSON-serialized. Token accounting
52
- * intentionally uses the deterministic local estimate of four UTF-8 bytes/token;
53
- * callers can therefore reproduce the ceiling without installing a tokenizer.
54
- */
55
- export function applyResponseBudget(value, operation, request, defaults) {
56
- const byteRequest = positiveInt(request.bytes, defaults.bytes);
57
- const tokenLimit = positiveInt(request.tokens, defaults.tokens);
58
- const byteLimit = Math.min(byteRequest, tokenLimit * 4);
59
- if (byteLimit < MIN_RESPONSE_BUDGET_BYTES) {
60
- throw new RangeError(`response budget must allow at least ${MIN_RESPONSE_BUDGET_BYTES} serialized bytes (64 estimated tokens)`);
61
- }
62
- const itemLimit = positiveInt(request.items, defaults.items);
63
- const payload = structuredClone(value);
64
- const root = Array.isArray(payload)
65
- ? { results: payload }
66
- : payload && typeof payload === "object"
67
- ? payload
68
- : { result: payload };
69
- const totals = {};
70
- let truncated = false;
71
- const initialArrays = collectArrays(root);
72
- const reportedTotals = [...initialArrays]
73
- .sort((left, right) => left.path.split("/").length - right.path.split("/").length ||
74
- left.path.localeCompare(right.path))
75
- .slice(0, MAX_REPORTED_TOTAL_PATHS);
76
- for (const entry of reportedTotals)
77
- totals[entry.path] = entry.value.length;
78
- for (const entry of initialArrays) {
79
- if (entry.value.length > itemLimit) {
80
- entry.value.splice(itemLimit);
81
- truncated = true;
82
- }
83
- }
84
- const metadata = {
85
- byteLimit,
86
- tokenLimit,
87
- itemLimit,
88
- serializedBytes: 0,
89
- estimatedTokens: 0,
90
- truncated: false,
91
- totals,
92
- ...(initialArrays.length > reportedTotals.length
93
- ? { omittedTotalPaths: initialArrays.length - reportedTotals.length }
94
- : {}),
95
- };
96
- root.responseBudget = metadata;
97
- // Preserve collection contracts and identifiers where possible: large source,
98
- // artifact and diagnostic strings are the first expendable detail.
99
- while (bytes(root) > byteLimit) {
100
- const candidate = collectStrings(root)
101
- .filter((entry) => !entry.path.startsWith("$/responseBudget") &&
102
- entry.value.length > 0 &&
103
- isTruncatableDetail(entry))
104
- .sort((a, b) => b.value.length - a.value.length)[0];
105
- if (!candidate)
106
- break;
107
- const marker = "\n… [truncated]";
108
- if (candidate.value.length <= marker.length + 1) {
109
- if (Array.isArray(candidate.parent))
110
- candidate.parent.splice(candidate.key, 1);
111
- else
112
- delete candidate.parent[candidate.key];
113
- truncated = true;
114
- continue;
115
- }
116
- const keep = Math.max(0, Math.floor((candidate.value.length - marker.length) / 2));
117
- candidate.parent[candidate.key] =
118
- `${candidate.value.slice(0, keep)}${marker}`;
119
- truncated = true;
120
- }
121
- // Only drop tail items after scalar detail has been exhausted.
122
- while (bytes(root) > byteLimit) {
123
- const candidate = collectArrays(root)
124
- .filter((entry) => !entry.path.startsWith("$/responseBudget") && entry.value.length > 0)
125
- .sort((a, b) => bytes(b.value) - bytes(a.value))[0];
126
- if (!candidate)
127
- break;
128
- candidate.value.pop();
129
- truncated = true;
130
- }
131
- metadata.truncated = truncated;
132
- if (truncated) {
133
- metadata.continuation = {
134
- operation,
135
- after: "returned-items",
136
- instruction: `Repeat ${operation} with a narrower selector, larger budget, or query drill-down.`,
137
- };
138
- }
139
- // Metadata changes the byte count, so converge after setting it. The reserve
140
- // above is normally enough; this final pass handles unusually long paths.
141
- metadata.serializedBytes = bytes(root);
142
- metadata.estimatedTokens = Math.ceil(metadata.serializedBytes / 4);
143
- if (bytes(root) > byteLimit && metadata.continuation) {
144
- metadata.continuation.instruction = "Narrow or increase budget.";
145
- metadata.serializedBytes = bytes(root);
146
- metadata.estimatedTokens = Math.ceil(metadata.serializedBytes / 4);
147
- }
148
- while (bytes(root) > byteLimit) {
149
- const candidate = collectStrings(root)
150
- .filter((entry) => !entry.path.startsWith("$/responseBudget") &&
151
- entry.value.length > 0 &&
152
- isTruncatableDetail(entry))
153
- .sort((a, b) => b.value.length - a.value.length)[0];
154
- if (!candidate)
155
- break;
156
- const marker = "\n… [truncated]";
157
- if (candidate.value.length <= marker.length + 1) {
158
- if (Array.isArray(candidate.parent))
159
- candidate.parent.splice(candidate.key, 1);
160
- else
161
- delete candidate.parent[candidate.key];
162
- }
163
- else {
164
- const keep = Math.max(0, Math.floor((candidate.value.length - marker.length) / 2));
165
- candidate.parent[candidate.key] =
166
- `${candidate.value.slice(0, keep)}${marker}`;
167
- }
168
- metadata.truncated = true;
169
- }
170
- metadata.serializedBytes = bytes(root);
171
- metadata.estimatedTokens = Math.ceil(metadata.serializedBytes / 4);
172
- while (bytes(root) > byteLimit) {
173
- const candidate = collectArrays(root)
174
- .filter((entry) => !entry.path.startsWith("$/responseBudget") && entry.value.length > 0)
175
- .sort((a, b) => bytes(b.value) - bytes(a.value))[0];
176
- if (!candidate)
177
- break;
178
- candidate.value.pop();
179
- metadata.truncated = true;
180
- metadata.serializedBytes = bytes(root);
181
- metadata.estimatedTokens = Math.ceil(metadata.serializedBytes / 4);
182
- }
183
- for (let iteration = 0; iteration < 8; iteration++) {
184
- const actual = bytes(root);
185
- const tokens = Math.ceil(actual / 4);
186
- if (metadata.serializedBytes === actual && metadata.estimatedTokens === tokens)
187
- break;
188
- metadata.serializedBytes = actual;
189
- metadata.estimatedTokens = tokens;
190
- }
191
- if (bytes(root) > byteLimit)
192
- throw new RangeError(`response budget ${byteLimit} cannot preserve the ${operation} response contract; narrow the request or increase the budget`);
193
- return root;
194
- }
@@ -1,172 +0,0 @@
1
- /**
2
- * knodin MCP server (stdio).
3
- *
4
- * Registration uses the low-level request-handler API — NOT the high-level
5
- * `server.tool()` overload — so `tsc` never pays the Zod→handler-arg inference
6
- * cost that OOMs large tool sets. Gateway `inputSchema`s are hand-written JSON
7
- * Schema literals; handler args are narrowed manually inside each dispatcher.
8
- */
9
- import path from "node:path";
10
- import { fileURLToPath } from "node:url";
11
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
14
- import { recordDiagnosticFailure } from "./diagnostics.js";
15
- import { getKnodinTools, handleKnodinTool } from "./tools/knodin-tools.js";
16
- import { KNODIN_VERSION } from "./version.js";
17
- const RETRY_SAFE_OPERATIONS = new Set([
18
- "architecture_overview",
19
- "context",
20
- "docs",
21
- "explain",
22
- "map",
23
- "query",
24
- "review",
25
- "search",
26
- "status",
27
- ]);
28
- class GatewayRequestQueue {
29
- tail = Promise.resolve();
30
- pending = 0;
31
- async run(signal, task) {
32
- const position = this.pending++;
33
- const predecessor = this.tail;
34
- let release;
35
- this.tail = new Promise((resolve) => {
36
- release = resolve;
37
- });
38
- try {
39
- await predecessor;
40
- if (signal.aborted)
41
- throw Object.assign(new Error("MCP request cancelled before dispatch"), {
42
- code: "KNODIN_REQUEST_CANCELLED",
43
- });
44
- return await task(position);
45
- }
46
- finally {
47
- this.pending--;
48
- release();
49
- }
50
- }
51
- }
52
- function ignoreNotificationFailure() {
53
- // Progress is advisory; a client that stops accepting it must not fail the tool call.
54
- }
55
- function startHeartbeat(notify, operation, startedAt) {
56
- return setInterval(() => {
57
- void notify(`${operation} still running (${Math.round((Date.now() - startedAt) / 1000)}s)`).catch(ignoreNotificationFailure);
58
- }, 10_000);
59
- }
60
- export function recordMcpDiagnosticFailure(args, error, defaultRepo = process.cwd()) {
61
- const effectiveRepo = typeof args?.repoPath === "string" ? args.repoPath : defaultRepo;
62
- const diagnostic = recordDiagnosticFailure(effectiveRepo, {
63
- surface: "mcp",
64
- operation: typeof args?.operation === "string" ? args.operation : "unknown",
65
- phase: "dispatch",
66
- error,
67
- });
68
- return diagnostic.recorded
69
- ? new Error(`${error instanceof Error ? error.message : String(error)} [diagnostic ${diagnostic.correlationId}]`, { cause: error })
70
- : error;
71
- }
72
- export function createServer(dispatch = handleKnodinTool) {
73
- const queue = new GatewayRequestQueue();
74
- const server = new Server({ name: "knodin", version: KNODIN_VERSION }, {
75
- capabilities: { tools: {} },
76
- instructions: "Use knodin first for cold or unfamiliar codebase work: context to orient, explain for source-evidenced symbol context, query impact before meaningful edits, and review before handoff. Prefer direct reads for exact literals, non-code files, or files just edited this turn. If status reports repair-needed, follow its typed repair steps: repair graph damage or init displaced lifecycle routing before relying on graph evidence.",
77
- });
78
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
79
- tools: getKnodinTools(),
80
- }));
81
- server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
82
- const { name, arguments: args } = request.params;
83
- if (name !== "knodin") {
84
- throw new Error(`unknown tool: ${name}`);
85
- }
86
- const values = args;
87
- const operation = typeof values?.operation === "string" ? values.operation : "unknown";
88
- const progressToken = extra._meta?.progressToken;
89
- const requestId = String(extra.requestId);
90
- const startedAt = Date.now();
91
- let heartbeat;
92
- try {
93
- const result = await queue.run(extra.signal, async (position) => {
94
- const notify = async (message) => {
95
- if (progressToken === undefined)
96
- return;
97
- await extra.sendNotification({
98
- method: "notifications/progress",
99
- params: {
100
- progressToken,
101
- progress: Date.now() - startedAt,
102
- message,
103
- },
104
- });
105
- };
106
- await notify(position > 0
107
- ? `Starting ${operation} after waiting behind ${position} earlier request(s)`
108
- : `Starting ${operation}`);
109
- heartbeat = startHeartbeat(notify, operation, startedAt);
110
- // Keep the production handler explicit: knodin's own source-evidence
111
- // extractor maps this low-level MCP registration to its dispatcher.
112
- return dispatch === handleKnodinTool ? await handleKnodinTool(args) : await dispatch(args);
113
- });
114
- return {
115
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
116
- };
117
- }
118
- catch (error) {
119
- const recorded = recordMcpDiagnosticFailure(values, error);
120
- const message = recorded instanceof Error ? recorded.message : String(recorded);
121
- const traceId = /\[diagnostic ([a-f0-9]{16})\]/.exec(message)?.[1] ?? `mcp-${requestId}`;
122
- const retrySafe = RETRY_SAFE_OPERATIONS.has(operation);
123
- return {
124
- isError: true,
125
- content: [
126
- {
127
- type: "text",
128
- text: JSON.stringify({
129
- code: error?.code === "KNODIN_REQUEST_CANCELLED"
130
- ? "KNODIN_REQUEST_CANCELLED"
131
- : "KNODIN_OPERATION_FAILED",
132
- message,
133
- traceId,
134
- requestId,
135
- operation,
136
- repository: typeof values?.repoPath === "string" ? values.repoPath : process.cwd(),
137
- phase: "dispatch",
138
- retrySafe,
139
- diagnosticLog: ".knodin/diagnostics/events.jsonl (only when opt-in diagnostics are enabled)",
140
- recovery: retrySafe
141
- ? "Retry once. If the transport closes, restart the MCP client and run `knodin doctor`; include `knodin diagnostics export` when diagnostics are enabled."
142
- : "Do not retry automatically. Run `knodin doctor`, inspect repository state, and include `knodin diagnostics export` when diagnostics are enabled.",
143
- }, null, 2),
144
- },
145
- ],
146
- };
147
- }
148
- finally {
149
- if (heartbeat)
150
- clearInterval(heartbeat);
151
- }
152
- });
153
- return server;
154
- }
155
- async function main() {
156
- const server = createServer();
157
- const transport = new StdioServerTransport();
158
- await server.connect(transport);
159
- // stdio server: log to stderr so stdout stays a clean JSON-RPC channel.
160
- console.error("knodin MCP server running on stdio");
161
- }
162
- /** Start the stdio gateway from either the direct server entry or the packaged CLI. */
163
- export async function startServer() {
164
- await main();
165
- }
166
- // Run only when invoked directly (not when imported by tests).
167
- if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
168
- startServer().catch((err) => {
169
- console.error(err);
170
- process.exit(1);
171
- });
172
- }