Skip to content

Commit 3d685d6

Browse files
authored
Merge pull request #99 from TrueNine/dev
fix: stabilize prompt loading and sync release 2026.10324.11958
2 parents dd43370 + 95782be commit 3d685d6

38 files changed

+1128
-234
lines changed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ members = [
99
]
1010

1111
[workspace.package]
12-
version = "2026.10324.10325"
12+
version = "2026.10324.11958"
1313
edition = "2024"
1414
license = "AGPL-3.0-only"
1515
authors = ["TrueNine"]

cli/npm/darwin-arm64/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@truenine/memory-sync-cli-darwin-arm64",
3-
"version": "2026.10324.10325",
3+
"version": "2026.10324.11958",
44
"os": [
55
"darwin"
66
],

cli/npm/darwin-x64/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@truenine/memory-sync-cli-darwin-x64",
3-
"version": "2026.10324.10325",
3+
"version": "2026.10324.11958",
44
"os": [
55
"darwin"
66
],

cli/npm/linux-arm64-gnu/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@truenine/memory-sync-cli-linux-arm64-gnu",
3-
"version": "2026.10324.10325",
3+
"version": "2026.10324.11958",
44
"os": [
55
"linux"
66
],

cli/npm/linux-x64-gnu/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@truenine/memory-sync-cli-linux-x64-gnu",
3-
"version": "2026.10324.10325",
3+
"version": "2026.10324.11958",
44
"os": [
55
"linux"
66
],

cli/npm/win32-x64-msvc/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@truenine/memory-sync-cli-win32-x64-msvc",
3-
"version": "2026.10324.10325",
3+
"version": "2026.10324.11958",
44
"os": [
55
"win32"
66
],

cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@truenine/memory-sync-cli",
33
"type": "module",
4-
"version": "2026.10324.10325",
4+
"version": "2026.10324.11958",
55
"description": "TrueNine Memory Synchronization CLI",
66
"author": "TrueNine",
77
"license": "AGPL-3.0-only",

cli/src/bridge/node.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use std::path::{Path, PathBuf};
77
use std::process::{Command, ExitCode, Stdio};
8+
use std::sync::{Mutex, OnceLock};
89

910
use crate::{
1011
BridgeCommandResult, CliError,
@@ -25,6 +26,41 @@ fn strip_win_prefix(path: PathBuf) -> PathBuf {
2526
}
2627

2728
const PACKAGE_NAME: &str = "@truenine/memory-sync-cli";
29+
static PLUGIN_RUNTIME_CACHE: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
30+
static NODE_CACHE: OnceLock<Mutex<Option<String>>> = OnceLock::new();
31+
32+
fn read_cached_success<T: Clone>(cache: &Mutex<Option<T>>) -> Option<T> {
33+
match cache.lock() {
34+
Ok(guard) => guard.clone(),
35+
Err(poisoned) => poisoned.into_inner().clone(),
36+
}
37+
}
38+
39+
fn store_cached_success<T: Clone>(cache: &Mutex<Option<T>>, value: &T) {
40+
match cache.lock() {
41+
Ok(mut guard) => {
42+
*guard = Some(value.clone());
43+
}
44+
Err(poisoned) => {
45+
*poisoned.into_inner() = Some(value.clone());
46+
}
47+
}
48+
}
49+
50+
fn detect_with_cached_success<T: Clone, F>(cache: &Mutex<Option<T>>, detect: F) -> Option<T>
51+
where
52+
F: FnOnce() -> Option<T>,
53+
{
54+
if let Some(cached) = read_cached_success(cache) {
55+
return Some(cached);
56+
}
57+
58+
let detected = detect();
59+
if let Some(value) = detected.as_ref() {
60+
store_cached_success(cache, value);
61+
}
62+
detected
63+
}
2864

2965
/// Locate the plugin runtime JS entry point.
3066
///
@@ -37,6 +73,11 @@ const PACKAGE_NAME: &str = "@truenine/memory-sync-cli";
3773
/// 6. npm/pnpm global install: `<global_root>/@truenine/memory-sync-cli/dist/plugin-runtime.mjs`
3874
/// 7. Embedded JS extracted to `~/.aindex/.cache/plugin-runtime-<version>.mjs`
3975
pub(crate) fn find_plugin_runtime() -> Option<PathBuf> {
76+
let cache = PLUGIN_RUNTIME_CACHE.get_or_init(|| Mutex::new(None));
77+
detect_with_cached_success(cache, detect_plugin_runtime)
78+
}
79+
80+
fn detect_plugin_runtime() -> Option<PathBuf> {
4081
let mut candidates: Vec<PathBuf> = Vec::new();
4182

4283
// Relative to binary location
@@ -166,6 +207,11 @@ fn extract_embedded_runtime() -> Option<PathBuf> {
166207

167208
/// Find the `node` executable.
168209
pub(crate) fn find_node() -> Option<String> {
210+
let cache = NODE_CACHE.get_or_init(|| Mutex::new(None));
211+
detect_with_cached_success(cache, detect_node)
212+
}
213+
214+
fn detect_node() -> Option<String> {
169215
// Try `node` in PATH
170216
if Command::new("node")
171217
.arg("--version")
@@ -452,6 +498,8 @@ fn find_index_mjs() -> Option<PathBuf> {
452498
#[cfg(test)]
453499
mod tests {
454500
use super::*;
501+
use std::cell::Cell;
502+
use std::sync::Mutex;
455503

456504
#[test]
457505
fn test_strip_win_prefix_with_prefix() {
@@ -473,4 +521,29 @@ mod tests {
473521
let result = strip_win_prefix(path.clone());
474522
assert_eq!(result, path);
475523
}
524+
525+
#[test]
526+
fn test_detect_with_cached_success_retries_until_success() {
527+
let cache = Mutex::new(None);
528+
let attempts = Cell::new(0);
529+
530+
let first = detect_with_cached_success(&cache, || {
531+
attempts.set(attempts.get() + 1);
532+
Option::<String>::None
533+
});
534+
assert_eq!(first, None);
535+
536+
let second = detect_with_cached_success(&cache, || {
537+
attempts.set(attempts.get() + 1);
538+
Some(String::from("node"))
539+
});
540+
assert_eq!(second, Some(String::from("node")));
541+
542+
let third = detect_with_cached_success(&cache, || {
543+
attempts.set(attempts.get() + 1);
544+
Some(String::from("other"))
545+
});
546+
assert_eq!(third, Some(String::from("node")));
547+
assert_eq!(attempts.get(), 2);
548+
}
476549
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as fs from 'node:fs'
2+
import * as os from 'node:os'
3+
import * as path from 'node:path'
4+
import {afterEach, describe, expect, it, vi} from 'vitest'
5+
6+
import {defineConfig} from './config'
7+
8+
const {collectInputContextMock} = vi.hoisted(() => ({
9+
collectInputContextMock: vi.fn(async () => {
10+
throw new Error('collectInputContext should not run for plugins fast path')
11+
})
12+
}))
13+
14+
vi.mock('./inputs/runtime', async importOriginal => {
15+
const actual = await importOriginal<typeof import('./inputs/runtime')>()
16+
17+
return {
18+
...actual,
19+
collectInputContext: collectInputContextMock
20+
}
21+
})
22+
23+
afterEach(() => {
24+
vi.clearAllMocks()
25+
})
26+
27+
describe('defineConfig plugins fast path', () => {
28+
it('skips input collection for plugins runtime commands', async () => {
29+
const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-plugins-fast-path-'))
30+
31+
try {
32+
const result = await defineConfig({
33+
loadUserConfig: false,
34+
pipelineArgs: ['node', 'tnmsc', 'plugins', '--json'],
35+
pluginOptions: {
36+
workspaceDir: tempWorkspace,
37+
plugins: []
38+
}
39+
})
40+
41+
expect(collectInputContextMock).not.toHaveBeenCalled()
42+
expect(result.context.workspace.directory.path).toBe(tempWorkspace)
43+
expect(result.context.aindexDir).toBe(path.join(tempWorkspace, 'aindex'))
44+
expect(result.outputPlugins).toEqual([])
45+
}
46+
finally {
47+
fs.rmSync(tempWorkspace, {recursive: true, force: true})
48+
}
49+
})
50+
})

0 commit comments

Comments
 (0)