kratos-memory 1.9.0 → 1.9.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kratos-memory",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "description": "Persistent memory for AI coding agents — CLI-first, local, zero network calls",
5
5
  "main": "dist/cli/index.js",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  "author": "",
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
- "better-sqlite3": "^12.8.0",
38
+ "better-sqlite3": "12.9.0",
39
39
  "chalk": "^5.3.0",
40
40
  "commander": "^12.1.0",
41
41
  "fs-extra": "^11.2.0"
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync, rmSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, rmSync, statSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { createRequire } from 'node:module';
5
5
  import { fileURLToPath } from 'node:url';
@@ -9,6 +9,9 @@ const __dirname = path.dirname(__filename);
9
9
  const packageRoot = path.resolve(__dirname, '..');
10
10
  const requireFromPackage = createRequire(path.join(packageRoot, 'package.json'));
11
11
 
12
+ const LOCK_WAIT_MS = 180_000; // how long a process waits for another's rebuild
13
+ const LOCK_STALE_MS = 600_000; // locks older than this are abandoned crashes
14
+
12
15
  function resolveBetterSqlite3Dir() {
13
16
  const pkgPath = requireFromPackage.resolve('better-sqlite3/package.json');
14
17
  return path.dirname(pkgPath);
@@ -70,6 +73,66 @@ function formatOutput(result) {
70
73
  return chunks.join('\n');
71
74
  }
72
75
 
76
+ function sleepSync(ms) {
77
+ spawnSync(process.execPath, ['-e', `setTimeout(() => process.exit(0), ${ms})`]);
78
+ }
79
+
80
+ /**
81
+ * Cross-process lock via atomic mkdir. Concurrent kratos invocations on a
82
+ * fresh install must not rebuild the native module simultaneously — the
83
+ * second process waits for the first, then re-checks instead of rebuilding.
84
+ */
85
+ function acquireLock(lockDir) {
86
+ const start = Date.now();
87
+ for (;;) {
88
+ try {
89
+ mkdirSync(lockDir);
90
+ return true;
91
+ } catch {
92
+ try {
93
+ const stat = statSync(lockDir);
94
+ if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
95
+ rmSync(lockDir, { recursive: true, force: true });
96
+ continue;
97
+ }
98
+ } catch {
99
+ continue; // lock vanished between attempts — retry immediately
100
+ }
101
+ if (Date.now() - start > LOCK_WAIT_MS) {
102
+ return false;
103
+ }
104
+ sleepSync(500);
105
+ }
106
+ }
107
+ }
108
+
109
+ function releaseLock(lockDir) {
110
+ rmSync(lockDir, { recursive: true, force: true });
111
+ }
112
+
113
+ /**
114
+ * Fast path: download the official prebuilt binary (seconds, no compiler).
115
+ * This is what better-sqlite3's own install script does — it gets skipped
116
+ * when users install with --ignore-scripts.
117
+ */
118
+ function tryPrebuildInstall(moduleDir) {
119
+ let prebuildBin;
120
+ try {
121
+ const requireFromModule = createRequire(path.join(moduleDir, 'package.json'));
122
+ const prebuildPkg = requireFromModule.resolve('prebuild-install/package.json');
123
+ const prebuildDir = path.dirname(prebuildPkg);
124
+ const binRel = JSON.parse(
125
+ spawnSync(process.execPath, ['-e', `console.log(JSON.stringify(require(${JSON.stringify(prebuildPkg)}).bin))`], { encoding: 'utf8' }).stdout.trim()
126
+ );
127
+ prebuildBin = path.join(prebuildDir, typeof binRel === 'string' ? binRel : Object.values(binRel)[0]);
128
+ } catch {
129
+ return false; // prebuild-install not resolvable — fall through to source build
130
+ }
131
+
132
+ const result = runCommand(process.execPath, [prebuildBin], moduleDir);
133
+ return result.status === 0;
134
+ }
135
+
73
136
  export function ensureBetterSqlite3() {
74
137
  let moduleDir;
75
138
 
@@ -84,30 +147,49 @@ export function ensureBetterSqlite3() {
84
147
  return;
85
148
  }
86
149
 
87
- const binaryPath = getBinaryPath(moduleDir);
88
- console.warn(`[kratos-memory] better-sqlite3 binary missing or unloadable at ${binaryPath}`);
89
- console.warn('[kratos-memory] rebuilding better-sqlite3 from source...');
90
-
91
- rmSync(path.join(moduleDir, 'build'), { recursive: true, force: true });
92
-
93
- const npm = findNpmCommand();
94
- const rebuild = runCommand(
95
- npm.command,
96
- [...npm.argsPrefix, 'run', 'build-release', '--foreground-scripts'],
97
- moduleDir
98
- );
99
-
100
- if (canLoadBetterSqlite3(moduleDir)) {
101
- console.warn('[kratos-memory] better-sqlite3 rebuild succeeded.');
102
- return;
150
+ const lockDir = path.join(moduleDir, '.kratos-setup-lock');
151
+ if (!acquireLock(lockDir)) {
152
+ // Another process held the lock the whole time — check its result
153
+ if (canLoadBetterSqlite3(moduleDir)) return;
154
+ throw new Error('Timed out waiting for another kratos process to finish native setup.');
103
155
  }
104
156
 
105
- const detail = formatOutput(rebuild);
106
- throw new Error(
107
- detail
108
- ? `Failed to build better-sqlite3.\n${detail}`
109
- : 'Failed to build better-sqlite3.'
110
- );
157
+ try {
158
+ // Another process may have completed setup while we waited for the lock
159
+ if (canLoadBetterSqlite3(moduleDir)) {
160
+ return;
161
+ }
162
+
163
+ rmSync(path.join(moduleDir, 'build'), { recursive: true, force: true });
164
+
165
+ console.warn('[kratos-memory] one-time native setup: fetching prebuilt SQLite binary...');
166
+ if (tryPrebuildInstall(moduleDir) && canLoadBetterSqlite3(moduleDir)) {
167
+ console.warn('[kratos-memory] prebuilt binary installed.');
168
+ return;
169
+ }
170
+
171
+ console.warn('[kratos-memory] no prebuilt binary available — compiling from source (one time, may take a minute)...');
172
+ const npm = findNpmCommand();
173
+ const rebuild = runCommand(
174
+ npm.command,
175
+ [...npm.argsPrefix, 'run', 'build-release', '--foreground-scripts'],
176
+ moduleDir
177
+ );
178
+
179
+ if (canLoadBetterSqlite3(moduleDir)) {
180
+ console.warn('[kratos-memory] better-sqlite3 rebuild succeeded.');
181
+ return;
182
+ }
183
+
184
+ const detail = formatOutput(rebuild);
185
+ throw new Error(
186
+ detail
187
+ ? `Failed to build better-sqlite3.\n${detail}`
188
+ : 'Failed to build better-sqlite3.'
189
+ );
190
+ } finally {
191
+ releaseLock(lockDir);
192
+ }
111
193
  }
112
194
 
113
195
  if (process.argv[1] === __filename) {