claude-brain 0.30.2 → 0.30.3
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 +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Context, Next } from 'hono'
|
|
2
|
+
|
|
3
|
+
interface RateLimitEntry {
|
|
4
|
+
count: number
|
|
5
|
+
resetAt: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Simple in-memory rate limiter.
|
|
10
|
+
*/
|
|
11
|
+
export function rateLimit(options: {
|
|
12
|
+
windowMs?: number
|
|
13
|
+
max?: number
|
|
14
|
+
} = {}) {
|
|
15
|
+
const windowMs = options.windowMs || 60_000
|
|
16
|
+
const max = options.max || 100
|
|
17
|
+
const clients = new Map<string, RateLimitEntry>()
|
|
18
|
+
|
|
19
|
+
// Cleanup old entries periodically
|
|
20
|
+
const cleanup = setInterval(() => {
|
|
21
|
+
const now = Date.now()
|
|
22
|
+
for (const [key, entry] of clients) {
|
|
23
|
+
if (entry.resetAt <= now) clients.delete(key)
|
|
24
|
+
}
|
|
25
|
+
}, windowMs)
|
|
26
|
+
cleanup.unref()
|
|
27
|
+
|
|
28
|
+
return async (c: Context, next: Next) => {
|
|
29
|
+
const clientId = c.req.header('X-Forwarded-For')
|
|
30
|
+
|| c.req.header('X-Real-IP')
|
|
31
|
+
|| 'localhost'
|
|
32
|
+
|
|
33
|
+
const now = Date.now()
|
|
34
|
+
let entry = clients.get(clientId)
|
|
35
|
+
|
|
36
|
+
if (!entry || entry.resetAt <= now) {
|
|
37
|
+
entry = { count: 0, resetAt: now + windowMs }
|
|
38
|
+
clients.set(clientId, entry)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
entry.count++
|
|
42
|
+
|
|
43
|
+
c.header('X-RateLimit-Limit', max.toString())
|
|
44
|
+
c.header('X-RateLimit-Remaining', Math.max(0, max - entry.count).toString())
|
|
45
|
+
c.header('X-RateLimit-Reset', Math.ceil(entry.resetAt / 1000).toString())
|
|
46
|
+
|
|
47
|
+
if (entry.count > max) {
|
|
48
|
+
return c.json({ error: 'Too many requests' }, 429)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await next()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Validation Middleware
|
|
3
|
+
* Phase 1: Security Hardening
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
import type { Context } from 'hono'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate request body against a Zod schema.
|
|
11
|
+
* Returns parsed data or sends 400 response.
|
|
12
|
+
*/
|
|
13
|
+
export async function validateBody<T extends z.ZodType>(
|
|
14
|
+
c: Context,
|
|
15
|
+
schema: T
|
|
16
|
+
): Promise<z.infer<T> | null> {
|
|
17
|
+
try {
|
|
18
|
+
const body = await c.req.json()
|
|
19
|
+
const result = schema.safeParse(body)
|
|
20
|
+
if (!result.success) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
return result.data
|
|
24
|
+
} catch {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate query parameters against a Zod schema.
|
|
31
|
+
*/
|
|
32
|
+
export function validateQuery<T extends z.ZodType>(
|
|
33
|
+
c: Context,
|
|
34
|
+
schema: T
|
|
35
|
+
): z.infer<T> | null {
|
|
36
|
+
const params = Object.fromEntries(new URL(c.req.url).searchParams)
|
|
37
|
+
const result = schema.safeParse(params)
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
return result.data
|
|
42
|
+
}
|
|
@@ -1,136 +1,137 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PID file manager for the Claude Brain server.
|
|
3
|
-
* Ensures only one server instance runs at a time.
|
|
4
|
-
* Tracks daemon port and activity for idle watchdog.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'
|
|
8
|
-
import {
|
|
9
|
-
import { join } from 'node:path'
|
|
10
|
-
import { getHomePaths } from '@/config/home'
|
|
11
|
-
|
|
12
|
-
const PID_FILENAME = 'server.pid'
|
|
13
|
-
|
|
14
|
-
interface PidFileContent {
|
|
15
|
-
pid: number
|
|
16
|
-
port: number
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class ServerPidManager {
|
|
20
|
-
private pidFilePath: string
|
|
21
|
-
private lastActivityTs: number = Date.now()
|
|
22
|
-
|
|
23
|
-
constructor() {
|
|
24
|
-
const paths = getHomePaths()
|
|
25
|
-
this.pidFilePath = join(paths.data, PID_FILENAME)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Record activity — call on every HTTP request or MCP tool call */
|
|
29
|
-
touchActivity(): void {
|
|
30
|
-
this.lastActivityTs = Date.now()
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Milliseconds since last activity */
|
|
34
|
-
getIdleMs(): number {
|
|
35
|
-
return Date.now() - this.lastActivityTs
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get running daemon instance info (PID + port).
|
|
40
|
-
* Returns null if no daemon is running.
|
|
41
|
-
* Backward-compatible with old plain-number PID files.
|
|
42
|
-
*/
|
|
43
|
-
getRunningInstance(): { pid: number; port: number } | null {
|
|
44
|
-
if (!existsSync(this.pidFilePath)) return null
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const raw = readFileSync(this.pidFilePath, 'utf-8').trim()
|
|
48
|
-
|
|
49
|
-
let pid: number
|
|
50
|
-
let port: number
|
|
51
|
-
|
|
52
|
-
// Try JSON format first (new)
|
|
53
|
-
if (raw.startsWith('{')) {
|
|
54
|
-
const parsed: PidFileContent = JSON.parse(raw)
|
|
55
|
-
pid = parsed.pid
|
|
56
|
-
port = parsed.port
|
|
57
|
-
} else {
|
|
58
|
-
// Backward compat: old plain-number format
|
|
59
|
-
pid = parseInt(raw, 10)
|
|
60
|
-
port = 3000 // default port assumption for old format
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (isNaN(pid)) {
|
|
64
|
-
this.cleanup()
|
|
65
|
-
return null
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Signal 0 tests if process exists without killing it
|
|
69
|
-
try {
|
|
70
|
-
process.kill(pid, 0)
|
|
71
|
-
} catch (signalError:
|
|
72
|
-
// On Windows, process.kill(pid, 0) can throw unexpected errors
|
|
73
|
-
// Fall back to tasklist to verify the PID exists
|
|
74
|
-
if (process.platform === 'win32' && signalError?.code !== 'ESRCH') {
|
|
75
|
-
try {
|
|
76
|
-
const result =
|
|
77
|
-
encoding: 'utf-8', stdio: 'pipe', timeout: 3000,
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
process.on('
|
|
131
|
-
process.on('
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* PID file manager for the Claude Brain server.
|
|
3
|
+
* Ensures only one server instance runs at a time.
|
|
4
|
+
* Tracks daemon port and activity for idle watchdog.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'
|
|
8
|
+
import { spawnSync } from 'node:child_process'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
import { getHomePaths } from '@/config/home'
|
|
11
|
+
|
|
12
|
+
const PID_FILENAME = 'server.pid'
|
|
13
|
+
|
|
14
|
+
interface PidFileContent {
|
|
15
|
+
pid: number
|
|
16
|
+
port: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ServerPidManager {
|
|
20
|
+
private pidFilePath: string
|
|
21
|
+
private lastActivityTs: number = Date.now()
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
const paths = getHomePaths()
|
|
25
|
+
this.pidFilePath = join(paths.data, PID_FILENAME)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Record activity — call on every HTTP request or MCP tool call */
|
|
29
|
+
touchActivity(): void {
|
|
30
|
+
this.lastActivityTs = Date.now()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Milliseconds since last activity */
|
|
34
|
+
getIdleMs(): number {
|
|
35
|
+
return Date.now() - this.lastActivityTs
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get running daemon instance info (PID + port).
|
|
40
|
+
* Returns null if no daemon is running.
|
|
41
|
+
* Backward-compatible with old plain-number PID files.
|
|
42
|
+
*/
|
|
43
|
+
getRunningInstance(): { pid: number; port: number } | null {
|
|
44
|
+
if (!existsSync(this.pidFilePath)) return null
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const raw = readFileSync(this.pidFilePath, 'utf-8').trim()
|
|
48
|
+
|
|
49
|
+
let pid: number
|
|
50
|
+
let port: number
|
|
51
|
+
|
|
52
|
+
// Try JSON format first (new)
|
|
53
|
+
if (raw.startsWith('{')) {
|
|
54
|
+
const parsed: PidFileContent = JSON.parse(raw)
|
|
55
|
+
pid = parsed.pid
|
|
56
|
+
port = parsed.port
|
|
57
|
+
} else {
|
|
58
|
+
// Backward compat: old plain-number format
|
|
59
|
+
pid = parseInt(raw, 10)
|
|
60
|
+
port = 3000 // default port assumption for old format
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (isNaN(pid)) {
|
|
64
|
+
this.cleanup()
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Signal 0 tests if process exists without killing it
|
|
69
|
+
try {
|
|
70
|
+
process.kill(pid, 0)
|
|
71
|
+
} catch (signalError: unknown) {
|
|
72
|
+
// On Windows, process.kill(pid, 0) can throw unexpected errors
|
|
73
|
+
// Fall back to tasklist to verify the PID exists
|
|
74
|
+
if (process.platform === 'win32' && (signalError as Record<string, unknown>)?.code !== 'ESRCH') {
|
|
75
|
+
try {
|
|
76
|
+
const result = spawnSync('tasklist', ['/FI', `PID eq ${pid}`, '/NH'], {
|
|
77
|
+
encoding: 'utf-8', stdio: 'pipe', timeout: 3000,
|
|
78
|
+
})
|
|
79
|
+
const output = result.stdout || ''
|
|
80
|
+
if (!output.includes(String(pid))) {
|
|
81
|
+
this.cleanup()
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
this.cleanup()
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
this.cleanup()
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { pid, port }
|
|
94
|
+
} catch {
|
|
95
|
+
// Invalid file format, clean up stale PID file
|
|
96
|
+
this.cleanup()
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Check if another server instance is already running. Returns the PID if alive, null otherwise. */
|
|
102
|
+
getRunningPid(): number | null {
|
|
103
|
+
return this.getRunningInstance()?.pid ?? null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Write the current process PID and port to the PID file. */
|
|
107
|
+
writePidFile(port?: number): void {
|
|
108
|
+
const content: PidFileContent = {
|
|
109
|
+
pid: process.pid,
|
|
110
|
+
port: port ?? 3000,
|
|
111
|
+
}
|
|
112
|
+
writeFileSync(this.pidFilePath, JSON.stringify(content), 'utf-8')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Remove the PID file. Safe to call multiple times. */
|
|
116
|
+
cleanup(): void {
|
|
117
|
+
try {
|
|
118
|
+
unlinkSync(this.pidFilePath)
|
|
119
|
+
} catch {
|
|
120
|
+
// Already removed or doesn't exist
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Register cleanup handlers on SIGINT, SIGTERM, SIGHUP (non-Windows), and process exit. */
|
|
125
|
+
registerCleanupHandlers(): void {
|
|
126
|
+
const doCleanup = () => {
|
|
127
|
+
this.cleanup()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
process.on('exit', doCleanup)
|
|
131
|
+
process.on('SIGINT', doCleanup)
|
|
132
|
+
process.on('SIGTERM', doCleanup)
|
|
133
|
+
if (process.platform !== 'win32') {
|
|
134
|
+
process.on('SIGHUP', doCleanup)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|