@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.
Files changed (203) hide show
  1. package/dist/adapters.d.ts +60 -0
  2. package/dist/adapters.js +160 -0
  3. package/dist/admission.d.ts +49 -0
  4. package/dist/admission.js +90 -0
  5. package/dist/analysis/analyzer.d.ts +10 -0
  6. package/dist/analysis/analyzer.js +67 -0
  7. package/dist/analysis/complexity-scorer.d.ts +24 -0
  8. package/dist/analysis/complexity-scorer.js +39 -0
  9. package/dist/analysis/context-builder.d.ts +14 -0
  10. package/dist/analysis/context-builder.js +79 -0
  11. package/dist/analysis/convention-detector.d.ts +9 -0
  12. package/dist/analysis/convention-detector.js +177 -0
  13. package/dist/analysis/dependency-parser.d.ts +17 -0
  14. package/dist/analysis/dependency-parser.js +167 -0
  15. package/dist/analysis/file-walker.d.ts +18 -0
  16. package/dist/analysis/file-walker.js +158 -0
  17. package/dist/analysis/hotspot-analyzer.d.ts +28 -0
  18. package/dist/analysis/hotspot-analyzer.js +85 -0
  19. package/dist/analysis/index.d.ts +13 -0
  20. package/dist/analysis/index.js +12 -0
  21. package/dist/analysis/pattern-detector.d.ts +10 -0
  22. package/dist/analysis/pattern-detector.js +86 -0
  23. package/dist/analysis/types.d.ts +94 -0
  24. package/dist/analysis/types.js +5 -0
  25. package/dist/audit-archival.d.ts +48 -0
  26. package/dist/audit-archival.js +72 -0
  27. package/dist/audit-export.d.ts +45 -0
  28. package/dist/audit-export.js +100 -0
  29. package/dist/audit-extended.d.ts +28 -0
  30. package/dist/audit-extended.js +38 -0
  31. package/dist/audit-scheduler.d.ts +32 -0
  32. package/dist/audit-scheduler.js +52 -0
  33. package/dist/audit-sqlite-sink.d.ts +11 -0
  34. package/dist/audit-sqlite-sink.js +51 -0
  35. package/dist/autonomy-tracker.d.ts +76 -0
  36. package/dist/autonomy-tracker.js +277 -0
  37. package/dist/check-runs.d.ts +37 -0
  38. package/dist/check-runs.js +72 -0
  39. package/dist/cli/commands/agents.d.ts +6 -0
  40. package/dist/cli/commands/agents.js +33 -0
  41. package/dist/cli/commands/complexity.d.ts +6 -0
  42. package/dist/cli/commands/complexity.js +35 -0
  43. package/dist/cli/commands/cost.d.ts +6 -0
  44. package/dist/cli/commands/cost.js +53 -0
  45. package/dist/cli/commands/dashboard.d.ts +9 -0
  46. package/dist/cli/commands/dashboard.js +50 -0
  47. package/dist/cli/commands/health.d.ts +6 -0
  48. package/dist/cli/commands/health.js +33 -0
  49. package/dist/cli/commands/init.d.ts +6 -0
  50. package/dist/cli/commands/init.js +127 -0
  51. package/dist/cli/commands/routing.d.ts +6 -0
  52. package/dist/cli/commands/routing.js +46 -0
  53. package/dist/cli/commands/run.d.ts +6 -0
  54. package/dist/cli/commands/run.js +36 -0
  55. package/dist/cli/commands/start.d.ts +6 -0
  56. package/dist/cli/commands/start.js +26 -0
  57. package/dist/cli/commands/status.d.ts +6 -0
  58. package/dist/cli/commands/status.js +34 -0
  59. package/dist/cli/dashboard-renderer.d.ts +24 -0
  60. package/dist/cli/dashboard-renderer.js +129 -0
  61. package/dist/cli/formatters/index.d.ts +6 -0
  62. package/dist/cli/formatters/index.js +18 -0
  63. package/dist/cli/formatters/json.d.ts +5 -0
  64. package/dist/cli/formatters/json.js +7 -0
  65. package/dist/cli/formatters/minimal.d.ts +5 -0
  66. package/dist/cli/formatters/minimal.js +36 -0
  67. package/dist/cli/formatters/table.d.ts +5 -0
  68. package/dist/cli/formatters/table.js +184 -0
  69. package/dist/cli/index.d.ts +6 -0
  70. package/dist/cli/index.js +35 -0
  71. package/dist/compliance-extended.d.ts +41 -0
  72. package/dist/compliance-extended.js +58 -0
  73. package/dist/config.d.ts +26 -0
  74. package/dist/config.js +66 -0
  75. package/dist/context-enrichment.d.ts +33 -0
  76. package/dist/context-enrichment.js +125 -0
  77. package/dist/cost-governance.d.ts +22 -0
  78. package/dist/cost-governance.js +106 -0
  79. package/dist/cost-tracker.d.ts +75 -0
  80. package/dist/cost-tracker.js +177 -0
  81. package/dist/defaults.d.ts +140 -0
  82. package/dist/defaults.js +218 -0
  83. package/dist/deploy/flyio-target.d.ts +19 -0
  84. package/dist/deploy/flyio-target.js +114 -0
  85. package/dist/deploy/index.d.ts +15 -0
  86. package/dist/deploy/index.js +11 -0
  87. package/dist/deploy/kubernetes-target.d.ts +25 -0
  88. package/dist/deploy/kubernetes-target.js +124 -0
  89. package/dist/deploy/metrics-collector.d.ts +31 -0
  90. package/dist/deploy/metrics-collector.js +75 -0
  91. package/dist/deploy/rollout-controller.d.ts +42 -0
  92. package/dist/deploy/rollout-controller.js +192 -0
  93. package/dist/deploy/rollout-types.d.ts +86 -0
  94. package/dist/deploy/rollout-types.js +5 -0
  95. package/dist/deploy/types.d.ts +47 -0
  96. package/dist/deploy/types.js +5 -0
  97. package/dist/deploy/vercel-target.d.ts +16 -0
  98. package/dist/deploy/vercel-target.js +115 -0
  99. package/dist/discovery.d.ts +26 -0
  100. package/dist/discovery.js +46 -0
  101. package/dist/episodic-enhanced.d.ts +88 -0
  102. package/dist/episodic-enhanced.js +212 -0
  103. package/dist/execute.d.ts +81 -0
  104. package/dist/execute.js +703 -0
  105. package/dist/fix-ci.d.ts +61 -0
  106. package/dist/fix-ci.js +380 -0
  107. package/dist/handoff-executor.d.ts +61 -0
  108. package/dist/handoff-executor.js +119 -0
  109. package/dist/index.d.ts +70 -0
  110. package/dist/index.js +87 -0
  111. package/dist/instrumented.d.ts +27 -0
  112. package/dist/instrumented.js +34 -0
  113. package/dist/logger.d.ts +17 -0
  114. package/dist/logger.js +37 -0
  115. package/dist/multi-repo/impact-analyzer.d.ts +22 -0
  116. package/dist/multi-repo/impact-analyzer.js +95 -0
  117. package/dist/multi-repo/index.d.ts +5 -0
  118. package/dist/multi-repo/index.js +4 -0
  119. package/dist/multi-repo/monorepo-detector.d.ts +16 -0
  120. package/dist/multi-repo/monorepo-detector.js +246 -0
  121. package/dist/multi-repo/service-map-builder.d.ts +24 -0
  122. package/dist/multi-repo/service-map-builder.js +235 -0
  123. package/dist/multi-repo/types.d.ts +54 -0
  124. package/dist/multi-repo/types.js +5 -0
  125. package/dist/notifications/index.d.ts +4 -0
  126. package/dist/notifications/index.js +4 -0
  127. package/dist/notifications/notification-router.d.ts +62 -0
  128. package/dist/notifications/notification-router.js +206 -0
  129. package/dist/notifications/slack-messenger.d.ts +26 -0
  130. package/dist/notifications/slack-messenger.js +69 -0
  131. package/dist/notifications/teams-messenger.d.ts +21 -0
  132. package/dist/notifications/teams-messenger.js +85 -0
  133. package/dist/notifications.d.ts +13 -0
  134. package/dist/notifications.js +15 -0
  135. package/dist/orchestration.d.ts +20 -0
  136. package/dist/orchestration.js +53 -0
  137. package/dist/orchestrator.d.ts +160 -0
  138. package/dist/orchestrator.js +372 -0
  139. package/dist/otel-exporter.d.ts +37 -0
  140. package/dist/otel-exporter.js +128 -0
  141. package/dist/plugin.d.ts +45 -0
  142. package/dist/plugin.js +8 -0
  143. package/dist/policy-evaluators.d.ts +40 -0
  144. package/dist/policy-evaluators.js +57 -0
  145. package/dist/process-escalation.d.ts +38 -0
  146. package/dist/process-escalation.js +129 -0
  147. package/dist/progressive-gates.d.ts +54 -0
  148. package/dist/progressive-gates.js +135 -0
  149. package/dist/provenance.d.ts +29 -0
  150. package/dist/provenance.js +61 -0
  151. package/dist/reconcilers.d.ts +28 -0
  152. package/dist/reconcilers.js +37 -0
  153. package/dist/runners/claude-code.d.ts +17 -0
  154. package/dist/runners/claude-code.js +200 -0
  155. package/dist/runners/codex.d.ts +19 -0
  156. package/dist/runners/codex.js +153 -0
  157. package/dist/runners/copilot-stub.d.ts +9 -0
  158. package/dist/runners/copilot-stub.js +15 -0
  159. package/dist/runners/copilot.d.ts +15 -0
  160. package/dist/runners/copilot.js +119 -0
  161. package/dist/runners/cursor-stub.d.ts +9 -0
  162. package/dist/runners/cursor-stub.js +15 -0
  163. package/dist/runners/cursor.d.ts +20 -0
  164. package/dist/runners/cursor.js +140 -0
  165. package/dist/runners/devin-stub.d.ts +9 -0
  166. package/dist/runners/devin-stub.js +15 -0
  167. package/dist/runners/generic-llm.d.ts +51 -0
  168. package/dist/runners/generic-llm.js +125 -0
  169. package/dist/runners/index.d.ts +8 -0
  170. package/dist/runners/index.js +7 -0
  171. package/dist/runners/runner-registry.d.ts +50 -0
  172. package/dist/runners/runner-registry.js +156 -0
  173. package/dist/runners/types.d.ts +59 -0
  174. package/dist/runners/types.js +7 -0
  175. package/dist/security.d.ts +51 -0
  176. package/dist/security.js +70 -0
  177. package/dist/shared.d.ts +134 -0
  178. package/dist/shared.js +284 -0
  179. package/dist/state/index.d.ts +4 -0
  180. package/dist/state/index.js +3 -0
  181. package/dist/state/schema.d.ts +16 -0
  182. package/dist/state/schema.js +264 -0
  183. package/dist/state/store.d.ts +109 -0
  184. package/dist/state/store.js +644 -0
  185. package/dist/state/types.d.ts +190 -0
  186. package/dist/state/types.js +5 -0
  187. package/dist/structured-logger.d.ts +20 -0
  188. package/dist/structured-logger.js +64 -0
  189. package/dist/task-decomposer.d.ts +61 -0
  190. package/dist/task-decomposer.js +265 -0
  191. package/dist/telemetry-extended.d.ts +24 -0
  192. package/dist/telemetry-extended.js +31 -0
  193. package/dist/types.d.ts +9 -0
  194. package/dist/types.js +7 -0
  195. package/dist/validate-agent-output.d.ts +30 -0
  196. package/dist/validate-agent-output.js +81 -0
  197. package/dist/validate-issue.d.ts +25 -0
  198. package/dist/validate-issue.js +102 -0
  199. package/dist/watch.d.ts +43 -0
  200. package/dist/watch.js +99 -0
  201. package/dist/webhook-manager.d.ts +38 -0
  202. package/dist/webhook-manager.js +66 -0
  203. package/package.json +59 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Fix-CI orchestrator — detects CI failures on agent-created PRs,
3
+ * fetches failure logs, and re-invokes the agent with error context.
4
+ * Capped at MAX_FIX_ATTEMPTS to prevent infinite loops.
5
+ */
6
+ import { type IssueTracker, type AuditLog, type MetricStore, type AgentMemory, type SecretStore, type CIPipeline } from '@ai-sdlc/reference';
7
+ import { type Logger } from './logger.js';
8
+ import type { AgentRunner } from './runners/types.js';
9
+ import { type SecurityContext } from './security.js';
10
+ export declare const MAX_FIX_ATTEMPTS = 2;
11
+ export declare const MAX_LOG_LINES = 150;
12
+ export declare const RETRY_MARKER = "<!-- ai-sdlc-fix-ci-attempt -->";
13
+ export interface FixCIOptions {
14
+ /** Override the config directory (defaults to `.ai-sdlc`). */
15
+ configDir?: string;
16
+ /** Override the working directory (defaults to repo root). */
17
+ workDir?: string;
18
+ /** Inject a custom runner (for testing). */
19
+ runner?: AgentRunner;
20
+ /** Inject a custom logger (for testing). */
21
+ logger?: Logger;
22
+ /** Inject PR comments for testing (bypasses IssueTracker call). */
23
+ _prComments?: string[];
24
+ /** Inject CI logs for testing (bypasses `gh` CLI call). */
25
+ _ciLogs?: string;
26
+ /** Inject a custom audit log (for testing). */
27
+ auditLog?: AuditLog;
28
+ /** Inject a custom issue tracker (for testing). */
29
+ tracker?: IssueTracker;
30
+ /** In-process metric store for testable telemetry. */
31
+ metricStore?: MetricStore;
32
+ /** Agent memory for episodic recall. */
33
+ memory?: AgentMemory;
34
+ /** Security context for kill switch and JIT credentials. */
35
+ security?: SecurityContext;
36
+ /** Use the reference structured logger instead of the plain console logger. */
37
+ useStructuredLogger?: boolean;
38
+ /** Secret store adapter for resolving credentials (defaults to process.env). */
39
+ secretStore?: SecretStore;
40
+ /** CI pipeline adapter for fetching logs (falls back to `gh` CLI). */
41
+ ciAdapter?: CIPipeline;
42
+ }
43
+ /**
44
+ * Count how many fix-CI retry attempts have been made on a PR
45
+ * by scanning comments for the hidden retry marker.
46
+ */
47
+ export declare function countRetryAttempts(comments: string[]): number;
48
+ /**
49
+ * Fetch CI failure logs for a given workflow run ID.
50
+ * When a `CIPipeline` adapter is provided, uses it instead of shelling out to `gh`.
51
+ * Truncates to the last MAX_LOG_LINES lines.
52
+ */
53
+ export declare function fetchCILogs(runId: number, injectedLogs?: string, ciAdapter?: CIPipeline): Promise<string>;
54
+ /**
55
+ * Execute the fix-CI pipeline for a failing PR.
56
+ *
57
+ * Returns gracefully (no throw) when the retry limit is reached.
58
+ * Throws on agent failure or guardrail violations.
59
+ */
60
+ export declare function executeFixCI(prNumber: number, runId: number, options?: FixCIOptions): Promise<void>;
61
+ //# sourceMappingURL=fix-ci.d.ts.map
package/dist/fix-ci.js ADDED
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Fix-CI orchestrator — detects CI failures on agent-created PRs,
3
+ * fetches failure logs, and re-invokes the agent with error context.
4
+ * Capped at MAX_FIX_ATTEMPTS to prevent infinite loops.
5
+ */
6
+ import { createGitHubIssueTracker, evaluateDemotion, withSpan, getMeter, SPAN_NAMES, METRIC_NAMES, ATTRIBUTE_KEYS, } from '@ai-sdlc/reference';
7
+ import { loadConfig } from './config.js';
8
+ import { createLogger } from './logger.js';
9
+ import { createStructuredConsoleLogger } from './structured-logger.js';
10
+ import { ClaudeCodeRunner } from './runners/claude-code.js';
11
+ import { execFileAsync, getGitHubConfig, extractIssueNumber, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, recordMetric, validateAndAuditOutput, authorizeFilesChanged, } from './shared.js';
12
+ import { renderTemplate } from './notifications.js';
13
+ import { parseDuration } from './policy-evaluators.js';
14
+ import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, } from './security.js';
15
+ import { DEFAULT_MAX_FIX_ATTEMPTS, DEFAULT_MAX_LOG_LINES, DEFAULT_GH_CLI_TIMEOUT_MS, DEFAULT_CONFIG_DIR_NAME, defaultSandboxConstraints, NOTIFICATION_TITLES, } from './defaults.js';
16
+ export const MAX_FIX_ATTEMPTS = DEFAULT_MAX_FIX_ATTEMPTS;
17
+ export const MAX_LOG_LINES = DEFAULT_MAX_LOG_LINES;
18
+ export const RETRY_MARKER = '<!-- ai-sdlc-fix-ci-attempt -->';
19
+ /**
20
+ * Count how many fix-CI retry attempts have been made on a PR
21
+ * by scanning comments for the hidden retry marker.
22
+ */
23
+ export function countRetryAttempts(comments) {
24
+ let count = 0;
25
+ for (const body of comments) {
26
+ const matches = body.match(new RegExp(RETRY_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'));
27
+ if (matches) {
28
+ count += matches.length;
29
+ }
30
+ }
31
+ return count;
32
+ }
33
+ /**
34
+ * Fetch CI failure logs for a given workflow run ID.
35
+ * When a `CIPipeline` adapter is provided, uses it instead of shelling out to `gh`.
36
+ * Truncates to the last MAX_LOG_LINES lines.
37
+ */
38
+ export async function fetchCILogs(runId, injectedLogs, ciAdapter) {
39
+ if (injectedLogs !== undefined) {
40
+ return truncateLogs(injectedLogs);
41
+ }
42
+ if (ciAdapter) {
43
+ const status = await ciAdapter.getBuildStatus(String(runId));
44
+ const lines = [
45
+ `Build ${status.id}: ${status.status}`,
46
+ status.startedAt ? `Started: ${status.startedAt}` : '',
47
+ status.completedAt ? `Completed: ${status.completedAt}` : '',
48
+ ].filter(Boolean);
49
+ return truncateLogs(lines.join('\n'));
50
+ }
51
+ const { stdout } = await execFileAsync('gh', ['run', 'view', String(runId), '--log-failed'], {
52
+ timeout: DEFAULT_GH_CLI_TIMEOUT_MS,
53
+ });
54
+ return truncateLogs(stdout);
55
+ }
56
+ function truncateLogs(logs) {
57
+ const lines = logs.split('\n');
58
+ if (lines.length <= MAX_LOG_LINES) {
59
+ return logs;
60
+ }
61
+ return lines.slice(-MAX_LOG_LINES).join('\n');
62
+ }
63
+ /**
64
+ * Execute the fix-CI pipeline for a failing PR.
65
+ *
66
+ * Returns gracefully (no throw) when the retry limit is reached.
67
+ * Throws on agent failure or guardrail violations.
68
+ */
69
+ export async function executeFixCI(prNumber, runId, options = {}) {
70
+ const workDir = options.workDir ?? (await resolveRepoRoot());
71
+ const configDir = options.configDir ?? `${workDir}/${DEFAULT_CONFIG_DIR_NAME}`;
72
+ const log = options.logger ??
73
+ (options.useStructuredLogger ? createStructuredConsoleLogger() : createLogger());
74
+ const auditLog = options.auditLog ?? createDefaultAuditLog(workDir);
75
+ const metricStore = options.metricStore;
76
+ // 1. Load config
77
+ log.stage('load-config');
78
+ const config = loadConfig(configDir);
79
+ log.stageEnd('load-config');
80
+ // Kill switch check (before any work)
81
+ if (options.security) {
82
+ await checkKillSwitch(options.security);
83
+ }
84
+ if (!config.agentRole) {
85
+ throw new Error('No AgentRole resource found in .ai-sdlc/');
86
+ }
87
+ if (!config.autonomyPolicy) {
88
+ throw new Error('No AutonomyPolicy resource found in .ai-sdlc/');
89
+ }
90
+ const agentRole = config.agentRole;
91
+ const autonomyPolicy = config.autonomyPolicy;
92
+ // Derive max fix attempts from pipeline config (code stage onFailure.maxRetries)
93
+ const codeStage = config.pipeline?.spec.stages.find((s) => s.name === 'code');
94
+ const maxFixAttempts = codeStage?.onFailure?.maxRetries ?? MAX_FIX_ATTEMPTS;
95
+ // Notification templates
96
+ const notifTemplates = config.pipeline?.spec.notifications?.templates;
97
+ // Create default tracker when needed (lazy to avoid resolving secrets in test environments).
98
+ // In production (no _prComments injected), the tracker is always available.
99
+ let _tracker = options.tracker;
100
+ function getTracker() {
101
+ if (!_tracker) {
102
+ const { org, repo } = getGitHubConfig(options.secretStore);
103
+ const ghConfig = { org, repo, token: { secretRef: 'github-token' } };
104
+ _tracker = createGitHubIssueTracker(ghConfig);
105
+ }
106
+ return _tracker;
107
+ }
108
+ // Tracker is available if injected directly or if we're not in test mode
109
+ const trackerAvailable = !!options.tracker || options._prComments === undefined;
110
+ // 2. Count retry attempts (via injected comments or IssueTracker)
111
+ log.stage('check-retries');
112
+ let comments;
113
+ if (options._prComments !== undefined) {
114
+ comments = options._prComments;
115
+ }
116
+ else {
117
+ const issueComments = await getTracker().getComments(String(prNumber));
118
+ comments = issueComments.map((c) => c.body);
119
+ }
120
+ const attempts = countRetryAttempts(comments);
121
+ log.info(`Fix-CI attempt ${attempts + 1} of ${maxFixAttempts}`);
122
+ log.stageEnd('check-retries');
123
+ // Helper to add a comment via tracker (uses default tracker in production)
124
+ const addComment = async (body) => {
125
+ if (trackerAvailable) {
126
+ await getTracker().addComment(String(prNumber), body);
127
+ }
128
+ };
129
+ if (attempts >= maxFixAttempts) {
130
+ log.info(`Fix-CI retry limit reached (${maxFixAttempts}). Commenting and stopping.`);
131
+ auditLog.record({
132
+ actor: 'system',
133
+ action: 'evaluate',
134
+ resource: `pr#${prNumber}`,
135
+ decision: 'denied',
136
+ details: { reason: 'retry-limit-reached', attempts, max: maxFixAttempts },
137
+ });
138
+ const limitTpl = notifTemplates?.['fix-ci-limit'];
139
+ const limitComment = limitTpl
140
+ ? renderTemplate(limitTpl, {
141
+ attempts: String(attempts),
142
+ max: String(maxFixAttempts),
143
+ })
144
+ : {
145
+ title: NOTIFICATION_TITLES.fixCIRetryLimit,
146
+ body: `This PR has reached the maximum number of automated fix attempts (${maxFixAttempts}). Manual intervention is needed.`,
147
+ };
148
+ await addComment(`## ${limitComment.title}\n\n${limitComment.body}`);
149
+ return;
150
+ }
151
+ // 3. Fetch CI logs
152
+ log.stage('fetch-logs');
153
+ const ciLogs = await fetchCILogs(runId, options._ciLogs, options.ciAdapter);
154
+ log.stageEnd('fetch-logs');
155
+ // 4. Determine branch and issue number
156
+ const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], {
157
+ cwd: workDir,
158
+ });
159
+ const currentBranch = branchStdout.trim();
160
+ const issueNumber = extractIssueNumber(currentBranch);
161
+ if (issueNumber === null) {
162
+ throw new Error(`Branch "${currentBranch}" does not match ai-sdlc/issue-N pattern`);
163
+ }
164
+ // 5. Resolve autonomy level and constraints
165
+ const currentLevel = resolveAutonomyLevel(autonomyPolicy);
166
+ const resolved = resolveConstraints(agentRole.spec.constraints, currentLevel);
167
+ // 6. Fetch issue data (via tracker when available)
168
+ let issueTitle = `Issue #${issueNumber}`;
169
+ let issueBody = '';
170
+ if (trackerAvailable) {
171
+ const issueData = await getTracker().getIssue(String(issueNumber));
172
+ issueTitle = issueData.title;
173
+ issueBody = issueData.description ?? '';
174
+ }
175
+ // Store issue context in working memory
176
+ if (options.memory) {
177
+ options.memory.working.set('currentIssue', { prNumber, issueNumber, currentBranch });
178
+ }
179
+ // Query episodic memory for previous fix-CI attempts
180
+ if (options.memory) {
181
+ const previousAttempts = options.memory.episodic.search('fix-ci-execution');
182
+ if (previousAttempts.length > 0) {
183
+ log.info(`Found ${previousAttempts.length} previous fix-CI episodes in memory`);
184
+ }
185
+ }
186
+ const meter = getMeter();
187
+ // Wrap agent+validation+push in try/catch for failure episodes
188
+ try {
189
+ // 7. Invoke agent with CI error context (with sandbox + JIT credential lifecycle)
190
+ log.stage('agent');
191
+ const runner = options.runner ?? new ClaudeCodeRunner();
192
+ // Sandbox isolation around agent execution
193
+ let sandboxId;
194
+ let result;
195
+ try {
196
+ if (options.security) {
197
+ const timeoutMs = codeStage?.timeout ? parseDuration(codeStage.timeout) : undefined;
198
+ sandboxId = await options.security.sandbox.isolate(`issue-${issueNumber}`, defaultSandboxConstraints(workDir, timeoutMs));
199
+ }
200
+ // Issue JIT credentials before agent execution
201
+ const jitCred = options.security
202
+ ? await issueAgentCredentials(options.security, agentRole.metadata.name)
203
+ : undefined;
204
+ try {
205
+ result = await withSpan(SPAN_NAMES.AGENT_TASK, {
206
+ [ATTRIBUTE_KEYS.AGENT]: agentRole.metadata.name,
207
+ [ATTRIBUTE_KEYS.RESOURCE_NAME]: `pr#${prNumber}`,
208
+ }, async () => {
209
+ const r = await runner.run({
210
+ issueNumber,
211
+ issueTitle,
212
+ issueBody,
213
+ workDir,
214
+ branch: currentBranch,
215
+ constraints: {
216
+ maxFilesPerChange: resolved.maxFiles,
217
+ requireTests: resolved.requireTests,
218
+ blockedPaths: resolved.blockedPaths,
219
+ },
220
+ ciErrors: ciLogs,
221
+ });
222
+ if (!r.success) {
223
+ log.stageEnd('agent');
224
+ auditLog.record({
225
+ actor: 'system',
226
+ action: 'execute',
227
+ resource: `agent/${agentRole.metadata.name}`,
228
+ decision: 'denied',
229
+ details: { error: r.error },
230
+ });
231
+ meter.createCounter(METRIC_NAMES.TASK_FAILURE_TOTAL).add(1);
232
+ recordMetric(metricStore, METRIC_NAMES.TASK_FAILURE_TOTAL, 1);
233
+ // Evaluate demotion on agent failure
234
+ const agentMetrics = {
235
+ name: agentRole.metadata.name,
236
+ currentLevel: currentLevel.level,
237
+ totalTasksCompleted: 0,
238
+ metrics: {},
239
+ approvals: [],
240
+ };
241
+ const demotion = evaluateDemotion(autonomyPolicy, agentMetrics, 'failed-test');
242
+ log.info(`Demotion evaluation: ${demotion.demoted ? `demoted from ${demotion.fromLevel} to ${demotion.toLevel}` : 'no demotion'}`);
243
+ auditLog.record({
244
+ actor: 'system',
245
+ action: 'evaluate',
246
+ resource: `agent/${agentRole.metadata.name}`,
247
+ policy: 'demotion',
248
+ decision: demotion.demoted ? 'denied' : 'allowed',
249
+ details: {
250
+ trigger: demotion.trigger,
251
+ fromLevel: demotion.fromLevel,
252
+ toLevel: demotion.toLevel,
253
+ },
254
+ });
255
+ const agentFailTpl = notifTemplates?.['agent-failure'];
256
+ const errorDetail = r.error ?? 'Unknown error';
257
+ const agentFailComment = agentFailTpl
258
+ ? renderTemplate(agentFailTpl, { stageName: 'fix-ci', details: errorDetail })
259
+ : { title: NOTIFICATION_TITLES.fixCIAgentFailed, body: errorDetail };
260
+ await addComment(`## ${agentFailComment.title}\n\n${agentFailComment.body}\n\n${RETRY_MARKER}`);
261
+ throw new Error(`Fix-CI agent failed on PR #${prNumber}: ${r.error}`);
262
+ }
263
+ log.stageEnd('agent');
264
+ auditLog.record({
265
+ actor: 'system',
266
+ action: 'execute',
267
+ resource: `agent/${agentRole.metadata.name}`,
268
+ decision: 'allowed',
269
+ details: { filesChanged: r.filesChanged.length },
270
+ });
271
+ meter.createCounter(METRIC_NAMES.TASK_SUCCESS_TOTAL).add(1);
272
+ recordMetric(metricStore, METRIC_NAMES.TASK_SUCCESS_TOTAL, 1);
273
+ return r;
274
+ });
275
+ }
276
+ finally {
277
+ // Revoke JIT credentials after agent execution (success or failure)
278
+ if (jitCred && options.security) {
279
+ await revokeAgentCredentials(options.security, jitCred.id);
280
+ }
281
+ }
282
+ }
283
+ finally {
284
+ // Destroy sandbox after agent execution
285
+ if (sandboxId && options.security) {
286
+ await options.security.sandbox.destroy(sandboxId);
287
+ }
288
+ }
289
+ // 8. ABAC authorization check (if write permissions are defined)
290
+ if (currentLevel.permissions.write.length > 0) {
291
+ authorizeFilesChanged(result.filesChanged, currentLevel.permissions, agentRole.spec.constraints, auditLog, agentRole.metadata.name);
292
+ }
293
+ // 9. Validate agent output against guardrails
294
+ await withSpan(SPAN_NAMES.PIPELINE_STAGE, {
295
+ [ATTRIBUTE_KEYS.STAGE]: 'validate-output',
296
+ }, async () => {
297
+ await validateAndAuditOutput({
298
+ filesChanged: result.filesChanged,
299
+ workDir,
300
+ constraints: {
301
+ maxFilesPerChange: resolved.maxFiles,
302
+ requireTests: resolved.requireTests,
303
+ blockedPaths: resolved.blockedPaths,
304
+ },
305
+ guardrails: { maxLinesPerPR: currentLevel.guardrails.maxLinesPerPR },
306
+ auditLog,
307
+ log,
308
+ onViolation: async (violationList) => {
309
+ await addComment(`## ${NOTIFICATION_TITLES.fixCIGuardrailViolations}\n\n${violationList}\n\n${RETRY_MARKER}`);
310
+ },
311
+ });
312
+ });
313
+ // 10. Push to the same branch (CI re-runs automatically)
314
+ log.stage('push');
315
+ await execFileAsync('git', ['push', 'origin', currentBranch], { cwd: workDir });
316
+ log.stageEnd('push');
317
+ auditLog.record({
318
+ actor: 'system',
319
+ action: 'create',
320
+ resource: `push/${currentBranch}`,
321
+ decision: 'allowed',
322
+ details: { prNumber, attempt: attempts + 1 },
323
+ });
324
+ // 11. Comment on PR with success details
325
+ const successTpl = notifTemplates?.['fix-ci-success'];
326
+ const successComment = successTpl
327
+ ? renderTemplate(successTpl, {
328
+ attempt: String(attempts + 1),
329
+ max: String(maxFixAttempts),
330
+ branch: currentBranch,
331
+ })
332
+ : {
333
+ title: NOTIFICATION_TITLES.fixCIApplied,
334
+ body: `Attempt ${attempts + 1} of ${maxFixAttempts} — pushed fixes to \`${currentBranch}\`.`,
335
+ };
336
+ await addComment([
337
+ `## ${successComment.title}`,
338
+ '',
339
+ successComment.body,
340
+ '',
341
+ '### Changes',
342
+ result.filesChanged.map((f) => `- \`${f}\``).join('\n'),
343
+ '',
344
+ RETRY_MARKER,
345
+ ].join('\n'));
346
+ // 12. Record episodic memory (success)
347
+ if (options.memory) {
348
+ options.memory.episodic.append({
349
+ key: 'fix-ci-execution',
350
+ value: {
351
+ prNumber,
352
+ issueNumber,
353
+ filesChanged: result.filesChanged.length,
354
+ outcome: 'success',
355
+ },
356
+ metadata: { summary: `Fix-CI for PR #${prNumber} (attempt ${attempts + 1})` },
357
+ });
358
+ options.memory.working.clear();
359
+ }
360
+ }
361
+ catch (err) {
362
+ // Record failure episode before rethrowing
363
+ if (options.memory) {
364
+ options.memory.episodic.append({
365
+ key: 'fix-ci-execution',
366
+ value: {
367
+ prNumber,
368
+ issueNumber,
369
+ outcome: 'failure',
370
+ error: err instanceof Error ? err.message : String(err),
371
+ },
372
+ metadata: { summary: `Failed fix-CI for PR #${prNumber}` },
373
+ });
374
+ options.memory.working.clear();
375
+ }
376
+ throw err;
377
+ }
378
+ log.summary();
379
+ }
380
+ //# sourceMappingURL=fix-ci.js.map
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Handoff executor — runtime data passing between agents with contract validation and audit.
3
+ *
4
+ * Design decision D2: Wraps validateHandoff() with actual data serialization.
5
+ * Each StepResult.output becomes the handoff payload, validated against the
6
+ * contract schema before passing to the next step. Immutable handoff_events table.
7
+ */
8
+ import { type AgentRole, type StepResult, type SchemaResolver, type HandoffValidationError } from '@ai-sdlc/reference';
9
+ import type { StateStore } from './state/store.js';
10
+ import type { HandoffEvent } from './state/types.js';
11
+ export interface HandoffPayload {
12
+ fromAgent: string;
13
+ toAgent: string;
14
+ data: unknown;
15
+ timestamp: string;
16
+ }
17
+ export interface HandoffResult {
18
+ success: boolean;
19
+ payload?: HandoffPayload;
20
+ error?: string;
21
+ validationError?: HandoffValidationError;
22
+ }
23
+ export interface HandoffExecutorOptions {
24
+ /** State store for audit trail persistence. */
25
+ stateStore?: StateStore;
26
+ /** Schema resolver for contract validation. */
27
+ schemaResolver?: SchemaResolver;
28
+ /** Run ID for audit correlation. */
29
+ runId?: string;
30
+ }
31
+ export declare class HandoffExecutor {
32
+ private stateStore?;
33
+ private schemaResolver?;
34
+ private runId;
35
+ constructor(options?: HandoffExecutorOptions);
36
+ /**
37
+ * Execute a handoff from one agent to another, validating the payload
38
+ * against the handoff contract and recording an audit event.
39
+ */
40
+ executeHandoff(fromAgent: AgentRole, toAgent: AgentRole, stepResult: StepResult): HandoffResult;
41
+ /**
42
+ * Execute a chain of handoffs through a sequence of agents.
43
+ * Each agent's output becomes the next agent's input.
44
+ */
45
+ executeChain(agents: AgentRole[], stepResults: StepResult[]): HandoffResult[];
46
+ /**
47
+ * Validate a handoff payload against a JSON Schema without executing.
48
+ */
49
+ validatePayload(schema: Record<string, unknown>, payload: unknown): {
50
+ valid: boolean;
51
+ errors: string[];
52
+ };
53
+ /**
54
+ * Get all handoff events for a run from the state store.
55
+ */
56
+ getHandoffEvents(runId?: string): HandoffEvent[];
57
+ private extractPayload;
58
+ private hashPayload;
59
+ private recordHandoffEvent;
60
+ }
61
+ //# sourceMappingURL=handoff-executor.d.ts.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Handoff executor — runtime data passing between agents with contract validation and audit.
3
+ *
4
+ * Design decision D2: Wraps validateHandoff() with actual data serialization.
5
+ * Each StepResult.output becomes the handoff payload, validated against the
6
+ * contract schema before passing to the next step. Immutable handoff_events table.
7
+ */
8
+ import { validateHandoff, simpleSchemaValidate, } from '@ai-sdlc/reference';
9
+ // ── Executor ─────────────────────────────────────────────────────────
10
+ export class HandoffExecutor {
11
+ stateStore;
12
+ schemaResolver;
13
+ runId;
14
+ constructor(options = {}) {
15
+ this.stateStore = options.stateStore;
16
+ this.schemaResolver = options.schemaResolver;
17
+ this.runId = options.runId ?? `handoff-${Date.now()}`;
18
+ }
19
+ /**
20
+ * Execute a handoff from one agent to another, validating the payload
21
+ * against the handoff contract and recording an audit event.
22
+ */
23
+ executeHandoff(fromAgent, toAgent, stepResult) {
24
+ const payload = this.extractPayload(stepResult);
25
+ const payloadHash = this.hashPayload(payload);
26
+ // Validate handoff contract
27
+ const validationError = validateHandoff(fromAgent, toAgent, payload, this.schemaResolver);
28
+ const result = validationError
29
+ ? {
30
+ success: false,
31
+ error: validationError.message,
32
+ validationError,
33
+ }
34
+ : {
35
+ success: true,
36
+ payload: {
37
+ fromAgent: fromAgent.metadata.name,
38
+ toAgent: toAgent.metadata.name,
39
+ data: payload,
40
+ timestamp: new Date().toISOString(),
41
+ },
42
+ };
43
+ // Record audit event
44
+ this.recordHandoffEvent({
45
+ runId: this.runId,
46
+ fromAgent: fromAgent.metadata.name,
47
+ toAgent: toAgent.metadata.name,
48
+ payloadHash,
49
+ validationResult: result.success ? 'valid' : 'invalid',
50
+ errorMessage: result.error,
51
+ });
52
+ return result;
53
+ }
54
+ /**
55
+ * Execute a chain of handoffs through a sequence of agents.
56
+ * Each agent's output becomes the next agent's input.
57
+ */
58
+ executeChain(agents, stepResults) {
59
+ const results = [];
60
+ for (let i = 0; i < agents.length - 1; i++) {
61
+ const from = agents[i];
62
+ const to = agents[i + 1];
63
+ const stepResult = stepResults[i];
64
+ if (!stepResult) {
65
+ results.push({
66
+ success: false,
67
+ error: `No step result for agent "${from.metadata.name}"`,
68
+ });
69
+ break;
70
+ }
71
+ const result = this.executeHandoff(from, to, stepResult);
72
+ results.push(result);
73
+ if (!result.success)
74
+ break; // Stop chain on first failure
75
+ }
76
+ return results;
77
+ }
78
+ /**
79
+ * Validate a handoff payload against a JSON Schema without executing.
80
+ */
81
+ validatePayload(schema, payload) {
82
+ const errors = simpleSchemaValidate(schema, payload);
83
+ return {
84
+ valid: errors.length === 0,
85
+ errors: errors.map((e) => `${e.path}: ${e.message}`),
86
+ };
87
+ }
88
+ /**
89
+ * Get all handoff events for a run from the state store.
90
+ */
91
+ getHandoffEvents(runId) {
92
+ if (!this.stateStore)
93
+ return [];
94
+ return this.stateStore.getHandoffEvents(runId ?? this.runId);
95
+ }
96
+ // ── Internal ─────────────────────────────────────────────────────
97
+ extractPayload(stepResult) {
98
+ if (stepResult.output !== undefined && stepResult.output !== null) {
99
+ return stepResult.output;
100
+ }
101
+ return {};
102
+ }
103
+ hashPayload(payload) {
104
+ const str = JSON.stringify(payload ?? {});
105
+ // Simple hash for audit purposes (not cryptographic)
106
+ let hash = 0;
107
+ for (let i = 0; i < str.length; i++) {
108
+ const char = str.charCodeAt(i);
109
+ hash = ((hash << 5) - hash + char) | 0;
110
+ }
111
+ return Math.abs(hash).toString(16).padStart(8, '0');
112
+ }
113
+ recordHandoffEvent(event) {
114
+ if (!this.stateStore)
115
+ return;
116
+ this.stateStore.saveHandoffEvent(event);
117
+ }
118
+ }
119
+ //# sourceMappingURL=handoff-executor.js.map