diff --git a/.githooks/sync-versions.test.ts b/.githooks/sync-versions.test.ts index 505ad446..2b1b5a83 100644 --- a/.githooks/sync-versions.test.ts +++ b/.githooks/sync-versions.test.ts @@ -2,7 +2,7 @@ import {execFileSync} from 'node:child_process' import {mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync} from 'node:fs' import {tmpdir} from 'node:os' import {dirname, join} from 'node:path' -import {afterEach, describe, expect, it} from 'vitest' +import {afterEach, describe, expect, it} from 'bun:test' import {runSyncVersions} from './sync-versions' function writeJson(filePath: string, value: Record): void { @@ -56,6 +56,22 @@ function createFixtureRepo(): string { version: initialVersion, private: true }) + writeJson(join(rootDir, 'obsidian-plugin', 'package.json'), { + name: 'tnmso', + version: initialVersion, + private: true + }) + const manifest = { + id: 'tnmso', + name: 'TNMSO', + version: initialVersion, + minAppVersion: '1.0.0', + isDesktopOnly: false + } + writeJson(join(rootDir, 'obsidian-plugin', 'manifest.json'), manifest) + writeJson(join(rootDir, 'manifest.json'), manifest) + writeJson(join(rootDir, 'obsidian-plugin', 'versions.json'), {[initialVersion]: '1.0.0'}) + writeJson(join(rootDir, 'versions.json'), {[initialVersion]: '1.0.0'}) writeJson(join(rootDir, 'cli', 'npm', 'darwin-arm64', 'package.json'), { name: '@truenine/memory-sync-cli-darwin-arm64', version: initialVersion @@ -149,6 +165,14 @@ function expectSharedVersionSurfaces(rootDir: string, nextVersion: string): void }) expect(JSON.parse(readFileSync(join(rootDir, 'gui', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion}) expect(JSON.parse(readFileSync(join(rootDir, 'doc', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion}) + expect(JSON.parse(readFileSync(join(rootDir, 'obsidian-plugin', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion}) + const pluginManifest = JSON.parse(readFileSync(join(rootDir, 'obsidian-plugin', 'manifest.json'), 'utf-8')) as {version: string, minAppVersion: string} + expect(pluginManifest).toMatchObject({version: nextVersion, minAppVersion: '1.0.0'}) + expect(JSON.parse(readFileSync(join(rootDir, 'manifest.json'), 'utf-8'))).toEqual(pluginManifest) + expect(JSON.parse(readFileSync(join(rootDir, 'obsidian-plugin', 'versions.json'), 'utf-8'))).toMatchObject({[nextVersion]: '1.0.0'}) + expect(JSON.parse(readFileSync(join(rootDir, 'versions.json'), 'utf-8'))).toEqual( + JSON.parse(readFileSync(join(rootDir, 'obsidian-plugin', 'versions.json'), 'utf-8')) + ) expect(JSON.parse(readFileSync(join(rootDir, 'cli', 'npm', 'darwin-arm64', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion}) expect(JSON.parse(readFileSync(join(rootDir, 'cli', 'npm', 'linux-x64-gnu', 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion}) expect(readFileSync(join(rootDir, 'Cargo.toml'), 'utf-8')).toContain(`version = "${nextVersion}"`) @@ -197,7 +221,12 @@ describe('sync-versions hook', () => { 'gui/src-tauri/Cargo.toml', 'gui/src-tauri/tauri.conf.json', 'mcp/package.json', - 'package.json' + 'manifest.json', + 'obsidian-plugin/manifest.json', + 'obsidian-plugin/package.json', + 'obsidian-plugin/versions.json', + 'package.json', + 'versions.json' ])) }) @@ -229,7 +258,12 @@ describe('sync-versions hook', () => { 'gui/src-tauri/Cargo.toml', 'gui/src-tauri/tauri.conf.json', 'mcp/package.json', - 'package.json' + 'manifest.json', + 'obsidian-plugin/manifest.json', + 'obsidian-plugin/package.json', + 'obsidian-plugin/versions.json', + 'package.json', + 'versions.json' ])) }) @@ -252,6 +286,25 @@ describe('sync-versions hook', () => { expectSharedVersionSurfaces(rootDir, nextVersion) }) + it('accepts the TNMSO package version as the staged system version source', () => { + const rootDir = createFixtureRepo() + tempDirs.push(rootDir) + + const nextVersion = '2026.10324.10319' + writeJson(join(rootDir, 'obsidian-plugin', 'package.json'), { + name: 'tnmso', + version: nextVersion, + private: true + }) + runGit(rootDir, ['add', 'obsidian-plugin/package.json']) + + const result = runSyncVersions({rootDir}) + + expect(result.targetVersion).toBe(nextVersion) + expect(result.versionSource).toBe('obsidian-plugin/package.json') + expectSharedVersionSurfaces(rootDir, nextVersion) + }) + it('fails when staged package.json files propose conflicting versions', () => { const rootDir = createFixtureRepo() tempDirs.push(rootDir) diff --git a/.githooks/sync-versions.ts b/.githooks/sync-versions.ts index 7833eb41..43a623ab 100644 --- a/.githooks/sync-versions.ts +++ b/.githooks/sync-versions.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env tsx +#!/usr/bin/env bun /** * Version Sync Script * Auto-sync all publishable package versions before commit. @@ -45,6 +45,57 @@ function writeJsonFile(filePath: string, value: VersionedJson): void { writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf-8') } +function writeJsonFileIfChanged( + filePath: string, + value: VersionedJson, + changedPaths: Set, +): void { + let current: VersionedJson | undefined + try { + current = readJsonFile(filePath) + } catch { + current = undefined + } + + if (current != null && JSON.stringify(current) === JSON.stringify(value)) { + return + } + + writeJsonFile(filePath, value) + changedPaths.add(filePath) +} + +function syncObsidianReleaseMetadata( + rootDir: string, + targetVersion: string, + changedPaths: Set, +): void { + const pluginManifestPath = resolve(rootDir, 'obsidian-plugin', 'manifest.json') + const rootManifestPath = resolve(rootDir, 'manifest.json') + const pluginVersionsPath = resolve(rootDir, 'obsidian-plugin', 'versions.json') + const rootVersionsPath = resolve(rootDir, 'versions.json') + const manifest = readJsonFile(pluginManifestPath) + const minAppVersion = manifest.minAppVersion + + if (manifest.id !== 'tnmso' || typeof minAppVersion !== 'string' || minAppVersion.trim() === '') { + throw new Error('TNMSO manifest must define id=tnmso and a non-empty minAppVersion') + } + + const updatedManifest = {...manifest, version: targetVersion} + writeJsonFileIfChanged(pluginManifestPath, updatedManifest, changedPaths) + writeJsonFileIfChanged(rootManifestPath, updatedManifest, changedPaths) + + let versions: VersionedJson + try { + versions = readJsonFile(pluginVersionsPath) + } catch { + versions = {} + } + const updatedVersions = {...versions, [targetVersion]: minAppVersion} + writeJsonFileIfChanged(pluginVersionsPath, updatedVersions, changedPaths) + writeJsonFileIfChanged(rootVersionsPath, updatedVersions, changedPaths) +} + function discoverFilesByName(baseDir: string, fileName: string): string[] { const found: string[] = [] const entries = readdirSync(baseDir, {withFileTypes: true}) @@ -469,6 +520,8 @@ export function runSyncVersions(options: SyncVersionsOptions = {}): SyncVersions syncJsonVersion(filePath, target.version, changedPaths) } + syncObsidianReleaseMetadata(rootDir, target.version, changedPaths) + stageFiles(rootDir, [...changedPaths].sort()) return { diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml new file mode 100644 index 00000000..5a026536 --- /dev/null +++ b/.github/actions/setup-bun/action.yml @@ -0,0 +1,31 @@ +name: Setup Bun +description: Setup the pinned Bun runtime and optionally install TNMSO dependencies + +inputs: + bun-version: + description: Bun version + required: false + default: "1.3.14" + install: + description: Whether to install dependencies + required: false + default: "true" + working-directory: + description: Directory containing bun.lock + required: false + default: obsidian-plugin + +runs: + using: composite + steps: + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: ${{ inputs.bun-version }} + + - name: Install Bun dependencies + if: inputs.install == 'true' + shell: bash + run: bun install --frozen-lockfile --cwd "$BUN_WORKING_DIRECTORY" + env: + BUN_WORKING_DIRECTORY: ${{ inputs.working-directory }} diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index b178482a..14bbec2b 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -26,7 +26,7 @@ runs: uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ inputs.rust-version }} - components: rustfmt + components: rustfmt,clippy targets: ${{ inputs.targets }} - name: Cache cargo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5eaa0c6c..12e911bf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-24.04 outputs: docs: ${{ steps.filter.outputs.docs }} - gui: ${{ steps.filter.outputs.gui }} + obsidian: ${{ steps.filter.outputs.obsidian }} steps: - uses: actions/checkout@v6 @@ -40,16 +40,37 @@ jobs: - 'pnpm-lock.yaml' - 'pnpm-workspace.yaml' - 'xtask/**' - gui: - - 'gui/**' - - '.github/actions/build-gui-platform/action.yml' - - '.github/actions/setup-tauri/action.yml' + obsidian: + - 'obsidian-plugin/**' + - 'manifest.json' + - 'versions.json' + - '.githooks/sync-versions.ts' + - '.githooks/sync-versions.test.ts' + - 'scripts/shared/check-version-surfaces.ts' + - '.github/actions/setup-bun/action.yml' - '.github/workflows/ci.yml' - '.github/workflows/release.yml' - 'package.json' - - 'pnpm-lock.yaml' - - 'pnpm-workspace.yaml' - - 'xtask/**' + + version-surfaces: + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-bun + with: + install: "false" + + - name: Test version synchronization + run: bun test ./.githooks/sync-versions.test.ts + + - name: Validate current version surfaces + shell: bash + run: | + version="$(bun -e 'console.log(require("./package.json").version)')" + bun scripts/shared/check-version-surfaces.ts "$version" validate-monorepo: if: github.event_name != 'pull_request' || github.event.pull_request.draft == false @@ -93,38 +114,41 @@ jobs: run: cargo build --release -p tnmsc -p tnmsm - name: CLI packaging smoke - run: cargo test -p tnmsc-local-tests packaging_smoke_covers_release_binary_and_global_install -- --exact --nocapture - - - name: MCP packaging smoke - run: cargo test -p tnmsm-local-tests packaging_smoke_covers_release_binary_and_global_install -- --exact --nocapture - - gui-smoke: - needs: changes - if: | - (github.event_name != 'pull_request' || github.event.pull_request.draft == false) && - needs.changes.outputs.gui == 'true' - runs-on: ubuntu-24.04 - timeout-minutes: 30 - steps: - - uses: actions/checkout@v6 - - - uses: ./.github/actions/setup-node-pnpm - - - name: Read GUI version - id: gui-version + shell: bash run: | - version="$(node -p 'require("./gui/package.json").version')" - echo "version=$version" >> "$GITHUB_OUTPUT" + set -euo pipefail + target/release/tnmsc assemble-npm --profile release - - uses: ./.github/actions/setup-tauri - with: - version: ${{ steps.gui-version.outputs.version }} + pack_dir="$(mktemp -d)" + prefix_dir="$(mktemp -d)" + trap 'rm -rf "$pack_dir" "$prefix_dir"' EXIT + pnpm -C cli/npm/linux-x64-gnu pack --pack-destination "$pack_dir" + pnpm -C cli pack --pack-destination "$pack_dir" - - name: Build GUI - run: cargo run -p xtask -- gui-build + platform_tarballs=("$pack_dir"/truenine-memory-sync-cli-linux-x64-gnu-*.tgz) + main_tarballs=("$pack_dir"/truenine-memory-sync-cli-[0-9]*.tgz) + npm install --global --prefix "$prefix_dir" "${platform_tarballs[0]}" "${main_tarballs[0]}" --ignore-scripts + "$prefix_dir/bin/tnmsc" help >"$pack_dir/tnmsc-help.txt" + grep -q 'install' "$pack_dir/tnmsc-help.txt" - - name: Test GUI - run: pnpm -C gui test + - name: MCP packaging smoke + shell: bash + run: | + set -euo pipefail + target/release/tnmsm assemble-npm --profile release + + pack_dir="$(mktemp -d)" + prefix_dir="$(mktemp -d)" + trap 'rm -rf "$pack_dir" "$prefix_dir"' EXIT + pnpm -C mcp/npm/linux-x64-gnu pack --pack-destination "$pack_dir" + pnpm -C mcp pack --pack-destination "$pack_dir" + + platform_tarballs=("$pack_dir"/truenine-memory-sync-mcp-linux-x64-gnu-*.tgz) + main_tarballs=("$pack_dir"/truenine-memory-sync-mcp-[0-9]*.tgz) + npm install --global --prefix "$prefix_dir" "${platform_tarballs[0]}" "${main_tarballs[0]}" --ignore-scripts + printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \ + | "$prefix_dir/bin/tnmsm" >"$pack_dir/tnmsm-initialize.json" + grep -q '"jsonrpc":"2.0"' "$pack_dir/tnmsm-initialize.json" docs-check: needs: changes @@ -151,3 +175,34 @@ jobs: - name: Build docs run: pnpm -C doc run build + + obsidian-plugin-check: + needs: changes + if: | + (github.event_name != 'pull_request' || github.event.pull_request.draft == false) && + needs.changes.outputs.obsidian == 'true' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-bun + + - name: Test TNMSO + run: bun test + working-directory: obsidian-plugin + + - name: Typecheck TNMSO + run: bun run check:type + working-directory: obsidian-plugin + + - name: Lint TNMSO + run: bun run lint + working-directory: obsidian-plugin + + - name: Build and package TNMSO + run: | + bun run build + bun run package:release + bun run verify:dist + working-directory: obsidian-plugin diff --git a/.github/workflows/debug-gui-rebuild.yml b/.github/workflows/debug-gui-rebuild.yml index 5e62bac7..e0b97d1d 100644 --- a/.github/workflows/debug-gui-rebuild.yml +++ b/.github/workflows/debug-gui-rebuild.yml @@ -1,4 +1,4 @@ -name: Debug GUI Rebuild +name: GUI Build (Manual) on: workflow_dispatch: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b6de7fe0..391d91c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,10 @@ jobs: cache: "false" install: "true" + - uses: ./.github/actions/setup-bun + with: + install: "false" + - name: Resolve release version id: release-version shell: bash @@ -55,7 +59,22 @@ jobs: echo "version=$version" >> "$GITHUB_OUTPUT" - name: Validate version surfaces - run: pnpm exec tsx scripts/shared/check-version-surfaces.ts "${{ steps.release-version.outputs.version }}" + run: bun scripts/shared/check-version-surfaces.ts "${{ steps.release-version.outputs.version }}" + + - name: Validate existing release tag targets + shell: bash + run: | + set -euo pipefail + git fetch --force --tags + for tag in "v${{ steps.release-version.outputs.version }}" "${{ steps.release-version.outputs.version }}"; do + if git rev-parse --verify --quiet "refs/tags/${tag}" >/dev/null; then + tag_commit="$(git rev-list -n 1 "refs/tags/${tag}")" + if [[ "$tag_commit" != "${GITHUB_SHA}" ]]; then + echo "::error::Existing tag ${tag} points to ${tag_commit}, expected ${GITHUB_SHA}." + exit 1 + fi + fi + done build-cli-binaries: needs: validate-version @@ -417,71 +436,41 @@ jobs: registry-url: ${{ env.NPM_REGISTRY_URL }} package-dir: mcp - build-gui: + build-obsidian-plugin: needs: validate-version - strategy: - fail-fast: false - matrix: - include: - - os: windows-latest - tauri-command: pnpm tauri build - artifact-path: | - target/*/release/bundle/**/*.exe - target/*/release/bundle/**/*.msi - target/*/release/bundle/**/*.sig - target/*/release/bundle/**/*.zip - target/release/bundle/**/*.exe - target/release/bundle/**/*.msi - target/release/bundle/**/*.sig - target/release/bundle/**/*.zip - - os: ubuntu-24.04 - tauri-command: pnpm tauri build - artifact-path: | - target/*/release/bundle/**/*.AppImage - target/*/release/bundle/**/*.deb - target/*/release/bundle/**/*.rpm - target/*/release/bundle/**/*.sig - target/release/bundle/**/*.AppImage - target/release/bundle/**/*.deb - target/release/bundle/**/*.rpm - target/release/bundle/**/*.sig - - os: macos-14 - rust_targets: aarch64-apple-darwin,x86_64-apple-darwin - tauri-command: pnpm tauri build --target universal-apple-darwin - artifact-path: | - target/*/release/bundle/**/*.dmg - target/*/release/bundle/**/*.tar.gz - target/*/release/bundle/**/*.sig - target/release/bundle/**/*.dmg - target/release/bundle/**/*.tar.gz - target/release/bundle/**/*.sig - runs-on: ${{ matrix.os }} - timeout-minutes: 75 + runs-on: ubuntu-24.04 + timeout-minutes: 20 steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/build-gui-platform + - uses: ./.github/actions/setup-bun + + - name: Test and validate TNMSO + working-directory: obsidian-plugin + run: | + bun test + bun run check:type + bun run lint + + - name: Build and package TNMSO + working-directory: obsidian-plugin + run: | + bun run build + bun run package:release + bun run verify:dist + + - name: Upload TNMSO release artifact + uses: actions/upload-artifact@v7 with: - tauri-command: ${{ matrix.tauri-command }} - artifact-name: gui-${{ matrix.os }} - artifact-path: ${{ matrix.artifact-path }} - rust-targets: ${{ matrix.rust_targets || '' }} - signing-private-key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - signing-private-key-password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - version: ${{ needs.validate-version.outputs.version }} + name: obsidian-plugin-release + path: obsidian-plugin/dist/ + if-no-files-found: error create-github-release: - needs: [validate-version, build-cli-binaries, publish-cli-npm, build-mcp-binaries, publish-mcp-npm, build-gui] + needs: [validate-version, build-cli-binaries, publish-cli-npm, build-mcp-binaries, publish-mcp-npm, build-obsidian-plugin] runs-on: ubuntu-24.04 timeout-minutes: 20 steps: - - name: Download GUI artifacts - uses: actions/download-artifact@v8 - with: - path: artifacts/gui - pattern: gui-* - merge-multiple: true - - name: Download CLI archive artifacts uses: actions/download-artifact@v8 with: @@ -496,31 +485,20 @@ jobs: pattern: mcp-archive-* merge-multiple: true - - name: Clean up unnecessary macOS artifacts - shell: bash - run: | - find artifacts/gui -name '*.icns' -delete - find artifacts/gui -name 'Info.plist' -delete + - name: Download TNMSO artifacts + uses: actions/download-artifact@v8 + with: + name: obsidian-plugin-release + path: artifacts/obsidian - name: Verify release artifacts shell: bash run: | set -euo pipefail - installer_count=$(find artifacts/gui -type f \( -name '*.dmg' -o -name '*.exe' -o -name '*.msi' -o -name '*.AppImage' -o -name '*.deb' -o -name '*.rpm' \) | wc -l | tr -d ' ') - updater_count=$(find artifacts/gui -type f \( -name '*.sig' -o -name '*.tar.gz' -o -name '*.zip' \) | wc -l | tr -d ' ') cli_archive_count=$(find artifacts/cli -type f \( -name '*.tar.gz' -o -name '*.zip' \) | wc -l | tr -d ' ') mcp_archive_count=$(find artifacts/mcp -type f \( -name '*.tar.gz' -o -name '*.zip' \) | wc -l | tr -d ' ') - - if [[ "$installer_count" -eq 0 ]]; then - echo "::error::No GUI installer artifacts were downloaded." - exit 1 - fi - - if [[ "$updater_count" -eq 0 ]]; then - echo "::error::No GUI updater artifacts were downloaded." - exit 1 - fi + obsidian_archive_count=$(find artifacts/obsidian -maxdepth 1 -type f -name 'tnmso-${{ needs.validate-version.outputs.version }}.zip' | wc -l | tr -d ' ') if [[ "$cli_archive_count" -ne 5 ]]; then echo "::error::Expected 5 CLI archives, found ${cli_archive_count}." @@ -532,24 +510,63 @@ jobs: exit 1 fi + if [[ "$obsidian_archive_count" -ne 1 ]]; then + echo "::error::Expected one TNMSO archive, found ${obsidian_archive_count}." + exit 1 + fi + - name: Publish GitHub release uses: softprops/action-gh-release@v3.0.0 with: tag_name: v${{ needs.validate-version.outputs.version }} + target_commitish: ${{ github.sha }} name: v${{ needs.validate-version.outputs.version }} files: | - artifacts/gui/**/*.dmg - artifacts/gui/**/*.exe - artifacts/gui/**/*.msi - artifacts/gui/**/*.AppImage - artifacts/gui/**/*.deb - artifacts/gui/**/*.rpm - artifacts/gui/**/*.sig - artifacts/gui/**/*.tar.gz - artifacts/gui/**/*.zip artifacts/cli/**/*.tar.gz artifacts/cli/**/*.zip artifacts/mcp/**/*.tar.gz artifacts/mcp/**/*.zip + artifacts/obsidian/tnmso-*.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + create-obsidian-release: + needs: [validate-version, build-obsidian-plugin] + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Download TNMSO artifacts + uses: actions/download-artifact@v8 + with: + name: obsidian-plugin-release + path: artifacts/obsidian + + - name: Verify Obsidian release files + shell: bash + run: | + set -euo pipefail + for file in main.js manifest.json styles.css "tnmso-${{ needs.validate-version.outputs.version }}.zip"; do + test -f "artifacts/obsidian/${file}" || { + echo "::error::Missing TNMSO release file: ${file}" + exit 1 + } + done + + - name: Publish Obsidian release + uses: softprops/action-gh-release@v3.0.0 + with: + tag_name: ${{ needs.validate-version.outputs.version }} + target_commitish: ${{ github.sha }} + name: TNMSO ${{ needs.validate-version.outputs.version }} + body: | + TNMSO safely previews prompt-focused MDX files as Markdown in Obsidian without evaluating JavaScript. + + Install `main.js`, `manifest.json`, and `styles.css` in `/.obsidian/plugins/tnmso/`, then reload Obsidian and enable TNMSO. + fail_on_unmatched_files: true + files: | + artifacts/obsidian/main.js + artifacts/obsidian/manifest.json + artifacts/obsidian/styles.css + artifacts/obsidian/tnmso-${{ needs.validate-version.outputs.version }}.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 9447357d..f0d7053b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2835,7 +2835,7 @@ dependencies = [ [[package]] name = "memory-sync" -version = "2026.10801.0" +version = "2026.10805.0" dependencies = [ "tnmsc", ] @@ -5893,7 +5893,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tnmsc" -version = "2026.10801.0" +version = "2026.10805.0" dependencies = [ "clap", "serde_json", @@ -5902,7 +5902,7 @@ dependencies = [ [[package]] name = "tnmsc-local-tests" -version = "2026.10801.0" +version = "2026.10805.0" dependencies = [ "dirs", "json5", @@ -5911,7 +5911,7 @@ dependencies = [ [[package]] name = "tnmsd" -version = "2026.10801.0" +version = "2026.10805.0" dependencies = [ "base64 0.23.0", "chrono", @@ -5940,7 +5940,7 @@ dependencies = [ [[package]] name = "tnmsg" -version = "2026.10801.0" +version = "2026.10805.0" dependencies = [ "dirs", "proptest", @@ -5955,7 +5955,7 @@ dependencies = [ [[package]] name = "tnmsm" -version = "2026.10801.0" +version = "2026.10805.0" dependencies = [ "assert_cmd", "clap", @@ -7286,7 +7286,7 @@ dependencies = [ [[package]] name = "xtask" -version = "2026.10801.0" +version = "2026.10805.0" dependencies = [ "clap", "serde", diff --git a/Cargo.toml b/Cargo.toml index f2075b61..947f9013 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ members = [ ] [workspace.package] -version = "2026.10801.0" +version = "2026.10805.0" edition = "2024" rust-version = "1.88" license = "AGPL-3.0-only" diff --git a/README.md b/README.md index 62312c5b..02986d56 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A rat carries even its own memories when moving. `memory-sync` is that kind of t - Auto-write configs for each tool: AGENTS.md, Claude Code, Codex CLI, Cursor, Windsurf, Qoder, Trae, Warp, JetBrains AI, etc. - Manage derived artifacts: prompt outputs, skills exports organized as `skills///`, README-class outputs - Multiple entry points: `tnmsc` CLI, private SDK, MCP stdio server, Tauri GUI +- Obsidian integration: TNMSO opens `.mdx` as native Markdown and safely previews static prompt syntax - Fine-grained write-scope control (`outputScopes`, `cleanupProtection`) - Source and derivations are auditable — no silent source mutations, no hidden residuals - Memories follow the person, not the project — no leakage @@ -26,6 +27,12 @@ MCP server: npm install -g @truenine/memory-sync-mcp ``` +### TNMSO for Obsidian + +TNMSO is the Obsidian plugin in [`obsidian-plugin/`](obsidian-plugin/README.md). Download `main.js`, `manifest.json`, and `styles.css` from the GitHub Release tagged with the exact CalVer version, then install them under `/.obsidian/plugins/tnmso/`. BRAT users can install `TrueNine/memory-sync` for beta testing. + +TNMSO shares the repository version. System releases use `v` tags; Obsidian releases use the matching plain `` tag because the community plugin updater requires the tag and manifest version to be identical. + ## Supported Tools | Type | Tools | @@ -68,4 +75,4 @@ If you're scraping by in a world of profoundly unequal resources — free tiers, - [zjarlin](https://github.com/zjarlin) ## License -[AGPL-3.0](LICENSE) \ No newline at end of file +[AGPL-3.0](LICENSE) diff --git a/cli/local-tests/tests/agents_md_smoke.rs b/cli/local-tests/tests/agents_md_smoke.rs index 961491d4..452396d5 100644 --- a/cli/local-tests/tests/agents_md_smoke.rs +++ b/cli/local-tests/tests/agents_md_smoke.rs @@ -93,7 +93,12 @@ fn init_git_repo(project_dir: &Path) { .arg("--quiet") .current_dir(project_dir) .output() - .unwrap_or_else(|error| panic!("failed to run git init in {}: {error}", project_dir.display())); + .unwrap_or_else(|error| { + panic!( + "failed to run git init in {}: {error}", + project_dir.display() + ) + }); assert!( output.status.success(), diff --git a/cli/local-tests/tests/claude_smoke.rs b/cli/local-tests/tests/claude_smoke.rs index 6159f156..353e6f25 100644 --- a/cli/local-tests/tests/claude_smoke.rs +++ b/cli/local-tests/tests/claude_smoke.rs @@ -111,7 +111,12 @@ fn init_git_repo(project_dir: &Path) { .arg("--quiet") .current_dir(project_dir) .output() - .unwrap_or_else(|error| panic!("failed to run git init in {}: {error}", project_dir.display())); + .unwrap_or_else(|error| { + panic!( + "failed to run git init in {}: {error}", + project_dir.display() + ) + }); assert!( output.status.success(), diff --git a/cli/local-tests/tests/clean_blackbox.rs b/cli/local-tests/tests/clean_blackbox.rs index fc7db17d..874b7d8c 100644 --- a/cli/local-tests/tests/clean_blackbox.rs +++ b/cli/local-tests/tests/clean_blackbox.rs @@ -102,7 +102,12 @@ fn init_git_repo(project_dir: &Path) { .arg("--quiet") .current_dir(project_dir) .output() - .unwrap_or_else(|error| panic!("failed to run git init in {}: {error}", project_dir.display())); + .unwrap_or_else(|error| { + panic!( + "failed to run git init in {}: {error}", + project_dir.display() + ) + }); assert!( output.status.success(), diff --git a/cli/local-tests/tests/codex_smoke.rs b/cli/local-tests/tests/codex_smoke.rs index 4b71f0cc..ba2cf3fd 100644 --- a/cli/local-tests/tests/codex_smoke.rs +++ b/cli/local-tests/tests/codex_smoke.rs @@ -122,7 +122,12 @@ fn init_git_repo(project_dir: &Path) { .arg("--quiet") .current_dir(project_dir) .output() - .unwrap_or_else(|error| panic!("failed to run git init in {}: {error}", project_dir.display())); + .unwrap_or_else(|error| { + panic!( + "failed to run git init in {}: {error}", + project_dir.display() + ) + }); assert!( output.status.success(), diff --git a/cli/local-tests/tests/install_smoke.rs b/cli/local-tests/tests/install_smoke.rs index e0319e58..5b0a76d5 100644 --- a/cli/local-tests/tests/install_smoke.rs +++ b/cli/local-tests/tests/install_smoke.rs @@ -100,7 +100,12 @@ fn init_git_repo(project_dir: &Path) { .arg("--quiet") .current_dir(project_dir) .output() - .unwrap_or_else(|error| panic!("failed to run git init in {}: {error}", project_dir.display())); + .unwrap_or_else(|error| { + panic!( + "failed to run git init in {}: {error}", + project_dir.display() + ) + }); assert!( output.status.success(), diff --git a/cli/local-tests/tests/rules_source_smoke.rs b/cli/local-tests/tests/rules_source_smoke.rs index 75e6a71e..b26378e9 100644 --- a/cli/local-tests/tests/rules_source_smoke.rs +++ b/cli/local-tests/tests/rules_source_smoke.rs @@ -79,7 +79,12 @@ fn init_git_repo(project_dir: &Path) { .arg("--quiet") .current_dir(project_dir) .output() - .unwrap_or_else(|error| panic!("failed to run git init in {}: {error}", project_dir.display())); + .unwrap_or_else(|error| { + panic!( + "failed to run git init in {}: {error}", + project_dir.display() + ) + }); assert!( output.status.success(), diff --git a/cli/local-tests/tests/support/opencode.rs b/cli/local-tests/tests/support/opencode.rs index 830a4554..836fa873 100644 --- a/cli/local-tests/tests/support/opencode.rs +++ b/cli/local-tests/tests/support/opencode.rs @@ -142,7 +142,12 @@ fn init_git_repo(project_dir: &Path) { .arg("--quiet") .current_dir(project_dir) .output() - .unwrap_or_else(|error| panic!("failed to run git init in {}: {error}", project_dir.display())); + .unwrap_or_else(|error| { + panic!( + "failed to run git init in {}: {error}", + project_dir.display() + ) + }); assert!( output.status.success(), diff --git a/cli/local-tests/tests/trae_smoke.rs b/cli/local-tests/tests/trae_smoke.rs index 5743a5a8..c84b9672 100644 --- a/cli/local-tests/tests/trae_smoke.rs +++ b/cli/local-tests/tests/trae_smoke.rs @@ -104,7 +104,12 @@ fn init_git_repo(project_dir: &Path) { .arg("--quiet") .current_dir(project_dir) .output() - .unwrap_or_else(|error| panic!("failed to run git init in {}: {error}", project_dir.display())); + .unwrap_or_else(|error| { + panic!( + "failed to run git init in {}: {error}", + project_dir.display() + ) + }); assert!( output.status.success(), diff --git a/cli/npm/darwin-arm64/package.json b/cli/npm/darwin-arm64/package.json index e0bf677a..1337da3c 100644 --- a/cli/npm/darwin-arm64/package.json +++ b/cli/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-darwin-arm64", - "version": "2026.10801.0", + "version": "2026.10805.0", "description": "tnmsc native binary for macOS arm64", "author": "TrueNine", "license": "AGPL-3.0-only", diff --git a/cli/npm/darwin-x64/package.json b/cli/npm/darwin-x64/package.json index a9a4b7e7..2c31f097 100644 --- a/cli/npm/darwin-x64/package.json +++ b/cli/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-darwin-x64", - "version": "2026.10801.0", + "version": "2026.10805.0", "description": "tnmsc native binary for macOS x64", "author": "TrueNine", "license": "AGPL-3.0-only", diff --git a/cli/npm/linux-arm64-gnu/package.json b/cli/npm/linux-arm64-gnu/package.json index c9c70607..0bb2f61b 100644 --- a/cli/npm/linux-arm64-gnu/package.json +++ b/cli/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-linux-arm64-gnu", - "version": "2026.10801.0", + "version": "2026.10805.0", "description": "tnmsc native binary for Linux arm64 (glibc)", "author": "TrueNine", "license": "AGPL-3.0-only", diff --git a/cli/npm/linux-x64-gnu/package.json b/cli/npm/linux-x64-gnu/package.json index 89b07e59..f1949660 100644 --- a/cli/npm/linux-x64-gnu/package.json +++ b/cli/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-linux-x64-gnu", - "version": "2026.10801.0", + "version": "2026.10805.0", "description": "tnmsc native binary for Linux x64 (glibc)", "author": "TrueNine", "license": "AGPL-3.0-only", diff --git a/cli/npm/win32-x64-msvc/package.json b/cli/npm/win32-x64-msvc/package.json index 5483ab18..48459f51 100644 --- a/cli/npm/win32-x64-msvc/package.json +++ b/cli/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli-win32-x64-msvc", - "version": "2026.10801.0", + "version": "2026.10805.0", "description": "tnmsc native binary for Windows x64", "author": "TrueNine", "license": "AGPL-3.0-only", diff --git a/cli/package.json b/cli/package.json index 871a90e8..41494e5f 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-cli", - "version": "2026.10801.0", + "version": "2026.10805.0", "description": "TrueNine Memory Synchronization CLI metadata package", "author": "TrueNine", "license": "AGPL-3.0-only", @@ -34,10 +34,10 @@ "test": "cargo test --manifest-path Cargo.toml" }, "optionalDependencies": { - "@truenine/memory-sync-cli-darwin-arm64": "2026.10801.0", - "@truenine/memory-sync-cli-darwin-x64": "2026.10801.0", - "@truenine/memory-sync-cli-linux-arm64-gnu": "2026.10801.0", - "@truenine/memory-sync-cli-linux-x64-gnu": "2026.10801.0", - "@truenine/memory-sync-cli-win32-x64-msvc": "2026.10801.0" + "@truenine/memory-sync-cli-darwin-arm64": "2026.10805.0", + "@truenine/memory-sync-cli-darwin-x64": "2026.10805.0", + "@truenine/memory-sync-cli-linux-arm64-gnu": "2026.10805.0", + "@truenine/memory-sync-cli-linux-x64-gnu": "2026.10805.0", + "@truenine/memory-sync-cli-win32-x64-msvc": "2026.10805.0" } } diff --git a/doc/app/docs/[[...mdxPath]]/layout.tsx b/doc/app/docs/[[...mdxPath]]/layout.tsx index 571999f0..ee369d1f 100644 --- a/doc/app/docs/[[...mdxPath]]/layout.tsx +++ b/doc/app/docs/[[...mdxPath]]/layout.tsx @@ -4,7 +4,7 @@ import {DocsSectionNav} from '../../../components/docs-section-nav' import {isDocSectionName} from '../../../lib/docs-sections' import {siteConfig, withBasePath} from '../../../lib/site' -type PageMapItem = { +interface PageMapItem { readonly name?: string readonly route?: string readonly title?: string @@ -15,7 +15,7 @@ function DocsSidebar({pageMap}: {readonly pageMap: readonly PageMapItem[]}) { return (