knodin 0.5.1 → 0.7.3

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 (70) hide show
  1. package/README.md +190 -535
  2. package/dist/bin/cli.js +173 -17
  3. package/dist/bin/launcher.js +11 -0
  4. package/dist/src/agent-integration.js +4 -16
  5. package/dist/src/artifact-refresh.js +1 -1
  6. package/dist/src/cli-args.js +8 -1
  7. package/dist/src/cli-model.js +35 -1
  8. package/dist/src/codeflow-replay.js +80 -0
  9. package/dist/src/competitive-cold-mcp.js +40 -0
  10. package/dist/src/competitive-manifest.js +106 -25
  11. package/dist/src/competitive-runner.js +37 -3
  12. package/dist/src/competitive-sandbox.js +4 -4
  13. package/dist/src/context-export.js +1 -1
  14. package/dist/src/diagnostics.js +449 -0
  15. package/dist/src/docs-sections.js +5 -5
  16. package/dist/src/engine/ann-hnsw.js +5 -5
  17. package/dist/src/engine/embeddings.js +4 -4
  18. package/dist/src/engine/git-history.js +289 -0
  19. package/dist/src/engine/index.js +450 -103
  20. package/dist/src/engine/prune.js +2 -2
  21. package/dist/src/engine/scip-import.js +408 -0
  22. package/dist/src/engine/symbol-delete.js +1 -1
  23. package/dist/src/execution-profile.js +203 -0
  24. package/dist/src/failure-diagnosis.js +69 -10
  25. package/dist/src/hook-manager-integration.js +156 -0
  26. package/dist/src/index-activity.js +1 -1
  27. package/dist/src/init-progress-worker.js +1 -1
  28. package/dist/src/init.js +361 -92
  29. package/dist/src/lifecycle-health.js +51 -14
  30. package/dist/src/lsp-readonly.js +1 -1
  31. package/dist/src/output-compression.js +1 -1
  32. package/dist/src/output-telemetry.js +8 -4
  33. package/dist/src/pr-triage.js +2 -2
  34. package/dist/src/progressive-evidence.js +473 -0
  35. package/dist/src/pure-compression-cli.js +101 -0
  36. package/dist/src/release-preflight.js +510 -0
  37. package/dist/src/repair-progress-worker.js +1 -1
  38. package/dist/src/repository-init-process.js +1 -1
  39. package/dist/src/repository-management.js +145 -3
  40. package/dist/src/response-budget.js +11 -1
  41. package/dist/src/server.js +23 -3
  42. package/dist/src/structural-fast-path.js +303 -0
  43. package/dist/src/structural-snapshot.js +33 -0
  44. package/dist/src/system-config.js +7 -7
  45. package/dist/src/tools/knodin-tools.js +106 -15
  46. package/dist/src/update-ceremony.js +158 -0
  47. package/dist/src/update-policy.js +4 -4
  48. package/dist/src/wait-for-fresh.js +2 -2
  49. package/dist/src/worktree-lifecycle.js +2 -2
  50. package/docs/CLI.md +22 -0
  51. package/docs/COMMAND-OUTPUT-COMPRESSION.md +32 -16
  52. package/docs/CONTAINED-EXECUTION.md +77 -0
  53. package/docs/DIAGNOSTICS.md +45 -0
  54. package/docs/DOCTOR-AND-UPDATES.md +7 -4
  55. package/docs/GIT-HISTORY-REVIEW.md +39 -0
  56. package/docs/INSTALLATION.md +1 -1
  57. package/docs/MCP.md +15 -0
  58. package/docs/PROGRESSIVE-EVIDENCE.md +37 -0
  59. package/docs/REPOSITORIES-AND-WORKTREES.md +31 -1
  60. package/docs/SCIP-IMPORT.md +57 -0
  61. package/docs/SIGNED-UPDATES.md +5 -0
  62. package/docs/SYSTEMS-AND-RELATIONSHIPS.md +2 -2
  63. package/docs/TELEMETRY.md +6 -2
  64. package/docs/releases/0.6.0.md +18 -0
  65. package/docs/releases/0.7.0.md +24 -0
  66. package/docs/releases/0.7.1.md +21 -0
  67. package/docs/releases/0.7.2.md +21 -0
  68. package/docs/releases/0.7.3.md +23 -0
  69. package/package.json +34 -3
  70. package/dist/src/tools/reckon-tools.js +0 -5
@@ -1,7 +1,8 @@
1
1
  import childProcess from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- const MANAGED_MARKERS = ["KNODIN MANAGED HOOK", "RECKON-GRAPH MANAGED HOOK"];
4
+ import { inspectLefthookIntegration, isActiveLefthookHook } from "./hook-manager-integration.js";
5
+ const MANAGED_MARKER = "KNODIN MANAGED HOOK";
5
6
  const HOOK_NAMES = ["post-commit", "post-checkout", "post-merge", "post-rewrite"];
6
7
  const HOOK_FAILURE_FILE = "last-refresh-failure";
7
8
  function readEvent(target) {
@@ -35,11 +36,11 @@ function readEvent(target) {
35
36
  }
36
37
  }
37
38
  function readLastEvent(repo) {
38
- return readEvent(path.join(repo, ".reckon", "hooks", "last-refresh-event"));
39
+ return readEvent(path.join(repo, ".knodin", "hooks", "last-refresh-event"));
39
40
  }
40
41
  function readPendingEvent(repo) {
41
42
  try {
42
- const directory = path.join(repo, ".reckon", "hooks", "events");
43
+ const directory = path.join(repo, ".knodin", "hooks", "events");
43
44
  const next = fs
44
45
  .readdirSync(directory)
45
46
  .filter((entry) => entry.endsWith(".event"))
@@ -51,7 +52,7 @@ function readPendingEvent(repo) {
51
52
  }
52
53
  }
53
54
  function processorState(repo) {
54
- const pidPath = path.join(repo, ".reckon", "hooks", "refresh.lock", "pid");
55
+ const pidPath = path.join(repo, ".knodin", "hooks", "refresh.lock", "pid");
55
56
  if (!fs.existsSync(path.dirname(pidPath)))
56
57
  return { state: "idle", pid: null };
57
58
  let pid = null;
@@ -78,7 +79,7 @@ function processorState(repo) {
78
79
  function queuedEventCount(repo) {
79
80
  try {
80
81
  return fs
81
- .readdirSync(path.join(repo, ".reckon", "hooks", "events"))
82
+ .readdirSync(path.join(repo, ".knodin", "hooks", "events"))
82
83
  .filter((entry) => entry.endsWith(".event")).length;
83
84
  }
84
85
  catch {
@@ -89,26 +90,45 @@ function activeHooksDirectory(repo) {
89
90
  const output = childProcess.execFileSync("git", ["rev-parse", "--path-format=absolute", "--git-path", "hooks"], { cwd: repo, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
90
91
  return output.trim();
91
92
  }
92
- function inspectManagedHooks(hooksDirectory) {
93
+ function inspectManagedHooks(repo, hooksDirectory) {
93
94
  const activeHooks = [];
94
95
  const missingHooks = [];
96
+ let wrappers = 0;
97
+ let managerNative = 0;
98
+ const lefthook = inspectLefthookIntegration(repo);
95
99
  for (const hookName of HOOK_NAMES) {
96
100
  try {
97
101
  const hookPath = path.join(hooksDirectory, hookName);
98
102
  const content = fs.readFileSync(hookPath, "utf-8");
99
103
  const mode = fs.statSync(hookPath).mode;
100
- const active = (mode & 0o111) !== 0 &&
101
- MANAGED_MARKERS.some((marker) => content.startsWith(`#!/bin/sh\n# ${marker}: ${hookName}\n`));
104
+ const wrapper = content.startsWith(`#!/bin/sh\n# ${MANAGED_MARKER}: ${hookName}\n`);
105
+ const native = lefthook.configured && isActiveLefthookHook(content);
106
+ const active = (mode & 0o111) !== 0 && (wrapper || native);
107
+ if (active && wrapper)
108
+ wrappers++;
109
+ else if (active && native)
110
+ managerNative++;
102
111
  (active ? activeHooks : missingHooks).push(hookName);
103
112
  }
104
113
  catch {
105
114
  missingHooks.push(hookName);
106
115
  }
107
116
  }
108
- return { activeHooks, missingHooks };
117
+ return {
118
+ activeHooks,
119
+ missingHooks,
120
+ hookManager: lefthook.configured ? "lefthook" : null,
121
+ routing: wrappers > 0 && managerNative > 0
122
+ ? "mixed"
123
+ : wrappers > 0
124
+ ? "managed-wrapper"
125
+ : managerNative > 0
126
+ ? "manager-native"
127
+ : "unavailable",
128
+ };
109
129
  }
110
130
  function readRefreshFailure(repo) {
111
- const failurePath = path.join(repo, ".reckon", "hooks", HOOK_FAILURE_FILE);
131
+ const failurePath = path.join(repo, ".knodin", "hooks", HOOK_FAILURE_FILE);
112
132
  if (!fs.existsSync(failurePath))
113
133
  return null;
114
134
  try {
@@ -122,7 +142,7 @@ function readRefreshFailure(repo) {
122
142
  /** Read-only proof that committed Git activity can still reach knodin's indexer. */
123
143
  export function inspectLifecycleHealth(repoPath) {
124
144
  const repo = path.resolve(repoPath);
125
- if (!fs.existsSync(path.join(repo, ".reckon", "db.sqlite"))) {
145
+ if (!fs.existsSync(path.join(repo, ".knodin", "db.sqlite"))) {
126
146
  return {
127
147
  status: "unconfigured",
128
148
  refreshCapable: false,
@@ -135,6 +155,8 @@ export function inspectLifecycleHealth(repoPath) {
135
155
  pendingEvent: null,
136
156
  lastEvent: null,
137
157
  lastError: null,
158
+ hookManager: null,
159
+ routing: "unavailable",
138
160
  };
139
161
  }
140
162
  let hooksDirectory;
@@ -154,17 +176,19 @@ export function inspectLifecycleHealth(repoPath) {
154
176
  pendingEvent: readPendingEvent(repo),
155
177
  lastEvent: readLastEvent(repo),
156
178
  lastError: null,
179
+ hookManager: null,
180
+ routing: "unavailable",
157
181
  };
158
182
  }
159
- const { activeHooks, missingHooks } = inspectManagedHooks(hooksDirectory);
160
- const background = path.join(repo, ".reckon", "hooks", "background-index.sh");
183
+ const { activeHooks, missingHooks, hookManager, routing } = inspectManagedHooks(repo, hooksDirectory);
184
+ const background = path.join(repo, ".knodin", "hooks", "background-index.sh");
161
185
  const backgroundReady = fs.existsSync(background) && (fs.statSync(background).mode & 0o111) !== 0;
162
186
  const issues = missingHooks.map((hook) => `${hook} no longer routes through knodin's active Git hook path`);
163
187
  if (!backgroundReady)
164
188
  issues.push("background indexer is missing or not executable");
165
189
  const lastError = readRefreshFailure(repo);
166
190
  if (lastError)
167
- issues.push(`${lastError} See .reckon/indexer.log for details`);
191
+ issues.push(`${lastError} See .knodin/indexer.log for details`);
168
192
  const refreshCapable = missingHooks.length === 0 && backgroundReady && issues.length === 0;
169
193
  return {
170
194
  status: refreshCapable ? "healthy" : "degraded",
@@ -178,6 +202,8 @@ export function inspectLifecycleHealth(repoPath) {
178
202
  pendingEvent: readPendingEvent(repo),
179
203
  lastEvent: readLastEvent(repo),
180
204
  lastError,
205
+ hookManager,
206
+ routing,
181
207
  };
182
208
  }
183
209
  /** Merge graph and refresh-path health without reporting absent evidence as healthy. */
@@ -211,3 +237,14 @@ export function attachLifecycleHealth(repo, graph) {
211
237
  lifecycle,
212
238
  };
213
239
  }
240
+ /** Keep graph-repair success distinct from lifecycle integration health. */
241
+ export function attachRepairLifecycle(repo, result) {
242
+ const lifecycle = inspectLifecycleHealth(repo);
243
+ return {
244
+ ...result,
245
+ lifecycle,
246
+ nextSteps: lifecycle.status === "degraded"
247
+ ? ["Run `knodin init` to restore lifecycle routing, then `knodin status`."]
248
+ : [],
249
+ };
250
+ }
@@ -59,7 +59,7 @@ function filesForService(repoPath, ts) {
59
59
  const files = [];
60
60
  const visit = (directory) => {
61
61
  for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
62
- if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".reckon")
62
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".knodin")
63
63
  continue;
64
64
  const candidate = path.join(directory, entry.name);
65
65
  if (entry.isDirectory())
@@ -335,7 +335,7 @@ function percentReduction(input, output) {
335
335
  }
336
336
  function artifactDirectory(repo) {
337
337
  const resolvedRepo = fs.realpathSync(repo);
338
- const knodinDirectory = path.join(resolvedRepo, ".reckon");
338
+ const knodinDirectory = path.join(resolvedRepo, ".knodin");
339
339
  const outputDirectory = path.join(knodinDirectory, "output");
340
340
  for (const candidate of [knodinDirectory, outputDirectory]) {
341
341
  if (fs.existsSync(candidate) && fs.lstatSync(candidate).isSymbolicLink())
@@ -5,7 +5,7 @@ import { encode } from "gpt-tokenizer/encoding/o200k_base";
5
5
  export const TELEMETRY_TOKENIZER = "gpt-tokenizer@3.4.0:o200k_base";
6
6
  export const countOutputTokens = (value) => encode(value).length;
7
7
  export const DEFAULT_TELEMETRY_RETENTION_DAYS = 30;
8
- const DEFAULT_INPUT = ".reckon-telemetry.jsonl";
8
+ const DEFAULT_INPUT = ".knodin-telemetry.jsonl";
9
9
  function lineCount(value) {
10
10
  return value.length === 0 ? 0 : value.split(/\r?\n/).length;
11
11
  }
@@ -104,7 +104,7 @@ export function measureOutput(options) {
104
104
  const availability = options.output.availability;
105
105
  const freshness = options.output.freshness;
106
106
  const fidelity = options.output.fidelity;
107
- const database = path.join(fs.realpathSync(options.repo), ".reckon", "db.sqlite");
107
+ const database = path.join(fs.realpathSync(options.repo), ".knodin", "db.sqlite");
108
108
  const latencyMs = Math.round((performance.now() - options.startedAt) * 100) / 100;
109
109
  const outcome = (status !== undefined &&
110
110
  ["error", "failed", "unavailable", "blocked", "timeout"].includes(status)) ||
@@ -121,6 +121,7 @@ export function measureOutput(options) {
121
121
  at: new Date().toISOString(),
122
122
  repositoryId: repositoryIdentifier(options.repo),
123
123
  operation: options.operation,
124
+ ...(options.executionProfile ? { executionProfile: options.executionProfile } : {}),
124
125
  latencyMs,
125
126
  rssBytes: process.memoryUsage().rss,
126
127
  temperature: "cold",
@@ -180,7 +181,7 @@ function telemetrySummary(records) {
180
181
  indexingMs: records.reduce((sum, record) => sum + (record.indexingMs ?? 0), 0),
181
182
  };
182
183
  }
183
- export function writeTelemetryReport(repoPath, records, outputPath = ".reckon/telemetry-report.html") {
184
+ export function writeTelemetryReport(repoPath, records, outputPath = ".knodin/telemetry-report.html") {
184
185
  const { repo, target: output } = privatePath(repoPath, outputPath, "report");
185
186
  const groups = new Map();
186
187
  for (const record of records) {
@@ -231,6 +232,9 @@ function sanitizeTelemetryRecord(record) {
231
232
  at: record.at,
232
233
  repositoryId: record.repositoryId,
233
234
  operation: record.operation,
235
+ ...(record.executionProfile && /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(record.executionProfile)
236
+ ? { executionProfile: record.executionProfile }
237
+ : {}),
234
238
  latencyMs: record.latencyMs,
235
239
  rssBytes: record.rssBytes,
236
240
  temperature: record.temperature,
@@ -326,7 +330,7 @@ export function telemetryStatus(repoPath, inputPath = DEFAULT_INPUT, retentionDa
326
330
  tokenizer: TELEMETRY_TOKENIZER,
327
331
  };
328
332
  }
329
- export function exportTelemetry(repoPath, records, outputPath = ".reckon/telemetry-export.json") {
333
+ export function exportTelemetry(repoPath, records, outputPath = ".knodin/telemetry-export.json") {
330
334
  const { repo, target } = privatePath(repoPath, outputPath, "export");
331
335
  const bundle = {
332
336
  schemaVersion: 1,
@@ -12,10 +12,10 @@
12
12
  * than `ensureGhAvailable()` (throwing, still used by the CLI's hard-fail).
13
13
  */
14
14
  import child_process from "node:child_process";
15
- /** Resolve the `gh` binary to invoke. Tests override via `RECKON_GH_BIN` to
15
+ /** Resolve the `gh` binary to invoke. Tests override via `KNODIN_GH_BIN` to
16
16
  * point at a stub script fixture — no network in tests. */
17
17
  function ghBin() {
18
- return process.env.RECKON_GH_BIN || "gh";
18
+ return process.env.KNODIN_GH_BIN || "gh";
19
19
  }
20
20
  const STALE_DAYS = 30;
21
21
  const PR_LIST_FIELDS = "number,title,updatedAt,reviewDecision,statusCheckRollup,files,headRefName";