knodin 0.12.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/bin/cli.js +173 -14
- package/dist/src/agent-events.js +25 -7
- package/dist/src/authenticated-cursor.js +81 -0
- package/dist/src/class-consumer-contract.js +18 -0
- package/dist/src/class-consumer-cursor.js +91 -0
- package/dist/src/class-consumer-delivery.js +22 -0
- package/dist/src/class-consumer-page.js +148 -0
- package/dist/src/cli-model.js +9 -3
- package/dist/src/docs-sections.js +1 -0
- package/dist/src/engine/apex-class-uses.js +430 -0
- package/dist/src/engine/apex-entry-points.js +98 -0
- package/dist/src/engine/apex-receiver.js +301 -0
- package/dist/src/engine/embedding-reuse.js +57 -0
- package/dist/src/engine/embeddings.js +22 -0
- package/dist/src/engine/index-coverage.js +215 -0
- package/dist/src/engine/index.js +2495 -252
- package/dist/src/engine/salesforce-components.js +460 -0
- package/dist/src/engine/seal.js +3 -0
- package/dist/src/engine/sqlite.js +44 -0
- package/dist/src/evidence-bundle.js +283 -0
- package/dist/src/evidence-graph.js +163 -0
- package/dist/src/failure-diagnosis.js +80 -5
- package/dist/src/file-dependency.js +35 -0
- package/dist/src/graph-query-health.js +47 -1
- package/dist/src/implementation-search.js +69 -0
- package/dist/src/index-coverage-read.js +33 -0
- package/dist/src/investigation.js +195 -0
- package/dist/src/mcp-reliability.js +4 -0
- package/dist/src/mcp-worker-supervisor.js +122 -6
- package/dist/src/progressive-evidence.js +4 -4
- package/dist/src/response-budget.js +129 -3
- package/dist/src/server.js +18 -5
- package/dist/src/shared-index/publisher.js +41 -1
- package/dist/src/tools/knodin-tools.js +224 -41
- package/docs/CLI.md +92 -0
- package/docs/MCP.md +67 -0
- package/docs/PROGRESSIVE-EVIDENCE.md +62 -0
- package/docs/SALESFORCE-BINDINGS.md +121 -0
- package/docs/SALESFORCE-DEAD-CODE.md +45 -0
- package/docs/SCOPED-INDEXING.md +76 -0
- package/docs/apex-receiver-resolution.md +41 -0
- package/docs/releases/0.13.0.md +62 -0
- package/docs/structural-only-indexing.md +20 -0
- package/package.json +11 -5
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
import fs from "node:fs";
|
|
10
10
|
import nodePath from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { ClassConsumerQueryError, } from "../class-consumer-contract.js";
|
|
13
|
+
import { readClassConsumerCursor } from "../class-consumer-cursor.js";
|
|
14
|
+
import { validateClassConsumerDelivery } from "../class-consumer-delivery.js";
|
|
15
|
+
import { finalizeClassConsumerPage } from "../class-consumer-page.js";
|
|
12
16
|
import { resolveCliRuntimeCommand } from "../cli-args.js";
|
|
13
17
|
import { compactExplainResult, compactQueryResult, compactSearchResult, expandCompactIdentity, } from "../compact-structural.js";
|
|
14
18
|
import { buildKnodinContext } from "../context.js";
|
|
@@ -20,17 +24,21 @@ import { createEngine, KNODIN_SCHEMA_VERSION, REPO_WIDE_QUERY_PATTERNS, } from "
|
|
|
20
24
|
import { measurePerfPhaseSync } from "../engine/perf.js";
|
|
21
25
|
import { runSealedQuery } from "../engine/sealed-query.js";
|
|
22
26
|
import { resolveDbPath } from "../engine/state-paths.js";
|
|
27
|
+
import { deliverEvidenceBundle } from "../evidence-bundle.js";
|
|
28
|
+
import { createEvidenceGraphAdapter } from "../evidence-graph.js";
|
|
23
29
|
import { runExecutionProfile } from "../execution-profile.js";
|
|
24
30
|
import { diagnoseFailure, } from "../failure-diagnosis.js";
|
|
25
31
|
import { decorateGraphQueryResult, inspectGraphQueryHealth } from "../graph-query-health.js";
|
|
32
|
+
import { findImplementationCandidates } from "../implementation-search.js";
|
|
26
33
|
import { inspectRepositoryIntegrationStatus } from "../init.js";
|
|
34
|
+
import { buildTaskInvestigation } from "../investigation.js";
|
|
27
35
|
import { attachLifecycleHealth, attachRepairLifecycle } from "../lifecycle-health.js";
|
|
28
36
|
import { readManagerUpdateState, readUpdateJournal, updateAttention } from "../manager-update.js";
|
|
29
37
|
import { listMirrors } from "../mirror.js";
|
|
30
38
|
import { compressOutput, compressOutputFile, deleteOutputArtifact, readOutputArtifact, } from "../output-compression.js";
|
|
31
39
|
import { appendTelemetryRecord, clearTelemetry, countOutputTokens, exportTelemetry, measureOutput, readTelemetryRecords, telemetryStatus, writeTelemetryReport, } from "../output-telemetry.js";
|
|
32
40
|
import { auditPullRequests, ghUnavailableReason, triagePrDetail } from "../pr-triage.js";
|
|
33
|
-
import { deliverProgressiveEvidence, } from "../progressive-evidence.js";
|
|
41
|
+
import { deliverProgressiveEvidence, evidenceHandleSecret, } from "../progressive-evidence.js";
|
|
34
42
|
import { createRepairPlan } from "../repair-progress.js";
|
|
35
43
|
import { runRepositoryInitializationProcess } from "../repository-init-process.js";
|
|
36
44
|
import { discoverRepositories, initializeRepositories, inventoryRepository, searchRepositories, } from "../repository-management.js";
|
|
@@ -95,9 +103,8 @@ function inspectGatewayGraphHealth(repo) {
|
|
|
95
103
|
* The probe is cheap — `engine.status(..., { audit: "cached" })` — so the latch
|
|
96
104
|
* was buying very little in exchange for that failure mode.
|
|
97
105
|
*/
|
|
98
|
-
async function
|
|
99
|
-
|
|
100
|
-
return health.available ? null : health;
|
|
106
|
+
async function compactGraphHealth(repo) {
|
|
107
|
+
return inspectGatewayGraphHealth(repo);
|
|
101
108
|
}
|
|
102
109
|
/** Runs `fn`, converting any thrown error into a structured `{ error }` result
|
|
103
110
|
* instead of letting it escape into the stdio transport. Extracted from the
|
|
@@ -155,6 +162,7 @@ const QUERY_PATTERNS = [
|
|
|
155
162
|
"handlers_of",
|
|
156
163
|
"endpoints_for",
|
|
157
164
|
"consumers_of",
|
|
165
|
+
"class_consumers",
|
|
158
166
|
"children_of",
|
|
159
167
|
"community",
|
|
160
168
|
"federated_repos",
|
|
@@ -236,9 +244,15 @@ function buildDocumentedKnodinTools() {
|
|
|
236
244
|
enum: ["locate", "outline", "evidence", "expand"],
|
|
237
245
|
description: "evidence only: deterministic progressive-delivery level.",
|
|
238
246
|
},
|
|
247
|
+
evidenceFiles: {
|
|
248
|
+
type: "array",
|
|
249
|
+
items: { type: "string" },
|
|
250
|
+
maxItems: 32,
|
|
251
|
+
description: "evidence only: multiple repo-relative files for a verified evidence/expand bundle; exclusive with file. alreadyPresent accepts returned bundle file handles.",
|
|
252
|
+
},
|
|
239
253
|
continuation: {
|
|
240
254
|
type: "string",
|
|
241
|
-
description: "evidence
|
|
255
|
+
description: "evidence/class_consumers: authenticated continuation from the previous page; repeat the same target and filters.",
|
|
242
256
|
},
|
|
243
257
|
baselineHash: {
|
|
244
258
|
type: "string",
|
|
@@ -271,7 +285,7 @@ function buildDocumentedKnodinTools() {
|
|
|
271
285
|
description: "feature_path — deterministic downstream DFS over resolved source references. It is bounded by depth and item limits, source-evidenced per hop, cycle-guarded, and never claims runtime execution; dynamic dispatch and unresolved calls are omitted. " +
|
|
272
286
|
"lsp_diagnostics accepts a repo-relative TypeScript/JavaScript file; lsp_definitions, lsp_declarations, and lsp_implementations accept file:line:column. These use an optional local TypeScript language-service adapter, never start a daemon or write files, and return an explicit unavailable result when no local adapter supports the file type. " +
|
|
273
287
|
"api_contract_mismatches — bounded, source-evidenced static mismatch findings for literal Express/Fastify routes and literal fetch/Axios clients. Matching requires method and normalized path, and requires exact origin equality when a client uses an absolute origin; dynamic routes remain explicitly unresolved. mcp_tools — list indexed TypeScript MCP SDK tool registrations, or pass symbol to look up one exact tool name; returns description/schema/handler/file and exact versus heuristic confidence. import_cycles — repo-wide canonical directed file-import cycles; no symbol required, type-only imports included, non-import lineage/ORM edges excluded, and limit/truncated bounds output. " +
|
|
274
|
-
"query: the graph pattern to run (callers_of, callees_of, imports_of, importers_of, inheritors_of, structural_implementations_of, tests_for, file_summary, shortest_path, cross_substrate_path, impact, dead_code, large_functions, large_files, rename_preview, flows, flow_of, flow_analysis, resource_reachability, stats, traverse, knowledge_gaps, community, federated_repos). cross_substrate_path proves only one static Salesforce Flow action to one uniquely resolved Apex @InvocableMethod and returns exact endpoint identities/evidence, freshness, budgets, and explicit unsupported crossings; it does not resolve Terraform, dbt, or arbitrary substrate paths. resource_reachability is a repo-wide, bounded, cached, on-demand TS/JS static heuristic for literal process.env/fs.readFileSync sources reaching console.log/fetch/db.query sinks; it reports evidence, coverage, omissions, freshness, truncation, and continuation and is never runtime reachability or exploitability proof. structural_implementations_of returns TypeScript structurally typed object implementations separately from nominal extends/implements results. federated_repos — discover and list all registered/configured repository paths that this engine federates queries across. community — fetch a single community's full detail (name, size, cohesion, files, and symbols) by name or substring filter; empty `symbol` = all communities. large_files — whole-file line counts (min 200), sorted descending — the file-level counterpart to large_functions, for spotting god files rather than god functions; same optional path-substring filter, empty `symbol` = all files. stats — repo-level index size + health in one call (per-repo symbol/reference/dependency/embedding/file counts, per-kind and per-language breakdowns, orphaned-embedding count, schema version, last-indexed HEAD, and cached community/hub/bridge/flow totals); federated, empty `symbol`. traverse — typed BFS from `symbol` within `depth` hops (default 3, 1–6), selectable upstream/downstream/both and explicit edge kinds; every discovered edge includes direction, kind, provenance, confidence, files, and source line, with optional Tree-sitter-grounded argument expressions honestly marked heuristic. Sets `truncated` when the `limit` cap binds; single-repo. knowledge_gaps — repo-health weaknesses in one call: thin communities (<3 symbols), single-file communities, isolated (zero-degree) symbols, and untested hub/bridge hotspots; sourced from the cached map(), federated, empty `symbol`, one row carrying a `knowledgeGaps` block. surprising_connections — resolved edges scored by a composite surprise formula (cross-community, cross-language, peripheral-to-hub, cross-test-boundary, unusual edge kind) to surface unexpected coupling; sorted highest-first, top 15 by default, federated, empty `symbol`, each row carrying a `surprise` block. suggested_questions — prioritized, human-readable review prompts synthesized from knowledge_gaps + surprising_connections (untested hotspots first, then surprising edges, then thin/single-file communities); federated, empty `symbol`, each row a `question` string citing the real symbol/file, top 10 by default. architecture_overview — a federated architecture view with scoped community coupling and independently selectable packages, layers, boundaries, hotspots, entry points, and language facets; `path` is segment-safe and applies consistently. Spring/event patterns (Java): triggers_of — the schedule edge between a @Scheduled method and its synthetic scheduler (pass the method to get its scheduler, or the scheduler to list scheduled methods). publishers_of — methods that publish a given event type (ApplicationEventPublisher.publishEvent). listeners_of — @EventListener methods that listen for a given event type. handlers_of — methods that handle a given HTTP endpoint path (Spring @*Mapping + the existing JS/Python endpoints). endpoints_for — the inverse: endpoint path(s) a given method handles. consumers_of — classes/methods consuming a given @Value config property. children_of — symbols contained in a file path (same as file_summary) or, for a class name, its member symbols by line-range containment. All seven require `symbol` (the target); single-repo, not federated.",
|
|
288
|
+
"query: the graph pattern to run (callers_of, callees_of, imports_of, importers_of, inheritors_of, structural_implementations_of, tests_for, file_summary, shortest_path, cross_substrate_path, impact, dead_code, large_functions, large_files, rename_preview, flows, flow_of, flow_analysis, resource_reachability, stats, traverse, knowledge_gaps, community, federated_repos). cross_substrate_path proves only one static Salesforce Flow action to one uniquely resolved Apex @InvocableMethod and returns exact endpoint identities/evidence, freshness, budgets, and explicit unsupported crossings; it does not resolve Terraform, dbt, or arbitrary substrate paths. resource_reachability is a repo-wide, bounded, cached, on-demand TS/JS static heuristic for literal process.env/fs.readFileSync sources reaching console.log/fetch/db.query sinks; it reports evidence, coverage, omissions, freshness, truncation, and continuation and is never runtime reachability or exploitability proof. structural_implementations_of returns TypeScript structurally typed object implementations separately from nominal extends/implements results. federated_repos — discover and list all registered/configured repository paths that this engine federates queries across. community — fetch a single community's full detail (name, size, cohesion, files, and symbols) by name or substring filter; empty `symbol` = all communities. large_files — whole-file line counts (min 200), sorted descending — the file-level counterpart to large_functions, for spotting god files rather than god functions; same optional path-substring filter, empty `symbol` = all files. stats — repo-level index size + health in one call (per-repo symbol/reference/dependency/embedding/file counts, per-kind and per-language breakdowns, orphaned-embedding count, schema version, last-indexed HEAD, and cached community/hub/bridge/flow totals); federated, empty `symbol`. traverse — typed BFS from `symbol` within `depth` hops (default 3, 1–6), selectable upstream/downstream/both and explicit edge kinds; every discovered edge includes direction, kind, provenance, confidence, files, and source line, with optional Tree-sitter-grounded argument expressions honestly marked heuristic. Sets `truncated` when the `limit` cap binds; single-repo. knowledge_gaps — repo-health weaknesses in one call: thin communities (<3 symbols), single-file communities, isolated (zero-degree) symbols, and untested hub/bridge hotspots; sourced from the cached map(), federated, empty `symbol`, one row carrying a `knowledgeGaps` block. surprising_connections — resolved edges scored by a composite surprise formula (cross-community, cross-language, peripheral-to-hub, cross-test-boundary, unusual edge kind) to surface unexpected coupling; sorted highest-first, top 15 by default, federated, empty `symbol`, each row carrying a `surprise` block. suggested_questions — prioritized, human-readable review prompts synthesized from knowledge_gaps + surprising_connections (untested hotspots first, then surprising edges, then thin/single-file communities); federated, empty `symbol`, each row a `question` string citing the real symbol/file, top 10 by default. architecture_overview — a federated architecture view with scoped community coupling and independently selectable packages, layers, boundaries, hotspots, entry points, and language facets; `path` is segment-safe and applies consistently. class_consumers returns direct typed Apex class-use sites separately from method callers and Java config consumers; count is observed sites, consumerFileCount distinct files, testScope defaults all, limit caps sites (1-1000), path is a canonical prefix, and continuation resumes the same target/filters only while the graph snapshot remains unchanged. Supported syntax only, not runtime or complete class-use coverage. Spring/event patterns (Java): triggers_of — the schedule edge between a @Scheduled method and its synthetic scheduler (pass the method to get its scheduler, or the scheduler to list scheduled methods). publishers_of — methods that publish a given event type (ApplicationEventPublisher.publishEvent). listeners_of — @EventListener methods that listen for a given event type. handlers_of — methods that handle a given HTTP endpoint path (Spring @*Mapping + the existing JS/Python endpoints). endpoints_for — the inverse: endpoint path(s) a given method handles. consumers_of — classes/methods consuming a given @Value config property. children_of — symbols contained in a file path (same as file_summary) or, for a class name, its member symbols by line-range containment. All seven require `symbol` (the target); single-repo, not federated.",
|
|
275
289
|
},
|
|
276
290
|
depth: {
|
|
277
291
|
type: "number",
|
|
@@ -405,7 +419,7 @@ function buildDocumentedKnodinTools() {
|
|
|
405
419
|
testScope: {
|
|
406
420
|
type: "string",
|
|
407
421
|
enum: ["all", "test", "production"],
|
|
408
|
-
description: "search/file_metrics:
|
|
422
|
+
description: "search/file_metrics/class_consumers: all (default), test-only, or production-only files.",
|
|
409
423
|
},
|
|
410
424
|
caseSensitive: {
|
|
411
425
|
type: "boolean",
|
|
@@ -488,6 +502,11 @@ function buildDocumentedKnodinTools() {
|
|
|
488
502
|
type: "string",
|
|
489
503
|
description: "context only (required): free-text description of what you're doing (e.g. 'review PR #42', 'debug login timeout', 'how does auth work'). Drives a keyword heuristic that suggests which operation to run next — a hint only, never a constraint.",
|
|
490
504
|
},
|
|
505
|
+
contextMode: {
|
|
506
|
+
type: "string",
|
|
507
|
+
enum: ["orientation", "implementation"],
|
|
508
|
+
description: "context only: implementation ranks candidate functions/methods/classes without selecting a target. Omit for orientation; supply symbol for a target investigation.",
|
|
509
|
+
},
|
|
491
510
|
changedFiles: {
|
|
492
511
|
type: "array",
|
|
493
512
|
items: { type: "string" },
|
|
@@ -734,14 +753,14 @@ export function getDocumentedKnodinToolsForSchemaProof() {
|
|
|
734
753
|
const COMPACT_PARAMETER_DESCRIPTIONS = {
|
|
735
754
|
operation: "Capability.",
|
|
736
755
|
symbol: "Target.",
|
|
737
|
-
pattern: "
|
|
738
|
-
impactMode: "Symbol
|
|
756
|
+
pattern: "Pattern.",
|
|
757
|
+
impactMode: "Symbol/file.",
|
|
739
758
|
apply: "Apply edits.",
|
|
740
759
|
// Shortened from "Review diff scope." / "Documentation section." on main: the
|
|
741
760
|
// `remote` operation added to the enum costs tokens, and this surface has a
|
|
742
761
|
// hard ceiling enforced by tool-schema-budget.spec.ts.
|
|
743
762
|
diffScope: "Diff scope.",
|
|
744
|
-
section: "Docs
|
|
763
|
+
section: "Docs.",
|
|
745
764
|
persistTelemetry: "Persist telemetry.",
|
|
746
765
|
};
|
|
747
766
|
/**
|
|
@@ -996,6 +1015,11 @@ async function handlePullRequestsOperation(options) {
|
|
|
996
1015
|
}));
|
|
997
1016
|
}
|
|
998
1017
|
function validateKnodinArgs(args) {
|
|
1018
|
+
if (args.contextMode !== undefined &&
|
|
1019
|
+
!["orientation", "implementation"].includes(args.contextMode))
|
|
1020
|
+
throw new Error("Invalid contextMode");
|
|
1021
|
+
if (args.contextMode === "implementation" && args.symbol)
|
|
1022
|
+
throw new Error("Choose implementation discovery or a target symbol, not both");
|
|
999
1023
|
const requireStringArray = (name, value) => {
|
|
1000
1024
|
if (value !== undefined &&
|
|
1001
1025
|
(!Array.isArray(value) || value.some((item) => typeof item !== "string")))
|
|
@@ -1010,6 +1034,7 @@ function validateKnodinArgs(args) {
|
|
|
1010
1034
|
"exclude",
|
|
1011
1035
|
"roots",
|
|
1012
1036
|
"alreadyPresent",
|
|
1037
|
+
"evidenceFiles",
|
|
1013
1038
|
"chatFiles",
|
|
1014
1039
|
])
|
|
1015
1040
|
requireStringArray(name, args[name]);
|
|
@@ -1148,6 +1173,10 @@ function compactFailureRelation(relation) {
|
|
|
1148
1173
|
function compactFailureDiagnosis(result) {
|
|
1149
1174
|
return {
|
|
1150
1175
|
status: result.status,
|
|
1176
|
+
available: result.available,
|
|
1177
|
+
state: result.state,
|
|
1178
|
+
indexCoverage: result.indexCoverage,
|
|
1179
|
+
coverageNotice: result.coverageNotice,
|
|
1151
1180
|
input: [result.input.kind, result.input.artifactId, result.input.bytes, result.input.complete],
|
|
1152
1181
|
// `reference` is [reported path, line, column]. `owner` is
|
|
1153
1182
|
// [stable identity, symbol, kind, start, end, signature].
|
|
@@ -1335,31 +1364,61 @@ function handleDocsOperation(section, bounded) {
|
|
|
1335
1364
|
}
|
|
1336
1365
|
async function tryCompactStructural(args, repo, selector, bounded) {
|
|
1337
1366
|
const { operation, detailLevel, includeSource, symbol, file, byteBudget } = args;
|
|
1367
|
+
const compactCandidate = detailLevel === "compact" &&
|
|
1368
|
+
((operation === "explain" && includeSource === true && !!symbol) ||
|
|
1369
|
+
(operation === "search" && !!args.query) ||
|
|
1370
|
+
(operation === "query" &&
|
|
1371
|
+
["file_summary", "batch_outline", "project_overview"].includes(args.pattern ?? "")));
|
|
1372
|
+
if (!compactCandidate)
|
|
1373
|
+
return { handled: false, value: NOT_COMPACT_STRUCTURAL };
|
|
1374
|
+
const health = await compactGraphHealth(repo);
|
|
1375
|
+
// Compact strings cannot carry the mandatory scope contract. Scoped, pending
|
|
1376
|
+
// and unknown coverage use the normal decorated, budgeted response instead.
|
|
1377
|
+
if (health.available) {
|
|
1378
|
+
const coverage = health.graph.indexCoverage;
|
|
1379
|
+
if (coverage?.mode !== "full" ||
|
|
1380
|
+
coverage.intentState !== "complete" ||
|
|
1381
|
+
coverage.repositoryComplete !== true ||
|
|
1382
|
+
coverage.negativeScope !== "repository")
|
|
1383
|
+
return { handled: false, value: NOT_COMPACT_STRUCTURAL, qualifiedFallback: true };
|
|
1384
|
+
}
|
|
1338
1385
|
const unavailable = async (op) => {
|
|
1339
|
-
|
|
1340
|
-
|
|
1386
|
+
return health.available ? null : bounded(health, op, true);
|
|
1387
|
+
};
|
|
1388
|
+
const verifiedCompact = async (value) => {
|
|
1389
|
+
if (!health.available)
|
|
1390
|
+
return { handled: true, value };
|
|
1391
|
+
const after = await compactGraphHealth(repo);
|
|
1392
|
+
if (!after.available)
|
|
1393
|
+
return { handled: true, value: bounded(after, operation, true) };
|
|
1394
|
+
const beforeCoverage = health.graph.indexCoverage;
|
|
1395
|
+
const afterCoverage = after.graph.indexCoverage;
|
|
1396
|
+
if (!afterCoverage ||
|
|
1397
|
+
afterCoverage.mode !== "full" ||
|
|
1398
|
+
afterCoverage.intentState !== "complete" ||
|
|
1399
|
+
!afterCoverage.repositoryComplete ||
|
|
1400
|
+
afterCoverage.negativeScope !== "repository" ||
|
|
1401
|
+
afterCoverage.digest !== beforeCoverage?.digest ||
|
|
1402
|
+
afterCoverage.revision !== beforeCoverage?.revision ||
|
|
1403
|
+
afterCoverage.origin !== beforeCoverage?.origin)
|
|
1404
|
+
return { handled: false, value: NOT_COMPACT_STRUCTURAL, qualifiedFallback: true };
|
|
1405
|
+
return { handled: true, value };
|
|
1341
1406
|
};
|
|
1342
1407
|
if (operation === "explain" && detailLevel === "compact" && includeSource === true && symbol) {
|
|
1343
1408
|
const blocked = await unavailable("explain");
|
|
1344
|
-
return
|
|
1345
|
-
handled: true,
|
|
1346
|
-
value: blocked ?? (await runCompactExplain(symbol, repo, selector, file, byteBudget)),
|
|
1347
|
-
};
|
|
1409
|
+
return verifiedCompact(blocked ?? (await runCompactExplain(symbol, repo, selector, file, byteBudget)));
|
|
1348
1410
|
}
|
|
1349
1411
|
if (operation === "search" && detailLevel === "compact" && args.query) {
|
|
1350
1412
|
const blocked = await unavailable("search");
|
|
1351
|
-
return
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
offset: args.offset,
|
|
1361
|
-
}, byteBudget)),
|
|
1362
|
-
};
|
|
1413
|
+
return verifiedCompact(blocked ??
|
|
1414
|
+
(await runCompactSearch(args.query, repo, Math.min(args.limit ?? args.itemBudget ?? 5, args.itemBudget ?? 100), {
|
|
1415
|
+
languages: args.languages,
|
|
1416
|
+
extensions: args.extensions,
|
|
1417
|
+
kinds: args.kinds,
|
|
1418
|
+
path: args.path,
|
|
1419
|
+
testScope: args.testScope,
|
|
1420
|
+
offset: args.offset,
|
|
1421
|
+
}, byteBudget)));
|
|
1363
1422
|
}
|
|
1364
1423
|
const compactPattern = operation === "query" &&
|
|
1365
1424
|
detailLevel === "compact" &&
|
|
@@ -1371,11 +1430,8 @@ async function tryCompactStructural(args, repo, selector, bounded) {
|
|
|
1371
1430
|
if (!compactPattern)
|
|
1372
1431
|
return { handled: false, value: NOT_COMPACT_STRUCTURAL };
|
|
1373
1432
|
const blocked = await unavailable(`query:${compactPattern}`);
|
|
1374
|
-
return
|
|
1375
|
-
|
|
1376
|
-
value: blocked ??
|
|
1377
|
-
(await runCompactQuery(compactPattern, symbol ?? "", repo, Math.min(args.limit ?? args.itemBudget ?? 100, args.itemBudget ?? 1000), args.depth, selector, byteBudget)),
|
|
1378
|
-
};
|
|
1433
|
+
return verifiedCompact(blocked ??
|
|
1434
|
+
(await runCompactQuery(compactPattern, symbol ?? "", repo, Math.min(args.limit ?? args.itemBudget ?? 100, args.itemBudget ?? 1000), args.depth, selector, byteBudget)));
|
|
1379
1435
|
}
|
|
1380
1436
|
const mcpRequestSignals = new WeakMap();
|
|
1381
1437
|
export function attachMcpRequestSignal(args, signal) {
|
|
@@ -1423,7 +1479,7 @@ async function handleDiagnosticsOperation(repo, args, bounded) {
|
|
|
1423
1479
|
})
|
|
1424
1480
|
: persistDiagnosticsPreview(repo, options), `diagnostics:${args.telemetryAction === "export" ? "archive" : "preview"}`);
|
|
1425
1481
|
}
|
|
1426
|
-
async function dispatchKnodinTool(args) {
|
|
1482
|
+
async function dispatchKnodinTool(args, qualifiedCompactFallback = false) {
|
|
1427
1483
|
const startedAt = performance.now();
|
|
1428
1484
|
const parsedArgs = (args ?? {});
|
|
1429
1485
|
const { operation, symbol, base, diffScope, from, toRevision, reviewFiles, query, pattern, queries, to, limit, depth, impactMode, direction, relationKinds, minConfidence, includeTests, includeDataFlow, flowVariable, apply, force, detailLevel, prNumber, prState, branches, auditRange, auditBase, auditHead, expectedLogin, worktreeAction, worktreePath, telemetryAction, telemetryRetentionDays, task, changedFiles, repoPath, section, systemAction, repositories, scope, distributed, repositoryIds, components, evidence, relationshipType, requireComplete, repositoryAction, roots, linkedWorktrees, cursor, allowPartial, dryRun, manifestPath, planDigest, identity, file, kind, toIdentity, toFile, toKind, byteBudget, tokenBudget, itemBudget, includeSource, languages, extensions, kinds, path, architectureFacets, testScope, caseSensitive, offset, minLines, minComplexity, topN, sort, packAction, format, include, exclude, filePolicies, alreadyPresent, chatFiles, lineNumbers, includeTree, outputPath, artifactPath, startLine, endLine, regex, regexFlags, gitDiffScope, gitLog, statusAudit, timeoutMs, client, repairPlan, persistTelemetry, } = parsedArgs;
|
|
@@ -1483,6 +1539,11 @@ async function dispatchKnodinTool(args) {
|
|
|
1483
1539
|
const observe = (output, op, minimal = false) => {
|
|
1484
1540
|
const root = output;
|
|
1485
1541
|
const budgetMeta = root.responseBudget;
|
|
1542
|
+
const evidenceBudget = op === "evidence" &&
|
|
1543
|
+
(root.protocol === "knodin-evidence-bundle-v1" ||
|
|
1544
|
+
root.protocol === "knodin-progressive-evidence-v1")
|
|
1545
|
+
? root.budget
|
|
1546
|
+
: undefined;
|
|
1486
1547
|
if (op === "review")
|
|
1487
1548
|
measurePerfPhaseSync("review_serialization", () => JSON.stringify(output));
|
|
1488
1549
|
const telemetry = measureOutput({
|
|
@@ -1492,6 +1553,7 @@ async function dispatchKnodinTool(args) {
|
|
|
1492
1553
|
startedAt,
|
|
1493
1554
|
detailMode: minimal ? "minimal" : (detailLevel ?? "standard"),
|
|
1494
1555
|
truncated: budgetMeta?.truncated === true ||
|
|
1556
|
+
evidenceBudget?.truncated === true ||
|
|
1495
1557
|
root.telemetry?.truncated === true,
|
|
1496
1558
|
schemaTokens: gatewaySchemaTokens(),
|
|
1497
1559
|
});
|
|
@@ -1543,12 +1605,14 @@ async function dispatchKnodinTool(args) {
|
|
|
1543
1605
|
const verified = await inspectGatewayGraphHealth(repo);
|
|
1544
1606
|
if (!verified.available)
|
|
1545
1607
|
return bounded(verified, op, minimal);
|
|
1546
|
-
return bounded(decorateGraphQueryResult(result, verified.state, verified.graph.freshness), op, minimal);
|
|
1608
|
+
return bounded(decorateGraphQueryResult(result, verified.state, verified.graph.freshness, verified.graph.indexCoverage, health.graph.indexCoverage), op, minimal);
|
|
1547
1609
|
};
|
|
1548
1610
|
validateKnodinArgs(parsedArgs);
|
|
1549
1611
|
const compactStructural = await tryCompactStructural(parsedArgs, repo, selector, bounded);
|
|
1550
1612
|
if (compactStructural.handled)
|
|
1551
1613
|
return compactStructural.value;
|
|
1614
|
+
if (compactStructural.qualifiedFallback)
|
|
1615
|
+
return dispatchKnodinTool({ ...parsedArgs, detailLevel: "minimal" }, true);
|
|
1552
1616
|
if (detailLevel === "compact")
|
|
1553
1617
|
throw new Error("knodin: compact detail is unsupported here");
|
|
1554
1618
|
switch (operation) {
|
|
@@ -1687,6 +1751,22 @@ async function dispatchKnodinTool(args) {
|
|
|
1687
1751
|
case "execute":
|
|
1688
1752
|
return handleExecutionOperation(repo, parsedArgs.profile, engine, bounded);
|
|
1689
1753
|
case "evidence":
|
|
1754
|
+
if (parsedArgs.evidenceFiles) {
|
|
1755
|
+
if (parsedArgs.file)
|
|
1756
|
+
throw new Error("Choose file or evidenceFiles, not both");
|
|
1757
|
+
if (parsedArgs.evidenceLevel !== "evidence" && parsedArgs.evidenceLevel !== "expand")
|
|
1758
|
+
throw new Error("Evidence bundles require evidence or expand level");
|
|
1759
|
+
return observe(await deliverEvidenceBundle({
|
|
1760
|
+
repo,
|
|
1761
|
+
files: parsedArgs.evidenceFiles,
|
|
1762
|
+
level: parsedArgs.evidenceLevel,
|
|
1763
|
+
continuation: parsedArgs.continuation,
|
|
1764
|
+
alreadyPresent: parsedArgs.alreadyPresent,
|
|
1765
|
+
byteLimit: parsedArgs.byteBudget,
|
|
1766
|
+
tokenLimit: parsedArgs.tokenBudget,
|
|
1767
|
+
itemLimit: parsedArgs.itemBudget,
|
|
1768
|
+
}, createEvidenceGraphAdapter(engine)), "evidence");
|
|
1769
|
+
}
|
|
1690
1770
|
return handleProgressiveEvidenceOperation(repo, parsedArgs, observe);
|
|
1691
1771
|
case "pack": {
|
|
1692
1772
|
if (packAction !== undefined && !["export", "read", "grep"].includes(packAction))
|
|
@@ -1756,7 +1836,11 @@ async function dispatchKnodinTool(args) {
|
|
|
1756
1836
|
case "context": {
|
|
1757
1837
|
if (!task)
|
|
1758
1838
|
throw new Error("knodin context requires `task`");
|
|
1759
|
-
|
|
1839
|
+
if (parsedArgs.contextMode === "implementation")
|
|
1840
|
+
return graphRead("context", () => findImplementationCandidates(engine, repo, { task, limit, offset }));
|
|
1841
|
+
return graphRead("context", () => symbol
|
|
1842
|
+
? buildTaskInvestigation(engine, repo, { task, symbol, ...selector, limit, depth })
|
|
1843
|
+
: buildKnodinContext(engine, task, repo, base, changedFiles));
|
|
1760
1844
|
}
|
|
1761
1845
|
case "search":
|
|
1762
1846
|
if (!query)
|
|
@@ -1769,7 +1853,8 @@ async function dispatchKnodinTool(args) {
|
|
|
1769
1853
|
kinds,
|
|
1770
1854
|
path,
|
|
1771
1855
|
testScope,
|
|
1772
|
-
includeSource,
|
|
1856
|
+
includeSource: qualifiedCompactFallback ? false : includeSource,
|
|
1857
|
+
structural: qualifiedCompactFallback ? true : undefined,
|
|
1773
1858
|
offset,
|
|
1774
1859
|
}));
|
|
1775
1860
|
case "textSearch": {
|
|
@@ -1824,7 +1909,7 @@ async function dispatchKnodinTool(args) {
|
|
|
1824
1909
|
return bounded({
|
|
1825
1910
|
results: outputs.map((item) => ({
|
|
1826
1911
|
...item,
|
|
1827
|
-
output: decorateGraphQueryResult(item.output, verifiedQueryHealth.state, verifiedQueryHealth.graph.freshness),
|
|
1912
|
+
output: decorateGraphQueryResult(item.output, verifiedQueryHealth.state, verifiedQueryHealth.graph.freshness, verifiedQueryHealth.graph.indexCoverage, queryHealth.graph.indexCoverage),
|
|
1828
1913
|
})),
|
|
1829
1914
|
}, "query_batch");
|
|
1830
1915
|
}
|
|
@@ -1862,12 +1947,100 @@ async function dispatchKnodinTool(args) {
|
|
|
1862
1947
|
: await inspectGatewayGraphHealth(repo);
|
|
1863
1948
|
if (queryHealth && !queryHealth.available)
|
|
1864
1949
|
return bounded(queryHealth, `query:${pattern}`);
|
|
1950
|
+
if (pattern === "class_consumers") {
|
|
1951
|
+
if (limit !== undefined && limit > 1000)
|
|
1952
|
+
throw new Error("class_consumers limit must not exceed 1000 sites");
|
|
1953
|
+
if (offset !== undefined ||
|
|
1954
|
+
kinds !== undefined ||
|
|
1955
|
+
relationKinds !== undefined ||
|
|
1956
|
+
direction !== undefined ||
|
|
1957
|
+
depth !== undefined ||
|
|
1958
|
+
includeTests !== undefined)
|
|
1959
|
+
throw new Error("class_consumers supports testScope/path filters and signed continuation, not offset/kinds/relations/direction/depth");
|
|
1960
|
+
const secret = evidenceHandleSecret(repo).secret;
|
|
1961
|
+
const cursor = parsedArgs.continuation
|
|
1962
|
+
? readClassConsumerCursor(parsedArgs.continuation, secret)
|
|
1963
|
+
: undefined;
|
|
1964
|
+
const request = {
|
|
1965
|
+
symbol: target ?? "",
|
|
1966
|
+
selector,
|
|
1967
|
+
testScope: testScope ?? "all",
|
|
1968
|
+
path,
|
|
1969
|
+
limit: limit ?? 100,
|
|
1970
|
+
...(cursor
|
|
1971
|
+
? {
|
|
1972
|
+
after: cursor.after,
|
|
1973
|
+
expectedQueryDigest: cursor.queryDigest,
|
|
1974
|
+
expectedSnapshotDigest: cursor.snapshotDigest,
|
|
1975
|
+
}
|
|
1976
|
+
: {}),
|
|
1977
|
+
};
|
|
1978
|
+
try {
|
|
1979
|
+
const page = await engine.queryClassConsumers(repo, request);
|
|
1980
|
+
// Defer maintenance, not the audit, until a later eligible status or close.
|
|
1981
|
+
const verified = await inspectGraphQueryHealth(repo, async (target) => attachLifecycleHealth(target, await engine.status(target, { audit: "cached", checkpoint: "defer" })));
|
|
1982
|
+
if (!verified.available)
|
|
1983
|
+
return bounded(verified, "query:class_consumers");
|
|
1984
|
+
// Revalidate facts revisions too: a health probe alone cannot detect every same-scope edit.
|
|
1985
|
+
await validateClassConsumerDelivery(page, request, (next) => engine.queryClassConsumers(repo, next));
|
|
1986
|
+
const sharedIndex = sharedIndexEnvelope();
|
|
1987
|
+
return observe(finalizeClassConsumerPage(page, {
|
|
1988
|
+
secret,
|
|
1989
|
+
budget,
|
|
1990
|
+
decorate: (candidate) => ({
|
|
1991
|
+
...decorateGraphQueryResult(candidate, verified.state, undefined, verified.graph.indexCoverage, queryHealth?.available ? queryHealth.graph.indexCoverage : undefined),
|
|
1992
|
+
sharedIndex,
|
|
1993
|
+
}),
|
|
1994
|
+
}), "query:class_consumers");
|
|
1995
|
+
}
|
|
1996
|
+
catch (error) {
|
|
1997
|
+
if (error instanceof ClassConsumerQueryError &&
|
|
1998
|
+
[
|
|
1999
|
+
"CLASS_CONSUMER_TARGET_NOT_FOUND",
|
|
2000
|
+
"CLASS_CONSUMER_TARGET_AMBIGUOUS",
|
|
2001
|
+
"CLASS_CONSUMER_TARGET_CAPABILITY_MISSING",
|
|
2002
|
+
].includes(error.code)) {
|
|
2003
|
+
const targetResolution = error.code === "CLASS_CONSUMER_TARGET_CAPABILITY_MISSING"
|
|
2004
|
+
? "capability-missing"
|
|
2005
|
+
: error.code === "CLASS_CONSUMER_TARGET_NOT_FOUND"
|
|
2006
|
+
? "not-found"
|
|
2007
|
+
: "ambiguous";
|
|
2008
|
+
const delivered = bounded({
|
|
2009
|
+
available: false,
|
|
2010
|
+
status: "unavailable",
|
|
2011
|
+
pattern,
|
|
2012
|
+
targetResolution,
|
|
2013
|
+
code: error.code,
|
|
2014
|
+
error: error.message,
|
|
2015
|
+
candidates: error.candidates,
|
|
2016
|
+
...(queryHealth?.available
|
|
2017
|
+
? {
|
|
2018
|
+
indexCoverage: queryHealth.graph.indexCoverage,
|
|
2019
|
+
freshness: queryHealth.graph.freshness,
|
|
2020
|
+
}
|
|
2021
|
+
: {}),
|
|
2022
|
+
}, "query:class_consumers");
|
|
2023
|
+
if (delivered.code !== error.code ||
|
|
2024
|
+
delivered.targetResolution !== targetResolution ||
|
|
2025
|
+
delivered.available !== false ||
|
|
2026
|
+
delivered.status !== "unavailable")
|
|
2027
|
+
throw new Error("Response budget cannot preserve the class-consumer error contract; increase the budget.");
|
|
2028
|
+
return delivered;
|
|
2029
|
+
}
|
|
2030
|
+
throw error;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
1865
2033
|
if (pattern === "rename_preview") {
|
|
1866
2034
|
// rename_preview routes through the rename engine: dry-run (preview +
|
|
1867
2035
|
// unified diff) by default, writing to disk only when `apply` is true.
|
|
1868
2036
|
return engine
|
|
1869
2037
|
.rename(symbol ?? "", to ?? "", repo, apply === true, true, selector)
|
|
1870
|
-
.then((result) =>
|
|
2038
|
+
.then(async (result) => {
|
|
2039
|
+
const verified = await inspectGatewayGraphHealth(repo);
|
|
2040
|
+
if (!verified.available)
|
|
2041
|
+
return bounded(verified, "query:rename_preview");
|
|
2042
|
+
return bounded(decorateGraphQueryResult(result, verified.state, verified.graph.freshness, verified.graph.indexCoverage, queryHealth?.available ? queryHealth.graph.indexCoverage : undefined), "query:rename_preview");
|
|
2043
|
+
});
|
|
1871
2044
|
}
|
|
1872
2045
|
const queryResult = await engine.query(pattern, target ?? "", repo, to, Math.min(limit ?? itemBudget ?? 100, itemBudget ?? 1000), depth, detailLevel === "source" ? undefined : detailLevel, selector, pattern === "impact"
|
|
1873
2046
|
? {
|
|
@@ -1908,9 +2081,19 @@ async function dispatchKnodinTool(args) {
|
|
|
1908
2081
|
// A verified probe supersedes the unverified one for both fields, so
|
|
1909
2082
|
// state and freshness always come from the same observation.
|
|
1910
2083
|
const authoritative = verifiedQueryHealth?.available ? verifiedQueryHealth : queryHealth;
|
|
1911
|
-
decorated = decorateGraphQueryResult(result, authoritative.state, authoritative.graph.freshness);
|
|
2084
|
+
decorated = decorateGraphQueryResult(result, authoritative.state, authoritative.graph.freshness, authoritative.graph.indexCoverage, queryHealth.graph.indexCoverage);
|
|
2085
|
+
}
|
|
2086
|
+
const delivered = bounded(decorated, `query:${pattern}`);
|
|
2087
|
+
if (pattern === "inheritors_of") {
|
|
2088
|
+
const qualification = decorated
|
|
2089
|
+
.inheritanceQuery;
|
|
2090
|
+
const retained = delivered
|
|
2091
|
+
.inheritanceQuery;
|
|
2092
|
+
if (qualification &&
|
|
2093
|
+
["version", "mode", "partial", "truncated", "apex"].some((key) => retained?.[key] !== qualification[key]))
|
|
2094
|
+
throw new Error("Response budget cannot preserve the inheritance qualification; increase the budget.");
|
|
1912
2095
|
}
|
|
1913
|
-
return
|
|
2096
|
+
return delivered;
|
|
1914
2097
|
}
|
|
1915
2098
|
default:
|
|
1916
2099
|
throw new Error(`unknown knodin operation: ${String(operation)}`);
|
package/docs/CLI.md
CHANGED
|
@@ -66,6 +66,35 @@ example repository existence, mutually exclusive configuration modes, and
|
|
|
66
66
|
graph-query target rules), but syntax cannot reach a handler unless the shared
|
|
67
67
|
declarative model accepts it first.
|
|
68
68
|
|
|
69
|
+
## Task discovery and investigation
|
|
70
|
+
|
|
71
|
+
`context` supports explicit implementation discovery and selected-symbol
|
|
72
|
+
investigation in addition to ordinary repository orientation:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
knodin context "find code that persists an order" --implementations --limit 10 --json
|
|
76
|
+
knodin context "rename persist" --symbol persist --file src/storage.ts --json
|
|
77
|
+
knodin context "rename persist" --symbol persist --identity '<returned-identity>' --limit 20 --depth 3 --json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`--implementations` ranks functions, methods, constructors, and classes and
|
|
81
|
+
returns identity/file-specific followups. It chooses no target and intentionally
|
|
82
|
+
excludes types, interfaces, and constants; use ordinary `search` to find those.
|
|
83
|
+
Use `--offset` and `--limit` to page through candidates. Incomplete semantic
|
|
84
|
+
coverage remains visible even when an exact-name candidate can be returned.
|
|
85
|
+
|
|
86
|
+
`--symbol` composes definition/source, rename sites, callers, upstream impact,
|
|
87
|
+
tests, and file importers. A duplicated name requires `--file` or `--identity`;
|
|
88
|
+
an unknown target is reported separately from a symbol with no callers. Bounded
|
|
89
|
+
or unavailable sections include followups and unresolved work. This is static
|
|
90
|
+
evidence to inspect, not a complete edit plan or proof of test coverage.
|
|
91
|
+
`--implementations` and `--symbol` are mutually exclusive.
|
|
92
|
+
|
|
93
|
+
The single MCP gateway uses `operation: "context"`, `task`, and
|
|
94
|
+
`contextMode: "implementation"` for discovery. For investigation, supply
|
|
95
|
+
`symbol` with optional `file` or `identity` and omit implementation mode. Both
|
|
96
|
+
surfaces return compatible identity/file-specific followups.
|
|
97
|
+
|
|
69
98
|
Optional Claude lifecycle integration is explicit and user-global:
|
|
70
99
|
|
|
71
100
|
```bash
|
|
@@ -128,6 +157,13 @@ exact local source and stable continuations. A returned evidence handle plus
|
|
|
128
157
|
current-file match; all other baseline states return the required source. See
|
|
129
158
|
`docs/PROGRESSIVE-EVIDENCE.md` for the protocol and limitations.
|
|
130
159
|
|
|
160
|
+
Use `knodin evidence evidence --files src/widget.ts,src/caller.ts --items 20
|
|
161
|
+
--json` for a multi-file bundle, or `expand` to include indexed direct file
|
|
162
|
+
dependencies. A signed `--continuation` recovers remaining source; completed
|
|
163
|
+
per-file receipts can later be passed through `--already-present` to reuse
|
|
164
|
+
unchanged content. Manifest entries have a null receipt until complete delivery,
|
|
165
|
+
and the manifest itself counts toward the byte/token budget.
|
|
166
|
+
|
|
131
167
|
`knodin review` returns separately itemized graph impact, test gaps, structural
|
|
132
168
|
centrality, churn, co-change, and coupling evidence. Git history has explicit
|
|
133
169
|
commit/file/time bounds and truthful unavailable or truncation states; see
|
|
@@ -157,3 +193,59 @@ not Node's backtracking regular-expression engine. Backreferences and lookaround
|
|
|
157
193
|
are rejected because they cannot retain RE2's linear-time guarantee. Route
|
|
158
194
|
matching treats application path text literally and recognizes only complete
|
|
159
195
|
Express/NestJS-style `:name` or `{name}` path segments as parameters.
|
|
196
|
+
|
|
197
|
+
### Local flow-analysis budgets
|
|
198
|
+
|
|
199
|
+
`knodin query flow_analysis <symbol> --bytes 1536 --tokens 384 --json`
|
|
200
|
+
requires an effective budget of at least 1536 serialized bytes (384 estimated
|
|
201
|
+
tokens). The effective ceiling is the stricter of `--bytes` and `--tokens × 4`;
|
|
202
|
+
a smaller ceiling is rejected, never automatically enlarged. Required evidence
|
|
203
|
+
and qualifications may still require more than this minimum.
|
|
204
|
+
|
|
205
|
+
Flow budget metadata and drill-down continuation guidance use operation
|
|
206
|
+
`query:flow_analysis`, consistently with MCP. This guidance is not a resumable
|
|
207
|
+
cursor: repeat the query with a narrower selector or an explicitly larger budget.
|
|
208
|
+
Other commands retain their existing operation names and budget policies.
|
|
209
|
+
|
|
210
|
+
### Typed Apex class consumers
|
|
211
|
+
|
|
212
|
+
`knodin query inheritors_of Base --json` aggregates same-name nominal inheritance
|
|
213
|
+
across languages; it does not select one global target. `inheritanceQuery` preserves
|
|
214
|
+
the query mode, partial/truncation flags, and Apex resolution status. Apex rows
|
|
215
|
+
require independently verified typed endpoints; an ambiguous or missing Apex
|
|
216
|
+
capability can leave qualified non-Apex results. Apex `--file` or `--identity`
|
|
217
|
+
selectors establish a selected typed target; non-Apex inheritance retains its
|
|
218
|
+
legacy name matching rather than promising exact identity resolution. Budgets
|
|
219
|
+
that cannot preserve this qualification fail closed.
|
|
220
|
+
The tuple describes engine analysis; `responseBudget.truncated` separately reports
|
|
221
|
+
wire-size trimming. Read both before interpreting a bounded result as complete.
|
|
222
|
+
|
|
223
|
+
`knodin query class_consumers GlobalWrappers --test-scope all --limit 100 --json`
|
|
224
|
+
returns direct typed source sites for an Apex class or interface, separately from method `callers_of` and Java
|
|
225
|
+
configuration `consumers_of`. `count` is total observed matching sites,
|
|
226
|
+
`consumerFileCount` is distinct files, and `returnedCount` is this page's sites.
|
|
227
|
+
`files` contains aggregates only for files represented on this page.
|
|
228
|
+
`targetKind` identifies the resolved declaration as `class` or `interface` and is
|
|
229
|
+
preserved on every page. Interface consumers cover supported type-position roles,
|
|
230
|
+
not constructors, static-field reads, `implements` relationships, or inferred
|
|
231
|
+
runtime dispatch. Query implementation relationships separately.
|
|
232
|
+
|
|
233
|
+
Use `--test-scope test|production|all` (default `all`), a canonical repository-relative
|
|
234
|
+
`--path` prefix, and stable `--identity`/`--file` target selectors when needed.
|
|
235
|
+
`--limit` accepts 1–1000 sites; `--bytes`, `--tokens`, and `--items` bound the complete
|
|
236
|
+
response. Repeat the same target and filters with `--continuation '<returned token>'`;
|
|
237
|
+
page budgets may change. Tokens authenticate the last delivered site and the graph,
|
|
238
|
+
coverage, and typed-facts snapshot. Forged, mismatched, or stale tokens fail closed.
|
|
239
|
+
A minimum complete site plus qualifications must fit; source fields are never shortened.
|
|
240
|
+
|
|
241
|
+
Sites retain exact spelling, role, source-owner identity when known, and exclusive
|
|
242
|
+
end positions: one-based lines/UTF-16 columns and zero-based UTF-8 byte offsets.
|
|
243
|
+
The target's own file is excluded. Nine supported syntax roles do not cover every
|
|
244
|
+
Apex use, metadata relationship, or runtime path. Read `classConsumerAnalysis` and
|
|
245
|
+
`indexCoverage` before interpreting a negative; missing/invalid descriptors and
|
|
246
|
+
unresolved uses remain qualified, not proof that a class is unused.
|
|
247
|
+
An indexed retained interface declaration without a safely matching interface
|
|
248
|
+
symbol returns `CLASS_CONSUMER_TARGET_CAPABILITY_MISSING`, `targetResolution: capability-missing`,
|
|
249
|
+
and a nonzero exit—not a successful zero or a fabricated identity. Follow the
|
|
250
|
+
reported repair guidance within the declared index scope. An unknown name remains
|
|
251
|
+
`not-found`; this qualification does not certify interfaces outside indexed coverage.
|