Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/setup-rust/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ inputs:
rust-version:
description: Rust toolchain version
required: false
default: "1.88.0"
default: "1.93.1"
targets:
description: Additional Rust targets to install (comma-separated)
required: false
Expand Down
38 changes: 36 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,15 @@ jobs:

- uses: ./.github/actions/setup-node-pnpm

- uses: ./.github/actions/setup-rust
- name: Read GUI version
id: gui-version
run: |
version="$(node -p 'require("./gui/package.json").version')"
echo "version=$version" >> "$GITHUB_OUTPUT"

- uses: ./.github/actions/setup-tauri
with:
cache-key: pr
version: ${{ steps.gui-version.outputs.version }}

- name: Build native modules
run: pnpm run build:native
Expand All @@ -134,6 +140,34 @@ jobs:
- name: Run all tests
run: pnpm run test

test-gui:
if: github.event.pull_request.draft == false
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v6

- uses: ./.github/actions/setup-node-pnpm

- name: Read GUI version
id: gui-version
run: |
version="$(node -p 'require("./gui/package.json").version')"
echo "version=$version" >> "$GITHUB_OUTPUT"

- uses: ./.github/actions/setup-tauri
with:
version: ${{ steps.gui-version.outputs.version }}

- name: Build native modules
run: pnpm run build:native

- name: Build
run: pnpm run build

- name: Run GUI tests
run: pnpm turbo test --filter=@truenine/memory-sync-gui

test-sdk:
if: github.event.pull_request.draft == false
runs-on: ubuntu-24.04
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/release-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,33 @@ jobs:
fi
done <<< "$CLI_NATIVE_BINDING_PREFIXES"
done
- name: Validate NAPI binary format and size
shell: bash
run: |
shopt -s nullglob
min_size=$((1024 * 1024))
for target_dir in cli/npm/*/; do
if [ ! -f "${target_dir}package.json" ]; then
continue
fi
for node_file in "${target_dir}"*.node; do
if [ ! -f "$node_file" ]; then
continue
fi
size=$(stat -c%s "$node_file" 2>/dev/null || stat -f%z "$node_file" 2>/dev/null || echo 0)
if [ "$size" -lt "$min_size" ]; then
echo "ERROR: ${node_file} is too small (${size} bytes), expected at least ${min_size} bytes"
exit 1
fi
file_output=$(file "$node_file")
echo "File info for ${node_file}: ${file_output}"
if [[ "$file_output" == *"ELF"* ]] || [[ "$file_output" == *"Mach-O"* ]] || [[ "$file_output" == *"PE"* ]]; then
echo "OK: ${node_file} is a valid native binary"
else
echo "WARNING: ${node_file} format could not be verified as ELF/Mach-O/PE"
fi
done
done
- name: Publish CLI platform sub-packages
uses: ./.github/actions/npm-publish-package
with:
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
"dist",
"dist/tnmsc.schema.json"
],
"engines": {
"node": ">= 22"
},
"napi": {
"binaryName": "napi-memory-sync-cli",
"targets": [
Expand Down
13 changes: 1 addition & 12 deletions cli/src/commands/PluginsCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,10 @@ export class PluginsCommand implements Command {
readonly name = 'plugins'

async execute(ctx: CommandContext): Promise<CommandResult> {
const {logger, outputPlugins, userConfigOptions} = ctx
const {logger, outputPlugins} = ctx
const pluginInfos: JsonPluginInfo[] = []

for (const plugin of userConfigOptions.plugins) {
pluginInfos.push({
name: plugin.name,
kind: 'Output',
description: plugin.name,
dependencies: [...plugin.dependsOn ?? []]
})
}

const registeredNames = new Set(pluginInfos.map(plugin => plugin.name))
for (const plugin of outputPlugins) {
if (registeredNames.has(plugin.name)) continue
pluginInfos.push({
name: plugin.name,
kind: 'Output',
Expand Down
54 changes: 28 additions & 26 deletions cli/src/plugin.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
CursorOutputPlugin,
defineConfig,
DroidCLIOutputPlugin,
EditorConfigOutputPlugin,
GeminiCLIOutputPlugin,
GitExcludeOutputPlugin,
JetBrainsAIAssistantCodexOutputPlugin,
Expand All @@ -24,6 +23,10 @@ import {
ZedIDEConfigOutputPlugin
} from '@truenine/memory-sync-sdk'

type DefineConfigWithOutputPlugins = Parameters<typeof defineConfig>[0] & {
readonly outputPlugins: PipelineConfig['outputPlugins']
}

export function resolveRuntimeCommandFromArgv(argv: readonly string[] = process.argv): RuntimeCommand {
const args = argv.filter((arg): arg is string => arg != null)
const userArgs = args.slice(2)
Expand All @@ -39,34 +42,33 @@ export async function createDefaultPluginConfig(
runtimeCommand: RuntimeCommand = resolveRuntimeCommandFromArgv(argv),
executionCwd: string = process.cwd()
): Promise<PipelineConfig> {
const outputPlugins: PipelineConfig['outputPlugins'] = [
new AgentsOutputPlugin(),
new ClaudeCodeCLIOutputPlugin(),
new CodexCLIOutputPlugin(),
new JetBrainsAIAssistantCodexOutputPlugin(),
new DroidCLIOutputPlugin(),
new GeminiCLIOutputPlugin(),
new KiroCLIOutputPlugin(),
new OpencodeCLIOutputPlugin(),
new QoderIDEPluginOutputPlugin(),
new TraeIDEOutputPlugin(),
new TraeCNIDEOutputPlugin(),
new WarpIDEOutputPlugin(),
new WindsurfOutputPlugin(),
new CursorOutputPlugin(),
new GitExcludeOutputPlugin(),
new JetBrainsIDECodeStyleConfigOutputPlugin(),
new VisualStudioCodeIDEConfigOutputPlugin(),
new ZedIDEConfigOutputPlugin(),
new ReadmeMdConfigFileOutputPlugin()
]

return defineConfig({
executionCwd,
runtimeCommand,
pluginOptions: {
plugins: [
new AgentsOutputPlugin(),
new ClaudeCodeCLIOutputPlugin(),
new CodexCLIOutputPlugin(),
new JetBrainsAIAssistantCodexOutputPlugin(),
new DroidCLIOutputPlugin(),
new GeminiCLIOutputPlugin(),
new KiroCLIOutputPlugin(),
new OpencodeCLIOutputPlugin(),
new QoderIDEPluginOutputPlugin(),
new TraeIDEOutputPlugin(),
new TraeCNIDEOutputPlugin(),
new WarpIDEOutputPlugin(),
new WindsurfOutputPlugin(),
new CursorOutputPlugin(),
new GitExcludeOutputPlugin(),
new JetBrainsIDECodeStyleConfigOutputPlugin(),
new EditorConfigOutputPlugin(),
new VisualStudioCodeIDEConfigOutputPlugin(),
new ZedIDEConfigOutputPlugin(),
new ReadmeMdConfigFileOutputPlugin()
]
}
})
outputPlugins
} as DefineConfigWithOutputPlugins)
}

export default createDefaultPluginConfig
3 changes: 1 addition & 2 deletions cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"@truenine/plugin-claude-code-cli": ["./src/plugins/plugin-claude-code-cli.ts"],
"@truenine/plugin-cursor": ["./src/plugins/plugin-cursor.ts"],
"@truenine/plugin-droid-cli": ["./src/plugins/plugin-droid-cli.ts"],
"@truenine/plugin-editorconfig": ["./src/plugins/plugin-editorconfig.ts"],
"@truenine/plugin-gemini-cli": ["./src/plugins/plugin-gemini-cli.ts"],
"@truenine/plugin-git-exclude": ["./src/plugins/plugin-git-exclude.ts"],
"@truenine/plugin-jetbrains-ai-codex": ["./src/plugins/plugin-jetbrains-ai-codex.ts"],
Expand All @@ -37,7 +36,7 @@
"@truenine/plugin-readme": ["./src/plugins/plugin-readme.ts"],
"@truenine/plugin-trae-ide": ["./src/plugins/plugin-trae-ide.ts"],
"@truenine/plugin-vscode": ["./src/plugins/plugin-vscode.ts"],
"@truenine/plugin-warp-ide": ["./src/plugins/plugin-warp-ide.ts"],
"@truenine/plugin-warp-ide": ["../sdk/src/plugins/WarpIDEOutputPlugin.ts"],
"@truenine/plugin-windsurf": ["./src/plugins/plugin-windsurf.ts"],
"@truenine/plugin-zed": ["./src/plugins/plugin-zed.ts"]
},
Expand Down
3 changes: 2 additions & 1 deletion doc/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {Metadata} from 'next'
import {Inter, JetBrains_Mono} from 'next/font/google'
import {getSiteUrl, siteConfig} from '../lib/site'
import React from 'react'
import {getSiteUrl, siteConfig} from '@/lib/site'
import 'nextra-theme-docs/style.css'
import './globals.scss'

Expand Down
2 changes: 1 addition & 1 deletion doc/app/robots.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {MetadataRoute} from 'next'
import {getSiteUrl} from '../lib/site'
import {getSiteUrl} from '@/lib/site'

export default function robots(): MetadataRoute.Robots {
const siteUrl = getSiteUrl()
Expand Down
2 changes: 1 addition & 1 deletion doc/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {MetadataRoute} from 'next'
import {readdir} from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
import {getSiteUrl} from '../lib/site'
import {getSiteUrl} from '@/lib/site'

const MDX_EXTENSION = '.mdx'
const TRAILING_SLASHES_PATTERN = /\/+$/u
Expand Down
51 changes: 13 additions & 38 deletions doc/content/cli/cleanup-protection.mdx
Original file line number Diff line number Diff line change
@@ -1,53 +1,28 @@
---
title: Cleanup Protection
description: Explains how cleanupProtection rules keep the clean command from deleting files that should not be removed.
description: Explains that `cleanupProtection` is now a legacy `.tnmsc.json` field and how cleanup safety works today.
sidebarTitle: Cleanup Protection
status: stable
---

# Cleanup Protection

`clean` is not a brainless file-deletion command, so `cleanupProtection` is not an optional afterthought.
`cleanupProtection` should now be treated as a legacy config block in `~/.aindex/.tnmsc.json`.

## Rule Structure
The `clean` command still has protection behavior, but it now comes from fixed runtime guardrails and plugin-declared cleanup boundaries rather than user-authored JSON rules in the global config file.

Each protection rule supports these fields:
## What Still Protects Cleanup Today

| Field | Required | Description |
| --- | --- | --- |
| `path` | Yes | Target path or glob |
| `protectionMode` | Yes | `direct` or `recursive` |
| `matcher` | No | `path` or `glob` |
| `reason` | No | Why this protection rule exists |
- `tnmsc clean --dry-run` lets you inspect the exact cleanup plan first.
- Workspace-level reserved roots still stay protected.
- Output plugins still declare their own cleanup and protection boundaries internally.

## Semantics
## What You Should Do Instead

- `direct`: protect only this exact target.
- `recursive`: protect the entire subtree under this path.
- Keep hand-written files out of tnmsc-managed output directories.
- Use `dry-run` before a real clean.
- If a target needs different cleanup behavior, change the project/plugin assembly rather than adding a `cleanupProtection` block back into `~/.aindex/.tnmsc.json`.

## When You Must Configure It
## What To Remove From Old Configs

If your output directories are mixed with:

- hand-written files
- files generated by other tools that are not owned by tnmsc
- temporary manual patches

then add protection rules before enabling `clean`, or sooner or later you will delete things that should have stayed.

## Recommended Shape

```json
{
"cleanupProtection": {
"rules": [
{
"path": ".cursor/local-notes",
"protectionMode": "recursive",
"matcher": "path",
"reason": "Maintained manually and not owned by tnmsc outputs"
}
]
}
}
```
If an older config still contains `cleanupProtection`, remove it. It is no longer part of the supported user-facing config surface.
2 changes: 2 additions & 0 deletions doc/content/cli/cli-commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ The commands currently exposed by `tnmsc --help` are:

The global user config lives at `~/.aindex/.tnmsc.json`. Edit that file directly. The authoritative field list and fixed aindex layout now live in [aindex and `.tnmsc.json`](/docs/quick-guide/aindex-and-config).

That includes the newer `codeStyles` block for lightweight user code-style preferences such as `indent` and `tabSize`.

### `logLevel` Is Also Strictly Enumerated

It can only be:
Expand Down
10 changes: 5 additions & 5 deletions doc/content/cli/dry-run-and-clean.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: dry-run and clean
description: Explains how tnmsc previews outputs, then performs cleanup, with cleanupProtection used to control risk.
description: Explains how tnmsc previews outputs, then performs cleanup, with dry-run and built-in guardrails used to control risk.
sidebarTitle: dry-run and clean
status: stable
---
Expand All @@ -13,12 +13,12 @@ status: stable

- When integrating a project for the first time
- Right after changing `plugin.config.ts`
- Right after changing [Output Scopes](/docs/cli/output-scopes)
- Right after changing source prompt scope or global config
- Before validating the impact of a large source-content change

## What `clean` Does

`tnmsc clean` removes generated output files. It is not a blind directory deletion command. It follows the current output model and cleanup-protection rules.
`tnmsc clean` removes generated output files. It is not a blind directory deletion command. It follows the current output model and the cleanup declarations built into the runtime.

After normal cleanup finishes, `tnmsc clean` also scans the current project source tree and removes remaining empty directories. That empty-dir sweep explicitly skips Git internals as well as dependency, build-output, and cache directory trees.

Expand All @@ -32,10 +32,10 @@ tnmsc clean --dry-run

## Risk Boundary

If your output directories also contain hand-written files or outputs from other tools, read [Cleanup Protection](/docs/cli/cleanup-protection) first. Without protection rules, the risk of `clean` rises sharply.
If your output directories also contain hand-written files or outputs from other tools, do not rely on a `cleanupProtection` block in `~/.aindex/.tnmsc.json`. Keep those files out of tnmsc-managed output paths, or adjust the project/plugin assembly before running a real clean.

## Recommended Habits

1. Run `dry-run` first when you change config or switch projects.
1. Run `dry-run` first when you change config, source scope, or plugin assembly.
2. Run `clean --dry-run` first when you really intend to clean.
3. If something looks wrong, continue with [Troubleshooting](/docs/cli/troubleshooting).
5 changes: 4 additions & 1 deletion doc/content/cli/frontmatter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Optional fields include:

## 2. The Sync System's `frontMatter` Config

The only public config field in the schema and `config.ts` right now is:
The `frontMatter` block currently exposes only one field:

```json
{
Expand All @@ -36,6 +36,8 @@ The only public config field in the schema and `config.ts` right now is:

Its job is not to describe the page itself. It controls whether a blank line is preserved after front matter during output.

If you are looking for lightweight personal indentation preferences such as `tabSize` or `indent`, that is now the separate `codeStyles` block in `~/.aindex/.tnmsc.json`, not `frontMatter`.

## 3. Source-Content Frontmatter

Different input types also store description, trigger conditions, tool constraints, and similar fields in their own source-file frontmatter. Multiple output plugins consume those fields to map target metadata.
Expand All @@ -52,6 +54,7 @@ See [Technical Details](/docs/technical-details) for the boundary between those

- Docs frontmatter is for the docs site
- The `frontMatter` config is for output behavior
- `codeStyles` is the separate config block for lightweight code-style preferences such as indentation
- Source-content frontmatter is for the sync system and output plugins, but `skills` and `subagents` now take their names from the path rather than `name`

These are three different concerns.
Loading
Loading