@echomem/mcp 1.4.7 → 1.4.9

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.
Files changed (46) hide show
  1. package/README.md +35 -9
  2. package/assets/canonical-scorer/README.md +18 -0
  3. package/assets/canonical-scorer/analyze-10-problems.mjs +857 -0
  4. package/assets/canonical-scorer/build-session-waste-dashboard.mjs +1628 -0
  5. package/assets/canonical-scorer/golden_anchors.mjs +83 -0
  6. package/assets/canonical-scorer/optimizable_detail.mjs +633 -0
  7. package/assets/hud/claude.svg +1 -0
  8. package/assets/hud/codex.svg +1 -0
  9. package/assets/hud/session-viewer.html +35 -0
  10. package/dist/city/chaos-to-clarity-pencil.html +582 -0
  11. package/dist/city/echo-ai-city-only.html +1126 -109
  12. package/dist/city/echo-ai-city-only.template.html +1126 -109
  13. package/dist/city/echo-face-cutout.png +0 -0
  14. package/dist/city/pencil-pie-generator.html +883 -0
  15. package/dist/city/pencil-webgl-landscape.html +1239 -0
  16. package/dist/city/spatial-fan-story.html +479 -0
  17. package/dist/codex-session-files.js +283 -0
  18. package/dist/codex-sync.js +7 -2
  19. package/dist/context-analysis/canonical-golden.js +47 -0
  20. package/dist/context-analysis/claude-native-canonical.js +1193 -0
  21. package/dist/context-analysis/vendored-canonical.js +793 -0
  22. package/dist/context-analysis/workspace-report.js +1838 -0
  23. package/dist/context-metrics/calculate.js +56 -0
  24. package/dist/context-metrics/model-limits.js +26 -0
  25. package/dist/context-metrics/types.js +1 -0
  26. package/dist/forensics-10-problems.js +7 -6
  27. package/dist/forensics.js +863 -132
  28. package/dist/hud/adapters.js +8 -4
  29. package/dist/hud/autostart.js +66 -0
  30. package/dist/hud/cli.js +31 -0
  31. package/dist/hud/electron-main.js +182 -19
  32. package/dist/hud/metric.js +13 -4
  33. package/dist/hud/monitor.js +171 -84
  34. package/dist/hud/preload.cjs +3 -0
  35. package/dist/hud/server.js +321 -4
  36. package/dist/hud/web.js +880 -270
  37. package/dist/index.js +122 -24
  38. package/dist/local-data-paths.js +87 -0
  39. package/dist/migrate.js +55 -29
  40. package/dist/report.js +101 -40
  41. package/dist/setup-page.js +4257 -245
  42. package/dist/setup-preview.js +245 -0
  43. package/dist/setup.js +786 -75
  44. package/dist/v1-contract.js +20 -2
  45. package/package.json +6 -4
  46. package/templates/echomem-recall.md +2 -2
@@ -0,0 +1,56 @@
1
+ export function calculateContextMetrics(input) {
2
+ const latestInputTokens = nonNegative(input.latestInputTokens);
3
+ const modelContextLimitTokens = optionalPositive(input.modelContextLimitTokens);
4
+ const currentResidentWasteTokens = nonNegative(input.currentResidentWasteTokens);
5
+ const currentResidentUsefulTokens = Math.max(0, latestInputTokens - currentResidentWasteTokens);
6
+ const newOutputThisTurnTokens = optionalNonNegative(input.newOutputThisTurnTokens);
7
+ const newWasteThisTurnTokens = optionalNonNegative(input.newWasteThisTurnTokens);
8
+ const contextFullnessPct = modelContextLimitTokens ? latestInputTokens / modelContextLimitTokens : undefined;
9
+ const noisePct = latestInputTokens > 0 ? currentResidentWasteTokens / latestInputTokens : 0;
10
+ const usefulPct = latestInputTokens > 0 ? currentResidentUsefulTokens / latestInputTokens : 0;
11
+ const projectedNextFullnessPct = modelContextLimitTokens && newOutputThisTurnTokens !== undefined
12
+ ? (latestInputTokens + newOutputThisTurnTokens) / modelContextLimitTokens
13
+ : undefined;
14
+ const healthScorePct = calculateHealthScorePct({
15
+ noisePct,
16
+ contextFullnessPct,
17
+ projectedNextFullnessPct,
18
+ });
19
+ return {
20
+ latestInputTokens,
21
+ modelContextLimitTokens,
22
+ currentResidentWasteTokens,
23
+ currentResidentUsefulTokens,
24
+ ...(newOutputThisTurnTokens !== undefined ? { newOutputThisTurnTokens } : {}),
25
+ ...(newWasteThisTurnTokens !== undefined ? { newWasteThisTurnTokens } : {}),
26
+ ...(contextFullnessPct !== undefined ? { contextFullnessPct } : {}),
27
+ noisePct,
28
+ usefulPct,
29
+ healthScorePct,
30
+ snr: currentResidentWasteTokens > 0 ? currentResidentUsefulTokens / currentResidentWasteTokens : null,
31
+ ...(projectedNextFullnessPct !== undefined ? { projectedNextFullnessPct } : {}),
32
+ };
33
+ }
34
+ function calculateHealthScorePct(input) {
35
+ const pressurePct = Math.max(input.contextFullnessPct ?? 0, input.projectedNextFullnessPct ?? 0);
36
+ // Noise is a direct quality loss. Fullness becomes a separate pressure penalty after 70% because
37
+ // clean-but-nearly-full sessions still become slow, brittle, or compaction-prone.
38
+ const fullnessPenaltyPct = pressurePct > 0.7
39
+ ? clamp01((pressurePct - 0.7) / 0.3) * 35
40
+ : 0;
41
+ return Math.max(0, Math.min(100, Math.round(100 - input.noisePct * 100 - fullnessPenaltyPct)));
42
+ }
43
+ function nonNegative(value) {
44
+ return Number.isFinite(value) ? Math.max(0, Math.round(value)) : 0;
45
+ }
46
+ function optionalNonNegative(value) {
47
+ return value === undefined ? undefined : nonNegative(value);
48
+ }
49
+ function optionalPositive(value) {
50
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
51
+ return null;
52
+ return Math.round(value);
53
+ }
54
+ function clamp01(value) {
55
+ return Math.max(0, Math.min(1, value));
56
+ }
@@ -0,0 +1,26 @@
1
+ const MODEL_CONTEXT_LIMITS = [
2
+ // Claude transcript logs do not carry context_window_size, so use the published family limit only
3
+ // as an explicit fallback. Codex logs should prefer their per-call model_context_window field.
4
+ { match: /^claude-(?:opus|sonnet|haiku|fable)-4/i, tokens: 200_000 },
5
+ ];
6
+ export function modelContextLimitTokensFor(model) {
7
+ const name = String(model || "").trim();
8
+ if (!name)
9
+ return null;
10
+ const found = MODEL_CONTEXT_LIMITS.find((entry) => entry.match.test(name));
11
+ return found?.tokens ?? null;
12
+ }
13
+ export function resolveModelContextLimit(params) {
14
+ const logged = positive(params.loggedLimitTokens);
15
+ if (logged)
16
+ return { tokens: logged, source: "logged" };
17
+ const fallback = modelContextLimitTokensFor(params.model);
18
+ if (fallback)
19
+ return { tokens: fallback, source: "model-default" };
20
+ return { tokens: null, source: "unavailable" };
21
+ }
22
+ function positive(value) {
23
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
24
+ return null;
25
+ return Math.round(value);
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -24,9 +24,10 @@
24
24
  *
25
25
  * All data stays local. No LLM calls. Transcripts never leave the machine.
26
26
  */
27
- import os from "node:os";
28
27
  import path from "node:path";
29
28
  import { eachLine, walk } from "./report.js";
29
+ import { discoverCodexSessionFiles } from "./codex-session-files.js";
30
+ import { resolveClaudeProjectsDir } from "./local-data-paths.js";
30
31
  // ---------------------------------------------------------------------------
31
32
  // Constants
32
33
  // ---------------------------------------------------------------------------
@@ -584,11 +585,11 @@ function aggregateResults(sessionResults, firstTs, lastTs) {
584
585
  // ---------------------------------------------------------------------------
585
586
  export function buildProblemReport(opts) {
586
587
  const sources = opts?.sources ?? ["codex", "claude"];
587
- const codexFiles = sources.includes("codex")
588
- ? walk(path.join(os.homedir(), ".codex", "sessions"), (p) => /rollout-.*\.jsonl$/.test(p), () => false).sort()
589
- : [];
590
- const claudeFiles = sources.includes("claude")
591
- ? walk(path.join(os.homedir(), ".claude", "projects"), (p) => p.endsWith(".jsonl"), (name) => name === "subagents" || name === "workflows").sort()
588
+ const codexDiscovery = sources.includes("codex") ? discoverCodexSessionFiles({ includeArchived: true }) : null;
589
+ const claudeRoot = sources.includes("claude") ? resolveClaudeProjectsDir() : null;
590
+ const codexFiles = codexDiscovery?.files.map((file) => file.path) ?? [];
591
+ const claudeFiles = claudeRoot
592
+ ? walk(claudeRoot, (p) => p.endsWith(".jsonl"), (name) => name === "subagents" || name === "workflows").sort()
592
593
  : [];
593
594
  const total = codexFiles.length + claudeFiles.length;
594
595
  let done = 0;