@cstart/coldstart 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +236 -0
- package/dist/cache/disk-cache.d.ts +14 -0
- package/dist/cache/disk-cache.d.ts.map +1 -0
- package/dist/cache/disk-cache.js +371 -0
- package/dist/cache/disk-cache.js.map +1 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +206 -0
- package/dist/cli.js.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +111 -0
- package/dist/constants.js.map +1 -0
- package/dist/daemon-lock.d.ts +63 -0
- package/dist/daemon-lock.d.ts.map +1 -0
- package/dist/daemon-lock.js +218 -0
- package/dist/daemon-lock.js.map +1 -0
- package/dist/daemon-log.d.ts +26 -0
- package/dist/daemon-log.d.ts.map +1 -0
- package/dist/daemon-log.js +110 -0
- package/dist/daemon-log.js.map +1 -0
- package/dist/index-manager.d.ts +103 -0
- package/dist/index-manager.d.ts.map +1 -0
- package/dist/index-manager.js +361 -0
- package/dist/index-manager.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer/content-tokens.d.ts +74 -0
- package/dist/indexer/content-tokens.d.ts.map +1 -0
- package/dist/indexer/content-tokens.js +406 -0
- package/dist/indexer/content-tokens.js.map +1 -0
- package/dist/indexer/cpp-include-roots.d.ts +7 -0
- package/dist/indexer/cpp-include-roots.d.ts.map +1 -0
- package/dist/indexer/cpp-include-roots.js +166 -0
- package/dist/indexer/cpp-include-roots.js.map +1 -0
- package/dist/indexer/csharp-synthetic.d.ts +9 -0
- package/dist/indexer/csharp-synthetic.d.ts.map +1 -0
- package/dist/indexer/csharp-synthetic.js +51 -0
- package/dist/indexer/csharp-synthetic.js.map +1 -0
- package/dist/indexer/django-synthetic.d.ts +12 -0
- package/dist/indexer/django-synthetic.d.ts.map +1 -0
- package/dist/indexer/django-synthetic.js +49 -0
- package/dist/indexer/django-synthetic.js.map +1 -0
- package/dist/indexer/extractors/angularjs.d.ts +16 -0
- package/dist/indexer/extractors/angularjs.d.ts.map +1 -0
- package/dist/indexer/extractors/angularjs.js +49 -0
- package/dist/indexer/extractors/angularjs.js.map +1 -0
- package/dist/indexer/extractors/cpp.d.ts +9 -0
- package/dist/indexer/extractors/cpp.d.ts.map +1 -0
- package/dist/indexer/extractors/cpp.js +254 -0
- package/dist/indexer/extractors/cpp.js.map +1 -0
- package/dist/indexer/extractors/csharp.d.ts +19 -0
- package/dist/indexer/extractors/csharp.d.ts.map +1 -0
- package/dist/indexer/extractors/csharp.js +327 -0
- package/dist/indexer/extractors/csharp.js.map +1 -0
- package/dist/indexer/extractors/env.d.ts +9 -0
- package/dist/indexer/extractors/env.d.ts.map +1 -0
- package/dist/indexer/extractors/env.js +43 -0
- package/dist/indexer/extractors/env.js.map +1 -0
- package/dist/indexer/extractors/go.d.ts +9 -0
- package/dist/indexer/extractors/go.d.ts.map +1 -0
- package/dist/indexer/extractors/go.js +227 -0
- package/dist/indexer/extractors/go.js.map +1 -0
- package/dist/indexer/extractors/graphql.d.ts +22 -0
- package/dist/indexer/extractors/graphql.d.ts.map +1 -0
- package/dist/indexer/extractors/graphql.js +88 -0
- package/dist/indexer/extractors/graphql.js.map +1 -0
- package/dist/indexer/extractors/groovy.d.ts +9 -0
- package/dist/indexer/extractors/groovy.d.ts.map +1 -0
- package/dist/indexer/extractors/groovy.js +356 -0
- package/dist/indexer/extractors/groovy.js.map +1 -0
- package/dist/indexer/extractors/java.d.ts +10 -0
- package/dist/indexer/extractors/java.d.ts.map +1 -0
- package/dist/indexer/extractors/java.js +586 -0
- package/dist/indexer/extractors/java.js.map +1 -0
- package/dist/indexer/extractors/kotlin.d.ts +10 -0
- package/dist/indexer/extractors/kotlin.d.ts.map +1 -0
- package/dist/indexer/extractors/kotlin.js +398 -0
- package/dist/indexer/extractors/kotlin.js.map +1 -0
- package/dist/indexer/extractors/node-helpers.d.ts +13 -0
- package/dist/indexer/extractors/node-helpers.d.ts.map +1 -0
- package/dist/indexer/extractors/node-helpers.js +17 -0
- package/dist/indexer/extractors/node-helpers.js.map +1 -0
- package/dist/indexer/extractors/parser-factory.d.ts +4 -0
- package/dist/indexer/extractors/parser-factory.d.ts.map +1 -0
- package/dist/indexer/extractors/parser-factory.js +28 -0
- package/dist/indexer/extractors/parser-factory.js.map +1 -0
- package/dist/indexer/extractors/php.d.ts +17 -0
- package/dist/indexer/extractors/php.d.ts.map +1 -0
- package/dist/indexer/extractors/php.js +363 -0
- package/dist/indexer/extractors/php.js.map +1 -0
- package/dist/indexer/extractors/python.d.ts +14 -0
- package/dist/indexer/extractors/python.d.ts.map +1 -0
- package/dist/indexer/extractors/python.js +449 -0
- package/dist/indexer/extractors/python.js.map +1 -0
- package/dist/indexer/extractors/ruby.d.ts +10 -0
- package/dist/indexer/extractors/ruby.d.ts.map +1 -0
- package/dist/indexer/extractors/ruby.js +996 -0
- package/dist/indexer/extractors/ruby.js.map +1 -0
- package/dist/indexer/extractors/rust.d.ts +9 -0
- package/dist/indexer/extractors/rust.d.ts.map +1 -0
- package/dist/indexer/extractors/rust.js +322 -0
- package/dist/indexer/extractors/rust.js.map +1 -0
- package/dist/indexer/extractors/toml.d.ts +9 -0
- package/dist/indexer/extractors/toml.d.ts.map +1 -0
- package/dist/indexer/extractors/toml.js +84 -0
- package/dist/indexer/extractors/toml.js.map +1 -0
- package/dist/indexer/extractors/xml.d.ts +9 -0
- package/dist/indexer/extractors/xml.d.ts.map +1 -0
- package/dist/indexer/extractors/xml.js +163 -0
- package/dist/indexer/extractors/xml.js.map +1 -0
- package/dist/indexer/extractors/yaml.d.ts +9 -0
- package/dist/indexer/extractors/yaml.d.ts.map +1 -0
- package/dist/indexer/extractors/yaml.js +117 -0
- package/dist/indexer/extractors/yaml.js.map +1 -0
- package/dist/indexer/git.d.ts +19 -0
- package/dist/indexer/git.d.ts.map +1 -0
- package/dist/indexer/git.js +55 -0
- package/dist/indexer/git.js.map +1 -0
- package/dist/indexer/graph.d.ts +7 -0
- package/dist/indexer/graph.d.ts.map +1 -0
- package/dist/indexer/graph.js +20 -0
- package/dist/indexer/graph.js.map +1 -0
- package/dist/indexer/indexed-file.d.ts +15 -0
- package/dist/indexer/indexed-file.d.ts.map +1 -0
- package/dist/indexer/indexed-file.js +50 -0
- package/dist/indexer/indexed-file.js.map +1 -0
- package/dist/indexer/invariants.d.ts +15 -0
- package/dist/indexer/invariants.d.ts.map +1 -0
- package/dist/indexer/invariants.js +96 -0
- package/dist/indexer/invariants.js.map +1 -0
- package/dist/indexer/laravel-synthetic.d.ts +10 -0
- package/dist/indexer/laravel-synthetic.d.ts.map +1 -0
- package/dist/indexer/laravel-synthetic.js +78 -0
- package/dist/indexer/laravel-synthetic.js.map +1 -0
- package/dist/indexer/parser.d.ts +4 -0
- package/dist/indexer/parser.d.ts.map +1 -0
- package/dist/indexer/parser.js +499 -0
- package/dist/indexer/parser.js.map +1 -0
- package/dist/indexer/patch.d.ts +3 -0
- package/dist/indexer/patch.d.ts.map +1 -0
- package/dist/indexer/patch.js +291 -0
- package/dist/indexer/patch.js.map +1 -0
- package/dist/indexer/rails-synthetic.d.ts +11 -0
- package/dist/indexer/rails-synthetic.d.ts.map +1 -0
- package/dist/indexer/rails-synthetic.js +80 -0
- package/dist/indexer/rails-synthetic.js.map +1 -0
- package/dist/indexer/reconcile.d.ts +9 -0
- package/dist/indexer/reconcile.d.ts.map +1 -0
- package/dist/indexer/reconcile.js +96 -0
- package/dist/indexer/reconcile.js.map +1 -0
- package/dist/indexer/resolvers/cpp.d.ts +15 -0
- package/dist/indexer/resolvers/cpp.d.ts.map +1 -0
- package/dist/indexer/resolvers/cpp.js +35 -0
- package/dist/indexer/resolvers/cpp.js.map +1 -0
- package/dist/indexer/resolvers/csharp.d.ts +2 -0
- package/dist/indexer/resolvers/csharp.d.ts.map +1 -0
- package/dist/indexer/resolvers/csharp.js +89 -0
- package/dist/indexer/resolvers/csharp.js.map +1 -0
- package/dist/indexer/resolvers/generic.d.ts +10 -0
- package/dist/indexer/resolvers/generic.d.ts.map +1 -0
- package/dist/indexer/resolvers/generic.js +35 -0
- package/dist/indexer/resolvers/generic.js.map +1 -0
- package/dist/indexer/resolvers/go.d.ts +2 -0
- package/dist/indexer/resolvers/go.d.ts.map +1 -0
- package/dist/indexer/resolvers/go.js +171 -0
- package/dist/indexer/resolvers/go.js.map +1 -0
- package/dist/indexer/resolvers/index.d.ts +26 -0
- package/dist/indexer/resolvers/index.d.ts.map +1 -0
- package/dist/indexer/resolvers/index.js +301 -0
- package/dist/indexer/resolvers/index.js.map +1 -0
- package/dist/indexer/resolvers/java.d.ts +17 -0
- package/dist/indexer/resolvers/java.d.ts.map +1 -0
- package/dist/indexer/resolvers/java.js +104 -0
- package/dist/indexer/resolvers/java.js.map +1 -0
- package/dist/indexer/resolvers/php.d.ts +2 -0
- package/dist/indexer/resolvers/php.d.ts.map +1 -0
- package/dist/indexer/resolvers/php.js +108 -0
- package/dist/indexer/resolvers/php.js.map +1 -0
- package/dist/indexer/resolvers/python.d.ts +17 -0
- package/dist/indexer/resolvers/python.d.ts.map +1 -0
- package/dist/indexer/resolvers/python.js +63 -0
- package/dist/indexer/resolvers/python.js.map +1 -0
- package/dist/indexer/resolvers/ruby.d.ts +13 -0
- package/dist/indexer/resolvers/ruby.d.ts.map +1 -0
- package/dist/indexer/resolvers/ruby.js +199 -0
- package/dist/indexer/resolvers/ruby.js.map +1 -0
- package/dist/indexer/resolvers/rust.d.ts +12 -0
- package/dist/indexer/resolvers/rust.d.ts.map +1 -0
- package/dist/indexer/resolvers/rust.js +61 -0
- package/dist/indexer/resolvers/rust.js.map +1 -0
- package/dist/indexer/resolvers/shared.d.ts +18 -0
- package/dist/indexer/resolvers/shared.d.ts.map +1 -0
- package/dist/indexer/resolvers/shared.js +71 -0
- package/dist/indexer/resolvers/shared.js.map +1 -0
- package/dist/indexer/rust-workspace.d.ts +25 -0
- package/dist/indexer/rust-workspace.d.ts.map +1 -0
- package/dist/indexer/rust-workspace.js +155 -0
- package/dist/indexer/rust-workspace.js.map +1 -0
- package/dist/indexer/symbol-edges.d.ts +19 -0
- package/dist/indexer/symbol-edges.d.ts.map +1 -0
- package/dist/indexer/symbol-edges.js +117 -0
- package/dist/indexer/symbol-edges.js.map +1 -0
- package/dist/indexer/tokenize.d.ts +26 -0
- package/dist/indexer/tokenize.d.ts.map +1 -0
- package/dist/indexer/tokenize.js +200 -0
- package/dist/indexer/tokenize.js.map +1 -0
- package/dist/indexer/ts-parser.d.ts +10 -0
- package/dist/indexer/ts-parser.d.ts.map +1 -0
- package/dist/indexer/ts-parser.js +605 -0
- package/dist/indexer/ts-parser.js.map +1 -0
- package/dist/indexer/walker.d.ts +9 -0
- package/dist/indexer/walker.d.ts.map +1 -0
- package/dist/indexer/walker.js +91 -0
- package/dist/indexer/walker.js.map +1 -0
- package/dist/init.d.ts +111 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +787 -0
- package/dist/init.js.map +1 -0
- package/dist/kb/cli.d.ts +2 -0
- package/dist/kb/cli.d.ts.map +1 -0
- package/dist/kb/cli.js +377 -0
- package/dist/kb/cli.js.map +1 -0
- package/dist/kb/commit.d.ts +6 -0
- package/dist/kb/commit.d.ts.map +1 -0
- package/dist/kb/commit.js +84 -0
- package/dist/kb/commit.js.map +1 -0
- package/dist/kb/fold.d.ts +42 -0
- package/dist/kb/fold.d.ts.map +1 -0
- package/dist/kb/fold.js +285 -0
- package/dist/kb/fold.js.map +1 -0
- package/dist/kb/freshness.d.ts +9 -0
- package/dist/kb/freshness.d.ts.map +1 -0
- package/dist/kb/freshness.js +52 -0
- package/dist/kb/freshness.js.map +1 -0
- package/dist/kb/git.d.ts +4 -0
- package/dist/kb/git.d.ts.map +1 -0
- package/dist/kb/git.js +28 -0
- package/dist/kb/git.js.map +1 -0
- package/dist/kb/ids.d.ts +8 -0
- package/dist/kb/ids.d.ts.map +1 -0
- package/dist/kb/ids.js +42 -0
- package/dist/kb/ids.js.map +1 -0
- package/dist/kb/lint.d.ts +23 -0
- package/dist/kb/lint.d.ts.map +1 -0
- package/dist/kb/lint.js +102 -0
- package/dist/kb/lint.js.map +1 -0
- package/dist/kb/lookup.d.ts +55 -0
- package/dist/kb/lookup.d.ts.map +1 -0
- package/dist/kb/lookup.js +92 -0
- package/dist/kb/lookup.js.map +1 -0
- package/dist/kb/notes-index.d.ts +31 -0
- package/dist/kb/notes-index.d.ts.map +1 -0
- package/dist/kb/notes-index.js +0 -0
- package/dist/kb/notes-index.js.map +1 -0
- package/dist/kb/raw-log.d.ts +34 -0
- package/dist/kb/raw-log.d.ts.map +1 -0
- package/dist/kb/raw-log.js +106 -0
- package/dist/kb/raw-log.js.map +1 -0
- package/dist/kb/render.d.ts +17 -0
- package/dist/kb/render.d.ts.map +1 -0
- package/dist/kb/render.js +101 -0
- package/dist/kb/render.js.map +1 -0
- package/dist/kb/search.d.ts +110 -0
- package/dist/kb/search.d.ts.map +1 -0
- package/dist/kb/search.js +453 -0
- package/dist/kb/search.js.map +1 -0
- package/dist/kb/store.d.ts +35 -0
- package/dist/kb/store.d.ts.map +1 -0
- package/dist/kb/store.js +160 -0
- package/dist/kb/store.js.map +1 -0
- package/dist/kb/types.d.ts +142 -0
- package/dist/kb/types.d.ts.map +1 -0
- package/dist/kb/types.js +13 -0
- package/dist/kb/types.js.map +1 -0
- package/dist/kb/view-template.d.ts +8 -0
- package/dist/kb/view-template.d.ts.map +1 -0
- package/dist/kb/view-template.js +8 -0
- package/dist/kb/view-template.js.map +1 -0
- package/dist/kb/view.d.ts +42 -0
- package/dist/kb/view.d.ts.map +1 -0
- package/dist/kb/view.js +118 -0
- package/dist/kb/view.js.map +1 -0
- package/dist/kb/write.d.ts +72 -0
- package/dist/kb/write.d.ts.map +1 -0
- package/dist/kb/write.js +345 -0
- package/dist/kb/write.js.map +1 -0
- package/dist/keeper-state.d.ts +29 -0
- package/dist/keeper-state.d.ts.map +1 -0
- package/dist/keeper-state.js +89 -0
- package/dist/keeper-state.js.map +1 -0
- package/dist/keeper.d.ts +23 -0
- package/dist/keeper.d.ts.map +1 -0
- package/dist/keeper.js +105 -0
- package/dist/keeper.js.map +1 -0
- package/dist/migrate.d.ts +9 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +102 -0
- package/dist/migrate.js.map +1 -0
- package/dist/restart.d.ts +15 -0
- package/dist/restart.d.ts.map +1 -0
- package/dist/restart.js +48 -0
- package/dist/restart.js.map +1 -0
- package/dist/server/find.d.ts +39 -0
- package/dist/server/find.d.ts.map +1 -0
- package/dist/server/find.js +979 -0
- package/dist/server/find.js.map +1 -0
- package/dist/server/mcp.d.ts +117 -0
- package/dist/server/mcp.d.ts.map +1 -0
- package/dist/server/mcp.js +301 -0
- package/dist/server/mcp.js.map +1 -0
- package/dist/server/searcher.d.ts +14 -0
- package/dist/server/searcher.d.ts.map +1 -0
- package/dist/server/searcher.js +146 -0
- package/dist/server/searcher.js.map +1 -0
- package/dist/server/tools.d.ts +17 -0
- package/dist/server/tools.d.ts.map +1 -0
- package/dist/server/tools.js +875 -0
- package/dist/server/tools.js.map +1 -0
- package/dist/status.d.ts +12 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +157 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/watcher.d.ts +11 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +67 -0
- package/dist/watcher.js.map +1 -0
- package/hooks/canonical-find-key.mjs +68 -0
- package/hooks/codex-find-nudge.mjs +15 -0
- package/hooks/codex-find-preguard.mjs +14 -0
- package/hooks/codex-kb-elicit.mjs +329 -0
- package/hooks/codex-kb-recall.mjs +151 -0
- package/hooks/codex-nudge-handler.mjs +370 -0
- package/hooks/codex-preguard-handler.mjs +75 -0
- package/hooks/codex-run-hook.mjs +132 -0
- package/hooks/coldstart-call.mjs +58 -0
- package/hooks/cursor-find-nudge.mjs +15 -0
- package/hooks/cursor-find-preguard.mjs +14 -0
- package/hooks/cursor-input.mjs +72 -0
- package/hooks/cursor-kb-elicit.mjs +335 -0
- package/hooks/cursor-kb-recall.mjs +138 -0
- package/hooks/cursor-nudge-handler.mjs +33 -0
- package/hooks/cursor-preguard-handler.mjs +39 -0
- package/hooks/cursor-run-hook.mjs +129 -0
- package/hooks/find-nudge.mjs +15 -0
- package/hooks/find-preguard.mjs +14 -0
- package/hooks/kb-elicit.mjs +334 -0
- package/hooks/kb-recall.mjs +151 -0
- package/hooks/nudge-handler.mjs +370 -0
- package/hooks/preguard-handler.mjs +75 -0
- package/hooks/run-hook.mjs +132 -0
- package/package.json +69 -0
- package/templates/coldstart.cli.md +60 -0
- package/templates/coldstart.mcp.md +59 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, unlink, open, readdir } from 'node:fs/promises';
|
|
2
|
+
import { readFileSync, watch, existsSync } from 'node:fs';
|
|
3
|
+
import { join, resolve, basename, dirname } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
export function daemonDir() {
|
|
8
|
+
return join(homedir(), '.coldstart', 'daemon');
|
|
9
|
+
}
|
|
10
|
+
export function rootHash(rootDir) {
|
|
11
|
+
return createHash('sha256').update(resolve(rootDir)).digest('hex').slice(0, 16);
|
|
12
|
+
}
|
|
13
|
+
export function lockBasename(rootDir) {
|
|
14
|
+
const abs = resolve(rootDir);
|
|
15
|
+
return `${basename(abs)}-${rootHash(abs)}`;
|
|
16
|
+
}
|
|
17
|
+
function lockPath(rootDir) {
|
|
18
|
+
return join(daemonDir(), `${lockBasename(rootDir)}.json`);
|
|
19
|
+
}
|
|
20
|
+
function spawnLockPath(rootDir) {
|
|
21
|
+
return join(daemonDir(), `${lockBasename(rootDir)}.spawn`);
|
|
22
|
+
}
|
|
23
|
+
export function daemonLogPath(rootDir) {
|
|
24
|
+
return join(daemonDir(), `${lockBasename(rootDir)}.log`);
|
|
25
|
+
}
|
|
26
|
+
export function daemonLogPrevPath(rootDir) {
|
|
27
|
+
return join(daemonDir(), `${lockBasename(rootDir)}.log.prev`);
|
|
28
|
+
}
|
|
29
|
+
export async function readLock(rootDir) {
|
|
30
|
+
try {
|
|
31
|
+
const raw = await readFile(lockPath(rootDir), 'utf-8');
|
|
32
|
+
const lock = JSON.parse(raw);
|
|
33
|
+
if (typeof lock.pid !== 'number')
|
|
34
|
+
return null;
|
|
35
|
+
return lock;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function writeLock(rootDir, pid, version) {
|
|
42
|
+
await mkdir(daemonDir(), { recursive: true });
|
|
43
|
+
const payload = { pid, rootDir: resolve(rootDir), version };
|
|
44
|
+
await writeFile(lockPath(rootDir), JSON.stringify(payload));
|
|
45
|
+
}
|
|
46
|
+
export async function deleteLock(rootDir) {
|
|
47
|
+
try {
|
|
48
|
+
await unlink(lockPath(rootDir));
|
|
49
|
+
}
|
|
50
|
+
catch { /* ignore */ }
|
|
51
|
+
}
|
|
52
|
+
export async function listDaemonLocks() {
|
|
53
|
+
let entries;
|
|
54
|
+
try {
|
|
55
|
+
entries = await readdir(daemonDir());
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const name of entries) {
|
|
62
|
+
if (!name.endsWith('.json'))
|
|
63
|
+
continue;
|
|
64
|
+
const full = join(daemonDir(), name);
|
|
65
|
+
try {
|
|
66
|
+
const raw = await readFile(full, 'utf-8');
|
|
67
|
+
const parsed = JSON.parse(raw);
|
|
68
|
+
if (typeof parsed.pid !== 'number')
|
|
69
|
+
continue;
|
|
70
|
+
out.push({
|
|
71
|
+
basename: name.replace(/\.json$/, ''),
|
|
72
|
+
lockPath: full,
|
|
73
|
+
lock: parsed,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch { /* skip malformed */ }
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
export function isDaemonAlive(pid) {
|
|
81
|
+
try {
|
|
82
|
+
process.kill(pid, 0);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get the current version of coldstart-mcp from package.json.
|
|
91
|
+
*/
|
|
92
|
+
export function getCurrentVersion() {
|
|
93
|
+
try {
|
|
94
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
95
|
+
const pkgPath = resolve(dirname(__filename), '..', 'package.json');
|
|
96
|
+
const content = readFileSync(pkgPath, 'utf-8');
|
|
97
|
+
const pkg = JSON.parse(content);
|
|
98
|
+
return pkg.version ?? 'unknown';
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return 'unknown';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Kill a daemon by PID with escalating signals: SIGTERM (5s wait), then SIGKILL.
|
|
106
|
+
* Returns true if the daemon was alive and has been killed; false if already dead.
|
|
107
|
+
*/
|
|
108
|
+
export async function killDaemon(pid) {
|
|
109
|
+
if (!isDaemonAlive(pid))
|
|
110
|
+
return false;
|
|
111
|
+
try {
|
|
112
|
+
process.kill(pid, 'SIGTERM');
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
// Wait up to 5 seconds for graceful shutdown
|
|
118
|
+
const deadline = Date.now() + 5000;
|
|
119
|
+
while (Date.now() < deadline) {
|
|
120
|
+
if (!isDaemonAlive(pid))
|
|
121
|
+
return true;
|
|
122
|
+
await new Promise(r => setTimeout(r, 100));
|
|
123
|
+
}
|
|
124
|
+
// SIGKILL if still alive
|
|
125
|
+
try {
|
|
126
|
+
process.kill(pid, 'SIGKILL');
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Already dead or permission denied — either way, treat as killed
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Try to atomically acquire a spawn lock so only one reader spawns the keeper.
|
|
135
|
+
* Returns a release function if acquired, null if another process already holds it.
|
|
136
|
+
*/
|
|
137
|
+
export async function tryAcquireSpawnLock(rootDir) {
|
|
138
|
+
await mkdir(daemonDir(), { recursive: true });
|
|
139
|
+
const path = spawnLockPath(rootDir);
|
|
140
|
+
try {
|
|
141
|
+
const fd = await open(path, 'wx'); // O_CREAT | O_EXCL — atomic
|
|
142
|
+
await fd.writeFile(String(process.pid));
|
|
143
|
+
await fd.close();
|
|
144
|
+
return async () => { try {
|
|
145
|
+
await unlink(path);
|
|
146
|
+
}
|
|
147
|
+
catch { /* ignore */ } };
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null; // Another reader already holds the spawn lock
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Fix #6: Watch the daemon lockfile directory for deletions of our own lockfile.
|
|
155
|
+
* If our lockfile is deleted (e.g., user ran `rm ~/.coldstart/daemon/foo.json`),
|
|
156
|
+
* call onMissing() so the daemon can exit cleanly.
|
|
157
|
+
*
|
|
158
|
+
* fs.watch drops events under load (observed live: a keeper surviving its
|
|
159
|
+
* lockfile deletion, then coexisting with its replacement — two keepers
|
|
160
|
+
* racing saves on one root). Two hardenings:
|
|
161
|
+
* - a slow poll backstop, so a missed event only delays shutdown
|
|
162
|
+
* - lock OWNERSHIP: if the lockfile exists but names a different pid,
|
|
163
|
+
* another keeper has taken over this root — the old one must exit.
|
|
164
|
+
*
|
|
165
|
+
* Returns a function to stop watching.
|
|
166
|
+
*/
|
|
167
|
+
export const LOCK_POLL_INTERVAL_MS = 30_000;
|
|
168
|
+
export function watchOwnLockfile(rootDir, onMissing, pollIntervalMs = LOCK_POLL_INTERVAL_MS) {
|
|
169
|
+
const lock = lockPath(rootDir);
|
|
170
|
+
const parent = daemonDir();
|
|
171
|
+
let debounceTimer = null;
|
|
172
|
+
let watcher = null;
|
|
173
|
+
let fired = false;
|
|
174
|
+
const lockLost = () => {
|
|
175
|
+
if (!existsSync(lock))
|
|
176
|
+
return true;
|
|
177
|
+
try {
|
|
178
|
+
const parsed = JSON.parse(readFileSync(lock, 'utf-8'));
|
|
179
|
+
return typeof parsed.pid === 'number' && parsed.pid !== process.pid;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return false; // unreadable mid-write — never shut down on a read race
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const fire = () => {
|
|
186
|
+
if (fired)
|
|
187
|
+
return;
|
|
188
|
+
fired = true;
|
|
189
|
+
onMissing();
|
|
190
|
+
};
|
|
191
|
+
const poll = setInterval(() => { if (lockLost())
|
|
192
|
+
fire(); }, pollIntervalMs);
|
|
193
|
+
poll.unref();
|
|
194
|
+
try {
|
|
195
|
+
watcher = watch(parent, (_eventType, _filename) => {
|
|
196
|
+
if (debounceTimer)
|
|
197
|
+
clearTimeout(debounceTimer);
|
|
198
|
+
debounceTimer = setTimeout(() => {
|
|
199
|
+
debounceTimer = null;
|
|
200
|
+
if (lockLost())
|
|
201
|
+
fire();
|
|
202
|
+
}, 200);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
// Keep the poll backstop even when fs.watch is unavailable.
|
|
207
|
+
return () => {
|
|
208
|
+
clearInterval(poll);
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return () => {
|
|
212
|
+
if (debounceTimer)
|
|
213
|
+
clearTimeout(debounceTimer);
|
|
214
|
+
clearInterval(poll);
|
|
215
|
+
watcher?.close();
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=daemon-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-lock.js","sourceRoot":"","sources":["../src/daemon-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAezC,MAAM,UAAU,SAAS;IACvB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,QAAQ,CAAC,OAAe;IAC/B,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAe;IAC5C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;QACpD,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC9C,OAAO,IAAkB,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAe,EACf,GAAW,EACX,OAAgB;IAEhB,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAe,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACxE,MAAM,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAe;IAC9C,IAAI,CAAC;QAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;AACjE,CAAC;AAaD,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,SAAS;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;YACtD,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;gBAAE,SAAS;YAC7C,GAAG,CAAC,IAAI,CAAC;gBACP,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;gBACrC,QAAQ,EAAE,IAAI;gBACd,IAAI,EAAE,MAAoB;aAC3B,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACnE,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAyB,CAAC;QACxD,OAAO,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAEtC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IAED,6CAA6C;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IACnC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACrC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,yBAAyB;IACzB,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,kEAAkE;IACpE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAAe;IACvD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,4BAA4B;QAC/D,MAAM,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACxC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,KAAK,IAAI,EAAE,GAAG,IAAI,CAAC;YAAC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,8CAA8C;IAC7D,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE5C,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,SAAqB,EACrB,cAAc,GAAG,qBAAqB;IAEtC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,aAAa,GAAyC,IAAI,CAAC;IAC/D,IAAI,OAAO,GAAoC,IAAI,CAAC;IACpD,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,MAAM,QAAQ,GAAG,GAAY,EAAE;QAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAwB,CAAC;YAC9E,OAAO,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC,CAAC,wDAAwD;QACxE,CAAC;IACH,CAAC,CAAC;IACF,MAAM,IAAI,GAAG,GAAS,EAAE;QACtB,IAAI,KAAK;YAAE,OAAO;QAClB,KAAK,GAAG,IAAI,CAAC;QACb,SAAS,EAAE,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,IAAI,QAAQ,EAAE;QAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;IAC5E,IAAI,CAAC,KAAK,EAAE,CAAC;IAEb,IAAI,CAAC;QACH,OAAO,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE;YAChD,IAAI,aAAa;gBAAE,YAAY,CAAC,aAAa,CAAC,CAAC;YAC/C,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC9B,aAAa,GAAG,IAAI,CAAC;gBACrB,IAAI,QAAQ,EAAE;oBAAE,IAAI,EAAE,CAAC;YACzB,CAAC,EAAE,GAAG,CAAC,CAAC;QACV,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,4DAA4D;QAC5D,OAAO,GAAG,EAAE;YACV,aAAa,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,EAAE;QACV,IAAI,aAAa;YAAE,YAAY,CAAC,aAAa,CAAC,CAAC;QAC/C,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-daemon log file lives next to the daemon lockfile at
|
|
3
|
+
* `~/.coldstart/daemon/<basename>-<hash>.log`. On rotation the previous
|
|
4
|
+
* contents move to `<basename>-<hash>.log.prev` so the most recent crash
|
|
5
|
+
* is always preserved.
|
|
6
|
+
*
|
|
7
|
+
* Sizing rationale: a daemon emits ~5 KB of startup output on a normal
|
|
8
|
+
* repo and is near-silent in steady state. 1 MB comfortably holds a full
|
|
9
|
+
* workday of patch events plus an occasional error storm without thrashing
|
|
10
|
+
* disk, and worst-case per project is 2 MB across .log + .log.prev.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MAX_LOG_BYTES: number;
|
|
13
|
+
/**
|
|
14
|
+
* Attach a file-backed logger for the running daemon process.
|
|
15
|
+
*
|
|
16
|
+
* - Rotates any existing `.log` to `.log.prev` before opening a fresh file,
|
|
17
|
+
* so the new daemon run starts with a clean log and the previous run's
|
|
18
|
+
* output (likely containing the crash, if any) is preserved.
|
|
19
|
+
* - Overrides `process.stderr.write` so all existing `log()` call sites
|
|
20
|
+
* automatically land in the file — no caller changes needed.
|
|
21
|
+
* - Starts a 60 s timer that rotates the file when it crosses 1 MB.
|
|
22
|
+
*
|
|
23
|
+
* Returns a teardown function for graceful shutdown.
|
|
24
|
+
*/
|
|
25
|
+
export declare function attachDaemonLogger(rootDir: string): () => void;
|
|
26
|
+
//# sourceMappingURL=daemon-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-log.d.ts","sourceRoot":"","sources":["../src/daemon-log.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,QAAkB,CAAC;AAqB7C;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,IAAI,CAyD9D"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createWriteStream, existsSync, mkdirSync, renameSync, statSync, } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { daemonLogPath, daemonLogPrevPath } from './daemon-lock.js';
|
|
4
|
+
/**
|
|
5
|
+
* Per-daemon log file lives next to the daemon lockfile at
|
|
6
|
+
* `~/.coldstart/daemon/<basename>-<hash>.log`. On rotation the previous
|
|
7
|
+
* contents move to `<basename>-<hash>.log.prev` so the most recent crash
|
|
8
|
+
* is always preserved.
|
|
9
|
+
*
|
|
10
|
+
* Sizing rationale: a daemon emits ~5 KB of startup output on a normal
|
|
11
|
+
* repo and is near-silent in steady state. 1 MB comfortably holds a full
|
|
12
|
+
* workday of patch events plus an occasional error storm without thrashing
|
|
13
|
+
* disk, and worst-case per project is 2 MB across .log + .log.prev.
|
|
14
|
+
*/
|
|
15
|
+
export const MAX_LOG_BYTES = 1 * 1024 * 1024;
|
|
16
|
+
const ROTATE_CHECK_INTERVAL_MS = 60 * 1000;
|
|
17
|
+
let currentStream = null;
|
|
18
|
+
let currentPath = null;
|
|
19
|
+
let prevPath = null;
|
|
20
|
+
let rotateTimer = null;
|
|
21
|
+
let originalStderrWrite = null;
|
|
22
|
+
/**
|
|
23
|
+
* Atomically rotate `<root>.log` to `<root>.log.prev`. Best-effort —
|
|
24
|
+
* rotation must never crash the daemon, so all errors are swallowed.
|
|
25
|
+
*/
|
|
26
|
+
function rotateNow(logPath, prevTarget) {
|
|
27
|
+
try {
|
|
28
|
+
if (existsSync(logPath)) {
|
|
29
|
+
try {
|
|
30
|
+
renameSync(logPath, prevTarget);
|
|
31
|
+
}
|
|
32
|
+
catch { /* ignore */ }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch { /* ignore */ }
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Attach a file-backed logger for the running daemon process.
|
|
39
|
+
*
|
|
40
|
+
* - Rotates any existing `.log` to `.log.prev` before opening a fresh file,
|
|
41
|
+
* so the new daemon run starts with a clean log and the previous run's
|
|
42
|
+
* output (likely containing the crash, if any) is preserved.
|
|
43
|
+
* - Overrides `process.stderr.write` so all existing `log()` call sites
|
|
44
|
+
* automatically land in the file — no caller changes needed.
|
|
45
|
+
* - Starts a 60 s timer that rotates the file when it crosses 1 MB.
|
|
46
|
+
*
|
|
47
|
+
* Returns a teardown function for graceful shutdown.
|
|
48
|
+
*/
|
|
49
|
+
export function attachDaemonLogger(rootDir) {
|
|
50
|
+
const logPath = daemonLogPath(rootDir);
|
|
51
|
+
const prevTarget = daemonLogPrevPath(rootDir);
|
|
52
|
+
try {
|
|
53
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
catch { /* directory may already exist */ }
|
|
56
|
+
rotateNow(logPath, prevTarget);
|
|
57
|
+
currentStream = createWriteStream(logPath, { flags: 'a' });
|
|
58
|
+
currentPath = logPath;
|
|
59
|
+
prevPath = prevTarget;
|
|
60
|
+
originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
61
|
+
// Route every process.stderr.write through the log file. Cast through
|
|
62
|
+
// unknown because Node's Writable.write has three overloaded signatures.
|
|
63
|
+
process.stderr.write =
|
|
64
|
+
(chunk, ...rest) => {
|
|
65
|
+
const stream = currentStream;
|
|
66
|
+
if (!stream)
|
|
67
|
+
return originalStderrWrite(chunk, ...rest);
|
|
68
|
+
return stream.write(chunk, ...rest);
|
|
69
|
+
};
|
|
70
|
+
rotateTimer = setInterval(() => {
|
|
71
|
+
try {
|
|
72
|
+
if (!currentPath || !prevPath)
|
|
73
|
+
return;
|
|
74
|
+
if (!existsSync(currentPath))
|
|
75
|
+
return;
|
|
76
|
+
const size = statSync(currentPath).size;
|
|
77
|
+
if (size <= MAX_LOG_BYTES)
|
|
78
|
+
return;
|
|
79
|
+
const oldStream = currentStream;
|
|
80
|
+
currentStream = null;
|
|
81
|
+
if (oldStream)
|
|
82
|
+
oldStream.end();
|
|
83
|
+
rotateNow(currentPath, prevPath);
|
|
84
|
+
currentStream = createWriteStream(currentPath, { flags: 'a' });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Rotation is best-effort — never crash the daemon over a log roll.
|
|
88
|
+
}
|
|
89
|
+
}, ROTATE_CHECK_INTERVAL_MS);
|
|
90
|
+
// Don't keep the daemon alive solely because of the rotation timer.
|
|
91
|
+
rotateTimer.unref();
|
|
92
|
+
return () => {
|
|
93
|
+
if (rotateTimer) {
|
|
94
|
+
clearInterval(rotateTimer);
|
|
95
|
+
rotateTimer = null;
|
|
96
|
+
}
|
|
97
|
+
const stream = currentStream;
|
|
98
|
+
currentStream = null;
|
|
99
|
+
currentPath = null;
|
|
100
|
+
prevPath = null;
|
|
101
|
+
if (stream)
|
|
102
|
+
stream.end();
|
|
103
|
+
if (originalStderrWrite) {
|
|
104
|
+
process.stderr.write =
|
|
105
|
+
originalStderrWrite;
|
|
106
|
+
originalStderrWrite = null;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=daemon-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-log.js","sourceRoot":"","sources":["../src/daemon-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { CodebaseIndex } from './types.js';
|
|
2
|
+
export type RebuildFn = () => Promise<CodebaseIndex>;
|
|
3
|
+
export interface IndexContext {
|
|
4
|
+
index: CodebaseIndex;
|
|
5
|
+
/** True while a full rebuild is running. Tool calls served from previous snapshot. */
|
|
6
|
+
isRebuilding: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Owns the live in-memory index and orchestrates incremental patches vs full rebuilds.
|
|
10
|
+
*
|
|
11
|
+
* Decision logic (fires on each debounced batch from the file watcher):
|
|
12
|
+
* - batch.size === 0 → no-op
|
|
13
|
+
* - batch.size <= 30 → incremental patch (serial, ~2-5ms per file)
|
|
14
|
+
* - batch.size > 30 → full rebuild in background, serve stale index until complete
|
|
15
|
+
* - patch throws → fallback to full rebuild
|
|
16
|
+
*
|
|
17
|
+
* Changes that arrive WHILE a rebuild is running are collected into a pending set.
|
|
18
|
+
* After the rebuild completes, the pending set is processed as a new batch so no
|
|
19
|
+
* changes are silently dropped.
|
|
20
|
+
*
|
|
21
|
+
* Tool calls always read `getContext().index` which is the last stable snapshot.
|
|
22
|
+
* Index swaps are atomic (single JS assignment).
|
|
23
|
+
*/
|
|
24
|
+
export declare class IndexManager {
|
|
25
|
+
private readonly rebuild;
|
|
26
|
+
private readonly cacheDir;
|
|
27
|
+
private readonly noCache;
|
|
28
|
+
private readonly quiet;
|
|
29
|
+
private activeIndex;
|
|
30
|
+
private rebuilding;
|
|
31
|
+
private stopWatcherFn;
|
|
32
|
+
private stopCacheWatcherFn;
|
|
33
|
+
private pendingCacheSave;
|
|
34
|
+
/**
|
|
35
|
+
* Paths that changed while a rebuild was in progress.
|
|
36
|
+
* Processed as a follow-up batch once the rebuild completes.
|
|
37
|
+
*/
|
|
38
|
+
private pendingDuringRebuild;
|
|
39
|
+
constructor(initialIndex: CodebaseIndex, rebuild: RebuildFn, cacheDir: string | undefined, noCache: boolean, quiet: boolean);
|
|
40
|
+
/** Returns the current index snapshot and rebuild status for tool calls. */
|
|
41
|
+
getContext(): IndexContext;
|
|
42
|
+
/** Start watching the project root for file changes. */
|
|
43
|
+
startWatching(): void;
|
|
44
|
+
/** Stop watching (called on process exit). */
|
|
45
|
+
stopWatching(): void;
|
|
46
|
+
private log;
|
|
47
|
+
/**
|
|
48
|
+
* Notebook `.raw` logs changed (an agent wrote a note, or a git merge
|
|
49
|
+
* unioned two logs) — re-fold and re-render the derived md for those ids.
|
|
50
|
+
* Never touches the code index; md here is a browsability nicety (search
|
|
51
|
+
* folds from `.raw` directly), so failures only log.
|
|
52
|
+
*/
|
|
53
|
+
private handleNotebookRawBatch;
|
|
54
|
+
/**
|
|
55
|
+
* Rebuild the keeper-maintained KB notes index (per-anchor symbol
|
|
56
|
+
* inventories + absence verdict stamps) from the live code index, and
|
|
57
|
+
* persist it beside the cache. Single-flight: a refresh landing while one
|
|
58
|
+
* runs is coalesced into one follow-up pass, so stamps never go stale
|
|
59
|
+
* silently. kb readers only ever READ this file (C1 decoupling).
|
|
60
|
+
*/
|
|
61
|
+
private refreshingKb;
|
|
62
|
+
private kbRefreshQueued;
|
|
63
|
+
private refreshKbNotesIndex;
|
|
64
|
+
private handleBatch;
|
|
65
|
+
/** Best-effort observability writers (skipped under --no-cache). Stamps are
|
|
66
|
+
* serialized through one promise chain: updateKeeperState is read-merge-
|
|
67
|
+
* write, so two in-flight stamps would silently drop each other's fields. */
|
|
68
|
+
private stampChain;
|
|
69
|
+
private stamp;
|
|
70
|
+
private report;
|
|
71
|
+
private triggerRebuild;
|
|
72
|
+
/**
|
|
73
|
+
* A failed rebuild leaves the keeper serving a stale snapshot with no
|
|
74
|
+
* guarantee another change event ever arrives — retry on a timer, backing
|
|
75
|
+
* off, and give up (until the next real change) after a few attempts.
|
|
76
|
+
*/
|
|
77
|
+
private rebuildFailures;
|
|
78
|
+
private rebuildRetryTimer;
|
|
79
|
+
private scheduleRebuildRetry;
|
|
80
|
+
/**
|
|
81
|
+
* Sampled fingerprint audit — after each cache save, stat a rotating window
|
|
82
|
+
* of indexed files against their parse-time fingerprints. Catches changes
|
|
83
|
+
* the watcher missed (dropped fs events, edits in paths fs.watch didn't
|
|
84
|
+
* cover); drifted files are re-fed through the normal patch path.
|
|
85
|
+
*/
|
|
86
|
+
private auditCursor;
|
|
87
|
+
private auditFingerprints;
|
|
88
|
+
/** Debounced cache write — avoids hammering disk on rapid successive patches. */
|
|
89
|
+
private savingCache;
|
|
90
|
+
private scheduleCacheSave;
|
|
91
|
+
/**
|
|
92
|
+
* Fix #2: Watch the cache directory for manual deletes (e.g., user runs
|
|
93
|
+
* `rm -rf ~/.coldstart/indexes/<x>/meta.json`). If meta.json is missing,
|
|
94
|
+
* trigger a rebuild so the cache is recreated.
|
|
95
|
+
*
|
|
96
|
+
* Uses fs.watch on the parent directory (Windows quirk) and checks for
|
|
97
|
+
* meta.json existence. The existence check prevents a loop: the daemon
|
|
98
|
+
* itself writes meta.json on every save, which would fire the watch event,
|
|
99
|
+
* but we only rebuild if meta.json is MISSING.
|
|
100
|
+
*/
|
|
101
|
+
private startCacheWatcher;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=index-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-manager.d.ts","sourceRoot":"","sources":["../src/index-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAYhD,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,sFAAsF;IACtF,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAY;IAerB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAjBxB,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,gBAAgB,CAA8C;IAEtE;;;OAGG;IACH,OAAO,CAAC,oBAAoB,CAAqB;gBAG/C,YAAY,EAAE,aAAa,EACV,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,OAAO;IAKjC,4EAA4E;IAC5E,UAAU,IAAI,YAAY;IAI1B,wDAAwD;IACxD,aAAa,IAAI,IAAI;IAgBrB,8CAA8C;IAC9C,YAAY,IAAI,IAAI;IAapB,OAAO,CAAC,GAAG;IAIX;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAe9B;;;;;;OAMG;IACH,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,eAAe,CAAS;YAClB,mBAAmB;YAoBnB,WAAW;IAuCzB;;kFAE8E;IAC9E,OAAO,CAAC,UAAU,CAAoC;YACxC,KAAK;YAQL,MAAM;YAKN,cAAc;IA4B5B;;;;OAIG;IACH,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,iBAAiB,CAA8C;IACvE,OAAO,CAAC,oBAAoB;IAgB5B;;;;;OAKG;IACH,OAAO,CAAC,WAAW,CAAK;YACV,iBAAiB;IAsB/B,iFAAiF;IACjF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB;IAwBzB;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB;CAwC1B"}
|