@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.
- package/dist/src/config/repository-config.d.ts +7 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +15 -1
- package/dist/src/config/repository-config.test.js +1 -1
- package/dist/src/governor/decision-engine-adapter.js +5 -10
- package/dist/src/governor/decision-engine-adapter.test.js +13 -14
- package/dist/src/governor/decision-engine.js +3 -7
- package/dist/src/governor/decision-engine.test.js +5 -5
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/merge-queue/adapters/local.d.ts +68 -0
- package/dist/src/merge-queue/adapters/local.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/local.js +136 -0
- package/dist/src/merge-queue/adapters/local.test.d.ts +2 -0
- package/dist/src/merge-queue/adapters/local.test.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/local.test.js +176 -0
- package/dist/src/merge-queue/index.d.ts +13 -5
- package/dist/src/merge-queue/index.d.ts.map +1 -1
- package/dist/src/merge-queue/index.js +13 -6
- package/dist/src/merge-queue/merge-queue.integration.test.js +19 -0
- package/dist/src/merge-queue/merge-worker.d.ts.map +1 -1
- package/dist/src/merge-queue/merge-worker.js +29 -0
- package/dist/src/merge-queue/types.d.ts +1 -1
- package/dist/src/merge-queue/types.d.ts.map +1 -1
- package/dist/src/orchestrator/index.d.ts +4 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +3 -0
- package/dist/src/orchestrator/orchestrator.d.ts +31 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +263 -11
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
- package/dist/src/orchestrator/parse-work-result.js +3 -1
- package/dist/src/orchestrator/parse-work-result.test.js +6 -0
- package/dist/src/orchestrator/quality-baseline.d.ts +83 -0
- package/dist/src/orchestrator/quality-baseline.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-baseline.js +313 -0
- package/dist/src/orchestrator/quality-baseline.test.d.ts +2 -0
- package/dist/src/orchestrator/quality-baseline.test.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-baseline.test.js +448 -0
- package/dist/src/orchestrator/quality-ratchet.d.ts +70 -0
- package/dist/src/orchestrator/quality-ratchet.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-ratchet.js +162 -0
- package/dist/src/orchestrator/quality-ratchet.test.d.ts +2 -0
- package/dist/src/orchestrator/quality-ratchet.test.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-ratchet.test.js +335 -0
- package/dist/src/orchestrator/types.d.ts +2 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/providers/codex-app-server-provider.d.ts +37 -1
- package/dist/src/providers/codex-app-server-provider.d.ts.map +1 -1
- package/dist/src/providers/codex-app-server-provider.js +290 -35
- package/dist/src/providers/codex-app-server-provider.test.js +72 -12
- package/dist/src/providers/codex-approval-bridge.d.ts +49 -0
- package/dist/src/providers/codex-approval-bridge.d.ts.map +1 -0
- package/dist/src/providers/codex-approval-bridge.js +117 -0
- package/dist/src/providers/codex-approval-bridge.test.d.ts +2 -0
- package/dist/src/providers/codex-approval-bridge.test.d.ts.map +1 -0
- package/dist/src/providers/codex-approval-bridge.test.js +188 -0
- package/dist/src/providers/types.d.ts +25 -0
- package/dist/src/providers/types.d.ts.map +1 -1
- package/dist/src/routing/types.d.ts +1 -1
- package/dist/src/templates/adapters.d.ts +25 -0
- package/dist/src/templates/adapters.d.ts.map +1 -1
- package/dist/src/templates/adapters.js +70 -0
- package/dist/src/templates/adapters.test.js +49 -0
- package/dist/src/templates/index.d.ts +1 -0
- package/dist/src/templates/index.d.ts.map +1 -1
- package/dist/src/templates/registry.d.ts +8 -0
- package/dist/src/templates/registry.d.ts.map +1 -1
- package/dist/src/templates/registry.js +11 -0
- package/dist/src/templates/types.d.ts +22 -0
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +12 -0
- package/dist/src/tools/index.d.ts +2 -0
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/index.js +1 -0
- package/dist/src/tools/registry.d.ts +9 -1
- package/dist/src/tools/registry.d.ts.map +1 -1
- package/dist/src/tools/registry.js +13 -1
- package/dist/src/tools/stdio-server-entry.d.ts +25 -0
- package/dist/src/tools/stdio-server-entry.d.ts.map +1 -0
- package/dist/src/tools/stdio-server-entry.js +205 -0
- package/dist/src/tools/stdio-server.d.ts +87 -0
- package/dist/src/tools/stdio-server.d.ts.map +1 -0
- package/dist/src/tools/stdio-server.js +138 -0
- package/dist/src/workflow/workflow-types.d.ts +3 -3
- 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
|
-
|
|
1013
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
?
|
|
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;
|
|
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"}
|