@stackmemoryai/stackmemory 1.10.5 → 1.12.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/README.md +104 -23
- package/dist/src/cli/claude-sm.js +266 -84
- package/dist/src/cli/codex-sm.js +185 -33
- package/dist/src/cli/commands/bench.js +209 -2
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/daemon.js +41 -0
- package/dist/src/cli/commands/handoff.js +40 -9
- package/dist/src/cli/commands/onboard.js +70 -3
- package/dist/src/cli/commands/optimize.js +117 -0
- package/dist/src/cli/commands/orchestrate.js +230 -5
- package/dist/src/cli/commands/orchestrator.js +312 -24
- package/dist/src/cli/commands/pack.js +322 -0
- package/dist/src/cli/commands/search.js +40 -1
- package/dist/src/cli/commands/setup.js +177 -7
- package/dist/src/cli/commands/skills.js +10 -1
- package/dist/src/cli/commands/state.js +265 -0
- package/dist/src/cli/gemini-sm.js +19 -29
- package/dist/src/cli/index.js +90 -29
- package/dist/src/cli/opencode-sm.js +38 -21
- package/dist/src/cli/utils/determinism-watcher.js +66 -0
- package/dist/src/cli/utils/real-cli-bin.js +44 -0
- package/dist/src/core/cache/content-cache.js +238 -0
- package/dist/src/core/cache/index.js +11 -0
- package/dist/src/core/cache/token-estimator.js +16 -0
- package/dist/src/core/context/frame-database.js +38 -30
- package/dist/src/core/cross-search/cross-project-search.js +269 -0
- package/dist/src/core/{merge → cross-search}/index.js +6 -4
- package/dist/src/core/database/sqlite-adapter.js +0 -83
- package/dist/src/core/extensions/provider-adapter.js +5 -0
- package/dist/src/core/models/model-router.js +22 -2
- package/dist/src/core/monitoring/logger.js +2 -1
- package/dist/src/core/optimization/trace-optimizer.js +413 -0
- package/dist/src/core/provenance/confidence-scorer.js +128 -0
- package/dist/src/core/provenance/index.js +40 -0
- package/dist/src/core/provenance/provenance-store.js +194 -0
- package/dist/src/core/provenance/types.js +82 -0
- package/dist/src/core/session/project-handoff.js +64 -0
- package/dist/src/core/session/session-manager.js +28 -0
- package/dist/src/core/shared-state/canonical-store.js +564 -0
- package/dist/src/core/skill-packs/index.js +18 -0
- package/dist/src/core/skill-packs/parser.js +42 -0
- package/dist/src/core/skill-packs/registry.js +224 -0
- package/dist/src/core/skill-packs/types.js +66 -0
- package/dist/src/core/trace/trace-event-store.js +282 -0
- package/dist/src/core/trace/trace-event.js +4 -0
- package/dist/src/daemon/daemon-config.js +7 -0
- package/dist/src/daemon/services/github-service.js +126 -0
- package/dist/src/daemon/unified-daemon.js +30 -0
- package/dist/src/features/sweep/pty-wrapper.js +13 -5
- package/dist/src/hooks/schemas.js +2 -0
- package/dist/src/integrations/claude-code/subagent-client.js +89 -0
- package/dist/src/integrations/github/pr-state.js +158 -0
- package/dist/src/integrations/linear/client.js +4 -1
- package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +188 -0
- package/dist/src/integrations/mcp/handlers/index.js +40 -59
- package/dist/src/integrations/mcp/server.js +425 -311
- package/dist/src/integrations/mcp/tool-alias-registry.js +370 -0
- package/dist/src/integrations/mcp/tool-definitions.js +98 -229
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +3 -40
- package/dist/src/integrations/ralph/learning/pattern-learner.js +1 -20
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -2
- package/dist/src/mcp/stackmemory-mcp-server.js +315 -0
- package/dist/src/orchestrators/multimodal/determinism.js +243 -0
- package/dist/src/orchestrators/multimodal/harness.js +147 -77
- package/dist/src/orchestrators/multimodal/providers.js +44 -3
- package/dist/src/utils/hook-installer.js +0 -8
- package/package.json +10 -1
- package/packs/coding/python-fastapi/instructions.md +60 -0
- package/packs/coding/python-fastapi/pack.yaml +28 -0
- package/packs/coding/typescript-react/instructions.md +47 -0
- package/packs/coding/typescript-react/pack.yaml +28 -0
- package/packs/core/commands/capture.md +32 -0
- package/packs/core/commands/learn.md +73 -0
- package/packs/core/commands/next.md +36 -0
- package/packs/core/commands/restart.md +58 -0
- package/packs/core/commands/restore.md +29 -0
- package/packs/core/commands/start.md +57 -0
- package/packs/core/commands/stop.md +65 -0
- package/packs/core/commands/summary.md +40 -0
- package/packs/core/manifest.json +24 -0
- package/packs/ops/decision-recovery/instructions.md +65 -0
- package/packs/ops/decision-recovery/pack.yaml +89 -0
- package/dist/src/cli/commands/team.js +0 -168
- package/dist/src/core/context/shared-context-layer.js +0 -620
- package/dist/src/core/context/stack-merge-resolver.js +0 -748
- package/dist/src/core/merge/conflict-detector.js +0 -430
- package/dist/src/core/merge/resolution-engine.js +0 -557
- package/dist/src/core/merge/stack-diff.js +0 -531
- package/dist/src/core/merge/unified-merge-resolver.js +0 -302
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
- package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
- /package/dist/src/core/{merge → cache}/types.js +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
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 Database from "better-sqlite3";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import { logger } from "../monitoring/logger.js";
|
|
9
|
+
import { SkillPackManifestSchema } from "./types.js";
|
|
10
|
+
const SCHEMA_VERSION = 1;
|
|
11
|
+
const SCHEMA_SQL = `
|
|
12
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
13
|
+
version INTEGER PRIMARY KEY
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS packs (
|
|
17
|
+
name TEXT PRIMARY KEY,
|
|
18
|
+
version TEXT NOT NULL,
|
|
19
|
+
manifest TEXT NOT NULL,
|
|
20
|
+
instructions TEXT,
|
|
21
|
+
installed_at TEXT NOT NULL,
|
|
22
|
+
source TEXT
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS packs_fts USING fts5(
|
|
26
|
+
name,
|
|
27
|
+
description,
|
|
28
|
+
instructions,
|
|
29
|
+
content='packs',
|
|
30
|
+
content_rowid='rowid'
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE TRIGGER IF NOT EXISTS packs_ai AFTER INSERT ON packs BEGIN
|
|
34
|
+
INSERT INTO packs_fts(rowid, name, description, instructions)
|
|
35
|
+
VALUES (new.rowid, new.name,
|
|
36
|
+
json_extract(new.manifest, '$.description'),
|
|
37
|
+
COALESCE(new.instructions, ''));
|
|
38
|
+
END;
|
|
39
|
+
|
|
40
|
+
CREATE TRIGGER IF NOT EXISTS packs_ad AFTER DELETE ON packs BEGIN
|
|
41
|
+
INSERT INTO packs_fts(packs_fts, rowid, name, description, instructions)
|
|
42
|
+
VALUES ('delete', old.rowid, old.name,
|
|
43
|
+
json_extract(old.manifest, '$.description'),
|
|
44
|
+
COALESCE(old.instructions, ''));
|
|
45
|
+
END;
|
|
46
|
+
|
|
47
|
+
CREATE TRIGGER IF NOT EXISTS packs_au AFTER UPDATE ON packs BEGIN
|
|
48
|
+
INSERT INTO packs_fts(packs_fts, rowid, name, description, instructions)
|
|
49
|
+
VALUES ('delete', old.rowid, old.name,
|
|
50
|
+
json_extract(old.manifest, '$.description'),
|
|
51
|
+
COALESCE(old.instructions, ''));
|
|
52
|
+
INSERT INTO packs_fts(rowid, name, description, instructions)
|
|
53
|
+
VALUES (new.rowid, new.name,
|
|
54
|
+
json_extract(new.manifest, '$.description'),
|
|
55
|
+
COALESCE(new.instructions, ''));
|
|
56
|
+
END;
|
|
57
|
+
`;
|
|
58
|
+
function getDefaultDbPath() {
|
|
59
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || "/tmp";
|
|
60
|
+
return path.join(home, ".stackmemory", "skill-packs.db");
|
|
61
|
+
}
|
|
62
|
+
function rowToPack(row) {
|
|
63
|
+
const manifest = SkillPackManifestSchema.parse(
|
|
64
|
+
JSON.parse(row["manifest"])
|
|
65
|
+
);
|
|
66
|
+
const source = row["source"];
|
|
67
|
+
const metadata = {
|
|
68
|
+
installedAt: row["installed_at"],
|
|
69
|
+
...source ? { source } : {}
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
manifest,
|
|
73
|
+
instructions: row["instructions"] || void 0,
|
|
74
|
+
metadata
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
class SkillPackRegistry {
|
|
78
|
+
db;
|
|
79
|
+
dbPath;
|
|
80
|
+
constructor(dbPath) {
|
|
81
|
+
this.dbPath = dbPath || getDefaultDbPath();
|
|
82
|
+
const dir = path.dirname(this.dbPath);
|
|
83
|
+
if (!fs.existsSync(dir)) {
|
|
84
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
this.db = new Database(this.dbPath);
|
|
87
|
+
this.db.pragma("journal_mode = WAL");
|
|
88
|
+
this.db.pragma("busy_timeout = 5000");
|
|
89
|
+
this.initSchema();
|
|
90
|
+
}
|
|
91
|
+
initSchema() {
|
|
92
|
+
const versionRow = (() => {
|
|
93
|
+
try {
|
|
94
|
+
return this.db.prepare(
|
|
95
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'"
|
|
96
|
+
).get();
|
|
97
|
+
} catch {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
})();
|
|
101
|
+
if (!versionRow) {
|
|
102
|
+
this.db.exec(SCHEMA_SQL);
|
|
103
|
+
this.db.prepare("INSERT OR REPLACE INTO schema_version (version) VALUES (?)").run(SCHEMA_VERSION);
|
|
104
|
+
logger.debug("SkillPackRegistry: created schema v" + SCHEMA_VERSION);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ============================================================
|
|
108
|
+
// CRUD
|
|
109
|
+
// ============================================================
|
|
110
|
+
/**
|
|
111
|
+
* Install or update a skill pack. Upserts by name.
|
|
112
|
+
*/
|
|
113
|
+
install(pack) {
|
|
114
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
115
|
+
const manifestJson = JSON.stringify(pack.manifest);
|
|
116
|
+
this.db.prepare(
|
|
117
|
+
`INSERT INTO packs (name, version, manifest, instructions, installed_at, source)
|
|
118
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
119
|
+
ON CONFLICT(name) DO UPDATE SET
|
|
120
|
+
version = excluded.version,
|
|
121
|
+
manifest = excluded.manifest,
|
|
122
|
+
instructions = excluded.instructions,
|
|
123
|
+
installed_at = excluded.installed_at,
|
|
124
|
+
source = excluded.source`
|
|
125
|
+
).run(
|
|
126
|
+
pack.manifest.name,
|
|
127
|
+
pack.manifest.version,
|
|
128
|
+
manifestJson,
|
|
129
|
+
pack.instructions ?? null,
|
|
130
|
+
pack.metadata?.installedAt ?? now,
|
|
131
|
+
pack.metadata?.source ?? null
|
|
132
|
+
);
|
|
133
|
+
logger.debug(
|
|
134
|
+
`SkillPackRegistry: installed ${pack.manifest.name}@${pack.manifest.version}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Uninstall a pack by name.
|
|
139
|
+
*/
|
|
140
|
+
uninstall(name) {
|
|
141
|
+
const result = this.db.prepare("DELETE FROM packs WHERE name = ?").run(name);
|
|
142
|
+
return result.changes > 0;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get a single pack by name.
|
|
146
|
+
*/
|
|
147
|
+
get(name) {
|
|
148
|
+
const row = this.db.prepare("SELECT * FROM packs WHERE name = ?").get(name);
|
|
149
|
+
return row ? rowToPack(row) : void 0;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* List packs with optional filters.
|
|
153
|
+
*/
|
|
154
|
+
list(query) {
|
|
155
|
+
const conditions = [];
|
|
156
|
+
const params = [];
|
|
157
|
+
if (query?.namespace) {
|
|
158
|
+
conditions.push("name LIKE ? || '/%'");
|
|
159
|
+
params.push(query.namespace);
|
|
160
|
+
}
|
|
161
|
+
if (query?.runtime) {
|
|
162
|
+
conditions.push("json_extract(manifest, '$.runtime.type') = ?");
|
|
163
|
+
params.push(query.runtime);
|
|
164
|
+
}
|
|
165
|
+
const where = conditions.length ? "WHERE " + conditions.join(" AND ") : "";
|
|
166
|
+
const sql = `SELECT * FROM packs ${where} ORDER BY name`;
|
|
167
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
168
|
+
return rows.map(rowToPack);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Find the pack that provides a given MCP tool name.
|
|
172
|
+
*/
|
|
173
|
+
getByTool(toolName) {
|
|
174
|
+
const rows = this.db.prepare("SELECT * FROM packs").all();
|
|
175
|
+
for (const row of rows) {
|
|
176
|
+
const manifest = JSON.parse(
|
|
177
|
+
row["manifest"]
|
|
178
|
+
);
|
|
179
|
+
const tools = manifest.mcp?.tools ?? [];
|
|
180
|
+
if (tools.some((t) => t.name === toolName)) {
|
|
181
|
+
return rowToPack(row);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Full-text search across pack name, description, and instructions.
|
|
188
|
+
*/
|
|
189
|
+
search(query) {
|
|
190
|
+
const sanitized = query.replace(/[^\w\s/-]/g, "").split(/\s+/).filter(Boolean).map((t) => `"${t}"`).join(" ");
|
|
191
|
+
if (!sanitized) return [];
|
|
192
|
+
const rows = this.db.prepare(
|
|
193
|
+
`SELECT p.* FROM packs p
|
|
194
|
+
JOIN packs_fts f ON p.rowid = f.rowid
|
|
195
|
+
WHERE packs_fts MATCH ?
|
|
196
|
+
ORDER BY rank`
|
|
197
|
+
).all(sanitized);
|
|
198
|
+
return rows.map(rowToPack);
|
|
199
|
+
}
|
|
200
|
+
// ============================================================
|
|
201
|
+
// LIFECYCLE
|
|
202
|
+
// ============================================================
|
|
203
|
+
close() {
|
|
204
|
+
this.db.close();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
let registryInstance;
|
|
208
|
+
function getSkillPackRegistry(dbPath) {
|
|
209
|
+
if (!registryInstance) {
|
|
210
|
+
registryInstance = new SkillPackRegistry(dbPath);
|
|
211
|
+
}
|
|
212
|
+
return registryInstance;
|
|
213
|
+
}
|
|
214
|
+
function resetSkillPackRegistry() {
|
|
215
|
+
if (registryInstance) {
|
|
216
|
+
registryInstance.close();
|
|
217
|
+
registryInstance = void 0;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export {
|
|
221
|
+
SkillPackRegistry,
|
|
222
|
+
getSkillPackRegistry,
|
|
223
|
+
resetSkillPackRegistry
|
|
224
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
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 { z } from "zod";
|
|
6
|
+
const SemverSchema = z.string().regex(
|
|
7
|
+
/^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/,
|
|
8
|
+
"version must be valid semver (e.g. 1.0.0)"
|
|
9
|
+
);
|
|
10
|
+
const SkillPackRuntimeTypeSchema = z.enum([
|
|
11
|
+
"local",
|
|
12
|
+
"e2b",
|
|
13
|
+
"cua",
|
|
14
|
+
"modal"
|
|
15
|
+
]);
|
|
16
|
+
const SkillPackRuntimeSchema = z.object({
|
|
17
|
+
type: SkillPackRuntimeTypeSchema.default("local"),
|
|
18
|
+
template: z.string().optional()
|
|
19
|
+
});
|
|
20
|
+
const SkillPackIngestionSchema = z.object({
|
|
21
|
+
sources: z.array(z.string()).default([]),
|
|
22
|
+
scope: z.string().optional()
|
|
23
|
+
});
|
|
24
|
+
const SkillPackOntologySchema = z.object({
|
|
25
|
+
entities: z.array(z.string()).default([]),
|
|
26
|
+
relations: z.array(z.string()).default([])
|
|
27
|
+
});
|
|
28
|
+
const SkillPackMcpToolSchema = z.object({
|
|
29
|
+
name: z.string().min(1),
|
|
30
|
+
description: z.string().min(1),
|
|
31
|
+
inputSchema: z.record(z.unknown()).optional()
|
|
32
|
+
});
|
|
33
|
+
const SkillPackMcpSchema = z.object({
|
|
34
|
+
tools: z.array(SkillPackMcpToolSchema).default([])
|
|
35
|
+
});
|
|
36
|
+
const SkillPackExampleSchema = z.object({
|
|
37
|
+
input: z.string().min(1),
|
|
38
|
+
output: z.string().min(1)
|
|
39
|
+
});
|
|
40
|
+
const PackNameSchema = z.string().min(1).regex(
|
|
41
|
+
/^[\w-]+\/[\w-]+$/,
|
|
42
|
+
'name must be namespace/pack-name (e.g. "coding/typescript-react")'
|
|
43
|
+
);
|
|
44
|
+
const SkillPackManifestSchema = z.object({
|
|
45
|
+
name: PackNameSchema,
|
|
46
|
+
version: SemverSchema,
|
|
47
|
+
description: z.string().min(1),
|
|
48
|
+
author: z.string().min(1),
|
|
49
|
+
license: z.string().default("MIT"),
|
|
50
|
+
runtime: SkillPackRuntimeSchema.optional(),
|
|
51
|
+
ingestion: SkillPackIngestionSchema.optional(),
|
|
52
|
+
ontology: SkillPackOntologySchema.optional(),
|
|
53
|
+
mcp: SkillPackMcpSchema.optional(),
|
|
54
|
+
examples: z.array(SkillPackExampleSchema).optional(),
|
|
55
|
+
instructions: z.string().optional()
|
|
56
|
+
});
|
|
57
|
+
export {
|
|
58
|
+
SkillPackExampleSchema,
|
|
59
|
+
SkillPackIngestionSchema,
|
|
60
|
+
SkillPackManifestSchema,
|
|
61
|
+
SkillPackMcpSchema,
|
|
62
|
+
SkillPackMcpToolSchema,
|
|
63
|
+
SkillPackOntologySchema,
|
|
64
|
+
SkillPackRuntimeSchema,
|
|
65
|
+
SkillPackRuntimeTypeSchema
|
|
66
|
+
};
|
|
@@ -0,0 +1,282 @@
|
|
|
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 { v4 as uuidv4 } from "uuid";
|
|
6
|
+
import { logger } from "../monitoring/logger.js";
|
|
7
|
+
class TraceEventStore {
|
|
8
|
+
db;
|
|
9
|
+
constructor(db) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.initSchema();
|
|
12
|
+
}
|
|
13
|
+
initSchema() {
|
|
14
|
+
this.db.exec(`
|
|
15
|
+
CREATE TABLE IF NOT EXISTS trace_events (
|
|
16
|
+
id TEXT PRIMARY KEY,
|
|
17
|
+
timestamp TEXT NOT NULL,
|
|
18
|
+
session_id TEXT NOT NULL,
|
|
19
|
+
trace_id TEXT NOT NULL,
|
|
20
|
+
parent_trace_id TEXT,
|
|
21
|
+
tenant_id TEXT NOT NULL DEFAULT 'local',
|
|
22
|
+
actor_host TEXT NOT NULL DEFAULT 'unknown',
|
|
23
|
+
actor_agent TEXT NOT NULL DEFAULT 'stackmemory-mcp',
|
|
24
|
+
actor_user TEXT NOT NULL DEFAULT 'anonymous',
|
|
25
|
+
operation TEXT NOT NULL,
|
|
26
|
+
inputs TEXT NOT NULL DEFAULT '{}',
|
|
27
|
+
outputs TEXT NOT NULL DEFAULT '{}',
|
|
28
|
+
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
30
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
31
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
32
|
+
score REAL,
|
|
33
|
+
feedback TEXT,
|
|
34
|
+
provenance TEXT NOT NULL DEFAULT '{"sources":[],"derivation":[],"confidence":1}',
|
|
35
|
+
error TEXT,
|
|
36
|
+
tags TEXT
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_te_session ON trace_events(session_id);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_te_trace ON trace_events(trace_id);
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_te_operation ON trace_events(operation);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_te_timestamp ON trace_events(timestamp);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_te_score ON trace_events(score) WHERE score IS NOT NULL;
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
// ------------------------------------------------------------------
|
|
47
|
+
// Write
|
|
48
|
+
// ------------------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* Record a trace event. Generates ID if not present in trace_id.
|
|
51
|
+
*/
|
|
52
|
+
record(event) {
|
|
53
|
+
const id = uuidv4();
|
|
54
|
+
this.db.prepare(
|
|
55
|
+
`INSERT INTO trace_events (
|
|
56
|
+
id, timestamp, session_id, trace_id, parent_trace_id, tenant_id,
|
|
57
|
+
actor_host, actor_agent, actor_user,
|
|
58
|
+
operation, inputs, outputs,
|
|
59
|
+
tokens_in, tokens_out, cost_usd, duration_ms,
|
|
60
|
+
score, feedback, provenance, error, tags
|
|
61
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
62
|
+
).run(
|
|
63
|
+
id,
|
|
64
|
+
event.timestamp,
|
|
65
|
+
event.session_id,
|
|
66
|
+
event.trace_id,
|
|
67
|
+
event.parent_trace_id ?? null,
|
|
68
|
+
event.tenant_id,
|
|
69
|
+
event.actor.host,
|
|
70
|
+
event.actor.agent,
|
|
71
|
+
event.actor.user,
|
|
72
|
+
event.operation,
|
|
73
|
+
JSON.stringify(event.inputs),
|
|
74
|
+
JSON.stringify(event.outputs),
|
|
75
|
+
event.tokens_in,
|
|
76
|
+
event.tokens_out,
|
|
77
|
+
event.cost_usd,
|
|
78
|
+
event.duration_ms,
|
|
79
|
+
event.score ?? null,
|
|
80
|
+
event.feedback ?? null,
|
|
81
|
+
JSON.stringify(event.provenance),
|
|
82
|
+
event.error ?? null,
|
|
83
|
+
event.tags ? JSON.stringify(event.tags) : null
|
|
84
|
+
);
|
|
85
|
+
logger.debug(`TraceEvent recorded: ${event.operation} [${id}]`);
|
|
86
|
+
return id;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Record multiple events in a single transaction.
|
|
90
|
+
*/
|
|
91
|
+
recordBatch(events) {
|
|
92
|
+
const ids = [];
|
|
93
|
+
this.db.transaction(() => {
|
|
94
|
+
for (const event of events) {
|
|
95
|
+
ids.push(this.record(event));
|
|
96
|
+
}
|
|
97
|
+
})();
|
|
98
|
+
return ids;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Add score and/or feedback to an existing event.
|
|
102
|
+
*/
|
|
103
|
+
annotate(id, annotation) {
|
|
104
|
+
const sets = [];
|
|
105
|
+
const params = [];
|
|
106
|
+
if (annotation.score !== void 0) {
|
|
107
|
+
sets.push("score = ?");
|
|
108
|
+
params.push(annotation.score);
|
|
109
|
+
}
|
|
110
|
+
if (annotation.feedback !== void 0) {
|
|
111
|
+
sets.push("feedback = ?");
|
|
112
|
+
params.push(annotation.feedback);
|
|
113
|
+
}
|
|
114
|
+
if (sets.length === 0) return false;
|
|
115
|
+
params.push(id);
|
|
116
|
+
const result = this.db.prepare(`UPDATE trace_events SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
117
|
+
return result.changes > 0;
|
|
118
|
+
}
|
|
119
|
+
// ------------------------------------------------------------------
|
|
120
|
+
// Read
|
|
121
|
+
// ------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* Get a single event by ID.
|
|
124
|
+
*/
|
|
125
|
+
get(id) {
|
|
126
|
+
const row = this.db.prepare("SELECT * FROM trace_events WHERE id = ?").get(id);
|
|
127
|
+
return row ? this.rowToEvent(row) : void 0;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Query events with filters.
|
|
131
|
+
*/
|
|
132
|
+
query(filter = {}) {
|
|
133
|
+
const conditions = [];
|
|
134
|
+
const params = [];
|
|
135
|
+
if (filter.session_id) {
|
|
136
|
+
conditions.push("session_id = ?");
|
|
137
|
+
params.push(filter.session_id);
|
|
138
|
+
}
|
|
139
|
+
if (filter.operation) {
|
|
140
|
+
conditions.push("operation = ?");
|
|
141
|
+
params.push(filter.operation);
|
|
142
|
+
}
|
|
143
|
+
if (filter.min_score !== void 0) {
|
|
144
|
+
conditions.push("score >= ?");
|
|
145
|
+
params.push(filter.min_score);
|
|
146
|
+
}
|
|
147
|
+
if (filter.has_feedback) {
|
|
148
|
+
conditions.push("feedback IS NOT NULL");
|
|
149
|
+
}
|
|
150
|
+
if (filter.since) {
|
|
151
|
+
conditions.push("timestamp >= ?");
|
|
152
|
+
params.push(filter.since);
|
|
153
|
+
}
|
|
154
|
+
if (filter.until) {
|
|
155
|
+
conditions.push("timestamp <= ?");
|
|
156
|
+
params.push(filter.until);
|
|
157
|
+
}
|
|
158
|
+
const where = conditions.length ? "WHERE " + conditions.join(" AND ") : "";
|
|
159
|
+
const limit = filter.limit ?? 100;
|
|
160
|
+
const offset = filter.offset ?? 0;
|
|
161
|
+
const rows = this.db.prepare(
|
|
162
|
+
`SELECT * FROM trace_events ${where} ORDER BY timestamp DESC LIMIT ? OFFSET ?`
|
|
163
|
+
).all(...params, limit, offset);
|
|
164
|
+
return rows.map((r) => this.rowToEvent(r));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get events for a specific session.
|
|
168
|
+
*/
|
|
169
|
+
getBySession(sessionId) {
|
|
170
|
+
return this.query({ session_id: sessionId, limit: 1e3 });
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get events with scores (for GEPA consumption).
|
|
174
|
+
*/
|
|
175
|
+
getScoredEvents(minScore) {
|
|
176
|
+
return this.query({
|
|
177
|
+
min_score: minScore ?? 0,
|
|
178
|
+
limit: 500
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get events with feedback (for GEPA ASI consumption).
|
|
183
|
+
*/
|
|
184
|
+
getFeedbackEvents() {
|
|
185
|
+
return this.query({ has_feedback: true, limit: 500 });
|
|
186
|
+
}
|
|
187
|
+
// ------------------------------------------------------------------
|
|
188
|
+
// Stats
|
|
189
|
+
// ------------------------------------------------------------------
|
|
190
|
+
/**
|
|
191
|
+
* Aggregate statistics across all events.
|
|
192
|
+
*/
|
|
193
|
+
getStats(filter) {
|
|
194
|
+
const conditions = [];
|
|
195
|
+
const params = [];
|
|
196
|
+
if (filter?.session_id) {
|
|
197
|
+
conditions.push("session_id = ?");
|
|
198
|
+
params.push(filter.session_id);
|
|
199
|
+
}
|
|
200
|
+
if (filter?.since) {
|
|
201
|
+
conditions.push("timestamp >= ?");
|
|
202
|
+
params.push(filter.since);
|
|
203
|
+
}
|
|
204
|
+
const where = conditions.length ? "WHERE " + conditions.join(" AND ") : "";
|
|
205
|
+
const agg = this.db.prepare(
|
|
206
|
+
`SELECT
|
|
207
|
+
COUNT(*) as total_events,
|
|
208
|
+
COALESCE(SUM(tokens_in), 0) as total_tokens_in,
|
|
209
|
+
COALESCE(SUM(tokens_out), 0) as total_tokens_out,
|
|
210
|
+
COALESCE(SUM(cost_usd), 0) as total_cost_usd,
|
|
211
|
+
AVG(score) as avg_score,
|
|
212
|
+
SUM(CASE WHEN feedback IS NOT NULL THEN 1 ELSE 0 END) as events_with_feedback,
|
|
213
|
+
SUM(CASE WHEN error IS NOT NULL THEN 1 ELSE 0 END) as events_with_errors
|
|
214
|
+
FROM trace_events ${where}`
|
|
215
|
+
).get(...params);
|
|
216
|
+
const opRows = this.db.prepare(
|
|
217
|
+
`SELECT operation, COUNT(*) as cnt FROM trace_events ${where} GROUP BY operation ORDER BY cnt DESC`
|
|
218
|
+
).all(...params);
|
|
219
|
+
const hostRows = this.db.prepare(
|
|
220
|
+
`SELECT actor_host, COUNT(*) as cnt FROM trace_events ${where} GROUP BY actor_host ORDER BY cnt DESC`
|
|
221
|
+
).all(...params);
|
|
222
|
+
const operations = {};
|
|
223
|
+
for (const r of opRows) operations[r.operation] = r.cnt;
|
|
224
|
+
const hosts = {};
|
|
225
|
+
for (const r of hostRows) hosts[r.actor_host] = r.cnt;
|
|
226
|
+
return {
|
|
227
|
+
total_events: agg["total_events"] || 0,
|
|
228
|
+
total_tokens_in: agg["total_tokens_in"] || 0,
|
|
229
|
+
total_tokens_out: agg["total_tokens_out"] || 0,
|
|
230
|
+
total_cost_usd: agg["total_cost_usd"] || 0,
|
|
231
|
+
avg_score: agg["avg_score"],
|
|
232
|
+
events_with_feedback: agg["events_with_feedback"] || 0,
|
|
233
|
+
events_with_errors: agg["events_with_errors"] || 0,
|
|
234
|
+
operations,
|
|
235
|
+
hosts
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
// ------------------------------------------------------------------
|
|
239
|
+
// Lifecycle
|
|
240
|
+
// ------------------------------------------------------------------
|
|
241
|
+
/**
|
|
242
|
+
* Delete events older than the given ISO timestamp.
|
|
243
|
+
*/
|
|
244
|
+
evict(olderThan) {
|
|
245
|
+
const result = this.db.prepare("DELETE FROM trace_events WHERE timestamp < ?").run(olderThan);
|
|
246
|
+
return result.changes;
|
|
247
|
+
}
|
|
248
|
+
// ------------------------------------------------------------------
|
|
249
|
+
// Helpers
|
|
250
|
+
// ------------------------------------------------------------------
|
|
251
|
+
rowToEvent(row) {
|
|
252
|
+
const provenance = JSON.parse(row.provenance);
|
|
253
|
+
const actor = {
|
|
254
|
+
host: row.actor_host,
|
|
255
|
+
agent: row.actor_agent,
|
|
256
|
+
user: row.actor_user
|
|
257
|
+
};
|
|
258
|
+
return {
|
|
259
|
+
timestamp: row.timestamp,
|
|
260
|
+
session_id: row.session_id,
|
|
261
|
+
trace_id: row.trace_id,
|
|
262
|
+
parent_trace_id: row.parent_trace_id ?? void 0,
|
|
263
|
+
tenant_id: row.tenant_id,
|
|
264
|
+
actor,
|
|
265
|
+
operation: row.operation,
|
|
266
|
+
inputs: JSON.parse(row.inputs),
|
|
267
|
+
outputs: JSON.parse(row.outputs),
|
|
268
|
+
tokens_in: row.tokens_in,
|
|
269
|
+
tokens_out: row.tokens_out,
|
|
270
|
+
cost_usd: row.cost_usd,
|
|
271
|
+
duration_ms: row.duration_ms,
|
|
272
|
+
score: row.score ?? void 0,
|
|
273
|
+
feedback: row.feedback ?? void 0,
|
|
274
|
+
provenance,
|
|
275
|
+
error: row.error ?? void 0,
|
|
276
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
export {
|
|
281
|
+
TraceEventStore
|
|
282
|
+
};
|
|
@@ -23,6 +23,10 @@ const DEFAULT_DAEMON_CONFIG = {
|
|
|
23
23
|
retryAttempts: 3,
|
|
24
24
|
retryDelay: 3e4
|
|
25
25
|
},
|
|
26
|
+
github: {
|
|
27
|
+
enabled: false,
|
|
28
|
+
interval: 5
|
|
29
|
+
},
|
|
26
30
|
maintenance: {
|
|
27
31
|
enabled: true,
|
|
28
32
|
interval: 360,
|
|
@@ -96,6 +100,7 @@ function loadDaemonConfig() {
|
|
|
96
100
|
...config,
|
|
97
101
|
context: { ...DEFAULT_DAEMON_CONFIG.context, ...config.context },
|
|
98
102
|
linear: { ...DEFAULT_DAEMON_CONFIG.linear, ...config.linear },
|
|
103
|
+
github: { ...DEFAULT_DAEMON_CONFIG.github, ...config.github },
|
|
99
104
|
maintenance: {
|
|
100
105
|
...DEFAULT_DAEMON_CONFIG.maintenance,
|
|
101
106
|
...config.maintenance
|
|
@@ -115,6 +120,7 @@ function saveDaemonConfig(config) {
|
|
|
115
120
|
...config,
|
|
116
121
|
context: { ...currentConfig.context, ...config.context },
|
|
117
122
|
linear: { ...currentConfig.linear, ...config.linear },
|
|
123
|
+
github: { ...currentConfig.github, ...config.github },
|
|
118
124
|
maintenance: { ...currentConfig.maintenance, ...config.maintenance },
|
|
119
125
|
memory: { ...currentConfig.memory, ...config.memory },
|
|
120
126
|
fileWatch: { ...currentConfig.fileWatch, ...config.fileWatch }
|
|
@@ -128,6 +134,7 @@ function readDaemonStatus() {
|
|
|
128
134
|
services: {
|
|
129
135
|
context: { enabled: false },
|
|
130
136
|
linear: { enabled: false },
|
|
137
|
+
github: { enabled: false },
|
|
131
138
|
maintenance: { enabled: false },
|
|
132
139
|
memory: { enabled: false },
|
|
133
140
|
fileWatch: { enabled: false }
|