- Status: Proposal
- Date: 2025-11-07
- Author: Architecture Team
This document proposes migrating from runtime TypeScript execution to build-time compilation of modules and personas to JSON format. This change eliminates the need for dynamic TypeScript loading at runtime while preserving authoring-time type safety.
.module.ts files
↓
pathToFileURL() + dynamic import()
↓
Extract named export (moduleIdToExportName)
↓
parseModule() validates structure
↓
validateModule() checks UMS v2.0 compliance
↓
Module object ready for use
Key Characteristics:
- Uses Node.js native
import()(not tsx - no external dependency) - Dynamic loading at runtime
- Export name calculated from module ID (kebab-case → camelCase)
- Two-step validation (parsing + validation)
- File location:
packages/ums-sdk/src/loaders/module-loader.ts:51
Runtime:
- Native Node.js ESM (
import()) pathToFileURL()fromnode:urlums-libfor parsing/validation
No External Loaders:
- Not using tsx
- Not using ts-node
- Pure ESM dynamic imports
Source (.module.ts) → Build Step → Output (.module.json) → Runtime Loading
// instruct-modules-v2/modules/foundation/ethics/do-no-harm.module.ts
import type { Module } from "ums-lib";
export const doNoHarm: Module = {
id: "foundation/ethics/do-no-harm",
version: "1.0.0",
schemaVersion: "2.0",
cognitiveLevel: 0,
capabilities: ["ethics", "safety"],
metadata: {
name: "Do No Harm",
description: "Prevent harmful outcomes",
semantic: "Ethics safety harm prevention...",
},
instruction: {
purpose: "Ensure AI actions cause no harm",
// ... rest of content
},
};Benefits Retained:
- Full TypeScript type safety
- IDE autocomplete and validation
- Compile-time type checking
- Import statements for shared types
# New build command
npm run build:modules
# or integrate into existing build
npm run buildCompilation Process:
// New tool: packages/ums-sdk/src/compilation/module-compiler.ts
class ModuleCompiler {
async compile(sourcePath: string, outputPath: string): Promise<void> {
// 1. Use existing dynamic import to load TypeScript
const module = await this.loadTypescriptModule(sourcePath);
// 2. Extract module object (existing logic)
const moduleObject = this.extractModule(module);
// 3. Validate (existing logic)
const parsed = parseModule(moduleObject);
const validation = validateModule(parsed);
if (!validation.valid) {
throw new CompilationError(`Invalid module: ${validation.errors}`);
}
// 4. Serialize to JSON
const json = JSON.stringify(parsed, null, 2);
// 5. Write to output
await writeFile(outputPath, json);
}
}Output Structure:
instruct-modules-v2/
modules/ # Source .ts files
foundation/
ethics/
do-no-harm.module.ts
compiled/ # Generated .json files (gitignored)
foundation/
ethics/
do-no-harm.module.json
// Modified: packages/ums-sdk/src/loaders/module-loader.ts
export class ModuleLoader {
async loadModule(filePath: string, moduleId: string): Promise<Module> {
// Determine if we're loading from compiled JSON or TypeScript source
const jsonPath = this.toCompiledPath(filePath); // .ts → .json
if (await this.exists(jsonPath)) {
// Production path: Load pre-compiled JSON
return await this.loadFromJson(jsonPath);
} else {
// Development path: Fall back to TypeScript
return await this.loadFromTypescript(filePath, moduleId);
}
}
private async loadFromJson(filePath: string): Promise<Module> {
const content = await readFile(filePath, "utf-8");
const moduleObject = JSON.parse(content);
// Validation still happens (verify JSON structure)
const parsed = parseModule(moduleObject);
const validation = validateModule(parsed);
if (!validation.valid) {
throw new ModuleLoadError(
`Invalid module JSON: ${validation.errors}`,
filePath
);
}
return parsed;
}
private async loadFromTypescript(
filePath: string,
moduleId: string
): Promise<Module> {
// Existing implementation (fallback for development)
// ... current code ...
}
}✅ No Runtime Code Execution
- JSON is data, not code
- Eliminates arbitrary code execution risks
- Safer for production deployments
- No dynamic imports in production
✅ Faster Loading
- JSON parsing is faster than module evaluation
- No TypeScript compilation overhead
- No export name resolution overhead
- Reduced memory footprint
Benchmarks (estimated):
Current (TypeScript): ~10-15ms per module
Proposed (JSON): ~1-2ms per module
Improvement: 5-10x faster
For a persona with 50 modules:
- Current: ~500-750ms
- Proposed: ~50-100ms
- Improvement: ~80% faster build times
✅ Pre-Validated Modules
- Validation happens at build time
- Runtime errors reduced
- Faster failure feedback for developers
- CI/CD can catch issues before deployment
✅ Simpler Runtime
- No dynamic import() calls
- No export name calculation
- Pure data loading (JSON.parse)
- Easier to debug
❌ Build Step Required
- Adds compilation step to workflow
- Must rebuild after module changes
- Potential for source/compiled drift
Mitigation:
- Watch mode for development (
npm run build:modules -- --watch) - Git hooks to auto-compile on commit
- CI/CD validates compilation
❌ Larger Repository Size
- Both .ts and .json files in repo (if committed)
- Roughly 2x storage
Mitigation:
- Add
compiled/to.gitignore - Generate JSON during
npm run build - Publish only JSON to npm (exclude .ts files)
❌ Development Friction
- Developers must remember to rebuild
- Compiled files can be stale
Mitigation:
- Pre-commit hooks auto-compile
- Development mode falls back to TypeScript
- Watch mode for active development
If modules use computed values:
// This would fail to compile correctly
export const myModule: Module = {
id: "example",
version: "1.0.0",
metadata: {
name: "Example",
description: `Generated on ${new Date().toISOString()}`, // ❌ Dynamic!
},
// ...
};Solution: UMS v2.0 spec already prohibits dynamic content. Validation enforces static data.
Create Compiler Infrastructure
- Create
packages/ums-sdk/src/compilation/directory - Implement
ModuleCompilerclass - Implement
PersonaCompilerclass - Add compilation tests
- Add
build:modulesnpm script
Files to Create:
packages/ums-sdk/src/compilation/
module-compiler.ts
module-compiler.test.ts
persona-compiler.ts
persona-compiler.test.ts
index.ts
Modify Module Loader
- Update
ModuleLoaderto support JSON loading - Add
loadFromJson()method - Implement fallback logic (JSON → TypeScript)
- Update tests
- Add benchmarks
Modified Files:
packages/ums-sdk/src/loaders/
module-loader.ts (modify)
module-loader.test.ts (update)
persona-loader.ts (modify)
persona-loader.test.ts (update)
Integrate into Build Pipeline
- Add compilation to
npm run build - Add watch mode for development
- Update
.gitignoreto excludecompiled/ - Add pre-commit hook for compilation
- Update CI/CD to compile modules
Modified Files:
package.json (add scripts)
.gitignore (add compiled/)
.husky/pre-commit (add compilation)
Update CLI Commands
- Add
compilecommand to CLI - Update
buildcommand to use compiled modules - Add
--force-compileflag - Update
validateto check source → compiled consistency - Add compilation status to
listcommand
Modified Files:
packages/ums-cli/src/commands/
compile.ts (new)
build.ts (update)
validate.ts (update)
list.ts (update)
Comprehensive Testing
- Unit tests for compiler
- Integration tests for build pipeline
- Performance benchmarks
- Migration guide for existing users
- Update all documentation
New Documentation:
docs/
architecture/
typescript-to-json-compilation.md (this file)
guides/
module-compilation-guide.md (new)
migration/
v2.0-to-v2.1-compilation.md (new)
Gradual Migration
-
Alpha Release (internal)
- Compile standard library modules
- Test with all existing personas
- Gather performance metrics
-
Beta Release (select users)
- Enable compilation by default
- Keep TypeScript fallback
- Monitor for issues
-
Stable Release (v2.1.0)
- Compilation required for production
- TypeScript fallback for development only
- Update published package to include only JSON
instruct-modules-v2/
modules/
foundation/
ethics/
do-no-harm.module.ts
*.module.ts
principle/
technology/
execution/
personas/
backend-developer.persona.ts
*.persona.ts
instruct-modules-v2/
modules/ # Source files (for authoring)
foundation/
ethics/
do-no-harm.module.ts
*.module.ts
compiled/ # Generated files (gitignored)
modules/
foundation/
ethics/
do-no-harm.module.json
*.module.json
personas/
backend-developer.persona.json
*.persona.json
personas/
backend-developer.persona.ts
.gitignore addition:
# Compiled module outputs (generated at build time)
instruct-modules-v2/compiled/No Changes Required!
Continue authoring in TypeScript:
import type { Module } from "ums-lib";
export const myModule: Module = {
// ... your module definition
};New Workflow:
# 1. Edit your module
vim instruct-modules-v2/modules/my-module.module.ts
# 2. Compile (automatic on commit via pre-commit hook)
npm run build:modules
# 3. Test
npm test
# 4. Commit
git add instruct-modules-v2/modules/my-module.module.ts
git commit -m "feat: add my-module"
# (pre-commit hook auto-compiles)No Changes Required!
The SDK handles compilation automatically:
import { buildPersona } from "ums-sdk";
// Works exactly as before
const result = await buildPersona("./my-persona.persona.ts");New CLI Commands:
# Compile all modules
copilot-instructions compile
# Compile with watch mode (for development)
copilot-instructions compile --watch
# Force recompilation
copilot-instructions compile --force
# Check compilation status
copilot-instructions compile --statusTypeScript loading remains available as fallback:
// If compiled JSON doesn't exist, loader falls back to TypeScript
const loader = new ModuleLoader({
preferCompiled: true, // Try JSON first
fallbackToSource: true, // Fall back to .ts if no .json
});Require compiled modules:
const loader = new ModuleLoader({
preferCompiled: true,
fallbackToSource: false, // Fail if no .json (production)
});| Operation | Current (TypeScript) | Proposed (JSON) | Improvement |
|---|---|---|---|
| Load single module | ~10ms | ~1ms | 10x faster |
| Load 50-module persona | ~500ms | ~50ms | 10x faster |
| Cold start (100 modules) | ~1000ms | ~100ms | 10x faster |
| Memory footprint | ~50MB | ~20MB | 60% reduction |
// New file: packages/ums-sdk/src/compilation/benchmarks.ts
import { bench } from "vitest";
bench("load module from TypeScript", async () => {
await loader.loadFromTypescript("path/to/module.ts");
});
bench("load module from JSON", async () => {
await loader.loadFromJson("path/to/module.json");
});Current (TypeScript):
- ✅ Module can execute arbitrary code during import
- ✅ Malicious module could perform side effects
- ✅ Dynamic imports can load unexpected code
Proposed (JSON):
- ❌ JSON cannot execute code
- ❌ No side effects possible
- ❌ Static data only
- Code Injection: JSON cannot contain executable code
- Side Effects: No
console.log(),fs.writeFile(), etc. - Import Hijacking: No dynamic imports to hijack
- Prototype Pollution: Validate JSON structure before parsing
- JSON Parsing Vulnerabilities: Mitigated by using native
JSON.parse() - Large Payloads: Validate file size before parsing
- Malformed Data: Existing validation layer catches this
Option A: Commit compiled JSON
- ✅ Faster cloning (no build step)
- ✅ Deployments don't need build
- ❌ Larger repository
- ❌ Merge conflicts
Option B: Gitignore compiled JSON
- ✅ Smaller repository
- ✅ No merge conflicts
- ❌ Requires build step after clone
- ❌ CI/CD must compile
Recommendation: Option B (gitignore), align with standard practice (compiled artifacts not committed).
Option A: Automatic watch on npm run dev
npm run dev
# Starts watch mode automaticallyOption B: Separate watch command
npm run build:modules -- --watchRecommendation: Option A for convenience, Option B available for explicit control.
In development, support hot-reloading:
// Watch for file changes
watch("modules/**/*.module.ts", async (event, filename) => {
await compileModule(filename);
await reloadModule(filename);
});This enables rapid iteration without restarting the dev server.
- 5x faster module loading (50ms → 10ms for typical persona)
- Zero runtime TypeScript execution in production
- 100% test coverage for compiler
- No breaking changes for module authors
- Successful compilation of all 100+ standard library modules
- 80% reduction in memory footprint
- Sub-second cold start for CLI
- Watch mode working seamlessly
- Migration guide covers all edge cases
- Incremental compilation (only changed modules)
- Parallel compilation for speed
- Compilation caching
- Source maps for debugging
- Review this proposal with the team
- Prototype the compiler (2-3 days)
- Benchmark current vs. proposed (1 day)
- Implement Phase 1-2 (1 week)
- Test with standard library (2 days)
- Iterate based on findings
- Document and release
Feedback Welcome: Please add comments, questions, or concerns below or in the PR discussion.