nexus-prime 7.2.0 → 7.3.1
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/architects/config.js +6 -1
- package/dist/cli/doctor-storage.d.ts +19 -0
- package/dist/cli/doctor-storage.js +72 -3
- package/dist/cli/install-wizard.d.ts +15 -0
- package/dist/cli/install-wizard.js +50 -32
- package/dist/cli.js +10 -53
- package/dist/dashboard/routes/health.js +20 -0
- package/dist/dashboard/server.js +1 -1
- package/dist/engines/ngram-index.d.ts +37 -3
- package/dist/engines/ngram-index.js +209 -23
- package/dist/engines/orchestrator/types.d.ts +5 -0
- package/dist/engines/orchestrator/types.js +64 -2
- package/dist/engines/orchestrator.js +29 -4
- package/dist/engines/runtime-hygiene.d.ts +4 -0
- package/dist/engines/runtime-hygiene.js +45 -1
- package/dist/install/claude-code-hooks.d.ts +33 -0
- package/dist/install/claude-code-hooks.js +96 -0
- package/dist/install/manifest.d.ts +29 -0
- package/dist/install/manifest.js +44 -1
- package/dist/install/state-locator.d.ts +11 -0
- package/dist/install/state-locator.js +30 -0
- package/dist/licensing/enforcement.js +13 -1
- package/dist/licensing/index.d.ts +1 -1
- package/dist/licensing/index.js +1 -1
- package/dist/licensing/license-manager.d.ts +12 -0
- package/dist/licensing/license-manager.js +91 -4
- package/dist/licensing/types.d.ts +5 -1
- package/dist/licensing/upgrade-prompts.d.ts +10 -0
- package/dist/licensing/upgrade-prompts.js +23 -0
- package/dist/licensing/web-auth.d.ts +4 -1
- package/dist/licensing/web-auth.js +34 -9
- package/dist/synapse/config.js +15 -11
- package/package.json +2 -2
|
@@ -7,9 +7,14 @@ function parseInteger(value, fallback) {
|
|
|
7
7
|
const parsed = Number.parseInt(value ?? '', 10);
|
|
8
8
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
9
9
|
}
|
|
10
|
+
function clampInteger(value, min, max) {
|
|
11
|
+
if (!Number.isFinite(value))
|
|
12
|
+
return min;
|
|
13
|
+
return Math.max(min, Math.min(max, Math.floor(value)));
|
|
14
|
+
}
|
|
10
15
|
export const ArchitectsConfig = {
|
|
11
16
|
enabled: parseBoolean(process.env.ARCHITECTS_ENABLED, true),
|
|
12
|
-
maxConcurrent: parseInteger(process.env.ARCHITECTS_MAX_CONCURRENT,
|
|
17
|
+
maxConcurrent: clampInteger(parseInteger(process.env.ARCHITECTS_MAX_CONCURRENT, 2), 1, 4),
|
|
13
18
|
sentinelPatrolMs: parseInteger(process.env.ARCHITECTS_SENTINEL_PATROL_MS, 120_000),
|
|
14
19
|
wardPatrolMs: parseInteger(process.env.ARCHITECTS_WARD_PATROL_MS, 180_000),
|
|
15
20
|
convergenceStrategy: (process.env.ARCHITECTS_CONVERGENCE_STRATEGY || 'bisecting'),
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { StateScope } from '../install/state-locator.js';
|
|
2
|
+
import type { LicenseStatus } from '../licensing/types.js';
|
|
3
|
+
export interface NgramFootprint {
|
|
4
|
+
dbPath: string;
|
|
5
|
+
dbBytes: number;
|
|
6
|
+
walBytes: number;
|
|
7
|
+
shmBytes: number;
|
|
8
|
+
totalBytes: number;
|
|
9
|
+
walLimitBytes: number;
|
|
10
|
+
footprintLimitBytes: number;
|
|
11
|
+
archives: number;
|
|
12
|
+
}
|
|
2
13
|
export interface DoctorReport {
|
|
3
14
|
stateDir: string;
|
|
4
15
|
totals: Record<StateScope, number>;
|
|
@@ -15,5 +26,13 @@ export interface DoctorReport {
|
|
|
15
26
|
limit: number;
|
|
16
27
|
}>;
|
|
17
28
|
suggestCleanup: boolean;
|
|
29
|
+
/** Install manifest's recorded architecture generation, if present. */
|
|
30
|
+
installArchGeneration?: string;
|
|
31
|
+
/** Architecture generation built into this binary. */
|
|
32
|
+
currentArchGeneration: string;
|
|
33
|
+
/** ngram-index footprint snapshot for the canonical state-dir DB. */
|
|
34
|
+
ngram: NgramFootprint;
|
|
35
|
+
/** Active license status. */
|
|
36
|
+
license: Pick<LicenseStatus, 'tier' | 'valid' | 'expiresAt' | 'trial' | 'degradedReason'>;
|
|
18
37
|
}
|
|
19
38
|
export declare function runDoctorStorage(): DoctorReport;
|
|
@@ -2,13 +2,29 @@
|
|
|
2
2
|
* nexus doctor storage — read-only disk usage report.
|
|
3
3
|
*
|
|
4
4
|
* Shows scope-by-scope bytes under the state dir + tmp prefixes, flags any
|
|
5
|
-
* scope that exceeds the corresponding cleanup budget,
|
|
6
|
-
*
|
|
5
|
+
* scope that exceeds the corresponding cleanup budget, surfaces the install
|
|
6
|
+
* architecture generation + ngram WAL footprint + license status, and prints
|
|
7
|
+
* a single remediation hint (`nexus cleanup --fix`) when needed.
|
|
7
8
|
*/
|
|
8
9
|
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
9
11
|
import { resolveRunsBudget, resolveWorktreeBudget } from './cleanup.js';
|
|
10
12
|
import { dirBytes, formatBytes } from '../install/fs-purge.js';
|
|
11
|
-
import { enumerateStatePaths, getNexusStateDir, getRuntimeTmpRoots, getWorktreeRoots, } from '../install/state-locator.js';
|
|
13
|
+
import { enumerateNgramArchives, enumerateStatePaths, getNexusStateDir, getRuntimeTmpRoots, getWorktreeRoots, } from '../install/state-locator.js';
|
|
14
|
+
import { INSTALL_ARCH_GENERATION, loadManifest, } from '../install/manifest.js';
|
|
15
|
+
import { getNgramFootprintBytes } from '../engines/ngram-index.js';
|
|
16
|
+
import { getSharedLicenseManager } from '../licensing/license-manager.js';
|
|
17
|
+
const NGRAM_DEFAULT_WAL_LIMIT_BYTES = 64 * 1024 * 1024;
|
|
18
|
+
const NGRAM_DEFAULT_FOOTPRINT_BYTES = 512 * 1024 * 1024;
|
|
19
|
+
function readEnvBytesPositive(name, fallback) {
|
|
20
|
+
const raw = process.env[name];
|
|
21
|
+
if (!raw)
|
|
22
|
+
return fallback;
|
|
23
|
+
const parsed = Number(raw);
|
|
24
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
25
|
+
return fallback;
|
|
26
|
+
return Math.floor(parsed);
|
|
27
|
+
}
|
|
12
28
|
export function runDoctorStorage() {
|
|
13
29
|
const stateDir = getNexusStateDir();
|
|
14
30
|
const entries = [];
|
|
@@ -47,14 +63,67 @@ export function runDoctorStorage() {
|
|
|
47
63
|
if (totals.runtime > runsBudget.maxBytes) {
|
|
48
64
|
flags.push({ scope: 'runtime', reason: 'bytes-over-limit', actual: totals.runtime, limit: runsBudget.maxBytes });
|
|
49
65
|
}
|
|
66
|
+
const ngramDbPath = path.join(stateDir, 'ngram-index.db');
|
|
67
|
+
const walLimitBytes = readEnvBytesPositive('NEXUS_NGRAM_WAL_LIMIT_BYTES', NGRAM_DEFAULT_WAL_LIMIT_BYTES);
|
|
68
|
+
const footprintLimitBytes = readEnvBytesPositive('NEXUS_NGRAM_MAX_FOOTPRINT_BYTES', NGRAM_DEFAULT_FOOTPRINT_BYTES);
|
|
69
|
+
const safeStat = (p) => {
|
|
70
|
+
try {
|
|
71
|
+
return fs.existsSync(p) ? fs.statSync(p).size : 0;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const ngram = {
|
|
78
|
+
dbPath: ngramDbPath,
|
|
79
|
+
dbBytes: safeStat(ngramDbPath),
|
|
80
|
+
walBytes: safeStat(`${ngramDbPath}-wal`),
|
|
81
|
+
shmBytes: safeStat(`${ngramDbPath}-shm`),
|
|
82
|
+
totalBytes: getNgramFootprintBytes(ngramDbPath),
|
|
83
|
+
walLimitBytes,
|
|
84
|
+
footprintLimitBytes,
|
|
85
|
+
archives: enumerateNgramArchives(stateDir).length,
|
|
86
|
+
};
|
|
87
|
+
if (ngram.walBytes > walLimitBytes) {
|
|
88
|
+
flags.push({ scope: 'db', reason: 'ngram-wal-over-limit', actual: ngram.walBytes, limit: walLimitBytes });
|
|
89
|
+
}
|
|
90
|
+
if (ngram.totalBytes > footprintLimitBytes) {
|
|
91
|
+
flags.push({ scope: 'db', reason: 'ngram-footprint-over-limit', actual: ngram.totalBytes, limit: footprintLimitBytes });
|
|
92
|
+
}
|
|
93
|
+
const manifest = loadManifest();
|
|
94
|
+
const license = (() => {
|
|
95
|
+
try {
|
|
96
|
+
const status = getSharedLicenseManager().getStatus();
|
|
97
|
+
return {
|
|
98
|
+
tier: status.tier,
|
|
99
|
+
valid: status.valid,
|
|
100
|
+
expiresAt: status.expiresAt,
|
|
101
|
+
trial: status.trial,
|
|
102
|
+
degradedReason: status.degradedReason,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return { tier: 'free', valid: false, expiresAt: null, trial: false, degradedReason: 'malformed' };
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
50
109
|
const report = {
|
|
51
110
|
stateDir,
|
|
52
111
|
totals,
|
|
53
112
|
entries,
|
|
54
113
|
flags,
|
|
55
114
|
suggestCleanup: flags.length > 0,
|
|
115
|
+
installArchGeneration: manifest.architectureGeneration,
|
|
116
|
+
currentArchGeneration: INSTALL_ARCH_GENERATION,
|
|
117
|
+
ngram,
|
|
118
|
+
license,
|
|
56
119
|
};
|
|
57
120
|
console.log(`\n[doctor:storage] state dir: ${stateDir}`);
|
|
121
|
+
console.log(` install arch ${report.installArchGeneration ?? '(unstamped)'} → current ${report.currentArchGeneration}`);
|
|
122
|
+
const trialNote = license.trial && license.expiresAt
|
|
123
|
+
? ` (trial, ${Math.max(0, Math.ceil((license.expiresAt - Date.now()) / (24 * 60 * 60 * 1000)))}d left)`
|
|
124
|
+
: '';
|
|
125
|
+
console.log(` license tier=${license.tier} valid=${license.valid}${trialNote}${license.degradedReason ? ` reason=${license.degradedReason}` : ''}`);
|
|
126
|
+
console.log(` ngram db db=${formatBytes(ngram.dbBytes)} wal=${formatBytes(ngram.walBytes)} shm=${formatBytes(ngram.shmBytes)} total=${formatBytes(ngram.totalBytes)} (cap=${formatBytes(footprintLimitBytes)}) archives=${ngram.archives}`);
|
|
58
127
|
for (const [scope, bytes] of Object.entries(totals)) {
|
|
59
128
|
console.log(` ${scope.padEnd(10)} ${formatBytes(bytes)}`);
|
|
60
129
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Invoked via: nexus-prime setup
|
|
5
5
|
*/
|
|
6
6
|
import { type IDEId } from '../agents/adapters/ide-compat.js';
|
|
7
|
+
import { INSTALL_ARCH_GENERATION } from '../install/manifest.js';
|
|
7
8
|
export interface WizardOptions {
|
|
8
9
|
/** Target workspace root (defaults to cwd) */
|
|
9
10
|
workspaceRoot?: string;
|
|
@@ -36,6 +37,20 @@ export declare function configureIDE(ide: IDEId, opts?: WizardOptions): Promise<
|
|
|
36
37
|
configPath: string | null;
|
|
37
38
|
writtenHash: string | null;
|
|
38
39
|
}>;
|
|
40
|
+
/** Architecture-generation migration step. Idempotent and best-effort: when
|
|
41
|
+
* the install manifest predates INSTALL_ARCH_GENERATION (or carries an older
|
|
42
|
+
* one), we drop stale generated artefacts that the current generation no
|
|
43
|
+
* longer owns. The manifest's path/registration entries are preserved so
|
|
44
|
+
* uninstall can still reverse them. */
|
|
45
|
+
export interface ArchUpgradeResult {
|
|
46
|
+
previousGeneration?: string;
|
|
47
|
+
currentGeneration: typeof INSTALL_ARCH_GENERATION;
|
|
48
|
+
migrated: boolean;
|
|
49
|
+
ngramArchivesPruned: number;
|
|
50
|
+
}
|
|
51
|
+
export declare function runArchitectureUpgrade(opts?: {
|
|
52
|
+
dryRun?: boolean;
|
|
53
|
+
}): ArchUpgradeResult;
|
|
39
54
|
/** Run the install wizard: detect IDEs and write MCP configs. */
|
|
40
55
|
export declare function runInstallWizard(opts?: WizardOptions): Promise<WizardResult>;
|
|
41
56
|
/** Print how to add nexus-prime manually to an MCP client. */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Invoked via: nexus-prime setup
|
|
5
5
|
*/
|
|
6
6
|
import { createHash } from 'crypto';
|
|
7
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import { dirname, join, resolve } from 'path';
|
|
@@ -15,7 +15,9 @@ import { resolveNexusStateDir } from '../engines/runtime-registry.js';
|
|
|
15
15
|
import { resolveWorkspaceContext } from '../engines/workspace-resolver.js';
|
|
16
16
|
import { runPostinstallCleanup } from '../postinstall/cleanup.js';
|
|
17
17
|
import { openBrowser, printHandoffBanner, withSpinner } from './interactive-setup.js';
|
|
18
|
-
import { appendToManifest, recordPath, recordRegistration, recordSetupMarker } from '../install/manifest.js';
|
|
18
|
+
import { appendToManifest, detectArchitectureUpgrade, loadManifest, recordArchitectureGeneration, recordPath, recordRegistration, recordSetupMarker, INSTALL_ARCH_GENERATION, } from '../install/manifest.js';
|
|
19
|
+
import { enumerateNgramArchives } from '../install/state-locator.js';
|
|
20
|
+
import { NEXUS_HOOK_COMMAND_MARKER, writeNexusClaudeCodeHooks } from '../install/claude-code-hooks.js';
|
|
19
21
|
/** Compute sha256 of a file's content. Returns null if the file cannot be read. */
|
|
20
22
|
export function computeFileHash(filePath) {
|
|
21
23
|
try {
|
|
@@ -147,35 +149,8 @@ function _nexusMcpEntry(workspaceRoot) {
|
|
|
147
149
|
*/
|
|
148
150
|
function _writeClaudeCodeHooks(workspaceRoot) {
|
|
149
151
|
const settingsPath = join(workspaceRoot, '.claude', 'settings.json');
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
existing = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
154
|
-
}
|
|
155
|
-
catch { /* start fresh if unreadable */ }
|
|
156
|
-
}
|
|
157
|
-
const NEXUS_CMD_MARKER = 'nexus-prime hook';
|
|
158
|
-
const nexusHooks = {
|
|
159
|
-
UserPromptSubmit: [{ matcher: '', hooks: [{ type: 'command', command: 'nexus-prime hook bootstrap' }] }],
|
|
160
|
-
PreToolUse: [{ matcher: 'Edit|Write|MultiEdit', hooks: [{ type: 'command', command: 'nexus-prime hook mindkit' }] }],
|
|
161
|
-
PostToolUse: [{ matcher: 'Edit|Write|MultiEdit|Bash', hooks: [{ type: 'command', command: 'nexus-prime hook memory' }] }],
|
|
162
|
-
Stop: [{ matcher: '', hooks: [{ type: 'command', command: 'nexus-prime hook session-dna' }] }],
|
|
163
|
-
};
|
|
164
|
-
const existingHooks = (existing.hooks && typeof existing.hooks === 'object' && !Array.isArray(existing.hooks))
|
|
165
|
-
? existing.hooks
|
|
166
|
-
: {};
|
|
167
|
-
for (const [event, entries] of Object.entries(nexusHooks)) {
|
|
168
|
-
const current = Array.isArray(existingHooks[event]) ? existingHooks[event] : [];
|
|
169
|
-
// Strip stale nexus-prime hook entries then append fresh ones
|
|
170
|
-
const filtered = current.filter((e) => typeof e === 'object' && e !== null &&
|
|
171
|
-
!(Array.isArray(e.hooks) &&
|
|
172
|
-
e.hooks.some((h) => typeof h === 'object' && h !== null && typeof h.command === 'string' &&
|
|
173
|
-
h.command.includes(NEXUS_CMD_MARKER))));
|
|
174
|
-
existingHooks[event] = [...filtered, ...entries];
|
|
175
|
-
}
|
|
176
|
-
existing.hooks = existingHooks;
|
|
177
|
-
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
178
|
-
writeFileSync(settingsPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
152
|
+
// Single source of truth: hook spec + dedup logic live in install/claude-code-hooks.ts.
|
|
153
|
+
writeNexusClaudeCodeHooks(settingsPath);
|
|
179
154
|
const isHome = workspaceRoot === homedir();
|
|
180
155
|
appendToManifest((m) => {
|
|
181
156
|
const withPath = recordPath(m, { path: settingsPath, kind: 'file', scope: 'state' });
|
|
@@ -183,7 +158,7 @@ function _writeClaudeCodeHooks(workspaceRoot) {
|
|
|
183
158
|
target: isHome ? 'claude-home' : 'claude-workspace',
|
|
184
159
|
filePath: settingsPath,
|
|
185
160
|
jsonPath: ['hooks'],
|
|
186
|
-
entryMarker:
|
|
161
|
+
entryMarker: NEXUS_HOOK_COMMAND_MARKER,
|
|
187
162
|
});
|
|
188
163
|
});
|
|
189
164
|
}
|
|
@@ -255,6 +230,45 @@ function _writeWorkspaceLocalConfigs(callerIde, workspaceRoot) {
|
|
|
255
230
|
}
|
|
256
231
|
}
|
|
257
232
|
}
|
|
233
|
+
export function runArchitectureUpgrade(opts = {}) {
|
|
234
|
+
const manifest = loadManifest();
|
|
235
|
+
const detection = detectArchitectureUpgrade(manifest);
|
|
236
|
+
if (!detection.requiresMigration) {
|
|
237
|
+
if (manifest.architectureGeneration !== INSTALL_ARCH_GENERATION && !opts.dryRun) {
|
|
238
|
+
// Fresh install or no recorded entries — just stamp the marker.
|
|
239
|
+
appendToManifest((m) => recordArchitectureGeneration(m));
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
previousGeneration: detection.previousGeneration,
|
|
243
|
+
currentGeneration: detection.currentGeneration,
|
|
244
|
+
migrated: false,
|
|
245
|
+
ngramArchivesPruned: 0,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
let ngramArchivesPruned = 0;
|
|
249
|
+
if (!opts.dryRun) {
|
|
250
|
+
// Stale ngram archives from prior generations: keep nothing — the new
|
|
251
|
+
// rotation path archives at most one and uses footprint accounting.
|
|
252
|
+
try {
|
|
253
|
+
const archives = enumerateNgramArchives();
|
|
254
|
+
for (const archive of archives) {
|
|
255
|
+
try {
|
|
256
|
+
rmSync(archive.path, { force: true });
|
|
257
|
+
ngramArchivesPruned += 1;
|
|
258
|
+
}
|
|
259
|
+
catch { /* best-effort */ }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch { /* best-effort */ }
|
|
263
|
+
appendToManifest((m) => recordArchitectureGeneration(m));
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
previousGeneration: detection.previousGeneration,
|
|
267
|
+
currentGeneration: detection.currentGeneration,
|
|
268
|
+
migrated: true,
|
|
269
|
+
ngramArchivesPruned,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
258
272
|
/** Run the install wizard: detect IDEs and write MCP configs. */
|
|
259
273
|
export async function runInstallWizard(opts = {}) {
|
|
260
274
|
const workspaceRoot = resolve(opts.workspaceRoot ?? process.cwd());
|
|
@@ -263,6 +277,10 @@ export async function runInstallWizard(opts = {}) {
|
|
|
263
277
|
const setupMarker = readSetupMarker();
|
|
264
278
|
const log = (msg) => { if (verbose)
|
|
265
279
|
process.stdout.write(msg + '\n'); };
|
|
280
|
+
const upgrade = runArchitectureUpgrade({ dryRun });
|
|
281
|
+
if (upgrade.migrated && verbose) {
|
|
282
|
+
log(` [arch] migrated install ${upgrade.previousGeneration ?? 'legacy'} → ${upgrade.currentGeneration} (ngram archives pruned: ${upgrade.ngramArchivesPruned})`);
|
|
283
|
+
}
|
|
266
284
|
const detected = detectInstalledIDEs(workspaceRoot);
|
|
267
285
|
if (detected.length === 0) {
|
|
268
286
|
log(' No supported IDEs detected in this workspace. Skipping MCP config setup.');
|
package/dist/cli.js
CHANGED
|
@@ -38,67 +38,24 @@ import { runUninstall } from './cli/uninstall.js';
|
|
|
38
38
|
import { runCleanup } from './cli/cleanup.js';
|
|
39
39
|
import { runDoctorStorage } from './cli/doctor-storage.js';
|
|
40
40
|
const tokenEngine = new TokenSupremacyEngine();
|
|
41
|
+
import { getNexusHookSpec, writeNexusClaudeCodeHooks, } from './install/claude-code-hooks.js';
|
|
41
42
|
/**
|
|
42
43
|
* Write (or merge) nexus-prime Claude Code hook entries into ~/.claude/settings.json.
|
|
43
|
-
* Idempotent
|
|
44
|
+
* Idempotent — delegates to the shared writer in install/claude-code-hooks.ts
|
|
45
|
+
* so cli.ts and install-wizard.ts cannot drift on the hook spec or dedup logic.
|
|
44
46
|
*/
|
|
45
47
|
function writeClaudeCodeHooks(settingsPath, dryRun) {
|
|
46
|
-
|
|
47
|
-
if (existsSync(settingsPath)) {
|
|
48
|
-
try {
|
|
49
|
-
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
settings = {};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const hooks = (settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks))
|
|
56
|
-
? { ...settings.hooks }
|
|
57
|
-
: {};
|
|
58
|
-
const nexusHooks = {
|
|
59
|
-
UserPromptSubmit: [
|
|
60
|
-
{ hooks: [{ type: 'command', command: 'nexus-prime hook bootstrap', timeout: 30 }] },
|
|
61
|
-
],
|
|
62
|
-
PreToolUse: [
|
|
63
|
-
{
|
|
64
|
-
matcher: 'Edit|Write|MultiEdit',
|
|
65
|
-
hooks: [{ type: 'command', command: 'nexus-prime hook mindkit', timeout: 10 }],
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
PostToolUse: [
|
|
69
|
-
{
|
|
70
|
-
matcher: 'Edit|Write|MultiEdit|Bash',
|
|
71
|
-
hooks: [{ type: 'command', command: 'nexus-prime hook memory', timeout: 10 }],
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
Stop: [
|
|
75
|
-
{ hooks: [{ type: 'command', command: 'nexus-prime hook session-dna', timeout: 60 }] },
|
|
76
|
-
],
|
|
77
|
-
};
|
|
78
|
-
for (const [event, entries] of Object.entries(nexusHooks)) {
|
|
79
|
-
const existing = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
80
|
-
// Remove stale nexus-prime hook entries before re-adding
|
|
81
|
-
const filtered = existing.filter((entry) => {
|
|
82
|
-
if (!entry || typeof entry !== 'object')
|
|
83
|
-
return true;
|
|
84
|
-
const hooksList = entry.hooks;
|
|
85
|
-
if (!Array.isArray(hooksList))
|
|
86
|
-
return true;
|
|
87
|
-
return !hooksList.some((h) => h && typeof h.command === 'string' &&
|
|
88
|
-
h.command.startsWith('nexus-prime hook'));
|
|
89
|
-
});
|
|
90
|
-
hooks[event] = [...filtered, ...entries];
|
|
91
|
-
}
|
|
92
|
-
settings.hooks = hooks;
|
|
48
|
+
const result = writeNexusClaudeCodeHooks(settingsPath, { dryRun });
|
|
93
49
|
if (dryRun) {
|
|
94
50
|
console.log('Hooks preview (would write to ~/.claude/settings.json):');
|
|
95
|
-
console.log(JSON.stringify(
|
|
51
|
+
console.log(JSON.stringify(getNexusHookSpec(), null, 2));
|
|
96
52
|
return;
|
|
97
53
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
54
|
+
console.log(` Hooks: ${settingsPath}${result.changed ? '' : ' (no change)'}`);
|
|
55
|
+
result.events.forEach((ev) => {
|
|
56
|
+
const tail = ev.toLowerCase().replace('tooluse', '').trim() || ev;
|
|
57
|
+
console.log(` ${ev} → nexus-prime hook ${tail}`);
|
|
58
|
+
});
|
|
102
59
|
}
|
|
103
60
|
const program = new Command();
|
|
104
61
|
let nexus = null;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
import { DASHBOARD_API_VERSION } from '../contract.js';
|
|
2
|
+
import { INSTALL_ARCH_GENERATION, loadManifest } from '../../install/manifest.js';
|
|
1
3
|
import { getToolHealthSummary } from '../../agents/adapters/mcp/tool-health.js';
|
|
2
4
|
export const handleHealthRoutes = async (ctx, req, res, url) => {
|
|
5
|
+
if (req.method === 'GET'
|
|
6
|
+
&& (url.pathname === '/api/version' || url.pathname === '/api/dashboard/version')) {
|
|
7
|
+
// Stable version endpoint so external probes (CI smoke, foreign installs
|
|
8
|
+
// sharing port 3377) can identify the running dashboard generation.
|
|
9
|
+
const manifest = (() => { try {
|
|
10
|
+
return loadManifest();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
} })();
|
|
15
|
+
ctx.respondJson(res, {
|
|
16
|
+
dashboardApiVersion: DASHBOARD_API_VERSION,
|
|
17
|
+
architectureGeneration: INSTALL_ARCH_GENERATION,
|
|
18
|
+
installArchGeneration: manifest?.architectureGeneration ?? null,
|
|
19
|
+
product: 'nexus-prime',
|
|
20
|
+
});
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
3
23
|
if (req.method === 'GET' && url.pathname === '/api/license') {
|
|
4
24
|
const { getSharedLicenseManager } = await import('../../licensing/index.js');
|
|
5
25
|
ctx.respondJson(res, getSharedLicenseManager().getStatus());
|
package/dist/dashboard/server.js
CHANGED
|
@@ -21,6 +21,7 @@ import { handleAuthoringRoutes } from './routes/authoring.js';
|
|
|
21
21
|
import { handleArchitectsRoutes } from './routes/architects.js';
|
|
22
22
|
import { handleWorkforceRoutes } from './routes/workforce.js';
|
|
23
23
|
import { handleGraphRoutes } from './routes/graph.js';
|
|
24
|
+
import { DASHBOARD_API_VERSION } from './contract.js';
|
|
24
25
|
import { SseBroker } from './stream/sse-broker.js';
|
|
25
26
|
import { nexusEventBus } from '../engines/event-bus.js';
|
|
26
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -30,7 +31,6 @@ const DEFAULT_PORT = parseInt(process.env.NEXUS_DASHBOARD_PORT || '3377', 10);
|
|
|
30
31
|
const MAX_PORT_SCAN = 24;
|
|
31
32
|
const DASHBOARD_PROBE_TIMEOUT_MS = 3000;
|
|
32
33
|
const DASHBOARD_PROBE_ATTEMPTS = 2;
|
|
33
|
-
const DASHBOARD_API_VERSION = '4';
|
|
34
34
|
const DASHBOARD_SCHEMA_VERSION = 1;
|
|
35
35
|
const DASHBOARD_PRETTY_JSON = process.env.NEXUS_DASHBOARD_PRETTY_JSON === '1';
|
|
36
36
|
const CORE_CAPABILITIES = {
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Persistence: SQLite (same pattern as memory.db / graph.db)
|
|
13
13
|
*/
|
|
14
|
+
export declare function getNgramWalPath(dbPath: string): string;
|
|
15
|
+
export declare function getNgramShmPath(dbPath: string): string;
|
|
16
|
+
export declare function getNgramFootprintBytes(dbPath: string): number;
|
|
14
17
|
export interface Posting {
|
|
15
18
|
docId: string;
|
|
16
19
|
locMask: number;
|
|
@@ -54,6 +57,9 @@ export declare class NgramIndex {
|
|
|
54
57
|
private deleteStmt;
|
|
55
58
|
private lookupStmt;
|
|
56
59
|
private docExistsStmt;
|
|
60
|
+
private writesSinceCheckpoint;
|
|
61
|
+
private lastCheckpointAt;
|
|
62
|
+
private quotaSkipNoticeShown;
|
|
57
63
|
private _searchStats;
|
|
58
64
|
constructor(dbPath?: string, options?: NgramIndexOptions);
|
|
59
65
|
private rotateOversizeDbIfNeeded;
|
|
@@ -64,14 +70,35 @@ export declare class NgramIndex {
|
|
|
64
70
|
private warmHashSet;
|
|
65
71
|
/** Warm the hash set after a deferred init. No-op if already warm. */
|
|
66
72
|
warmHashSetDeferred(): void;
|
|
73
|
+
/** Run an opportunistic WAL checkpoint. PASSIVE first, escalate to TRUNCATE
|
|
74
|
+
* if the WAL is still over its configured limit. Returns the WAL byte size
|
|
75
|
+
* observed after the attempt so callers can report progress. */
|
|
76
|
+
private checkpointIfNeeded;
|
|
77
|
+
/** Footprint quota check before a write. Returns true if the write should
|
|
78
|
+
* proceed; false if it should be skipped. Throws when NEXUS_NGRAM_STRICT_QUOTA=1
|
|
79
|
+
* so callers can surface hard quota failures during tests/CI. */
|
|
80
|
+
private allowWrite;
|
|
67
81
|
/** Index a document's text content */
|
|
68
82
|
addDocument(docId: string, text: string): void;
|
|
83
|
+
/** Trigger a passive checkpoint when either the doc or time interval threshold
|
|
84
|
+
* is hit. Cheap on a healthy DB, prevents WAL growth on bulk indexing. */
|
|
85
|
+
private maybePeriodicCheckpoint;
|
|
69
86
|
/** Remove a document from the index */
|
|
70
87
|
removeDocument(docId: string): void;
|
|
71
88
|
/** Check if a document is already indexed */
|
|
72
89
|
isIndexed(docId: string): boolean;
|
|
73
90
|
/** Get count of indexed documents */
|
|
74
91
|
getDocCount(): number;
|
|
92
|
+
/** Path to the underlying SQLite database file. */
|
|
93
|
+
getDbPath(): string;
|
|
94
|
+
/** Bytes occupied by the .db file alone. */
|
|
95
|
+
getDbBytes(): number;
|
|
96
|
+
/** Bytes occupied by the .db-wal file. */
|
|
97
|
+
getWalBytes(): number;
|
|
98
|
+
/** Bytes occupied by the .db-shm file. */
|
|
99
|
+
getShmBytes(): number;
|
|
100
|
+
/** Total SQLite footprint = db + wal + shm. Use this for quota checks. */
|
|
101
|
+
getSqliteFootprintBytes(): number;
|
|
75
102
|
/**
|
|
76
103
|
* Search for documents matching a text query.
|
|
77
104
|
* Returns candidate document IDs ranked by trigram match count.
|
|
@@ -120,8 +147,9 @@ export declare class NgramIndex {
|
|
|
120
147
|
optimizeStorage(force?: boolean): void;
|
|
121
148
|
/**
|
|
122
149
|
* Operator-focused maintenance for the on-disk ngram DB.
|
|
123
|
-
* - Bounds runaway DB growth via rotation (default >= 1GB)
|
|
124
|
-
*
|
|
150
|
+
* - Bounds runaway DB growth via rotation (default >= 1GB), counting the
|
|
151
|
+
* full SQLite footprint (db + wal + shm) so a runaway WAL triggers it.
|
|
152
|
+
* - Vacuums only when safe (<= vacuumMaxBytes) and either forced or dirty.
|
|
125
153
|
*/
|
|
126
154
|
maintainBounded(options?: {
|
|
127
155
|
force?: boolean;
|
|
@@ -132,7 +160,13 @@ export declare class NgramIndex {
|
|
|
132
160
|
uniqueTrigramCount: number;
|
|
133
161
|
dbSizeBytes: number;
|
|
134
162
|
};
|
|
135
|
-
/** Close the database connection
|
|
163
|
+
/** Close the database connection.
|
|
164
|
+
*
|
|
165
|
+
* Always truncates the WAL so the .db-wal sibling can never outgrow the
|
|
166
|
+
* configured cap on shutdown — the failure mode that produced the 84GB
|
|
167
|
+
* WAL on a user machine. VACUUM is only run when the operator has both a
|
|
168
|
+
* small DB and explicitly opts in via NEXUS_NGRAM_VACUUM_ON_CLOSE=1, since
|
|
169
|
+
* blind VACUUM on multi-GB DBs blocks shutdown indefinitely. */
|
|
136
170
|
close(): void;
|
|
137
171
|
}
|
|
138
172
|
/** Get or create the shared NgramIndex instance */
|