@gamaze/hicortex 0.20.7 → 0.20.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -41
- package/assets/dashboard.html +3989 -836
- package/dist/calibration.d.ts +293 -0
- package/dist/calibration.js +379 -0
- package/dist/capture-health.d.ts +87 -0
- package/dist/capture-health.js +106 -0
- package/dist/capture-pause.d.ts +86 -0
- package/dist/capture-pause.js +127 -0
- package/dist/capture.d.ts +24 -3
- package/dist/capture.js +11 -1
- package/dist/classify-domains.d.ts +6 -0
- package/dist/classify-domains.js +7 -1
- package/dist/cli.js +38 -3
- package/dist/config-read.d.ts +1 -1
- package/dist/config-read.js +96 -9
- package/dist/consolidate.d.ts +114 -68
- package/dist/consolidate.js +302 -182
- package/dist/dashboard.d.ts +326 -6
- package/dist/dashboard.js +592 -7
- package/dist/db.js +105 -0
- package/dist/dedup.d.ts +34 -26
- package/dist/dedup.js +91 -57
- package/dist/distiller.js +1 -1
- package/dist/domain-classify.d.ts +7 -6
- package/dist/domain-classify.js +12 -10
- package/dist/eval/decay-eval.d.ts +3 -3
- package/dist/eval/decay-eval.js +4 -4
- package/dist/eval/importance-eval.d.ts +85 -0
- package/dist/eval/importance-eval.js +286 -0
- package/dist/eval/planted-eval.d.ts +26 -0
- package/dist/eval/planted-eval.js +97 -0
- package/dist/eval/planted-fixtures.d.ts +107 -0
- package/dist/eval/planted-fixtures.js +283 -0
- package/dist/eval/planted-harness.d.ts +176 -0
- package/dist/eval/planted-harness.js +649 -0
- package/dist/eval/ranking-battery.d.ts +78 -0
- package/dist/eval/ranking-battery.js +181 -0
- package/dist/eval/ranking-eval.d.ts +41 -0
- package/dist/eval/ranking-eval.js +391 -0
- package/dist/eval/ranking-fixtures.d.ts +77 -0
- package/dist/eval/ranking-fixtures.js +226 -0
- package/dist/identity-store.d.ts +21 -0
- package/dist/identity-store.js +49 -0
- package/dist/index.js +4 -3
- package/dist/init.d.ts +23 -3
- package/dist/init.js +84 -9
- package/dist/llm.d.ts +43 -58
- package/dist/llm.js +87 -101
- package/dist/mcp-server.d.ts +12 -0
- package/dist/mcp-server.js +213 -32
- package/dist/nightly.d.ts +9 -1
- package/dist/nightly.js +164 -110
- package/dist/nofit.d.ts +4 -11
- package/dist/nofit.js +6 -23
- package/dist/prompts.d.ts +10 -0
- package/dist/prompts.js +28 -5
- package/dist/recall-index.d.ts +30 -28
- package/dist/recall-index.js +21 -18
- package/dist/recall-registry.d.ts +2 -1
- package/dist/recall-registry.js +35 -1
- package/dist/reconsolidation.d.ts +168 -87
- package/dist/reconsolidation.js +818 -377
- package/dist/relink.js +3 -4
- package/dist/rescore-importance.d.ts +80 -0
- package/dist/rescore-importance.js +236 -0
- package/dist/retrieval.d.ts +80 -35
- package/dist/retrieval.js +322 -105
- package/dist/run-deadline.d.ts +62 -0
- package/dist/run-deadline.js +73 -0
- package/dist/schema-prototypes.d.ts +3 -3
- package/dist/schema-prototypes.js +3 -3
- package/dist/stages.d.ts +37 -0
- package/dist/stages.js +51 -0
- package/dist/state.d.ts +34 -9
- package/dist/storage.d.ts +50 -18
- package/dist/storage.js +125 -30
- package/dist/telemetry.d.ts +8 -7
- package/dist/token-budget.js +3 -4
- package/dist/type-classify.js +4 -4
- package/dist/types.d.ts +143 -155
- package/domains.example.json +4 -5
- package/hermes-plugin/hicortex/README.md +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -1
- package/pi-extension/hicortex/README.md +1 -1
- package/server.json +3 -3
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The real-query battery for the #425 ranking gate — the DETERMINISTIC,
|
|
3
|
+
* dependency-free half (kept out of ranking-eval.ts so vitest can import
|
|
4
|
+
* it without pulling the ONNX embedder; same split as planted-fixtures vs
|
|
5
|
+
* planted-eval).
|
|
6
|
+
*
|
|
7
|
+
* The battery is a stable set of ~30 queries derived from the SNAPSHOT
|
|
8
|
+
* CORPUS ITSELF: fixed df windows over content tokens, fixed tie-breaks —
|
|
9
|
+
* same snapshot, same queries, every run. Bands: 10 high-df (50-400 docs),
|
|
10
|
+
* 10 mid-df (10-50), 5 rare-df (3-10), 4 mid-sentence capitalized proper
|
|
11
|
+
* nouns (df 2-8), plus the fixed "Sirnäs" query (the owner's live case).
|
|
12
|
+
*
|
|
13
|
+
* Also carries the photo-comparison gates the sweep protocol defines:
|
|
14
|
+
* - case 2 byte-stability (no-match queries must not drift),
|
|
15
|
+
* - battery top-1 stability >= 90%,
|
|
16
|
+
* - no query losing a both-channel exact match from its top-3.
|
|
17
|
+
*/
|
|
18
|
+
export declare const SIRNAS_QUERY = "Sirn\u00E4s";
|
|
19
|
+
/** Compact English stopword block (battery derivation only, not product). */
|
|
20
|
+
export declare const STOPWORDS: Set<string>;
|
|
21
|
+
export interface BatteryQuery {
|
|
22
|
+
q: string;
|
|
23
|
+
band: "proper-noun-fixed" | "high-df" | "mid-df" | "rare-df" | "proper-noun";
|
|
24
|
+
}
|
|
25
|
+
export interface BatteryResult {
|
|
26
|
+
q: string;
|
|
27
|
+
band: string;
|
|
28
|
+
top8: string[];
|
|
29
|
+
/** Per-result provenance (ranking-eval fills it; the gates read it). */
|
|
30
|
+
meta?: Array<{
|
|
31
|
+
id: string;
|
|
32
|
+
source: string | null;
|
|
33
|
+
term: boolean | null;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export interface BatteryPhoto {
|
|
37
|
+
queries: BatteryResult[];
|
|
38
|
+
/** Rank (1-based) of the real Sirnäs memory for the fixed query; null = not in top-8. */
|
|
39
|
+
sirnasRank: number | null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Derive the battery from live corpus contents — DETERMINISTIC given the
|
|
43
|
+
* snapshot: fixed stopword list, fixed df windows, fixed tie-breaks
|
|
44
|
+
* (df desc, then token asc).
|
|
45
|
+
*/
|
|
46
|
+
export declare function deriveBatteryQueries(rows: Array<{
|
|
47
|
+
id: string;
|
|
48
|
+
content: string;
|
|
49
|
+
}>): BatteryQuery[];
|
|
50
|
+
/** Case 2 gate: the FULL returned list must be byte-identical (ids + order). */
|
|
51
|
+
export declare function compareCase2(baseline: string[], current: string[]): boolean;
|
|
52
|
+
export interface BatteryComparison {
|
|
53
|
+
top1Stability: number;
|
|
54
|
+
/** Stability with intended D3 promotions excluded: old top-1 was NOT a
|
|
55
|
+
* both-channel exact match and the new top-1 IS one (the boost doing its
|
|
56
|
+
* job on real queries — reported, not gated). */
|
|
57
|
+
top1StabilityExPromotions: number;
|
|
58
|
+
changedTop1: string[];
|
|
59
|
+
/** Queries whose top-3 LOST every both-channel exact match (the gate:
|
|
60
|
+
* must be empty). */
|
|
61
|
+
lostBothChannelExact: string[];
|
|
62
|
+
/** Per-id exact-match (term-carrying) rows displaced from top-3 — reported
|
|
63
|
+
* for the sweep record, not gated (multiple genuine matches make per-id
|
|
64
|
+
* membership arbitrary). */
|
|
65
|
+
lostExactMatches: string[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Battery gates vs a recorded baseline photo. `contentById` maps memory id →
|
|
69
|
+
* content for the corpus BOTH photos were recorded against.
|
|
70
|
+
*
|
|
71
|
+
* Gates: (a) any query whose baseline top-3 held a BOTH-CHANNEL exact match
|
|
72
|
+
* (vector+FTS agreeing on a term-carrying row) must still hold one — the
|
|
73
|
+
* genuine-match guarantee never regresses; (b) top-1 stability. Intended D3
|
|
74
|
+
* promotions (a both-channel exact match taking top-1 from a non-exact or
|
|
75
|
+
* single-channel row) are counted separately — they are the feature firing,
|
|
76
|
+
* and the sweep record shows both numbers.
|
|
77
|
+
*/
|
|
78
|
+
export declare function batteryComparison(baseline: BatteryPhoto, current: BatteryPhoto, contentById: Map<string, string>): BatteryComparison;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The real-query battery for the #425 ranking gate — the DETERMINISTIC,
|
|
4
|
+
* dependency-free half (kept out of ranking-eval.ts so vitest can import
|
|
5
|
+
* it without pulling the ONNX embedder; same split as planted-fixtures vs
|
|
6
|
+
* planted-eval).
|
|
7
|
+
*
|
|
8
|
+
* The battery is a stable set of ~30 queries derived from the SNAPSHOT
|
|
9
|
+
* CORPUS ITSELF: fixed df windows over content tokens, fixed tie-breaks —
|
|
10
|
+
* same snapshot, same queries, every run. Bands: 10 high-df (50-400 docs),
|
|
11
|
+
* 10 mid-df (10-50), 5 rare-df (3-10), 4 mid-sentence capitalized proper
|
|
12
|
+
* nouns (df 2-8), plus the fixed "Sirnäs" query (the owner's live case).
|
|
13
|
+
*
|
|
14
|
+
* Also carries the photo-comparison gates the sweep protocol defines:
|
|
15
|
+
* - case 2 byte-stability (no-match queries must not drift),
|
|
16
|
+
* - battery top-1 stability >= 90%,
|
|
17
|
+
* - no query losing a both-channel exact match from its top-3.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.STOPWORDS = exports.SIRNAS_QUERY = void 0;
|
|
21
|
+
exports.deriveBatteryQueries = deriveBatteryQueries;
|
|
22
|
+
exports.compareCase2 = compareCase2;
|
|
23
|
+
exports.batteryComparison = batteryComparison;
|
|
24
|
+
exports.SIRNAS_QUERY = "Sirnäs";
|
|
25
|
+
/** Compact English stopword block (battery derivation only, not product). */
|
|
26
|
+
exports.STOPWORDS = new Set(("the a an and or but if while with without for from into onto about after before during under over " +
|
|
27
|
+
"this that these those there here when where which who whom whose what why how all any both each " +
|
|
28
|
+
"few more most other some such no nor not only own same so than too very can will just should now " +
|
|
29
|
+
"was were been being are was is am be have has had having do does did doing would could might must " +
|
|
30
|
+
"also because through between against above below off again further then once its it their they " +
|
|
31
|
+
"them we you your our i me my he she his her him us of as at by in on to per via due using use " +
|
|
32
|
+
"used using make made get gets got set sets let run ran running work works working need needs " +
|
|
33
|
+
"added add adds change changed new next last first second one two three session sessions " +
|
|
34
|
+
"memory memories project projects user users system systems thing things stuff lot bit way ways " +
|
|
35
|
+
"time times day days week weeks month months year years")
|
|
36
|
+
.split(/\s+/));
|
|
37
|
+
/**
|
|
38
|
+
* Derive the battery from live corpus contents — DETERMINISTIC given the
|
|
39
|
+
* snapshot: fixed stopword list, fixed df windows, fixed tie-breaks
|
|
40
|
+
* (df desc, then token asc).
|
|
41
|
+
*/
|
|
42
|
+
function deriveBatteryQueries(rows) {
|
|
43
|
+
const docs = rows.map((r) => r.content.toLowerCase());
|
|
44
|
+
const df = new Map();
|
|
45
|
+
for (const doc of docs) {
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
for (const m of doc.matchAll(/[\p{L}\p{N}]{5,}/gu)) {
|
|
48
|
+
const t = m[0];
|
|
49
|
+
if (exports.STOPWORDS.has(t) || seen.has(t))
|
|
50
|
+
continue;
|
|
51
|
+
seen.add(t);
|
|
52
|
+
df.set(t, (df.get(t) ?? 0) + 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const inWindow = (lo, hi) => [...df.entries()]
|
|
56
|
+
.filter(([t, d]) => d >= lo && d < hi && !exports.STOPWORDS.has(t))
|
|
57
|
+
.sort((a, b) => (b[1] - a[1] !== 0 ? b[1] - a[1] : a[0].localeCompare(b[0])))
|
|
58
|
+
.map(([t, d]) => [t, d]);
|
|
59
|
+
const queries = [
|
|
60
|
+
{ q: exports.SIRNAS_QUERY, band: "proper-noun-fixed" },
|
|
61
|
+
];
|
|
62
|
+
for (const [t] of inWindow(50, 401).slice(0, 10))
|
|
63
|
+
queries.push({ q: t, band: "high-df" });
|
|
64
|
+
for (const [t] of inWindow(10, 50).slice(0, 10))
|
|
65
|
+
queries.push({ q: t, band: "mid-df" });
|
|
66
|
+
for (const [t] of inWindow(3, 10).slice(0, 5))
|
|
67
|
+
queries.push({ q: t, band: "rare-df" });
|
|
68
|
+
// Proper nouns: capitalized, appearing mid-sentence (not the first word
|
|
69
|
+
// after a sentence break — cheap deterministic heuristic), df 2-8.
|
|
70
|
+
// "Sirnäs" itself may or may not surface here; the fixed query above
|
|
71
|
+
// guarantees coverage.
|
|
72
|
+
const capDf = new Map();
|
|
73
|
+
for (const content of rows.map((r) => r.content)) {
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
for (const m of content.matchAll(/[^\s.!?;:]\s+([\p{Lu}][\p{L}\p{N}\p{M}-]{3,})/gu)) {
|
|
76
|
+
const t = m[1];
|
|
77
|
+
if (!exports.STOPWORDS.has(t.toLowerCase()))
|
|
78
|
+
seen.add(t);
|
|
79
|
+
}
|
|
80
|
+
for (const t of seen)
|
|
81
|
+
capDf.set(t, (capDf.get(t) ?? 0) + 1);
|
|
82
|
+
}
|
|
83
|
+
const properNouns = [...capDf.entries()]
|
|
84
|
+
.filter(([t, d]) => d >= 2 && d <= 8)
|
|
85
|
+
.sort((a, b) => (b[1] - a[1] !== 0 ? b[1] - a[1] : a[0].localeCompare(b[0])))
|
|
86
|
+
.map(([t]) => t);
|
|
87
|
+
for (const t of properNouns.slice(0, 4))
|
|
88
|
+
queries.push({ q: t, band: "proper-noun" });
|
|
89
|
+
return queries;
|
|
90
|
+
}
|
|
91
|
+
/** Case 2 gate: the FULL returned list must be byte-identical (ids + order). */
|
|
92
|
+
function compareCase2(baseline, current) {
|
|
93
|
+
return (baseline.length === current.length &&
|
|
94
|
+
baseline.every((id, i) => id === current[i]));
|
|
95
|
+
}
|
|
96
|
+
/** A both-channel row carrying the query term — the genuine-match signature. */
|
|
97
|
+
function isBothChannelExact(m, id, term, contentById) {
|
|
98
|
+
if (m)
|
|
99
|
+
return m.source === "both" && m.term === true;
|
|
100
|
+
// Baseline photos recorded before `meta` existed: fall back to content
|
|
101
|
+
// containment; conservatively treat source as unknown (not "both").
|
|
102
|
+
const content = contentById.get(id);
|
|
103
|
+
return Boolean(content && content.toLowerCase().includes(term));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Battery gates vs a recorded baseline photo. `contentById` maps memory id →
|
|
107
|
+
* content for the corpus BOTH photos were recorded against.
|
|
108
|
+
*
|
|
109
|
+
* Gates: (a) any query whose baseline top-3 held a BOTH-CHANNEL exact match
|
|
110
|
+
* (vector+FTS agreeing on a term-carrying row) must still hold one — the
|
|
111
|
+
* genuine-match guarantee never regresses; (b) top-1 stability. Intended D3
|
|
112
|
+
* promotions (a both-channel exact match taking top-1 from a non-exact or
|
|
113
|
+
* single-channel row) are counted separately — they are the feature firing,
|
|
114
|
+
* and the sweep record shows both numbers.
|
|
115
|
+
*/
|
|
116
|
+
function batteryComparison(baseline, current, contentById) {
|
|
117
|
+
const baseByQ = new Map(baseline.queries.map((b) => [b.q, b]));
|
|
118
|
+
const baseMeta = (b, rank) => b.meta?.[rank];
|
|
119
|
+
let stable = 0;
|
|
120
|
+
let stableExPromotions = 0;
|
|
121
|
+
let compared = 0;
|
|
122
|
+
const changedTop1 = [];
|
|
123
|
+
const lostBothChannelExact = [];
|
|
124
|
+
const lostExactMatches = [];
|
|
125
|
+
for (const cur of current.queries) {
|
|
126
|
+
const base = baseByQ.get(cur.q);
|
|
127
|
+
if (!base)
|
|
128
|
+
continue;
|
|
129
|
+
compared++;
|
|
130
|
+
if (cur.top8[0] === base.top8[0]) {
|
|
131
|
+
stable++;
|
|
132
|
+
stableExPromotions++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
changedTop1.push(cur.q);
|
|
136
|
+
const term = cur.q.toLowerCase();
|
|
137
|
+
const oldExact = baseMeta(base, 0) !== undefined
|
|
138
|
+
? isBothChannelExact(baseMeta(base, 0), base.top8[0], term, contentById)
|
|
139
|
+
: null;
|
|
140
|
+
const newExact = cur.meta?.[0] !== undefined
|
|
141
|
+
? isBothChannelExact(cur.meta[0], cur.top8[0], term, contentById)
|
|
142
|
+
: null;
|
|
143
|
+
// An intended promotion: the new top-1 is a both-channel exact match the
|
|
144
|
+
// old top-1 was not. (Null = provenance unknown — pre-meta baseline —
|
|
145
|
+
// counts as a plain change.)
|
|
146
|
+
if (newExact === true && oldExact === false)
|
|
147
|
+
stableExPromotions++;
|
|
148
|
+
}
|
|
149
|
+
for (const cur of current.queries) {
|
|
150
|
+
const base = baseByQ.get(cur.q);
|
|
151
|
+
if (!base || cur.q === exports.SIRNAS_QUERY)
|
|
152
|
+
continue;
|
|
153
|
+
const term = cur.q.toLowerCase();
|
|
154
|
+
const curTop3 = new Set(cur.top8.slice(0, 3));
|
|
155
|
+
let baseHadBothChannelExact = false;
|
|
156
|
+
for (let i = 0; i < Math.min(3, base.top8.length); i++) {
|
|
157
|
+
const id = base.top8[i];
|
|
158
|
+
const content = contentById.get(id);
|
|
159
|
+
const carries = Boolean(content && content.toLowerCase().includes(term));
|
|
160
|
+
if (carries && !curTop3.has(id))
|
|
161
|
+
lostExactMatches.push(`${cur.q}: ${id.slice(0, 8)}`);
|
|
162
|
+
const m = base.meta?.[i];
|
|
163
|
+
if (m ? m.source === "both" && m.term === true : false)
|
|
164
|
+
baseHadBothChannelExact = true;
|
|
165
|
+
}
|
|
166
|
+
if (baseHadBothChannelExact) {
|
|
167
|
+
const stillHas = cur.top8
|
|
168
|
+
.slice(0, 3)
|
|
169
|
+
.some((id, i) => cur.meta?.[i]?.source === "both" && cur.meta?.[i]?.term === true);
|
|
170
|
+
if (!stillHas)
|
|
171
|
+
lostBothChannelExact.push(cur.q);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
top1Stability: compared > 0 ? stable / compared : 1,
|
|
176
|
+
top1StabilityExPromotions: compared > 0 ? stableExPromotions / compared : 1,
|
|
177
|
+
changedTop1,
|
|
178
|
+
lostBothChannelExact,
|
|
179
|
+
lostExactMatches,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Search-trust ranking gate (#425) — the AC1/AC2/AC3 before/after photo.
|
|
4
|
+
*
|
|
5
|
+
* npm run eval:ranking [--snapshot <db>] [--photo <out.json>]
|
|
6
|
+
* [--compare <baseline.json>] [--scoring '<json>']
|
|
7
|
+
*
|
|
8
|
+
* --snapshot <db> enables case 3: the deterministic real-query battery
|
|
9
|
+
* against a READONLY snapshot (openSnapshot — never
|
|
10
|
+
* initDb; the snapshot is never touched). Also reports
|
|
11
|
+
* where the real "Sirnäs" memory ranks today (the
|
|
12
|
+
* measured red photo pre-fix, AC10 pre-condition).
|
|
13
|
+
* --photo <out.json> where to write the machine photo (defaults to
|
|
14
|
+
* data/ranking-eval-photo.json). ALWAYS written, even
|
|
15
|
+
* on a red run — the photo IS the measurement.
|
|
16
|
+
* --compare <json> gate against a previously recorded photo: case 2's
|
|
17
|
+
* returned list must be byte-identical, and the battery
|
|
18
|
+
* must hold top-1 stability >= 90% with no query losing
|
|
19
|
+
* a both-channel exact match from its top-3.
|
|
20
|
+
* --scoring '<json>' a JSON object of ScoringWeights overrides passed to
|
|
21
|
+
* configureScoring (the eval/test seam, #408) — the
|
|
22
|
+
* sweep knob. Production runs omit it.
|
|
23
|
+
*
|
|
24
|
+
* Cases (fixtures: ranking-fixtures.ts, real bge-small-en-v1.5 embedder):
|
|
25
|
+
* 1. sirnas_exact_match — planted corpus in a THROWAWAY writable DB;
|
|
26
|
+
* declared expectation: the proper-noun exact-match row returns #1.
|
|
27
|
+
* Exit code reflects the verdict (a pre-fix run is EXPECTED to exit
|
|
28
|
+
* non-zero — that red photo is the before picture).
|
|
29
|
+
* 2. no_match_control — same corpus, a "mamma"-class query with zero FTS
|
|
30
|
+
* hits; records the full returned list for byte-stability comparison.
|
|
31
|
+
* 3. real-query battery (needs --snapshot) — ~30 queries derived
|
|
32
|
+
* deterministically from the snapshot corpus (top/mid/rare-frequency
|
|
33
|
+
* distinctive terms + proper nouns) + the fixed "Sirnäs" query; top-8
|
|
34
|
+
* ids per query recorded to the photo.
|
|
35
|
+
*
|
|
36
|
+
* Honesty invariants: similarities are MEASURED (real embedder, embed-once,
|
|
37
|
+
* queryEmbedding reuse), noStrengthen on every retrieve() (the probe never
|
|
38
|
+
* perturbs the store), readonly snapshot access, and the planted corpora
|
|
39
|
+
* live in mkdtemp dirs removed on exit. Never touches ~/.hicortex.
|
|
40
|
+
*/
|
|
41
|
+
export {};
|