moflo 4.12.2 → 4.12.3-rc.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/bin/hooks.mjs +5 -2
- package/bin/index-all.mjs +38 -0
- package/bin/lib/moflo-paths.mjs +37 -1
- package/bin/lib/process-manager.mjs +16 -1
- package/bin/lib/registry-cleanup.cjs +9 -1
- package/bin/session-start-launcher.mjs +5 -2
- package/dist/src/cli/commands/agent.js +7 -2
- package/dist/src/cli/commands/benchmark.js +5 -2
- package/dist/src/cli/commands/daemon.js +81 -9
- package/dist/src/cli/commands/doctor-checks-config.js +55 -19
- package/dist/src/cli/commands/doctor-checks-memory.js +3 -2
- package/dist/src/cli/commands/doctor-checks-writers-audit.js +5 -1
- package/dist/src/cli/commands/doctor-embedding-hygiene.js +2 -1
- package/dist/src/cli/commands/doctor-fixes.js +20 -5
- package/dist/src/cli/commands/doctor-zombies.js +4 -3
- package/dist/src/cli/commands/embeddings.js +10 -5
- package/dist/src/cli/commands/hooks.js +11 -8
- package/dist/src/cli/commands/swarm.js +2 -1
- package/dist/src/cli/index.js +18 -6
- package/dist/src/cli/memory/bridge-core.js +8 -2
- package/dist/src/cli/memory/daemon-write-client.js +5 -5
- package/dist/src/cli/memory/database-provider.js +14 -5
- package/dist/src/cli/memory/entries-read.js +5 -4
- package/dist/src/cli/memory/entries-write.js +3 -2
- package/dist/src/cli/memory/hnsw-singleton.js +2 -1
- package/dist/src/cli/memory/init.js +14 -8
- package/dist/src/cli/memory/learnings-overview.js +2 -1
- package/dist/src/cli/runtime/headless.js +8 -3
- package/dist/src/cli/services/daemon-dashboard.js +4 -4
- package/dist/src/cli/services/embeddings-migration.js +2 -1
- package/dist/src/cli/services/ephemeral-namespace-purge.js +3 -2
- package/dist/src/cli/services/project-root.js +108 -1
- package/dist/src/cli/services/soft-delete-purge.js +2 -1
- package/dist/src/cli/services/worker-daemon.js +67 -1
- package/dist/src/cli/version.js +1 -1
- package/package.json +8 -7
|
@@ -21,6 +21,7 @@ import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
|
21
21
|
// eagerly is cheap. The heavy imports (fastembed wrapper, upgrade renderer,
|
|
22
22
|
// etc.) stay deferred behind the early returns so session-start stays fast.
|
|
23
23
|
import { CANONICAL_EMBEDDING_MODEL, EMBEDDINGS_VERSION, } from '../embeddings/migration/types.js';
|
|
24
|
+
import { resolveStateRoot } from './project-root.js';
|
|
24
25
|
/**
|
|
25
26
|
* Run the migration if and only if it's needed.
|
|
26
27
|
*
|
|
@@ -33,7 +34,7 @@ import { CANONICAL_EMBEDDING_MODEL, EMBEDDINGS_VERSION, } from '../embeddings/mi
|
|
|
33
34
|
export async function runEmbeddingsMigrationIfNeeded(options = {}) {
|
|
34
35
|
const fs = await import('fs');
|
|
35
36
|
const path = await import('path');
|
|
36
|
-
const dbPath = path.resolve(options.dbPath ?? memoryDbPath(
|
|
37
|
+
const dbPath = path.resolve(options.dbPath ?? memoryDbPath(resolveStateRoot()));
|
|
37
38
|
if (!fs.existsSync(dbPath))
|
|
38
39
|
return false;
|
|
39
40
|
// node:sqlite via the unified factory (Phase 5 / #1084). The migration
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import { PURGE_ON_SESSION_START_NAMESPACES, PURGE_ON_SESSION_START_PREFIXES, TASKLIST_RETENTION_CAP, } from '../memory/bridge-embedder.js';
|
|
31
31
|
import { memoryDbPath } from './moflo-paths.js';
|
|
32
32
|
import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
33
|
+
import { resolveStateRoot } from './project-root.js';
|
|
33
34
|
/**
|
|
34
35
|
* Hard-delete rows in {@link PURGE_ON_SESSION_START_NAMESPACES} and trim the
|
|
35
36
|
* `tasklist` namespace to its retention cap, then VACUUM. Returns
|
|
@@ -41,7 +42,7 @@ import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
|
41
42
|
export async function purgeEphemeralNamespaces(options = {}) {
|
|
42
43
|
const fs = await import('fs');
|
|
43
44
|
const path = await import('path');
|
|
44
|
-
const dbPath = path.resolve(options.dbPath ?? memoryDbPath(
|
|
45
|
+
const dbPath = path.resolve(options.dbPath ?? memoryDbPath(resolveStateRoot()));
|
|
45
46
|
if (!fs.existsSync(dbPath))
|
|
46
47
|
return { purged: 0, trimmed: 0 };
|
|
47
48
|
// node:sqlite via the unified factory (Phase 5 / #1084). WAL persists each
|
|
@@ -128,7 +129,7 @@ export async function purgeEphemeralNamespaces(options = {}) {
|
|
|
128
129
|
export async function purgeMemoryProbeNamespaces(options = {}) {
|
|
129
130
|
const fs = await import('fs');
|
|
130
131
|
const path = await import('path');
|
|
131
|
-
const dbPath = path.resolve(options.dbPath ?? memoryDbPath(
|
|
132
|
+
const dbPath = path.resolve(options.dbPath ?? memoryDbPath(resolveStateRoot()));
|
|
132
133
|
if (!fs.existsSync(dbPath))
|
|
133
134
|
return { purged: 0 };
|
|
134
135
|
const prefixes = Array.from(PURGE_ON_SESSION_START_PREFIXES);
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* changed Pass A from nearest-wins to topmost-wins to fix monorepo daemon
|
|
44
44
|
* fragmentation.
|
|
45
45
|
*/
|
|
46
|
-
import { existsSync } from 'node:fs';
|
|
46
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
47
47
|
import { resolve, dirname, parse, join, basename } from 'node:path';
|
|
48
48
|
/**
|
|
49
49
|
* Walk strictly upward from `dir` (exclusive) and return the nearest ancestor
|
|
@@ -75,6 +75,113 @@ export function findAncestorMofloRoot(dir) {
|
|
|
75
75
|
}
|
|
76
76
|
return null;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Memo for {@link resolveStateRoot}, keyed on the two inputs that can change
|
|
80
|
+
* within a process: the starting directory and `CLAUDE_PROJECT_DIR`.
|
|
81
|
+
*
|
|
82
|
+
* Only MARKER-PROVEN results are cached — i.e. resolutions that landed on a
|
|
83
|
+
* directory actually holding `.moflo/moflo.db`. That distinction is what makes
|
|
84
|
+
* the cache safe: an existing `moflo.db` does not move during a process
|
|
85
|
+
* lifetime, whereas a fall-through result (Pass B/C, no moflo state anywhere)
|
|
86
|
+
* is exactly the case `flo init` is about to invalidate by creating one. So
|
|
87
|
+
* the un-cached path stays correct and the cached path stays stable.
|
|
88
|
+
*
|
|
89
|
+
* Motivation: a single `flo doctor` run resolves independently from ~6 check
|
|
90
|
+
* modules, each re-walking the ancestor chain.
|
|
91
|
+
*/
|
|
92
|
+
const stateRootCache = new Map();
|
|
93
|
+
/** Clear the {@link resolveStateRoot} memo. Test seam only. */
|
|
94
|
+
export function _resetStateRootCacheForTest() {
|
|
95
|
+
stateRootCache.clear();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolve the anchor that any code READING OR WRITING `.moflo/` state must use
|
|
99
|
+
* (#1315). Use this instead of `process.cwd()` anywhere a `.moflo/` path is
|
|
100
|
+
* built — the daemon, the healer, and any command that persists metrics,
|
|
101
|
+
* benchmarks, or reports.
|
|
102
|
+
*
|
|
103
|
+
* `findProjectRoot()` short-circuits on `CLAUDE_PROJECT_DIR` before Pass A.
|
|
104
|
+
* That is correct for most callers, but wrong for state anchoring: Claude Code
|
|
105
|
+
* sets the variable to the SESSION's directory, hooks inherit it, and a spawned
|
|
106
|
+
* daemon inherits it again via `daemonEnv` (`commands/daemon.ts`). In a
|
|
107
|
+
* monorepo session rooted at a sub-workspace the whole chain therefore agreed
|
|
108
|
+
* the project root was the sub-workspace, and `daemon start` minted a fresh
|
|
109
|
+
* `.moflo/` there — re-seeding the daemon islands #1174 was closed for.
|
|
110
|
+
*
|
|
111
|
+
* The rule here: an existing `CLAUDE_PROJECT_DIR` wins outright — it is what
|
|
112
|
+
* Claude Code says the project is, and the marker walk is never allowed to
|
|
113
|
+
* climb above it. Otherwise walk up from cwd and take the TOPMOST
|
|
114
|
+
* `.moflo/moflo.db`, which is the canonical root for a monorepo. That walk is
|
|
115
|
+
* the actual #1315 fix: the state sites previously used `process.cwd()` raw,
|
|
116
|
+
* so every sub-directory invocation minted an island where it stood.
|
|
117
|
+
*
|
|
118
|
+
* Why the env is not merely a starting point: the walk has no upper bound, so
|
|
119
|
+
* climbing past an explicit anchor lets any stray ancestor marker capture every
|
|
120
|
+
* project beneath it. See the note in `resolveStateRootUncached`.
|
|
121
|
+
*
|
|
122
|
+
* Deliberately a separate export rather than a change inside `findProjectRoot`
|
|
123
|
+
* itself: that resolver is shared with the MCP server, the memory bridge, and
|
|
124
|
+
* the gate hooks, whose env-override semantics are load-bearing elsewhere.
|
|
125
|
+
* Callers that merely need "which project am I in" should keep using
|
|
126
|
+
* `findProjectRoot`; callers that are about to CREATE something use this.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveStateRoot(opts) {
|
|
129
|
+
const envDir = process.env.CLAUDE_PROJECT_DIR;
|
|
130
|
+
const cacheKey = `${opts?.cwd ?? process.cwd()}\0${envDir ?? ''}`;
|
|
131
|
+
const cached = stateRootCache.get(cacheKey);
|
|
132
|
+
if (cached !== undefined)
|
|
133
|
+
return cached;
|
|
134
|
+
const resolved = resolveStateRootUncached(opts, envDir);
|
|
135
|
+
// Cache only a marker-proven anchor — see the note on `stateRootCache`.
|
|
136
|
+
if (existsSync(join(resolved, '.moflo', 'moflo.db'))) {
|
|
137
|
+
stateRootCache.set(cacheKey, resolved);
|
|
138
|
+
}
|
|
139
|
+
return resolved;
|
|
140
|
+
}
|
|
141
|
+
function resolveStateRootUncached(opts, envDir) {
|
|
142
|
+
// An explicitly-set `CLAUDE_PROJECT_DIR` is AUTHORITATIVE and is never
|
|
143
|
+
// climbed above. Claude Code sets it to the project the user opened, and the
|
|
144
|
+
// marker walk has no upper bound — it runs to the filesystem root and takes
|
|
145
|
+
// the TOPMOST `.moflo/moflo.db`. Letting it climb past an explicit anchor
|
|
146
|
+
// means any stray ancestor marker captures every project beneath it: one
|
|
147
|
+
// accidental `flo init` in `$HOME` silently re-roots everything, and a
|
|
148
|
+
// scratch project created inside another checkout resolves to that checkout
|
|
149
|
+
// and mutates it. (Verified, not hypothetical: with the climb enabled the
|
|
150
|
+
// session-start launcher operated on this repo instead of its fixture.)
|
|
151
|
+
//
|
|
152
|
+
// A typo'd or stale value naming a directory that does not exist is ignored
|
|
153
|
+
// — callers mkdir what we return, so honoring it would materialize `.moflo/`
|
|
154
|
+
// at the typo path.
|
|
155
|
+
const envAbs = envDir ? resolve(envDir) : undefined;
|
|
156
|
+
if (envAbs && existsSync(envAbs)) {
|
|
157
|
+
return canonicalize(envAbs);
|
|
158
|
+
}
|
|
159
|
+
// No usable env anchor: walk up from cwd. This is the #1315 fix proper —
|
|
160
|
+
// the state-anchoring sites used to take `process.cwd()` raw, so any
|
|
161
|
+
// sub-directory invocation minted an island there. Pass A's topmost-wins
|
|
162
|
+
// walk lands on the canonical root instead.
|
|
163
|
+
const resolved = findProjectRoot({ honorEnv: false, cwd: opts?.cwd });
|
|
164
|
+
return canonicalize(resolved);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Absolutize + realpath a resolved root.
|
|
168
|
+
*
|
|
169
|
+
* The env value is caller-supplied and may be relative or carry a trailing
|
|
170
|
+
* separator, and on macOS `/var/folders/...` must collapse to
|
|
171
|
+
* `/private/var/folders/...` or this root won't compare equal to the realpath'd
|
|
172
|
+
* one `daemon-port.ts:normalizeProjectRoot` derives for same-project matching
|
|
173
|
+
* (CLAUDE.md Rule #1 §2). Falls back to the un-canonicalized path when it
|
|
174
|
+
* doesn't exist, which is the right degradation — never throw from a resolver.
|
|
175
|
+
*/
|
|
176
|
+
function canonicalize(p) {
|
|
177
|
+
const abs = resolve(p);
|
|
178
|
+
try {
|
|
179
|
+
return realpathSync(abs);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return abs;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
78
185
|
export function findProjectRoot(opts) {
|
|
79
186
|
const honorEnv = opts?.honorEnv !== false;
|
|
80
187
|
if (honorEnv && process.env.CLAUDE_PROJECT_DIR) {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
20
20
|
import { memoryDbPath } from './moflo-paths.js';
|
|
21
21
|
import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
22
|
+
import { resolveStateRoot } from './project-root.js';
|
|
22
23
|
/**
|
|
23
24
|
* Hard-delete all `status='deleted'` rows from the memory DB and VACUUM.
|
|
24
25
|
*
|
|
@@ -30,7 +31,7 @@ import { openDaemonDatabase } from '../memory/daemon-backend.js';
|
|
|
30
31
|
export async function purgeSoftDeletedEntries(options = {}) {
|
|
31
32
|
const fs = await import('fs');
|
|
32
33
|
const path = await import('path');
|
|
33
|
-
const dbPath = path.resolve(options.dbPath ?? memoryDbPath(
|
|
34
|
+
const dbPath = path.resolve(options.dbPath ?? memoryDbPath(resolveStateRoot()));
|
|
34
35
|
if (!fs.existsSync(dbPath))
|
|
35
36
|
return { purged: 0 };
|
|
36
37
|
// node:sqlite via the unified factory (Phase 5 / #1084). WAL persists each
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* findings UI, not a default-on background loop.
|
|
24
24
|
*/
|
|
25
25
|
import { EventEmitter } from 'events';
|
|
26
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
26
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, statSync } from 'fs';
|
|
27
27
|
import { atomicWriteFileSync } from './atomic-file-write.js';
|
|
28
28
|
import { mofloDir } from './moflo-paths.js';
|
|
29
29
|
import { cpus } from 'os';
|
|
@@ -34,6 +34,7 @@ import { attachSignalHandlers } from '../shared/resilience/signal-handlers.js';
|
|
|
34
34
|
import { calculateDelay } from '../production/retry.js';
|
|
35
35
|
import { CircuitBreaker } from '../production/circuit-breaker.js';
|
|
36
36
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
37
|
+
import { readMofloEnv } from './env-compat.js';
|
|
37
38
|
/**
|
|
38
39
|
* Runtime allow-list of known {@link WorkerType} values. Used by
|
|
39
40
|
* `initializeWorkerStates` to silently drop entries from a stale
|
|
@@ -557,6 +558,68 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
557
558
|
config: this.config,
|
|
558
559
|
};
|
|
559
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* #1315 — exit when the project root has been deleted out from under us.
|
|
563
|
+
*
|
|
564
|
+
* A daemon started inside a git worktree outlived `git worktree remove`: the
|
|
565
|
+
* directory was gone, `git worktree list`/`prune` both reported clean, yet
|
|
566
|
+
* every tick the worker paths called `mkdirSync(stateDir, {recursive:true})`
|
|
567
|
+
* and RECREATED the deleted tree just to write `daemon-state.json` and
|
|
568
|
+
* `metrics/` into it. Deleting the husk by hand simply got it back on the
|
|
569
|
+
* next tick, because nothing ever asked whether the root still existed.
|
|
570
|
+
*
|
|
571
|
+
* Checked at the top of each tick, before any writer runs.
|
|
572
|
+
*
|
|
573
|
+
* Cross-platform (CLAUDE.md Rule #1): `existsSync` is platform-neutral, but
|
|
574
|
+
* the SCENARIO is mostly POSIX — Windows refuses to remove a directory that
|
|
575
|
+
* is a live process's cwd (sharing violation), and the daemon is spawned with
|
|
576
|
+
* `cwd: projectRoot`. So on Windows this check simply never fires, which is
|
|
577
|
+
* correct rather than a gap: the husk-recreation it guards against cannot
|
|
578
|
+
* happen there. No `process.platform` branch is warranted.
|
|
579
|
+
*
|
|
580
|
+
* @returns true when shutdown was initiated (caller must stop immediately).
|
|
581
|
+
*/
|
|
582
|
+
async shutdownIfRootVanished() {
|
|
583
|
+
// `statSync`, NOT `existsSync`. `existsSync` swallows every error and
|
|
584
|
+
// returns false, so EACCES, EIO, a disconnected SMB share, or a sleeping
|
|
585
|
+
// network volume would be indistinguishable from "deleted" and would exit
|
|
586
|
+
// a perfectly healthy daemon on one bad sample. Only ENOENT means gone.
|
|
587
|
+
try {
|
|
588
|
+
statSync(this.projectRoot);
|
|
589
|
+
return false; // still there
|
|
590
|
+
}
|
|
591
|
+
catch (err) {
|
|
592
|
+
if (err?.code !== 'ENOENT') {
|
|
593
|
+
// Can't tell — assume present. A false positive kills a healthy
|
|
594
|
+
// daemon, which is far worse than one more tick of an orphan.
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
this.log('warn', `Project root ${this.projectRoot} no longer exists — shutting down instead of recreating it`);
|
|
599
|
+
try {
|
|
600
|
+
await this.stop();
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
/* best effort — we're leaving either way */
|
|
604
|
+
}
|
|
605
|
+
// Only a genuine daemon process may take the process down with it. A CLI
|
|
606
|
+
// command or a test that happens to hold a WorkerDaemon must not be
|
|
607
|
+
// exited out from under itself. `MOFLO_TEST_NO_DAEMON_SELF_EXIT` is the
|
|
608
|
+
// test-runner kill switch (vitest.setup.ts) — without it a vitest worker
|
|
609
|
+
// that inherited MOFLO_DAEMON would exit(0) mid-run and report every
|
|
610
|
+
// remaining test as passed.
|
|
611
|
+
// `readMofloEnv('DAEMON')`, not raw `process.env.MOFLO_DAEMON` — it also
|
|
612
|
+
// honors the legacy `CLAUDE_FLOW_DAEMON` prefix, which is what OS service
|
|
613
|
+
// units written by older moflo versions still set. Reading only the modern
|
|
614
|
+
// name left those daemons clearing their timers but never exiting: a live
|
|
615
|
+
// process still holding its dashboard port. Matches how `daemon.ts`
|
|
616
|
+
// identifies a daemon process.
|
|
617
|
+
const isDaemonProcess = readMofloEnv('DAEMON') === '1';
|
|
618
|
+
if (isDaemonProcess && process.env.MOFLO_TEST_NO_DAEMON_SELF_EXIT !== '1') {
|
|
619
|
+
process.exit(0);
|
|
620
|
+
}
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
560
623
|
/**
|
|
561
624
|
* Schedule a worker to run at intervals with staggered start
|
|
562
625
|
*/
|
|
@@ -574,6 +637,9 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
574
637
|
const runAndReschedule = async () => {
|
|
575
638
|
if (!this.running)
|
|
576
639
|
return;
|
|
640
|
+
// #1315 — before any writer runs, confirm we still have a project.
|
|
641
|
+
if (await this.shutdownIfRootVanished())
|
|
642
|
+
return;
|
|
577
643
|
// Use concurrency-controlled execution (P0 fix)
|
|
578
644
|
await this.executeWorkerWithConcurrencyControl(workerConfig);
|
|
579
645
|
if (this.running && workerConfig.enabled && !state.disabledByOverrun) {
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.12.
|
|
3
|
+
"version": "4.12.3-rc.1",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
|
|
5
5
|
"main": "dist/src/cli/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"test:smoke": "node harness/consumer-smoke/run.mjs",
|
|
57
57
|
"test:smoke:populated": "node harness/consumer-smoke/run-populated.mjs",
|
|
58
58
|
"bench": "vitest run --config vitest.bench.config.ts",
|
|
59
|
-
"lint": "eslint src/ bin/ .claude/scripts/ --
|
|
59
|
+
"lint": "eslint src/ bin/ .claude/scripts/ --max-warnings 0",
|
|
60
60
|
"security:audit": "npm audit --omit=dev --audit-level high",
|
|
61
61
|
"security:fix": "npm audit fix",
|
|
62
62
|
"version": "node scripts/sync-version.mjs && git add src/cli/version.ts",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@anush008/tokenizers": "^0.6.0",
|
|
67
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
67
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
68
68
|
"js-yaml": "^5.2.0",
|
|
69
69
|
"lru-cache": "^11.3.5",
|
|
70
70
|
"onnxruntime-node": "^1.27.0",
|
|
@@ -92,10 +92,11 @@
|
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
94
|
"@types/node": "^24.12.2",
|
|
95
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
96
|
-
"@typescript-eslint/parser": "^
|
|
97
|
-
"eslint": "^8.0
|
|
98
|
-
"
|
|
95
|
+
"@typescript-eslint/eslint-plugin": "^8.65.0",
|
|
96
|
+
"@typescript-eslint/parser": "^8.65.0",
|
|
97
|
+
"eslint": "^10.8.0",
|
|
98
|
+
"glob": "^11.1.0",
|
|
99
|
+
"moflo": "^4.12.2",
|
|
99
100
|
"tsx": "^4.21.0",
|
|
100
101
|
"typescript": "^5.9.3",
|
|
101
102
|
"vitest": "^4.0.0"
|