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.
- package/README.md +4 -1
- package/dist/bin/cli.js +108 -1859
- package/dist/bin/launcher.js +3 -10
- package/dist/chunks/chunk-DMQAGX77.js +654 -0
- package/dist/chunks/chunk-F4Z3Z766.js +4 -0
- package/dist/chunks/chunk-SIJAQVSX.js +3 -0
- package/dist/chunks/chunk-X6M4HUUE.js +2 -0
- package/dist/chunks/chunk-YPRMY2LP.js +8 -0
- package/dist/chunks/pure-compression-cli-4TA2TQD5.js +5 -0
- package/dist/chunks/server-7EDF4CBY.js +14 -0
- package/dist/chunks/structural-fast-path-KD5KQSPX.js +4 -0
- package/dist/src/init-progress-worker.js +2 -106
- package/dist/src/repair-progress-worker.js +2 -120
- package/docs/INSTALLATION.md +17 -1
- package/docs/releases/0.7.6.md +25 -0
- package/package.json +1 -110
- package/dist/src/agent-integration.js +0 -238
- package/dist/src/artifact-refresh.js +0 -81
- package/dist/src/cli-args.js +0 -274
- package/dist/src/cli-model.js +0 -358
- package/dist/src/codeflow-replay.js +0 -80
- package/dist/src/compact-structural.js +0 -96
- package/dist/src/competitive-cold-mcp.js +0 -40
- package/dist/src/competitive-constraints.js +0 -20
- package/dist/src/competitive-manifest.js +0 -411
- package/dist/src/competitive-measurement.js +0 -183
- package/dist/src/competitive-runner.js +0 -487
- package/dist/src/competitive-sandbox.js +0 -108
- package/dist/src/context-export.js +0 -422
- package/dist/src/context.js +0 -102
- package/dist/src/diagnostics.js +0 -449
- package/dist/src/docs-sections.js +0 -141
- package/dist/src/doctor.js +0 -380
- package/dist/src/engine/ann-hnsw.js +0 -271
- package/dist/src/engine/embeddings.js +0 -193
- package/dist/src/engine/file-walker.js +0 -43
- package/dist/src/engine/git-history.js +0 -289
- package/dist/src/engine/index.js +0 -13377
- package/dist/src/engine/perf.js +0 -115
- package/dist/src/engine/prune.js +0 -112
- package/dist/src/engine/scip-import.js +0 -408
- package/dist/src/engine/source-policy.js +0 -69
- package/dist/src/engine/sqlite.js +0 -71
- package/dist/src/engine/symbol-delete.js +0 -58
- package/dist/src/execution-profile.js +0 -203
- package/dist/src/failure-diagnosis.js +0 -649
- package/dist/src/fleet.js +0 -7
- package/dist/src/git-executable.js +0 -31
- package/dist/src/graph-query-health.js +0 -115
- package/dist/src/hook-manager-integration.js +0 -156
- package/dist/src/index-activity.js +0 -125
- package/dist/src/init-progress.js +0 -155
- package/dist/src/init.js +0 -1254
- package/dist/src/lifecycle-health.js +0 -250
- package/dist/src/lsp-readonly.js +0 -217
- package/dist/src/output-compression.js +0 -629
- package/dist/src/output-telemetry.js +0 -363
- package/dist/src/pr-triage.js +0 -638
- package/dist/src/progressive-evidence.js +0 -473
- package/dist/src/pure-compression-cli.js +0 -101
- package/dist/src/relationship-adapters.js +0 -370
- package/dist/src/release-attestation.js +0 -533
- package/dist/src/release-preflight.js +0 -510
- package/dist/src/repair-progress.js +0 -262
- package/dist/src/repository-init-process.js +0 -173
- package/dist/src/repository-management.js +0 -1231
- package/dist/src/response-budget.js +0 -194
- package/dist/src/server.js +0 -73
- package/dist/src/structural-fast-path.js +0 -338
- package/dist/src/structural-snapshot.js +0 -33
- package/dist/src/system-config.js +0 -615
- package/dist/src/terminal-help.js +0 -83
- package/dist/src/tools/knodin-tools.js +0 -1529
- package/dist/src/update-ceremony.js +0 -158
- package/dist/src/update-policy.js +0 -944
- package/dist/src/update-trust.js +0 -503
- package/dist/src/version.js +0 -13
- package/dist/src/visualization.js +0 -162
- package/dist/src/wait-for-fresh.js +0 -98
- package/dist/src/worktree-lifecycle.js +0 -231
- package/docs/CONTAINED-EXECUTION.md +0 -77
- package/docs/DIAGNOSTICS.md +0 -45
- package/docs/GIT-HISTORY-REVIEW.md +0 -39
- package/docs/PROGRESSIVE-EVIDENCE.md +0 -37
- package/docs/PT-ACCESS-RECOMMENDATION.md +0 -91
- package/docs/RELEASE-0.3-EVIDENCE.md +0 -73
- package/docs/SCIP-IMPORT.md +0 -57
- package/docs/SIGNED-UPDATES.md +0 -151
- package/docs/TELEMETRY.md +0 -46
- package/docs/assets/knodin-favicon.svg +0 -4
- package/docs/releases/0.3.0.md +0 -46
- package/docs/releases/0.4.0.md +0 -68
- package/docs/releases/0.4.1.md +0 -28
- package/docs/releases/0.4.2.md +0 -27
- package/docs/releases/0.4.3.md +0 -23
- package/docs/releases/0.5.0.md +0 -29
- package/docs/releases/0.5.1.md +0 -17
- package/docs/releases/0.6.0.md +0 -18
- package/docs/releases/0.7.0.md +0 -24
- package/docs/releases/0.7.1.md +0 -21
- package/docs/releases/0.7.2.md +0 -21
- package/docs/releases/0.7.3.md +0 -23
- package/docs/releases/0.7.4.md +0 -17
- package/schemas/release-attestation-v1.schema.json +0 -210
package/dist/src/cli-args.js
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
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
|
-
const isDir = (p) => {
|
|
33
|
-
try {
|
|
34
|
-
return fs.statSync(p).isDirectory();
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
const RUNTIME_MODULE_FLAGS = new Set([
|
|
41
|
-
"--import",
|
|
42
|
-
"--loader",
|
|
43
|
-
"--experimental-loader",
|
|
44
|
-
"--require",
|
|
45
|
-
"-r",
|
|
46
|
-
]);
|
|
47
|
-
function resolveRuntimeModuleReference(value, cwd, resolveModule) {
|
|
48
|
-
if (path.isAbsolute(value) || /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value))
|
|
49
|
-
return value;
|
|
50
|
-
if (value.startsWith("."))
|
|
51
|
-
return path.resolve(cwd, value);
|
|
52
|
-
return resolveModule(value, cwd);
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Reconstruct the executable command that launched this CLI. Source-checkout
|
|
56
|
-
* invocations need loader arguments such as `node --import tsx`; dropping
|
|
57
|
-
* `execArgv` creates a background hook that exists but cannot start. Bare
|
|
58
|
-
* loader names are resolved now because the hook later runs from another repo.
|
|
59
|
-
*/
|
|
60
|
-
export function resolveCliRuntimeCommand(runtime, resolveModule = (specifier, cwd) => createRequire(path.join(cwd, "package.json")).resolve(specifier)) {
|
|
61
|
-
const entry = runtime.argv[1];
|
|
62
|
-
if (!entry)
|
|
63
|
-
throw new Error("knodin: CLI entry path is unavailable");
|
|
64
|
-
const cwd = runtime.cwd();
|
|
65
|
-
const runtimeArguments = [];
|
|
66
|
-
for (let index = 0; index < runtime.execArgv.length; index++) {
|
|
67
|
-
const argument = runtime.execArgv[index];
|
|
68
|
-
if (RUNTIME_MODULE_FLAGS.has(argument)) {
|
|
69
|
-
const value = runtime.execArgv[index + 1];
|
|
70
|
-
if (!value)
|
|
71
|
-
throw new Error(`knodin: ${argument} requires a runtime module`);
|
|
72
|
-
runtimeArguments.push(argument, resolveRuntimeModuleReference(value, cwd, resolveModule));
|
|
73
|
-
index++;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const equalsFlag = [...RUNTIME_MODULE_FLAGS].find((flag) => argument.startsWith(`${flag}=`));
|
|
77
|
-
if (equalsFlag) {
|
|
78
|
-
const value = argument.slice(equalsFlag.length + 1);
|
|
79
|
-
if (!value)
|
|
80
|
-
throw new Error(`knodin: ${equalsFlag} requires a runtime module`);
|
|
81
|
-
runtimeArguments.push(`${equalsFlag}=${resolveRuntimeModuleReference(value, cwd, resolveModule)}`);
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
runtimeArguments.push(argument);
|
|
85
|
-
}
|
|
86
|
-
return [path.resolve(cwd, runtime.execPath), ...runtimeArguments, path.resolve(cwd, entry)];
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Pull a global `--repo <path>` / `--repo=<path>` out of an argv slice and
|
|
90
|
-
* move any other documented global flags before the subcommand to immediately
|
|
91
|
-
* after it. This preserves the existing command parsers while accepting both
|
|
92
|
-
* conventional `knodin --file x explain y` and `knodin explain y --file x`.
|
|
93
|
-
*/
|
|
94
|
-
export function extractRepoFlag(argv) {
|
|
95
|
-
const rest = [];
|
|
96
|
-
const leadingGlobalOptions = [];
|
|
97
|
-
let repoFlag;
|
|
98
|
-
let commandSeen = false;
|
|
99
|
-
for (let i = 0; i < argv.length; i++) {
|
|
100
|
-
const a = argv[i];
|
|
101
|
-
if (a === "--repo") {
|
|
102
|
-
repoFlag = argv[i + 1] ?? "";
|
|
103
|
-
i++; // consume the value
|
|
104
|
-
}
|
|
105
|
-
else if (a.startsWith("--repo=")) {
|
|
106
|
-
repoFlag = a.slice("--repo=".length);
|
|
107
|
-
}
|
|
108
|
-
else if (!commandSeen && GLOBAL_VALUE_FLAGS.has(a)) {
|
|
109
|
-
leadingGlobalOptions.push(a);
|
|
110
|
-
if (argv[i + 1] !== undefined)
|
|
111
|
-
leadingGlobalOptions.push(argv[++i]);
|
|
112
|
-
}
|
|
113
|
-
else if (!commandSeen && GLOBAL_BOOLEAN_FLAGS.has(a)) {
|
|
114
|
-
leadingGlobalOptions.push(a);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
rest.push(a);
|
|
118
|
-
commandSeen = true;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
rest.push(...leadingGlobalOptions);
|
|
122
|
-
return { repoFlag, rest };
|
|
123
|
-
}
|
|
124
|
-
/** Return positional arguments without mistaking recognized option values for paths. */
|
|
125
|
-
export function extractPositionals(args) {
|
|
126
|
-
const positionals = [];
|
|
127
|
-
for (let index = 0; index < args.length; index++) {
|
|
128
|
-
const argument = args[index];
|
|
129
|
-
if (argument.startsWith("--")) {
|
|
130
|
-
if (!argument.includes("=") && GLOBAL_VALUE_FLAGS.has(argument))
|
|
131
|
-
index++;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
positionals.push(argument);
|
|
135
|
-
}
|
|
136
|
-
return positionals;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Resolve the target repo: an explicit `--repo` (validated as an existing
|
|
140
|
-
* directory) wins over `cwd`. An absent flag falls back to `cwd`.
|
|
141
|
-
*/
|
|
142
|
-
export function resolveRepo(repoFlag, cwd) {
|
|
143
|
-
if (repoFlag === undefined)
|
|
144
|
-
return { ok: true, repo: cwd };
|
|
145
|
-
if (repoFlag === "")
|
|
146
|
-
return { ok: false, error: "knodin: --repo requires a <path>" };
|
|
147
|
-
const repo = path.resolve(cwd, repoFlag);
|
|
148
|
-
if (!fs.existsSync(repo)) {
|
|
149
|
-
return { ok: false, error: `knodin: --repo path does not exist: ${repo}` };
|
|
150
|
-
}
|
|
151
|
-
if (!isDir(repo)) {
|
|
152
|
-
return { ok: false, error: `knodin: --repo path is not a directory: ${repo}` };
|
|
153
|
-
}
|
|
154
|
-
return { ok: true, repo };
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Decide what `knodin index [positionals...]` actually targets, closing the
|
|
158
|
-
* silent-no-op hole:
|
|
159
|
-
* - a lone directory positional with no `--repo` IS the repo (full reindex) —
|
|
160
|
-
* this is the observed-bug fix: `knodin index /other/repo` now indexes it;
|
|
161
|
-
* - any other directory positional is rejected, pointing at `--repo`;
|
|
162
|
-
* - file positionals resolving outside the target repo are rejected loudly;
|
|
163
|
-
* - otherwise the positionals are files to index within the resolved repo.
|
|
164
|
-
*/
|
|
165
|
-
export function planIndex(repoFlag, positionals, cwd) {
|
|
166
|
-
// Case A: no --repo and a single directory positional => that directory is
|
|
167
|
-
// the repo to index, not a file. Absorb it as the target.
|
|
168
|
-
if (repoFlag === undefined && positionals.length === 1) {
|
|
169
|
-
const abs = path.resolve(cwd, positionals[0]);
|
|
170
|
-
if (isDir(abs)) {
|
|
171
|
-
return { ok: true, repo: abs, files: undefined };
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const resolved = resolveRepo(repoFlag, cwd);
|
|
175
|
-
if (!resolved.ok)
|
|
176
|
-
return resolved;
|
|
177
|
-
const repo = resolved.repo;
|
|
178
|
-
if (positionals.length === 0) {
|
|
179
|
-
return { ok: true, repo, files: undefined }; // full reindex
|
|
180
|
-
}
|
|
181
|
-
// Case B: positionals are files. Validate each is a within-repo non-directory.
|
|
182
|
-
const rejectedDirs = [];
|
|
183
|
-
const rejectedOutside = [];
|
|
184
|
-
const files = [];
|
|
185
|
-
for (const p of positionals) {
|
|
186
|
-
const abs = path.resolve(repo, p);
|
|
187
|
-
const rel = path.relative(repo, abs);
|
|
188
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
189
|
-
rejectedOutside.push(p);
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
if (isDir(abs)) {
|
|
193
|
-
rejectedDirs.push(p);
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
files.push(p);
|
|
197
|
-
}
|
|
198
|
-
if (rejectedOutside.length > 0 || rejectedDirs.length > 0) {
|
|
199
|
-
const parts = [];
|
|
200
|
-
if (rejectedOutside.length > 0) {
|
|
201
|
-
parts.push(`outside repo (${rejectedOutside.join(", ")})`);
|
|
202
|
-
}
|
|
203
|
-
if (rejectedDirs.length > 0) {
|
|
204
|
-
const hint = repoFlag === undefined
|
|
205
|
-
? "use --repo to index a whole repo"
|
|
206
|
-
: "pass files, not directories, alongside --repo";
|
|
207
|
-
parts.push(`directories — ${hint} (${rejectedDirs.join(", ")})`);
|
|
208
|
-
}
|
|
209
|
-
return {
|
|
210
|
-
ok: false,
|
|
211
|
-
error: `knodin index: refusing to index against ${repo}; rejected ${parts.join("; ")}`,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
return { ok: true, repo, files };
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Guard against reporting success for a no-op: an index run that touched zero
|
|
218
|
-
* files must exit non-zero with a diagnostic naming the repo it actually used.
|
|
219
|
-
*/
|
|
220
|
-
export function checkIndexed(indexed, repo) {
|
|
221
|
-
if (!indexed || indexed.length === 0) {
|
|
222
|
-
return { ok: false, error: `knodin index: indexed 0 files in ${repo} (nothing matched)` };
|
|
223
|
-
}
|
|
224
|
-
return { ok: true };
|
|
225
|
-
}
|
|
226
|
-
/** Pure parser for `knodin review`, shared by the executable and CLI contract tests. */
|
|
227
|
-
export function parseReviewArgs(args) {
|
|
228
|
-
const value = (flag) => {
|
|
229
|
-
const index = args.indexOf(flag);
|
|
230
|
-
if (index < 0)
|
|
231
|
-
return undefined;
|
|
232
|
-
const candidate = args[index + 1];
|
|
233
|
-
if (!candidate || candidate.startsWith("--")) {
|
|
234
|
-
throw new Error(`knodin review: ${flag} requires a value`);
|
|
235
|
-
}
|
|
236
|
-
return candidate;
|
|
237
|
-
};
|
|
238
|
-
const rawScope = value("--scope");
|
|
239
|
-
const scopes = ["unstaged", "staged", "all", "compare"];
|
|
240
|
-
if (rawScope && !scopes.includes(rawScope)) {
|
|
241
|
-
throw new Error(`invalid review scope: ${rawScope}`);
|
|
242
|
-
}
|
|
243
|
-
const optionValues = new Set([
|
|
244
|
-
...GLOBAL_VALUE_FLAGS,
|
|
245
|
-
"--scope",
|
|
246
|
-
"--from",
|
|
247
|
-
"--to",
|
|
248
|
-
"--files",
|
|
249
|
-
"--scip",
|
|
250
|
-
]);
|
|
251
|
-
const positionals = args.filter((arg, index) => {
|
|
252
|
-
if (arg.startsWith("--"))
|
|
253
|
-
return false;
|
|
254
|
-
return index === 0 || !optionValues.has(args[index - 1]);
|
|
255
|
-
});
|
|
256
|
-
const filesValue = value("--files");
|
|
257
|
-
const files = filesValue
|
|
258
|
-
?.split(",")
|
|
259
|
-
.map((file) => file.trim())
|
|
260
|
-
.filter(Boolean);
|
|
261
|
-
if (filesValue !== undefined && files?.length === 0) {
|
|
262
|
-
throw new Error("knodin review: --files requires at least one repo-relative path");
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
base: positionals.find((item) => item !== "minimal") ?? "HEAD~1",
|
|
266
|
-
detailLevel: positionals.includes("minimal") ? "minimal" : "standard",
|
|
267
|
-
options: {
|
|
268
|
-
scope: rawScope,
|
|
269
|
-
from: value("--from"),
|
|
270
|
-
to: value("--to"),
|
|
271
|
-
files,
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
}
|
package/dist/src/cli-model.js
DELETED
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import { Argument, Command, CommanderError, Option } from "commander";
|
|
2
|
-
import { formatTerminalHelp } from "./terminal-help.js";
|
|
3
|
-
function integer(value, flags) {
|
|
4
|
-
const parsed = Number(value);
|
|
5
|
-
if (!Number.isInteger(parsed))
|
|
6
|
-
throw new CommanderError(1, "knodin.invalidNumber", `${flags.split(" ")[0]} must be an integer`);
|
|
7
|
-
return parsed;
|
|
8
|
-
}
|
|
9
|
-
function numeric(value, flags) {
|
|
10
|
-
const parsed = Number(value);
|
|
11
|
-
if (!Number.isFinite(parsed))
|
|
12
|
-
throw new CommanderError(1, "knodin.invalidNumber", `${flags.split(" ")[0]} must be a number`);
|
|
13
|
-
return parsed;
|
|
14
|
-
}
|
|
15
|
-
function collect(value, previous) {
|
|
16
|
-
return [...previous, value];
|
|
17
|
-
}
|
|
18
|
-
function option(flags, description, parser) {
|
|
19
|
-
const configured = new Option(flags, description);
|
|
20
|
-
if (parser === "integer")
|
|
21
|
-
configured.argParser((value) => integer(value, flags));
|
|
22
|
-
else if (parser === "number")
|
|
23
|
-
configured.argParser((value) => numeric(value, flags));
|
|
24
|
-
else if (parser === "collect")
|
|
25
|
-
configured.argParser(collect).default([]);
|
|
26
|
-
return configured;
|
|
27
|
-
}
|
|
28
|
-
function leaf(parent, syntax, description, capture) {
|
|
29
|
-
const command = parent.command(syntax).description(description).allowExcessArguments(false);
|
|
30
|
-
command.action((...values) => capture(values.at(-1)));
|
|
31
|
-
return command;
|
|
32
|
-
}
|
|
33
|
-
function addGlobalOptions(program) {
|
|
34
|
-
program
|
|
35
|
-
.option("-v, --version", "print the installed knodin version")
|
|
36
|
-
.addOption(option("--repo <path>", "target a repository instead of the current directory"))
|
|
37
|
-
.option("--json", "emit stable JSON")
|
|
38
|
-
.addOption(option("--identity <id>", "select a stable symbol identity"))
|
|
39
|
-
.addOption(option("--file <path>", "select a repo-relative definition file"))
|
|
40
|
-
.addOption(option("--kind <kind>", "select a symbol kind"))
|
|
41
|
-
.addOption(option("--to-identity <id>", "select a destination identity"))
|
|
42
|
-
.addOption(option("--to-file <path>", "select a destination file"))
|
|
43
|
-
.addOption(option("--to-kind <kind>", "select a destination kind"))
|
|
44
|
-
.addOption(option("--bytes <count>", "bound serialized response bytes", "integer"))
|
|
45
|
-
.addOption(option("--tokens <count>", "bound serialized response tokens", "integer"))
|
|
46
|
-
.addOption(option("--items <count>", "bound serialized response items", "integer"))
|
|
47
|
-
.option("--impact-mode <mode>", "symbol or file impact")
|
|
48
|
-
.option("--direction <direction>", "upstream, downstream, or both")
|
|
49
|
-
.addOption(option("--depth <count>", "bounded traversal depth", "integer"))
|
|
50
|
-
.option("--relations <kinds>", "comma-separated relationship kinds")
|
|
51
|
-
.addOption(option("--min-confidence <value>", "minimum edge confidence", "number"))
|
|
52
|
-
.option("--limit <count>", "result limit")
|
|
53
|
-
.option("--exclude-tests", "exclude tests from impact")
|
|
54
|
-
.option("--data-flow", "include bounded data-flow evidence");
|
|
55
|
-
}
|
|
56
|
-
function addRepositoryCommands(program, capture) {
|
|
57
|
-
const repos = program.command("repos").description("manage a portfolio of repositories");
|
|
58
|
-
for (const action of ["discover", "init", "status", "doctor"]) {
|
|
59
|
-
const command = leaf(repos, `${action} <roots...>`, `${action} repositories beneath one or more roots`, capture)
|
|
60
|
-
.addOption(option("--depth <count>", "maximum discovery depth", "integer"))
|
|
61
|
-
.option("--linked-worktrees <mode>", "skip or include linked worktrees");
|
|
62
|
-
if (action === "init") {
|
|
63
|
-
command
|
|
64
|
-
.addOption(option("--include <selector>", "include a repository id or path", "collect"))
|
|
65
|
-
.addOption(option("--exclude <selector>", "exclude a repository id or path", "collect"))
|
|
66
|
-
.option("--manifest <path>", "write or resume a portfolio manifest")
|
|
67
|
-
.option("--dry-run", "report actions without changing repositories");
|
|
68
|
-
}
|
|
69
|
-
if (action === "discover") {
|
|
70
|
-
command
|
|
71
|
-
.option("--signals", "include bounded repository applicability signals")
|
|
72
|
-
.addOption(option("--items <count>", "bound returned repositories and signal arrays", "integer"))
|
|
73
|
-
.addOption(option("--bytes <count>", "bound serialized response bytes", "integer"))
|
|
74
|
-
.addOption(option("--tokens <count>", "bound estimated response tokens", "integer"))
|
|
75
|
-
.addHelpText("after", "\n--signals returns hookManager, markerFiles, sanitized remotes, ciProviders, agentConfigs, and aidevTrackReferenced when known; every returned repository has signals, including {}. Inspection uses fixed documented marker/hook allowlists, reads only bounded hook configuration, and performs no network, credential, or marker-content reads. Linked worktrees inspect their own checkout when --linked-worktrees include is selected. See docs/REPOSITORIES-AND-WORKTREES.md for omissions and limits.\n");
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
leaf(repos, "search <query>", "search selected repositories sequentially", capture)
|
|
79
|
-
.addOption(option("--root <path>", "portfolio root", "collect"))
|
|
80
|
-
.addOption(option("--include <selector>", "include a repository id or path", "collect"))
|
|
81
|
-
.addOption(option("--exclude <selector>", "exclude a repository id or path", "collect"))
|
|
82
|
-
.option("--cursor <cursor>", "resume from an opaque cursor")
|
|
83
|
-
.option("--allow-partial", "return healthy repository results when another degrades");
|
|
84
|
-
const fleet = program.command("fleet").description("deprecated repository-fleet compatibility");
|
|
85
|
-
leaf(fleet, "init <roots...>", "initialize a repository fleet", capture)
|
|
86
|
-
.addOption(option("--depth <count>", "maximum discovery depth", "integer"))
|
|
87
|
-
.option("--worktrees <mode>", "skip or include linked worktrees")
|
|
88
|
-
.option("--dry-run", "report actions without mutation");
|
|
89
|
-
}
|
|
90
|
-
function addGraphCommands(program, capture) {
|
|
91
|
-
leaf(program, "context <task> [base]", "build compact task orientation", capture);
|
|
92
|
-
leaf(program, "explain <symbol> [detail]", "explain one ambiguity-safe symbol", capture);
|
|
93
|
-
leaf(program, "review [base] [detail]", "review an explicit Git diff scope", capture)
|
|
94
|
-
.option("--scope <scope>", "unstaged, staged, all, or compare")
|
|
95
|
-
.option("--from <ref>", "comparison start revision")
|
|
96
|
-
.option("--to <ref>", "comparison end revision")
|
|
97
|
-
.option("--files <paths>", "comma-separated repo-relative paths");
|
|
98
|
-
leaf(program, "map", "show architecture communities and edges", capture)
|
|
99
|
-
.option("--standard", "include full map detail")
|
|
100
|
-
.addOption(option("--top <count>", "maximum ranked communities", "integer"))
|
|
101
|
-
.option("--sort <mode>", "relevance, name, size, degree, or complexity")
|
|
102
|
-
.option("--relations <kinds>", "comma-separated relationship kinds");
|
|
103
|
-
leaf(program, "wiki", "write local architecture wiki pages", capture).option("--force", "rewrite unchanged pages");
|
|
104
|
-
leaf(program, "visualize <entry>", "write a local architecture/call-flow HTML artifact", capture)
|
|
105
|
-
.requiredOption("--output <path>", "repo-relative HTML output path")
|
|
106
|
-
.addOption(option("--depth <count>", "call-flow depth", "integer"))
|
|
107
|
-
.addOption(option("--max-bytes <count>", "hard artifact budget", "integer"));
|
|
108
|
-
leaf(program, "search <query> [limit]", "hybrid symbol search", capture)
|
|
109
|
-
.option("--languages <values>", "comma-separated languages")
|
|
110
|
-
.option("--extensions <values>", "comma-separated extensions")
|
|
111
|
-
.option("--kinds <values>", "comma-separated symbol kinds")
|
|
112
|
-
.option("--path <prefix>", "repo-relative path prefix")
|
|
113
|
-
.option("--tests-only", "search test symbols only")
|
|
114
|
-
.option("--production-only", "search production symbols only")
|
|
115
|
-
.option("--no-source", "omit source snippets")
|
|
116
|
-
.addOption(option("--offset <count>", "pagination offset", "integer"))
|
|
117
|
-
.addOption(option("--limit <count>", "result limit", "integer"));
|
|
118
|
-
leaf(program, "query <pattern> [targets...]", "run one structured graph query", capture)
|
|
119
|
-
.option("--impact-mode <mode>", "symbol or file impact")
|
|
120
|
-
.option("--direction <direction>", "upstream, downstream, or both")
|
|
121
|
-
.addOption(option("--depth <count>", "bounded traversal depth", "integer"))
|
|
122
|
-
.option("--relations <kinds>", "comma-separated relationship kinds")
|
|
123
|
-
.addOption(option("--min-confidence <value>", "minimum edge confidence", "number"))
|
|
124
|
-
.option("--exclude-tests", "exclude tests from impact")
|
|
125
|
-
.option("--data-flow", "include bounded data-flow evidence")
|
|
126
|
-
.option("--limit <count>", "result limit")
|
|
127
|
-
.addOption(option("--min-lines <count>", "minimum line count", "integer"))
|
|
128
|
-
.addOption(option("--min-complexity <count>", "minimum complexity", "integer"))
|
|
129
|
-
.option("--kinds <values>", "comma-separated symbol kinds")
|
|
130
|
-
.option("--path <prefix>", "repo-relative path prefix")
|
|
131
|
-
.option("--variable <name>", "flow-analysis variable")
|
|
132
|
-
.option("--facets <values>", "comma-separated architecture facets")
|
|
133
|
-
.addOption(option("--top <count>", "maximum ranked results", "integer"))
|
|
134
|
-
.option("--sort <mode>", "result ordering");
|
|
135
|
-
leaf(program, "rename <old> <new>", "preview or apply an ambiguity-safe rename", capture)
|
|
136
|
-
.option("--apply", "apply the verified edit")
|
|
137
|
-
.option("--no-verify", "skip post-apply typecheck");
|
|
138
|
-
}
|
|
139
|
-
function addArtifactCommands(program, capture) {
|
|
140
|
-
leaf(program, "evidence <level> <file>", "deliver progressive source evidence", capture)
|
|
141
|
-
.option("--continuation <handle>", "resume an exact prior response")
|
|
142
|
-
.option("--baseline-hash <sha256>", "complete baseline SHA-256")
|
|
143
|
-
.addOption(option("--baseline-bytes <count>", "complete baseline UTF-8 bytes", "integer"))
|
|
144
|
-
.addOption(option("--start <line>", "first source line", "integer"))
|
|
145
|
-
.addOption(option("--end <line>", "last source line", "integer"));
|
|
146
|
-
const pack = program.command("pack").description("export bounded portable context");
|
|
147
|
-
pack
|
|
148
|
-
.argument("[input]")
|
|
149
|
-
.option("--format <format>", "markdown, json, or xml")
|
|
150
|
-
.option("--include <globs>", "comma-separated include globs")
|
|
151
|
-
.option("--exclude <globs>", "comma-separated exclude globs")
|
|
152
|
-
.option("--policy <assignments>", "comma-separated glob policies")
|
|
153
|
-
.option("--already-present <paths>", "comma-separated paths already in context")
|
|
154
|
-
.option("--chat-files <paths>", "comma-separated chat paths")
|
|
155
|
-
.option("--line-numbers", "include source line numbers")
|
|
156
|
-
.option("--tree", "include repository tree")
|
|
157
|
-
.option("--output <path>", "write a repo-relative retained artifact")
|
|
158
|
-
.option("--diff-scope <scope>", "unstaged, staged, all, or compare")
|
|
159
|
-
.option("--from <ref>", "comparison start revision")
|
|
160
|
-
.option("--to <ref>", "comparison end revision")
|
|
161
|
-
.addOption(option("--log <count>", "include recent commits", "integer"))
|
|
162
|
-
.action((...values) => capture(values.at(-1)));
|
|
163
|
-
leaf(pack, "read <artifact>", "read a retained context artifact", capture)
|
|
164
|
-
.addOption(option("--start <line>", "first line", "integer"))
|
|
165
|
-
.addOption(option("--end <line>", "last line", "integer"));
|
|
166
|
-
leaf(pack, "grep <artifact> <regex>", "search an artifact with a linear-time regex", capture)
|
|
167
|
-
.option("--flags <flags>", "regular-expression flags")
|
|
168
|
-
.addOption(option("--limit <count>", "match limit", "integer"));
|
|
169
|
-
const compress = program.command("compress").description("compress already-produced output");
|
|
170
|
-
compress
|
|
171
|
-
.argument("[input]")
|
|
172
|
-
.option("--strategy <strategy>", "smart, head-tail, or errors-only")
|
|
173
|
-
.option("--adapter <adapter>", "structured output adapter")
|
|
174
|
-
.addOption(option("--lines <count>", "hard line budget", "integer"))
|
|
175
|
-
.addOption(option("--max-output-bytes <count>", "hard output byte budget", "integer"))
|
|
176
|
-
.addOption(option("--context <count>", "signal context lines", "integer"))
|
|
177
|
-
.addOption(option("--exit-code <code>", "source process exit code", "integer"))
|
|
178
|
-
.option("--signal <name>", "source process termination signal")
|
|
179
|
-
.addOption(option("--max-input-bytes <count>", "hard input byte limit", "integer"))
|
|
180
|
-
.option("--no-retain", "do not retain raw drill-down data")
|
|
181
|
-
.option("--no-redact", "disable secret redaction")
|
|
182
|
-
.action((...values) => capture(values.at(-1)));
|
|
183
|
-
for (const action of ["read", "diagnose"]) {
|
|
184
|
-
leaf(compress, `${action} <artifact>`, `${action} a retained output artifact`, capture)
|
|
185
|
-
.addOption(option("--start <line>", "first line", "integer"))
|
|
186
|
-
.addOption(option("--end <line>", "last line", "integer"))
|
|
187
|
-
.addOption(option("--max-output-bytes <count>", "hard output byte budget", "integer"))
|
|
188
|
-
.addOption(option("--context <count>", "diagnostic context lines", "integer"))
|
|
189
|
-
.addOption(option("--limit <count>", "diagnostic result limit", "integer"))
|
|
190
|
-
.addOption(option("--offset <count>", "resume retained diagnostics at offset", "integer"))
|
|
191
|
-
.option("--raw", "return unredacted retained bytes");
|
|
192
|
-
}
|
|
193
|
-
leaf(compress, "delete <artifact>", "delete a retained output artifact", capture);
|
|
194
|
-
}
|
|
195
|
-
function createCliProgram(capture = () => { }) {
|
|
196
|
-
const program = new Command("knodin")
|
|
197
|
-
.description("knodin — source-evidenced local code intelligence with known bounds")
|
|
198
|
-
.showHelpAfterError()
|
|
199
|
-
.showSuggestionAfterError()
|
|
200
|
-
.passThroughOptions(false)
|
|
201
|
-
.allowExcessArguments(false)
|
|
202
|
-
.exitOverride();
|
|
203
|
-
program.configureOutput({ writeErr: () => { } });
|
|
204
|
-
addGlobalOptions(program);
|
|
205
|
-
program.addHelpText("after", "\nMCP callers use the `prs` operation and corresponding operations on the single `knodin` gateway.\nknodin update status|check|explain|apply|rollback consumes only threshold-signed metadata.\n");
|
|
206
|
-
leaf(program, "init", "initialize graph, lifecycle hooks, and agent integration", capture)
|
|
207
|
-
.option("--scope <scope>", "personal, team, or cli-only")
|
|
208
|
-
.addHelpText("after", "\nUsage: knodin init [--scope personal|team|cli-only] [--json]\n\npersonal recommended; local/excluded agent adapters and a clean Git status\nteam commit-ready shared agent configuration\ncli-only no agent discovery; AI agents will not know to invoke knodin\n\nChange later with `knodin configure --scope <scope>`.\nTracked files are never added to Git exclude files.\n");
|
|
209
|
-
leaf(program, "configure", "change or inspect agent integration", capture)
|
|
210
|
-
.option("--scope <scope>", "personal, team, or cli-only")
|
|
211
|
-
.option("--status", "inspect configuration without changing it");
|
|
212
|
-
leaf(program, "index [files...]", "index a repository or selected files", capture)
|
|
213
|
-
.option("--clean", "rebuild selected index state")
|
|
214
|
-
.option("--force", "force clean indexing")
|
|
215
|
-
.option("--scip <file>", "opt in to a bounded local SCIP protobuf import");
|
|
216
|
-
leaf(program, "doctor", "diagnose installation, clients, hooks, graph, and updates", capture).option("--client <client>", "claude, codex, gemini, or antigravity");
|
|
217
|
-
leaf(program, "status", "report graph, lifecycle, integration, and update state", capture)
|
|
218
|
-
.option("--deep", "run a full graph audit")
|
|
219
|
-
.option("--watch", "stream status snapshots")
|
|
220
|
-
.addOption(option("--interval <seconds>", "watch interval", "number"));
|
|
221
|
-
leaf(program, "wait", "wait for current graph evidence", capture)
|
|
222
|
-
.option("--fresh", "wait for a fresh graph")
|
|
223
|
-
.addOption(option("--timeout <seconds>", "deadline", "number"));
|
|
224
|
-
leaf(program, "repair", "audit and reconcile graph state", capture)
|
|
225
|
-
.option("--plan", "report the repair plan without mutation")
|
|
226
|
-
.option("--jsonl", "stream JSONL progress")
|
|
227
|
-
.option("--progress <mode>", "tty, jsonl, plain, or none")
|
|
228
|
-
.option("--progress-interval <duration>", "progress interval such as 750ms, 30s, or 2m");
|
|
229
|
-
leaf(program, "serve", "run the one-tool MCP gateway on stdio", capture);
|
|
230
|
-
leaf(program, "version", "print the installed knodin version", capture);
|
|
231
|
-
leaf(program, "hook-refresh <kind> [values...]", "internal Git lifecycle refresh", capture);
|
|
232
|
-
leaf(program, "refresh-artifacts [event]", "refresh external graph artifacts", capture);
|
|
233
|
-
addRepositoryCommands(program, capture);
|
|
234
|
-
addGraphCommands(program, capture);
|
|
235
|
-
addArtifactCommands(program, capture);
|
|
236
|
-
const system = program.command("system").description("inspect declared multi-repository systems");
|
|
237
|
-
leaf(system, "list", "list configured systems", capture);
|
|
238
|
-
for (const action of ["show", "validate", "query"]) {
|
|
239
|
-
const command = leaf(system, `${action} <system-id>`, `${action} one configured system`, capture);
|
|
240
|
-
if (action === "query")
|
|
241
|
-
command.option("--allow-partial", "return healthy components");
|
|
242
|
-
}
|
|
243
|
-
const update = program
|
|
244
|
-
.command("update")
|
|
245
|
-
.description("inspect or apply threshold-signed updates")
|
|
246
|
-
.allowExcessArguments(false)
|
|
247
|
-
.addArgument(new Argument("<action>").choices(["status", "check", "explain", "apply", "rollback"]));
|
|
248
|
-
update.action((...values) => capture(values.at(-1)));
|
|
249
|
-
leaf(program, "docs <topic>", "read canonical product documentation", capture);
|
|
250
|
-
leaf(program, "prs [action]", "audit pull requests, reviews, and checks", capture)
|
|
251
|
-
.option("--state <state>", "open, merged, closed, or all")
|
|
252
|
-
.addOption(option("--limit <count>", "pull-request limit", "integer"))
|
|
253
|
-
.option("--branches <pattern>", "branch-name pattern")
|
|
254
|
-
.option("--range <range>", "pull-request number range")
|
|
255
|
-
.option("--base <ref>", "audit base revision")
|
|
256
|
-
.option("--head <ref>", "audit head revision")
|
|
257
|
-
.option("--expected-login <login>", "expected GitHub identity");
|
|
258
|
-
leaf(program, "worktrees [action] [path]", "inspect, reconcile, or remove managed worktrees", capture).option("--dry-run", "report removal without mutation");
|
|
259
|
-
const telemetry = program
|
|
260
|
-
.command("telemetry")
|
|
261
|
-
.description("manage local opt-in ROI telemetry")
|
|
262
|
-
.allowExcessArguments(false)
|
|
263
|
-
.addArgument(new Argument("<action>").choices(["status", "report", "export", "clear"]));
|
|
264
|
-
telemetry
|
|
265
|
-
.option("--input <path>", "telemetry input path")
|
|
266
|
-
.option("--output <path>", "dashboard or evidence-bundle output path")
|
|
267
|
-
.addOption(option("--retention-days <count>", "retention window in days", "integer"))
|
|
268
|
-
.action((...values) => capture(values.at(-1)));
|
|
269
|
-
const diagnostics = program
|
|
270
|
-
.command("diagnostics")
|
|
271
|
-
.description("manage local troubleshooting diagnostics and support bundles")
|
|
272
|
-
.allowExcessArguments(false)
|
|
273
|
-
.addArgument(new Argument("<action>").choices([
|
|
274
|
-
"enable",
|
|
275
|
-
"status",
|
|
276
|
-
"collect",
|
|
277
|
-
"inspect",
|
|
278
|
-
"clear",
|
|
279
|
-
"disable",
|
|
280
|
-
]))
|
|
281
|
-
.addArgument(new Argument("[bundle]"));
|
|
282
|
-
diagnostics
|
|
283
|
-
.addOption(option("--retention-days <count>", "local event retention in days", "integer"))
|
|
284
|
-
.option("--since <duration>", "collection window, such as 24h or 7d")
|
|
285
|
-
.option("--output <path>", "repository-contained .json.gz bundle path")
|
|
286
|
-
.action((...values) => capture(values.at(-1)));
|
|
287
|
-
return program;
|
|
288
|
-
}
|
|
289
|
-
function flattenPositionals(values) {
|
|
290
|
-
return values
|
|
291
|
-
.flatMap((value) => (Array.isArray(value) ? value : [value]))
|
|
292
|
-
.filter((value) => typeof value === "string");
|
|
293
|
-
}
|
|
294
|
-
/** Parse and validate one invocation without running a product operation. */
|
|
295
|
-
export function parseCliInvocation(argv) {
|
|
296
|
-
let selected;
|
|
297
|
-
const program = createCliProgram((command) => {
|
|
298
|
-
selected = command;
|
|
299
|
-
});
|
|
300
|
-
program.parse([process.execPath, "knodin", ...argv]);
|
|
301
|
-
if (!selected)
|
|
302
|
-
return { commandPath: [], positionals: [], options: program.opts() };
|
|
303
|
-
const command = selected;
|
|
304
|
-
const commandPath = [];
|
|
305
|
-
for (let current = command; current?.parent; current = current.parent) {
|
|
306
|
-
commandPath.unshift(current.name());
|
|
307
|
-
}
|
|
308
|
-
return {
|
|
309
|
-
commandPath,
|
|
310
|
-
positionals: flattenPositionals(command.processedArgs),
|
|
311
|
-
options: command.optsWithGlobals(),
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
/** Render root or nested help from the exact parser model. */
|
|
315
|
-
export function renderCliHelp(commandPath, columns) {
|
|
316
|
-
const program = createCliProgram();
|
|
317
|
-
let selected = program;
|
|
318
|
-
for (const name of commandPath) {
|
|
319
|
-
const child = selected.commands.find((command) => command.name() === name);
|
|
320
|
-
if (!child)
|
|
321
|
-
throw new Error(`knodin: unknown help command ${commandPath.join(" ")}`);
|
|
322
|
-
selected = child;
|
|
323
|
-
}
|
|
324
|
-
selected.configureHelp({ helpWidth: Math.max(60, Math.floor(columns ?? 100) - 1) });
|
|
325
|
-
let output = "";
|
|
326
|
-
selected.configureOutput({ writeOut: (value) => (output += value), writeErr: () => { } });
|
|
327
|
-
selected.outputHelp();
|
|
328
|
-
return formatTerminalHelp(output, columns);
|
|
329
|
-
}
|
|
330
|
-
/** Resolve the deepest declared command before a help flag or positional. */
|
|
331
|
-
export function helpCommandPath(argv) {
|
|
332
|
-
const program = createCliProgram();
|
|
333
|
-
const path = [];
|
|
334
|
-
let selected = program;
|
|
335
|
-
for (let index = 0; index < argv.length; index++) {
|
|
336
|
-
const value = argv[index];
|
|
337
|
-
if (value === "-h" || value === "--help")
|
|
338
|
-
break;
|
|
339
|
-
if (value.startsWith("-")) {
|
|
340
|
-
const optionName = value.split("=", 1)[0];
|
|
341
|
-
let cursor = selected;
|
|
342
|
-
let declared;
|
|
343
|
-
while (cursor && !declared) {
|
|
344
|
-
declared = cursor.options.find((candidate) => candidate.short === optionName || candidate.long === optionName);
|
|
345
|
-
cursor = cursor.parent;
|
|
346
|
-
}
|
|
347
|
-
if (declared?.required && !value.includes("="))
|
|
348
|
-
index++;
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
const child = selected.commands.find((command) => command.name() === value);
|
|
352
|
-
if (!child)
|
|
353
|
-
break;
|
|
354
|
-
selected = child;
|
|
355
|
-
path.push(value);
|
|
356
|
-
}
|
|
357
|
-
return path;
|
|
358
|
-
}
|