@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.
- package/dist/action-enforcement.d.ts +26 -0
- package/dist/action-enforcement.js +70 -0
- package/dist/adapters.d.ts +18 -3
- package/dist/adapters.js +92 -2
- package/dist/admission-score.d.ts +58 -0
- package/dist/admission-score.js +164 -0
- package/dist/cli/commands/init.js +4 -8
- package/dist/cli/commands/run.js +2 -2
- package/dist/config.d.ts +3 -0
- package/dist/config.js +14 -5
- package/dist/cycle-utils.d.ts +51 -0
- package/dist/cycle-utils.js +77 -0
- package/dist/defaults.d.ts +5 -0
- package/dist/defaults.js +5 -0
- package/dist/execute.d.ts +5 -2
- package/dist/execute.js +212 -62
- package/dist/fix-ci.js +45 -13
- package/dist/fix-review.d.ts +66 -0
- package/dist/fix-review.js +441 -0
- package/dist/index.d.ts +14 -4
- package/dist/index.js +18 -3
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +31 -9
- package/dist/pipeline-cycle-detector.d.ts +70 -0
- package/dist/pipeline-cycle-detector.js +111 -0
- package/dist/plugin.d.ts +9 -3
- package/dist/priority.d.ts +28 -0
- package/dist/priority.js +230 -0
- package/dist/review.d.ts +31 -0
- package/dist/review.js +74 -0
- package/dist/runners/claude-code.js +367 -35
- package/dist/runners/codex.js +15 -4
- package/dist/runners/copilot.js +15 -4
- package/dist/runners/cursor.js +15 -4
- package/dist/runners/generic-llm.js +1 -1
- package/dist/runners/index.d.ts +3 -1
- package/dist/runners/index.js +2 -0
- package/dist/runners/review-agent.d.ts +47 -0
- package/dist/runners/review-agent.js +220 -0
- package/dist/runners/security-triage.d.ts +43 -0
- package/dist/runners/security-triage.js +158 -0
- package/dist/runners/types.d.ts +24 -1
- package/dist/security.d.ts +8 -3
- package/dist/security.js +13 -2
- package/dist/shared.d.ts +17 -0
- package/dist/shared.js +27 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/schema.d.ts +4 -1
- package/dist/state/schema.js +89 -1
- package/dist/state/store.d.ts +31 -1
- package/dist/state/store.js +208 -13
- package/dist/state/types.d.ts +52 -0
- package/dist/triage.d.ts +36 -0
- package/dist/triage.js +133 -0
- package/dist/types.d.ts +1 -1
- package/dist/watch.d.ts +6 -2
- package/dist/watch.js +34 -6
- package/dist/workflow-patterns/artifact-writer.d.ts +16 -0
- package/dist/workflow-patterns/artifact-writer.js +34 -0
- package/dist/workflow-patterns/classifiers.d.ts +10 -0
- package/dist/workflow-patterns/classifiers.js +72 -0
- package/dist/workflow-patterns/detector.d.ts +27 -0
- package/dist/workflow-patterns/detector.js +186 -0
- package/dist/workflow-patterns/index.d.ts +8 -0
- package/dist/workflow-patterns/index.js +7 -0
- package/dist/workflow-patterns/proposal-generator.d.ts +15 -0
- package/dist/workflow-patterns/proposal-generator.js +183 -0
- package/dist/workflow-patterns/telemetry-ingest.d.ts +27 -0
- package/dist/workflow-patterns/telemetry-ingest.js +103 -0
- package/dist/workflow-patterns/types.d.ts +61 -0
- package/dist/workflow-patterns/types.js +11 -0
- 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
|
package/dist/adapters.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
59
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -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 <
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|