knodin 0.10.5 → 0.10.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/dist/bin/cli.js CHANGED
@@ -401,7 +401,13 @@ function formatStatusHuman(result) {
401
401
  const semanticNote = result.semanticReadiness && result.semanticReadiness !== "ready"
402
402
  ? ` Semantic search coverage is ${result.semanticReadiness}: \`search\` will under-return until embedding completes (\`knodin index\`).`
403
403
  : "";
404
- const coverage = `${result.coverage.sourceFiles} source files, ${result.coverage.indexedFiles} indexed files, ${result.coverage.filesWithSymbols} files with symbols${formatCoverageGaps(result.coverage.skipped)}${mirrorNote}${semanticNote}`;
404
+ // `countsUnknown` means the graph could not be read, so these are placeholders
405
+ // rather than measurements. Printing "0 indexed files" makes an absent graph
406
+ // indistinguishable from a fully destroyed one.
407
+ const indexedCounts = result.coverage.countsUnknown
408
+ ? "indexed and symbol counts unknown; the graph could not be read"
409
+ : `${result.coverage.indexedFiles} indexed files, ${result.coverage.filesWithSymbols} files with symbols`;
410
+ const coverage = `${result.coverage.sourceFiles} source files, ${indexedCounts}${formatCoverageGaps(result.coverage.skipped)}${mirrorNote}${semanticNote}`;
405
411
  if (result.status === "indexing" && result.activity) {
406
412
  const count = result.activity.phaseTotal === undefined
407
413
  ? ""
@@ -437,8 +443,18 @@ function formatStatusHuman(result) {
437
443
  return `Graph content is intact but evidence is stale (${coverage}).${result.verification.mode === "persisted-audit"
438
444
  ? ` Cached deep-audit evidence is from ${result.verification.auditVerifiedAt ?? "an earlier run"}; freshness was probed ${result.verification.verifiedAt ?? "now"}. Run \`knodin status --deep\` for an exact current audit.`
439
445
  : ""}\n${freshnessLine}${lifecycleLine}${integrationLine}Run \`knodin wait --fresh\` or issue a graph query to reconcile bounded drift.\n`;
440
- const outstanding = result.missing.files.length + result.missing.records.length;
441
- const firstIssue = result.missing.files[0] ?? result.missing.records[0];
446
+ // When the graph could not be read, every source file lands in `missing.files`
447
+ // because none of them are indexed. That is ONE structural problem, not one
448
+ // per file: a 94-file repository with no database reported "95 issue(s)" and
449
+ // led with an arbitrary source filename, so an absent graph read as damage
450
+ // proportional to repository size. `missing.files` still carries the repair
451
+ // worklist — only the count and the headline shown to a human change here.
452
+ const outstanding = result.coverage.countsUnknown
453
+ ? result.missing.records.length
454
+ : result.missing.files.length + result.missing.records.length;
455
+ const firstIssue = result.coverage.countsUnknown
456
+ ? result.missing.records[0]
457
+ : (result.missing.files[0] ?? result.missing.records[0]);
442
458
  const detail = firstIssue ? ` First issue: ${firstIssue}.` : "";
443
459
  const repairCommand = result.lifecycle?.status === "degraded" &&
444
460
  result.missing.records.every((record) => result.lifecycle?.issues.includes(record))
@@ -528,8 +528,19 @@ function scrubText(raw, repo) {
528
528
  };
529
529
  for (const exact of [repo, os.homedir()].filter(Boolean).sort((a, b) => b.length - a.length))
530
530
  replace(new RegExp(exact.replace(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`), "g"), "<path>");
531
- replace(/\b(?:ghp|github_pat|sk|xox[baprs])-[-A-Za-z0-9_]{10,}\b/g, "<secret>");
532
- replace(/\b(?:password|passwd|token|secret|api[_-]?key|authorization)\s*[=:]\s*[^\s,;]+/gi, "$1=<secret>");
531
+ // Either separator. GitHub issues its tokens with an UNDERSCORE after the
532
+ // prefix, so a hyphen-only pattern matched a shape GitHub never mints and
533
+ // missed every shape it does — a real token in an error message survived
534
+ // scrubbing and reached the bundle a user shares (KNODIN-10). Slack and
535
+ // OpenAI really do use a hyphen, so both separators must be accepted.
536
+ replace(/\b(?:ghp|github_pat|sk|xox[baprs])[-_][-A-Za-z0-9_]{10,}\b/g, "<secret>");
537
+ // The value pattern stops at whitespace, so `authorization: Bearer <token>`
538
+ // used to match only the word "Bearer" and leave the credential in place —
539
+ // the standard header form leaked the one thing worth redacting (KNODIN-10).
540
+ // An optional scheme is consumed first so the credential after it is the
541
+ // part that gets replaced. Only known schemes are skipped, so an ordinary
542
+ // `password=x next word` still redacts one value rather than eating prose.
543
+ replace(/\b(?:password|passwd|token|secret|api[_-]?key|authorization)\s*[=:]\s*(?:(?:bearer|basic|digest|token)\s+)?[^\s,;]+/gi, "<secret>");
533
544
  replace(/\b[A-Z]:\\(?:[^\s<>:"|?*]+\\)*[^\s<>:"|?*]*/g, "<path>");
534
545
  replace(/(?:^|[\s('"`])\/(?:[^\s)'"`]+\/)*[^\s)'"`]*/g, "<path>");
535
546
  replace(/\b(?:[A-Za-z0-9_.-]+\/)+(?:[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,12})\b/g, "<path>");
@@ -1,6 +1,8 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { isSealedDatabase } from "./seal.js";
5
+ import { Database } from "./sqlite.js";
4
6
  import { resolveDbPath, resolveStateDir } from "./state-paths.js";
5
7
  const PROMOTION_SCHEMA_VERSION = 1;
6
8
  const CANDIDATES_DIRECTORY = "candidates";
@@ -166,6 +168,29 @@ export function recoverInterruptedPromotion(repo) {
166
168
  fsyncDirectory(path.dirname(marker));
167
169
  return outcome;
168
170
  }
171
+ /**
172
+ * Whether the database currently at the active path carries embedded source.
173
+ *
174
+ * An active database that cannot be opened reports false ON PURPOSE. Replacing
175
+ * a damaged graph is exactly what promotion exists to do, and a graph too
176
+ * corrupt to open is the ordinary case for that — refusing here would break
177
+ * repair for the situation it is most needed in. The trade is deliberate: a
178
+ * sealed artifact that is ALSO unreadable can still be replaced, but such an
179
+ * artifact is already unrecoverable.
180
+ */
181
+ function activeDatabaseIsSealed(activePath) {
182
+ let db = null;
183
+ try {
184
+ db = new Database(activePath, { readonly: true });
185
+ return isSealedDatabase(db);
186
+ }
187
+ catch {
188
+ return false;
189
+ }
190
+ finally {
191
+ db?.close();
192
+ }
193
+ }
169
194
  /** Same-filesystem, marker-backed promotion. Caller must close and audit both databases first. */
170
195
  export function promoteCandidateFile(repo, candidate) {
171
196
  assertCandidate(repo, candidate);
@@ -176,6 +201,18 @@ export function promoteCandidateFile(repo, candidate) {
176
201
  for (const suffix of ["-wal", "-shm"])
177
202
  if (fs.existsSync(`${candidate.databasePath}${suffix}`))
178
203
  throw new Error("candidate database has uncheckpointed sidecar files");
204
+ // Promotion REPLACES the active database. When that file is a sealed
205
+ // artifact, replacing it destroys the only copy of the source embedded in
206
+ // it. `repair` reaches here with a candidate built from the working tree,
207
+ // so an artifact sitting at the active path was emptied by a command whose
208
+ // entire purpose is to make a graph healthier (KNODIN-9).
209
+ //
210
+ // This is the chokepoint rather than a guard inside `repair`, because every
211
+ // promotion has the same consequence regardless of which caller arrives.
212
+ // An ordinary working graph is never sealed, so normal promotion and repair
213
+ // are unaffected.
214
+ if (fs.existsSync(activePath) && activeDatabaseIsSealed(activePath))
215
+ throw new Error("active database is a sealed artifact; promotion would replace it");
179
216
  fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
180
217
  fsyncFile(candidate.databasePath);
181
218
  const backupPath = path.join(stateDir, `db.sqlite.backup.${Date.now()}.${crypto.randomUUID().slice(0, 8)}`);
@@ -8860,6 +8860,16 @@ export async function getOrInitDb(repoPath, options = {}) {
8860
8860
  // Runs after the CREATE TABLEs below, since the table must exist.
8861
8861
  const needsOrphanPurge = storedVersion < KNODIN_SCHEMA_VERSION;
8862
8862
  const needsEmbeddingRepresentationRefresh = storedVersion >= 22 && storedVersion < 23;
8863
+ // An artifact sealed by an older build carries that build's schema
8864
+ // version, so a newer knodin opening it for write lands here and drops
8865
+ // every table below — including the embedded source, which no working
8866
+ // tree can supply again. Today's artifacts report the current version
8867
+ // and never reach it; one from a single release ago would.
8868
+ //
8869
+ // Refuse rather than skip: skipping would leave the caller holding a
8870
+ // database it believes is writable and current, and it is neither.
8871
+ if (storedVersion < LAST_REBUILD_SCHEMA_VERSION && isSealedDatabase(db))
8872
+ throw new Error("refusing to rebuild the schema of a sealed artifact: it would drop the embedded source, which is the only copy");
8863
8873
  if (storedVersion < LAST_REBUILD_SCHEMA_VERSION) {
8864
8874
  db.run("DROP TRIGGER IF EXISTS after_symbol_insert;");
8865
8875
  db.run("DROP TRIGGER IF EXISTS after_symbol_delete;");
@@ -12941,6 +12951,9 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
12941
12951
  let indexedFiles = 0;
12942
12952
  let filesWithSymbols = 0;
12943
12953
  let lastSuccessfulReconciliation = null;
12954
+ // No database, or counters that threw, means the zeros below were
12955
+ // never measured.
12956
+ let countsUnknown = !db;
12944
12957
  if (db) {
12945
12958
  try {
12946
12959
  schemaVersion =
@@ -12957,7 +12970,10 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
12957
12970
  }
12958
12971
  catch {
12959
12972
  // A schema migration can briefly make counters unavailable;
12960
- // the live operation remains the authoritative status.
12973
+ // the live operation remains the authoritative status. Whatever
12974
+ // was read before the throw is partial, so report the counts as
12975
+ // unknown rather than presenting a half-filled tally as measured.
12976
+ countsUnknown = true;
12961
12977
  }
12962
12978
  if (!activeDb)
12963
12979
  db.close();
@@ -12980,6 +12996,7 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
12980
12996
  ? Math.round((Math.min(indexedFiles, sourceFiles.length) / sourceFiles.length) * 10000) / 100
12981
12997
  : 100,
12982
12998
  skipped,
12999
+ ...(countsUnknown ? { countsUnknown: true } : {}),
12983
13000
  },
12984
13001
  orphaned: { embeddings: 0, references: 0, dependencies: 0 },
12985
13002
  missing: { files: [], records: [] },
@@ -13011,6 +13028,8 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
13011
13028
  filesWithSymbols: 0,
13012
13029
  percent: sourceFiles.length ? 0 : 100,
13013
13030
  skipped: buildCoverageSkips(collected.skippedByExtension, null),
13031
+ // There is no database to count, so these are placeholders.
13032
+ countsUnknown: true,
13014
13033
  },
13015
13034
  orphaned: { embeddings: 0, references: 0, dependencies: 0 },
13016
13035
  missing: {
@@ -13183,6 +13202,9 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
13183
13202
  filesWithSymbols: 0,
13184
13203
  percent: sourceFiles.length ? 0 : 100,
13185
13204
  skipped: coverageSkips,
13205
+ // The schema is not one this build can read, so index_state is
13206
+ // not counted here even though rows may well exist.
13207
+ countsUnknown: true,
13186
13208
  },
13187
13209
  orphaned: { embeddings: 0, references: 0, dependencies: 0 },
13188
13210
  missing: { files: sourceFiles, records: schemaProblems },
@@ -13531,7 +13553,7 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
13531
13553
  else
13532
13554
  counts.indexed++;
13533
13555
  }
13534
- else {
13556
+ else if (shouldPurgeMissingFile(db)) {
13535
13557
  db.run("BEGIN TRANSACTION;");
13536
13558
  try {
13537
13559
  deleteSymbolsForFile(db, file);
@@ -13547,6 +13569,17 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
13547
13569
  }
13548
13570
  counts.removed++;
13549
13571
  }
13572
+ else {
13573
+ // A sealed artifact's rows ARE the artifact: it has no working
13574
+ // tree, so "this file is missing" is its normal condition rather
13575
+ // than drift to clean up (KNODIN-9).
13576
+ //
13577
+ // This was the sixth purge site and the only unguarded one.
13578
+ // `removeIndexState` above carries its own guard, which is why
13579
+ // the damage looked so strange: index_state survived while the
13580
+ // symbols, references and dependencies around it were deleted.
13581
+ counts.skipped++;
13582
+ }
13550
13583
  committedWork = true;
13551
13584
  setMeta(db, "repairPostprocessingPending", "1");
13552
13585
  completedFiles = fileIndex + 1;
@@ -26,7 +26,13 @@ const CODE_EXTENSIONS = [
26
26
  ".cpp",
27
27
  ".h",
28
28
  ".hpp",
29
- ];
29
+ // Longest first. `referenceAt` takes the FIRST extension that matches at a
30
+ // cursor, so a shorter extension listed earlier wins and truncates the path:
31
+ // `src/main.cpp:3:4` parsed as `src/main.c` with no line, pointing diagnosis
32
+ // at a file that does not exist. `.tsx` only worked because it happened to
33
+ // precede `.ts`. Sorting makes longest-match the rule rather than an
34
+ // accident of list order.
35
+ ].sort((left, right) => right.length - left.length);
30
36
  const MAX_ANALYSIS_BYTES = 4 * 1024 * 1024;
31
37
  const MAX_SOURCE_FILE_BYTES = 1024 * 1024;
32
38
  const MAX_MANIFEST_BYTES = 256 * 1024;
@@ -0,0 +1,139 @@
1
+ # knodin 0.10.6
2
+
3
+ Four fixes, continuing the theme 0.10.2 through 0.10.5 have been working on: **a
4
+ result that looks complete while quietly being wrong**. Three of the four were
5
+ found the same way — by writing a test per case and watching which ones failed,
6
+ rather than by reading the code and deciding it looked correct.
7
+
8
+ Two of them disclose credentials, so read that section first if you have ever
9
+ attached a diagnostics bundle to a support request.
10
+
11
+ ## Credentials survived redaction
12
+
13
+ `knodin` scrubs secrets out of recorded failures before they can reach a
14
+ diagnostics bundle — the artefact a user hands to someone else when asking for
15
+ help. Two things it claimed to redact, it did not.
16
+
17
+ **GitHub tokens were never redacted at all.** The pattern required a hyphen
18
+ after the prefix:
19
+
20
+ ```
21
+ \b(?:ghp|github_pat|sk|xox[baprs])-[-A-Za-z0-9_]{10,}\b
22
+ ```
23
+
24
+ GitHub issues both its formats with an *underscore*. So the two GitHub
25
+ alternatives matched a shape GitHub never mints and missed every shape it does.
26
+ Slack and OpenAI genuinely do use a hyphen, so the separator now accepts either
27
+ rather than being swapped.
28
+
29
+ **An authorization header leaked the credential and redacted its label.** The
30
+ value pattern stops at whitespace, so for the standard form
31
+
32
+ ```
33
+ authorization: Bearer <token>
34
+ ```
35
+
36
+ it matched only the word `Bearer` — replacing the part that identifies the
37
+ header and preserving the part that authenticates. Exactly inverted. An optional
38
+ known scheme is now consumed first, so the credential after it is what gets
39
+ replaced.
40
+
41
+ This was quiet in an unhelpful way. The journal stores only a *fingerprint* of
42
+ the scrubbed message, never the message, so an unredacted credential is
43
+ invisible locally and appears only in the bundle that gets shared. The tests
44
+ assert redaction by fingerprint equivalence for that reason, and by
45
+ value-independence — two messages differing only in the secret must fingerprint
46
+ identically — which is how the `Bearer` case was found. An assertion written
47
+ against expected output text would have passed, because the output *did* contain
48
+ a redaction marker.
49
+
50
+ Redaction is on by default, so this needed no unusual configuration to hit.
51
+
52
+ A second redactor, in output compression, has different gaps in the opposite
53
+ direction: it catches GitHub classic but not fine-grained, and misses Slack and
54
+ OpenAI. Neither pattern list is a superset of the other. That one is filed
55
+ rather than patched here, because adding three regexes to one of two diverging
56
+ lists would only re-create the problem later.
57
+
58
+ ## `repair` emptied a sealed artifact
59
+
60
+ Pointed at a sealed artifact, `knodin repair` deleted its symbols, references
61
+ and dependencies. A sealed artifact's embedded source is the only copy of that
62
+ source, so this was unrecoverable.
63
+
64
+ 0.10.5 audited five sites that purge rows for files missing from disk and
65
+ guarded all five. This was a **sixth**, in repair's own removal branch, which
66
+ never consulted the guard.
67
+
68
+ It also explains a symptom that made no sense while the mechanism was unknown:
69
+ `index_state` survived intact while everything around it was deleted.
70
+ `removeIndexState`, called *inside* that same block, carries its own guard and
71
+ declined correctly — so the damage was the guard working on one line and being
72
+ absent from the four above it.
73
+
74
+ Two adjacent hazards are guarded alongside it: a schema rebuild would drop every
75
+ table of an artifact sealed by an older build, and promotion would overwrite a
76
+ sealed artifact sitting at the active path.
77
+
78
+ ## `status` reported an unreadable graph as a measured zero
79
+
80
+ A 94-file repository whose database was missing printed:
81
+
82
+ ```
83
+ Graph or lifecycle needs repair: 95 issue(s) found
84
+ (94 source files, 0 indexed files, 0 files with symbols)
85
+ First issue: <an arbitrary source file>
86
+ ```
87
+
88
+ `repair`, seconds later, reported 94 indexed and 53 with symbols. The two
89
+ commands never counted differently. `status` was rendering a graph it could not
90
+ read as a measurement of zero.
91
+
92
+ The `95` was arithmetic, not a tally: the issue count is
93
+ `missing.files + missing.records`, and the no-database branch fills
94
+ `missing.files` with every source file. Ninety-four files plus one record. So
95
+ the count scaled with repository size, and a graph that had simply never been
96
+ built read as damage proportional to how much code you have.
97
+
98
+ Three sites emitted zeros they had not measured, and now report the counts as
99
+ unknown instead:
100
+
101
+ ```
102
+ Graph or lifecycle needs repair: 1 issue(s) found
103
+ (3 source files, indexed and symbol counts unknown; the graph could not be read)
104
+ First issue: local index database is missing
105
+ ```
106
+
107
+ `missing.files` is deliberately still populated. `repair` consumes it as its
108
+ worklist and `seal` reads it as dirty paths, so emptying it — the obvious way to
109
+ stop the count inflating — would have left repair with nothing to do.
110
+
111
+ ## C++ diagnostics pointed at files that do not exist
112
+
113
+ Source extensions are matched first-listed rather than longest, and `.c`
114
+ preceded `.cpp`. A C++ stack frame did not merely lose precision:
115
+
116
+ ```
117
+ in at f (src/main.cpp:3:4)
118
+ was { path: "src/main.c", line: null, column: null }
119
+ now { path: "src/main.cpp", line: 3, column: 4 }
120
+ ```
121
+
122
+ A path to a file that does not exist, with the location dropped. `.tsx` worked
123
+ only because it happened to precede `.ts`; longest-match is now the rule rather
124
+ than an accident of list order.
125
+
126
+ ## Coverage measures the parse path again
127
+
128
+ Not a shipped behaviour change, but worth recording because it changes what the
129
+ project's own numbers mean.
130
+
131
+ Generic source files are parsed off the main thread, and v8 coverage instruments
132
+ only the isolate it runs in — so everything the parse path did executed inside a
133
+ worker thread where it could not be observed, and was reported as untested. On
134
+ an identical set of shards, with no test changed, disabling parse workers for
135
+ the coverage run alone moved branch coverage from 74.85% to 77.16%: 414 branches
136
+ the suite was already exercising.
137
+
138
+ Sixteen functions had been sitting in the uncovered map looking like dead code.
139
+ They were not.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knodin",
3
- "version": "0.10.5",
3
+ "version": "0.10.6",
4
4
  "knodin": {
5
5
  "compatibility": "compatible"
6
6
  },
@@ -23,32 +23,44 @@
23
23
  "knodin": "dist/bin/launcher.js"
24
24
  },
25
25
  "files": [
26
+ "*.wasm",
27
+ "benchmarks/competitors/SYNTHESIS.md",
26
28
  "dist",
27
- "skills",
28
- "docs/prompts/declare-multi-repository-system.md",
29
- "docs/DEAD-CODE-AND-IMPACT.md",
30
- "docs/DOCTOR-AND-UPDATES.md",
31
29
  "docs/BACKUP-RETENTION.md",
32
- "docs/DIAGNOSTICS.md",
33
30
  "docs/BEHAVIORAL-CONTRACT.md",
34
- "docs/DEMO.md",
31
+ "docs/CLI.md",
32
+ "docs/COMMAND-OUTPUT-COMPRESSION.md",
35
33
  "docs/COMPARISON.md",
36
34
  "docs/COMPETITIVE-LANDSCAPE-2026-08.md",
37
- "docs/INDEXING-POLICY-AND-PROVENANCE.md",
35
+ "docs/CONTAINED-EXECUTION.md",
36
+ "docs/DEAD-CODE-AND-IMPACT.md",
37
+ "docs/DEMO.md",
38
+ "docs/DIAGNOSTICS.md",
39
+ "docs/DOCTOR-AND-UPDATES.md",
40
+ "docs/GIT-HISTORY-REVIEW.md",
38
41
  "docs/HANDOFF.md",
42
+ "docs/INDEXING-POLICY-AND-PROVENANCE.md",
39
43
  "docs/INSTALLATION.md",
40
44
  "docs/MCP.md",
41
- "docs/COMMAND-OUTPUT-COMPRESSION.md",
42
- "docs/CONTAINED-EXECUTION.md",
43
45
  "docs/PROGRESSIVE-EVIDENCE.md",
44
- "docs/GIT-HISTORY-REVIEW.md",
45
- "docs/SCIP-IMPORT.md",
46
- "docs/CLI.md",
47
46
  "docs/PT-ACCESS-RECOMMENDATION.md",
48
- "docs/REPOSITORIES-AND-WORKTREES.md",
49
47
  "docs/RELEASE-0.3-EVIDENCE.md",
50
- "docs/SIGNED-UPDATES.md",
48
+ "docs/REPOSITORIES-AND-WORKTREES.md",
49
+ "docs/SCIP-IMPORT.md",
51
50
  "docs/SHARED-INDEX-CONTRACT.md",
51
+ "docs/SIGNED-UPDATES.md",
52
+ "docs/SYSTEMS-AND-RELATIONSHIPS.md",
53
+ "docs/TELEMETRY.md",
54
+ "docs/TOKEN-OPTIMIZER-SCORECARD.md",
55
+ "docs/assets/knodin-favicon.svg",
56
+ "docs/prompts/declare-multi-repository-system.md",
57
+ "docs/releases/0.10.0.md",
58
+ "docs/releases/0.10.1.md",
59
+ "docs/releases/0.10.2.md",
60
+ "docs/releases/0.10.3.md",
61
+ "docs/releases/0.10.4.md",
62
+ "docs/releases/0.10.5.md",
63
+ "docs/releases/0.10.6.md",
52
64
  "docs/releases/0.3.0.md",
53
65
  "docs/releases/0.4.0.md",
54
66
  "docs/releases/0.4.1.md",
@@ -71,25 +83,14 @@
71
83
  "docs/releases/0.8.6.md",
72
84
  "docs/releases/0.8.7.md",
73
85
  "docs/releases/0.9.0.md",
74
- "docs/releases/0.10.0.md",
75
- "docs/releases/0.10.1.md",
76
- "docs/releases/0.10.2.md",
77
- "docs/releases/0.10.3.md",
78
- "docs/releases/0.10.4.md",
79
- "docs/releases/0.10.5.md",
80
- "docs/assets/knodin-favicon.svg",
81
- "docs/SYSTEMS-AND-RELATIONSHIPS.md",
82
- "docs/TELEMETRY.md",
83
- "docs/TOKEN-OPTIMIZER-SCORECARD.md",
84
- "benchmarks/competitors/SYNTHESIS.md",
85
86
  "roadmap/competitive-roadmap.md",
86
87
  "schemas/release-attestation-v1.schema.json",
87
- "schemas/support-bundle-v2.schema.json",
88
- "schemas/shared-index-config-v1.schema.json",
89
88
  "schemas/shared-index-branch-pointer-v1.schema.json",
89
+ "schemas/shared-index-config-v1.schema.json",
90
90
  "schemas/shared-index-manifest-v1.schema.json",
91
91
  "schemas/shared-index-provenance-v1.schema.json",
92
- "*.wasm"
92
+ "schemas/support-bundle-v2.schema.json",
93
+ "skills"
93
94
  ],
94
95
  "engines": {
95
96
  "node": ">=24.0.0"