@stackmemoryai/stackmemory 1.12.0 → 1.14.0

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 (162) hide show
  1. package/LICENSE +131 -64
  2. package/README.md +3 -1
  3. package/bin/claude-sm +16 -1
  4. package/bin/claude-smd +16 -1
  5. package/bin/codex-smd +16 -1
  6. package/bin/gemini-sm +16 -1
  7. package/bin/hermes-sm +21 -0
  8. package/bin/hermes-smd +21 -0
  9. package/bin/opencode-sm +16 -1
  10. package/dist/src/cli/codex-sm.js +51 -11
  11. package/dist/src/cli/commands/brain.js +206 -0
  12. package/dist/src/cli/commands/company-os.js +184 -0
  13. package/dist/src/cli/commands/context.js +5 -0
  14. package/dist/src/cli/commands/operator.js +127 -0
  15. package/dist/src/cli/commands/orchestrate.js +2 -0
  16. package/dist/src/cli/commands/orchestrator.js +3 -2
  17. package/dist/src/cli/commands/patterns.js +254 -0
  18. package/dist/src/cli/commands/portal.js +161 -0
  19. package/dist/src/cli/commands/scaffold.js +92 -0
  20. package/dist/src/cli/commands/setup.js +1 -4
  21. package/dist/src/cli/commands/sync.js +253 -0
  22. package/dist/src/cli/commands/tasks.js +130 -1
  23. package/dist/src/cli/commands/vision.js +221 -0
  24. package/dist/src/cli/hermes-sm.js +224 -0
  25. package/dist/src/cli/index.js +15 -10
  26. package/dist/src/cli/utils/real-cli-bin.js +72 -0
  27. package/dist/src/core/brain/brain-store.js +187 -0
  28. package/dist/src/core/brain/brain-sync.js +193 -0
  29. package/dist/src/core/brain/index.js +78 -0
  30. package/dist/src/core/brain/types.js +10 -0
  31. package/dist/src/core/cache/token-estimator.js +24 -1
  32. package/dist/src/core/config/feature-flags.js +2 -6
  33. package/dist/src/core/context/frame-database.js +44 -0
  34. package/dist/src/core/context/recursive-context-manager.js +1 -1
  35. package/dist/src/core/context/rehydration.js +2 -1
  36. package/dist/src/core/database/sqlite-adapter.js +14 -1
  37. package/dist/src/core/models/model-router.js +33 -1
  38. package/dist/src/core/models/provider-pricing.js +58 -4
  39. package/dist/src/core/patterns/index.js +22 -0
  40. package/dist/src/core/patterns/pattern-applier.js +39 -0
  41. package/dist/src/core/patterns/pattern-observer.js +157 -0
  42. package/dist/src/core/patterns/pattern-store.js +259 -0
  43. package/dist/src/core/patterns/types.js +19 -0
  44. package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
  45. package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
  46. package/dist/src/core/skill-packs/types.js +14 -1
  47. package/dist/src/core/storage/cloud-sync-manager.js +116 -0
  48. package/dist/src/core/storage/cloud-sync.js +574 -0
  49. package/dist/src/core/storage/two-tier-storage.js +5 -1
  50. package/dist/src/core/tasks/master-tasks-template.js +43 -0
  51. package/dist/src/core/tasks/md-task-parser.js +138 -0
  52. package/dist/src/core/vision/index.js +27 -0
  53. package/dist/src/core/vision/signals.js +79 -0
  54. package/dist/src/core/vision/types.js +22 -0
  55. package/dist/src/core/vision/vision-file.js +146 -0
  56. package/dist/src/core/vision/vision-loop.js +220 -0
  57. package/dist/src/core/wiki/wiki-compiler.js +103 -1
  58. package/dist/src/daemon/daemon-config.js +45 -0
  59. package/dist/src/daemon/services/desire-path-service.js +566 -0
  60. package/dist/src/daemon/services/research-stream-service.js +320 -0
  61. package/dist/src/daemon/services/telemetry-service.js +192 -0
  62. package/dist/src/daemon/unified-daemon.js +28 -1
  63. package/dist/src/features/browser/cli-browser-agent.js +417 -0
  64. package/dist/src/features/browser/stagehand-workflows.js +578 -0
  65. package/dist/src/features/operator/adapter-factory.js +62 -0
  66. package/dist/src/features/operator/browser-adapter.js +109 -0
  67. package/dist/src/features/operator/desktop-adapter.js +125 -0
  68. package/dist/src/features/operator/index.js +39 -0
  69. package/dist/src/features/operator/llm-decision.js +137 -0
  70. package/dist/src/features/operator/operator-logger.js +92 -0
  71. package/dist/src/features/operator/overnight-runner.js +327 -0
  72. package/dist/src/features/operator/screen-adapter.js +91 -0
  73. package/dist/src/features/operator/session-manager.js +127 -0
  74. package/dist/src/features/operator/state-machine.js +227 -0
  75. package/dist/src/features/operator/task-queue.js +81 -0
  76. package/dist/src/features/portal/index.js +26 -0
  77. package/dist/src/features/portal/server.js +240 -0
  78. package/dist/src/features/portal/types.js +14 -0
  79. package/dist/src/features/portal/ui.js +195 -0
  80. package/dist/src/features/tasks/task-aware-context.js +2 -1
  81. package/dist/src/features/tui/simple-monitor.js +0 -23
  82. package/dist/src/features/tui/swarm-monitor.js +8 -66
  83. package/dist/src/{integrations/diffmem/index.js → features/web/client/hooks/use-socket.js} +6 -5
  84. package/dist/src/features/web/client/lib/utils.js +12 -0
  85. package/dist/src/features/web/client/stores/session-store.js +12 -0
  86. package/dist/src/features/web/server/gcp-billing.js +76 -0
  87. package/dist/src/features/web/server/index.js +10 -0
  88. package/dist/src/features/web/server/spend-calculator.js +228 -0
  89. package/dist/src/hooks/schemas.js +4 -1
  90. package/dist/src/integrations/anthropic/client.js +3 -2
  91. package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
  92. package/dist/src/integrations/claude-code/subagent-client.js +218 -11
  93. package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
  94. package/dist/src/integrations/linear/webhook-retry.js +196 -0
  95. package/dist/src/integrations/linear/webhook-server.js +18 -22
  96. package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
  97. package/dist/src/integrations/mcp/handlers/index.js +27 -52
  98. package/dist/src/integrations/mcp/server.js +122 -335
  99. package/dist/src/integrations/mcp/tool-alias-registry.js +0 -73
  100. package/dist/src/integrations/mcp/tool-definitions.js +111 -510
  101. package/dist/src/mcp/stackmemory-mcp-server.js +404 -379
  102. package/dist/src/orchestrators/multimodal/determinism.js +2 -1
  103. package/dist/src/orchestrators/multimodal/harness.js +2 -1
  104. package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
  105. package/dist/src/utils/process-cleanup.js +1 -7
  106. package/docs/README.md +42 -0
  107. package/docs/guides/README_INSTALL.md +208 -0
  108. package/package.json +18 -9
  109. package/scripts/claude-code-wrapper.sh +11 -0
  110. package/scripts/claude-sm-setup.sh +12 -1
  111. package/scripts/codex-wrapper.sh +11 -0
  112. package/scripts/git-hooks/branch-context-manager.sh +11 -0
  113. package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
  114. package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
  115. package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
  116. package/scripts/hooks/cleanup-shell.sh +12 -1
  117. package/scripts/hooks/task-complete.sh +12 -1
  118. package/scripts/install-code-execution-hooks.sh +12 -1
  119. package/scripts/install-sweep-hook.sh +12 -0
  120. package/scripts/install.sh +11 -0
  121. package/scripts/opencode-wrapper.sh +11 -0
  122. package/scripts/portal/cloud-init.yaml +69 -0
  123. package/scripts/portal/setup.sh +69 -0
  124. package/scripts/portal/stackmemory-portal.service +34 -0
  125. package/scripts/setup-claude-integration.sh +12 -1
  126. package/scripts/smoke-init-db.sh +23 -0
  127. package/scripts/stackmemory-daemon.sh +11 -0
  128. package/scripts/verify-dist.cjs +11 -4
  129. package/dist/src/cli/commands/ralph.js +0 -1053
  130. package/dist/src/hooks/diffmem-hooks.js +0 -376
  131. package/dist/src/integrations/diffmem/client.js +0 -208
  132. package/dist/src/integrations/diffmem/config.js +0 -14
  133. package/dist/src/integrations/greptile/client.js +0 -101
  134. package/dist/src/integrations/greptile/config.js +0 -14
  135. package/dist/src/integrations/greptile/index.js +0 -11
  136. package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +0 -188
  137. package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
  138. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
  139. package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
  140. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
  141. package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
  142. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -354
  143. package/dist/src/integrations/ralph/index.js +0 -17
  144. package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -416
  145. package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
  146. package/dist/src/integrations/ralph/loopmax.js +0 -488
  147. package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
  148. package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
  149. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
  150. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
  151. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
  152. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
  153. package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
  154. package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
  155. package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
  156. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
  157. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1005
  158. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
  159. package/scripts/ralph-loop-implementation.js +0 -404
  160. /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
  161. /package/dist/src/{integrations/greptile → features/operator}/types.js +0 -0
  162. /package/dist/src/{integrations/ralph/types.js → features/web/client/next-env.d.js} +0 -0
@@ -38,7 +38,15 @@ const MODEL_TOKEN_LIMITS = {
38
38
  "THUDM/glm-4-9b-chat": 128e3,
39
39
  // Moonshot (Kimi)
40
40
  "kimi-k2.6": 256e3,
41
- "kimi-k2.5": 256e3
41
+ "kimi-k2.5": 256e3,
42
+ // xAI (Grok)
43
+ "grok-4.1-fast": 131072,
44
+ "grok-4.3": 131072,
45
+ "grok-4": 131072,
46
+ // DeepSeek
47
+ "deepseek-v4-flash": 131072,
48
+ "deepseek-v4": 131072,
49
+ "deepseek-v4-pro": 131072
42
50
  };
43
51
  const DEFAULT_MODEL_TOKEN_LIMIT = 2e5;
44
52
  function getModelTokenLimit(model) {
@@ -100,6 +108,18 @@ const DEFAULT_CONFIG = {
100
108
  baseUrl: "https://api.moonshot.ai/v1",
101
109
  apiKeyEnv: "MOONSHOT_API_KEY"
102
110
  },
111
+ xai: {
112
+ provider: "xai",
113
+ model: "grok-4.1-fast",
114
+ baseUrl: "https://api.x.ai/v1",
115
+ apiKeyEnv: "XAI_API_KEY"
116
+ },
117
+ deepseek: {
118
+ provider: "deepseek",
119
+ model: "deepseek-v4-flash",
120
+ baseUrl: "https://api.deepseek.com/v1",
121
+ apiKeyEnv: "DEEPSEEK_API_KEY"
122
+ },
103
123
  "anthropic-batch": {
104
124
  provider: "anthropic-batch",
105
125
  model: "claude-sonnet-4-5-20250929",
@@ -251,6 +271,18 @@ const FALLBACK_CHAIN = [
251
271
  "anthropic"
252
272
  ];
253
273
  const CHEAP_PROVIDERS = [
274
+ {
275
+ provider: "deepseek",
276
+ model: "deepseek-v4-flash",
277
+ apiKeyEnv: "DEEPSEEK_API_KEY",
278
+ baseUrl: "https://api.deepseek.com/v1"
279
+ },
280
+ {
281
+ provider: "xai",
282
+ model: "grok-4.1-fast",
283
+ apiKeyEnv: "XAI_API_KEY",
284
+ baseUrl: "https://api.x.ai/v1"
285
+ },
254
286
  {
255
287
  provider: "moonshot",
256
288
  model: "kimi-k2.6",
@@ -3,7 +3,28 @@ import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  const MODEL_PRICING = {
6
- // Anthropic (direct API)
6
+ // Anthropic (direct API) — Opus 4.x share one price; 1M context, no
7
+ // long-context premium. Sourced platform.claude.com 2026-05-26.
8
+ "anthropic/claude-opus-4-8": {
9
+ inputPer1M: 5,
10
+ outputPer1M: 25,
11
+ source: "platform.claude.com"
12
+ },
13
+ "anthropic/claude-opus-4-7": {
14
+ inputPer1M: 5,
15
+ outputPer1M: 25,
16
+ source: "platform.claude.com"
17
+ },
18
+ "anthropic/claude-opus-4-6": {
19
+ inputPer1M: 5,
20
+ outputPer1M: 25,
21
+ source: "platform.claude.com"
22
+ },
23
+ "anthropic/claude-sonnet-4-6": {
24
+ inputPer1M: 3,
25
+ outputPer1M: 15,
26
+ source: "platform.claude.com"
27
+ },
7
28
  "anthropic/claude-sonnet-4-5-20250929": {
8
29
  inputPer1M: 3,
9
30
  outputPer1M: 15,
@@ -15,9 +36,9 @@ const MODEL_PRICING = {
15
36
  source: "anthropic.com"
16
37
  },
17
38
  "anthropic/claude-haiku-4-5-20251001": {
18
- inputPer1M: 0.8,
19
- outputPer1M: 4,
20
- source: "anthropic.com"
39
+ inputPer1M: 1,
40
+ outputPer1M: 5,
41
+ source: "platform.claude.com"
21
42
  },
22
43
  // OpenAI (direct API)
23
44
  "openai/gpt-4o": {
@@ -56,8 +77,41 @@ function formatCost(usd) {
56
77
  if (usd < 0.01) return `$${usd.toFixed(6)}`;
57
78
  return `$${usd.toFixed(4)}`;
58
79
  }
80
+ const MAX_PLAN_DISCOUNT_RAMP = {
81
+ start: process.env.STACKMEMORY_COST_RAMP_START ?? "2026-06-06",
82
+ end: process.env.STACKMEMORY_COST_RAMP_END ?? "2026-09-06",
83
+ startMultiplier: Number(
84
+ process.env.STACKMEMORY_COST_RAMP_START_MULTIPLIER ?? "0.2"
85
+ ),
86
+ endMultiplier: 1
87
+ };
88
+ function effectiveSpendMultiplier(date = /* @__PURE__ */ new Date(), ramp = MAX_PLAN_DISCOUNT_RAMP) {
89
+ const start = Date.parse(ramp.start);
90
+ const end = Date.parse(ramp.end);
91
+ const now = date.getTime();
92
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) {
93
+ return ramp.endMultiplier;
94
+ }
95
+ if (now <= start) return ramp.startMultiplier;
96
+ if (now >= end) return ramp.endMultiplier;
97
+ const progress = (now - start) / (end - start);
98
+ return ramp.startMultiplier + progress * (ramp.endMultiplier - ramp.startMultiplier);
99
+ }
100
+ function effectiveCost(provider, model, inputTokens, outputTokens, date = /* @__PURE__ */ new Date()) {
101
+ const list = calculateCost(provider, model, inputTokens, outputTokens);
102
+ if (!list) return null;
103
+ const multiplier = effectiveSpendMultiplier(date);
104
+ return {
105
+ listCost: list.totalCost,
106
+ effectiveCost: list.totalCost * multiplier,
107
+ multiplier
108
+ };
109
+ }
59
110
  export {
111
+ MAX_PLAN_DISCOUNT_RAMP,
60
112
  MODEL_PRICING,
61
113
  calculateCost,
114
+ effectiveCost,
115
+ effectiveSpendMultiplier,
62
116
  formatCost
63
117
  };
@@ -0,0 +1,22 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import {
6
+ computeConfidence,
7
+ CONFIDENCE_DECAY_PER_WEEK,
8
+ CONFIDENCE_BOOST_PER_OBSERVATION,
9
+ CONFIDENCE_PENALTY_PER_CONTRADICTION
10
+ } from "./types.js";
11
+ import { PatternStore } from "./pattern-store.js";
12
+ import { PatternObserver } from "./pattern-observer.js";
13
+ import { PatternApplier } from "./pattern-applier.js";
14
+ export {
15
+ CONFIDENCE_BOOST_PER_OBSERVATION,
16
+ CONFIDENCE_DECAY_PER_WEEK,
17
+ CONFIDENCE_PENALTY_PER_CONTRADICTION,
18
+ PatternApplier,
19
+ PatternObserver,
20
+ PatternStore,
21
+ computeConfidence
22
+ };
@@ -0,0 +1,39 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ class PatternApplier {
6
+ constructor(store) {
7
+ this.store = store;
8
+ }
9
+ /**
10
+ * Find and format patterns relevant to a task.
11
+ * Returns a markdown block to inject into context.
12
+ */
13
+ apply(taskDescription, projectId) {
14
+ const patterns = this.store.search(taskDescription, projectId);
15
+ if (patterns.length === 0) return "";
16
+ for (const p of patterns) {
17
+ this.store.recordMatch(p.id);
18
+ }
19
+ const lines = ["## Learned Patterns", ""];
20
+ for (const p of patterns) {
21
+ const bar = this.confidenceBar(p.confidence);
22
+ lines.push(
23
+ `- ${bar} **${p.trigger}** \u2192 ${p.action} _(${p.domain}, ${p.observationCount} obs)_`
24
+ );
25
+ }
26
+ return lines.join("\n");
27
+ }
28
+ /** Get patterns as structured data (for MCP tool) */
29
+ query(taskDescription, projectId) {
30
+ return this.store.search(taskDescription, projectId);
31
+ }
32
+ confidenceBar(confidence) {
33
+ const filled = Math.round(confidence * 5);
34
+ return "\u2588".repeat(filled) + "\u2591".repeat(5 - filled);
35
+ }
36
+ }
37
+ export {
38
+ PatternApplier
39
+ };
@@ -0,0 +1,157 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ class PatternObserver {
6
+ constructor(store) {
7
+ this.store = store;
8
+ }
9
+ /**
10
+ * Analyze a session's trace events and extract patterns.
11
+ * Call at session_end with the session's trace events.
12
+ */
13
+ observe(events, projectId) {
14
+ if (events.length < 3) return [];
15
+ const learned = [];
16
+ const sequences = this.findRepeatedSequences(events);
17
+ for (const seq of sequences) {
18
+ const id = this.sequenceId(seq.operations);
19
+ const existing = this.store.get(id);
20
+ if (existing) {
21
+ this.store.reinforce(id, `Session observation (${seq.count}x)`);
22
+ } else {
23
+ this.store.create({
24
+ id,
25
+ domain: this.inferDomain(seq.operations),
26
+ trigger: `When performing a ${this.describeIntent(seq.operations)} workflow`,
27
+ action: `Follow sequence: ${seq.operations.join(" \u2192 ")}`,
28
+ evidence: [`Observed ${seq.count}x in session`],
29
+ scope: projectId ? "project" : "global",
30
+ projectId,
31
+ source: "observed"
32
+ });
33
+ }
34
+ learned.push(id);
35
+ }
36
+ const errorFixes = this.findErrorFixPairs(events);
37
+ for (const { error, fix } of errorFixes) {
38
+ const id = `fix-${this.slugify(error.operation)}-${this.slugify(fix.operation)}`;
39
+ const existing = this.store.get(id);
40
+ if (existing) {
41
+ this.store.reinforce(
42
+ id,
43
+ `Error\u2192fix: ${error.error} \u2192 ${fix.operation}`
44
+ );
45
+ } else {
46
+ this.store.create({
47
+ id,
48
+ domain: "debugging",
49
+ trigger: `When encountering error in ${error.operation}`,
50
+ action: `Resolve with ${fix.operation}: ${this.summarizeInputs(fix)}`,
51
+ evidence: [`Error: ${error.error?.slice(0, 200)}`],
52
+ scope: projectId ? "project" : "global",
53
+ projectId,
54
+ source: "observed"
55
+ });
56
+ }
57
+ learned.push(id);
58
+ }
59
+ const preferences = this.findToolPreferences(events);
60
+ for (const { before, after, count } of preferences) {
61
+ const id = `prefer-${this.slugify(before)}-before-${this.slugify(after)}`;
62
+ const existing = this.store.get(id);
63
+ if (existing) {
64
+ this.store.reinforce(id, `${before}\u2192${after} (${count}x)`);
65
+ } else if (count >= 3) {
66
+ this.store.create({
67
+ id,
68
+ domain: "workflow",
69
+ trigger: `When about to use ${after}`,
70
+ action: `Use ${before} first`,
71
+ evidence: [`Observed ${count}x: ${before} always precedes ${after}`],
72
+ scope: "global",
73
+ source: "observed"
74
+ });
75
+ learned.push(id);
76
+ }
77
+ }
78
+ return learned;
79
+ }
80
+ // ── Sequence Detection ────────────────────────────────
81
+ findRepeatedSequences(events) {
82
+ const ops = events.map((e) => e.operation);
83
+ const sequences = /* @__PURE__ */ new Map();
84
+ for (let windowSize = 2; windowSize <= 4; windowSize++) {
85
+ for (let i = 0; i <= ops.length - windowSize; i++) {
86
+ const seq = ops.slice(i, i + windowSize);
87
+ const key = seq.join("\u2192");
88
+ const existing = sequences.get(key);
89
+ if (existing) {
90
+ existing.count++;
91
+ } else {
92
+ sequences.set(key, { operations: seq, count: 1 });
93
+ }
94
+ }
95
+ }
96
+ return Array.from(sequences.values()).filter((s) => s.count >= 3);
97
+ }
98
+ // ── Error→Fix Detection ───────────────────────────────
99
+ findErrorFixPairs(events) {
100
+ const pairs = [];
101
+ for (let i = 0; i < events.length - 1; i++) {
102
+ const event = events[i];
103
+ if (!event.error) continue;
104
+ for (let j = i + 1; j < Math.min(i + 4, events.length); j++) {
105
+ const next = events[j];
106
+ if (!next.error) {
107
+ pairs.push({ error: event, fix: next });
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ return pairs;
113
+ }
114
+ // ── Tool Preference Detection ─────────────────────────
115
+ findToolPreferences(events) {
116
+ const pairs = /* @__PURE__ */ new Map();
117
+ const ops = events.map((e) => e.operation);
118
+ for (let i = 0; i < ops.length - 1; i++) {
119
+ const key = `${ops[i]}\u2192${ops[i + 1]}`;
120
+ pairs.set(key, (pairs.get(key) ?? 0) + 1);
121
+ }
122
+ return Array.from(pairs.entries()).filter(([, count]) => count >= 3).map(([key, count]) => {
123
+ const [before, after] = key.split("\u2192");
124
+ return { before, after, count };
125
+ });
126
+ }
127
+ // ── Helpers ───────────────────────────────────────────
128
+ sequenceId(ops) {
129
+ return `seq-${ops.map((o) => this.slugify(o)).join("-")}`;
130
+ }
131
+ slugify(s) {
132
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
133
+ }
134
+ inferDomain(ops) {
135
+ const joined = ops.join(" ").toLowerCase();
136
+ if (/test|vitest|jest/.test(joined)) return "testing";
137
+ if (/git|commit|branch|push/.test(joined)) return "git";
138
+ if (/grep|glob|read|search/.test(joined)) return "workflow";
139
+ if (/edit|write/.test(joined)) return "code-style";
140
+ if (/security|auth|token/.test(joined)) return "security";
141
+ return "general";
142
+ }
143
+ describeIntent(ops) {
144
+ const unique = [...new Set(ops)];
145
+ if (unique.length <= 2) return unique.join(" + ");
146
+ return `${unique[0]} \u2192 ${unique[unique.length - 1]}`;
147
+ }
148
+ summarizeInputs(event) {
149
+ const inputs = event.inputs;
150
+ if (!inputs || typeof inputs !== "object") return "";
151
+ const keys = Object.keys(inputs).slice(0, 3);
152
+ return keys.join(", ");
153
+ }
154
+ }
155
+ export {
156
+ PatternObserver
157
+ };
@@ -0,0 +1,259 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import {
6
+ computeConfidence,
7
+ CONFIDENCE_DECAY_PER_WEEK,
8
+ CONFIDENCE_BOOST_PER_OBSERVATION
9
+ } from "./types.js";
10
+ class PatternStore {
11
+ constructor(db) {
12
+ this.db = db;
13
+ }
14
+ /** Create a new pattern */
15
+ create(input) {
16
+ const now = Date.now();
17
+ const confidence = input.confidence ?? computeConfidence(1);
18
+ this.db.prepare(
19
+ `
20
+ INSERT INTO patterns (id, domain, trigger, action, evidence, confidence, observation_count, scope, project_id, status, source, created_at, updated_at)
21
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
22
+ `
23
+ ).run(
24
+ input.id,
25
+ input.domain,
26
+ input.trigger,
27
+ input.action,
28
+ JSON.stringify(input.evidence ?? []),
29
+ confidence,
30
+ 1,
31
+ input.scope ?? "project",
32
+ input.projectId ?? null,
33
+ "pending",
34
+ input.source ?? "manual",
35
+ now,
36
+ now
37
+ );
38
+ return this.get(input.id);
39
+ }
40
+ /** Get a pattern by ID */
41
+ get(id) {
42
+ const row = this.db.prepare("SELECT * FROM patterns WHERE id = ?").get(id);
43
+ return row ? this.toPattern(row) : void 0;
44
+ }
45
+ /** List patterns with filtering */
46
+ list(query = {}) {
47
+ const conditions = [];
48
+ const params = [];
49
+ if (query.domain) {
50
+ conditions.push("domain = ?");
51
+ params.push(query.domain);
52
+ }
53
+ if (query.status) {
54
+ conditions.push("status = ?");
55
+ params.push(query.status);
56
+ }
57
+ if (query.scope) {
58
+ conditions.push("scope = ?");
59
+ params.push(query.scope);
60
+ }
61
+ if (query.projectId) {
62
+ conditions.push("(project_id = ? OR scope = 'global')");
63
+ params.push(query.projectId);
64
+ }
65
+ if (query.minConfidence !== void 0) {
66
+ conditions.push("confidence >= ?");
67
+ params.push(query.minConfidence);
68
+ }
69
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
70
+ const limit = query.limit ?? 100;
71
+ const rows = this.db.prepare(
72
+ `SELECT * FROM patterns ${where} ORDER BY confidence DESC, updated_at DESC LIMIT ?`
73
+ ).all(...params, limit);
74
+ return rows.map((r) => this.toPattern(r));
75
+ }
76
+ /** Record an observation that reinforces a pattern */
77
+ reinforce(id, evidence) {
78
+ const pattern = this.get(id);
79
+ if (!pattern) return;
80
+ const newCount = pattern.observationCount + 1;
81
+ const baseConfidence = computeConfidence(newCount);
82
+ const boosted = Math.min(
83
+ 1,
84
+ baseConfidence + CONFIDENCE_BOOST_PER_OBSERVATION
85
+ );
86
+ const newEvidence = [...pattern.evidence, evidence].slice(-20);
87
+ this.db.prepare(
88
+ `
89
+ UPDATE patterns
90
+ SET observation_count = ?, confidence = ?, evidence = ?, updated_at = ?, status = CASE WHEN status = 'pending' AND ? >= 0.5 THEN 'active' ELSE status END
91
+ WHERE id = ?
92
+ `
93
+ ).run(
94
+ newCount,
95
+ boosted,
96
+ JSON.stringify(newEvidence),
97
+ Date.now(),
98
+ boosted,
99
+ id
100
+ );
101
+ }
102
+ /** Apply weekly confidence decay to all patterns */
103
+ applyDecay() {
104
+ const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3;
105
+ const result = this.db.prepare(
106
+ `
107
+ UPDATE patterns
108
+ SET confidence = MAX(0.05, confidence - ?),
109
+ updated_at = ?
110
+ WHERE updated_at < ? AND status != 'archived'
111
+ `
112
+ ).run(CONFIDENCE_DECAY_PER_WEEK, Date.now(), oneWeekAgo);
113
+ return result.changes;
114
+ }
115
+ /** Mark a pattern as matched during retrieval */
116
+ recordMatch(id) {
117
+ this.db.prepare("UPDATE patterns SET last_matched_at = ? WHERE id = ?").run(Date.now(), id);
118
+ }
119
+ /** Activate a pending pattern */
120
+ activate(id) {
121
+ this.db.prepare(
122
+ "UPDATE patterns SET status = 'active', updated_at = ? WHERE id = ?"
123
+ ).run(Date.now(), id);
124
+ }
125
+ /** Archive a pattern */
126
+ archive(id, supersededBy) {
127
+ this.db.prepare(
128
+ "UPDATE patterns SET status = 'archived', superseded_by = ?, updated_at = ? WHERE id = ?"
129
+ ).run(supersededBy ?? null, Date.now(), id);
130
+ }
131
+ /** Prune old pending patterns that never got promoted */
132
+ prune(maxAgeDays = 30) {
133
+ const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
134
+ const result = this.db.prepare(
135
+ "DELETE FROM patterns WHERE status = 'pending' AND created_at < ?"
136
+ ).run(cutoff);
137
+ return result.changes;
138
+ }
139
+ /** Get aggregate stats */
140
+ stats() {
141
+ const all = this.list({ limit: 1e3 });
142
+ const byDomain = {};
143
+ const byStatus = {};
144
+ let totalConfidence = 0;
145
+ for (const p of all) {
146
+ byDomain[p.domain] = (byDomain[p.domain] ?? 0) + 1;
147
+ byStatus[p.status] = (byStatus[p.status] ?? 0) + 1;
148
+ totalConfidence += p.confidence;
149
+ }
150
+ const topPatterns = all.filter((p) => p.status === "active").slice(0, 10).map((p) => ({ id: p.id, confidence: p.confidence, trigger: p.trigger }));
151
+ return {
152
+ total: all.length,
153
+ byDomain,
154
+ byStatus,
155
+ avgConfidence: all.length > 0 ? totalConfidence / all.length : 0,
156
+ topPatterns
157
+ };
158
+ }
159
+ /** Promote a project-scoped pattern to global */
160
+ promote(id) {
161
+ this.db.prepare(
162
+ "UPDATE patterns SET scope = 'global', project_id = NULL, updated_at = ? WHERE id = ?"
163
+ ).run(Date.now(), id);
164
+ }
165
+ /** List distinct project IDs with pattern counts */
166
+ projects() {
167
+ const rows = this.db.prepare(
168
+ `
169
+ SELECT project_id, COUNT(*) as count, AVG(confidence) as avg_confidence
170
+ FROM patterns
171
+ WHERE project_id IS NOT NULL AND status != 'archived'
172
+ GROUP BY project_id
173
+ ORDER BY count DESC
174
+ `
175
+ ).all();
176
+ return rows.map((r) => ({
177
+ projectId: r.project_id,
178
+ count: r.count,
179
+ avgConfidence: r.avg_confidence
180
+ }));
181
+ }
182
+ /** Find patterns that appear in 2+ projects (promotion candidates) */
183
+ promotionCandidates(minConfidence = 0.7) {
184
+ const rows = this.db.prepare(
185
+ `
186
+ SELECT p1.* FROM patterns p1
187
+ WHERE p1.scope = 'project'
188
+ AND p1.confidence >= ?
189
+ AND p1.status = 'active'
190
+ AND EXISTS (
191
+ SELECT 1 FROM patterns p2
192
+ WHERE p2.trigger = p1.trigger
193
+ AND p2.action = p1.action
194
+ AND p2.project_id != p1.project_id
195
+ AND p2.status = 'active'
196
+ )
197
+ ORDER BY p1.confidence DESC
198
+ `
199
+ ).all(minConfidence);
200
+ return rows.map((r) => this.toPattern(r));
201
+ }
202
+ /** Find clusters of related patterns (for evolve) */
203
+ findClusters(minSize = 2) {
204
+ const active = this.list({ status: "active", limit: 500 });
205
+ const byDomain = /* @__PURE__ */ new Map();
206
+ for (const p of active) {
207
+ const list = byDomain.get(p.domain) ?? [];
208
+ list.push(p);
209
+ byDomain.set(p.domain, list);
210
+ }
211
+ return Array.from(byDomain.entries()).filter(([, patterns]) => patterns.length >= minSize).map(([domain, patterns]) => ({
212
+ domain,
213
+ patterns: patterns.sort((a, b) => b.confidence - a.confidence)
214
+ }));
215
+ }
216
+ /** Find patterns relevant to a query string */
217
+ search(query, projectId) {
218
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
219
+ if (words.length === 0) return [];
220
+ const candidates = this.list({
221
+ status: "active",
222
+ projectId,
223
+ minConfidence: 0.3,
224
+ limit: 200
225
+ });
226
+ const scored = candidates.map((p) => {
227
+ const text = `${p.trigger} ${p.action} ${p.domain}`.toLowerCase();
228
+ const hits = words.filter((w) => text.includes(w)).length;
229
+ const score = hits / words.length;
230
+ return { pattern: p, score };
231
+ });
232
+ return scored.filter((s) => s.score > 0.2).sort(
233
+ (a, b) => b.score * b.pattern.confidence - a.score * a.pattern.confidence
234
+ ).slice(0, 10).map((s) => s.pattern);
235
+ }
236
+ // ── Private ───────────────────────────────────────────
237
+ toPattern(row) {
238
+ return {
239
+ id: row.id,
240
+ domain: row.domain,
241
+ trigger: row.trigger,
242
+ action: row.action,
243
+ evidence: JSON.parse(row.evidence || "[]"),
244
+ confidence: row.confidence,
245
+ observationCount: row.observation_count,
246
+ scope: row.scope,
247
+ projectId: row.project_id,
248
+ status: row.status,
249
+ source: row.source,
250
+ createdAt: row.created_at,
251
+ updatedAt: row.updated_at,
252
+ lastMatchedAt: row.last_matched_at,
253
+ supersededBy: row.superseded_by
254
+ };
255
+ }
256
+ }
257
+ export {
258
+ PatternStore
259
+ };
@@ -0,0 +1,19 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ function computeConfidence(observationCount) {
6
+ if (observationCount >= 11) return 0.85;
7
+ if (observationCount >= 6) return 0.7;
8
+ if (observationCount >= 3) return 0.5;
9
+ return 0.3;
10
+ }
11
+ const CONFIDENCE_DECAY_PER_WEEK = 0.02;
12
+ const CONFIDENCE_BOOST_PER_OBSERVATION = 0.05;
13
+ const CONFIDENCE_PENALTY_PER_CONTRADICTION = 0.1;
14
+ export {
15
+ CONFIDENCE_BOOST_PER_OBSERVATION,
16
+ CONFIDENCE_DECAY_PER_WEEK,
17
+ CONFIDENCE_PENALTY_PER_CONTRADICTION,
18
+ computeConfidence
19
+ };
@@ -8,6 +8,7 @@ import {
8
8
  DEFAULT_RETRIEVAL_CONFIG
9
9
  } from "./types.js";
10
10
  import { logger } from "../monitoring/logger.js";
11
+ import { estimateTokens } from "../cache/token-estimator.js";
11
12
  import { LazyContextLoader } from "../performance/lazy-context-loader.js";
12
13
  import { ContextCache } from "../performance/context-cache.js";
13
14
  import { createLLMProvider } from "./llm-provider.js";
@@ -113,11 +114,11 @@ class HeuristicAnalyzer {
113
114
  let tokens = 50;
114
115
  tokens += frame.eventCount * 30;
115
116
  tokens += frame.anchorCount * 40;
116
- if (frame.digestPreview) tokens += frame.digestPreview.length / 4;
117
+ if (frame.digestPreview) tokens += estimateTokens(frame.digestPreview);
117
118
  return Math.floor(tokens);
118
119
  }
119
120
  estimateSummaryTokens(summary) {
120
- return Math.floor(JSON.stringify(summary).length / 4);
121
+ return estimateTokens(JSON.stringify(summary));
121
122
  }
122
123
  assessQueryComplexity(query, parsedQuery) {
123
124
  const wordCount = query.split(/\s+/).length;
@@ -428,8 +429,8 @@ Respond with only the JSON object, no other text.`;
428
429
  })),
429
430
  metadata: {
430
431
  analysisTimeMs: 0,
431
- summaryTokens: Math.floor(
432
- JSON.stringify(request.compressedSummary).length / 4
432
+ summaryTokens: estimateTokens(
433
+ JSON.stringify(request.compressedSummary)
433
434
  ),
434
435
  queryComplexity: this.assessQueryComplexity(request.currentQuery),
435
436
  matchedPatterns: [],