@panaversity/ksor 0.0.14 → 0.0.15
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/CHANGELOG.md +54 -0
- package/dist/cli.mjs +43 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# @panaversity/ksor
|
|
2
2
|
|
|
3
|
+
## 0.0.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5b076e6: Short documents reach search again — navigation is a shape, not a length
|
|
8
|
+
|
|
9
|
+
A record could be fully ingested, report "embedded 16, failed 0", and still be
|
|
10
|
+
unable to answer questions it plainly contained. Sections were classified as
|
|
11
|
+
navigation by LENGTH — anything under 250 characters — and navigation is
|
|
12
|
+
excluded from every retrieval arm. On a handbook that inverts the intent,
|
|
13
|
+
because a handbook's most valuable statements are its shortest.
|
|
14
|
+
|
|
15
|
+
Walked on 0.0.14 with three ordinary policy statements — a refund window, an
|
|
16
|
+
escalation path, a badge rule, 200-300 characters each. Three of four chunks
|
|
17
|
+
were unsearchable, and:
|
|
18
|
+
|
|
19
|
+
> **Q.** "how long does a buyer have to send something back"
|
|
20
|
+
> **A.** the scaffold's placeholder page — against a record stating _thirty days_
|
|
21
|
+
|
|
22
|
+
The answer was in the corpus, correctly ingested, readable by slug, and
|
|
23
|
+
unreachable by search.
|
|
24
|
+
|
|
25
|
+
Navigation is now decided by shape: a section is navigation when link lines are
|
|
26
|
+
most of it, or when what remains after them is too short to answer anything —
|
|
27
|
+
the same floor the serving predicate already applies. Length is no longer
|
|
28
|
+
consulted, so a 180-character link list is navigation and a 51-character fact is
|
|
29
|
+
not, which is the ordering length had backwards.
|
|
30
|
+
|
|
31
|
+
Measured on an authored handbook gold set with real embeddings, paired: short
|
|
32
|
+
substantive facts went **0/9 to 9/9 at rank 1**, the long-prose control held at
|
|
33
|
+
**4/4**, and the link-list page was returned **0** times. That last number is the
|
|
34
|
+
one that matters — admitting everything would have improved the first two and
|
|
35
|
+
made the product worse.
|
|
36
|
+
|
|
37
|
+
**To pick this up, re-run `ksor ingest`.** Chunks are re-classified on every
|
|
38
|
+
build and unchanged content is not re-embedded, so the upgrade costs a build,
|
|
39
|
+
not an embedding bill. `CHUNK_POLICY` moves to v6 because it is persisted
|
|
40
|
+
provenance and the behaviour it labels has changed.
|
|
41
|
+
|
|
42
|
+
- 5763e8b: Internal: a pool test that raced Postgres, and a comment that had it backwards
|
|
43
|
+
|
|
44
|
+
No adopter-visible behaviour changes.
|
|
45
|
+
|
|
46
|
+
`idle.db.test.ts` sampled `pg_stat_activity` immediately after a previous test's
|
|
47
|
+
`pool.end()`. Those are two different clocks — `end()` resolves when the client
|
|
48
|
+
socket closes, while the row disappears only once the server-side backend
|
|
49
|
+
actually exits — so the suite was order-coupled through the database and went
|
|
50
|
+
red in CI on a branch that changed nothing but a document. Each test now waits
|
|
51
|
+
for a quiet database before it starts, and states that it does.
|
|
52
|
+
|
|
53
|
+
The comment added in the previous release explaining the `env.example` guard fix
|
|
54
|
+
described the rename backwards: the TEMPLATE holds `env.example` and
|
|
55
|
+
`materialize.ts` maps it to `.env.example` on emit, not the other way round.
|
|
56
|
+
|
|
3
57
|
## 0.0.14
|
|
4
58
|
|
|
5
59
|
### Patch Changes
|
package/dist/cli.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
16
16
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
17
17
|
import { parseArgs } from "node:util";
|
|
18
18
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
19
|
-
//#region ../content-gateway/dist/main-
|
|
19
|
+
//#region ../content-gateway/dist/main-wfyAlzsD.mjs
|
|
20
20
|
/**
|
|
21
21
|
* A connection could not be ESTABLISHED in time — retryable.
|
|
22
22
|
*
|
|
@@ -430,7 +430,7 @@ const EMBED_DIM$1 = 1536;
|
|
|
430
430
|
const EMBED_TASK_DOCUMENT$1 = "RETRIEVAL_DOCUMENT";
|
|
431
431
|
const EMBED_TASK_QUERY$1 = "RETRIEVAL_QUERY";
|
|
432
432
|
/** bump ⇒ provenance (v5: CommonMark fences). All char limits count CODE POINTS (Python len parity). */
|
|
433
|
-
const CHUNK_POLICY$1 = "heading-aware-1500-content-only-
|
|
433
|
+
const CHUNK_POLICY$1 = "heading-aware-1500-content-only-v6";
|
|
434
434
|
/**
|
|
435
435
|
* The kernel's view of `instance.md` (adapted from oracle SC/instance.py
|
|
436
436
|
* under decision 11 — an adaptation, not a port: ksor has ONE instance file
|
|
@@ -4452,7 +4452,7 @@ async function withPgRetry(op, options = {}) {
|
|
|
4452
4452
|
throw lastError;
|
|
4453
4453
|
}
|
|
4454
4454
|
//#endregion
|
|
4455
|
-
//#region ../content/dist/commands-
|
|
4455
|
+
//#region ../content/dist/commands-_8HvMWfD.mjs
|
|
4456
4456
|
/**
|
|
4457
4457
|
* EVAL-LOCKED constants, quarried verbatim from the oracle
|
|
4458
4458
|
* (sor-agentfactory @ b554f91, config.py) — changing any of these is a
|
|
@@ -4467,7 +4467,7 @@ const EMBED_DIM = 1536;
|
|
|
4467
4467
|
const EMBED_TASK_DOCUMENT = "RETRIEVAL_DOCUMENT";
|
|
4468
4468
|
const EMBED_TASK_QUERY = "RETRIEVAL_QUERY";
|
|
4469
4469
|
/** bump ⇒ provenance (v5: CommonMark fences). All char limits count CODE POINTS (Python len parity). */
|
|
4470
|
-
const CHUNK_POLICY = "heading-aware-1500-content-only-
|
|
4470
|
+
const CHUNK_POLICY = "heading-aware-1500-content-only-v6";
|
|
4471
4471
|
const MAX_CHARS = 1500;
|
|
4472
4472
|
/** < Gemini's 2048-token embed input. */
|
|
4473
4473
|
const HARD_MAX_CHARS = 4e3;
|
|
@@ -7882,11 +7882,48 @@ function cleanBody(rawBody) {
|
|
|
7882
7882
|
function teachingBody(content) {
|
|
7883
7883
|
return pyStrip(pySplitLines(content, false).filter((ln) => !HEADING.test(ln)).join("\n"));
|
|
7884
7884
|
}
|
|
7885
|
+
/**
|
|
7886
|
+
* A line that is navigation rather than prose: strip its list marker and what
|
|
7887
|
+
* remains is nothing but links.
|
|
7888
|
+
*
|
|
7889
|
+
* Deliberately narrow. A line with prose AROUND a link ("Claim within thirty
|
|
7890
|
+
* days; see the [expenses page](x) to file.") is prose, because the sentence is
|
|
7891
|
+
* the content and the link is incidental.
|
|
7892
|
+
*/
|
|
7893
|
+
const NAV_LINE = /^(?:[-*+]\s+|\d+[.)]\s+)?(?:\[[^\]]*\]\([^)]*\)|<https?:\/\/[^>]*>|https?:\/\/\S+)(?:[\s,;·|>—–-]*(?:\[[^\]]*\]\([^)]*\)|<https?:\/\/[^>]*>|https?:\/\/\S+))*[\s.,;:]*$/;
|
|
7894
|
+
/**
|
|
7895
|
+
* Is this segment NAVIGATION — a thing that points at content rather than
|
|
7896
|
+
* being content?
|
|
7897
|
+
*
|
|
7898
|
+
* The oracle answered this with length: under NAV_MAX_CHARS (250) meant nav.
|
|
7899
|
+
* On the curriculum corpus it was tuned against, that proxy holds — a short
|
|
7900
|
+
* segment there really is a link list. On a handbook it inverts, because a
|
|
7901
|
+
* handbook's most valuable statements are its shortest ("Six months, with a
|
|
7902
|
+
* written review at three and six"), and `nav` is excluded from search. Issue
|
|
7903
|
+
* #55, walked live on 0.0.14: three of four chunks in an ordinary policy
|
|
7904
|
+
* record were unsearchable, and a question the record plainly answered was
|
|
7905
|
+
* served the scaffold's placeholder instead.
|
|
7906
|
+
*
|
|
7907
|
+
* So the question is asked about SHAPE, which is what "navigation" always
|
|
7908
|
+
* meant. A segment is nav when link lines are most of it, or when what is left
|
|
7909
|
+
* after them is too little to answer anything (MIN_CONTENT_CHARS — the same
|
|
7910
|
+
* floor the serving predicate applies, so this never labels `prose` something
|
|
7911
|
+
* search would refuse to return anyway).
|
|
7912
|
+
*
|
|
7913
|
+
* Length is no longer consulted. A 180-character link list is nav and a
|
|
7914
|
+
* 51-character fact is prose, which is the ordering length got backwards.
|
|
7915
|
+
*/
|
|
7916
|
+
function isNavShaped(content) {
|
|
7917
|
+
const lines = pySplitLines(teachingBody(content), false).map((ln) => pyStrip(ln)).filter((ln) => ln !== "");
|
|
7918
|
+
if (lines.length === 0) return true;
|
|
7919
|
+
if (lines.filter((ln) => NAV_LINE.test(ln)).length * 2 > lines.length) return true;
|
|
7920
|
+
return cpLen(lines.filter((ln) => !NAV_LINE.test(ln)).join(" ")) < 24;
|
|
7921
|
+
}
|
|
7885
7922
|
function classify(content, headingPath) {
|
|
7886
7923
|
if (JSX_ASSESS.test(content)) return "assessment";
|
|
7887
7924
|
const leaf = headingPath.length > 0 ? headingPath[headingPath.length - 1] : "";
|
|
7888
7925
|
if (JSX_EMBED.test(content) || content.includes("docs.google.com/presentation") || leaf.includes("Teaching Aid")) return "embed";
|
|
7889
|
-
if (
|
|
7926
|
+
if (isNavShaped(content)) return "nav";
|
|
7890
7927
|
return "prose";
|
|
7891
7928
|
}
|
|
7892
7929
|
/** A segment DOMINATED by a line-leading widget (with < NAV_MAX_CHARS of
|
|
@@ -8006,7 +8043,7 @@ function chunkText(text, maxChars = MAX_CHARS) {
|
|
|
8006
8043
|
} else prefix += seg.text;
|
|
8007
8044
|
continue;
|
|
8008
8045
|
}
|
|
8009
|
-
const segIsNav =
|
|
8046
|
+
const segIsNav = isNavShaped(seg.text);
|
|
8010
8047
|
const segMarker = segmentMarkerType(seg.text);
|
|
8011
8048
|
for (const piece of subsplit(seg.text, maxChars)) {
|
|
8012
8049
|
let sourceType;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panaversity/ksor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "Knowledge System of Record — compile governed markdown into a static site for people and an MCP server for AI agents, with citations and measured abstention.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abstention",
|