@gamaze/hicortex 0.16.0 → 0.16.1

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 CHANGED
@@ -226,6 +226,7 @@ Config at `~/.hicortex/config.json`. Created by `init`. Key options:
226
226
  | `recallMinSimilarity` | Relevance floor for index entries (default: 0.55; text-search matches always pass) |
227
227
  | `recallReshowTurns` | Turns before an already-shown memory may reappear in the same session (default: 30) |
228
228
  | `recallMinPromptChars` | Prompts shorter than this skip the recall index (default: 20) |
229
+ | `recallTitleChars` | Chars of each memory's first line shown in an index entry (default: 150, range 40–400). Raised from 100 on 2026-08-02: with topic-first memory titles, 150 chars carries the subject *and* its claim, where 100 cut the claim mid-sentence. Costs roughly +74 tokens per 6-line block |
229
230
  | `sessionIntentWeight` | Blend weight of the session-intent rolling centroid in the recall search vector: `query = (1-w)·prompt + w·centroid` (default: 0.33; set 0 to disable — pure-prompt recall, the kill-switch). The first turn of a session searches with pure prompt and seeds the centroid; subsequent turns blend so recall follows the session's intent instead of being query-literal. The EMA rate (0.4) is a shipped constant, not configurable |
230
231
  | `dedupMergeThreshold` | Minimum cosine similarity for `hicortex dedup` to cluster memories as near-duplicates (default: 0.92) |
231
232
  | `supersessionMinSimilarity` | Minimum cosine similarity for a nightly supersession candidate pair (default: 0.80) |
@@ -307,7 +307,10 @@ async function stageReflection(db, memories, llm, budget, embedFn, dryRun) {
307
307
  const severity = String(lo.severity ?? "important");
308
308
  const confidence = String(lo.confidence ?? "medium");
309
309
  const sourcePattern = String(lo.source_pattern ?? "");
310
- let content = `## Lesson: ${lessonText}\n\n`;
310
+ // No `## Lesson:` prefix: memory_type='lesson' carries the type, and the
311
+ // text is the topic-first first line (display reads the first line, not a
312
+ // header parse — see lessons-context.ts / index.ts).
313
+ let content = `${lessonText}\n\n`;
311
314
  content += `**Type:** ${lessonType}\n`;
312
315
  content += `**Severity:** ${severity}\n`;
313
316
  content += `**Confidence:** ${confidence}\n`;
package/dist/distiller.js CHANGED
@@ -314,6 +314,14 @@ async function distillChunk(llm, transcript, projectName, date) {
314
314
  return { entries: [], dropped: [] };
315
315
  }
316
316
  const parsed = parseDistilledEntries(result);
317
+ // Smoke alarm (PR #218 review): the prompt enforces topic-first, but models
318
+ // sometimes ignore constraints (cf. the prior max-15-bullet failure). Count
319
+ // entries that still look actor-led or bracket-led so a format regression
320
+ // shows in nightly logs, not months later in the next eval. Non-blocking.
321
+ const offTopic = parsed.filter((e) => /^\s*(user|ai|the user|assistant)\b/i.test(e) || /^\s*\[/.test(e)).length;
322
+ if (parsed.length > 0 && offTopic > 0) {
323
+ console.log(`[hicortex] topic-first check: ${offTopic}/${parsed.length} entries look actor/bracket-led (prompt may be ignored)`);
324
+ }
317
325
  const entries = [];
318
326
  const dropped = [];
319
327
  for (const entry of parsed) {
@@ -409,23 +417,22 @@ function hasMinimalSubstance(entry) {
409
417
  function parseDistilledEntries(markdown) {
410
418
  const entries = [];
411
419
  const lines = markdown.split("\n");
412
- let currentSection = "";
413
420
  for (const line of lines) {
414
421
  const trimmed = line.trim();
415
- // Section headers
416
- if (trimmed.startsWith("### ")) {
417
- currentSection = trimmed.slice(4).trim();
422
+ // Skip all markdown headers (session title, classification, section
423
+ // headings). Sections are NOT prefixed onto entries: each bullet already
424
+ // starts with its [SUBJECT] (topic-first, enforced by prompts.ts), and
425
+ // prepending "[Section]" re-introduced the category-first prefix the
426
+ // 2026-08-02 corpus rewrite removed. The section label is unused
427
+ // downstream (distilled memories all store memory_type='episode').
428
+ if (trimmed.startsWith("# ") ||
429
+ trimmed.startsWith("## ") ||
430
+ trimmed.startsWith("### ")) {
418
431
  continue;
419
432
  }
420
- // Skip top-level headers and classification
421
- if (trimmed.startsWith("# ") || trimmed.startsWith("## "))
422
- continue;
423
- // Bullet items are individual memories
433
+ // Bullet items are individual, already topic-first memories.
424
434
  if (trimmed.startsWith("- ") && trimmed.length > 5) {
425
- const entry = currentSection
426
- ? `[${currentSection}] ${trimmed.slice(2)}`
427
- : trimmed.slice(2);
428
- entries.push(entry);
435
+ entries.push(trimmed.slice(2));
429
436
  }
430
437
  }
431
438
  return entries;
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Real-query relevance + SNIPPET eval — recall QUALITY on real agent prompts.
4
+ *
5
+ * v2 (spec `specs/2026-08-02-relevance-eval.md`) extends the v1 selection-only
6
+ * eval with the SNIPPET layer: v1 asked "did retrieve() surface the right
7
+ * memories?" (judge sees up to 2000 chars). Production shows the agent only a
8
+ * ~100-char one-liner (`recall-index.ts#memoryTitle`), so a memory can be
9
+ * genuinely relevant while its rendered line is useless — v1 scored that as a
10
+ * win. v2 grades BOTH: `full_verdict` (selection quality, judge sees full
11
+ * content) and `line_verdict` (snippet quality, judge sees ONLY the rendered
12
+ * production one-liner — imported from `recall-index.ts`, never reimplemented).
13
+ *
14
+ * v2 additions (spec §4, §5, §6, §6b) layered onto the v1 base (prompt
15
+ * sampling, readonly snapshot handling, embed-once + neverCalledEmbed,
16
+ * lenient JSON parse, distribution/CI reporting):
17
+ * 1. Dual verdict per surfaced memory — TWO separate, blind judge calls.
18
+ * 2. Snippet-length sweep (100/200/300/title+1st-sentence) on a fixed
19
+ * 40-prompt subset (8 per source).
20
+ * 3. Similarity-floor + retrieval-source analysis (near-free — logged, not
21
+ * re-judged).
22
+ * 4. Token-cost estimate (char/4) per K and per snippet-length variant.
23
+ * 5. ~20 rendered ACTUAL production blocks dumped into the report.
24
+ * 6. Redundancy — one set-level judge call per (prompt × mode) over the
25
+ * production 6.
26
+ * 7. Rate limiting + resumability (§6b, MANDATORY): serial calls,
27
+ * `--judge-delay-ms` (default 2000), exponential backoff with jitter on
28
+ * 429/5xx/timeout (5→10→20→40→80s, max 5 retries, respects
29
+ * `Retry-After`), checkpoint-per-call to a `.jsonl` sidecar, `--resume`,
30
+ * progress logging, 10%-error-rate abort, `--max-calls` budget guard
31
+ * (default 900).
32
+ *
33
+ * Prompt corpus (spec §2, owner decision §11.1): EVEN split, 20 prompts per
34
+ * source × 5 sources — Hermes (lenny, raider, nano) + CC (the DevOps
35
+ * `infrastructure` project, the `aironic-marine` project). Saved to
36
+ * `data/prompts.json`, stable/reused verbatim once a valid v2 set exists.
37
+ *
38
+ * Judge: GLM-5.2 via z.ai — the INSTRUMENT only. It never picks candidates;
39
+ * retrieve() (LLM-free) does. A dedicated raw HTTP caller (NOT `LlmClient`) is
40
+ * used here on purpose: `LlmClient.completeReflect` bakes in a
41
+ * nightly-tolerant retry policy (30s/60s/120s, unlimited rate-limit patience)
42
+ * that conflicts with §6b's specific real-time batch policy (5/10/20/40/80s +
43
+ * jitter, 5 retries, a hard call budget). Implemented directly here rather
44
+ * than adding a second retry mode to `llm.ts` (out of scope for this eval,
45
+ * and another agent is concurrently working elsewhere in this repo).
46
+ *
47
+ * Honesty invariants (non-negotiable — mirror recall-sweep.ts + spec §7):
48
+ * - Snapshot opened READONLY via openSnapshot — never initDb.
49
+ * - noStrengthen: true on every retrieve() call.
50
+ * - Real bge-small-en-v1.5 embedder, embed-once + queryEmbedding reuse.
51
+ * - neverCalledEmbed self-check ABORTS the run if retrieve() ignores
52
+ * queryEmbedding (would invalidate every measured number).
53
+ * - GLM-5.2 is the JUDGE only, real prompts, no synthetic queries.
54
+ * - Production renderer (`formatIndexLine`/`memoryTitle`) imported from
55
+ * `recall-index.ts`, never reimplemented.
56
+ * - judge_error batches/units excluded from every denominator, reported
57
+ * separately.
58
+ *
59
+ * Run:
60
+ * npm run eval:relevance -- <snapshot.db> [prompts.json] [report.md] \
61
+ * [--judge-delay-ms=2000] [--max-calls=900] [--resume] \
62
+ * [--verdicts-json=path] [--verdicts-jsonl=path]
63
+ */
64
+ export {};