@ai-sdlc/orchestrator 0.2.0 → 0.4.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/adapters.d.ts +2 -2
- package/dist/adapters.js +5 -0
- package/dist/cli/commands/init.js +111 -20
- package/dist/cli/commands/mcp-setup.d.ts +16 -0
- package/dist/cli/commands/mcp-setup.js +131 -0
- package/dist/cli/commands/workspace-detect.d.ts +22 -0
- package/dist/cli/commands/workspace-detect.js +65 -0
- package/package.json +2 -2
package/dist/adapters.d.ts
CHANGED
|
@@ -55,6 +55,6 @@ export declare function scanPipelineAdapters(options: ScanOptions): Promise<Scan
|
|
|
55
55
|
* Requires `GITHUB_REPOSITORY_OWNER` and `GITHUB_REPOSITORY` env vars.
|
|
56
56
|
*/
|
|
57
57
|
export declare function createPipelineCIAdapter(): CIPipeline;
|
|
58
|
-
export { createGitHubCIPipeline, createDockerSandbox, createLinearIssueTracker, resolveSecret, createGitLabSourceControl, createGitLabCIPipeline, createJiraIssueTracker, 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, WebhookServer, WebhookServerConfig, WebhookProviderConfig, GitHubWebhookConfig, GitHubWebhookBridges, GitLabWebhookConfig, JiraWebhookConfig, LinearWebhookConfig, } from '@ai-sdlc/reference';
|
|
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';
|
|
60
60
|
//# 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
|
+
// Backlog.md adapter
|
|
18
|
+
createBacklogMdIssueTracker,
|
|
17
19
|
// Webhook bridge (used in function body)
|
|
18
20
|
createWebhookBridge,
|
|
19
21
|
// Git resolver (used in function bodies)
|
|
@@ -45,6 +47,7 @@ export function createPipelineAdapterRegistry() {
|
|
|
45
47
|
registry.register(stubMeta('bitbucket-stub', 'Bitbucket', 'SourceControl', 'bitbucket'), () => createStubBitbucket());
|
|
46
48
|
registry.register(stubMeta('sonarqube-stub', 'SonarQube', 'CodeAnalysis', 'sonarqube'), () => createStubSonarQube());
|
|
47
49
|
registry.register(stubMeta('semgrep-stub', 'Semgrep', 'CodeAnalysis', 'semgrep'), () => createStubSemgrep());
|
|
50
|
+
registry.register(stubMeta('backlog-md', 'Backlog.md', 'IssueTracker', 'backlog-md'), () => createBacklogMdIssueTracker({ backlogDir: process.env.AI_SDLC_BACKLOG_DIR ?? './backlog' }));
|
|
48
51
|
// Infrastructure adapter stubs
|
|
49
52
|
registry.register(stubMeta('memory-audit-sink', 'In-Memory Audit Sink', 'AuditSink@v1', 'memory'), () => createInMemoryAuditSink());
|
|
50
53
|
// file-audit-sink is registered metadata-only; the actual file path is
|
|
@@ -143,6 +146,8 @@ export {
|
|
|
143
146
|
createGitHubCIPipeline, createDockerSandbox, createLinearIssueTracker, resolveSecret,
|
|
144
147
|
// Production adapters
|
|
145
148
|
createGitLabSourceControl, createGitLabCIPipeline, createJiraIssueTracker,
|
|
149
|
+
// Backlog.md adapter
|
|
150
|
+
createBacklogMdIssueTracker,
|
|
146
151
|
// Webhook server
|
|
147
152
|
createWebhookServer,
|
|
148
153
|
// Webhook providers
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* ai-sdlc init — initialize a project with AI-SDLC config files.
|
|
3
3
|
*/
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
6
|
-
import { join } from 'node:path';
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'node:fs';
|
|
6
|
+
import { join, basename } from 'node:path';
|
|
7
|
+
import { detectAgents, installMcpServer } from './mcp-setup.js';
|
|
8
|
+
import { detectWorkspace, generateWorkspaceYaml } from './workspace-detect.js';
|
|
7
9
|
const PIPELINE_YAML = `apiVersion: ai-sdlc.io/v1alpha1
|
|
8
10
|
kind: Pipeline
|
|
9
11
|
metadata:
|
|
@@ -132,23 +134,42 @@ spec:
|
|
|
132
134
|
action: demote-one-level
|
|
133
135
|
cooldown: 2w
|
|
134
136
|
`;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.
|
|
138
|
-
|
|
139
|
-
.
|
|
140
|
-
|
|
137
|
+
const GITIGNORE_ENTRIES = [
|
|
138
|
+
'# AI-SDLC runtime artifacts',
|
|
139
|
+
'.ai-sdlc/state.db',
|
|
140
|
+
'.ai-sdlc/state/',
|
|
141
|
+
'.ai-sdlc/audit.jsonl',
|
|
142
|
+
];
|
|
143
|
+
/** Ensure .gitignore includes AI-SDLC runtime artifact entries. */
|
|
144
|
+
function ensureGitignore(projectDir, dryRun, prefix = '') {
|
|
145
|
+
const gitignorePath = join(projectDir, '.gitignore');
|
|
146
|
+
const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
|
|
147
|
+
const missing = GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
|
|
148
|
+
if (missing.length === 0)
|
|
149
|
+
return;
|
|
150
|
+
if (dryRun) {
|
|
151
|
+
console.log(`${prefix} Would update .gitignore`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const block = '\n' + missing.join('\n') + '\n';
|
|
155
|
+
appendFileSync(gitignorePath, block, 'utf-8');
|
|
156
|
+
console.log(`${prefix} updated .gitignore`);
|
|
157
|
+
}
|
|
158
|
+
/** Initialize a single project directory with AI-SDLC config files. */
|
|
159
|
+
function initProject(projectDir, configDirName, dryRun, prefix = '') {
|
|
160
|
+
const configDir = join(projectDir, configDirName);
|
|
141
161
|
const files = [
|
|
142
162
|
{ name: 'pipeline.yaml', content: PIPELINE_YAML },
|
|
143
163
|
{ name: 'agent-role.yaml', content: AGENT_ROLE_YAML },
|
|
144
164
|
{ name: 'quality-gate.yaml', content: QUALITY_GATE_YAML },
|
|
145
165
|
{ name: 'autonomy-policy.yaml', content: AUTONOMY_POLICY_YAML },
|
|
146
166
|
];
|
|
147
|
-
if (
|
|
148
|
-
console.log(
|
|
167
|
+
if (dryRun) {
|
|
168
|
+
console.log(`${prefix}Would create ${configDir}/`);
|
|
149
169
|
for (const f of files) {
|
|
150
|
-
console.log(
|
|
170
|
+
console.log(`${prefix} ${f.name}`);
|
|
151
171
|
}
|
|
172
|
+
ensureGitignore(projectDir, true, prefix);
|
|
152
173
|
return;
|
|
153
174
|
}
|
|
154
175
|
if (!existsSync(configDir)) {
|
|
@@ -157,20 +178,90 @@ export const initCommand = new Command('init')
|
|
|
157
178
|
for (const f of files) {
|
|
158
179
|
const path = join(configDir, f.name);
|
|
159
180
|
if (existsSync(path)) {
|
|
160
|
-
console.log(
|
|
181
|
+
console.log(`${prefix} skip ${f.name} (already exists)`);
|
|
161
182
|
}
|
|
162
183
|
else {
|
|
163
184
|
writeFileSync(path, f.content, 'utf-8');
|
|
164
|
-
console.log(
|
|
185
|
+
console.log(`${prefix} created ${f.name}`);
|
|
165
186
|
}
|
|
166
187
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
188
|
+
ensureGitignore(projectDir, false, prefix);
|
|
189
|
+
}
|
|
190
|
+
/** Initialize the workspace root with workspace.yaml. */
|
|
191
|
+
function initWorkspaceRoot(workspacePath, repos, configDirName, dryRun) {
|
|
192
|
+
const configDir = join(workspacePath, configDirName);
|
|
193
|
+
const workspaceFile = join(configDir, 'workspace.yaml');
|
|
194
|
+
const workspaceName = basename(workspacePath);
|
|
195
|
+
const yaml = generateWorkspaceYaml(workspaceName, repos);
|
|
196
|
+
if (dryRun) {
|
|
197
|
+
console.log(`Would create ${configDir}/`);
|
|
198
|
+
console.log(` workspace.yaml`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (!existsSync(configDir)) {
|
|
202
|
+
mkdirSync(configDir, { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
if (existsSync(workspaceFile)) {
|
|
205
|
+
console.log(` skip workspace.yaml (already exists)`);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
writeFileSync(workspaceFile, yaml, 'utf-8');
|
|
209
|
+
console.log(` created workspace.yaml`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export const initCommand = new Command('init')
|
|
213
|
+
.description('Initialize AI-SDLC configuration in the current project')
|
|
214
|
+
.option('--dry-run', 'Show what would be created without writing files')
|
|
215
|
+
.option('--skip-mcp', 'Skip MCP server auto-configuration')
|
|
216
|
+
.option('-d, --dir <path>', 'Config directory name', '.ai-sdlc')
|
|
217
|
+
.action(async (opts) => {
|
|
218
|
+
const projectDir = process.cwd();
|
|
219
|
+
const configDirName = opts.dir ?? '.ai-sdlc';
|
|
220
|
+
const dryRun = !!opts.dryRun;
|
|
221
|
+
// Detect workspace (multi-repo parent directory)
|
|
222
|
+
const workspace = detectWorkspace(projectDir);
|
|
223
|
+
if (workspace.isWorkspace) {
|
|
224
|
+
console.log(`Workspace detected with ${workspace.repos.length} repositories:`);
|
|
225
|
+
for (const repo of workspace.repos) {
|
|
226
|
+
console.log(` ${repo.name} (${repo.path})`);
|
|
227
|
+
}
|
|
228
|
+
console.log('');
|
|
229
|
+
// Initialize workspace root with workspace.yaml
|
|
230
|
+
initWorkspaceRoot(projectDir, workspace.repos, configDirName, dryRun);
|
|
231
|
+
// Cascade into each child repo
|
|
232
|
+
for (const repo of workspace.repos) {
|
|
233
|
+
console.log(`\n${repo.name}/`);
|
|
234
|
+
initProject(repo.absPath, configDirName, dryRun, ' ');
|
|
235
|
+
}
|
|
236
|
+
// MCP setup at workspace root (serves all repos)
|
|
237
|
+
if (!opts.skipMcp) {
|
|
238
|
+
const agents = detectAgents(projectDir, { isWorkspace: true });
|
|
239
|
+
console.log(`\nMCP server setup (workspace root):`);
|
|
240
|
+
console.log(` detected ${agents.map((a) => a.name).join(', ')}`);
|
|
241
|
+
for (const agent of agents) {
|
|
242
|
+
const status = installMcpServer(projectDir, agent, dryRun);
|
|
243
|
+
const pad = status === 'merged' ? ' ' : '';
|
|
244
|
+
console.log(` ${status}${pad} ${agent.configPath} (${agent.name})`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
console.log(`\nAI-SDLC workspace initialized in ${projectDir}/`);
|
|
248
|
+
console.log(`Run 'ai-sdlc health' to verify your configuration.`);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
// Single-repo mode (original behavior)
|
|
252
|
+
initProject(projectDir, configDirName, dryRun);
|
|
253
|
+
if (!opts.skipMcp) {
|
|
254
|
+
const agents = detectAgents(projectDir);
|
|
255
|
+
console.log(`\nMCP server setup:`);
|
|
256
|
+
console.log(` detected ${agents.map((a) => a.name).join(', ')}`);
|
|
257
|
+
for (const agent of agents) {
|
|
258
|
+
const status = installMcpServer(projectDir, agent, dryRun);
|
|
259
|
+
const pad = status === 'merged' ? ' ' : '';
|
|
260
|
+
console.log(` ${status}${pad} ${agent.configPath} (${agent.name})`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
console.log(`\nAI-SDLC config initialized in ${join(projectDir, configDirName)}/`);
|
|
264
|
+
console.log(`Run 'ai-sdlc health' to verify your configuration.`);
|
|
172
265
|
}
|
|
173
|
-
console.log(`\nAI-SDLC config initialized in ${configDir}/`);
|
|
174
|
-
console.log(`Run 'ai-sdlc health' to verify your configuration.`);
|
|
175
266
|
});
|
|
176
267
|
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server setup — detect coding agents and install MCP config.
|
|
3
|
+
*/
|
|
4
|
+
export interface DetectedAgent {
|
|
5
|
+
name: string;
|
|
6
|
+
configPath: string;
|
|
7
|
+
configKey: string;
|
|
8
|
+
serverEntry: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface DetectAgentsOptions {
|
|
11
|
+
/** When true, adds AI_SDLC_WORKSPACE env to server entries. */
|
|
12
|
+
isWorkspace?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function detectAgents(projectDir: string, options?: DetectAgentsOptions): DetectedAgent[];
|
|
15
|
+
export declare function installMcpServer(projectDir: string, agent: DetectedAgent, dryRun: boolean): 'created' | 'merged' | 'skipped';
|
|
16
|
+
//# sourceMappingURL=mcp-setup.d.ts.map
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server setup — detect coding agents and install MCP config.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
function standardEntry(env) {
|
|
8
|
+
const entry = {
|
|
9
|
+
command: 'npx',
|
|
10
|
+
args: ['-y', '@ai-sdlc/mcp-advisor'],
|
|
11
|
+
};
|
|
12
|
+
if (env)
|
|
13
|
+
entry.env = env;
|
|
14
|
+
return entry;
|
|
15
|
+
}
|
|
16
|
+
function vscodeEntry(env) {
|
|
17
|
+
const entry = {
|
|
18
|
+
type: 'stdio',
|
|
19
|
+
command: 'npx',
|
|
20
|
+
args: ['-y', '@ai-sdlc/mcp-advisor'],
|
|
21
|
+
};
|
|
22
|
+
if (env)
|
|
23
|
+
entry.env = env;
|
|
24
|
+
return entry;
|
|
25
|
+
}
|
|
26
|
+
const AGENT_SPECS = [
|
|
27
|
+
{
|
|
28
|
+
name: 'Claude Code',
|
|
29
|
+
configPath: '.mcp.json',
|
|
30
|
+
configKey: 'mcpServers',
|
|
31
|
+
entryFn: standardEntry,
|
|
32
|
+
binary: 'claude',
|
|
33
|
+
alwaysDetect: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Cursor',
|
|
37
|
+
configPath: '.cursor/mcp.json',
|
|
38
|
+
configKey: 'mcpServers',
|
|
39
|
+
entryFn: standardEntry,
|
|
40
|
+
configDir: '.cursor',
|
|
41
|
+
binary: 'cursor',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'VS Code',
|
|
45
|
+
configPath: '.vscode/mcp.json',
|
|
46
|
+
configKey: 'servers',
|
|
47
|
+
entryFn: vscodeEntry,
|
|
48
|
+
configDir: '.vscode',
|
|
49
|
+
binary: 'code',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'Windsurf',
|
|
53
|
+
configPath: '.windsurf/mcp.json',
|
|
54
|
+
configKey: 'mcpServers',
|
|
55
|
+
entryFn: standardEntry,
|
|
56
|
+
configDir: '.windsurf',
|
|
57
|
+
binary: 'windsurf',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
function hasBinary(name) {
|
|
61
|
+
try {
|
|
62
|
+
execSync(`which ${name}`, { stdio: 'ignore' });
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function detectAgents(projectDir, options) {
|
|
70
|
+
const detected = [];
|
|
71
|
+
const env = options?.isWorkspace ? { AI_SDLC_WORKSPACE: '.' } : undefined;
|
|
72
|
+
for (const spec of AGENT_SPECS) {
|
|
73
|
+
if (spec.alwaysDetect) {
|
|
74
|
+
detected.push({
|
|
75
|
+
name: spec.name,
|
|
76
|
+
configPath: spec.configPath,
|
|
77
|
+
configKey: spec.configKey,
|
|
78
|
+
serverEntry: spec.entryFn(env),
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const hasDir = spec.configDir ? existsSync(join(projectDir, spec.configDir)) : false;
|
|
83
|
+
const hasBin = spec.binary ? hasBinary(spec.binary) : false;
|
|
84
|
+
if (hasDir || hasBin) {
|
|
85
|
+
detected.push({
|
|
86
|
+
name: spec.name,
|
|
87
|
+
configPath: spec.configPath,
|
|
88
|
+
configKey: spec.configKey,
|
|
89
|
+
serverEntry: spec.entryFn(env),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return detected;
|
|
94
|
+
}
|
|
95
|
+
export function installMcpServer(projectDir, agent, dryRun) {
|
|
96
|
+
const fullPath = join(projectDir, agent.configPath);
|
|
97
|
+
if (existsSync(fullPath)) {
|
|
98
|
+
let existing;
|
|
99
|
+
try {
|
|
100
|
+
existing = JSON.parse(readFileSync(fullPath, 'utf-8'));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// If the file is malformed JSON, treat as new
|
|
104
|
+
existing = {};
|
|
105
|
+
}
|
|
106
|
+
const section = (existing[agent.configKey] ?? {});
|
|
107
|
+
if (section['ai-sdlc']) {
|
|
108
|
+
return 'skipped';
|
|
109
|
+
}
|
|
110
|
+
if (!dryRun) {
|
|
111
|
+
section['ai-sdlc'] = agent.serverEntry;
|
|
112
|
+
existing[agent.configKey] = section;
|
|
113
|
+
writeFileSync(fullPath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
return 'merged';
|
|
116
|
+
}
|
|
117
|
+
if (!dryRun) {
|
|
118
|
+
const dir = dirname(fullPath);
|
|
119
|
+
if (!existsSync(dir)) {
|
|
120
|
+
mkdirSync(dir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
const config = {
|
|
123
|
+
[agent.configKey]: {
|
|
124
|
+
'ai-sdlc': agent.serverEntry,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
writeFileSync(fullPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
128
|
+
}
|
|
129
|
+
return 'created';
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=mcp-setup.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace detection — identify multi-repo workspaces and child repositories.
|
|
3
|
+
*/
|
|
4
|
+
export interface WorkspaceRepo {
|
|
5
|
+
name: string;
|
|
6
|
+
path: string;
|
|
7
|
+
absPath: string;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkspaceInfo {
|
|
10
|
+
isWorkspace: boolean;
|
|
11
|
+
workspacePath: string;
|
|
12
|
+
repos: WorkspaceRepo[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Detect if a directory is a multi-repo workspace root.
|
|
16
|
+
* A workspace root is a directory that:
|
|
17
|
+
* 1. Is NOT itself a git repository
|
|
18
|
+
* 2. Contains at least 2 child directories that ARE git repositories
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectWorkspace(dir: string): WorkspaceInfo;
|
|
21
|
+
export declare function generateWorkspaceYaml(workspaceName: string, repos: WorkspaceRepo[]): string;
|
|
22
|
+
//# sourceMappingURL=workspace-detect.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace detection — identify multi-repo workspaces and child repositories.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
/**
|
|
7
|
+
* Detect if a directory is a multi-repo workspace root.
|
|
8
|
+
* A workspace root is a directory that:
|
|
9
|
+
* 1. Is NOT itself a git repository
|
|
10
|
+
* 2. Contains at least 2 child directories that ARE git repositories
|
|
11
|
+
*/
|
|
12
|
+
export function detectWorkspace(dir) {
|
|
13
|
+
const absDir = resolve(dir);
|
|
14
|
+
const isGitRepo = existsSync(join(absDir, '.git'));
|
|
15
|
+
if (isGitRepo) {
|
|
16
|
+
return { isWorkspace: false, workspacePath: absDir, repos: [] };
|
|
17
|
+
}
|
|
18
|
+
const repos = [];
|
|
19
|
+
let entries;
|
|
20
|
+
try {
|
|
21
|
+
entries = readdirSync(absDir);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return { isWorkspace: false, workspacePath: absDir, repos: [] };
|
|
25
|
+
}
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
if (entry.startsWith('.'))
|
|
28
|
+
continue;
|
|
29
|
+
const childPath = join(absDir, entry);
|
|
30
|
+
try {
|
|
31
|
+
if (!statSync(childPath).isDirectory())
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (existsSync(join(childPath, '.git'))) {
|
|
38
|
+
repos.push({
|
|
39
|
+
name: entry,
|
|
40
|
+
path: `./${entry}`,
|
|
41
|
+
absPath: childPath,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
isWorkspace: repos.length >= 2,
|
|
47
|
+
workspacePath: absDir,
|
|
48
|
+
repos,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const WORKSPACE_YAML_TEMPLATE = (name, repos) => {
|
|
52
|
+
const repoEntries = repos.map((r) => ` - name: ${r.name}\n path: ${r.path}`).join('\n');
|
|
53
|
+
return `apiVersion: ai-sdlc.io/v1alpha1
|
|
54
|
+
kind: Workspace
|
|
55
|
+
metadata:
|
|
56
|
+
name: ${name}
|
|
57
|
+
spec:
|
|
58
|
+
repos:
|
|
59
|
+
${repoEntries}
|
|
60
|
+
`;
|
|
61
|
+
};
|
|
62
|
+
export function generateWorkspaceYaml(workspaceName, repos) {
|
|
63
|
+
return WORKSPACE_YAML_TEMPLATE(workspaceName, repos);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=workspace-detect.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdlc/orchestrator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "AI-SDLC Orchestrator — long-running runtime that drives issues through the complete SDLC with AI agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"better-sqlite3": "^11.0.0",
|
|
46
46
|
"commander": "^12.0.0",
|
|
47
47
|
"yaml": "^2.7.0",
|
|
48
|
-
"@ai-sdlc/reference": "0.
|
|
48
|
+
"@ai-sdlc/reference": "0.4.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/better-sqlite3": "^7.6.0",
|