knodin 0.7.6 → 0.8.2

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 (130) hide show
  1. package/README.md +19 -7
  2. package/benchmarks/competitors/SYNTHESIS.md +66 -0
  3. package/dist/bin/cli.js +2164 -108
  4. package/dist/bin/launcher.js +25 -3
  5. package/dist/src/agent-integration.js +304 -0
  6. package/dist/src/artifact-refresh.js +82 -0
  7. package/dist/src/cli-args.js +292 -0
  8. package/dist/src/cli-model.js +384 -0
  9. package/dist/src/codeflow-replay.js +81 -0
  10. package/dist/src/compact-structural.js +96 -0
  11. package/dist/src/compare.js +39 -0
  12. package/dist/src/competitive-cold-mcp.js +40 -0
  13. package/dist/src/competitive-constraints.js +21 -0
  14. package/dist/src/competitive-manifest.js +411 -0
  15. package/dist/src/competitive-measurement.js +183 -0
  16. package/dist/src/competitive-runner.js +487 -0
  17. package/dist/src/competitive-sandbox.js +108 -0
  18. package/dist/src/context-export.js +423 -0
  19. package/dist/src/context.js +102 -0
  20. package/dist/src/deterministic-random.js +34 -0
  21. package/dist/src/diagnostics-write-helper.js +473 -0
  22. package/dist/src/diagnostics.js +1476 -0
  23. package/dist/src/docs-sections.js +141 -0
  24. package/dist/src/doctor.js +382 -0
  25. package/dist/src/engine/ann-hnsw.js +261 -0
  26. package/dist/src/engine/embeddings.js +193 -0
  27. package/dist/src/engine/file-walker.js +49 -0
  28. package/dist/src/engine/git-history.js +289 -0
  29. package/dist/src/engine/index.js +14238 -0
  30. package/dist/src/engine/perf.js +115 -0
  31. package/dist/src/engine/prune.js +112 -0
  32. package/dist/src/engine/sarif-import.js +341 -0
  33. package/dist/src/engine/scip-import.js +423 -0
  34. package/dist/src/engine/source-policy.js +85 -0
  35. package/dist/src/engine/sqlite.js +71 -0
  36. package/dist/src/engine/state-paths.js +175 -0
  37. package/dist/src/engine/symbol-delete.js +58 -0
  38. package/dist/src/execution-profile.js +208 -0
  39. package/dist/src/failure-diagnosis.js +655 -0
  40. package/dist/src/fleet.js +7 -0
  41. package/dist/src/git-executable.js +31 -0
  42. package/dist/src/graph-layout.js +173 -0
  43. package/dist/src/graph-query-health.js +115 -0
  44. package/dist/src/hook-manager-integration.js +156 -0
  45. package/dist/src/index-activity.js +126 -0
  46. package/dist/src/init-progress-worker.js +106 -2
  47. package/dist/src/init-progress.js +155 -0
  48. package/dist/src/init.js +1295 -0
  49. package/dist/src/lifecycle-health.js +282 -0
  50. package/dist/src/lsp-readonly.js +217 -0
  51. package/dist/src/mcp-graph-worker.js +69 -0
  52. package/dist/src/mcp-reliability.js +154 -0
  53. package/dist/src/mcp-worker-supervisor.js +350 -0
  54. package/dist/src/mirror.js +290 -0
  55. package/dist/src/node-runtime.js +157 -0
  56. package/dist/src/output-compression.js +630 -0
  57. package/dist/src/output-telemetry.js +368 -0
  58. package/dist/src/pr-triage.js +638 -0
  59. package/dist/src/progressive-evidence.js +477 -0
  60. package/dist/src/pure-compression-cli.js +102 -0
  61. package/dist/src/relationship-adapters.js +377 -0
  62. package/dist/src/release-attestation.js +533 -0
  63. package/dist/src/release-preflight.js +513 -0
  64. package/dist/src/repair-lease.js +85 -0
  65. package/dist/src/repair-progress-worker.js +120 -2
  66. package/dist/src/repair-progress.js +262 -0
  67. package/dist/src/repository-init-process.js +177 -0
  68. package/dist/src/repository-management.js +1261 -0
  69. package/dist/src/response-budget.js +196 -0
  70. package/dist/src/server.js +217 -0
  71. package/dist/src/structural-fast-path.js +344 -0
  72. package/dist/src/structural-snapshot.js +37 -0
  73. package/dist/src/system-config.js +638 -0
  74. package/dist/src/terminal-help.js +83 -0
  75. package/dist/src/tools/knodin-tools.js +1640 -0
  76. package/dist/src/update-ceremony.js +162 -0
  77. package/dist/src/update-policy.js +944 -0
  78. package/dist/src/update-trust.js +504 -0
  79. package/dist/src/version.js +13 -0
  80. package/dist/src/visualization.js +515 -0
  81. package/dist/src/wait-for-fresh.js +98 -0
  82. package/dist/src/worktree-lifecycle.js +234 -0
  83. package/docs/BEHAVIORAL-CONTRACT.md +72 -0
  84. package/docs/CLI.md +20 -1
  85. package/docs/COMPARISON.md +403 -0
  86. package/docs/COMPETITIVE-LANDSCAPE-2026-08.md +267 -0
  87. package/docs/CONTAINED-EXECUTION.md +77 -0
  88. package/docs/DIAGNOSTICS.md +80 -0
  89. package/docs/GIT-HISTORY-REVIEW.md +39 -0
  90. package/docs/HANDOFF.md +180 -0
  91. package/docs/INSTALLATION.md +21 -18
  92. package/docs/MCP.md +59 -8
  93. package/docs/PROGRESSIVE-EVIDENCE.md +37 -0
  94. package/docs/PT-ACCESS-RECOMMENDATION.md +89 -0
  95. package/docs/RELEASE-0.3-EVIDENCE.md +73 -0
  96. package/docs/REPOSITORIES-AND-WORKTREES.md +18 -6
  97. package/docs/SCIP-IMPORT.md +62 -0
  98. package/docs/SIGNED-UPDATES.md +151 -0
  99. package/docs/TELEMETRY.md +46 -0
  100. package/docs/TOKEN-OPTIMIZER-SCORECARD.md +79 -0
  101. package/docs/assets/knodin-favicon.svg +4 -0
  102. package/docs/releases/0.3.0.md +46 -0
  103. package/docs/releases/0.4.0.md +68 -0
  104. package/docs/releases/0.4.1.md +28 -0
  105. package/docs/releases/0.4.2.md +27 -0
  106. package/docs/releases/0.4.3.md +23 -0
  107. package/docs/releases/0.5.0.md +29 -0
  108. package/docs/releases/0.5.1.md +17 -0
  109. package/docs/releases/0.6.0.md +18 -0
  110. package/docs/releases/0.7.0.md +24 -0
  111. package/docs/releases/0.7.1.md +21 -0
  112. package/docs/releases/0.7.2.md +21 -0
  113. package/docs/releases/0.7.3.md +23 -0
  114. package/docs/releases/0.7.4.md +17 -0
  115. package/docs/releases/0.7.5.md +20 -0
  116. package/docs/releases/0.8.0.md +74 -0
  117. package/docs/releases/0.8.2.md +34 -0
  118. package/package.json +127 -4
  119. package/roadmap/competitive-roadmap.md +3801 -0
  120. package/schemas/release-attestation-v1.schema.json +210 -0
  121. package/schemas/support-bundle-v2.schema.json +212 -0
  122. package/dist/chunks/chunk-DMQAGX77.js +0 -654
  123. package/dist/chunks/chunk-F4Z3Z766.js +0 -4
  124. package/dist/chunks/chunk-SIJAQVSX.js +0 -3
  125. package/dist/chunks/chunk-X6M4HUUE.js +0 -2
  126. package/dist/chunks/chunk-YPRMY2LP.js +0 -8
  127. package/dist/chunks/pure-compression-cli-4TA2TQD5.js +0 -5
  128. package/dist/chunks/server-7EDF4CBY.js +0 -14
  129. package/dist/chunks/structural-fast-path-KD5KQSPX.js +0 -4
  130. package/docs/releases/0.7.6.md +0 -25
@@ -0,0 +1,292 @@
1
+ /**
2
+ * CLI argument & repo-target resolution for the `knodin` CLI (R19).
3
+ *
4
+ * The MCP gateway takes an explicit `repoPath`; the CLI historically fell back
5
+ * to `process.cwd()` with no equivalent flag, so `knodin index /other/repo` from
6
+ * the wrong cwd silently indexed nothing and exited 0. These pure helpers give
7
+ * the CLI one unambiguous way to target a repo (`--repo`) and make every no-op
8
+ * fail loudly. Kept side-effect-free (only stat the filesystem) so bin/cli.ts is
9
+ * a thin caller and the logic is unit-testable without an engine or database.
10
+ */
11
+ import fs from "node:fs";
12
+ import { createRequire } from "node:module";
13
+ import path from "node:path";
14
+ const GLOBAL_VALUE_FLAGS = new Set([
15
+ "--identity",
16
+ "--file",
17
+ "--kind",
18
+ "--to-identity",
19
+ "--to-file",
20
+ "--to-kind",
21
+ "--bytes",
22
+ "--tokens",
23
+ "--items",
24
+ "--impact-mode",
25
+ "--direction",
26
+ "--depth",
27
+ "--relations",
28
+ "--min-confidence",
29
+ "--limit",
30
+ ]);
31
+ const GLOBAL_BOOLEAN_FLAGS = new Set(["--exclude-tests", "--data-flow", "--json"]);
32
+ /**
33
+ * Command-scoped flags that take a value. These are NOT global — they are not
34
+ * hoisted or reordered — but their value must never be mistaken for a
35
+ * positional path. `knodin index --sarif results.sarif` asks to import that log,
36
+ * not to index the log as a source file.
37
+ */
38
+ const COMMAND_VALUE_FLAGS = new Set([
39
+ "--scip",
40
+ "--scip-max-bytes",
41
+ "--scip-max-files",
42
+ "--scip-max-facts",
43
+ "--scip-timeout-ms",
44
+ "--sarif",
45
+ "--sarif-max-bytes",
46
+ "--sarif-max-findings",
47
+ "--sarif-timeout-ms",
48
+ ]);
49
+ const isDir = (p) => {
50
+ try {
51
+ return fs.statSync(p).isDirectory();
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ };
57
+ const RUNTIME_MODULE_FLAGS = new Set([
58
+ "--import",
59
+ "--loader",
60
+ "--experimental-loader",
61
+ "--require",
62
+ "-r",
63
+ ]);
64
+ function resolveRuntimeModuleReference(value, cwd, resolveModule) {
65
+ if (path.isAbsolute(value) || /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value))
66
+ return value;
67
+ if (value.startsWith("."))
68
+ return path.resolve(cwd, value);
69
+ return resolveModule(value, cwd);
70
+ }
71
+ /**
72
+ * Reconstruct the executable command that launched this CLI. Source-checkout
73
+ * invocations need loader arguments such as `node --import tsx`; dropping
74
+ * `execArgv` creates a background hook that exists but cannot start. Bare
75
+ * loader names are resolved now because the hook later runs from another repo.
76
+ */
77
+ export function resolveCliRuntimeCommand(runtime, resolveModule = (specifier, cwd) => createRequire(path.join(cwd, "package.json")).resolve(specifier)) {
78
+ const entry = runtime.argv[1];
79
+ if (!entry)
80
+ throw new Error("knodin: CLI entry path is unavailable");
81
+ const cwd = runtime.cwd();
82
+ const runtimeArguments = [];
83
+ for (let index = 0; index < runtime.execArgv.length; index++) {
84
+ const argument = runtime.execArgv[index];
85
+ if (RUNTIME_MODULE_FLAGS.has(argument)) {
86
+ const value = runtime.execArgv[index + 1];
87
+ if (!value)
88
+ throw new Error(`knodin: ${argument} requires a runtime module`);
89
+ runtimeArguments.push(argument, resolveRuntimeModuleReference(value, cwd, resolveModule));
90
+ index++;
91
+ continue;
92
+ }
93
+ const equalsFlag = [...RUNTIME_MODULE_FLAGS].find((flag) => argument.startsWith(`${flag}=`));
94
+ if (equalsFlag) {
95
+ const value = argument.slice(equalsFlag.length + 1);
96
+ if (!value)
97
+ throw new Error(`knodin: ${equalsFlag} requires a runtime module`);
98
+ runtimeArguments.push(`${equalsFlag}=${resolveRuntimeModuleReference(value, cwd, resolveModule)}`);
99
+ continue;
100
+ }
101
+ runtimeArguments.push(argument);
102
+ }
103
+ return [path.resolve(cwd, runtime.execPath), ...runtimeArguments, path.resolve(cwd, entry)];
104
+ }
105
+ /**
106
+ * Pull a global `--repo <path>` / `--repo=<path>` out of an argv slice and
107
+ * move any other documented global flags before the subcommand to immediately
108
+ * after it. This preserves the existing command parsers while accepting both
109
+ * conventional `knodin --file x explain y` and `knodin explain y --file x`.
110
+ */
111
+ export function extractRepoFlag(argv) {
112
+ const rest = [];
113
+ const leadingGlobalOptions = [];
114
+ let repoFlag;
115
+ let commandSeen = false;
116
+ for (let i = 0; i < argv.length; i++) {
117
+ const a = argv[i];
118
+ if (a === "--repo") {
119
+ repoFlag = argv[i + 1] ?? "";
120
+ i++; // consume the value
121
+ }
122
+ else if (a.startsWith("--repo=")) {
123
+ repoFlag = a.slice("--repo=".length);
124
+ }
125
+ else if (!commandSeen && GLOBAL_VALUE_FLAGS.has(a)) {
126
+ leadingGlobalOptions.push(a);
127
+ if (argv[i + 1] !== undefined)
128
+ leadingGlobalOptions.push(argv[++i]);
129
+ }
130
+ else if (!commandSeen && GLOBAL_BOOLEAN_FLAGS.has(a)) {
131
+ leadingGlobalOptions.push(a);
132
+ }
133
+ else {
134
+ rest.push(a);
135
+ commandSeen = true;
136
+ }
137
+ }
138
+ rest.push(...leadingGlobalOptions);
139
+ return { repoFlag, rest };
140
+ }
141
+ /** Return positional arguments without mistaking recognized option values for paths. */
142
+ export function extractPositionals(args) {
143
+ const positionals = [];
144
+ for (let index = 0; index < args.length; index++) {
145
+ const argument = args[index];
146
+ if (argument.startsWith("--")) {
147
+ if (!argument.includes("=") &&
148
+ (GLOBAL_VALUE_FLAGS.has(argument) || COMMAND_VALUE_FLAGS.has(argument)))
149
+ index++;
150
+ continue;
151
+ }
152
+ positionals.push(argument);
153
+ }
154
+ return positionals;
155
+ }
156
+ /**
157
+ * Resolve the target repo: an explicit `--repo` (validated as an existing
158
+ * directory) wins over `cwd`. An absent flag falls back to `cwd`.
159
+ */
160
+ export function resolveRepo(repoFlag, cwd) {
161
+ if (repoFlag === undefined)
162
+ return { ok: true, repo: cwd };
163
+ if (repoFlag === "")
164
+ return { ok: false, error: "knodin: --repo requires a <path>" };
165
+ const repo = path.resolve(cwd, repoFlag);
166
+ if (!fs.existsSync(repo)) {
167
+ return { ok: false, error: `knodin: --repo path does not exist: ${repo}` };
168
+ }
169
+ if (!isDir(repo)) {
170
+ return { ok: false, error: `knodin: --repo path is not a directory: ${repo}` };
171
+ }
172
+ return { ok: true, repo };
173
+ }
174
+ /**
175
+ * Decide what `knodin index [positionals...]` actually targets, closing the
176
+ * silent-no-op hole:
177
+ * - a lone directory positional with no `--repo` IS the repo (full reindex) —
178
+ * this is the observed-bug fix: `knodin index /other/repo` now indexes it;
179
+ * - any other directory positional is rejected, pointing at `--repo`;
180
+ * - file positionals resolving outside the target repo are rejected loudly;
181
+ * - otherwise the positionals are files to index within the resolved repo.
182
+ */
183
+ export function planIndex(repoFlag, positionals, cwd) {
184
+ // Case A: no --repo and a single directory positional => that directory is
185
+ // the repo to index, not a file. Absorb it as the target.
186
+ if (repoFlag === undefined && positionals.length === 1) {
187
+ const abs = path.resolve(cwd, positionals[0]);
188
+ if (isDir(abs)) {
189
+ return { ok: true, repo: abs, files: undefined };
190
+ }
191
+ }
192
+ const resolved = resolveRepo(repoFlag, cwd);
193
+ if (!resolved.ok)
194
+ return resolved;
195
+ const repo = resolved.repo;
196
+ if (positionals.length === 0) {
197
+ return { ok: true, repo, files: undefined }; // full reindex
198
+ }
199
+ // Case B: positionals are files. Validate each is a within-repo non-directory.
200
+ const rejectedDirs = [];
201
+ const rejectedOutside = [];
202
+ const files = [];
203
+ for (const p of positionals) {
204
+ const abs = path.resolve(repo, p);
205
+ const rel = path.relative(repo, abs);
206
+ if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
207
+ rejectedOutside.push(p);
208
+ continue;
209
+ }
210
+ if (isDir(abs)) {
211
+ rejectedDirs.push(p);
212
+ continue;
213
+ }
214
+ files.push(p);
215
+ }
216
+ if (rejectedOutside.length > 0 || rejectedDirs.length > 0) {
217
+ const parts = [];
218
+ if (rejectedOutside.length > 0) {
219
+ parts.push(`outside repo (${rejectedOutside.join(", ")})`);
220
+ }
221
+ if (rejectedDirs.length > 0) {
222
+ const hint = repoFlag === undefined
223
+ ? "use --repo to index a whole repo"
224
+ : "pass files, not directories, alongside --repo";
225
+ parts.push(`directories — ${hint} (${rejectedDirs.join(", ")})`);
226
+ }
227
+ return {
228
+ ok: false,
229
+ error: `knodin index: refusing to index against ${repo}; rejected ${parts.join("; ")}`,
230
+ };
231
+ }
232
+ return { ok: true, repo, files };
233
+ }
234
+ /**
235
+ * Guard against reporting success for a no-op: an index run that touched zero
236
+ * files must exit non-zero with a diagnostic naming the repo it actually used.
237
+ */
238
+ export function checkIndexed(indexed, repo) {
239
+ if (!indexed || indexed.length === 0) {
240
+ return { ok: false, error: `knodin index: indexed 0 files in ${repo} (nothing matched)` };
241
+ }
242
+ return { ok: true };
243
+ }
244
+ /** Pure parser for `knodin review`, shared by the executable and CLI contract tests. */
245
+ export function parseReviewArgs(args) {
246
+ const value = (flag) => {
247
+ const index = args.indexOf(flag);
248
+ if (index < 0)
249
+ return undefined;
250
+ const candidate = args[index + 1];
251
+ if (!candidate || candidate.startsWith("--")) {
252
+ throw new Error(`knodin review: ${flag} requires a value`);
253
+ }
254
+ return candidate;
255
+ };
256
+ const rawScope = value("--scope");
257
+ const scopes = ["unstaged", "staged", "all", "compare"];
258
+ if (rawScope && !scopes.includes(rawScope)) {
259
+ throw new Error(`invalid review scope: ${rawScope}`);
260
+ }
261
+ const optionValues = new Set([
262
+ ...GLOBAL_VALUE_FLAGS,
263
+ "--scope",
264
+ "--from",
265
+ "--to",
266
+ "--files",
267
+ "--scip",
268
+ ]);
269
+ const positionals = args.filter((arg, index) => {
270
+ if (arg.startsWith("--"))
271
+ return false;
272
+ return index === 0 || !optionValues.has(args[index - 1]);
273
+ });
274
+ const filesValue = value("--files");
275
+ const files = filesValue
276
+ ?.split(",")
277
+ .map((file) => file.trim())
278
+ .filter(Boolean);
279
+ if (filesValue !== undefined && files?.length === 0) {
280
+ throw new Error("knodin review: --files requires at least one repo-relative path");
281
+ }
282
+ return {
283
+ base: positionals.find((item) => item !== "minimal") ?? "HEAD~1",
284
+ detailLevel: positionals.includes("minimal") ? "minimal" : "standard",
285
+ options: {
286
+ scope: rawScope,
287
+ from: value("--from"),
288
+ to: value("--to"),
289
+ files,
290
+ },
291
+ };
292
+ }