@theokit/sdk 1.7.0 → 1.8.1

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.
@@ -0,0 +1,176 @@
1
+ ---
2
+ user-invocable: false
3
+ paths:
4
+ - "**/*budget*"
5
+ - "**/*Budget*"
6
+ - "**/*cost*"
7
+ - "**/*token*"
8
+ description: TheoKit SDK Budget API reference — cost tracking, token budget enforcement, usage reporting
9
+ ---
10
+
11
+ # TheoKit Budget
12
+
13
+ Token cost enforcement primitive. Track, warn, and block LLM spend at the
14
+ process level with stacked USD limits and configurable enforcement modes.
15
+
16
+ ## Quick start
17
+
18
+ ```typescript
19
+ import { Budget } from "@theokit/sdk";
20
+
21
+ const handle = Budget.create({
22
+ name: "my-bot",
23
+ scope: "process",
24
+ mode: "warn",
25
+ limits: [
26
+ { window: "1h", limitUsd: 5.0 },
27
+ { window: "1d", limitUsd: 50.0 },
28
+ ],
29
+ onThreshold: (event) => {
30
+ console.warn(`Budget ${event.budgetName}: ${event.threshold * 100}% of ${event.window} limit`);
31
+ },
32
+ onExceed: (event) => {
33
+ console.error(`Budget ${event.budgetName}: exceeded ${event.window} limit`);
34
+ },
35
+ });
36
+ ```
37
+
38
+ ## Enforcement modes
39
+
40
+ | Mode | Behavior |
41
+ |---|---|
42
+ | `"audit"` | Log only. Never throws, never blocks. |
43
+ | `"warn"` | Callbacks fire at 80%, 95%, and 100% thresholds. No throw. Default. |
44
+ | `"block"` | Preflight throw (`BudgetExceededError`) BEFORE the LLM call when would-exceed. |
45
+
46
+ ## Stacked limits
47
+
48
+ Pass multiple limits. ANY exceeded limit triggers enforcement:
49
+
50
+ ```typescript
51
+ Budget.create({
52
+ name: "emergency-stop",
53
+ scope: "process",
54
+ mode: "block",
55
+ limits: [
56
+ { window: "1h", limitUsd: 2.0 },
57
+ { window: "1d", limitUsd: 10.0 },
58
+ { window: "30d", limitUsd: 100.0 },
59
+ ],
60
+ });
61
+ ```
62
+
63
+ Empty `limits[]` is valid: pure tracking with no threshold/exceed callbacks.
64
+
65
+ ## Time windows
66
+
67
+ | Window | Alignment |
68
+ |---|---|
69
+ | `"1h"` | Relative (last 60 minutes) |
70
+ | `"1d"` | UTC midnight boundary |
71
+ | `"1w"` | Monday 00:00 UTC |
72
+ | `"30d"` | 1st of month 00:00 UTC |
73
+ | `"365d"` | Jan 1 00:00 UTC |
74
+
75
+ ## Managing budgets
76
+
77
+ ```typescript
78
+ // Retrieve a budget handle
79
+ const handle = Budget.get("my-bot");
80
+
81
+ // Check spend and remaining
82
+ handle?.spentIn("1d"); // USD spent in current day window
83
+ handle?.remainingIn("1d"); // USD remaining before limit
84
+
85
+ // List all active budgets
86
+ const budgets = Budget.list();
87
+
88
+ // Snapshot all windows for all budgets
89
+ const snapshots = Budget.snapshot();
90
+ // [{ name, window, spentUsd, limitUsd, ratio }, ...]
91
+
92
+ // Delete a budget
93
+ Budget.delete("my-bot");
94
+ ```
95
+
96
+ ## Type reference
97
+
98
+ ```typescript
99
+ type BudgetScope = "agent" | "call" | "process";
100
+ type BudgetWindow = "1h" | "1d" | "1w" | "30d" | "365d";
101
+ type BudgetMode = "audit" | "warn" | "block";
102
+
103
+ interface BudgetLimit {
104
+ readonly window: BudgetWindow;
105
+ readonly limitUsd: number;
106
+ }
107
+
108
+ interface BudgetOptions {
109
+ readonly name: string; // must match ^[a-z0-9][a-z0-9_-]*$
110
+ readonly scope: BudgetScope;
111
+ readonly limits: ReadonlyArray<BudgetLimit>;
112
+ readonly mode?: BudgetMode; // default "warn"
113
+ readonly onThreshold?: (event: BudgetThresholdEvent) => void | Promise<void>;
114
+ readonly onExceed?: (event: BudgetExceedEvent) => void | Promise<void>;
115
+ }
116
+
117
+ interface BudgetHandle {
118
+ readonly name: string;
119
+ readonly mode: BudgetMode;
120
+ readonly scope: BudgetScope;
121
+ readonly limits: ReadonlyArray<BudgetLimit>;
122
+ spentIn(window: BudgetWindow): number;
123
+ remainingIn(window: BudgetWindow): number;
124
+ }
125
+
126
+ interface BudgetSnapshot {
127
+ readonly name: string;
128
+ readonly window: BudgetWindow;
129
+ readonly spentUsd: number;
130
+ readonly limitUsd: number;
131
+ readonly ratio: number;
132
+ }
133
+
134
+ interface BudgetThresholdEvent {
135
+ readonly budgetName: string;
136
+ readonly window: BudgetWindow;
137
+ readonly threshold: 0.8 | 0.95;
138
+ readonly spentUsd: number;
139
+ readonly limitUsd: number;
140
+ }
141
+
142
+ interface BudgetExceedEvent {
143
+ readonly budgetName: string;
144
+ readonly window: BudgetWindow;
145
+ readonly spentUsd: number;
146
+ readonly limitUsd: number;
147
+ readonly mode: BudgetMode;
148
+ }
149
+ ```
150
+
151
+ ## Wiring with agents
152
+
153
+ Budget enforcement integrates with `agent.send()`. When a budget is active
154
+ and mode is `"block"`, a preflight check runs before the LLM call. If the
155
+ estimated cost would exceed a limit, `BudgetExceededError` is thrown.
156
+
157
+ The `turn-ended` InteractionUpdate carries token usage that feeds the budget
158
+ ledger automatically:
159
+
160
+ ```typescript
161
+ { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens }
162
+ ```
163
+
164
+ ## Errors
165
+
166
+ | Error | When |
167
+ |---|---|
168
+ | `ConfigurationError` | Invalid `name` (grammar violation) or duplicate `name` |
169
+ | `BudgetExceededError` | `mode: "block"` and spend would exceed a limit |
170
+
171
+ ## Known limitations
172
+
173
+ - v1 supports `scope: "process"` only. `"agent"` and `"call"` scopes are
174
+ reserved for future multi-tenant scenarios.
175
+ - In-flight `agent.send` calls referencing a deleted budget treat subsequent
176
+ charges as a silent no-op with a stderr warning.
@@ -0,0 +1,139 @@
1
+ ---
2
+ user-invocable: false
3
+ paths:
4
+ - "**/.theokit/**"
5
+ - "**/config.*"
6
+ - "**/theo.config.*"
7
+ description: TheoKit SDK configuration reference — .theokit/ structure, mcp.json, hooks, env vars, config discovery
8
+ ---
9
+
10
+ # TheoKit Configuration
11
+
12
+ ## Directory structure
13
+
14
+ ```
15
+ .theokit/
16
+ +-- hooks/ # one .md per hook
17
+ | +-- shell-policy.md
18
+ +-- context/ # one .md per context source
19
+ | +-- bot-readme.md
20
+ +-- skills/<name>/SKILL.md # named capability packs
21
+ +-- plugins/<name>/PLUGIN.md # plugin definitions
22
+ +-- cron/
23
+ | +-- jobs.json # local cron state (auto-created)
24
+ +-- agents/*.md # subagent definitions (name + description frontmatter)
25
+ +-- memory/ # local memory storage
26
+ ```
27
+
28
+ User-level config lives at `~/.theokit/` with the same structure.
29
+
30
+ ## Config file format (v1.5+)
31
+
32
+ Markdown + YAML frontmatter. One file per entity.
33
+
34
+ ### Hook example
35
+
36
+ ```markdown
37
+ ---
38
+ event: preToolUse
39
+ matcher: ^shell$
40
+ command: node .theokit/policy.js
41
+ ---
42
+
43
+ # Shell tool policy gate
44
+
45
+ Vets shell tool invocations before spawn.
46
+ ```
47
+
48
+ ### Disabling an entry
49
+
50
+ Rename `<name>.md` to `<name>.md.disabled` — the loader silently skips it.
51
+
52
+ ## Setting sources
53
+
54
+ `local.settingSources` controls which config layers a local agent loads:
55
+
56
+ | Value | Source |
57
+ |---|---|
58
+ | `"project"` | `.theokit/` in the workspace |
59
+ | `"user"` | `~/.theokit/` |
60
+ | `"team"` | Team settings synced from the dashboard |
61
+ | `"mdm"` | MDM-managed enterprise settings |
62
+ | `"plugins"` | Plugin-provided settings |
63
+ | `"all"` | All of the above |
64
+
65
+ Cloud agents always load project / team / plugins and ignore this field.
66
+
67
+ ```typescript
68
+ const agent = await Agent.create({
69
+ apiKey: process.env.THEOKIT_API_KEY!,
70
+ model: { id: "google/gemini-2.0-flash-001" },
71
+ local: { cwd: process.cwd(), settingSources: ["project", "user"] },
72
+ });
73
+ ```
74
+
75
+ ## Environment variables
76
+
77
+ | Env var | Purpose |
78
+ |---|---|
79
+ | `THEOKIT_API_KEY` | Default API key (user or service account) |
80
+ | `THEOKIT_REDACT_SECRETS` | Set `false` to disable secret redaction (default: `true`) |
81
+ | `OLLAMA_HOST` | Ollama server URL (default: `http://localhost:11434`) |
82
+ | `OLLAMA_API_KEY` | Bearer token for Ollama Cloud or proxy |
83
+ | `OPENROUTER_API_KEY` | OpenRouter provider key |
84
+ | `ANTHROPIC_API_KEY` | Anthropic provider key |
85
+ | `OPENAI_API_KEY` | OpenAI provider key |
86
+
87
+ ## MCP server discovery
88
+
89
+ Servers are loaded with first-match-wins precedence:
90
+
91
+ 1. `mcpServers` on `agent.send()` — fully replaces creation-time servers
92
+ 2. `mcpServers` on `Agent.create()` — used when no per-send override
93
+ 3. Plugin servers (if settingSources includes `"plugins"`)
94
+ 4. Project servers from `.theokit/mcp.json` (if settingSources includes `"project"`)
95
+ 5. User servers from `~/.theokit/mcp.json` (if settingSources includes `"user"`)
96
+
97
+ ## Context manager config
98
+
99
+ ```typescript
100
+ const agent = await Agent.create({
101
+ context: {
102
+ manager: "file", // reads .theokit/context/<name>.md
103
+ maxTokens: 1200,
104
+ },
105
+ local: { cwd: process.cwd(), settingSources: ["project"] },
106
+ });
107
+ ```
108
+
109
+ `manager: "inline"` uses `sources` passed directly in `Agent.create()`.
110
+
111
+ ## Skills config
112
+
113
+ ```typescript
114
+ const agent = await Agent.create({
115
+ skills: {
116
+ enabled: ["code-review", "test-architect"],
117
+ },
118
+ local: { cwd: process.cwd(), settingSources: ["project"] },
119
+ });
120
+ ```
121
+
122
+ Skills live at `.theokit/skills/<name>/SKILL.md` with strict YAML frontmatter
123
+ (`name`, `description` required).
124
+
125
+ ## Migration from legacy JSON
126
+
127
+ ```bash
128
+ npx theokit-migrate-config --apply
129
+ ```
130
+
131
+ Dry-run by default; `--apply` writes. Backs up originals to
132
+ `<file>.json.<unix-ts>.bak`. Legacy JSON still works in v1.x with a
133
+ deprecation warning. JSON support removed in v2.0.
134
+
135
+ ## `agent.reload()`
136
+
137
+ Re-reads filesystem config (context, skills, hooks, project MCP, subagents)
138
+ without disposing the agent or losing conversation state. Invalid files
139
+ raise `ConfigurationError`.
@@ -0,0 +1,148 @@
1
+ ---
2
+ user-invocable: false
3
+ paths:
4
+ - "**/*cron*"
5
+ - "**/*Cron*"
6
+ - "**/*job*"
7
+ - "**/*schedule*"
8
+ description: TheoKit SDK Cron jobs API reference — Cron.create, schedule syntax, job lifecycle
9
+ ---
10
+
11
+ # TheoKit Cron Jobs
12
+
13
+ Schedule Theo agent runs on a cron expression. Two runtimes mirror the agent
14
+ split: local (in-process scheduler) and cloud (Theo PaaS, pre-release).
15
+
16
+ ## Creating a job
17
+
18
+ ```typescript
19
+ import { Cron } from "@theokit/sdk";
20
+
21
+ const job = await Cron.create({
22
+ cron: "0 9 * * *", // every day at 09:00
23
+ timezone: "America/Sao_Paulo",
24
+ message: "Summarize yesterday's commits and post to #engineering",
25
+ agent: {
26
+ apiKey: process.env.THEOKIT_API_KEY!,
27
+ model: { id: "google/gemini-2.0-flash-001" },
28
+ local: { cwd: process.cwd() },
29
+ },
30
+ });
31
+
32
+ await Cron.start(); // required for local jobs to fire
33
+ ```
34
+
35
+ ## Agent binding
36
+
37
+ Pass exactly one of:
38
+
39
+ - **`agent`** (full `AgentOptions`) — a fresh agent is created on every fire.
40
+ Use for independent runs.
41
+ - **`agentId`** (string) — reuses an existing agent's conversation context
42
+ across fires. Use for continuity (e.g., weekly review building on past notes).
43
+
44
+ Setting both raises `ConfigurationError`.
45
+
46
+ ## Cron expressions
47
+
48
+ | Format | Example | Meaning |
49
+ |---|---|---|
50
+ | 5-field POSIX | `0 9 * * *` | Minute, hour, day-of-month, month, day-of-week |
51
+ | `@hourly` | `@hourly` | Every hour at minute 0 |
52
+ | `@daily` | `@daily` | Every day at midnight |
53
+ | `@weekly` | `@weekly` | Every Sunday at midnight |
54
+ | `@monthly` | `@monthly` | First day of month at midnight |
55
+ | `@yearly` | `@yearly` | January 1 at midnight |
56
+
57
+ `timezone` accepts any IANA identifier (default: `"UTC"`). Invalid expressions
58
+ throw `ConfigurationError` synchronously at create time.
59
+
60
+ ## Managing jobs
61
+
62
+ ```typescript
63
+ const { items } = await Cron.list({ runtime: "local", cwd: process.cwd() });
64
+ const job = await Cron.get(jobId);
65
+
66
+ await Cron.disable(jobId); // pause without deleting
67
+ await Cron.enable(jobId); // resume
68
+ await Cron.delete(jobId); // permanent removal
69
+ ```
70
+
71
+ ## Manual fire (off-schedule)
72
+
73
+ ```typescript
74
+ const run = await Cron.run(jobId);
75
+
76
+ for await (const event of run.stream()) {
77
+ // same SDKMessage events as any other run
78
+ }
79
+ ```
80
+
81
+ Manual fires do not update `lastRunAt` — only scheduled fires do.
82
+
83
+ ## Local scheduler lifecycle
84
+
85
+ ```typescript
86
+ await Cron.start({ cwd: process.cwd() }); // reads .theokit/cron/jobs.json
87
+ const status = await Cron.status();
88
+ // { running: true, jobCount: 3, nextFireAt: 1747... }
89
+ await Cron.stop(); // halts scheduling; does NOT delete jobs
90
+ ```
91
+
92
+ Local jobs only fire while the host process is alive. Run as `pm2` / `systemd`
93
+ service for 24/7 local scheduling.
94
+
95
+ ## Persistence
96
+
97
+ Local cron state lives in `.theokit/cron/jobs.json`. Created automatically by
98
+ `Cron.create()`. Commit it if jobs should travel with the repo; add to
99
+ `.gitignore` if environment-specific.
100
+
101
+ ## Type reference
102
+
103
+ ```typescript
104
+ interface CronJob {
105
+ id: string;
106
+ name?: string;
107
+ cron: string;
108
+ timezone?: string;
109
+ message: string | SDKUserMessage;
110
+ agent?: AgentOptions; // mutually exclusive with agentId
111
+ agentId?: string;
112
+ enabled: boolean;
113
+ status: "scheduled" | "running" | "paused" | "errored";
114
+ runtime: "local" | "cloud";
115
+ lastRunAt?: number;
116
+ nextRunAt?: number;
117
+ createdAt: number;
118
+ }
119
+
120
+ interface CronCreateOptions {
121
+ cron: string;
122
+ message: string | SDKUserMessage;
123
+ agent?: AgentOptions;
124
+ agentId?: string;
125
+ name?: string;
126
+ timezone?: string;
127
+ enabled?: boolean; // defaults to true
128
+ apiKey?: string;
129
+ }
130
+
131
+ interface CronSchedulerStatus {
132
+ running: boolean;
133
+ jobCount: number;
134
+ nextFireAt?: number;
135
+ lastError?: { jobId: string; message: string; at: number };
136
+ }
137
+ ```
138
+
139
+ ## Cloud jobs (pre-release)
140
+
141
+ Cloud jobs use Theo PaaS and do not need `Cron.start()`. Pass `agent.cloud`
142
+ to create a cloud-scheduled job. Pre-release — not yet GA.
143
+
144
+ ## Known limitations
145
+
146
+ - Local jobs only fire while the host process is alive.
147
+ - In-flight fires are not resumed if the host process crashes mid-run.
148
+ - `Cron.run()` (manual fire) does not update `lastRunAt`.
@@ -0,0 +1,233 @@
1
+ ---
2
+ user-invocable: false
3
+ description: Dependency injection container, decorators, scopes, and modules for @theokit/di.
4
+ paths:
5
+ - "**/*container*"
6
+ - "**/*inject*"
7
+ - "**/*provider*"
8
+ - "**/*module*"
9
+ ---
10
+
11
+ # TheoKit DI -- Dependency Injection
12
+
13
+ Quick reference for `@theokit/di` -- a TypeScript DI container with decorator metadata.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @theokit/di reflect-metadata
19
+ ```
20
+
21
+ Requires `reflect-metadata` polyfill imported once at app entry and `experimentalDecorators` + `emitDecoratorMetadata` in `tsconfig.json`.
22
+
23
+ ## Core decorators
24
+
25
+ ### @Injectable
26
+
27
+ ```typescript
28
+ import { Injectable, Scope } from "@theokit/di";
29
+
30
+ @Injectable()
31
+ class UserRepository {
32
+ findById(id: string) { /* ... */ }
33
+ }
34
+
35
+ // With options:
36
+ @Injectable({ scope: Scope.REQUEST })
37
+ class RequestScopedService { /* ... */ }
38
+ ```
39
+
40
+ `InjectableOptions`:
41
+
42
+ | Option | Type | Default | Description |
43
+ |---|---|---|---|
44
+ | `scope` | `Scope` | `Scope.SINGLETON` | Lifecycle scope. |
45
+
46
+ ### @Inject
47
+
48
+ ```typescript
49
+ import { Inject } from "@theokit/di";
50
+
51
+ @Injectable()
52
+ class OrderService {
53
+ constructor(
54
+ @Inject("DATABASE_URL") private readonly dbUrl: string,
55
+ private readonly repo: UserRepository, // auto-resolved by type
56
+ ) {}
57
+ }
58
+ ```
59
+
60
+ Use `@Inject(token)` for string/symbol tokens. Constructor parameter types are auto-resolved via `reflect-metadata` when the parameter is a class.
61
+
62
+ ### @Optional
63
+
64
+ ```typescript
65
+ import { Optional } from "@theokit/di";
66
+
67
+ @Injectable()
68
+ class NotificationService {
69
+ constructor(
70
+ @Optional() private readonly sms?: SmsGateway,
71
+ ) {}
72
+ }
73
+ ```
74
+
75
+ Resolves to `undefined` instead of throwing `TokenNotFoundError` when the dependency is not registered.
76
+
77
+ ### @Qualifier
78
+
79
+ ```typescript
80
+ import { Qualifier } from "@theokit/di";
81
+
82
+ @Injectable()
83
+ class PaymentService {
84
+ constructor(
85
+ @Qualifier("stripe") private readonly gateway: PaymentGateway,
86
+ ) {}
87
+ }
88
+ ```
89
+
90
+ Disambiguates between multiple providers registered under the same interface token.
91
+
92
+ ### @Primary
93
+
94
+ ```typescript
95
+ import { Primary, Injectable } from "@theokit/di";
96
+
97
+ @Injectable()
98
+ @Primary()
99
+ class StripeGateway implements PaymentGateway { /* ... */ }
100
+ ```
101
+
102
+ Marks a provider as the default when multiple are registered for the same token. Wins over non-primary providers unless `@Qualifier` is used.
103
+
104
+ ### @PostConstruct / @PreDestroy
105
+
106
+ ```typescript
107
+ import { PostConstruct, PreDestroy, Injectable } from "@theokit/di";
108
+
109
+ @Injectable()
110
+ class DatabasePool {
111
+ @PostConstruct()
112
+ async init() { /* called after construction */ }
113
+
114
+ @PreDestroy()
115
+ async shutdown() { /* called on container.dispose() */ }
116
+ }
117
+ ```
118
+
119
+ ## Container
120
+
121
+ ```typescript
122
+ import { Container } from "@theokit/di";
123
+
124
+ const container = new Container();
125
+
126
+ // Register classes
127
+ container.register(UserRepository);
128
+ container.register(OrderService);
129
+
130
+ // Register value providers
131
+ container.register("DATABASE_URL", { useValue: "postgres://..." });
132
+
133
+ // Register factory providers
134
+ container.register("Logger", {
135
+ useFactory: (ctx) => new Logger(ctx.resolve("DATABASE_URL")),
136
+ });
137
+
138
+ // Register existing (alias)
139
+ container.register("PrimaryRepo", { useExisting: UserRepository });
140
+
141
+ // Resolve
142
+ const service = container.resolve(OrderService);
143
+ const asyncService = await container.resolveAsync(OrderService);
144
+
145
+ // Dispose (calls @PreDestroy hooks)
146
+ await container.dispose();
147
+ ```
148
+
149
+ ### ContainerOptions
150
+
151
+ ```typescript
152
+ interface ContainerOptions {
153
+ parent?: Container; // hierarchical containers
154
+ autoRegister?: boolean; // default false
155
+ }
156
+ ```
157
+
158
+ ### Provider types
159
+
160
+ ```typescript
161
+ type Provider =
162
+ | ClassProvider // { useClass: Constructor, scope? }
163
+ | ValueProvider // { useValue: any }
164
+ | FactoryProvider // { useFactory: (ctx) => any, scope? }
165
+ | ExistingProvider; // { useExisting: Token }
166
+ ```
167
+
168
+ ## Scopes
169
+
170
+ ```typescript
171
+ import { Scope } from "@theokit/di";
172
+ ```
173
+
174
+ | Scope | Behavior |
175
+ |---|---|
176
+ | `Scope.SINGLETON` | One instance per container (default). |
177
+ | `Scope.TRANSIENT` | New instance on every resolve. |
178
+ | `Scope.REQUEST` | One instance per request scope (via `container.createScope()`). |
179
+
180
+ Request scope example:
181
+
182
+ ```typescript
183
+ const requestContainer = container.createScope();
184
+ const handler = requestContainer.resolve(RequestHandler);
185
+ // All REQUEST-scoped deps share the same instance within this scope
186
+ ```
187
+
188
+ ## @Module
189
+
190
+ ```typescript
191
+ import { Module } from "@theokit/di";
192
+
193
+ @Module({
194
+ providers: [UserRepository, OrderService],
195
+ imports: [DatabaseModule],
196
+ exports: [UserRepository],
197
+ })
198
+ class UserModule {}
199
+
200
+ // Load module into container
201
+ container.loadModule(UserModule);
202
+ ```
203
+
204
+ `ModuleMetadata`:
205
+
206
+ | Field | Type | Description |
207
+ |---|---|---|
208
+ | `providers` | `Provider[]` | Classes/providers registered in this module. |
209
+ | `imports` | `Module[]` | Other modules whose exports become available. |
210
+ | `exports` | `Token[]` | Tokens visible to importing modules. |
211
+
212
+ ## Errors
213
+
214
+ | Error | Cause |
215
+ |---|---|
216
+ | `TokenNotFoundError` | Token not registered and not `@Optional`. |
217
+ | `CyclicDependencyError` | Circular dependency detected in resolution graph. |
218
+ | `MissingInjectableError` | Class used as dependency without `@Injectable`. |
219
+ | `ScopeViolationError` | Singleton depends on transient/request-scoped dep. |
220
+ | `ContainerDisposedError` | Resolve called after `container.dispose()`. |
221
+ | `ContainerFrozenError` | Register called after container is frozen. |
222
+ | `AsyncProviderInSyncResolveError` | Async factory used with `resolve()` instead of `resolveAsync()`. |
223
+ | `ReflectMetadataMissingError` | `reflect-metadata` polyfill not imported. |
224
+ | `CyclicModuleImportError` | Circular module imports detected. |
225
+ | `InvalidModuleError` | Module class missing `@Module` decorator. |
226
+ | `InvalidExportError` | Module exports a token not in its providers. |
227
+
228
+ ## Graph analysis
229
+
230
+ ```typescript
231
+ const graph: DependencyGraph = container.analyzeGraph();
232
+ // Useful for debugging dependency chains and detecting issues
233
+ ```