@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.
- package/LICENSE +131 -64
- package/README.md +3 -1
- package/bin/claude-sm +16 -1
- package/bin/claude-smd +16 -1
- package/bin/codex-smd +16 -1
- package/bin/gemini-sm +16 -1
- package/bin/hermes-sm +21 -0
- package/bin/hermes-smd +21 -0
- package/bin/opencode-sm +16 -1
- package/dist/src/cli/codex-sm.js +51 -11
- package/dist/src/cli/commands/brain.js +206 -0
- package/dist/src/cli/commands/company-os.js +184 -0
- package/dist/src/cli/commands/context.js +5 -0
- package/dist/src/cli/commands/operator.js +127 -0
- package/dist/src/cli/commands/orchestrate.js +2 -0
- package/dist/src/cli/commands/orchestrator.js +3 -2
- package/dist/src/cli/commands/patterns.js +254 -0
- package/dist/src/cli/commands/portal.js +161 -0
- package/dist/src/cli/commands/scaffold.js +92 -0
- package/dist/src/cli/commands/setup.js +1 -4
- package/dist/src/cli/commands/sync.js +253 -0
- package/dist/src/cli/commands/tasks.js +130 -1
- package/dist/src/cli/commands/vision.js +221 -0
- package/dist/src/cli/hermes-sm.js +224 -0
- package/dist/src/cli/index.js +15 -10
- package/dist/src/cli/utils/real-cli-bin.js +72 -0
- package/dist/src/core/brain/brain-store.js +187 -0
- package/dist/src/core/brain/brain-sync.js +193 -0
- package/dist/src/core/brain/index.js +78 -0
- package/dist/src/core/brain/types.js +10 -0
- package/dist/src/core/cache/token-estimator.js +24 -1
- package/dist/src/core/config/feature-flags.js +2 -6
- package/dist/src/core/context/frame-database.js +44 -0
- package/dist/src/core/context/recursive-context-manager.js +1 -1
- package/dist/src/core/context/rehydration.js +2 -1
- package/dist/src/core/database/sqlite-adapter.js +14 -1
- package/dist/src/core/models/model-router.js +33 -1
- package/dist/src/core/models/provider-pricing.js +58 -4
- package/dist/src/core/patterns/index.js +22 -0
- package/dist/src/core/patterns/pattern-applier.js +39 -0
- package/dist/src/core/patterns/pattern-observer.js +157 -0
- package/dist/src/core/patterns/pattern-store.js +259 -0
- package/dist/src/core/patterns/types.js +19 -0
- package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
- package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
- package/dist/src/core/skill-packs/types.js +14 -1
- package/dist/src/core/storage/cloud-sync-manager.js +116 -0
- package/dist/src/core/storage/cloud-sync.js +574 -0
- package/dist/src/core/storage/two-tier-storage.js +5 -1
- package/dist/src/core/tasks/master-tasks-template.js +43 -0
- package/dist/src/core/tasks/md-task-parser.js +138 -0
- package/dist/src/core/vision/index.js +27 -0
- package/dist/src/core/vision/signals.js +79 -0
- package/dist/src/core/vision/types.js +22 -0
- package/dist/src/core/vision/vision-file.js +146 -0
- package/dist/src/core/vision/vision-loop.js +220 -0
- package/dist/src/core/wiki/wiki-compiler.js +103 -1
- package/dist/src/daemon/daemon-config.js +45 -0
- package/dist/src/daemon/services/desire-path-service.js +566 -0
- package/dist/src/daemon/services/research-stream-service.js +320 -0
- package/dist/src/daemon/services/telemetry-service.js +192 -0
- package/dist/src/daemon/unified-daemon.js +28 -1
- package/dist/src/features/browser/cli-browser-agent.js +417 -0
- package/dist/src/features/browser/stagehand-workflows.js +578 -0
- package/dist/src/features/operator/adapter-factory.js +62 -0
- package/dist/src/features/operator/browser-adapter.js +109 -0
- package/dist/src/features/operator/desktop-adapter.js +125 -0
- package/dist/src/features/operator/index.js +39 -0
- package/dist/src/features/operator/llm-decision.js +137 -0
- package/dist/src/features/operator/operator-logger.js +92 -0
- package/dist/src/features/operator/overnight-runner.js +327 -0
- package/dist/src/features/operator/screen-adapter.js +91 -0
- package/dist/src/features/operator/session-manager.js +127 -0
- package/dist/src/features/operator/state-machine.js +227 -0
- package/dist/src/features/operator/task-queue.js +81 -0
- package/dist/src/features/portal/index.js +26 -0
- package/dist/src/features/portal/server.js +240 -0
- package/dist/src/features/portal/types.js +14 -0
- package/dist/src/features/portal/ui.js +195 -0
- package/dist/src/features/tasks/task-aware-context.js +2 -1
- package/dist/src/features/tui/simple-monitor.js +0 -23
- package/dist/src/features/tui/swarm-monitor.js +8 -66
- package/dist/src/{integrations/diffmem/index.js → features/web/client/hooks/use-socket.js} +6 -5
- package/dist/src/features/web/client/lib/utils.js +12 -0
- package/dist/src/features/web/client/stores/session-store.js +12 -0
- package/dist/src/features/web/server/gcp-billing.js +76 -0
- package/dist/src/features/web/server/index.js +10 -0
- package/dist/src/features/web/server/spend-calculator.js +228 -0
- package/dist/src/hooks/schemas.js +4 -1
- package/dist/src/integrations/anthropic/client.js +3 -2
- package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
- package/dist/src/integrations/claude-code/subagent-client.js +218 -11
- package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
- package/dist/src/integrations/linear/webhook-retry.js +196 -0
- package/dist/src/integrations/linear/webhook-server.js +18 -22
- package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
- package/dist/src/integrations/mcp/handlers/index.js +27 -52
- package/dist/src/integrations/mcp/server.js +122 -335
- package/dist/src/integrations/mcp/tool-alias-registry.js +0 -73
- package/dist/src/integrations/mcp/tool-definitions.js +111 -510
- package/dist/src/mcp/stackmemory-mcp-server.js +404 -379
- package/dist/src/orchestrators/multimodal/determinism.js +2 -1
- package/dist/src/orchestrators/multimodal/harness.js +2 -1
- package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
- package/dist/src/utils/process-cleanup.js +1 -7
- package/docs/README.md +42 -0
- package/docs/guides/README_INSTALL.md +208 -0
- package/package.json +18 -9
- package/scripts/claude-code-wrapper.sh +11 -0
- package/scripts/claude-sm-setup.sh +12 -1
- package/scripts/codex-wrapper.sh +11 -0
- package/scripts/git-hooks/branch-context-manager.sh +11 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
- package/scripts/hooks/cleanup-shell.sh +12 -1
- package/scripts/hooks/task-complete.sh +12 -1
- package/scripts/install-code-execution-hooks.sh +12 -1
- package/scripts/install-sweep-hook.sh +12 -0
- package/scripts/install.sh +11 -0
- package/scripts/opencode-wrapper.sh +11 -0
- package/scripts/portal/cloud-init.yaml +69 -0
- package/scripts/portal/setup.sh +69 -0
- package/scripts/portal/stackmemory-portal.service +34 -0
- package/scripts/setup-claude-integration.sh +12 -1
- package/scripts/smoke-init-db.sh +23 -0
- package/scripts/stackmemory-daemon.sh +11 -0
- package/scripts/verify-dist.cjs +11 -4
- package/dist/src/cli/commands/ralph.js +0 -1053
- package/dist/src/hooks/diffmem-hooks.js +0 -376
- package/dist/src/integrations/diffmem/client.js +0 -208
- package/dist/src/integrations/diffmem/config.js +0 -14
- package/dist/src/integrations/greptile/client.js +0 -101
- package/dist/src/integrations/greptile/config.js +0 -14
- package/dist/src/integrations/greptile/index.js +0 -11
- package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +0 -188
- package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
- package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
- package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
- package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
- package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -354
- package/dist/src/integrations/ralph/index.js +0 -17
- package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -416
- package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
- package/dist/src/integrations/ralph/loopmax.js +0 -488
- package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
- package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
- package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
- package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
- package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
- package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
- package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
- package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
- package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
- package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1005
- package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
- package/scripts/ralph-loop-implementation.js +0 -404
- /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
- /package/dist/src/{integrations/greptile → features/operator}/types.js +0 -0
- /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:
|
|
19
|
-
outputPer1M:
|
|
20
|
-
source: "
|
|
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
|
|
117
|
+
if (frame.digestPreview) tokens += estimateTokens(frame.digestPreview);
|
|
117
118
|
return Math.floor(tokens);
|
|
118
119
|
}
|
|
119
120
|
estimateSummaryTokens(summary) {
|
|
120
|
-
return
|
|
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:
|
|
432
|
-
JSON.stringify(request.compressedSummary)
|
|
432
|
+
summaryTokens: estimateTokens(
|
|
433
|
+
JSON.stringify(request.compressedSummary)
|
|
433
434
|
),
|
|
434
435
|
queryComplexity: this.assessQueryComplexity(request.currentQuery),
|
|
435
436
|
matchedPatterns: [],
|