@ai-sdlc/orchestrator 0.4.0 → 0.5.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/execute.js CHANGED
@@ -5,13 +5,13 @@
5
5
  * load config -> fetch issue -> validate -> check autonomy ->
6
6
  * create branch -> invoke agent -> authorize -> validate -> push -> create PR -> comment
7
7
  */
8
- import { createGitHubIssueTracker, createGitHubSourceControl, routeByComplexity, evaluatePromotion, evaluateComplexity, selectModel, withSpan, getMeter, SPAN_NAMES, METRIC_NAMES, ATTRIBUTE_KEYS, } from '@ai-sdlc/reference';
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
11
  import { createLogger } from './logger.js';
12
12
  import { createStructuredConsoleLogger, createStructuredBufferLogger, } from './structured-logger.js';
13
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';
14
+ import { execFileAsync, getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, isAutonomousStrategy, recordMetric, validateAndAuditOutput, evaluatePipelineCompliance, authorizeFilesChanged, interpolateBranchPattern, interpolatePRTitle, issueIdToNumber, formatIssueRef, } from './shared.js';
15
15
  import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, classifyAndSubmitApproval, createPipelineSecurity, } from './security.js';
16
16
  import { createPipelineOrchestration, executePipelineOrchestration, validatePipelineHandoffs, } from './orchestration.js';
17
17
  import { createPipelineProvenance, attachProvenanceToPR, validatePipelineProvenance, } from './provenance.js';
@@ -21,7 +21,9 @@ import { createPipelineExpressionEvaluator, createPipelineLLMEvaluator, createPi
21
21
  import { verifyAuditIntegrity, createFileAuditLog, loadAuditEntries, computeAuditHash, } from './audit-extended.js';
22
22
  import { renderTemplate } from './notifications.js';
23
23
  import { admitIssueResource, createPipelineAdmission, } from './admission.js';
24
- import { createPipelineAdapterRegistry, createPipelineWebhookBridge, resolveAdapterFromGit, scanPipelineAdapters, } from './adapters.js';
24
+ import { createPipelineAdapterRegistry, createPipelineWebhookBridge, resolveAdapterFromGit, scanPipelineAdapters, resolveIssueTrackerFromConfig, } from './adapters.js';
25
+ import { existsSync, readFileSync, appendFileSync } from 'node:fs';
26
+ import { join } from 'node:path';
25
27
  import { defaultSandboxConstraints, DEFAULT_CONFIG_DIR_NAME, DEFAULT_PR_FOOTER, NOTIFICATION_TITLES, } from './defaults.js';
26
28
  import { checkFrameworkCompliance, getControlCatalog, getFrameworkMappings, listSupportedFrameworks, } from './compliance-extended.js';
27
29
  import { createSilentLogger, withPipelineSpanSync, getPipelineTracer, validateResourceSchema, } from './telemetry-extended.js';
@@ -31,9 +33,9 @@ import { CostTracker } from './cost-tracker.js';
31
33
  import { enrichAgentContext } from './context-enrichment.js';
32
34
  import { reportGateCheckRuns } from './check-runs.js';
33
35
  /**
34
- * Execute the full AI-SDLC pipeline for a given issue number.
36
+ * Execute the full AI-SDLC pipeline for a given issue ID.
35
37
  */
36
- export async function executePipeline(issueNumber, options = {}) {
38
+ export async function executePipeline(issueId, options = {}) {
37
39
  const workDir = options.workDir ?? (await resolveRepoRoot());
38
40
  const configDir = options.configDir ?? `${workDir}/${DEFAULT_CONFIG_DIR_NAME}`;
39
41
  const log = options.logger ??
@@ -86,22 +88,22 @@ export async function executePipeline(issueNumber, options = {}) {
86
88
  // 2. Create adapters (or use injected ones)
87
89
  const { org, repo } = getGitHubConfig(options.secretStore);
88
90
  const ghConfig = { org, repo, token: { secretRef: 'github-token' } };
89
- const tracker = options.tracker ?? createGitHubIssueTracker(ghConfig);
91
+ const tracker = options.tracker ?? resolveIssueTrackerFromConfig(config, ghConfig);
90
92
  const sc = options.sourceControl ?? createGitHubSourceControl(ghConfig);
91
93
  // 3. Fetch issue
92
94
  log.stage('validate-issue');
93
- const issue = await tracker.getIssue(String(issueNumber));
95
+ const issue = await tracker.getIssue(issueId);
94
96
  // Store issue context in working memory if provided
95
97
  if (options.memory) {
96
98
  options.memory.working.set('currentIssue', {
97
- number: issueNumber,
99
+ issueId,
98
100
  title: issue.title,
99
101
  description: issue.description,
100
102
  });
101
103
  }
102
104
  // Wrap pipeline body in try/catch to record failure episodes
103
105
  try {
104
- return await executePipelineBody(issueNumber, issue, config, qualityGate, agentRole, autonomyPolicy, tracker, sc, auditLog, metricStore, options, log, workDir);
106
+ return await executePipelineBody(issueId, issue, config, qualityGate, agentRole, autonomyPolicy, tracker, sc, auditLog, metricStore, options, log, workDir);
105
107
  }
106
108
  catch (err) {
107
109
  // Record failure episode before rethrowing
@@ -109,18 +111,19 @@ export async function executePipeline(issueNumber, options = {}) {
109
111
  options.memory.episodic.append({
110
112
  key: 'pipeline-execution',
111
113
  value: {
112
- issueNumber,
114
+ issueId,
113
115
  outcome: 'failure',
114
116
  error: err instanceof Error ? err.message : String(err),
115
117
  },
116
- metadata: { summary: `Failed issue #${issueNumber}: ${issue.title}` },
118
+ metadata: { summary: `Failed issue ${formatIssueRef(issueId)}: ${issue.title}` },
117
119
  });
118
120
  options.memory.working.clear();
119
121
  }
120
122
  throw err;
121
123
  }
122
124
  }
123
- async function executePipelineBody(issueNumber, issue, config, qualityGate, agentRole, autonomyPolicy, tracker, sc, auditLog, metricStore, options, log, workDir) {
125
+ async function executePipelineBody(issueId, issue, config, qualityGate, agentRole, autonomyPolicy, tracker, sc, auditLog, metricStore, options, log, workDir) {
126
+ const issueNumber = issueIdToNumber(issueId);
124
127
  const meter = getMeter();
125
128
  // Discovery: register agent and resolve by issue labels
126
129
  const discovery = createPipelineDiscovery();
@@ -167,7 +170,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
167
170
  auditLog.record({
168
171
  actor: 'system',
169
172
  action: 'evaluate',
170
- resource: `issue#${issueNumber}`,
173
+ resource: `issue#${issueId}`,
171
174
  policy: qualityGate.metadata.name,
172
175
  decision: 'denied',
173
176
  details: {
@@ -183,13 +186,13 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
183
186
  const gateFailTitle = gateFailTpl
184
187
  ? renderTemplate(gateFailTpl, { details: failures }).title
185
188
  : NOTIFICATION_TITLES.issueValidationFailed;
186
- await tracker.addComment(String(issueNumber), `## ${gateFailTitle}\n\n${gateFailBody}`);
187
- throw new Error(`Issue #${issueNumber} failed quality gate validation`);
189
+ await tracker.addComment(issueId, `## ${gateFailTitle}\n\n${gateFailBody}`);
190
+ throw new Error(`Issue ${formatIssueRef(issueId)} failed quality gate validation:\n${failures}`);
188
191
  }
189
192
  auditLog.record({
190
193
  actor: 'system',
191
194
  action: 'evaluate',
192
- resource: `issue#${issueNumber}`,
195
+ resource: `issue#${issueId}`,
193
196
  policy: qualityGate.metadata.name,
194
197
  decision: 'allowed',
195
198
  });
@@ -202,7 +205,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
202
205
  auditLog.record({
203
206
  actor: 'system',
204
207
  action: 'route',
205
- resource: `issue#${issueNumber}`,
208
+ resource: `issue#${issueId}`,
206
209
  decision: isAutonomousStrategy(strategy) ? 'allowed' : 'denied',
207
210
  details: { score: complexity, strategy },
208
211
  });
@@ -214,21 +217,21 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
214
217
  title: NOTIFICATION_TITLES.complexityTooHigh,
215
218
  body: `Issue complexity (${complexity}) routed as "${strategy}" — requires human involvement.`,
216
219
  };
217
- await tracker.addComment(String(issueNumber), `## ${complexityComment.title}\n\n${complexityComment.body}`);
218
- throw new Error(`Issue #${issueNumber} complexity ${complexity} routed as "${strategy}"`);
220
+ await tracker.addComment(issueId, `## ${complexityComment.title}\n\n${complexityComment.body}`);
221
+ throw new Error(`Issue ${formatIssueRef(issueId)} complexity ${complexity} routed as "${strategy}"`);
219
222
  }
220
223
  // Approval workflow check (after routing, before agent)
221
224
  if (options.security) {
222
- const approval = await classifyAndSubmitApproval(options.security, complexity, agentRole.metadata.name, `Execute pipeline for issue #${issueNumber}`);
225
+ const approval = await classifyAndSubmitApproval(options.security, complexity, agentRole.metadata.name, `Execute pipeline for issue ${formatIssueRef(issueId)}`);
223
226
  if (approval.status === 'pending') {
224
- throw new Error(`Issue #${issueNumber} requires approval (tier: ${approval.tier}) — status is pending`);
227
+ throw new Error(`Issue ${formatIssueRef(issueId)} requires approval (tier: ${approval.tier}) — status is pending`);
225
228
  }
226
229
  }
227
230
  // 6. Check autonomy level allows coding
228
231
  const currentLevel = resolveAutonomyLevel(autonomyPolicy);
229
232
  log.stageEnd('validate-issue');
230
233
  // 7. Create branch and checkout locally (read pattern from pipeline config)
231
- const branchVars = { issueNumber: String(issueNumber), issueTitle: issue.title };
234
+ const branchVars = { issueNumber: issueId, issueTitle: issue.title };
232
235
  const branchName = interpolateBranchPattern(config.pipeline?.spec.branching?.pattern, branchVars);
233
236
  await sc.createBranch({ name: branchName });
234
237
  await execFileAsync('git', ['fetch', 'origin', branchName], { cwd: workDir });
@@ -248,6 +251,9 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
248
251
  log.info(`Model selected: ${modelResult.model} (${modelResult.reason})`);
249
252
  }
250
253
  }
254
+ // 8b. Ensure .gitignore covers runtime artifacts so the agent doesn't
255
+ // re-add entries on every run.
256
+ ensureRuntimeGitignore(workDir);
251
257
  // 9. Invoke agent (with sandbox + JIT credential lifecycle when security is provided)
252
258
  log.stage('agent');
253
259
  const runner = options.runner ?? new ClaudeCodeRunner();
@@ -258,7 +264,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
258
264
  try {
259
265
  if (options.security) {
260
266
  const timeoutMs = codeStage?.timeout ? parseDuration(codeStage.timeout) : undefined;
261
- sandboxId = await options.security.sandbox.isolate(`issue-${issueNumber}`, defaultSandboxConstraints(workDir, timeoutMs));
267
+ sandboxId = await options.security.sandbox.isolate(`issue-${issueId}`, defaultSandboxConstraints(workDir, timeoutMs));
262
268
  }
263
269
  // Issue JIT credentials before agent execution
264
270
  const jitCred = options.security
@@ -272,7 +278,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
272
278
  // Enrich agent context with episodic memory
273
279
  const episodicContext = options.stateStore
274
280
  ? enrichAgentContext(options.stateStore, {
275
- issueNumber,
281
+ issueNumber: issueNumber ?? undefined,
276
282
  agentName: effectiveAgent.metadata.name,
277
283
  files: result ? result.filesChanged : undefined,
278
284
  })
@@ -280,9 +286,10 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
280
286
  const orchestrationResult = await executePipelineOrchestration(plan, [effectiveAgent], async (agent) => {
281
287
  return withSpan(SPAN_NAMES.AGENT_TASK, {
282
288
  [ATTRIBUTE_KEYS.AGENT]: agent.metadata.name,
283
- [ATTRIBUTE_KEYS.RESOURCE_NAME]: `issue#${issueNumber}`,
289
+ [ATTRIBUTE_KEYS.RESOURCE_NAME]: `issue#${issueId}`,
284
290
  }, () => runner.run({
285
- issueNumber,
291
+ issueId,
292
+ issueNumber: issueNumber ?? undefined,
286
293
  issueTitle: issue.title,
287
294
  issueBody: issue.description ?? '',
288
295
  workDir,
@@ -296,6 +303,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
296
303
  memory: options.memory,
297
304
  codebaseContext: options.codebaseContext,
298
305
  episodicContext,
306
+ sandboxId,
299
307
  }));
300
308
  });
301
309
  // Extract the AgentResult from orchestration output
@@ -319,8 +327,8 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
319
327
  const agentFailComment = agentFailTpl
320
328
  ? renderTemplate(agentFailTpl, { stageName: 'code', details: errorDetail })
321
329
  : { title: NOTIFICATION_TITLES.agentFailed, body: errorDetail };
322
- await tracker.addComment(String(issueNumber), `## ${agentFailComment.title}\n\n${agentFailComment.body}`);
323
- throw new Error(`Agent failed on issue #${issueNumber}: ${err}`);
330
+ await tracker.addComment(issueId, `## ${agentFailComment.title}\n\n${agentFailComment.body}`);
331
+ throw new Error(`Agent failed on issue ${formatIssueRef(issueId)}: ${err}`);
324
332
  }
325
333
  result = stepOutput;
326
334
  log.stageEnd('agent');
@@ -367,7 +375,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
367
375
  auditLog,
368
376
  log,
369
377
  onViolation: async (violationList) => {
370
- await tracker.addComment(String(issueNumber), `## ${NOTIFICATION_TITLES.guardrailViolations}\n\n${violationList}`);
378
+ await tracker.addComment(issueId, `## ${NOTIFICATION_TITLES.guardrailViolations}\n\n${violationList}`);
371
379
  },
372
380
  });
373
381
  });
@@ -396,7 +404,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
396
404
  auditLog.record({
397
405
  actor: 'system',
398
406
  action: 'evaluate',
399
- resource: `issue#${issueNumber}`,
407
+ resource: `issue#${issueId}`,
400
408
  policy: 'post-agent-gates',
401
409
  decision: gateResults.every((g) => g.verdict === 'pass') ? 'allowed' : 'denied',
402
410
  details: {
@@ -423,7 +431,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
423
431
  auditLog.record({
424
432
  actor: 'system',
425
433
  action: 'evaluate',
426
- resource: `issue#${issueNumber}`,
434
+ resource: `issue#${issueId}`,
427
435
  policy: 'post-agent-complexity',
428
436
  decision: 'allowed',
429
437
  details: {
@@ -468,9 +476,14 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
468
476
  promptText: issue.description,
469
477
  cost: costReceipt,
470
478
  });
471
- provenanceBlock = '\n\n' + attachProvenanceToPR(provenance);
479
+ let provenanceText = attachProvenanceToPR(provenance);
480
+ // Include priority score in provenance section when available
481
+ if (options.priorityScore) {
482
+ provenanceText += `\n- **Priority Score**: ${options.priorityScore.composite.toFixed(4)} (confidence: ${options.priorityScore.confidence.toFixed(2)})`;
483
+ }
484
+ provenanceBlock = '\n\n' + provenanceText;
472
485
  }
473
- const prVars = { issueNumber: String(issueNumber), issueTitle: issue.title };
486
+ const prVars = { issueNumber: issueId, issueTitle: issue.title };
474
487
  const prTitle = interpolatePRTitle(prConfig?.titleTemplate, prVars);
475
488
  const closeKeyword = prConfig?.closeKeyword ?? 'Closes';
476
489
  const targetBranch = config.pipeline?.spec.branching?.targetBranch ?? 'main';
@@ -486,7 +499,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
486
499
  if (section === 'changes')
487
500
  parts.push('## Changes', '', result.filesChanged.map((f) => `- \`${f}\``).join('\n'));
488
501
  if (section === 'closes')
489
- parts.push('', `${closeKeyword} #${issueNumber}`);
502
+ parts.push('', `${closeKeyword} ${formatIssueRef(issueId)}`);
490
503
  }
491
504
  const footer = options.prFooter ?? DEFAULT_PR_FOOTER;
492
505
  parts.push('', '---', footer);
@@ -504,7 +517,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
504
517
  action: 'create',
505
518
  resource: 'pull-request',
506
519
  decision: 'allowed',
507
- details: { prUrl: prResult.url, issueNumber },
520
+ details: { prUrl: prResult.url, issueId },
508
521
  });
509
522
  return prResult;
510
523
  });
@@ -514,15 +527,15 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
514
527
  const prCreatedComment = prCreatedTemplate
515
528
  ? renderTemplate(prCreatedTemplate, {
516
529
  prUrl: pr.url,
517
- issueNumber: String(issueNumber),
530
+ issueNumber: issueId,
518
531
  })
519
532
  : { title: NOTIFICATION_TITLES.prCreated, body: `Pull request created: ${pr.url}` };
520
- await tracker.addComment(String(issueNumber), `## ${prCreatedComment.title}\n\n${prCreatedComment.body}`);
533
+ await tracker.addComment(issueId, `## ${prCreatedComment.title}\n\n${prCreatedComment.body}`);
521
534
  // 14b. Record cost from agent result
522
535
  if (options.costTracker && result.tokenUsage) {
523
536
  const tu = result.tokenUsage;
524
537
  options.costTracker.recordCost({
525
- runId: `run-${Date.now()}-${issueNumber}`,
538
+ runId: `run-${Date.now()}-${issueId}`,
526
539
  agentName: agentRole.metadata.name,
527
540
  pipelineType: 'execute',
528
541
  model: tu.model,
@@ -530,7 +543,7 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
530
543
  outputTokens: tu.outputTokens,
531
544
  cacheReadTokens: tu.cacheReadTokens,
532
545
  stageName: 'code',
533
- issueNumber,
546
+ issueNumber: issueNumber ?? undefined,
534
547
  });
535
548
  }
536
549
  // 14c. Record task outcome in autonomy tracker
@@ -617,18 +630,35 @@ async function executePipelineBody(issueNumber, issue, config, qualityGate, agen
617
630
  }
618
631
  // 17. Record episodic memory (success)
619
632
  if (options.memory) {
633
+ const episodicValue = {
634
+ issueId,
635
+ prUrl: pr.url,
636
+ filesChanged: result.filesChanged.length,
637
+ outcome: 'success',
638
+ };
639
+ if (options.priorityScore) {
640
+ episodicValue.priorityComposite = options.priorityScore.composite;
641
+ episodicValue.priorityConfidence = options.priorityScore.confidence;
642
+ }
620
643
  options.memory.episodic.append({
621
644
  key: 'pipeline-execution',
622
- value: {
623
- issueNumber,
624
- prUrl: pr.url,
625
- filesChanged: result.filesChanged.length,
626
- outcome: 'success',
627
- },
628
- metadata: { summary: `Completed issue #${issueNumber}: ${issue.title}` },
645
+ value: episodicValue,
646
+ metadata: { summary: `Completed issue ${formatIssueRef(issueId)}: ${issue.title}` },
629
647
  });
630
648
  options.memory.working.clear();
631
649
  }
650
+ // 17b. Record priority calibration sample for feedback loop
651
+ if (options.stateStore && options.priorityScore) {
652
+ options.stateStore.savePrioritySample({
653
+ issueId,
654
+ priorityComposite: options.priorityScore.composite,
655
+ priorityConfidence: options.priorityScore.confidence,
656
+ priorityDimensions: JSON.stringify(options.priorityScore.dimensions),
657
+ actualComplexity: complexity,
658
+ filesChanged: result.filesChanged.length,
659
+ outcome: 'success',
660
+ });
661
+ }
632
662
  // 18. Audit integrity verification (non-blocking)
633
663
  if (options.auditFilePath) {
634
664
  try {
@@ -738,4 +768,29 @@ function runPipelineDiagnostics(input) {
738
768
  // which would empty it before artifact upload can capture the contents.
739
769
  }
740
770
  }
771
+ // ── 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
+ ];
778
+ /**
779
+ * Ensure .gitignore in the working directory covers AI-SDLC runtime artifacts.
780
+ * Without this the agent sees untracked runtime files and appends duplicate
781
+ * gitignore entries on every run.
782
+ */
783
+ function ensureRuntimeGitignore(workDir) {
784
+ try {
785
+ const gitignorePath = join(workDir, '.gitignore');
786
+ const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
787
+ const missing = RUNTIME_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
788
+ if (missing.length === 0)
789
+ return;
790
+ appendFileSync(gitignorePath, '\n' + missing.join('\n') + '\n', 'utf-8');
791
+ }
792
+ catch {
793
+ // Best-effort — workDir may not exist yet in tests or dry-run scenarios
794
+ }
795
+ }
741
796
  //# sourceMappingURL=execute.js.map
package/dist/fix-ci.js CHANGED
@@ -8,7 +8,7 @@ import { loadConfig } from './config.js';
8
8
  import { createLogger } from './logger.js';
9
9
  import { createStructuredConsoleLogger } from './structured-logger.js';
10
10
  import { ClaudeCodeRunner } from './runners/claude-code.js';
11
- import { execFileAsync, getGitHubConfig, extractIssueNumber, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, recordMetric, validateAndAuditOutput, authorizeFilesChanged, } from './shared.js';
11
+ import { execFileAsync, getGitHubConfig, extractIssueId, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, recordMetric, validateAndAuditOutput, authorizeFilesChanged, issueIdToNumber, } from './shared.js';
12
12
  import { renderTemplate } from './notifications.js';
13
13
  import { parseDuration } from './policy-evaluators.js';
14
14
  import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, } from './security.js';
@@ -157,24 +157,25 @@ export async function executeFixCI(prNumber, runId, options = {}) {
157
157
  cwd: workDir,
158
158
  });
159
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`);
160
+ const issueId = extractIssueId(currentBranch);
161
+ if (issueId === null) {
162
+ throw new Error(`Branch "${currentBranch}" does not match ai-sdlc/issue-<id> pattern`);
163
163
  }
164
+ const issueNumber = issueIdToNumber(issueId);
164
165
  // 5. Resolve autonomy level and constraints
165
166
  const currentLevel = resolveAutonomyLevel(autonomyPolicy);
166
167
  const resolved = resolveConstraints(agentRole.spec.constraints, currentLevel);
167
168
  // 6. Fetch issue data (via tracker when available)
168
- let issueTitle = `Issue #${issueNumber}`;
169
+ let issueTitle = `Issue ${issueId}`;
169
170
  let issueBody = '';
170
171
  if (trackerAvailable) {
171
- const issueData = await getTracker().getIssue(String(issueNumber));
172
+ const issueData = await getTracker().getIssue(issueId);
172
173
  issueTitle = issueData.title;
173
174
  issueBody = issueData.description ?? '';
174
175
  }
175
176
  // Store issue context in working memory
176
177
  if (options.memory) {
177
- options.memory.working.set('currentIssue', { prNumber, issueNumber, currentBranch });
178
+ options.memory.working.set('currentIssue', { prNumber, issueId, currentBranch });
178
179
  }
179
180
  // Query episodic memory for previous fix-CI attempts
180
181
  if (options.memory) {
@@ -195,7 +196,7 @@ export async function executeFixCI(prNumber, runId, options = {}) {
195
196
  try {
196
197
  if (options.security) {
197
198
  const timeoutMs = codeStage?.timeout ? parseDuration(codeStage.timeout) : undefined;
198
- sandboxId = await options.security.sandbox.isolate(`issue-${issueNumber}`, defaultSandboxConstraints(workDir, timeoutMs));
199
+ sandboxId = await options.security.sandbox.isolate(`issue-${issueId}`, defaultSandboxConstraints(workDir, timeoutMs));
199
200
  }
200
201
  // Issue JIT credentials before agent execution
201
202
  const jitCred = options.security
@@ -207,7 +208,8 @@ export async function executeFixCI(prNumber, runId, options = {}) {
207
208
  [ATTRIBUTE_KEYS.RESOURCE_NAME]: `pr#${prNumber}`,
208
209
  }, async () => {
209
210
  const r = await runner.run({
210
- issueNumber,
211
+ issueId,
212
+ issueNumber: issueNumber ?? undefined,
211
213
  issueTitle,
212
214
  issueBody,
213
215
  workDir,
@@ -349,7 +351,7 @@ export async function executeFixCI(prNumber, runId, options = {}) {
349
351
  key: 'fix-ci-execution',
350
352
  value: {
351
353
  prNumber,
352
- issueNumber,
354
+ issueId,
353
355
  filesChanged: result.filesChanged.length,
354
356
  outcome: 'success',
355
357
  },
@@ -365,7 +367,7 @@ export async function executeFixCI(prNumber, runId, options = {}) {
365
367
  key: 'fix-ci-execution',
366
368
  value: {
367
369
  prNumber,
368
- issueNumber,
370
+ issueId,
369
371
  outcome: 'failure',
370
372
  error: err instanceof Error ? err.message : String(err),
371
373
  },
package/dist/index.d.ts CHANGED
@@ -5,7 +5,8 @@ export { validateAgentOutput, type ValidationContext, type ValidationResult, typ
5
5
  export { createLogger, type Logger } from './logger.js';
6
6
  export { validateConfigFiles, type FileValidationResult } from './validate-config.js';
7
7
  export { executeFixCI, countRetryAttempts, fetchCILogs, type FixCIOptions } from './fix-ci.js';
8
- export { getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, mergeBlockedPaths, isAutonomousStrategy, recordMetric, validateAndAuditOutput, createPipelineMemory, evaluatePipelineCompliance, authorizeFilesChanged, extractIssueNumber, BRANCH_PATTERN, createAbacPermissionHook, createBlockedPathsHook, createAuditLoggingHook, createPipelineAuthorizationChain, type GitHubEnvConfig, type ValidateAndAuditParams, } from './shared.js';
8
+ export { executeTriage, type TriageOptions, type TriageResult } from './triage.js';
9
+ export { getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, mergeBlockedPaths, isAutonomousStrategy, recordMetric, validateAndAuditOutput, createPipelineMemory, evaluatePipelineCompliance, authorizeFilesChanged, extractIssueNumber, extractIssueId, issueIdToNumber, formatIssueRef, BRANCH_PATTERN, createAbacPermissionHook, createBlockedPathsHook, createAuditLoggingHook, createPipelineAuthorizationChain, type GitHubEnvConfig, type ValidateAndAuditParams, } from './shared.js';
9
10
  export { DEFAULT_MODEL, DEFAULT_GITHUB_ORG, DEFAULT_GITHUB_REPO, DEFAULT_GITHUB_REPOSITORY, DEFAULT_CONFIG_DIR_NAME, DEFAULT_SANDBOX_MEMORY_MB, DEFAULT_SANDBOX_CPU_PERCENT, DEFAULT_SANDBOX_NETWORK_POLICY, DEFAULT_SANDBOX_TIMEOUT_MS, defaultSandboxConstraints, DEFAULT_RUNNER_TIMEOUT_MS, DEFAULT_ALLOWED_TOOLS, DEFAULT_MAX_FILES_PER_CHANGE, DEFAULT_REQUIRE_TESTS, DEFAULT_BLOCKED_PATHS, DEFAULT_MAX_FIX_ATTEMPTS, DEFAULT_MAX_LOG_LINES, DEFAULT_GH_CLI_TIMEOUT_MS, DEFAULT_JIT_TTL_MS, DEFAULT_JIT_SCOPE, DEFAULT_BRANCH_TEMPLATE, DEFAULT_BRANCH_PATTERN, DEFAULT_PR_TITLE_TEMPLATE, DEFAULT_PR_FOOTER, DEFAULT_COMPLEXITY_THRESHOLDS, DEFAULT_MAX_LINES_PER_PR, DEFAULT_ANALYSIS_INCLUDE, DEFAULT_ANALYSIS_EXCLUDE, DEFAULT_GIT_HISTORY_DAYS, DEFAULT_HOTSPOT_THRESHOLD, NOTIFICATION_TITLES, DEFAULT_MODEL_COSTS, DEFAULT_COST_BUDGET_USD, DEFAULT_DASHBOARD_REFRESH_MS, PROGRESSIVE_GATE_PROFILES, DEFAULT_LINT_COMMAND, DEFAULT_FORMAT_COMMAND, DEFAULT_COMMIT_MESSAGE_TEMPLATE, DEFAULT_COMMIT_CO_AUTHOR, DEFAULT_OPENAI_API_URL, DEFAULT_OPENAI_MODEL, DEFAULT_ANTHROPIC_API_URL, DEFAULT_ANTHROPIC_MODEL, DEFAULT_GENERIC_LLM_MODEL, DEFAULT_LLM_TIMEOUT_MS, DEFAULT_LLM_MAX_TOKENS, DEFAULT_LLM_SYSTEM_PROMPT, DEFAULT_DOCKER_IMAGE, DEFAULT_WORKFLOW_FILE, DEFAULT_LABEL_TO_SKILL_MAP, DEFAULT_ANALYSIS_CACHE_TTL_MS, } from './defaults.js';
10
11
  export type { ComplexityBand, GateProfile } from './defaults.js';
11
12
  export { renderTemplate } from './notifications.js';
@@ -16,19 +17,20 @@ export { createPipelineMetricStore, createInstrumentedEnforcement, createInstrum
16
17
  export { createPipelineDiscovery, findMatchingAgent, resolveAgentForIssue, matchAgentBySkill, createStubAgentCardFetcher, createPipelineAgentCardFetcher, } from './discovery.js';
17
18
  export { createStructuredConsoleLogger, createStructuredBufferLogger, } from './structured-logger.js';
18
19
  export { startWatch, type WatchOptions, type WatchHandle } from './watch.js';
20
+ export { computePriority, rankWorkItems, type PriorityScore, type PriorityInput, type PriorityConfig, } from './priority.js';
19
21
  export { createPipelineOrchestration, executePipelineOrchestration, validatePipelineHandoffs, sequential, parallel, hybrid, hierarchical, swarm, validateHandoff, simpleSchemaValidate, } from './orchestration.js';
20
22
  export { createPipelineRegoEvaluator, createPipelineCELEvaluator, createPipelineABACHook, createPipelineExpressionEvaluator, createPipelineLLMEvaluator, evaluatePipelineGate, scorePipelineComplexity, evaluatePipelineComplexityRouting, } from './policy-evaluators.js';
21
- export { createPipelineAdapterRegistry, createPipelineWebhookBridge, resolveAdapterFromGit, resolveInfrastructure, scanPipelineAdapters, } from './adapters.js';
23
+ export { createPipelineAdapterRegistry, createPipelineWebhookBridge, resolveAdapterFromGit, resolveInfrastructure, resolveIssueTrackerFromConfig, scanPipelineAdapters, } from './adapters.js';
22
24
  export { createPipelineReconciler, createGateReconciler, createAutonomyReconciler, hasResourceChanged, fingerprintResource, } from './reconcilers.js';
23
25
  export { createFileAuditLog, verifyAuditIntegrity, loadAuditEntries, rotateAuditLog, computeAuditHash, } from './audit-extended.js';
24
26
  export { checkFrameworkCompliance, getControlCatalog, getFrameworkMappings, listSupportedFrameworks, } from './compliance-extended.js';
25
27
  export { createSilentLogger, withPipelineSpanSync, getPipelineTracer, validateResourceSchema, } from './telemetry-extended.js';
26
- export { ClaudeCodeRunner, ClaudeCodeRunner as GitHubActionsRunner, GenericLLMRunner, CopilotRunner, CursorRunner, CodexRunner, RunnerRegistry, createRunnerRegistry, type AgentRunner, type AgentContext, type AgentResult, type GenericLLMConfig, type RegisteredRunner, } from './runners/index.js';
28
+ export { ClaudeCodeRunner, ClaudeCodeRunner as GitHubActionsRunner, GenericLLMRunner, CopilotRunner, CursorRunner, CodexRunner, RunnerRegistry, createRunnerRegistry, SecurityTriageRunner, type AgentRunner, type AgentContext, type AgentResult, type GenericLLMConfig, type RegisteredRunner, type SecurityTriageConfig, type TriageVerdict, } from './runners/index.js';
27
29
  export type { TokenUsage } from './runners/index.js';
28
30
  export { SlackMessenger, TeamsMessenger, NotificationRouter } from './notifications/index.js';
29
31
  export type { SlackConfig, TeamsConfig, PipelineEvent, PipelineEventType, NotificationRoute, NotificationTemplate, } from './notifications/index.js';
30
32
  export { StateStore } from './state/index.js';
31
- export type { HotspotRecord, RoutingDecision, CostLedgerEntry, GateThresholdOverride, AutonomyEvent, AutonomyEventType, HandoffEvent, DeploymentRecord, DeploymentRecordState, RolloutStepRecord, AuditEntryRecord, } from './state/index.js';
33
+ export type { HotspotRecord, RoutingDecision, CostLedgerEntry, GateThresholdOverride, AutonomyEvent, AutonomyEventType, HandoffEvent, DeploymentRecord, DeploymentRecordState, RolloutStepRecord, AuditEntryRecord, PriorityCalibrationSample, } from './state/index.js';
32
34
  export { createKubernetesTarget, createVercelTarget, createFlyioTarget, createHttpMetricsCollector, createStubMetricsCollector, RolloutController, } from './deploy/index.js';
33
35
  export type { DeploymentTargetConfig, HealthCheckConfig, DeploymentState, DeploymentResult, DeploymentTarget, ExecFn, FetchFn, KubernetesConfig, VercelConfig, FlyioConfig, CanaryStep, CanaryConfig, BlueGreenConfig, RollingConfig, RolloutStrategy, RolloutPhase, RolloutStatus, RolloutMetrics, MetricsSource, RolloutControllerConfig, HttpMetricsConfig, } from './deploy/index.js';
34
36
  export { getComplexityBand, getGateProfile, adjustEnforcement, adjustGateForComplexity, adjustGatesForComplexity, computeGateAdjustments, } from './progressive-gates.js';
package/dist/index.js CHANGED
@@ -6,8 +6,9 @@ export { validateAgentOutput, } from './validate-agent-output.js';
6
6
  export { createLogger } from './logger.js';
7
7
  export { validateConfigFiles } from './validate-config.js';
8
8
  export { executeFixCI, countRetryAttempts, fetchCILogs } from './fix-ci.js';
9
+ export { executeTriage } from './triage.js';
9
10
  // Shared utilities
10
- export { getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, mergeBlockedPaths, isAutonomousStrategy, recordMetric, validateAndAuditOutput, createPipelineMemory, evaluatePipelineCompliance, authorizeFilesChanged, extractIssueNumber, BRANCH_PATTERN, createAbacPermissionHook, createBlockedPathsHook, createAuditLoggingHook, createPipelineAuthorizationChain, } from './shared.js';
11
+ export { getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, mergeBlockedPaths, isAutonomousStrategy, recordMetric, validateAndAuditOutput, createPipelineMemory, evaluatePipelineCompliance, authorizeFilesChanged, extractIssueNumber, extractIssueId, issueIdToNumber, formatIssueRef, BRANCH_PATTERN, createAbacPermissionHook, createBlockedPathsHook, createAuditLoggingHook, createPipelineAuthorizationChain, } from './shared.js';
11
12
  // Defaults
12
13
  export { DEFAULT_MODEL, DEFAULT_GITHUB_ORG, DEFAULT_GITHUB_REPO, DEFAULT_GITHUB_REPOSITORY, DEFAULT_CONFIG_DIR_NAME, DEFAULT_SANDBOX_MEMORY_MB, DEFAULT_SANDBOX_CPU_PERCENT, DEFAULT_SANDBOX_NETWORK_POLICY, DEFAULT_SANDBOX_TIMEOUT_MS, defaultSandboxConstraints, DEFAULT_RUNNER_TIMEOUT_MS, DEFAULT_ALLOWED_TOOLS, DEFAULT_MAX_FILES_PER_CHANGE, DEFAULT_REQUIRE_TESTS, DEFAULT_BLOCKED_PATHS, DEFAULT_MAX_FIX_ATTEMPTS, DEFAULT_MAX_LOG_LINES, DEFAULT_GH_CLI_TIMEOUT_MS, DEFAULT_JIT_TTL_MS, DEFAULT_JIT_SCOPE, DEFAULT_BRANCH_TEMPLATE, DEFAULT_BRANCH_PATTERN, DEFAULT_PR_TITLE_TEMPLATE, DEFAULT_PR_FOOTER, DEFAULT_COMPLEXITY_THRESHOLDS, DEFAULT_MAX_LINES_PER_PR, DEFAULT_ANALYSIS_INCLUDE, DEFAULT_ANALYSIS_EXCLUDE, DEFAULT_GIT_HISTORY_DAYS, DEFAULT_HOTSPOT_THRESHOLD, NOTIFICATION_TITLES, DEFAULT_MODEL_COSTS, DEFAULT_COST_BUDGET_USD, DEFAULT_DASHBOARD_REFRESH_MS, PROGRESSIVE_GATE_PROFILES, DEFAULT_LINT_COMMAND, DEFAULT_FORMAT_COMMAND, DEFAULT_COMMIT_MESSAGE_TEMPLATE, DEFAULT_COMMIT_CO_AUTHOR, DEFAULT_OPENAI_API_URL, DEFAULT_OPENAI_MODEL, DEFAULT_ANTHROPIC_API_URL, DEFAULT_ANTHROPIC_MODEL, DEFAULT_GENERIC_LLM_MODEL, DEFAULT_LLM_TIMEOUT_MS, DEFAULT_LLM_MAX_TOKENS, DEFAULT_LLM_SYSTEM_PROMPT, DEFAULT_DOCKER_IMAGE, DEFAULT_WORKFLOW_FILE, DEFAULT_LABEL_TO_SKILL_MAP, DEFAULT_ANALYSIS_CACHE_TTL_MS, } from './defaults.js';
13
14
  // Notifications
@@ -26,12 +27,14 @@ export { createPipelineDiscovery, findMatchingAgent, resolveAgentForIssue, match
26
27
  export { createStructuredConsoleLogger, createStructuredBufferLogger, } from './structured-logger.js';
27
28
  // Watch mode
28
29
  export { startWatch } from './watch.js';
30
+ // Priority scoring (PPA)
31
+ export { computePriority, rankWorkItems, } from './priority.js';
29
32
  // Agent orchestration
30
33
  export { createPipelineOrchestration, executePipelineOrchestration, validatePipelineHandoffs, sequential, parallel, hybrid, hierarchical, swarm, validateHandoff, simpleSchemaValidate, } from './orchestration.js';
31
34
  // Policy evaluators
32
35
  export { createPipelineRegoEvaluator, createPipelineCELEvaluator, createPipelineABACHook, createPipelineExpressionEvaluator, createPipelineLLMEvaluator, evaluatePipelineGate, scorePipelineComplexity, evaluatePipelineComplexityRouting, } from './policy-evaluators.js';
33
36
  // Adapter ecosystem
34
- export { createPipelineAdapterRegistry, createPipelineWebhookBridge, resolveAdapterFromGit, resolveInfrastructure, scanPipelineAdapters, } from './adapters.js';
37
+ export { createPipelineAdapterRegistry, createPipelineWebhookBridge, resolveAdapterFromGit, resolveInfrastructure, resolveIssueTrackerFromConfig, scanPipelineAdapters, } from './adapters.js';
35
38
  // Reconcilers (generalized names)
36
39
  export { createPipelineReconciler, createGateReconciler, createAutonomyReconciler, hasResourceChanged, fingerprintResource, } from './reconcilers.js';
37
40
  // Extended audit
@@ -41,7 +44,7 @@ export { checkFrameworkCompliance, getControlCatalog, getFrameworkMappings, list
41
44
  // Extended telemetry
42
45
  export { createSilentLogger, withPipelineSpanSync, getPipelineTracer, validateResourceSchema, } from './telemetry-extended.js';
43
46
  // Runners
44
- export { ClaudeCodeRunner, ClaudeCodeRunner as GitHubActionsRunner, GenericLLMRunner, CopilotRunner, CursorRunner, CodexRunner, RunnerRegistry, createRunnerRegistry, } from './runners/index.js';
47
+ export { ClaudeCodeRunner, ClaudeCodeRunner as GitHubActionsRunner, GenericLLMRunner, CopilotRunner, CursorRunner, CodexRunner, RunnerRegistry, createRunnerRegistry, SecurityTriageRunner, } from './runners/index.js';
45
48
  // Notifications
46
49
  export { SlackMessenger, TeamsMessenger, NotificationRouter } from './notifications/index.js';
47
50
  // State store
@@ -74,7 +74,7 @@ export declare class Orchestrator {
74
74
  /**
75
75
  * Run the full pipeline for a single issue.
76
76
  */
77
- run(issueNumber: number, overrides?: Partial<ExecuteOptions>): Promise<PipelineResult>;
77
+ run(issueId: string, overrides?: Partial<ExecuteOptions>): Promise<PipelineResult>;
78
78
  /**
79
79
  * Start watch mode — continuous reconciliation loop.
80
80
  */
@@ -8,6 +8,7 @@ import { executeFixCI } from './fix-ci.js';
8
8
  import { loadConfig } from './config.js';
9
9
  import { StateStore } from './state/index.js';
10
10
  import { createLogger } from './logger.js';
11
+ import { issueIdToNumber } from './shared.js';
11
12
  import { analyzeCodebase } from './analysis/analyzer.js';
12
13
  import { buildCodebaseContext } from './analysis/context-builder.js';
13
14
  import { DEFAULT_ANALYSIS_CACHE_TTL_MS } from './defaults.js';
@@ -48,14 +49,16 @@ export class Orchestrator {
48
49
  /**
49
50
  * Run the full pipeline for a single issue.
50
51
  */
51
- async run(issueNumber, overrides) {
52
- const runId = `run-${Date.now()}-${issueNumber}`;
52
+ async run(issueId, overrides) {
53
+ const issueNumber = issueIdToNumber(issueId);
54
+ const runId = `run-${Date.now()}-${issueId}`;
53
55
  const startedAt = new Date().toISOString();
54
56
  // Record pipeline start in state store
55
57
  if (this._state) {
56
58
  this._state.savePipelineRun({
57
59
  runId,
58
- issueNumber,
60
+ issueId,
61
+ issueNumber: issueNumber ?? undefined,
59
62
  pipelineType: 'execute',
60
63
  status: 'running',
61
64
  currentStage: 'init',
@@ -84,11 +87,16 @@ export class Orchestrator {
84
87
  }
85
88
  // Notify plugins before run
86
89
  for (const plugin of this.plugins) {
87
- await plugin.beforeRun?.({ runId, issueNumber, startedAt });
90
+ await plugin.beforeRun?.({
91
+ runId,
92
+ issueId,
93
+ issueNumber: issueNumber ?? undefined,
94
+ startedAt,
95
+ });
88
96
  }
89
97
  const runStart = Date.now();
90
98
  try {
91
- const result = await executePipeline(issueNumber, {
99
+ const result = await executePipeline(issueId, {
92
100
  configDir: this.config.configDir,
93
101
  workDir: this.config.workDir,
94
102
  runner: this.config.runner,
@@ -109,7 +117,8 @@ export class Orchestrator {
109
117
  }),
110
118
  });
111
119
  this._state.saveEpisodicRecord({
112
- issueNumber,
120
+ issueId,
121
+ issueNumber: issueNumber ?? undefined,
113
122
  pipelineType: 'execute',
114
123
  outcome: 'success',
115
124
  filesChanged: result.filesChanged.length,
@@ -118,7 +127,13 @@ export class Orchestrator {
118
127
  // Notify plugins after successful run
119
128
  const durationMs = Date.now() - runStart;
120
129
  for (const plugin of this.plugins) {
121
- await plugin.afterRun?.({ runId, issueNumber, result, durationMs });
130
+ await plugin.afterRun?.({
131
+ runId,
132
+ issueId,
133
+ issueNumber: issueNumber ?? undefined,
134
+ result,
135
+ durationMs,
136
+ });
122
137
  }
123
138
  return result;
124
139
  }
@@ -131,7 +146,8 @@ export class Orchestrator {
131
146
  }),
132
147
  });
133
148
  this._state.saveEpisodicRecord({
134
- issueNumber,
149
+ issueId,
150
+ issueNumber: issueNumber ?? undefined,
135
151
  pipelineType: 'execute',
136
152
  outcome: 'failure',
137
153
  errorMessage: err instanceof Error ? err.message : String(err),
@@ -141,7 +157,13 @@ export class Orchestrator {
141
157
  const errorDurationMs = Date.now() - runStart;
142
158
  const error = err instanceof Error ? err : new Error(String(err));
143
159
  for (const plugin of this.plugins) {
144
- await plugin.onError?.({ runId, issueNumber, error, durationMs: errorDurationMs });
160
+ await plugin.onError?.({
161
+ runId,
162
+ issueId,
163
+ issueNumber: issueNumber ?? undefined,
164
+ error,
165
+ durationMs: errorDurationMs,
166
+ });
145
167
  }
146
168
  throw err;
147
169
  }