@ai-sdlc/orchestrator 0.5.0 → 0.6.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 (47) hide show
  1. package/dist/action-enforcement.d.ts +26 -0
  2. package/dist/action-enforcement.js +70 -0
  3. package/dist/admission-score.d.ts +58 -0
  4. package/dist/admission-score.js +164 -0
  5. package/dist/cycle-utils.d.ts +51 -0
  6. package/dist/cycle-utils.js +77 -0
  7. package/dist/defaults.d.ts +5 -0
  8. package/dist/defaults.js +5 -0
  9. package/dist/execute.js +121 -26
  10. package/dist/fix-ci.js +32 -2
  11. package/dist/fix-review.d.ts +66 -0
  12. package/dist/fix-review.js +441 -0
  13. package/dist/index.d.ts +10 -2
  14. package/dist/index.js +13 -1
  15. package/dist/pipeline-cycle-detector.d.ts +70 -0
  16. package/dist/pipeline-cycle-detector.js +111 -0
  17. package/dist/priority.d.ts +2 -76
  18. package/dist/review.d.ts +31 -0
  19. package/dist/review.js +74 -0
  20. package/dist/runners/claude-code.js +314 -32
  21. package/dist/runners/index.d.ts +2 -1
  22. package/dist/runners/index.js +1 -0
  23. package/dist/runners/review-agent.d.ts +47 -0
  24. package/dist/runners/review-agent.js +220 -0
  25. package/dist/runners/security-triage.js +4 -0
  26. package/dist/runners/types.d.ts +19 -0
  27. package/dist/state/index.d.ts +1 -1
  28. package/dist/state/schema.d.ts +2 -1
  29. package/dist/state/schema.js +54 -1
  30. package/dist/state/store.d.ts +17 -1
  31. package/dist/state/store.js +122 -0
  32. package/dist/state/types.d.ts +35 -0
  33. package/dist/workflow-patterns/artifact-writer.d.ts +16 -0
  34. package/dist/workflow-patterns/artifact-writer.js +34 -0
  35. package/dist/workflow-patterns/classifiers.d.ts +10 -0
  36. package/dist/workflow-patterns/classifiers.js +72 -0
  37. package/dist/workflow-patterns/detector.d.ts +27 -0
  38. package/dist/workflow-patterns/detector.js +186 -0
  39. package/dist/workflow-patterns/index.d.ts +8 -0
  40. package/dist/workflow-patterns/index.js +7 -0
  41. package/dist/workflow-patterns/proposal-generator.d.ts +15 -0
  42. package/dist/workflow-patterns/proposal-generator.js +183 -0
  43. package/dist/workflow-patterns/telemetry-ingest.d.ts +27 -0
  44. package/dist/workflow-patterns/telemetry-ingest.js +103 -0
  45. package/dist/workflow-patterns/types.d.ts +61 -0
  46. package/dist/workflow-patterns/types.js +11 -0
  47. package/package.json +2 -2
package/dist/execute.js CHANGED
@@ -8,10 +8,11 @@
8
8
  import { createGitHubSourceControl, routeByComplexity, evaluatePromotion, evaluateComplexity, selectModel, withSpan, getMeter, SPAN_NAMES, METRIC_NAMES, ATTRIBUTE_KEYS, } from '@ai-sdlc/reference';
9
9
  import { loadConfigAsync } from './config.js';
10
10
  import { validateIssue, validateIssueWithExtensions, parseComplexity } from './validate-issue.js';
11
+ import { validateAgentOutput } from './validate-agent-output.js';
11
12
  import { createLogger } from './logger.js';
12
13
  import { createStructuredConsoleLogger, createStructuredBufferLogger, } from './structured-logger.js';
13
14
  import { ClaudeCodeRunner } from './runners/claude-code.js';
14
- import { execFileAsync, getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, isAutonomousStrategy, recordMetric, validateAndAuditOutput, evaluatePipelineCompliance, authorizeFilesChanged, interpolateBranchPattern, interpolatePRTitle, issueIdToNumber, formatIssueRef, } from './shared.js';
15
+ import { execFileAsync, getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, isAutonomousStrategy, recordMetric, evaluatePipelineCompliance, authorizeFilesChanged, interpolateBranchPattern, interpolatePRTitle, issueIdToNumber, formatIssueRef, } from './shared.js';
15
16
  import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, classifyAndSubmitApproval, createPipelineSecurity, } from './security.js';
16
17
  import { createPipelineOrchestration, executePipelineOrchestration, validatePipelineHandoffs, } from './orchestration.js';
17
18
  import { createPipelineProvenance, attachProvenanceToPR, validatePipelineProvenance, } from './provenance.js';
@@ -24,7 +25,7 @@ import { admitIssueResource, createPipelineAdmission, } from './admission.js';
24
25
  import { createPipelineAdapterRegistry, createPipelineWebhookBridge, resolveAdapterFromGit, scanPipelineAdapters, resolveIssueTrackerFromConfig, } from './adapters.js';
25
26
  import { existsSync, readFileSync, appendFileSync } from 'node:fs';
26
27
  import { join } from 'node:path';
27
- import { defaultSandboxConstraints, DEFAULT_CONFIG_DIR_NAME, DEFAULT_PR_FOOTER, NOTIFICATION_TITLES, } from './defaults.js';
28
+ import { defaultSandboxConstraints, DEFAULT_CONFIG_DIR_NAME, DEFAULT_PR_FOOTER, DEFAULT_LINT_COMMAND, DEFAULT_FORMAT_COMMAND, DEFAULT_COMMIT_MESSAGE_TEMPLATE, DEFAULT_COMMIT_CO_AUTHOR, NOTIFICATION_TITLES, } from './defaults.js';
28
29
  import { checkFrameworkCompliance, getControlCatalog, getFrameworkMappings, listSupportedFrameworks, } from './compliance-extended.js';
29
30
  import { createSilentLogger, withPipelineSpanSync, getPipelineTracer, validateResourceSchema, } from './telemetry-extended.js';
30
31
  import { createPipelineMemory } from './shared.js';
@@ -125,6 +126,36 @@ export async function executePipeline(issueId, options = {}) {
125
126
  async function executePipelineBody(issueId, issue, config, qualityGate, agentRole, autonomyPolicy, tracker, sc, auditLog, metricStore, options, log, workDir) {
126
127
  const issueNumber = issueIdToNumber(issueId);
127
128
  const meter = getMeter();
129
+ // Slack notifications (optional — activates when SLACK_BOT_TOKEN is set)
130
+ let slackThreadId;
131
+ const slackToken = process.env.SLACK_BOT_TOKEN;
132
+ const slackChannel = process.env.SLACK_CHANNEL;
133
+ const notifySlack = async (message) => {
134
+ if (!slackToken || !slackChannel)
135
+ return;
136
+ try {
137
+ const body = { channel: slackChannel, text: message };
138
+ if (slackThreadId)
139
+ body.thread_ts = slackThreadId;
140
+ const res = await fetch('https://slack.com/api/chat.postMessage', {
141
+ method: 'POST',
142
+ headers: {
143
+ 'Content-Type': 'application/json',
144
+ Authorization: `Bearer ${slackToken}`,
145
+ },
146
+ body: JSON.stringify(body),
147
+ });
148
+ const data = (await res.json());
149
+ // Capture the first message's timestamp as thread ID
150
+ if (data.ok && !slackThreadId && data.ts) {
151
+ slackThreadId = data.ts;
152
+ }
153
+ }
154
+ catch {
155
+ // Best-effort — don't fail the pipeline for Slack
156
+ }
157
+ };
158
+ await notifySlack(`:rocket: *Pipeline started* for <https://github.com/${process.env.GITHUB_REPOSITORY ?? ''}/issues/${issueId}|#${issueId}: ${issue.title}>`);
128
159
  // Discovery: register agent and resolve by issue labels
129
160
  const discovery = createPipelineDiscovery();
130
161
  discovery.register(agentRole);
@@ -256,7 +287,26 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
256
287
  ensureRuntimeGitignore(workDir);
257
288
  // 9. Invoke agent (with sandbox + JIT credential lifecycle when security is provided)
258
289
  log.stage('agent');
290
+ // Post progress comment so users can see the pipeline is working
291
+ await tracker.addComment(issueId, `## AI-SDLC: Agent Started\n\n` +
292
+ `The AI agent is now working on this issue on branch \`${branchName}\`.\n\n` +
293
+ `| Detail | Value |\n|---|---|\n` +
294
+ `| Model | ${selectedModel ?? 'default'} |\n` +
295
+ `| Complexity | ${complexity} |\n` +
296
+ `| Strategy | ${strategy} |\n`);
297
+ await notifySlack(`:hammer_and_wrench: Agent working on \`${branchName}\` (model: ${selectedModel ?? 'default'}, complexity: ${complexity})`);
259
298
  const runner = options.runner ?? new ClaudeCodeRunner();
299
+ // Set up progress tracking — collects streaming events for the activity log
300
+ const progressLog = [];
301
+ const onProgress = (event) => {
302
+ const timestamp = new Date().toISOString().slice(11, 19);
303
+ if (event.type === 'tool_start' && event.tool) {
304
+ progressLog.push(`[${timestamp}] ${event.tool}${event.file ? `: ${event.file}` : ''}`);
305
+ }
306
+ else if (event.type === 'cost') {
307
+ progressLog.push(`[${timestamp}] Cost: $${event.costUsd?.toFixed(4)}`);
308
+ }
309
+ };
260
310
  // Sandbox isolation around agent execution
261
311
  const codeStage = config.pipeline?.spec.stages.find((s) => s.name === 'code');
262
312
  let sandboxId;
@@ -304,6 +354,12 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
304
354
  codebaseContext: options.codebaseContext,
305
355
  episodicContext,
306
356
  sandboxId,
357
+ lintCommand: DEFAULT_LINT_COMMAND,
358
+ formatCommand: DEFAULT_FORMAT_COMMAND,
359
+ typecheckCommand: process.env.AI_SDLC_TYPECHECK_COMMAND,
360
+ commitMessageTemplate: DEFAULT_COMMIT_MESSAGE_TEMPLATE,
361
+ commitCoAuthor: DEFAULT_COMMIT_CO_AUTHOR,
362
+ onProgress,
307
363
  }));
308
364
  });
309
365
  // Extract the AgentResult from orchestration output
@@ -359,11 +415,31 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
359
415
  if (currentLevel.permissions.write.length > 0) {
360
416
  authorizeFilesChanged(result.filesChanged, currentLevel.permissions, agentRole.spec.constraints, auditLog, agentRole.metadata.name);
361
417
  }
362
- // 11. Validate agent output against guardrails
363
- await withSpan(SPAN_NAMES.PIPELINE_STAGE, {
418
+ // 11. Post activity log so users can see what the agent did
419
+ const activitySection = progressLog.length > 0
420
+ ? `### Activity Log\n\n\`\`\`\n${progressLog.slice(-40).join('\n')}\n\`\`\``
421
+ : '';
422
+ await tracker.addComment(issueId, `## AI-SDLC: Agent Complete\n\n` +
423
+ `**${result.filesChanged.length} files changed.** Pushing branch...\n\n` +
424
+ activitySection);
425
+ await notifySlack(`:white_check_mark: Agent complete — ${result.filesChanged.length} files changed. Pushing...`);
426
+ // 12. Push branch (before validation to preserve work even if validation fails)
427
+ log.stage('push');
428
+ await execFileAsync('git', ['push', 'origin', branchName], { cwd: workDir });
429
+ log.stageEnd('push');
430
+ auditLog.record({
431
+ actor: 'system',
432
+ action: 'push',
433
+ resource: `branch/${branchName}`,
434
+ decision: 'allowed',
435
+ details: { filesChanged: result.filesChanged.length, issueId },
436
+ });
437
+ // 13. Validate agent output against guardrails (after push)
438
+ const validation = await withSpan(SPAN_NAMES.PIPELINE_STAGE, {
364
439
  [ATTRIBUTE_KEYS.STAGE]: 'validate-output',
365
440
  }, async () => {
366
- await validateAndAuditOutput({
441
+ log.stage('validate-output');
442
+ const validationResult = await validateAgentOutput({
367
443
  filesChanged: result.filesChanged,
368
444
  workDir,
369
445
  constraints: {
@@ -372,14 +448,37 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
372
448
  blockedPaths: resolved.blockedPaths,
373
449
  },
374
450
  guardrails: { maxLinesPerPR: currentLevel.guardrails.maxLinesPerPR },
375
- auditLog,
376
- log,
377
- onViolation: async (violationList) => {
378
- await tracker.addComment(issueId, `## ${NOTIFICATION_TITLES.guardrailViolations}\n\n${violationList}`);
379
- },
380
451
  });
452
+ log.stageEnd('validate-output');
453
+ return validationResult;
454
+ });
455
+ if (!validation.passed) {
456
+ // Record validation failure in audit log
457
+ auditLog.record({
458
+ actor: 'system',
459
+ action: 'check',
460
+ resource: 'agent-output',
461
+ decision: 'denied',
462
+ details: { violations: validation.violations.map((v) => v.rule) },
463
+ });
464
+ // Post comment explaining the violations
465
+ const violationList = validation.violations
466
+ .map((v) => `- **${v.rule}**: ${v.message}`)
467
+ .join('\n');
468
+ await tracker.addComment(issueId, `## ${NOTIFICATION_TITLES.guardrailViolations}\n\n${violationList}\n\n` +
469
+ `The branch \`${branchName}\` has been pushed with your changes. You can review the work, ` +
470
+ `cherry-pick valid changes, or adjust the guardrails as needed.`);
471
+ // Exit without creating PR
472
+ throw new Error(`Agent output failed guardrail validation. Branch ${branchName} preserved for review.`);
473
+ }
474
+ // Validation passed — record success
475
+ auditLog.record({
476
+ actor: 'system',
477
+ action: 'check',
478
+ resource: 'agent-output',
479
+ decision: 'allowed',
381
480
  });
382
- // 11b. Evaluate quality gates and report as GitHub Check Runs
481
+ // 13b. Evaluate quality gates and report as GitHub Check Runs
383
482
  if (qualityGate.spec.gates.length > 0) {
384
483
  try {
385
484
  const { stdout: headSha } = await execFileAsync('git', ['rev-parse', 'HEAD'], {
@@ -416,7 +515,7 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
416
515
  log.info('Post-agent gate evaluation skipped');
417
516
  }
418
517
  }
419
- // 11c. Post-agent complexity evaluation (non-blocking)
518
+ // 13c. Post-agent complexity evaluation (non-blocking)
420
519
  try {
421
520
  const { stdout: diffStat } = await execFileAsync('git', ['diff', '--stat', 'HEAD~1'], {
422
521
  cwd: workDir,
@@ -445,11 +544,7 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
445
544
  catch {
446
545
  log.info('Post-agent complexity evaluation skipped');
447
546
  }
448
- // 12. Push branch
449
- log.stage('push');
450
- await execFileAsync('git', ['push', 'origin', branchName], { cwd: workDir });
451
- log.stageEnd('push');
452
- // 12b. Compute cost receipt for provenance (before PR creation)
547
+ // 14. Compute cost receipt for provenance (before PR creation)
453
548
  let costReceipt;
454
549
  if (result.tokenUsage) {
455
550
  const tu = result.tokenUsage;
@@ -467,7 +562,7 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
467
562
  },
468
563
  };
469
564
  }
470
- // 13. Create PR (with optional provenance, reading config from pipeline)
565
+ // 15. Create PR (with optional provenance, reading config from pipeline)
471
566
  const prConfig = config.pipeline?.spec.pullRequest;
472
567
  const shouldIncludeProvenance = options.includeProvenance ?? prConfig?.includeProvenance ?? options.security !== undefined;
473
568
  let provenanceBlock = '';
@@ -531,6 +626,7 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
531
626
  })
532
627
  : { title: NOTIFICATION_TITLES.prCreated, body: `Pull request created: ${pr.url}` };
533
628
  await tracker.addComment(issueId, `## ${prCreatedComment.title}\n\n${prCreatedComment.body}`);
629
+ await notifySlack(`:pull_request: PR created: ${pr.url}`);
534
630
  // 14b. Record cost from agent result
535
631
  if (options.costTracker && result.tokenUsage) {
536
632
  const tu = result.tokenUsage;
@@ -769,25 +865,24 @@ function runPipelineDiagnostics(input) {
769
865
  }
770
866
  }
771
867
  // ── Gitignore helper ─────────────────────────────────────────────────
772
- const RUNTIME_GITIGNORE_ENTRIES = [
773
- '# AI-SDLC runtime artifacts',
774
- '.ai-sdlc/state.db',
775
- '.ai-sdlc/state/',
776
- '.ai-sdlc/audit.jsonl',
777
- ];
868
+ const RUNTIME_GITIGNORE_PATHS = ['.ai-sdlc/state.db', '.ai-sdlc/state/', '.ai-sdlc/audit.jsonl'];
778
869
  /**
779
870
  * Ensure .gitignore in the working directory covers AI-SDLC runtime artifacts.
780
871
  * Without this the agent sees untracked runtime files and appends duplicate
781
872
  * gitignore entries on every run.
873
+ *
874
+ * Only checks path entries (not the comment header) to avoid false mismatches.
875
+ * Writes the block once with any missing paths.
782
876
  */
783
877
  function ensureRuntimeGitignore(workDir) {
784
878
  try {
785
879
  const gitignorePath = join(workDir, '.gitignore');
786
880
  const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
787
- const missing = RUNTIME_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
881
+ const missing = RUNTIME_GITIGNORE_PATHS.filter((entry) => !existing.includes(entry));
788
882
  if (missing.length === 0)
789
883
  return;
790
- appendFileSync(gitignorePath, '\n' + missing.join('\n') + '\n', 'utf-8');
884
+ const block = '\n# AI-SDLC runtime artifacts\n' + missing.join('\n') + '\n';
885
+ appendFileSync(gitignorePath, block, 'utf-8');
791
886
  }
792
887
  catch {
793
888
  // Best-effort — workDir may not exist yet in tests or dry-run scenarios
package/dist/fix-ci.js CHANGED
@@ -13,6 +13,7 @@ import { renderTemplate } from './notifications.js';
13
13
  import { parseDuration } from './policy-evaluators.js';
14
14
  import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, } from './security.js';
15
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
+ import { createCycleDetectorFromConfig, checkAndHandleCycle } from './cycle-utils.js';
16
17
  export const MAX_FIX_ATTEMPTS = DEFAULT_MAX_FIX_ATTEMPTS;
17
18
  export const MAX_LOG_LINES = DEFAULT_MAX_LOG_LINES;
18
19
  export const RETRY_MARKER = '<!-- ai-sdlc-fix-ci-attempt -->';
@@ -107,6 +108,8 @@ export async function executeFixCI(prNumber, runId, options = {}) {
107
108
  }
108
109
  // Tracker is available if injected directly or if we're not in test mode
109
110
  const trackerAvailable = !!options.tracker || options._prComments === undefined;
111
+ // Create cycle detector (marker generated after all guard conditions pass)
112
+ const cycleDetector = createCycleDetectorFromConfig(config.pipeline?.spec ?? {});
110
113
  // 2. Count retry attempts (via injected comments or IssueTracker)
111
114
  log.stage('check-retries');
112
115
  let comments;
@@ -126,6 +129,27 @@ export async function executeFixCI(prNumber, runId, options = {}) {
126
129
  await getTracker().addComment(String(prNumber), body);
127
130
  }
128
131
  };
132
+ // Check for pipeline-level cycles AFTER retry counting
133
+ // (so legitimate retries under the limit aren't blocked)
134
+ if (trackerAvailable) {
135
+ const cycleCheck = await checkAndHandleCycle({
136
+ issueOrPrId: String(prNumber),
137
+ stage: 'fix-ci',
138
+ tracker: getTracker(),
139
+ detector: cycleDetector,
140
+ });
141
+ if (cycleCheck.cycleDetected) {
142
+ log.info('Pipeline cycle detected. Halting fix-ci execution.');
143
+ auditLog.record({
144
+ actor: 'system',
145
+ action: 'evaluate',
146
+ resource: `pr#${prNumber}`,
147
+ decision: 'denied',
148
+ details: { reason: 'pipeline-cycle-detected' },
149
+ });
150
+ return;
151
+ }
152
+ }
129
153
  if (attempts >= maxFixAttempts) {
130
154
  log.info(`Fix-CI retry limit reached (${maxFixAttempts}). Commenting and stopping.`);
131
155
  auditLog.record({
@@ -148,6 +172,9 @@ export async function executeFixCI(prNumber, runId, options = {}) {
148
172
  await addComment(`## ${limitComment.title}\n\n${limitComment.body}`);
149
173
  return;
150
174
  }
175
+ // Generate cycle marker AFTER all guard conditions (retry limit + cycle detection)
176
+ // so it's only recorded when we actually proceed with execution
177
+ const cycleMarker = cycleDetector.recordInvocation('fix-ci');
151
178
  // 3. Fetch CI logs
152
179
  log.stage('fetch-logs');
153
180
  const ciLogs = await fetchCILogs(runId, options._ciLogs, options.ciAdapter);
@@ -259,7 +286,8 @@ export async function executeFixCI(prNumber, runId, options = {}) {
259
286
  const agentFailComment = agentFailTpl
260
287
  ? renderTemplate(agentFailTpl, { stageName: 'fix-ci', details: errorDetail })
261
288
  : { title: NOTIFICATION_TITLES.fixCIAgentFailed, body: errorDetail };
262
- await addComment(`## ${agentFailComment.title}\n\n${agentFailComment.body}\n\n${RETRY_MARKER}`);
289
+ // Use pre-created marker (one per execution, prevents double-counting)
290
+ await addComment(`## ${agentFailComment.title}\n\n${agentFailComment.body}\n\n${RETRY_MARKER}\n${cycleMarker}`);
263
291
  throw new Error(`Fix-CI agent failed on PR #${prNumber}: ${r.error}`);
264
292
  }
265
293
  log.stageEnd('agent');
@@ -308,7 +336,7 @@ export async function executeFixCI(prNumber, runId, options = {}) {
308
336
  auditLog,
309
337
  log,
310
338
  onViolation: async (violationList) => {
311
- await addComment(`## ${NOTIFICATION_TITLES.fixCIGuardrailViolations}\n\n${violationList}\n\n${RETRY_MARKER}`);
339
+ await addComment(`## ${NOTIFICATION_TITLES.fixCIGuardrailViolations}\n\n${violationList}\n\n${RETRY_MARKER}\n${cycleMarker}`);
312
340
  },
313
341
  });
314
342
  });
@@ -335,6 +363,7 @@ export async function executeFixCI(prNumber, runId, options = {}) {
335
363
  title: NOTIFICATION_TITLES.fixCIApplied,
336
364
  body: `Attempt ${attempts + 1} of ${maxFixAttempts} — pushed fixes to \`${currentBranch}\`.`,
337
365
  };
366
+ // Use pre-created marker (one per execution, prevents double-counting)
338
367
  await addComment([
339
368
  `## ${successComment.title}`,
340
369
  '',
@@ -344,6 +373,7 @@ export async function executeFixCI(prNumber, runId, options = {}) {
344
373
  result.filesChanged.map((f) => `- \`${f}\``).join('\n'),
345
374
  '',
346
375
  RETRY_MARKER,
376
+ cycleMarker,
347
377
  ].join('\n'));
348
378
  // 12. Record episodic memory (success)
349
379
  if (options.memory) {
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Fix-Review orchestrator — detects review findings on agent-created PRs,
3
+ * fetches review comments, and re-invokes the agent with review context.
4
+ * Capped at MAX_FIX_ATTEMPTS to prevent infinite loops.
5
+ */
6
+ import { type IssueTracker, type AuditLog, type MetricStore, type AgentMemory, type SecretStore } 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_REVIEW_FIX_ATTEMPTS = 2;
11
+ export declare const RETRY_MARKER = "<!-- ai-sdlc-fix-review-attempt -->";
12
+ export interface FixReviewOptions {
13
+ /** Override the config directory (defaults to `.ai-sdlc`). */
14
+ configDir?: string;
15
+ /** Override the working directory (defaults to repo root). */
16
+ workDir?: string;
17
+ /** Inject a custom runner (for testing). */
18
+ runner?: AgentRunner;
19
+ /** Inject a custom logger (for testing). */
20
+ logger?: Logger;
21
+ /** Inject PR comments for testing (bypasses IssueTracker call). */
22
+ _prComments?: string[];
23
+ /** Inject review findings for testing (bypasses GitHub API call). */
24
+ _reviewFindings?: string;
25
+ /** Inject a custom audit log (for testing). */
26
+ auditLog?: AuditLog;
27
+ /** Inject a custom issue tracker (for testing). */
28
+ tracker?: IssueTracker;
29
+ /** In-process metric store for testable telemetry. */
30
+ metricStore?: MetricStore;
31
+ /** Agent memory for episodic recall. */
32
+ memory?: AgentMemory;
33
+ /** Security context for kill switch and JIT credentials. */
34
+ security?: SecurityContext;
35
+ /** Use the reference structured logger instead of the plain console logger. */
36
+ useStructuredLogger?: boolean;
37
+ /** Secret store adapter for resolving credentials (defaults to process.env). */
38
+ secretStore?: SecretStore;
39
+ }
40
+ /**
41
+ * Count how many fix-review retry attempts have been made on a PR
42
+ * by scanning comments for the hidden retry marker.
43
+ */
44
+ export declare function countRetryAttempts(comments: string[]): number;
45
+ /**
46
+ * Validate that a PR number is a positive integer.
47
+ */
48
+ export declare function validatePrNumber(prNumber: number): void;
49
+ /**
50
+ * Sanitize a git branch name to prevent command injection.
51
+ * Allows only alphanumeric characters, slashes, dashes, underscores, and dots.
52
+ */
53
+ export declare function sanitizeBranchName(branch: string): string;
54
+ /**
55
+ * Fetch review findings from PR reviews that requested changes.
56
+ * Returns a formatted string with all findings from review agents.
57
+ */
58
+ export declare function fetchReviewFindings(prNumber: number, injectedFindings?: string, _secretStore?: SecretStore): Promise<string>;
59
+ /**
60
+ * Execute the fix-review pipeline for a PR with review findings.
61
+ *
62
+ * Returns gracefully (no throw) when the retry limit is reached.
63
+ * Throws on agent failure or guardrail violations.
64
+ */
65
+ export declare function executeFixReview(prNumber: number, options?: FixReviewOptions): Promise<void>;
66
+ //# sourceMappingURL=fix-review.d.ts.map