@atscript/core 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/core",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Core library for Atscript parsing and file generation.",
5
5
  "keywords": [
6
6
  "annotations",
@@ -20,8 +20,13 @@
20
20
  "url": "git+https://github.com/moostjs/atscript.git",
21
21
  "directory": "packages/core"
22
22
  },
23
+ "bin": {
24
+ "atscript-core-skill": "./scripts/setup-skills.js"
25
+ },
23
26
  "files": [
24
- "dist"
27
+ "dist",
28
+ "skills",
29
+ "scripts/setup-skills.js"
25
30
  ],
26
31
  "type": "module",
27
32
  "main": "dist/index.mjs",
@@ -46,6 +51,7 @@
46
51
  },
47
52
  "scripts": {
48
53
  "pub": "pnpm publish --access public",
49
- "test": "vitest"
54
+ "test": "vitest",
55
+ "setup-skills": "node ./scripts/setup-skills.js"
50
56
  }
51
57
  }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ /* prettier-ignore */
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import os from 'os'
6
+ import { fileURLToPath } from 'url'
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
+
10
+ const SKILL_NAME = 'atscript-core'
11
+ const SKILL_SRC = path.join(__dirname, '..', 'skills', SKILL_NAME)
12
+
13
+ if (!fs.existsSync(SKILL_SRC)) {
14
+ console.error(`No skills found at ${SKILL_SRC}`)
15
+ console.error('Add your SKILL.md files to the skills/' + SKILL_NAME + '/ directory first.')
16
+ process.exit(1)
17
+ }
18
+
19
+ const AGENTS = {
20
+ 'Claude Code': { dir: '.claude/skills', global: path.join(os.homedir(), '.claude', 'skills') },
21
+ 'Cursor': { dir: '.cursor/skills', global: path.join(os.homedir(), '.cursor', 'skills') },
22
+ 'Windsurf': { dir: '.windsurf/skills', global: path.join(os.homedir(), '.windsurf', 'skills') },
23
+ 'Codex': { dir: '.codex/skills', global: path.join(os.homedir(), '.codex', 'skills') },
24
+ 'OpenCode': { dir: '.opencode/skills', global: path.join(os.homedir(), '.opencode', 'skills') },
25
+ }
26
+
27
+ const args = process.argv.slice(2)
28
+ const isGlobal = args.includes('--global') || args.includes('-g')
29
+ const isPostinstall = args.includes('--postinstall')
30
+ let installed = 0, skipped = 0
31
+ const installedDirs = []
32
+
33
+ for (const [agentName, cfg] of Object.entries(AGENTS)) {
34
+ const targetBase = isGlobal ? cfg.global : path.join(process.cwd(), cfg.dir)
35
+ const agentRootDir = path.dirname(cfg.global)
36
+
37
+ if (isPostinstall || isGlobal) {
38
+ if (!fs.existsSync(agentRootDir)) { skipped++; continue }
39
+ }
40
+
41
+ const dest = path.join(targetBase, SKILL_NAME)
42
+ try {
43
+ fs.mkdirSync(dest, { recursive: true })
44
+ fs.cpSync(SKILL_SRC, dest, { recursive: true })
45
+ console.log(`✅ ${agentName}: installed to ${dest}`)
46
+ installed++
47
+ if (!isGlobal) installedDirs.push(cfg.dir + '/' + SKILL_NAME)
48
+ } catch (err) {
49
+ console.warn(`⚠️ ${agentName}: failed — ${err.message}`)
50
+ }
51
+ }
52
+
53
+ // Add locally-installed skill dirs to .gitignore
54
+ if (!isGlobal && installedDirs.length > 0) {
55
+ const gitignorePath = path.join(process.cwd(), '.gitignore')
56
+ let gitignoreContent = ''
57
+ try { gitignoreContent = fs.readFileSync(gitignorePath, 'utf8') } catch {}
58
+ const linesToAdd = installedDirs.filter(d => !gitignoreContent.includes(d))
59
+ if (linesToAdd.length > 0) {
60
+ const hasHeader = gitignoreContent.includes('# AI agent skills')
61
+ const block = (gitignoreContent && !gitignoreContent.endsWith('\n') ? '\n' : '')
62
+ + (hasHeader ? '' : '\n# AI agent skills (auto-generated by setup-skills)\n')
63
+ + linesToAdd.join('\n') + '\n'
64
+ fs.appendFileSync(gitignorePath, block)
65
+ console.log(`📝 Added ${linesToAdd.length} entries to .gitignore`)
66
+ }
67
+ }
68
+
69
+ if (installed === 0 && isPostinstall) {
70
+ // Silence — no agents present
71
+ } else if (installed === 0 && skipped === Object.keys(AGENTS).length) {
72
+ console.log('No agent directories detected. Try --global or run without it for project-local install.')
73
+ } else if (installed === 0) {
74
+ console.log('Nothing installed. Run without --global to install project-locally.')
75
+ } else {
76
+ console.log(`\n✨ Done! Restart your AI agent to pick up the "${SKILL_NAME}" skill.`)
77
+ }
File without changes
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: atscript-core
3
+ description: Use when working with @atscript/core — configuring atscript.config.ts with defineConfig, defining custom AnnotationSpec annotations, creating custom TPrimitiveConfig primitives, building TAtscriptPlugin plugins with config/resolve/load/onDocument/render/buildEnd hooks, understanding built-in @meta.* @expect.* @ui.* @db.* @emit.* annotations, or working with AtscriptDoc/AtscriptRepo APIs.
4
+ ---
5
+
6
+ # @atscript/core
7
+
8
+ The foundation package for Atscript — a universal type and metadata description language. Provides the parser, AST, annotation system, plugin architecture, and all built-in annotations and primitives that language extensions (TypeScript, Python, etc.) build upon.
9
+
10
+ ## How to use this skill
11
+
12
+ Read the domain file that matches the task. Do not load all files — only what you need.
13
+
14
+ | Domain | File | Load when… |
15
+ | ------------------------ | -------------------------------- | --------------------------------------------------------------------------------- |
16
+ | Setup & configuration | [core.md](core.md) | Installing, configuring `atscript.config.ts`, understanding the package structure |
17
+ | Annotations reference | [annotations.md](annotations.md) | Using or defining `@meta.*`, `@expect.*`, `@ui.*`, `@db.*`, `@emit.*` annotations |
18
+ | Primitives reference | [primitives.md](primitives.md) | Using built-in primitives (`string.email`, `number.int`) or defining custom ones |
19
+ | Plugin development | [plugins.md](plugins.md) | Creating plugins with `createAtscriptPlugin`, implementing hooks |
20
+
21
+ ## Quick reference
22
+
23
+ ```ts
24
+ // Configuration
25
+ import { defineConfig, AnnotationSpec } from '@atscript/core'
26
+
27
+ // Plugin creation
28
+ import { createAtscriptPlugin } from '@atscript/core'
29
+
30
+ // AST / document types (for plugin authors)
31
+ import { AtscriptDoc, AtscriptRepo } from '@atscript/core'
32
+ ```
@@ -0,0 +1,157 @@
1
+ # Annotations Reference — @atscript/core
2
+
3
+ > Complete reference for all built-in annotations and the `AnnotationSpec` API for defining custom ones.
4
+
5
+ ## Built-in Annotation Namespaces
6
+
7
+ ### `@meta.*` — Semantic Metadata
8
+
9
+ | Annotation | Arguments | Multiple | NodeType | Description |
10
+ | --------------------- | -------------------- | -------- | ---------------- | ---------------------------------------------------------- |
11
+ | `@meta.label` | `text: string` | no | any | Human-readable label for UI, logs, documentation |
12
+ | `@meta.id` | _(none)_ | no | prop | Mark field as unique identifier; multiple = composite PK |
13
+ | `@meta.description` | `text: string` | no | any | Detailed description |
14
+ | `@meta.documentation` | `text: string` | **yes** | any | Multi-line Markdown docs (each appends a line) |
15
+ | `@meta.sensitive` | _(none)_ | no | prop, type | Sensitive data (passwords, keys) — hidden in logs/UI |
16
+ | `@meta.readonly` | _(none)_ | no | prop, type | Read-only field |
17
+ | `@meta.required` | `message?: string` | no | string, boolean | Required: strings must be non-whitespace; booleans `true` |
18
+ | `@meta.default` | `value: string` | no | prop, type | Default value (strings as-is, others parsed as JSON) |
19
+ | `@meta.example` | `value: string` | no | prop, type | Example value (strings as-is, others parsed as JSON) |
20
+
21
+ ### `@expect.*` — Validation Constraints
22
+
23
+ | Annotation | Arguments | Multiple | Applies To | Description |
24
+ | ------------------- | ------------------------------------------------------- | -------- | ------------- | --------------------------- |
25
+ | `@expect.minLength` | `length: number`, `message?: string` | no | string, array | Minimum length |
26
+ | `@expect.maxLength` | `length: number`, `message?: string` | no | string, array | Maximum length |
27
+ | `@expect.min` | `minValue: number`, `message?: string` | no | number | Minimum value |
28
+ | `@expect.max` | `maxValue: number`, `message?: string` | no | number | Maximum value |
29
+ | `@expect.int` | _(none)_ | no | number | Must be integer |
30
+ | `@expect.pattern` | `pattern: string`, `flags?: string`, `message?: string` | **yes** (append) | string | Regex validation |
31
+ | `@expect.array.key` | _(none)_ | no | string, number | Key field in arrays |
32
+
33
+ ### `@ui.*` — UI / Presentation Hints
34
+
35
+ | Annotation | Arguments | Multiple | NodeType | Description |
36
+ | ----------------- | ------------------------------ | --------------- | ----------------------- | ---------------------------------------- |
37
+ | `@ui.placeholder` | `text: string` | no | prop, type | Input placeholder text |
38
+ | `@ui.component` | `name: string` | no | prop, type | UI component hint (`"select"`, etc.) |
39
+ | `@ui.hidden` | _(none)_ | no | prop, type, interface | Hide from UI forms/tables |
40
+ | `@ui.group` | `name: string` | no | prop | Group fields into form sections |
41
+ | `@ui.order` | `order: number` | no | prop | Display order (lower = first) |
42
+ | `@ui.width` | `width: string` | no | prop, type | Layout hint (`"half"`, `"full"`, etc.) |
43
+ | `@ui.icon` | `name: string` | no | prop, type, interface | Icon hint |
44
+ | `@ui.hint` | `text: string` | no | prop, type | Help text / tooltip |
45
+ | `@ui.disabled` | _(none)_ | no | prop, type | Non-interactive field |
46
+ | `@ui.type` | `type: string` | no | prop, type | Input type (`"textarea"`, `"password"`) |
47
+ | `@ui.attr` | `key: string`, `value: string` | **yes** (append) | prop, type, interface | Arbitrary HTML/component attribute |
48
+ | `@ui.class` | `names: string` | **yes** (append) | prop, type, interface | CSS class names |
49
+ | `@ui.style` | `css: string` | **yes** (append) | prop, type, interface | Inline CSS styles |
50
+
51
+ ### `@db.*` — Database Schema
52
+
53
+ | Annotation | Arguments | Multiple | NodeType | Description |
54
+ | ------------------- | ---------------------------- | --------------- | --------- | ---------------------------------------- |
55
+ | `@db.table` | `name: string \| true` | no | interface | Database table/collection name |
56
+ | `@db.schema` | `name: string` | no | interface | Database schema (PostgreSQL, etc.) |
57
+ | `@db.index.plain` | `name?: string`, `sort?: string` | **yes** (append) | prop | Standard index (shared name = compound) |
58
+ | `@db.index.unique` | `name?: string` | **yes** (append) | prop | Unique constraint index |
59
+ | `@db.index.fulltext`| `name?: string` | **yes** (append) | prop | Fulltext search index |
60
+ | `@db.column` | `name: string` | no | prop | Override database column name |
61
+ | `@db.default.value` | `value: string` | no | prop | Static default value |
62
+ | `@db.default.fn` | `fn: string` | no | prop | Database function for default |
63
+ | `@db.ignore` | _(none)_ | no | prop | Exclude field from database |
64
+
65
+ ### `@emit.*` — Build-time Directives
66
+
67
+ | Annotation | NodeType | Description |
68
+ | ------------------ | --------- | ----------------------------------------------- |
69
+ | `@emit.jsonSchema` | interface | Pre-compute and embed JSON Schema at build time |
70
+
71
+ ## Custom Annotations
72
+
73
+ Define in `atscript.config.ts` under the `annotations` key:
74
+
75
+ ```ts
76
+ import { defineConfig, AnnotationSpec } from '@atscript/core'
77
+
78
+ export default defineConfig({
79
+ annotations: {
80
+ // Flat: @tag "value"
81
+ tag: new AnnotationSpec({
82
+ multiple: true,
83
+ mergeStrategy: 'append',
84
+ argument: { name: 'value', type: 'string' },
85
+ description: 'Tag for categorization',
86
+ }),
87
+
88
+ // Namespaced: @api.deprecated "Use v2 instead"
89
+ api: {
90
+ deprecated: new AnnotationSpec({
91
+ argument: { name: 'message', type: 'string', optional: true },
92
+ nodeType: ['prop', 'interface'],
93
+ description: 'Mark as deprecated',
94
+ }),
95
+ },
96
+ },
97
+ })
98
+ ```
99
+
100
+ ### `AnnotationSpec` Options
101
+
102
+ ```ts
103
+ new AnnotationSpec({
104
+ description?: string, // IntelliSense hover text
105
+ nodeType?: ('interface' | 'type' | 'prop')[], // Where it can be applied
106
+ defType?: string[], // Restrict to value types: 'string', 'number', etc.
107
+
108
+ // Arguments
109
+ argument?: TAnnotationArgument | TAnnotationArgument[],
110
+ // Each: { name, type: 'string'|'number'|'boolean', optional?, description?, values? }
111
+
112
+ // Multiplicity
113
+ multiple?: boolean, // Can appear more than once (default: false)
114
+ mergeStrategy?: 'replace' | 'append', // How values merge on inheritance (default: 'replace')
115
+
116
+ // Advanced
117
+ validate?: (mainToken, args, doc) => TMessages | undefined, // Custom validation
118
+ modify?: (mainToken, args, doc) => void, // AST modification after parsing
119
+ })
120
+ ```
121
+
122
+ ### How Annotations Map to Runtime Values
123
+
124
+ | Config | Metadata value type |
125
+ | ------------------------------- | ---------------------------- |
126
+ | No arguments | `true` (boolean flag) |
127
+ | Single argument | The argument value directly |
128
+ | Multiple named arguments | `{ name1: val1, name2: val2 }` |
129
+ | `multiple: true` | Array of the above |
130
+ | `multiple: true` + `append` | Concatenated array on merge |
131
+
132
+ ### Merge Strategies
133
+
134
+ When annotations inherit (type → prop, ad-hoc annotate blocks):
135
+
136
+ - **replace** (default) — higher-priority annotation replaces lower entirely
137
+ - **append** — values from both sides concatenate into a single array
138
+
139
+ ## Annotation Resolution
140
+
141
+ ```ts
142
+ import { resolveAnnotation } from '@atscript/core'
143
+
144
+ // Looks up 'ui.placeholder' in the annotation tree
145
+ const spec = resolveAnnotation('ui.placeholder', config.annotations)
146
+ // Returns AnnotationSpec or undefined
147
+ ```
148
+
149
+ The function splits the dotted name and walks the `TAnnotationsTree` hierarchy.
150
+
151
+ ## Best Practices
152
+
153
+ - Use namespaced annotations (`@ns.name`) to avoid collisions
154
+ - Set `nodeType` to catch misuse early (e.g., `@db.table` only on interfaces)
155
+ - Use `mergeStrategy: 'append'` for annotations that accumulate (patterns, tags, styles)
156
+ - Use `validate` for complex cross-field checks (e.g., `@db.ignore` conflicts with `@meta.id`)
157
+ - Keep `description` concise — it shows in IDE hover tooltips
@@ -0,0 +1,125 @@
1
+ # Setup & Configuration — @atscript/core
2
+
3
+ > How to install, configure, and understand the @atscript/core package.
4
+
5
+ ## Overview
6
+
7
+ `@atscript/core` is the foundation of the Atscript ecosystem. It provides:
8
+ - Parser for `.as` files → AST
9
+ - Annotation system (`AnnotationSpec`, annotation trees)
10
+ - Primitive type system
11
+ - Plugin architecture
12
+ - Built-in annotations (`@meta.*`, `@expect.*`, `@ui.*`, `@db.*`, `@emit.*`)
13
+ - Built-in primitives (`string`, `number`, `boolean`, `null`, `void`, `phantom` + extensions)
14
+
15
+ Language extensions like `@atscript/typescript` build on top of core.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @atscript/core
21
+ ```
22
+
23
+ ## Configuration
24
+
25
+ Create `atscript.config.ts` (or `.js`) at your project root:
26
+
27
+ ```ts
28
+ import { defineConfig } from '@atscript/core'
29
+ import tsPlugin from '@atscript/typescript'
30
+
31
+ export default defineConfig({
32
+ rootDir: 'src',
33
+ plugins: [tsPlugin()],
34
+
35
+ // Optional: custom annotations
36
+ annotations: {
37
+ myNamespace: {
38
+ myAnnotation: new AnnotationSpec({ ... }),
39
+ },
40
+ },
41
+
42
+ // Optional: custom primitives
43
+ primitives: {
44
+ currency: { type: 'string', annotations: { 'expect.pattern': { pattern: '^\\d+\\.\\d{2}$' } } },
45
+ },
46
+
47
+ // Optional: how unknown annotations are handled
48
+ unknownAnnotation: 'error', // 'error' (default) | 'warn' | 'allow'
49
+
50
+ // Optional: file patterns
51
+ include: ['**/*.as'],
52
+ exclude: ['**/node_modules/**'],
53
+ })
54
+ ```
55
+
56
+ ### `TAtscriptConfig` Fields
57
+
58
+ | Field | Type | Description |
59
+ | ------------------- | ----------------------------- | ---------------------------------------------- |
60
+ | `rootDir` | `string` | Root directory for `.as` files |
61
+ | `entries` | `string[]` | Explicit entry files (instead of auto-discover) |
62
+ | `plugins` | `TAtscriptPlugin[]` | Plugins to load (order matters) |
63
+ | `annotations` | `TAnnotationsTree` | Custom annotation definitions |
64
+ | `primitives` | `Record<string, TPrimitiveConfig>` | Custom primitive type definitions |
65
+ | `unknownAnnotation` | `'error' \| 'warn' \| 'allow'` | How to handle unrecognized annotations |
66
+ | `include` | `string[]` | Glob patterns to include |
67
+ | `exclude` | `string[]` | Glob patterns to exclude |
68
+ | `format` | `string` | Output format (set by CLI or build tool) |
69
+ | `outDir` | `string` | Output directory |
70
+
71
+ ## Package Exports
72
+
73
+ ```ts
74
+ // Configuration
75
+ export { defineConfig } from '@atscript/core'
76
+
77
+ // Annotations
78
+ export { AnnotationSpec, isAnnotationSpec, resolveAnnotation } from '@atscript/core'
79
+
80
+ // Config types
81
+ export type { TAtscriptConfig, TAnnotationsTree } from '@atscript/core'
82
+
83
+ // Plugin system
84
+ export { createAtscriptPlugin } from '@atscript/core'
85
+ export type { TAtscriptPlugin, TPluginOutput } from '@atscript/core'
86
+ export { DEFAULT_FORMAT } from '@atscript/core'
87
+
88
+ // Document & Repo (for plugin authors)
89
+ export { AtscriptDoc } from '@atscript/core'
90
+ export { AtscriptRepo } from '@atscript/core'
91
+
92
+ // Parser nodes (for plugin authors)
93
+ export { /* node type guards and types */ } from '@atscript/core'
94
+
95
+ // Build utilities
96
+ export { build } from '@atscript/core'
97
+ ```
98
+
99
+ ## Architecture
100
+
101
+ ```
102
+ atscript.config.ts
103
+ └─ defineConfig({ plugins, annotations, primitives })
104
+ └─ Plugins merge configs via defu (deep defaults)
105
+ └─ AtscriptRepo orchestrates parsing
106
+ └─ AtscriptDoc per .as file (AST + metadata)
107
+ └─ Plugins render output (render hook)
108
+ └─ Plugins aggregate (buildEnd hook)
109
+ ```
110
+
111
+ Plugins execute in array order. Each plugin's `config()` output is merged using `defu`, so multiple plugins can contribute annotations and primitives without conflicts.
112
+
113
+ ## Annotation Namespaces (Built-in)
114
+
115
+ The core ships five annotation namespaces:
116
+
117
+ | Namespace | Purpose | Example |
118
+ | ---------- | ---------------------------------- | -------------------------- |
119
+ | `meta.*` | Semantic metadata | `@meta.label "Name"` |
120
+ | `expect.*` | Validation constraints | `@expect.min 0` |
121
+ | `ui.*` | Presentation / UI hints | `@ui.placeholder "Enter…"` |
122
+ | `db.*` | Database schema | `@db.table "users"` |
123
+ | `emit.*` | Build-time directives | `@emit.jsonSchema` |
124
+
125
+ See [annotations.md](annotations.md) for the full reference.
@@ -0,0 +1,206 @@
1
+ # Plugin Development — @atscript/core
2
+
3
+ > How to create Atscript plugins that add annotations, primitives, and code generators.
4
+
5
+ ## Concepts
6
+
7
+ A plugin is a plain object implementing `TAtscriptPlugin` — a `name` plus optional hooks. Plugins extend Atscript by adding annotations, primitives, and output generators without modifying the parser.
8
+
9
+ ## Creating a Plugin
10
+
11
+ ```ts
12
+ import { createAtscriptPlugin, AnnotationSpec } from '@atscript/core'
13
+
14
+ export const myPlugin = () =>
15
+ createAtscriptPlugin({
16
+ name: 'my-plugin',
17
+
18
+ // Add annotations and primitives
19
+ config(config) {
20
+ return {
21
+ annotations: {
22
+ api: {
23
+ deprecated: new AnnotationSpec({
24
+ description: 'Mark as deprecated',
25
+ argument: { name: 'message', type: 'string', optional: true },
26
+ }),
27
+ },
28
+ },
29
+ }
30
+ },
31
+
32
+ // Generate output files
33
+ render(doc, format) {
34
+ if (format === 'json') {
35
+ return [{ fileName: `${doc.name}.json`, content: JSON.stringify(doc.metadata) }]
36
+ }
37
+ return []
38
+ },
39
+ })
40
+ ```
41
+
42
+ `createAtscriptPlugin` is a type-safe identity function — returns the object as-is but provides IntelliSense on hook signatures.
43
+
44
+ ## Registering Plugins
45
+
46
+ ```ts
47
+ import { defineConfig } from '@atscript/core'
48
+ import { myPlugin } from './plugins/my-plugin'
49
+
50
+ export default defineConfig({
51
+ rootDir: 'src',
52
+ plugins: [myPlugin()],
53
+ })
54
+ ```
55
+
56
+ Plugins execute in array order. Each plugin's `config()` output is merged with accumulated config using `defu` (deep defaults).
57
+
58
+ ## The TAtscriptPlugin Interface
59
+
60
+ ```ts
61
+ interface TAtscriptPlugin {
62
+ name: string
63
+ config?(config: TAtscriptConfig): TAtscriptConfig | undefined
64
+ resolve?(id: string): string | undefined
65
+ load?(id: string): string | undefined
66
+ onDocument?(doc: AtscriptDoc): void
67
+ render?(doc: AtscriptDoc, format: string): TPluginOutput[]
68
+ buildEnd?(output: TOutput[], format: string, repo: AtscriptRepo): void
69
+ }
70
+ ```
71
+
72
+ All hooks except `name` are optional. Hooks can be sync or async.
73
+
74
+ ## Hook Reference
75
+
76
+ ### `config(config)`
77
+
78
+ Called once during initialization. Return additional config to merge (annotations, primitives). Multiple plugins merge via `defu`.
79
+
80
+ ```ts
81
+ config(config) {
82
+ return {
83
+ primitives: {
84
+ url: { type: 'string', annotations: { 'expect.pattern': { pattern: '^https?://' } } },
85
+ },
86
+ annotations: {
87
+ cache: { ttl: new AnnotationSpec({ argument: { name: 'seconds', type: 'number' } }) },
88
+ },
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### `resolve(id)`
94
+
95
+ Remap or virtualize module paths. Return a new path string or `undefined` to skip.
96
+
97
+ ```ts
98
+ resolve(id) {
99
+ if (id === '@my-lib/types') return '/path/to/virtual-types.as'
100
+ }
101
+ ```
102
+
103
+ ### `load(id)`
104
+
105
+ Provide virtual file content. Return `.as` source string or `undefined`.
106
+
107
+ ```ts
108
+ load(id) {
109
+ if (id === '/virtual/base-entity.as') {
110
+ return `export interface BaseEntity { id: string.uuid }`
111
+ }
112
+ }
113
+ ```
114
+
115
+ ### `onDocument(doc)`
116
+
117
+ Post-process a parsed document. Access the AST, inject virtual props, run checks.
118
+
119
+ ```ts
120
+ onDocument(doc) {
121
+ // Access parsed interfaces
122
+ for (const [name, node] of doc.interfaces) {
123
+ // Inspect or modify the AST
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### `render(doc, format)`
129
+
130
+ Generate output files for each document. Return `TPluginOutput[]`.
131
+
132
+ ```ts
133
+ render(doc, format) {
134
+ if (format === 'dts' || format === DEFAULT_FORMAT) {
135
+ return [{ fileName: `${doc.name}.d.ts`, content: generateTypes(doc) }]
136
+ }
137
+ return []
138
+ }
139
+ ```
140
+
141
+ The `DEFAULT_FORMAT` constant is the format used when saving `.as` files in an editor. Check for it alongside your plugin's format strings.
142
+
143
+ ### `buildEnd(output, format, repo)`
144
+
145
+ Called after all documents are rendered. Aggregate across all docs — useful for global type declarations, index files, etc.
146
+
147
+ ```ts
148
+ buildEnd(output, format, repo) {
149
+ // repo.getUsedAnnotations() — all annotation specs registered
150
+ // output — array of { doc, files } from render phase
151
+ }
152
+ ```
153
+
154
+ ## AtscriptDoc API (Key Methods)
155
+
156
+ | Method / Property | Description |
157
+ | ----------------------- | ------------------------------------------------ |
158
+ | `doc.name` | File name (without extension) |
159
+ | `doc.path` | Full file path |
160
+ | `doc.interfaces` | Map of interface names → AST nodes |
161
+ | `doc.types` | Map of type names → AST nodes |
162
+ | `doc.imports` | Import declarations |
163
+ | `doc.exports` | Export declarations |
164
+ | `doc.unwindType(id, chain)` | Resolve a type reference to its definition |
165
+
166
+ ## AtscriptRepo API (Key Methods)
167
+
168
+ | Method / Property | Description |
169
+ | ------------------------------ | ---------------------------------------------- |
170
+ | `repo.getUsedAnnotations()` | Iterator of all registered annotation specs |
171
+ | `repo.documents` | Map of all parsed documents |
172
+ | `repo.config` | Merged configuration |
173
+
174
+ ## Common Patterns
175
+
176
+ ### Annotation-only plugin
177
+
178
+ Just `config()` — the simplest plugin type:
179
+
180
+ ```ts
181
+ export const tagsPlugin = () =>
182
+ createAtscriptPlugin({
183
+ name: 'tags',
184
+ config: () => ({
185
+ annotations: {
186
+ tag: new AnnotationSpec({
187
+ multiple: true,
188
+ mergeStrategy: 'append',
189
+ argument: { name: 'value', type: 'string' },
190
+ }),
191
+ },
192
+ }),
193
+ })
194
+ ```
195
+
196
+ ### Full language extension
197
+
198
+ `config()` + `render()` + `buildEnd()` — see `@atscript/typescript` for the reference implementation.
199
+
200
+ ## Best Practices
201
+
202
+ - Keep plugin names unique and descriptive
203
+ - Use `config()` for annotations/primitives, not hardcoded checks
204
+ - Check `format` in `render()` — plugins may be called with different formats
205
+ - Use `DEFAULT_FORMAT` for output that's essential to the dev experience (e.g., type declarations)
206
+ - Return empty array from `render()` when format doesn't match