knodin 0.5.0 → 0.6.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.
Files changed (41) hide show
  1. package/README.md +166 -534
  2. package/dist/bin/cli.js +5 -5
  3. package/dist/src/agent-integration.js +1 -15
  4. package/dist/src/artifact-refresh.js +1 -1
  5. package/dist/src/competitive-sandbox.js +4 -4
  6. package/dist/src/context-export.js +1 -1
  7. package/dist/src/docs-sections.js +5 -5
  8. package/dist/src/engine/ann-hnsw.js +5 -5
  9. package/dist/src/engine/embeddings.js +4 -4
  10. package/dist/src/engine/index.js +33 -33
  11. package/dist/src/engine/prune.js +2 -2
  12. package/dist/src/engine/symbol-delete.js +1 -1
  13. package/dist/src/index-activity.js +1 -1
  14. package/dist/src/init-progress-worker.js +1 -1
  15. package/dist/src/init.js +42 -59
  16. package/dist/src/lifecycle-health.js +10 -11
  17. package/dist/src/lsp-readonly.js +1 -1
  18. package/dist/src/output-compression.js +1 -1
  19. package/dist/src/output-telemetry.js +4 -4
  20. package/dist/src/pr-triage.js +2 -2
  21. package/dist/src/repair-progress-worker.js +1 -1
  22. package/dist/src/repository-init-process.js +1 -1
  23. package/dist/src/repository-management.js +3 -3
  24. package/dist/src/server.js +1 -1
  25. package/dist/src/system-config.js +7 -7
  26. package/dist/src/tools/knodin-tools.js +2 -2
  27. package/dist/src/update-policy.js +4 -4
  28. package/dist/src/wait-for-fresh.js +2 -2
  29. package/dist/src/worktree-lifecycle.js +2 -2
  30. package/docs/COMMAND-OUTPUT-COMPRESSION.md +1 -1
  31. package/docs/DOCTOR-AND-UPDATES.md +2 -2
  32. package/docs/INSTALLATION.md +5 -5
  33. package/docs/REPOSITORIES-AND-WORKTREES.md +1 -1
  34. package/docs/SYSTEMS-AND-RELATIONSHIPS.md +2 -2
  35. package/docs/TELEMETRY.md +2 -2
  36. package/docs/assets/knodin-favicon.svg +4 -0
  37. package/docs/releases/0.5.1.md +17 -0
  38. package/docs/releases/0.6.0.md +18 -0
  39. package/package.json +8 -6
  40. package/schemas/release-attestation-v1.schema.json +1 -1
  41. package/dist/src/tools/reckon-tools.js +0 -5
package/dist/bin/cli.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * knodin explain <symbol> [minimal] edit-ready source + direct call paths + blast radius
6
6
  * knodin review [base] risk-scored context with explicit git diff scopes
7
7
  * knodin map subsystems + confidence-tagged edges
8
- * knodin wiki [--force] write .reckon/wiki/ (index.md + per-community pages)
8
+ * knodin wiki [--force] write .knodin/wiki/ (index.md + per-community pages)
9
9
  * knodin serve run the MCP server on stdio
10
10
  *
11
11
  * The same engine backs both the CLI and the MCP server, so any assistant and
@@ -313,7 +313,7 @@ function createProgressWorkerRenderer(workerName, startMessage) {
313
313
  : [workerPath];
314
314
  const worker = spawn(process.execPath, workerArgs, {
315
315
  stdio: ["pipe", "ignore", "inherit"],
316
- env: { ...process.env, RECKON_FORCE_PROGRESS_TTY: "1" },
316
+ env: { ...process.env, KNODIN_FORCE_PROGRESS_TTY: "1" },
317
317
  });
318
318
  let failed = false;
319
319
  const closed = new Promise((resolve) => {
@@ -788,7 +788,7 @@ async function main() {
788
788
  output = await checkTrustedUpdate(options);
789
789
  }
790
790
  finally {
791
- if (process.env.RECKON_UPDATE_BACKGROUND === "1")
791
+ if (process.env.KNODIN_UPDATE_BACKGROUND === "1")
792
792
  releaseScheduledUpdateCheck();
793
793
  }
794
794
  }
@@ -812,7 +812,7 @@ async function main() {
812
812
  const background = spawn(executable, [...arguments_, "update", "check", "--json"], {
813
813
  detached: true,
814
814
  stdio: "ignore",
815
- env: { ...process.env, RECKON_UPDATE_BACKGROUND: "1" },
815
+ env: { ...process.env, KNODIN_UPDATE_BACKGROUND: "1" },
816
816
  });
817
817
  background.unref();
818
818
  }
@@ -1130,7 +1130,7 @@ async function main() {
1130
1130
  const scope = explicitScope(rawRest);
1131
1131
  if (!scope)
1132
1132
  throw new Error("knodin configure: missing --scope");
1133
- if (!fs.existsSync(path.join(repo, ".reckon", "db.sqlite"))) {
1133
+ if (!fs.existsSync(path.join(repo, ".knodin", "db.sqlite"))) {
1134
1134
  throw new Error("knodin configure changes agent integration only; this repository is not initialized. Run `knodin init` first.");
1135
1135
  }
1136
1136
  const agents = scope === "team" ? [] : integrationAgents(repo);
@@ -7,11 +7,8 @@ export function parseInitScope(value) {
7
7
  throw new Error("knodin: --scope must be personal, team, or cli-only");
8
8
  }
9
9
  const MCP_NAME = "knodin";
10
- const LEGACY_MCP_NAME = "reckon-graph";
11
10
  const TOML_START = "# knodin:start";
12
11
  const TOML_END = "# knodin:end";
13
- const LEGACY_TOML_START = "# reckon-graph:start";
14
- const LEGACY_TOML_END = "# reckon-graph:end";
15
12
  const TOML_BLOCK = `${TOML_START}
16
13
  [mcp_servers."${MCP_NAME}"]
17
14
  command = "knodin"
@@ -70,16 +67,6 @@ export function configurePersonalAgents(options) {
70
67
  for (const agent of options.detected) {
71
68
  if (agent !== "claude")
72
69
  continue;
73
- const removeLegacy = {
74
- executable: "claude",
75
- args: ["mcp", "remove", LEGACY_MCP_NAME, "--scope", "local"],
76
- };
77
- try {
78
- run(removeLegacy);
79
- }
80
- catch {
81
- // A missing legacy registration is already the desired state.
82
- }
83
70
  const command = options.remove
84
71
  ? {
85
72
  executable: "claude",
@@ -143,7 +130,6 @@ function updateJsonMcp(filePath, remove) {
143
130
  const servers = typeof existing === "object" && existing !== null && !Array.isArray(existing)
144
131
  ? { ...existing }
145
132
  : {};
146
- delete servers[LEGACY_MCP_NAME];
147
133
  if (remove)
148
134
  delete servers[MCP_NAME];
149
135
  else
@@ -176,7 +162,7 @@ function updateTomlMcp(filePath, remove) {
176
162
  ? `${content.slice(0, start)}${content.slice(end + endMarker.length)}`.trim()
177
163
  : content.trim();
178
164
  }
179
- const without = removeManagedBlock(removeManagedBlock(existing, LEGACY_TOML_START, LEGACY_TOML_END), TOML_START, TOML_END);
165
+ const without = removeManagedBlock(existing, TOML_START, TOML_END);
180
166
  const next = remove ? without : [without, TOML_BLOCK].filter(Boolean).join("\n\n");
181
167
  if (!next) {
182
168
  fs.rmSync(filePath, { force: true });
@@ -75,7 +75,7 @@ export function refreshExternalGraphArtifacts(repoPath, event, runtime = default
75
75
  }
76
76
  /** Persist an audit record locally; no source content or tool output is retained. */
77
77
  export function writeArtifactRefreshRecord(repoPath, result) {
78
- const knodinDir = path.join(path.resolve(repoPath), ".reckon");
78
+ const knodinDir = path.join(path.resolve(repoPath), ".knodin");
79
79
  fs.mkdirSync(knodinDir, { recursive: true });
80
80
  fs.appendFileSync(path.join(knodinDir, "artifact-refresh.jsonl"), `${JSON.stringify({ at: new Date().toISOString(), ...result })}\n`, "utf8");
81
81
  }
@@ -65,8 +65,8 @@ export function createCompetitiveSandbox(sourceRepository, sourceFixture, compet
65
65
  GIT_CONFIG_GLOBAL: path.join(config, "gitconfig"),
66
66
  GIT_CONFIG_NOSYSTEM: "1",
67
67
  PATH: `${binaries}${path.delimiter}${process.env.PATH ?? ""}`,
68
- RECKON_BENCHMARK_SOURCE_REPOSITORY: sourceRepository,
69
- ...(realBun ? { RECKON_BENCHMARK_REAL_BUN: realBun } : {}),
68
+ KNODIN_BENCHMARK_SOURCE_REPOSITORY: sourceRepository,
69
+ ...(realBun ? { KNODIN_BENCHMARK_REAL_BUN: realBun } : {}),
70
70
  };
71
71
  try {
72
72
  if (realBun) {
@@ -74,9 +74,9 @@ export function createCompetitiveSandbox(sourceRepository, sourceFixture, compet
74
74
  fs.writeFileSync(bunShim, `#!/bin/sh
75
75
  if [ "$1" = "src/server.ts" ]; then
76
76
  shift
77
- exec node --import tsx "$RECKON_BENCHMARK_SOURCE_REPOSITORY/src/server.ts" "$@"
77
+ exec node --import tsx "$KNODIN_BENCHMARK_SOURCE_REPOSITORY/src/server.ts" "$@"
78
78
  fi
79
- exec "$RECKON_BENCHMARK_REAL_BUN" "$@"
79
+ exec "$KNODIN_BENCHMARK_REAL_BUN" "$@"
80
80
  `, { mode: 0o755 });
81
81
  }
82
82
  runGit(["clone", "--quiet", "--shared", "--no-checkout", sourceRepository, repository], root, environment);
@@ -3,7 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { RE2 } from "re2-wasm";
5
5
  import { measurePerfPhaseSync } from "./engine/perf.js";
6
- const DEFAULT_EXCLUDES = [".git/**", ".reckon/**", "node_modules/**", "dist/**", "build/**"];
6
+ const DEFAULT_EXCLUDES = [".git/**", ".knodin/**", "node_modules/**", "dist/**", "build/**"];
7
7
  const GLOB_CACHE_LIMIT = 256;
8
8
  const globCache = new Map();
9
9
  function normalizeRelative(value) {
@@ -39,12 +39,12 @@ The \`query\` operation lets you ask precise, structured questions about your co
39
39
 
40
40
  Federation is knodin's internal query-composition mechanism, not the primary
41
41
  user-facing model. Use \`knodin repos\` for operational checkout management and
42
- \`knodin system\` with commit-ready \`reckon.yaml\` stable identities for
42
+ \`knodin system\` with commit-ready \`knodin.yaml\` stable identities for
43
43
  cooperating components. Sibling directory placement and shared search matches
44
44
  never establish system membership.
45
45
 
46
- Legacy \`.reckon/federation.json\` remains a compatibility input and is not
47
- silently discarded. Migrate it to \`reckon.yaml\` plus personal XDG checkout
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
48
  paths. The \`federated_repos\` query remains available for inspecting the
49
49
  engine's currently composed repository paths; it does not prove those
50
50
  repositories form one system.`,
@@ -82,7 +82,7 @@ Go, Rust, PHP, Ruby, Kotlin, Swift, Perl, PowerShell, Bash, and MuleSoft/RAML ar
82
82
 
83
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
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 \`.reckon-telemetry.jsonl\`; knodin never sends it over the network. Use \`telemetryAction: "report"\` or \`knodin telemetry report\` for a static repository-local HTML report.
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
86
 
87
87
  Common issues and how to resolve them when using knodin:
88
88
 
@@ -100,7 +100,7 @@ The \`prs\` triage operation requires the official GitHub CLI (\`gh\`) to be ins
100
100
 
101
101
  ### Legacy federation configuration
102
102
  If legacy federation configuration fails, validate
103
- \`.reckon/federation.json\`, then migrate stable identities to \`reckon.yaml\`
103
+ \`.knodin/federation.json\`, then migrate stable identities to \`knodin.yaml\`
104
104
  and local paths to XDG configuration. Do not infer membership from siblings.`,
105
105
  };
106
106
  const DOC_TOPIC_FILES = {
@@ -11,7 +11,7 @@
11
11
  * The durable lesson, now a hard gate: no approximate search path may engage BY
12
12
  * DEFAULT without a measured >=95% top-10 recall vs the exact scan on a real
13
13
  * corpus, with the method stated. This module is therefore OPT-IN ONLY, behind
14
- * `RECKON_ANN=1`; `search()`'s default remains the exact scan at every corpus
14
+ * `KNODIN_ANN=1`; `search()`'s default remains the exact scan at every corpus
15
15
  * size. See scripts/ann-bench.ts for the recall/latency harness.
16
16
  *
17
17
  * MEASURED, GATE NOT CLEARED (so it stays opt-in). scripts/ann-bench.ts on
@@ -25,7 +25,7 @@
25
25
  * 88.0% top-10 is far better than R11(a)'s LSH (43.3% top-10, AND slower) — the
26
26
  * three queries LSH scored 0% on ("session token", "webhook handler", "metrics
27
27
  * collection") here score 80/80/90% top-10 — but it misses the hard 95% floor,
28
- * so the exact scan REMAINS the default. Recall is tunable via RECKON_ANN_EF (a
28
+ * so the exact scan REMAINS the default. Recall is tunable via KNODIN_ANN_EF (a
29
29
  * larger ef trades latency for recall); default-engagement would require a fresh
30
30
  * measurement clearing 95% at the corpus size where the path engages. Given the
31
31
  * exact scan is 38ms at 18.6k and extrapolates sub-second to ~250k symbols,
@@ -257,11 +257,11 @@ export function buildHnswIndex(rows, params = {}) {
257
257
  * at a corpus size where the path would actually engage (see module header).
258
258
  */
259
259
  export function annEnabled() {
260
- return process.env.RECKON_ANN === "1";
260
+ return process.env.KNODIN_ANN === "1";
261
261
  }
262
- /** efSearch beam width, overridable via RECKON_ANN_EF (a benchmark knob). */
262
+ /** efSearch beam width, overridable via KNODIN_ANN_EF (a benchmark knob). */
263
263
  export function annEfSearch() {
264
- const raw = process.env.RECKON_ANN_EF;
264
+ const raw = process.env.KNODIN_ANN_EF;
265
265
  if (raw) {
266
266
  const n = Number.parseInt(raw, 10);
267
267
  if (Number.isFinite(n) && n > 0)
@@ -24,7 +24,7 @@ const DEFAULT_MODEL_REMOTE_HOST = "https://huggingface.co/";
24
24
  */
25
25
  export function getModelCacheDirectory(options = {}) {
26
26
  const environment = options.env ?? process.env;
27
- const override = environment.RECKON_MODEL_CACHE?.trim();
27
+ const override = environment.KNODIN_MODEL_CACHE?.trim();
28
28
  if (override)
29
29
  return path.resolve(override);
30
30
  const platform = options.platform ?? process.platform;
@@ -42,16 +42,16 @@ export function getModelCacheDirectory(options = {}) {
42
42
  * point at an approved mirror without changing where model files are cached.
43
43
  */
44
44
  export function getModelRemoteHost(environment = process.env) {
45
- const configured = environment.RECKON_MODEL_HOST?.trim() || DEFAULT_MODEL_REMOTE_HOST;
45
+ const configured = environment.KNODIN_MODEL_HOST?.trim() || DEFAULT_MODEL_REMOTE_HOST;
46
46
  let parsed;
47
47
  try {
48
48
  parsed = new URL(configured);
49
49
  }
50
50
  catch {
51
- throw new Error(`RECKON_MODEL_HOST must be an absolute HTTP(S) URL: ${configured}`);
51
+ throw new Error(`KNODIN_MODEL_HOST must be an absolute HTTP(S) URL: ${configured}`);
52
52
  }
53
53
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
54
- throw new Error(`RECKON_MODEL_HOST must use HTTP(S): ${configured}`);
54
+ throw new Error(`KNODIN_MODEL_HOST must use HTTP(S): ${configured}`);
55
55
  }
56
56
  parsed.hash = "";
57
57
  parsed.search = "";
@@ -796,7 +796,7 @@ const FLOW_MAX_ENTRIES = 200;
796
796
  const flowsCache = new Map();
797
797
  const fileToFlowLookupCache = new Map();
798
798
  const FILE_TO_FLOW_LOOKUP_CACHE_LIMIT = 8;
799
- // R18: opt-in ANN (RECKON_ANN=1). An in-memory HNSW index over a repo's
799
+ // R18: opt-in ANN (KNODIN_ANN=1). An in-memory HNSW index over a repo's
800
800
  // embeddings, built lazily on first ANN query and cached per repo + invalidated
801
801
  // by indexGeneration exactly like mapCache/flowsCache — so a reindex rebuilds it
802
802
  // and close() drops it. Holds NO stored state (no schema bump, nothing to
@@ -4814,7 +4814,7 @@ export function parseSimpleNameFromUniqueId(uniqueId) {
4814
4814
  export async function indexDbtManifestFile(content, _absolutePath, relativePath, repoPath, db) {
4815
4815
  try {
4816
4816
  const manifest = JSON.parse(content);
4817
- const dbtDir = path.join(repoPath, ".reckon/dbt");
4817
+ const dbtDir = path.join(repoPath, ".knodin/dbt");
4818
4818
  await fs.promises.mkdir(dbtDir, { recursive: true });
4819
4819
  const assets = [];
4820
4820
  // 1. Process nodes (models, seeds, tests)
@@ -4951,9 +4951,9 @@ export async function indexDbtManifestFile(content, _absolutePath, relativePath,
4951
4951
  db.run("BEGIN TRANSACTION;");
4952
4952
  try {
4953
4953
  // Clear out any previous records associated with this dbt manifest or virtual paths
4954
- deleteSymbolsMatchingPath(db, ".reckon/dbt/%");
4955
- db.run("DELETE FROM \"references\" WHERE callerFile LIKE '.reckon/dbt/%'");
4956
- db.run("DELETE FROM dependencies WHERE fromFile LIKE '.reckon/dbt/%'");
4954
+ deleteSymbolsMatchingPath(db, ".knodin/dbt/%");
4955
+ db.run("DELETE FROM \"references\" WHERE callerFile LIKE '.knodin/dbt/%'");
4956
+ db.run("DELETE FROM dependencies WHERE fromFile LIKE '.knodin/dbt/%'");
4957
4957
  deleteSymbolsForFile(db, relativePath);
4958
4958
  db.run('DELETE FROM "references" WHERE callerFile = ?', [relativePath]);
4959
4959
  db.run("DELETE FROM dependencies WHERE fromFile = ?", [relativePath]);
@@ -4971,7 +4971,7 @@ export async function indexDbtManifestFile(content, _absolutePath, relativePath,
4971
4971
  `);
4972
4972
  for (const asset of assets) {
4973
4973
  const safeId = asset.unique_id.replace(/[/\\]/g, "_");
4974
- const virtualFilePath = `.reckon/dbt/${safeId}.sql`;
4974
+ const virtualFilePath = `.knodin/dbt/${safeId}.sql`;
4975
4975
  const absVirtualPath = path.join(repoPath, virtualFilePath);
4976
4976
  let code = asset.sqlCode;
4977
4977
  if (!code) {
@@ -4990,7 +4990,7 @@ export async function indexDbtManifestFile(content, _absolutePath, relativePath,
4990
4990
  for (const depId of asset.depends_on_nodes) {
4991
4991
  const depAsset = assetMap.get(depId);
4992
4992
  const safeDepId = depId.replace(/[/\\]/g, "_");
4993
- const depVirtualFilePath = `.reckon/dbt/${safeDepId}.sql`;
4993
+ const depVirtualFilePath = `.knodin/dbt/${safeDepId}.sql`;
4994
4994
  insertDep.run(virtualFilePath, depVirtualFilePath, "dbt-lineage");
4995
4995
  const depSimpleName = depAsset ? depAsset.name : parseSimpleNameFromUniqueId(depId);
4996
4996
  insertRef.run(asset.name, virtualFilePath, depSimpleName, depVirtualFilePath, 1, 1);
@@ -5247,7 +5247,7 @@ const MAX_EMBEDDING_BATCH_SIZE = 32;
5247
5247
  // every symbol without turning source preparation into an unbounded memory sink.
5248
5248
  const EMBEDDING_SOURCE_CACHE_MAX_BYTES = 64 * 1024 * 1024;
5249
5249
  function embeddingBatchSize() {
5250
- const raw = process.env.RECKON_EMBEDDING_BATCH_SIZE;
5250
+ const raw = process.env.KNODIN_EMBEDDING_BATCH_SIZE;
5251
5251
  if (raw === undefined)
5252
5252
  return DEFAULT_EMBEDDING_BATCH_SIZE;
5253
5253
  const parsed = Number(raw);
@@ -5829,11 +5829,11 @@ const WATCHED_FRESHNESS_LEASE_MS = 60_000;
5829
5829
  const FRESHNESS_FALLBACK_FILE_BOUND = 50000;
5830
5830
  /**
5831
5831
  * The non-git fallback's file bound, overridable with
5832
- * `RECKON_FRESHNESS_FILE_BOUND` for repos where even a stat sweep of the
5832
+ * `KNODIN_FRESHNESS_FILE_BOUND` for repos where even a stat sweep of the
5833
5833
  * indexed set is too much to pay per query.
5834
5834
  */
5835
5835
  function freshnessFallbackBound() {
5836
- const raw = process.env.RECKON_FRESHNESS_FILE_BOUND;
5836
+ const raw = process.env.KNODIN_FRESHNESS_FILE_BOUND;
5837
5837
  if (raw !== undefined) {
5838
5838
  const parsed = Number.parseInt(raw, 10);
5839
5839
  if (Number.isFinite(parsed) && parsed >= 0)
@@ -6336,9 +6336,9 @@ async function getOrInitDb(repoPath, options = {}) {
6336
6336
  dbPath = ":memory:";
6337
6337
  }
6338
6338
  else {
6339
- const knodinDir = path.join(normalizedPath, ".reckon");
6339
+ const knodinDir = path.join(normalizedPath, ".knodin");
6340
6340
  await fs.promises.mkdir(knodinDir, { recursive: true });
6341
- // Ensure .reckon is in .gitignore
6341
+ // Ensure .knodin is in .gitignore
6342
6342
  try {
6343
6343
  const gitignorePath = path.join(normalizedPath, ".gitignore");
6344
6344
  let gitignoreContent = "";
@@ -6346,11 +6346,11 @@ async function getOrInitDb(repoPath, options = {}) {
6346
6346
  gitignoreContent = await fs.promises.readFile(gitignorePath, "utf-8");
6347
6347
  }
6348
6348
  const lines = gitignoreContent.split("\n").map((l) => l.trim());
6349
- if (!lines.includes(".reckon") &&
6350
- !lines.includes(".reckon/") &&
6351
- !lines.includes("/.reckon")) {
6349
+ if (!lines.includes(".knodin") &&
6350
+ !lines.includes(".knodin/") &&
6351
+ !lines.includes("/.knodin")) {
6352
6352
  const prefix = gitignoreContent.length > 0 && !gitignoreContent.endsWith("\n") ? "\n" : "";
6353
- await fs.promises.appendFile(gitignorePath, `${prefix}\n# knodin\n.reckon\n`);
6353
+ await fs.promises.appendFile(gitignorePath, `${prefix}\n# knodin\n.knodin\n`);
6354
6354
  }
6355
6355
  }
6356
6356
  catch (e) {
@@ -6401,7 +6401,7 @@ async function getOrInitDb(repoPath, options = {}) {
6401
6401
  // an unknown response shape, preserving C9's precision-first rule. v21
6402
6402
  // adds source-only LWR/Experience Bundle topology and must revisit its
6403
6403
  // newly indexable JSON files.
6404
- const RECKON_SCHEMA_VERSION = 21;
6404
+ const KNODIN_SCHEMA_VERSION = 21;
6405
6405
  // Highest version whose upgrade needs the stored data REBUILT. Versions
6406
6406
  // above it migrate in place, so an upgrade costs a DELETE rather than a
6407
6407
  // full re-index + re-embed (~40 min of CPU on an 18k-symbol corpus).
@@ -6411,7 +6411,7 @@ async function getOrInitDb(repoPath, options = {}) {
6411
6411
  const needsMcpBackfill = storedVersion < 17;
6412
6412
  // v13 -> v14 purges embeddings orphaned while the FK cascade was inert.
6413
6413
  // Runs after the CREATE TABLEs below, since the table must exist.
6414
- const needsOrphanPurge = storedVersion < RECKON_SCHEMA_VERSION;
6414
+ const needsOrphanPurge = storedVersion < KNODIN_SCHEMA_VERSION;
6415
6415
  if (storedVersion < LAST_REBUILD_SCHEMA_VERSION) {
6416
6416
  db.run("DROP TRIGGER IF EXISTS after_symbol_insert;");
6417
6417
  db.run("DROP TRIGGER IF EXISTS after_symbol_delete;");
@@ -6426,8 +6426,8 @@ async function getOrInitDb(repoPath, options = {}) {
6426
6426
  db.run("DROP TABLE IF EXISTS mcp_tools;");
6427
6427
  db.run("DROP TABLE IF EXISTS api_contracts;");
6428
6428
  }
6429
- if (storedVersion < RECKON_SCHEMA_VERSION) {
6430
- db.run(`PRAGMA user_version = ${RECKON_SCHEMA_VERSION};`);
6429
+ if (storedVersion < KNODIN_SCHEMA_VERSION) {
6430
+ db.run(`PRAGMA user_version = ${KNODIN_SCHEMA_VERSION};`);
6431
6431
  }
6432
6432
  db.run(`
6433
6433
  CREATE TABLE IF NOT EXISTS symbols (
@@ -6712,15 +6712,15 @@ async function getFederatedRepos(repoPath) {
6712
6712
  const repos = [resolvedRepoPath];
6713
6713
  // Federation is EXPLICIT by default: a repo is treated as standalone unless it
6714
6714
  // is told otherwise, so results never silently depend on what else happens to
6715
- // live next to it. Two opt-in signals, read from `.reckon/federation.json`
6715
+ // live next to it. Two opt-in signals, read from `.knodin/federation.json`
6716
6716
  // ({ "repos"?: string[], "autoDiscover"?: boolean }):
6717
6717
  // • repos — sibling paths (relative to this repo) to federate to.
6718
6718
  // • autoDiscover — scan the parent directory for already-indexed siblings.
6719
- // autoDiscover can also be forced with RECKON_AUTO_FEDERATE=1. Without either
6719
+ // autoDiscover can also be forced with KNODIN_AUTO_FEDERATE=1. Without either
6720
6720
  // signal, knodin federates to exactly the repos listed in `repos` (possibly
6721
6721
  // none) and nothing else.
6722
- let autoDiscover = process.env.RECKON_AUTO_FEDERATE === "1" || process.env.RECKON_AUTO_FEDERATE === "true";
6723
- const configPath = path.join(resolvedRepoPath, ".reckon/federation.json");
6722
+ let autoDiscover = process.env.KNODIN_AUTO_FEDERATE === "1" || process.env.KNODIN_AUTO_FEDERATE === "true";
6723
+ const configPath = path.join(resolvedRepoPath, ".knodin/federation.json");
6724
6724
  if (fs.existsSync(configPath)) {
6725
6725
  try {
6726
6726
  const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
@@ -6744,7 +6744,7 @@ async function getFederatedRepos(repoPath) {
6744
6744
  return repos;
6745
6745
  }
6746
6746
  // Auto-discovery (opt-in): siblings in the parent directory that knodin has
6747
- // already indexed (i.e. have a .reckon/db.sqlite). Under test the DB is
6747
+ // already indexed (i.e. have a .knodin/db.sqlite). Under test the DB is
6748
6748
  // in-memory, so no repo has that on-disk marker; it would only ever match
6749
6749
  // repos a *production* run indexed elsewhere on the machine, so test
6750
6750
  // federation is driven purely by in-process `dbInstances` — skip the disk
@@ -6761,7 +6761,7 @@ async function getFederatedRepos(repoPath) {
6761
6761
  if (siblingPath === resolvedRepoPath)
6762
6762
  continue;
6763
6763
  const isAlreadyIndexed = dbInstances.has(path.resolve(siblingPath)) ||
6764
- (!isTest && fs.existsSync(path.join(siblingPath, ".reckon", "db.sqlite")));
6764
+ (!isTest && fs.existsSync(path.join(siblingPath, ".knodin", "db.sqlite")));
6765
6765
  if (isAlreadyIndexed) {
6766
6766
  repos.push(siblingPath);
6767
6767
  }
@@ -8643,7 +8643,7 @@ function renderCommunityPage(community, map) {
8643
8643
  lines.push("");
8644
8644
  return lines.join("\n");
8645
8645
  }
8646
- /** Renders `.reckon/wiki/index.md`: repo path, community table, one link per page. */
8646
+ /** Renders `.knodin/wiki/index.md`: repo path, community table, one link per page. */
8647
8647
  function renderWikiIndex(resolvedRepoPath, map, slugs) {
8648
8648
  const lines = [];
8649
8649
  lines.push("# knodin Wiki", "");
@@ -8681,14 +8681,14 @@ function writeWikiPageIfChanged(dirPath, relFile, content, force, written, skipp
8681
8681
  written.push(relFile);
8682
8682
  }
8683
8683
  /**
8684
- * Generates the graph wiki (R5): `.reckon/wiki/index.md` + one page per
8684
+ * Generates the graph wiki (R5): `.knodin/wiki/index.md` + one page per
8685
8685
  * `map()` community. Reuses `engine.map(, "standard")` for community/hub/bridge/edge
8686
8686
  * data — no community-detection logic is duplicated here.
8687
8687
  */
8688
8688
  async function writeWiki(engine, repoPath, force) {
8689
8689
  const resolvedRepoPath = path.resolve(repoPath);
8690
8690
  const map = await engine.map(repoPath, "standard");
8691
- const wikiDir = path.join(resolvedRepoPath, ".reckon", "wiki");
8691
+ const wikiDir = path.join(resolvedRepoPath, ".knodin", "wiki");
8692
8692
  fs.mkdirSync(wikiDir, { recursive: true });
8693
8693
  // Slugify community names into unique page filenames (lowercase, hyphenated).
8694
8694
  const slugs = new Map();
@@ -9651,7 +9651,7 @@ export function createEngine() {
9651
9651
  // repair and timestamp drift before reporting it. Reuse an active DB in
9652
9652
  // long-lived processes, or open the existing local DB read-only.
9653
9653
  const activeDb = dbInstances.get(resolved);
9654
- const diskDbPath = path.join(resolved, ".reckon", "db.sqlite");
9654
+ const diskDbPath = path.join(resolved, ".knodin", "db.sqlite");
9655
9655
  const db = activeDb ??
9656
9656
  (fs.existsSync(diskDbPath) ? new Database(diskDbPath, { readonly: true }) : null);
9657
9657
  const verifiedAt = new Date().toISOString();
@@ -10057,7 +10057,7 @@ export function createEngine() {
10057
10057
  // Repair partial current-schema databases before normal initialization,
10058
10058
  // whose reconciliation queries require these columns. ALTER preserves all
10059
10059
  // healthy index_state rows; the reconciler then refreshes their snapshots.
10060
- const diskDbPath = path.join(resolved, ".reckon", "db.sqlite");
10060
+ const diskDbPath = path.join(resolved, ".knodin", "db.sqlite");
10061
10061
  if (fs.existsSync(diskDbPath)) {
10062
10062
  const activeDb = dbInstances.get(resolved);
10063
10063
  const schemaDb = activeDb ?? new Database(diskDbPath);
@@ -10551,7 +10551,7 @@ export function createEngine() {
10551
10551
  // full-corpus graph. Keep the opt-in ANN path for unfiltered searches;
10552
10552
  // filtered requests use the exact default over only eligible rows.
10553
10553
  if (ann.annEnabled() && !hasFilters) {
10554
- // R18 OPT-IN ANN PATH (RECKON_ANN=1). Build/reuse an in-memory HNSW for
10554
+ // R18 OPT-IN ANN PATH (KNODIN_ANN=1). Build/reuse an in-memory HNSW for
10555
10555
  // this repo and query it instead of computing cosine over every row. The
10556
10556
  // exact scan below stays the DEFAULT at every corpus size; this path only
10557
10557
  // engages behind the env flag, until a measured >=95% top-10 recall gate
@@ -12933,7 +12933,7 @@ export function createEngine() {
12933
12933
  for (const rel of wouldChange) {
12934
12934
  const abs = path.resolve(resolvedRepoPath, rel);
12935
12935
  const content = newContents.get(rel);
12936
- const tmp = path.join(path.dirname(abs), `.${path.basename(abs)}.reckon-rename-${process.pid}-${Date.now()}.tmp`);
12936
+ const tmp = path.join(path.dirname(abs), `.${path.basename(abs)}.knodin-rename-${process.pid}-${Date.now()}.tmp`);
12937
12937
  fs.writeFileSync(tmp, content, "utf-8");
12938
12938
  fs.renameSync(tmp, abs);
12939
12939
  }
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  /**
3
3
  * Single source of truth for "which directories does knodin ignore" (R14).
4
4
  *
5
- * Before this module, the same intent — skip node_modules, .git, .reckon,
5
+ * Before this module, the same intent — skip node_modules, .git, .knodin,
6
6
  * build output, caches, VCS internals — was encoded THREE separate times:
7
7
  * 1. src/__tests__/setup.ts's `PRUNE_DIRS` regex (glob-shim exclude filter)
8
8
  * 2. the indexer's own directory filter (`EXCLUDED_DIR_SEGMENTS` / `isIndexablePath`)
@@ -25,7 +25,7 @@ import path from "node:path";
25
25
  export const PRUNE_DIR_NAMES = new Set([
26
26
  "node_modules",
27
27
  ".git",
28
- ".reckon",
28
+ ".knodin",
29
29
  // Local state maintained by other code-intelligence tools. It is neither
30
30
  // repository source nor stable project configuration, and indexing it makes
31
31
  // a healthy checkout look perpetually stale as those tools update databases
@@ -45,7 +45,7 @@ export function deleteSymbolsForFile(db, filePath) {
45
45
  /**
46
46
  * Delete every symbol whose file path matches a LIKE pattern, plus its
47
47
  * embeddings. Used by the dbt path, whose symbols live under synthetic
48
- * `.reckon/dbt/%` paths rather than one real file.
48
+ * `.knodin/dbt/%` paths rather than one real file.
49
49
  */
50
50
  export function deleteSymbolsMatchingPath(db, likePattern) {
51
51
  db.run("DELETE FROM symbol_embeddings WHERE symbolId IN (SELECT id FROM symbols WHERE filePath LIKE ?)", [likePattern]);
@@ -2,7 +2,7 @@ import child_process from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  function activityPath(repo) {
5
- return path.join(path.resolve(repo), ".reckon", "index-activity.json");
5
+ return path.join(path.resolve(repo), ".knodin", "index-activity.json");
6
6
  }
7
7
  const ACTIVITY_CLOCK_SKEW_MS = 2_000;
8
8
  const MAX_ACTIVITY_AGE_MS = 7 * 24 * 60 * 60 * 1_000;
@@ -2,7 +2,7 @@
2
2
  import { clearLine as clearTerminalLine, moveCursor as moveTerminalCursor, cursorTo as moveTerminalCursorTo, } from "node:readline";
3
3
  import ora from "ora";
4
4
  import { formatProgress, normalizeTerminalDimension, } from "./init-progress.js";
5
- const terminalEnabled = process.stderr.isTTY === true || process.env.RECKON_FORCE_PROGRESS_TTY === "1";
5
+ const terminalEnabled = process.stderr.isTTY === true || process.env.KNODIN_FORCE_PROGRESS_TTY === "1";
6
6
  // Ora divides rendered text width by `stream.columns`. Some pseudo-terminals
7
7
  // report zero columns, which turns its clear loop into an infinite loop. Keep
8
8
  // the real terminal methods while guaranteeing finite viewport dimensions.