@ai-sdlc/orchestrator 0.4.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 (72) hide show
  1. package/dist/action-enforcement.d.ts +26 -0
  2. package/dist/action-enforcement.js +70 -0
  3. package/dist/adapters.d.ts +18 -3
  4. package/dist/adapters.js +92 -2
  5. package/dist/admission-score.d.ts +58 -0
  6. package/dist/admission-score.js +164 -0
  7. package/dist/cli/commands/init.js +4 -8
  8. package/dist/cli/commands/run.js +2 -2
  9. package/dist/config.d.ts +3 -0
  10. package/dist/config.js +14 -5
  11. package/dist/cycle-utils.d.ts +51 -0
  12. package/dist/cycle-utils.js +77 -0
  13. package/dist/defaults.d.ts +5 -0
  14. package/dist/defaults.js +5 -0
  15. package/dist/execute.d.ts +5 -2
  16. package/dist/execute.js +212 -62
  17. package/dist/fix-ci.js +45 -13
  18. package/dist/fix-review.d.ts +66 -0
  19. package/dist/fix-review.js +441 -0
  20. package/dist/index.d.ts +14 -4
  21. package/dist/index.js +18 -3
  22. package/dist/orchestrator.d.ts +1 -1
  23. package/dist/orchestrator.js +31 -9
  24. package/dist/pipeline-cycle-detector.d.ts +70 -0
  25. package/dist/pipeline-cycle-detector.js +111 -0
  26. package/dist/plugin.d.ts +9 -3
  27. package/dist/priority.d.ts +28 -0
  28. package/dist/priority.js +230 -0
  29. package/dist/review.d.ts +31 -0
  30. package/dist/review.js +74 -0
  31. package/dist/runners/claude-code.js +367 -35
  32. package/dist/runners/codex.js +15 -4
  33. package/dist/runners/copilot.js +15 -4
  34. package/dist/runners/cursor.js +15 -4
  35. package/dist/runners/generic-llm.js +1 -1
  36. package/dist/runners/index.d.ts +3 -1
  37. package/dist/runners/index.js +2 -0
  38. package/dist/runners/review-agent.d.ts +47 -0
  39. package/dist/runners/review-agent.js +220 -0
  40. package/dist/runners/security-triage.d.ts +43 -0
  41. package/dist/runners/security-triage.js +158 -0
  42. package/dist/runners/types.d.ts +24 -1
  43. package/dist/security.d.ts +8 -3
  44. package/dist/security.js +13 -2
  45. package/dist/shared.d.ts +17 -0
  46. package/dist/shared.js +27 -0
  47. package/dist/state/index.d.ts +1 -1
  48. package/dist/state/schema.d.ts +4 -1
  49. package/dist/state/schema.js +89 -1
  50. package/dist/state/store.d.ts +31 -1
  51. package/dist/state/store.js +208 -13
  52. package/dist/state/types.d.ts +52 -0
  53. package/dist/triage.d.ts +36 -0
  54. package/dist/triage.js +133 -0
  55. package/dist/types.d.ts +1 -1
  56. package/dist/watch.d.ts +6 -2
  57. package/dist/watch.js +34 -6
  58. package/dist/workflow-patterns/artifact-writer.d.ts +16 -0
  59. package/dist/workflow-patterns/artifact-writer.js +34 -0
  60. package/dist/workflow-patterns/classifiers.d.ts +10 -0
  61. package/dist/workflow-patterns/classifiers.js +72 -0
  62. package/dist/workflow-patterns/detector.d.ts +27 -0
  63. package/dist/workflow-patterns/detector.js +186 -0
  64. package/dist/workflow-patterns/index.d.ts +8 -0
  65. package/dist/workflow-patterns/index.js +7 -0
  66. package/dist/workflow-patterns/proposal-generator.d.ts +15 -0
  67. package/dist/workflow-patterns/proposal-generator.js +183 -0
  68. package/dist/workflow-patterns/telemetry-ingest.d.ts +27 -0
  69. package/dist/workflow-patterns/telemetry-ingest.js +103 -0
  70. package/dist/workflow-patterns/types.d.ts +61 -0
  71. package/dist/workflow-patterns/types.js +11 -0
  72. package/package.json +4 -2
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Action enforcement — checks shell commands against blockedActions
3
+ * patterns from AgentRole constraints. Prevents agents from executing
4
+ * dangerous operations like merging PRs, force-pushing, or dismissing reviews.
5
+ */
6
+ import type { AuditLog } from '@ai-sdlc/reference';
7
+ export interface ActionEnforcementResult {
8
+ allowed: boolean;
9
+ /** The pattern that matched, if blocked. */
10
+ matchedPattern?: string;
11
+ /** The full command that was checked. */
12
+ command: string;
13
+ }
14
+ /**
15
+ * Check if a shell command is allowed by the blocked actions policy.
16
+ */
17
+ export declare function checkAction(command: string, blockedActions: string[]): ActionEnforcementResult;
18
+ /**
19
+ * Check an action and record the result in the audit log if blocked.
20
+ */
21
+ export declare function enforceAction(command: string, blockedActions: string[], auditLog?: AuditLog, agentName?: string): ActionEnforcementResult;
22
+ /**
23
+ * Default blocked actions for all agents.
24
+ */
25
+ export declare const DEFAULT_BLOCKED_ACTIONS: string[];
26
+ //# sourceMappingURL=action-enforcement.d.ts.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Action enforcement — checks shell commands against blockedActions
3
+ * patterns from AgentRole constraints. Prevents agents from executing
4
+ * dangerous operations like merging PRs, force-pushing, or dismissing reviews.
5
+ */
6
+ /**
7
+ * Convert a glob-like blocked action pattern to a regex.
8
+ * Supports * (any characters) anywhere in the pattern.
9
+ */
10
+ function patternToRegex(pattern) {
11
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
12
+ const regexStr = escaped.replace(/\*/g, '.*');
13
+ return new RegExp(`^${regexStr}$`, 'i');
14
+ }
15
+ /**
16
+ * Check if a shell command is allowed by the blocked actions policy.
17
+ */
18
+ export function checkAction(command, blockedActions) {
19
+ const trimmed = command.trim();
20
+ if (!trimmed)
21
+ return { allowed: true, command: trimmed };
22
+ for (const pattern of blockedActions) {
23
+ const regex = patternToRegex(pattern);
24
+ if (regex.test(trimmed)) {
25
+ return {
26
+ allowed: false,
27
+ matchedPattern: pattern,
28
+ command: trimmed,
29
+ };
30
+ }
31
+ }
32
+ return { allowed: true, command: trimmed };
33
+ }
34
+ /**
35
+ * Check an action and record the result in the audit log if blocked.
36
+ */
37
+ export function enforceAction(command, blockedActions, auditLog, agentName) {
38
+ const result = checkAction(command, blockedActions);
39
+ if (!result.allowed && auditLog) {
40
+ auditLog.record({
41
+ actor: agentName ?? 'agent',
42
+ action: 'execute',
43
+ resource: `command/${result.command.slice(0, 100)}`,
44
+ decision: 'denied',
45
+ details: {
46
+ reason: 'blocked-action',
47
+ pattern: result.matchedPattern,
48
+ command: result.command,
49
+ },
50
+ });
51
+ }
52
+ return result;
53
+ }
54
+ /**
55
+ * Default blocked actions for all agents.
56
+ */
57
+ export const DEFAULT_BLOCKED_ACTIONS = [
58
+ 'gh pr merge*',
59
+ 'git merge*',
60
+ 'git push --force*',
61
+ 'git push -f*',
62
+ 'gh pr close*',
63
+ 'gh issue close*',
64
+ 'git branch -D*',
65
+ 'git branch -d*',
66
+ 'git reset --hard*',
67
+ 'git checkout -- .',
68
+ 'git restore .',
69
+ ];
70
+ //# sourceMappingURL=action-enforcement.js.map
@@ -3,7 +3,8 @@
3
3
  * webhook bridge, git-based adapter resolver, and adapter registry/scanner
4
4
  * into the dogfood pipeline.
5
5
  */
6
- import { type AdapterRegistry, type ScanOptions, type ScanResult, type WebhookBridge, type GitAdapterFetcher, type GitResolveResult, type AuditLog, type AuditSink, type Sandbox, type SecretStore, type MemoryStore, type EventBus, type CIPipeline } from '@ai-sdlc/reference';
6
+ import type { AiSdlcConfig } from './config.js';
7
+ import { type IssueTracker, type AdapterRegistry, type ScanOptions, type ScanResult, type WebhookBridge, type GitAdapterFetcher, type GitResolveResult, type AuditLog, type AuditSink, type Sandbox, type SecretStore, type MemoryStore, type EventBus, type CIPipeline } from '@ai-sdlc/reference';
7
8
  /**
8
9
  * Create an adapter registry pre-loaded with all built-in and community adapters.
9
10
  */
@@ -55,6 +56,20 @@ export declare function scanPipelineAdapters(options: ScanOptions): Promise<Scan
55
56
  * Requires `GITHUB_REPOSITORY_OWNER` and `GITHUB_REPOSITORY` env vars.
56
57
  */
57
58
  export declare function createPipelineCIAdapter(): CIPipeline;
58
- export { createGitHubCIPipeline, createDockerSandbox, createLinearIssueTracker, resolveSecret, createGitLabSourceControl, createGitLabCIPipeline, createJiraIssueTracker, createBacklogMdIssueTracker, createWebhookServer, createGitHubWebhookProvider, verifyGitHubSignature, createGitLabWebhookProvider, verifyGitLabToken, createJiraWebhookProvider, createLinearWebhookProvider, verifyLinearSignature, createAdapterRegistry, validateAdapterMetadata, parseMetadataYaml, scanLocalAdapters, createStubCodeAnalysis, createStubMessenger, createStubDeploymentTarget, createStubGitLabCI, createStubGitLabSource, createStubJira, createStubBitbucket, createStubSonarQube, createStubSemgrep, createInMemoryAuditSink, createFileSink, createEnvSecretStore, createInMemoryMemoryStore, createInProcessEventBus, createStubSandbox, createWebhookBridge, parseGitAdapterRef, buildRawUrl, createGitAdapterFetcher, createStubGitAdapterFetcher, resolveGitAdapter, } from '@ai-sdlc/reference';
59
- export type { AdapterRegistry, AdapterMetadata, AdapterStability, AdapterFactory, MetadataValidationResult, ScanOptions, ScanResult, WebhookBridge, WebhookTransformer, GitAdapterReference, GitAdapterFetcher, GitResolveResult, CIPipeline, CodeAnalysis, Messenger, DeploymentTarget, EventBus, IssueTracker, LinearClientLike, IssueComment, AdapterInterfaces, EventStream, IssueFilter, CommitStatus, TestResults, CoverageReport, Finding, SeveritySummary, DeploymentStatus, StubCodeAnalysisConfig, StubCodeAnalysisAdapter, NotificationLogEntry, StubMessengerAdapter, StubDeploymentTargetAdapter, StubGitLabCIAdapter, StubGitLabSourceAdapter, StubJiraAdapter, StubBitbucketAdapter, StubSonarQubeConfig, StubSonarQubeAdapter, StubSemgrepConfig, StubSemgrepAdapter, AuditSink, AuditLog, InMemoryAuditSink, SecretStore, Sandbox, MemoryStore, InMemoryMemoryStore, InProcessEventBus, DockerSandboxConfig, BuildStatus, GitLabConfig, JiraConfig, BacklogMdConfig, BacklogFs, WebhookServer, WebhookServerConfig, WebhookProviderConfig, GitHubWebhookConfig, GitHubWebhookBridges, GitLabWebhookConfig, JiraWebhookConfig, LinearWebhookConfig, } from '@ai-sdlc/reference';
59
+ /**
60
+ * Resolve an IssueTracker from the adapterBindings in config.
61
+ *
62
+ * - 0 bindings → falls back to GitHub IssueTracker
63
+ * - 1 binding → returns that tracker directly
64
+ * - N bindings → wraps in CompositeIssueTracker
65
+ */
66
+ export declare function resolveIssueTrackerFromConfig(config: AiSdlcConfig, fallbackGitHubConfig: {
67
+ org: string;
68
+ repo: string;
69
+ token: {
70
+ secretRef: string;
71
+ };
72
+ }): IssueTracker;
73
+ export { createGitHubCIPipeline, createDockerSandbox, createLinearIssueTracker, resolveSecret, createGitLabSourceControl, createGitLabCIPipeline, createJiraIssueTracker, createBacklogMdIssueTracker, createWebhookServer, createGitHubWebhookProvider, verifyGitHubSignature, createGitLabWebhookProvider, verifyGitLabToken, createJiraWebhookProvider, createLinearWebhookProvider, verifyLinearSignature, createAdapterRegistry, validateAdapterMetadata, parseMetadataYaml, scanLocalAdapters, createStubCodeAnalysis, createStubMessenger, createStubDeploymentTarget, createStubGitLabCI, createStubGitLabSource, createStubJira, createStubBitbucket, createStubSonarQube, createStubSemgrep, createInMemoryAuditSink, createFileSink, createEnvSecretStore, createInMemoryMemoryStore, createInProcessEventBus, createStubSandbox, createCompositeIssueTracker, createWebhookBridge, parseGitAdapterRef, buildRawUrl, createGitAdapterFetcher, createStubGitAdapterFetcher, resolveGitAdapter, } from '@ai-sdlc/reference';
74
+ export type { AdapterRegistry, AdapterMetadata, AdapterStability, AdapterFactory, MetadataValidationResult, ScanOptions, ScanResult, WebhookBridge, WebhookTransformer, GitAdapterReference, GitAdapterFetcher, GitResolveResult, CIPipeline, CodeAnalysis, Messenger, DeploymentTarget, EventBus, IssueTracker, LinearClientLike, IssueComment, AdapterInterfaces, EventStream, IssueFilter, CommitStatus, TestResults, CoverageReport, Finding, SeveritySummary, DeploymentStatus, StubCodeAnalysisConfig, StubCodeAnalysisAdapter, NotificationLogEntry, StubMessengerAdapter, StubDeploymentTargetAdapter, StubGitLabCIAdapter, StubGitLabSourceAdapter, StubJiraAdapter, StubBitbucketAdapter, StubSonarQubeConfig, StubSonarQubeAdapter, StubSemgrepConfig, StubSemgrepAdapter, AuditSink, AuditLog, InMemoryAuditSink, SecretStore, Sandbox, MemoryStore, InMemoryMemoryStore, InProcessEventBus, DockerSandboxConfig, BuildStatus, GitLabConfig, JiraConfig, BacklogMdConfig, BacklogFs, WebhookServer, WebhookServerConfig, WebhookProviderConfig, GitHubWebhookConfig, GitHubWebhookBridges, GitLabWebhookConfig, JiraWebhookConfig, LinearWebhookConfig, CompositeIssueTrackerConfig, BackendRoute, } from '@ai-sdlc/reference';
60
75
  //# sourceMappingURL=adapters.d.ts.map
package/dist/adapters.js CHANGED
@@ -14,6 +14,8 @@ createStubCodeAnalysis, createStubMessenger, createStubDeploymentTarget, createS
14
14
  createInMemoryAuditSink, createFileSink, createAuditLog, createEnvSecretStore, createInMemoryMemoryStore, createInProcessEventBus, createStubSandbox,
15
15
  // Docker sandbox
16
16
  createDockerSandbox,
17
+ // OpenShell sandbox
18
+ createOpenShellSandbox,
17
19
  // Backlog.md adapter
18
20
  createBacklogMdIssueTracker,
19
21
  // Webhook bridge (used in function body)
@@ -21,7 +23,13 @@ createWebhookBridge,
21
23
  // Git resolver (used in function bodies)
22
24
  createStubGitAdapterFetcher, createGitAdapterFetcher, resolveGitAdapter,
23
25
  // GitHub CI adapter
24
- createGitHubCIPipeline, } from '@ai-sdlc/reference';
26
+ createGitHubCIPipeline,
27
+ // GitHub issue tracker
28
+ createGitHubIssueTracker,
29
+ // Composite issue tracker
30
+ createCompositeIssueTracker,
31
+ // Production adapters (for resolveIssueTrackerFromConfig)
32
+ createJiraIssueTracker, createLinearIssueTracker, } from '@ai-sdlc/reference';
25
33
  /**
26
34
  * Create an adapter registry pre-loaded with all built-in and community adapters.
27
35
  */
@@ -69,6 +77,24 @@ export function createPipelineAdapterRegistry() {
69
77
  };
70
78
  return createDockerSandbox(exec, config);
71
79
  });
80
+ registry.register(stubMeta('openshell-sandbox', 'OpenShell Sandbox', 'Sandbox@v1', 'openshell'), () => {
81
+ const exec = async (cmd) => {
82
+ const { exec: cpExec } = await import('node:child_process');
83
+ const { promisify } = await import('node:util');
84
+ const execAsync = promisify(cpExec);
85
+ const { stdout } = await execAsync(cmd);
86
+ return stdout;
87
+ };
88
+ const config = {
89
+ workDir: process.env.AI_SDLC_WORK_DIR,
90
+ binaryPath: process.env.AI_SDLC_OPENSHELL_BIN,
91
+ autoProviders: [
92
+ { name: 'claude', type: 'claude' },
93
+ { name: 'github', type: 'github' },
94
+ ],
95
+ };
96
+ return createOpenShellSandbox(exec, config);
97
+ });
72
98
  registry.register(stubMeta('env-secret-store', 'Environment Secret Store', 'SecretStore@v1', 'env'), () => createEnvSecretStore());
73
99
  registry.register(stubMeta('memory-store', 'In-Memory Memory Store', 'MemoryStore@v1', 'memory'), () => createInMemoryMemoryStore());
74
100
  registry.register(stubMeta('in-process-event-bus', 'In-Process Event Bus', 'EventBus@v1', 'in-process'), () => createInProcessEventBus());
@@ -87,7 +113,12 @@ export function resolveInfrastructure(registry, config) {
87
113
  const auditLog = createAuditLog(auditSink);
88
114
  // Resolve from registry — factories are guaranteed present when using
89
115
  // createPipelineAdapterRegistry(), but we guard for custom registries.
90
- const sandboxProvider = process.env.AI_SDLC_SANDBOX_PROVIDER === 'docker' ? 'docker-sandbox' : 'stub-sandbox';
116
+ const sandboxEnv = process.env.AI_SDLC_SANDBOX_PROVIDER;
117
+ const sandboxProvider = sandboxEnv === 'openshell'
118
+ ? 'openshell-sandbox'
119
+ : sandboxEnv === 'docker'
120
+ ? 'docker-sandbox'
121
+ : 'stub-sandbox';
91
122
  const sandbox = registry.getFactory(sandboxProvider)?.() ?? createStubSandbox();
92
123
  const secretStore = registry.getFactory('env-secret-store')?.() ??
93
124
  createEnvSecretStore();
@@ -140,6 +171,63 @@ export function createPipelineCIAdapter() {
140
171
  workflowFile: process.env.AI_SDLC_WORKFLOW_FILE ?? DEFAULT_WORKFLOW_FILE,
141
172
  });
142
173
  }
174
+ // ── Issue tracker resolution from config ────────────────────────────
175
+ /**
176
+ * Resolve an IssueTracker from the adapterBindings in config.
177
+ *
178
+ * - 0 bindings → falls back to GitHub IssueTracker
179
+ * - 1 binding → returns that tracker directly
180
+ * - N bindings → wraps in CompositeIssueTracker
181
+ */
182
+ export function resolveIssueTrackerFromConfig(config, fallbackGitHubConfig) {
183
+ const bindings = (config.adapterBindings ?? []).filter((b) => b.spec.interface === 'IssueTracker');
184
+ if (bindings.length === 0) {
185
+ return createGitHubIssueTracker(fallbackGitHubConfig);
186
+ }
187
+ const backends = bindings.map((binding) => {
188
+ const cfg = binding.spec.config ?? {};
189
+ switch (binding.spec.type) {
190
+ case 'backlog-md':
191
+ return {
192
+ prefix: cfg.taskPrefix ?? 'AISDLC',
193
+ adapter: createBacklogMdIssueTracker({
194
+ backlogDir: cfg.backlogDir ?? './backlog',
195
+ taskPrefix: cfg.taskPrefix,
196
+ }),
197
+ };
198
+ case 'github':
199
+ return {
200
+ prefix: null,
201
+ adapter: createGitHubIssueTracker({
202
+ org: cfg.org ?? fallbackGitHubConfig.org,
203
+ repo: cfg.repo ?? fallbackGitHubConfig.repo,
204
+ token: fallbackGitHubConfig.token,
205
+ }),
206
+ };
207
+ case 'jira':
208
+ return {
209
+ prefix: cfg.projectKey ?? null,
210
+ adapter: createJiraIssueTracker(cfg),
211
+ };
212
+ case 'linear':
213
+ return {
214
+ prefix: cfg.teamKey ?? null,
215
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
216
+ adapter: createLinearIssueTracker(cfg),
217
+ };
218
+ default:
219
+ // Unknown type — fall back to GitHub
220
+ return {
221
+ prefix: null,
222
+ adapter: createGitHubIssueTracker(fallbackGitHubConfig),
223
+ };
224
+ }
225
+ });
226
+ if (backends.length === 1) {
227
+ return backends[0].adapter;
228
+ }
229
+ return createCompositeIssueTracker({ backends });
230
+ }
143
231
  // Direct re-exports (passthrough)
144
232
  export {
145
233
  // Core adapters
@@ -158,6 +246,8 @@ createAdapterRegistry, validateAdapterMetadata, parseMetadataYaml, scanLocalAdap
158
246
  createStubCodeAnalysis, createStubMessenger, createStubDeploymentTarget, createStubGitLabCI, createStubGitLabSource, createStubJira, createStubBitbucket, createStubSonarQube, createStubSemgrep,
159
247
  // Infrastructure adapter stubs
160
248
  createInMemoryAuditSink, createFileSink, createEnvSecretStore, createInMemoryMemoryStore, createInProcessEventBus, createStubSandbox,
249
+ // Composite issue tracker
250
+ createCompositeIssueTracker,
161
251
  // Webhook bridge
162
252
  createWebhookBridge,
163
253
  // Git resolver
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Issue admission scoring — maps GitHub issue fields to PPA dimensions
3
+ * and determines whether an issue should enter the pipeline.
4
+ *
5
+ * Extracted from dogfood/scripts/ppa-score.ts for reuse across CLI
6
+ * scripts and workflows.
7
+ */
8
+ import { type PriorityInput, type PriorityScore, type PriorityConfig } from './priority.js';
9
+ /**
10
+ * GitHub author_association values indicating trust level.
11
+ * OWNER/MEMBER/COLLABORATOR = trusted (project team)
12
+ * CONTRIBUTOR = semi-trusted (has had PRs merged)
13
+ * NONE = untrusted (external)
14
+ */
15
+ export type AuthorAssociation = 'OWNER' | 'MEMBER' | 'COLLABORATOR' | 'CONTRIBUTOR' | 'FIRST_TIMER' | 'FIRST_TIME_CONTRIBUTOR' | 'NONE';
16
+ export interface AdmissionInput {
17
+ issueNumber: number;
18
+ title: string;
19
+ body: string;
20
+ labels: string[];
21
+ /** Total thumbsUp + heart reactions. */
22
+ reactionCount: number;
23
+ /** Number of human comments. */
24
+ commentCount: number;
25
+ /** ISO timestamp of issue creation. */
26
+ createdAt: string;
27
+ /** GitHub author_association — determines trust-based signal boosting. */
28
+ authorAssociation?: AuthorAssociation;
29
+ }
30
+ export interface AdmissionThresholds {
31
+ minimumScore: number;
32
+ minimumConfidence: number;
33
+ }
34
+ export interface IssueAdmissionResult {
35
+ admitted: boolean;
36
+ score: PriorityScore;
37
+ reason: string;
38
+ suggestions?: string[];
39
+ }
40
+ /**
41
+ * Map GitHub issue fields to PPA PriorityInput dimensions.
42
+ *
43
+ * Heuristics (ported from dogfood/scripts/ppa-score.ts):
44
+ * - Labels → bug severity, soul alignment hints, builder conviction
45
+ * - Reactions → team consensus, customer request count
46
+ * - Comments → demand signal
47
+ * - Body complexity section → complexity score
48
+ * - Issue age → competitive drift
49
+ */
50
+ export declare function mapIssueToPriorityInput(input: AdmissionInput): PriorityInput;
51
+ /**
52
+ * Score a GitHub issue for pipeline admission using the Product Priority Algorithm.
53
+ *
54
+ * Returns whether the issue is admitted (score and confidence above thresholds)
55
+ * along with the full score and, if rejected, suggestions for improvement.
56
+ */
57
+ export declare function scoreIssueForAdmission(input: AdmissionInput, thresholds: AdmissionThresholds, priorityConfig?: PriorityConfig): IssueAdmissionResult;
58
+ //# sourceMappingURL=admission-score.d.ts.map
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Issue admission scoring — maps GitHub issue fields to PPA dimensions
3
+ * and determines whether an issue should enter the pipeline.
4
+ *
5
+ * Extracted from dogfood/scripts/ppa-score.ts for reuse across CLI
6
+ * scripts and workflows.
7
+ */
8
+ import { computePriority, } from './priority.js';
9
+ // ── Mapping ──────────────────────────────────────────────────────────
10
+ /**
11
+ * Map GitHub issue fields to PPA PriorityInput dimensions.
12
+ *
13
+ * Heuristics (ported from dogfood/scripts/ppa-score.ts):
14
+ * - Labels → bug severity, soul alignment hints, builder conviction
15
+ * - Reactions → team consensus, customer request count
16
+ * - Comments → demand signal
17
+ * - Body complexity section → complexity score
18
+ * - Issue age → competitive drift
19
+ */
20
+ export function mapIssueToPriorityInput(input) {
21
+ const labels = input.labels;
22
+ const assoc = input.authorAssociation ?? 'NONE';
23
+ // ── Trust-based signal boosting ────────────────────────────────
24
+ // Trusted sources (project team) get baseline conviction and demand.
25
+ // Untrusted sources need external validation (reactions, comments).
26
+ const isTrusted = assoc === 'OWNER' || assoc === 'MEMBER' || assoc === 'COLLABORATOR';
27
+ const isContributor = assoc === 'CONTRIBUTOR';
28
+ // ── Complexity from issue body ───────────────────────────────
29
+ const complexityMatch = input.body?.match(/###?\s*Complexity\s*\n+\s*(\d+)/i);
30
+ const complexity = complexityMatch ? Number(complexityMatch[1]) : undefined;
31
+ // ── Bug severity from labels ─────────────────────────────────
32
+ let bugSeverity;
33
+ if (labels.includes('critical') || labels.includes('P0'))
34
+ bugSeverity = 5;
35
+ else if (labels.includes('bug'))
36
+ bugSeverity = 3;
37
+ // ── Soul alignment heuristic from labels ─────────────────────
38
+ let soulAlignment = 0.5;
39
+ if (labels.includes('security') || labels.includes('security-triage'))
40
+ soulAlignment = 0.7;
41
+ if (labels.includes('enhancement'))
42
+ soulAlignment = 0.6;
43
+ if (labels.includes('governance') || labels.includes('compliance'))
44
+ soulAlignment = 0.85;
45
+ if (labels.includes('spec') || labels.includes('rfc'))
46
+ soulAlignment = 0.9;
47
+ // Trusted authors get a soul alignment floor — they know the project mission
48
+ if (isTrusted && soulAlignment < 0.6)
49
+ soulAlignment = 0.6;
50
+ // ── Reactions → demand / consensus ───────────────────────────
51
+ const reactionConsensus = Math.min(1, input.reactionCount / 5);
52
+ // Trusted sources carry implicit team consensus
53
+ const teamConsensus = isTrusted ? Math.max(0.5, reactionConsensus) : reactionConsensus;
54
+ // ── Comment count → demand signal ────────────────────────────
55
+ const commentDemand = Math.min(1, input.commentCount / 5);
56
+ // Trusted sources filing an issue IS demand — they wouldn't file it otherwise
57
+ const demandSignal = isTrusted
58
+ ? Math.max(0.4, commentDemand)
59
+ : isContributor
60
+ ? Math.max(0.2, commentDemand)
61
+ : commentDemand;
62
+ // ── Builder conviction ─────────────────────────────────────────
63
+ // Trusted: high conviction (they're the builders)
64
+ // Contributor: moderate (proven track record)
65
+ // ai-eligible label: explicit signal
66
+ // Default: low (needs validation)
67
+ const builderConviction = labels.includes('ai-eligible')
68
+ ? 0.8
69
+ : isTrusted
70
+ ? 0.8
71
+ : isContributor
72
+ ? 0.6
73
+ : 0.4;
74
+ // ── Age → competitive drift ──────────────────────────────────
75
+ const ageMs = Date.now() - new Date(input.createdAt).getTime();
76
+ const ageDays = ageMs / (1000 * 60 * 60 * 24);
77
+ const competitiveDrift = Math.min(1, Math.max(0, (ageDays - 30) / 180));
78
+ // ── Security-rejected veto ───────────────────────────────────
79
+ if (labels.includes('security-rejected')) {
80
+ return {
81
+ itemId: `#${input.issueNumber}`,
82
+ title: input.title,
83
+ description: input.body ?? '',
84
+ labels,
85
+ soulAlignment: 0, // veto
86
+ };
87
+ }
88
+ return {
89
+ itemId: `#${input.issueNumber}`,
90
+ title: input.title,
91
+ description: input.body ?? '',
92
+ labels,
93
+ soulAlignment,
94
+ bugSeverity,
95
+ customerRequestCount: input.reactionCount,
96
+ demandSignal,
97
+ builderConviction,
98
+ complexity,
99
+ competitiveDrift,
100
+ teamConsensus,
101
+ explicitPriority: labels.includes('high') ? 0.8 : labels.includes('low') ? 0.2 : undefined,
102
+ };
103
+ }
104
+ // ── Suggestions ──────────────────────────────────────────────────────
105
+ function generateSuggestions(input, score) {
106
+ const suggestions = [];
107
+ const d = score.dimensions;
108
+ if (score.confidence < 0.2) {
109
+ suggestions.push('Add more detail to improve scoring confidence (complexity, acceptance criteria, labels)');
110
+ }
111
+ if (!input.body?.match(/###?\s*Complexity\s*\n/i)) {
112
+ suggestions.push('Add a `### Complexity` section with a score from 1-10');
113
+ }
114
+ if (!input.body?.match(/###?\s*Acceptance Criteria/i)) {
115
+ suggestions.push('Add an `### Acceptance Criteria` section with testable criteria');
116
+ }
117
+ if (input.body.length < 50) {
118
+ suggestions.push('Provide a more detailed description of the problem or feature');
119
+ }
120
+ const assoc = input.authorAssociation ?? 'NONE';
121
+ const isTrusted = assoc === 'OWNER' || assoc === 'MEMBER' || assoc === 'COLLABORATOR';
122
+ if (d.demandPressure < 0.3 && !isTrusted) {
123
+ suggestions.push('Low demand signal — add reactions or comments to show interest');
124
+ }
125
+ if (d.soulAlignment < 0.5) {
126
+ suggestions.push('Add labels that indicate alignment with the project mission (e.g., governance, spec, security)');
127
+ }
128
+ return suggestions;
129
+ }
130
+ // ── Public API ───────────────────────────────────────────────────────
131
+ /**
132
+ * Score a GitHub issue for pipeline admission using the Product Priority Algorithm.
133
+ *
134
+ * Returns whether the issue is admitted (score and confidence above thresholds)
135
+ * along with the full score and, if rejected, suggestions for improvement.
136
+ */
137
+ export function scoreIssueForAdmission(input, thresholds, priorityConfig) {
138
+ const priorityInput = mapIssueToPriorityInput(input);
139
+ const score = computePriority(priorityInput, priorityConfig);
140
+ const scorePasses = score.composite >= thresholds.minimumScore;
141
+ const confidencePasses = score.confidence >= thresholds.minimumConfidence;
142
+ const admitted = scorePasses && confidencePasses;
143
+ if (admitted) {
144
+ return {
145
+ admitted: true,
146
+ score,
147
+ reason: `Score ${score.composite.toFixed(4)} meets threshold ${thresholds.minimumScore} with ${(score.confidence * 100).toFixed(0)}% confidence`,
148
+ };
149
+ }
150
+ const reasons = [];
151
+ if (!scorePasses) {
152
+ reasons.push(`score ${score.composite.toFixed(4)} below minimum ${thresholds.minimumScore}`);
153
+ }
154
+ if (!confidencePasses) {
155
+ reasons.push(`confidence ${(score.confidence * 100).toFixed(0)}% below minimum ${(thresholds.minimumConfidence * 100).toFixed(0)}%`);
156
+ }
157
+ return {
158
+ admitted: false,
159
+ score,
160
+ reason: `Not admitted: ${reasons.join('; ')}`,
161
+ suggestions: generateSuggestions(input, score),
162
+ };
163
+ }
164
+ //# sourceMappingURL=admission-score.js.map
@@ -134,24 +134,20 @@ spec:
134
134
  action: demote-one-level
135
135
  cooldown: 2w
136
136
  `;
137
- const GITIGNORE_ENTRIES = [
138
- '# AI-SDLC runtime artifacts',
139
- '.ai-sdlc/state.db',
140
- '.ai-sdlc/state/',
141
- '.ai-sdlc/audit.jsonl',
142
- ];
137
+ const GITIGNORE_PATHS = ['.ai-sdlc/state.db', '.ai-sdlc/state/', '.ai-sdlc/audit.jsonl'];
143
138
  /** Ensure .gitignore includes AI-SDLC runtime artifact entries. */
144
139
  function ensureGitignore(projectDir, dryRun, prefix = '') {
145
140
  const gitignorePath = join(projectDir, '.gitignore');
146
141
  const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
147
- const missing = GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
142
+ // Only check path entries (not comments) to avoid duplicate blocks
143
+ const missing = GITIGNORE_PATHS.filter((entry) => !existing.includes(entry));
148
144
  if (missing.length === 0)
149
145
  return;
150
146
  if (dryRun) {
151
147
  console.log(`${prefix} Would update .gitignore`);
152
148
  return;
153
149
  }
154
- const block = '\n' + missing.join('\n') + '\n';
150
+ const block = '\n# AI-SDLC runtime artifacts\n' + missing.join('\n') + '\n';
155
151
  appendFileSync(gitignorePath, block, 'utf-8');
156
152
  console.log(`${prefix} updated .gitignore`);
157
153
  }
@@ -6,7 +6,7 @@ import { Orchestrator } from '../../orchestrator.js';
6
6
  import { formatOutput } from '../formatters/index.js';
7
7
  export const runCommand = new Command('run')
8
8
  .description('Run the AI-SDLC pipeline for a specific issue')
9
- .requiredOption('-i, --issue <number>', 'Issue number to process', parseInt)
9
+ .requiredOption('-i, --issue <id>', 'Issue ID to process')
10
10
  .option('--state <path>', 'SQLite state database path')
11
11
  .action(async (opts, cmd) => {
12
12
  const globalOpts = cmd.parent?.opts() ?? {};
@@ -19,7 +19,7 @@ export const runCommand = new Command('run')
19
19
  const result = await orchestrator.run(opts.issue);
20
20
  console.log(formatOutput(format, {
21
21
  type: 'run',
22
- issueNumber: opts.issue,
22
+ issueId: opts.issue,
23
23
  prUrl: result.prUrl,
24
24
  filesChanged: result.filesChanged.length,
25
25
  promotionEligible: result.promotionEligible,
package/dist/config.d.ts CHANGED
@@ -11,7 +11,10 @@ export interface AiSdlcConfig {
11
11
  agentRole?: AgentRole;
12
12
  qualityGate?: QualityGate;
13
13
  autonomyPolicy?: AutonomyPolicy;
14
+ /** @deprecated Use `adapterBindings` instead. Returns the first binding if any exist. */
14
15
  adapterBinding?: AdapterBinding;
16
+ /** All AdapterBinding resources found in the config directory. */
17
+ adapterBindings?: AdapterBinding[];
15
18
  adapterRegistry?: AdapterRegistry;
16
19
  }
17
20
  /**
package/dist/config.js CHANGED
@@ -9,12 +9,12 @@ import { readFileSync, readdirSync, existsSync } from 'node:fs';
9
9
  import { resolve, join } from 'node:path';
10
10
  import { parse as parseYaml } from 'yaml';
11
11
  import { validateResource, createAdapterRegistry, scanLocalAdapters, } from '@ai-sdlc/reference';
12
+ /** Resource kinds that allow only a single instance. */
12
13
  const KIND_KEY = {
13
14
  Pipeline: 'pipeline',
14
15
  AgentRole: 'agentRole',
15
16
  QualityGate: 'qualityGate',
16
17
  AutonomyPolicy: 'autonomyPolicy',
17
- AdapterBinding: 'adapterBinding',
18
18
  };
19
19
  /**
20
20
  * Load all YAML files from the given directory, validate each against
@@ -39,11 +39,20 @@ export function loadConfig(configDir) {
39
39
  throw new Error(`Validation failed for ${file}:\n${msgs}`);
40
40
  }
41
41
  const resource = result.data;
42
- const key = KIND_KEY[resource.kind];
43
- if (key) {
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
- config[key] = resource;
42
+ if (resource.kind === 'AdapterBinding') {
43
+ (config.adapterBindings ??= []).push(resource);
46
44
  }
45
+ else {
46
+ const key = KIND_KEY[resource.kind];
47
+ if (key) {
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ config[key] = resource;
50
+ }
51
+ }
52
+ }
53
+ // Backward compat: set adapterBinding to the first binding
54
+ if (config.adapterBindings?.length) {
55
+ config.adapterBinding = config.adapterBindings[0];
47
56
  }
48
57
  return config;
49
58
  }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Utility functions for cycle detection and handling.
3
+ */
4
+ import type { IssueTracker } from '@ai-sdlc/reference';
5
+ import { PipelineCycleDetector, type PipelineStage } from './pipeline-cycle-detector.js';
6
+ export interface CycleHandlerOptions {
7
+ /** Issue or PR identifier. */
8
+ issueOrPrId: string;
9
+ /** Stage being invoked. */
10
+ stage: PipelineStage;
11
+ /** Issue tracker for fetching/posting comments. */
12
+ tracker: IssueTracker;
13
+ /** Cycle detector instance. */
14
+ detector: PipelineCycleDetector;
15
+ /** Optional Slack notification callback. */
16
+ notifySlack?: (message: string) => Promise<void>;
17
+ /** Custom cycle notification template (optional). */
18
+ cycleTemplate?: {
19
+ title: string;
20
+ body: string;
21
+ };
22
+ }
23
+ export interface CycleCheckResult {
24
+ /** Whether a cycle was detected. */
25
+ cycleDetected: boolean;
26
+ /** Marker to append to comments for tracking. */
27
+ marker: string;
28
+ /** Formatted message describing the cycle (if detected). */
29
+ cycleMessage?: string;
30
+ }
31
+ /**
32
+ * Check for pipeline cycles and post notification if detected.
33
+ *
34
+ * The marker is generated upfront so the caller can append it to comments
35
+ * BEFORE executing the stage (records intent, prevents race conditions).
36
+ * Cycle detection accounts for the pending invocation (+1 to current count).
37
+ */
38
+ export declare function checkAndHandleCycle(options: CycleHandlerOptions): Promise<CycleCheckResult>;
39
+ /**
40
+ * Create a cycle detector from pipeline config.
41
+ * Reads maxRetries from stage configurations.
42
+ */
43
+ export declare function createCycleDetectorFromConfig(config: {
44
+ stages?: Array<{
45
+ name: string;
46
+ onFailure?: {
47
+ maxRetries?: number;
48
+ };
49
+ }>;
50
+ }): PipelineCycleDetector;
51
+ //# sourceMappingURL=cycle-utils.d.ts.map