@panaversity/ksor 0.0.13 → 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 +134 -0
- package/dist/cli.mjs +94 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,139 @@
|
|
|
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
|
+
|
|
57
|
+
## 0.0.14
|
|
58
|
+
|
|
59
|
+
### Patch Changes
|
|
60
|
+
|
|
61
|
+
- a0d98b0: Cut dead weight, and repair two guards that had quietly stopped guarding
|
|
62
|
+
|
|
63
|
+
A sweep across every package, with each candidate handed to a second reviewer
|
|
64
|
+
whose job was to prove it still alive. Net −154 lines. Nothing an adopter can
|
|
65
|
+
observe changes; two things that were supposed to fail no longer stay silent.
|
|
66
|
+
|
|
67
|
+
**The two repairs.** A guard asserting that no scaffolded document describes
|
|
68
|
+
serving as publishing — a claim this repo has had to correct four times — ran
|
|
69
|
+
`readFileSync` inside a `try` whose `catch` returned quietly, and one of its five
|
|
70
|
+
filenames was `.env.example` while the scaffold emits `env.example`. So the row
|
|
71
|
+
covering the file that actually carries the serving variables had never executed.
|
|
72
|
+
The name is fixed and a missing file now fails instead of passing. Separately,
|
|
73
|
+
two doc-blocks described a stdio transport in the present tense; there is no
|
|
74
|
+
stdio door in the product, and the suite claiming to drive one drives HTTP.
|
|
75
|
+
|
|
76
|
+
**The removals.** A 134-line live-walk script pinned to `@panaversity/ksor@0.0.4`
|
|
77
|
+
that nothing referenced. `AuthConfig.jwksUrl`, computed and stored but never read
|
|
78
|
+
— its live twin is `explicitJwksUrl`; the boot-time validation of
|
|
79
|
+
`KSOR_JWKS_URL` stays exactly where it was. An `allowedAudiences.length > 0 &&`
|
|
80
|
+
operand that no path can reach as false, and whose false side would have skipped
|
|
81
|
+
the audience allowlist entirely. A `PoolTimeoutError` message parameter no caller
|
|
82
|
+
passed, which was also the one input where two retry classifiers disagreed —
|
|
83
|
+
removing it closes that. Two `instanceof X || instanceof Error` disjuncts where
|
|
84
|
+
`X extends Error`, so the first could never decide anything. One unused icon
|
|
85
|
+
export in the workbench shell.
|
|
86
|
+
|
|
87
|
+
**Left alone deliberately.** `SearchScope.kinds` is genuinely dead, but removing
|
|
88
|
+
it renumbers positional parameters across three SQL statements, two of which
|
|
89
|
+
derive a shared CTE by string substitution, and the test that would catch a wrong
|
|
90
|
+
renumber is gated on a database. That is a change to make on its own, with the
|
|
91
|
+
gate watching — not alongside a release.
|
|
92
|
+
|
|
93
|
+
- ce1595b: Ingest names the real reason it could not record a commit
|
|
94
|
+
|
|
95
|
+
Every first ingest of a freshly scaffolded project printed "knowledge/ is not in
|
|
96
|
+
a git repository". That is false: `ksor init` runs `git init`, so the repository
|
|
97
|
+
exists — it simply has no commit yet, and `rev-parse HEAD` fails with "unknown
|
|
98
|
+
revision" rather than because nothing is there. The reader was sent to `git
|
|
99
|
+
init`, which they had already run, in the one message that decides whether an
|
|
100
|
+
answer can be traced back to a reviewed commit.
|
|
101
|
+
|
|
102
|
+
Three different states were collapsing into that one sentence, and each has a
|
|
103
|
+
different next command:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
knowledge/ is in a git repository with no commits yet …
|
|
107
|
+
fix: commit the record (git add knowledge && git commit) and re-run
|
|
108
|
+
|
|
109
|
+
knowledge/ is not in a git repository …
|
|
110
|
+
fix: git init, commit the record, and re-run
|
|
111
|
+
|
|
112
|
+
git is not installed …
|
|
113
|
+
fix: install git, or pass --source-commit <sha> if the record is versioned elsewhere
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Verified on a real scaffold: the fresh case prints the first, and committing the
|
|
117
|
+
record turns the next ingest's `source:` line into an actual SHA.
|
|
118
|
+
|
|
119
|
+
- 474dedc: Internal: the env-contract drift test scans only the checkout's source
|
|
120
|
+
|
|
121
|
+
No adopter-visible behaviour changes. The test that guarantees every
|
|
122
|
+
adopter-settable environment variable is named in the scaffold's `env.example`
|
|
123
|
+
walked `packages/` with a `statSync` per entry, and descended into the fake npm
|
|
124
|
+
install another suite roots inside `packages/ksor`. That cost two ways: the
|
|
125
|
+
copied template sources were scanned twice, and an entry deleted between the
|
|
126
|
+
`readdir` and the `statSync` crashed the whole run — which is what took CI red
|
|
127
|
+
on run 32526491721, on an `llms.txt` being cleaned up concurrently.
|
|
128
|
+
|
|
129
|
+
The walk now takes each entry's type from the readdir snapshot itself, so a
|
|
130
|
+
vanishing entry cannot crash it, and it skips transient install trees, so its
|
|
131
|
+
input no longer depends on whether another suite is mid-run. The `REPO_ONLY`
|
|
132
|
+
exemption list was deleted as dead: it named seven variables that no scanned
|
|
133
|
+
file can contain, because the walk excludes test files in the first place. The
|
|
134
|
+
honesty check that is supposed to catch stale exemptions now covers every
|
|
135
|
+
exemption list, which is what its name always claimed.
|
|
136
|
+
|
|
3
137
|
## 0.0.13
|
|
4
138
|
|
|
5
139
|
### 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
|
*
|
|
@@ -42,8 +42,8 @@ var ConnectTimeoutError$1 = class extends Error {
|
|
|
42
42
|
* a thundering herd aimed at the component already drowning.
|
|
43
43
|
*/
|
|
44
44
|
var PoolTimeoutError$1 = class extends Error {
|
|
45
|
-
constructor(
|
|
46
|
-
super(
|
|
45
|
+
constructor() {
|
|
46
|
+
super("pool checkout timed out (the configured checkout bound) — the pool is saturated; shedding this request is the recovery path, retrying it is not");
|
|
47
47
|
this.name = "PoolTimeoutError";
|
|
48
48
|
}
|
|
49
49
|
};
|
|
@@ -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
|
|
@@ -3079,7 +3079,7 @@ function buildServer(ctx, version) {
|
|
|
3079
3079
|
structuredContent: result
|
|
3080
3080
|
};
|
|
3081
3081
|
} catch (error) {
|
|
3082
|
-
if (error instanceof
|
|
3082
|
+
if (error instanceof Error) return {
|
|
3083
3083
|
content: [{
|
|
3084
3084
|
type: "text",
|
|
3085
3085
|
text: `Error: ${error.message}`
|
|
@@ -3188,7 +3188,7 @@ function toolError(error) {
|
|
|
3188
3188
|
return {
|
|
3189
3189
|
content: [{
|
|
3190
3190
|
type: "text",
|
|
3191
|
-
text: `Error: ${error instanceof
|
|
3191
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
3192
3192
|
}],
|
|
3193
3193
|
isError: true
|
|
3194
3194
|
};
|
|
@@ -3366,12 +3366,10 @@ function configFromEnv(env) {
|
|
|
3366
3366
|
const allowedAudiences = (env.KSOR_JWT_ALLOWED_AUDIENCES ?? "").split(",").map((a) => a.trim()).filter((a) => a !== "");
|
|
3367
3367
|
const issuer = (env.KSOR_SSO_ISSUER ?? "").trim() || null;
|
|
3368
3368
|
const explicit = (env.KSOR_JWKS_URL ?? "").trim();
|
|
3369
|
-
|
|
3370
|
-
assertHttpUrl("KSOR_JWKS_URL", jwksUrl, true);
|
|
3369
|
+
assertHttpUrl("KSOR_JWKS_URL", explicit || `${ssoUrl}/api/auth/jwks`, true);
|
|
3371
3370
|
return {
|
|
3372
3371
|
ssoUrl,
|
|
3373
3372
|
resourceUrl,
|
|
3374
|
-
jwksUrl,
|
|
3375
3373
|
explicitJwksUrl: explicit === "" ? null : explicit,
|
|
3376
3374
|
allowedAudiences,
|
|
3377
3375
|
issuer,
|
|
@@ -3492,7 +3490,7 @@ function createVerify(config, deps, jwksOf) {
|
|
|
3492
3490
|
cause: err
|
|
3493
3491
|
});
|
|
3494
3492
|
}
|
|
3495
|
-
if (
|
|
3493
|
+
if (!audOk(claims.aud, config.allowedAudiences)) {
|
|
3496
3494
|
reject(key);
|
|
3497
3495
|
throw new TokenVerifyError(`token aud ${JSON.stringify(claims.aud ?? null)} not in allowlist ${JSON.stringify(config.allowedAudiences)}`, { transient: false });
|
|
3498
3496
|
}
|
|
@@ -3648,8 +3646,8 @@ const UNDESCRIBED_RECORD = "instance.md is still the scaffold template — agent
|
|
|
3648
3646
|
* Composition (oracle main.py's boot order, adapted): instance → DSN via
|
|
3649
3647
|
* the declared env NAME → provider → pool → space guard → service context.
|
|
3650
3648
|
* Auth is built by the door that needs it (http.ts) — BEFORE the pool
|
|
3651
|
-
* serves anything;
|
|
3652
|
-
* with auth
|
|
3649
|
+
* serves anything; a loopback bind is the local-equivalent door and is the
|
|
3650
|
+
* only posture that may run with auth explicitly disabled.
|
|
3653
3651
|
*/
|
|
3654
3652
|
async function compose(instancePath, version) {
|
|
3655
3653
|
let instanceText;
|
|
@@ -4149,8 +4147,8 @@ var ConnectTimeoutError = class extends Error {
|
|
|
4149
4147
|
* a thundering herd aimed at the component already drowning.
|
|
4150
4148
|
*/
|
|
4151
4149
|
var PoolTimeoutError = class extends Error {
|
|
4152
|
-
constructor(
|
|
4153
|
-
super(
|
|
4150
|
+
constructor() {
|
|
4151
|
+
super("pool checkout timed out (the configured checkout bound) — the pool is saturated; shedding this request is the recovery path, retrying it is not");
|
|
4154
4152
|
this.name = "PoolTimeoutError";
|
|
4155
4153
|
}
|
|
4156
4154
|
};
|
|
@@ -4454,7 +4452,7 @@ async function withPgRetry(op, options = {}) {
|
|
|
4454
4452
|
throw lastError;
|
|
4455
4453
|
}
|
|
4456
4454
|
//#endregion
|
|
4457
|
-
//#region ../content/dist/commands-
|
|
4455
|
+
//#region ../content/dist/commands-_8HvMWfD.mjs
|
|
4458
4456
|
/**
|
|
4459
4457
|
* EVAL-LOCKED constants, quarried verbatim from the oracle
|
|
4460
4458
|
* (sor-agentfactory @ b554f91, config.py) — changing any of these is a
|
|
@@ -4469,7 +4467,7 @@ const EMBED_DIM = 1536;
|
|
|
4469
4467
|
const EMBED_TASK_DOCUMENT = "RETRIEVAL_DOCUMENT";
|
|
4470
4468
|
const EMBED_TASK_QUERY = "RETRIEVAL_QUERY";
|
|
4471
4469
|
/** bump ⇒ provenance (v5: CommonMark fences). All char limits count CODE POINTS (Python len parity). */
|
|
4472
|
-
const CHUNK_POLICY = "heading-aware-1500-content-only-
|
|
4470
|
+
const CHUNK_POLICY = "heading-aware-1500-content-only-v6";
|
|
4473
4471
|
const MAX_CHARS = 1500;
|
|
4474
4472
|
/** < Gemini's 2048-token embed input. */
|
|
4475
4473
|
const HARD_MAX_CHARS = 4e3;
|
|
@@ -7884,11 +7882,48 @@ function cleanBody(rawBody) {
|
|
|
7884
7882
|
function teachingBody(content) {
|
|
7885
7883
|
return pyStrip(pySplitLines(content, false).filter((ln) => !HEADING.test(ln)).join("\n"));
|
|
7886
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
|
+
}
|
|
7887
7922
|
function classify(content, headingPath) {
|
|
7888
7923
|
if (JSX_ASSESS.test(content)) return "assessment";
|
|
7889
7924
|
const leaf = headingPath.length > 0 ? headingPath[headingPath.length - 1] : "";
|
|
7890
7925
|
if (JSX_EMBED.test(content) || content.includes("docs.google.com/presentation") || leaf.includes("Teaching Aid")) return "embed";
|
|
7891
|
-
if (
|
|
7926
|
+
if (isNavShaped(content)) return "nav";
|
|
7892
7927
|
return "prose";
|
|
7893
7928
|
}
|
|
7894
7929
|
/** A segment DOMINATED by a line-leading widget (with < NAV_MAX_CHARS of
|
|
@@ -8008,7 +8043,7 @@ function chunkText(text, maxChars = MAX_CHARS) {
|
|
|
8008
8043
|
} else prefix += seg.text;
|
|
8009
8044
|
continue;
|
|
8010
8045
|
}
|
|
8011
|
-
const segIsNav =
|
|
8046
|
+
const segIsNav = isNavShaped(seg.text);
|
|
8012
8047
|
const segMarker = segmentMarkerType(seg.text);
|
|
8013
8048
|
for (const piece of subsplit(seg.text, maxChars)) {
|
|
8014
8049
|
let sourceType;
|
|
@@ -8692,19 +8727,46 @@ function composeProvider(instance) {
|
|
|
8692
8727
|
return fail$1(REFUSED, `instance embedding.provider: ${exc instanceof Error ? exc.message : String(exc)}`);
|
|
8693
8728
|
}
|
|
8694
8729
|
}
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8730
|
+
function provenanceGap(knowledgeDir) {
|
|
8731
|
+
if (knowledgeDir === void 0) return "not-asked";
|
|
8732
|
+
const run = (args) => {
|
|
8733
|
+
try {
|
|
8734
|
+
return {
|
|
8735
|
+
ok: true,
|
|
8736
|
+
out: execFileSync("git", [
|
|
8737
|
+
"-C",
|
|
8738
|
+
knowledgeDir,
|
|
8739
|
+
...args
|
|
8740
|
+
], {
|
|
8741
|
+
encoding: "utf8",
|
|
8742
|
+
stdio: [
|
|
8743
|
+
"ignore",
|
|
8744
|
+
"pipe",
|
|
8745
|
+
"ignore"
|
|
8746
|
+
]
|
|
8747
|
+
}).trim()
|
|
8748
|
+
};
|
|
8749
|
+
} catch {
|
|
8750
|
+
return {
|
|
8751
|
+
ok: false,
|
|
8752
|
+
out: ""
|
|
8753
|
+
};
|
|
8754
|
+
}
|
|
8755
|
+
};
|
|
8756
|
+
if (!run(["--version"]).ok && !run(["rev-parse", "--git-dir"]).ok) return "no-git";
|
|
8757
|
+
if (!run(["rev-parse", "--git-dir"]).ok) return "no-repo";
|
|
8758
|
+
return "no-commit";
|
|
8759
|
+
}
|
|
8760
|
+
/** The remedy for each, because the reader's next command differs. */
|
|
8761
|
+
function provenanceNotice(gap) {
|
|
8762
|
+
const why = "so this generation cannot be traced back to a reviewed commit";
|
|
8763
|
+
switch (gap) {
|
|
8764
|
+
case "no-commit": return `source: unspecified — knowledge/ is in a git repository with no commits yet, ${why}.\n fix: commit the record (git add knowledge && git commit) and re-run`;
|
|
8765
|
+
case "no-repo": return `source: unspecified — knowledge/ is not in a git repository, ${why}.\n fix: git init, commit the record, and re-run`;
|
|
8766
|
+
case "no-git": return `source: unspecified — git is not installed, ${why}.\n fix: install git, or pass --source-commit <sha> if the record is versioned elsewhere`;
|
|
8767
|
+
case "not-asked": return `source: unspecified — no knowledge directory was given, ${why}.`;
|
|
8768
|
+
}
|
|
8769
|
+
}
|
|
8708
8770
|
function detectSourceCommit(knowledgeDir) {
|
|
8709
8771
|
if (knowledgeDir === void 0) return "unspecified";
|
|
8710
8772
|
try {
|
|
@@ -8890,7 +8952,7 @@ async function ingestCommand(args) {
|
|
|
8890
8952
|
process.stdout.write(`ingest: unchanged — generation ${report.generation} already serves this corpus\n`);
|
|
8891
8953
|
return 0;
|
|
8892
8954
|
}
|
|
8893
|
-
process.stdout.write(sourceCommit === "unspecified" ?
|
|
8955
|
+
process.stdout.write(sourceCommit === "unspecified" ? provenanceNotice(provenanceGap(values.knowledge)) + "\n" : `source: ${sourceCommit}\n`);
|
|
8894
8956
|
process.stdout.write(`ingest: generation ${report.generation} — ${report.nodes} nodes, ${report.chunks} chunks; embedded ${report.embedded}, carried ${report.carried}, failed ${report.failed}\n`);
|
|
8895
8957
|
if (report.unsearchable > 0) {
|
|
8896
8958
|
const pct = Math.round(report.unsearchable / Math.max(report.chunks, 1) * 100);
|
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",
|