knodin 0.7.4 → 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 (104) 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 -110
  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 -73
  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/schemas/release-attestation-v1.schema.json +0 -210
@@ -1,449 +0,0 @@
1
- import crypto from "node:crypto";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import zlib from "node:zlib";
6
- const CONFIG_PATH = ".knodin/diagnostics/config.json";
7
- const JOURNAL_PATH = ".knodin/diagnostics/events.jsonl";
8
- const JOURNAL_LOCK_PATH = ".knodin/diagnostics/events.lock";
9
- const DEFAULT_RETENTION_DAYS = 14;
10
- const MAX_RETENTION_DAYS = 365;
11
- const MAX_RECORDS = 500;
12
- const MAX_LOG_BYTES = 64 * 1024;
13
- const MAX_BUNDLE_BYTES = 20 * 1024 * 1024;
14
- const MAX_JOURNAL_BYTES = 2 * 1024 * 1024;
15
- const OPERATIONS = new Set([
16
- "init",
17
- "configure",
18
- "index",
19
- "doctor",
20
- "status",
21
- "wait",
22
- "repair",
23
- "serve",
24
- "context",
25
- "explain",
26
- "review",
27
- "map",
28
- "search",
29
- "query",
30
- "rename",
31
- "wiki",
32
- "visualize",
33
- "pack",
34
- "compress",
35
- "prs",
36
- "worktrees",
37
- "telemetry",
38
- "diagnostics",
39
- "system",
40
- "repos",
41
- "repositories",
42
- "update",
43
- "docs",
44
- "unknown",
45
- ]);
46
- function containedPath(repoPath, requested, purpose) {
47
- const repo = fs.realpathSync(repoPath);
48
- const target = path.resolve(repo, requested);
49
- if (target !== repo && !target.startsWith(`${repo}${path.sep}`))
50
- throw new Error(`knodin diagnostics ${purpose}: path must stay inside the repository`);
51
- let cursor = repo;
52
- for (const segment of path.relative(repo, target).split(path.sep).filter(Boolean)) {
53
- cursor = path.join(cursor, segment);
54
- try {
55
- if (fs.lstatSync(cursor).isSymbolicLink())
56
- throw new Error(`knodin diagnostics ${purpose}: refusing symlinked path`);
57
- }
58
- catch (error) {
59
- if (error.code === "ENOENT")
60
- break;
61
- throw error;
62
- }
63
- }
64
- return { repo, target };
65
- }
66
- function atomicPrivateWrite(target, data) {
67
- fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
68
- const temporary = `${target}.${process.pid}.${Date.now()}.${crypto.randomBytes(6).toString("hex")}.tmp`;
69
- fs.writeFileSync(temporary, data, { mode: 0o600, flag: "wx" });
70
- fs.renameSync(temporary, target);
71
- }
72
- function withJournalLock(repo, run) {
73
- const { target: lock } = containedPath(repo, JOURNAL_LOCK_PATH, "record");
74
- fs.mkdirSync(path.dirname(lock), { recursive: true, mode: 0o700 });
75
- for (let attempt = 0;; attempt++) {
76
- try {
77
- fs.mkdirSync(lock, { mode: 0o700 });
78
- break;
79
- }
80
- catch (error) {
81
- if (error.code !== "EEXIST")
82
- throw error;
83
- try {
84
- if (Date.now() - fs.statSync(lock).mtimeMs > 30_000) {
85
- fs.rmdirSync(lock);
86
- continue;
87
- }
88
- }
89
- catch (inspectionError) {
90
- if (inspectionError.code === "ENOENT")
91
- continue;
92
- throw inspectionError;
93
- }
94
- if (attempt >= 100)
95
- throw new Error("knodin diagnostics record: journal is busy");
96
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
97
- }
98
- }
99
- try {
100
- return run();
101
- }
102
- finally {
103
- try {
104
- fs.rmdirSync(lock);
105
- }
106
- catch {
107
- // Recording is best-effort; stale-lock recovery handles interrupted cleanup.
108
- }
109
- }
110
- }
111
- function validRetention(days) {
112
- if (!Number.isInteger(days) || days < 1 || days > MAX_RETENTION_DAYS)
113
- throw new Error(`knodin diagnostics: retention days must be an integer from 1 to ${MAX_RETENTION_DAYS}`);
114
- return days;
115
- }
116
- function readConfig(repoPath) {
117
- const { target } = containedPath(repoPath, CONFIG_PATH, "status");
118
- if (!fs.existsSync(target))
119
- return null;
120
- try {
121
- const value = JSON.parse(fs.readFileSync(target, "utf8"));
122
- if (value.schemaVersion !== 1 || value.enabled !== true)
123
- return null;
124
- validRetention(value.retentionDays);
125
- if (!Number.isInteger(value.generation) || value.generation < 1)
126
- value.generation = 1;
127
- if (typeof value.sessionId !== "string" || !/^[a-f0-9]{32}$/.test(value.sessionId))
128
- value.sessionId = crypto
129
- .createHash("sha256")
130
- .update(value.enabledAt)
131
- .digest("hex")
132
- .slice(0, 32);
133
- return value;
134
- }
135
- catch {
136
- return null;
137
- }
138
- }
139
- function safeLabel(value, fallback) {
140
- return value && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,63}$/.test(value) ? value : fallback;
141
- }
142
- function errorCode(error) {
143
- const code = error?.code;
144
- return typeof code === "string" &&
145
- /^(?:E[A-Z0-9]+|ERR_[A-Z0-9_]+|SQLITE_[A-Z0-9_]+|KNODIN_[A-Z0-9_]+)$/.test(code)
146
- ? code
147
- : null;
148
- }
149
- function scrubText(raw, repo) {
150
- let value = raw;
151
- let redactions = 0;
152
- const replace = (pattern, replacement) => {
153
- value = value.replace(pattern, () => {
154
- redactions++;
155
- return replacement;
156
- });
157
- };
158
- for (const exact of [repo, os.homedir()].filter(Boolean).sort((a, b) => b.length - a.length))
159
- replace(new RegExp(exact.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "<path>");
160
- replace(/\b(?:ghp|github_pat|sk|xox[baprs])-[-A-Za-z0-9_]{10,}\b/g, "<secret>");
161
- replace(/\b(?:password|passwd|token|secret|api[_-]?key|authorization)\s*[=:]\s*[^\s,;]+/gi, "$1=<secret>");
162
- replace(/\b[A-Z]:\\(?:[^\s<>:"|?*]+\\)*[^\s<>:"|?*]*/g, "<path>");
163
- replace(/(?:^|[\s('"`])\/(?:[^\s)'"`]+\/)*[^\s)'"`]*/g, "<path>");
164
- replace(/\b(?:[A-Za-z0-9_.-]+\/)+(?:[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,12})\b/g, "<path>");
165
- replace(/\b[A-Za-z0-9_.+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, "<email>");
166
- return { value: value.slice(0, 2_000), redactions };
167
- }
168
- function sanitizeStack(error, repo) {
169
- return (error.stack ?? "")
170
- .split(/\r?\n/)
171
- .slice(1, 9)
172
- .map((line) => `at ${line.includes(repo) ? "<repository>" : "<runtime>"}`);
173
- }
174
- function sanitizeUnknown(value, repo, state) {
175
- if (typeof value === "string") {
176
- const scrubbed = scrubText(value, repo);
177
- state.redactions += scrubbed.redactions;
178
- return scrubbed.value;
179
- }
180
- if (Array.isArray(value))
181
- return value.slice(0, 500).map((item) => sanitizeUnknown(item, repo, state));
182
- if (value && typeof value === "object") {
183
- const output = {};
184
- for (const [key, item] of Object.entries(value).slice(0, 500)) {
185
- const safeKey = /[\\/]|\.[A-Za-z0-9]{1,12}$/.test(key) ? "<path-key>" : key;
186
- if (safeKey !== key)
187
- state.redactions++;
188
- if (/^(?:source|content|query|command|arguments?|env(?:ironment)?|remote)$/i.test(key)) {
189
- output[safeKey] = "<omitted>";
190
- state.redactions++;
191
- }
192
- else
193
- output[safeKey] = sanitizeUnknown(item, repo, state);
194
- }
195
- return output;
196
- }
197
- return value;
198
- }
199
- function readEvents(repoPath, retentionDays, since) {
200
- const { target } = containedPath(repoPath, JOURNAL_PATH, "read");
201
- if (!fs.existsSync(target))
202
- return [];
203
- if (fs.statSync(target).size > MAX_JOURNAL_BYTES)
204
- throw new Error("knodin diagnostics read: journal exceeds the 2 MiB safety limit");
205
- const cutoff = Math.max(Date.now() - validRetention(retentionDays) * 86_400_000, since?.getTime() ?? Number.NEGATIVE_INFINITY);
206
- return fs
207
- .readFileSync(target, "utf8")
208
- .split(/\r?\n/)
209
- .filter(Boolean)
210
- .flatMap((line) => {
211
- try {
212
- const record = JSON.parse(line);
213
- return record.schemaVersion === 1 && Date.parse(record.at) >= cutoff ? [record] : [];
214
- }
215
- catch {
216
- return [];
217
- }
218
- })
219
- .slice(-MAX_RECORDS);
220
- }
221
- export function enableDiagnostics(repoPath, retentionDays = DEFAULT_RETENTION_DAYS) {
222
- const { repo, target } = containedPath(repoPath, CONFIG_PATH, "enable");
223
- withJournalLock(repo, () => {
224
- const previous = readConfig(repo);
225
- const config = {
226
- schemaVersion: 1,
227
- enabled: true,
228
- retentionDays: validRetention(retentionDays),
229
- enabledAt: new Date().toISOString(),
230
- generation: (previous?.generation ?? 0) + 1,
231
- sessionId: crypto.randomBytes(16).toString("hex"),
232
- };
233
- atomicPrivateWrite(target, `${JSON.stringify(config, null, 2)}\n`);
234
- });
235
- return { ...diagnosticsStatus(repo), message: "Local diagnostics enabled; nothing is uploaded." };
236
- }
237
- export function disableDiagnostics(repoPath) {
238
- const { repo, target } = containedPath(repoPath, CONFIG_PATH, "disable");
239
- withJournalLock(repo, () => {
240
- if (fs.existsSync(target))
241
- fs.unlinkSync(target);
242
- });
243
- return {
244
- ...diagnosticsStatus(repo),
245
- message: "Local diagnostics disabled; retained events were not deleted.",
246
- };
247
- }
248
- export function diagnosticsStatus(repoPath) {
249
- const config = readConfig(repoPath);
250
- const retentionDays = config?.retentionDays ?? DEFAULT_RETENTION_DAYS;
251
- let events = [];
252
- let journal = {
253
- status: "healthy",
254
- issue: null,
255
- };
256
- try {
257
- events = readEvents(repoPath, retentionDays);
258
- }
259
- catch (error) {
260
- journal = {
261
- status: "unavailable",
262
- issue: error instanceof Error ? error.message : "diagnostic journal is unavailable",
263
- };
264
- }
265
- return {
266
- schemaVersion: 1,
267
- enabled: config !== null,
268
- localOnly: true,
269
- uploaded: false,
270
- retentionDays,
271
- records: events.length,
272
- oldestAt: events[0]?.at ?? null,
273
- newestAt: events.at(-1)?.at ?? null,
274
- journal,
275
- };
276
- }
277
- export function recordDiagnosticFailure(repoPath, input) {
278
- try {
279
- const repo = fs.realpathSync(repoPath);
280
- const observed = readConfig(repo);
281
- if (!observed)
282
- return { recorded: false, reason: "disabled" };
283
- return withJournalLock(repo, () => {
284
- const config = readConfig(repo);
285
- if (!config)
286
- return { recorded: false, reason: "disabled" };
287
- if (config.sessionId !== observed.sessionId || config.generation !== observed.generation)
288
- return { recorded: false, reason: "state-changed" };
289
- const error = input.error instanceof Error ? input.error : new Error(String(input.error));
290
- const message = scrubText(error.message, repo).value;
291
- const event = {
292
- schemaVersion: 1,
293
- at: new Date().toISOString(),
294
- correlationId: crypto.randomBytes(8).toString("hex"),
295
- surface: input.surface,
296
- operation: OPERATIONS.has(input.operation) ? input.operation : "unknown",
297
- phase: safeLabel(input.phase, "operation"),
298
- error: {
299
- name: safeLabel(error.name, "Error"),
300
- code: errorCode(input.error),
301
- messageFingerprint: crypto
302
- .createHash("sha256")
303
- .update(message)
304
- .digest("hex")
305
- .slice(0, 16),
306
- stack: sanitizeStack(error, repo),
307
- },
308
- };
309
- const events = [...readEvents(repo, config.retentionDays), event].slice(-MAX_RECORDS);
310
- const { target } = containedPath(repo, JOURNAL_PATH, "record");
311
- atomicPrivateWrite(target, `${events.map((record) => JSON.stringify(record)).join("\n")}\n`);
312
- return { recorded: true, correlationId: event.correlationId };
313
- });
314
- }
315
- catch {
316
- return { recorded: false, reason: "unavailable" };
317
- }
318
- }
319
- export function clearDiagnostics(repoPath) {
320
- const { repo, target } = containedPath(repoPath, JOURNAL_PATH, "clear");
321
- return withJournalLock(repo, () => {
322
- if (!fs.existsSync(target))
323
- return { removed: false, records: 0, bytes: 0 };
324
- let records = null;
325
- try {
326
- records = readEvents(repo, MAX_RETENTION_DAYS).length;
327
- }
328
- catch {
329
- // Deletion must remain the recovery path for an unreadable or oversized journal.
330
- }
331
- const bytes = fs.statSync(target).size;
332
- fs.unlinkSync(target);
333
- const config = readConfig(repo);
334
- if (config) {
335
- atomicPrivateWrite(containedPath(repo, CONFIG_PATH, "clear").target, `${JSON.stringify({ ...config, generation: config.generation + 1 }, null, 2)}\n`);
336
- }
337
- return { removed: true, records, bytes };
338
- });
339
- }
340
- function defaultBundlePath() {
341
- return `.knodin/diagnostics/knodin-diagnostics-${new Date().toISOString().replace(/[:.]/g, "-")}.json.gz`;
342
- }
343
- function lifecycleLog(repo, state) {
344
- const target = path.join(repo, ".knodin", "indexer.log");
345
- if (!fs.existsSync(target) || fs.lstatSync(target).isSymbolicLink())
346
- return [];
347
- const size = fs.statSync(target).size;
348
- const descriptor = fs.openSync(target, "r");
349
- try {
350
- const length = Math.min(size, MAX_LOG_BYTES);
351
- const buffer = Buffer.alloc(length);
352
- fs.readSync(descriptor, buffer, 0, length, Math.max(0, size - length));
353
- return buffer
354
- .toString("utf8")
355
- .split(/\r?\n/)
356
- .filter(Boolean)
357
- .slice(-200)
358
- .map((line) => sanitizeUnknown(line, repo, state));
359
- }
360
- finally {
361
- fs.closeSync(descriptor);
362
- }
363
- }
364
- export function collectDiagnostics(repoPath, options = {}) {
365
- const { repo, target } = containedPath(repoPath, options.outputPath ?? defaultBundlePath(), "collect");
366
- if (fs.existsSync(target))
367
- throw new Error("knodin diagnostics collect: refusing to overwrite an existing bundle");
368
- const sinceHours = options.sinceHours ?? 24;
369
- if (!Number.isFinite(sinceHours) || sinceHours <= 0 || sinceHours > 24 * 365)
370
- throw new Error("knodin diagnostics collect: --since must be from 1h through 8760h");
371
- const state = { redactions: 0 };
372
- const status = diagnosticsStatus(repo);
373
- const bundle = {
374
- manifest: {
375
- schemaVersion: 1,
376
- generatedAt: new Date().toISOString(),
377
- privacy: "redacted-local-only",
378
- localOnly: true,
379
- uploaded: false,
380
- redactions: 0,
381
- omissions: [
382
- "source",
383
- "queries-and-arguments",
384
- "environment-values",
385
- "git-remotes-diffs-and-messages",
386
- "raw-paths",
387
- ],
388
- },
389
- runtime: {
390
- ...(options.knodinVersion ? { knodinVersion: options.knodinVersion } : {}),
391
- node: process.version,
392
- platform: process.platform,
393
- arch: process.arch,
394
- },
395
- diagnostics: status,
396
- diagnosticEvents: readEvents(repo, status.retentionDays, new Date(Date.now() - sinceHours * 3_600_000)),
397
- telemetry: sanitizeUnknown(options.telemetry ?? [], repo, state),
398
- doctor: sanitizeUnknown(options.doctor ?? null, repo, state),
399
- graph: sanitizeUnknown(options.graph ?? null, repo, state),
400
- lifecycleLog: lifecycleLog(repo, state),
401
- };
402
- bundle.manifest.redactions = state.redactions;
403
- atomicPrivateWrite(target, zlib.gzipSync(`${JSON.stringify(bundle, null, 2)}\n`, { level: 9 }));
404
- return {
405
- schemaVersion: 1,
406
- outputPath: path.relative(repo, target),
407
- format: "gzip-json",
408
- localOnly: true,
409
- uploaded: false,
410
- bytes: fs.statSync(target).size,
411
- records: bundle.diagnosticEvents.length,
412
- redactions: bundle.manifest.redactions,
413
- message: "Bundle created locally. Run `knodin diagnostics inspect <bundle>` before sharing it.",
414
- };
415
- }
416
- export function inspectDiagnosticsBundle(repoPath, bundlePath) {
417
- const { target } = containedPath(repoPath, bundlePath, "inspect");
418
- if (!fs.existsSync(target))
419
- throw new Error("knodin diagnostics inspect: bundle does not exist");
420
- if (fs.statSync(target).size > 10 * 1024 * 1024)
421
- throw new Error("knodin diagnostics inspect: bundle exceeds the 10 MiB safety limit");
422
- let parsed;
423
- try {
424
- parsed = JSON.parse(zlib
425
- .gunzipSync(fs.readFileSync(target), { maxOutputLength: MAX_BUNDLE_BYTES })
426
- .toString("utf8"));
427
- }
428
- catch {
429
- throw new Error("knodin diagnostics inspect: invalid gzip JSON bundle");
430
- }
431
- if (parsed.manifest?.schemaVersion !== 1 || parsed.manifest.privacy !== "redacted-local-only")
432
- throw new Error("knodin diagnostics inspect: unsupported or unsafe bundle manifest");
433
- if (!Array.isArray(parsed.diagnosticEvents) ||
434
- !Array.isArray(parsed.telemetry) ||
435
- !Array.isArray(parsed.lifecycleLog))
436
- throw new Error("knodin diagnostics inspect: malformed bundle sections");
437
- return {
438
- manifest: parsed.manifest,
439
- runtime: parsed.runtime,
440
- diagnostics: parsed.diagnostics,
441
- sections: ["diagnosticEvents", "telemetry", "doctor", "graph", "lifecycleLog"],
442
- counts: {
443
- diagnosticEvents: parsed.diagnosticEvents.length,
444
- telemetry: parsed.telemetry.length,
445
- lifecycleLogLines: parsed.lifecycleLog.length,
446
- },
447
- bundle: parsed,
448
- };
449
- }
@@ -1,141 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- export const DOCS_SECTIONS = {
5
- quickstart: `# Quickstart Guide
6
-
7
- knodin — source-evidenced local code intelligence with known bounds. It builds a semantic and syntactic knowledge graph of your repository and exposes five primary operations through its MCP tool gateway or CLI:
8
-
9
- 1. **context**: Orient on a repository. Returns high-level statistics, key subsystems/hubs/flows, a heuristic next-operation recommendation, and a diff-based risk score if changes are detected.
10
- 2. **explain**: Understand a specific symbol (function, class, or file). Returns edit-ready source code, inbound/outbound call paths, and blast radius (transitive callers).
11
- 3. **review**: Analyze local modifications. Displays risk-scored changes, affected execution flows, and missing test coverage before you commit or create a PR.
12
- 4. **map**: Visualize subsystem boundaries. Partitions the code into cohesive modular communities using Louvain community detection.
13
- 5. **search**: Fuzzy semantic and keyword lookup to find symbols when you do not know their exact name.
14
-
15
- ### Command Usage (CLI)
16
- \`\`\`bash
17
- knodin context "explore auth"
18
- knodin explain login
19
- knodin map
20
- \`\`\``,
21
- "query-patterns": `# Structured Query Patterns
22
-
23
- The \`query\` operation lets you ask precise, structured questions about your code using a variety of built-in graph patterns:
24
-
25
- - **callers_of <target>**: Who invokes this symbol (transitive/direct).
26
- - **callees_of <target>**: What other symbols does this symbol invoke.
27
- - **tests_for <target>**: Find test files that cover this symbol.
28
- - **file_summary <target>**: Lists all symbols defined in a file.
29
- - **shortest_path <from> <to>**: Find the call-graph chain connecting two symbols.
30
- - **impact <symbol>**: Directional, depth-bounded symbol reach with stable selectors, relation/confidence/test filters, edge evidence, and heuristic summaries. Use \`impactMode: "file"\` explicitly for a distinctly labeled changed-file blast radius.
31
- - **dead_code**: Surface exported or unexported symbols with zero references.
32
- - **large_functions / large_files**: Spot complex "god" functions and files exceeding size thresholds.
33
- - **traverse <symbol>**: Perform a bounded BFS neighborhood walk. Use \`direction\` (\`upstream|downstream|both\`), \`relationKinds\`, and optional \`includeDataFlow\`; every discovered hop carries its exact kind, direction, confidence, provenance, source line, and files. Call arguments are labeled heuristic source evidence, never runtime proof.
34
- - **knowledge_gaps**: Report repo-health weaknesses like thin communities (<3 symbols) and untested hubs/bridges.
35
- - **surprising_connections**: Score and rank coupling that crosses community, language, or test boundaries.
36
- - **suggested_questions**: prioritizes human-readable review prompts based on untested hotspots and high-surprise edges.
37
- - **architecture_overview**: Formats community cohesion and coupling plus independently selectable \`architectureFacets\`: packages, layers, boundaries, hotspots, entryPoints, and languages. \`path\` scopes every facet consistently.`,
38
- federation: `# Cross-Repository Composition
39
-
40
- Federation is knodin's internal query-composition mechanism, not the primary
41
- user-facing model. Use \`knodin repos\` for operational checkout management and
42
- \`knodin system\` with commit-ready \`knodin.yaml\` stable identities for
43
- cooperating components. Sibling directory placement and shared search matches
44
- never establish system membership.
45
-
46
- Legacy \`.knodin/federation.json\` remains a compatibility input and is not
47
- silently discarded. Migrate it to \`knodin.yaml\` plus personal XDG checkout
48
- paths. The \`federated_repos\` query remains available for inspecting the
49
- engine's currently composed repository paths; it does not prove those
50
- repositories form one system.`,
51
- "rename-safety": `# Rename Safety and Refactoring
52
-
53
- knodin provides an AST-backed, type-safe refactoring pipeline for renaming symbols.
54
-
55
- ### Two-Step Pipeline
56
- 1. **Dry-Run (default)**: Returns every edit site (definitions, references, imports) and generates a unified diff. Refuses to make changes if there's an ambiguity or conflict.
57
- 2. **Apply (\`apply: true\` or \`--apply\`)**: Atomically writes line-scoped word-boundary edits to disk and triggers an automatic reindexing of touched files.
58
-
59
- ### Safety Guards
60
- The engine refuses to write to disk and rolls back any changes if:
61
- - **Ambiguity**: The old symbol name resolves to more than one definition file.
62
- - **Collision**: The new name already exists as a symbol in any of the edit-site files.
63
- - **Out of Scope**: The edit targets files outside the repository.
64
- - **Type Errors**: If verification is enabled (default), it verifies the workspace compiles successfully using \`tsc\` (or equivalent) after applying the edit, and automatically rolls back if compilation fails.`,
65
- "language-support": `# Language and Metadata Support
66
-
67
- knodin is multi-language, not language-universal.
68
-
69
- ### Native syntax
70
- TypeScript (\`.ts/.tsx/.mts/.cts\`), JavaScript (\`.js/.jsx/.mjs/.cjs\`), Python, Java, C#, Salesforce Apex, SQL/PLSQL, Prisma, and XML-backed formats.
71
-
72
- ### Domain-specific structure
73
- Salesforce LWC, Aura, Visualforce, Experience Cloud, and selected Salesforce DX metadata/automations; Terraform/HCL; Dockerfiles; dbt manifests; and Workday Studio XML.
74
-
75
- ### Imported graph
76
- LSIF can import symbols and relationships produced by a compatible language server. This is not native parsing.
77
-
78
- Go, Rust, PHP, Ruby, Kotlin, Swift, Perl, PowerShell, Bash, and MuleSoft/RAML are known native-parser gaps. Coverage differs by language, and static dead-code candidates must be corroborated when runtime or platform configuration can invoke code dynamically. Salesforce candidates should be checked against deployed-org and platform dependency data before deletion.`,
79
- troubleshooting: `# Troubleshooting and Recovery
80
-
81
- ### Index health and surgical repair
82
-
83
- Run \`knodin status\` (or MCP \`operation: "status"\`) to inspect the local schema/model/version, file and symbol coverage, orphaned or missing records, and the last successful reconciliation. Its repair steps are actionable. Run \`knodin repair\` (or MCP \`operation: "repair"\`) to rebuild only missing/damaged rows and verify health; healthy indexed files are not deleted or rebuilt.
84
-
85
- The MCP \`telemetry\` operation reads process-local metadata-only measurements: actual response bytes and \`gpt-tokenizer@3.4.0:o200k_base\` tokens, executed baselines where available, negative or positive savings, latency, RSS, schema cost, truncation, and detail mode. It never contains source or file paths and does not change existing operation response shapes. Telemetry is not persisted by default. Set MCP \`persistTelemetry: true\` to append metadata-only JSONL to the repo-root \`.knodin-telemetry.jsonl\`; knodin never sends it over the network. Use \`telemetryAction: "report"\` or \`knodin telemetry report\` for a static repository-local HTML report.
86
-
87
- Common issues and how to resolve them when using knodin:
88
-
89
- ### Corrupt Embedding Row
90
- If you see warnings like \`Skipping corrupt embedding... dimension mismatch\`, your index database contains malformed embeddings.
91
- - **Fix**: Force a clean re-index of the repository to rebuild the vector store.
92
- \`\`\`bash
93
- # Prefer surgical local repair; it preserves healthy state
94
- knodin repair
95
- \`\`\`
96
-
97
- ### Missing 'gh' CLI Dependency
98
- The \`prs\` triage operation requires the official GitHub CLI (\`gh\`) to be installed, in your PATH, and authenticated.
99
- - **Fix**: Run \`gh auth login\` to authenticate locally.
100
-
101
- ### Legacy federation configuration
102
- If legacy federation configuration fails, validate
103
- \`.knodin/federation.json\`, then migrate stable identities to \`knodin.yaml\`
104
- and local paths to XDG configuration. Do not infer membership from siblings.`,
105
- };
106
- const DOC_TOPIC_FILES = {
107
- installation: "INSTALLATION.md",
108
- mcp: "MCP.md",
109
- repositories: "REPOSITORIES-AND-WORKTREES.md",
110
- systems: "SYSTEMS-AND-RELATIONSHIPS.md",
111
- provenance: "INDEXING-POLICY-AND-PROVENANCE.md",
112
- "dead-code": "DEAD-CODE-AND-IMPACT.md",
113
- doctor: "DOCTOR-AND-UPDATES.md",
114
- compression: "COMMAND-OUTPUT-COMPRESSION.md",
115
- };
116
- function packageRoot() {
117
- let current = path.dirname(fileURLToPath(import.meta.url));
118
- while (!fs.existsSync(path.join(current, "package.json")) &&
119
- current !== path.parse(current).root) {
120
- current = path.dirname(current);
121
- }
122
- return current;
123
- }
124
- export function listDocTopics() {
125
- return [...new Set([...Object.keys(DOCS_SECTIONS), ...Object.keys(DOC_TOPIC_FILES)])].sort((left, right) => left.localeCompare(right));
126
- }
127
- /** Read canonical long-form guides directly so CLI and MCP cannot drift from Markdown. */
128
- export function getDocSection(topic) {
129
- const file = DOC_TOPIC_FILES[topic];
130
- if (file) {
131
- try {
132
- return fs.readFileSync(path.join(packageRoot(), "docs", file), "utf-8");
133
- }
134
- catch (error) {
135
- if (error.code === "ENOENT")
136
- return undefined;
137
- throw error;
138
- }
139
- }
140
- return DOCS_SECTIONS[topic];
141
- }