@renseiai/agentfactory 0.8.19 → 0.8.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 (87) hide show
  1. package/dist/src/config/repository-config.d.ts +7 -0
  2. package/dist/src/config/repository-config.d.ts.map +1 -1
  3. package/dist/src/config/repository-config.js +15 -1
  4. package/dist/src/config/repository-config.test.js +1 -1
  5. package/dist/src/governor/decision-engine-adapter.js +5 -10
  6. package/dist/src/governor/decision-engine-adapter.test.js +13 -14
  7. package/dist/src/governor/decision-engine.js +3 -7
  8. package/dist/src/governor/decision-engine.test.js +5 -5
  9. package/dist/src/index.d.ts +1 -0
  10. package/dist/src/index.d.ts.map +1 -1
  11. package/dist/src/index.js +1 -0
  12. package/dist/src/merge-queue/adapters/local.d.ts +68 -0
  13. package/dist/src/merge-queue/adapters/local.d.ts.map +1 -0
  14. package/dist/src/merge-queue/adapters/local.js +136 -0
  15. package/dist/src/merge-queue/adapters/local.test.d.ts +2 -0
  16. package/dist/src/merge-queue/adapters/local.test.d.ts.map +1 -0
  17. package/dist/src/merge-queue/adapters/local.test.js +176 -0
  18. package/dist/src/merge-queue/index.d.ts +13 -5
  19. package/dist/src/merge-queue/index.d.ts.map +1 -1
  20. package/dist/src/merge-queue/index.js +13 -6
  21. package/dist/src/merge-queue/merge-queue.integration.test.js +19 -0
  22. package/dist/src/merge-queue/merge-worker.d.ts.map +1 -1
  23. package/dist/src/merge-queue/merge-worker.js +29 -0
  24. package/dist/src/merge-queue/types.d.ts +1 -1
  25. package/dist/src/merge-queue/types.d.ts.map +1 -1
  26. package/dist/src/orchestrator/index.d.ts +4 -0
  27. package/dist/src/orchestrator/index.d.ts.map +1 -1
  28. package/dist/src/orchestrator/index.js +3 -0
  29. package/dist/src/orchestrator/orchestrator.d.ts +31 -0
  30. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
  31. package/dist/src/orchestrator/orchestrator.js +263 -11
  32. package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
  33. package/dist/src/orchestrator/parse-work-result.js +3 -1
  34. package/dist/src/orchestrator/parse-work-result.test.js +6 -0
  35. package/dist/src/orchestrator/quality-baseline.d.ts +83 -0
  36. package/dist/src/orchestrator/quality-baseline.d.ts.map +1 -0
  37. package/dist/src/orchestrator/quality-baseline.js +313 -0
  38. package/dist/src/orchestrator/quality-baseline.test.d.ts +2 -0
  39. package/dist/src/orchestrator/quality-baseline.test.d.ts.map +1 -0
  40. package/dist/src/orchestrator/quality-baseline.test.js +448 -0
  41. package/dist/src/orchestrator/quality-ratchet.d.ts +70 -0
  42. package/dist/src/orchestrator/quality-ratchet.d.ts.map +1 -0
  43. package/dist/src/orchestrator/quality-ratchet.js +162 -0
  44. package/dist/src/orchestrator/quality-ratchet.test.d.ts +2 -0
  45. package/dist/src/orchestrator/quality-ratchet.test.d.ts.map +1 -0
  46. package/dist/src/orchestrator/quality-ratchet.test.js +335 -0
  47. package/dist/src/orchestrator/types.d.ts +2 -0
  48. package/dist/src/orchestrator/types.d.ts.map +1 -1
  49. package/dist/src/providers/codex-app-server-provider.d.ts +37 -1
  50. package/dist/src/providers/codex-app-server-provider.d.ts.map +1 -1
  51. package/dist/src/providers/codex-app-server-provider.js +290 -35
  52. package/dist/src/providers/codex-app-server-provider.test.js +72 -12
  53. package/dist/src/providers/codex-approval-bridge.d.ts +49 -0
  54. package/dist/src/providers/codex-approval-bridge.d.ts.map +1 -0
  55. package/dist/src/providers/codex-approval-bridge.js +117 -0
  56. package/dist/src/providers/codex-approval-bridge.test.d.ts +2 -0
  57. package/dist/src/providers/codex-approval-bridge.test.d.ts.map +1 -0
  58. package/dist/src/providers/codex-approval-bridge.test.js +188 -0
  59. package/dist/src/providers/types.d.ts +25 -0
  60. package/dist/src/providers/types.d.ts.map +1 -1
  61. package/dist/src/routing/types.d.ts +1 -1
  62. package/dist/src/templates/adapters.d.ts +25 -0
  63. package/dist/src/templates/adapters.d.ts.map +1 -1
  64. package/dist/src/templates/adapters.js +70 -0
  65. package/dist/src/templates/adapters.test.js +49 -0
  66. package/dist/src/templates/index.d.ts +1 -0
  67. package/dist/src/templates/index.d.ts.map +1 -1
  68. package/dist/src/templates/registry.d.ts +8 -0
  69. package/dist/src/templates/registry.d.ts.map +1 -1
  70. package/dist/src/templates/registry.js +11 -0
  71. package/dist/src/templates/types.d.ts +22 -0
  72. package/dist/src/templates/types.d.ts.map +1 -1
  73. package/dist/src/templates/types.js +12 -0
  74. package/dist/src/tools/index.d.ts +2 -0
  75. package/dist/src/tools/index.d.ts.map +1 -1
  76. package/dist/src/tools/index.js +1 -0
  77. package/dist/src/tools/registry.d.ts +9 -1
  78. package/dist/src/tools/registry.d.ts.map +1 -1
  79. package/dist/src/tools/registry.js +13 -1
  80. package/dist/src/tools/stdio-server-entry.d.ts +25 -0
  81. package/dist/src/tools/stdio-server-entry.d.ts.map +1 -0
  82. package/dist/src/tools/stdio-server-entry.js +205 -0
  83. package/dist/src/tools/stdio-server.d.ts +87 -0
  84. package/dist/src/tools/stdio-server.d.ts.map +1 -0
  85. package/dist/src/tools/stdio-server.js +138 -0
  86. package/dist/src/workflow/workflow-types.d.ts +3 -3
  87. package/package.json +3 -2
@@ -18,10 +18,11 @@ import { isSessionLoggingEnabled, getLogAnalysisConfig } from './log-config.js';
18
18
  import { parseWorkResult } from './parse-work-result.js';
19
19
  import { parseSecurityScanOutput } from './security-scan-event.js';
20
20
  import { runBackstop, formatBackstopComment } from './session-backstop.js';
21
+ import { captureQualityBaseline, computeQualityDelta, formatQualityReport, saveBaseline, loadBaseline, } from './quality-baseline.js';
21
22
  import { createActivityEmitter } from './activity-emitter.js';
22
23
  import { createApiActivityEmitter } from './api-activity-emitter.js';
23
24
  import { createLogger } from '../logger.js';
24
- import { TemplateRegistry, createToolPermissionAdapter } from '../templates/index.js';
25
+ import { TemplateRegistry, CodexToolPermissionAdapter, createToolPermissionAdapter } from '../templates/index.js';
25
26
  import { loadRepositoryConfig, getProjectConfig, getProvidersConfig } from '../config/index.js';
26
27
  import { ToolRegistry } from '../tools/index.js';
27
28
  import { createMergeQueueAdapter } from '../merge-queue/index.js';
@@ -1009,8 +1010,11 @@ export class AgentOrchestrator {
1009
1010
  // Initialize merge queue adapter from repository config
1010
1011
  if (repoConfig.mergeQueue?.enabled && !config.mergeQueueAdapter) {
1011
1012
  try {
1012
- this.mergeQueueAdapter = createMergeQueueAdapter(repoConfig.mergeQueue.provider ?? 'github-native');
1013
- console.log(`[orchestrator] Merge queue adapter initialized: ${repoConfig.mergeQueue.provider ?? 'github-native'}`);
1013
+ const provider = repoConfig.mergeQueue.provider ?? 'local';
1014
+ this.mergeQueueAdapter = createMergeQueueAdapter(provider, {
1015
+ storage: config.mergeQueueStorage,
1016
+ });
1017
+ console.log(`[orchestrator] Merge queue adapter initialized: ${provider}`);
1014
1018
  }
1015
1019
  catch (error) {
1016
1020
  console.warn(`[orchestrator] Failed to initialize merge queue adapter: ${error instanceof Error ? error.message : String(error)}`);
@@ -1610,6 +1614,19 @@ export class AgentOrchestrator {
1610
1614
  this.writeWorktreeHelpers(worktreePath);
1611
1615
  // Configure mergiraf merge driver if enabled
1612
1616
  this.configureMergiraf(worktreePath);
1617
+ // Capture quality baseline for delta checking (runs test/typecheck on main)
1618
+ if (this.isQualityBaselineEnabled()) {
1619
+ try {
1620
+ const qualityConfig = this.buildQualityConfig();
1621
+ const baseline = captureQualityBaseline(worktreePath, qualityConfig);
1622
+ saveBaseline(worktreePath, baseline);
1623
+ console.log(`Quality baseline captured: ${baseline.tests.total} tests, ${baseline.typecheck.errorCount} type errors, ${baseline.lint.errorCount} lint errors`);
1624
+ }
1625
+ catch (baselineError) {
1626
+ // Log but don't fail worktree creation — quality gate is advisory
1627
+ console.warn(`Failed to capture quality baseline: ${baselineError instanceof Error ? baselineError.message : String(baselineError)}`);
1628
+ }
1629
+ }
1613
1630
  return { worktreePath, worktreeIdentifier };
1614
1631
  }
1615
1632
  /**
@@ -1770,6 +1787,48 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
1770
1787
  console.warn(`Failed to configure mergiraf in worktree: ${error instanceof Error ? error.message : String(error)}`);
1771
1788
  }
1772
1789
  }
1790
+ /**
1791
+ * Check if quality baseline capture is enabled via repository config.
1792
+ */
1793
+ isQualityBaselineEnabled() {
1794
+ const quality = this.repoConfig?.quality;
1795
+ return quality?.baselineEnabled ?? false;
1796
+ }
1797
+ /**
1798
+ * Build quality config from orchestrator settings.
1799
+ */
1800
+ buildQualityConfig() {
1801
+ return {
1802
+ testCommand: this.testCommand,
1803
+ validateCommand: this.validateCommand,
1804
+ packageManager: this.packageManager ?? 'pnpm',
1805
+ timeoutMs: 120_000,
1806
+ };
1807
+ }
1808
+ /**
1809
+ * Load quality baseline from a worktree and convert to TemplateContext shape.
1810
+ */
1811
+ loadQualityBaselineForContext(worktreePath) {
1812
+ if (!worktreePath || !this.isQualityBaselineEnabled())
1813
+ return undefined;
1814
+ try {
1815
+ const baseline = loadBaseline(worktreePath);
1816
+ if (!baseline)
1817
+ return undefined;
1818
+ return {
1819
+ tests: {
1820
+ total: baseline.tests.total,
1821
+ passed: baseline.tests.passed,
1822
+ failed: baseline.tests.failed,
1823
+ },
1824
+ typecheckErrors: baseline.typecheck.errorCount,
1825
+ lintErrors: baseline.lint.errorCount,
1826
+ };
1827
+ }
1828
+ catch {
1829
+ return undefined;
1830
+ }
1831
+ }
1773
1832
  /**
1774
1833
  * Link dependencies from the main repo into a worktree via symlinks.
1775
1834
  *
@@ -2108,6 +2167,61 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2108
2167
  * Resolve the provider for a specific spawn, using the full priority cascade.
2109
2168
  * Returns a cached provider instance (creating one if needed) and the resolved name.
2110
2169
  */
2170
+ /**
2171
+ * Build base instructions for Codex App Server agents (SUP-1746).
2172
+ *
2173
+ * Assembles safety rules and project-specific instructions (AGENTS.md / CLAUDE.md)
2174
+ * into a persistent system prompt passed via `instructions` on `thread/start`.
2175
+ */
2176
+ buildCodexBaseInstructions(workType, worktreePath) {
2177
+ const sections = [];
2178
+ // Safety rules — mirrors autonomousCanUseTool deny patterns as natural-language rules
2179
+ sections.push(`# Safety Rules
2180
+
2181
+ You are running in an AgentFactory-managed worktree. Follow these rules strictly:
2182
+
2183
+ 1. NEVER run: rm -rf / (or any rm of the filesystem root)
2184
+ 2. NEVER run: git worktree remove, git worktree prune
2185
+ 3. NEVER run: git reset --hard
2186
+ 4. NEVER run: git push --force (use --force-with-lease on feature branches if needed)
2187
+ 5. NEVER run: git checkout <branch>, git switch <branch> (do not change the checked-out branch)
2188
+ 6. NEVER modify files in the .git directory
2189
+ 7. Commit changes with descriptive messages before reporting completion`);
2190
+ // Project-specific instructions — load AGENTS.md or CLAUDE.md from worktree root
2191
+ if (worktreePath) {
2192
+ for (const filename of ['AGENTS.md', 'CLAUDE.md']) {
2193
+ const instrPath = resolve(worktreePath, filename);
2194
+ if (existsSync(instrPath)) {
2195
+ try {
2196
+ const content = readFileSync(instrPath, 'utf-8');
2197
+ if (content.trim()) {
2198
+ sections.push(`# Project Instructions (${filename})\n\n${content.trim()}`);
2199
+ break; // Only load one: AGENTS.md takes priority
2200
+ }
2201
+ }
2202
+ catch {
2203
+ // Ignore read errors — project instructions are optional
2204
+ }
2205
+ }
2206
+ }
2207
+ }
2208
+ return sections.join('\n\n');
2209
+ }
2210
+ /**
2211
+ * Build Codex permission config from template permissions (SUP-1748).
2212
+ *
2213
+ * Translates abstract template `tools.allow` / `tools.disallow` into
2214
+ * structured regex patterns for the Codex approval bridge.
2215
+ */
2216
+ buildCodexPermissionConfig(workType) {
2217
+ if (!this.templateRegistry || !workType)
2218
+ return undefined;
2219
+ const { allow, disallow } = this.templateRegistry.getRawToolPermissions(workType);
2220
+ if (allow.length === 0 && disallow.length === 0)
2221
+ return undefined;
2222
+ const adapter = new CodexToolPermissionAdapter();
2223
+ return adapter.buildPermissionConfig(allow, disallow);
2224
+ }
2111
2225
  resolveProviderForSpawn(context) {
2112
2226
  const { name, source } = resolveProviderWithSource({
2113
2227
  project: context.projectName,
@@ -2154,6 +2268,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2154
2268
  testCommand: perProject?.testCommand ?? this.testCommand,
2155
2269
  validateCommand: perProject?.validateCommand ?? this.validateCommand,
2156
2270
  agentBugBacklog: process.env.AGENT_BUG_BACKLOG || undefined,
2271
+ mergeQueueEnabled: !!this.mergeQueueAdapter,
2272
+ qualityBaseline: this.loadQualityBaselineForContext(worktreePath),
2157
2273
  };
2158
2274
  const rendered = this.templateRegistry.renderPrompt(workType, context);
2159
2275
  prompt = rendered ?? generatePromptForWorkType(identifier, workType);
@@ -2334,15 +2450,27 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2334
2450
  env.LINEAR_TEAM_NAME = teamName;
2335
2451
  }
2336
2452
  log.info('Starting agent via provider', { provider: spawnProviderName, source: providerSource, cwd: worktreePath ?? 'repo-root', workType, promptPreview: prompt.substring(0, 50) });
2337
- // Create in-process tool servers from registered plugins
2453
+ // Create tool servers from registered plugins
2454
+ const toolPluginContext = { env, cwd: worktreePath ?? process.cwd() };
2338
2455
  const toolServers = spawnProviderName === 'claude'
2339
- ? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
2456
+ ? this.toolRegistry.createServers(toolPluginContext)
2457
+ : undefined;
2458
+ // Create stdio MCP server configs for Codex provider (SUP-1744)
2459
+ const stdioServers = spawnProviderName === 'codex'
2460
+ ? this.toolRegistry.createStdioServerConfigs(toolPluginContext)
2340
2461
  : undefined;
2341
2462
  // Coordinators need significantly more turns than standard agents
2342
2463
  // since they spawn sub-agents and poll their status repeatedly.
2343
2464
  // Inflight also gets the bump — it may be resuming coordination work.
2344
2465
  const needsMoreTurns = workType === 'coordination' || workType === 'inflight-coordination' || workType === 'qa-coordination' || workType === 'acceptance-coordination' || workType === 'refinement-coordination' || workType === 'inflight';
2345
2466
  const maxTurns = needsMoreTurns ? 200 : undefined;
2467
+ // SUP-1746/SUP-1748: Build Codex-specific base instructions and permission config
2468
+ const codexBaseInstructions = spawnProviderName === 'codex'
2469
+ ? this.buildCodexBaseInstructions(workType, worktreePath)
2470
+ : undefined;
2471
+ const codexPermissionConfig = spawnProviderName === 'codex' && this.templateRegistry
2472
+ ? this.buildCodexPermissionConfig(workType)
2473
+ : undefined;
2346
2474
  // Spawn agent via provider interface
2347
2475
  const spawnConfig = {
2348
2476
  prompt,
@@ -2353,7 +2481,10 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2353
2481
  sandboxEnabled: this.config.sandboxEnabled,
2354
2482
  mcpServers: toolServers?.servers,
2355
2483
  mcpToolNames: toolServers?.toolNames,
2484
+ mcpStdioServers: stdioServers?.servers,
2356
2485
  maxTurns,
2486
+ baseInstructions: codexBaseInstructions,
2487
+ permissionConfig: codexPermissionConfig,
2357
2488
  onProcessSpawned: (pid) => {
2358
2489
  agent.pid = pid;
2359
2490
  log.info('Agent process spawned', { pid });
@@ -2525,10 +2656,66 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2525
2656
  }
2526
2657
  }
2527
2658
  }
2659
+ // --- Quality Gate: Check quality delta for code-producing work types ---
2660
+ if (agent.status === 'completed' && agent.worktreePath && this.isQualityBaselineEnabled()) {
2661
+ const codeProducingTypes = ['development', 'inflight', 'coordination', 'inflight-coordination'];
2662
+ const agentWorkType = agent.workType ?? 'development';
2663
+ if (codeProducingTypes.includes(agentWorkType)) {
2664
+ try {
2665
+ const baseline = loadBaseline(agent.worktreePath);
2666
+ if (baseline) {
2667
+ const qualityConfig = this.buildQualityConfig();
2668
+ const current = captureQualityBaseline(agent.worktreePath, qualityConfig);
2669
+ const delta = computeQualityDelta(baseline, current);
2670
+ if (!delta.passed) {
2671
+ const report = formatQualityReport(baseline, current, delta);
2672
+ log?.warn('Quality gate FAILED — agent worsened quality metrics', {
2673
+ testFailuresDelta: delta.testFailuresDelta,
2674
+ typeErrorsDelta: delta.typeErrorsDelta,
2675
+ lintErrorsDelta: delta.lintErrorsDelta,
2676
+ });
2677
+ // Post quality gate failure comment
2678
+ try {
2679
+ await this.client.createComment(issueId, `## Quality Gate Failed\n\n` +
2680
+ `The agent's changes worsened quality metrics compared to the baseline (main).\n\n` +
2681
+ report +
2682
+ `\n\n**Status promotion blocked.** The agent must fix quality regressions before this work can advance to QA.`);
2683
+ }
2684
+ catch {
2685
+ // Best-effort comment
2686
+ }
2687
+ // Block status promotion by marking agent as failed
2688
+ agent.status = 'failed';
2689
+ agent.workResult = 'failed';
2690
+ }
2691
+ else {
2692
+ log?.info('Quality gate passed', {
2693
+ testFailuresDelta: delta.testFailuresDelta,
2694
+ typeErrorsDelta: delta.typeErrorsDelta,
2695
+ testCountDelta: delta.testCountDelta,
2696
+ });
2697
+ if (delta.testFailuresDelta < 0 || delta.typeErrorsDelta < 0 || delta.lintErrorsDelta < 0) {
2698
+ log?.info('Boy scout rule: agent improved quality metrics', {
2699
+ testFailuresDelta: delta.testFailuresDelta,
2700
+ typeErrorsDelta: delta.typeErrorsDelta,
2701
+ lintErrorsDelta: delta.lintErrorsDelta,
2702
+ });
2703
+ }
2704
+ }
2705
+ }
2706
+ }
2707
+ catch (qualityError) {
2708
+ log?.warn('Quality gate check failed (non-fatal)', {
2709
+ error: qualityError instanceof Error ? qualityError.message : String(qualityError),
2710
+ });
2711
+ // Quality gate check failure should not block the session — degrade gracefully
2712
+ }
2713
+ }
2714
+ }
2528
2715
  // Update Linear status based on work type if auto-transition is enabled
2529
2716
  if ((agent.status === 'completed' || agent.status === 'failed') && this.config.autoTransition) {
2530
2717
  const workType = agent.workType ?? 'development';
2531
- const isResultSensitive = workType === 'qa' || workType === 'acceptance' || workType === 'coordination' || workType === 'qa-coordination' || workType === 'acceptance-coordination';
2718
+ const isResultSensitive = workType === 'qa' || workType === 'acceptance' || workType === 'coordination' || workType === 'qa-coordination' || workType === 'acceptance-coordination' || workType === 'inflight-coordination' || workType === 'merge';
2532
2719
  let targetStatus = null;
2533
2720
  if (isResultSensitive) {
2534
2721
  if (agent.status === 'failed') {
@@ -3529,10 +3716,20 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
3529
3716
  });
3530
3717
  // Build recovery prompt
3531
3718
  const recoveryPrompt = prompt ?? buildRecoveryPrompt(recoveryCheck.state, recoveryCheck.todos);
3532
- // Use existing provider session ID for resume if available
3533
- const providerSessionId = recoveryCheck.state.providerSessionId ?? undefined;
3534
3719
  // Inherit work type from previous state if not provided
3535
3720
  const recoveryWorkType = workType ?? recoveryCheck.state.workType ?? effectiveWorkType;
3721
+ // Use existing provider session ID for resume if available,
3722
+ // but clear it when the work type has changed (e.g., dev → QA).
3723
+ // A session from a different work type cannot be resumed — attempting
3724
+ // it produces "No conversation found" and wastes the recovery attempt.
3725
+ const workTypeChanged = recoveryWorkType !== recoveryCheck.state.workType;
3726
+ const providerSessionId = workTypeChanged
3727
+ ? undefined
3728
+ : (recoveryCheck.state.providerSessionId ?? undefined);
3729
+ if (workTypeChanged && recoveryCheck.state.providerSessionId) {
3730
+ console.log(`Clearing stale providerSessionId — work type changed from ${recoveryCheck.state.workType} to ${recoveryWorkType}`);
3731
+ updateState(worktreePath, { providerSessionId: null });
3732
+ }
3536
3733
  const effectiveSessionId = sessionId ?? recoveryCheck.state.linearSessionId ?? randomUUID();
3537
3734
  console.log(`Resuming work on ${identifier} (recovery attempt ${updatedState?.recoveryAttempts ?? 1})`);
3538
3735
  // Update status based on work type if auto-transition is enabled
@@ -4065,14 +4262,26 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
4065
4262
  resuming: !!providerSessionId,
4066
4263
  workType: workType ?? 'development',
4067
4264
  });
4068
- // Create in-process tool servers from registered plugins
4265
+ // Create tool servers from registered plugins
4266
+ const toolPluginContext = { env, cwd: worktreePath ?? process.cwd() };
4069
4267
  const toolServers = spawnProviderName === 'claude'
4070
- ? this.toolRegistry.createServers({ env, cwd: worktreePath ?? process.cwd() })
4268
+ ? this.toolRegistry.createServers(toolPluginContext)
4269
+ : undefined;
4270
+ // Create stdio MCP server configs for Codex provider (SUP-1744)
4271
+ const stdioServers = spawnProviderName === 'codex'
4272
+ ? this.toolRegistry.createStdioServerConfigs(toolPluginContext)
4071
4273
  : undefined;
4072
4274
  // Coordinators need significantly more turns than standard agents
4073
4275
  const resolvedWorkType = workType ?? 'development';
4074
4276
  const needsMoreTurns = resolvedWorkType === 'coordination' || resolvedWorkType === 'qa-coordination' || resolvedWorkType === 'acceptance-coordination' || resolvedWorkType === 'refinement-coordination' || resolvedWorkType === 'inflight';
4075
4277
  const maxTurns = needsMoreTurns ? 200 : undefined;
4278
+ // SUP-1746/SUP-1748: Build Codex-specific base instructions and permission config
4279
+ const codexBaseInstructions = spawnProviderName === 'codex'
4280
+ ? this.buildCodexBaseInstructions(workType, worktreePath)
4281
+ : undefined;
4282
+ const codexPermissionConfig = spawnProviderName === 'codex' && this.templateRegistry
4283
+ ? this.buildCodexPermissionConfig(workType)
4284
+ : undefined;
4076
4285
  // Spawn agent via provider interface (with resume if session ID available)
4077
4286
  const spawnConfig = {
4078
4287
  prompt,
@@ -4083,14 +4292,17 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
4083
4292
  sandboxEnabled: this.config.sandboxEnabled,
4084
4293
  mcpServers: toolServers?.servers,
4085
4294
  mcpToolNames: toolServers?.toolNames,
4295
+ mcpStdioServers: stdioServers?.servers,
4086
4296
  maxTurns,
4297
+ baseInstructions: codexBaseInstructions,
4298
+ permissionConfig: codexPermissionConfig,
4087
4299
  onProcessSpawned: (pid) => {
4088
4300
  agent.pid = pid;
4089
4301
  log.info('Agent process spawned', { pid });
4090
4302
  },
4091
4303
  };
4092
4304
  const handle = providerSessionId
4093
- ? spawnProvider.resume(providerSessionId, spawnConfig)
4305
+ ? this.createResumeWithFallbackHandle(spawnProvider, providerSessionId, spawnConfig, agent, log)
4094
4306
  : spawnProvider.spawn(spawnConfig);
4095
4307
  this.agentHandles.set(issueId, handle);
4096
4308
  agent.status = 'running';
@@ -4098,6 +4310,46 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
4098
4310
  this.processEventStream(issueId, identifier, sessionId, handle, emitter, agent);
4099
4311
  return agent;
4100
4312
  }
4313
+ /**
4314
+ * Create a resume handle that falls back to a fresh spawn if the session is stale.
4315
+ * This avoids wasting a recovery attempt when the Claude Code session has expired.
4316
+ */
4317
+ createResumeWithFallbackHandle(provider, providerSessionId, spawnConfig, agent, log) {
4318
+ let currentHandle = provider.resume(providerSessionId, spawnConfig);
4319
+ const fallbackStream = async function* () {
4320
+ for await (const event of currentHandle.stream) {
4321
+ // Detect stale session error: the resume failed because the session no longer exists
4322
+ if (event.type === 'result' &&
4323
+ !event.success &&
4324
+ event.errors?.some(e => e.includes('No conversation found with session ID'))) {
4325
+ log?.warn('Stale session detected during resume — falling back to fresh spawn', {
4326
+ staleSessionId: providerSessionId,
4327
+ });
4328
+ // Clear stale session from worktree state
4329
+ if (agent.worktreePath) {
4330
+ try {
4331
+ updateState(agent.worktreePath, { providerSessionId: null });
4332
+ }
4333
+ catch {
4334
+ // Ignore state update errors
4335
+ }
4336
+ }
4337
+ agent.providerSessionId = undefined;
4338
+ // Spawn fresh and yield all its events instead
4339
+ currentHandle = provider.spawn(spawnConfig);
4340
+ yield* currentHandle.stream;
4341
+ return;
4342
+ }
4343
+ yield event;
4344
+ }
4345
+ };
4346
+ return {
4347
+ get sessionId() { return currentHandle.sessionId; },
4348
+ stream: fallbackStream(),
4349
+ injectMessage: (text) => currentHandle.injectMessage(text),
4350
+ stop: () => currentHandle.stop(),
4351
+ };
4352
+ }
4101
4353
  /**
4102
4354
  * Stop all running agents
4103
4355
  */
@@ -1 +1 @@
1
- {"version":3,"file":"parse-work-result.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/parse-work-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA6HjD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,QAAQ,EAAE,aAAa,GACtB,eAAe,CAkDjB"}
1
+ {"version":3,"file":"parse-work-result.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/parse-work-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA+HjD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,QAAQ,EAAE,aAAa,GACtB,eAAe,CAkDjB"}
@@ -71,8 +71,10 @@ const ACCEPTANCE_FAIL_PATTERNS = [
71
71
  * These apply to the 'coordination' work type (development coordination).
72
72
  */
73
73
  const COORDINATION_PASS_PATTERNS = [
74
- // "all 8/8 sub-issues completed" or "all sub-issues completed"
74
+ // "all 8/8 sub-issues completed" or "all sub-issues completed/finished"
75
75
  /\ball\s+(?:\d+\/\d+\s+)?sub-issues?\s+(?:completed|finished)/i,
76
+ // "All N sub-issues: Finished" / "All 6 sub-issues: Finished" (colon-separated format)
77
+ /\ball\s+\d+\s+sub-issues?[:\s—–-]+\s*finished/i,
76
78
  // "8/8 sub-issues completed/finished" (without leading "all")
77
79
  /\b(\d+)\/\1\b.*\bsub-issues?\s+(?:completed|finished)/i,
78
80
  // Explicit result labels
@@ -185,6 +185,12 @@ describe('parseWorkResult', () => {
185
185
  it('detects "Parent issue marked Finished"', () => {
186
186
  expect(parseWorkResult('Execution: Wave 1...Wave 2...Wave 3. Parent issue marked Finished in Linear.', 'coordination')).toBe('passed');
187
187
  });
188
+ it('detects "All 6 sub-issues: Finished" (colon format)', () => {
189
+ expect(parseWorkResult('### All 6 sub-issues: Finished\n\n| SUP-1607 | SubscriptionProvider | Finished |', 'coordination')).toBe('passed');
190
+ });
191
+ it('detects "All 3 sub-issues — Finished" (dash format)', () => {
192
+ expect(parseWorkResult('All 3 sub-issues — Finished. PR created.', 'coordination')).toBe('passed');
193
+ });
188
194
  it('does not match coordination patterns for non-coordination work types', () => {
189
195
  expect(parseWorkResult('All sub-issues completed.', 'development')).toBe('unknown');
190
196
  expect(parseWorkResult('All sub-issues completed.', 'qa')).toBe('unknown');
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Quality Baseline — Capture & Compare
3
+ *
4
+ * Captures quality metrics (test counts, typecheck errors, lint errors) from a
5
+ * worktree at a point in time. The orchestrator captures a baseline on main
6
+ * before the agent starts, then compares after the agent finishes. If the agent
7
+ * made quality worse (delta > 0 for any failure metric), promotion is blocked.
8
+ *
9
+ * This module is standalone — no orchestrator or template dependencies.
10
+ */
11
+ export interface QualityBaseline {
12
+ timestamp: string;
13
+ commitSha: string;
14
+ tests: {
15
+ total: number;
16
+ passed: number;
17
+ failed: number;
18
+ skipped: number;
19
+ };
20
+ typecheck: {
21
+ errorCount: number;
22
+ exitCode: number;
23
+ };
24
+ lint: {
25
+ errorCount: number;
26
+ warningCount: number;
27
+ };
28
+ }
29
+ export interface QualityDelta {
30
+ /** Positive = worse (more failures) */
31
+ testFailuresDelta: number;
32
+ /** Positive = worse (more errors) */
33
+ typeErrorsDelta: number;
34
+ /** Positive = worse (more errors) */
35
+ lintErrorsDelta: number;
36
+ /** Negative = tests removed (warning, not a gate failure) */
37
+ testCountDelta: number;
38
+ /** True if no metric got worse */
39
+ passed: boolean;
40
+ }
41
+ export interface QualityConfig {
42
+ testCommand?: string;
43
+ validateCommand?: string;
44
+ lintCommand?: string;
45
+ packageManager?: string;
46
+ timeoutMs?: number;
47
+ }
48
+ /**
49
+ * Capture quality metrics from a worktree. Runs test, typecheck, and lint
50
+ * commands, parsing their output for structured counts.
51
+ *
52
+ * Each command is independent — a failure in one does not prevent capturing the others.
53
+ */
54
+ export declare function captureQualityBaseline(worktreePath: string, config?: QualityConfig): QualityBaseline;
55
+ /**
56
+ * Compute the quality delta between a baseline and current snapshot.
57
+ * Pure arithmetic — no side effects.
58
+ */
59
+ export declare function computeQualityDelta(baseline: QualityBaseline, current: QualityBaseline): QualityDelta;
60
+ /**
61
+ * Format a quality comparison into a markdown table for diagnostic comments.
62
+ */
63
+ export declare function formatQualityReport(baseline: QualityBaseline, current: QualityBaseline, delta: QualityDelta): string;
64
+ /**
65
+ * Save a quality baseline to the worktree's .agent/ directory.
66
+ */
67
+ export declare function saveBaseline(worktreePath: string, baseline: QualityBaseline): void;
68
+ /**
69
+ * Load a previously saved quality baseline from the worktree's .agent/ directory.
70
+ * Returns null if no baseline exists.
71
+ */
72
+ export declare function loadBaseline(worktreePath: string): QualityBaseline | null;
73
+ /**
74
+ * Parse vitest JSON reporter output.
75
+ * Expected fields: numTotalTests, numPassedTests, numFailedTests
76
+ */
77
+ export declare function parseVitestJson(output: string): QualityBaseline['tests'] | null;
78
+ /**
79
+ * Count TypeScript errors in tsc output.
80
+ * Matches lines like: "src/foo.ts(10,5): error TS2304: ..."
81
+ */
82
+ export declare function countTypescriptErrors(output: string): number;
83
+ //# sourceMappingURL=quality-baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-baseline.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/quality-baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,CAAA;QACd,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;IACD,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAA;IACD,IAAI,EAAE;QACJ,UAAU,EAAE,MAAM,CAAA;QAClB,YAAY,EAAE,MAAM,CAAA;KACrB,CAAA;CACF;AAED,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,iBAAiB,EAAE,MAAM,CAAA;IACzB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAA;IACtB,kCAAkC;IAClC,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAMD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,MAAM,GAAE,aAAkB,GACzB,eAAe,CAiBjB;AAMD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,eAAe,GACvB,YAAY,CAad;AAMD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,YAAY,GAClB,MAAM,CAmBR;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAMlF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAQzE;AA0HD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,IAAI,CAmC/E;AAoCD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAI5D"}