@lossless-claude/lcm 0.6.0 → 0.8.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 (108) hide show
  1. package/.claude-plugin/marketplace.json +4 -9
  2. package/.claude-plugin/plugin.json +7 -1
  3. package/dist/bin/lcm.js +214 -0
  4. package/dist/bin/lcm.js.map +1 -1
  5. package/dist/installer/install.js +19 -1
  6. package/dist/installer/install.js.map +1 -1
  7. package/dist/installer/setup.sh +213 -0
  8. package/dist/src/cli-help.js +53 -2
  9. package/dist/src/cli-help.js.map +1 -1
  10. package/dist/src/daemon/config.d.ts +10 -0
  11. package/dist/src/daemon/config.js +10 -0
  12. package/dist/src/daemon/config.js.map +1 -1
  13. package/dist/src/daemon/lifecycle.js +5 -1
  14. package/dist/src/daemon/lifecycle.js.map +1 -1
  15. package/dist/src/daemon/routes/compact.js +6 -6
  16. package/dist/src/daemon/routes/compact.js.map +1 -1
  17. package/dist/src/daemon/routes/ingest.js +6 -6
  18. package/dist/src/daemon/routes/ingest.js.map +1 -1
  19. package/dist/src/daemon/routes/pool-stats.d.ts +2 -0
  20. package/dist/src/daemon/routes/pool-stats.js +14 -0
  21. package/dist/src/daemon/routes/pool-stats.js.map +1 -0
  22. package/dist/src/daemon/routes/promote-events.d.ts +3 -0
  23. package/dist/src/daemon/routes/promote-events.js +180 -0
  24. package/dist/src/daemon/routes/promote-events.js.map +1 -0
  25. package/dist/src/daemon/routes/restore.js +32 -1
  26. package/dist/src/daemon/routes/restore.js.map +1 -1
  27. package/dist/src/daemon/routes/stats.d.ts +2 -0
  28. package/dist/src/daemon/routes/stats.js +14 -0
  29. package/dist/src/daemon/routes/stats.js.map +1 -0
  30. package/dist/src/daemon/routes/status.js +3 -2
  31. package/dist/src/daemon/routes/status.js.map +1 -1
  32. package/dist/src/daemon/server.d.ts +2 -1
  33. package/dist/src/daemon/server.js +8 -13
  34. package/dist/src/daemon/server.js.map +1 -1
  35. package/dist/src/daemon/version.d.ts +10 -0
  36. package/dist/src/daemon/version.js +31 -0
  37. package/dist/src/daemon/version.js.map +1 -0
  38. package/dist/src/db/connection.d.ts +17 -0
  39. package/dist/src/db/connection.js +27 -0
  40. package/dist/src/db/connection.js.map +1 -1
  41. package/dist/src/db/events-path.d.ts +2 -0
  42. package/dist/src/db/events-path.js +11 -0
  43. package/dist/src/db/events-path.js.map +1 -0
  44. package/dist/src/db/events-stats.d.ts +29 -0
  45. package/dist/src/db/events-stats.js +107 -0
  46. package/dist/src/db/events-stats.js.map +1 -0
  47. package/dist/src/db/migration.js +21 -9
  48. package/dist/src/db/migration.js.map +1 -1
  49. package/dist/src/db/promoted.d.ts +5 -0
  50. package/dist/src/db/promoted.js +21 -0
  51. package/dist/src/db/promoted.js.map +1 -1
  52. package/dist/src/db/redaction-stats.d.ts +1 -0
  53. package/dist/src/db/redaction-stats.js +3 -1
  54. package/dist/src/db/redaction-stats.js.map +1 -1
  55. package/dist/src/doctor/doctor.d.ts +1 -1
  56. package/dist/src/doctor/doctor.js +184 -32
  57. package/dist/src/doctor/doctor.js.map +1 -1
  58. package/dist/src/generated-patterns.d.ts +7 -0
  59. package/dist/src/generated-patterns.js +227 -0
  60. package/dist/src/generated-patterns.js.map +1 -0
  61. package/dist/src/hooks/compact.js +7 -0
  62. package/dist/src/hooks/compact.js.map +1 -1
  63. package/dist/src/hooks/dispatch.d.ts +1 -1
  64. package/dist/src/hooks/dispatch.js +6 -1
  65. package/dist/src/hooks/dispatch.js.map +1 -1
  66. package/dist/src/hooks/events-db.d.ts +49 -0
  67. package/dist/src/hooks/events-db.js +227 -0
  68. package/dist/src/hooks/events-db.js.map +1 -0
  69. package/dist/src/hooks/extractors.d.ts +22 -0
  70. package/dist/src/hooks/extractors.js +215 -0
  71. package/dist/src/hooks/extractors.js.map +1 -0
  72. package/dist/src/hooks/hook-errors.d.ts +14 -0
  73. package/dist/src/hooks/hook-errors.js +56 -0
  74. package/dist/src/hooks/hook-errors.js.map +1 -0
  75. package/dist/src/hooks/post-tool.d.ts +4 -0
  76. package/dist/src/hooks/post-tool.js +43 -0
  77. package/dist/src/hooks/post-tool.js.map +1 -0
  78. package/dist/src/hooks/restore.js +65 -3
  79. package/dist/src/hooks/restore.js.map +1 -1
  80. package/dist/src/hooks/session-end.d.ts +1 -0
  81. package/dist/src/hooks/session-end.js +18 -0
  82. package/dist/src/hooks/session-end.js.map +1 -1
  83. package/dist/src/hooks/session-snapshot.js +12 -0
  84. package/dist/src/hooks/session-snapshot.js.map +1 -1
  85. package/dist/src/hooks/user-prompt.js +27 -0
  86. package/dist/src/hooks/user-prompt.js.map +1 -1
  87. package/dist/src/installer/settings.js +1 -0
  88. package/dist/src/installer/settings.js.map +1 -1
  89. package/dist/src/mcp/server.js +5 -1
  90. package/dist/src/mcp/server.js.map +1 -1
  91. package/dist/src/mcp/tools/lcm-store.js +1 -1
  92. package/dist/src/mcp/tools/lcm-store.js.map +1 -1
  93. package/dist/src/portable-knowledge.d.ts +66 -0
  94. package/dist/src/portable-knowledge.js +175 -0
  95. package/dist/src/portable-knowledge.js.map +1 -0
  96. package/dist/src/scrub.d.ts +35 -11
  97. package/dist/src/scrub.js +88 -23
  98. package/dist/src/scrub.js.map +1 -1
  99. package/dist/src/sensitive.js +43 -10
  100. package/dist/src/sensitive.js.map +1 -1
  101. package/dist/src/stats.d.ts +3 -0
  102. package/dist/src/stats.js +18 -0
  103. package/dist/src/stats.js.map +1 -1
  104. package/dist/src/store/regex-safety.js +1 -1
  105. package/dist/src/store/regex-safety.js.map +1 -1
  106. package/docs/passive-learning.md +138 -0
  107. package/docs/tag-schema.md +107 -0
  108. package/package.json +2 -2
@@ -0,0 +1,49 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { ExtractedEvent } from "./extractors.js";
3
+ export interface EventRow {
4
+ event_id: number;
5
+ session_id: string;
6
+ seq: number;
7
+ type: string;
8
+ category: string;
9
+ data: string;
10
+ priority: number;
11
+ source_hook: string;
12
+ prev_event_id: number | null;
13
+ processed_at: string | null;
14
+ created_at: string;
15
+ }
16
+ export interface HealthStats {
17
+ totalEvents: number;
18
+ unprocessed: number;
19
+ errors: number;
20
+ lastCapture: string | null;
21
+ lastError: string | null;
22
+ }
23
+ export declare class EventsDb {
24
+ private db;
25
+ private dbPath;
26
+ constructor(dbPath: string);
27
+ private migrate;
28
+ insertEvent(sessionId: string, event: ExtractedEvent, sourceHook: string): number;
29
+ getUnprocessed(limit?: number): EventRow[];
30
+ markProcessed(eventIds: number[]): void;
31
+ pruneProcessed(olderThanDays: number): number;
32
+ setPrevEventId(eventId: number, prevEventId: number): void;
33
+ logHookError(hook: string, error: unknown, sessionId?: string): void;
34
+ getHealthStats(): HealthStats;
35
+ pruneUnprocessed(maxRows?: number, maxAgeDays?: number): {
36
+ pruned: number;
37
+ };
38
+ pruneErrorLog(olderThanDays?: number): number;
39
+ /** Expose raw DB for testing only. */
40
+ raw(): DatabaseSync;
41
+ close(): void;
42
+ }
43
+ /**
44
+ * Clear the migration-done cache. Intended for tests that create/destroy temp
45
+ * databases and need migration to re-run on the same path.
46
+ *
47
+ * @internal
48
+ */
49
+ export declare function _resetMigratedPathsForTesting(): void;
@@ -0,0 +1,227 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { getLcmConnection, closeLcmConnection, isLcmConnectionOpen } from "../db/connection.js";
4
+ /**
5
+ * Tracks which db paths have already had migrations applied in this process.
6
+ * When a pooled connection is reused (refs > 1), we skip the migration check
7
+ * entirely — it already ran during the first open. Cleared on process exit or
8
+ * when a path is explicitly evicted from the pool.
9
+ */
10
+ const _migratedPaths = new Set();
11
+ const SCHEMA_VERSION = 2;
12
+ const SCHEMA_SQL = `
13
+ CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL);
14
+ CREATE TABLE IF NOT EXISTS events (
15
+ event_id INTEGER PRIMARY KEY AUTOINCREMENT,
16
+ session_id TEXT NOT NULL,
17
+ seq INTEGER NOT NULL DEFAULT 0,
18
+ type TEXT NOT NULL,
19
+ category TEXT NOT NULL,
20
+ data TEXT NOT NULL,
21
+ priority INTEGER DEFAULT 3,
22
+ source_hook TEXT NOT NULL,
23
+ prev_event_id INTEGER,
24
+ processed_at TEXT,
25
+ created_at TEXT DEFAULT (datetime('now'))
26
+ );
27
+ CREATE INDEX IF NOT EXISTS idx_events_unprocessed ON events(processed_at) WHERE processed_at IS NULL;
28
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id, created_at);
29
+ CREATE TABLE IF NOT EXISTS error_log (
30
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
31
+ hook TEXT NOT NULL,
32
+ error TEXT NOT NULL,
33
+ session_id TEXT,
34
+ created_at TEXT DEFAULT (datetime('now'))
35
+ );
36
+ CREATE INDEX IF NOT EXISTS idx_error_log_created ON error_log(created_at);
37
+ `;
38
+ export class EventsDb {
39
+ db;
40
+ dbPath;
41
+ constructor(dbPath) {
42
+ mkdirSync(dirname(dbPath), { recursive: true, mode: 0o700 });
43
+ this.dbPath = dbPath;
44
+ // getLcmConnection returns the pooled (or newly-opened) DatabaseSync handle
45
+ // and increments its ref-count. Connections are kept alive across EventsDb
46
+ // instances so that high-frequency hooks (PostToolUse fires 50-200x/session)
47
+ // reuse the same underlying connection instead of opening/closing each time.
48
+ this.db = getLcmConnection(dbPath);
49
+ if (!_migratedPaths.has(dbPath)) {
50
+ try {
51
+ this.migrate();
52
+ }
53
+ catch (e) {
54
+ // Migration failed — release the pooled connection so the ref-count
55
+ // doesn't leak. The constructor will re-throw, so callers see the error.
56
+ closeLcmConnection(dbPath);
57
+ throw e;
58
+ }
59
+ _migratedPaths.add(dbPath);
60
+ }
61
+ }
62
+ migrate() {
63
+ // Check if schema_version table exists
64
+ const row = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'").get();
65
+ if (!row) {
66
+ this.db.exec(SCHEMA_SQL);
67
+ this.db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(SCHEMA_VERSION);
68
+ return;
69
+ }
70
+ const versionRow = this.db.prepare("SELECT version FROM schema_version").get();
71
+ // Handle empty schema_version table (table exists but has no rows)
72
+ if (!versionRow) {
73
+ this.db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(SCHEMA_VERSION);
74
+ // Ensure v2 tables exist even in this edge case
75
+ this.db.exec(`
76
+ CREATE TABLE IF NOT EXISTS error_log (
77
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
78
+ hook TEXT NOT NULL,
79
+ error TEXT NOT NULL,
80
+ session_id TEXT,
81
+ created_at TEXT DEFAULT (datetime('now'))
82
+ );
83
+ CREATE INDEX IF NOT EXISTS idx_error_log_created ON error_log(created_at);
84
+ `);
85
+ return;
86
+ }
87
+ const currentVersion = versionRow.version;
88
+ if (currentVersion < 2) {
89
+ this.db.exec("BEGIN EXCLUSIVE");
90
+ try {
91
+ this.db.exec(`
92
+ CREATE TABLE IF NOT EXISTS error_log (
93
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
94
+ hook TEXT NOT NULL,
95
+ error TEXT NOT NULL,
96
+ session_id TEXT,
97
+ created_at TEXT DEFAULT (datetime('now'))
98
+ );
99
+ CREATE INDEX IF NOT EXISTS idx_error_log_created ON error_log(created_at);
100
+ `);
101
+ this.db.prepare("UPDATE schema_version SET version = 2").run();
102
+ this.db.exec("COMMIT");
103
+ }
104
+ catch (e) {
105
+ try {
106
+ this.db.exec("ROLLBACK");
107
+ }
108
+ catch { /* ignore */ }
109
+ throw e;
110
+ }
111
+ }
112
+ }
113
+ insertEvent(sessionId, event, sourceHook) {
114
+ const stmt = this.db.prepare(`
115
+ INSERT INTO events (session_id, seq, type, category, data, priority, source_hook)
116
+ VALUES (?, (SELECT COALESCE(MAX(seq), 0) + 1 FROM events WHERE session_id = ?),
117
+ ?, ?, ?, ?, ?)
118
+ `);
119
+ const result = stmt.run(sessionId, sessionId, event.type, event.category, event.data, event.priority, sourceHook);
120
+ return Number(result.lastInsertRowid);
121
+ }
122
+ getUnprocessed(limit = 500) {
123
+ return this.db.prepare("SELECT * FROM events WHERE processed_at IS NULL ORDER BY session_id, seq LIMIT ?").all(limit);
124
+ }
125
+ markProcessed(eventIds) {
126
+ if (eventIds.length === 0)
127
+ return;
128
+ const placeholders = eventIds.map(() => "?").join(",");
129
+ this.db.prepare(`UPDATE events SET processed_at = datetime('now') WHERE event_id IN (${placeholders})`).run(...eventIds);
130
+ }
131
+ pruneProcessed(olderThanDays) {
132
+ const result = this.db.prepare(`DELETE FROM events WHERE processed_at IS NOT NULL
133
+ AND processed_at < datetime('now', '-' || ? || ' days')`).run(olderThanDays);
134
+ return Number(result.changes);
135
+ }
136
+ setPrevEventId(eventId, prevEventId) {
137
+ this.db.prepare("UPDATE events SET prev_event_id = ? WHERE event_id = ?")
138
+ .run(prevEventId, eventId);
139
+ }
140
+ logHookError(hook, error, sessionId) {
141
+ const msg = error instanceof Error ? error.message : String(error);
142
+ this.db.prepare("INSERT INTO error_log (hook, error, session_id) VALUES (?, ?, ?)").run(hook, msg, sessionId ?? null);
143
+ }
144
+ getHealthStats() {
145
+ const eventTotals = this.db.prepare("SELECT COUNT(*) as totalEvents, MAX(created_at) as lastCapture FROM events").get();
146
+ const unprocessedRow = this.db.prepare("SELECT COUNT(*) as unprocessed FROM events WHERE processed_at IS NULL").get();
147
+ const errorTotals = this.db.prepare("SELECT COUNT(*) as errors, MAX(created_at) as lastError FROM error_log WHERE hook NOT LIKE 'maintenance:%' AND created_at >= datetime('now', '-30 days')").get();
148
+ return {
149
+ totalEvents: eventTotals.totalEvents,
150
+ unprocessed: unprocessedRow.unprocessed,
151
+ errors: errorTotals.errors,
152
+ lastCapture: eventTotals.lastCapture,
153
+ lastError: errorTotals.lastError,
154
+ };
155
+ }
156
+ pruneUnprocessed(maxRows = 10_000, maxAgeDays = 30) {
157
+ let pruned = 0;
158
+ this.db.exec("BEGIN");
159
+ try {
160
+ const ageCountRow = this.db.prepare(`SELECT COUNT(*) as c FROM events
161
+ WHERE processed_at IS NULL
162
+ AND created_at < datetime('now', '-' || ? || ' days')`).get(maxAgeDays);
163
+ const ageCount = ageCountRow.c;
164
+ const totalUnprocessedRow = this.db.prepare("SELECT COUNT(*) as c FROM events WHERE processed_at IS NULL").get();
165
+ const totalUnprocessed = totalUnprocessedRow.c;
166
+ const remainingAfterAge = totalUnprocessed - ageCount;
167
+ let excess = 0;
168
+ if (remainingAfterAge > maxRows) {
169
+ excess = remainingAfterAge - maxRows;
170
+ }
171
+ pruned = ageCount + excess;
172
+ if (pruned > 0) {
173
+ this.db.prepare("INSERT INTO error_log (hook, error, session_id) VALUES (?, ?, NULL)").run("maintenance:pruneUnprocessed", `pruned ${pruned} unprocessed events (age/cap limit)`);
174
+ }
175
+ if (ageCount > 0) {
176
+ this.db.prepare(`DELETE FROM events WHERE processed_at IS NULL
177
+ AND event_id IN (
178
+ SELECT event_id FROM events WHERE processed_at IS NULL
179
+ AND created_at < datetime('now', '-' || ? || ' days')
180
+ )`).run(maxAgeDays);
181
+ }
182
+ if (excess > 0) {
183
+ this.db.prepare(`DELETE FROM events WHERE event_id IN (
184
+ SELECT event_id FROM events WHERE processed_at IS NULL
185
+ ORDER BY event_id ASC LIMIT ?
186
+ )`).run(excess);
187
+ }
188
+ this.db.exec("COMMIT");
189
+ }
190
+ catch (e) {
191
+ try {
192
+ this.db.exec("ROLLBACK");
193
+ }
194
+ catch { /* ignore */ }
195
+ throw e;
196
+ }
197
+ return { pruned };
198
+ }
199
+ pruneErrorLog(olderThanDays = 30) {
200
+ const result = this.db.prepare("DELETE FROM error_log WHERE created_at < datetime('now', '-' || ? || ' days')").run(olderThanDays);
201
+ return Number(result.changes);
202
+ }
203
+ /** Expose raw DB for testing only. */
204
+ raw() {
205
+ return this.db;
206
+ }
207
+ close() {
208
+ // Decrement pool ref-count. The underlying connection stays open as long as
209
+ // other callers hold a reference — it is only closed when refs reach 0.
210
+ closeLcmConnection(this.dbPath);
211
+ // If the connection was fully evicted from the pool, invalidate the
212
+ // migration-done cache so the next open re-runs migrations on a fresh handle.
213
+ if (!isLcmConnectionOpen(this.dbPath)) {
214
+ _migratedPaths.delete(this.dbPath);
215
+ }
216
+ }
217
+ }
218
+ /**
219
+ * Clear the migration-done cache. Intended for tests that create/destroy temp
220
+ * databases and need migration to re-run on the same path.
221
+ *
222
+ * @internal
223
+ */
224
+ export function _resetMigratedPathsForTesting() {
225
+ _migratedPaths.clear();
226
+ }
227
+ //# sourceMappingURL=events-db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events-db.js","sourceRoot":"","sources":["../../../src/hooks/events-db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export interface ExtractedEvent {
2
+ type: string;
3
+ category: string;
4
+ data: string;
5
+ priority: number;
6
+ tags?: string[];
7
+ }
8
+ interface PostToolInput {
9
+ tool_name: string;
10
+ tool_input: Record<string, unknown>;
11
+ tool_response?: unknown;
12
+ tool_output?: {
13
+ isError?: boolean;
14
+ };
15
+ }
16
+ export declare function extractPostToolEvents(input: PostToolInput): ExtractedEvent[];
17
+ export declare function normalizePromptWithChannels(prompt: string): {
18
+ text: string;
19
+ fromChannel: boolean;
20
+ };
21
+ export declare function extractUserPromptEvents(prompt: string): ExtractedEvent[];
22
+ export {};
@@ -0,0 +1,215 @@
1
+ // src/hooks/extractors.ts
2
+ const SENSITIVE_PATHS = [".env", ".ssh/", "credentials", "secrets/", ".npmrc", ".netrc"];
3
+ const DATA_SOFT_CAP = 2000;
4
+ const NEGATIVE_PATTERNS = [
5
+ "don't worry", "dont worry",
6
+ "never mind", "nevermind",
7
+ "not sure", "no idea",
8
+ "doesn't matter", "doesnt matter", "does not matter",
9
+ "forget about", "forget it",
10
+ "no preference", "whatever you think",
11
+ "up to you",
12
+ ];
13
+ const ENV_COMMANDS = ["npm install", "npm i ", "yarn add", "pip install", "pip3 install",
14
+ "nvm use", "volta install", "pnpm add", "uv pip install", "brew install"];
15
+ const GIT_COMMANDS = ["git commit", "git merge", "git rebase", "git checkout", "git switch",
16
+ "git branch", "git push", "git pull", "git stash", "git reset", "git cherry-pick"];
17
+ function truncate(s) {
18
+ return s.length > DATA_SOFT_CAP ? s.slice(0, DATA_SOFT_CAP) + "..." : s;
19
+ }
20
+ function isSensitivePath(path) {
21
+ const lower = path.toLowerCase();
22
+ return SENSITIVE_PATHS.some(p => lower.includes(p));
23
+ }
24
+ function classifyFile(path) {
25
+ if (/\.(test|spec)\.[tj]sx?$/.test(path) || path.includes("__tests__"))
26
+ return "test";
27
+ if (/\.(json|ya?ml|toml|ini|env)$/.test(path) || path.includes("config"))
28
+ return "config";
29
+ if (/\.(md|txt|rst)$/.test(path) || path.includes("docs/"))
30
+ return "docs";
31
+ return "source";
32
+ }
33
+ function extractBashEvents(input) {
34
+ const command = String(input.tool_input.command ?? "");
35
+ const isError = input.tool_output?.isError === true;
36
+ // Error detection (priority 1)
37
+ if (isError) {
38
+ const prefix = command.split(/\s+/).slice(0, 3).join(" ");
39
+ return [{ type: "error_tool", category: "error", data: truncate(`Bash error: ${prefix}`), priority: 1 }];
40
+ }
41
+ // Git operations (priority 2)
42
+ const gitMatch = GIT_COMMANDS.find(gc => command.startsWith(gc));
43
+ if (gitMatch) {
44
+ const commitMsgMatch = command.match(/-m\s+["']([^"']+)["']/);
45
+ const data = commitMsgMatch
46
+ ? `${gitMatch}: ${commitMsgMatch[1]}`
47
+ : gitMatch;
48
+ return [{ type: `git_${gitMatch.split(" ")[1]}`, category: "git", data: truncate(data), priority: 2 }];
49
+ }
50
+ // Env commands (priority 2)
51
+ const envMatch = ENV_COMMANDS.find(ec => command.startsWith(ec));
52
+ if (envMatch) {
53
+ return [{ type: "env_install", category: "env", data: truncate(command), priority: 2 }];
54
+ }
55
+ return [];
56
+ }
57
+ function extractFileEvents(toolName, input) {
58
+ const filePath = String(input.tool_input.file_path ?? input.tool_input.path ?? input.tool_input.pattern ?? "");
59
+ if (!filePath || isSensitivePath(filePath))
60
+ return [];
61
+ const typeMap = {
62
+ Read: "file_read", Edit: "file_edit", Write: "file_write",
63
+ Glob: "file_glob", Grep: "file_grep",
64
+ };
65
+ return [{
66
+ type: typeMap[toolName] ?? "file_access",
67
+ category: "file",
68
+ data: truncate(`${filePath} (${classifyFile(filePath)})`),
69
+ priority: 3,
70
+ }];
71
+ }
72
+ export function extractPostToolEvents(input) {
73
+ const { tool_name } = input;
74
+ // Skip lcm_store to prevent feedback loops
75
+ if (tool_name.includes("lcm_store") || tool_name.includes("lcm__lcm_store"))
76
+ return [];
77
+ // AskUserQuestion — extract Q+A pair (priority 1)
78
+ if (tool_name === "AskUserQuestion") {
79
+ const question = String(input.tool_input.question ?? "");
80
+ const answer = String(input.tool_response ?? "");
81
+ return [{
82
+ type: "decision",
83
+ category: "decision",
84
+ data: truncate(`Q: ${question}\nA: ${answer}`),
85
+ priority: 1,
86
+ }];
87
+ }
88
+ // Plan mode (priority 1)
89
+ if (tool_name === "EnterPlanMode") {
90
+ return [{ type: "plan_enter", category: "plan", data: "Entered plan mode", priority: 1 }];
91
+ }
92
+ if (tool_name === "ExitPlanMode") {
93
+ const response = String(input.tool_response ?? "");
94
+ const status = /approve/i.test(response) ? "approved" : /reject/i.test(response) ? "rejected" : "exited";
95
+ return [{ type: "plan_exit", category: "plan", data: `Plan ${status}`, priority: 1 }];
96
+ }
97
+ // Bash — multiple categories
98
+ if (tool_name === "Bash") {
99
+ return extractBashEvents(input);
100
+ }
101
+ // File operations (priority 3)
102
+ if (["Read", "Edit", "Write", "Glob", "Grep"].includes(tool_name)) {
103
+ return extractFileEvents(tool_name, input);
104
+ }
105
+ // Task operations (priority 2)
106
+ if (tool_name === "TaskCreate" || tool_name === "TaskUpdate") {
107
+ const subject = String(input.tool_input.subject ?? input.tool_input.taskId ?? "");
108
+ const status = String(input.tool_input.status ?? "created");
109
+ return [{
110
+ type: `task_${tool_name === "TaskCreate" ? "create" : "update"}`,
111
+ category: "task",
112
+ data: truncate(`${subject} → ${status}`),
113
+ priority: 2,
114
+ }];
115
+ }
116
+ // Agent/subagent (priority 3)
117
+ if (tool_name === "Agent") {
118
+ const desc = String(input.tool_input.description ?? "");
119
+ return [{ type: "subagent_dispatch", category: "subagent", data: truncate(desc), priority: 3 }];
120
+ }
121
+ // Skill (priority 3)
122
+ if (tool_name === "Skill") {
123
+ const skill = String(input.tool_input.skill ?? "");
124
+ return [{ type: "skill_use", category: "skill", data: skill, priority: 3 }];
125
+ }
126
+ // MCP tools (priority 3) — tool name only, no args
127
+ if (tool_name.startsWith("mcp__")) {
128
+ return [{ type: "mcp_call", category: "mcp", data: tool_name, priority: 3 }];
129
+ }
130
+ // Any other tool with isError flag
131
+ if (input.tool_output?.isError === true) {
132
+ return [{
133
+ type: "error_tool",
134
+ category: "error",
135
+ data: truncate(`${tool_name} error`),
136
+ priority: 1,
137
+ }];
138
+ }
139
+ return [];
140
+ }
141
+ // Extract text content from <channel> XML tags, or return prompt as-is
142
+ export function normalizePromptWithChannels(prompt) {
143
+ const normalized = prompt.replace(/<channel[^>]*>([\s\S]*?)<\/channel>/g, (_, content) => content.trim());
144
+ return { text: normalized, fromChannel: normalized !== prompt };
145
+ }
146
+ export function extractUserPromptEvents(prompt) {
147
+ const events = [];
148
+ // Strip <channel> XML tags before running extractors
149
+ const { text: normalizedPrompt, fromChannel } = normalizePromptWithChannels(prompt);
150
+ const lower = normalizedPrompt.toLowerCase();
151
+ const channelTags = fromChannel ? ["source:telegram"] : undefined;
152
+ // Decision extraction with negative-match guards
153
+ const hasNegative = NEGATIVE_PATTERNS.some(np => lower.includes(np));
154
+ if (!hasNegative) {
155
+ const decisionPatterns = [
156
+ /\b(don'?t|never|always|prefer|use .+ instead)\b/i,
157
+ ];
158
+ for (const pattern of decisionPatterns) {
159
+ if (pattern.test(normalizedPrompt)) {
160
+ const event = {
161
+ type: "user_decision",
162
+ category: "decision",
163
+ data: truncate(normalizedPrompt),
164
+ priority: 1,
165
+ };
166
+ if (channelTags)
167
+ event.tags = channelTags;
168
+ events.push(event);
169
+ break;
170
+ }
171
+ }
172
+ }
173
+ // Role extraction
174
+ const rolePatterns = [
175
+ /\b(i'?m a|act as|i am a|as a|my role)\b/i,
176
+ /\b(senior|junior|staff|lead|principal)\s+(engineer|developer|scientist|designer)\b/i,
177
+ ];
178
+ for (const pattern of rolePatterns) {
179
+ if (pattern.test(normalizedPrompt)) {
180
+ const event = {
181
+ type: "user_role",
182
+ category: "role",
183
+ data: truncate(normalizedPrompt),
184
+ priority: 2,
185
+ };
186
+ if (channelTags)
187
+ event.tags = channelTags;
188
+ events.push(event);
189
+ break;
190
+ }
191
+ }
192
+ // Intent extraction
193
+ const intentMap = [
194
+ [/\b(why|explain|debug|investigate|understand)\b/i, "investigate"],
195
+ [/\b(create|fix|build|implement|add|write)\b/i, "implement"],
196
+ [/\b(review|check|verify|test|validate)\b/i, "review"],
197
+ [/\b(refactor|clean|simplify|optimize)\b/i, "refactor"],
198
+ ];
199
+ for (const [pattern, intent] of intentMap) {
200
+ if (pattern.test(normalizedPrompt)) {
201
+ const event = {
202
+ type: `intent_${intent}`,
203
+ category: "intent",
204
+ data: intent,
205
+ priority: 3,
206
+ };
207
+ if (channelTags)
208
+ event.tags = channelTags;
209
+ events.push(event);
210
+ break;
211
+ }
212
+ }
213
+ return events;
214
+ }
215
+ //# sourceMappingURL=extractors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractors.js","sourceRoot":"","sources":["../../../src/hooks/extractors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /** Returns the log path — overridable via LCM_LOG_PATH env var for test isolation. */
2
+ export declare function getLogPath(): string;
3
+ /** Reset circuit breaker — for testing only. */
4
+ export declare function _resetCircuitBreaker(): void;
5
+ /**
6
+ * Three-layer error fence for hook processes.
7
+ * Layer 1: Sidecar DB error_log table (queryable by doctor/stats)
8
+ * Layer 2: Flat file ~/.lossless-claude/logs/events.log
9
+ * Layer 3: Swallow silently — hooks must never crash
10
+ */
11
+ export declare function safeLogError(hook: string, error: unknown, opts: {
12
+ cwd?: string;
13
+ sessionId?: string;
14
+ }): void;
@@ -0,0 +1,56 @@
1
+ // src/hooks/hook-errors.ts
2
+ import { EventsDb } from "./events-db.js";
3
+ import { eventsDbPath } from "../db/events-path.js";
4
+ import { appendFileSync, mkdirSync } from "node:fs";
5
+ import { dirname } from "node:path";
6
+ import { join } from "node:path";
7
+ import { homedir } from "node:os";
8
+ /** Returns the log path — overridable via LCM_LOG_PATH env var for test isolation. */
9
+ export function getLogPath() {
10
+ return process.env.LCM_LOG_PATH ?? join(homedir(), ".lossless-claude", "logs", "events.log");
11
+ }
12
+ let dbCircuitOpen = false;
13
+ /** Reset circuit breaker — for testing only. */
14
+ export function _resetCircuitBreaker() {
15
+ dbCircuitOpen = false;
16
+ }
17
+ /**
18
+ * Three-layer error fence for hook processes.
19
+ * Layer 1: Sidecar DB error_log table (queryable by doctor/stats)
20
+ * Layer 2: Flat file ~/.lossless-claude/logs/events.log
21
+ * Layer 3: Swallow silently — hooks must never crash
22
+ */
23
+ export function safeLogError(hook, error, opts) {
24
+ // Layer 1: Sidecar DB (skip if cwd missing or circuit open)
25
+ if (opts.cwd && !dbCircuitOpen) {
26
+ try {
27
+ const db = new EventsDb(eventsDbPath(opts.cwd));
28
+ try {
29
+ db.logHookError(hook, error, opts.sessionId);
30
+ }
31
+ finally {
32
+ db.close();
33
+ }
34
+ return;
35
+ }
36
+ catch {
37
+ dbCircuitOpen = true; // skip DB on subsequent calls this process
38
+ }
39
+ }
40
+ // Layer 2: Flat file (include cwd for diagnosing DB-skip cases)
41
+ try {
42
+ const logPath = getLogPath();
43
+ mkdirSync(dirname(logPath), { recursive: true });
44
+ appendFileSync(logPath, JSON.stringify({
45
+ ts: new Date().toISOString(),
46
+ hook,
47
+ error: error instanceof Error ? error.message : String(error),
48
+ session_id: opts.sessionId,
49
+ cwd: opts.cwd,
50
+ }) + "\n");
51
+ return;
52
+ }
53
+ catch { /* file failed — fall through */ }
54
+ // Layer 3: Swallow silently — hooks must never crash
55
+ }
56
+ //# sourceMappingURL=hook-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-errors.js","sourceRoot":"","sources":["../../../src/hooks/hook-errors.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,sFAAsF;AACtF,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;AAC/F,CAAC;AAED,IAAI,aAAa,GAAG,KAAK,CAAC;AAE1B,gDAAgD;AAChD,MAAM,UAAU,oBAAoB;IAClC,aAAa,GAAG,KAAK,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,KAAc,EACd,IAA0C;IAE1C,4DAA4D;IAC5D,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAChD,IAAI,CAAC;gBACH,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAC/C,CAAC;oBAAS,CAAC;gBACT,EAAE,CAAC,KAAK,EAAE,CAAC;YACb,CAAC;YACD,OAAO;QACT,CAAC;QAAC,MAAM,CAAC;YACP,aAAa,GAAG,IAAI,CAAC,CAAC,2CAA2C;QACnE,CAAC;IACH,CAAC;IAED,gEAAgE;IAChE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;QAC7B,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC;YACrC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI;YACJ,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAC7D,UAAU,EAAE,IAAI,CAAC,SAAS;YAC1B,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC,GAAG,IAAI,CAAC,CAAC;QACX,OAAO;IACT,CAAC;IAAC,MAAM,CAAC,CAAC,gCAAgC,CAAC,CAAC;IAE5C,qDAAqD;AACvD,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function handlePostToolUse(stdin: string): Promise<{
2
+ exitCode: number;
3
+ stdout: string;
4
+ }>;