From 4ea386cb2005d611507f23ed1baf120d0fb9ad75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Sat, 4 Apr 2026 01:24:15 +0800 Subject: [PATCH 1/2] Simplify config handling around fixed aindex paths --- cli/src/cli.rs | 73 ------ cli/src/commands/Command.ts | 14 +- cli/src/commands/ConfigCommand.ts | 150 ------------ cli/src/commands/ConfigShowCommand.ts | 21 -- cli/src/commands/HelpCommand.ts | 19 +- cli/src/commands/config_cmd.rs | 33 --- cli/src/commands/config_show.rs | 15 -- .../factories/ConfigCommandFactory.ts | 22 -- cli/src/commands/help.rs | 5 +- cli/src/commands/mod.rs | 2 - cli/src/main.rs | 4 +- cli/src/pipeline/CliArgumentParser.ts | 40 ---- doc/content/cli/cli-commands.mdx | 7 +- doc/content/cli/workspace-setup.mdx | 4 +- doc/content/quick-guide/aindex-and-config.mdx | 99 +++----- doc/content/quick-guide/index.mdx | 2 +- gui/src-tauri/src/commands.rs | 97 +++----- gui/src/i18n/en-US.json | 2 - gui/src/i18n/zh-CN.json | 2 - gui/src/pages/ConfigPage.tsx | 18 -- .../utils/configValidation.property.test.ts | 18 +- gui/src/utils/configValidation.test.ts | 80 ++----- gui/src/utils/configValidation.ts | 58 +---- sdk/src/ConfigLoader.test.ts | 48 +++- sdk/src/ConfigLoader.ts | 17 +- sdk/src/config.test.ts | 13 - sdk/src/config.ts | 1 - sdk/src/core/config/mod.rs | 222 +++++++++++------- sdk/src/lib.rs | 50 +--- .../plugin-core/AindexConfigDefaults.ts | 11 - .../plugins/plugin-core/ConfigTypes.schema.ts | 5 +- 31 files changed, 299 insertions(+), 853 deletions(-) delete mode 100644 cli/src/commands/ConfigCommand.ts delete mode 100644 cli/src/commands/ConfigShowCommand.ts delete mode 100644 cli/src/commands/config_cmd.rs delete mode 100644 cli/src/commands/config_show.rs delete mode 100644 cli/src/commands/factories/ConfigCommandFactory.ts diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 077aec39..08c8092e 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -52,9 +52,6 @@ pub enum CliCommand { /// Remove all generated output files and directories Clean(CleanArgs), - /// Set or show configuration values - Config(ConfigArgs), - /// List all registered plugins Plugins, } @@ -66,20 +63,6 @@ pub struct CleanArgs { pub dry_run: bool, } -#[derive(Args, Debug)] -pub struct ConfigArgs { - /// Show merged configuration as JSON - #[arg(long = "show")] - pub show: bool, - - /// Configuration key=value pairs to set - #[arg(long = "set", value_name = "KEY=VALUE")] - pub set: Vec, - - /// Positional key=value pairs - pub positional: Vec, -} - /// Resolved log level from CLI flags. /// When multiple flags are provided, the most verbose wins. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -161,34 +144,9 @@ pub enum ResolvedCommand { DryRun, Clean, DryRunClean, - Config(Vec<(String, String)>), - ConfigShow, Plugins, } -/// Parse --set and positional key=value pairs into (key, value) tuples. -fn parse_key_value_pairs(args: &ConfigArgs) -> Vec<(String, String)> { - let mut pairs = Vec::new(); - - for s in &args.set { - if let Some(eq_idx) = s.find('=') - && eq_idx > 0 - { - pairs.push((s[..eq_idx].to_string(), s[eq_idx + 1..].to_string())); - } - } - - for s in &args.positional { - if let Some(eq_idx) = s.find('=') - && eq_idx > 0 - { - pairs.push((s[..eq_idx].to_string(), s[eq_idx + 1..].to_string())); - } - } - - pairs -} - /// Resolve the command to execute from parsed CLI args. pub fn resolve_command(cli: &Cli) -> ResolvedCommand { match &cli.command { @@ -203,18 +161,6 @@ pub fn resolve_command(cli: &Cli) -> ResolvedCommand { ResolvedCommand::Clean } } - Some(CliCommand::Config(args)) => { - if args.show { - ResolvedCommand::ConfigShow - } else { - let pairs = parse_key_value_pairs(args); - if pairs.is_empty() { - ResolvedCommand::Execute - } else { - ResolvedCommand::Config(pairs) - } - } - } Some(CliCommand::Plugins) => ResolvedCommand::Plugins, } } @@ -235,23 +181,4 @@ mod tests { let cli = Cli::parse_from(["tnmsc", "clean", "--dry-run"]); assert_eq!(resolve_command(&cli), ResolvedCommand::DryRunClean); } - - #[test] - fn config_key_value_parsing_combines_flag_and_positional_pairs() { - let cli = Cli::parse_from([ - "tnmsc", - "config", - "--set", - "workspaceDir=/tmp/workspace", - "logLevel=debug", - ]); - - assert_eq!( - resolve_command(&cli), - ResolvedCommand::Config(vec![ - ("workspaceDir".to_string(), "/tmp/workspace".to_string()), - ("logLevel".to_string(), "debug".to_string()), - ]) - ); - } } diff --git a/cli/src/commands/Command.ts b/cli/src/commands/Command.ts index 13793886..866c5d62 100644 --- a/cli/src/commands/Command.ts +++ b/cli/src/commands/Command.ts @@ -5,8 +5,7 @@ import type { OutputCollectedContext, OutputPlugin, OutputWriteContext, - PluginOptions, - UserConfigFile + PluginOptions } from '@truenine/memory-sync-sdk' export interface CommandContext { @@ -35,17 +34,6 @@ export interface PluginExecutionResult { readonly duration?: number } -export interface JsonConfigInfo { - readonly merged: UserConfigFile - readonly sources: readonly ConfigSource[] -} - -export interface ConfigSource { - readonly path: string - readonly layer: 'programmatic' | 'global' | 'default' - readonly config: Partial -} - export interface JsonPluginInfo { readonly name: string readonly kind: 'Input' | 'Output' diff --git a/cli/src/commands/ConfigCommand.ts b/cli/src/commands/ConfigCommand.ts deleted file mode 100644 index 90defede..00000000 --- a/cli/src/commands/ConfigCommand.ts +++ /dev/null @@ -1,150 +0,0 @@ -import type {AindexConfigKeyPath} from '@truenine/memory-sync-sdk' -import type {Command, CommandContext, CommandResult} from './Command' -import * as fs from 'node:fs' -import * as path from 'node:path' -import {AINDEX_CONFIG_KEY_PATHS, buildUsageDiagnostic, diagnosticLines, getRequiredGlobalConfigPath} from '@truenine/memory-sync-sdk' - -type ValidConfigKey = 'workspaceDir' | 'logLevel' | AindexConfigKeyPath -const VALID_CONFIG_KEYS: readonly ValidConfigKey[] = ['workspaceDir', ...AINDEX_CONFIG_KEY_PATHS, 'logLevel'] - -function isValidConfigKey(key: string): key is ValidConfigKey { - return VALID_CONFIG_KEYS.includes(key as ValidConfigKey) -} - -function isValidLogLevel(value: string): boolean { - return ['trace', 'debug', 'info', 'warn', 'error'].includes(value) -} - -type ConfigValue = string | ConfigObject -interface ConfigObject { - [key: string]: ConfigValue | undefined -} - -function readGlobalConfig(): ConfigObject { - const configPath = getRequiredGlobalConfigPath() - if (!fs.existsSync(configPath)) return {} - try { - return JSON.parse(fs.readFileSync(configPath, 'utf8')) as ConfigObject - } catch { - return {} - } -} - -function writeGlobalConfig(config: ConfigObject): void { - const configPath = getRequiredGlobalConfigPath() - const configDir = path.dirname(configPath) - if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, {recursive: true}) - fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8') -} - -function setNestedValue(obj: ConfigObject, key: string, value: string): void { - const parts = key.split('.') - let current: ConfigObject = obj - for (let i = 0; i < parts.length - 1; i++) { - const part = parts[i] - if (part == null) continue - const next = current[part] - if (typeof next !== 'object' || next === null || Array.isArray(next)) current[part] = {} - current = current[part] as ConfigObject - } - const lastPart = parts.at(-1) - if (lastPart != null) current[lastPart] = value -} - -function getNestedValue(obj: ConfigObject, key: string): ConfigValue | undefined { - const parts = key.split('.') - let current: ConfigValue | undefined = obj - for (const part of parts) { - if (typeof current !== 'object' || current === null || Array.isArray(current)) return void 0 - current = current[part] - } - return current -} - -export class ConfigCommand implements Command { - readonly name = 'config' - - constructor(private readonly options: readonly [key: string, value: string][]) {} - - async execute(ctx: CommandContext): Promise { - const {logger} = ctx - - if (this.options.length === 0) { - logger.error( - buildUsageDiagnostic({ - code: 'CONFIG_COMMAND_ARGUMENTS_MISSING', - title: 'Config command requires at least one key=value pair', - rootCause: diagnosticLines('tnmsc config was invoked without any configuration assignments.'), - exactFix: diagnosticLines('Run `tnmsc config key=value` with at least one supported configuration key.'), - possibleFixes: [diagnosticLines(`Use one of the supported keys: ${VALID_CONFIG_KEYS.join(', ')}`)], - details: {validKeys: [...VALID_CONFIG_KEYS]} - }) - ) - logger.info('Usage: tnmsc config key=value') - logger.info(`Valid keys: ${VALID_CONFIG_KEYS.join(', ')}`) - return {success: false, filesAffected: 0, dirsAffected: 0, message: 'No options provided'} - } - - let config: ConfigObject - try { - config = readGlobalConfig() - } catch (error) { - return {success: false, filesAffected: 0, dirsAffected: 0, message: error instanceof Error ? error.message : String(error)} - } - - const errors: string[] = [] - const updated: string[] = [] - for (const [key, value] of this.options) { - if (!isValidConfigKey(key)) { - errors.push(`Invalid key: ${key}`) - logger.error( - buildUsageDiagnostic({ - code: 'CONFIG_COMMAND_KEY_INVALID', - title: `Unsupported config key: ${key}`, - rootCause: diagnosticLines(`The config command received "${key}", which is not a supported configuration key.`), - exactFix: diagnosticLines('Use one of the supported config keys and rerun the command.'), - possibleFixes: [diagnosticLines(`Supported keys: ${VALID_CONFIG_KEYS.join(', ')}`)], - details: {key, validKeys: [...VALID_CONFIG_KEYS]} - }) - ) - continue - } - - if (key === 'logLevel' && !isValidLogLevel(value)) { - errors.push(`Invalid logLevel value: ${value}`) - logger.error( - buildUsageDiagnostic({ - code: 'CONFIG_COMMAND_LOG_LEVEL_INVALID', - title: `Unsupported logLevel value: ${value}`, - rootCause: diagnosticLines(`The config command received "${value}" for logLevel, but tnmsc does not support that level.`), - exactFix: diagnosticLines('Set logLevel to one of: trace, debug, info, warn, or error.'), - details: {key, value, validLevels: ['trace', 'debug', 'info', 'warn', 'error']} - }) - ) - continue - } - - const oldValue = getNestedValue(config, key) - setNestedValue(config, key, value) - if (oldValue !== value) updated.push(`${key}=${value}`) - logger.info('configuration updated', {key, value}) - } - - if (updated.length > 0) { - try { - writeGlobalConfig(config) - } catch (error) { - return {success: false, filesAffected: 0, dirsAffected: 0, message: error instanceof Error ? error.message : String(error)} - } - logger.info('global config written', {path: getRequiredGlobalConfigPath()}) - } - - const success = errors.length === 0 - return { - success, - filesAffected: updated.length > 0 ? 1 : 0, - dirsAffected: 0, - message: success ? `Configuration updated: ${updated.join(', ')}` : `Partial update: ${updated.join(', ')}. Errors: ${errors.join(', ')}` - } - } -} diff --git a/cli/src/commands/ConfigShowCommand.ts b/cli/src/commands/ConfigShowCommand.ts deleted file mode 100644 index f07072d6..00000000 --- a/cli/src/commands/ConfigShowCommand.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type {Command, CommandContext, CommandResult, ConfigSource, JsonConfigInfo} from './Command' -import process from 'node:process' -import {ConfigLoader} from '@truenine/memory-sync-sdk' - -export class ConfigShowCommand implements Command { - readonly name = 'config-show' - - async execute(ctx: CommandContext): Promise { - const {logger} = ctx - const loader = new ConfigLoader() - const mergedResult = loader.load() - const sources: ConfigSource[] = mergedResult.sources.map(sourcePath => { - const loaded = loader.loadFromFile(sourcePath) - return {path: sourcePath, layer: 'global', config: loaded.config} - }) - const configInfo: JsonConfigInfo = {merged: mergedResult.config, sources} - process.stdout.write(`${JSON.stringify(configInfo)}\n`) - logger.info('config shown', {sources: mergedResult.sources.length}) - return {success: true, filesAffected: 0, dirsAffected: 0, message: `Configuration displayed (${sources.length} source(s))`} - } -} diff --git a/cli/src/commands/HelpCommand.ts b/cli/src/commands/HelpCommand.ts index 5bbf3bdc..3e36de1a 100644 --- a/cli/src/commands/HelpCommand.ts +++ b/cli/src/commands/HelpCommand.ts @@ -1,9 +1,7 @@ import type {Command, CommandContext, CommandResult} from './Command' -import {AINDEX_CONFIG_KEY_PATHS} from '@truenine/memory-sync-sdk' import {getCliVersion} from './VersionCommand' const CLI_NAME = 'tnmsc' -const CONFIG_KEY_LIST_TEXT = ['workspaceDir', 'logLevel', ...AINDEX_CONFIG_KEY_PATHS].join(',\n ') const HELP_TEXT = ` ${CLI_NAME} v${getCliVersion()} - Memory Sync CLI @@ -17,20 +15,17 @@ USAGE: ${CLI_NAME} dry-run Preview what would be written ${CLI_NAME} clean Remove all generated files ${CLI_NAME} clean --dry-run Preview what would be cleaned - ${CLI_NAME} config key=value Set configuration value SUBCOMMANDS: help Show this help message version Show version information dry-run Preview changes without writing files clean Remove all generated output files and directories - config Set configuration values in global config file (~/.aindex/.tnmsc.json) ALIASES: ${CLI_NAME} --help, ${CLI_NAME} -h Same as '${CLI_NAME} help' ${CLI_NAME} --version, ${CLI_NAME} -v Same as '${CLI_NAME} version' ${CLI_NAME} clean -n Same as '${CLI_NAME} clean --dry-run' - ${CLI_NAME} config key=value Set config value in global config file LOG LEVEL OPTIONS: --trace Most verbose output @@ -42,18 +37,10 @@ LOG LEVEL OPTIONS: CLEAN OPTIONS: -n, --dry-run Preview cleanup without removing files -CONFIG OPTIONS: - key=value Set a configuration value in global config (~/.aindex/.tnmsc.json) - Valid keys: ${CONFIG_KEY_LIST_TEXT} - - Examples: - ${CLI_NAME} config workspaceDir=~/my-project - ${CLI_NAME} config aindex.skills.src=skills - ${CLI_NAME} config logLevel=debug - CONFIGURATION: - Configure via plugin.config.ts in your project root. - See documentation for detailed configuration options. + Global user config lives at ~/.aindex/.tnmsc.json. + Edit that file directly, then use plugin.config.ts in your project root + for project-side plugin assembly and runtime overrides. `.trim() export class HelpCommand implements Command { diff --git a/cli/src/commands/config_cmd.rs b/cli/src/commands/config_cmd.rs deleted file mode 100644 index 5ac94898..00000000 --- a/cli/src/commands/config_cmd.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::path::Path; -use std::process::ExitCode; - -use tnmsc_logger::create_logger; - -pub fn execute(pairs: &[(String, String)]) -> ExitCode { - let logger = create_logger("config", None); - - for (key, _) in pairs { - if key != "workspaceDir" && key != "logLevel" { - logger.info( - format!( - "Unknown config key was ignored: {key}. Supported keys: workspaceDir, logLevel" - ), - None, - ); - } - } - - match tnmsc::update_global_config_from_pairs(Path::new("."), pairs) { - Ok(config_path) => { - logger.info( - serde_json::Value::String(format!("Config saved to {}", config_path.display())), - None, - ); - ExitCode::SUCCESS - } - Err(error) => { - eprintln!("{error}"); - ExitCode::FAILURE - } - } -} diff --git a/cli/src/commands/config_show.rs b/cli/src/commands/config_show.rs deleted file mode 100644 index 4525933e..00000000 --- a/cli/src/commands/config_show.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::path::Path; -use std::process::ExitCode; - -pub fn execute() -> ExitCode { - match tnmsc::config_show(Path::new(".")) { - Ok(json) => { - println!("{json}"); - ExitCode::SUCCESS - } - Err(error) => { - eprintln!("{error}"); - ExitCode::FAILURE - } - } -} diff --git a/cli/src/commands/factories/ConfigCommandFactory.ts b/cli/src/commands/factories/ConfigCommandFactory.ts deleted file mode 100644 index 005ea10f..00000000 --- a/cli/src/commands/factories/ConfigCommandFactory.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type {Command} from '../Command' -import type {CommandFactory} from '../CommandFactory' -import type {ParsedCliArgs} from '@/pipeline/CliArgumentParser' -import {ConfigCommand} from '../ConfigCommand' -import {ConfigShowCommand} from '../ConfigShowCommand' - -export class ConfigCommandFactory implements CommandFactory { - canHandle(args: ParsedCliArgs): boolean { - return args.subcommand === 'config' - } - - createCommand(args: ParsedCliArgs): Command { - if (args.showFlag) return new ConfigShowCommand() - - const parsedPositional: [key: string, value: string][] = [] - for (const arg of args.positional) { - const eqIndex = arg.indexOf('=') - if (eqIndex > 0) parsedPositional.push([arg.slice(0, eqIndex), arg.slice(eqIndex + 1)]) - } - return new ConfigCommand([...args.setOption, ...parsedPositional]) - } -} diff --git a/cli/src/commands/help.rs b/cli/src/commands/help.rs index d731677f..e22f4143 100644 --- a/cli/src/commands/help.rs +++ b/cli/src/commands/help.rs @@ -10,7 +10,6 @@ pub fn execute() -> ExitCode { println!(" (default) Sync AI memory and configuration files"); println!(" dry-run Preview changes without writing files"); println!(" clean Remove all generated output files"); - println!(" config Set or show configuration values"); println!(" plugins List all registered plugins"); println!(" version Show version information"); println!(" help Show this help message"); @@ -21,5 +20,9 @@ pub fn execute() -> ExitCode { println!(" --info Set log level to info"); println!(" --warn Set log level to warn"); println!(" --error Set log level to error"); + println!(); + println!("CONFIGURATION:"); + println!(" Global user config: ~/.aindex/.tnmsc.json"); + println!(" Project runtime assembly: plugin.config.ts"); ExitCode::SUCCESS } diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index cad337be..c0e7037a 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -1,5 +1,3 @@ pub mod bridge; -pub mod config_cmd; -pub mod config_show; pub mod help; pub mod version; diff --git a/cli/src/main.rs b/cli/src/main.rs index d7526231..5e1199de 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,6 +1,6 @@ //! tnmsc — Rust CLI shell entry point. //! -//! Pure Rust commands: help, version, config, config-show +//! Pure Rust commands: help, version //! Bridge commands (Node.js): execute, dry-run, clean, plugins mod cli; @@ -25,8 +25,6 @@ fn main() -> ExitCode { let exit_code = match command { ResolvedCommand::Help => commands::help::execute(), ResolvedCommand::Version => commands::version::execute(), - ResolvedCommand::Config(pairs) => commands::config_cmd::execute(&pairs), - ResolvedCommand::ConfigShow => commands::config_show::execute(), ResolvedCommand::Execute => commands::bridge::execute(), ResolvedCommand::DryRun => commands::bridge::dry_run(), ResolvedCommand::Clean => commands::bridge::clean(), diff --git a/cli/src/pipeline/CliArgumentParser.ts b/cli/src/pipeline/CliArgumentParser.ts index c4bff28b..61108a6d 100644 --- a/cli/src/pipeline/CliArgumentParser.ts +++ b/cli/src/pipeline/CliArgumentParser.ts @@ -2,7 +2,6 @@ import type {Command} from '@/commands/Command' import {FactoryPriority} from '@/commands/CommandFactory' import {CommandRegistry} from '@/commands/CommandRegistry' import {CleanCommandFactory} from '@/commands/factories/CleanCommandFactory' -import {ConfigCommandFactory} from '@/commands/factories/ConfigCommandFactory' import {DryRunCommandFactory} from '@/commands/factories/DryRunCommandFactory' import {ExecuteCommandFactory} from '@/commands/factories/ExecuteCommandFactory' import {HelpCommandFactory} from '@/commands/factories/HelpCommandFactory' @@ -15,7 +14,6 @@ export type Subcommand | 'version' | 'dry-run' | 'clean' - | 'config' | 'plugins' export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' @@ -24,9 +22,7 @@ export interface ParsedCliArgs { readonly helpFlag: boolean readonly versionFlag: boolean readonly dryRun: boolean - readonly showFlag: boolean readonly logLevel: LogLevel | undefined - readonly setOption: readonly [key: string, value: string][] readonly unknownCommand: string | undefined readonly positional: readonly string[] readonly unknown: readonly string[] @@ -37,7 +33,6 @@ const VALID_SUBCOMMANDS: ReadonlySet = new Set([ 'version', 'dry-run', 'clean', - 'config', 'plugins' ]) const LOG_LEVEL_FLAGS: ReadonlyMap = new Map([ @@ -108,9 +103,7 @@ export function parseArgs(args: readonly string[]): ParsedCliArgs { helpFlag: boolean versionFlag: boolean dryRun: boolean - showFlag: boolean logLevel: LogLevel | undefined - setOption: [key: string, value: string][] unknownCommand: string | undefined positional: string[] unknown: string[] @@ -119,9 +112,7 @@ export function parseArgs(args: readonly string[]): ParsedCliArgs { helpFlag: false, versionFlag: false, dryRun: false, - showFlag: false, logLevel: void 0, - setOption: [], unknownCommand: void 0, positional: [], unknown: [] @@ -157,33 +148,6 @@ export function parseArgs(args: readonly string[]): ParsedCliArgs { case '--dry-run': result.dryRun = true break - case '--show': - result.showFlag = true - break - case '--set': { - if (parts.length > 1) { - const keyValue = parts.slice(1).join('=') - const eqIndex = keyValue.indexOf('=') - if (eqIndex > 0) - { result.setOption.push([ - keyValue.slice(0, eqIndex), - keyValue.slice(eqIndex + 1) - ]) } - } else { - const nextArg = args[i + 1] - if (nextArg != null) { - const eqIndex = nextArg.indexOf('=') - if (eqIndex > 0) { - result.setOption.push([ - nextArg.slice(0, eqIndex), - nextArg.slice(eqIndex + 1) - ]) - i++ - } - } - } - break - } default: result.unknown.push(arg) } @@ -241,10 +205,6 @@ function createDefaultCommandRegistry(): CommandRegistry { new PluginsCommandFactory(), FactoryPriority.Subcommand ) - registry.registerWithPriority( - new ConfigCommandFactory(), - FactoryPriority.Subcommand - ) registry.registerWithPriority( new ExecuteCommandFactory(), FactoryPriority.Subcommand diff --git a/doc/content/cli/cli-commands.mdx b/doc/content/cli/cli-commands.mdx index 40678d61..3b731b1e 100644 --- a/doc/content/cli/cli-commands.mdx +++ b/doc/content/cli/cli-commands.mdx @@ -17,13 +17,12 @@ The commands currently exposed by `tnmsc --help` are: | `tnmsc dry-run` | Preview the files that would be written | | `tnmsc clean` | Delete generated outputs and continue cleaning empty directories from the project source tree | | `tnmsc clean --dry-run` | Preview what would be cleaned, including empty directories that would be swept afterward | -| `tnmsc config key=value` | Change global config | ## Key Takeaways -### `config` Can Only Change a Whitelisted Set of Keys +### Global Config Is File-Based -The full `.aindex` and `.tnmsc.json` key list now lives in [aindex and `.tnmsc.json`](/docs/quick-guide/aindex-and-config), together with the config location and default path rules. +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). ### `logLevel` Is Also Strictly Enumerated @@ -38,4 +37,4 @@ trace / debug / info / warn / error 1. Run `tnmsc help` first in a new environment. 2. Run `tnmsc dry-run` before writing outputs. 3. Run `tnmsc clean --dry-run` before cleanup. -4. If you really need to change global config, use `tnmsc config` and verify the supported keys in [aindex and `.tnmsc.json`](/docs/quick-guide/aindex-and-config). +4. If you need to change global config, edit `~/.aindex/.tnmsc.json` and keep only supported user-facing fields. diff --git a/doc/content/cli/workspace-setup.mdx b/doc/content/cli/workspace-setup.mdx index ebd936a9..9fce915b 100644 --- a/doc/content/cli/workspace-setup.mdx +++ b/doc/content/cli/workspace-setup.mdx @@ -13,8 +13,8 @@ Use that page if you need: - where `.aindex` actually lives - where `~/.aindex/.tnmsc.json` is loaded from -- what each `aindex.*` field means -- which keys `tnmsc config` can change +- which user config fields are still supported +- which aindex paths are fixed by convention - the current defaults and path rules ## What Still Belongs Here diff --git a/doc/content/quick-guide/aindex-and-config.mdx b/doc/content/quick-guide/aindex-and-config.mdx index 66028ae9..22dacfee 100644 --- a/doc/content/quick-guide/aindex-and-config.mdx +++ b/doc/content/quick-guide/aindex-and-config.mdx @@ -24,12 +24,6 @@ They are related, but they are not the same thing. The aindex content tree lives under: -```text -/ -``` - -With default settings, that becomes: - ```text /aindex ``` @@ -75,11 +69,7 @@ The separation is intentional: ```json { - "workspaceDir": "/repo/demo", - "aindex": { - "dir": "aindex" - }, - "logLevel": "info" + "workspaceDir": "/repo/demo" } ``` @@ -89,6 +79,7 @@ With that config: - the aindex root is `/repo/demo/aindex` - `skills` resolves to `/repo/demo/aindex/skills` - the default global prompt source resolves to `/repo/demo/aindex/global.src.mdx` +- `logLevel` falls back to `info` unless you set it explicitly ## Root fields in `~/.aindex/.tnmsc.json` @@ -96,7 +87,6 @@ With that config: | --- | --- | --- | --- | | `version` | `string` | `"0.0.0"` in merged runtime options | Version or release marker passed through the runtime | | `workspaceDir` | `string` | `"~/project"` in merged runtime options | Project root directory | -| `aindex` | `object` | See the table below | Source and output path mapping | | `logLevel` | enum | `info` | `trace` / `debug` / `info` / `warn` / `error` | | `commandSeriesOptions` | `object` | `{}` | Command-series naming and per-plugin overrides | | `outputScopes` | `object` | `{}` | Output-scope overrides per plugin | @@ -105,70 +95,37 @@ With that config: | `windows` | `object` | `{}` | Windows and WSL-specific runtime options | | `profile` | `object` | omitted | User profile metadata | -## `aindex` fields - -All `aindex.*.src` and `aindex.*.dist` paths are resolved relative to: +Everything under the workspace aindex tree keeps a fixed name and fixed relative location. The structure is: ```text -/ +/ + aindex/ + skills/ + commands/ + subagents/ + rules/ + app/ + ext/ + arch/ + softwares/ + global.src.mdx + workspace.src.mdx + dist/ + skills/ + commands/ + subagents/ + rules/ + app/ + ext/ + arch/ + softwares/ + global.mdx + workspace.mdx ``` -| Field | Type | Default | Meaning | -| --- | --- | --- | --- | -| `aindex.dir` | `string` | `aindex` | Name of the aindex root inside `workspaceDir` | -| `aindex.skills.src` | `string` | `skills` | Skill source tree | -| `aindex.skills.dist` | `string` | `dist/skills` | Compiled skill output tree | -| `aindex.commands.src` | `string` | `commands` | Command source tree | -| `aindex.commands.dist` | `string` | `dist/commands` | Compiled command output tree | -| `aindex.subAgents.src` | `string` | `subagents` | Sub-agent source tree | -| `aindex.subAgents.dist` | `string` | `dist/subagents` | Compiled sub-agent output tree | -| `aindex.rules.src` | `string` | `rules` | Rule source tree | -| `aindex.rules.dist` | `string` | `dist/rules` | Compiled rule output tree | -| `aindex.globalPrompt.src` | `string` | `global.src.mdx` | Source file for the root global prompt | -| `aindex.globalPrompt.dist` | `string` | `dist/global.mdx` | Generated global prompt file | -| `aindex.workspacePrompt.src` | `string` | `workspace.src.mdx` | Source file for the root workspace prompt | -| `aindex.workspacePrompt.dist` | `string` | `dist/workspace.mdx` | Generated workspace prompt file | -| `aindex.app.src` | `string` | `app` | Project-memory source tree for app projects | -| `aindex.app.dist` | `string` | `dist/app` | Generated app project-memory tree | -| `aindex.ext.src` | `string` | `ext` | Project-memory source tree for extension projects | -| `aindex.ext.dist` | `string` | `dist/ext` | Generated extension project-memory tree | -| `aindex.arch.src` | `string` | `arch` | Project-memory source tree for architecture projects | -| `aindex.arch.dist` | `string` | `dist/arch` | Generated architecture project-memory tree | -| `aindex.softwares.src` | `string` | `softwares` | Software-series source tree | -| `aindex.softwares.dist` | `string` | `dist/softwares` | Generated software-series tree | - -## `tnmsc config` can change only these config keys - -The current `ConfigCommand` allows: - -- `workspaceDir` -- `aindex.skills.src` -- `aindex.skills.dist` -- `aindex.commands.src` -- `aindex.commands.dist` -- `aindex.subAgents.src` -- `aindex.subAgents.dist` -- `aindex.rules.src` -- `aindex.rules.dist` -- `aindex.globalPrompt.src` -- `aindex.globalPrompt.dist` -- `aindex.workspacePrompt.src` -- `aindex.workspacePrompt.dist` -- `aindex.app.src` -- `aindex.app.dist` -- `aindex.ext.src` -- `aindex.ext.dist` -- `aindex.arch.src` -- `aindex.arch.dist` -- `aindex.softwares.src` -- `aindex.softwares.dist` -- `logLevel` - -`logLevel` can only be: +None of the fixed paths inside that tree are user-configurable in `~/.aindex/.tnmsc.json`. -```text -trace / debug / info / warn / error -``` +If old config files still contain `aindex`, `dir`, or `*.src` / `*.dist` override fields, treat them as removed legacy fields. The current system no longer documents or supports those user-facing overrides. ## Other supported config objects diff --git a/doc/content/quick-guide/index.mdx b/doc/content/quick-guide/index.mdx index 8fc78e0e..ef543d8a 100644 --- a/doc/content/quick-guide/index.mdx +++ b/doc/content/quick-guide/index.mdx @@ -44,7 +44,7 @@ If you want to install the command first and decide between CLI, GUI, and MCP la ## Config Setup in One Page -If you need the real setup and config facts for `.aindex` and `~/.aindex/.tnmsc.json`, use [aindex and `.tnmsc.json`](/docs/quick-guide/aindex-and-config). That page now collects the setup path, config location, field meanings, defaults, and `tnmsc config` key whitelist in one place. +If you need the real setup and config facts for `.aindex` and `~/.aindex/.tnmsc.json`, use [aindex and `.tnmsc.json`](/docs/quick-guide/aindex-and-config). That page now collects the setup path, config location, supported user fields, and fixed aindex layout in one place. ## Shortest Starting Paths diff --git a/gui/src-tauri/src/commands.rs b/gui/src-tauri/src/commands.rs index ed92e5cd..991e01ae 100644 --- a/gui/src-tauri/src/commands.rs +++ b/gui/src-tauri/src/commands.rs @@ -9,28 +9,10 @@ use std::process::Command as StdCommand; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; +use tnmsc::core::config as core_config; const PRIMARY_SOURCE_MDX_EXTENSION: &str = ".src.mdx"; const SOURCE_MDX_FILE_TYPE: &str = "sourceMdx"; -const DEFAULT_AINDEX_DIR: &str = "aindex"; -const DEFAULT_SKILLS_SRC_DIR: &str = "skills"; -const DEFAULT_SKILLS_DIST_DIR: &str = "dist/skills"; -const DEFAULT_COMMANDS_SRC_DIR: &str = "commands"; -const DEFAULT_COMMANDS_DIST_DIR: &str = "dist/commands"; -const DEFAULT_SUB_AGENTS_SRC_DIR: &str = "subagents"; -const DEFAULT_SUB_AGENTS_DIST_DIR: &str = "dist/subagents"; -const DEFAULT_RULES_SRC_DIR: &str = "rules"; -const DEFAULT_RULES_DIST_DIR: &str = "dist/rules"; -const DEFAULT_APP_SRC_DIR: &str = "app"; -const DEFAULT_APP_DIST_DIR: &str = "dist/app"; -const DEFAULT_EXT_SRC_DIR: &str = "ext"; -const DEFAULT_EXT_DIST_DIR: &str = "dist/ext"; -const DEFAULT_ARCH_SRC_DIR: &str = "arch"; -const DEFAULT_ARCH_DIST_DIR: &str = "dist/arch"; -const DEFAULT_GLOBAL_PROMPT_SRC: &str = "global.src.mdx"; -const DEFAULT_GLOBAL_PROMPT_DIST: &str = "dist/global.mdx"; -const DEFAULT_WORKSPACE_PROMPT_SRC: &str = "workspace.src.mdx"; -const DEFAULT_WORKSPACE_PROMPT_DIST: &str = "dist/workspace.mdx"; const PROJECT_SERIES_CATEGORIES: [&str; 3] = ["app", "ext", "arch"]; const INTERNAL_BRIDGE_JSON_FLAG: &str = "--bridge-json"; @@ -399,7 +381,7 @@ fn resolve_category_paths( config: &tnmsc::core::config::UserConfigFile, category: &str, ) -> Result { - let aindex = config.aindex.as_ref(); + let aindex = &config.aindex; let resolve_pair = |pair: Option<&tnmsc::core::config::DirPair>, default_source: &str, @@ -419,39 +401,39 @@ fn resolve_category_paths( match category { "skills" => Ok(resolve_pair( - aindex.and_then(|value| value.skills.as_ref()), - DEFAULT_SKILLS_SRC_DIR, - DEFAULT_SKILLS_DIST_DIR, + aindex.skills.as_ref(), + core_config::DEFAULT_SKILLS_SRC_DIR, + core_config::DEFAULT_SKILLS_DIST_DIR, )), "commands" => Ok(resolve_pair( - aindex.and_then(|value| value.commands.as_ref()), - DEFAULT_COMMANDS_SRC_DIR, - DEFAULT_COMMANDS_DIST_DIR, + aindex.commands.as_ref(), + core_config::DEFAULT_COMMANDS_SRC_DIR, + core_config::DEFAULT_COMMANDS_DIST_DIR, )), "agents" => Ok(resolve_pair( - aindex.and_then(|value| value.sub_agents.as_ref()), - DEFAULT_SUB_AGENTS_SRC_DIR, - DEFAULT_SUB_AGENTS_DIST_DIR, + aindex.sub_agents.as_ref(), + core_config::DEFAULT_SUB_AGENTS_SRC_DIR, + core_config::DEFAULT_SUB_AGENTS_DIST_DIR, )), "rules" => Ok(resolve_pair( - aindex.and_then(|value| value.rules.as_ref()), - DEFAULT_RULES_SRC_DIR, - DEFAULT_RULES_DIST_DIR, + aindex.rules.as_ref(), + core_config::DEFAULT_RULES_SRC_DIR, + core_config::DEFAULT_RULES_DIST_DIR, )), "app" => Ok(resolve_pair( - aindex.and_then(|value| value.app.as_ref()), - DEFAULT_APP_SRC_DIR, - DEFAULT_APP_DIST_DIR, + aindex.app.as_ref(), + core_config::DEFAULT_APP_SRC_DIR, + core_config::DEFAULT_APP_DIST_DIR, )), "ext" => Ok(resolve_pair( - aindex.and_then(|value| value.ext.as_ref()), - DEFAULT_EXT_SRC_DIR, - DEFAULT_EXT_DIST_DIR, + aindex.ext.as_ref(), + core_config::DEFAULT_EXT_SRC_DIR, + core_config::DEFAULT_EXT_DIST_DIR, )), "arch" => Ok(resolve_pair( - aindex.and_then(|value| value.arch.as_ref()), - DEFAULT_ARCH_SRC_DIR, - DEFAULT_ARCH_DIST_DIR, + aindex.arch.as_ref(), + core_config::DEFAULT_ARCH_SRC_DIR, + core_config::DEFAULT_ARCH_DIST_DIR, )), _ => Err(format!("Unknown category: {category}")), } @@ -505,17 +487,17 @@ fn collect_root_memory_prompt_files( fn collect_root_memory_prompt_pairs( config: &tnmsc::core::config::UserConfigFile, ) -> Vec<(String, String)> { - let aindex = config.aindex.as_ref(); + let aindex = &config.aindex; [ ( - aindex.and_then(|value| value.global_prompt.as_ref()), - DEFAULT_GLOBAL_PROMPT_SRC, - DEFAULT_GLOBAL_PROMPT_DIST, + aindex.global_prompt.as_ref(), + core_config::DEFAULT_GLOBAL_PROMPT_SRC, + core_config::DEFAULT_GLOBAL_PROMPT_DIST, ), ( - aindex.and_then(|value| value.workspace_prompt.as_ref()), - DEFAULT_WORKSPACE_PROMPT_SRC, - DEFAULT_WORKSPACE_PROMPT_DIST, + aindex.workspace_prompt.as_ref(), + core_config::DEFAULT_WORKSPACE_PROMPT_SRC, + core_config::DEFAULT_WORKSPACE_PROMPT_DIST, ), ] .into_iter() @@ -570,9 +552,9 @@ fn load_resolved_config(cwd: &str) -> Result { let workspace_dir = tnmsc::core::config::resolve_tilde(workspace_dir); let aindex_dir = config .aindex - .as_ref() - .and_then(|value| value.dir.as_deref()) - .unwrap_or(DEFAULT_AINDEX_DIR); + .dir + .as_deref() + .unwrap_or(core_config::DEFAULT_AINDEX_DIR_NAME); Ok(ResolvedConfig { aindex_root: workspace_dir.join(aindex_dir), @@ -996,18 +978,7 @@ mod tests { } fn create_test_config() -> tnmsc::core::config::UserConfigFile { - serde_json::from_value(serde_json::json!({ - "aindex": { - "app": {"src": "app", "dist": "dist/app"}, - "ext": {"src": "ext", "dist": "dist/ext"}, - "arch": {"src": "arch", "dist": "dist/arch"}, - "skills": {"src": "skills", "dist": "dist/skills"}, - "commands": {"src": "commands", "dist": "dist/commands"}, - "subAgents": {"src": "subagents", "dist": "dist/subagents"}, - "rules": {"src": "rules", "dist": "dist/rules"} - } - })) - .expect("test config should deserialize") + tnmsc::core::config::UserConfigFile::default() } #[test] diff --git a/gui/src/i18n/en-US.json b/gui/src/i18n/en-US.json index 0678e6e9..e5d3c473 100644 --- a/gui/src/i18n/en-US.json +++ b/gui/src/i18n/en-US.json @@ -32,8 +32,6 @@ "config.openDir": "Open Config Dir", "config.field.workspaceDir": "Workspace Dir", "config.field.workspaceDir.desc": "Root workspace directory path", - "config.field.aindex.dir": "Aindex Dir", - "config.field.aindex.dir.desc": "Directory name of the aindex inside the workspace", "config.field.logLevel": "Log Level", "config.field.logLevel.desc": "CLI log output level", "plugins.title": "Plugins", diff --git a/gui/src/i18n/zh-CN.json b/gui/src/i18n/zh-CN.json index c72963a5..9e6bd708 100644 --- a/gui/src/i18n/zh-CN.json +++ b/gui/src/i18n/zh-CN.json @@ -32,8 +32,6 @@ "config.openDir": "打开配置目录", "config.field.workspaceDir": "工作区目录", "config.field.workspaceDir.desc": "工作区根目录路径", - "config.field.aindex.dir": "Aindex 目录名", - "config.field.aindex.dir.desc": "工作区内 aindex 目录的名称", "config.field.logLevel": "日志级别", "config.field.logLevel.desc": "CLI 日志输出级别", "plugins.title": "插件列表", diff --git a/gui/src/pages/ConfigPage.tsx b/gui/src/pages/ConfigPage.tsx index de37743f..a83a09e9 100644 --- a/gui/src/pages/ConfigPage.tsx +++ b/gui/src/pages/ConfigPage.tsx @@ -85,16 +85,6 @@ const ConfigForm: FC = ({ data, onChange, t }) => { } }, [data, onChange]) - const updateNestedField = useCallback((parent: string, field: string, value: unknown) => { - const parentObj = (typeof data[parent] === 'object' && data[parent] !== null ? data[parent] : {}) as Record - const next = { ...data, [parent]: { ...parentObj, [field]: value === '' ? undefined : value } } - onChange(next) - }, [data, onChange]) - - const aindex = (typeof data['aindex'] === 'object' && data['aindex'] !== null - ? data['aindex'] - : {}) as Record - return (
{TOP_LEVEL_STRING_FIELDS.map((field) => ( @@ -108,14 +98,6 @@ const ConfigForm: FC = ({ data, onChange, t }) => { /> ))} - updateNestedField('aindex', 'dir', v)} - placeholder="aindex" - /> -