@ai-sdlc/orchestrator 0.5.0 → 0.9.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 (227) hide show
  1. package/dist/action-enforcement.d.ts +26 -0
  2. package/dist/action-enforcement.js +70 -0
  3. package/dist/admission-composite.d.ts +67 -0
  4. package/dist/admission-composite.js +158 -0
  5. package/dist/admission-enrichment.d.ts +142 -0
  6. package/dist/admission-enrichment.js +331 -0
  7. package/dist/admission-hc.d.ts +62 -0
  8. package/dist/admission-hc.js +83 -0
  9. package/dist/admission-score.d.ts +148 -0
  10. package/dist/admission-score.js +237 -0
  11. package/dist/analysis/analyzer.js +3 -2
  12. package/dist/analysis/convention-detector.d.ts +85 -2
  13. package/dist/analysis/convention-detector.js +375 -70
  14. package/dist/analysis/diff-analyzer.d.ts +33 -0
  15. package/dist/analysis/diff-analyzer.js +122 -0
  16. package/dist/analysis/hotspot-analyzer.js +3 -1
  17. package/dist/analysis/index.d.ts +2 -1
  18. package/dist/analysis/index.js +2 -1
  19. package/dist/artifacts/index.d.ts +65 -0
  20. package/dist/artifacts/index.js +142 -0
  21. package/dist/backlog-adapter.d.ts +121 -0
  22. package/dist/backlog-adapter.js +438 -0
  23. package/dist/calibration.d.ts +43 -0
  24. package/dist/calibration.js +76 -0
  25. package/dist/cli/commands/agents.d.ts +24 -0
  26. package/dist/cli/commands/agents.js +66 -1
  27. package/dist/cli/commands/git-remote.d.ts +49 -0
  28. package/dist/cli/commands/git-remote.js +91 -0
  29. package/dist/cli/commands/health.d.ts +4 -0
  30. package/dist/cli/commands/health.js +13 -1
  31. package/dist/cli/commands/init.d.ts +26 -0
  32. package/dist/cli/commands/init.js +164 -22
  33. package/dist/cli/commands/mcp-setup.d.ts +31 -0
  34. package/dist/cli/commands/mcp-setup.js +78 -8
  35. package/dist/cli/formatters/table.js +15 -2
  36. package/dist/cli/index.d.ts +14 -1
  37. package/dist/cli/index.js +81 -20
  38. package/dist/cli/versions.d.ts +57 -0
  39. package/dist/cli/versions.js +128 -0
  40. package/dist/code-area-classifier.d.ts +21 -0
  41. package/dist/code-area-classifier.js +48 -0
  42. package/dist/config.d.ts +33 -1
  43. package/dist/config.js +78 -8
  44. package/dist/cycle-utils.d.ts +51 -0
  45. package/dist/cycle-utils.js +77 -0
  46. package/dist/database/adapters/external.d.ts +24 -0
  47. package/dist/database/adapters/external.js +80 -0
  48. package/dist/database/adapters/neon.d.ts +41 -0
  49. package/dist/database/adapters/neon.js +98 -0
  50. package/dist/database/adapters/pg-snapshot-restore.d.ts +28 -0
  51. package/dist/database/adapters/pg-snapshot-restore.js +68 -0
  52. package/dist/database/adapters/sqlite-copy.d.ts +32 -0
  53. package/dist/database/adapters/sqlite-copy.js +145 -0
  54. package/dist/database/connection-injection.d.ts +35 -0
  55. package/dist/database/connection-injection.js +93 -0
  56. package/dist/database/index.d.ts +12 -0
  57. package/dist/database/index.js +23 -0
  58. package/dist/database/registry.d.ts +13 -0
  59. package/dist/database/registry.js +27 -0
  60. package/dist/database/topology.d.ts +52 -0
  61. package/dist/database/topology.js +44 -0
  62. package/dist/database/types.d.ts +89 -0
  63. package/dist/database/types.js +26 -0
  64. package/dist/defaults.d.ts +5 -0
  65. package/dist/defaults.js +5 -0
  66. package/dist/design-authority.d.ts +40 -0
  67. package/dist/design-authority.js +71 -0
  68. package/dist/design-lookahead.d.ts +64 -0
  69. package/dist/design-lookahead.js +86 -0
  70. package/dist/design-quality-trend.d.ts +87 -0
  71. package/dist/design-quality-trend.js +190 -0
  72. package/dist/design-system-context.d.ts +46 -0
  73. package/dist/design-system-context.js +80 -0
  74. package/dist/design-system-correction-loop.d.ts +64 -0
  75. package/dist/design-system-correction-loop.js +128 -0
  76. package/dist/design-system-metrics.d.ts +61 -0
  77. package/dist/design-system-metrics.js +104 -0
  78. package/dist/design-system-stewardship.d.ts +22 -0
  79. package/dist/design-system-stewardship.js +85 -0
  80. package/dist/design-system-validation.d.ts +37 -0
  81. package/dist/design-system-validation.js +88 -0
  82. package/dist/dispatch/index.d.ts +4 -0
  83. package/dist/dispatch/index.js +4 -0
  84. package/dist/dispatch/merge-gate.d.ts +46 -0
  85. package/dist/dispatch/merge-gate.js +90 -0
  86. package/dist/dispatch/requeue.d.ts +57 -0
  87. package/dist/dispatch/requeue.js +131 -0
  88. package/dist/dispatch/worker-pool.d.ts +62 -0
  89. package/dist/dispatch/worker-pool.js +60 -0
  90. package/dist/execute.d.ts +40 -0
  91. package/dist/execute.js +295 -30
  92. package/dist/fix-ci.js +40 -3
  93. package/dist/fix-review.d.ts +66 -0
  94. package/dist/fix-review.js +448 -0
  95. package/dist/harness/adapters/claude-code.d.ts +29 -0
  96. package/dist/harness/adapters/claude-code.js +191 -0
  97. package/dist/harness/adapters/codex.d.ts +25 -0
  98. package/dist/harness/adapters/codex.js +61 -0
  99. package/dist/harness/independence.d.ts +51 -0
  100. package/dist/harness/independence.js +67 -0
  101. package/dist/harness/index.d.ts +14 -0
  102. package/dist/harness/index.js +20 -0
  103. package/dist/harness/registry.d.ts +17 -0
  104. package/dist/harness/registry.js +31 -0
  105. package/dist/harness/types.d.ts +123 -0
  106. package/dist/harness/types.js +8 -0
  107. package/dist/harness/version-probe.d.ts +14 -0
  108. package/dist/harness/version-probe.js +123 -0
  109. package/dist/index.d.ts +39 -5
  110. package/dist/index.js +48 -3
  111. package/dist/models/classifier.d.ts +76 -0
  112. package/dist/models/classifier.js +221 -0
  113. package/dist/models/index.d.ts +3 -0
  114. package/dist/models/index.js +3 -0
  115. package/dist/models/registry.d.ts +97 -0
  116. package/dist/models/registry.js +173 -0
  117. package/dist/pillar-breakdown.d.ts +63 -0
  118. package/dist/pillar-breakdown.js +153 -0
  119. package/dist/pipeline-cycle-detector.d.ts +70 -0
  120. package/dist/pipeline-cycle-detector.js +111 -0
  121. package/dist/priority.d.ts +7 -76
  122. package/dist/priority.js +18 -5
  123. package/dist/review-meta.d.ts +65 -0
  124. package/dist/review-meta.js +149 -0
  125. package/dist/review.d.ts +35 -0
  126. package/dist/review.js +84 -0
  127. package/dist/runners/claude-code-sdk.d.ts +31 -0
  128. package/dist/runners/claude-code-sdk.js +238 -0
  129. package/dist/runners/claude-code.d.ts +3 -0
  130. package/dist/runners/claude-code.js +319 -73
  131. package/dist/runners/codex.js +4 -1
  132. package/dist/runners/copilot.js +4 -1
  133. package/dist/runners/cursor.js +4 -1
  134. package/dist/runners/git-utils.d.ts +81 -0
  135. package/dist/runners/git-utils.js +201 -0
  136. package/dist/runners/index.d.ts +5 -1
  137. package/dist/runners/index.js +4 -0
  138. package/dist/runners/review-agent.d.ts +67 -0
  139. package/dist/runners/review-agent.js +350 -0
  140. package/dist/runners/runner-registry.js +10 -0
  141. package/dist/runners/sdk-review-runner.d.ts +65 -0
  142. package/dist/runners/sdk-review-runner.js +185 -0
  143. package/dist/runners/security-triage.d.ts +20 -4
  144. package/dist/runners/security-triage.js +42 -14
  145. package/dist/runners/types.d.ts +25 -0
  146. package/dist/runtime/attestations.d.ts +274 -0
  147. package/dist/runtime/attestations.js +460 -0
  148. package/dist/runtime/git-env.d.ts +53 -0
  149. package/dist/runtime/git-env.js +60 -0
  150. package/dist/runtime/index.d.ts +7 -0
  151. package/dist/runtime/index.js +7 -0
  152. package/dist/runtime/parallelism-flag.d.ts +10 -0
  153. package/dist/runtime/parallelism-flag.js +18 -0
  154. package/dist/runtime/port-allocator.d.ts +21 -0
  155. package/dist/runtime/port-allocator.js +66 -0
  156. package/dist/runtime/worktree-pool.d.ts +86 -0
  157. package/dist/runtime/worktree-pool.js +204 -0
  158. package/dist/runtime/worktree.d.ts +25 -0
  159. package/dist/runtime/worktree.js +111 -0
  160. package/dist/sa-scoring/auto-calibrate.d.ts +69 -0
  161. package/dist/sa-scoring/auto-calibrate.js +107 -0
  162. package/dist/sa-scoring/c1-sa2-computable.d.ts +42 -0
  163. package/dist/sa-scoring/c1-sa2-computable.js +59 -0
  164. package/dist/sa-scoring/composite.d.ts +107 -0
  165. package/dist/sa-scoring/composite.js +139 -0
  166. package/dist/sa-scoring/depparse-client.d.ts +79 -0
  167. package/dist/sa-scoring/depparse-client.js +187 -0
  168. package/dist/sa-scoring/did-compiler.d.ts +122 -0
  169. package/dist/sa-scoring/did-compiler.js +286 -0
  170. package/dist/sa-scoring/drift-monitor.d.ts +84 -0
  171. package/dist/sa-scoring/drift-monitor.js +186 -0
  172. package/dist/sa-scoring/exemplar-bank.d.ts +78 -0
  173. package/dist/sa-scoring/exemplar-bank.js +154 -0
  174. package/dist/sa-scoring/feedback-store.d.ts +100 -0
  175. package/dist/sa-scoring/feedback-store.js +156 -0
  176. package/dist/sa-scoring/index.d.ts +71 -0
  177. package/dist/sa-scoring/index.js +158 -0
  178. package/dist/sa-scoring/layer1-deterministic.d.ts +115 -0
  179. package/dist/sa-scoring/layer1-deterministic.js +298 -0
  180. package/dist/sa-scoring/layer2-structural.d.ts +71 -0
  181. package/dist/sa-scoring/layer2-structural.js +151 -0
  182. package/dist/sa-scoring/layer3-llm.d.ts +86 -0
  183. package/dist/sa-scoring/layer3-llm.js +282 -0
  184. package/dist/sa-scoring/rescore-orchestrator.d.ts +52 -0
  185. package/dist/sa-scoring/rescore-orchestrator.js +47 -0
  186. package/dist/scheduling/burn-down.d.ts +27 -0
  187. package/dist/scheduling/burn-down.js +43 -0
  188. package/dist/scheduling/calibration.d.ts +42 -0
  189. package/dist/scheduling/calibration.js +150 -0
  190. package/dist/scheduling/index.d.ts +8 -0
  191. package/dist/scheduling/index.js +8 -0
  192. package/dist/scheduling/ledger.d.ts +59 -0
  193. package/dist/scheduling/ledger.js +216 -0
  194. package/dist/scheduling/off-peak.d.ts +27 -0
  195. package/dist/scheduling/off-peak.js +112 -0
  196. package/dist/scheduling/schedule-decision.d.ts +39 -0
  197. package/dist/scheduling/schedule-decision.js +89 -0
  198. package/dist/scheduling/tier-analysis.d.ts +47 -0
  199. package/dist/scheduling/tier-analysis.js +81 -0
  200. package/dist/scheduling/types.d.ts +140 -0
  201. package/dist/scheduling/types.js +11 -0
  202. package/dist/shared.d.ts +13 -0
  203. package/dist/shared.js +32 -0
  204. package/dist/state/index.d.ts +1 -1
  205. package/dist/state/schema.d.ts +6 -1
  206. package/dist/state/schema.js +276 -1
  207. package/dist/state/store.d.ts +71 -1
  208. package/dist/state/store.js +464 -7
  209. package/dist/state/types.d.ts +174 -0
  210. package/dist/types.d.ts +1 -1
  211. package/dist/validate-agent-output.js +4 -1
  212. package/dist/watch.js +6 -0
  213. package/dist/workflow-patterns/artifact-writer.d.ts +16 -0
  214. package/dist/workflow-patterns/artifact-writer.js +34 -0
  215. package/dist/workflow-patterns/classifiers.d.ts +10 -0
  216. package/dist/workflow-patterns/classifiers.js +72 -0
  217. package/dist/workflow-patterns/detector.d.ts +27 -0
  218. package/dist/workflow-patterns/detector.js +186 -0
  219. package/dist/workflow-patterns/index.d.ts +8 -0
  220. package/dist/workflow-patterns/index.js +7 -0
  221. package/dist/workflow-patterns/proposal-generator.d.ts +15 -0
  222. package/dist/workflow-patterns/proposal-generator.js +183 -0
  223. package/dist/workflow-patterns/telemetry-ingest.d.ts +27 -0
  224. package/dist/workflow-patterns/telemetry-ingest.js +103 -0
  225. package/dist/workflow-patterns/types.d.ts +61 -0
  226. package/dist/workflow-patterns/types.js +11 -0
  227. package/package.json +2 -2
package/dist/config.js CHANGED
@@ -9,9 +9,8 @@ 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
+ /** Resource kinds that allow only a single instance. Multi-instance kinds are excluded. */
13
13
  const KIND_KEY = {
14
- Pipeline: 'pipeline',
15
14
  AgentRole: 'agentRole',
16
15
  QualityGate: 'qualityGate',
17
16
  AutonomyPolicy: 'autonomyPolicy',
@@ -27,21 +26,54 @@ export function loadConfig(configDir) {
27
26
  }
28
27
  const files = readdirSync(dir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'));
29
28
  const config = {};
29
+ const warnings = [];
30
30
  for (const file of files) {
31
- // Skip non-resource YAML files (e.g. manifest.yaml)
32
- if (file === 'manifest.yaml')
31
+ let doc;
32
+ try {
33
+ const raw = readFileSync(resolve(dir, file), 'utf-8');
34
+ doc = parseYaml(raw);
35
+ }
36
+ catch (err) {
37
+ // Malformed YAML — record and continue. One bad file should not
38
+ // poison the rest of the config (forward-looking YAMLs in active
39
+ // adoption are common).
40
+ warnings.push({ file, error: `parse error: ${err.message}` });
41
+ continue;
42
+ }
43
+ // Skip non-resource YAML files (review-exemplars.yaml, manifest.yaml, etc.)
44
+ // AI-SDLC resources always have an apiVersion field.
45
+ if (!doc || typeof doc !== 'object' || !('apiVersion' in doc) || !('kind' in doc)) {
33
46
  continue;
34
- const raw = readFileSync(resolve(dir, file), 'utf-8');
35
- const doc = parseYaml(raw);
47
+ }
36
48
  const result = validateResource(doc);
37
49
  if (!result.valid) {
38
- const msgs = (result.errors ?? []).map((e) => ` ${e.path}: ${e.message}`).join('\n');
39
- throw new Error(`Validation failed for ${file}:\n${msgs}`);
50
+ const msgs = (result.errors ?? []).map((e) => `${e.path}: ${e.message}`).join('; ');
51
+ // Forward-looking schemas are common during incremental adoption
52
+ // (RFC-0008 §A.4 readers landing in phases). Record + skip rather
53
+ // than throw — callers see the warning, the rest of the config
54
+ // (the parts that DO validate) continues to drive admission.
55
+ warnings.push({ file, error: `validation failed: ${msgs}` });
56
+ continue;
40
57
  }
41
58
  const resource = result.data;
42
59
  if (resource.kind === 'AdapterBinding') {
43
60
  (config.adapterBindings ??= []).push(resource);
44
61
  }
62
+ else if (resource.kind === 'DesignSystemBinding') {
63
+ (config.designSystemBindings ??= []).push(resource);
64
+ }
65
+ else if (resource.kind === 'DesignIntentDocument') {
66
+ (config.designIntentDocuments ??= []).push(resource);
67
+ }
68
+ else if (resource.kind === 'Pipeline') {
69
+ (config.pipelines ??= []).push(resource);
70
+ // Backward-compat: `config.pipeline` (singular) = the canonical one.
71
+ // Prefer the file literally named `pipeline.yaml` (the default workflow).
72
+ // Fall back to the first loaded otherwise.
73
+ if (file === 'pipeline.yaml' || file === 'pipeline.yml' || !config.pipeline) {
74
+ config.pipeline = resource;
75
+ }
76
+ }
45
77
  else {
46
78
  const key = KIND_KEY[resource.kind];
47
79
  if (key) {
@@ -50,12 +82,50 @@ export function loadConfig(configDir) {
50
82
  }
51
83
  }
52
84
  }
85
+ if (warnings.length > 0) {
86
+ config.warnings = warnings;
87
+ }
53
88
  // Backward compat: set adapterBinding to the first binding
54
89
  if (config.adapterBindings?.length) {
55
90
  config.adapterBinding = config.adapterBindings[0];
56
91
  }
92
+ // RFC-0008: validate DID → DSB cross-references
93
+ if (config.designIntentDocuments?.length) {
94
+ validateDesignIntentDocumentReferences(config);
95
+ }
57
96
  return config;
58
97
  }
98
+ /**
99
+ * Validate that every DesignIntentDocument's `spec.designSystemRef.name`
100
+ * resolves to a loaded DesignSystemBinding. Namespace match is required
101
+ * only when both resources declare a namespace (RFC-0008 §4.5).
102
+ *
103
+ * Unidirectional: DID → DSB. DesignSystemBinding surface is not modified.
104
+ */
105
+ export function validateDesignIntentDocumentReferences(config) {
106
+ const dids = config.designIntentDocuments ?? [];
107
+ const dsbs = config.designSystemBindings ?? [];
108
+ const unresolved = [];
109
+ for (const did of dids) {
110
+ const targetName = did.spec.designSystemRef.name;
111
+ const targetNamespace = did.spec.designSystemRef.namespace;
112
+ const match = dsbs.find((dsb) => {
113
+ if (dsb.metadata.name !== targetName)
114
+ return false;
115
+ if (targetNamespace && dsb.metadata.namespace && dsb.metadata.namespace !== targetNamespace) {
116
+ return false;
117
+ }
118
+ return true;
119
+ });
120
+ if (!match) {
121
+ const nsSuffix = targetNamespace ? `/${targetNamespace}` : '';
122
+ unresolved.push(` DesignIntentDocument "${did.metadata.name}" references "${targetName}${nsSuffix}" which does not resolve to any loaded DesignSystemBinding`);
123
+ }
124
+ }
125
+ if (unresolved.length > 0) {
126
+ throw new Error(`DID → DesignSystemBinding reference validation failed:\n${unresolved.join('\n')}`);
127
+ }
128
+ }
59
129
  /**
60
130
  * Async variant of loadConfig that also scans for local adapter plugins.
61
131
  */
@@ -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
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Utility functions for cycle detection and handling.
3
+ */
4
+ import { PipelineCycleDetector } from './pipeline-cycle-detector.js';
5
+ import { NOTIFICATION_TITLES } from './defaults.js';
6
+ /**
7
+ * Sanitize template content to prevent markdown/HTML injection.
8
+ * Strips HTML tags and limits length.
9
+ */
10
+ function sanitizeTemplate(text) {
11
+ return text.replace(/<[^>]*>/g, '').slice(0, 2000);
12
+ }
13
+ /**
14
+ * Check for pipeline cycles and post notification if detected.
15
+ *
16
+ * The marker is generated upfront so the caller can append it to comments
17
+ * BEFORE executing the stage (records intent, prevents race conditions).
18
+ * Cycle detection accounts for the pending invocation (+1 to current count).
19
+ */
20
+ export async function checkAndHandleCycle(options) {
21
+ const { issueOrPrId, stage, tracker, detector, notifySlack, cycleTemplate } = options;
22
+ // Detect cycles from existing comment markers (no +1 — marker is separate)
23
+ const cycleResult = await detector.detectCycle(tracker, issueOrPrId);
24
+ // Generate marker for the caller to append to comments
25
+ const marker = detector.recordInvocation(stage);
26
+ if (cycleResult.cycleDetected) {
27
+ const loopingSummary = cycleResult.loopingStages
28
+ .map((s) => `- **${s.stage}**: ${s.count}/${s.max} invocations`)
29
+ .join('\n');
30
+ const title = sanitizeTemplate(cycleTemplate?.title ?? NOTIFICATION_TITLES.pipelineCycleDetected);
31
+ const body = sanitizeTemplate(cycleTemplate?.body ??
32
+ `The pipeline has detected an infinite loop across the following stages:\n\n${loopingSummary}\n\n**Manual intervention is required** to resolve the cycle. Please review the issue and PR to determine the root cause.`);
33
+ const cycleMessage = `## ${title}\n\n${body}`;
34
+ // Post cycle detection comment
35
+ await tracker.addComment(issueOrPrId, cycleMessage);
36
+ // Send Slack notification if configured
37
+ if (notifySlack) {
38
+ const slackMessage = `:warning: *Pipeline Cycle Detected* for #${issueOrPrId}\n\nLooping stages:\n${cycleResult.loopingStages.map((s) => `• ${s.stage}: ${s.count}/${s.max}`).join('\n')}`;
39
+ await notifySlack(slackMessage).catch((err) => {
40
+ console.error('[cycle-detector] Slack notification failed:', err instanceof Error ? err.message : String(err));
41
+ });
42
+ }
43
+ return { cycleDetected: true, marker, cycleMessage };
44
+ }
45
+ return { cycleDetected: false, marker };
46
+ }
47
+ /**
48
+ * Create a cycle detector from pipeline config.
49
+ * Reads maxRetries from stage configurations.
50
+ */
51
+ export function createCycleDetectorFromConfig(config) {
52
+ const detector = new PipelineCycleDetector();
53
+ if (config.stages) {
54
+ const overrides = {};
55
+ for (const stage of config.stages) {
56
+ const maxRetries = stage.onFailure?.maxRetries;
57
+ if (maxRetries !== undefined) {
58
+ if (stage.name === 'code') {
59
+ overrides['fix-ci'] = maxRetries;
60
+ }
61
+ else if (stage.name === 'review') {
62
+ overrides['fix-review'] = maxRetries;
63
+ }
64
+ else if (stage.name === 'admission' ||
65
+ stage.name === 'triage' ||
66
+ stage.name === 'agent') {
67
+ overrides[stage.name] = maxRetries;
68
+ }
69
+ }
70
+ }
71
+ if (Object.keys(overrides).length > 0) {
72
+ detector.updateMaxInvocations(overrides);
73
+ }
74
+ }
75
+ return detector;
76
+ }
77
+ //# sourceMappingURL=cycle-utils.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * ExternalAdapter per RFC §15.2. Operator-declared shell hooks for proprietary or
3
+ * custom branching mechanisms. Pipeline-load REQUIRES `acknowledgeUntrusted: true`
4
+ * because shell hooks execute with full credential scope.
5
+ */
6
+ import { type DatabaseBranchAdapter, type DatabaseBranchCapabilities, type DatabaseBranchHandle, type DatabaseAdapterName, type ResolvedDatabaseBranchPool, type ValidationResult } from '../types.js';
7
+ export interface ExternalAdapterDeps {
8
+ exec?: (cmd: string, args: string[]) => Promise<{
9
+ stdout: string;
10
+ stderr: string;
11
+ }>;
12
+ }
13
+ export declare class ExternalAdapter implements DatabaseBranchAdapter {
14
+ private readonly deps;
15
+ readonly name: DatabaseAdapterName;
16
+ readonly capabilities: DatabaseBranchCapabilities;
17
+ constructor(deps?: ExternalAdapterDeps);
18
+ validate(pool: ResolvedDatabaseBranchPool): Promise<ValidationResult>;
19
+ allocate(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
20
+ reclaim(handle: DatabaseBranchHandle): Promise<void>;
21
+ list(_pool: ResolvedDatabaseBranchPool): Promise<DatabaseBranchHandle[]>;
22
+ isAvailable(pool: ResolvedDatabaseBranchPool): Promise<boolean>;
23
+ }
24
+ //# sourceMappingURL=external.d.ts.map
@@ -0,0 +1,80 @@
1
+ /**
2
+ * ExternalAdapter per RFC §15.2. Operator-declared shell hooks for proprietary or
3
+ * custom branching mechanisms. Pipeline-load REQUIRES `acknowledgeUntrusted: true`
4
+ * because shell hooks execute with full credential scope.
5
+ */
6
+ import { execFile } from 'node:child_process';
7
+ import { promisify } from 'node:util';
8
+ import { DatabaseBranchAdapterError, } from '../types.js';
9
+ const execFileAsync = promisify(execFile);
10
+ export class ExternalAdapter {
11
+ deps;
12
+ name = 'external';
13
+ capabilities = {
14
+ branchCreationLatencyP50Ms: 0, // declared by the hook script's behavior
15
+ maxBranches: 0, // declared by operator config
16
+ supportsMigrations: true,
17
+ supportsReadOnlyBranches: false,
18
+ supportsBranchFromBranch: true,
19
+ multiDatabase: true,
20
+ costModel: 'free',
21
+ };
22
+ constructor(deps = {}) {
23
+ this.deps = deps;
24
+ }
25
+ async validate(pool) {
26
+ const creds = pool.credentials ?? {};
27
+ if (creds.acknowledgeUntrusted !== true) {
28
+ return {
29
+ ok: false,
30
+ error: 'external adapter requires credentials.acknowledgeUntrusted: true. Operator-controlled shell hooks execute with full credential scope; this opt-in is intentional friction.',
31
+ };
32
+ }
33
+ if (typeof creds.allocateCommand !== 'string' || !creds.allocateCommand) {
34
+ return { ok: false, error: 'credentials.allocateCommand (string) is required' };
35
+ }
36
+ if (typeof creds.reclaimCommand !== 'string' || !creds.reclaimCommand) {
37
+ return { ok: false, error: 'credentials.reclaimCommand (string) is required' };
38
+ }
39
+ return { ok: true };
40
+ }
41
+ async allocate(pool, branchKey) {
42
+ const cmd = pool.credentials?.allocateCommand;
43
+ const exec = this.deps.exec ?? ((c, a) => execFileAsync(c, a));
44
+ let stdout;
45
+ try {
46
+ const result = await exec('sh', ['-c', `${cmd} ${branchKey}`]);
47
+ stdout = result.stdout;
48
+ }
49
+ catch (err) {
50
+ throw new DatabaseBranchAdapterError(`external allocate hook failed for branch ${branchKey}: ${err.message}`, this.name, branchKey, err);
51
+ }
52
+ const connectionString = stdout.trim();
53
+ if (!connectionString) {
54
+ throw new DatabaseBranchAdapterError(`external allocate hook produced empty stdout (expected connection string)`, this.name, branchKey);
55
+ }
56
+ return {
57
+ branchKey,
58
+ connectionString,
59
+ createdAt: new Date(),
60
+ upstream: pool.upstream.branchFrom ?? 'external',
61
+ metadata: { hook: 'external' },
62
+ };
63
+ }
64
+ async reclaim(handle) {
65
+ // The pool isn't passed here; the orchestrator MUST track (handle, pool) pairs and
66
+ // call this via the dispatcher. For test purposes, callers that use ExternalAdapter
67
+ // directly must provide their own dispatcher that supplies the reclaim command.
68
+ void handle;
69
+ }
70
+ async list(_pool) {
71
+ // External hooks don't expose a list mechanism; orchestrator tracks active branches
72
+ // in its own state.
73
+ return [];
74
+ }
75
+ async isAvailable(pool) {
76
+ const v = await this.validate(pool);
77
+ return v.ok;
78
+ }
79
+ }
80
+ //# sourceMappingURL=external.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * NeonAdapter per RFC §15.2. Wraps Neon REST API for branch lifecycle. Phase 6 ships
3
+ * the adapter shell + capability matrix + validation; the actual REST calls (allocate,
4
+ * reclaim, list) are stubbed until end-to-end integration testing against a real Neon
5
+ * sandbox project lands as a follow-up.
6
+ *
7
+ * Operators wire this up via:
8
+ * credentials:
9
+ * apiTokenEnv: NEON_API_TOKEN
10
+ * projectId: prj_abc123
11
+ */
12
+ import { type DatabaseBranchAdapter, type DatabaseBranchCapabilities, type DatabaseBranchHandle, type DatabaseAdapterName, type ResolvedDatabaseBranchPool, type ValidationResult } from '../types.js';
13
+ export interface NeonAdapterDeps {
14
+ env?: NodeJS.ProcessEnv;
15
+ /** Override for tests; replaces the actual fetch calls. */
16
+ api?: {
17
+ createBranch: (projectId: string, branchName: string, parentBranch: string) => Promise<{
18
+ branchId: string;
19
+ connectionString: string;
20
+ }>;
21
+ deleteBranch: (projectId: string, branchId: string) => Promise<void>;
22
+ listBranches: (projectId: string) => Promise<Array<{
23
+ branchId: string;
24
+ branchName: string;
25
+ parent: string;
26
+ createdAt: string;
27
+ }>>;
28
+ };
29
+ }
30
+ export declare class NeonAdapter implements DatabaseBranchAdapter {
31
+ private readonly deps;
32
+ readonly name: DatabaseAdapterName;
33
+ readonly capabilities: DatabaseBranchCapabilities;
34
+ constructor(deps?: NeonAdapterDeps);
35
+ validate(pool: ResolvedDatabaseBranchPool): Promise<ValidationResult>;
36
+ allocate(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
37
+ reclaim(handle: DatabaseBranchHandle): Promise<void>;
38
+ list(pool: ResolvedDatabaseBranchPool): Promise<DatabaseBranchHandle[]>;
39
+ isAvailable(pool: ResolvedDatabaseBranchPool): Promise<boolean>;
40
+ }
41
+ //# sourceMappingURL=neon.d.ts.map
@@ -0,0 +1,98 @@
1
+ /**
2
+ * NeonAdapter per RFC §15.2. Wraps Neon REST API for branch lifecycle. Phase 6 ships
3
+ * the adapter shell + capability matrix + validation; the actual REST calls (allocate,
4
+ * reclaim, list) are stubbed until end-to-end integration testing against a real Neon
5
+ * sandbox project lands as a follow-up.
6
+ *
7
+ * Operators wire this up via:
8
+ * credentials:
9
+ * apiTokenEnv: NEON_API_TOKEN
10
+ * projectId: prj_abc123
11
+ */
12
+ import { DatabaseBranchAdapterError, } from '../types.js';
13
+ export class NeonAdapter {
14
+ deps;
15
+ name = 'neon';
16
+ capabilities = {
17
+ branchCreationLatencyP50Ms: 1500,
18
+ maxBranches: 5000,
19
+ supportsMigrations: true,
20
+ supportsReadOnlyBranches: true,
21
+ supportsBranchFromBranch: true,
22
+ multiDatabase: false,
23
+ costModel: 'per-branch-storage',
24
+ };
25
+ constructor(deps = {}) {
26
+ this.deps = deps;
27
+ }
28
+ async validate(pool) {
29
+ const env = this.deps.env ?? process.env;
30
+ const creds = pool.credentials ?? {};
31
+ const apiTokenEnv = creds.apiTokenEnv;
32
+ const projectId = creds.projectId;
33
+ if (!apiTokenEnv) {
34
+ return { ok: false, error: 'credentials.apiTokenEnv is required for neon adapter' };
35
+ }
36
+ if (!env[apiTokenEnv]) {
37
+ return {
38
+ ok: false,
39
+ error: `${apiTokenEnv} is not set in env; cannot authenticate against Neon API`,
40
+ };
41
+ }
42
+ if (!projectId) {
43
+ return { ok: false, error: 'credentials.projectId is required for neon adapter' };
44
+ }
45
+ if (!pool.upstream.branchFrom) {
46
+ return {
47
+ ok: false,
48
+ error: 'upstream.branchFrom is required for neon (named branch to fork from)',
49
+ };
50
+ }
51
+ return { ok: true };
52
+ }
53
+ async allocate(pool, branchKey) {
54
+ if (!this.deps.api) {
55
+ throw new DatabaseBranchAdapterError('NeonAdapter.allocate is not wired against the live Neon REST API yet (Phase 6 follow-up). ' +
56
+ 'Tests should inject deps.api; production deployments require the integration test pass before use.', this.name, branchKey);
57
+ }
58
+ const projectId = pool.credentials?.projectId;
59
+ const parent = pool.upstream.branchFrom;
60
+ const result = await this.deps.api.createBranch(projectId, branchKey, parent);
61
+ return {
62
+ branchKey,
63
+ connectionString: result.connectionString,
64
+ createdAt: new Date(),
65
+ upstream: parent,
66
+ metadata: { branchId: result.branchId, projectId },
67
+ };
68
+ }
69
+ async reclaim(handle) {
70
+ if (!this.deps.api) {
71
+ throw new DatabaseBranchAdapterError('NeonAdapter.reclaim not wired (Phase 6 follow-up)', this.name, handle.branchKey);
72
+ }
73
+ const projectId = handle.metadata.projectId;
74
+ const branchId = handle.metadata.branchId;
75
+ if (!projectId || !branchId) {
76
+ throw new DatabaseBranchAdapterError(`handle missing projectId/branchId in metadata: ${JSON.stringify(handle.metadata)}`, this.name, handle.branchKey);
77
+ }
78
+ await this.deps.api.deleteBranch(projectId, branchId);
79
+ }
80
+ async list(pool) {
81
+ if (!this.deps.api)
82
+ return [];
83
+ const projectId = pool.credentials?.projectId;
84
+ const branches = await this.deps.api.listBranches(projectId);
85
+ return branches.map((b) => ({
86
+ branchKey: b.branchName,
87
+ connectionString: '<masked — re-fetch via API>',
88
+ createdAt: new Date(b.createdAt),
89
+ upstream: b.parent,
90
+ metadata: { projectId, branchId: b.branchId },
91
+ }));
92
+ }
93
+ async isAvailable(pool) {
94
+ const v = await this.validate(pool);
95
+ return v.ok;
96
+ }
97
+ }
98
+ //# sourceMappingURL=neon.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * PgSnapshotRestoreAdapter per RFC §15.2. For vanilla Postgres or AWS RDS — uses
3
+ * `pg_dump` + `pg_restore` for vanilla, or RDS snapshot/restore APIs for AWS.
4
+ *
5
+ * Phase 6 ships the adapter shell with capability matrix + validation. End-to-end
6
+ * integration test against local Postgres in CI is a follow-up.
7
+ */
8
+ import { type DatabaseBranchAdapter, type DatabaseBranchCapabilities, type DatabaseBranchHandle, type DatabaseAdapterName, type ResolvedDatabaseBranchPool, type ValidationResult } from '../types.js';
9
+ export interface PgSnapshotRestoreAdapterDeps {
10
+ env?: NodeJS.ProcessEnv;
11
+ /** Override for tests; mocks the dump+restore execution. */
12
+ exec?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => Promise<{
13
+ stdout: string;
14
+ stderr: string;
15
+ }>;
16
+ }
17
+ export declare class PgSnapshotRestoreAdapter implements DatabaseBranchAdapter {
18
+ private readonly deps;
19
+ readonly name: DatabaseAdapterName;
20
+ readonly capabilities: DatabaseBranchCapabilities;
21
+ constructor(deps?: PgSnapshotRestoreAdapterDeps);
22
+ validate(pool: ResolvedDatabaseBranchPool): Promise<ValidationResult>;
23
+ allocate(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
24
+ reclaim(handle: DatabaseBranchHandle): Promise<void>;
25
+ list(_pool: ResolvedDatabaseBranchPool): Promise<DatabaseBranchHandle[]>;
26
+ isAvailable(pool: ResolvedDatabaseBranchPool): Promise<boolean>;
27
+ }
28
+ //# sourceMappingURL=pg-snapshot-restore.d.ts.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * PgSnapshotRestoreAdapter per RFC §15.2. For vanilla Postgres or AWS RDS — uses
3
+ * `pg_dump` + `pg_restore` for vanilla, or RDS snapshot/restore APIs for AWS.
4
+ *
5
+ * Phase 6 ships the adapter shell with capability matrix + validation. End-to-end
6
+ * integration test against local Postgres in CI is a follow-up.
7
+ */
8
+ import { DatabaseBranchAdapterError, } from '../types.js';
9
+ export class PgSnapshotRestoreAdapter {
10
+ deps;
11
+ name = 'pg-snapshot-restore';
12
+ capabilities = {
13
+ branchCreationLatencyP50Ms: 30_000,
14
+ maxBranches: 100,
15
+ supportsMigrations: true,
16
+ supportsReadOnlyBranches: false,
17
+ supportsBranchFromBranch: false,
18
+ multiDatabase: false,
19
+ costModel: 'per-snapshot',
20
+ };
21
+ constructor(deps = {}) {
22
+ this.deps = deps;
23
+ }
24
+ async validate(pool) {
25
+ const env = this.deps.env ?? process.env;
26
+ const creds = pool.credentials ?? {};
27
+ const adminEnv = creds.adminConnectionStringEnv;
28
+ const storageVolume = creds.storageVolume;
29
+ if (!adminEnv) {
30
+ return {
31
+ ok: false,
32
+ error: 'credentials.adminConnectionStringEnv is required (privileged user with CREATEDB)',
33
+ };
34
+ }
35
+ if (!env[adminEnv]) {
36
+ return { ok: false, error: `${adminEnv} not set in env` };
37
+ }
38
+ if (!storageVolume) {
39
+ return {
40
+ ok: false,
41
+ error: 'credentials.storageVolume is required (where snapshots are restored)',
42
+ };
43
+ }
44
+ return { ok: true };
45
+ }
46
+ async allocate(pool, branchKey) {
47
+ if (!this.deps.exec) {
48
+ throw new DatabaseBranchAdapterError('PgSnapshotRestoreAdapter.allocate not wired (Phase 6 follow-up against fixture Postgres in CI)', this.name, branchKey);
49
+ }
50
+ // Placeholder — actual implementation: pg_dump from upstream, createdb branch_<key>, pg_restore into branch.
51
+ throw new DatabaseBranchAdapterError('PgSnapshotRestoreAdapter integration pending', this.name, branchKey);
52
+ }
53
+ async reclaim(handle) {
54
+ if (!this.deps.exec)
55
+ return;
56
+ // Placeholder — actual: dropdb branch_<key>
57
+ void handle;
58
+ }
59
+ async list(_pool) {
60
+ // Placeholder — actual: query pg_database for branch_-prefixed databases.
61
+ return [];
62
+ }
63
+ async isAvailable(pool) {
64
+ const v = await this.validate(pool);
65
+ return v.ok;
66
+ }
67
+ }
68
+ //# sourceMappingURL=pg-snapshot-restore.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * SqliteCopyAdapter per RFC §15.2. Copies the upstream `.sqlite` file to a per-branch
3
+ * file in the worktree. Hardlinks for read-only branches; full copy for write/migrate.
4
+ *
5
+ * Connection-string format (the orchestrator's existing convention): plain absolute
6
+ * file path, optionally prefixed with `file://`.
7
+ */
8
+ import { type DatabaseBranchAdapter, type DatabaseBranchCapabilities, type DatabaseBranchHandle, type DatabaseAdapterName, type ResolvedDatabaseBranchPool, type ValidationResult } from '../types.js';
9
+ export interface SqliteCopyAdapterDeps {
10
+ env?: NodeJS.ProcessEnv;
11
+ /** Override the per-branch storage root for tests. Defaults to `<cwd>/.ai-sdlc/db`. */
12
+ storageRoot?: string;
13
+ }
14
+ export declare class SqliteCopyAdapter implements DatabaseBranchAdapter {
15
+ private readonly deps;
16
+ readonly name: DatabaseAdapterName;
17
+ readonly capabilities: DatabaseBranchCapabilities;
18
+ constructor(deps?: SqliteCopyAdapterDeps);
19
+ private storageRootFor;
20
+ validate(pool: ResolvedDatabaseBranchPool): Promise<ValidationResult>;
21
+ allocate(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
22
+ /**
23
+ * Read-only branch via hardlink (saves disk space when many readers share the same
24
+ * upstream snapshot). Caller is responsible for never opening the resulting file
25
+ * for write — SQLite will happily corrupt the hardlinked upstream if treated as RW.
26
+ */
27
+ allocateReadOnly(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
28
+ reclaim(handle: DatabaseBranchHandle): Promise<void>;
29
+ list(pool: ResolvedDatabaseBranchPool): Promise<DatabaseBranchHandle[]>;
30
+ isAvailable(pool: ResolvedDatabaseBranchPool): Promise<boolean>;
31
+ }
32
+ //# sourceMappingURL=sqlite-copy.d.ts.map