knodin 0.8.2 → 0.8.4

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.
@@ -0,0 +1,163 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ const CONFIG = ".knodin/telemetry-config.json";
5
+ const EVENTS = ".knodin/session-events.jsonl";
6
+ const KEY = ".knodin/telemetry-key";
7
+ function target(repo, relative) {
8
+ const root = fs.realpathSync(repo);
9
+ const value = path.resolve(root, relative);
10
+ if (!value.startsWith(`${root}${path.sep}`))
11
+ throw new Error("knodin telemetry path escaped repository");
12
+ let candidate = root;
13
+ for (const segment of path.relative(root, value).split(path.sep)) {
14
+ candidate = path.join(candidate, segment);
15
+ try {
16
+ if (fs.lstatSync(candidate).isSymbolicLink())
17
+ throw new Error("knodin telemetry refuses symlinks");
18
+ }
19
+ catch (error) {
20
+ if (error.code !== "ENOENT")
21
+ throw error;
22
+ }
23
+ }
24
+ return value;
25
+ }
26
+ function atomic(filePath, value) {
27
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
28
+ const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
29
+ fs.writeFileSync(temporary, value, { mode: 0o600, flag: "wx" });
30
+ fs.renameSync(temporary, filePath);
31
+ }
32
+ function enabled(repo) {
33
+ try {
34
+ return JSON.parse(fs.readFileSync(target(repo, CONFIG), "utf8")).enabled === true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ export function enableSessionTelemetry(repo) {
41
+ atomic(target(repo, CONFIG), `${JSON.stringify({ schemaVersion: 1, enabled: true })}\n`);
42
+ const key = target(repo, KEY);
43
+ if (!fs.existsSync(key))
44
+ atomic(key, crypto.randomBytes(32).toString("hex"));
45
+ return { schemaVersion: 1, enabled: true, localOnly: true };
46
+ }
47
+ export function disableSessionTelemetry(repo) {
48
+ atomic(target(repo, CONFIG), `${JSON.stringify({ schemaVersion: 1, enabled: false })}\n`);
49
+ return { schemaVersion: 1, enabled: false, retained: readSessionEvents(repo).length };
50
+ }
51
+ function sessionHash(repo, sessionId) {
52
+ const secret = fs.readFileSync(target(repo, KEY), "utf8").trim();
53
+ return `session_${crypto.createHmac("sha256", secret).update(sessionId).digest("hex").slice(0, 20)}`;
54
+ }
55
+ export function appendSessionEvent(repo, input) {
56
+ if (!enabled(repo))
57
+ return false;
58
+ if (!input.sessionId || input.sessionId.length > 256)
59
+ return false;
60
+ const record = {
61
+ schemaVersion: 1,
62
+ event: input.event,
63
+ session: sessionHash(repo, input.sessionId),
64
+ at: input.at ?? new Date().toISOString(),
65
+ client: input.client ?? "claude",
66
+ ...(input.context ? { context: input.context } : {}),
67
+ ...(input.toolCategory ? { toolCategory: input.toolCategory } : {}),
68
+ ...(input.operation && /^[a-z][a-z0-9_-]{0,63}$/.test(input.operation)
69
+ ? { operation: input.operation }
70
+ : {}),
71
+ ...(input.freshness ? { freshness: input.freshness } : {}),
72
+ ...(typeof input.latencyMs === "number" ? { latencyMs: input.latencyMs } : {}),
73
+ ...(typeof input.cacheHit === "boolean" ? { cacheHit: input.cacheHit } : {}),
74
+ };
75
+ const filePath = target(repo, EVENTS);
76
+ fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`, { encoding: "utf8", mode: 0o600 });
77
+ return true;
78
+ }
79
+ export function readSessionEvents(repo, retentionDays, now = new Date()) {
80
+ const filePath = target(repo, EVENTS);
81
+ if (!fs.existsSync(filePath))
82
+ return [];
83
+ const cutoff = retentionDays === undefined ? undefined : now.getTime() - retentionDays * 24 * 60 * 60 * 1000;
84
+ return fs
85
+ .readFileSync(filePath, "utf8")
86
+ .split(/\r?\n/)
87
+ .filter(Boolean)
88
+ .flatMap((line) => {
89
+ try {
90
+ const value = JSON.parse(line);
91
+ if (value.schemaVersion !== 1 || typeof value.session !== "string")
92
+ return [];
93
+ if (cutoff !== undefined) {
94
+ const timestamp = typeof value.at === "string" ? Date.parse(value.at) : Number.NaN;
95
+ if (!Number.isFinite(timestamp) || timestamp < cutoff)
96
+ return [];
97
+ }
98
+ return [value];
99
+ }
100
+ catch {
101
+ return [];
102
+ }
103
+ });
104
+ }
105
+ export function summarizeAdoption(events) {
106
+ const sessions = new Set(events.map(({ session }) => session));
107
+ const evidenceOperations = new Set(["context", "explain", "review", "map", "search", "query"]);
108
+ const knodinSessions = new Set(events
109
+ .filter(({ event, toolCategory, operation }) => event === "tool_success" &&
110
+ toolCategory === "knodin" &&
111
+ operation !== undefined &&
112
+ evidenceOperations.has(operation))
113
+ .map(({ session }) => session));
114
+ let fallbackTraversalAfterKnodin = 0;
115
+ let evidenceBeforeEdit = 0;
116
+ const seenKnodin = new Set();
117
+ for (const event of events) {
118
+ if (event.event === "turn_start")
119
+ seenKnodin.delete(event.session);
120
+ if (event.toolCategory === "knodin" &&
121
+ event.event === "tool_success" &&
122
+ event.operation !== undefined &&
123
+ evidenceOperations.has(event.operation))
124
+ seenKnodin.add(event.session);
125
+ if (event.event === "tool_success" &&
126
+ event.toolCategory === "manual_traversal" &&
127
+ seenKnodin.has(event.session))
128
+ fallbackTraversalAfterKnodin += 1;
129
+ if (event.event === "tool_success" &&
130
+ event.toolCategory === "edit" &&
131
+ seenKnodin.has(event.session))
132
+ evidenceBeforeEdit += 1;
133
+ if (event.event === "turn_end")
134
+ seenKnodin.delete(event.session);
135
+ }
136
+ return {
137
+ sessions: sessions.size,
138
+ contextDelivered: events.filter(({ event, context }) => event === "session_start" && context === "delivered").length,
139
+ contextDegraded: events.filter(({ event, context }) => event === "session_start" && context === "degraded").length,
140
+ knodinSessions: knodinSessions.size,
141
+ turns: events.filter(({ event }) => event === "turn_start").length,
142
+ fallbackTraversalAfterKnodin,
143
+ evidenceBeforeEdit,
144
+ correctiveRoundTrips: null,
145
+ };
146
+ }
147
+ export function sessionTelemetryStatus(repo, retentionDays) {
148
+ const records = readSessionEvents(repo, retentionDays);
149
+ return {
150
+ schemaVersion: 1,
151
+ enabled: enabled(repo),
152
+ localOnly: true,
153
+ records: records.length,
154
+ adoption: summarizeAdoption(records),
155
+ };
156
+ }
157
+ export function clearSessionTelemetry(repo) {
158
+ const filePath = target(repo, EVENTS);
159
+ const records = readSessionEvents(repo).length;
160
+ const bytes = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0;
161
+ fs.rmSync(filePath, { force: true });
162
+ return { removed: bytes > 0, records, bytes };
163
+ }
@@ -116,6 +116,7 @@ const QUERY_PATTERNS = [
116
116
  "batch_outline",
117
117
  "project_overview",
118
118
  "shortest_path",
119
+ "cross_substrate_path",
119
120
  "impact",
120
121
  "dead_code",
121
122
  "large_functions",
@@ -127,6 +128,7 @@ const QUERY_PATTERNS = [
127
128
  "traverse",
128
129
  "feature_path",
129
130
  "flow_analysis",
131
+ "resource_reachability",
130
132
  "knowledge_gaps",
131
133
  "surprising_connections",
132
134
  "suggested_questions",
@@ -227,15 +229,15 @@ function buildDocumentedKnodinTools() {
227
229
  },
228
230
  toIdentity: {
229
231
  type: "string",
230
- description: "shortest_path destination identity selector.",
232
+ description: "shortest_path/cross_substrate_path destination identity selector.",
231
233
  },
232
234
  toFile: {
233
235
  type: "string",
234
- description: "shortest_path destination file selector.",
236
+ description: "shortest_path/cross_substrate_path destination file selector.",
235
237
  },
236
238
  toKind: {
237
239
  type: "string",
238
- description: "shortest_path destination kind selector.",
240
+ description: "shortest_path/cross_substrate_path destination kind selector.",
239
241
  },
240
242
  pattern: {
241
243
  type: "string",
@@ -243,7 +245,7 @@ function buildDocumentedKnodinTools() {
243
245
  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. " +
244
246
  "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. " +
245
247
  "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. " +
246
- "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, impact, dead_code, large_functions, large_files, rename_preview, flows, flow_of, stats, traverse, knowledge_gaps, community, federated_repos). 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. `detailLevel` 'minimal' omits per-edge coupling; 'standard' includes it. 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.",
248
+ "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.",
247
249
  },
248
250
  depth: {
249
251
  type: "number",
@@ -292,7 +294,7 @@ function buildDocumentedKnodinTools() {
292
294
  },
293
295
  to: {
294
296
  type: "string",
295
- description: "query shortest_path: destination symbol (start symbol goes in `symbol`). query rename_preview: the new name (old name goes in `symbol`). Read-only by default (returns edit sites + a unified diff); pass `apply: true` to write the rename to disk.",
297
+ description: "query shortest_path/cross_substrate_path: destination symbol (start symbol goes in `symbol`). query rename_preview: the new name (old name goes in `symbol`). Read-only by default (returns edit sites + a unified diff); pass `apply: true` to write the rename to disk.",
296
298
  },
297
299
  apply: {
298
300
  type: "boolean",
@@ -1569,8 +1571,8 @@ async function dispatchKnodinTool(args) {
1569
1571
  const repoWide = REPO_WIDE_QUERY_PATTERNS.includes(pattern);
1570
1572
  if (!symbol && !repoWide)
1571
1573
  throw new Error(`knodin query ${pattern} requires \`symbol\``);
1572
- if (pattern === "shortest_path" && !to)
1573
- throw new Error("knodin query shortest_path requires `to`");
1574
+ if ((pattern === "shortest_path" || pattern === "cross_substrate_path") && !to)
1575
+ throw new Error(`knodin query ${pattern} requires \`to\``);
1574
1576
  if (pattern === "rename_preview" && !to)
1575
1577
  throw new Error("knodin query rename_preview requires `symbol` and `to`");
1576
1578
  const queryHealth = GRAPH_INDEPENDENT_QUERY_PATTERNS.has(pattern)
@@ -1602,6 +1604,9 @@ async function dispatchKnodinTool(args) {
1602
1604
  direction: pattern === "traverse" ? direction : undefined,
1603
1605
  includeDataFlow: pattern === "traverse" ? includeDataFlow : undefined,
1604
1606
  flowVariable: pattern === "flow_analysis" ? flowVariable : undefined,
1607
+ resourceOffset: pattern === "resource_reachability" ? offset : undefined,
1608
+ resourceMaxBytes: pattern === "resource_reachability" ? byteBudget : undefined,
1609
+ resourceMaxTokens: pattern === "resource_reachability" ? tokenBudget : undefined,
1605
1610
  architectureFacets,
1606
1611
  topN,
1607
1612
  sort,
@@ -16,10 +16,11 @@ action-specific response semantics fail closed when a required signal is absent.
16
16
  - **Value:** less manual context assembly and fewer missed dependencies. The
17
17
  paired task method and its bounded results are checked in under
18
18
  [`benchmarks/evaluations/c93-engineering-outcomes/`](../benchmarks/evaluations/c93-engineering-outcomes/).
19
- - **ROI:** fewer corrective agent round trips without hosted-service, account,
20
- authentication, or source-egress overhead. C93 reports measured local task
21
- outcomes and explicitly leaves billed cost and token counts unavailable when
22
- the harness cannot observe them.
19
+ - **ROI:** the intended mechanism is fewer corrective agent round trips without
20
+ hosted-service, account, authentication, or source-egress overhead. C93's
21
+ accepted two-task replay measured no round-trip benefit; both arms needed one
22
+ correction. It therefore authorizes no productivity claim and leaves billed
23
+ cost and unobserved token counts unavailable.
23
24
  - **Tomorrow:** run `knodin init`, inspect `knodin status`, then use the existing
24
25
  checkout through the CLI or the single `knodin.knodin` MCP gateway. The
25
26
  installation and client readiness evidence remains bounded by C91 below.
@@ -35,6 +36,13 @@ preserving its stable identity and evidence provenance. Stale, unavailable, or
35
36
  ambiguous graph evidence must remain explicit and cannot be promoted to a
36
37
  confident failure diagnosis, impact claim, or runtime-causality proof.
37
38
 
39
+ `ExplainResult.callers[].kind` and `ExplainResult.omissions[]` are governed by
40
+ the existing explain mapping: relationship kinds require source provenance and
41
+ stable resolved endpoints, while omissions require an exact source location and
42
+ must never be promoted into a graph edge. C99 exercises these added fields
43
+ without adding a gateway operation or discriminator, so the C95 applicability
44
+ matrix is unchanged.
45
+
38
46
  The C95 fixtures run in a linked non-default worktree. A child harness installs
39
47
  guards before importing the production dispatcher and observes global `fetch`,
40
48
  HTTP/HTTPS request and get, TCP/TLS connection, callback and promise DNS
@@ -46,6 +54,39 @@ credential forwarding, source bytes at network/subprocess boundaries, and
46
54
  native-addon loads must remain zero. Local subprocess execution is counted and
47
55
  permitted only when those observed payload checks remain clean.
48
56
 
57
+ ## C101 composition closure
58
+
59
+ C101 composes four independently bounded decisions without adding an MCP tool
60
+ or operation:
61
+
62
+ - C97 retained lazy semantic residency in one local process. It does **not**
63
+ authorize an idle-RSS-reclamation claim.
64
+ - C98 reports literal TS/JS source-to-sink paths as static heuristics with
65
+ coverage, omissions, freshness, budgets, and continuation. Truncation cannot
66
+ become a complete-result claim.
67
+ - C99 persists only uniquely source-resolved Inversify/tsyringe relationships;
68
+ ambiguous, dynamic, factory, alias, conditional, and reflective wiring stays
69
+ omission evidence rather than an edge.
70
+ - C100 proves one Salesforce Flow action to one uniquely resolved Apex
71
+ `@InvocableMethod`. It does not prove Terraform, dbt, arbitrary Salesforce
72
+ metadata, or general cross-substrate traversal.
73
+
74
+ `npm run verify:c101` source-binds those decisions, checks the CLI and one-tool
75
+ MCP query declarations, and rejects eight adversarial mutations of actual
76
+ production responses. Stale resource/path evidence and heuristic-to-exact
77
+ promotion fail; a truncated result cannot be presented as complete; and a DI
78
+ ambiguity, missing source witness, or path omission cannot become a supported
79
+ edge.
80
+
81
+ ## Repository stewardship
82
+
83
+ An authoritative pull request starts the full test matrix and Sonar analysis,
84
+ so it is a long, resource-intensive operation. Assistants must not automatically
85
+ open one PR per item. Dependency-compatible, coherently scoped commits should
86
+ normally be stacked into one reasonably sized shared PR; unrelated work must
87
+ not be bundled merely to make the PR larger. The user or approved batch decides
88
+ when that review unit is ready.
89
+
49
90
  ## Known bounds
50
91
 
51
92
  This replay proves the checked operation/action fixtures, not all repositories,
@@ -53,7 +94,8 @@ languages, frameworks, dynamic runtime behavior, clients, or deployment
53
94
  environments. Static relationships and failure correlations are candidates,
54
95
  not runtime-causality proof. Budgets can truncate results; indexes can be stale
55
96
  or unavailable; names can be ambiguous; generated code, dynamic dispatch,
56
- framework wiring, and language coverage can leave relationships unresolved.
97
+ framework wiring outside C99's declared Inversify/tsyringe patterns, and
98
+ language coverage can leave relationships unresolved.
57
99
 
58
100
  C91 remains parked pending native client, runtime-manager, Homebrew, and
59
101
  Artifactory certification evidence. C94 remains blocked on C91 and therefore
package/docs/CLI.md CHANGED
@@ -32,6 +32,33 @@ example repository existence, mutually exclusive configuration modes, and
32
32
  graph-query target rules), but syntax cannot reach a handler unless the shared
33
33
  declarative model accepts it first.
34
34
 
35
+ Optional Claude lifecycle integration is explicit and user-global:
36
+
37
+ ```bash
38
+ knodin agent-hooks install --client claude --dry-run
39
+ knodin agent-hooks install --client claude
40
+ knodin agent-hooks status --client claude
41
+ knodin agent-hooks uninstall --client claude
42
+ ```
43
+
44
+ Installation atomically merges knodin-owned `SessionStart`, turn, tool, and
45
+ session-end handlers into the user's Claude settings. It preserves unrelated
46
+ settings and hooks, including aidev-track, and uninstall removes only exact
47
+ knodin entries. SessionStart emits at most 600 tokens, 8 KiB, and 12 bounded
48
+ orientation items. Hooks fail open and no-op outside initialized repositories.
49
+ The internal `agent-event` command is machine-facing and consumes bounded JSON
50
+ from stdin; it is not a general event-ingestion API.
51
+
52
+ `knodin query resource_reachability --limit 100` runs cached, on-demand static
53
+ TS/JS source-to-sink analysis. It covers literal `process.env` and
54
+ `fs.readFileSync` reads reaching `console.log`, `fetch`, or `db.query` through
55
+ bounded assignment, argument, and return handoff. Results include ordered source
56
+ evidence, stable path identities, registry coverage, omissions, freshness, and
57
+ hard truncation/continuation metadata. Depth is fixed at 6 and paths at 100;
58
+ dynamic names, reflection, computed aliases, and unsupported languages are
59
+ reported rather than guessed. This is a static heuristic, not runtime reachability
60
+ or an exploitability verdict.
61
+
35
62
  For support evidence, `knodin diagnostics enable|status|preview|archive|inspect|clear|disable`
36
63
  manages an explicit local failure journal and redacted gzip JSON bundles.
37
64
  Collection does not upload data. See `docs/DIAGNOSTICS.md` for retention,
@@ -51,6 +51,16 @@ the evaluation's competitor limitations. The active
51
51
  [competitive roadmap](../roadmap/competitive-roadmap.md) links each claim to
52
52
  its acceptance criteria and replay state.
53
53
 
54
+ The current positioning lead is the checked behavioral contract, not the raw
55
+ capability count. C98 adds a bounded TS/JS resource-reachability heuristic; C99
56
+ adds only uniquely source-resolved Inversify/tsyringe relationships; C100 proves
57
+ one Salesforce Flow action to one Apex `@InvocableMethod`; and C97 retains the
58
+ one-process semantic design without claiming idle RSS reclamation. C101 binds
59
+ those decisions and rejects stale, truncated, ambiguous, or omission-only
60
+ results when promoted into complete or exact claims. None of these fixtures
61
+ supports general runtime causality, arbitrary cross-substrate paths, broad DI
62
+ framework coverage, or universal competitor superiority.
63
+
54
64
  ## The quartet: knodin vs its three direct progenitors
55
65
 
56
66
  knodin exists to fuse three tools this project already used side by side
package/docs/DEMO.md ADDED
@@ -0,0 +1,49 @@
1
+ # Bounded product walkthrough
2
+
3
+ This is the maintained positioning walkthrough, not C94's pending certified
4
+ five-minute client demonstration. C94 remains parked behind native installation
5
+ and client evidence; this document must not be cited as completing it.
6
+
7
+ ## Value
8
+
9
+ On an existing checkout, initialize and orient before assembling context by
10
+ hand:
11
+
12
+ ```bash
13
+ knodin init
14
+ knodin status --deep
15
+ knodin context "change the review submission path"
16
+ knodin query impact submit --direction upstream
17
+ ```
18
+
19
+ The intended value is less manual context assembly and fewer missed
20
+ dependencies. C100 demonstrates one narrow improvement: a Salesforce Flow
21
+ action can resolve to the exact local Apex `@InvocableMethod`, rather than only
22
+ its class file. It does not demonstrate arbitrary cross-substrate analysis.
23
+
24
+ ## ROI
25
+
26
+ Use the same local CLI or one MCP gateway against the checkout already on disk;
27
+ no hosted index, account, authentication, or source-code egress is required.
28
+ Fewer corrective round trips are an intended mechanism, not a measured product
29
+ claim: C93's accepted two-task replay found no round-trip difference and both
30
+ arms required one correction.
31
+
32
+ ## Tomorrow
33
+
34
+ After `knodin init`, use `context`, `explain`, structured `query`, and `review`
35
+ through the CLI or the single operation-routed MCP tool. If status reports stale
36
+ or damaged state, repair it before relying on graph evidence.
37
+
38
+ ## Secret sauce and bounds
39
+
40
+ Stable identity, exact source evidence, explicit freshness, and truthful
41
+ budgets compose behind the compact local surface. C101 mutates actual production
42
+ responses for resource reachability, TypeScript DI, and one Flow-to-Apex path;
43
+ stale, truncated, ambiguous, or evidence-free variants must fail the contract
44
+ oracle. C97 separately retains one-process semantic residency and authorizes no
45
+ idle-RSS-reclamation claim.
46
+
47
+ These checked fixtures do not prove runtime causality, broad DI framework
48
+ coverage, arbitrary Salesforce/Terraform/dbt paths, every client or platform,
49
+ or superiority over competitors.
@@ -205,6 +205,17 @@ idempotent. Local fixtures use zero hosted spend, no credentials, and no source
205
205
  egress. Diagnostic archives remain governed by the privacy-safe preview and
206
206
  archive contract in [`DIAGNOSTICS.md`](DIAGNOSTICS.md).
207
207
 
208
+ Claude users may explicitly install one user-global lifecycle adapter after
209
+ previewing its settings merge:
210
+
211
+ ```bash
212
+ knodin agent-hooks install --client claude --dry-run
213
+ knodin agent-hooks install --client claude
214
+ ```
215
+
216
+ This is independent of `knodin init` and telemetry persistence. Remove only
217
+ knodin-owned entries with `knodin agent-hooks uninstall --client claude`.
218
+
208
219
  Upgrade with the manager that owns the executable:
209
220
 
210
221
  ```bash
package/docs/MCP.md CHANGED
@@ -4,6 +4,11 @@ knodin exposes exactly one MCP tool named `knodin`. Capabilities such as
4
4
  context, explain, review, search, docs, doctor, repositories, and systems are
5
5
  operations of that gateway, not separate top-level tools.
6
6
 
7
+ Use `operation: "query", pattern: "resource_reachability"` for the same bounded,
8
+ cached, repo-wide TS/JS analysis as the CLI. It requires no symbol and retains
9
+ the gateway's freshness refusal and response budgets; returned paths remain
10
+ source-evidenced static heuristics with explicit coverage and omissions.
11
+
7
12
  Use `operation: "evidence"` for deterministic `locate`, `outline`, `evidence`,
8
13
  and `expand` source delivery with a verified complete-file hash handshake and
9
14
  recoverable hard-budget continuations. See `docs/PROGRESSIVE-EVIDENCE.md`.
package/docs/TELEMETRY.md CHANGED
@@ -25,13 +25,31 @@ knodin telemetry export
25
25
  knodin telemetry clear
26
26
  ```
27
27
 
28
+ Claude adoption evidence is an independent repository-local opt-in:
29
+
30
+ ```text
31
+ knodin telemetry enable
32
+ knodin telemetry disable
33
+ ```
34
+
35
+ When enabled and the optional Claude hooks are installed, knodin records HMAC'd
36
+ session identity, lifecycle event kind, turn boundaries, coarse tool category,
37
+ supported knodin operation, freshness, context delivery, cache state, and
38
+ latency. It never persists prompts, transcript paths, source, raw tool input or
39
+ output, shell command text, usernames, or absolute paths. Disable stops new
40
+ session events without deleting retained history; `clear` removes both
41
+ operation and session event stores.
42
+
28
43
  `--retention-days 1..3650` changes the read/persistence window. `--input` and
29
44
  `--output` accept only repository-contained, non-symlink paths. `clear` is the
30
45
  only command that deletes the private JSONL file; report and export never do.
31
46
 
32
47
  The default report is `.knodin/telemetry-report.html`. It includes summary,
33
48
  operation, time-series, private repository, indexing-cost, latency, freshness,
34
- and fidelity views with light/dark styling. The default machine-readable export
49
+ fidelity, session adoption, evidence-before-edit, and observed fallback-traversal
50
+ views with light/dark styling. Ordinary hooks cannot establish why a follow-up
51
+ occurred, so corrective round trips and task success remain unavailable unless
52
+ supplied by a controlled outcome replay. The default machine-readable export
35
53
  is `.knodin/telemetry-export.json` and labels measured, modeled, and unknown
36
54
  baselines explicitly. Neither artifact needs a daemon, hosted service,
37
55
  authentication, or source egress.
@@ -0,0 +1,47 @@
1
+ # knodin 0.8.3
2
+
3
+ This release strengthens Knodin's source-evidenced graph across repository
4
+ lifecycle, cross-substrate analysis, and bounded organization-scale workflows.
5
+
6
+ - Adds bounded repository-wide resource reachability for source-proven
7
+ environment, file, fetch, console, and database paths. Results retain stable
8
+ identities, exact evidence, explicit unsupported cases, freshness, response
9
+ budgets, and deterministic continuation.
10
+ - Resolves source-proven TypeScript dependency-injection wiring for supported
11
+ Inversify and tsyringe forms. Framework and registry scoping, ambiguity,
12
+ conditional wiring, incremental refresh, and removal are represented without
13
+ guessing across containers or frameworks.
14
+ - Proves exact Salesforce Flow-to-Apex paths from Flow action metadata to unique
15
+ `@InvocableMethod` targets, including targeted Apex refresh, source evidence,
16
+ omissions, freshness, and bounded responses.
17
+ - Makes repair converge truthfully across schema migration, empty repositories,
18
+ final verification failure, and future-schema refusal. Scoped repairs avoid
19
+ unrelated TypeScript DI reads while still rebuilding affected DI evidence.
20
+ - Strengthens the public behavioral contract and positioning with replayed
21
+ production CLI, MCP, and engine responses. C97-C101 evidence is independently
22
+ replayable and source-bound; failed performance experiments remain recorded
23
+ as limitations rather than superiority claims.
24
+ - Prevents stale package and barrel-resolution caches, avoids caching transient
25
+ filesystem failures, and keeps independent engine instances from closing
26
+ resources still owned by another instance.
27
+ - Reuses a bounded, generation-scoped resource corpus so warm reachability
28
+ queries do not reread every indexed source file while still failing closed on
29
+ disk drift.
30
+ - Bounds cross-substrate evidence consistently with the outer response budget,
31
+ fixes member-call reachability false positives, and makes evidence verifiers
32
+ recompute their claim-bearing gates instead of trusting stored booleans.
33
+ - Removes duplicated progress-worker and response-budget machinery, updates the
34
+ audited development dependency set, and fixes lifecycle evaluation owners so
35
+ the complete test process terminates.
36
+ - Records that pull requests are intentionally expensive because Sonar and the
37
+ complete repository gates run on each review stream; compatible work should
38
+ be stacked into one coherent PR rather than opened automatically as many small
39
+ PRs.
40
+
41
+ The one-process semantic-residency evaluation remains a retained no-go: neither
42
+ in-process ONNX disposal, worker threads, quantized weights, nor vector/cache
43
+ eviction met the repeatable memory gate without violating the local one-process
44
+ product boundary. Knodin retains lazy loading and bounded caches rather than
45
+ claiming unsupported reclamation.
46
+
47
+ This remains an ordinary 0.x release, not a dogfood-only build and not GA.
@@ -0,0 +1,22 @@
1
+ # knodin 0.8.4
2
+
3
+ This release adds optional, bounded coding-session orientation and local
4
+ adoption evidence while preserving knodin's freshness and privacy boundaries.
5
+
6
+ - Adds an explicit, idempotent Claude hook installer for compact SessionStart
7
+ context. Injection is limited to 600 tokens, 8 KiB, and 12 items, reports its
8
+ bounds, rejects stale cached evidence, and fails open outside initialized
9
+ repositories.
10
+ - Adds opt-in, repository-local session telemetry and an adoption dashboard for
11
+ delivered context, successful graph-evidence operations, fallback traversal,
12
+ and evidence-before-edit observations.
13
+ - Persists only coarse allowlisted metadata and locally keyed session hashes;
14
+ prompts, source, raw tool input, commands, paths, and unhashed session IDs are
15
+ not retained.
16
+ - Keeps corrective round trips explicitly unavailable because ordinary hook
17
+ events cannot establish why an agent made a follow-up call or prove a causal
18
+ productivity improvement.
19
+ - Adds C102's preregistered paired-evaluation design so future outcome claims
20
+ require replay evidence rather than being inferred from adoption counters.
21
+
22
+ This remains an ordinary 0.x release, not a dogfood-only build and not GA.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knodin",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "description": "knodin — source-evidenced local code intelligence with known bounds. Stable identity, fresh evidence, truthful budgets, and recoverable bounded views.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,6 +25,7 @@
25
25
  "docs/DOCTOR-AND-UPDATES.md",
26
26
  "docs/DIAGNOSTICS.md",
27
27
  "docs/BEHAVIORAL-CONTRACT.md",
28
+ "docs/DEMO.md",
28
29
  "docs/COMPARISON.md",
29
30
  "docs/COMPETITIVE-LANDSCAPE-2026-08.md",
30
31
  "docs/INDEXING-POLICY-AND-PROVENANCE.md",
@@ -57,6 +58,8 @@
57
58
  "docs/releases/0.7.5.md",
58
59
  "docs/releases/0.8.0.md",
59
60
  "docs/releases/0.8.2.md",
61
+ "docs/releases/0.8.3.md",
62
+ "docs/releases/0.8.4.md",
60
63
  "docs/assets/knodin-favicon.svg",
61
64
  "docs/SYSTEMS-AND-RELATIONSHIPS.md",
62
65
  "docs/TELEMETRY.md",
@@ -114,6 +117,16 @@
114
117
  "bench:c93": "tsx benchmarks/evaluations/c93-engineering-outcomes/runner.ts",
115
118
  "verify:c93": "tsx scripts/verify-c93.ts",
116
119
  "verify:c95": "tsx scripts/verify-c95.ts && vitest run src/__tests__/unit/behavior-contract.spec.ts src/__tests__/unit/failure-diagnosis.spec.ts src/__tests__/unit/impact.spec.ts src/__tests__/unit/freshness.spec.ts",
120
+ "bench:c97": "tsx benchmarks/evaluations/c97-one-process-semantic-residency/runner.ts",
121
+ "verify:c97": "tsx scripts/verify-c97.ts",
122
+ "bench:c98": "tsx benchmarks/evaluations/c98-resource-reachability/runner.ts",
123
+ "verify:c98": "tsx scripts/verify-c98.ts && vitest run src/__tests__/unit/resource-reachability.spec.ts",
124
+ "bench:c99": "tsx benchmarks/evaluations/c99-typescript-di/runner.ts",
125
+ "verify:c99": "tsx scripts/verify-c99.ts && vitest run src/__tests__/unit/typescript-di.spec.ts",
126
+ "bench:c100": "tsx benchmarks/evaluations/c100-flow-apex-path/runner.ts",
127
+ "verify:c100": "tsx scripts/verify-c100.ts && vitest run src/__tests__/unit/salesforce-metadata.spec.ts",
128
+ "bench:c101": "tsx benchmarks/evaluations/c101-contract-positioning/runner.ts",
129
+ "verify:c101": "tsx scripts/verify-c101.ts",
117
130
  "bench:ann": "bun scripts/ann-bench.ts",
118
131
  "bench:perf": "tsx scripts/perf-bench.ts",
119
132
  "bench:competitive": "tsx scripts/competitive-bakeoff.ts",