@ai-sdlc/orchestrator 0.6.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.
- package/dist/admission-composite.d.ts +67 -0
- package/dist/admission-composite.js +158 -0
- package/dist/admission-enrichment.d.ts +142 -0
- package/dist/admission-enrichment.js +331 -0
- package/dist/admission-hc.d.ts +62 -0
- package/dist/admission-hc.js +83 -0
- package/dist/admission-score.d.ts +95 -5
- package/dist/admission-score.js +84 -11
- package/dist/analysis/analyzer.js +3 -2
- package/dist/analysis/convention-detector.d.ts +85 -2
- package/dist/analysis/convention-detector.js +375 -70
- package/dist/analysis/diff-analyzer.d.ts +33 -0
- package/dist/analysis/diff-analyzer.js +122 -0
- package/dist/analysis/hotspot-analyzer.js +3 -1
- package/dist/analysis/index.d.ts +2 -1
- package/dist/analysis/index.js +2 -1
- package/dist/artifacts/index.d.ts +65 -0
- package/dist/artifacts/index.js +142 -0
- package/dist/backlog-adapter.d.ts +121 -0
- package/dist/backlog-adapter.js +438 -0
- package/dist/calibration.d.ts +43 -0
- package/dist/calibration.js +76 -0
- package/dist/cli/commands/agents.d.ts +24 -0
- package/dist/cli/commands/agents.js +66 -1
- package/dist/cli/commands/git-remote.d.ts +49 -0
- package/dist/cli/commands/git-remote.js +91 -0
- package/dist/cli/commands/health.d.ts +4 -0
- package/dist/cli/commands/health.js +13 -1
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.js +164 -22
- package/dist/cli/commands/mcp-setup.d.ts +31 -0
- package/dist/cli/commands/mcp-setup.js +78 -8
- package/dist/cli/formatters/table.js +15 -2
- package/dist/cli/index.d.ts +14 -1
- package/dist/cli/index.js +81 -20
- package/dist/cli/versions.d.ts +57 -0
- package/dist/cli/versions.js +128 -0
- package/dist/code-area-classifier.d.ts +21 -0
- package/dist/code-area-classifier.js +48 -0
- package/dist/config.d.ts +33 -1
- package/dist/config.js +78 -8
- package/dist/database/adapters/external.d.ts +24 -0
- package/dist/database/adapters/external.js +80 -0
- package/dist/database/adapters/neon.d.ts +41 -0
- package/dist/database/adapters/neon.js +98 -0
- package/dist/database/adapters/pg-snapshot-restore.d.ts +28 -0
- package/dist/database/adapters/pg-snapshot-restore.js +68 -0
- package/dist/database/adapters/sqlite-copy.d.ts +32 -0
- package/dist/database/adapters/sqlite-copy.js +145 -0
- package/dist/database/connection-injection.d.ts +35 -0
- package/dist/database/connection-injection.js +93 -0
- package/dist/database/index.d.ts +12 -0
- package/dist/database/index.js +23 -0
- package/dist/database/registry.d.ts +13 -0
- package/dist/database/registry.js +27 -0
- package/dist/database/topology.d.ts +52 -0
- package/dist/database/topology.js +44 -0
- package/dist/database/types.d.ts +89 -0
- package/dist/database/types.js +26 -0
- package/dist/design-authority.d.ts +40 -0
- package/dist/design-authority.js +71 -0
- package/dist/design-lookahead.d.ts +64 -0
- package/dist/design-lookahead.js +86 -0
- package/dist/design-quality-trend.d.ts +87 -0
- package/dist/design-quality-trend.js +190 -0
- package/dist/design-system-context.d.ts +46 -0
- package/dist/design-system-context.js +80 -0
- package/dist/design-system-correction-loop.d.ts +64 -0
- package/dist/design-system-correction-loop.js +128 -0
- package/dist/design-system-metrics.d.ts +61 -0
- package/dist/design-system-metrics.js +104 -0
- package/dist/design-system-stewardship.d.ts +22 -0
- package/dist/design-system-stewardship.js +85 -0
- package/dist/design-system-validation.d.ts +37 -0
- package/dist/design-system-validation.js +88 -0
- package/dist/dispatch/index.d.ts +4 -0
- package/dist/dispatch/index.js +4 -0
- package/dist/dispatch/merge-gate.d.ts +46 -0
- package/dist/dispatch/merge-gate.js +90 -0
- package/dist/dispatch/requeue.d.ts +57 -0
- package/dist/dispatch/requeue.js +131 -0
- package/dist/dispatch/worker-pool.d.ts +62 -0
- package/dist/dispatch/worker-pool.js +60 -0
- package/dist/execute.d.ts +40 -0
- package/dist/execute.js +185 -15
- package/dist/fix-ci.js +8 -1
- package/dist/fix-review.js +8 -1
- package/dist/harness/adapters/claude-code.d.ts +29 -0
- package/dist/harness/adapters/claude-code.js +191 -0
- package/dist/harness/adapters/codex.d.ts +25 -0
- package/dist/harness/adapters/codex.js +61 -0
- package/dist/harness/independence.d.ts +51 -0
- package/dist/harness/independence.js +67 -0
- package/dist/harness/index.d.ts +14 -0
- package/dist/harness/index.js +20 -0
- package/dist/harness/registry.d.ts +17 -0
- package/dist/harness/registry.js +31 -0
- package/dist/harness/types.d.ts +123 -0
- package/dist/harness/types.js +8 -0
- package/dist/harness/version-probe.d.ts +14 -0
- package/dist/harness/version-probe.js +123 -0
- package/dist/index.d.ts +30 -4
- package/dist/index.js +35 -2
- package/dist/models/classifier.d.ts +76 -0
- package/dist/models/classifier.js +221 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.js +3 -0
- package/dist/models/registry.d.ts +97 -0
- package/dist/models/registry.js +173 -0
- package/dist/pillar-breakdown.d.ts +63 -0
- package/dist/pillar-breakdown.js +153 -0
- package/dist/priority.d.ts +5 -0
- package/dist/priority.js +18 -5
- package/dist/review-meta.d.ts +65 -0
- package/dist/review-meta.js +149 -0
- package/dist/review.d.ts +4 -0
- package/dist/review.js +12 -2
- package/dist/runners/claude-code-sdk.d.ts +31 -0
- package/dist/runners/claude-code-sdk.js +238 -0
- package/dist/runners/claude-code.d.ts +3 -0
- package/dist/runners/claude-code.js +30 -66
- package/dist/runners/codex.js +4 -1
- package/dist/runners/copilot.js +4 -1
- package/dist/runners/cursor.js +4 -1
- package/dist/runners/git-utils.d.ts +81 -0
- package/dist/runners/git-utils.js +201 -0
- package/dist/runners/index.d.ts +3 -0
- package/dist/runners/index.js +3 -0
- package/dist/runners/review-agent.d.ts +20 -0
- package/dist/runners/review-agent.js +171 -41
- package/dist/runners/runner-registry.js +10 -0
- package/dist/runners/sdk-review-runner.d.ts +65 -0
- package/dist/runners/sdk-review-runner.js +185 -0
- package/dist/runners/security-triage.d.ts +20 -4
- package/dist/runners/security-triage.js +39 -15
- package/dist/runners/types.d.ts +6 -0
- package/dist/runtime/attestations.d.ts +274 -0
- package/dist/runtime/attestations.js +460 -0
- package/dist/runtime/git-env.d.ts +53 -0
- package/dist/runtime/git-env.js +60 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/parallelism-flag.d.ts +10 -0
- package/dist/runtime/parallelism-flag.js +18 -0
- package/dist/runtime/port-allocator.d.ts +21 -0
- package/dist/runtime/port-allocator.js +66 -0
- package/dist/runtime/worktree-pool.d.ts +86 -0
- package/dist/runtime/worktree-pool.js +204 -0
- package/dist/runtime/worktree.d.ts +25 -0
- package/dist/runtime/worktree.js +111 -0
- package/dist/sa-scoring/auto-calibrate.d.ts +69 -0
- package/dist/sa-scoring/auto-calibrate.js +107 -0
- package/dist/sa-scoring/c1-sa2-computable.d.ts +42 -0
- package/dist/sa-scoring/c1-sa2-computable.js +59 -0
- package/dist/sa-scoring/composite.d.ts +107 -0
- package/dist/sa-scoring/composite.js +139 -0
- package/dist/sa-scoring/depparse-client.d.ts +79 -0
- package/dist/sa-scoring/depparse-client.js +187 -0
- package/dist/sa-scoring/did-compiler.d.ts +122 -0
- package/dist/sa-scoring/did-compiler.js +286 -0
- package/dist/sa-scoring/drift-monitor.d.ts +84 -0
- package/dist/sa-scoring/drift-monitor.js +186 -0
- package/dist/sa-scoring/exemplar-bank.d.ts +78 -0
- package/dist/sa-scoring/exemplar-bank.js +154 -0
- package/dist/sa-scoring/feedback-store.d.ts +100 -0
- package/dist/sa-scoring/feedback-store.js +156 -0
- package/dist/sa-scoring/index.d.ts +71 -0
- package/dist/sa-scoring/index.js +158 -0
- package/dist/sa-scoring/layer1-deterministic.d.ts +115 -0
- package/dist/sa-scoring/layer1-deterministic.js +298 -0
- package/dist/sa-scoring/layer2-structural.d.ts +71 -0
- package/dist/sa-scoring/layer2-structural.js +151 -0
- package/dist/sa-scoring/layer3-llm.d.ts +86 -0
- package/dist/sa-scoring/layer3-llm.js +282 -0
- package/dist/sa-scoring/rescore-orchestrator.d.ts +52 -0
- package/dist/sa-scoring/rescore-orchestrator.js +47 -0
- package/dist/scheduling/burn-down.d.ts +27 -0
- package/dist/scheduling/burn-down.js +43 -0
- package/dist/scheduling/calibration.d.ts +42 -0
- package/dist/scheduling/calibration.js +150 -0
- package/dist/scheduling/index.d.ts +8 -0
- package/dist/scheduling/index.js +8 -0
- package/dist/scheduling/ledger.d.ts +59 -0
- package/dist/scheduling/ledger.js +216 -0
- package/dist/scheduling/off-peak.d.ts +27 -0
- package/dist/scheduling/off-peak.js +112 -0
- package/dist/scheduling/schedule-decision.d.ts +39 -0
- package/dist/scheduling/schedule-decision.js +89 -0
- package/dist/scheduling/tier-analysis.d.ts +47 -0
- package/dist/scheduling/tier-analysis.js +81 -0
- package/dist/scheduling/types.d.ts +140 -0
- package/dist/scheduling/types.js +11 -0
- package/dist/shared.d.ts +13 -0
- package/dist/shared.js +32 -0
- package/dist/state/schema.d.ts +5 -1
- package/dist/state/schema.js +223 -1
- package/dist/state/store.d.ts +55 -1
- package/dist/state/store.js +342 -7
- package/dist/state/types.d.ts +139 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate-agent-output.js +4 -1
- package/dist/watch.js +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded-concurrency worker pool per RFC-0010 §9. Accepts a stream of work items in
|
|
3
|
+
* PPA-priority order, dispatches up to `maxConcurrent` simultaneously, and surfaces
|
|
4
|
+
* structured admission/dispatch events.
|
|
5
|
+
*/
|
|
6
|
+
export interface WorkItem<T> {
|
|
7
|
+
/** Unique id (typically issue id). */
|
|
8
|
+
id: string;
|
|
9
|
+
/** PPA composite score; higher = higher priority. */
|
|
10
|
+
ppaScore: number;
|
|
11
|
+
/** The actual work to perform when admitted. */
|
|
12
|
+
payload: T;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkerPoolDeps<T, R> {
|
|
15
|
+
/** Number of in-flight items allowed at once. */
|
|
16
|
+
maxConcurrent: number;
|
|
17
|
+
/** Execute a single work item; called by the pool with concurrency-bounded parallelism. */
|
|
18
|
+
execute: (item: WorkItem<T>) => Promise<R>;
|
|
19
|
+
/** Optional admission gate; return false to defer the item back into the queue. */
|
|
20
|
+
admit?: (item: WorkItem<T>) => Promise<boolean>;
|
|
21
|
+
/** Optional structured event sink. */
|
|
22
|
+
onEvent?: (event: WorkerPoolEvent<T>) => void;
|
|
23
|
+
}
|
|
24
|
+
export type WorkerPoolEvent<T> = {
|
|
25
|
+
type: 'queued';
|
|
26
|
+
item: WorkItem<T>;
|
|
27
|
+
queueSize: number;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'admission-deferred';
|
|
30
|
+
item: WorkItem<T>;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'started';
|
|
33
|
+
item: WorkItem<T>;
|
|
34
|
+
inFlight: number;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'completed';
|
|
37
|
+
item: WorkItem<T>;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
} | {
|
|
40
|
+
type: 'failed';
|
|
41
|
+
item: WorkItem<T>;
|
|
42
|
+
error: Error;
|
|
43
|
+
};
|
|
44
|
+
export interface WorkerPoolResult<T, R> {
|
|
45
|
+
succeeded: Array<{
|
|
46
|
+
item: WorkItem<T>;
|
|
47
|
+
result: R;
|
|
48
|
+
}>;
|
|
49
|
+
failed: Array<{
|
|
50
|
+
item: WorkItem<T>;
|
|
51
|
+
error: Error;
|
|
52
|
+
}>;
|
|
53
|
+
deferred: Array<WorkItem<T>>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Process all items with bounded concurrency. Items are dispatched in PPA score order
|
|
57
|
+
* (descending). Items that the admission gate rejects are returned in `deferred` for
|
|
58
|
+
* the caller to requeue or escalate. Failed executions are returned in `failed`; the
|
|
59
|
+
* pool does NOT auto-retry — that's the caller's responsibility (see RFC §9.4).
|
|
60
|
+
*/
|
|
61
|
+
export declare function runWorkerPool<T, R>(items: WorkItem<T>[], deps: WorkerPoolDeps<T, R>): Promise<WorkerPoolResult<T, R>>;
|
|
62
|
+
//# sourceMappingURL=worker-pool.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded-concurrency worker pool per RFC-0010 §9. Accepts a stream of work items in
|
|
3
|
+
* PPA-priority order, dispatches up to `maxConcurrent` simultaneously, and surfaces
|
|
4
|
+
* structured admission/dispatch events.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Process all items with bounded concurrency. Items are dispatched in PPA score order
|
|
8
|
+
* (descending). Items that the admission gate rejects are returned in `deferred` for
|
|
9
|
+
* the caller to requeue or escalate. Failed executions are returned in `failed`; the
|
|
10
|
+
* pool does NOT auto-retry — that's the caller's responsibility (see RFC §9.4).
|
|
11
|
+
*/
|
|
12
|
+
export async function runWorkerPool(items, deps) {
|
|
13
|
+
if (deps.maxConcurrent < 1) {
|
|
14
|
+
throw new Error(`maxConcurrent must be >= 1 (got ${deps.maxConcurrent})`);
|
|
15
|
+
}
|
|
16
|
+
const sorted = [...items].sort((a, b) => b.ppaScore - a.ppaScore);
|
|
17
|
+
const result = { succeeded: [], failed: [], deferred: [] };
|
|
18
|
+
const onEvent = deps.onEvent ?? (() => { });
|
|
19
|
+
for (const item of sorted) {
|
|
20
|
+
onEvent({ type: 'queued', item, queueSize: sorted.length });
|
|
21
|
+
}
|
|
22
|
+
let inFlight = 0;
|
|
23
|
+
const queue = [...sorted];
|
|
24
|
+
// Spawn N workers that pull from the shared queue until empty.
|
|
25
|
+
const worker = async () => {
|
|
26
|
+
for (;;) {
|
|
27
|
+
const item = queue.shift();
|
|
28
|
+
if (!item)
|
|
29
|
+
return;
|
|
30
|
+
inFlight++;
|
|
31
|
+
try {
|
|
32
|
+
if (deps.admit) {
|
|
33
|
+
const admitted = await deps.admit(item);
|
|
34
|
+
if (!admitted) {
|
|
35
|
+
onEvent({ type: 'admission-deferred', item });
|
|
36
|
+
result.deferred.push(item);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
onEvent({ type: 'started', item, inFlight });
|
|
42
|
+
const r = await deps.execute(item);
|
|
43
|
+
onEvent({ type: 'completed', item, durationMs: Date.now() - start });
|
|
44
|
+
result.succeeded.push({ item, result: r });
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
48
|
+
onEvent({ type: 'failed', item, error });
|
|
49
|
+
result.failed.push({ item, error });
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
inFlight--;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const workers = Array.from({ length: Math.min(deps.maxConcurrent, sorted.length) }, () => worker());
|
|
57
|
+
await Promise.all(workers);
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=worker-pool.js.map
|
package/dist/execute.d.ts
CHANGED
|
@@ -76,9 +76,49 @@ export interface ExecuteOptions {
|
|
|
76
76
|
stateStore?: StateStore;
|
|
77
77
|
/** Priority score from PPA scoring (set by watch loop or caller). */
|
|
78
78
|
priorityScore?: PriorityScore;
|
|
79
|
+
/**
|
|
80
|
+
* Override the Pipeline resource selected for this execution. When set, takes
|
|
81
|
+
* precedence over the on-disk `config.pipeline` loaded from `.ai-sdlc/`. The
|
|
82
|
+
* watch loop uses this to pass the dispatcher-selected pipeline (e.g., the
|
|
83
|
+
* backlog pipeline for AISDLC-* issues) instead of letting executePipeline
|
|
84
|
+
* fall back to the canonical pipeline.yaml.
|
|
85
|
+
*/
|
|
86
|
+
pipelineOverride?: import('@ai-sdlc/reference').Pipeline;
|
|
79
87
|
}
|
|
80
88
|
/**
|
|
81
89
|
* Execute the full AI-SDLC pipeline for a given issue ID.
|
|
82
90
|
*/
|
|
83
91
|
export declare function executePipeline(issueId: string, options?: ExecuteOptions): Promise<PipelineResult>;
|
|
92
|
+
/**
|
|
93
|
+
* Capture the current branch name (or commit SHA in detached-HEAD state) so the
|
|
94
|
+
* pipeline can restore the user's worktree after the run. Returns null on git
|
|
95
|
+
* failure — callers treat null as "don't try to restore".
|
|
96
|
+
*
|
|
97
|
+
* @internal — exported for unit tests.
|
|
98
|
+
*/
|
|
99
|
+
export declare function captureCurrentBranch(workDir: string): Promise<string | null>;
|
|
100
|
+
/**
|
|
101
|
+
* Restore the user's original HEAD if the pipeline switched branches AND the
|
|
102
|
+
* working tree is clean. If the working tree has uncommitted changes (from a
|
|
103
|
+
* partial pipeline run), log a warning and leave HEAD where it is — better to
|
|
104
|
+
* preserve the pipeline's state than to clobber whatever the user has.
|
|
105
|
+
*
|
|
106
|
+
* @internal — exported for unit tests.
|
|
107
|
+
*/
|
|
108
|
+
export declare function restoreOriginalBranch(workDir: string, originalHead: string | null, log: {
|
|
109
|
+
info: (msg: string) => void;
|
|
110
|
+
} | undefined): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Push a branch to origin, rebasing onto the remote tip if it has advanced.
|
|
113
|
+
* Pre-AISDLC-68-rerun the pipeline did a plain `git push` and rejected with
|
|
114
|
+
* non-fast-forward when the remote branch had drifted (e.g. a previous run's
|
|
115
|
+
* commits or a manual edit). The fix: try push; on non-fast-forward, fetch +
|
|
116
|
+
* rebase HEAD onto origin/<branch>, retry once. Rebase conflicts surface as
|
|
117
|
+
* the original error so the operator can resolve manually.
|
|
118
|
+
*
|
|
119
|
+
* @internal — exported for unit tests.
|
|
120
|
+
*/
|
|
121
|
+
export declare function pushBranchWithRebase(workDir: string, branchName: string, log: {
|
|
122
|
+
info: (msg: string) => void;
|
|
123
|
+
} | undefined): Promise<void>;
|
|
84
124
|
//# sourceMappingURL=execute.d.ts.map
|
package/dist/execute.js
CHANGED
|
@@ -12,7 +12,8 @@ import { validateAgentOutput } from './validate-agent-output.js';
|
|
|
12
12
|
import { createLogger } from './logger.js';
|
|
13
13
|
import { createStructuredConsoleLogger, createStructuredBufferLogger, } from './structured-logger.js';
|
|
14
14
|
import { ClaudeCodeRunner } from './runners/claude-code.js';
|
|
15
|
-
import { execFileAsync, getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, isAutonomousStrategy, recordMetric, evaluatePipelineCompliance, authorizeFilesChanged, interpolateBranchPattern, interpolatePRTitle, issueIdToNumber, formatIssueRef, } from './shared.js';
|
|
15
|
+
import { execFileAsync, getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, isAutonomousStrategy, recordMetric, evaluatePipelineCompliance, authorizeFilesChanged, interpolateBranchPattern, interpolatePRTitle, issueIdToNumber, formatIssueRef, buildIssueTemplateVars, } from './shared.js';
|
|
16
|
+
import { cleanGitEnv } from './runtime/git-env.js';
|
|
16
17
|
import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, classifyAndSubmitApproval, createPipelineSecurity, } from './security.js';
|
|
17
18
|
import { createPipelineOrchestration, executePipelineOrchestration, validatePipelineHandoffs, } from './orchestration.js';
|
|
18
19
|
import { createPipelineProvenance, attachProvenanceToPR, validatePipelineProvenance, } from './provenance.js';
|
|
@@ -23,7 +24,7 @@ import { verifyAuditIntegrity, createFileAuditLog, loadAuditEntries, computeAudi
|
|
|
23
24
|
import { renderTemplate } from './notifications.js';
|
|
24
25
|
import { admitIssueResource, createPipelineAdmission, } from './admission.js';
|
|
25
26
|
import { createPipelineAdapterRegistry, createPipelineWebhookBridge, resolveAdapterFromGit, scanPipelineAdapters, resolveIssueTrackerFromConfig, } from './adapters.js';
|
|
26
|
-
import { existsSync, readFileSync,
|
|
27
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
27
28
|
import { join } from 'node:path';
|
|
28
29
|
import { defaultSandboxConstraints, DEFAULT_CONFIG_DIR_NAME, DEFAULT_PR_FOOTER, DEFAULT_LINT_COMMAND, DEFAULT_FORMAT_COMMAND, DEFAULT_COMMIT_MESSAGE_TEMPLATE, DEFAULT_COMMIT_CO_AUTHOR, NOTIFICATION_TITLES, } from './defaults.js';
|
|
29
30
|
import { checkFrameworkCompliance, getControlCatalog, getFrameworkMappings, listSupportedFrameworks, } from './compliance-extended.js';
|
|
@@ -33,6 +34,7 @@ import { hasResourceChanged, fingerprintResource } from './reconcilers.js';
|
|
|
33
34
|
import { CostTracker } from './cost-tracker.js';
|
|
34
35
|
import { enrichAgentContext } from './context-enrichment.js';
|
|
35
36
|
import { reportGateCheckRuns } from './check-runs.js';
|
|
37
|
+
import { WorktreePoolManager, readParallelismMode } from './runtime/index.js';
|
|
36
38
|
/**
|
|
37
39
|
* Execute the full AI-SDLC pipeline for a given issue ID.
|
|
38
40
|
*/
|
|
@@ -43,10 +45,29 @@ export async function executePipeline(issueId, options = {}) {
|
|
|
43
45
|
(options.useStructuredLogger ? createStructuredConsoleLogger() : createLogger());
|
|
44
46
|
const auditLog = options.auditLog ?? createDefaultAuditLog(workDir);
|
|
45
47
|
const metricStore = options.metricStore;
|
|
48
|
+
// RFC-0010 §6/§7 Phase 2: parallelism opt-in. When AI_SDLC_PARALLELISM is unset (default),
|
|
49
|
+
// execution proceeds serially exactly as today. When set to 'experimental' or 'on', a
|
|
50
|
+
// WorktreePoolManager is instantiated for the worker-pool dispatcher landing in Phase 3
|
|
51
|
+
// (RFC-0010 §9). For now the manager is constructed but not yet routed through — Phase 2
|
|
52
|
+
// ships the wire-in surface; Phase 3 wires the worker pool to consume it.
|
|
53
|
+
const parallelismMode = readParallelismMode();
|
|
54
|
+
let worktreePool;
|
|
55
|
+
if (parallelismMode !== 'off') {
|
|
56
|
+
worktreePool = new WorktreePoolManager(workDir);
|
|
57
|
+
log.info(`[parallelism] mode=${parallelismMode}, pool root=${worktreePool.rootDir}`);
|
|
58
|
+
}
|
|
59
|
+
void worktreePool; // referenced by Phase 3 dispatcher; silences unused-var lint until then
|
|
46
60
|
// 1. Load .ai-sdlc/ config (async: includes adapter scanning + manifest distribution)
|
|
47
61
|
log.stage('load-config');
|
|
48
62
|
const config = await loadConfigAsync(configDir);
|
|
49
63
|
log.stageEnd('load-config');
|
|
64
|
+
// If the caller (e.g., cli-watch's pipeline dispatcher) selected a specific
|
|
65
|
+
// Pipeline resource, override the on-disk default. Without this, multi-
|
|
66
|
+
// pipeline configs (RFC-0010 dual workflow: pipeline.yaml + pipeline-backlog.yaml)
|
|
67
|
+
// always fall back to pipeline.yaml because loadConfig prefers it as canonical.
|
|
68
|
+
if (options.pipelineOverride) {
|
|
69
|
+
config.pipeline = options.pipelineOverride;
|
|
70
|
+
}
|
|
50
71
|
// Kill switch check (before any work)
|
|
51
72
|
if (options.security) {
|
|
52
73
|
await checkKillSwitch(options.security);
|
|
@@ -102,6 +123,12 @@ export async function executePipeline(issueId, options = {}) {
|
|
|
102
123
|
description: issue.description,
|
|
103
124
|
});
|
|
104
125
|
}
|
|
126
|
+
// Capture the user's current HEAD so we can restore it after the pipeline.
|
|
127
|
+
// Without this, `git checkout <issue-branch>` inside the pipeline body
|
|
128
|
+
// leaves the user's working tree on the issue branch even after the run
|
|
129
|
+
// completes (the AISDLC-68 incident). Long-term fix is a worktree pool
|
|
130
|
+
// (RFC-0010 §7); this is the pragmatic save+restore.
|
|
131
|
+
const originalHead = await captureCurrentBranch(workDir);
|
|
105
132
|
// Wrap pipeline body in try/catch to record failure episodes
|
|
106
133
|
try {
|
|
107
134
|
return await executePipelineBody(issueId, issue, config, qualityGate, agentRole, autonomyPolicy, tracker, sc, auditLog, metricStore, options, log, workDir);
|
|
@@ -122,6 +149,121 @@ export async function executePipeline(issueId, options = {}) {
|
|
|
122
149
|
}
|
|
123
150
|
throw err;
|
|
124
151
|
}
|
|
152
|
+
finally {
|
|
153
|
+
await restoreOriginalBranch(workDir, originalHead, log);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Capture the current branch name (or commit SHA in detached-HEAD state) so the
|
|
158
|
+
* pipeline can restore the user's worktree after the run. Returns null on git
|
|
159
|
+
* failure — callers treat null as "don't try to restore".
|
|
160
|
+
*
|
|
161
|
+
* @internal — exported for unit tests.
|
|
162
|
+
*/
|
|
163
|
+
export async function captureCurrentBranch(workDir) {
|
|
164
|
+
// cleanGitEnv() strips GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE so git
|
|
165
|
+
// resolves against `workDir`'s own .git rather than whatever a parent
|
|
166
|
+
// process (e.g. husky pre-push hook running in another worktree) leaked
|
|
167
|
+
// into the env (AISDLC-72).
|
|
168
|
+
try {
|
|
169
|
+
const { stdout } = await execFileAsync('git', ['symbolic-ref', '--short', 'HEAD'], {
|
|
170
|
+
cwd: workDir,
|
|
171
|
+
env: cleanGitEnv(),
|
|
172
|
+
});
|
|
173
|
+
return stdout.trim();
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Detached HEAD or no git repo. Try the SHA fallback.
|
|
177
|
+
try {
|
|
178
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], {
|
|
179
|
+
cwd: workDir,
|
|
180
|
+
env: cleanGitEnv(),
|
|
181
|
+
});
|
|
182
|
+
return stdout.trim();
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Restore the user's original HEAD if the pipeline switched branches AND the
|
|
191
|
+
* working tree is clean. If the working tree has uncommitted changes (from a
|
|
192
|
+
* partial pipeline run), log a warning and leave HEAD where it is — better to
|
|
193
|
+
* preserve the pipeline's state than to clobber whatever the user has.
|
|
194
|
+
*
|
|
195
|
+
* @internal — exported for unit tests.
|
|
196
|
+
*/
|
|
197
|
+
export async function restoreOriginalBranch(workDir, originalHead, log) {
|
|
198
|
+
if (!originalHead)
|
|
199
|
+
return;
|
|
200
|
+
try {
|
|
201
|
+
const current = await captureCurrentBranch(workDir);
|
|
202
|
+
if (current === originalHead)
|
|
203
|
+
return;
|
|
204
|
+
// Refuse to checkout if the worktree has uncommitted changes — git would
|
|
205
|
+
// either block the checkout or carry the changes across, both bad.
|
|
206
|
+
// cleanGitEnv() prevents leaked GIT_DIR from corrupting these calls (AISDLC-72).
|
|
207
|
+
const { stdout } = await execFileAsync('git', ['status', '--porcelain'], {
|
|
208
|
+
cwd: workDir,
|
|
209
|
+
env: cleanGitEnv(),
|
|
210
|
+
});
|
|
211
|
+
if (stdout.trim().length > 0) {
|
|
212
|
+
log?.info(`[pipeline] worktree dirty after pipeline; HEAD left at \`${current}\`. ` +
|
|
213
|
+
`To restore: \`git stash && git checkout ${originalHead}\``);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
await execFileAsync('git', ['checkout', originalHead], {
|
|
217
|
+
cwd: workDir,
|
|
218
|
+
env: cleanGitEnv(),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
log?.info(`[pipeline] failed to restore HEAD to \`${originalHead}\`: ${err.message}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Push a branch to origin, rebasing onto the remote tip if it has advanced.
|
|
227
|
+
* Pre-AISDLC-68-rerun the pipeline did a plain `git push` and rejected with
|
|
228
|
+
* non-fast-forward when the remote branch had drifted (e.g. a previous run's
|
|
229
|
+
* commits or a manual edit). The fix: try push; on non-fast-forward, fetch +
|
|
230
|
+
* rebase HEAD onto origin/<branch>, retry once. Rebase conflicts surface as
|
|
231
|
+
* the original error so the operator can resolve manually.
|
|
232
|
+
*
|
|
233
|
+
* @internal — exported for unit tests.
|
|
234
|
+
*/
|
|
235
|
+
export async function pushBranchWithRebase(workDir, branchName, log) {
|
|
236
|
+
// cleanGitEnv() ensures these git calls bind to `workDir`'s own .git, not
|
|
237
|
+
// a leaked GIT_DIR from a parent process (AISDLC-72).
|
|
238
|
+
const env = cleanGitEnv();
|
|
239
|
+
try {
|
|
240
|
+
await execFileAsync('git', ['push', 'origin', branchName], { cwd: workDir, env });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
const stderr = err.stderr ?? err.message;
|
|
245
|
+
if (!/non-fast-forward|rejected/i.test(stderr)) {
|
|
246
|
+
throw err;
|
|
247
|
+
}
|
|
248
|
+
log?.info(`[pipeline] push rejected (non-fast-forward); rebasing onto origin/${branchName}`);
|
|
249
|
+
}
|
|
250
|
+
// Fetch + rebase + retry. If the rebase fails, surface a clear error.
|
|
251
|
+
try {
|
|
252
|
+
await execFileAsync('git', ['fetch', 'origin', branchName], { cwd: workDir, env });
|
|
253
|
+
await execFileAsync('git', ['rebase', `origin/${branchName}`], { cwd: workDir, env });
|
|
254
|
+
}
|
|
255
|
+
catch (rebaseErr) {
|
|
256
|
+
// Abort the partial rebase so the worktree isn't left in a half-merged state.
|
|
257
|
+
try {
|
|
258
|
+
await execFileAsync('git', ['rebase', '--abort'], { cwd: workDir, env });
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
/* nothing to abort */
|
|
262
|
+
}
|
|
263
|
+
throw new Error(`Push rebase failed for branch ${branchName}: ${rebaseErr.message}. ` +
|
|
264
|
+
`Resolve conflicts manually and re-run the pipeline.`);
|
|
265
|
+
}
|
|
266
|
+
await execFileAsync('git', ['push', 'origin', branchName], { cwd: workDir, env });
|
|
125
267
|
}
|
|
126
268
|
async function executePipelineBody(issueId, issue, config, qualityGate, agentRole, autonomyPolicy, tracker, sc, auditLog, metricStore, options, log, workDir) {
|
|
127
269
|
const issueNumber = issueIdToNumber(issueId);
|
|
@@ -262,11 +404,15 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
262
404
|
const currentLevel = resolveAutonomyLevel(autonomyPolicy);
|
|
263
405
|
log.stageEnd('validate-issue');
|
|
264
406
|
// 7. Create branch and checkout locally (read pattern from pipeline config)
|
|
265
|
-
const branchVars =
|
|
407
|
+
const branchVars = buildIssueTemplateVars(issueId, issue.title);
|
|
266
408
|
const branchName = interpolateBranchPattern(config.pipeline?.spec.branching?.pattern, branchVars);
|
|
267
409
|
await sc.createBranch({ name: branchName });
|
|
268
|
-
|
|
269
|
-
await execFileAsync('git', ['
|
|
410
|
+
// cleanGitEnv() prevents leaked GIT_DIR from corrupting these calls (AISDLC-72).
|
|
411
|
+
await execFileAsync('git', ['fetch', 'origin', branchName], {
|
|
412
|
+
cwd: workDir,
|
|
413
|
+
env: cleanGitEnv(),
|
|
414
|
+
});
|
|
415
|
+
await execFileAsync('git', ['checkout', branchName], { cwd: workDir, env: cleanGitEnv() });
|
|
270
416
|
// 8. Resolve agent constraints
|
|
271
417
|
const resolved = resolveConstraints(agentRole.spec.constraints, currentLevel);
|
|
272
418
|
// 8b. Model selection: choose model based on complexity and budget pressure
|
|
@@ -423,9 +569,13 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
423
569
|
`**${result.filesChanged.length} files changed.** Pushing branch...\n\n` +
|
|
424
570
|
activitySection);
|
|
425
571
|
await notifySlack(`:white_check_mark: Agent complete — ${result.filesChanged.length} files changed. Pushing...`);
|
|
426
|
-
// 12. Push branch (before validation to preserve work even if validation fails)
|
|
572
|
+
// 12. Push branch (before validation to preserve work even if validation fails).
|
|
573
|
+
// Fetch first + rebase if the remote has diverged so push fast-forwards
|
|
574
|
+
// cleanly. Without this, re-runs against an existing remote branch (e.g. when
|
|
575
|
+
// the issue branch was updated by another pipeline run or by a hand-edit)
|
|
576
|
+
// reject with non-fast-forward and the agent's work is stranded locally.
|
|
427
577
|
log.stage('push');
|
|
428
|
-
await
|
|
578
|
+
await pushBranchWithRebase(workDir, branchName, log);
|
|
429
579
|
log.stageEnd('push');
|
|
430
580
|
auditLog.record({
|
|
431
581
|
actor: 'system',
|
|
@@ -453,14 +603,24 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
453
603
|
return validationResult;
|
|
454
604
|
});
|
|
455
605
|
if (!validation.passed) {
|
|
456
|
-
//
|
|
606
|
+
// One-line summary suitable for log lines + thrown error message.
|
|
607
|
+
const violationSummary = validation.violations.map((v) => `${v.rule}: ${v.message}`).join('; ');
|
|
608
|
+
// Record validation failure in audit log with full violation detail (rule
|
|
609
|
+
// + message), not just the rule names — the rule name alone doesn't tell
|
|
610
|
+
// the operator what to fix.
|
|
457
611
|
auditLog.record({
|
|
458
612
|
actor: 'system',
|
|
459
613
|
action: 'check',
|
|
460
614
|
resource: 'agent-output',
|
|
461
615
|
decision: 'denied',
|
|
462
|
-
details: {
|
|
616
|
+
details: {
|
|
617
|
+
violations: validation.violations.map((v) => ({ rule: v.rule, message: v.message })),
|
|
618
|
+
},
|
|
463
619
|
});
|
|
620
|
+
// Surface the rejection detail to the structured log so the operator sees
|
|
621
|
+
// it in stderr immediately. Without this the only signal was the
|
|
622
|
+
// (detail-free) thrown error message.
|
|
623
|
+
log.error(`[validate-output] rejected: ${violationSummary}`);
|
|
464
624
|
// Post comment explaining the violations
|
|
465
625
|
const violationList = validation.violations
|
|
466
626
|
.map((v) => `- **${v.rule}**: ${v.message}`)
|
|
@@ -468,8 +628,10 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
468
628
|
await tracker.addComment(issueId, `## ${NOTIFICATION_TITLES.guardrailViolations}\n\n${violationList}\n\n` +
|
|
469
629
|
`The branch \`${branchName}\` has been pushed with your changes. You can review the work, ` +
|
|
470
630
|
`cherry-pick valid changes, or adjust the guardrails as needed.`);
|
|
471
|
-
// Exit without creating PR
|
|
472
|
-
|
|
631
|
+
// Exit without creating PR. Include the violation summary in the error
|
|
632
|
+
// message so cli-watch and downstream loggers see the actual rejection.
|
|
633
|
+
throw new Error(`Agent output failed guardrail validation: ${violationSummary}. ` +
|
|
634
|
+
`Branch ${branchName} preserved for review.`);
|
|
473
635
|
}
|
|
474
636
|
// Validation passed — record success
|
|
475
637
|
auditLog.record({
|
|
@@ -483,6 +645,7 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
483
645
|
try {
|
|
484
646
|
const { stdout: headSha } = await execFileAsync('git', ['rev-parse', 'HEAD'], {
|
|
485
647
|
cwd: workDir,
|
|
648
|
+
env: cleanGitEnv(),
|
|
486
649
|
});
|
|
487
650
|
const gateResults = qualityGate.spec.gates.map((gate) => {
|
|
488
651
|
const evalResult = evaluatePipelineGate(gate, {
|
|
@@ -519,6 +682,7 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
519
682
|
try {
|
|
520
683
|
const { stdout: diffStat } = await execFileAsync('git', ['diff', '--stat', 'HEAD~1'], {
|
|
521
684
|
cwd: workDir,
|
|
685
|
+
env: cleanGitEnv(),
|
|
522
686
|
});
|
|
523
687
|
const insertMatch = diffStat.match(/(\d+) insertions?\(\+\)/);
|
|
524
688
|
const deleteMatch = diffStat.match(/(\d+) deletions?\(-\)/);
|
|
@@ -578,7 +742,7 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
578
742
|
}
|
|
579
743
|
provenanceBlock = '\n\n' + provenanceText;
|
|
580
744
|
}
|
|
581
|
-
const prVars =
|
|
745
|
+
const prVars = buildIssueTemplateVars(issueId, issue.title);
|
|
582
746
|
const prTitle = interpolatePRTitle(prConfig?.titleTemplate, prVars);
|
|
583
747
|
const closeKeyword = prConfig?.closeKeyword ?? 'Closes';
|
|
584
748
|
const targetBranch = config.pipeline?.spec.branching?.targetBranch ?? 'main';
|
|
@@ -878,11 +1042,17 @@ function ensureRuntimeGitignore(workDir) {
|
|
|
878
1042
|
try {
|
|
879
1043
|
const gitignorePath = join(workDir, '.gitignore');
|
|
880
1044
|
const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
|
|
881
|
-
const
|
|
1045
|
+
const SENTINEL = '# ai-sdlc:runtime-gitignore';
|
|
1046
|
+
if (existing.includes(SENTINEL))
|
|
1047
|
+
return;
|
|
1048
|
+
const missing = RUNTIME_GITIGNORE_PATHS.filter((entry) => !existing.split('\n').some((line) => line.trim() === entry));
|
|
882
1049
|
if (missing.length === 0)
|
|
883
1050
|
return;
|
|
884
|
-
|
|
885
|
-
|
|
1051
|
+
// Write atomically (writeFileSync, not appendFileSync) to avoid race conditions
|
|
1052
|
+
// when parallel test processes both read before either writes.
|
|
1053
|
+
const block = `${SENTINEL}\n` + missing.join('\n') + '\n';
|
|
1054
|
+
const newContent = existing.length > 0 ? existing.trimEnd() + '\n' + block : block;
|
|
1055
|
+
writeFileSync(gitignorePath, newContent, 'utf-8');
|
|
886
1056
|
}
|
|
887
1057
|
catch {
|
|
888
1058
|
// Best-effort — workDir may not exist yet in tests or dry-run scenarios
|
package/dist/fix-ci.js
CHANGED
|
@@ -14,6 +14,7 @@ import { parseDuration } from './policy-evaluators.js';
|
|
|
14
14
|
import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, } from './security.js';
|
|
15
15
|
import { DEFAULT_MAX_FIX_ATTEMPTS, DEFAULT_MAX_LOG_LINES, DEFAULT_GH_CLI_TIMEOUT_MS, DEFAULT_CONFIG_DIR_NAME, defaultSandboxConstraints, NOTIFICATION_TITLES, } from './defaults.js';
|
|
16
16
|
import { createCycleDetectorFromConfig, checkAndHandleCycle } from './cycle-utils.js';
|
|
17
|
+
import { cleanGitEnv } from './runtime/git-env.js';
|
|
17
18
|
export const MAX_FIX_ATTEMPTS = DEFAULT_MAX_FIX_ATTEMPTS;
|
|
18
19
|
export const MAX_LOG_LINES = DEFAULT_MAX_LOG_LINES;
|
|
19
20
|
export const RETRY_MARKER = '<!-- ai-sdlc-fix-ci-attempt -->';
|
|
@@ -180,8 +181,10 @@ export async function executeFixCI(prNumber, runId, options = {}) {
|
|
|
180
181
|
const ciLogs = await fetchCILogs(runId, options._ciLogs, options.ciAdapter);
|
|
181
182
|
log.stageEnd('fetch-logs');
|
|
182
183
|
// 4. Determine branch and issue number
|
|
184
|
+
// cleanGitEnv() prevents leaked GIT_DIR from binding to a parent repo (AISDLC-72).
|
|
183
185
|
const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], {
|
|
184
186
|
cwd: workDir,
|
|
187
|
+
env: cleanGitEnv(),
|
|
185
188
|
});
|
|
186
189
|
const currentBranch = branchStdout.trim();
|
|
187
190
|
const issueId = extractIssueId(currentBranch);
|
|
@@ -341,8 +344,12 @@ export async function executeFixCI(prNumber, runId, options = {}) {
|
|
|
341
344
|
});
|
|
342
345
|
});
|
|
343
346
|
// 10. Push to the same branch (CI re-runs automatically)
|
|
347
|
+
// cleanGitEnv() prevents leaked GIT_DIR from binding to a parent repo (AISDLC-72).
|
|
344
348
|
log.stage('push');
|
|
345
|
-
await execFileAsync('git', ['push', 'origin', currentBranch], {
|
|
349
|
+
await execFileAsync('git', ['push', 'origin', currentBranch], {
|
|
350
|
+
cwd: workDir,
|
|
351
|
+
env: cleanGitEnv(),
|
|
352
|
+
});
|
|
346
353
|
log.stageEnd('push');
|
|
347
354
|
auditLog.record({
|
|
348
355
|
actor: 'system',
|
package/dist/fix-review.js
CHANGED
|
@@ -14,6 +14,7 @@ import { parseDuration } from './policy-evaluators.js';
|
|
|
14
14
|
import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, } from './security.js';
|
|
15
15
|
import { DEFAULT_GH_CLI_TIMEOUT_MS, DEFAULT_CONFIG_DIR_NAME, defaultSandboxConstraints, NOTIFICATION_TITLES, } from './defaults.js';
|
|
16
16
|
import { createCycleDetectorFromConfig, checkAndHandleCycle } from './cycle-utils.js';
|
|
17
|
+
import { cleanGitEnv } from './runtime/git-env.js';
|
|
17
18
|
// Default max review-fix attempts (lower than CI-fix since reviews are more deterministic)
|
|
18
19
|
export const MAX_REVIEW_FIX_ATTEMPTS = 2;
|
|
19
20
|
export const RETRY_MARKER = '<!-- ai-sdlc-fix-review-attempt -->';
|
|
@@ -210,8 +211,10 @@ export async function executeFixReview(prNumber, options = {}) {
|
|
|
210
211
|
return;
|
|
211
212
|
}
|
|
212
213
|
// 4. Determine branch and issue number
|
|
214
|
+
// cleanGitEnv() prevents leaked GIT_DIR from binding to a parent repo (AISDLC-72).
|
|
213
215
|
const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], {
|
|
214
216
|
cwd: workDir,
|
|
217
|
+
env: cleanGitEnv(),
|
|
215
218
|
});
|
|
216
219
|
const currentBranch = sanitizeBranchName(branchStdout.trim());
|
|
217
220
|
const issueId = extractIssueId(currentBranch);
|
|
@@ -371,8 +374,12 @@ export async function executeFixReview(prNumber, options = {}) {
|
|
|
371
374
|
});
|
|
372
375
|
});
|
|
373
376
|
// 10. Push to the same branch (review agents re-run automatically via pull_request.synchronize)
|
|
377
|
+
// cleanGitEnv() prevents leaked GIT_DIR from binding to a parent repo (AISDLC-72).
|
|
374
378
|
log.stage('push');
|
|
375
|
-
await execFileAsync('git', ['push', 'origin', currentBranch], {
|
|
379
|
+
await execFileAsync('git', ['push', 'origin', currentBranch], {
|
|
380
|
+
cwd: workDir,
|
|
381
|
+
env: cleanGitEnv(),
|
|
382
|
+
});
|
|
376
383
|
log.stageEnd('push');
|
|
377
384
|
auditLog.record({
|
|
378
385
|
actor: 'system',
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeCodeAdapter — wraps Claude Code CLI behind the HarnessAdapter contract.
|
|
3
|
+
* Uses the local `claude` CLI in `-p --output-format stream-json` mode, authenticating
|
|
4
|
+
* via whatever credential the CLI is logged into (Pro/Max subscription or API key).
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import type { HarnessAdapter, HarnessAvailability, HarnessCapabilities, HarnessEvent, HarnessInput, HarnessName, HarnessRequires, HarnessResult } from '../types.js';
|
|
8
|
+
export interface ClaudeCodeAdapterDeps {
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
/** Override the actual invocation path; tests inject a stub. */
|
|
11
|
+
invoke?: (input: HarnessInput, onEvent?: (e: HarnessEvent) => void) => Promise<HarnessResult>;
|
|
12
|
+
/** Override version probe for tests. */
|
|
13
|
+
probe?: () => Promise<HarnessAvailability>;
|
|
14
|
+
/** Override the spawn function (tests inject a fake child process). */
|
|
15
|
+
spawn?: typeof spawn;
|
|
16
|
+
}
|
|
17
|
+
export declare class ClaudeCodeAdapter implements HarnessAdapter {
|
|
18
|
+
private readonly deps;
|
|
19
|
+
readonly name: HarnessName;
|
|
20
|
+
readonly capabilities: HarnessCapabilities;
|
|
21
|
+
readonly requires: HarnessRequires;
|
|
22
|
+
private cachedAvailability;
|
|
23
|
+
constructor(deps?: ClaudeCodeAdapterDeps);
|
|
24
|
+
getAccountId(): Promise<string | null>;
|
|
25
|
+
isAvailable(): Promise<HarnessAvailability>;
|
|
26
|
+
invoke(input: HarnessInput, onEvent?: (e: HarnessEvent) => void): Promise<HarnessResult>;
|
|
27
|
+
availableModels(): Promise<string[]>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=claude-code.d.ts.map
|