@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
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Shared git utilities for agent runners.
3
+ */
4
+ import { execFile } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+ import { cleanGitEnv } from '../runtime/git-env.js';
7
+ const execFileAsync = promisify(execFile);
8
+ /**
9
+ * Run a git command in the given directory.
10
+ *
11
+ * Always passes `-c core.quotePath=false` so paths containing non-ASCII
12
+ * characters (e.g. ↔, é, 中) come back as raw UTF-8 instead of git's default
13
+ * octal-escaped form (`"file with \342\206\224.md"`). Without this, the AISDLC-68
14
+ * task file (which has ↔ in its name) showed up in `git diff --name-only`
15
+ * as a quoted+escaped string and the subsequent `git add -- <file>` rejected
16
+ * with "pathspec did not match".
17
+ *
18
+ * Uses `cleanGitEnv()` (AISDLC-72) so git resolves against `workDir`'s own
19
+ * .git rather than whatever a parent process (husky pre-push hook running
20
+ * in another worktree) leaked into GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE.
21
+ */
22
+ export async function gitExec(workDir, args) {
23
+ const { stdout } = await execFileAsync('git', ['-c', 'core.quotePath=false', ...args], {
24
+ cwd: workDir,
25
+ env: cleanGitEnv(),
26
+ });
27
+ return stdout.trim();
28
+ }
29
+ /**
30
+ * Snapshot the untracked + modified file lists in the worktree. Captured before
31
+ * the agent runs so detectChangedFiles can subtract pre-existing noise (SQLite
32
+ * working files, draft RFCs the user hasn't decided to commit yet, in-flight
33
+ * edits on the previous branch). Without this, `git add -A` sweeps everything
34
+ * into the agent's commit (the AISDLC-68 incident).
35
+ *
36
+ * Failures degrade gracefully to an empty baseline — never block the pipeline
37
+ * because the snapshot couldn't run.
38
+ */
39
+ export async function snapshotWorktree(workDir) {
40
+ try {
41
+ const [untrackedOutput, modifiedOutput] = await Promise.all([
42
+ gitExec(workDir, ['ls-files', '--others', '--exclude-standard']),
43
+ gitExec(workDir, ['diff', '--name-only']),
44
+ ]);
45
+ return {
46
+ untracked: new Set(untrackedOutput.split('\n').filter(Boolean)),
47
+ modified: new Set(modifiedOutput.split('\n').filter(Boolean)),
48
+ };
49
+ }
50
+ catch {
51
+ return { untracked: new Set(), modified: new Set() };
52
+ }
53
+ }
54
+ /**
55
+ * Detect files changed by an agent. Three signals contribute:
56
+ *
57
+ * - **Unstaged working-tree changes** (`git diff --name-only`)
58
+ * - **Staged but uncommitted changes** (`git diff --name-only --cached`) — the
59
+ * agent sometimes runs `git add` itself before yielding control. Without
60
+ * this signal the orchestrator returned "Agent made no changes" and bailed
61
+ * even though the agent's work was sitting in the index (the AISDLC-68
62
+ * fourth-rerun bug).
63
+ * - **Untracked files** (`git ls-files --others`)
64
+ *
65
+ * When `baseline` is provided, untracked files that existed BEFORE the agent
66
+ * ran are excluded — they belong to the user, not the agent's diff.
67
+ *
68
+ * Also checks whether the agent self-committed (compares HEAD to merge-base
69
+ * with origin/main). Self-committed runs short-circuit the orchestrator's
70
+ * commit step.
71
+ */
72
+ export async function detectChangedFiles(workDir, baseline) {
73
+ const [diffOutput, stagedOutput, untrackedOutput] = await Promise.all([
74
+ gitExec(workDir, ['diff', '--name-only']),
75
+ gitExec(workDir, ['diff', '--name-only', '--cached']),
76
+ gitExec(workDir, ['ls-files', '--others', '--exclude-standard']),
77
+ ]);
78
+ const allUntracked = untrackedOutput.split('\n').filter(Boolean);
79
+ // Untracked files that existed pre-agent are user state — exclude them.
80
+ const agentUntracked = baseline
81
+ ? allUntracked.filter((f) => !baseline.untracked.has(f))
82
+ : allUntracked;
83
+ // Combine + dedupe: a file may show up in both unstaged and staged diffs
84
+ // (partial-stage scenario), and untracked files are mutually exclusive
85
+ // with diffs but include for completeness.
86
+ const uncommittedSet = new Set([
87
+ ...diffOutput.split('\n').filter(Boolean),
88
+ ...stagedOutput.split('\n').filter(Boolean),
89
+ ...agentUntracked,
90
+ ]);
91
+ const uncommittedFiles = [...uncommittedSet];
92
+ // Check if agent already committed — compare against merge base with main
93
+ let committedFiles = [];
94
+ let agentAlreadyCommitted = false;
95
+ try {
96
+ const mergeBase = (await gitExec(workDir, ['merge-base', 'HEAD', 'origin/main'])).trim();
97
+ if (mergeBase) {
98
+ const commitDiff = await gitExec(workDir, ['diff', '--name-only', `${mergeBase}..HEAD`]);
99
+ committedFiles = commitDiff.split('\n').filter(Boolean);
100
+ agentAlreadyCommitted = committedFiles.length > 0 && uncommittedFiles.length === 0;
101
+ }
102
+ }
103
+ catch {
104
+ // merge-base may fail if main doesn't exist locally — that's fine
105
+ }
106
+ return {
107
+ filesChanged: agentAlreadyCommitted ? committedFiles : uncommittedFiles,
108
+ agentAlreadyCommitted,
109
+ };
110
+ }
111
+ /**
112
+ * Detect writes the agent made into sibling git repositories (i.e., directories
113
+ * adjacent to `workDir` that are themselves git repos). Surfaced as a warning,
114
+ * not a hard failure — the AISDLC-68 task LEGITIMATELY needed to sync into
115
+ * `../ai-sdlc-io/`, and we don't want to forbid that. The orchestrator just
116
+ * needs to surface that the changes exist so the operator knows to commit them
117
+ * separately in the sibling repo.
118
+ *
119
+ * Returns one entry per dirty sibling repo, with the list of changed files.
120
+ * Empty array when no siblings are dirty (the common case).
121
+ */
122
+ export async function detectCrossRepoWrites(workDir) {
123
+ const { readdir, stat } = await import('node:fs/promises');
124
+ const { dirname, join, resolve } = await import('node:path');
125
+ let workTreeRoot;
126
+ try {
127
+ workTreeRoot = await gitExec(workDir, ['rev-parse', '--show-toplevel']);
128
+ }
129
+ catch {
130
+ return [];
131
+ }
132
+ const parent = dirname(resolve(workTreeRoot));
133
+ let entries;
134
+ try {
135
+ entries = await readdir(parent);
136
+ }
137
+ catch {
138
+ return [];
139
+ }
140
+ const writes = [];
141
+ for (const entry of entries) {
142
+ const candidate = join(parent, entry);
143
+ if (resolve(candidate) === resolve(workTreeRoot))
144
+ continue; // skip self
145
+ try {
146
+ const s = await stat(candidate);
147
+ if (!s.isDirectory())
148
+ continue;
149
+ // Quick git-repo check via `rev-parse --is-inside-work-tree`.
150
+ await gitExec(candidate, ['rev-parse', '--is-inside-work-tree']);
151
+ }
152
+ catch {
153
+ continue;
154
+ }
155
+ try {
156
+ const status = await gitExec(candidate, ['status', '--porcelain']);
157
+ if (!status.trim())
158
+ continue;
159
+ // Porcelain v1: first 2 chars are status (XY), then 1+ whitespace, then
160
+ // path. Cannot use slice(3) — gitExec already trimmed the leading space
161
+ // when X is unmodified ("M " becomes "M" after trim, dropping a column
162
+ // and eating the first filename character).
163
+ const files = status
164
+ .split('\n')
165
+ .filter(Boolean)
166
+ .map((line) => {
167
+ const m = line.match(/^.{1,2}\s+(.+)$/);
168
+ return m ? m[1] : line;
169
+ });
170
+ writes.push({ repoPath: candidate, files });
171
+ }
172
+ catch {
173
+ // Couldn't read status — skip silently.
174
+ }
175
+ }
176
+ return writes;
177
+ }
178
+ /**
179
+ * Run lint and format auto-fix commands (best-effort, non-fatal).
180
+ */
181
+ export async function runAutoFix(workDir, lintCmd, fmtCmd) {
182
+ if (fmtCmd) {
183
+ try {
184
+ const [bin, ...args] = fmtCmd.split(' ');
185
+ await execFileAsync(bin, args, { cwd: workDir });
186
+ }
187
+ catch {
188
+ // Format failures are non-fatal
189
+ }
190
+ }
191
+ if (lintCmd) {
192
+ try {
193
+ const [bin, ...args] = lintCmd.split(' ');
194
+ await execFileAsync(bin, args, { cwd: workDir });
195
+ }
196
+ catch {
197
+ // Lint --fix failures are non-fatal
198
+ }
199
+ }
200
+ }
201
+ //# sourceMappingURL=git-utils.js.map
@@ -1,9 +1,13 @@
1
- export type { AgentRunner, AgentContext, AgentResult, TokenUsage } from './types.js';
1
+ export type { AgentRunner, AgentContext, AgentResult, AgentProgressEvent, TokenUsage, } from './types.js';
2
2
  export { ClaudeCodeRunner, GitHubActionsRunner } from './claude-code.js';
3
+ export { ClaudeCodeSdkRunner } from './claude-code-sdk.js';
4
+ export { gitExec, detectChangedFiles, runAutoFix, type DetectedChanges } from './git-utils.js';
3
5
  export { GenericLLMRunner, type GenericLLMConfig, type ChatCompletionResponse, } from './generic-llm.js';
4
6
  export { CopilotRunner } from './copilot.js';
5
7
  export { CursorRunner } from './cursor.js';
6
8
  export { CodexRunner } from './codex.js';
7
9
  export { RunnerRegistry, createRunnerRegistry, type RegisteredRunner } from './runner-registry.js';
8
10
  export { SecurityTriageRunner, type SecurityTriageConfig, type TriageVerdict, TRIAGE_SYSTEM_PROMPT, } from './security-triage.js';
11
+ export { ReviewAgentRunner, REVIEW_PROMPTS, type ReviewAgentConfig, type ReviewType, type ReviewFinding, type ReviewVerdict, } from './review-agent.js';
12
+ export { runParallelSdkReviews, DEFAULT_REVIEW_CONFIGS, type SdkReviewConfig, type SdkParallelReviewOptions, type SdkParallelReviewResult, } from './sdk-review-runner.js';
9
13
  //# sourceMappingURL=index.d.ts.map
@@ -1,8 +1,12 @@
1
1
  export { ClaudeCodeRunner, GitHubActionsRunner } from './claude-code.js';
2
+ export { ClaudeCodeSdkRunner } from './claude-code-sdk.js';
3
+ export { gitExec, detectChangedFiles, runAutoFix } from './git-utils.js';
2
4
  export { GenericLLMRunner, } from './generic-llm.js';
3
5
  export { CopilotRunner } from './copilot.js';
4
6
  export { CursorRunner } from './cursor.js';
5
7
  export { CodexRunner } from './codex.js';
6
8
  export { RunnerRegistry, createRunnerRegistry } from './runner-registry.js';
7
9
  export { SecurityTriageRunner, TRIAGE_SYSTEM_PROMPT, } from './security-triage.js';
10
+ export { ReviewAgentRunner, REVIEW_PROMPTS, } from './review-agent.js';
11
+ export { runParallelSdkReviews, DEFAULT_REVIEW_CONFIGS, } from './sdk-review-runner.js';
8
12
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * PR Review agent runner — analyzes pull request diffs for testing coverage,
3
+ * code quality, and security issues. Read-only: never modifies files.
4
+ *
5
+ * Uses the Anthropic Messages API directly (not Claude Code CLI)
6
+ * to produce a structured review verdict. Follows the SecurityTriageRunner
7
+ * pattern exactly.
8
+ */
9
+ import type { AgentRunner, AgentContext, AgentResult } from './types.js';
10
+ export type ReviewType = 'testing' | 'critic' | 'security';
11
+ export interface ReviewFinding {
12
+ severity: 'critical' | 'major' | 'minor' | 'suggestion';
13
+ file?: string;
14
+ line?: number;
15
+ message: string;
16
+ /** Self-assessed confidence score (0-1). Findings below 0.5 are suppressed. */
17
+ confidence?: number;
18
+ /** Category of the finding. */
19
+ category?: 'logic-error' | 'security' | 'design' | 'performance' | 'testing' | 'other';
20
+ /** Evidence supporting the finding. Required for critical/major. */
21
+ evidence?: {
22
+ codePathTraced?: string;
23
+ failureScenario?: string;
24
+ };
25
+ }
26
+ export interface ReviewVerdict {
27
+ type: ReviewType;
28
+ approved: boolean;
29
+ findings: ReviewFinding[];
30
+ summary: string;
31
+ }
32
+ export interface ReviewAgentConfig {
33
+ /** Anthropic API URL. Defaults to https://api.anthropic.com/v1/messages */
34
+ apiUrl?: string;
35
+ /** Anthropic API key. Defaults to ANTHROPIC_API_KEY env var. */
36
+ apiKey?: string;
37
+ /** Model to use. Defaults to claude-sonnet-4-5. */
38
+ model?: string;
39
+ /**
40
+ * Model to escalate to when the input exceeds the large-context threshold.
41
+ * Defaults to AI_SDLC_REVIEW_LARGE_MODEL env var, then claude-opus-4-7.
42
+ */
43
+ largeContextModel?: string;
44
+ /**
45
+ * Char-count threshold above which the runner switches to `largeContextModel`
46
+ * and sets the Anthropic 1M-context beta header. Default ~150k tokens
47
+ * (the standard Anthropic context limit) at the 4-chars-per-token heuristic.
48
+ */
49
+ largeContextThresholdChars?: number;
50
+ /** Request timeout in ms. Defaults to 120_000. */
51
+ timeoutMs?: number;
52
+ /** Which review perspective to use. */
53
+ reviewType: ReviewType;
54
+ /** Project-specific review policy to prepend to the system prompt (calibration context). */
55
+ reviewPolicy?: string;
56
+ }
57
+ declare const REVIEW_PROMPTS: Record<ReviewType, string>;
58
+ export declare class ReviewAgentRunner implements AgentRunner {
59
+ private config;
60
+ constructor(config: ReviewAgentConfig);
61
+ get reviewType(): ReviewType;
62
+ run(ctx: AgentContext): Promise<AgentResult>;
63
+ private callAPI;
64
+ parseVerdict(text: string): ReviewVerdict;
65
+ }
66
+ export { REVIEW_PROMPTS };
67
+ //# sourceMappingURL=review-agent.d.ts.map
@@ -0,0 +1,350 @@
1
+ /**
2
+ * PR Review agent runner — analyzes pull request diffs for testing coverage,
3
+ * code quality, and security issues. Read-only: never modifies files.
4
+ *
5
+ * Uses the Anthropic Messages API directly (not Claude Code CLI)
6
+ * to produce a structured review verdict. Follows the SecurityTriageRunner
7
+ * pattern exactly.
8
+ */
9
+ import { DEFAULT_ANTHROPIC_API_URL, DEFAULT_ANTHROPIC_MODEL, DEFAULT_LLM_TIMEOUT_MS, } from '../defaults.js';
10
+ /** Minimum confidence to include a finding in the verdict. */
11
+ const CONFIDENCE_THRESHOLD = 0.5;
12
+ /**
13
+ * Default escalation threshold. Anthropic's standard context window is 200k tokens;
14
+ * we leave headroom for the system prompt + response and trigger escalation around
15
+ * 150k tokens (≈ 600k chars at the 4-char/token heuristic). The user's recurring
16
+ * "PR too large for review" failure on PR #67 happened above this threshold.
17
+ */
18
+ const DEFAULT_LARGE_CONTEXT_THRESHOLD_CHARS = 600_000;
19
+ const DEFAULT_LARGE_CONTEXT_MODEL = process.env.AI_SDLC_REVIEW_LARGE_MODEL ?? 'claude-opus-4-7';
20
+ /** Anthropic 1M-context beta header. Required when sending > 200k tokens. */
21
+ const ANTHROPIC_LONG_CONTEXT_BETA = 'context-1m-2025-08-07';
22
+ // ── CI boundary ─────────────────────────────────────────────────────
23
+ /**
24
+ * Declarative CI boundary — tells review agents what CI already validates.
25
+ * Agents MUST NOT duplicate findings for issues CI catches deterministically.
26
+ * Prepended to every REVIEW_PROMPTS entry.
27
+ */
28
+ const CI_BOUNDARY_PREAMBLE = `## CI Boundary — What You Must NOT Flag
29
+
30
+ The following checks run deterministically in CI on every PR. They are authoritative.
31
+ Do NOT flag issues that these checks catch — they run independently and will pass or fail
32
+ on their own. If CI covers it, it is OUT OF YOUR SCOPE.
33
+
34
+ **CI checks (deterministic, authoritative):**
35
+ - **Lint (ESLint)**: All lint violations, unused imports, naming conventions
36
+ - **Format (Prettier)**: All formatting — whitespace, semicolons, commas, line length
37
+ - **TypeScript typecheck (pnpm build)**: Type errors, missing types, generics
38
+ - **Unit tests (Vitest)**: Test failures, broken assertions
39
+ - **Coverage (Codecov patch)**: Line coverage on changed code (80% patch target)
40
+ - **Schema validation**: YAML/JSON schema conformance
41
+
42
+ **Your job is to find issues CI CANNOT catch:**
43
+ - Logic errors that pass type checking but produce wrong results
44
+ - Security vulnerabilities (injection, auth bypass, credential exposure)
45
+ - Missing error handling for edge cases that tests don't cover
46
+ - Design problems (wrong abstraction, pattern violations)
47
+ - Race conditions and concurrency issues
48
+ - Performance anti-patterns (N+1 queries, unbounded allocations)
49
+ - Acceptance criteria not addressed by the implementation
50
+
51
+ **If unsure whether CI catches something, do NOT flag it.**
52
+
53
+ `;
54
+ // ── System prompts ───────────────────────────────────────────────────
55
+ const REVIEW_PROMPTS = {
56
+ testing: `${CI_BOUNDARY_PREAMBLE}You are a testing review agent analyzing a pull request diff. Your job is to verify that the changes are well-tested and that acceptance criteria are met.
57
+
58
+ Analyze the diff and any provided acceptance criteria. Check for:
59
+ 1. **Untested logic paths**: Are there logic branches that existing tests don't exercise? (Do NOT flag coverage percentages — Codecov handles that.)
60
+ 2. **Acceptance criteria**: If provided, are all acceptance criteria addressed?
61
+ 3. **Edge cases**: Are boundary conditions and error paths tested?
62
+ 4. **Test quality**: Are tests meaningful (not just asserting true)?
63
+ 5. **Missing edge-case tests**: Are there missing tests for error paths and boundary conditions that the test suite cannot catch?
64
+
65
+ Do NOT flag: coverage percentages, missing tests for config/YAML files, type-only files, or barrel exports. Codecov and CI handle these.
66
+
67
+ ## Structured Output Format
68
+
69
+ Respond with ONLY a JSON object (no markdown, no code fences).
70
+
71
+ Each finding MUST include a confidence score (0.0-1.0) and a category.
72
+ For critical/major findings, you MUST include evidence with a concrete failure scenario.
73
+ **No evidence = no critical/major finding.** If you cannot trace the code path to a failure, downgrade to minor/suggestion.
74
+ Findings below 0.5 confidence will be automatically suppressed.
75
+
76
+ {
77
+ "approved": true/false,
78
+ "findings": [
79
+ {
80
+ "severity": "critical|major|minor|suggestion",
81
+ "confidence": 0.0-1.0,
82
+ "category": "testing|logic-error|security|design|performance|other",
83
+ "file": "path/to/file.ts",
84
+ "line": 42,
85
+ "evidence": {
86
+ "codePathTraced": "Function X calls Y which can return null when Z",
87
+ "failureScenario": "When input is empty, line 42 throws TypeError"
88
+ },
89
+ "message": "Concise description of the issue"
90
+ }
91
+ ],
92
+ "summary": "1-2 sentence overall assessment"
93
+ }
94
+
95
+ Severity guide:
96
+ - critical: Missing tests for critical logic paths, acceptance criteria not met. MUST have failureScenario.
97
+ - major: Significant untested logic branches. MUST have failureScenario.
98
+ - minor: Minor test improvements possible
99
+ - suggestion: Nice-to-have test additions`,
100
+ critic: `${CI_BOUNDARY_PREAMBLE}You are a code quality review agent analyzing a pull request diff. Your job is to identify logic errors and design problems.
101
+
102
+ Analyze the diff for:
103
+ 1. **Logic errors**: Incorrect conditions, off-by-one errors, race conditions
104
+ 2. **Design issues**: Wrong abstraction, unnecessary complexity, pattern violations
105
+ 3. **Error handling**: Missing error cases at system boundaries
106
+ 4. **Performance**: Obvious inefficiencies (N+1 queries, unbounded allocations)
107
+
108
+ Do NOT flag: style issues (formatting, whitespace, import order), type errors, lint violations, or naming conventions. ESLint, Prettier, and TypeScript handle these deterministically.
109
+
110
+ ## Structured Output Format
111
+
112
+ Respond with ONLY a JSON object (no markdown, no code fences).
113
+
114
+ Each finding MUST include a confidence score (0.0-1.0) and a category.
115
+ For critical/major findings, you MUST include evidence with a concrete failure scenario.
116
+ **No evidence = no critical/major finding.** If you cannot trace the code path to a failure, downgrade to minor/suggestion.
117
+ Findings below 0.5 confidence will be automatically suppressed.
118
+
119
+ {
120
+ "approved": true/false,
121
+ "findings": [
122
+ {
123
+ "severity": "critical|major|minor|suggestion",
124
+ "confidence": 0.0-1.0,
125
+ "category": "logic-error|design|performance|other",
126
+ "file": "path/to/file.ts",
127
+ "line": 42,
128
+ "evidence": {
129
+ "codePathTraced": "Function X calls Y which can return null when Z",
130
+ "failureScenario": "When input is empty, line 42 throws TypeError"
131
+ },
132
+ "message": "Concise description of the issue"
133
+ }
134
+ ],
135
+ "summary": "1-2 sentence overall assessment"
136
+ }
137
+
138
+ Severity guide:
139
+ - critical: Logic errors, data loss risks, broken functionality. MUST have failureScenario.
140
+ - major: Significant design issues that should be fixed before merge. MUST have failureScenario.
141
+ - minor: Improvements that would make the code better
142
+ - suggestion: Optional enhancements`,
143
+ security: `${CI_BOUNDARY_PREAMBLE}You are a security review agent analyzing a pull request diff. Your job is to identify security vulnerabilities in the changed code.
144
+
145
+ Analyze the diff for:
146
+ 1. **Injection vulnerabilities**: SQL injection, command injection, XSS, template injection
147
+ 2. **Authentication/authorization**: Missing auth checks, privilege escalation
148
+ 3. **Credential exposure**: Hardcoded secrets, API keys, tokens in code
149
+ 4. **Path traversal**: Unsanitized file paths, directory traversal
150
+ 5. **Unsafe deserialization**: JSON.parse on untrusted input without validation
151
+ 6. **Dependency issues**: Known vulnerable patterns, unsafe API usage
152
+
153
+ Do NOT flag: type safety issues (TypeScript handles these), or issues in trusted internal code paths (config files, env vars set by the platform).
154
+
155
+ ## Structured Output Format
156
+
157
+ Respond with ONLY a JSON object (no markdown, no code fences).
158
+
159
+ Each finding MUST include a confidence score (0.0-1.0) and a category.
160
+ For critical/major findings, you MUST include evidence with a concrete failure scenario.
161
+ **No evidence = no critical/major finding.** If you cannot describe a realistic attack vector, downgrade to minor/suggestion.
162
+ Findings below 0.5 confidence will be automatically suppressed.
163
+
164
+ {
165
+ "approved": true/false,
166
+ "findings": [
167
+ {
168
+ "severity": "critical|major|minor|suggestion",
169
+ "confidence": 0.0-1.0,
170
+ "category": "security|other",
171
+ "file": "path/to/file.ts",
172
+ "line": 42,
173
+ "evidence": {
174
+ "codePathTraced": "User input flows from X to Y without sanitization",
175
+ "failureScenario": "Attacker sends malicious input via Z, causing command injection at line 42"
176
+ },
177
+ "message": "Concise description of the vulnerability"
178
+ }
179
+ ],
180
+ "summary": "1-2 sentence overall assessment"
181
+ }
182
+
183
+ Severity guide:
184
+ - critical: Exploitable vulnerability (injection, credential leak, auth bypass). MUST have failureScenario with attack vector.
185
+ - major: Security weakness that should be fixed (missing validation, unsafe patterns). MUST have failureScenario.
186
+ - minor: Defense-in-depth improvement
187
+ - suggestion: Security hardening opportunity`,
188
+ };
189
+ // ── Runner ───────────────────────────────────────────────────────────
190
+ export class ReviewAgentRunner {
191
+ config;
192
+ constructor(config) {
193
+ this.config = config;
194
+ }
195
+ get reviewType() {
196
+ return this.config.reviewType;
197
+ }
198
+ async run(ctx) {
199
+ const apiKey = this.config.apiKey ?? process.env.ANTHROPIC_API_KEY;
200
+ if (!apiKey) {
201
+ return {
202
+ success: false,
203
+ filesChanged: [],
204
+ summary: 'Missing ANTHROPIC_API_KEY for PR review',
205
+ error: 'ANTHROPIC_API_KEY environment variable is not set',
206
+ };
207
+ }
208
+ const userContent = [
209
+ `## Pull Request Diff to Review`,
210
+ '',
211
+ ctx.issueBody, // diff is passed via issueBody
212
+ '',
213
+ ...(ctx.ciErrors ? [`## Acceptance Criteria`, '', ctx.ciErrors, ''] : []),
214
+ `## Context`,
215
+ '',
216
+ `**Issue Title:** ${ctx.issueTitle}`,
217
+ ].join('\n');
218
+ try {
219
+ const verdict = await this.callAPI(apiKey, userContent);
220
+ return {
221
+ success: true,
222
+ filesChanged: [], // Read-only — never modifies files
223
+ summary: JSON.stringify(verdict),
224
+ tokenUsage: verdict._tokenUsage,
225
+ };
226
+ }
227
+ catch (err) {
228
+ return {
229
+ success: false,
230
+ filesChanged: [],
231
+ summary: 'PR review failed',
232
+ error: err instanceof Error ? err.message : String(err),
233
+ };
234
+ }
235
+ }
236
+ async callAPI(apiKey, userContent) {
237
+ const apiUrl = this.config.apiUrl ?? DEFAULT_ANTHROPIC_API_URL;
238
+ const baseModel = this.config.model ?? DEFAULT_ANTHROPIC_MODEL;
239
+ const timeoutMs = this.config.timeoutMs ?? DEFAULT_LLM_TIMEOUT_MS;
240
+ const system = this.config.reviewPolicy
241
+ ? `${this.config.reviewPolicy}\n\n---\n\n${REVIEW_PROMPTS[this.config.reviewType]}`
242
+ : REVIEW_PROMPTS[this.config.reviewType];
243
+ // Escalate to a 1M-context model when the input is large enough to risk
244
+ // overflowing the standard 200k-token window. The signal we use is char count
245
+ // of (system + user) since precise tokenization isn't available client-side.
246
+ const threshold = this.config.largeContextThresholdChars ?? DEFAULT_LARGE_CONTEXT_THRESHOLD_CHARS;
247
+ const inputChars = system.length + userContent.length;
248
+ const escalate = inputChars > threshold;
249
+ const model = escalate
250
+ ? (this.config.largeContextModel ?? DEFAULT_LARGE_CONTEXT_MODEL)
251
+ : baseModel;
252
+ const controller = new AbortController();
253
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
254
+ try {
255
+ const headers = {
256
+ 'Content-Type': 'application/json',
257
+ 'x-api-key': apiKey,
258
+ 'anthropic-version': '2023-06-01',
259
+ };
260
+ if (escalate)
261
+ headers['anthropic-beta'] = ANTHROPIC_LONG_CONTEXT_BETA;
262
+ const res = await fetch(apiUrl, {
263
+ method: 'POST',
264
+ headers,
265
+ body: JSON.stringify({
266
+ model,
267
+ max_tokens: 4096,
268
+ system,
269
+ messages: [{ role: 'user', content: userContent }],
270
+ }),
271
+ signal: controller.signal,
272
+ });
273
+ if (!res.ok) {
274
+ const text = await res.text().catch(() => '');
275
+ throw new Error(`Anthropic API error ${res.status}: ${text.slice(0, 200)}`);
276
+ }
277
+ const body = (await res.json());
278
+ const text = body.content?.[0]?.text ?? '';
279
+ const verdict = this.parseVerdict(text);
280
+ const tokenUsage = body.usage
281
+ ? {
282
+ inputTokens: body.usage.input_tokens,
283
+ outputTokens: body.usage.output_tokens,
284
+ model: body.model ?? model,
285
+ }
286
+ : undefined;
287
+ return { ...verdict, _tokenUsage: tokenUsage };
288
+ }
289
+ finally {
290
+ clearTimeout(timeout);
291
+ }
292
+ }
293
+ parseVerdict(text) {
294
+ // Strip markdown fences if the model wraps the JSON
295
+ const cleaned = text.replace(/^```(?:json)?\s*/m, '').replace(/\s*```$/m, '');
296
+ try {
297
+ const parsed = JSON.parse(cleaned);
298
+ const rawFindings = Array.isArray(parsed.findings)
299
+ ? parsed.findings.map((f) => {
300
+ const evidence = f.evidence;
301
+ return {
302
+ severity: ['critical', 'major', 'minor', 'suggestion'].includes(String(f.severity))
303
+ ? String(f.severity)
304
+ : 'minor',
305
+ file: f.file ? String(f.file) : undefined,
306
+ line: typeof f.line === 'number' ? f.line : undefined,
307
+ message: String(f.message ?? ''),
308
+ confidence: typeof f.confidence === 'number' ? f.confidence : undefined,
309
+ category: f.category ? String(f.category) : undefined,
310
+ evidence: evidence
311
+ ? {
312
+ codePathTraced: evidence.codePathTraced
313
+ ? String(evidence.codePathTraced)
314
+ : undefined,
315
+ failureScenario: evidence.failureScenario
316
+ ? String(evidence.failureScenario)
317
+ : undefined,
318
+ }
319
+ : undefined,
320
+ };
321
+ })
322
+ : [];
323
+ // Filter out low-confidence findings
324
+ const findings = rawFindings.filter((f) => f.confidence === undefined || f.confidence >= CONFIDENCE_THRESHOLD);
325
+ return {
326
+ type: this.config.reviewType,
327
+ approved: Boolean(parsed.approved),
328
+ findings,
329
+ summary: String(parsed.summary ?? ''),
330
+ };
331
+ }
332
+ catch {
333
+ // If JSON parse fails, treat as not approved — conservative
334
+ return {
335
+ type: this.config.reviewType,
336
+ approved: false,
337
+ findings: [
338
+ {
339
+ severity: 'critical',
340
+ message: 'Failed to parse review verdict — treating as not approved',
341
+ },
342
+ ],
343
+ summary: `Review agent response was not valid JSON: ${text.slice(0, 200)}`,
344
+ };
345
+ }
346
+ }
347
+ }
348
+ // ── Exported prompts for testing ─────────────────────────────────────
349
+ export { REVIEW_PROMPTS };
350
+ //# sourceMappingURL=review-agent.js.map
@@ -4,6 +4,7 @@
4
4
  * Design decision D4: Registry auto-discovers available runners from environment.
5
5
  */
6
6
  import { ClaudeCodeRunner } from './claude-code.js';
7
+ import { ClaudeCodeSdkRunner } from './claude-code-sdk.js';
7
8
  import { GenericLLMRunner } from './generic-llm.js';
8
9
  import { CopilotRunner } from './copilot.js';
9
10
  import { CursorRunner } from './cursor.js';
@@ -70,6 +71,15 @@ export class RunnerRegistry {
70
71
  source: 'built-in',
71
72
  });
72
73
  }
74
+ // Claude Code SDK runner — available when @anthropic-ai/claude-agent-sdk is installed
75
+ if (!this.runners.has('claude-code-sdk')) {
76
+ this.runners.set('claude-code-sdk', {
77
+ name: 'claude-code-sdk',
78
+ runner: new ClaudeCodeSdkRunner(),
79
+ available: true,
80
+ source: 'built-in',
81
+ });
82
+ }
73
83
  // OpenAI-compatible runner from env
74
84
  const openaiKey = env.OPENAI_API_KEY;
75
85
  if (openaiKey && !this.runners.has('openai')) {