@stackmemoryai/stackmemory 0.3.17 → 0.3.19
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/dist/cli/claude-sm.js +51 -5
- package/dist/cli/claude-sm.js.map +2 -2
- package/dist/cli/codex-sm.js +52 -19
- package/dist/cli/codex-sm.js.map +2 -2
- package/dist/cli/commands/db.js +143 -0
- package/dist/cli/commands/db.js.map +7 -0
- package/dist/cli/commands/login.js +50 -0
- package/dist/cli/commands/login.js.map +7 -0
- package/dist/cli/commands/migrate.js +178 -0
- package/dist/cli/commands/migrate.js.map +7 -0
- package/dist/cli/commands/onboard.js +158 -2
- package/dist/cli/commands/onboard.js.map +2 -2
- package/dist/cli/commands/skills.js +15 -2
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/index.js +118 -834
- package/dist/cli/index.js.map +3 -3
- package/dist/core/context/dual-stack-manager.js +1 -1
- package/dist/core/context/dual-stack-manager.js.map +1 -1
- package/dist/core/context/frame-database.js +1 -0
- package/dist/core/context/frame-database.js.map +2 -2
- package/dist/core/context/frame-manager.js +59 -2
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/core/database/database-adapter.js +6 -1
- package/dist/core/database/database-adapter.js.map +2 -2
- package/dist/core/database/sqlite-adapter.js +60 -2
- package/dist/core/database/sqlite-adapter.js.map +2 -2
- package/dist/integrations/claude-code/subagent-client.js +106 -3
- package/dist/integrations/claude-code/subagent-client.js.map +2 -2
- package/dist/servers/railway/config.js +51 -0
- package/dist/servers/railway/config.js.map +7 -0
- package/dist/servers/railway/index-enhanced.js +156 -0
- package/dist/servers/railway/index-enhanced.js.map +7 -0
- package/dist/servers/railway/index.js +843 -82
- package/dist/servers/railway/index.js.map +3 -3
- package/dist/servers/railway/minimal.js +48 -3
- package/dist/servers/railway/minimal.js.map +2 -2
- package/dist/servers/railway/storage-test.js +455 -0
- package/dist/servers/railway/storage-test.js.map +7 -0
- package/dist/skills/claude-skills.js +13 -12
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/skills/recursive-agent-orchestrator.js +27 -18
- package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
- package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
- package/package.json +13 -21
- package/scripts/README-TESTING.md +186 -0
- package/scripts/analyze-cli-security.js +288 -0
- package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
- package/scripts/archive/analyze-linear-duplicates.js +214 -0
- package/scripts/archive/analyze-remaining-duplicates.js +230 -0
- package/scripts/archive/analyze-sta-duplicates.js +292 -0
- package/scripts/archive/analyze-sta-graphql.js +399 -0
- package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
- package/scripts/archive/check-all-duplicates.ts +419 -0
- package/scripts/archive/clean-duplicate-tasks.js +114 -0
- package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
- package/scripts/archive/create-phase-tasks.js +387 -0
- package/scripts/archive/delete-linear-duplicates.js +182 -0
- package/scripts/archive/delete-remaining-duplicates.js +158 -0
- package/scripts/archive/delete-sta-duplicates.js +201 -0
- package/scripts/archive/delete-sta-oauth.js +201 -0
- package/scripts/archive/export-sta-tasks.js +62 -0
- package/scripts/archive/install-auto-sync.js +266 -0
- package/scripts/archive/install-chromadb-hooks.sh +133 -0
- package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
- package/scripts/archive/install-post-task-hooks.sh +289 -0
- package/scripts/archive/install-stackmemory-hooks.sh +420 -0
- package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
- package/scripts/archive/merge-linear-duplicates.ts +180 -0
- package/scripts/archive/remove-sta-tasks.js +70 -0
- package/scripts/archive/setup-background-sync.sh +168 -0
- package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
- package/scripts/archive/setup-claude-autostart.sh +305 -0
- package/scripts/archive/setup-git-hooks.sh +25 -0
- package/scripts/archive/setup-linear-oauth.sh +46 -0
- package/scripts/archive/setup-mcp.sh +113 -0
- package/scripts/archive/setup-railway-deployment.sh +81 -0
- package/scripts/auto-handoff.sh +262 -0
- package/scripts/background-sync-manager.js +416 -0
- package/scripts/benchmark-performance.ts +57 -0
- package/scripts/check-redis.ts +48 -0
- package/scripts/chromadb-auto-loader.sh +128 -0
- package/scripts/chromadb-context-loader.js +479 -0
- package/scripts/claude-chromadb-hook.js +460 -0
- package/scripts/claude-code-wrapper.sh +66 -0
- package/scripts/claude-linear-skill.js +455 -0
- package/scripts/claude-pre-commit.sh +302 -0
- package/scripts/claude-sm-autostart.js +532 -0
- package/scripts/claude-sm-setup.sh +367 -0
- package/scripts/claude-with-chromadb.sh +69 -0
- package/scripts/claude-worktree-manager.sh +323 -0
- package/scripts/claude-worktree-monitor.sh +371 -0
- package/scripts/claude-worktree-setup.sh +327 -0
- package/scripts/clean-linear-backlog.js +273 -0
- package/scripts/cleanup-old-sessions.sh +57 -0
- package/scripts/codex-wrapper.sh +88 -0
- package/scripts/create-sandbox.sh +269 -0
- package/scripts/debug-linear-update.js +174 -0
- package/scripts/delete-linear-tasks.js +167 -0
- package/scripts/deploy.sh +89 -0
- package/scripts/deployment/railway.sh +352 -0
- package/scripts/deployment/test-deployment.js +194 -0
- package/scripts/detect-and-rehydrate.js +162 -0
- package/scripts/detect-and-rehydrate.mjs +165 -0
- package/scripts/development/create-demo-tasks.js +143 -0
- package/scripts/development/debug-frame-test.js +16 -0
- package/scripts/development/demo-auto-sync.js +128 -0
- package/scripts/development/fix-all-imports.js +213 -0
- package/scripts/development/fix-imports.js +229 -0
- package/scripts/development/fix-lint-loop.cjs +103 -0
- package/scripts/development/fix-project-id.ts +161 -0
- package/scripts/development/fix-strict-mode-issues.ts +291 -0
- package/scripts/development/reorganize-structure.sh +228 -0
- package/scripts/development/test-persistence-direct.js +148 -0
- package/scripts/development/test-persistence.js +114 -0
- package/scripts/development/test-tasks.js +93 -0
- package/scripts/development/update-imports.js +212 -0
- package/scripts/fetch-linear-status.js +125 -0
- package/scripts/git-hooks/README.md +310 -0
- package/scripts/git-hooks/branch-context-manager.sh +342 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
- package/scripts/hooks/cleanup-shell.sh +130 -0
- package/scripts/hooks/task-complete.sh +114 -0
- package/scripts/initialize.ts +129 -0
- package/scripts/install-claude-hooks-auto.js +104 -0
- package/scripts/install-claude-hooks.sh +133 -0
- package/scripts/install-global.sh +296 -0
- package/scripts/install.sh +235 -0
- package/scripts/linear-auto-sync.js +262 -0
- package/scripts/linear-auto-sync.sh +161 -0
- package/scripts/linear-sync-daemon.js +150 -0
- package/scripts/linear-task-review.js +237 -0
- package/scripts/list-linear-tasks.ts +178 -0
- package/scripts/mcp-proxy.js +66 -0
- package/scripts/opencode-wrapper.sh +85 -0
- package/scripts/publish-local.js +74 -0
- package/scripts/query-chromadb.ts +201 -0
- package/scripts/railway-env-setup.sh +39 -0
- package/scripts/reconcile-local-tasks.js +170 -0
- package/scripts/recreate-frames-db.js +89 -0
- package/scripts/setup/claude-integration.js +138 -0
- package/scripts/setup/configure-alias.js +125 -0
- package/scripts/setup/configure-codex-alias.js +161 -0
- package/scripts/setup/configure-opencode-alias.js +175 -0
- package/scripts/setup-claude-integration.js +204 -0
- package/scripts/setup-claude-integration.sh +183 -0
- package/scripts/setup-railway-deployment.sh +37 -0
- package/scripts/setup.sh +31 -0
- package/scripts/show-linear-summary.ts +172 -0
- package/scripts/stackmemory-auto-handoff.sh +231 -0
- package/scripts/stackmemory-daemon.sh +40 -0
- package/scripts/start-linear-sync-daemon.sh +141 -0
- package/scripts/start-temporal-paradox.sh +214 -0
- package/scripts/status.ts +159 -0
- package/scripts/sync-and-clean-tasks.js +258 -0
- package/scripts/sync-frames-from-railway.js +228 -0
- package/scripts/sync-linear-graphql.js +303 -0
- package/scripts/sync-linear-tasks.js +186 -0
- package/scripts/test-auto-triggers.sh +57 -0
- package/scripts/test-browser-mcp.js +74 -0
- package/scripts/test-chromadb-full.js +115 -0
- package/scripts/test-chromadb-hooks.sh +28 -0
- package/scripts/test-chromadb-sync.ts +245 -0
- package/scripts/test-cli-security.js +293 -0
- package/scripts/test-hooks-persistence.sh +220 -0
- package/scripts/test-installation-scenarios.sh +359 -0
- package/scripts/test-installation.sh +224 -0
- package/scripts/test-mcp.js +163 -0
- package/scripts/test-pre-publish-quick.sh +75 -0
- package/scripts/test-quality-gates.sh +263 -0
- package/scripts/test-railway-db.js +222 -0
- package/scripts/test-redis-storage.ts +490 -0
- package/scripts/test-rlm-basic.sh +122 -0
- package/scripts/test-rlm-comprehensive.sh +260 -0
- package/scripts/test-rlm-e2e.sh +268 -0
- package/scripts/test-rlm-simple.js +90 -0
- package/scripts/test-rlm.js +110 -0
- package/scripts/test-session-handoff.sh +165 -0
- package/scripts/test-shell-integration.sh +275 -0
- package/scripts/testing/ab-test-runner.ts +508 -0
- package/scripts/testing/collect-metrics.ts +457 -0
- package/scripts/testing/quick-effectiveness-demo.js +187 -0
- package/scripts/testing/real-performance-test.js +422 -0
- package/scripts/testing/run-effectiveness-tests.sh +176 -0
- package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
- package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
- package/scripts/testing/simple-effectiveness-test.js +310 -0
- package/scripts/testing/src/core/context/context-bridge.js +253 -0
- package/scripts/testing/src/core/context/frame-manager.js +746 -0
- package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
- package/scripts/testing/src/core/database/database-adapter.js +54 -0
- package/scripts/testing/src/core/errors/index.js +291 -0
- package/scripts/testing/src/core/errors/recovery.js +268 -0
- package/scripts/testing/src/core/monitoring/logger.js +145 -0
- package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
- package/scripts/testing/src/core/session/index.js +1 -0
- package/scripts/testing/src/core/session/session-manager.js +323 -0
- package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
- package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
- package/scripts/testing/src/core/trace/debug-trace.js +398 -0
- package/scripts/testing/src/core/trace/index.js +120 -0
- package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
- package/scripts/update-linear-status.js +268 -0
- package/scripts/update-linear-tasks-fixed.js +284 -0
- package/scripts/verify-railway-schema.ts +35 -0
- package/templates/claude-hooks/hooks.json +5 -0
- package/templates/claude-hooks/on-clear.js +56 -0
- package/templates/claude-hooks/on-startup.js +56 -0
- package/templates/claude-hooks/tool-use-trace.js +67 -0
- package/dist/features/tui/components/analytics-panel.js +0 -157
- package/dist/features/tui/components/analytics-panel.js.map +0 -7
- package/dist/features/tui/components/frame-visualizer.js +0 -377
- package/dist/features/tui/components/frame-visualizer.js.map +0 -7
- package/dist/features/tui/components/pr-tracker.js +0 -135
- package/dist/features/tui/components/pr-tracker.js.map +0 -7
- package/dist/features/tui/components/session-monitor.js +0 -299
- package/dist/features/tui/components/session-monitor.js.map +0 -7
- package/dist/features/tui/components/subagent-fleet.js +0 -395
- package/dist/features/tui/components/subagent-fleet.js.map +0 -7
- package/dist/features/tui/components/task-board.js +0 -1139
- package/dist/features/tui/components/task-board.js.map +0 -7
- package/dist/features/tui/index.js +0 -408
- package/dist/features/tui/index.js.map +0 -7
- package/dist/features/tui/services/data-service.js +0 -641
- package/dist/features/tui/services/data-service.js.map +0 -7
- package/dist/features/tui/services/linear-task-reader.js +0 -102
- package/dist/features/tui/services/linear-task-reader.js.map +0 -7
- package/dist/features/tui/services/websocket-client.js +0 -162
- package/dist/features/tui/services/websocket-client.js.map +0 -7
- package/dist/features/tui/terminal-compat.js +0 -220
- package/dist/features/tui/terminal-compat.js.map +0 -7
- package/dist/features/tui/types.js +0 -1
- package/dist/features/tui/types.js.map +0 -7
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes for StackMemory
|
|
3
|
+
* Provides a hierarchy of error types for better error handling and debugging
|
|
4
|
+
*/
|
|
5
|
+
export var ErrorCode;
|
|
6
|
+
(function (ErrorCode) {
|
|
7
|
+
// Database errors (1000-1999)
|
|
8
|
+
ErrorCode["DB_CONNECTION_FAILED"] = "DB_001";
|
|
9
|
+
ErrorCode["DB_QUERY_FAILED"] = "DB_002";
|
|
10
|
+
ErrorCode["DB_TRANSACTION_FAILED"] = "DB_003";
|
|
11
|
+
ErrorCode["DB_MIGRATION_FAILED"] = "DB_004";
|
|
12
|
+
ErrorCode["DB_CONSTRAINT_VIOLATION"] = "DB_005";
|
|
13
|
+
ErrorCode["DB_SCHEMA_ERROR"] = "DB_006";
|
|
14
|
+
ErrorCode["DB_INSERT_FAILED"] = "DB_007";
|
|
15
|
+
ErrorCode["DB_UPDATE_FAILED"] = "DB_008";
|
|
16
|
+
ErrorCode["DB_DELETE_FAILED"] = "DB_009";
|
|
17
|
+
// Frame errors (2000-2999)
|
|
18
|
+
ErrorCode["FRAME_NOT_FOUND"] = "FRAME_001";
|
|
19
|
+
ErrorCode["FRAME_INVALID_STATE"] = "FRAME_002";
|
|
20
|
+
ErrorCode["FRAME_PARENT_NOT_FOUND"] = "FRAME_003";
|
|
21
|
+
ErrorCode["FRAME_CYCLE_DETECTED"] = "FRAME_004";
|
|
22
|
+
ErrorCode["FRAME_ALREADY_CLOSED"] = "FRAME_005";
|
|
23
|
+
ErrorCode["FRAME_INIT_FAILED"] = "FRAME_006";
|
|
24
|
+
ErrorCode["FRAME_INVALID_INPUT"] = "FRAME_007";
|
|
25
|
+
ErrorCode["FRAME_STACK_OVERFLOW"] = "FRAME_008";
|
|
26
|
+
// Task errors (3000-3999)
|
|
27
|
+
ErrorCode["TASK_NOT_FOUND"] = "TASK_001";
|
|
28
|
+
ErrorCode["TASK_INVALID_STATE"] = "TASK_002";
|
|
29
|
+
ErrorCode["TASK_DEPENDENCY_CONFLICT"] = "TASK_003";
|
|
30
|
+
ErrorCode["TASK_CIRCULAR_DEPENDENCY"] = "TASK_004";
|
|
31
|
+
// Integration errors (4000-4999)
|
|
32
|
+
ErrorCode["LINEAR_AUTH_FAILED"] = "LINEAR_001";
|
|
33
|
+
ErrorCode["LINEAR_API_ERROR"] = "LINEAR_002";
|
|
34
|
+
ErrorCode["LINEAR_SYNC_FAILED"] = "LINEAR_003";
|
|
35
|
+
ErrorCode["LINEAR_WEBHOOK_FAILED"] = "LINEAR_004";
|
|
36
|
+
// MCP errors (5000-5999)
|
|
37
|
+
ErrorCode["MCP_TOOL_NOT_FOUND"] = "MCP_001";
|
|
38
|
+
ErrorCode["MCP_INVALID_PARAMS"] = "MCP_002";
|
|
39
|
+
ErrorCode["MCP_EXECUTION_FAILED"] = "MCP_003";
|
|
40
|
+
ErrorCode["MCP_RATE_LIMITED"] = "MCP_004";
|
|
41
|
+
// Project errors (6000-6999)
|
|
42
|
+
ErrorCode["PROJECT_NOT_FOUND"] = "PROJECT_001";
|
|
43
|
+
ErrorCode["PROJECT_INVALID_PATH"] = "PROJECT_002";
|
|
44
|
+
ErrorCode["PROJECT_GIT_ERROR"] = "PROJECT_003";
|
|
45
|
+
// Validation errors (7000-7999)
|
|
46
|
+
ErrorCode["VALIDATION_FAILED"] = "VAL_001";
|
|
47
|
+
ErrorCode["INVALID_INPUT"] = "VAL_002";
|
|
48
|
+
ErrorCode["MISSING_REQUIRED_FIELD"] = "VAL_003";
|
|
49
|
+
ErrorCode["TYPE_MISMATCH"] = "VAL_004";
|
|
50
|
+
// System errors (8000-8999)
|
|
51
|
+
ErrorCode["INITIALIZATION_ERROR"] = "SYS_001";
|
|
52
|
+
ErrorCode["NOT_FOUND"] = "SYS_002";
|
|
53
|
+
ErrorCode["INTERNAL_ERROR"] = "SYS_003";
|
|
54
|
+
ErrorCode["CONFIGURATION_ERROR"] = "SYS_004";
|
|
55
|
+
ErrorCode["PERMISSION_DENIED"] = "SYS_005";
|
|
56
|
+
ErrorCode["RESOURCE_EXHAUSTED"] = "SYS_006";
|
|
57
|
+
ErrorCode["SERVICE_UNAVAILABLE"] = "SYS_007";
|
|
58
|
+
ErrorCode["SYSTEM_INIT_FAILED"] = "SYS_008";
|
|
59
|
+
// Collaboration errors (9000-9999)
|
|
60
|
+
ErrorCode["STACK_CONTEXT_NOT_FOUND"] = "COLLAB_001";
|
|
61
|
+
ErrorCode["HANDOFF_REQUEST_EXPIRED"] = "COLLAB_002";
|
|
62
|
+
ErrorCode["MERGE_CONFLICT_UNRESOLVABLE"] = "COLLAB_003";
|
|
63
|
+
ErrorCode["PERMISSION_VIOLATION"] = "COLLAB_004";
|
|
64
|
+
ErrorCode["OPERATION_FAILED"] = "COLLAB_005";
|
|
65
|
+
ErrorCode["OPERATION_EXPIRED"] = "COLLAB_006";
|
|
66
|
+
ErrorCode["INVALID_STATE"] = "COLLAB_007";
|
|
67
|
+
ErrorCode["RESOURCE_NOT_FOUND"] = "COLLAB_008";
|
|
68
|
+
ErrorCode["HANDOFF_ALREADY_EXISTS"] = "COLLAB_009";
|
|
69
|
+
ErrorCode["MERGE_SESSION_INVALID"] = "COLLAB_010";
|
|
70
|
+
ErrorCode["STACK_SWITCH_FAILED"] = "COLLAB_011";
|
|
71
|
+
ErrorCode["APPROVAL_TIMEOUT"] = "COLLAB_012";
|
|
72
|
+
ErrorCode["CONFLICT_RESOLUTION_FAILED"] = "COLLAB_013";
|
|
73
|
+
ErrorCode["TEAM_ACCESS_DENIED"] = "COLLAB_014";
|
|
74
|
+
ErrorCode["STACK_LIMIT_EXCEEDED"] = "COLLAB_015";
|
|
75
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
76
|
+
/**
|
|
77
|
+
* Base error class for all StackMemory errors
|
|
78
|
+
*/
|
|
79
|
+
export class StackMemoryError extends Error {
|
|
80
|
+
constructor(options) {
|
|
81
|
+
super(options.message);
|
|
82
|
+
this.name = this.constructor.name;
|
|
83
|
+
this.code = options.code;
|
|
84
|
+
this.context = options.context;
|
|
85
|
+
this.cause = options.cause;
|
|
86
|
+
this.isRetryable = options.isRetryable ?? false;
|
|
87
|
+
this.httpStatus = options.httpStatus ?? 500;
|
|
88
|
+
this.timestamp = new Date();
|
|
89
|
+
// Maintains proper stack trace for where our error was thrown
|
|
90
|
+
if (Error.captureStackTrace) {
|
|
91
|
+
Error.captureStackTrace(this, this.constructor);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
toJSON() {
|
|
95
|
+
return {
|
|
96
|
+
name: this.name,
|
|
97
|
+
code: this.code,
|
|
98
|
+
message: this.message,
|
|
99
|
+
context: this.context,
|
|
100
|
+
isRetryable: this.isRetryable,
|
|
101
|
+
httpStatus: this.httpStatus,
|
|
102
|
+
timestamp: this.timestamp.toISOString(),
|
|
103
|
+
stack: this.stack,
|
|
104
|
+
cause: this.cause?.message,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Database-related errors
|
|
110
|
+
*/
|
|
111
|
+
export class DatabaseError extends StackMemoryError {
|
|
112
|
+
constructor(message, code = ErrorCode.DB_QUERY_FAILED, context, cause) {
|
|
113
|
+
super({
|
|
114
|
+
code,
|
|
115
|
+
message,
|
|
116
|
+
context,
|
|
117
|
+
cause,
|
|
118
|
+
isRetryable: code === ErrorCode.DB_CONNECTION_FAILED,
|
|
119
|
+
httpStatus: 503,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Frame-related errors
|
|
125
|
+
*/
|
|
126
|
+
export class FrameError extends StackMemoryError {
|
|
127
|
+
constructor(message, code = ErrorCode.FRAME_INVALID_STATE, context) {
|
|
128
|
+
super({
|
|
129
|
+
code,
|
|
130
|
+
message,
|
|
131
|
+
context,
|
|
132
|
+
isRetryable: false,
|
|
133
|
+
httpStatus: 400,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Task-related errors
|
|
139
|
+
*/
|
|
140
|
+
export class TaskError extends StackMemoryError {
|
|
141
|
+
constructor(message, code = ErrorCode.TASK_INVALID_STATE, context) {
|
|
142
|
+
super({
|
|
143
|
+
code,
|
|
144
|
+
message,
|
|
145
|
+
context,
|
|
146
|
+
isRetryable: false,
|
|
147
|
+
httpStatus: 400,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Integration errors (Linear, etc.)
|
|
153
|
+
*/
|
|
154
|
+
export class IntegrationError extends StackMemoryError {
|
|
155
|
+
constructor(message, code = ErrorCode.LINEAR_API_ERROR, context, cause) {
|
|
156
|
+
super({
|
|
157
|
+
code,
|
|
158
|
+
message,
|
|
159
|
+
context,
|
|
160
|
+
cause,
|
|
161
|
+
isRetryable: true,
|
|
162
|
+
httpStatus: 502,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* MCP-related errors
|
|
168
|
+
*/
|
|
169
|
+
export class MCPError extends StackMemoryError {
|
|
170
|
+
constructor(message, code = ErrorCode.MCP_EXECUTION_FAILED, context) {
|
|
171
|
+
super({
|
|
172
|
+
code,
|
|
173
|
+
message,
|
|
174
|
+
context,
|
|
175
|
+
isRetryable: code === ErrorCode.MCP_RATE_LIMITED,
|
|
176
|
+
httpStatus: code === ErrorCode.MCP_RATE_LIMITED ? 429 : 400,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Validation errors
|
|
182
|
+
*/
|
|
183
|
+
export class ValidationError extends StackMemoryError {
|
|
184
|
+
constructor(message, code = ErrorCode.VALIDATION_FAILED, context) {
|
|
185
|
+
super({
|
|
186
|
+
code,
|
|
187
|
+
message,
|
|
188
|
+
context,
|
|
189
|
+
isRetryable: false,
|
|
190
|
+
httpStatus: 400,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Project-related errors
|
|
196
|
+
*/
|
|
197
|
+
export class ProjectError extends StackMemoryError {
|
|
198
|
+
constructor(message, code = ErrorCode.PROJECT_NOT_FOUND, context) {
|
|
199
|
+
super({
|
|
200
|
+
code,
|
|
201
|
+
message,
|
|
202
|
+
context,
|
|
203
|
+
isRetryable: false,
|
|
204
|
+
httpStatus: 404,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* System/Internal errors
|
|
210
|
+
*/
|
|
211
|
+
export class SystemError extends StackMemoryError {
|
|
212
|
+
constructor(message, code = ErrorCode.INTERNAL_ERROR, context, cause) {
|
|
213
|
+
super({
|
|
214
|
+
code,
|
|
215
|
+
message,
|
|
216
|
+
context,
|
|
217
|
+
cause,
|
|
218
|
+
isRetryable: code === ErrorCode.SERVICE_UNAVAILABLE,
|
|
219
|
+
httpStatus: 500,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Helper function to determine if an error is retryable
|
|
225
|
+
*/
|
|
226
|
+
export function isRetryableError(error) {
|
|
227
|
+
if (error instanceof StackMemoryError) {
|
|
228
|
+
return error.isRetryable;
|
|
229
|
+
}
|
|
230
|
+
// Check for common retryable error patterns
|
|
231
|
+
if (error instanceof Error) {
|
|
232
|
+
const message = error.message.toLowerCase();
|
|
233
|
+
return (message.includes('econnrefused') ||
|
|
234
|
+
message.includes('timeout') ||
|
|
235
|
+
message.includes('enotfound') ||
|
|
236
|
+
message.includes('socket hang up'));
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Helper function to safely extract error message
|
|
242
|
+
*/
|
|
243
|
+
export function getErrorMessage(error) {
|
|
244
|
+
if (error instanceof Error) {
|
|
245
|
+
return error.message;
|
|
246
|
+
}
|
|
247
|
+
if (typeof error === 'string') {
|
|
248
|
+
return error;
|
|
249
|
+
}
|
|
250
|
+
if (error && typeof error === 'object' && 'message' in error) {
|
|
251
|
+
return String(error.message);
|
|
252
|
+
}
|
|
253
|
+
return 'An unknown error occurred';
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Helper function to wrap unknown errors in StackMemoryError
|
|
257
|
+
*/
|
|
258
|
+
export function wrapError(error, defaultMessage, code = ErrorCode.INTERNAL_ERROR, context) {
|
|
259
|
+
if (error instanceof StackMemoryError) {
|
|
260
|
+
return error;
|
|
261
|
+
}
|
|
262
|
+
const cause = error instanceof Error ? error : undefined;
|
|
263
|
+
const message = error instanceof Error ? error.message : defaultMessage;
|
|
264
|
+
return new SystemError(message, code, context, cause);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Type guard to check if error is a StackMemoryError
|
|
268
|
+
*/
|
|
269
|
+
export function isStackMemoryError(error) {
|
|
270
|
+
return error instanceof StackMemoryError;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Create context-aware error handler
|
|
274
|
+
*/
|
|
275
|
+
export function createErrorHandler(defaultContext) {
|
|
276
|
+
return (error, additionalContext) => {
|
|
277
|
+
const context = { ...defaultContext, ...additionalContext };
|
|
278
|
+
if (error instanceof StackMemoryError) {
|
|
279
|
+
// Create a new error with merged context since context is readonly
|
|
280
|
+
return new StackMemoryError({
|
|
281
|
+
code: error.code,
|
|
282
|
+
message: error.message,
|
|
283
|
+
context: { ...error.context, ...context },
|
|
284
|
+
cause: error.cause,
|
|
285
|
+
isRetryable: error.isRetryable,
|
|
286
|
+
httpStatus: error.httpStatus,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return wrapError(error, getErrorMessage(error), ErrorCode.INTERNAL_ERROR, context);
|
|
290
|
+
};
|
|
291
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error recovery utilities for StackMemory
|
|
3
|
+
* Provides retry logic, circuit breakers, and fallback mechanisms
|
|
4
|
+
*/
|
|
5
|
+
import { logger } from '../monitoring/logger.js';
|
|
6
|
+
import { isRetryableError, getErrorMessage, } from './index.js';
|
|
7
|
+
export var CircuitState;
|
|
8
|
+
(function (CircuitState) {
|
|
9
|
+
CircuitState["CLOSED"] = "closed";
|
|
10
|
+
CircuitState["OPEN"] = "open";
|
|
11
|
+
CircuitState["HALF_OPEN"] = "half_open";
|
|
12
|
+
})(CircuitState || (CircuitState = {}));
|
|
13
|
+
/**
|
|
14
|
+
* Exponential backoff with jitter
|
|
15
|
+
*/
|
|
16
|
+
export function calculateBackoff(attempt, initialDelay, maxDelay, factor) {
|
|
17
|
+
const exponentialDelay = Math.min(initialDelay * Math.pow(factor, attempt - 1), maxDelay);
|
|
18
|
+
// Add jitter (0-25% of delay)
|
|
19
|
+
const jitter = exponentialDelay * Math.random() * 0.25;
|
|
20
|
+
return Math.floor(exponentialDelay + jitter);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Retry with exponential backoff
|
|
24
|
+
*/
|
|
25
|
+
export async function retry(fn, options = {}) {
|
|
26
|
+
const { maxAttempts = 3, initialDelay = 1000, maxDelay = 30000, backoffFactor = 2, timeout, onRetry, } = options;
|
|
27
|
+
let lastError;
|
|
28
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
29
|
+
try {
|
|
30
|
+
// Add timeout if specified
|
|
31
|
+
if (timeout) {
|
|
32
|
+
return await Promise.race([
|
|
33
|
+
fn(),
|
|
34
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout)),
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
return await fn();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
lastError = error;
|
|
41
|
+
// Don't retry if not retryable or last attempt
|
|
42
|
+
if (!isRetryableError(error) || attempt === maxAttempts) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const delay = calculateBackoff(attempt, initialDelay, maxDelay, backoffFactor);
|
|
46
|
+
logger.warn(`Retry attempt ${attempt}/${maxAttempts} after ${delay}ms`, {
|
|
47
|
+
error: getErrorMessage(error),
|
|
48
|
+
attempt,
|
|
49
|
+
delay,
|
|
50
|
+
});
|
|
51
|
+
if (onRetry) {
|
|
52
|
+
onRetry(attempt, error);
|
|
53
|
+
}
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
throw lastError;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Circuit breaker implementation
|
|
61
|
+
*/
|
|
62
|
+
export class CircuitBreaker {
|
|
63
|
+
constructor(name, options = {}) {
|
|
64
|
+
this.name = name;
|
|
65
|
+
this.state = CircuitState.CLOSED;
|
|
66
|
+
this.failures = 0;
|
|
67
|
+
this.successCount = 0;
|
|
68
|
+
this.options = {
|
|
69
|
+
failureThreshold: options.failureThreshold ?? 5,
|
|
70
|
+
resetTimeout: options.resetTimeout ?? 60000,
|
|
71
|
+
halfOpenRequests: options.halfOpenRequests ?? 3,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async execute(fn) {
|
|
75
|
+
// Check if circuit should transition from OPEN to HALF_OPEN
|
|
76
|
+
if (this.state === CircuitState.OPEN) {
|
|
77
|
+
const timeSinceLastFailure = this.lastFailTime
|
|
78
|
+
? Date.now() - this.lastFailTime.getTime()
|
|
79
|
+
: 0;
|
|
80
|
+
if (timeSinceLastFailure >= this.options.resetTimeout) {
|
|
81
|
+
this.state = CircuitState.HALF_OPEN;
|
|
82
|
+
this.successCount = 0;
|
|
83
|
+
logger.info(`Circuit breaker ${this.name} entering half-open state`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
throw new Error(`Circuit breaker ${this.name} is OPEN. Retry after ${this.options.resetTimeout - timeSinceLastFailure}ms`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const result = await fn();
|
|
91
|
+
// Handle success
|
|
92
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
93
|
+
this.successCount++;
|
|
94
|
+
if (this.successCount >= this.options.halfOpenRequests) {
|
|
95
|
+
this.state = CircuitState.CLOSED;
|
|
96
|
+
this.failures = 0;
|
|
97
|
+
logger.info(`Circuit breaker ${this.name} is now CLOSED`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
this.failures = 0;
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
this.handleFailure(error);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
handleFailure(error) {
|
|
111
|
+
this.failures++;
|
|
112
|
+
this.lastFailTime = new Date();
|
|
113
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
114
|
+
this.state = CircuitState.OPEN;
|
|
115
|
+
logger.error(`Circuit breaker ${this.name} reopened due to failure in half-open state`);
|
|
116
|
+
}
|
|
117
|
+
else if (this.state === CircuitState.CLOSED &&
|
|
118
|
+
this.failures >= this.options.failureThreshold) {
|
|
119
|
+
this.state = CircuitState.OPEN;
|
|
120
|
+
logger.error(`Circuit breaker ${this.name} opened after ${this.failures} failures`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
getState() {
|
|
124
|
+
return this.state;
|
|
125
|
+
}
|
|
126
|
+
reset() {
|
|
127
|
+
this.state = CircuitState.CLOSED;
|
|
128
|
+
this.failures = 0;
|
|
129
|
+
this.successCount = 0;
|
|
130
|
+
this.lastFailTime = undefined;
|
|
131
|
+
logger.info(`Circuit breaker ${this.name} manually reset`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Fallback with multiple strategies
|
|
136
|
+
*/
|
|
137
|
+
export async function withFallback(primary, fallbacks, context) {
|
|
138
|
+
const errors = [];
|
|
139
|
+
// Try primary
|
|
140
|
+
try {
|
|
141
|
+
return await primary();
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
errors.push(error);
|
|
145
|
+
logger.warn('Primary operation failed, trying fallbacks', {
|
|
146
|
+
error: getErrorMessage(error),
|
|
147
|
+
context,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// Try fallbacks in order
|
|
151
|
+
for (let i = 0; i < fallbacks.length; i++) {
|
|
152
|
+
try {
|
|
153
|
+
const result = await fallbacks[i]();
|
|
154
|
+
logger.info(`Fallback ${i + 1} succeeded`, { context });
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
errors.push(error);
|
|
159
|
+
if (i < fallbacks.length - 1) {
|
|
160
|
+
logger.warn(`Fallback ${i + 1} failed, trying next`, {
|
|
161
|
+
error: getErrorMessage(error),
|
|
162
|
+
context,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// All attempts failed
|
|
168
|
+
throw new Error(`All attempts failed. Errors: ${errors.map(getErrorMessage).join(', ')}`);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Bulkhead pattern - limit concurrent operations
|
|
172
|
+
*/
|
|
173
|
+
export class Bulkhead {
|
|
174
|
+
constructor(name, maxConcurrent) {
|
|
175
|
+
this.name = name;
|
|
176
|
+
this.maxConcurrent = maxConcurrent;
|
|
177
|
+
this.running = 0;
|
|
178
|
+
this.queue = [];
|
|
179
|
+
}
|
|
180
|
+
async execute(fn) {
|
|
181
|
+
if (this.running >= this.maxConcurrent) {
|
|
182
|
+
await new Promise((resolve) => {
|
|
183
|
+
this.queue.push(resolve);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
this.running++;
|
|
187
|
+
try {
|
|
188
|
+
return await fn();
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
this.running--;
|
|
192
|
+
const next = this.queue.shift();
|
|
193
|
+
if (next) {
|
|
194
|
+
next();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
getStats() {
|
|
199
|
+
return {
|
|
200
|
+
running: this.running,
|
|
201
|
+
queued: this.queue.length,
|
|
202
|
+
maxConcurrent: this.maxConcurrent,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Timeout wrapper with proper cleanup
|
|
208
|
+
*/
|
|
209
|
+
export async function withTimeout(fn, timeoutMs, timeoutMessage) {
|
|
210
|
+
return Promise.race([
|
|
211
|
+
fn(),
|
|
212
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(timeoutMessage ?? `Operation timed out after ${timeoutMs}ms`)), timeoutMs)),
|
|
213
|
+
]);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Graceful degradation helper
|
|
217
|
+
*/
|
|
218
|
+
export async function gracefulDegrade(fn, defaultValue, logContext) {
|
|
219
|
+
try {
|
|
220
|
+
return await fn();
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
logger.warn('Operation failed, using default value', {
|
|
224
|
+
error: getErrorMessage(error),
|
|
225
|
+
...logContext,
|
|
226
|
+
});
|
|
227
|
+
return defaultValue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Create a resilient operation with multiple recovery strategies
|
|
232
|
+
*/
|
|
233
|
+
export function createResilientOperation(name, options = {}) {
|
|
234
|
+
const circuitBreaker = options.circuitBreaker
|
|
235
|
+
? new CircuitBreaker(name, options.circuitBreaker)
|
|
236
|
+
: null;
|
|
237
|
+
const bulkhead = options.bulkhead
|
|
238
|
+
? new Bulkhead(name, options.bulkhead)
|
|
239
|
+
: null;
|
|
240
|
+
return async (fn) => {
|
|
241
|
+
let currentFn = fn;
|
|
242
|
+
// Wrap with bulkhead if configured
|
|
243
|
+
if (bulkhead) {
|
|
244
|
+
const wrapped = currentFn;
|
|
245
|
+
currentFn = () => bulkhead.execute(wrapped);
|
|
246
|
+
}
|
|
247
|
+
// Wrap with timeout if configured
|
|
248
|
+
if (options.timeout) {
|
|
249
|
+
const wrapped = currentFn;
|
|
250
|
+
currentFn = () => withTimeout(wrapped, options.timeout);
|
|
251
|
+
}
|
|
252
|
+
// Wrap with retry if configured
|
|
253
|
+
if (options.retry) {
|
|
254
|
+
const wrapped = currentFn;
|
|
255
|
+
currentFn = () => retry(wrapped, options.retry);
|
|
256
|
+
}
|
|
257
|
+
// Wrap with circuit breaker if configured
|
|
258
|
+
if (circuitBreaker) {
|
|
259
|
+
const wrapped = currentFn;
|
|
260
|
+
currentFn = () => circuitBreaker.execute(wrapped);
|
|
261
|
+
}
|
|
262
|
+
// Execute with fallback if configured
|
|
263
|
+
if (options.fallback) {
|
|
264
|
+
return withFallback(currentFn, [options.fallback]);
|
|
265
|
+
}
|
|
266
|
+
return currentFn();
|
|
267
|
+
};
|
|
268
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured logging utility for StackMemory CLI
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
export var LogLevel;
|
|
7
|
+
(function (LogLevel) {
|
|
8
|
+
LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
|
|
9
|
+
LogLevel[LogLevel["WARN"] = 1] = "WARN";
|
|
10
|
+
LogLevel[LogLevel["INFO"] = 2] = "INFO";
|
|
11
|
+
LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
|
|
12
|
+
})(LogLevel || (LogLevel = {}));
|
|
13
|
+
export class Logger {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.logLevel = LogLevel.INFO;
|
|
16
|
+
this.fileLoggingDisabledNotified = false;
|
|
17
|
+
// Set log level from environment
|
|
18
|
+
const envLevel = process.env.STACKMEMORY_LOG_LEVEL?.toUpperCase();
|
|
19
|
+
switch (envLevel) {
|
|
20
|
+
case 'ERROR':
|
|
21
|
+
this.logLevel = LogLevel.ERROR;
|
|
22
|
+
break;
|
|
23
|
+
case 'WARN':
|
|
24
|
+
this.logLevel = LogLevel.WARN;
|
|
25
|
+
break;
|
|
26
|
+
case 'DEBUG':
|
|
27
|
+
this.logLevel = LogLevel.DEBUG;
|
|
28
|
+
break;
|
|
29
|
+
default:
|
|
30
|
+
this.logLevel = LogLevel.INFO;
|
|
31
|
+
}
|
|
32
|
+
// Set up log file if in debug mode or if specified
|
|
33
|
+
if (this.logLevel === LogLevel.DEBUG || process.env.STACKMEMORY_LOG_FILE) {
|
|
34
|
+
this.logFile =
|
|
35
|
+
process.env.STACKMEMORY_LOG_FILE ||
|
|
36
|
+
path.join(process.env.HOME || '.', '.stackmemory', 'logs', 'cli.log');
|
|
37
|
+
this.ensureLogDirectory();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
static getInstance() {
|
|
41
|
+
if (!Logger.instance) {
|
|
42
|
+
Logger.instance = new Logger();
|
|
43
|
+
}
|
|
44
|
+
return Logger.instance;
|
|
45
|
+
}
|
|
46
|
+
ensureLogDirectory() {
|
|
47
|
+
if (!this.logFile)
|
|
48
|
+
return;
|
|
49
|
+
const logDir = path.dirname(this.logFile);
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(logDir)) {
|
|
52
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
// Disable file logging if we cannot create the directory (e.g., ENOSPC)
|
|
57
|
+
this.logFile = undefined;
|
|
58
|
+
if (!this.fileLoggingDisabledNotified) {
|
|
59
|
+
this.fileLoggingDisabledNotified = true;
|
|
60
|
+
// Emit a single warning to console so we don't spam output
|
|
61
|
+
const msg = '[Logger] File logging disabled (failed to create log directory). Falling back to console only.';
|
|
62
|
+
// Use console directly to avoid recursion
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.warn(msg);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
writeLog(entry) {
|
|
69
|
+
const logLine = JSON.stringify(entry) + '\n';
|
|
70
|
+
// Always write to file if configured
|
|
71
|
+
if (this.logFile) {
|
|
72
|
+
try {
|
|
73
|
+
fs.appendFileSync(this.logFile, logLine);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
// Disable file logging on error (e.g., ENOSPC) to avoid repeated failures
|
|
77
|
+
this.logFile = undefined;
|
|
78
|
+
if (!this.fileLoggingDisabledNotified) {
|
|
79
|
+
this.fileLoggingDisabledNotified = true;
|
|
80
|
+
const msg = '[Logger] File logging disabled (write failed). Falling back to console only.';
|
|
81
|
+
// eslint-disable-next-line no-console
|
|
82
|
+
console.warn(msg);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Console output based on level
|
|
87
|
+
if (entry.level <= this.logLevel) {
|
|
88
|
+
const levelNames = ['ERROR', 'WARN', 'INFO', 'DEBUG'];
|
|
89
|
+
const levelName = levelNames[entry.level] || 'UNKNOWN';
|
|
90
|
+
const consoleMessage = `[${entry.timestamp}] ${levelName}: ${entry.message}`;
|
|
91
|
+
if (entry.level === LogLevel.ERROR) {
|
|
92
|
+
console.error(consoleMessage);
|
|
93
|
+
if (entry.error) {
|
|
94
|
+
console.error(entry.error.stack);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (entry.level === LogLevel.WARN) {
|
|
98
|
+
console.warn(consoleMessage);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.log(consoleMessage);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
error(message, errorOrContext, context) {
|
|
106
|
+
const isError = errorOrContext instanceof Error;
|
|
107
|
+
this.writeLog({
|
|
108
|
+
timestamp: new Date().toISOString(),
|
|
109
|
+
level: LogLevel.ERROR,
|
|
110
|
+
message,
|
|
111
|
+
context: isError ? context : errorOrContext,
|
|
112
|
+
error: isError ? errorOrContext : undefined,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
warn(message, errorOrContext) {
|
|
116
|
+
const isError = errorOrContext instanceof Error;
|
|
117
|
+
this.writeLog({
|
|
118
|
+
timestamp: new Date().toISOString(),
|
|
119
|
+
level: LogLevel.WARN,
|
|
120
|
+
message,
|
|
121
|
+
context: isError
|
|
122
|
+
? undefined
|
|
123
|
+
: errorOrContext,
|
|
124
|
+
error: isError ? errorOrContext : undefined,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
info(message, context) {
|
|
128
|
+
this.writeLog({
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
level: LogLevel.INFO,
|
|
131
|
+
message,
|
|
132
|
+
context,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
debug(message, context) {
|
|
136
|
+
this.writeLog({
|
|
137
|
+
timestamp: new Date().toISOString(),
|
|
138
|
+
level: LogLevel.DEBUG,
|
|
139
|
+
message,
|
|
140
|
+
context,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Export singleton instance
|
|
145
|
+
export const logger = Logger.getInstance();
|