@wundam/orchex 1.0.0-rc.2 → 1.0.0-rc.21

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 (98) hide show
  1. package/README.md +59 -18
  2. package/dist/cloud-executor.d.ts +71 -0
  3. package/dist/cloud-executor.js +335 -0
  4. package/dist/cloud-sync.d.ts +8 -0
  5. package/dist/cloud-sync.js +52 -0
  6. package/dist/config.d.ts +30 -4
  7. package/dist/config.js +61 -2
  8. package/dist/context-builder.d.ts +2 -0
  9. package/dist/context-builder.js +11 -3
  10. package/dist/cost.js +1 -1
  11. package/dist/entitlements/jwt.d.ts +7 -0
  12. package/dist/entitlements/jwt.js +78 -0
  13. package/dist/entitlements/resolve.d.ts +17 -0
  14. package/dist/entitlements/resolve.js +49 -0
  15. package/dist/entitlements/types.d.ts +21 -0
  16. package/dist/entitlements/types.js +4 -0
  17. package/dist/executors/base.d.ts +1 -1
  18. package/dist/executors/bedrock-executor.d.ts +39 -0
  19. package/dist/executors/bedrock-executor.js +197 -0
  20. package/dist/executors/index.d.ts +1 -0
  21. package/dist/executors/index.js +24 -1
  22. package/dist/index.js +468 -23
  23. package/dist/intelligence/index.d.ts +44 -0
  24. package/dist/intelligence/index.js +160 -0
  25. package/dist/key-cache.d.ts +31 -0
  26. package/dist/key-cache.js +84 -0
  27. package/dist/login-helpers.d.ts +25 -0
  28. package/dist/login-helpers.js +54 -0
  29. package/dist/manifest.js +18 -1
  30. package/dist/mcp-instructions.d.ts +1 -0
  31. package/dist/mcp-instructions.js +84 -0
  32. package/dist/mcp-resources.d.ts +8 -0
  33. package/dist/mcp-resources.js +420 -0
  34. package/dist/model-cache.d.ts +18 -0
  35. package/dist/model-cache.js +62 -0
  36. package/dist/model-validator.d.ts +20 -0
  37. package/dist/model-validator.js +125 -0
  38. package/dist/orchestrator.d.ts +14 -0
  39. package/dist/orchestrator.js +191 -32
  40. package/dist/setup/ide-registry.d.ts +13 -0
  41. package/dist/setup/ide-registry.js +51 -0
  42. package/dist/setup/index.d.ts +1 -0
  43. package/dist/setup/index.js +111 -0
  44. package/dist/tier-gating.js +0 -16
  45. package/dist/tiers.d.ts +35 -5
  46. package/dist/tiers.js +39 -3
  47. package/dist/tools.d.ts +6 -1
  48. package/dist/tools.js +852 -95
  49. package/dist/types.d.ts +71 -60
  50. package/dist/types.js +3 -0
  51. package/dist/waves.d.ts +1 -1
  52. package/dist/waves.js +29 -2
  53. package/package.json +41 -5
  54. package/src/entitlements/public-key.pem +9 -0
  55. package/dist/intelligence/anti-pattern-detector.d.ts +0 -117
  56. package/dist/intelligence/anti-pattern-detector.js +0 -327
  57. package/dist/intelligence/budget-enforcer.d.ts +0 -119
  58. package/dist/intelligence/budget-enforcer.js +0 -226
  59. package/dist/intelligence/context-optimizer.d.ts +0 -111
  60. package/dist/intelligence/context-optimizer.js +0 -282
  61. package/dist/intelligence/cost-tracker.d.ts +0 -114
  62. package/dist/intelligence/cost-tracker.js +0 -183
  63. package/dist/intelligence/deliverable-extractor.d.ts +0 -134
  64. package/dist/intelligence/deliverable-extractor.js +0 -909
  65. package/dist/intelligence/dependency-inferrer.d.ts +0 -87
  66. package/dist/intelligence/dependency-inferrer.js +0 -403
  67. package/dist/intelligence/diagnostics.d.ts +0 -33
  68. package/dist/intelligence/diagnostics.js +0 -64
  69. package/dist/intelligence/error-analyzer.d.ts +0 -7
  70. package/dist/intelligence/error-analyzer.js +0 -76
  71. package/dist/intelligence/file-chunker.d.ts +0 -15
  72. package/dist/intelligence/file-chunker.js +0 -64
  73. package/dist/intelligence/fix-stream-manager.d.ts +0 -59
  74. package/dist/intelligence/fix-stream-manager.js +0 -212
  75. package/dist/intelligence/heuristics.d.ts +0 -23
  76. package/dist/intelligence/heuristics.js +0 -124
  77. package/dist/intelligence/learning-engine.d.ts +0 -157
  78. package/dist/intelligence/learning-engine.js +0 -433
  79. package/dist/intelligence/learning-feedback.d.ts +0 -96
  80. package/dist/intelligence/learning-feedback.js +0 -202
  81. package/dist/intelligence/pattern-analyzer.d.ts +0 -35
  82. package/dist/intelligence/pattern-analyzer.js +0 -189
  83. package/dist/intelligence/plan-parser.d.ts +0 -124
  84. package/dist/intelligence/plan-parser.js +0 -498
  85. package/dist/intelligence/planner.d.ts +0 -29
  86. package/dist/intelligence/planner.js +0 -86
  87. package/dist/intelligence/self-healer.d.ts +0 -16
  88. package/dist/intelligence/self-healer.js +0 -84
  89. package/dist/intelligence/slicing-metrics.d.ts +0 -62
  90. package/dist/intelligence/slicing-metrics.js +0 -202
  91. package/dist/intelligence/slicing-templates.d.ts +0 -81
  92. package/dist/intelligence/slicing-templates.js +0 -420
  93. package/dist/intelligence/split-suggester.d.ts +0 -69
  94. package/dist/intelligence/split-suggester.js +0 -176
  95. package/dist/intelligence/stream-generator.d.ts +0 -90
  96. package/dist/intelligence/stream-generator.js +0 -452
  97. package/dist/telemetry/telemetry-types.d.ts +0 -85
  98. package/dist/telemetry/telemetry-types.js +0 -1
package/dist/index.js CHANGED
@@ -2,7 +2,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { exec as execChildProcess } from 'child_process';
4
4
  import { registerTools, setProjectDir } from './tools.js';
5
- import { loadConfig, saveConfig, maskConfigForDisplay, PRODUCTION_URL, LLMProviderSchema } from './config.js';
5
+ import { ORCHEX_INSTRUCTIONS } from './mcp-instructions.js';
6
+ import { registerResources } from './mcp-resources.js';
7
+ import { loadConfig, saveConfig, maskConfigForDisplay, resolveApiUrl, PRODUCTION_URL, LLMProviderSchema } from './config.js';
6
8
  import { buildVerificationMessage, buildStatusMessage, parseLoginApiResponse, } from './login-helpers.js';
7
9
  /** Opens browser cross-platform. Errors are silently ignored — URL already printed to terminal. */
8
10
  function openBrowser(url) {
@@ -12,18 +14,12 @@ function openBrowser(url) {
12
14
  execChildProcess(cmd, () => { });
13
15
  }
14
16
  async function handleLoginCommand(args) {
15
- let apiUrl = PRODUCTION_URL;
16
17
  const apiUrlIdx = args.indexOf('--api-url');
17
- if (apiUrlIdx !== -1 && args[apiUrlIdx + 1])
18
- apiUrl = args[apiUrlIdx + 1];
19
- const stagingIdx = args.indexOf('--staging');
20
- if (stagingIdx !== -1) {
21
- const stagingUrl = process.env.ORCHEX_STAGING_URL;
22
- if (!stagingUrl) {
23
- console.warn('⚠ --staging used but ORCHEX_STAGING_URL is not set. Using production URL.');
24
- }
25
- apiUrl = stagingUrl ?? PRODUCTION_URL;
18
+ const flagValue = apiUrlIdx !== -1 ? args[apiUrlIdx + 1] : undefined;
19
+ if (args.includes('--staging')) {
20
+ console.warn('⚠ orchex login --staging is no longer needed. Set ORCHEX_API_URL=<url> in your environment instead.');
26
21
  }
22
+ const apiUrl = await resolveApiUrl(flagValue);
27
23
  // Step 1: Start device auth
28
24
  let startData;
29
25
  try {
@@ -86,6 +82,15 @@ async function handleLoginCommand(args) {
86
82
  const body = await meResp.json();
87
83
  const me = body.user;
88
84
  if (me) {
85
+ // Sync tier + trial info to local config for offline tier resolution
86
+ await saveConfig({
87
+ mode: 'cloud',
88
+ apiUrl,
89
+ apiKey: token,
90
+ tier: me.tier,
91
+ trialRunsRemaining: me.trialRunsRemaining,
92
+ accountCreatedAt: me.createdAt ?? undefined,
93
+ });
89
94
  console.log(`\n✓ Logged in as ${me.email}`);
90
95
  const expiryDays = me.tier === 'free' ? 30 : 90;
91
96
  const expiryDate = new Date(Date.now() + expiryDays * 24 * 60 * 60 * 1000).toLocaleDateString();
@@ -96,7 +101,54 @@ async function handleLoginCommand(args) {
96
101
  console.log(` Tier: ${me.tier.charAt(0).toUpperCase() + me.tier.slice(1)}`);
97
102
  }
98
103
  console.log(` Token expires: ${expiryDate}`);
99
- console.log(` Config saved to ~/.orchex/config.json\n`);
104
+ console.log(` Config saved to ~/.orchex/config.json`);
105
+ // Sync BYOK keys to local cache
106
+ try {
107
+ const { writeKeyCache } = await import('./key-cache.js');
108
+ const keysResp = await fetch(`${apiUrl}/api/v1/keys/resolve`, {
109
+ headers: { 'Authorization': `Bearer ${token}` },
110
+ });
111
+ if (keysResp.ok) {
112
+ const { keys } = (await keysResp.json());
113
+ if (keys.length > 0) {
114
+ await writeKeyCache({ keys, fetchedAt: new Date().toISOString() });
115
+ console.log(` Keys: ${keys.length} provider key(s) synced`);
116
+ }
117
+ }
118
+ }
119
+ catch {
120
+ // Non-fatal — keys can be fetched on-demand later
121
+ }
122
+ // Fetch entitlement JWT (non-fatal — login succeeds even if this fails)
123
+ try {
124
+ const entResp = await fetch(`${apiUrl}/api/v1/entitlements/me`, {
125
+ headers: { 'Authorization': `Bearer ${token}` },
126
+ });
127
+ if (entResp.ok) {
128
+ const { jwt: entitlementJwt } = await entResp.json();
129
+ const { readKeyCacheRaw, writeKeyCache: writeKC } = await import('./key-cache.js');
130
+ // IMPORTANT: Use readKeyCacheRaw() — NOT readKeyCache() — to avoid
131
+ // the 1hr BYOK TTL wiping existing keys when cache is older than 1hr.
132
+ const existingCache = await readKeyCacheRaw();
133
+ await writeKC({
134
+ keys: existingCache?.keys ?? [],
135
+ fetchedAt: existingCache?.fetchedAt ?? new Date().toISOString(),
136
+ entitlementJwt,
137
+ });
138
+ }
139
+ }
140
+ catch {
141
+ // Non-fatal — entitlement sync is best-effort
142
+ }
143
+ // Also cache model registry for offline use
144
+ try {
145
+ const { fetchAndCacheModels } = await import('./model-cache.js');
146
+ await fetchAndCacheModels(apiUrl);
147
+ }
148
+ catch {
149
+ // Non-fatal — model registry can be fetched on-demand later
150
+ }
151
+ console.log('');
100
152
  return;
101
153
  }
102
154
  }
@@ -112,8 +164,10 @@ async function handleLogoutCommand() {
112
164
  console.log('Not logged in (no API key configured).');
113
165
  return;
114
166
  }
115
- await saveConfig({ mode: 'local', apiKey: undefined });
116
- console.log('Logged out. Local mode enabled.');
167
+ const { clearKeyCache } = await import('./key-cache.js');
168
+ await clearKeyCache();
169
+ await saveConfig({ mode: 'local', apiKey: undefined, tier: 'free' });
170
+ console.log('Logged out. Local mode enabled. Cached keys cleared.');
117
171
  }
118
172
  async function handleStatusCommand(args) {
119
173
  const config = await loadConfig();
@@ -152,6 +206,26 @@ async function handleStatusCommand(args) {
152
206
  }
153
207
  else {
154
208
  console.log(buildStatusMessage(me));
209
+ // Show entitlement status if JWT is cached
210
+ try {
211
+ const { getCachedEntitlement } = await import('./key-cache.js');
212
+ const { loadPublicKey } = await import('./entitlements/resolve.js');
213
+ const publicKeyPem = await loadPublicKey();
214
+ const claims = await getCachedEntitlement(publicKeyPem);
215
+ if (claims) {
216
+ const srcLabel = claims.src.charAt(0).toUpperCase() + claims.src.slice(1);
217
+ const unlimitedFields = Object.entries(claims.eff)
218
+ .filter(([, v]) => v === -1)
219
+ .map(([k]) => k);
220
+ const desc = unlimitedFields.length > 0
221
+ ? `unlimited: ${unlimitedFields.join(', ')}`
222
+ : 'custom limits';
223
+ console.log(` Entitlement: ${srcLabel} (${desc})`);
224
+ }
225
+ }
226
+ catch {
227
+ // No entitlement info — skip silently
228
+ }
155
229
  }
156
230
  }
157
231
  catch (err) {
@@ -160,7 +234,14 @@ async function handleStatusCommand(args) {
160
234
  }
161
235
  function printHelp() {
162
236
  console.log(`
163
- orchex — Parallel AI orchestration with ownership enforcement
237
+ orchex — Autopilot AI orchestration with ownership enforcement
238
+
239
+ ORCHESTRATION:
240
+ orchex run "intent" Auto-plan and execute from natural language
241
+ orchex run "..." --yes Skip approval prompt (auto-approve)
242
+ orchex run "..." --dry-run Generate plan only, don't execute
243
+ orchex run "..." --provider openai --model gpt-4.1
244
+ orchex complete --archive Archive active orchestration and unblock future runs
164
245
 
165
246
  CLOUD COMMANDS:
166
247
  orchex login Authenticate with orchex cloud (opens browser)
@@ -168,10 +249,25 @@ CLOUD COMMANDS:
168
249
  orchex status Show login state, tier, and trial runs
169
250
  orchex status --json Output as JSON (for scripting)
170
251
 
252
+ LEARNING:
253
+ orchex reset-learning --confirm Reset all learning data
254
+ orchex reset-learning --confirm --patterns-only Reset detected patterns only
255
+ orchex reset-learning --confirm --reports-only Reset execution reports only
256
+
171
257
  CONFIG:
172
258
  orchex config Show current config (apiKey masked)
173
259
  orchex config --api-key <token> Save API key manually
174
- orchex config --staging Configure for staging server
260
+ orchex config --staging Configure for staging server (reads ORCHEX_API_URL)
261
+
262
+ ENVIRONMENT VARIABLES:
263
+ ORCHEX_API_URL Override API server URL (e.g. https://orchex.dev)
264
+ Priority: --api-url flag > ORCHEX_API_URL > config file > default
265
+
266
+ SETUP:
267
+ setup [options] Auto-configure MCP server for your IDE
268
+ --ide <name> Target specific IDE
269
+ --dry-run Preview changes
270
+ --force Overwrite existing
175
271
 
176
272
  MCP SERVER (invoked by your AI assistant):
177
273
  npx @wundam/orchex Start the MCP server
@@ -220,11 +316,19 @@ async function handleConfigCommand(args) {
220
316
  updates.apiKey = value;
221
317
  i++;
222
318
  break;
223
- case '--staging':
224
- // Shorthand: --staging sets mode=cloud and apiUrl to staging
319
+ case '--staging': {
320
+ // Shorthand: --staging sets mode=cloud and apiUrl to staging env
225
321
  updates.mode = 'cloud';
226
- updates.apiUrl = process.env.ORCHEX_STAGING_URL ?? PRODUCTION_URL;
322
+ if (process.env.ORCHEX_STAGING_URL && !process.env.ORCHEX_API_URL) {
323
+ console.warn('⚠ ORCHEX_STAGING_URL is deprecated. Use ORCHEX_API_URL instead.');
324
+ }
325
+ const stagingTarget = process.env.ORCHEX_API_URL ?? process.env.ORCHEX_STAGING_URL;
326
+ if (!stagingTarget) {
327
+ console.warn('⚠ --staging used but ORCHEX_API_URL is not set. Defaulting to production URL.');
328
+ }
329
+ updates.apiUrl = stagingTarget ?? PRODUCTION_URL;
227
330
  break;
331
+ }
228
332
  case '--local':
229
333
  // Shorthand: --local sets mode=local
230
334
  updates.mode = 'local';
@@ -240,6 +344,13 @@ async function handleConfigCommand(args) {
240
344
  process.exit(1);
241
345
  }
242
346
  updates.provider = value;
347
+ // Warn if setting non-Anthropic provider while in cloud mode
348
+ {
349
+ const currentConfig = await loadConfig();
350
+ if ((currentConfig.mode === 'cloud' || updates.mode === 'cloud') && value !== 'anthropic') {
351
+ console.warn(`\u26A0 Warning: Cloud mode currently only supports Anthropic. Provider "${value}" will be ignored in cloud execution.`);
352
+ }
353
+ }
243
354
  i++;
244
355
  break;
245
356
  case '--model':
@@ -266,6 +377,283 @@ async function handleConfigCommand(args) {
266
377
  console.log('Config updated:');
267
378
  console.log(JSON.stringify(maskConfigForDisplay(config), null, 2));
268
379
  }
380
+ async function handleRunCommand(args) {
381
+ // Parse flags
382
+ let providerFlag;
383
+ let modelFlag;
384
+ let autoApprove = false;
385
+ let dryRun = false;
386
+ let projectDir = process.cwd();
387
+ const positionalArgs = [];
388
+ for (let i = 0; i < args.length; i++) {
389
+ const flag = args[i];
390
+ switch (flag) {
391
+ case '--yes':
392
+ case '-y':
393
+ autoApprove = true;
394
+ break;
395
+ case '--dry-run':
396
+ dryRun = true;
397
+ break;
398
+ case '--provider':
399
+ providerFlag = args[++i];
400
+ break;
401
+ case '--model':
402
+ modelFlag = args[++i];
403
+ break;
404
+ case '--project-dir':
405
+ projectDir = args[++i];
406
+ break;
407
+ default:
408
+ if (!flag.startsWith('--')) {
409
+ positionalArgs.push(flag);
410
+ }
411
+ else {
412
+ console.error(`Unknown flag: ${flag}`);
413
+ console.error('Usage: orchex run "intent" [--yes] [--dry-run] [--provider NAME] [--model NAME]');
414
+ process.exit(1);
415
+ }
416
+ }
417
+ }
418
+ const intent = positionalArgs.join(' ').trim();
419
+ if (!intent) {
420
+ console.error('Error: Missing intent. Usage: orchex run "Add user authentication"');
421
+ process.exit(1);
422
+ }
423
+ // Dynamic imports (these are local-only modules)
424
+ const { gatherProjectContext, generatePlan, parsePlanDocument, extractDeliverables, processDeliverables, buildDependencyGraph, generateStreams, toInitFormat, formatPlanPreview, formatPlanPreviewText, detectSequentialEdits, autoFixSequentialEdits, createDiagnostics, } = await import('./intelligence/index.js');
425
+ const { calculateWaves } = await import('./waves.js');
426
+ const { createExecutor } = await import('./executors/index.js');
427
+ const { initOrchestration, loadManifest, manifestExists } = await import('./manifest.js');
428
+ const { executeWave } = await import('./orchestrator.js');
429
+ const { generateReport, saveReportLocally, generateLearningSummary } = await import('./intelligence/index.js');
430
+ // First-run check
431
+ const { isFirstRun, getFirstRunSuggestion, markFirstRunComplete } = await import('./intelligence/index.js');
432
+ if (await isFirstRun(projectDir)) {
433
+ console.log('\n' + getFirstRunSuggestion() + '\n');
434
+ }
435
+ console.log(`\nGenerating plan for: "${intent}"\n`);
436
+ // Create executor — priority: env var > cached BYOK key > error
437
+ const config = await loadConfig();
438
+ const resolvedProvider = providerFlag
439
+ ? providerFlag
440
+ : (await import('./config.js')).getConfiguredProvider(config);
441
+ // Try env var first (existing behavior)
442
+ const { getProviderApiKey } = await import('./config.js');
443
+ let apiKey = getProviderApiKey(resolvedProvider);
444
+ // Fallback: cached BYOK key from login sync
445
+ if (!apiKey && resolvedProvider !== 'ollama' && resolvedProvider !== 'bedrock') {
446
+ const { getCachedApiKey } = await import('./key-cache.js');
447
+ apiKey = await getCachedApiKey(resolvedProvider);
448
+ // Fallback: fetch fresh from cloud if logged in and cache miss/expired
449
+ if (!apiKey && config.mode === 'cloud' && config.apiKey) {
450
+ try {
451
+ const { writeKeyCache } = await import('./key-cache.js');
452
+ const resolvedApiUrl = await (await import('./config.js')).resolveApiUrl();
453
+ const resp = await fetch(`${resolvedApiUrl}/api/v1/keys/resolve`, {
454
+ headers: { 'Authorization': `Bearer ${config.apiKey}` },
455
+ });
456
+ if (resp.ok) {
457
+ const { keys } = (await resp.json());
458
+ if (keys.length > 0) {
459
+ await writeKeyCache({ keys, fetchedAt: new Date().toISOString() });
460
+ apiKey = keys.find(k => k.provider === resolvedProvider)?.apiKey;
461
+ }
462
+ }
463
+ }
464
+ catch {
465
+ // Non-fatal — will error below if no key found
466
+ }
467
+ }
468
+ }
469
+ if (!apiKey && resolvedProvider !== 'ollama' && resolvedProvider !== 'bedrock') {
470
+ const envVarName = resolvedProvider === 'anthropic' ? 'ANTHROPIC_API_KEY'
471
+ : resolvedProvider === 'openai' ? 'OPENAI_API_KEY'
472
+ : resolvedProvider === 'gemini' ? 'GEMINI_API_KEY'
473
+ : 'DEEPSEEK_API_KEY';
474
+ console.error(`No API key found for provider "${resolvedProvider}".`);
475
+ console.error('Options:');
476
+ console.error(` 1. Set env var: export ${envVarName}=<key>`);
477
+ console.error(' 2. Store a key at https://orchex.dev/dashboard/keys and run `orchex login`');
478
+ process.exit(1);
479
+ }
480
+ const exec = createExecutor({ provider: resolvedProvider, ...(apiKey ? { apiKey } : {}) });
481
+ // Resolve tier BEFORE plan generation so we can pass stream budget to the LLM
482
+ const { checkTierLimits } = await import('./tier-gating.js');
483
+ let effectiveTier;
484
+ try {
485
+ const { resolveEntitledTier, loadPublicKey } = await import('./entitlements/resolve.js');
486
+ const publicKey = await loadPublicKey();
487
+ effectiveTier = await resolveEntitledTier(config, publicKey);
488
+ }
489
+ catch {
490
+ // Fallback if entitlement module not available
491
+ const { getEffectiveTier, getEffectiveLimits } = await import('./tiers.js');
492
+ const effectiveTierId = config.mode === 'cloud' && config.apiKey
493
+ ? getEffectiveTier({
494
+ tier: config.tier ?? 'free',
495
+ createdAt: config.accountCreatedAt ?? new Date().toISOString(),
496
+ trialRunsRemaining: config.trialRunsRemaining ?? 0,
497
+ })
498
+ : (config.tier ?? 'free');
499
+ effectiveTier = getEffectiveLimits(effectiveTierId, null);
500
+ }
501
+ const maxStreams = effectiveTier.maxParallelAgents === -1 ? undefined : effectiveTier.maxParallelAgents;
502
+ // Generate plan
503
+ let planResult;
504
+ try {
505
+ planResult = await generatePlan(intent, projectDir, exec, { model: modelFlag, provider: resolvedProvider, maxStreams });
506
+ }
507
+ catch (err) {
508
+ console.error(`Plan generation failed: ${err.message}`);
509
+ process.exit(1);
510
+ }
511
+ // Run through learn pipeline
512
+ const diagnostics = createDiagnostics();
513
+ const plan = parsePlanDocument(planResult.planMarkdown);
514
+ const rawDeliverables = extractDeliverables(plan, { diagnostics });
515
+ const deliverables = processDeliverables(rawDeliverables);
516
+ if (deliverables.length === 0) {
517
+ console.error('Plan generated no deliverables. Try a more specific intent.');
518
+ process.exit(1);
519
+ }
520
+ const graph = buildDependencyGraph(deliverables, { diagnostics });
521
+ const result = generateStreams(deliverables, graph, {
522
+ validateAntiPatterns: true,
523
+ projectDir,
524
+ diagnostics,
525
+ });
526
+ // Sequential diagnostics
527
+ const seqDiag = detectSequentialEdits(result.streams);
528
+ const initStreams = toInitFormat(result);
529
+ const streamDefs = Object.fromEntries(Object.entries(initStreams).map(([id, s]) => [id, {
530
+ name: s.name, deps: s.deps || [], owns: s.owns || [],
531
+ reads: s.reads || [], setup: [], plan: s.plan,
532
+ verify: s.verify || [], status: 'pending', attempts: 0,
533
+ }]));
534
+ const { waves } = calculateWaves({
535
+ feature: plan.title, streams: streamDefs,
536
+ status: 'pending', created: new Date().toISOString(),
537
+ });
538
+ // Enforce tier limits on the generated plan (safety net)
539
+ const tierCheck = checkTierLimits({ tier: effectiveTier, streams: streamDefs, projectDir, mode: config.mode });
540
+ if (!tierCheck.allowed) {
541
+ console.error(`Tier limit exceeded: ${tierCheck.error}`);
542
+ if (tierCheck.suggestion)
543
+ console.error(tierCheck.suggestion);
544
+ process.exit(1);
545
+ }
546
+ const waveList = waves.map(w => ({ number: w.number, streams: w.streams.map(s => s.id) }));
547
+ const allWarnings = [...result.warnings, ...diagnostics.warnings];
548
+ // Validate models for each stream and build model decisions map
549
+ const { resolveModel } = await import('./model-validator.js');
550
+ const { resolveApiUrl, DEFAULT_MODELS } = await import('./config.js');
551
+ const { estimatePlannedCost } = await import('./intelligence/index.js');
552
+ const modelApiUrl = config.mode === 'cloud' && config.apiKey ? await resolveApiUrl() : undefined;
553
+ const modelDecisions = {};
554
+ for (const [id, stream] of Object.entries(initStreams)) {
555
+ const suggestedProvider = stream.suggestedProvider ?? resolvedProvider;
556
+ const suggestedModel = DEFAULT_MODELS[suggestedProvider] ?? modelFlag;
557
+ const validation = await resolveModel(suggestedProvider, suggestedModel, modelApiUrl);
558
+ // Estimate cost using cost-tracker
559
+ const ownedLines = (stream.owns ?? []).length * 100; // rough estimate
560
+ const costEstimate = estimatePlannedCost(ownedLines * 4, validation.resolvedModel); // ~4 tokens/line
561
+ modelDecisions[id] = {
562
+ provider: suggestedProvider,
563
+ model: validation.resolvedModel,
564
+ estimatedCostUsd: costEstimate.finalCost,
565
+ fallback: validation.wasFallback ? `${validation.originalModel} → ${validation.resolvedModel}` : undefined,
566
+ };
567
+ }
568
+ const preview = formatPlanPreview(initStreams, waveList, allWarnings, seqDiag, modelDecisions);
569
+ // Show preview
570
+ console.log(formatPlanPreviewText(preview));
571
+ if (dryRun) {
572
+ console.log('--- Generated Plan Markdown ---\n');
573
+ console.log(planResult.planMarkdown);
574
+ console.log('\n(Dry run — no execution)');
575
+ process.exit(0);
576
+ }
577
+ // Approval
578
+ if (!autoApprove) {
579
+ const readline = await import('readline');
580
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
581
+ const answer = await new Promise(resolve => {
582
+ rl.question('Execute this plan? [y/N] ', resolve);
583
+ });
584
+ rl.close();
585
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
586
+ console.log('Aborted.');
587
+ process.exit(0);
588
+ }
589
+ }
590
+ // Auto-fix sequential edits before execution
591
+ if (seqDiag.length > 0) {
592
+ autoFixSequentialEdits(result.streams);
593
+ console.log(`Auto-fixed ${seqDiag.length} sequential edit conflict(s).`);
594
+ }
595
+ // Check for existing orchestration
596
+ if (await manifestExists(projectDir)) {
597
+ console.error('An active orchestration already exists. Run `orchex complete --archive` to clear it.');
598
+ process.exit(1);
599
+ }
600
+ // Init and execute
601
+ console.log(`\nInitializing orchestration: "${plan.title}"\n`);
602
+ await initOrchestration(projectDir, plan.title, streamDefs);
603
+ const allResponses = [];
604
+ let executionError;
605
+ try {
606
+ let done = false;
607
+ let waveNum = 1;
608
+ while (!done) {
609
+ console.log(`Executing wave ${waveNum}...`);
610
+ const response = await executeWave(projectDir, exec, { model: modelFlag });
611
+ allResponses.push(response);
612
+ const completed = response.streams.filter(s => s.status === 'complete').length;
613
+ const failed = response.streams.filter(s => s.status === 'failed').length;
614
+ console.log(` Wave ${waveNum}: ${completed} complete, ${failed} failed`);
615
+ done = response.done;
616
+ waveNum++;
617
+ }
618
+ // Generate report
619
+ const manifest = await loadManifest(projectDir);
620
+ const report = generateReport(manifest, allResponses);
621
+ const reportPath = await saveReportLocally(projectDir, report);
622
+ // Sync report to cloud dashboard (fire-and-forget, only if logged in)
623
+ const { syncReportToCloud } = await import('./cloud-sync.js');
624
+ await syncReportToCloud(config, report);
625
+ // Learning summary
626
+ const summary = generateLearningSummary(report);
627
+ console.log('\n--- Orchex learned ---');
628
+ for (const insight of summary) {
629
+ console.log(` ${insight}`);
630
+ }
631
+ console.log(`\nReport saved: ${reportPath}`);
632
+ console.log(`Quality score: ${report.planQualityScore}/100\n`);
633
+ // Mark first-run complete after successful orchestration
634
+ if (await isFirstRun(projectDir)) {
635
+ await markFirstRunComplete(projectDir);
636
+ }
637
+ }
638
+ catch (err) {
639
+ executionError = err;
640
+ console.error(`\nExecution error: ${executionError.message}`);
641
+ }
642
+ finally {
643
+ // Always archive to unblock future runs
644
+ try {
645
+ const { archiveOrchestration } = await import('./manifest.js');
646
+ const archiveName = await archiveOrchestration(projectDir);
647
+ console.log(`Orchestration archived: ${archiveName}`);
648
+ }
649
+ catch {
650
+ // Best-effort — report is already saved if execution succeeded
651
+ }
652
+ }
653
+ if (executionError) {
654
+ process.exit(1);
655
+ }
656
+ }
269
657
  async function main() {
270
658
  const args = process.argv.slice(2);
271
659
  // Handle CLI config command (not MCP)
@@ -285,6 +673,65 @@ async function main() {
285
673
  await handleStatusCommand(args.slice(1));
286
674
  return;
287
675
  }
676
+ if (args[0] === 'run') {
677
+ await handleRunCommand(args.slice(1));
678
+ return;
679
+ }
680
+ if (args[0] === 'reset-learning') {
681
+ const { resetLearning } = await import('./intelligence/index.js');
682
+ const subArgs = args.slice(1);
683
+ const confirm = subArgs.includes('--confirm');
684
+ const patternsOnly = subArgs.includes('--patterns-only');
685
+ const reportsOnly = subArgs.includes('--reports-only');
686
+ try {
687
+ const result = await resetLearning(process.cwd(), { confirm, patternsOnly, reportsOnly });
688
+ console.log(result.message);
689
+ for (const f of result.deleted) {
690
+ console.log(` - ${f}`);
691
+ }
692
+ }
693
+ catch (err) {
694
+ console.error(err.message);
695
+ process.exit(1);
696
+ }
697
+ return;
698
+ }
699
+ if (args[0] === 'complete') {
700
+ const { manifestExists, archiveOrchestration } = await import('./manifest.js');
701
+ const subArgs = args.slice(1);
702
+ const pdFlag = subArgs.indexOf('--project-dir');
703
+ const projectDir = pdFlag !== -1 && subArgs[pdFlag + 1] ? subArgs[pdFlag + 1] : process.cwd();
704
+ if (!(await manifestExists(projectDir))) {
705
+ console.log('No active orchestration to archive.');
706
+ return;
707
+ }
708
+ if (subArgs.includes('--archive')) {
709
+ try {
710
+ const archiveName = await archiveOrchestration(projectDir);
711
+ console.log(`Orchestration archived: ${archiveName}`);
712
+ }
713
+ catch (err) {
714
+ console.error(`Archive failed: ${err.message}`);
715
+ process.exit(1);
716
+ }
717
+ }
718
+ else {
719
+ console.error('Usage: orchex complete --archive');
720
+ }
721
+ return;
722
+ }
723
+ if (args[0] === 'setup') {
724
+ const { handleSetupCommand } = await import('./setup/index.js');
725
+ await handleSetupCommand(args.slice(1));
726
+ return;
727
+ }
728
+ if (args[0] === '--version' || args[0] === '-v') {
729
+ const { createRequire } = await import('module');
730
+ const require = createRequire(import.meta.url);
731
+ const pkg = require('../package.json');
732
+ console.log(pkg.version);
733
+ process.exit(0);
734
+ }
288
735
  if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
289
736
  printHelp();
290
737
  process.exit(0);
@@ -295,11 +742,9 @@ async function main() {
295
742
  setProjectDir(args[pdIdx + 1]);
296
743
  }
297
744
  // Start MCP server
298
- const server = new McpServer({
299
- name: 'orchex',
300
- version: '1.0.0',
301
- });
745
+ const server = new McpServer({ name: 'orchex', version: '1.0.0' }, { instructions: ORCHEX_INSTRUCTIONS, capabilities: { logging: {} } });
302
746
  registerTools(server);
747
+ registerResources(server);
303
748
  const transport = new StdioServerTransport();
304
749
  await server.connect(transport);
305
750
  }
@@ -0,0 +1,44 @@
1
+ export { gatherProjectContext, generatePlan } from './auto-planner.js';
2
+ export { parsePlanDocument, getSectionsAtLevel, isUnpopulatedTemplate } from './plan-parser.js';
3
+ export { extractDeliverables, processDeliverables, formatDeliverablesReport } from './deliverable-extractor.js';
4
+ export type { Deliverable } from './deliverable-extractor.js';
5
+ export { buildDependencyGraph, formatDependencyReport } from './dependency-inferrer.js';
6
+ export { generateStreams, formatStreamsForReview, toInitFormat, extractPrerequisites } from './stream-generator.js';
7
+ export { formatPlanPreview, formatPlanPreviewText, type ModelDecision } from './plan-preview.js';
8
+ export { detectSequentialEdits, autoFixSequentialEdits } from './sequential-diagnostics.js';
9
+ export type { SequentialEditDiagnostic } from './sequential-diagnostics.js';
10
+ export { createDiagnostics, detectOwnershipConflicts } from './diagnostics.js';
11
+ export type { LearnDiagnostics } from './diagnostics.js';
12
+ export { generateReport, saveReportLocally } from './execution-report.js';
13
+ export type { ExecutionReport, ReportStreamResult } from './execution-report.js';
14
+ export { generateLearningSummary } from './learning-summary.js';
15
+ export { isFirstRun, getFirstRunSuggestion, markFirstRunComplete } from './first-run.js';
16
+ export { resetLearning } from './reset-learning.js';
17
+ export { analyzeError } from './error-analyzer.js';
18
+ export type { ErrorCategory, ErrorAnalysis } from './error-analyzer.js';
19
+ export { evaluateCompletion } from './iteration-evaluator.js';
20
+ export type { StreamResultSummary } from './iteration-evaluator.js';
21
+ export { DEFAULT_THRESHOLDS, categorizeStream, getRecommendedLimit, getThresholds, streamResultToTelemetryEvent, appendLocalEvents, runLearningCycle, } from './learning-engine.js';
22
+ export type { LearnedThresholds, LearningStreamResult } from './learning-engine.js';
23
+ export { createDetector } from './anti-pattern-detector.js';
24
+ export type { AntiPatternIssue, BatchAnalysisResult } from './anti-pattern-detector.js';
25
+ export { generateAllSuggestions, getSuggestionsSummary } from './split-suggester.js';
26
+ export type { InteractiveSuggestion } from './split-suggester.js';
27
+ export { autoMergeOwnershipConflicts } from './ownership-resolver.js';
28
+ export { suggestProvider, classifyTask } from './model-routing.js';
29
+ export { applyPartialApproval, rollbackStream } from './interactive-approval.js';
30
+ export { generateFixStream } from './self-healer.js';
31
+ export { cleanupOrphanFixStreams, getFixChainInfo, isFixStream, getOriginalStreamId, onStreamComplete } from './fix-stream-manager.js';
32
+ export { routeStream, loadRoutingRules } from './smart-router.js';
33
+ export type { RouteDecision, RouteOptions } from './smart-router.js';
34
+ export { detectPatterns, formatPatterns, patternsToPromptHints, savePatterns, loadPatterns, adaptReport } from './pattern-detector.js';
35
+ export type { DetectorInput } from './pattern-detector.js';
36
+ export { pruneUnusedFiles, estimateTokens, generateCachingHints } from './context-optimizer.js';
37
+ export { checkBudget, createBudgetConfig, ContextBudgetExceededError } from './budget-enforcer.js';
38
+ export type { BudgetCheckResult } from './budget-enforcer.js';
39
+ export { extractRelevantChunks } from './file-chunker.js';
40
+ export { getModelCosts, estimatePlannedCost } from './cost-tracker.js';
41
+ export { extractImports, resolveImportPath } from './heuristics.js';
42
+ export { findMatchingTemplate, applyTemplate, suggestSplit } from './slicing-templates.js';
43
+ export { optimizeTopology } from './topology-optimizer.js';
44
+ export type { TopologyInput, TopologyReport } from './topology-optimizer.js';