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.
- package/README.md +19 -7
- package/benchmarks/competitors/SYNTHESIS.md +66 -0
- package/dist/bin/cli.js +2164 -108
- package/dist/bin/launcher.js +25 -3
- package/dist/src/agent-integration.js +304 -0
- package/dist/src/artifact-refresh.js +82 -0
- package/dist/src/cli-args.js +292 -0
- package/dist/src/cli-model.js +384 -0
- package/dist/src/codeflow-replay.js +81 -0
- package/dist/src/compact-structural.js +96 -0
- package/dist/src/compare.js +39 -0
- package/dist/src/competitive-cold-mcp.js +40 -0
- package/dist/src/competitive-constraints.js +21 -0
- package/dist/src/competitive-manifest.js +411 -0
- package/dist/src/competitive-measurement.js +183 -0
- package/dist/src/competitive-runner.js +487 -0
- package/dist/src/competitive-sandbox.js +108 -0
- package/dist/src/context-export.js +423 -0
- package/dist/src/context.js +102 -0
- package/dist/src/deterministic-random.js +34 -0
- package/dist/src/diagnostics-write-helper.js +473 -0
- package/dist/src/diagnostics.js +1476 -0
- package/dist/src/docs-sections.js +141 -0
- package/dist/src/doctor.js +382 -0
- package/dist/src/engine/ann-hnsw.js +261 -0
- package/dist/src/engine/embeddings.js +193 -0
- package/dist/src/engine/file-walker.js +49 -0
- package/dist/src/engine/git-history.js +289 -0
- package/dist/src/engine/index.js +14238 -0
- package/dist/src/engine/perf.js +115 -0
- package/dist/src/engine/prune.js +112 -0
- package/dist/src/engine/sarif-import.js +341 -0
- package/dist/src/engine/scip-import.js +423 -0
- package/dist/src/engine/source-policy.js +85 -0
- package/dist/src/engine/sqlite.js +71 -0
- package/dist/src/engine/state-paths.js +175 -0
- package/dist/src/engine/symbol-delete.js +58 -0
- package/dist/src/execution-profile.js +208 -0
- package/dist/src/failure-diagnosis.js +655 -0
- package/dist/src/fleet.js +7 -0
- package/dist/src/git-executable.js +31 -0
- package/dist/src/graph-layout.js +173 -0
- package/dist/src/graph-query-health.js +115 -0
- package/dist/src/hook-manager-integration.js +156 -0
- package/dist/src/index-activity.js +126 -0
- package/dist/src/init-progress-worker.js +106 -2
- package/dist/src/init-progress.js +155 -0
- package/dist/src/init.js +1295 -0
- package/dist/src/lifecycle-health.js +282 -0
- package/dist/src/lsp-readonly.js +217 -0
- package/dist/src/mcp-graph-worker.js +69 -0
- package/dist/src/mcp-reliability.js +154 -0
- package/dist/src/mcp-worker-supervisor.js +350 -0
- package/dist/src/mirror.js +290 -0
- package/dist/src/node-runtime.js +157 -0
- package/dist/src/output-compression.js +630 -0
- package/dist/src/output-telemetry.js +368 -0
- package/dist/src/pr-triage.js +638 -0
- package/dist/src/progressive-evidence.js +477 -0
- package/dist/src/pure-compression-cli.js +102 -0
- package/dist/src/relationship-adapters.js +377 -0
- package/dist/src/release-attestation.js +533 -0
- package/dist/src/release-preflight.js +513 -0
- package/dist/src/repair-lease.js +85 -0
- package/dist/src/repair-progress-worker.js +120 -2
- package/dist/src/repair-progress.js +262 -0
- package/dist/src/repository-init-process.js +177 -0
- package/dist/src/repository-management.js +1261 -0
- package/dist/src/response-budget.js +196 -0
- package/dist/src/server.js +217 -0
- package/dist/src/structural-fast-path.js +344 -0
- package/dist/src/structural-snapshot.js +37 -0
- package/dist/src/system-config.js +638 -0
- package/dist/src/terminal-help.js +83 -0
- package/dist/src/tools/knodin-tools.js +1640 -0
- package/dist/src/update-ceremony.js +162 -0
- package/dist/src/update-policy.js +944 -0
- package/dist/src/update-trust.js +504 -0
- package/dist/src/version.js +13 -0
- package/dist/src/visualization.js +515 -0
- package/dist/src/wait-for-fresh.js +98 -0
- package/dist/src/worktree-lifecycle.js +234 -0
- package/docs/BEHAVIORAL-CONTRACT.md +72 -0
- package/docs/CLI.md +20 -1
- package/docs/COMPARISON.md +403 -0
- package/docs/COMPETITIVE-LANDSCAPE-2026-08.md +267 -0
- package/docs/CONTAINED-EXECUTION.md +77 -0
- package/docs/DIAGNOSTICS.md +80 -0
- package/docs/GIT-HISTORY-REVIEW.md +39 -0
- package/docs/HANDOFF.md +180 -0
- package/docs/INSTALLATION.md +21 -18
- package/docs/MCP.md +59 -8
- package/docs/PROGRESSIVE-EVIDENCE.md +37 -0
- package/docs/PT-ACCESS-RECOMMENDATION.md +89 -0
- package/docs/RELEASE-0.3-EVIDENCE.md +73 -0
- package/docs/REPOSITORIES-AND-WORKTREES.md +18 -6
- package/docs/SCIP-IMPORT.md +62 -0
- package/docs/SIGNED-UPDATES.md +151 -0
- package/docs/TELEMETRY.md +46 -0
- package/docs/TOKEN-OPTIMIZER-SCORECARD.md +79 -0
- package/docs/assets/knodin-favicon.svg +4 -0
- package/docs/releases/0.3.0.md +46 -0
- package/docs/releases/0.4.0.md +68 -0
- package/docs/releases/0.4.1.md +28 -0
- package/docs/releases/0.4.2.md +27 -0
- package/docs/releases/0.4.3.md +23 -0
- package/docs/releases/0.5.0.md +29 -0
- package/docs/releases/0.5.1.md +17 -0
- package/docs/releases/0.6.0.md +18 -0
- package/docs/releases/0.7.0.md +24 -0
- package/docs/releases/0.7.1.md +21 -0
- package/docs/releases/0.7.2.md +21 -0
- package/docs/releases/0.7.3.md +23 -0
- package/docs/releases/0.7.4.md +17 -0
- package/docs/releases/0.7.5.md +20 -0
- package/docs/releases/0.8.0.md +74 -0
- package/docs/releases/0.8.2.md +34 -0
- package/package.json +127 -4
- package/roadmap/competitive-roadmap.md +3801 -0
- package/schemas/release-attestation-v1.schema.json +210 -0
- package/schemas/support-bundle-v2.schema.json +212 -0
- package/dist/chunks/chunk-DMQAGX77.js +0 -654
- package/dist/chunks/chunk-F4Z3Z766.js +0 -4
- package/dist/chunks/chunk-SIJAQVSX.js +0 -3
- package/dist/chunks/chunk-X6M4HUUE.js +0 -2
- package/dist/chunks/chunk-YPRMY2LP.js +0 -8
- package/dist/chunks/pure-compression-cli-4TA2TQD5.js +0 -5
- package/dist/chunks/server-7EDF4CBY.js +0 -14
- package/dist/chunks/structural-fast-path-KD5KQSPX.js +0 -4
- 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
|
+
}
|