multimodel-dev-os 1.1.0 → 2.0.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.
Files changed (63) hide show
  1. package/.ai/adapters/custom-adapter.example.yaml +9 -0
  2. package/.ai/adapters/registry.yaml +56 -0
  3. package/.ai/models/README.md +14 -0
  4. package/.ai/models/local-models.yaml +20 -0
  5. package/.ai/models/providers.yaml +29 -0
  6. package/.ai/models/registry.yaml +73 -0
  7. package/.ai/models/routing-presets.yaml +23 -0
  8. package/.ai/skills/custom-skill.example.md +15 -0
  9. package/.ai/templates/custom-template.example.yaml +19 -0
  10. package/.ai/templates/registry.yaml +522 -0
  11. package/README.md +30 -18
  12. package/bin/multimodel-dev-os.js +835 -92
  13. package/docs/.vitepress/config.js +36 -1
  14. package/docs/adapter-authoring.md +46 -0
  15. package/docs/agent-compatibility.md +51 -0
  16. package/docs/cli-roadmap.md +12 -23
  17. package/docs/faq.md +1 -1
  18. package/docs/final-launch.md +5 -4
  19. package/docs/index.md +5 -5
  20. package/docs/local-models.md +48 -0
  21. package/docs/mobile-android.md +75 -0
  22. package/docs/model-compatibility.md +65 -0
  23. package/docs/model-routing.md +45 -0
  24. package/docs/npm-publishing.md +38 -11
  25. package/docs/package-safety.md +29 -0
  26. package/docs/provider-strategy.md +44 -0
  27. package/docs/public/llms-full.txt +82 -73
  28. package/docs/public/llms.txt +36 -34
  29. package/docs/public/sitemap.xml +51 -1
  30. package/docs/quickstart.md +11 -18
  31. package/docs/registry-contribution.md +20 -0
  32. package/docs/release-policy.md +9 -0
  33. package/docs/skill-authoring.md +56 -0
  34. package/docs/template-authoring.md +65 -0
  35. package/docs/templates-guide.md +3 -3
  36. package/docs/token-optimization.md +27 -0
  37. package/docs/v2-migration.md +31 -0
  38. package/docs/v2-release-checklist.md +30 -0
  39. package/docs/v2-roadmap.md +95 -0
  40. package/examples/expo-react-native-android/.ai/config.yaml +22 -0
  41. package/examples/expo-react-native-android/.ai/context/architecture.md +18 -0
  42. package/examples/expo-react-native-android/.ai/context/context-budget.md +4 -0
  43. package/examples/expo-react-native-android/.ai/context/model-map.md +6 -0
  44. package/examples/expo-react-native-android/.ai/context/project-brief.md +9 -0
  45. package/examples/expo-react-native-android/.ai/session-logs/.gitkeep +1 -0
  46. package/examples/expo-react-native-android/.ai/skills/expo-android-build.md +11 -0
  47. package/examples/expo-react-native-android/AGENTS.md +20 -0
  48. package/examples/expo-react-native-android/MEMORY.md +13 -0
  49. package/examples/expo-react-native-android/README.md +101 -0
  50. package/examples/expo-react-native-android/RUNBOOK.md +36 -0
  51. package/examples/expo-react-native-android/TASKS.md +14 -0
  52. package/examples/expo-react-native-android/app.config.ts +40 -0
  53. package/examples/expo-react-native-android/app.json +34 -0
  54. package/examples/expo-react-native-android/eas.json +26 -0
  55. package/examples/expo-react-native-android/jest.config.js +11 -0
  56. package/examples/expo-react-native-android/src/app/_layout.tsx +89 -0
  57. package/examples/expo-react-native-android/src/lib/secure-storage.ts +63 -0
  58. package/examples/expo-react-native-android/src/services/api-client.ts +106 -0
  59. package/package.json +3 -2
  60. package/scripts/install.ps1 +230 -230
  61. package/scripts/install.sh +1 -1
  62. package/scripts/prepublish-guard.js +43 -0
  63. package/scripts/verify.js +178 -1
@@ -13,7 +13,7 @@ const __filename = fileURLToPath(import.meta.url);
13
13
  const __dirname = dirname(__filename);
14
14
  const sourceRoot = resolve(__dirname, '..');
15
15
 
16
- let version = '0.5.1';
16
+ let version = '2.0.1';
17
17
  try {
18
18
  const pkgData = JSON.parse(readFileSync(resolve(sourceRoot, 'package.json'), 'utf8'));
19
19
  version = pkgData.version;
@@ -31,7 +31,18 @@ function parseArgs(args) {
31
31
  caveman: false,
32
32
  dryRun: false,
33
33
  force: false,
34
- help: false
34
+ help: false,
35
+ tokens: false,
36
+ modelPreset: null,
37
+ agent: null,
38
+ stack: null,
39
+ mobile: null,
40
+ aiApp: null,
41
+ json: false,
42
+ threshold: null,
43
+ registry: null,
44
+ allRegistries: false,
45
+ release: false
35
46
  };
36
47
 
37
48
  for (let i = 0; i < args.length; i++) {
@@ -50,6 +61,28 @@ function parseArgs(args) {
50
61
  params.force = true;
51
62
  } else if (arg === '--help' || arg === '-h') {
52
63
  params.help = true;
64
+ } else if (arg === '--tokens') {
65
+ params.tokens = true;
66
+ } else if (arg === '--all-registries') {
67
+ params.allRegistries = true;
68
+ } else if (arg === '--release') {
69
+ params.release = true;
70
+ } else if (arg === '--json') {
71
+ params.json = true;
72
+ } else if (arg === '--threshold') {
73
+ params.threshold = args[++i];
74
+ } else if (arg === '--registry') {
75
+ params.registry = args[++i];
76
+ } else if (arg === '--model-preset') {
77
+ params.modelPreset = args[++i];
78
+ } else if (arg === '--agent') {
79
+ params.agent = args[++i];
80
+ } else if (arg === '--stack') {
81
+ params.stack = args[++i];
82
+ } else if (arg === '--mobile') {
83
+ params.mobile = args[++i];
84
+ } else if (arg === '--ai-app') {
85
+ params.aiApp = args[++i];
53
86
  } else if (!params.command && !arg.startsWith('-')) {
54
87
  params.command = arg;
55
88
  }
@@ -60,43 +93,41 @@ function parseArgs(args) {
60
93
  const params = parseArgs(ARGS);
61
94
  const COMMAND = params.command;
62
95
 
63
- const TEMPLATES = {
64
- 'nextjs-saas': {
65
- name: 'nextjs-saas',
66
- description: 'Next.js App Router starter with TypeScript, Prisma database, Tailwind CSS, and Stripe subscription setup.',
67
- stack: 'Next.js 14, React 18, TypeScript, Tailwind CSS, Prisma ORM, Stripe payments',
68
- skill: 'nextjs-action-build.md',
69
- skillDesc: 'React Server Actions secure implementation conventions.'
70
- },
71
- 'wordpress-site': {
72
- name: 'wordpress-site',
73
- description: 'WordPress custom block theme and plugin development profile with secure PHP database query rules.',
74
- stack: 'WordPress Core, PHP, Gutenberg Block APIs, theme customization hooks',
75
- skill: 'plugin-boilerplate.md',
76
- skillDesc: 'PHP hook registrations and sanitization gates boilerplate.'
77
- },
78
- 'ecommerce-store': {
79
- name: 'ecommerce-store',
80
- description: 'PCI-compliant headless e-commerce store with secure checkout loops, card state validations, and Stripe webhooks.',
81
- stack: 'Headless Store API, cart states, secure payment webhooks, order database triggers',
82
- skill: 'webhook-handler.md',
83
- skillDesc: 'Stripe order checkout webhook secure listener verification rules.'
84
- },
85
- 'seo-landing-page': {
86
- name: 'seo-landing-page',
87
- description: 'Ultra-fast static landing page layout optimized for Astro, high Core Web Vitals scores, and JSON-LD schema markup.',
88
- stack: 'Astro, HTML5, structured JSON-LD SEO markup, asset minification frameworks',
89
- skill: 'seo-audit.md',
90
- skillDesc: 'Lighthouse audits optimization guidelines and Core Web Vitals targets.'
91
- },
92
- 'general-app': {
93
- name: 'general-app',
94
- description: 'Baseline generic fallback profile for standard backend systems (Python, Go, Node, Rust) and universal git workflows.',
95
- stack: 'Universal backends baseline structure, default git flow parameters',
96
- skill: 'example-skill.md',
97
- skillDesc: 'Generic baseline instructions and coding standards.'
98
- }
99
- };
96
+ function loadTemplates(customPath) {
97
+ let path = customPath || join(sourceRoot, '.ai', 'templates', 'registry.yaml');
98
+ try {
99
+ if (existsSync(path)) {
100
+ const templatesRegistry = parseYaml(readFileSync(path, 'utf8'));
101
+ return templatesRegistry.templates || {};
102
+ }
103
+ } catch (e) {}
104
+ return {
105
+ 'general-app': {
106
+ name: 'general-app',
107
+ description: 'Baseline generic fallback profile for standard backend systems.',
108
+ stack: 'Universal backends baseline structure',
109
+ skill: 'example-skill.md',
110
+ skillDesc: 'Generic baseline instructions and coding standards.',
111
+ status: 'stable',
112
+ maturity: 'production-ready',
113
+ required_files: ['AGENTS.md', 'MEMORY.md', 'TASKS.md', 'RUNBOOK.md', '.ai/config.yaml']
114
+ }
115
+ };
116
+ }
117
+
118
+ function loadAdapters(customPath) {
119
+ let path = customPath || join(sourceRoot, '.ai', 'adapters', 'registry.yaml');
120
+ try {
121
+ if (existsSync(path)) {
122
+ const adaptersRegistry = parseYaml(readFileSync(path, 'utf8'));
123
+ return adaptersRegistry.adapters || {};
124
+ }
125
+ } catch (e) {}
126
+ return {};
127
+ }
128
+
129
+ const TEMPLATES = loadTemplates(params.registry);
130
+ const ADAPTERS = loadAdapters(params.registry);
100
131
 
101
132
  if (params.help || !COMMAND) {
102
133
  showHelp();
@@ -104,11 +135,16 @@ if (params.help || !COMMAND) {
104
135
  }
105
136
 
106
137
  if (COMMAND === 'init') {
138
+ if (params.mobile === 'android') {
139
+ params.template = 'expo-react-native-android';
140
+ } else if (params.aiApp === 'rag') {
141
+ params.template = 'rag-knowledge-base';
142
+ }
107
143
  handleInit(params);
108
144
  } else if (COMMAND === 'verify') {
109
145
  handleVerify(params);
110
146
  } else if (COMMAND === 'templates' || COMMAND === 'list-templates') {
111
- handleListTemplates();
147
+ handleListTemplates(params);
112
148
  } else if (COMMAND === 'show-template') {
113
149
  const tName = ARGS[1];
114
150
  if (!tName || tName.startsWith('-')) {
@@ -120,6 +156,63 @@ if (COMMAND === 'init') {
120
156
  handleDoctor(params);
121
157
  } else if (COMMAND === 'validate') {
122
158
  handleValidate(params);
159
+ } else if (COMMAND === 'validate-template') {
160
+ const tName = ARGS[1];
161
+ if (!tName || tName.startsWith('-')) {
162
+ console.error('\x1b[31mError: Please specify a template name. Example: node bin/multimodel-dev-os.js validate-template nextjs-saas\x1b[0m');
163
+ process.exit(1);
164
+ }
165
+ handleValidateTemplate(tName);
166
+ } else if (COMMAND === 'validate-adapter') {
167
+ const aName = ARGS[1];
168
+ if (!aName || aName.startsWith('-')) {
169
+ console.error('\x1b[31mError: Please specify an adapter name. Example: node bin/multimodel-dev-os.js validate-adapter cursor\x1b[0m');
170
+ process.exit(1);
171
+ }
172
+ handleValidateAdapter(aName);
173
+ } else if (COMMAND === 'validate-skill') {
174
+ const sName = ARGS[1];
175
+ if (!sName || sName.startsWith('-')) {
176
+ console.error('\x1b[31mError: Please specify a skill name. Example: node bin/multimodel-dev-os.js validate-skill custom-skill.example\x1b[0m');
177
+ process.exit(1);
178
+ }
179
+ handleValidateSkill(sName, params);
180
+ } else if (COMMAND === 'models') {
181
+ handleListModels(params);
182
+ } else if (COMMAND === 'show-model') {
183
+ const mName = ARGS[1];
184
+ if (!mName || mName.startsWith('-')) {
185
+ console.error('\x1b[31mError: Please specify a model name. Example: node bin/multimodel-dev-os.js show-model claude-sonnet-latest\x1b[0m');
186
+ process.exit(1);
187
+ }
188
+ handleShowModel(mName);
189
+ } else if (COMMAND === 'providers') {
190
+ handleListProviders();
191
+ } else if (COMMAND === 'route-model') {
192
+ const taskName = ARGS[1];
193
+ if (!taskName || taskName.startsWith('-')) {
194
+ console.error('\x1b[31mError: Please specify a task. Example: node bin/multimodel-dev-os.js route-model planning\x1b[0m');
195
+ process.exit(1);
196
+ }
197
+ handleRouteModel(taskName);
198
+ } else if (COMMAND === 'adapters') {
199
+ handleListAdapters(params);
200
+ } else if (COMMAND === 'show-adapter') {
201
+ const aName = ARGS[1];
202
+ if (!aName || aName.startsWith('-')) {
203
+ console.error('\x1b[31mError: Please specify an adapter name. Example: node bin/multimodel-dev-os.js show-adapter cursor\x1b[0m');
204
+ process.exit(1);
205
+ }
206
+ handleShowAdapter(aName);
207
+ } else if (COMMAND === 'skills') {
208
+ handleListSkills(params);
209
+ } else if (COMMAND === 'show-skill') {
210
+ const sName = ARGS[1];
211
+ if (!sName || sName.startsWith('-')) {
212
+ console.error('\x1b[31mError: Please specify a skill name. Example: node bin/multimodel-dev-os.js show-skill bug-fix\x1b[0m');
213
+ process.exit(1);
214
+ }
215
+ handleShowSkill(sName, params);
123
216
  } else {
124
217
  console.error(`\x1b[31mUnknown command: ${COMMAND}\x1b[0m`);
125
218
  showHelp();
@@ -137,23 +230,42 @@ function showHelp() {
137
230
  console.log(' list-templates Alias for templates command');
138
231
  console.log(' show-template <t> Inspect detailed stack specifications of template <t>');
139
232
  console.log(' doctor Advisory checkup of project compatibility loops and ignored folders');
140
- console.log(' validate Strict validation checks to verify directory schema compliance\n');
233
+ console.log(' validate Strict validation checks to verify directory schema compliance');
234
+ console.log(' validate-template Validate registry keys and source folder files for template');
235
+ console.log(' validate-adapter Validate registry keys and source assets for IDE adapter');
236
+ console.log(' validate-skill Verify custom skill conforms to core prompt structure');
237
+ console.log(' models List registered model aliases in the capabilities registry');
238
+ console.log(' show-model <m> View specifications of model <m> in registry');
239
+ console.log(' providers List configured AI provider API endpoints');
240
+ console.log(' route-model <tsk> Suggest optimal model mapping for task <tsk>');
241
+ console.log(' adapters List IDE and terminal tool adapters');
242
+ console.log(' show-adapter <a> Inspect config specifications of adapter <a>');
243
+ console.log(' skills List active skills custom prompts in target workspace');
244
+ console.log(' show-skill <s> View prompt contents of target workspace skill <s>\n');
141
245
  console.log('Options:');
142
246
  console.log(' -t, --target <path> Target folder destination (default: current working directory)');
143
- console.log(' --template <name> Template profile: nextjs-saas, wordpress-site, ecommerce-store,');
144
- console.log(' seo-landing-page, general-app (default: general-app)');
145
- console.log(' -a, --adapter <name> Inject specific adapter: codex, antigravity, cursor, claude, gemini, vscode');
247
+ console.log(' --template <name> Template profile: nextjs-saas, expo-react-native-android, etc.');
248
+ console.log(' -a, --adapter <name> Inject specific adapter: cursor, claude, vscode, gemini, etc.');
146
249
  console.log(' --caveman Use minimal-token templates (~79% fewer tokens)');
250
+ console.log(' --tokens Run a deeper token-sink size analysis during doctor checkup');
251
+ console.log(' --json Output raw JSON data for listing commands (models, adapters, templates)');
252
+ console.log(' --threshold <val> Set custom size threshold for doctor tokens checks (e.g. 50KB)');
253
+ console.log(' --registry <path> Override default registry (for templates/adapters list or check)');
147
254
  console.log(' -d, --dry-run Preview planned file actions without modifying the filesystem');
148
255
  console.log(' -f, --force Overwrite existing files without prompting\n');
149
256
  }
150
257
 
151
- function handleListTemplates() {
258
+ function handleListTemplates(options) {
259
+ if (options && options.json) {
260
+ console.log(JSON.stringify(TEMPLATES, null, 2));
261
+ return;
262
+ }
152
263
  console.log(`\n🧠 \x1b[36mBuilt-in Template Profiles [v${version}]\x1b[0m`);
153
264
  console.log('==================================================');
154
265
  Object.keys(TEMPLATES).forEach(key => {
155
266
  const t = TEMPLATES[key];
156
- console.log(`\n\x1b[32m* ${t.name}\x1b[0m`);
267
+ const statusStr = t.status === 'planned' ? ' (Planned)' : t.status === 'experimental' ? ' (Experimental)' : '';
268
+ console.log(`\n\x1b[32m* ${t.name}${statusStr}\x1b[0m`);
157
269
  console.log(` \x1b[33mStack:\x1b[0m ${t.stack}`);
158
270
  console.log(` \x1b[37mDescription:\x1b[0m ${t.description}`);
159
271
  });
@@ -163,16 +275,20 @@ function handleListTemplates() {
163
275
  function handleShowTemplate(name) {
164
276
  const t = TEMPLATES[name];
165
277
  if (!t) {
166
- console.error(`\n\x1b[31mError: Template '${name}' does not exist. Available: nextjs-saas, wordpress-site, ecommerce-store, seo-landing-page, general-app\x1b[0m\n`);
278
+ const available = Object.keys(TEMPLATES).join(', ');
279
+ console.error(`\n\x1b[31mError: Template '${name}' does not exist. Available: ${available}\x1b[0m\n`);
167
280
  process.exit(1);
168
281
  }
169
282
 
170
- console.log(`\n🔍 \x1b[36mTemplate Profile: ${t.name}\x1b[0m`);
283
+ const statusStr = t.status === 'planned' ? ' (Planned)' : t.status === 'experimental' ? ' (Experimental)' : ' (Stable)';
284
+ console.log(`\n🔍 \x1b[36mTemplate Profile: ${t.name}${statusStr}\x1b[0m`);
171
285
  console.log('==================================================');
172
286
  console.log(`\x1b[33mStack Blueprint:\x1b[0m ${t.stack}`);
173
287
  console.log(`\x1b[33mOverview:\x1b[0m ${t.description}`);
174
- console.log(`\x1b[33mHighlighted Skill:\x1b[0m .ai/skills/${t.skill}`);
175
- console.log(` └─> ${t.skillDesc}`);
288
+ if (t.skill) {
289
+ console.log(`\x1b[33mHighlighted Skill:\x1b[0m .ai/skills/${t.skill}`);
290
+ console.log(` └─> ${t.skillDesc}`);
291
+ }
176
292
  console.log('\n\x1b[33mScaffolding Directory Layout:\x1b[0m');
177
293
  console.log(' ├── AGENTS.md (Stack building conventions)');
178
294
  console.log(' ├── MEMORY.md (Architectural constraints record)');
@@ -186,12 +302,26 @@ function handleShowTemplate(name) {
186
302
  console.log(' │ ├── model-map.md (AI routing specifications)');
187
303
  console.log(' │ └── context-budget.md (Token allocation guidelines)');
188
304
  console.log(` └── skills/`);
189
- console.log(` └── ${t.skill} (Custom template skills code boiler)`);
305
+ if (t.skill) {
306
+ console.log(` └── ${t.skill} (Custom template skills code boiler)`);
307
+ } else {
308
+ console.log(` └── [custom-skill].md (Custom template skills code boiler)`);
309
+ }
190
310
  console.log('\nUse \x1b[32minit --template ' + t.name + '\x1b[0m to bootstrap this profile.\n');
191
311
  }
192
312
 
193
313
  function handleInit(options) {
194
314
  console.log(`\n\x1b[34mInitializing multimodel-dev-os in: ${options.target}\x1b[0m`);
315
+
316
+ // Check if requested template is planned
317
+ const tInfo = TEMPLATES[options.template];
318
+ if (tInfo && tInfo.status === 'planned') {
319
+ console.warn(` \x1b[33m[WARNING] Template '${options.template}' is planned for a future release and is not yet available.\x1b[0m`);
320
+ console.warn(` To view available templates, run: \x1b[36mnpx multimodel-dev-os templates\x1b[0m`);
321
+ console.warn(` Falling back to the stable \x1b[32m'general-app'\x1b[0m profile...\n`);
322
+ options.template = 'general-app';
323
+ }
324
+
195
325
  console.log(`Template profile: \x1b[32m${options.template}\x1b[0m`);
196
326
  if (options.caveman) console.log('Bone variant: \x1b[33mCaveman Mode Active\x1b[0m');
197
327
  if (options.dryRun) console.log('\x1b[36mDry Run active - no actual modifications will occur\x1b[0m');
@@ -202,7 +332,9 @@ function handleInit(options) {
202
332
  // Source path mapping for core files
203
333
  let templateDir = join(sourceRoot, 'examples', options.template);
204
334
  if (!existsSync(templateDir)) {
205
- console.warn(` \x1b[33m[WARNING] Template '${options.template}' not found. Falling back to 'general-app' profile.\x1b[0m`);
335
+ console.warn(` \x1b[33m[WARNING] Template '${options.template}' source files could not be found.\x1b[0m`);
336
+ console.warn(` To view available templates, run: \x1b[36mnpx multimodel-dev-os templates\x1b[0m`);
337
+ console.warn(` Falling back to the stable \x1b[32m'general-app'\x1b[0m profile...\n`);
206
338
  templateDir = join(sourceRoot, 'examples', 'general-app');
207
339
  }
208
340
 
@@ -337,44 +469,15 @@ function handleInit(options) {
337
469
  // Copy root-level adapter rule files if selected
338
470
  if (!options.dryRun) {
339
471
  options.adapters.forEach(adapter => {
340
- if (adapter === 'cursor') {
341
- const srcFile = join(sourceRoot, 'adapters/cursor/.cursorrules');
342
- const destFile = join(options.target, '.cursorrules');
343
- if (existsSync(srcFile)) {
344
- writeFileSync(destFile, readFileSync(srcFile));
345
- console.log(` \x1b[32mCREATE ROOT ADAPTER FILE:\x1b[0m .cursorrules`);
346
- }
347
- } else if (adapter === 'claude') {
348
- const srcFile = join(sourceRoot, 'adapters/claude/CLAUDE.md');
349
- const destFile = join(options.target, 'CLAUDE.md');
350
- if (existsSync(srcFile)) {
351
- writeFileSync(destFile, readFileSync(srcFile));
352
- console.log(` \x1b[32mCREATE ROOT ADAPTER FILE:\x1b[0m CLAUDE.md`);
353
- }
354
- } else if (adapter === 'vscode') {
355
- const srcFile = join(sourceRoot, 'adapters/vscode/.vscode/settings.json');
356
- const destDir = join(options.target, '.vscode');
357
- const destFile = join(destDir, 'settings.json');
472
+ const a = ADAPTERS[adapter];
473
+ if (a && a.rules_file) {
474
+ const srcFile = join(sourceRoot, 'adapters', adapter, a.rules_file);
475
+ const destFile = join(options.target, a.rules_file);
476
+ const destDir = dirname(destFile);
358
477
  if (existsSync(srcFile)) {
359
478
  if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
360
479
  writeFileSync(destFile, readFileSync(srcFile));
361
- console.log(` \x1b[32mCREATE ROOT ADAPTER FILE:\x1b[0m .vscode/settings.json`);
362
- }
363
- } else if (adapter === 'gemini') {
364
- const srcFile = join(sourceRoot, 'adapters/gemini/GEMINI.md');
365
- const destFile = join(options.target, 'GEMINI.md');
366
- if (existsSync(srcFile)) {
367
- writeFileSync(destFile, readFileSync(srcFile));
368
- console.log(` \x1b[32mCREATE ROOT ADAPTER FILE:\x1b[0m GEMINI.md`);
369
- }
370
- } else if (adapter === 'antigravity') {
371
- const srcFile = join(sourceRoot, 'adapters/antigravity/.gemini/settings.json');
372
- const destDir = join(options.target, '.gemini');
373
- const destFile = join(destDir, 'settings.json');
374
- if (existsSync(srcFile)) {
375
- if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
376
- writeFileSync(destFile, readFileSync(srcFile));
377
- console.log(` \x1b[32mCREATE ROOT ADAPTER FILE:\x1b[0m .gemini/settings.json`);
480
+ console.log(` \x1b[32mCREATE ROOT ADAPTER FILE:\x1b[0m ${a.rules_file}`);
378
481
  }
379
482
  }
380
483
  });
@@ -393,15 +496,35 @@ function handleInit(options) {
393
496
  } else {
394
497
  // Dry run notes
395
498
  options.adapters.forEach(adapter => {
396
- if (adapter === 'cursor') console.log(` \x1b[36m[DRY-RUN] WOULD CREATE ROOT ADAPTER FILE:\x1b[0m .cursorrules`);
397
- else if (adapter === 'claude') console.log(` \x1b[36m[DRY-RUN] WOULD CREATE ROOT ADAPTER FILE:\x1b[0m CLAUDE.md`);
398
- else if (adapter === 'vscode') console.log(` \x1b[36m[DRY-RUN] WOULD CREATE ROOT ADAPTER FILE:\x1b[0m .vscode/settings.json`);
399
- else if (adapter === 'gemini') console.log(` \x1b[36m[DRY-RUN] WOULD CREATE ROOT ADAPTER FILE:\x1b[0m GEMINI.md`);
400
- else if (adapter === 'antigravity') console.log(` \x1b[36m[DRY-RUN] WOULD CREATE ROOT ADAPTER FILE:\x1b[0m .gemini/settings.json`);
499
+ const a = ADAPTERS[adapter];
500
+ if (a && a.rules_file) {
501
+ console.log(` \x1b[36m[DRY-RUN] WOULD CREATE ROOT ADAPTER FILE:\x1b[0m ${a.rules_file}`);
502
+ }
401
503
  });
402
504
  }
403
505
 
404
506
  console.log(`\n\x1b[32m✔ Project initialized successfully! [Total Operations: ${operations.length}]\x1b[0m\n`);
507
+ console.log(`\x1b[36mNext Steps to Complete Integration:\x1b[0m`);
508
+ console.log(` 1. \x1b[1mEdit AGENTS.md\x1b[0m in your project root to document your stack context.`);
509
+ console.log(` 2. \x1b[1mEdit .ai/config.yaml\x1b[0m to configure active model routing presets.`);
510
+ if (options.adapters.length > 0) {
511
+ console.log(` 3. \x1b[1mActivate IDE / Agent Rules:\x1b[0m`);
512
+ console.log(` Ensure adapter configuration files are copied or linked to the root of your workspace:`);
513
+ options.adapters.forEach(adapter => {
514
+ const a = ADAPTERS[adapter];
515
+ if (a && a.rules_file) {
516
+ console.log(` - For \x1b[32m${a.name || adapter}\x1b[0m: Check the root-level \x1b[33m${a.rules_file}\x1b[0m file`);
517
+ }
518
+ });
519
+ } else {
520
+ console.log(` 3. \x1b[1mSelect IDE / Tool Adapters:\x1b[0m`);
521
+ console.log(` To generate rules for Cursor, Claude Code, etc., run:`);
522
+ console.log(` \x1b[36mnpx multimodel-dev-os init --adapter cursor --adapter claude\x1b[0m`);
523
+ }
524
+ console.log(` 4. \x1b[1mRun Diagnostics:\x1b[0m`);
525
+ console.log(` Verify your workspace structural compliance:`);
526
+ console.log(` \x1b[36mnpx multimodel-dev-os validate\x1b[0m`);
527
+ console.log(` \x1b[36mnpx multimodel-dev-os doctor\x1b[0m\n`);
405
528
  }
406
529
 
407
530
  function handleVerify(options) {
@@ -458,6 +581,14 @@ function handleVerify(options) {
458
581
  }
459
582
 
460
583
  function handleDoctor(options) {
584
+ if (options.tokens) {
585
+ handleDoctorTokens(options);
586
+ return;
587
+ }
588
+ if (options.release) {
589
+ handleDoctorRelease(options);
590
+ return;
591
+ }
461
592
  console.log(`\n🩺 \x1b[36mRunning advisory doctor checkup in: ${options.target}\x1b[0m\n`);
462
593
 
463
594
  let warnings = 0;
@@ -562,6 +693,10 @@ function handleDoctor(options) {
562
693
  }
563
694
 
564
695
  function handleValidate(options) {
696
+ if (options && options.allRegistries) {
697
+ handleValidateAllRegistries();
698
+ return;
699
+ }
565
700
  console.log(`\n🛡 \x1b[34mRunning strict schema validation in: ${options.target}\x1b[0m\n`);
566
701
 
567
702
  let errors = 0;
@@ -642,6 +777,26 @@ function handleValidate(options) {
642
777
  assertAdapter('antigravity', '.gemini/settings.json');
643
778
  }
644
779
 
780
+ // Template-specific validation
781
+ if (options.template) {
782
+ const tInfo = TEMPLATES[options.template];
783
+ if (tInfo && Array.isArray(tInfo.required_files)) {
784
+ console.log(`\n📋 Validating required files for template '${options.template}':`);
785
+ tInfo.required_files.forEach(f => assertPath(f, 'file'));
786
+ } else if (options.template === 'expo-react-native-android') {
787
+ const mobileFiles = [
788
+ 'app.json',
789
+ 'eas.json',
790
+ 'app.config.ts',
791
+ 'jest.config.js',
792
+ 'src/app/_layout.tsx',
793
+ 'src/lib/secure-storage.ts',
794
+ 'src/services/api-client.ts'
795
+ ];
796
+ mobileFiles.forEach(f => assertPath(f, 'file'));
797
+ }
798
+ }
799
+
645
800
  console.log('\n==================================================');
646
801
  if (errors > 0) {
647
802
  console.error(` \x1b[31mValidation FAILED. Found ${errors} strict structural compliance errors.\x1b[0m\n`);
@@ -651,3 +806,591 @@ function handleValidate(options) {
651
806
  process.exit(0);
652
807
  }
653
808
  }
809
+
810
+ // --- YAML Parser Helper ---
811
+ function parseYaml(content) {
812
+ try {
813
+ const root = {};
814
+ const stack = [{ obj: root, indent: -1, key: null, isArray: false }];
815
+
816
+ const lines = content.split(/\r?\n/);
817
+ for (let line of lines) {
818
+ const commentIdx = line.indexOf('#');
819
+ if (commentIdx !== -1) {
820
+ line = line.substring(0, commentIdx);
821
+ }
822
+ line = line.trimEnd();
823
+ if (!line.trim()) continue;
824
+
825
+ const indent = line.match(/^ */)[0].length;
826
+ let trimmed = line.trim();
827
+
828
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
829
+ stack.pop();
830
+ }
831
+
832
+ const parent = stack[stack.length - 1];
833
+
834
+ if (trimmed.startsWith('-')) {
835
+ trimmed = trimmed.substring(1).trim();
836
+ if (!Array.isArray(parent.obj)) {
837
+ const grandparent = stack[stack.length - 2];
838
+ if (grandparent) {
839
+ grandparent.obj[parent.key] = [];
840
+ parent.obj = grandparent.obj[parent.key];
841
+ }
842
+ }
843
+
844
+ const colonIdx = trimmed.indexOf(':');
845
+ if (colonIdx === -1) {
846
+ parent.obj.push(trimmed);
847
+ } else {
848
+ const key = trimmed.substring(0, colonIdx).trim();
849
+ let val = trimmed.substring(colonIdx + 1).trim();
850
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
851
+ val = val.substring(1, val.length - 1);
852
+ }
853
+ if (val === 'true') val = true;
854
+ else if (val === 'false') val = false;
855
+ else if (val === 'null') val = null;
856
+ else if (/^\d+$/.test(val)) val = parseInt(val, 10);
857
+
858
+ const newObj = { [key]: val };
859
+ parent.obj.push(newObj);
860
+ stack.push({ obj: newObj, indent: indent, key: key, isArray: false });
861
+ }
862
+ } else {
863
+ const colonIdx = trimmed.indexOf(':');
864
+ if (colonIdx === -1) continue;
865
+
866
+ const key = trimmed.substring(0, colonIdx).trim();
867
+ let val = trimmed.substring(colonIdx + 1).trim();
868
+
869
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
870
+ val = val.substring(1, val.length - 1);
871
+ }
872
+ if (val === 'true') val = true;
873
+ else if (val === 'false') val = false;
874
+ else if (val === 'null') val = null;
875
+ else if (/^\d+$/.test(val)) val = parseInt(val, 10);
876
+
877
+ if (val === '') {
878
+ parent.obj[key] = {};
879
+ stack.push({ obj: parent.obj[key], indent: indent, key: key, isArray: false });
880
+ } else {
881
+ parent.obj[key] = val;
882
+ }
883
+ }
884
+ }
885
+ return root;
886
+ } catch (e) {
887
+ console.warn(`\x1b[33m[WARNING] Failed to parse YAML: ${e.message}\x1b[0m`);
888
+ return {};
889
+ }
890
+ }
891
+
892
+ // --- Command Handler Functions ---
893
+ function handleListModels(options) {
894
+ const registryPath = join(sourceRoot, '.ai', 'models', 'registry.yaml');
895
+ if (!existsSync(registryPath)) {
896
+ console.error('Error: Model registry not found.');
897
+ process.exit(1);
898
+ }
899
+ const registry = parseYaml(readFileSync(registryPath, 'utf8'));
900
+ const models = registry.models || {};
901
+ if (options && options.json) {
902
+ console.log(JSON.stringify(models, null, 2));
903
+ return;
904
+ }
905
+ console.log(`\n🤖 \x1b[36mModel Registry [v${version}]\x1b[0m`);
906
+ console.log('==================================================');
907
+ Object.keys(models).forEach(name => {
908
+ const m = models[name];
909
+ console.log(`\n\x1b[32m* ${name}\x1b[0m (${m.alias || ''})`);
910
+ console.log(` \x1b[33mProvider:\x1b[0m ${m.provider}`);
911
+ console.log(` \x1b[33mOfficial ID:\x1b[0m ${m.official_id}`);
912
+ console.log(` \x1b[33mContext Window:\x1b[0m ${m.context_window} tokens`);
913
+ console.log(` \x1b[33mTiers:\x1b[0m Cost: ${m.tiers?.cost}, Reasoning: ${m.tiers?.reasoning}, Coding: ${m.tiers?.coding}`);
914
+ });
915
+ console.log('\nUse \x1b[36mshow-model <model-alias>\x1b[0m to view detailed model capabilities.\n');
916
+ }
917
+
918
+ function handleShowModel(name) {
919
+ const registryPath = join(sourceRoot, '.ai', 'models', 'registry.yaml');
920
+ if (!existsSync(registryPath)) {
921
+ console.error('Error: Model registry not found.');
922
+ process.exit(1);
923
+ }
924
+ const registry = parseYaml(readFileSync(registryPath, 'utf8'));
925
+ const models = registry.models || {};
926
+ const m = models[name];
927
+ if (!m) {
928
+ console.error(`\x1b[31mError: Model alias '${name}' not found in registry.\x1b[0m`);
929
+ process.exit(1);
930
+ }
931
+ console.log(`\n🔍 \x1b[36mModel: ${name}\x1b[0m`);
932
+ console.log('==================================================');
933
+ console.log(`\x1b[33mProvider:\x1b[0m ${m.provider}`);
934
+ console.log(`\x1b[33mAlias:\x1b[0m ${m.alias}`);
935
+ console.log(`\x1b[33mOfficial ID:\x1b[0m ${m.official_id}`);
936
+ console.log(`\x1b[33mContext Window:\x1b[0m ${m.context_window} tokens`);
937
+ console.log(`\x1b[33mCapabilities:\x1b[0m`);
938
+ console.log(` ├─ Vision: ${m.capabilities?.vision ? 'Yes' : 'No'}`);
939
+ console.log(` └─ Tool Use: ${m.capabilities?.tool_use ? 'Yes' : 'No'}`);
940
+ console.log(`\x1b[33mTiers:\x1b[0m`);
941
+ console.log(` ├─ Cost: ${m.tiers?.cost}`);
942
+ console.log(` ├─ Speed: ${m.tiers?.speed}`);
943
+ console.log(` ├─ Reasoning: ${m.tiers?.reasoning}`);
944
+ console.log(` └─ Coding: ${m.tiers?.coding}`);
945
+ console.log();
946
+ }
947
+
948
+ function handleListProviders() {
949
+ const providersPath = join(sourceRoot, '.ai', 'models', 'providers.yaml');
950
+ if (!existsSync(providersPath)) {
951
+ console.error('Error: Providers registry not found.');
952
+ process.exit(1);
953
+ }
954
+ const reg = parseYaml(readFileSync(providersPath, 'utf8'));
955
+ const providers = reg.providers || {};
956
+ console.log(`\n🔌 \x1b[36mAI Providers [v${version}]\x1b[0m`);
957
+ console.log('==================================================');
958
+ Object.keys(providers).forEach(name => {
959
+ const p = providers[name];
960
+ console.log(`\n\x1b[32m* ${p.name || name}\x1b[0m (${name})`);
961
+ console.log(` \x1b[33mEndpoint:\x1b[0m ${p.api_endpoint || 'Local'}`);
962
+ console.log(` \x1b[33mEnv Key:\x1b[0m ${p.env_key || 'None'}`);
963
+ });
964
+ console.log();
965
+ }
966
+
967
+ function handleRouteModel(task) {
968
+ const presetsPath = join(sourceRoot, '.ai', 'models', 'routing-presets.yaml');
969
+ if (!existsSync(presetsPath)) {
970
+ console.error('Error: Routing presets not found.');
971
+ process.exit(1);
972
+ }
973
+ const reg = parseYaml(readFileSync(presetsPath, 'utf8'));
974
+ const presets = reg.presets || {};
975
+ const preset = presets[task];
976
+ if (!preset) {
977
+ console.error(`\x1b[31mError: Routing preset for task '${task}' not found. Available: ${Object.keys(presets).join(', ')}\x1b[0m`);
978
+ process.exit(1);
979
+ }
980
+ console.log(`\n🎯 \x1b[36mRouting Suggestion for: ${task}\x1b[0m`);
981
+ console.log('==================================================');
982
+ console.log(`\x1b[33mPrimary Model:\x1b[0m \x1b[32m${preset.primary}\x1b[0m`);
983
+ console.log(`\x1b[33mFallback Model:\x1b[0m \x1b[33m${preset.fallback}\x1b[0m`);
984
+ console.log();
985
+ }
986
+
987
+ function handleListAdapters(options) {
988
+ const adaptersPath = join(sourceRoot, '.ai', 'adapters', 'registry.yaml');
989
+ if (!existsSync(adaptersPath)) {
990
+ console.error('Error: Adapters registry not found.');
991
+ process.exit(1);
992
+ }
993
+ const reg = parseYaml(readFileSync(adaptersPath, 'utf8'));
994
+ const adapters = reg.adapters || {};
995
+ if (options && options.json) {
996
+ console.log(JSON.stringify(adapters, null, 2));
997
+ return;
998
+ }
999
+ console.log(`\n🔌 \x1b[36mIDE & Agent Adapters [v${version}]\x1b[0m`);
1000
+ console.log('==================================================');
1001
+ Object.keys(adapters).forEach(name => {
1002
+ const a = adapters[name];
1003
+ console.log(`\n\x1b[32m* ${a.name || name}\x1b[0m (${name})`);
1004
+ console.log(` \x1b[33mRules File:\x1b[0m ${a.rules_file}`);
1005
+ console.log(` \x1b[33mAdapter Type:\x1b[0m ${a.type}`);
1006
+ console.log(` \x1b[33mRule Format:\x1b[0m ${a.format}`);
1007
+ });
1008
+ console.log('\nUse \x1b[36mshow-adapter <adapter-name>\x1b[0m to view detailed adapter metadata.\n');
1009
+ }
1010
+
1011
+ function handleShowAdapter(name) {
1012
+ const adaptersPath = join(sourceRoot, '.ai', 'adapters', 'registry.yaml');
1013
+ if (!existsSync(adaptersPath)) {
1014
+ console.error('Error: Adapters registry not found.');
1015
+ process.exit(1);
1016
+ }
1017
+ const reg = parseYaml(readFileSync(adaptersPath, 'utf8'));
1018
+ const adapters = reg.adapters || {};
1019
+ const a = adapters[name];
1020
+ if (!a) {
1021
+ console.error(`\x1b[31mError: Adapter '${name}' not found in registry.\x1b[0m`);
1022
+ process.exit(1);
1023
+ }
1024
+ console.log(`\n🔍 \x1b[36mAdapter: ${a.name || name}\x1b[0m`);
1025
+ console.log('==================================================');
1026
+ console.log(`\x1b[33mRules File:\x1b[0m ${a.rules_file}`);
1027
+ console.log(`\x1b[33mType:\x1b[0m ${a.type}`);
1028
+ console.log(`\x1b[33mFormat:\x1b[0m ${a.format}`);
1029
+ console.log();
1030
+ }
1031
+
1032
+ function handleListSkills(options) {
1033
+ const skillsDir = join(options.target, '.ai', 'skills');
1034
+ if (!existsSync(skillsDir)) {
1035
+ console.log('\n\x1b[33m[Notice] .ai/skills directory is not initialized in the target workspace.\x1b[0m\n');
1036
+ return;
1037
+ }
1038
+ const files = readdirSync(skillsDir).filter(f => f.endsWith('.md'));
1039
+ console.log(`\n🧠 \x1b[36mAvailable Skills in Target [v${version}]\x1b[0m`);
1040
+ console.log('==================================================');
1041
+ files.forEach(f => {
1042
+ console.log(` \x1b[32m- ${f.replace('.md', '')}\x1b[0m (file: .ai/skills/${f})`);
1043
+ });
1044
+ console.log('\nUse \x1b[36mshow-skill <skill-name>\x1b[0m to read a skill\'s prompt text.\n');
1045
+ }
1046
+
1047
+ function handleShowSkill(name, options) {
1048
+ const skillsDir = join(options.target, '.ai', 'skills');
1049
+ const skillFile = join(skillsDir, name.endsWith('.md') ? name : `${name}.md`);
1050
+ if (!existsSync(skillFile)) {
1051
+ console.error(`\x1b[31mError: Skill '${name}' not found in target .ai/skills/.\x1b[0m`);
1052
+ process.exit(1);
1053
+ }
1054
+ console.log(`\n📖 \x1b[36mSkill Prompt: ${name}\x1b[0m`);
1055
+ console.log('==================================================');
1056
+ console.log(readFileSync(skillFile, 'utf8'));
1057
+ console.log();
1058
+ }
1059
+
1060
+ function parseThresholdToBytes(val) {
1061
+ if (!val) return 100 * 1024; // Default 100KB
1062
+ const matches = val.match(/^(\d+)(KB|MB|B)?$/i);
1063
+ if (!matches) return 100 * 1024;
1064
+ const num = parseInt(matches[1], 10);
1065
+ const unit = (matches[2] || '').toUpperCase();
1066
+ if (unit === 'MB') return num * 1024 * 1024;
1067
+ if (unit === 'KB') return num * 1024;
1068
+ return num;
1069
+ }
1070
+
1071
+ function handleDoctorTokens(options) {
1072
+ console.log(`\n🪙 \x1b[36mRunning Token Budget & Sink Audit in: ${options.target}\x1b[0m\n`);
1073
+
1074
+ const filesFound = [];
1075
+ const ignoredDirs = ['.git', 'node_modules', 'dist', 'build', '.next', '.expo', 'bin', 'assets', 'docs', 'web-build', 'out', 'coverage', '.nuxt', '.svelte-kit', 'bower_components', 'vendor'];
1076
+
1077
+ function scan(dir) {
1078
+ if (!existsSync(dir)) return;
1079
+ const items = readdirSync(dir);
1080
+ for (const item of items) {
1081
+ if (ignoredDirs.includes(item)) continue;
1082
+ const fullPath = join(dir, item);
1083
+ try {
1084
+ const stat = statSync(fullPath);
1085
+ if (stat.isDirectory()) {
1086
+ scan(fullPath);
1087
+ } else if (stat.isFile()) {
1088
+ filesFound.push({
1089
+ relPath: replaceBackslashes(fullPath.replace(options.target, '')),
1090
+ size: stat.size
1091
+ });
1092
+ }
1093
+ } catch (e) {}
1094
+ }
1095
+ }
1096
+
1097
+ function replaceBackslashes(p) {
1098
+ let clean = p.replace(/\\/g, '/');
1099
+ if (clean.startsWith('/')) clean = clean.substring(1);
1100
+ return clean;
1101
+ }
1102
+
1103
+ scan(options.target);
1104
+
1105
+ filesFound.sort((a, b) => b.size - a.size);
1106
+
1107
+ const thresholdBytes = parseThresholdToBytes(options.threshold);
1108
+ const thresholdStr = options.threshold || '100KB';
1109
+
1110
+ console.log('Top 10 Largest Files in Scanned Workspace:');
1111
+ filesFound.slice(0, 10).forEach(f => {
1112
+ let sizeDesc = `${f.size} bytes`;
1113
+ if (f.size > 1024 * 1024) sizeDesc = `${(f.size / (1024 * 1024)).toFixed(2)} MB`;
1114
+ else if (f.size > 1024) sizeDesc = `${(f.size / 1024).toFixed(2)} KB`;
1115
+
1116
+ let color = '\x1b[32m';
1117
+ if (f.size > thresholdBytes) color = '\x1b[31m';
1118
+ else if (f.size > thresholdBytes * 0.3) color = '\x1b[33m';
1119
+
1120
+ console.log(` ${color}* ${f.relPath}\x1b[0m (${sizeDesc})`);
1121
+ });
1122
+
1123
+ console.log('\n==================================================');
1124
+ console.log(`Total Scanned Files: ${filesFound.length}`);
1125
+ console.log(`Recommendation: Exclude files in red (>${thresholdStr}) from active coding prompts or add them to your adapter ignore rules.`);
1126
+ console.log();
1127
+ }
1128
+
1129
+ function handleValidateTemplate(name) {
1130
+ const t = TEMPLATES[name];
1131
+ if (!t) {
1132
+ console.error(`\x1b[31mError: Template '${name}' not found in registry.\x1b[0m`);
1133
+ process.exit(1);
1134
+ }
1135
+ console.log(`\n📋 \x1b[34mValidating Template: ${name}\x1b[0m`);
1136
+
1137
+ let errors = 0;
1138
+ const reqKeys = ['name', 'description', 'stack', 'category', 'status', 'maturity', 'required_files'];
1139
+ reqKeys.forEach(k => {
1140
+ if (t[k] === undefined || t[k] === null) {
1141
+ console.error(` \x1b[31m✗ Missing registry key: ${k}\x1b[0m`);
1142
+ errors++;
1143
+ } else {
1144
+ console.log(` \x1b[32m✓\x1b[0m Registry key: ${k}`);
1145
+ }
1146
+ });
1147
+
1148
+ const templateDir = join(sourceRoot, 'examples', name);
1149
+ if (!existsSync(templateDir)) {
1150
+ console.error(` \x1b[31m✗ Source folder missing: examples/${name}\x1b[0m`);
1151
+ errors++;
1152
+ } else {
1153
+ console.log(` \x1b[32m✓\x1b[0m Source folder: examples/${name}`);
1154
+ if (Array.isArray(t.required_files)) {
1155
+ t.required_files.forEach(f => {
1156
+ const filePath = join(templateDir, f);
1157
+ const globalPath = join(sourceRoot, f);
1158
+ if (existsSync(filePath)) {
1159
+ console.log(` \x1b[32m✓\x1b[0m Required file (template override): ${f}`);
1160
+ } else if (existsSync(globalPath)) {
1161
+ console.log(` \x1b[32m✓\x1b[0m Required file (global fallback): ${f}`);
1162
+ } else {
1163
+ console.error(` \x1b[31m✗ Required file missing: ${f}\x1b[0m`);
1164
+ errors++;
1165
+ }
1166
+ });
1167
+ }
1168
+ }
1169
+
1170
+ if (errors > 0) {
1171
+ console.error(`\n\x1b[31mValidation FAILED with ${errors} errors.\x1b[0m\n`);
1172
+ process.exit(1);
1173
+ } else {
1174
+ console.log(`\n\x1b[32m✔ Template '${name}' is fully valid and compliant!\x1b[0m\n`);
1175
+ process.exit(0);
1176
+ }
1177
+ }
1178
+
1179
+ function handleValidateAdapter(name) {
1180
+ const a = ADAPTERS[name];
1181
+ if (!a) {
1182
+ console.error(`\x1b[31mError: Adapter '${name}' not found in registry.\x1b[0m`);
1183
+ process.exit(1);
1184
+ }
1185
+ console.log(`\n📋 \x1b[34mValidating Adapter: ${name}\x1b[0m`);
1186
+
1187
+ let errors = 0;
1188
+ const reqKeys = ['name', 'rules_file', 'format', 'type'];
1189
+ reqKeys.forEach(k => {
1190
+ if (!a[k]) {
1191
+ console.error(` \x1b[31m✗ Missing registry key: ${k}\x1b[0m`);
1192
+ errors++;
1193
+ } else {
1194
+ console.log(` \x1b[32m✓\x1b[0m Registry key: ${k}`);
1195
+ }
1196
+ });
1197
+
1198
+ const adapterDir = join(sourceRoot, 'adapters', name);
1199
+ if (!existsSync(adapterDir)) {
1200
+ console.error(` \x1b[31m✗ Source folder missing: adapters/${name}\x1b[0m`);
1201
+ errors++;
1202
+ } else {
1203
+ console.log(` \x1b[32m✓\x1b[0m Source folder: adapters/${name}`);
1204
+ const setupFile = join(adapterDir, 'setup.md');
1205
+ if (existsSync(setupFile)) {
1206
+ console.log(` \x1b[32m✓\x1b[0m Required file: setup.md`);
1207
+ } else {
1208
+ console.error(` \x1b[31m✗ Required file missing: adapters/${name}/setup.md\x1b[0m`);
1209
+ errors++;
1210
+ }
1211
+
1212
+ if (a.rules_file) {
1213
+ const rulesFile = join(adapterDir, a.rules_file);
1214
+ if (existsSync(rulesFile)) {
1215
+ console.log(` \x1b[32m✓\x1b[0m Rules file: ${a.rules_file}`);
1216
+ } else {
1217
+ console.error(` \x1b[31m✗ Rules file missing: adapters/${name}/${a.rules_file}\x1b[0m`);
1218
+ errors++;
1219
+ }
1220
+ }
1221
+ }
1222
+
1223
+ if (errors > 0) {
1224
+ console.error(`\n\x1b[31mValidation FAILED with ${errors} errors.\x1b[0m\n`);
1225
+ process.exit(1);
1226
+ } else {
1227
+ console.log(`\n\x1b[32m✔ Adapter '${name}' is fully valid and compliant!\x1b[0m\n`);
1228
+ process.exit(0);
1229
+ }
1230
+ }
1231
+
1232
+ function handleValidateSkill(name, options) {
1233
+ const skillsDir = join(options.target, '.ai', 'skills');
1234
+ let skillFile = join(skillsDir, name.endsWith('.md') ? name : `${name}.md`);
1235
+ if (!existsSync(skillFile)) {
1236
+ skillFile = join(sourceRoot, '.ai', 'skills', name.endsWith('.md') ? name : `${name}.md`);
1237
+ }
1238
+
1239
+ if (!existsSync(skillFile)) {
1240
+ console.error(`\x1b[31mError: Skill '${name}' not found.\x1b[0m`);
1241
+ process.exit(1);
1242
+ }
1243
+
1244
+ console.log(`\n📋 \x1b[34mValidating Skill: ${name}\x1b[0m`);
1245
+ const content = readFileSync(skillFile, 'utf8');
1246
+ let errors = 0;
1247
+
1248
+ const reqHeaders = [
1249
+ { header: '# Purpose', regex: /^#\s+Purpose/mi },
1250
+ { header: '# Activation Trigger', regex: /^#\s+Activation\s+Trigger/mi },
1251
+ { header: '# Input Context', regex: /^#\s+Input\s+Context/mi },
1252
+ { header: '# Output Contract', regex: /^#\s+Output\s+Contract/mi },
1253
+ { header: '# Token Budget', regex: /^#\s+Token\s+Budget/mi }
1254
+ ];
1255
+
1256
+ reqHeaders.forEach(req => {
1257
+ if (req.regex.test(content)) {
1258
+ console.log(` \x1b[32m✓\x1b[0m Found required header: ${req.header}`);
1259
+ } else {
1260
+ console.error(` \x1b[31m✗ Missing required header: ${req.header}\x1b[0m`);
1261
+ errors++;
1262
+ }
1263
+ });
1264
+
1265
+ if (errors > 0) {
1266
+ console.error(`\n\x1b[31mValidation FAILED with ${errors} errors.\x1b[0m\n`);
1267
+ process.exit(1);
1268
+ } else {
1269
+ console.log(`\n\x1b[32m✔ Skill '${name}' is fully valid and compliant!\x1b[0m\n`);
1270
+ process.exit(0);
1271
+ }
1272
+ }
1273
+
1274
+ function handleValidateAllRegistries() {
1275
+ console.log(`\n🛡 \x1b[34mValidating All Registry Entries\x1b[0m\n`);
1276
+ let errors = 0;
1277
+
1278
+ // Validate all templates
1279
+ console.log('--- Templates Registry Validation ---');
1280
+ Object.keys(TEMPLATES).forEach(name => {
1281
+ const t = TEMPLATES[name];
1282
+ console.log(`\nValidating Template: ${name}`);
1283
+ const reqKeys = ['name', 'description', 'stack', 'category', 'status', 'maturity'];
1284
+ if (t.status !== 'planned') {
1285
+ reqKeys.push('required_files');
1286
+ }
1287
+ reqKeys.forEach(k => {
1288
+ if (t[k] === undefined || t[k] === null) {
1289
+ console.error(` \x1b[31m✗ Missing registry key: ${k}\x1b[0m`);
1290
+ errors++;
1291
+ }
1292
+ });
1293
+
1294
+ const templateDir = join(sourceRoot, 'examples', name);
1295
+ if (t.status === 'stable' && !existsSync(templateDir)) {
1296
+ console.error(` \x1b[31m✗ Stable template source folder missing: examples/${name}\x1b[0m`);
1297
+ errors++;
1298
+ }
1299
+ });
1300
+
1301
+ // Validate all adapters
1302
+ console.log('\n--- Adapters Registry Validation ---');
1303
+ Object.keys(ADAPTERS).forEach(name => {
1304
+ const a = ADAPTERS[name];
1305
+ console.log(`Validating Adapter: ${name}`);
1306
+ const reqKeys = ['name', 'rules_file', 'format', 'type'];
1307
+ reqKeys.forEach(k => {
1308
+ if (!a[k]) {
1309
+ console.error(` \x1b[31m✗ Missing registry key: ${k}\x1b[0m`);
1310
+ errors++;
1311
+ }
1312
+ });
1313
+ });
1314
+
1315
+ console.log('\n==================================================');
1316
+ if (errors > 0) {
1317
+ console.error(` \x1b[31mAll Registries validation FAILED. Found ${errors} schema errors.\x1b[0m\n`);
1318
+ process.exit(1);
1319
+ } else {
1320
+ console.log(' \x1b[32m✔ All Registries validation PASSED. All templates and adapters are valid.\x1b[0m\n');
1321
+ process.exit(0);
1322
+ }
1323
+ }
1324
+
1325
+ function handleDoctorRelease(options) {
1326
+ console.log(`\n🩺 \x1b[36mRunning release audit doctor in: ${sourceRoot}\x1b[0m\n`);
1327
+ let warnings = 0;
1328
+
1329
+ // 1. Version checks
1330
+ let packageVersion = 'unknown';
1331
+ try {
1332
+ const pkg = JSON.parse(readFileSync(join(sourceRoot, 'package.json'), 'utf8'));
1333
+ packageVersion = pkg.version;
1334
+ console.log(` \x1b[32m✓\x1b[0m package.json version: ${packageVersion}`);
1335
+ } catch (e) {
1336
+ console.warn(' \x1b[31m✗\x1b[0m Failed to parse package.json');
1337
+ warnings++;
1338
+ }
1339
+
1340
+ const checkInstallScript = (filename, regex) => {
1341
+ const filePath = join(sourceRoot, filename);
1342
+ if (existsSync(filePath)) {
1343
+ const content = readFileSync(filePath, 'utf8');
1344
+ const match = content.match(regex);
1345
+ if (match && match[1] === packageVersion) {
1346
+ console.log(` \x1b[32m✓\x1b[0m ${filename} version aligns: ${match[1]}`);
1347
+ } else {
1348
+ console.warn(` \x1b[33m[WARNING]\x1b[0m ${filename} version mismatch (found ${match ? match[1] : 'none'}, expected ${packageVersion})`);
1349
+ warnings++;
1350
+ }
1351
+ }
1352
+ };
1353
+
1354
+ checkInstallScript('scripts/install.sh', /VERSION="([^"]+)"/i);
1355
+ checkInstallScript('scripts/install.ps1', /\$VERSION\s*=\s*"([^"]+)"/i);
1356
+
1357
+ // 2. Blacklisted files audit
1358
+ const blacklist = ['.npmrc'];
1359
+ blacklist.forEach(file => {
1360
+ const fullPath = join(sourceRoot, file);
1361
+ if (existsSync(fullPath)) {
1362
+ console.warn(` \x1b[33m[WARNING]\x1b[0m Blacklisted file found in release root: ${file}`);
1363
+ warnings++;
1364
+ } else {
1365
+ console.log(` \x1b[32m✓\x1b[0m No root blacklisted file: ${file}`);
1366
+ }
1367
+ });
1368
+
1369
+ // Recursively scan examples/ for .env and keystores
1370
+ const scanSafety = (dir) => {
1371
+ if (!existsSync(dir)) return;
1372
+ const items = readdirSync(dir);
1373
+ for (const item of items) {
1374
+ const fullPath = join(dir, item);
1375
+ try {
1376
+ const stat = statSync(fullPath);
1377
+ if (stat.isDirectory()) {
1378
+ scanSafety(fullPath);
1379
+ } else if (stat.isFile()) {
1380
+ if (item === '.env' || item.endsWith('.keystore') || item.endsWith('.jks')) {
1381
+ console.warn(` \x1b[33m[WARNING]\x1b[0m Unsafe file inside templates/examples: ${fullPath.replace(sourceRoot, '')}`);
1382
+ warnings++;
1383
+ }
1384
+ }
1385
+ } catch (e) {}
1386
+ }
1387
+ };
1388
+ scanSafety(join(sourceRoot, 'examples'));
1389
+
1390
+ console.log('\n==================================================');
1391
+ if (warnings > 0) {
1392
+ console.warn(` \x1b[33mRelease doctor complete with ${warnings} warnings.\x1b[0m\n`);
1393
+ } else {
1394
+ console.log(' \x1b[32m✔ Release hygiene checks PASSED successfully!\x1b[0m\n');
1395
+ }
1396
+ }