npm package: @mnemonica/tactica
Description: TypeScript Language Service Plugin for Mnemonica - generates types for nested constructors
Author: went.out@gmail.com
Latest version: 0.1.0

# @mnemonica/tactica

**TypeScript Language Service Plugin for Mnemonica**

Tactica generates type definitions for Mnemonica's dynamic nested constructors, enabling TypeScript to understand runtime type hierarchies created through `define()` and `decorate()` calls.

## The Problem

Mnemonica enables powerful instance-level inheritance:

```typescript
const UserType = define('UserType', function (this: { name: string }) {
    this.name = '';
});

const AdminType = UserType.define('AdminType', function (this: { role: string }) {
    this.role = 'admin';
});

const user = new UserType();
const admin = new user.AdminType(); // Works at runtime!
```

But TypeScript doesn't know that `user.AdminType` exists because `UserType.define()` is a runtime operation.

## The Solution

Tactica analyzes your TypeScript source files and generates declaration files that tell TypeScript about the nested constructor hierarchy.

## Installation

```bash
npm install --save-dev @mnemonica/tactica
```

## Usage

### 1. As a TypeScript Language Service Plugin (Recommended)

Add to your `tsconfig.json`:

```json
{
  "compilerOptions": {
    "plugins": [
      {
        "name": "@mnemonica/tactica",
        "outputDir": ".tactica",
        "include": ["src/**/*.ts"],
        "exclude": ["**/*.test.ts", "**/*.spec.ts"]
      }
    ]
  }
}
```

Then include the generated types in your project:

```json
{
  "compilerOptions": {
    "typeRoots": ["./node_modules/@types", "./.tactica"]
  }
}
```

### 2. As a CLI Tool

Generate types once:

```bash
npx tactica
```

Watch mode for development:

```bash
npx tactica --watch
```

With custom options:

```bash
npx tactica --project ./src/tsconfig.json --output ./types/mnemonica
```

### 3. As a Module

```typescript
import { MnemonicaAnalyzer, TypesGenerator, TypesWriter } from '@mnemonica/tactica';
import * as ts from 'typescript';

const program = ts.createProgram(['./src/index.ts'], {});
const analyzer = new MnemonicaAnalyzer(program);

for (const sourceFile of program.getSourceFiles()) {
    if (!sourceFile.isDeclarationFile) {
        analyzer.analyzeFile(sourceFile);
    }
}

const generator = new TypesGenerator(analyzer.getGraph());

// Generate types.ts (exportable type aliases - default mode)
const generatedTypes = generator.generateTypesFile();
const writer = new TypesWriter('.tactica');
writer.writeTypesFile(generatedTypes);

// Or generate index.d.ts (global augmentation - legacy mode)
const generatedGlobal = generator.generateGlobalAugmentation();
writer.writeGlobalAugmentation(generatedGlobal);
```

## Configuration Options

### Plugin Options (tsconfig.json)

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `outputDir` | string | `.tactica` | Directory for generated types |
| `include` | string[] | `['**/*.ts']` | File patterns to include |
| `exclude` | string[] | `['**/*.d.ts']` | File patterns to exclude |
| `verbose` | boolean | `false` | Enable verbose logging |

### CLI Options

| Option | Short | Description |
|--------|-------|-------------|
| `--watch` | `-w` | Watch mode - regenerate on file changes |
| `--project` | `-p` | Path to tsconfig.json |
| `--output` | `-o` | Output directory (default: `.tactica`) |
| `--include` | `-i` | Include patterns (comma-separated) |
| `--exclude` | `-e` | Exclude patterns (comma-separated) |
| `--module-augmentation` | `-m` | Generate global augmentation (legacy mode) |
| `--verbose` | `-v` | Enable verbose logging |
| `--help` | `-h` | Show help message |

**Examples:**

```bash
# Default mode - generates .tactica/types.ts
npx tactica

# Global augmentation mode - generates .tactica/index.d.ts
npx tactica --module-augmentation

# Watch mode with custom output directory
npx tactica --watch --output ./custom-types

# Exclude test files
npx tactica --exclude "*.test.ts,*.spec.ts"
```

## Generated Output

By default, Tactica generates `.tactica/types.ts` with exported type aliases:

```typescript
// Generated by @mnemonica/tactica - DO NOT EDIT
export type UserTypeInstance = {
    name: string;
    email: string;
    AdminType: TypeConstructor<AdminTypeInstance>;
}

export type AdminTypeInstance = UserTypeInstance & {
    role: string;
    permissions: string[];
}
```

### Output Modes

**Default mode** (`npx tactica`):
- Generates `.tactica/types.ts` - Exportable type aliases
- Import types explicitly: `import type { UserTypeInstance } from './.tactica/types'`
- Include in tsconfig.json: `"include": ["src/**/*.ts", ".tactica/types.ts"]`
- **Recommended for new projects** - explicit imports, better tree-shaking

**Global mode** (`npx tactica --module-augmentation`):
- Generates `.tactica/index.d.ts` - Global type declarations
- Types are available without imports (via `declare global`)
- Add to tsconfig.json: `"typeRoots": ["./node_modules/@types", "./.tactica"]`
- Use triple-slash reference: `/// <reference types="./.tactica/index" />`

**Choosing a mode:**
- Use **Default mode** for new projects - explicit imports are clearer and work better with tree-shaking
- Use **Global mode** if you want types available without imports (legacy behavior)

## What Gets Analyzed

### 1. define() Calls

```typescript
// Root type
const UserType = define('UserType', function (this: { name: string }) {
    this.name = '';
});

// Nested type
const AdminType = UserType.define('AdminType', function (this: { role: string }) {
    this.role = 'admin';
});
```

### 2. @decorate() Decorator

```typescript
@decorate()
class User {
    name: string = '';
}

@decorate(User)
class Admin {
    role: string = 'admin';
}
```

#### Why Type Casting is Necessary for @decorate()

When using `@decorate()` on classes, TypeScript cannot automatically infer that instances have nested type constructors (like `user.Admin`). This is because:

1. **`define()` types work automatically**: When you use `define()`, the returned constructor has the correct type signature with nested constructors.

2. **`@decorate()` classes need casting**: When you decorate a class, TypeScript sees the class itself, not the augmented type that mnemonica creates at runtime.

**The Solution:** Cast to the instance type to access nested constructors:

```typescript
@decorate()
class Order {
    orderId: string = '';
    total: number = 0;
}

@decorate(Order)
class AugmentedOrder {
    addition: string = 'extra';
}

// Cast to OrderInstance to access AugmentedOrder constructor
const order = new Order() as OrderInstance;
const augmented = new order.AugmentedOrder(); // ✅ Works!

// The 'augmented' variable is automatically typed as AugmentedOrderInstance
console.log(augmented.orderId);  // From Order
console.log(augmented.addition); // From AugmentedOrder
```

Generated types (like `OrderInstance`, `AugmentedOrderInstance`) are automatically available globally - no imports needed!

#### @decorate() with Options

```typescript
@decorate({
    blockErrors: true,
    strictChain: false,
    exposeInstanceMethods: true
})
class ConfigurableClass {
    value: string = '';
}
```

### 3. Object.assign Pattern

```typescript
const UserType = define('UserType', function (this: any, data: any) {
    Object.assign(this, data);
});
```

### 4. Typeomatica Integration

Tactica works seamlessly with Typeomatica patterns:

```typescript
import { decorate } from 'mnemonica';
import { Strict, BaseClass } from 'typeomatica';

// @Strict decorator alongside @decorate
@decorate()
@Strict({ someProp: 123 })
class StrictDecorated {
    someProp!: number;
}

// BaseClass with Object.setPrototypeOf
@decorate()
class MyBaseClass {
    base_field = 555;
}

Object.setPrototypeOf(MyBaseClass.prototype, new BaseClass({ strict: true }));
```

### 5. ConstructorFunction Pattern

```typescript
import { define, ConstructorFunction } from 'mnemonica';

const MyFn = function (this: any) {
    this.field = 123;
} as ConstructorFunction<{ field: number }>;

const MyFnType = define('MyFnType', MyFn);
```

## CLI Features

### Tree Output

The CLI displays type hierarchy as a tree:

```bash
$ npx tactica

Type Hierarchy (Trie):
└── UserTypeInstance
    └── AdminTypeInstance
        └── SuperAdminTypeInstance
└── ProductTypeInstance
    ├── DigitalProductTypeInstance
    └── PhysicalProductTypeInstance
```

### Code Coverage

Run tests with coverage:

```bash
npm run test:coverage
```

## Integration with Your Workflow

### .gitignore

Tactica automatically adds `.tactica/` to your `.gitignore` if not already present.

### IDE Support

With the Language Service Plugin:
- **VS Code**: Automatic type updates on file save
- **WebStorm**: Works with TypeScript service
- **Vim/Neovim**: Works with coc.nvim, nvim-lspconfig

## API Reference

### MnemonicaAnalyzer

```typescript
class MnemonicaAnalyzer {
    constructor(program?: ts.Program);
    analyzeFile(sourceFile: ts.SourceFile): AnalyzeResult;
    analyzeSource(sourceCode: string, fileName?: string): AnalyzeResult;
    getGraph(): TypeGraphImpl;
}
```

### TypeGraphImpl

```typescript
class TypeGraphImpl implements TypeGraph {
    roots: Map<string, TypeNode>;
    allTypes: Map<string, TypeNode>;
    addRoot(node: TypeNode): void;
    addChild(parent: TypeNode, child: TypeNode): void;
    findType(fullPath: string): TypeNode | undefined;
    getAllTypes(): TypeNode[];
    *bfs(): Generator<TypeNode>;
    *dfs(node?: TypeNode): Generator<TypeNode>;
}
```

### TypesGenerator

```typescript
class TypesGenerator {
    constructor(graph: TypeGraphImpl);
    generate(): GeneratedTypes;
    generateSingleType(node: TypeNode): string;
}
```

### TypesWriter

```typescript
class TypesWriter {
    constructor(outputDir?: string);
    write(generated: GeneratedTypes): string;
    writeTo(filename: string, content: string): string;
    clean(): void;
    getOutputDir(): string;
}
```

## How It Works

1. **Parse**: TypeScript AST is parsed to find `define()` and `decorate()` calls
2. **Analyze**: The analyzer extracts type names, properties, and hierarchy
3. **Graph**: A Trie (tree) structure represents the type hierarchy
4. **Generate**: TypeScript declarations are generated from the graph
5. **Output**: Files are written to `.tactica/` directory

```
Type Hierarchy (Trie)
├── UserType
│   ├── properties: { name: string }
│   └── AdminType
│       ├── properties: { role: string }
│       └── SuperAdminType
│           └── properties: { permissions: string[] }
└── OrderType
    └── properties: { items: Item[] }
```

## Troubleshooting

### Types not updating

1. Check that the plugin is loaded in `tsconfig.json`
2. Restart TypeScript service (VS Code: Command Palette → "TypeScript: Restart TS Server")
3. Verify file patterns in `include`/`exclude` config

### Generated types have errors

1. Ensure all mnemonica types have explicit type annotations
2. Check that `define()` calls use string literals for type names
3. Verify property types are valid TypeScript

### Plugin not working

```bash
# Test with CLI first
npx tactica --verbose

# Check for parsing errors
npx tactica --verbose 2>&1 | grep -i error
```

## Related Projects

- [mnemonica](https://github.com/wentout/mnemonica) - Instance inheritance system
- [@mnemonica/topologica](https://github.com/mythographica/topologica) - Filesystem-based type discovery
- [typeomatica](https://github.com/wentout/typeomatica) - Runtime type checking with `@Strict` decorator and `BaseClass`

## Testing

Tactica includes comprehensive test coverage:

```bash
# Run all tests
npm test

# Run with coverage
npm run test:coverage
```

Test suites include:
- **Analyzer tests** - Core AST parsing functionality
- **Generator tests** - TypeScript declaration generation
- **Writer tests** - File I/O operations
- **Integration tests** - End-to-end workflows
- **Example tests** - Patterns from `tactica-test/` project
- **Typeomatica tests** - Combined mnemonica + typeomatica patterns

## License

MIT

## Contributing

Contributions welcome! Please read the [Contributing Guide](CONTRIBUTING.md) for details.