@ai-sdlc/orchestrator 0.1.0
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/adapters.d.ts +60 -0
- package/dist/adapters.js +160 -0
- package/dist/admission.d.ts +49 -0
- package/dist/admission.js +90 -0
- package/dist/analysis/analyzer.d.ts +10 -0
- package/dist/analysis/analyzer.js +67 -0
- package/dist/analysis/complexity-scorer.d.ts +24 -0
- package/dist/analysis/complexity-scorer.js +39 -0
- package/dist/analysis/context-builder.d.ts +14 -0
- package/dist/analysis/context-builder.js +79 -0
- package/dist/analysis/convention-detector.d.ts +9 -0
- package/dist/analysis/convention-detector.js +177 -0
- package/dist/analysis/dependency-parser.d.ts +17 -0
- package/dist/analysis/dependency-parser.js +167 -0
- package/dist/analysis/file-walker.d.ts +18 -0
- package/dist/analysis/file-walker.js +158 -0
- package/dist/analysis/hotspot-analyzer.d.ts +28 -0
- package/dist/analysis/hotspot-analyzer.js +85 -0
- package/dist/analysis/index.d.ts +13 -0
- package/dist/analysis/index.js +12 -0
- package/dist/analysis/pattern-detector.d.ts +10 -0
- package/dist/analysis/pattern-detector.js +86 -0
- package/dist/analysis/types.d.ts +94 -0
- package/dist/analysis/types.js +5 -0
- package/dist/audit-archival.d.ts +48 -0
- package/dist/audit-archival.js +72 -0
- package/dist/audit-export.d.ts +45 -0
- package/dist/audit-export.js +100 -0
- package/dist/audit-extended.d.ts +28 -0
- package/dist/audit-extended.js +38 -0
- package/dist/audit-scheduler.d.ts +32 -0
- package/dist/audit-scheduler.js +52 -0
- package/dist/audit-sqlite-sink.d.ts +11 -0
- package/dist/audit-sqlite-sink.js +51 -0
- package/dist/autonomy-tracker.d.ts +76 -0
- package/dist/autonomy-tracker.js +277 -0
- package/dist/check-runs.d.ts +37 -0
- package/dist/check-runs.js +72 -0
- package/dist/cli/commands/agents.d.ts +6 -0
- package/dist/cli/commands/agents.js +33 -0
- package/dist/cli/commands/complexity.d.ts +6 -0
- package/dist/cli/commands/complexity.js +35 -0
- package/dist/cli/commands/cost.d.ts +6 -0
- package/dist/cli/commands/cost.js +53 -0
- package/dist/cli/commands/dashboard.d.ts +9 -0
- package/dist/cli/commands/dashboard.js +50 -0
- package/dist/cli/commands/health.d.ts +6 -0
- package/dist/cli/commands/health.js +33 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.js +127 -0
- package/dist/cli/commands/routing.d.ts +6 -0
- package/dist/cli/commands/routing.js +46 -0
- package/dist/cli/commands/run.d.ts +6 -0
- package/dist/cli/commands/run.js +36 -0
- package/dist/cli/commands/start.d.ts +6 -0
- package/dist/cli/commands/start.js +26 -0
- package/dist/cli/commands/status.d.ts +6 -0
- package/dist/cli/commands/status.js +34 -0
- package/dist/cli/dashboard-renderer.d.ts +24 -0
- package/dist/cli/dashboard-renderer.js +129 -0
- package/dist/cli/formatters/index.d.ts +6 -0
- package/dist/cli/formatters/index.js +18 -0
- package/dist/cli/formatters/json.d.ts +5 -0
- package/dist/cli/formatters/json.js +7 -0
- package/dist/cli/formatters/minimal.d.ts +5 -0
- package/dist/cli/formatters/minimal.js +36 -0
- package/dist/cli/formatters/table.d.ts +5 -0
- package/dist/cli/formatters/table.js +184 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +35 -0
- package/dist/compliance-extended.d.ts +41 -0
- package/dist/compliance-extended.js +58 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.js +66 -0
- package/dist/context-enrichment.d.ts +33 -0
- package/dist/context-enrichment.js +125 -0
- package/dist/cost-governance.d.ts +22 -0
- package/dist/cost-governance.js +106 -0
- package/dist/cost-tracker.d.ts +75 -0
- package/dist/cost-tracker.js +177 -0
- package/dist/defaults.d.ts +140 -0
- package/dist/defaults.js +218 -0
- package/dist/deploy/flyio-target.d.ts +19 -0
- package/dist/deploy/flyio-target.js +114 -0
- package/dist/deploy/index.d.ts +15 -0
- package/dist/deploy/index.js +11 -0
- package/dist/deploy/kubernetes-target.d.ts +25 -0
- package/dist/deploy/kubernetes-target.js +124 -0
- package/dist/deploy/metrics-collector.d.ts +31 -0
- package/dist/deploy/metrics-collector.js +75 -0
- package/dist/deploy/rollout-controller.d.ts +42 -0
- package/dist/deploy/rollout-controller.js +192 -0
- package/dist/deploy/rollout-types.d.ts +86 -0
- package/dist/deploy/rollout-types.js +5 -0
- package/dist/deploy/types.d.ts +47 -0
- package/dist/deploy/types.js +5 -0
- package/dist/deploy/vercel-target.d.ts +16 -0
- package/dist/deploy/vercel-target.js +115 -0
- package/dist/discovery.d.ts +26 -0
- package/dist/discovery.js +46 -0
- package/dist/episodic-enhanced.d.ts +88 -0
- package/dist/episodic-enhanced.js +212 -0
- package/dist/execute.d.ts +81 -0
- package/dist/execute.js +703 -0
- package/dist/fix-ci.d.ts +61 -0
- package/dist/fix-ci.js +380 -0
- package/dist/handoff-executor.d.ts +61 -0
- package/dist/handoff-executor.js +119 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +87 -0
- package/dist/instrumented.d.ts +27 -0
- package/dist/instrumented.js +34 -0
- package/dist/logger.d.ts +17 -0
- package/dist/logger.js +37 -0
- package/dist/multi-repo/impact-analyzer.d.ts +22 -0
- package/dist/multi-repo/impact-analyzer.js +95 -0
- package/dist/multi-repo/index.d.ts +5 -0
- package/dist/multi-repo/index.js +4 -0
- package/dist/multi-repo/monorepo-detector.d.ts +16 -0
- package/dist/multi-repo/monorepo-detector.js +246 -0
- package/dist/multi-repo/service-map-builder.d.ts +24 -0
- package/dist/multi-repo/service-map-builder.js +235 -0
- package/dist/multi-repo/types.d.ts +54 -0
- package/dist/multi-repo/types.js +5 -0
- package/dist/notifications/index.d.ts +4 -0
- package/dist/notifications/index.js +4 -0
- package/dist/notifications/notification-router.d.ts +62 -0
- package/dist/notifications/notification-router.js +206 -0
- package/dist/notifications/slack-messenger.d.ts +26 -0
- package/dist/notifications/slack-messenger.js +69 -0
- package/dist/notifications/teams-messenger.d.ts +21 -0
- package/dist/notifications/teams-messenger.js +85 -0
- package/dist/notifications.d.ts +13 -0
- package/dist/notifications.js +15 -0
- package/dist/orchestration.d.ts +20 -0
- package/dist/orchestration.js +53 -0
- package/dist/orchestrator.d.ts +160 -0
- package/dist/orchestrator.js +372 -0
- package/dist/otel-exporter.d.ts +37 -0
- package/dist/otel-exporter.js +128 -0
- package/dist/plugin.d.ts +45 -0
- package/dist/plugin.js +8 -0
- package/dist/policy-evaluators.d.ts +40 -0
- package/dist/policy-evaluators.js +57 -0
- package/dist/process-escalation.d.ts +38 -0
- package/dist/process-escalation.js +129 -0
- package/dist/progressive-gates.d.ts +54 -0
- package/dist/progressive-gates.js +135 -0
- package/dist/provenance.d.ts +29 -0
- package/dist/provenance.js +61 -0
- package/dist/reconcilers.d.ts +28 -0
- package/dist/reconcilers.js +37 -0
- package/dist/runners/claude-code.d.ts +17 -0
- package/dist/runners/claude-code.js +200 -0
- package/dist/runners/codex.d.ts +19 -0
- package/dist/runners/codex.js +153 -0
- package/dist/runners/copilot-stub.d.ts +9 -0
- package/dist/runners/copilot-stub.js +15 -0
- package/dist/runners/copilot.d.ts +15 -0
- package/dist/runners/copilot.js +119 -0
- package/dist/runners/cursor-stub.d.ts +9 -0
- package/dist/runners/cursor-stub.js +15 -0
- package/dist/runners/cursor.d.ts +20 -0
- package/dist/runners/cursor.js +140 -0
- package/dist/runners/devin-stub.d.ts +9 -0
- package/dist/runners/devin-stub.js +15 -0
- package/dist/runners/generic-llm.d.ts +51 -0
- package/dist/runners/generic-llm.js +125 -0
- package/dist/runners/index.d.ts +8 -0
- package/dist/runners/index.js +7 -0
- package/dist/runners/runner-registry.d.ts +50 -0
- package/dist/runners/runner-registry.js +156 -0
- package/dist/runners/types.d.ts +59 -0
- package/dist/runners/types.js +7 -0
- package/dist/security.d.ts +51 -0
- package/dist/security.js +70 -0
- package/dist/shared.d.ts +134 -0
- package/dist/shared.js +284 -0
- package/dist/state/index.d.ts +4 -0
- package/dist/state/index.js +3 -0
- package/dist/state/schema.d.ts +16 -0
- package/dist/state/schema.js +264 -0
- package/dist/state/store.d.ts +109 -0
- package/dist/state/store.js +644 -0
- package/dist/state/types.d.ts +190 -0
- package/dist/state/types.js +5 -0
- package/dist/structured-logger.d.ts +20 -0
- package/dist/structured-logger.js +64 -0
- package/dist/task-decomposer.d.ts +61 -0
- package/dist/task-decomposer.js +265 -0
- package/dist/telemetry-extended.d.ts +24 -0
- package/dist/telemetry-extended.js +31 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.js +7 -0
- package/dist/validate-agent-output.d.ts +30 -0
- package/dist/validate-agent-output.js +81 -0
- package/dist/validate-issue.d.ts +25 -0
- package/dist/validate-issue.js +102 -0
- package/dist/watch.d.ts +43 -0
- package/dist/watch.js +99 -0
- package/dist/webhook-manager.d.ts +38 -0
- package/dist/webhook-manager.js +66 -0
- package/package.json +59 -0
package/dist/execute.js
ADDED
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main pipeline execution — the heart of the dogfood loop.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* load config -> fetch issue -> validate -> check autonomy ->
|
|
6
|
+
* create branch -> invoke agent -> authorize -> validate -> push -> create PR -> comment
|
|
7
|
+
*/
|
|
8
|
+
import { createGitHubIssueTracker, createGitHubSourceControl, routeByComplexity, evaluatePromotion, evaluateComplexity, selectModel, withSpan, getMeter, SPAN_NAMES, METRIC_NAMES, ATTRIBUTE_KEYS, } from '@ai-sdlc/reference';
|
|
9
|
+
import { loadConfigAsync } from './config.js';
|
|
10
|
+
import { validateIssue, validateIssueWithExtensions, parseComplexity } from './validate-issue.js';
|
|
11
|
+
import { createLogger } from './logger.js';
|
|
12
|
+
import { createStructuredConsoleLogger, createStructuredBufferLogger, } from './structured-logger.js';
|
|
13
|
+
import { ClaudeCodeRunner } from './runners/claude-code.js';
|
|
14
|
+
import { execFileAsync, getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, isAutonomousStrategy, recordMetric, validateAndAuditOutput, evaluatePipelineCompliance, authorizeFilesChanged, interpolateBranchPattern, interpolatePRTitle, } from './shared.js';
|
|
15
|
+
import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, classifyAndSubmitApproval, createPipelineSecurity, } from './security.js';
|
|
16
|
+
import { createPipelineOrchestration, executePipelineOrchestration, validatePipelineHandoffs, } from './orchestration.js';
|
|
17
|
+
import { createPipelineProvenance, attachProvenanceToPR, validatePipelineProvenance, } from './provenance.js';
|
|
18
|
+
import { createInstrumentedEnforcement, createInstrumentedAutonomy, createInstrumentedExecutor, } from './instrumented.js';
|
|
19
|
+
import { createPipelineDiscovery, resolveAgentForIssue, createPipelineAgentCardFetcher, } from './discovery.js';
|
|
20
|
+
import { createPipelineExpressionEvaluator, createPipelineLLMEvaluator, createPipelineRegoEvaluator, createPipelineCELEvaluator, createPipelineABACHook, evaluatePipelineGate, scorePipelineComplexity, evaluatePipelineComplexityRouting, parseDuration, } from './policy-evaluators.js';
|
|
21
|
+
import { verifyAuditIntegrity, createFileAuditLog, loadAuditEntries, computeAuditHash, } from './audit-extended.js';
|
|
22
|
+
import { renderTemplate } from './notifications.js';
|
|
23
|
+
import { admitIssueResource, createPipelineAdmission, } from './admission.js';
|
|
24
|
+
import { createPipelineAdapterRegistry, createPipelineWebhookBridge, resolveAdapterFromGit, scanPipelineAdapters, } from './adapters.js';
|
|
25
|
+
import { defaultSandboxConstraints, DEFAULT_CONFIG_DIR_NAME, DEFAULT_PR_FOOTER, NOTIFICATION_TITLES, } from './defaults.js';
|
|
26
|
+
import { checkFrameworkCompliance, getControlCatalog, getFrameworkMappings, listSupportedFrameworks, } from './compliance-extended.js';
|
|
27
|
+
import { createSilentLogger, withPipelineSpanSync, getPipelineTracer, validateResourceSchema, } from './telemetry-extended.js';
|
|
28
|
+
import { createPipelineMemory } from './shared.js';
|
|
29
|
+
import { hasResourceChanged, fingerprintResource } from './reconcilers.js';
|
|
30
|
+
import { CostTracker } from './cost-tracker.js';
|
|
31
|
+
import { enrichAgentContext } from './context-enrichment.js';
|
|
32
|
+
/**
|
|
33
|
+
* Execute the full AI-SDLC pipeline for a given issue number.
|
|
34
|
+
*/
|
|
35
|
+
export async function executePipeline(issueNumber, options = {}) {
|
|
36
|
+
const workDir = options.workDir ?? (await resolveRepoRoot());
|
|
37
|
+
const configDir = options.configDir ?? `${workDir}/${DEFAULT_CONFIG_DIR_NAME}`;
|
|
38
|
+
const log = options.logger ??
|
|
39
|
+
(options.useStructuredLogger ? createStructuredConsoleLogger() : createLogger());
|
|
40
|
+
const auditLog = options.auditLog ?? createDefaultAuditLog(workDir);
|
|
41
|
+
const metricStore = options.metricStore;
|
|
42
|
+
// 1. Load .ai-sdlc/ config (async: includes adapter scanning + manifest distribution)
|
|
43
|
+
log.stage('load-config');
|
|
44
|
+
const config = await loadConfigAsync(configDir);
|
|
45
|
+
log.stageEnd('load-config');
|
|
46
|
+
// Kill switch check (before any work)
|
|
47
|
+
if (options.security) {
|
|
48
|
+
await checkKillSwitch(options.security);
|
|
49
|
+
}
|
|
50
|
+
// Admission pipeline check (before resource validation)
|
|
51
|
+
if (options.admission && config.pipeline) {
|
|
52
|
+
const admissionResult = await admitIssueResource(config.pipeline, options.admission);
|
|
53
|
+
if (!admissionResult.admitted) {
|
|
54
|
+
throw new Error(`Pipeline admission denied: ${admissionResult.error ?? 'unknown'}`);
|
|
55
|
+
}
|
|
56
|
+
auditLog.record({
|
|
57
|
+
actor: 'system',
|
|
58
|
+
action: 'admit',
|
|
59
|
+
resource: config.pipeline.metadata.name,
|
|
60
|
+
decision: 'allowed',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (!config.qualityGate) {
|
|
64
|
+
throw new Error('No QualityGate resource found in .ai-sdlc/');
|
|
65
|
+
}
|
|
66
|
+
if (!config.agentRole) {
|
|
67
|
+
throw new Error('No AgentRole resource found in .ai-sdlc/');
|
|
68
|
+
}
|
|
69
|
+
if (!config.autonomyPolicy) {
|
|
70
|
+
throw new Error('No AutonomyPolicy resource found in .ai-sdlc/');
|
|
71
|
+
}
|
|
72
|
+
// Capture narrowed types for use in closures
|
|
73
|
+
const qualityGate = config.qualityGate;
|
|
74
|
+
const agentRole = config.agentRole;
|
|
75
|
+
const autonomyPolicy = config.autonomyPolicy;
|
|
76
|
+
// Auto-create default evaluators when requested and none provided
|
|
77
|
+
if (options.useDefaultEvaluators) {
|
|
78
|
+
if (!options.expressionEvaluator) {
|
|
79
|
+
options = { ...options, expressionEvaluator: createPipelineExpressionEvaluator() };
|
|
80
|
+
}
|
|
81
|
+
if (!options.llmEvaluator) {
|
|
82
|
+
options = { ...options, llmEvaluator: createPipelineLLMEvaluator() };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// 2. Create adapters (or use injected ones)
|
|
86
|
+
const { org, repo } = getGitHubConfig(options.secretStore);
|
|
87
|
+
const ghConfig = { org, repo, token: { secretRef: 'github-token' } };
|
|
88
|
+
const tracker = options.tracker ?? createGitHubIssueTracker(ghConfig);
|
|
89
|
+
const sc = options.sourceControl ?? createGitHubSourceControl(ghConfig);
|
|
90
|
+
// 3. Fetch issue
|
|
91
|
+
log.stage('validate-issue');
|
|
92
|
+
const issue = await tracker.getIssue(String(issueNumber));
|
|
93
|
+
// Store issue context in working memory if provided
|
|
94
|
+
if (options.memory) {
|
|
95
|
+
options.memory.working.set('currentIssue', {
|
|
96
|
+
number: issueNumber,
|
|
97
|
+
title: issue.title,
|
|
98
|
+
description: issue.description,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Wrap pipeline body in try/catch to record failure episodes
|
|
102
|
+
try {
|
|
103
|
+
return await executePipelineBody(issueNumber, issue, config, qualityGate, agentRole, autonomyPolicy, tracker, sc, auditLog, metricStore, options, log, workDir);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
// Record failure episode before rethrowing
|
|
107
|
+
if (options.memory) {
|
|
108
|
+
options.memory.episodic.append({
|
|
109
|
+
key: 'pipeline-execution',
|
|
110
|
+
value: {
|
|
111
|
+
issueNumber,
|
|
112
|
+
outcome: 'failure',
|
|
113
|
+
error: err instanceof Error ? err.message : String(err),
|
|
114
|
+
},
|
|
115
|
+
metadata: { summary: `Failed issue #${issueNumber}: ${issue.title}` },
|
|
116
|
+
});
|
|
117
|
+
options.memory.working.clear();
|
|
118
|
+
}
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function executePipelineBody(issueNumber, issue, config, qualityGate, agentRole, autonomyPolicy, tracker, sc, auditLog, metricStore, options, log, workDir) {
|
|
123
|
+
const meter = getMeter();
|
|
124
|
+
// Discovery: register agent and resolve by issue labels
|
|
125
|
+
const discovery = createPipelineDiscovery();
|
|
126
|
+
discovery.register(agentRole);
|
|
127
|
+
const resolvedAgent = resolveAgentForIssue(discovery, issue.labels ?? []);
|
|
128
|
+
if (resolvedAgent) {
|
|
129
|
+
log.info(`Discovery resolved agent: ${resolvedAgent.metadata.name}`);
|
|
130
|
+
}
|
|
131
|
+
// Validate handoff targets exist before orchestration
|
|
132
|
+
const handoffErrors = validatePipelineHandoffs([agentRole]);
|
|
133
|
+
if (handoffErrors.length > 0) {
|
|
134
|
+
log.info(`Handoff validation warnings: ${handoffErrors.join('; ')}`);
|
|
135
|
+
}
|
|
136
|
+
// Pre-flight: agent card fetcher for A2A discovery
|
|
137
|
+
const _cardFetcher = createPipelineAgentCardFetcher();
|
|
138
|
+
// Instrumented enforcement (wraps enforce with metric recording)
|
|
139
|
+
const instrumentedEnforce = metricStore ? createInstrumentedEnforcement(metricStore) : undefined;
|
|
140
|
+
// Instrumented executor for task-level metrics
|
|
141
|
+
const _instrumentedExec = metricStore ? createInstrumentedExecutor(metricStore) : undefined;
|
|
142
|
+
// 4. Validate issue against quality gates
|
|
143
|
+
// Compute cost metrics from CostTracker if available
|
|
144
|
+
const costMetrics = {};
|
|
145
|
+
if (options.costTracker && config.pipeline?.spec.costPolicy?.budget) {
|
|
146
|
+
const budget = options.costTracker.getBudgetStatus(config.pipeline.spec.costPolicy.budget.amount);
|
|
147
|
+
costMetrics['budget-remaining-percent'] =
|
|
148
|
+
budget.budgetUsd > 0 ? budget.remainingUsd / budget.budgetUsd : 1;
|
|
149
|
+
costMetrics['total-execution-cost'] = budget.spentUsd;
|
|
150
|
+
}
|
|
151
|
+
await withSpan(SPAN_NAMES.GATE_EVALUATION, {
|
|
152
|
+
[ATTRIBUTE_KEYS.PIPELINE]: config.pipeline?.metadata.name ?? 'dogfood',
|
|
153
|
+
[ATTRIBUTE_KEYS.GATE]: qualityGate.metadata.name,
|
|
154
|
+
}, async () => {
|
|
155
|
+
const enforcement = options.expressionEvaluator || options.llmEvaluator
|
|
156
|
+
? await validateIssueWithExtensions(issue, qualityGate, {
|
|
157
|
+
expressionEvaluator: options.expressionEvaluator,
|
|
158
|
+
llmEvaluator: options.llmEvaluator,
|
|
159
|
+
})
|
|
160
|
+
: validateIssue(issue, qualityGate, instrumentedEnforce, costMetrics);
|
|
161
|
+
if (!enforcement.allowed) {
|
|
162
|
+
const failures = enforcement.results
|
|
163
|
+
.filter((r) => r.verdict === 'fail')
|
|
164
|
+
.map((r) => `- ${r.gate}: ${r.message ?? 'failed'}`)
|
|
165
|
+
.join('\n');
|
|
166
|
+
auditLog.record({
|
|
167
|
+
actor: 'system',
|
|
168
|
+
action: 'evaluate',
|
|
169
|
+
resource: `issue#${issueNumber}`,
|
|
170
|
+
policy: qualityGate.metadata.name,
|
|
171
|
+
decision: 'denied',
|
|
172
|
+
details: {
|
|
173
|
+
failures: enforcement.results.filter((r) => r.verdict === 'fail').map((r) => r.gate),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
meter.createCounter(METRIC_NAMES.GATE_FAIL_TOTAL).add(1);
|
|
177
|
+
recordMetric(metricStore, METRIC_NAMES.GATE_FAIL_TOTAL, 1);
|
|
178
|
+
const gateFailTpl = config.pipeline?.spec.notifications?.templates?.['gate-failure'];
|
|
179
|
+
const gateFailBody = gateFailTpl
|
|
180
|
+
? renderTemplate(gateFailTpl, { details: failures }).body
|
|
181
|
+
: `This issue did not pass quality gate checks:\n\n${failures}`;
|
|
182
|
+
const gateFailTitle = gateFailTpl
|
|
183
|
+
? renderTemplate(gateFailTpl, { details: failures }).title
|
|
184
|
+
: NOTIFICATION_TITLES.issueValidationFailed;
|
|
185
|
+
await tracker.addComment(String(issueNumber), `## ${gateFailTitle}\n\n${gateFailBody}`);
|
|
186
|
+
throw new Error(`Issue #${issueNumber} failed quality gate validation`);
|
|
187
|
+
}
|
|
188
|
+
auditLog.record({
|
|
189
|
+
actor: 'system',
|
|
190
|
+
action: 'evaluate',
|
|
191
|
+
resource: `issue#${issueNumber}`,
|
|
192
|
+
policy: qualityGate.metadata.name,
|
|
193
|
+
decision: 'allowed',
|
|
194
|
+
});
|
|
195
|
+
meter.createCounter(METRIC_NAMES.GATE_PASS_TOTAL).add(1);
|
|
196
|
+
recordMetric(metricStore, METRIC_NAMES.GATE_PASS_TOTAL, 1);
|
|
197
|
+
});
|
|
198
|
+
// 5. Parse complexity and route by strategy
|
|
199
|
+
const complexity = parseComplexity(issue.description);
|
|
200
|
+
const strategy = routeByComplexity(complexity);
|
|
201
|
+
auditLog.record({
|
|
202
|
+
actor: 'system',
|
|
203
|
+
action: 'route',
|
|
204
|
+
resource: `issue#${issueNumber}`,
|
|
205
|
+
decision: isAutonomousStrategy(strategy) ? 'allowed' : 'denied',
|
|
206
|
+
details: { score: complexity, strategy },
|
|
207
|
+
});
|
|
208
|
+
if (!isAutonomousStrategy(strategy)) {
|
|
209
|
+
const complexityTpl = config.pipeline?.spec.notifications?.templates?.['complexity-too-high'];
|
|
210
|
+
const complexityComment = complexityTpl
|
|
211
|
+
? renderTemplate(complexityTpl, { score: String(complexity), strategy })
|
|
212
|
+
: {
|
|
213
|
+
title: NOTIFICATION_TITLES.complexityTooHigh,
|
|
214
|
+
body: `Issue complexity (${complexity}) routed as "${strategy}" — requires human involvement.`,
|
|
215
|
+
};
|
|
216
|
+
await tracker.addComment(String(issueNumber), `## ${complexityComment.title}\n\n${complexityComment.body}`);
|
|
217
|
+
throw new Error(`Issue #${issueNumber} complexity ${complexity} routed as "${strategy}"`);
|
|
218
|
+
}
|
|
219
|
+
// Approval workflow check (after routing, before agent)
|
|
220
|
+
if (options.security) {
|
|
221
|
+
const approval = await classifyAndSubmitApproval(options.security, complexity, agentRole.metadata.name, `Execute pipeline for issue #${issueNumber}`);
|
|
222
|
+
if (approval.status === 'pending') {
|
|
223
|
+
throw new Error(`Issue #${issueNumber} requires approval (tier: ${approval.tier}) — status is pending`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// 6. Check autonomy level allows coding
|
|
227
|
+
const currentLevel = resolveAutonomyLevel(autonomyPolicy);
|
|
228
|
+
log.stageEnd('validate-issue');
|
|
229
|
+
// 7. Create branch and checkout locally (read pattern from pipeline config)
|
|
230
|
+
const branchVars = { issueNumber: String(issueNumber), issueTitle: issue.title };
|
|
231
|
+
const branchName = interpolateBranchPattern(config.pipeline?.spec.branching?.pattern, branchVars);
|
|
232
|
+
await sc.createBranch({ name: branchName });
|
|
233
|
+
await execFileAsync('git', ['fetch', 'origin', branchName], { cwd: workDir });
|
|
234
|
+
await execFileAsync('git', ['checkout', branchName], { cwd: workDir });
|
|
235
|
+
// 8. Resolve agent constraints
|
|
236
|
+
const resolved = resolveConstraints(agentRole.spec.constraints, currentLevel);
|
|
237
|
+
// 8b. Model selection: choose model based on complexity and budget pressure
|
|
238
|
+
let selectedModel;
|
|
239
|
+
if (agentRole.spec.modelSelection && options.costTracker) {
|
|
240
|
+
const budgetStatus = options.costTracker.getBudgetStatus(config.pipeline?.spec.costPolicy?.budget?.amount);
|
|
241
|
+
const modelResult = selectModel(agentRole.spec.modelSelection, {
|
|
242
|
+
complexity: complexity / 10, // normalize 1-10 to 0-1 range
|
|
243
|
+
budgetUtilization: budgetStatus.utilizationPercent / 100,
|
|
244
|
+
});
|
|
245
|
+
if (modelResult) {
|
|
246
|
+
selectedModel = modelResult.model;
|
|
247
|
+
log.info(`Model selected: ${modelResult.model} (${modelResult.reason})`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// 9. Invoke agent (with sandbox + JIT credential lifecycle when security is provided)
|
|
251
|
+
log.stage('agent');
|
|
252
|
+
const runner = options.runner ?? new ClaudeCodeRunner();
|
|
253
|
+
// Sandbox isolation around agent execution
|
|
254
|
+
const codeStage = config.pipeline?.spec.stages.find((s) => s.name === 'code');
|
|
255
|
+
let sandboxId;
|
|
256
|
+
let result;
|
|
257
|
+
try {
|
|
258
|
+
if (options.security) {
|
|
259
|
+
const timeoutMs = codeStage?.timeout ? parseDuration(codeStage.timeout) : undefined;
|
|
260
|
+
sandboxId = await options.security.sandbox.isolate(`issue-${issueNumber}`, defaultSandboxConstraints(workDir, timeoutMs));
|
|
261
|
+
}
|
|
262
|
+
// Issue JIT credentials before agent execution
|
|
263
|
+
const jitCred = options.security
|
|
264
|
+
? await issueAgentCredentials(options.security, agentRole.metadata.name)
|
|
265
|
+
: undefined;
|
|
266
|
+
try {
|
|
267
|
+
// H1: Wrap agent invocation in orchestration engine
|
|
268
|
+
// H3: Use discovery-resolved agent when available
|
|
269
|
+
const effectiveAgent = resolvedAgent ?? agentRole;
|
|
270
|
+
const plan = createPipelineOrchestration([effectiveAgent], 'sequential');
|
|
271
|
+
// Enrich agent context with episodic memory
|
|
272
|
+
const episodicContext = options.stateStore
|
|
273
|
+
? enrichAgentContext(options.stateStore, {
|
|
274
|
+
issueNumber,
|
|
275
|
+
agentName: effectiveAgent.metadata.name,
|
|
276
|
+
files: result ? result.filesChanged : undefined,
|
|
277
|
+
})
|
|
278
|
+
: undefined;
|
|
279
|
+
const orchestrationResult = await executePipelineOrchestration(plan, [effectiveAgent], async (agent) => {
|
|
280
|
+
return withSpan(SPAN_NAMES.AGENT_TASK, {
|
|
281
|
+
[ATTRIBUTE_KEYS.AGENT]: agent.metadata.name,
|
|
282
|
+
[ATTRIBUTE_KEYS.RESOURCE_NAME]: `issue#${issueNumber}`,
|
|
283
|
+
}, () => runner.run({
|
|
284
|
+
issueNumber,
|
|
285
|
+
issueTitle: issue.title,
|
|
286
|
+
issueBody: issue.description ?? '',
|
|
287
|
+
workDir,
|
|
288
|
+
branch: branchName,
|
|
289
|
+
constraints: {
|
|
290
|
+
maxFilesPerChange: resolved.maxFiles,
|
|
291
|
+
requireTests: resolved.requireTests,
|
|
292
|
+
blockedPaths: resolved.blockedPaths,
|
|
293
|
+
},
|
|
294
|
+
model: selectedModel,
|
|
295
|
+
memory: options.memory,
|
|
296
|
+
codebaseContext: options.codebaseContext,
|
|
297
|
+
episodicContext,
|
|
298
|
+
}));
|
|
299
|
+
});
|
|
300
|
+
// Extract the AgentResult from orchestration output
|
|
301
|
+
const stepOutput = orchestrationResult.stepResults[0]?.output;
|
|
302
|
+
if (!orchestrationResult.success || !stepOutput?.success) {
|
|
303
|
+
const err = stepOutput?.error ??
|
|
304
|
+
orchestrationResult.stepResults[0]?.error ??
|
|
305
|
+
'Unknown orchestration error';
|
|
306
|
+
log.stageEnd('agent');
|
|
307
|
+
auditLog.record({
|
|
308
|
+
actor: 'system',
|
|
309
|
+
action: 'execute',
|
|
310
|
+
resource: `agent/${effectiveAgent.metadata.name}`,
|
|
311
|
+
decision: 'denied',
|
|
312
|
+
details: { error: err },
|
|
313
|
+
});
|
|
314
|
+
meter.createCounter(METRIC_NAMES.TASK_FAILURE_TOTAL).add(1);
|
|
315
|
+
recordMetric(metricStore, METRIC_NAMES.TASK_FAILURE_TOTAL, 1);
|
|
316
|
+
const agentFailTpl = config.pipeline?.spec.notifications?.templates?.['agent-failure'];
|
|
317
|
+
const errorDetail = typeof err === 'string' ? err : 'Unknown error';
|
|
318
|
+
const agentFailComment = agentFailTpl
|
|
319
|
+
? renderTemplate(agentFailTpl, { stageName: 'code', details: errorDetail })
|
|
320
|
+
: { title: NOTIFICATION_TITLES.agentFailed, body: errorDetail };
|
|
321
|
+
await tracker.addComment(String(issueNumber), `## ${agentFailComment.title}\n\n${agentFailComment.body}`);
|
|
322
|
+
throw new Error(`Agent failed on issue #${issueNumber}: ${err}`);
|
|
323
|
+
}
|
|
324
|
+
result = stepOutput;
|
|
325
|
+
log.stageEnd('agent');
|
|
326
|
+
auditLog.record({
|
|
327
|
+
actor: 'system',
|
|
328
|
+
action: 'execute',
|
|
329
|
+
resource: `agent/${effectiveAgent.metadata.name}`,
|
|
330
|
+
decision: 'allowed',
|
|
331
|
+
details: { filesChanged: result.filesChanged.length },
|
|
332
|
+
});
|
|
333
|
+
meter.createCounter(METRIC_NAMES.TASK_SUCCESS_TOTAL).add(1);
|
|
334
|
+
recordMetric(metricStore, METRIC_NAMES.TASK_SUCCESS_TOTAL, 1);
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
// Revoke JIT credentials after agent execution (success or failure)
|
|
338
|
+
if (jitCred && options.security) {
|
|
339
|
+
await revokeAgentCredentials(options.security, jitCred.id);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
// Destroy sandbox after agent execution
|
|
345
|
+
if (sandboxId && options.security) {
|
|
346
|
+
await options.security.sandbox.destroy(sandboxId);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// 10. ABAC authorization check (if write permissions are defined)
|
|
350
|
+
if (currentLevel.permissions.write.length > 0) {
|
|
351
|
+
authorizeFilesChanged(result.filesChanged, currentLevel.permissions, agentRole.spec.constraints, auditLog, agentRole.metadata.name);
|
|
352
|
+
}
|
|
353
|
+
// 11. Validate agent output against guardrails
|
|
354
|
+
await withSpan(SPAN_NAMES.PIPELINE_STAGE, {
|
|
355
|
+
[ATTRIBUTE_KEYS.STAGE]: 'validate-output',
|
|
356
|
+
}, async () => {
|
|
357
|
+
await validateAndAuditOutput({
|
|
358
|
+
filesChanged: result.filesChanged,
|
|
359
|
+
workDir,
|
|
360
|
+
constraints: {
|
|
361
|
+
maxFilesPerChange: resolved.maxFiles,
|
|
362
|
+
requireTests: resolved.requireTests,
|
|
363
|
+
blockedPaths: resolved.blockedPaths,
|
|
364
|
+
},
|
|
365
|
+
guardrails: { maxLinesPerPR: currentLevel.guardrails.maxLinesPerPR },
|
|
366
|
+
auditLog,
|
|
367
|
+
log,
|
|
368
|
+
onViolation: async (violationList) => {
|
|
369
|
+
await tracker.addComment(String(issueNumber), `## ${NOTIFICATION_TITLES.guardrailViolations}\n\n${violationList}`);
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
// 11b. Post-agent complexity evaluation (non-blocking)
|
|
374
|
+
try {
|
|
375
|
+
const { stdout: diffStat } = await execFileAsync('git', ['diff', '--stat', 'HEAD~1'], {
|
|
376
|
+
cwd: workDir,
|
|
377
|
+
});
|
|
378
|
+
const insertMatch = diffStat.match(/(\d+) insertions?\(\+\)/);
|
|
379
|
+
const deleteMatch = diffStat.match(/(\d+) deletions?\(-\)/);
|
|
380
|
+
const linesOfChange = (insertMatch ? Number(insertMatch[1]) : 0) + (deleteMatch ? Number(deleteMatch[1]) : 0);
|
|
381
|
+
const postAgentComplexity = evaluateComplexity({
|
|
382
|
+
filesAffected: result.filesChanged.length,
|
|
383
|
+
linesOfChange,
|
|
384
|
+
});
|
|
385
|
+
auditLog.record({
|
|
386
|
+
actor: 'system',
|
|
387
|
+
action: 'evaluate',
|
|
388
|
+
resource: `issue#${issueNumber}`,
|
|
389
|
+
policy: 'post-agent-complexity',
|
|
390
|
+
decision: 'allowed',
|
|
391
|
+
details: {
|
|
392
|
+
score: postAgentComplexity.score,
|
|
393
|
+
strategy: postAgentComplexity.strategy,
|
|
394
|
+
linesOfChange,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
log.info(`Post-agent complexity: ${postAgentComplexity.score} (${postAgentComplexity.strategy})`);
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
log.info('Post-agent complexity evaluation skipped');
|
|
401
|
+
}
|
|
402
|
+
// 12. Push branch
|
|
403
|
+
log.stage('push');
|
|
404
|
+
await execFileAsync('git', ['push', 'origin', branchName], { cwd: workDir });
|
|
405
|
+
log.stageEnd('push');
|
|
406
|
+
// 12b. Compute cost receipt for provenance (before PR creation)
|
|
407
|
+
let costReceipt;
|
|
408
|
+
if (result.tokenUsage) {
|
|
409
|
+
const tu = result.tokenUsage;
|
|
410
|
+
const totalCostUsd = CostTracker.computeCost(tu.inputTokens, tu.outputTokens, tu.model, tu.cacheReadTokens);
|
|
411
|
+
costReceipt = {
|
|
412
|
+
totalCost: totalCostUsd,
|
|
413
|
+
currency: 'USD',
|
|
414
|
+
breakdown: {
|
|
415
|
+
tokenCost: totalCostUsd,
|
|
416
|
+
},
|
|
417
|
+
execution: {
|
|
418
|
+
inputTokens: tu.inputTokens,
|
|
419
|
+
outputTokens: tu.outputTokens,
|
|
420
|
+
cacheReadTokens: tu.cacheReadTokens,
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
// 13. Create PR (with optional provenance, reading config from pipeline)
|
|
425
|
+
const prConfig = config.pipeline?.spec.pullRequest;
|
|
426
|
+
const shouldIncludeProvenance = options.includeProvenance ?? prConfig?.includeProvenance ?? options.security !== undefined;
|
|
427
|
+
let provenanceBlock = '';
|
|
428
|
+
if (shouldIncludeProvenance) {
|
|
429
|
+
const provenance = createPipelineProvenance({
|
|
430
|
+
promptText: issue.description,
|
|
431
|
+
cost: costReceipt,
|
|
432
|
+
});
|
|
433
|
+
provenanceBlock = '\n\n' + attachProvenanceToPR(provenance);
|
|
434
|
+
}
|
|
435
|
+
const prVars = { issueNumber: String(issueNumber), issueTitle: issue.title };
|
|
436
|
+
const prTitle = interpolatePRTitle(prConfig?.titleTemplate, prVars);
|
|
437
|
+
const closeKeyword = prConfig?.closeKeyword ?? 'Closes';
|
|
438
|
+
const targetBranch = config.pipeline?.spec.branching?.targetBranch ?? 'main';
|
|
439
|
+
log.stage('create-pr');
|
|
440
|
+
const pr = await withSpan(SPAN_NAMES.PIPELINE_STAGE, {
|
|
441
|
+
[ATTRIBUTE_KEYS.STAGE]: 'create-pr',
|
|
442
|
+
}, async () => {
|
|
443
|
+
const sections = prConfig?.descriptionSections ?? ['summary', 'changes', 'closes'];
|
|
444
|
+
const parts = [];
|
|
445
|
+
for (const section of sections) {
|
|
446
|
+
if (section === 'summary')
|
|
447
|
+
parts.push('## Summary', '', result.summary);
|
|
448
|
+
if (section === 'changes')
|
|
449
|
+
parts.push('## Changes', '', result.filesChanged.map((f) => `- \`${f}\``).join('\n'));
|
|
450
|
+
if (section === 'closes')
|
|
451
|
+
parts.push('', `${closeKeyword} #${issueNumber}`);
|
|
452
|
+
}
|
|
453
|
+
const footer = options.prFooter ?? DEFAULT_PR_FOOTER;
|
|
454
|
+
parts.push('', '---', footer);
|
|
455
|
+
if (provenanceBlock)
|
|
456
|
+
parts.push(provenanceBlock);
|
|
457
|
+
const prResult = await sc.createPR({
|
|
458
|
+
title: prTitle,
|
|
459
|
+
description: parts.join('\n'),
|
|
460
|
+
sourceBranch: branchName,
|
|
461
|
+
targetBranch,
|
|
462
|
+
});
|
|
463
|
+
log.stageEnd('create-pr');
|
|
464
|
+
auditLog.record({
|
|
465
|
+
actor: 'system',
|
|
466
|
+
action: 'create',
|
|
467
|
+
resource: 'pull-request',
|
|
468
|
+
decision: 'allowed',
|
|
469
|
+
details: { prUrl: prResult.url, issueNumber },
|
|
470
|
+
});
|
|
471
|
+
return prResult;
|
|
472
|
+
});
|
|
473
|
+
// 14. Comment on issue with success (use notification template when available)
|
|
474
|
+
const notifTemplates = config.pipeline?.spec.notifications?.templates;
|
|
475
|
+
const prCreatedTemplate = notifTemplates?.['pr-created'];
|
|
476
|
+
const prCreatedComment = prCreatedTemplate
|
|
477
|
+
? renderTemplate(prCreatedTemplate, {
|
|
478
|
+
prUrl: pr.url,
|
|
479
|
+
issueNumber: String(issueNumber),
|
|
480
|
+
})
|
|
481
|
+
: { title: NOTIFICATION_TITLES.prCreated, body: `Pull request created: ${pr.url}` };
|
|
482
|
+
await tracker.addComment(String(issueNumber), `## ${prCreatedComment.title}\n\n${prCreatedComment.body}`);
|
|
483
|
+
// 14b. Record cost from agent result
|
|
484
|
+
if (options.costTracker && result.tokenUsage) {
|
|
485
|
+
const tu = result.tokenUsage;
|
|
486
|
+
options.costTracker.recordCost({
|
|
487
|
+
runId: `run-${Date.now()}-${issueNumber}`,
|
|
488
|
+
agentName: agentRole.metadata.name,
|
|
489
|
+
pipelineType: 'execute',
|
|
490
|
+
model: tu.model,
|
|
491
|
+
inputTokens: tu.inputTokens,
|
|
492
|
+
outputTokens: tu.outputTokens,
|
|
493
|
+
cacheReadTokens: tu.cacheReadTokens,
|
|
494
|
+
stageName: 'code',
|
|
495
|
+
issueNumber,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
// 14c. Record task outcome in autonomy tracker
|
|
499
|
+
if (options.autonomyTracker) {
|
|
500
|
+
options.autonomyTracker.recordTaskOutcome(agentRole.metadata.name, result.success);
|
|
501
|
+
}
|
|
502
|
+
// 15. Evaluate promotion eligibility (with optional instrumented autonomy)
|
|
503
|
+
const instrumentedAutonomy = metricStore ? createInstrumentedAutonomy(metricStore) : undefined;
|
|
504
|
+
// Use real metrics from AutonomyTracker when available
|
|
505
|
+
let promotion;
|
|
506
|
+
if (options.autonomyTracker) {
|
|
507
|
+
promotion = options.autonomyTracker.evaluateAndPersistPromotion(agentRole.metadata.name);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
// Fallback to reference evaluatePromotion for backwards compatibility
|
|
511
|
+
const agentMetrics = {
|
|
512
|
+
name: agentRole.metadata.name,
|
|
513
|
+
currentLevel: currentLevel.level,
|
|
514
|
+
totalTasksCompleted: 1,
|
|
515
|
+
metrics: {},
|
|
516
|
+
approvals: [],
|
|
517
|
+
};
|
|
518
|
+
const refPromotion = evaluatePromotion(autonomyPolicy, agentMetrics);
|
|
519
|
+
promotion = {
|
|
520
|
+
eligible: refPromotion.eligible,
|
|
521
|
+
fromLevel: refPromotion.fromLevel,
|
|
522
|
+
toLevel: refPromotion.toLevel,
|
|
523
|
+
unmetConditions: refPromotion.unmetConditions,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (instrumentedAutonomy && promotion.eligible) {
|
|
527
|
+
instrumentedAutonomy.onPromotion(agentRole.metadata.name, promotion.fromLevel, promotion.toLevel);
|
|
528
|
+
}
|
|
529
|
+
log.info(`Promotion eligibility: ${promotion.eligible ? 'eligible' : 'not eligible'} (${promotion.unmetConditions.join(', ') || 'all conditions met'})`);
|
|
530
|
+
auditLog.record({
|
|
531
|
+
actor: 'system',
|
|
532
|
+
action: 'evaluate',
|
|
533
|
+
resource: `agent/${agentRole.metadata.name}`,
|
|
534
|
+
policy: 'promotion',
|
|
535
|
+
decision: promotion.eligible ? 'allowed' : 'denied',
|
|
536
|
+
details: {
|
|
537
|
+
fromLevel: promotion.fromLevel,
|
|
538
|
+
toLevel: promotion.toLevel,
|
|
539
|
+
unmetConditions: promotion.unmetConditions,
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
if (options.promotionCallback) {
|
|
543
|
+
await options.promotionCallback({
|
|
544
|
+
eligible: promotion.eligible,
|
|
545
|
+
fromLevel: promotion.fromLevel,
|
|
546
|
+
toLevel: promotion.toLevel,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
// 16. Compliance reporting
|
|
550
|
+
const complianceReports = evaluatePipelineCompliance(!!options.memory);
|
|
551
|
+
const avgCoverage = complianceReports.reduce((s, r) => s + r.coveragePercent, 0) / complianceReports.length;
|
|
552
|
+
log.info(`Compliance coverage: ${avgCoverage.toFixed(1)}%`);
|
|
553
|
+
auditLog.record({
|
|
554
|
+
actor: 'system',
|
|
555
|
+
action: 'evaluate',
|
|
556
|
+
resource: 'compliance',
|
|
557
|
+
decision: 'allowed',
|
|
558
|
+
details: { averageCoverage: avgCoverage, frameworks: complianceReports.length },
|
|
559
|
+
});
|
|
560
|
+
// 16b. Extended diagnostics (non-blocking)
|
|
561
|
+
try {
|
|
562
|
+
runPipelineDiagnostics({
|
|
563
|
+
config,
|
|
564
|
+
qualityGate,
|
|
565
|
+
agentRole,
|
|
566
|
+
autonomyPolicy,
|
|
567
|
+
metricStore,
|
|
568
|
+
provenance: shouldIncludeProvenance
|
|
569
|
+
? createPipelineProvenance({ promptText: issue.description })
|
|
570
|
+
: undefined,
|
|
571
|
+
workDir,
|
|
572
|
+
complexity,
|
|
573
|
+
filesChanged: result.filesChanged,
|
|
574
|
+
log,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
/* diagnostics are best-effort */
|
|
579
|
+
}
|
|
580
|
+
// 17. Record episodic memory (success)
|
|
581
|
+
if (options.memory) {
|
|
582
|
+
options.memory.episodic.append({
|
|
583
|
+
key: 'pipeline-execution',
|
|
584
|
+
value: {
|
|
585
|
+
issueNumber,
|
|
586
|
+
prUrl: pr.url,
|
|
587
|
+
filesChanged: result.filesChanged.length,
|
|
588
|
+
outcome: 'success',
|
|
589
|
+
},
|
|
590
|
+
metadata: { summary: `Completed issue #${issueNumber}: ${issue.title}` },
|
|
591
|
+
});
|
|
592
|
+
options.memory.working.clear();
|
|
593
|
+
}
|
|
594
|
+
// 18. Audit integrity verification (non-blocking)
|
|
595
|
+
if (options.auditFilePath) {
|
|
596
|
+
try {
|
|
597
|
+
const integrity = await verifyAuditIntegrity(options.auditFilePath);
|
|
598
|
+
log.info(`Audit integrity: ${integrity.valid ? 'valid' : 'INVALID'}`);
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
log.info(`Audit integrity check skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
log.summary();
|
|
605
|
+
return {
|
|
606
|
+
prUrl: pr.url,
|
|
607
|
+
filesChanged: result.filesChanged,
|
|
608
|
+
promotionEligible: promotion.eligible,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Non-blocking diagnostics pass that exercises all previously unwired modules.
|
|
613
|
+
* Runs after the main pipeline succeeds — failures are silently ignored.
|
|
614
|
+
*/
|
|
615
|
+
function runPipelineDiagnostics(input) {
|
|
616
|
+
const { config, qualityGate, agentRole, autonomyPolicy, log } = input;
|
|
617
|
+
// Policy evaluators: Rego, CEL, ABAC, gate evaluation, complexity scoring
|
|
618
|
+
const _rego = createPipelineRegoEvaluator();
|
|
619
|
+
const _cel = createPipelineCELEvaluator();
|
|
620
|
+
const _abac = createPipelineABACHook([]);
|
|
621
|
+
const firstGate = qualityGate.spec.gates[0];
|
|
622
|
+
if (firstGate) {
|
|
623
|
+
evaluatePipelineGate(firstGate, {
|
|
624
|
+
authorType: 'ai-agent',
|
|
625
|
+
repository: '',
|
|
626
|
+
metrics: {},
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
scorePipelineComplexity({
|
|
630
|
+
filesAffected: input.filesChanged.length,
|
|
631
|
+
linesOfChange: 0,
|
|
632
|
+
});
|
|
633
|
+
evaluatePipelineComplexityRouting({
|
|
634
|
+
filesAffected: input.filesChanged.length,
|
|
635
|
+
linesOfChange: 0,
|
|
636
|
+
});
|
|
637
|
+
// Adapter ecosystem: registry, webhook bridge, git resolver, scanner
|
|
638
|
+
const adapterRegistry = createPipelineAdapterRegistry();
|
|
639
|
+
log.info(`Adapter registry: ${adapterRegistry.list().length} adapters registered`);
|
|
640
|
+
const _bridge = createPipelineWebhookBridge();
|
|
641
|
+
// resolveAdapterFromGit and scanPipelineAdapters are async — fire and forget
|
|
642
|
+
void resolveAdapterFromGit('github:ai-sdlc-framework/ai-sdlc').catch(() => { });
|
|
643
|
+
void scanPipelineAdapters({ basePath: `${input.workDir}/.ai-sdlc/adapters` }).catch(() => { });
|
|
644
|
+
// Extended compliance: per-framework checks, control catalog, mappings
|
|
645
|
+
const controlIds = getControlCatalog();
|
|
646
|
+
const frameworks = listSupportedFrameworks();
|
|
647
|
+
for (const framework of frameworks) {
|
|
648
|
+
checkFrameworkCompliance(framework, controlIds);
|
|
649
|
+
getFrameworkMappings(framework);
|
|
650
|
+
}
|
|
651
|
+
// Telemetry: silent logger, sync spans, tracer, schema validation
|
|
652
|
+
const _silent = createSilentLogger();
|
|
653
|
+
withPipelineSpanSync('diagnostics', { phase: 'post-pipeline' }, () => { });
|
|
654
|
+
const _tracer = getPipelineTracer();
|
|
655
|
+
if (config.pipeline) {
|
|
656
|
+
validateResourceSchema(config.pipeline.kind, config.pipeline);
|
|
657
|
+
}
|
|
658
|
+
// Structured buffer logger (test-oriented but exercises the wrapper)
|
|
659
|
+
const _bufLogger = createStructuredBufferLogger();
|
|
660
|
+
// Provenance validation
|
|
661
|
+
if (input.provenance) {
|
|
662
|
+
validatePipelineProvenance(input.provenance);
|
|
663
|
+
}
|
|
664
|
+
// Security factory (exercises createPipelineSecurity)
|
|
665
|
+
const _defaultSecurity = createPipelineSecurity();
|
|
666
|
+
// Admission factory (exercises createPipelineAdmission)
|
|
667
|
+
const _defaultAdmission = createPipelineAdmission({
|
|
668
|
+
qualityGate,
|
|
669
|
+
evaluationContext: { authorType: 'ai-agent', repository: '', metrics: {} },
|
|
670
|
+
});
|
|
671
|
+
// Memory factory (exercises createPipelineMemory)
|
|
672
|
+
const _mem = createPipelineMemory(input.workDir);
|
|
673
|
+
// Reconciler utilities: fingerprint + change detection
|
|
674
|
+
const fp = fingerprintResource(agentRole);
|
|
675
|
+
hasResourceChanged(agentRole, agentRole); // same resource = no change
|
|
676
|
+
fingerprintResource(autonomyPolicy); // exercise with autonomy resource
|
|
677
|
+
log.info(`Agent fingerprint: ${fp.slice(0, 8)}`);
|
|
678
|
+
// Audit extended: hash computation (file ops skipped — non-blocking)
|
|
679
|
+
computeAuditHash({
|
|
680
|
+
id: 'diag',
|
|
681
|
+
timestamp: new Date().toISOString(),
|
|
682
|
+
actor: 'system',
|
|
683
|
+
action: 'diagnostics',
|
|
684
|
+
resource: 'pipeline',
|
|
685
|
+
decision: 'allowed',
|
|
686
|
+
});
|
|
687
|
+
// Audit file operations (best-effort)
|
|
688
|
+
// Use /tmp to avoid writing to .ai-sdlc/ which is a blocked path for agents
|
|
689
|
+
if (input.metricStore) {
|
|
690
|
+
const auditPath = `/tmp/ai-sdlc-diagnostics-audit.jsonl`;
|
|
691
|
+
const fileLog = createFileAuditLog(auditPath);
|
|
692
|
+
fileLog.record({
|
|
693
|
+
actor: 'diagnostics',
|
|
694
|
+
action: 'verify',
|
|
695
|
+
resource: 'pipeline',
|
|
696
|
+
decision: 'allowed',
|
|
697
|
+
});
|
|
698
|
+
void loadAuditEntries(auditPath).catch(() => { });
|
|
699
|
+
// Note: rotateAuditLog intentionally omitted — it truncates the file,
|
|
700
|
+
// which would empty it before artifact upload can capture the contents.
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
//# sourceMappingURL=execute.js.map
|